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