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