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