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