menu.c

Go to the documentation of this file.
00001 
00042 /*#*
00043  *#* $Log$
00044  *#* Revision 1.8  2006/09/13 13:58:32  bernie
00045  *#* Add RenderHook support.
00046  *#*
00047  *#* Revision 1.7  2006/08/01 12:22:46  bernie
00048  *#* Mention DevLib license.
00049  *#*
00050  *#* Revision 1.6  2006/07/19 12:56:27  bernie
00051  *#* Convert to new Doxygen style.
00052  *#*
00053  *#* Revision 1.5  2006/06/03 13:58:01  bernie
00054  *#* Fix recursive timeout and add exit status information.
00055  *#*
00056  *#* Revision 1.4  2006/06/02 12:26:18  bernie
00057  *#* Draw graphical checkmarks.
00058  *#*
00059  *#* Revision 1.3  2006/05/28 15:03:31  bernie
00060  *#* Avoid unnecessary rendering.
00061  *#*
00062  *#* Revision 1.2  2006/05/25 23:34:38  bernie
00063  *#* Implement menu timeouts.
00064  *#*
00065  *#* Revision 1.1  2006/05/15 07:20:54  bernie
00066  *#* Move menu to gui/.
00067  *#*
00068  *#* Revision 1.7  2006/04/27 05:39:24  bernie
00069  *#* Enhance text rendering to arbitrary x,y coords.
00070  *#*
00071  *#* Revision 1.6  2006/04/11 00:07:32  bernie
00072  *#* Implemenent MF_SAVESEL flag.
00073  *#*
00074  *#* Revision 1.5  2006/03/22 09:49:51  bernie
00075  *#* Simplifications from project_grl.
00076  *#*
00077  *#* Revision 1.4  2006/03/20 17:48:35  bernie
00078  *#* Implement support for ROM menus.
00079  *#*
00080  *#* Revision 1.3  2006/02/20 14:34:32  bernie
00081  *#* Include appconfig.h before using its definitions.
00082  *#*
00083  *#* Revision 1.2  2006/02/15 09:10:51  bernie
00084  *#* Make title bold; Fix height when we have no menubar.
00085  *#*/
00086 
00087 #include "menu.h"
00088 
00089 #include <gfx/gfx.h>
00090 #include <gfx/font.h>
00091 #include <gfx/text.h>
00092 #include <drv/kbd.h>
00093 #include <cfg/compiler.h>
00094 #include <cfg/debug.h>
00095 #include <appconfig.h>
00096 #include <string.h> /* strcpy() */
00097 
00098 #if CPU_HARVARD
00099 #include <avr/pgmspace.h> /* strncpy_P() */
00100 #endif
00101 
00102 #if CONFIG_MENU_SMOOTH
00103 #include <drv/lcd_gfx.h>
00104 #endif
00105 
00106 #if (CONFIG_MENU_TIMEOUT != 0)
00107 #include <drv/timer.h>
00108 #endif
00109 
00110 #if CONFIG_MENU_MENUBAR
00111 #include "menubar.h"
00112 #endif
00113 
00114 #if defined(CONFIG_LOCALE) && (CONFIG_LOCALE == 1)
00115 #include "msg.h"
00116 #else
00117 #define PTRMSG(x) ((const char *)x)
00118 #endif
00119 
00120 
00121 /* Temporary fake defines for ABORT stuff... */
00122 #define abort_top  0
00123 #define PUSH_ABORT false
00124 #define POP_ABORT  do {} while(0)
00125 #define DO_ABORT   do {} while(0)
00126 
00127 
00131 static int menu_count(const struct Menu *menu)
00132 {
00133     int cnt = 0;
00134 
00135     for (cnt = 0; /*NOP*/; ++cnt)
00136     {
00137         const MenuItem *item = &menu->items[cnt];
00138 #if CPU_HARVARD
00139         MenuItem ram_item;
00140         if (menu->flags & MF_ROMITEMS)
00141         {
00142             memcpy_P(&ram_item, item, sizeof(ram_item));
00143             item = &ram_item;
00144         }
00145 #endif
00146         if (!(item->label || item->hook))
00147             break;
00148     }
00149 
00150     return cnt;
00151 }
00152 
00153 #if CONFIG_MENU_MENUBAR
00154 
00158 static void menu_update_menubar(
00159         const struct Menu *menu,
00160         struct MenuBar *mb,
00161         int selected)
00162 {
00163     int item_flags;
00164 #if CPU_HARVARD
00165     if (menu->flags & MF_ROMITEMS)
00166     {
00167         ASSERT(sizeof(menu->items[selected].flags) == sizeof(int));
00168         item_flags = pgm_read_int(&menu->items[selected].flags);
00169     }
00170     else
00171 #endif
00172         item_flags = menu->items[selected].flags;
00173 
00174     const_iptr_t newlabel = (const_iptr_t)LABEL_OK;
00175 
00176     if (item_flags & MIF_DISABLED)
00177         newlabel = (const_iptr_t)LABEL_EMPTY;
00178     else if (item_flags & MIF_TOGGLE)
00179         newlabel = (const_iptr_t)LABEL_SEL;
00180     else if (item_flags & MIF_CHECKIT)
00181     {
00182         newlabel = (item_flags & MIF_CHECKED) ?
00183             (const_iptr_t)LABEL_EMPTY : (const_iptr_t)LABEL_SEL;
00184     }
00185 
00186     mb->labels[3] = newlabel;
00187     mbar_draw(mb);
00188 }
00189 #endif /* CONFIG_MENU_MENUBAR */
00190 
00191 
00192 static void menu_defaultRenderHook(struct Bitmap *bm, int ypos, bool selected, const struct MenuItem *item)
00193 {
00194     if (item->flags & MIF_CHECKIT)
00195     {
00196         gfx_rectClear(bm, 0, ypos,
00197                 bm->font->height, ypos + bm->font->height);
00198 
00199         if (item->flags & MIF_TOGGLE)
00200             gfx_rectDraw(bm, 2, ypos + 2,
00201                     bm->font->height - 2, ypos + bm->font->height - 2);
00202         if (item->flags & MIF_CHECKED)
00203         {
00204             gfx_line(bm,
00205                     3, ypos + 3,
00206                     bm->font->height - 3, ypos + bm->font->height - 3);
00207             gfx_line(bm,
00208                     bm->font->height - 3, ypos + 3,
00209                     3, ypos + bm->font->height - 3);
00210         }
00211     }
00212 
00213 #if CPU_HARVARD
00214     ((item->flags & MIF_RAMLABEL) ? text_xyprintf : text_xyprintf_P)
00215 #else
00216     text_xyprintf
00217 #endif
00218     (
00219         bm, (item->flags & MIF_CHECKIT) ? bm->font->height : 0, ypos,
00220         selected ? (STYLEF_INVERT | TEXT_FILL) : TEXT_FILL,
00221         PTRMSG(item->label)
00222     );
00223 }
00224 
00228 static void menu_layout(
00229         const struct Menu *menu,
00230         int first_item,
00231         int selected,
00232         bool redraw)
00233 {
00234     coord_t ypos;
00235     int i;
00236     const char * PROGMEM title = PTRMSG(menu->title);
00237     Bitmap *bm = menu->bitmap;
00238 
00239     ypos = bm->cr.ymin;
00240 
00241 #if 0
00242     if (redraw)
00243     {
00244         /* Clear screen */
00245         text_clear(menu->bitmap);
00246     }
00247 #endif
00248 
00249     if (title)
00250     {
00251         if (redraw)
00252             text_xyprintf(bm, 0, ypos, STYLEF_UNDERLINE | STYLEF_BOLD | TEXT_CENTER | TEXT_FILL, title);
00253         ypos += bm->font->height;
00254     }
00255 
00256 #if CONFIG_MENU_SMOOTH
00257     static coord_t yoffset = 0;
00258     static int old_first_item = 0;
00259     static int speed;
00260     coord_t old_ymin = bm->cr.ymin;
00261 
00262     /* Clip drawing inside menu items area */
00263     gfx_setClipRect(bm,
00264         bm->cr.xmin, bm->cr.ymin + ypos,
00265         bm->cr.xmax, bm->cr.ymax);
00266 
00267     if (old_first_item != first_item)
00268     {
00269         /* Speed proportional to distance */
00270         speed = ABS(old_first_item - first_item) * 3;
00271 
00272         if (old_first_item > first_item)
00273         {
00274             yoffset += speed;
00275             if (yoffset > bm->font->height)
00276             {
00277                     yoffset = 0;
00278                     --old_first_item;
00279             }
00280         }
00281         else
00282         {
00283             yoffset -= speed;
00284             if (yoffset < -bm->font->height)
00285             {
00286                     yoffset = 0;
00287                     ++old_first_item;
00288             }
00289         }
00290         first_item = MIN(old_first_item, menu_count(menu));
00291 
00292         ypos += yoffset;
00293         redraw = true;
00294     }
00295 #endif /* CONFIG_MENU_SMOOTH */
00296 
00297     if (redraw) for (i = first_item; ; ++i)
00298     {
00299         const MenuItem *item = &menu->items[i];
00300 #if CPU_HARVARD
00301         MenuItem ram_item;
00302         if (menu->flags & MF_ROMITEMS)
00303         {
00304             memcpy_P(&ram_item, item, sizeof(ram_item));
00305             item = &ram_item;
00306         }
00307 #endif /* CPU_HARVARD */
00308 
00309         /* Check for end of room */
00310         if (ypos > bm->cr.ymax)
00311             break;
00312 
00313         /* Check for end of menu */
00314         if (!(item->label || item->hook))
00315             break;
00316 
00317         /* Only print visible items */
00318         if (!(item->flags & MIF_HIDDEN))
00319         {
00320             /* Check if a special render function is supplied, otherwise use defaults */
00321             RenderHook renderhook = (item->flags & MIF_RENDERHOOK) ? (RenderHook)item->label : menu_defaultRenderHook;
00322 
00323             /* Render menuitem */
00324             renderhook(menu->bitmap, ypos++, (i == selected), item);
00325 
00326             ypos += bm->font->height;
00327         }
00328     }
00329 
00330 #if CONFIG_MENU_SMOOTH
00331     if (redraw)
00332     {
00333         /* Clear rest of area */
00334         gfx_rectClear(bm, bm->cr.xmin, ypos, bm->cr.xmax, bm->cr.ymax);
00335 
00336         lcd_blitBitmap(&lcd_bitmap);
00337     }
00338 
00339     /* Restore old cliprect */
00340     gfx_setClipRect(bm,
00341             bm->cr.xmin, old_ymin,
00342             bm->cr.xmax, bm->cr.ymax);
00343 
00344 #endif /* CONFIG_MENU_SMOOTH */
00345 }
00346 
00347 
00351 static iptr_t menu_doselect(const struct Menu *menu, struct MenuItem *item)
00352 {
00353     iptr_t result = 0;
00354 
00355     /* Exclude other items */
00356     int mask, i;
00357     for (mask = item->flags & MIF_EXCLUDE_MASK, i = 0; mask; mask >>= 1, ++i)
00358     {
00359         if (mask & 1)
00360             menu->items[i].flags &= ~MIF_CHECKED;
00361     }
00362 
00363     if (item->flags & MIF_DISABLED)
00364         return MENU_DISABLED;
00365 
00366     /* Handle checkable items */
00367     if (item->flags & MIF_TOGGLE)
00368         item->flags ^= MIF_CHECKED;
00369     else if (item->flags & MIF_CHECKIT)
00370         item->flags |= MIF_CHECKED;
00371 
00372     /* Handle items with callback hooks */
00373     if (item->hook)
00374     {
00375         /* Push a jmp buffer to abort the operation with the STOP/CANCEL key */
00376         if (!PUSH_ABORT)
00377         {
00378             result = item->hook(item->userdata);
00379             POP_ABORT;
00380         }
00381     }
00382     else
00383         result = item->userdata;
00384 
00385     return result;
00386 }
00387 
00388 
00392 static int menu_next_visible_item(const struct Menu *menu, int index)
00393 {
00394     int total = menu_count(menu);
00395     int item_flags;
00396 
00397     do
00398     {
00399         if (++index >= total)
00400            index = 0;
00401 
00402 #if CPU_HARVARD
00403         if (menu->flags & MF_ROMITEMS)
00404         {
00405             ASSERT(sizeof(menu->items[index].flags) == sizeof(int));
00406             item_flags = pgm_read_int(&menu->items[index].flags);
00407         }
00408         else
00409 #endif
00410             item_flags = menu->items[index].flags;
00411     }
00412     while (item_flags & MIF_HIDDEN);
00413 
00414     return index;
00415 }
00416 
00417 
00421 static int menu_prev_visible_item(const struct Menu *menu, int index)
00422 {
00423     int total = menu_count(menu);
00424     int item_flags;
00425 
00426     do
00427     {
00428         if (--index < 0)
00429             index = total - 1;
00430 
00431 #if CPU_HARVARD
00432         if (menu->flags & MF_ROMITEMS)
00433         {
00434             ASSERT(sizeof(menu->items[index].flags) == sizeof(int));
00435             item_flags = pgm_read_int(&menu->items[index].flags);
00436         }
00437         else
00438 #endif
00439             item_flags = menu->items[index].flags;
00440     }
00441     while (item_flags & MIF_HIDDEN);
00442 
00443     return index;
00444 }
00445 
00446 
00450 iptr_t menu_handle(const struct Menu *menu)
00451 {
00452     uint8_t items_per_page;
00453     uint8_t first_item = 0;
00454     uint8_t selected;
00455     iptr_t result = 0;
00456     bool redraw = true;
00457 
00458 #if (CONFIG_MENU_TIMEOUT != 0)
00459     ticks_t now, menu_idle_time = timer_clock();
00460 #endif
00461 
00462 #if CONFIG_MENU_MENUBAR
00463     struct MenuBar mb;
00464     const_iptr_t labels[] =
00465     {
00466         (const_iptr_t)LABEL_BACK,
00467         (const_iptr_t)LABEL_UPARROW,
00468         (const_iptr_t)LABEL_DOWNARROW,
00469         (const_iptr_t)0
00470     };
00471 
00472     /*
00473      * Initialize menu bar
00474      */
00475     if (menu->flags & MF_TOPLEVEL)
00476         labels[0] = (const_iptr_t)LABEL_EMPTY;
00477 
00478     mbar_init(&mb, menu->bitmap, labels, countof(labels));
00479 #endif /* CONFIG_MENU_MENUBAR */
00480 
00481 
00482     items_per_page =
00483         (menu->bitmap->height / menu->bitmap->font->height)
00484 #if CONFIG_MENU_MENUBAR
00485         - 1 /* menu bar labels */
00486 #endif
00487         - (menu->title ? 1 : 0);
00488 
00489     /* Selected item should be a visible entry */
00490     //first_item = selected = menu_next_visible_item(menu, menu->selected - 1);
00491     selected = menu->selected;
00492     first_item = 0;
00493 
00494     for(;;)
00495     {
00496         keymask_t key;
00497 
00498         /*
00499          * Keep selected item visible
00500          */
00501         while (selected < first_item)
00502             first_item = menu_prev_visible_item(menu, first_item);
00503         while (selected >= first_item + items_per_page)
00504             first_item = menu_next_visible_item(menu, first_item);
00505 
00506         menu_layout(menu, first_item, selected, redraw);
00507         redraw = false;
00508 
00509         #if CONFIG_MENU_MENUBAR
00510             menu_update_menubar(menu, &mb, selected);
00511         #endif
00512 
00513         #if CONFIG_MENU_SMOOTH || (CONFIG_MENU_TIMEOUT != 0)
00514             key = kbd_peek();
00515         #else
00516             key = kbd_get();
00517         #endif
00518 
00519         #if (CONFIG_MENU_TIMEOUT != 0)
00520             /* Reset idle timer on key press. */
00521             now = timer_clock();
00522             if (key)
00523                 menu_idle_time = now;
00524         #endif
00525 
00526         if (key & K_OK)
00527         {
00528             struct MenuItem *item = &(menu->items[selected]);
00529 #if CPU_HARVARD
00530             MenuItem ram_item;
00531             if (menu->flags & MF_ROMITEMS)
00532             {
00533                 memcpy_P(&ram_item, item, sizeof(ram_item));
00534                 item = &ram_item;
00535             }
00536 #endif
00537             result = menu_doselect(menu, item);
00538             redraw = true;
00539 
00540             /* Return immediately */
00541             if (!(menu->flags & MF_STICKY))
00542                 break;
00543 
00544             #if (CONFIG_MENU_TIMEOUT != 0)
00545                 /* Chain timeout */
00546                 if ((result == MENU_TIMEOUT) && !(menu->flags & MF_TOPLEVEL))
00547                     break;
00548 
00549                 /* Reset timeout */
00550                 menu_idle_time = timer_clock();
00551             #endif
00552         }
00553         else if (key & K_UP)
00554         {
00555             selected = menu_prev_visible_item(menu, selected);
00556             redraw = true;
00557         }
00558         else if (key & K_DOWN)
00559         {
00560             selected = menu_next_visible_item(menu, selected);
00561             redraw = true;
00562         }
00563         else if (!(menu->flags & MF_TOPLEVEL))
00564         {
00565             if (key & K_CANCEL)
00566             {
00567                 result = MENU_CANCEL;
00568                 break;
00569             }
00570 
00571             #if CONFIG_MENU_TIMEOUT != 0
00572                 if (now - menu_idle_time > ms_to_ticks(CONFIG_MENU_TIMEOUT))
00573                 {
00574                     result = MENU_TIMEOUT;
00575                     break;
00576                 }
00577             #endif
00578         }
00579     }
00580 
00581     /* Store currently selected item before leaving. */
00582     if (menu->flags & MF_SAVESEL)
00583         CONST_CAST(struct Menu *, menu)->selected = selected;
00584 
00585     return result;
00586 }
00587 
00588 
00598 int menu_setFlags(struct Menu *menu, int idx, int flags)
00599 {
00600     ASSERT(idx < menu_count(menu));
00601     ASSERT(!(menu->flags & MF_ROMITEMS));
00602 
00603     int old = menu->items[idx].flags;
00604     menu->items[idx].flags |= flags;
00605     return old;
00606 }
00607 
00608 
00618 int menu_clearFlags(struct Menu *menu, int idx, int flags)
00619 {
00620     ASSERT(idx < menu_count(menu));
00621     ASSERT(!(menu->flags & MF_ROMITEMS));
00622 
00623     int old = menu->items[idx].flags;
00624     menu->items[idx].flags &= ~flags;
00625     return old;
00626 }