menu.c

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