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