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