Add even more code duplication until we properly refactor debug.h.
[bertos.git] / mware / menu.c
1 /*!
2  * \file
3  * <!--
4  * Copyright 2003, 2004 Develer S.r.l. (http://www.develer.com/)
5  * Copyright 2000 Bernardo Innocenti <bernie@codewiz.org>
6  * All Rights Reserved.
7  * -->
8  *
9  * \version $Id$
10  *
11  * \author Bernardo Innocenti <bernie@develer.com>
12  * \author Stefano Fedrigo <aleph@develer.com>
13  *
14  * \brief General pourpose menu handling functions
15  */
16
17 /*#*
18  *#* $Log$
19  *#* Revision 1.3  2006/02/20 14:34:32  bernie
20  *#* Include appconfig.h before using its definitions.
21  *#*
22  *#* Revision 1.2  2006/02/15 09:10:51  bernie
23  *#* Make title bold; Fix height when we have no menubar.
24  *#*
25  *#* Revision 1.1  2006/02/10 12:29:36  bernie
26  *#* Add menu system.
27  *#*
28  *#* Revision 1.48  2005/11/27 23:02:55  bernie
29  *#* Move graphics modules from mware/ to gfx/.
30  *#*
31  *#* Revision 1.47  2005/11/16 18:10:19  bernie
32  *#* Move top-level headers to cfg/ as in DevLib.
33  *#*
34  *#* Revision 1.46  2005/02/17 03:49:21  bernie
35  *#* Update to new PGM api.
36  *#*
37  *#* Revision 1.45  2005/02/11 19:11:32  aleph
38  *#* Move menu_displaymsg() in new displaymsg module
39  *#*
40  *#* Revision 1.44  2005/01/21 20:05:57  aleph
41  *#* Fix build warning with debug off
42  *#*
43  *#* Revision 1.43  2005/01/13 16:56:36  aleph
44  *#* Fix progmem includes.
45  *#*
46  *#* Revision 1.42  2004/10/31 11:02:15  aleph
47  *#* Rename functions with correct codying conventions; Simplify version display
48  *#*
49  *#* Revision 1.41  2004/10/15 17:34:33  customer_pw
50  *#* Fix menuitem max length
51  *#*
52  *#* Revision 1.40  2004/10/06 12:55:08  customer_pw
53  *#* Declare unused (if !_DEBUG) menu_count()
54  *#*
55  *#* Revision 1.39  2004/10/01 14:04:59  customer_pw
56  *#* Add accessor functions for menu flags
57  *#*
58  *#* Revision 1.38  2004/09/27 12:05:46  customer_pw
59  *#* Use sel label for toggle menus and remove it
60  *#*
61  *#* Revision 1.37  2004/09/27 10:05:33  customer_pw
62  *#* Menu cosmetic fixes
63  *#*
64  *#* Revision 1.36  2004/09/14 22:18:03  bernie
65  *#* Adapt to stricter casting rules.
66  *#*
67  *#* Revision 1.35  2004/09/12 17:56:03  aleph
68  *#* Include debug.h instead of drv/kdebug.h
69  *#*/
70
71 #include "menu.h"
72
73 #include <gfx/gfx.h>
74 #include <gfx/font.h>
75 #include <gfx/text.h>
76 #include <drv/kbd.h>
77 #include <cfg/compiler.h>
78 #include <cfg/debug.h>
79 #include <appconfig.h>
80
81 #if CPU_HARVARD
82 #include <avr/pgmspace.h> /* strncpy_P() */
83 #endif
84
85 #include <string.h> /* strcpy() */
86
87 #if CONFIG_MENU_MENUBAR
88 #include "menubar.h"
89 #endif
90
91 #if defined(CONFIG_LOCALE) && (CONFIG_LOCALE == 1)
92 #include "msg.h"
93 #else
94 #define PTRMSG(x) ((const char *)x)
95 #endif
96
97
98 /* Temporary fake defines for ABORT stuff... */
99 #define abort_top  0
100 #define PUSH_ABORT false
101 #define POP_ABORT  do {} while(0)
102 #define DO_ABORT   do {} while(0)
103
104
105 #ifdef _DEBUG
106 /*!
107  * Count the items present in a menu.
108  */
109 static int UNUSED_FUNC menu_count(const struct Menu *menu)
110 {
111         int cnt = 0;
112         struct MenuItem *item;
113
114         for (item = menu->items; item->label; ++item)
115                 cnt++;
116
117         return cnt;
118 }
119 #endif /* _DEBUG */
120
121
122 #if CONFIG_MENU_MENUBAR
123
124 /*!
125  * Update the menu bar according to the selected item
126  * and redraw it.
127  */
128 static void menu_update_menubar(
129                 const struct Menu *menu,
130                 struct MenuBar *mb,
131                 int selected)
132 {
133         int item_flags = menu->items[selected].flags;
134         const_iptr_t newlabel = (const_iptr_t)LABEL_OK;
135
136         if (item_flags & MIF_DISABLED)
137                 newlabel = (const_iptr_t)LABEL_EMPTY;
138         else if (item_flags & MIF_TOGGLE)
139                 newlabel = (const_iptr_t)LABEL_SEL;
140         else if (item_flags & MIF_CHECKIT)
141         {
142                 newlabel = (item_flags & MIF_CHECKED) ?
143                         (const_iptr_t)LABEL_EMPTY : (const_iptr_t)LABEL_SEL;
144         }
145
146         mb->labels[3] = newlabel;
147         mbar_draw(mb);
148 }
149 #endif /* CONFIG_MENU_MENUBAR */
150
151
152 /*!
153  * Show a menu on the LCD display.
154  */
155 static void menu_layout(
156                 const struct Menu *menu,
157                 int first_item,
158                 int items_per_page,
159                 int selected)
160 {
161         const MenuItem *item;
162         int ypos, cnt;
163         const char * PROGMEM title = PTRMSG(menu->title);
164
165         ypos = menu->startrow;
166
167         if (title)
168                 text_xprintf(menu->bitmap, ypos++, 0, STYLEF_BOLD | TEXT_FILL, title);
169
170         for (
171                 cnt = 0, item = &menu->items[first_item];
172                 cnt < items_per_page;
173                 ++cnt, ++item)
174         {
175                 /* Check for end of menu */
176                 if (!(item->label || item->hook))
177                         break;
178
179                 /* Only print visible items */
180                 if (!(item->flags & MIF_HIDDEN))
181                 {
182 #if CPU_HARVARD
183                         text_xprintf_P
184 #else
185                         text_xprintf
186 #endif
187                         (
188                                 menu->bitmap, ypos++, 0,
189                                 (first_item + cnt == selected) ? (STYLEF_INVERT | TEXT_FILL) : TEXT_FILL,
190                                 (item->flags & MIF_RAMLABEL) ? PSTR("%s%s") : PSTR("%S%s"),
191                                 PTRMSG(item->label),
192                                 (item->flags & MIF_TOGGLE) ?
193                                         ( (item->flags & MIF_CHECKED) ? PSTR(":ON") : PSTR(":OFF") )
194                                         : ( (item->flags & MIF_CHECKED) ? PSTR("\04") : PSTR("") )
195                         );
196                 }
197         }
198 }
199
200
201 /*!
202  * Handle menu item selection
203  */
204 static void menu_doselect(const struct Menu *menu, struct MenuItem *item)
205 {
206         /* Exclude other items */
207         int mask, i;
208         for (mask = item->flags & MIF_EXCLUDE_MASK, i = 0; mask; mask >>= 1, ++i)
209         {
210                 if (mask & 1)
211                         menu->items[i].flags &= ~MIF_CHECKED;
212         }
213
214         if (item->flags & MIF_DISABLED)
215                 return;
216
217         /* Handle checkable items */
218         if (item->flags & MIF_TOGGLE)
219                 item->flags ^= MIF_CHECKED;
220         else if (item->flags & MIF_CHECKIT)
221                 item->flags |= MIF_CHECKED;
222
223         /* Handle items with callback hooks */
224         if (item->hook)
225         {
226                 /* Push a jmp buffer to abort the operation with the STOP key */
227                 if (!PUSH_ABORT)
228                 {
229                         item->hook(item->userdata);
230                         POP_ABORT;
231                 }
232         }
233 }
234
235
236 /*!
237  * Return the previous visible item (rolls back to the last item)
238  */
239 static int menu_next_visible_item(const struct Menu *menu, int index, int total)
240 {
241         do
242         {
243                 if (++index >= total)
244                    index = 0;
245         }
246         while (menu->items[index].flags & MIF_HIDDEN);
247
248         return index;
249 }
250
251
252 /*!
253  * Return the next visible item (rolls back to the first item)
254  */
255 static int menu_prev_visible_item(const struct Menu *menu, int index, int total)
256 {
257         do
258         {
259                 if (--index < 0)
260                         index = total - 1;
261         }
262         while (menu->items[index].flags & MIF_HIDDEN);
263
264         return index;
265 }
266
267
268 /*!
269  * Handle a menu and invoke hook functions for the selected menu items.
270  */
271 iptr_t menu_handle(const struct Menu *menu)
272 {
273         uint8_t entries, visible_entries, items_per_page;
274         uint8_t first_item, selected;
275
276 #if CONFIG_MENU_MENUBAR
277         struct MenuBar mb;
278         const_iptr_t labels[] =
279         {
280                 (const_iptr_t)LABEL_BACK,
281                 (const_iptr_t)LABEL_UPARROW,
282                 (const_iptr_t)LABEL_DOWNARROW,
283                 (const_iptr_t)0
284         };
285
286         /*
287          * Initialize menu bar
288          */
289         if (menu->flags & MF_TOPLEVEL)
290                 labels[0] = (const_iptr_t)LABEL_EMPTY;
291
292         mbar_init(&mb, menu->bitmap, labels, countof(labels));
293 #endif /* CONFIG_MENU_MENUBAR */
294
295
296         /* Compute total number of items in menu (entries) and
297          * the number of visible entries, which excludes items
298          * without a label.
299          */
300         {
301                 struct MenuItem *item;
302
303                 entries = 0;
304                 visible_entries = 0;
305
306                 for (item = menu->items; (item->label || item->hook); ++item)
307                 {
308                         ++entries;
309                         if (!(item->flags & MIF_HIDDEN))
310                                 ++visible_entries;
311                 }
312         }
313
314         items_per_page =
315                 (menu->bitmap->height / menu->bitmap->font->height)
316                 - menu->startrow
317 #if CONFIG_MENU_MENUBAR
318                 - 1 /* menu bar labels */
319 #endif
320                 - (menu->title ? 1 : 0);
321
322         /* Selected item should be a visible entry */
323         first_item = selected = menu_next_visible_item(menu, -1, entries);
324
325         for(;;)
326         {
327                 keymask_t key;
328
329                 /*
330                  * Keep selected item visible
331                  */
332                 while (selected < first_item)
333                         first_item = menu_prev_visible_item(menu, first_item, entries);
334                 while (selected >= first_item + items_per_page)
335                         first_item = menu_next_visible_item(menu, first_item, entries);
336
337                 /* Clear screen */
338                 text_clear(menu->bitmap);
339                 menu_layout(menu, first_item, items_per_page, selected);
340
341 #if CONFIG_MENU_MENUBAR
342                 menu_update_menubar(menu, &mb, selected);
343 #endif
344
345                 key = kbd_get();
346
347                 if (key & K_OK)
348                 {
349                         struct MenuItem *item = &(menu->items[selected]);
350                         menu_doselect(menu, item);
351
352                         /* Return userdata as result */
353                         if (!menu->flags & MF_STICKY)
354                                 return item->userdata;
355                 }
356                 else if (key & K_UP)
357                 {
358                         selected = menu_prev_visible_item(menu, selected, entries);
359                 }
360                 else if (key & K_DOWN)
361                 {
362                         selected = menu_next_visible_item(menu, selected, entries);
363                 }
364                 else if (key & K_CANCEL && !(menu->flags & MF_TOPLEVEL))
365                 {
366                         return 0;
367                 }
368         }
369 }
370
371
372 /*!
373  * Set flags on a menuitem.
374  *
375  * \param menu  Menu owner of the item to change.
376  * \param idx   Index of the menu item.
377  * \param flags Bit mask of the flags to set.
378  *
379  * \return Old flags.
380  */
381 int menu_setFlags(struct Menu *menu, int idx, int flags)
382 {
383         ASSERT(idx < menu_count(menu));
384
385         int old = menu->items[idx].flags;
386         menu->items[idx].flags |= flags;
387         return old;
388 }
389
390
391 /*!
392  * Clear flags on a menuitem.
393  *
394  * \param menu  Menu owner of the item to change.
395  * \param idx   Index of the menu item.
396  * \param flags Bit mask of the flags to clear.
397  *
398  * \return Old flags.
399  */
400 int menu_clearFlags(struct Menu *menu, int idx, int flags)
401 {
402         ASSERT(idx < menu_count(menu));
403
404         int old = menu->items[idx].flags;
405         menu->items[idx].flags &= ~flags;
406         return old;
407 }