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