BeRTOS
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             #warning __FILTER_NEXT_WARNING__
00275             RenderHook renderhook = (item->flags & MIF_RENDERHOOK) ? (RenderHook)item->label : menu_defaultRenderHook;
00276 
00277             /* Render menuitem */
00278             renderhook(menu->bitmap, ypos++, (i == selected), item);
00279 
00280             ypos += bm->font->height;
00281         }
00282     }
00283 
00284 #if CONFIG_MENU_SMOOTH
00285     if (redraw)
00286     {
00287         /* Clear rest of area */
00288         gfx_rectClear(bm, bm->cr.xmin, ypos, bm->cr.xmax, bm->cr.ymax);
00289 
00290         menu->lcd_blitBitmap(bm);
00291     }
00292 
00293     /* Restore old cliprect */
00294     gfx_setClipRect(bm,
00295             bm->cr.xmin, old_ymin,
00296             bm->cr.xmax, bm->cr.ymax);
00297 
00298 #endif /* CONFIG_MENU_SMOOTH */
00299 }
00300 
00301 
00305 static iptr_t menu_doselect(const struct Menu *menu, struct MenuItem *item)
00306 {
00307     iptr_t result = 0;
00308 
00309     /* Exclude other items */
00310     int mask, i;
00311     for (mask = item->flags & MIF_EXCLUDE_MASK, i = 0; mask; mask >>= 1, ++i)
00312     {
00313         if (mask & 1)
00314             menu->items[i].flags &= ~MIF_CHECKED;
00315     }
00316 
00317     if (item->flags & MIF_DISABLED)
00318         return MENU_DISABLED;
00319 
00320     /* Handle checkable items */
00321     if (item->flags & MIF_TOGGLE)
00322         item->flags ^= MIF_CHECKED;
00323     else if (item->flags & MIF_CHECKIT)
00324         item->flags |= MIF_CHECKED;
00325 
00326     /* Handle items with callback hooks */
00327     if (item->hook)
00328     {
00329         /* Push a jmp buffer to abort the operation with the STOP/CANCEL key */
00330         if (!PUSH_ABORT)
00331         {
00332             result = item->hook(item->userdata);
00333             POP_ABORT;
00334         }
00335     }
00336     else
00337         result = item->userdata;
00338 
00339     return result;
00340 }
00341 
00342 
00346 static int menu_next_visible_item(const struct Menu *menu, int index)
00347 {
00348     int total = menu_count(menu);
00349     int item_flags;
00350 
00351     do
00352     {
00353         if (++index >= total)
00354            index = 0;
00355 
00356 #if CPU_HARVARD
00357         if (menu->flags & MF_ROMITEMS)
00358         {
00359             ASSERT(sizeof(menu->items[index].flags) == sizeof(int));
00360             item_flags = pgm_read_int(&menu->items[index].flags);
00361         }
00362         else
00363 #endif
00364             item_flags = menu->items[index].flags;
00365     }
00366     while (item_flags & MIF_HIDDEN);
00367 
00368     return index;
00369 }
00370 
00371 
00375 static int menu_prev_visible_item(const struct Menu *menu, int index)
00376 {
00377     int total = menu_count(menu);
00378     int item_flags;
00379 
00380     do
00381     {
00382         if (--index < 0)
00383             index = total - 1;
00384 
00385 #if CPU_HARVARD
00386         if (menu->flags & MF_ROMITEMS)
00387         {
00388             ASSERT(sizeof(menu->items[index].flags) == sizeof(int));
00389             item_flags = pgm_read_int(&menu->items[index].flags);
00390         }
00391         else
00392 #endif
00393             item_flags = menu->items[index].flags;
00394     }
00395     while (item_flags & MIF_HIDDEN);
00396 
00397     return index;
00398 }
00399 
00400 
00404 iptr_t menu_handle(const struct Menu *menu)
00405 {
00406     uint8_t items_per_page;
00407     uint8_t first_item = 0;
00408     uint8_t selected;
00409     iptr_t result = 0;
00410     bool redraw = true;
00411 
00412 #if (CONFIG_MENU_TIMEOUT != 0)
00413     ticks_t now, menu_idle_time = timer_clock();
00414 #endif
00415 
00416 #if CONFIG_MENU_MENUBAR
00417     struct MenuBar mb;
00418     const_iptr_t labels[] =
00419     {
00420         (const_iptr_t)LABEL_BACK,
00421         (const_iptr_t)LABEL_UPARROW,
00422         (const_iptr_t)LABEL_DOWNARROW,
00423         (const_iptr_t)0
00424     };
00425 
00426     /*
00427      * Initialize menu bar
00428      */
00429     if (menu->flags & MF_TOPLEVEL)
00430         labels[0] = (const_iptr_t)LABEL_EMPTY;
00431 
00432     mbar_init(&mb, menu->bitmap, labels, countof(labels));
00433 #endif /* CONFIG_MENU_MENUBAR */
00434 
00435 
00436     items_per_page =
00437         (menu->bitmap->height / menu->bitmap->font->height - 1)
00438 #if CONFIG_MENU_MENUBAR
00439         - 1 /* menu bar labels */
00440 #endif
00441         - (menu->title ? 1 : 0);
00442 
00443     /* Selected item should be a visible entry */
00444     //first_item = selected = menu_next_visible_item(menu, menu->selected - 1);
00445     selected = menu->selected;
00446     first_item = 0;
00447 
00448     for(;;)
00449     {
00450         keymask_t key;
00451 
00452         /*
00453          * Keep selected item visible
00454          */
00455         while (selected < first_item)
00456             first_item = menu_prev_visible_item(menu, first_item);
00457         while (selected >= first_item + items_per_page)
00458             first_item = menu_next_visible_item(menu, first_item);
00459 
00460         menu_layout(menu, first_item, selected, redraw);
00461         redraw = false;
00462 
00463         #if CONFIG_MENU_MENUBAR
00464             menu_update_menubar(menu, &mb, selected);
00465         #endif
00466 
00467         #if CONFIG_MENU_SMOOTH || (CONFIG_MENU_TIMEOUT != 0)
00468             key = kbd_peek();
00469             cpu_relax();
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         #warning __FILTER_NEXT_WARNING__
00539         CONST_CAST(struct Menu *, menu)->selected = selected;
00540 
00541     return result;
00542 }
00543 
00544 
00554 int menu_setFlags(struct Menu *menu, int idx, int flags)
00555 {
00556     ASSERT(idx < menu_count(menu));
00557     ASSERT(!(menu->flags & MF_ROMITEMS));
00558 
00559     int old = menu->items[idx].flags;
00560     menu->items[idx].flags |= flags;
00561     return old;
00562 }
00563 
00564 
00574 int menu_clearFlags(struct Menu *menu, int idx, int flags)
00575 {
00576     ASSERT(idx < menu_count(menu));
00577     ASSERT(!(menu->flags & MF_ROMITEMS));
00578 
00579     int old = menu->items[idx].flags;
00580     menu->items[idx].flags &= ~flags;
00581     return old;
00582 }