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