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