2 * $Id: menubox.c,v 1.145 2012/12/30 21:11:02 tom Exp $
4 * menubox.c -- implements the menu box
6 * Copyright 2000-2011,2012 Thomas E. Dickey
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU Lesser General Public Licens, version 2.1e
10 * as published by the Free Software Foundation.
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this program; if not, write to
19 * Free Software Foundation, Inc.
20 * 51 Franklin St., Fifth Floor
21 * Boston, MA 02110, USA.
23 * An earlier version of this program lists as authors
24 * Savio Lam (lam836@cs.cuhk.hk)
37 /* the outer-window */
45 /* the inner-window */
47 DIALOG_LISTITEM
*items
;
51 #define MIN_HIGH (1 + (5 * MARGIN))
53 #define INPUT_ROWS 3 /* rows per inputmenu entry */
55 #define RowHeight(i) (is_inputmenu ? ((i) * INPUT_ROWS) : ((i) * 1))
56 #define ItemToRow(i) (is_inputmenu ? ((i) * INPUT_ROWS + 1) : (i))
57 #define RowToItem(i) (is_inputmenu ? ((i) / INPUT_ROWS + 0) : (i))
63 print_item(ALL_DATA
* data
,
65 DIALOG_LISTITEM
* item
,
70 chtype save
= dlg_get_attrs(win
);
72 int climit
= (data
->item_x
- data
->tag_x
- GUTTER
);
73 int my_width
= data
->menu_width
;
74 int my_x
= data
->item_x
;
75 int my_y
= ItemToRow(choice
);
76 bool both
= (!dialog_vars
.no_tags
&& !dialog_vars
.no_items
);
79 const char *show
= (dialog_vars
.no_items
89 bordchar
= item_selected_attr
;
92 bordchar
= dialog_attr
;
96 /* Clear 'residue' of last item and mark current current item */
98 (void) wattrset(win
, (selected
!= Unselected
) ? item_selected_attr
: item_attr
);
99 for (n
= my_y
- 1; n
< my_y
+ INPUT_ROWS
- 1; n
++) {
101 wprintw(win
, "%*s", my_width
, " ");
104 (void) wattrset(win
, menubox_attr
);
106 wprintw(win
, "%*s", my_width
, " ");
109 /* highlight first char of the tag to be special */
111 (void) wmove(win
, my_y
, data
->tag_x
);
112 dlg_print_listitem(win
, item
->name
, climit
, first
, selected
);
116 /* Draw the input field box (only for inputmenu) */
117 (void) wmove(win
, my_y
, my_x
);
120 dlg_draw_box(win
, my_y
- 1, my_x
, INPUT_ROWS
, my_width
- my_x
- data
->tag_x
,
127 /* print actual item */
128 wmove(win
, my_y
, my_x
);
129 dlg_print_listitem(win
, show
, my_width
- my_x
, first
, selected
);
132 dlg_item_help(item
->help
);
134 (void) wattrset(win
, save
);
138 * Allow the user to edit the text of a menu entry.
141 input_menu_edit(ALL_DATA
* data
,
142 DIALOG_LISTITEM
* items
,
146 chtype save
= dlg_get_attrs(data
->menu
);
149 int key
= 0, fkey
= 0;
152 bool is_inputmenu
= TRUE
;
153 int y
= ItemToRow(choice
);
155 int max_len
= dlg_max_input(MAX((int) strlen(items
->text
) + 1, MAX_LEN
));
157 result
= dlg_malloc(char, (size_t) max_len
);
158 assert_ptr(result
, "input_menu_edit");
160 /* original item is used to initialize the input string. */
162 strcpy(result
, items
->text
);
164 print_item(data
, data
->menu
, items
, choice
, Editing
, TRUE
);
166 /* taken out of inputbox.c - but somewhat modified */
169 key
= dlg_mouse_wgetch(data
->menu
, &fkey
);
170 if (dlg_edit_string(result
, &offset
, key
, fkey
, first
)) {
171 dlg_show_string(data
->menu
, result
, offset
, inputbox_attr
,
174 data
->menu_width
- data
->item_x
- 3,
177 } else if (key
== ESC
|| key
== TAB
) {
184 print_item(data
, data
->menu
, items
, choice
, Selected
, TRUE
);
185 (void) wattrset(data
->menu
, save
);
192 handle_button(int code
, DIALOG_LISTITEM
* items
, int choice
)
195 case DLG_EXIT_OK
: /* FALLTHRU */
197 dlg_add_string(items
[choice
].name
);
200 dlg_add_result("HELP ");
201 if (USE_ITEM_HELP(items
[choice
].help
)) {
202 dlg_add_string(items
[choice
].help
);
203 code
= DLG_EXIT_ITEM_HELP
;
205 dlg_add_string(items
[choice
].name
);
213 dlg_renamed_menutext(DIALOG_LISTITEM
* items
, int current
, char *newtext
)
215 if (dialog_vars
.input_result
)
216 dialog_vars
.input_result
[0] = '\0';
217 dlg_add_result("RENAMED ");
218 dlg_add_string(items
[current
].name
);
220 dlg_add_string(newtext
);
221 return DLG_EXIT_EXTRA
;
225 dlg_dummy_menutext(DIALOG_LISTITEM
* items
, int current
, char *newtext
)
230 return DLG_EXIT_ERROR
;
234 print_menu(ALL_DATA
* data
, int choice
, int scrollamt
, int max_choice
, bool is_inputmenu
)
238 for (i
= 0; i
< max_choice
; i
++) {
241 &data
->items
[i
+ scrollamt
],
243 (i
== choice
) ? Selected
: Unselected
,
247 /* Clean bottom lines */
249 int spare_lines
, x_count
;
250 spare_lines
= data
->menu_height
% INPUT_ROWS
;
251 (void) wattrset(data
->menu
, menubox_attr
);
252 for (; spare_lines
; spare_lines
--) {
253 wmove(data
->menu
, data
->menu_height
- spare_lines
, 0);
254 for (x_count
= 0; x_count
< data
->menu_width
;
256 waddch(data
->menu
, ' ');
261 (void) wnoutrefresh(data
->menu
);
263 dlg_draw_scrollbar(data
->dialog
,
266 scrollamt
+ max_choice
,
269 data
->box_x
+ data
->menu_width
,
271 data
->box_y
+ data
->menu_height
+ 1,
272 menubox_border2_attr
,
273 menubox_border_attr
);
277 check_hotkey(DIALOG_LISTITEM
* items
, int choice
)
281 if (dlg_match_char(dlg_last_getc(),
284 : items
[choice
].name
))) {
291 * This is an alternate interface to 'menu' which allows the application
292 * to read the list item states back directly without putting them in the
296 dlg_menu(const char *title
,
302 DIALOG_LISTITEM
* items
,
304 DIALOG_INPUTMENU rename_menutext
)
307 static DLG_KEYS_BINDING binding
[] = {
310 DLG_KEYS_DATA( DLGK_FIELD_NEXT
, ' ' ),
311 DLG_KEYS_DATA( DLGK_FIELD_NEXT
, KEY_RIGHT
),
312 DLG_KEYS_DATA( DLGK_FIELD_NEXT
, TAB
),
313 DLG_KEYS_DATA( DLGK_FIELD_PREV
, KEY_BTAB
),
314 DLG_KEYS_DATA( DLGK_FIELD_PREV
, KEY_LEFT
),
315 DLG_KEYS_DATA( DLGK_ITEM_NEXT
, '+' ),
316 DLG_KEYS_DATA( DLGK_ITEM_NEXT
, KEY_DOWN
),
317 DLG_KEYS_DATA( DLGK_ITEM_NEXT
, CHR_NEXT
),
318 DLG_KEYS_DATA( DLGK_ITEM_PREV
, '-' ),
319 DLG_KEYS_DATA( DLGK_ITEM_PREV
, KEY_UP
),
320 DLG_KEYS_DATA( DLGK_ITEM_PREV
, CHR_PREVIOUS
),
321 DLG_KEYS_DATA( DLGK_PAGE_FIRST
, KEY_HOME
),
322 DLG_KEYS_DATA( DLGK_PAGE_LAST
, KEY_END
),
323 DLG_KEYS_DATA( DLGK_PAGE_LAST
, KEY_LL
),
324 DLG_KEYS_DATA( DLGK_PAGE_NEXT
, KEY_NPAGE
),
325 DLG_KEYS_DATA( DLGK_PAGE_PREV
, KEY_PPAGE
),
328 static DLG_KEYS_BINDING binding2
[] = {
337 int old_height
= height
;
338 int old_width
= width
;
341 int i
, j
, x
, y
, cur_x
, cur_y
;
343 int button
= dialog_state
.visit_items
? -1 : dlg_default_button();
344 int choice
= dlg_default_listitem(items
);
345 int result
= DLG_EXIT_UNKNOWN
;
349 int use_width
, name_width
, text_width
, list_width
;
350 WINDOW
*dialog
, *menu
;
351 char *prompt
= dlg_strclone(cprompt
);
352 const char **buttons
= dlg_ok_labels();
353 bool is_inputmenu
= ((rename_menutext
!= 0)
354 && (rename_menutext
!= dlg_dummy_menutext
));
357 all
.item_no
= item_no
;
360 dlg_tab_correct_str(prompt
);
366 all
.menu_height
= menu_height
;
367 use_width
= dlg_calc_list_width(item_no
, items
) + 10;
368 use_width
= MAX(26, use_width
);
369 if (all
.menu_height
== 0) {
370 /* calculate height without items (4) */
371 dlg_auto_size(title
, prompt
, &height
, &width
, MIN_HIGH
, use_width
);
372 dlg_calc_listh(&height
, &all
.menu_height
, item_no
);
374 dlg_auto_size(title
, prompt
,
376 MIN_HIGH
+ all
.menu_height
, use_width
);
378 dlg_button_layout(buttons
, &width
);
379 dlg_print_size(height
, width
);
380 dlg_ctl_size(height
, width
);
382 x
= dlg_box_x_ordinate(width
);
383 y
= dlg_box_y_ordinate(height
);
385 dialog
= dlg_new_window(height
, width
, y
, x
);
388 dlg_register_window(dialog
, "menubox", binding
);
389 dlg_register_buttons(dialog
, "menubox", buttons
);
391 dlg_mouse_setbase(x
, y
);
393 dlg_draw_box2(dialog
, 0, 0, height
, width
, dialog_attr
, border_attr
, border2_attr
);
394 dlg_draw_bottom_box2(dialog
, border_attr
, border2_attr
, dialog_attr
);
395 dlg_draw_title(dialog
, title
);
397 (void) wattrset(dialog
, dialog_attr
);
398 dlg_print_autowrap(dialog
, prompt
, height
, width
);
400 all
.menu_width
= width
- 6;
401 getyx(dialog
, cur_y
, cur_x
);
402 all
.box_y
= cur_y
+ 1;
403 all
.box_x
= (width
- all
.menu_width
) / 2 - 1;
406 * After displaying the prompt, we know how much space we really have.
407 * Limit the list to avoid overwriting the ok-button.
409 if (all
.menu_height
+ MIN_HIGH
> height
- cur_y
)
410 all
.menu_height
= height
- MIN_HIGH
- cur_y
;
411 if (all
.menu_height
<= 0)
414 /* Find out maximal number of displayable items at once. */
415 max_choice
= MIN(all
.menu_height
,
418 max_choice
/= INPUT_ROWS
;
420 /* create new window for the menu */
421 menu
= dlg_sub_window(dialog
, all
.menu_height
, all
.menu_width
,
426 dlg_register_window(menu
, "menu", binding2
);
427 dlg_register_buttons(menu
, "menu", buttons
);
429 /* draw a box around the menu items */
431 all
.box_y
, all
.box_x
,
432 all
.menu_height
+ 2, all
.menu_width
+ 2,
433 menubox_border_attr
, menubox_border2_attr
);
438 /* Find length of longest item to center menu *
439 * only if --menu was given, using --inputmenu *
440 * won't be centered. */
441 for (i
= 0; i
< item_no
; i
++) {
442 name_width
= MAX(name_width
, dlg_count_columns(items
[i
].name
));
443 text_width
= MAX(text_width
, dlg_count_columns(items
[i
].text
));
446 /* If the name+text is wider than the list is allowed, then truncate
447 * one or both of them. If the name is no wider than 30% of the list,
450 * FIXME: the gutter width and name/list ratio should be configurable.
452 use_width
= (all
.menu_width
- GUTTER
);
453 if (dialog_vars
.no_tags
) {
454 list_width
= MIN(use_width
, text_width
);
455 } else if (dialog_vars
.no_items
) {
456 list_width
= MIN(use_width
, name_width
);
461 && text_width
+ name_width
> use_width
) {
462 int need
= (int) (0.30 * use_width
);
463 if (name_width
> need
) {
464 int want
= (int) (use_width
465 * ((double) name_width
)
466 / (text_width
+ name_width
));
467 name_width
= (want
> need
) ? want
: need
;
469 text_width
= use_width
- name_width
;
471 list_width
= (text_width
+ name_width
);
474 all
.tag_x
= (is_inputmenu
476 : (use_width
- list_width
) / 2);
477 all
.item_x
= ((dialog_vars
.no_tags
479 : (dialog_vars
.no_items
481 : (GUTTER
+ name_width
)))
484 if (choice
- scrollamt
>= max_choice
) {
485 scrollamt
= choice
- (max_choice
- 1);
486 choice
= max_choice
- 1;
489 print_menu(&all
, choice
, scrollamt
, max_choice
, is_inputmenu
);
491 /* register the new window, along with its borders */
492 dlg_mouse_mkbigregion(all
.box_y
+ 1, all
.box_x
,
493 all
.menu_height
+ 2, all
.menu_width
+ 2,
494 KEY_MAX
, 1, 1, 1 /* by lines */ );
496 dlg_draw_buttons(dialog
, height
- 2, 0, buttons
, button
, FALSE
, width
);
498 dlg_trace_win(dialog
);
499 while (result
== DLG_EXIT_UNKNOWN
) {
500 if (button
< 0) /* --visit-items */
502 all
.box_y
+ ItemToRow(choice
) + 1,
503 all
.box_x
+ all
.tag_x
+ 1);
505 key
= dlg_mouse_wgetch(dialog
, &fkey
);
506 if (dlg_result_key(key
, fkey
, &result
))
512 * Allow a mouse-click on a box to switch selection to that box.
513 * Handling a button click is a little more complicated, since we
514 * push a KEY_ENTER back onto the input stream so we'll put the
515 * cursor at the right place before handling the "keypress".
517 if (key
>= DLGK_MOUSE(KEY_MAX
)) {
518 key
-= DLGK_MOUSE(KEY_MAX
);
520 if (i
< max_choice
) {
526 } else if (is_DLGK_MOUSE(key
)
527 && dlg_ok_buttoncode(key
- M_EVENT
) >= 0) {
528 button
= (key
- M_EVENT
);
534 * Check if key pressed matches first character of any item tag in
535 * list. If there is more than one match, we will cycle through
536 * each one as the same key is pressed repeatedly.
538 if (button
< 0 || !dialog_state
.visit_items
) {
539 for (j
= scrollamt
+ choice
+ 1; j
< item_no
; j
++) {
540 if (check_hotkey(items
, j
)) {
547 for (j
= 0; j
<= scrollamt
+ choice
; j
++) {
548 if (check_hotkey(items
, j
)) {
557 } else if ((j
= dlg_char_to_button(key
, buttons
)) >= 0) {
564 * A single digit (1-9) positions the selection to that line in the
570 && (key
- '1' < max_choice
)) {
576 if (!found
&& fkey
) {
579 case DLGK_PAGE_FIRST
:
583 i
= item_no
- 1 - scrollamt
;
585 case DLGK_MOUSE(KEY_PPAGE
):
589 else if (scrollamt
!= 0)
590 i
= -MIN(scrollamt
, max_choice
);
594 case DLGK_MOUSE(KEY_NPAGE
):
596 i
= MIN(choice
+ max_choice
, item_no
- scrollamt
- 1);
600 if (choice
== 0 && scrollamt
== 0)
605 if (scrollamt
+ choice
>= item_no
- 1)
616 getyx(dialog
, cur_y
, cur_x
);
617 if (i
< 0 || i
>= max_choice
) {
622 choice
= max_choice
- 1;
623 scrollamt
+= (i
- max_choice
+ 1);
625 print_menu(&all
, choice
, scrollamt
, max_choice
, is_inputmenu
);
628 print_menu(&all
, choice
, scrollamt
, max_choice
, is_inputmenu
);
629 (void) wmove(dialog
, cur_y
, cur_x
);
633 continue; /* wait for another key press */
638 case DLGK_FIELD_PREV
:
639 button
= dlg_prev_button(buttons
, button
);
640 dlg_draw_buttons(dialog
, height
- 2, 0, buttons
, button
,
643 case DLGK_FIELD_NEXT
:
644 button
= dlg_next_button(buttons
, button
);
645 dlg_draw_buttons(dialog
, height
- 2, 0, buttons
, button
,
650 result
= dlg_ok_buttoncode(button
);
652 result
= dlg_enter_buttoncode(button
);
655 * If dlg_menu() is called from dialog_menu(), we want to
656 * capture the results into dialog_vars.input_result.
658 if (result
== DLG_EXIT_ERROR
) {
659 result
= DLG_EXIT_UNKNOWN
;
660 } else if (is_inputmenu
661 || rename_menutext
== dlg_dummy_menutext
) {
662 result
= handle_button(result
,
668 * If we have a rename_menutext function, interpret the Extra
669 * button as a request to rename the menu's text. If that
670 * function doesn't return "Unknown", we will exit from this
671 * function. Usually that is done for dialog_menu(), so the
672 * shell script can use the updated value. If it does return
673 * "Unknown", update the list item only. A direct caller of
674 * dlg_menu() can free the renamed value - we cannot.
676 if (is_inputmenu
&& result
== DLG_EXIT_EXTRA
) {
679 if (input_menu_edit(&all
,
680 &items
[scrollamt
+ choice
],
683 result
= rename_menutext(items
, scrollamt
+ choice
, tmp
);
684 if (result
== DLG_EXIT_UNKNOWN
) {
685 items
[scrollamt
+ choice
].text
= tmp
;
690 result
= DLG_EXIT_UNKNOWN
;
693 &items
[scrollamt
+ choice
],
697 (void) wnoutrefresh(menu
);
701 if (result
== DLG_EXIT_UNKNOWN
) {
702 dlg_draw_buttons(dialog
, height
- 2, 0,
703 buttons
, button
, FALSE
, width
);
714 dlg_del_window(dialog
);
716 dlg_mouse_free_regions();
726 dlg_mouse_free_regions();
727 dlg_unregister_window(menu
);
728 dlg_del_window(dialog
);
731 *current_item
= scrollamt
+ choice
;
736 * Display a menu for choosing among a number of options
739 dialog_menu(const char *title
,
750 DIALOG_LISTITEM
*listitems
;
752 listitems
= dlg_calloc(DIALOG_LISTITEM
, (size_t) item_no
+ 1);
753 assert_ptr(listitems
, "dialog_menu");
755 for (i
= j
= 0; i
< item_no
; ++i
) {
756 listitems
[i
].name
= items
[j
++];
757 listitems
[i
].text
= (dialog_vars
.no_items
760 listitems
[i
].help
= ((dialog_vars
.item_help
)
764 dlg_align_columns(&listitems
[0].text
, sizeof(DIALOG_LISTITEM
), item_no
);
766 result
= dlg_menu(title
,
774 (dialog_vars
.input_menu
775 ? dlg_renamed_menutext
776 : dlg_dummy_menutext
));
778 dlg_free_columns(&listitems
[0].text
, sizeof(DIALOG_LISTITEM
), item_no
);