Added support for DE200C VFD
[lcdproc-de200c.git] / server / menu.c
blob0f99f18f81f24e9429cd2d4039e3366bcd04be66
1 /*
2 * menu.c
3 * This file is part of LCDd, the lcdproc server.
5 * This file is released under the GNU General Public License. Refer to the
6 * COPYING file distributed with this package.
8 * Copyright (c) 1999, William Ferrell, Scott Scriven
9 * 2002, Joris Robijn
10 * 2004, F5 Networks, Inc. - IP-address input
11 * 2005, Peter Marschall - error checks, ...
13 * Handles a menu and all actions that can be performed on it. Note that a
14 * menu is itself also a menuitem.
16 * Menus are similar to "pull-down" menus, but have some extra features.
17 * They can contain "normal" menu items, checkboxes, sliders, "movers",
18 * etc..
20 * The servermenu is created from servermenu.c
22 * For separation this file should never need to include menuscreen.h.
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <unistd.h>
30 #include <assert.h>
32 #include "config.h"
34 #include "menuitem.h"
35 #include "menu.h"
36 #include "shared/report.h"
37 #include "drivers.h"
39 #include "screen.h"
40 #include "widget.h"
43 extern Menu *custom_main_menu;
45 /** Basicly a patched version of LL_GetByIndex() that ignores hidden
46 * entries completely. (But it takes a menu as an argument.) */
47 static void *
48 menu_get_subitem(Menu *menu, int index)
50 MenuItem *item;
51 int i = 0;
53 debug(RPT_DEBUG, "%s(menu=[%s], index=%d)", __FUNCTION__,
54 ((menu != NULL) ? menu->id : "(null)"), index);
55 for (item = LL_GetFirst(menu->data.menu.contents);
56 item != NULL;
57 item = LL_GetNext(menu->data.menu.contents))
59 if (! item->is_hidden)
61 if (i == index)
62 return item;
63 /* hidden items don't count at all... */
64 ++i;
67 return NULL;
70 /**
71 * Searches for a subitem with id item_id. This function ignores hidden
72 * entries completely.
74 * @return index of subitem if found and -1 otherwise. */
75 static int
76 menu_get_index_of(Menu *menu, char *item_id)
78 MenuItem *item;
79 int i = 0;
81 debug(RPT_DEBUG, "%s(menu=[%s], item_id=%s)", __FUNCTION__,
82 ((menu != NULL) ? menu->id : "(null)"), item_id);
83 for (item = LL_GetFirst(menu->data.menu.contents);
84 item != NULL;
85 item = LL_GetNext(menu->data.menu.contents))
87 if (! item->is_hidden)
89 if (strcmp(item_id, item->id) == 0)
90 return i;
91 /* hidden items don't count at all... */
92 ++i;
95 return -1;
98 static int
99 menu_visible_item_count(Menu *menu)
101 MenuItem *item;
102 int i = 0;
104 for (item = LL_GetFirst(menu->data.menu.contents);
105 item != NULL;
106 item = LL_GetNext(menu->data.menu.contents))
108 if (! item->is_hidden)
109 ++i;
111 return i;
115 Menu *
116 menu_create(char *id, MenuEventFunc(*event_func),
117 char *text, Client *client)
119 Menu *new_menu;
121 debug(RPT_DEBUG, "%s(id=\"%s\", event_func=%p, text=\"%s\", client=%p)",
122 __FUNCTION__, id, event_func, text, client);
124 new_menu = menuitem_create(MENUITEM_MENU, id, event_func, text, client);
126 if (new_menu != NULL) {
127 new_menu->data.menu.contents = LL_new();
128 new_menu->data.menu.association = NULL;
131 return new_menu;
134 void
135 menu_destroy(Menu *menu)
137 debug(RPT_DEBUG, "%s(menu=[%s])", __FUNCTION__,
138 ((menu != NULL) ? menu->id : "(null)"));
140 if (menu == NULL)
141 return;
143 if (custom_main_menu == menu)
144 custom_main_menu = NULL;
146 menu_destroy_all_items(menu);
147 LL_Destroy(menu->data.menu.contents);
148 menu->data.menu.contents = NULL;
150 /* After this the general menuitem routine destroys the rest... */
153 void
154 menu_add_item(Menu *menu, MenuItem *item)
156 debug(RPT_DEBUG, "%s(menu=[%s], item=[%s])", __FUNCTION__,
157 ((menu != NULL) ? menu->id : "(null)"),
158 ((item != NULL) ? item->id : "(null)"));
160 if ((menu == NULL) || (item == NULL))
161 return;
163 /* Add the item to the menu */
164 LL_Push(menu->data.menu.contents, item);
165 item->parent = menu;
168 void
169 menu_remove_item(Menu *menu, MenuItem *item)
171 int i;
172 MenuItem *item2;
174 debug(RPT_DEBUG, "%s(menu=[%s], item=[%s])", __FUNCTION__,
175 ((menu != NULL) ? menu->id : "(null)"),
176 ((item != NULL) ? item->id : "(null)"));
178 if ((menu == NULL) || (item == NULL))
179 return;
181 /* Find the item */
182 for (item2 = LL_GetFirst(menu->data.menu.contents), i = 0;
183 item2 != NULL;
184 item2 = LL_GetNext(menu->data.menu.contents), i++) {
185 if (item == item2) {
186 LL_DeleteNode(menu->data.menu.contents);
187 if (menu->data.menu.selector_pos >= i) {
188 menu->data.menu.selector_pos--;
189 if (menu->data.menu.scroll > 0)
190 menu->data.menu.scroll--;
192 return;
197 void
198 menu_destroy_all_items(Menu *menu)
200 MenuItem *item;
202 debug(RPT_DEBUG, "%s(menu=[%s])", __FUNCTION__,
203 ((menu != NULL) ? menu->id : "(null)"));
205 if (menu == NULL)
206 return;
208 for (item = menu_getfirst_item(menu); item != NULL; item = menu_getfirst_item(menu)) {
209 menuitem_destroy(item);
210 LL_Remove(menu->data.menu.contents, item);
214 MenuItem *menu_get_current_item(Menu *menu)
216 return (MenuItem*) ((menu != NULL)
217 ? menu_get_subitem(menu, menu->data.menu.selector_pos)
218 : NULL);
221 MenuItem *menu_find_item(Menu *menu, char *id, bool recursive)
223 MenuItem *item;
225 debug(RPT_DEBUG, "%s(menu=[%s], id=\"%s\", recursive=%d)", __FUNCTION__,
226 ((menu != NULL) ? menu->id : "(null)"), id, recursive);
228 if ((menu == NULL) || (id == NULL))
229 return NULL;
230 if (strcmp(menu->id, id) == 0)
231 return menu;
233 for (item = menu_getfirst_item(menu); item != NULL; item = menu_getnext_item(menu)) {
234 if (strcmp(item->id, id) == 0) {
235 return item;
237 else if (recursive && item->type == MENUITEM_MENU) {
238 MenuItem *res;
239 res = menu_find_item(item, id, recursive);
240 if (res) {
241 return res;
245 return NULL;
248 void menu_set_association(Menu *menu, void *assoc)
250 menu->data.menu.association = assoc;
253 void menu_reset(Menu *menu)
255 debug(RPT_DEBUG, "%s(menu=[%s])", __FUNCTION__,
256 ((menu != NULL) ? menu->id : "(null)"));
258 if (menu == NULL)
259 return;
261 menu->data.menu.selector_pos = 0;
262 menu->data.menu.scroll = 0;
265 void menu_build_screen(MenuItem *menu, Screen *s)
267 Widget *w;
268 MenuItem *subitem;
269 int itemnr;
271 debug(RPT_DEBUG, "%s(menu=[%s], screen=[%s])", __FUNCTION__,
272 ((menu != NULL) ? menu->id : "(null)"),
273 ((s != NULL) ? s->id : "(null)"));
275 if ((menu == NULL) || (s == NULL))
276 return;
278 /* TODO: Put menu in a frame to do easy scrolling */
279 /* Problem: frames are not handled correctly by renderer */
281 /* Create menu title widget */
282 w = widget_create("title", WID_TITLE, s);
283 if (w != NULL) {
284 screen_add_widget(s, w);
285 w->text = strdup(menu->text);
286 w->x = 1;
289 /* Create widgets for each subitem in the menu */
290 for (subitem = LL_GetFirst(menu->data.menu.contents), itemnr = 0;
291 subitem != NULL;
292 subitem = LL_GetNext(menu->data.menu.contents), itemnr ++)
294 char buf[10];
296 if (subitem->is_hidden)
297 continue;
298 snprintf(buf, sizeof(buf)-1, "text%d", itemnr);
299 buf[sizeof(buf)-1] = 0;
300 w = widget_create(buf, WID_STRING, s);
301 /* (buf will be copied) */
302 if (w != NULL) {
303 screen_add_widget(s, w);
304 w->x = 2;
306 switch (subitem->type) {
307 case MENUITEM_CHECKBOX:
309 /* Limit string length */
310 w->text = strdup(subitem->text);
311 if (strlen(subitem->text) >= display_props->width-2) {
312 (w->text)[display_props->width-2] = 0;
315 /* Add icon for checkbox */
316 snprintf(buf, sizeof(buf)-1, "icon%d", itemnr);
317 buf[sizeof(buf)-1] = 0;
318 w = widget_create(buf, WID_ICON, s);
319 /* (buf will be copied) */
320 screen_add_widget(s, w);
321 w->x = display_props->width - 1;
322 w->length = ICON_CHECKBOX_OFF;
323 break;
324 case MENUITEM_RING:
325 /* Create string for text + ringtext */
326 w->text = malloc(display_props->width);
327 break;
328 case MENUITEM_MENU:
329 /* Limit string length */
330 w->text = malloc(strlen(subitem->text) + 4);
331 strcpy(w->text, subitem->text);
332 strcat(w->text, " >");
333 if (strlen(subitem->text) >= display_props->width-1) {
334 (w->text)[display_props->width-1] = '\0';
336 break;
337 case MENUITEM_ACTION:
338 case MENUITEM_SLIDER:
339 case MENUITEM_NUMERIC:
340 case MENUITEM_ALPHA:
341 case MENUITEM_IP:
342 /* Limit string length */
343 w->text = strdup(subitem->text);
344 if (strlen(subitem->text) >= display_props->width-1) {
345 (w->text)[display_props->width-1] = '\0';
347 break;
348 default:
349 assert(!"unexpected menuitem type");
354 /* Add arrow for selection on the left */
355 w = widget_create("selector", WID_ICON, s);
356 if (w != NULL) {
357 screen_add_widget(s, w);
358 w->length = ICON_SELECTOR_AT_LEFT;
359 w->x = 1;
362 /* Add scrollers on the right side on top and bottom */
363 /* TODO: when menu is in a frame, these can be removed */
364 w = widget_create("upscroller", WID_ICON, s);
365 if (w != NULL) {
366 screen_add_widget(s, w);
367 w->length = ICON_ARROW_UP;
368 w->x = display_props->width;
369 w->y = 1;
372 w = widget_create("downscroller", WID_ICON, s);
373 if (w != NULL) {
374 screen_add_widget(s, w);
375 w->length = ICON_ARROW_DOWN;
376 w->x = display_props->width;
377 w->y = display_props->height;
382 void menu_update_screen(MenuItem *menu, Screen *s)
384 Widget *w;
385 MenuItem *subitem;
386 int itemnr;
387 int hidden_count = 0;
389 debug(RPT_DEBUG, "%s(menu=[%s], screen=[%s])", __FUNCTION__,
390 ((menu != NULL) ? menu->id : "(null)"),
391 ((s != NULL) ? s->id : "(null)"));
393 if ((menu == NULL) || (s == NULL))
394 return;
396 /* Update widgets for the title */
397 w = screen_find_widget(s, "title");
398 if (!w) report(RPT_ERR, "%s: could not find widget: %s", __FUNCTION__, "title");
399 w->y = 1 - menu->data.menu.scroll;
401 /* TODO: remove next 5 limes when rendering is safe */
402 if (w->y > 0 && w->y <= display_props->height) {
403 w->type = WID_TITLE;
404 } else {
405 w->type = WID_NONE; /* make invisible */
408 /* Update widgets for each subitem in the menu */
409 for (subitem = LL_GetFirst(menu->data.menu.contents), itemnr = 0;
410 subitem;
411 subitem = LL_GetNext(menu->data.menu.contents), itemnr ++)
413 char buf[10];
414 char *p;
416 if (subitem->is_hidden)
418 debug(RPT_DEBUG, "%s: menu %s has hidden menu: %s",
419 __FUNCTION__, menu->id, subitem->id);
420 ++hidden_count;
421 continue;
423 snprintf(buf, sizeof(buf)-1, "text%d", itemnr);
424 buf[sizeof(buf)-1] = 0;
425 w = screen_find_widget(s, buf);
426 if (!w) report(RPT_ERR, "%s: could not find widget: %s", __FUNCTION__, buf);
427 w->y = 2 + itemnr - hidden_count - menu->data.menu.scroll;
429 /* TODO: remove next 5 lines when rendering is safe */
430 if (w->y > 0 && w->y <= display_props->height) {
431 w->type = WID_STRING;
432 } else {
433 w->type = WID_NONE; /* make invisible */
436 switch (subitem->type) {
437 case MENUITEM_CHECKBOX:
439 /* Update icon value for checkbox */
440 snprintf(buf, sizeof(buf)-1, "icon%d", itemnr);
441 buf[sizeof(buf)-1] = 0;
442 w = screen_find_widget(s, buf);
443 if (!w) report(RPT_ERR, "%s: could not find widget: %s", __FUNCTION__, buf);
444 w->y = 2 + itemnr - menu->data.menu.scroll;
445 w->length = ((int[]){ICON_CHECKBOX_OFF,ICON_CHECKBOX_ON,ICON_CHECKBOX_GRAY})[subitem->data.checkbox.value];
447 /* TODO: remove next 5 lines when rendering is safe */
448 if (w->y > 0 && w->y <= display_props->height) {
449 w->type = WID_ICON;
450 } else {
451 w->type = WID_NONE; /* make invisible */
453 break;
454 case MENUITEM_RING:
455 if (subitem->data.ring.value >= LL_Length(subitem->data.ring.strings)) {
456 /* No strings available */
457 memcpy(w->text, subitem->text, display_props->width - 2);
458 w->text[ display_props->width - 2 ] = 0;
460 else {
461 /* Limit string length and add ringstring */
462 p = LL_GetByIndex(subitem->data.ring.strings, subitem->data.ring.value);
463 assert(p != NULL);
464 if (strlen(p) > display_props->width - 3) {
465 short a = display_props->width - 3;
466 /* We need to limit the ring string and DON'T
467 * display the item text */
468 strcpy(w->text, " ");
469 memcpy(w->text + 1, p, a);
470 w->text[a + 1] = 0;
472 else {
473 short b = display_props->width - 2 - strlen(p);
474 short c = min(strlen(subitem->text), b - 1);
475 /* We don't limit the ring string */
476 memset(w->text, ' ', b);
477 memcpy(w->text, subitem->text, c);
478 strcpy(w->text + b, p);
481 break;
482 default:
483 break;
487 /* Update selector position */
488 w = screen_find_widget(s, "selector");
489 if (w != NULL)
490 w->y = 2 + menu->data.menu.selector_pos - menu->data.menu.scroll;
491 else
492 report(RPT_ERR, "%s: could not find widget: %s", __FUNCTION__, "selector");
494 /* Enable upscroller (if necessary) */
495 w = screen_find_widget(s, "upscroller");
496 if (w != NULL)
497 w->type = (menu->data.menu.scroll > 0) ? WID_ICON : WID_NONE;
498 else
499 report(RPT_ERR, "%s: could not find widget: %s", __FUNCTION__, "upscroller");
501 /* Enable downscroller (if necessary) */
502 w = screen_find_widget(s, "downscroller");
503 if (w != NULL)
504 w->type = (menu_visible_item_count(menu) >= menu->data.menu.scroll + display_props->height)
505 ? WID_ICON : WID_NONE;
506 else
507 report(RPT_ERR, "%s: could not find widget: %s", __FUNCTION__, "downscroller");
510 MenuItem *menu_get_item_for_predecessor_check(Menu *menu)
512 MenuItem *subitem = menu_get_subitem(menu, menu->data.menu.selector_pos);
513 if (! subitem)
514 return NULL;
515 switch (subitem->type) {
516 case MENUITEM_ACTION:
517 case MENUITEM_CHECKBOX:
518 case MENUITEM_RING:
519 // for types without own screen: look for menu's
520 // predecessor if its subitem doesn't have one. (Since
521 // menus can't have successors this problem arises
522 // only for predecessors.)
523 if (subitem->predecessor_id == NULL)
524 return menu;
525 return subitem;
526 case MENUITEM_MENU:
527 case MENUITEM_SLIDER:
528 case MENUITEM_NUMERIC:
529 case MENUITEM_ALPHA:
530 case MENUITEM_IP:
531 return menu;
532 default:
533 return NULL;
537 MenuItem *menu_get_item_for_successor_check(Menu *menu)
539 MenuItem *subitem = menu_get_subitem(menu, menu->data.menu.selector_pos);
540 if (! subitem)
541 return NULL;
542 switch (subitem->type) {
543 case MENUITEM_ACTION:
544 case MENUITEM_CHECKBOX:
545 case MENUITEM_RING:
546 return subitem;
547 case MENUITEM_MENU:
548 case MENUITEM_SLIDER:
549 case MENUITEM_NUMERIC:
550 case MENUITEM_ALPHA:
551 case MENUITEM_IP:
552 return menu;
553 default:
554 return NULL;
558 MenuResult menu_process_input(Menu *menu, MenuToken token, const char *key, bool extended)
560 MenuItem *subitem;
561 debug(RPT_DEBUG, "%s(menu=[%s], token=%d, key=\"%s\")", __FUNCTION__,
562 ((menu != NULL) ? menu->id : "(null)"), token, key);
564 if (menu == NULL)
565 return MENURESULT_ERROR;
567 switch (token) {
568 case MENUTOKEN_MENU:
569 subitem = menu_get_item_for_predecessor_check(menu);
570 if (! subitem)
571 return MENURESULT_ERROR;
572 return menuitem_predecessor2menuresult(
573 subitem->predecessor_id, MENURESULT_CLOSE);
574 case MENUTOKEN_ENTER:
575 subitem = menu_get_subitem(menu, menu->data.menu.selector_pos);
576 if (!subitem)
577 break;
578 switch (subitem->type) {
579 case MENUITEM_ACTION:
580 if (subitem->event_func)
581 subitem->event_func(subitem, MENUEVENT_SELECT);
582 return menuitem_successor2menuresult(
583 subitem->successor_id, MENURESULT_NONE);
584 case MENUITEM_CHECKBOX:
585 if (subitem->data.checkbox.allow_gray) {
586 subitem->data.checkbox.value = (subitem->data.checkbox.value + 1) % 3;
588 else {
589 subitem->data.checkbox.value = (subitem->data.checkbox.value + 1) % 2;
591 if (subitem->event_func)
592 subitem->event_func(subitem, MENUEVENT_UPDATE);
593 return menuitem_successor2menuresult(
594 subitem->successor_id, MENURESULT_NONE);
595 case MENUITEM_RING:
596 subitem->data.ring.value = (subitem->data.ring.value + 1) % LL_Length(subitem->data.ring.strings);
597 if (subitem->event_func)
598 subitem->event_func(subitem, MENUEVENT_UPDATE);
599 return menuitem_successor2menuresult(
600 subitem->successor_id, MENURESULT_NONE);
601 case MENUITEM_MENU:
602 case MENUITEM_SLIDER:
603 case MENUITEM_NUMERIC:
604 case MENUITEM_ALPHA:
605 case MENUITEM_IP:
606 return MENURESULT_ENTER;
607 default:
608 break;
610 return MENURESULT_ERROR;
611 case MENUTOKEN_UP:
612 if (menu->data.menu.selector_pos > 0) {
613 menu->data.menu.selector_pos --;
614 if (menu->data.menu.selector_pos + 1 < menu->data.menu.scroll)
615 menu->data.menu.scroll --;
617 else if (menu->data.menu.selector_pos == 0) {
618 // wrap around to last menu entry
619 menu->data.menu.selector_pos = menu_visible_item_count(menu) - 1;
620 menu->data.menu.scroll = menu->data.menu.selector_pos + 2 - display_props->height;
622 return MENURESULT_NONE;
623 case MENUTOKEN_DOWN:
624 if (menu->data.menu.selector_pos < menu_visible_item_count(menu) - 1) {
625 menu->data.menu.selector_pos ++;
626 if (menu->data.menu.selector_pos - menu->data.menu.scroll + 2 > display_props->height)
627 menu->data.menu.scroll ++;
629 else {
630 // wrap araound to 1st menu entry
631 menu->data.menu.selector_pos = 0;
632 menu->data.menu.scroll = 0;
634 return MENURESULT_NONE;
635 case MENUTOKEN_LEFT:
636 if (!extended)
637 return MENURESULT_NONE;
639 subitem = menu_get_subitem(menu, menu->data.menu.selector_pos);
640 if (subitem == NULL)
641 break;
642 switch (subitem->type) {
643 case MENUITEM_CHECKBOX:
644 /* note: this dangerous looking code works since
645 * CheckboxValue is an enum >= 0. */
646 if (subitem->data.checkbox.allow_gray) {
647 subitem->data.checkbox.value = (subitem->data.checkbox.value - 1) % 3;
649 else {
650 subitem->data.checkbox.value = (subitem->data.checkbox.value - 1) % 2;
652 if (subitem->event_func)
653 subitem->event_func(subitem, MENUEVENT_UPDATE);
654 return MENURESULT_NONE;
655 case MENUITEM_RING:
656 /* ring: jump to the end if beginning is reached */
657 subitem->data.ring.value = (subitem->data.ring.value < 1)
658 ? LL_Length(subitem->data.ring.strings) - 1
659 : (subitem->data.ring.value - 1) % LL_Length(subitem->data.ring.strings);
660 if (subitem->event_func)
661 subitem->event_func(subitem, MENUEVENT_UPDATE);
662 return MENURESULT_NONE;
663 default:
664 break;
666 return MENURESULT_NONE;
667 case MENUTOKEN_RIGHT:
668 if (!extended)
669 return MENURESULT_NONE;
671 subitem = menu_get_subitem(menu, menu->data.menu.selector_pos);
672 if (subitem == NULL)
673 break;
674 switch (subitem->type) {
675 case MENUITEM_CHECKBOX:
676 if (subitem->data.checkbox.allow_gray) {
677 subitem->data.checkbox.value = (subitem->data.checkbox.value + 1) % 3;
679 else {
680 subitem->data.checkbox.value = (subitem->data.checkbox.value + 1) % 2;
682 if (subitem->event_func)
683 subitem->event_func(subitem, MENUEVENT_UPDATE);
684 return MENURESULT_NONE;
685 case MENUITEM_RING:
686 subitem->data.ring.value = (subitem->data.ring.value + 1) % LL_Length(subitem->data.ring.strings);
687 if (subitem->event_func)
688 subitem->event_func(subitem, MENUEVENT_UPDATE);
689 return MENURESULT_NONE;
690 case MENUITEM_MENU:
691 return MENURESULT_ENTER;
692 default:
693 break;
695 return MENURESULT_NONE;
696 case MENUTOKEN_OTHER:
697 /* TODO: move to the selected number and enter it */
698 return MENURESULT_NONE;
700 return MENURESULT_ERROR;
703 /** positions current item pointer on subitem subitem_id. If subitem_id is
704 * hidden or not valid subitem of menu this function does nothing. */
705 void menu_select_subitem(Menu *menu, char *subitem_id)
707 assert(menu != NULL);
708 debug(RPT_DEBUG, "%s(menu=[%s], subitem_id=\"%s\")", __FUNCTION__,
709 menu->id, subitem_id);
710 int position = menu_get_index_of(menu, subitem_id);
711 if (position < 0)
713 debug(RPT_DEBUG, "%s: subitem \"%s\" not found"
714 " or hidden in \"%s\", ignored",
715 __FUNCTION__, subitem_id, menu->id);
716 return;
718 // debug(RPT_DEBUG, "%s: %s->%s is at position %d,"
719 // " current item is at menu position: %d, scroll: %d",
720 // __FUNCTION__, menu->id, subitem_id, position,
721 // menu->data.menu.selector_pos, menu->data.menu.scroll);
722 menu->data.menu.selector_pos = position;
723 menu->data.menu.scroll = position;