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