Avoid unnecessary rendering.
[bertos.git] / gui / menu.c
1 /*!
2  * \file
3  * <!--
4  * Copyright 2003, 2004, 2006 Develer S.r.l. (http://www.develer.com/)
5  * Copyright 2000 Bernardo Innocenti <bernie@codewiz.org>
6  * All Rights Reserved.
7  * -->
8  *
9  * \version $Id$
10  *
11  * \author Bernardo Innocenti <bernie@develer.com>
12  * \author Stefano Fedrigo <aleph@develer.com>
13  *
14  * \brief General pourpose menu handling functions
15  */
16
17 /*#*
18  *#* $Log$
19  *#* Revision 1.3  2006/05/28 15:03:31  bernie
20  *#* Avoid unnecessary rendering.
21  *#*
22  *#* Revision 1.2  2006/05/25 23:34:38  bernie
23  *#* Implement menu timeouts.
24  *#*
25  *#* Revision 1.1  2006/05/15 07:20:54  bernie
26  *#* Move menu to gui/.
27  *#*
28  *#* Revision 1.7  2006/04/27 05:39:24  bernie
29  *#* Enhance text rendering to arbitrary x,y coords.
30  *#*
31  *#* Revision 1.6  2006/04/11 00:07:32  bernie
32  *#* Implemenent MF_SAVESEL flag.
33  *#*
34  *#* Revision 1.5  2006/03/22 09:49:51  bernie
35  *#* Simplifications from project_grl.
36  *#*
37  *#* Revision 1.4  2006/03/20 17:48:35  bernie
38  *#* Implement support for ROM menus.
39  *#*
40  *#* Revision 1.3  2006/02/20 14:34:32  bernie
41  *#* Include appconfig.h before using its definitions.
42  *#*
43  *#* Revision 1.2  2006/02/15 09:10:51  bernie
44  *#* Make title bold; Fix height when we have no menubar.
45  *#*/
46
47 #include "menu.h"
48 #include <gfx/gfx.h>
49 #include <gfx/font.h>
50 #include <gfx/text.h>
51 #include <drv/kbd.h>
52 #include <cfg/compiler.h>
53 #include <cfg/debug.h>
54 #include <appconfig.h>
55 #include <string.h> /* strcpy() */
56
57 #if CPU_HARVARD
58 #include <avr/pgmspace.h> /* strncpy_P() */
59 #endif
60
61 #if CONFIG_MENU_SMOOTH
62 #include <drv/lcd_gfx.h>
63 #endif
64
65 #if (CONFIG_MENU_TIMEOUT != 0)
66 #include <drv/timer.h>
67 #endif
68
69 #if CONFIG_MENU_MENUBAR
70 #include "menubar.h"
71 #endif
72
73 #if defined(CONFIG_LOCALE) && (CONFIG_LOCALE == 1)
74 #include "msg.h"
75 #else
76 #define PTRMSG(x) ((const char *)x)
77 #endif
78
79 /* Temporary fake defines for ABORT stuff... */
80 #define abort_top  0
81 #define PUSH_ABORT false
82 #define POP_ABORT  do {} while(0)
83 #define DO_ABORT   do {} while(0)
84
85
86 /**
87  * Return the total number of items in in a menu.
88  */
89 static int menu_count(const struct Menu *menu)
90 {
91         int cnt = 0;
92
93         for (cnt = 0; /*NOP*/; ++cnt)
94         {
95                 const MenuItem *item = &menu->items[cnt];
96 #if CPU_HARVARD
97                 MenuItem ram_item;
98                 if (menu->flags & MF_ROMITEMS)
99                 {
100                         memcpy_P(&ram_item, item, sizeof(ram_item));
101                         item = &ram_item;
102                 }
103 #endif
104                 if (!(item->label || item->hook))
105                         break;
106         }
107
108         return cnt;
109 }
110
111 #if CONFIG_MENU_MENUBAR
112
113 /*!
114  * Update the menu bar according to the selected item
115  * and redraw it.
116  */
117 static void menu_update_menubar(
118                 const struct Menu *menu,
119                 struct MenuBar *mb,
120                 int selected)
121 {
122         int item_flags;
123 #if CPU_HARVARD
124         if (menu->flags & MF_ROMITEMS)
125         {
126                 ASSERT(sizeof(menu->items[selected].flags) == sizeof(int));
127                 item_flags = pgm_read_int(&menu->items[selected].flags);
128         }
129         else
130 #endif
131                 item_flags = menu->items[selected].flags;
132
133         const_iptr_t newlabel = (const_iptr_t)LABEL_OK;
134
135         if (item_flags & MIF_DISABLED)
136                 newlabel = (const_iptr_t)LABEL_EMPTY;
137         else if (item_flags & MIF_TOGGLE)
138                 newlabel = (const_iptr_t)LABEL_SEL;
139         else if (item_flags & MIF_CHECKIT)
140         {
141                 newlabel = (item_flags & MIF_CHECKED) ?
142                         (const_iptr_t)LABEL_EMPTY : (const_iptr_t)LABEL_SEL;
143         }
144
145         mb->labels[3] = newlabel;
146         mbar_draw(mb);
147 }
148 #endif /* CONFIG_MENU_MENUBAR */
149
150
151 /**
152  * Show a menu on the display.
153  */
154 static void menu_layout(
155                 const struct Menu *menu,
156                 int first_item,
157                 int selected,
158                 bool redraw)
159 {
160         coord_t ypos;
161         int i;
162         const char * PROGMEM title = PTRMSG(menu->title);
163         Bitmap *bm = menu->bitmap;
164
165         ypos = bm->cr.ymin;
166
167 #if 0
168         if (redraw)
169         {
170                 /* Clear screen */
171                 text_clear(menu->bitmap);
172         }
173 #endif
174
175         if (title)
176         {
177                 if (redraw)
178                         text_xyprintf(bm, 0, ypos, STYLEF_UNDERLINE | STYLEF_BOLD | TEXT_CENTER | TEXT_FILL, title);
179                 ypos += bm->font->height;
180         }
181
182 #if CONFIG_MENU_SMOOTH
183         static coord_t yoffset = 0;
184         static int old_first_item = 0;
185         static int speed;
186         coord_t old_ymin = bm->cr.ymin;
187
188         /* Clip drawing inside menu items area */
189         gfx_setClipRect(bm,
190                 bm->cr.xmin, bm->cr.ymin + ypos,
191                 bm->cr.xmax, bm->cr.ymax);
192
193         if (old_first_item != first_item)
194         {
195                 /* Speed proportional to distance */
196                 speed = ABS(old_first_item - first_item) * 3;
197
198                 if (old_first_item > first_item)
199                 {
200                         yoffset += speed;
201                         if (yoffset > bm->font->height)
202                         {
203                                         yoffset = 0;
204                                         --old_first_item;
205                         }
206                 }
207                 else
208                 {
209                         yoffset -= speed;
210                         if (yoffset < -bm->font->height)
211                         {
212                                         yoffset = 0;
213                                         ++old_first_item;
214                         }
215                 }
216                 first_item = MIN(old_first_item, menu_count(menu));
217
218                 ypos += yoffset;
219                 redraw = true;
220         }
221 #endif /* CONFIG_MENU_SMOOTH */
222
223         if (redraw) for (i = first_item; /**/; ++i)
224         {
225                 const MenuItem *item = &menu->items[i];
226 #if CPU_HARVARD
227                 MenuItem ram_item;
228                 if (menu->flags & MF_ROMITEMS)
229                 {
230                         memcpy_P(&ram_item, item, sizeof(ram_item));
231                         item = &ram_item;
232                 }
233 #endif
234
235                 /* Check for end of room */
236                 if (ypos > bm->cr.ymax)
237                         break;
238
239                 /* Check for end of menu */
240                 if (!(item->label || item->hook))
241                         break;
242
243                 /* Only print visible items */
244                 if (!(item->flags & MIF_HIDDEN))
245                 {
246 #if CPU_HARVARD
247                         text_xyprintf_P
248 #else
249                         text_xyprintf
250 #endif
251                         (
252                                 bm, 0, ypos,
253                                 (i == selected) ? (STYLEF_INVERT | TEXT_FILL) : TEXT_FILL,
254                                 (item->flags & MIF_RAMLABEL) ? PSTR("%s%S") : PSTR("%S%S"),
255                                 PTRMSG(item->label),
256                                 (item->flags & MIF_TOGGLE) ?
257                                         ( (item->flags & MIF_CHECKED) ? PSTR(":ON") : PSTR(":OFF") )
258                                         : ( (item->flags & MIF_CHECKED) ? PSTR("\04") : PSTR("") )
259                         );
260                         ypos += bm->font->height;
261                 }
262         }
263
264 #if CONFIG_MENU_SMOOTH
265         if (redraw)
266         {
267                 /* Clear rest of area */
268                 gfx_rectClear(bm, bm->cr.xmin, ypos, bm->cr.xmax, bm->cr.ymax);
269
270                 lcd_blitBitmap(&lcd_bitmap);
271         }
272
273         /* Restore old cliprect */
274         gfx_setClipRect(bm,
275                         bm->cr.xmin, old_ymin,
276                         bm->cr.xmax, bm->cr.ymax);
277 #endif
278 }
279
280
281 /*!
282  * Handle menu item selection
283  */
284 static void menu_doselect(const struct Menu *menu, struct MenuItem *item)
285 {
286         /* Exclude other items */
287         int mask, i;
288         for (mask = item->flags & MIF_EXCLUDE_MASK, i = 0; mask; mask >>= 1, ++i)
289         {
290                 if (mask & 1)
291                         menu->items[i].flags &= ~MIF_CHECKED;
292         }
293
294         if (item->flags & MIF_DISABLED)
295                 return;
296
297         /* Handle checkable items */
298         if (item->flags & MIF_TOGGLE)
299                 item->flags ^= MIF_CHECKED;
300         else if (item->flags & MIF_CHECKIT)
301                 item->flags |= MIF_CHECKED;
302
303         /* Handle items with callback hooks */
304         if (item->hook)
305         {
306                 /* Push a jmp buffer to abort the operation with the STOP key */
307                 if (!PUSH_ABORT)
308                 {
309                         item->hook(item->userdata);
310                         POP_ABORT;
311                 }
312         }
313 }
314
315
316 /**
317  * Return the next visible item (rolls back to the first item)
318  */
319 static int menu_next_visible_item(const struct Menu *menu, int index)
320 {
321         int total = menu_count(menu);
322         int item_flags;
323
324         do
325         {
326                 if (++index >= total)
327                    index = 0;
328
329 #if CPU_HARVARD
330                 if (menu->flags & MF_ROMITEMS)
331                 {
332                         ASSERT(sizeof(menu->items[index].flags) == sizeof(int));
333                         item_flags = pgm_read_int(&menu->items[index].flags);
334                 }
335                 else
336 #endif
337                         item_flags = menu->items[index].flags;
338         }
339         while (item_flags & MIF_HIDDEN);
340
341         return index;
342 }
343
344
345 /**
346  * Return the previous visible item (rolls back to the last item)
347  */
348 static int menu_prev_visible_item(const struct Menu *menu, int index)
349 {
350         int total = menu_count(menu);
351         int item_flags;
352
353         do
354         {
355                 if (--index < 0)
356                         index = total - 1;
357
358 #if CPU_HARVARD
359                 if (menu->flags & MF_ROMITEMS)
360                 {
361                         ASSERT(sizeof(menu->items[index].flags) == sizeof(int));
362                         item_flags = pgm_read_int(&menu->items[index].flags);
363                 }
364                 else
365 #endif
366                         item_flags = menu->items[index].flags;
367         }
368         while (item_flags & MIF_HIDDEN);
369
370         return index;
371 }
372
373
374 /**
375  * Handle a menu and invoke hook functions for the selected menu items.
376  */
377 iptr_t menu_handle(const struct Menu *menu)
378 {
379         uint8_t items_per_page;
380         uint8_t first_item = 0;
381         uint8_t selected;
382         bool redraw = true;
383
384 #if (CONFIG_MENU_TIMEOUT != 0)
385         ticks_t now, menu_idle_time = timer_clock();
386 #endif
387
388 #if CONFIG_MENU_MENUBAR
389         struct MenuBar mb;
390         const_iptr_t labels[] =
391         {
392                 (const_iptr_t)LABEL_BACK,
393                 (const_iptr_t)LABEL_UPARROW,
394                 (const_iptr_t)LABEL_DOWNARROW,
395                 (const_iptr_t)0
396         };
397
398         /*
399          * Initialize menu bar
400          */
401         if (menu->flags & MF_TOPLEVEL)
402                 labels[0] = (const_iptr_t)LABEL_EMPTY;
403
404         mbar_init(&mb, menu->bitmap, labels, countof(labels));
405 #endif /* CONFIG_MENU_MENUBAR */
406
407
408         items_per_page =
409                 (menu->bitmap->height / menu->bitmap->font->height)
410 #if CONFIG_MENU_MENUBAR
411                 - 1 /* menu bar labels */
412 #endif
413                 - (menu->title ? 1 : 0);
414
415         /* Selected item should be a visible entry */
416         //first_item = selected = menu_next_visible_item(menu, menu->selected - 1);
417         selected = menu->selected;
418         first_item = 0;
419
420         for(;;)
421         {
422                 keymask_t key;
423
424                 /*
425                  * Keep selected item visible
426                  */
427                 while (selected < first_item)
428                         first_item = menu_prev_visible_item(menu, first_item);
429                 while (selected >= first_item + items_per_page)
430                         first_item = menu_next_visible_item(menu, first_item);
431
432                 menu_layout(menu, first_item, selected, redraw);
433                 redraw = false;
434
435                 #if CONFIG_MENU_MENUBAR
436                         menu_update_menubar(menu, &mb, selected);
437                 #endif
438
439                 #if CONFIG_MENU_SMOOTH || (CONFIG_MENU_TIMEOUT != 0)
440                         key = kbd_peek();
441                 #else
442                         key = kbd_get();
443                 #endif
444
445                 #if (CONFIG_MENU_TIMEOUT != 0)
446                         /* Reset idle timer on key press. */
447                         now = timer_clock();
448                         if (key)
449                                 menu_idle_time = now;
450                 #endif
451
452                 if (key & K_OK)
453                 {
454                         struct MenuItem *item = &(menu->items[selected]);
455 #if CPU_HARVARD
456                         MenuItem ram_item;
457                         if (menu->flags & MF_ROMITEMS)
458                         {
459                                 memcpy_P(&ram_item, item, sizeof(ram_item));
460                                 item = &ram_item;
461                         }
462 #endif
463                         menu_doselect(menu, item);
464                         redraw = true;
465
466                         /* Return userdata as result */
467                         if (!menu->flags & MF_STICKY)
468                         {
469                                 /* Store currently selected item before leaving. */
470                                 if (menu->flags & MF_SAVESEL)
471                                         CONST_CAST(struct Menu *, menu)->selected = selected;
472                                 return item->userdata;
473                         }
474                 }
475                 else if (key & K_UP)
476                 {
477                         selected = menu_prev_visible_item(menu, selected);
478                         redraw = true;
479                 }
480                 else if (key & K_DOWN)
481                 {
482                         selected = menu_next_visible_item(menu, selected);
483                         redraw = true;
484                 }
485                 else if (((key & K_CANCEL)
486                         #if CONFIG_MENU_TIMEOUT != 0
487                                 || (now - menu_idle_time > ms_to_ticks(CONFIG_MENU_TIMEOUT))
488                         #endif
489                                 ) && !(menu->flags & MF_TOPLEVEL)
490                 )
491                 {
492                         /* Store currently selected item before leaving. */
493                         if (menu->flags & MF_SAVESEL)
494                                 CONST_CAST(struct Menu *, menu)->selected = selected;
495                         return 0;
496                 }
497         }
498 }
499
500
501 /*!
502  * Set flags on a menuitem.
503  *
504  * \param menu  Menu owner of the item to change.
505  * \param idx   Index of the menu item.
506  * \param flags Bit mask of the flags to set.
507  *
508  * \return Old flags.
509  */
510 int menu_setFlags(struct Menu *menu, int idx, int flags)
511 {
512         ASSERT(idx < menu_count(menu));
513         ASSERT(!(menu->flags & MF_ROMITEMS));
514
515         int old = menu->items[idx].flags;
516         menu->items[idx].flags |= flags;
517         return old;
518 }
519
520
521 /*!
522  * Clear flags on a menuitem.
523  *
524  * \param menu  Menu owner of the item to change.
525  * \param idx   Index of the menu item.
526  * \param flags Bit mask of the flags to clear.
527  *
528  * \return Old flags.
529  */
530 int menu_clearFlags(struct Menu *menu, int idx, int flags)
531 {
532         ASSERT(idx < menu_count(menu));
533         ASSERT(!(menu->flags & MF_ROMITEMS));
534
535         int old = menu->items[idx].flags;
536         menu->items[idx].flags &= ~flags;
537         return old;
538 }