menu.c

Go to the documentation of this file.
00001 
00040 #include "menu.h"
00041 
00042 #include "cfg/cfg_menu.h"
00043 #include "cfg/cfg_arch.h"
00044 
00045 #include <cfg/compiler.h>
00046 #include <cfg/debug.h>
00047 
00048 #include <gfx/gfx.h>
00049 #include <gfx/font.h>
00050 #include <gfx/text.h>
00051 
00052 #include <drv/kbd.h>
00053 
00054 #include <string.h> /* strcpy() */
00055 
00056 #if CPU_HARVARD
00057 #include <avr/pgmspace.h> /* strncpy_P() */
00058 #endif
00059 
00060 #if (CONFIG_MENU_TIMEOUT != 0)
00061 #include <drv/timer.h>
00062 #endif
00063 
00064 #if CONFIG_MENU_MENUBAR
00065 #include "menubar.h"
00066 #endif
00067 
00068 #if defined(CONFIG_LOCALE) && (CONFIG_LOCALE == 1)
00069 #include "msg.h"
00070 #else
00071 #define PTRMSG(x) ((const char *)x)
00072 #endif
00073 
00074 
00075 /* Temporary fake defines for ABORT stuff... */
00076 #define abort_top  0
00077 #define PUSH_ABORT false
00078 #define POP_ABORT  do {} while(0)
00079 #define DO_ABORT   do {} while(0)
00080 
00081 
00085 static int menu_count(const struct Menu *menu)
00086 {
00087     int cnt = 0;
00088 
00089     for (cnt = 0; /*NOP*/; ++cnt)
00090     {
00091         const MenuItem *item = &menu->items[cnt];
00092 #if CPU_HARVARD
00093         MenuItem ram_item;
00094         if (menu->flags & MF_ROMITEMS)
00095         {
00096             memcpy_P(&ram_item, item, sizeof(ram_item));
00097             item = &ram_item;
00098         }
00099 #endif
00100         if (!(item->label || item->hook))
00101             break;
00102     }
00103 
00104     return cnt;
00105 }
00106 
00107 #if CONFIG_MENU_MENUBAR
00108 
00112 static void menu_update_menubar(
00113         const struct Menu *menu,
00114         struct MenuBar *mb,
00115         int selected)
00116 {
00117     int item_flags;
00118 #if CPU_HARVARD
00119     if (menu->flags & MF_ROMITEMS)
00120     {
00121         ASSERT(sizeof(menu->items[selected].flags) == sizeof(int));
00122         item_flags = pgm_read_int(&menu->items[selected].flags);
00123     }
00124     else
00125 #endif
00126         item_flags = menu->items[selected].flags;
00127 
00128     const_iptr_t newlabel = (const_iptr_t)LABEL_OK;
00129 
00130     if (item_flags & MIF_DISABLED)
00131         newlabel = (const_iptr_t)LABEL_EMPTY;
00132     else if (item_flags & MIF_TOGGLE)
00133         newlabel = (const_iptr_t)LABEL_SEL;
00134     else if (item_flags & MIF_CHECKIT)
00135     {
00136         newlabel = (item_flags & MIF_CHECKED) ?
00137             (const_iptr_t)LABEL_EMPTY : (const_iptr_t)LABEL_SEL;
00138     }
00139 
00140     mb->labels[3] = newlabel;
00141     mbar_draw(mb);
00142 }
00143 #endif /* CONFIG_MENU_MENUBAR */
00144 
00145 
00146 static void menu_defaultRenderHook(struct Bitmap *bm, int ypos, bool selected, const struct MenuItem *item)
00147 {
00148     if (item->flags & MIF_CHECKIT)
00149     {
00150         gfx_rectClear(bm, 0, ypos,
00151                 bm->font->height, ypos + bm->font->height);
00152 
00153         if (item->flags & MIF_TOGGLE)
00154             gfx_rectDraw(bm, 2, ypos + 2,
00155                     bm->font->height - 2, ypos + bm->font->height - 2);
00156         if (item->flags & MIF_CHECKED)
00157         {
00158             gfx_line(bm,
00159                     3, ypos + 3,
00160                     bm->font->height - 3, ypos + bm->font->height - 3);
00161             gfx_line(bm,
00162                     bm->font->height - 3, ypos + 3,
00163                     3, ypos + bm->font->height - 3);
00164         }
00165     }
00166 
00167 #if CPU_HARVARD
00168     ((item->flags & MIF_RAMLABEL) ? text_xyprintf : text_xyprintf_P)
00169 #else
00170     text_xyprintf
00171 #endif
00172     (
00173         bm, (item->flags & MIF_CHECKIT) ? bm->font->height : 0, ypos,
00174         selected ? (STYLEF_INVERT | TEXT_FILL) : TEXT_FILL,
00175         PTRMSG(item->label)
00176     );
00177 }
00178 
00182 static void menu_layout(
00183         const struct Menu *menu,
00184         int first_item,
00185         int selected,
00186         bool redraw)
00187 {
00188     coord_t ypos;
00189     int i;
00190     const char * PROGMEM title = PTRMSG(menu->title);
00191     Bitmap *bm = menu->bitmap;
00192 
00193     ypos = bm->cr.ymin;
00194 
00195     if (redraw)
00196     {
00197         /* Clear screen */
00198         text_clear(menu->bitmap);
00199     }
00200 
00201     if (title)
00202     {
00203         if (redraw)
00204             text_xyprintf(bm, 0, ypos, STYLEF_UNDERLINE | STYLEF_BOLD | TEXT_CENTER | TEXT_FILL, title);
00205         ypos += bm->font->height;
00206     }
00207 
00208 #if CONFIG_MENU_SMOOTH
00209     static coord_t yoffset = 0;
00210     static int old_first_item = 0;
00211     static int speed;
00212     coord_t old_ymin = bm->cr.ymin;
00213 
00214     /* Clip drawing inside menu items area */
00215     gfx_setClipRect(bm,
00216         bm->cr.xmin, bm->cr.ymin + ypos,
00217         bm->cr.xmax, bm->cr.ymax);
00218 
00219     if (old_first_item != first_item)
00220     {
00221         /* Speed proportional to distance */
00222         speed = ABS(old_first_item - first_item) * 3;
00223 
00224         if (old_first_item > first_item)
00225         {
00226             yoffset += speed;
00227             if (yoffset > bm->font->height)
00228             {
00229                     yoffset = 0;
00230                     --old_first_item;
00231             }
00232         }
00233         else
00234         {
00235             yoffset -= speed;
00236             if (yoffset < -bm->font->height)
00237             {
00238                     yoffset = 0;
00239                     ++old_first_item;
00240             }
00241         }
00242         first_item = MIN(old_first_item, menu_count(menu));
00243 
00244         ypos += yoffset;
00245         redraw = true;
00246     }
00247 #endif /* CONFIG_MENU_SMOOTH */
00248 
00249     if (redraw) for (i = first_item; ; ++i)
00250     {
00251         const MenuItem *item = &menu->items[i];
00252 #if CPU_HARVARD
00253         MenuItem ram_item;
00254         if (menu->flags & MF_ROMITEMS)
00255         {
00256             memcpy_P(&ram_item, item, sizeof(ram_item));
00257             item = &ram_item;
00258         }
00259 #endif /* CPU_HARVARD */
00260 
00261         /* Check for end of room */
00262         if (ypos > bm->cr.ymax)
00263             break;
00264 
00265         /* Check for end of menu */
00266         if (!(item->label || item->hook))
00267             break;
00268 
00269         /* Only print visible items */
00270         if (!(item->flags & MIF_HIDDEN))
00271         {
00272             /* Check if a special render function is supplied, otherwise use defaults */
00273             #if (ARCH & ARCH_NIGHTTEST)
00274                 #warning __FILTER_NEXT_WARNING__
00275             #endif
00276             RenderHook renderhook = (item->flags & MIF_RENDERHOOK) ? (RenderHook)item->label : menu_defaultRenderHook;
00277 
00278             /* Render menuitem */
00279             renderhook(menu->bitmap, ypos++, (i == selected), item);
00280 
00281             ypos += bm->font->height;
00282         }
00283     }
00284 
00285 #if CONFIG_MENU_SMOOTH
00286     if (redraw)
00287     {
00288         /* Clear rest of area */
00289         gfx_rectClear(bm, bm->cr.xmin, ypos, bm->cr.xmax, bm->cr.ymax);
00290 
00291         menu->lcd_blitBitmap(bm);
00292     }
00293 
00294     /* Restore old cliprect */
00295     gfx_setClipRect(bm,
00296             bm->cr.xmin, old_ymin,
00297             bm->cr.xmax, bm->cr.ymax);
00298 
00299 #endif /* CONFIG_MENU_SMOOTH */
00300 }
00301 
00302 
00306 static iptr_t menu_doselect(const struct Menu *menu, struct MenuItem *item)
00307 {
00308     iptr_t result = 0;
00309 
00310     /* Exclude other items */
00311     int mask, i;
00312     for (mask = item->flags & MIF_EXCLUDE_MASK, i = 0; mask; mask >>= 1, ++i)
00313     {
00314         if (mask & 1)
00315             menu->items[i].flags &= ~MIF_CHECKED;
00316     }
00317 
00318     if (item->flags & MIF_DISABLED)
00319         return MENU_DISABLED;
00320 
00321     /* Handle checkable items */
00322     if (item->flags & MIF_TOGGLE)
00323         item->flags ^= MIF_CHECKED;
00324     else if (item->flags & MIF_CHECKIT)
00325         item->flags |= MIF_CHECKED;
00326 
00327     /* Handle items with callback hooks */
00328     if (item->hook)
00329     {
00330         /* Push a jmp buffer to abort the operation with the STOP/CANCEL key */
00331         if (!PUSH_ABORT)
00332         {
00333             result = item->hook(item->userdata);
00334             POP_ABORT;
00335         }
00336     }
00337     else
00338         result = item->userdata;
00339 
00340     return result;
00341 }
00342 
00343 
00347 static int menu_next_visible_item(const struct Menu *menu, int index)
00348 {
00349     int total = menu_count(menu);
00350     int item_flags;
00351 
00352     do
00353     {
00354         if (++index >= total)
00355            index = 0;
00356 
00357 #if CPU_HARVARD
00358         if (menu->flags & MF_ROMITEMS)
00359         {
00360             ASSERT(sizeof(menu->items[index].flags) == sizeof(int));
00361             item_flags = pgm_read_int(&menu->items[index].flags);
00362         }
00363         else
00364 #endif
00365             item_flags = menu->items[index].flags;
00366     }
00367     while (item_flags & MIF_HIDDEN);
00368 
00369     return index;
00370 }
00371 
00372 
00376 static int menu_prev_visible_item(const struct Menu *menu, int index)
00377 {
00378     int total = menu_count(menu);
00379     int item_flags;
00380 
00381     do
00382     {
00383         if (--index < 0)
00384             index = total - 1;
00385 
00386 #if CPU_HARVARD
00387         if (menu->flags & MF_ROMITEMS)
00388         {
00389             ASSERT(sizeof(menu->items[index].flags) == sizeof(int));
00390             item_flags = pgm_read_int(&menu->items[index].flags);
00391         }
00392         else
00393 #endif
00394             item_flags = menu->items[index].flags;
00395     }
00396     while (item_flags & MIF_HIDDEN);
00397 
00398     return index;
00399 }
00400 
00401 
00405 iptr_t menu_handle(const struct Menu *menu)
00406 {
00407     uint8_t items_per_page;
00408     uint8_t first_item = 0;
00409     uint8_t selected;
00410     iptr_t result = 0;
00411     bool redraw = true;
00412 
00413 #if (CONFIG_MENU_TIMEOUT != 0)
00414     ticks_t now, menu_idle_time = timer_clock();
00415 #endif
00416 
00417 #if CONFIG_MENU_MENUBAR
00418     struct MenuBar mb;
00419     const_iptr_t labels[] =
00420     {
00421         (const_iptr_t)LABEL_BACK,
00422         (const_iptr_t)LABEL_UPARROW,
00423         (const_iptr_t)LABEL_DOWNARROW,
00424         (const_iptr_t)0
00425     };
00426 
00427     /*
00428      * Initialize menu bar
00429      */
00430     if (menu->flags & MF_TOPLEVEL)
00431         labels[0] = (const_iptr_t)LABEL_EMPTY;
00432 
00433     mbar_init(&mb, menu->bitmap, labels, countof(labels));
00434 #endif /* CONFIG_MENU_MENUBAR */
00435 
00436 
00437     items_per_page =
00438         (menu->bitmap->height / menu->bitmap->font->height - 1)
00439 #if CONFIG_MENU_MENUBAR
00440         - 1 /* menu bar labels */
00441 #endif
00442         - (menu->title ? 1 : 0);
00443 
00444     /* Selected item should be a visible entry */
00445     //first_item = selected = menu_next_visible_item(menu, menu->selected - 1);
00446     selected = menu->selected;
00447     first_item = 0;
00448 
00449     for(;;)
00450     {
00451         keymask_t key;
00452 
00453         /*
00454          * Keep selected item visible
00455          */
00456         while (selected < first_item)
00457             first_item = menu_prev_visible_item(menu, first_item);
00458         while (selected >= first_item + items_per_page)
00459             first_item = menu_next_visible_item(menu, first_item);
00460 
00461         menu_layout(menu, first_item, selected, redraw);
00462         redraw = false;
00463 
00464         #if CONFIG_MENU_MENUBAR
00465             menu_update_menubar(menu, &mb, selected);
00466         #endif
00467 
00468         #if CONFIG_MENU_SMOOTH || (CONFIG_MENU_TIMEOUT != 0)
00469             key = kbd_peek();
00470         #else
00471             key = kbd_get();
00472         #endif
00473 
00474         #if (CONFIG_MENU_TIMEOUT != 0)
00475             /* Reset idle timer on key press. */
00476             now = timer_clock();
00477             if (key)
00478                 menu_idle_time = now;
00479         #endif
00480 
00481         if (key & K_OK)
00482         {
00483             struct MenuItem *item = &(menu->items[selected]);
00484 #if CPU_HARVARD
00485             MenuItem ram_item;
00486             if (menu->flags & MF_ROMITEMS)
00487             {
00488                 memcpy_P(&ram_item, item, sizeof(ram_item));
00489                 item = &ram_item;
00490             }
00491 #endif
00492             result = menu_doselect(menu, item);
00493             redraw = true;
00494 
00495             /* Return immediately */
00496             if (!(menu->flags & MF_STICKY))
00497                 break;
00498 
00499             #if (CONFIG_MENU_TIMEOUT != 0)
00500                 /* Chain timeout */
00501                 if ((result == MENU_TIMEOUT) && !(menu->flags & MF_TOPLEVEL))
00502                     break;
00503 
00504                 /* Reset timeout */
00505                 menu_idle_time = timer_clock();
00506             #endif
00507         }
00508         else if (key & K_UP)
00509         {
00510             selected = menu_prev_visible_item(menu, selected);
00511             redraw = true;
00512         }
00513         else if (key & K_DOWN)
00514         {
00515             selected = menu_next_visible_item(menu, selected);
00516             redraw = true;
00517         }
00518         else if (!(menu->flags & MF_TOPLEVEL))
00519         {
00520             if (key & K_CANCEL)
00521             {
00522                 result = MENU_CANCEL;
00523                 break;
00524             }
00525 
00526             #if CONFIG_MENU_TIMEOUT != 0
00527                 if (now - menu_idle_time > ms_to_ticks(CONFIG_MENU_TIMEOUT))
00528                 {
00529                     result = MENU_TIMEOUT;
00530                     break;
00531                 }
00532             #endif
00533         }
00534     }
00535 
00536     /* Store currently selected item before leaving. */
00537     if (menu->flags & MF_SAVESEL)
00538         #if (ARCH & ARCH_NIGHTTEST)
00539             #warning __FILTER_NEXT_WARNING__
00540         #endif
00541         CONST_CAST(struct Menu *, menu)->selected = selected;
00542 
00543     return result;
00544 }
00545 
00546 
00556 int menu_setFlags(struct Menu *menu, int idx, int flags)
00557 {
00558     ASSERT(idx < menu_count(menu));
00559     ASSERT(!(menu->flags & MF_ROMITEMS));
00560 
00561     int old = menu->items[idx].flags;
00562     menu->items[idx].flags |= flags;
00563     return old;
00564 }
00565 
00566 
00576 int menu_clearFlags(struct Menu *menu, int idx, int flags)
00577 {
00578     ASSERT(idx < menu_count(menu));
00579     ASSERT(!(menu->flags & MF_ROMITEMS));
00580 
00581     int old = menu->items[idx].flags;
00582     menu->items[idx].flags &= ~flags;
00583     return old;
00584 }