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>
00055
00056 #if CPU_HARVARD
00057 #include <avr/pgmspace.h>
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
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; ; ++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
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
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
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
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
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
00266
00267
00268 if (ypos > bm->cr.ymax)
00269 break;
00270
00271
00272 if (!(item->label || item->hook))
00273 break;
00274
00275
00276 if (!(item->flags & MIF_HIDDEN))
00277 {
00278
00279 RenderHook renderhook = (item->flags & MIF_RENDERHOOK) ? (RenderHook)item->label : menu_defaultRenderHook;
00280
00281
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
00292 gfx_rectClear(bm, bm->cr.xmin, ypos, bm->cr.xmax, bm->cr.ymax);
00293
00294 lcd_blitBitmap(&lcd_bitmap);
00295 }
00296
00297
00298 gfx_setClipRect(bm,
00299 bm->cr.xmin, old_ymin,
00300 bm->cr.xmax, bm->cr.ymax);
00301
00302 #endif
00303 }
00304
00305
00309 static iptr_t menu_doselect(const struct Menu *menu, struct MenuItem *item)
00310 {
00311 iptr_t result = 0;
00312
00313
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
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
00331 if (item->hook)
00332 {
00333
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
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
00438
00439
00440 items_per_page =
00441 (menu->bitmap->height / menu->bitmap->font->height)
00442 #if CONFIG_MENU_MENUBAR
00443 - 1
00444 #endif
00445 - (menu->title ? 1 : 0);
00446
00447
00448
00449 selected = menu->selected;
00450 first_item = 0;
00451
00452 for(;;)
00453 {
00454 keymask_t key;
00455
00456
00457
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
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
00499 if (!(menu->flags & MF_STICKY))
00500 break;
00501
00502 #if (CONFIG_MENU_TIMEOUT != 0)
00503
00504 if ((result == MENU_TIMEOUT) && !(menu->flags & MF_TOPLEVEL))
00505 break;
00506
00507
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
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 }