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