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>
00056
00057 #if CPU_HARVARD
00058 #include <avr/pgmspace.h>
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
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; ; ++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
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
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
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
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
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
00267
00268
00269 if (ypos > bm->cr.ymax)
00270 break;
00271
00272
00273 if (!(item->label || item->hook))
00274 break;
00275
00276
00277 if (!(item->flags & MIF_HIDDEN))
00278 {
00279
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
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
00296 gfx_rectClear(bm, bm->cr.xmin, ypos, bm->cr.xmax, bm->cr.ymax);
00297
00298 lcd_blitBitmap(&lcd_bitmap);
00299 }
00300
00301
00302 gfx_setClipRect(bm,
00303 bm->cr.xmin, old_ymin,
00304 bm->cr.xmax, bm->cr.ymax);
00305
00306 #endif
00307 }
00308
00309
00313 static iptr_t menu_doselect(const struct Menu *menu, struct MenuItem *item)
00314 {
00315 iptr_t result = 0;
00316
00317
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
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
00335 if (item->hook)
00336 {
00337
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
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
00442
00443
00444 items_per_page =
00445 (menu->bitmap->height / menu->bitmap->font->height)
00446 #if CONFIG_MENU_MENUBAR
00447 - 1
00448 #endif
00449 - (menu->title ? 1 : 0);
00450
00451
00452
00453 selected = menu->selected;
00454 first_item = 0;
00455
00456 for(;;)
00457 {
00458 keymask_t key;
00459
00460
00461
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
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
00503 if (!(menu->flags & MF_STICKY))
00504 break;
00505
00506 #if (CONFIG_MENU_TIMEOUT != 0)
00507
00508 if ((result == MENU_TIMEOUT) && !(menu->flags & MF_TOPLEVEL))
00509 break;
00510
00511
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
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 }