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