menu.c

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