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