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