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
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",
20 * The servermenu is created from servermenu.c
22 * For separation this file should never need to include menuscreen.h.
36 #include "shared/report.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.) */
48 menu_get_subitem(Menu
*menu
, int index
)
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
);
57 item
= LL_GetNext(menu
->data
.menu
.contents
))
59 if (! item
->is_hidden
)
63 /* hidden items don't count at all... */
71 * Searches for a subitem with id item_id. This function ignores hidden
74 * @return index of subitem if found and -1 otherwise. */
76 menu_get_index_of(Menu
*menu
, char *item_id
)
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
);
85 item
= LL_GetNext(menu
->data
.menu
.contents
))
87 if (! item
->is_hidden
)
89 if (strcmp(item_id
, item
->id
) == 0)
91 /* hidden items don't count at all... */
99 menu_visible_item_count(Menu
*menu
)
104 for (item
= LL_GetFirst(menu
->data
.menu
.contents
);
106 item
= LL_GetNext(menu
->data
.menu
.contents
))
108 if (! item
->is_hidden
)
116 menu_create(char *id
, MenuEventFunc(*event_func
),
117 char *text
, Client
*client
)
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
;
135 menu_destroy(Menu
*menu
)
137 debug(RPT_DEBUG
, "%s(menu=[%s])", __FUNCTION__
,
138 ((menu
!= NULL
) ? menu
->id
: "(null)"));
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... */
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
))
163 /* Add the item to the menu */
164 LL_Push(menu
->data
.menu
.contents
, item
);
169 menu_remove_item(Menu
*menu
, MenuItem
*item
)
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
))
182 for (item2
= LL_GetFirst(menu
->data
.menu
.contents
), i
= 0;
184 item2
= LL_GetNext(menu
->data
.menu
.contents
), i
++) {
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
--;
198 menu_destroy_all_items(Menu
*menu
)
202 debug(RPT_DEBUG
, "%s(menu=[%s])", __FUNCTION__
,
203 ((menu
!= NULL
) ? menu
->id
: "(null)"));
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
)
221 MenuItem
*menu_find_item(Menu
*menu
, char *id
, bool recursive
)
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
))
230 if (strcmp(menu
->id
, id
) == 0)
233 for (item
= menu_getfirst_item(menu
); item
!= NULL
; item
= menu_getnext_item(menu
)) {
234 if (strcmp(item
->id
, id
) == 0) {
237 else if (recursive
&& item
->type
== MENUITEM_MENU
) {
239 res
= menu_find_item(item
, id
, recursive
);
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)"));
261 menu
->data
.menu
.selector_pos
= 0;
262 menu
->data
.menu
.scroll
= 0;
265 void menu_build_screen(MenuItem
*menu
, Screen
*s
)
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
))
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
);
284 screen_add_widget(s
, w
);
285 w
->text
= strdup(menu
->text
);
289 /* Create widgets for each subitem in the menu */
290 for (subitem
= LL_GetFirst(menu
->data
.menu
.contents
), itemnr
= 0;
292 subitem
= LL_GetNext(menu
->data
.menu
.contents
), itemnr
++)
296 if (subitem
->is_hidden
)
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) */
303 screen_add_widget(s
, w
);
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
;
325 /* Create string for text + ringtext */
326 w
->text
= malloc(display_props
->width
);
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';
337 case MENUITEM_ACTION
:
338 case MENUITEM_SLIDER
:
339 case MENUITEM_NUMERIC
:
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';
349 assert(!"unexpected menuitem type");
354 /* Add arrow for selection on the left */
355 w
= widget_create("selector", WID_ICON
, s
);
357 screen_add_widget(s
, w
);
358 w
->length
= ICON_SELECTOR_AT_LEFT
;
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
);
366 screen_add_widget(s
, w
);
367 w
->length
= ICON_ARROW_UP
;
368 w
->x
= display_props
->width
;
372 w
= widget_create("downscroller", WID_ICON
, s
);
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
)
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
))
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
) {
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;
411 subitem
= LL_GetNext(menu
->data
.menu
.contents
), itemnr
++)
416 if (subitem
->is_hidden
)
418 debug(RPT_DEBUG
, "%s: menu %s has hidden menu: %s",
419 __FUNCTION__
, menu
->id
, subitem
->id
);
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
;
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
) {
451 w
->type
= WID_NONE
; /* make invisible */
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;
461 /* Limit string length and add ringstring */
462 p
= LL_GetByIndex(subitem
->data
.ring
.strings
, subitem
->data
.ring
.value
);
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
);
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
);
487 /* Update selector position */
488 w
= screen_find_widget(s
, "selector");
490 w
->y
= 2 + menu
->data
.menu
.selector_pos
- menu
->data
.menu
.scroll
;
492 report(RPT_ERR
, "%s: could not find widget: %s", __FUNCTION__
, "selector");
494 /* Enable upscroller (if necessary) */
495 w
= screen_find_widget(s
, "upscroller");
497 w
->type
= (menu
->data
.menu
.scroll
> 0) ? WID_ICON
: WID_NONE
;
499 report(RPT_ERR
, "%s: could not find widget: %s", __FUNCTION__
, "upscroller");
501 /* Enable downscroller (if necessary) */
502 w
= screen_find_widget(s
, "downscroller");
504 w
->type
= (menu_visible_item_count(menu
) >= menu
->data
.menu
.scroll
+ display_props
->height
)
505 ? WID_ICON
: WID_NONE
;
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
);
515 switch (subitem
->type
) {
516 case MENUITEM_ACTION
:
517 case MENUITEM_CHECKBOX
:
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
)
527 case MENUITEM_SLIDER
:
528 case MENUITEM_NUMERIC
:
537 MenuItem
*menu_get_item_for_successor_check(Menu
*menu
)
539 MenuItem
*subitem
= menu_get_subitem(menu
, menu
->data
.menu
.selector_pos
);
542 switch (subitem
->type
) {
543 case MENUITEM_ACTION
:
544 case MENUITEM_CHECKBOX
:
548 case MENUITEM_SLIDER
:
549 case MENUITEM_NUMERIC
:
558 MenuResult
menu_process_input(Menu
*menu
, MenuToken token
, const char *key
, bool extended
)
561 debug(RPT_DEBUG
, "%s(menu=[%s], token=%d, key=\"%s\")", __FUNCTION__
,
562 ((menu
!= NULL
) ? menu
->id
: "(null)"), token
, key
);
565 return MENURESULT_ERROR
;
569 subitem
= menu_get_item_for_predecessor_check(menu
);
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
);
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;
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
);
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
);
602 case MENUITEM_SLIDER
:
603 case MENUITEM_NUMERIC
:
606 return MENURESULT_ENTER
;
610 return MENURESULT_ERROR
;
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
;
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
++;
630 // wrap araound to 1st menu entry
631 menu
->data
.menu
.selector_pos
= 0;
632 menu
->data
.menu
.scroll
= 0;
634 return MENURESULT_NONE
;
637 return MENURESULT_NONE
;
639 subitem
= menu_get_subitem(menu
, menu
->data
.menu
.selector_pos
);
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;
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
;
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
;
666 return MENURESULT_NONE
;
667 case MENUTOKEN_RIGHT
:
669 return MENURESULT_NONE
;
671 subitem
= menu_get_subitem(menu
, menu
->data
.menu
.selector_pos
);
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;
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
;
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
;
691 return MENURESULT_ENTER
;
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
);
713 debug(RPT_DEBUG
, "%s: subitem \"%s\" not found"
714 " or hidden in \"%s\", ignored",
715 __FUNCTION__
, subitem_id
, menu
->id
);
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
;