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