1217309Snwhitehorn/*
2255852Sdteske *  $Id: menubox.c,v 1.148 2013/09/02 17:15:13 tom Exp $
3217309Snwhitehorn *
4217309Snwhitehorn *  menubox.c -- implements the menu box
5217309Snwhitehorn *
6255852Sdteske *  Copyright 2000-2012,2013	Thomas E. Dickey
7217309Snwhitehorn *
8217309Snwhitehorn *  This program is free software; you can redistribute it and/or modify
9217309Snwhitehorn *  it under the terms of the GNU Lesser General Public Licens, version 2.1e
10217309Snwhitehorn *  as published by the Free Software Foundation.
11217309Snwhitehorn *
12217309Snwhitehorn *  This program is distributed in the hope that it will be useful, but
13217309Snwhitehorn *  WITHOUT ANY WARRANTY; without even the implied warranty of
14217309Snwhitehorn *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15217309Snwhitehorn *  Lesser General Public License for more details.
16217309Snwhitehorn *
17217309Snwhitehorn *  You should have received a copy of the GNU Lesser General Public
18217309Snwhitehorn *  License along with this program; if not, write to
19217309Snwhitehorn *	Free Software Foundation, Inc.
20217309Snwhitehorn *	51 Franklin St., Fifth Floor
21217309Snwhitehorn *	Boston, MA 02110, USA.
22217309Snwhitehorn *
23217309Snwhitehorn *  An earlier version of this program lists as authors
24217309Snwhitehorn *	Savio Lam (lam836@cs.cuhk.hk)
25217309Snwhitehorn */
26217309Snwhitehorn
27217309Snwhitehorn#include <dialog.h>
28217309Snwhitehorn#include <dlg_keys.h>
29217309Snwhitehorn
30217309Snwhitehorntypedef enum {
31217309Snwhitehorn    Unselected = 0,
32217309Snwhitehorn    Selected,
33217309Snwhitehorn    Editing
34217309Snwhitehorn} Mode;
35217309Snwhitehorn
36251843Sbapttypedef struct {
37251843Sbapt    /* the outer-window */
38251843Sbapt    WINDOW *dialog;
39251843Sbapt    int box_y;
40251843Sbapt    int box_x;
41251843Sbapt    int tag_x;
42251843Sbapt    int item_x;
43251843Sbapt    int menu_height;
44251843Sbapt    int menu_width;
45251843Sbapt    /* the inner-window */
46251843Sbapt    WINDOW *menu;
47251843Sbapt    DIALOG_LISTITEM *items;
48251843Sbapt    int item_no;
49251843Sbapt} ALL_DATA;
50251843Sbapt
51217309Snwhitehorn#define MIN_HIGH  (1 + (5 * MARGIN))
52217309Snwhitehorn
53217309Snwhitehorn#define INPUT_ROWS     3	/* rows per inputmenu entry */
54217309Snwhitehorn
55217309Snwhitehorn#define RowHeight(i) (is_inputmenu ? ((i) * INPUT_ROWS) : ((i) * 1))
56217309Snwhitehorn#define ItemToRow(i) (is_inputmenu ? ((i) * INPUT_ROWS + 1) : (i))
57217309Snwhitehorn#define RowToItem(i) (is_inputmenu ? ((i) / INPUT_ROWS + 0) : (i))
58217309Snwhitehorn
59217309Snwhitehorn/*
60217309Snwhitehorn * Print menu item
61217309Snwhitehorn */
62217309Snwhitehornstatic void
63251843Sbaptprint_item(ALL_DATA * data,
64251843Sbapt	   WINDOW *win,
65251843Sbapt	   DIALOG_LISTITEM * item,
66217309Snwhitehorn	   int choice,
67217309Snwhitehorn	   Mode selected,
68217309Snwhitehorn	   bool is_inputmenu)
69217309Snwhitehorn{
70220749Snwhitehorn    chtype save = dlg_get_attrs(win);
71217309Snwhitehorn    int n;
72251843Sbapt    int climit = (data->item_x - data->tag_x - GUTTER);
73251843Sbapt    int my_width = data->menu_width;
74251843Sbapt    int my_x = data->item_x;
75217309Snwhitehorn    int my_y = ItemToRow(choice);
76251843Sbapt    bool both = (!dialog_vars.no_tags && !dialog_vars.no_items);
77251843Sbapt    bool first = TRUE;
78217309Snwhitehorn    chtype bordchar;
79251843Sbapt    const char *show = (dialog_vars.no_items
80251843Sbapt			? item->name
81251843Sbapt			: item->text);
82217309Snwhitehorn
83217309Snwhitehorn    switch (selected) {
84217309Snwhitehorn    default:
85217309Snwhitehorn    case Unselected:
86217309Snwhitehorn	bordchar = item_attr;
87217309Snwhitehorn	break;
88217309Snwhitehorn    case Selected:
89217309Snwhitehorn	bordchar = item_selected_attr;
90217309Snwhitehorn	break;
91217309Snwhitehorn    case Editing:
92217309Snwhitehorn	bordchar = dialog_attr;
93217309Snwhitehorn	break;
94217309Snwhitehorn    }
95217309Snwhitehorn
96217309Snwhitehorn    /* Clear 'residue' of last item and mark current current item */
97217309Snwhitehorn    if (is_inputmenu) {
98251843Sbapt	(void) wattrset(win, (selected != Unselected) ? item_selected_attr : item_attr);
99217309Snwhitehorn	for (n = my_y - 1; n < my_y + INPUT_ROWS - 1; n++) {
100217309Snwhitehorn	    wmove(win, n, 0);
101217309Snwhitehorn	    wprintw(win, "%*s", my_width, " ");
102217309Snwhitehorn	}
103217309Snwhitehorn    } else {
104251843Sbapt	(void) wattrset(win, menubox_attr);
105217309Snwhitehorn	wmove(win, my_y, 0);
106217309Snwhitehorn	wprintw(win, "%*s", my_width, " ");
107217309Snwhitehorn    }
108217309Snwhitehorn
109251843Sbapt    /* highlight first char of the tag to be special */
110251843Sbapt    if (both) {
111251843Sbapt	(void) wmove(win, my_y, data->tag_x);
112251843Sbapt	dlg_print_listitem(win, item->name, climit, first, selected);
113251843Sbapt	first = FALSE;
114251843Sbapt    }
115217309Snwhitehorn
116217309Snwhitehorn    /* Draw the input field box (only for inputmenu) */
117217309Snwhitehorn    (void) wmove(win, my_y, my_x);
118217309Snwhitehorn    if (is_inputmenu) {
119217309Snwhitehorn	my_width -= 1;
120251843Sbapt	dlg_draw_box(win, my_y - 1, my_x, INPUT_ROWS, my_width - my_x - data->tag_x,
121217309Snwhitehorn		     bordchar,
122217309Snwhitehorn		     bordchar);
123217309Snwhitehorn	my_width -= 1;
124217309Snwhitehorn	++my_x;
125217309Snwhitehorn    }
126217309Snwhitehorn
127217309Snwhitehorn    /* print actual item */
128217309Snwhitehorn    wmove(win, my_y, my_x);
129251843Sbapt    dlg_print_listitem(win, show, my_width - my_x, first, selected);
130217309Snwhitehorn
131217309Snwhitehorn    if (selected) {
132251843Sbapt	dlg_item_help(item->help);
133217309Snwhitehorn    }
134251843Sbapt    (void) wattrset(win, save);
135217309Snwhitehorn}
136217309Snwhitehorn
137217309Snwhitehorn/*
138217309Snwhitehorn * Allow the user to edit the text of a menu entry.
139217309Snwhitehorn */
140217309Snwhitehornstatic int
141251843Sbaptinput_menu_edit(ALL_DATA * data,
142217309Snwhitehorn		DIALOG_LISTITEM * items,
143217309Snwhitehorn		int choice,
144217309Snwhitehorn		char **resultp)
145217309Snwhitehorn{
146251843Sbapt    chtype save = dlg_get_attrs(data->menu);
147217309Snwhitehorn    char *result;
148217309Snwhitehorn    int offset = 0;
149217309Snwhitehorn    int key = 0, fkey = 0;
150217309Snwhitehorn    int first = TRUE;
151217309Snwhitehorn    /* see above */
152217309Snwhitehorn    bool is_inputmenu = TRUE;
153217309Snwhitehorn    int y = ItemToRow(choice);
154217309Snwhitehorn    int code = TRUE;
155217309Snwhitehorn    int max_len = dlg_max_input(MAX((int) strlen(items->text) + 1, MAX_LEN));
156217309Snwhitehorn
157217309Snwhitehorn    result = dlg_malloc(char, (size_t) max_len);
158217309Snwhitehorn    assert_ptr(result, "input_menu_edit");
159217309Snwhitehorn
160217309Snwhitehorn    /* original item is used to initialize the input string. */
161217309Snwhitehorn    result[0] = '\0';
162217309Snwhitehorn    strcpy(result, items->text);
163217309Snwhitehorn
164251843Sbapt    print_item(data, data->menu, items, choice, Editing, TRUE);
165217309Snwhitehorn
166217309Snwhitehorn    /* taken out of inputbox.c - but somewhat modified */
167217309Snwhitehorn    for (;;) {
168217309Snwhitehorn	if (!first)
169251843Sbapt	    key = dlg_mouse_wgetch(data->menu, &fkey);
170217309Snwhitehorn	if (dlg_edit_string(result, &offset, key, fkey, first)) {
171251843Sbapt	    dlg_show_string(data->menu, result, offset, inputbox_attr,
172251843Sbapt			    y,
173251843Sbapt			    data->item_x + 1,
174251843Sbapt			    data->menu_width - data->item_x - 3,
175217309Snwhitehorn			    FALSE, first);
176217309Snwhitehorn	    first = FALSE;
177217309Snwhitehorn	} else if (key == ESC || key == TAB) {
178217309Snwhitehorn	    code = FALSE;
179217309Snwhitehorn	    break;
180217309Snwhitehorn	} else {
181217309Snwhitehorn	    break;
182217309Snwhitehorn	}
183217309Snwhitehorn    }
184251843Sbapt    print_item(data, data->menu, items, choice, Selected, TRUE);
185251843Sbapt    (void) wattrset(data->menu, save);
186217309Snwhitehorn
187217309Snwhitehorn    *resultp = result;
188217309Snwhitehorn    return code;
189217309Snwhitehorn}
190217309Snwhitehorn
191217309Snwhitehornstatic int
192217309Snwhitehornhandle_button(int code, DIALOG_LISTITEM * items, int choice)
193217309Snwhitehorn{
194255852Sdteske    char *help_result;
195255852Sdteske
196217309Snwhitehorn    switch (code) {
197217309Snwhitehorn    case DLG_EXIT_OK:		/* FALLTHRU */
198217309Snwhitehorn    case DLG_EXIT_EXTRA:
199217309Snwhitehorn	dlg_add_string(items[choice].name);
200217309Snwhitehorn	break;
201217309Snwhitehorn    case DLG_EXIT_HELP:
202255852Sdteske	dlg_add_help_listitem(&code, &help_result, &items[choice]);
203255852Sdteske	dlg_add_string(help_result);
204217309Snwhitehorn	break;
205217309Snwhitehorn    }
206217309Snwhitehorn    return code;
207217309Snwhitehorn}
208217309Snwhitehorn
209251843Sbaptint
210217309Snwhitehorndlg_renamed_menutext(DIALOG_LISTITEM * items, int current, char *newtext)
211217309Snwhitehorn{
212217309Snwhitehorn    if (dialog_vars.input_result)
213217309Snwhitehorn	dialog_vars.input_result[0] = '\0';
214217309Snwhitehorn    dlg_add_result("RENAMED ");
215217309Snwhitehorn    dlg_add_string(items[current].name);
216217309Snwhitehorn    dlg_add_result(" ");
217217309Snwhitehorn    dlg_add_string(newtext);
218217309Snwhitehorn    return DLG_EXIT_EXTRA;
219217309Snwhitehorn}
220217309Snwhitehorn
221251843Sbaptint
222217309Snwhitehorndlg_dummy_menutext(DIALOG_LISTITEM * items, int current, char *newtext)
223217309Snwhitehorn{
224217309Snwhitehorn    (void) items;
225217309Snwhitehorn    (void) current;
226217309Snwhitehorn    (void) newtext;
227217309Snwhitehorn    return DLG_EXIT_ERROR;
228217309Snwhitehorn}
229217309Snwhitehorn
230251843Sbaptstatic void
231251843Sbaptprint_menu(ALL_DATA * data, int choice, int scrollamt, int max_choice, bool is_inputmenu)
232251843Sbapt{
233251843Sbapt    int i;
234251843Sbapt
235251843Sbapt    for (i = 0; i < max_choice; i++) {
236251843Sbapt	print_item(data,
237251843Sbapt		   data->menu,
238251843Sbapt		   &data->items[i + scrollamt],
239251843Sbapt		   i,
240251843Sbapt		   (i == choice) ? Selected : Unselected,
241251843Sbapt		   is_inputmenu);
242251843Sbapt    }
243251843Sbapt
244251843Sbapt    /* Clean bottom lines */
245251843Sbapt    if (is_inputmenu) {
246251843Sbapt	int spare_lines, x_count;
247251843Sbapt	spare_lines = data->menu_height % INPUT_ROWS;
248251843Sbapt	(void) wattrset(data->menu, menubox_attr);
249251843Sbapt	for (; spare_lines; spare_lines--) {
250251843Sbapt	    wmove(data->menu, data->menu_height - spare_lines, 0);
251251843Sbapt	    for (x_count = 0; x_count < data->menu_width;
252251843Sbapt		 x_count++) {
253251843Sbapt		waddch(data->menu, ' ');
254251843Sbapt	    }
255251843Sbapt	}
256251843Sbapt    }
257251843Sbapt
258251843Sbapt    (void) wnoutrefresh(data->menu);
259251843Sbapt
260251843Sbapt    dlg_draw_scrollbar(data->dialog,
261251843Sbapt		       scrollamt,
262251843Sbapt		       scrollamt,
263251843Sbapt		       scrollamt + max_choice,
264251843Sbapt		       data->item_no,
265251843Sbapt		       data->box_x,
266251843Sbapt		       data->box_x + data->menu_width,
267251843Sbapt		       data->box_y,
268251843Sbapt		       data->box_y + data->menu_height + 1,
269251843Sbapt		       menubox_border2_attr,
270251843Sbapt		       menubox_border_attr);
271251843Sbapt}
272251843Sbapt
273251843Sbaptstatic bool
274251843Sbaptcheck_hotkey(DIALOG_LISTITEM * items, int choice)
275251843Sbapt{
276251843Sbapt    bool result = FALSE;
277251843Sbapt
278251843Sbapt    if (dlg_match_char(dlg_last_getc(),
279251843Sbapt		       (dialog_vars.no_tags
280251843Sbapt			? items[choice].text
281251843Sbapt			: items[choice].name))) {
282251843Sbapt	result = TRUE;
283251843Sbapt    }
284251843Sbapt    return result;
285251843Sbapt}
286251843Sbapt
287217309Snwhitehorn/*
288217309Snwhitehorn * This is an alternate interface to 'menu' which allows the application
289217309Snwhitehorn * to read the list item states back directly without putting them in the
290217309Snwhitehorn * output buffer.
291217309Snwhitehorn */
292217309Snwhitehornint
293217309Snwhitehorndlg_menu(const char *title,
294217309Snwhitehorn	 const char *cprompt,
295217309Snwhitehorn	 int height,
296217309Snwhitehorn	 int width,
297217309Snwhitehorn	 int menu_height,
298217309Snwhitehorn	 int item_no,
299217309Snwhitehorn	 DIALOG_LISTITEM * items,
300217309Snwhitehorn	 int *current_item,
301217309Snwhitehorn	 DIALOG_INPUTMENU rename_menutext)
302217309Snwhitehorn{
303217309Snwhitehorn    /* *INDENT-OFF* */
304217309Snwhitehorn    static DLG_KEYS_BINDING binding[] = {
305224014Snwhitehorn	HELPKEY_BINDINGS,
306217309Snwhitehorn	ENTERKEY_BINDINGS,
307217309Snwhitehorn	DLG_KEYS_DATA( DLGK_FIELD_NEXT,	' ' ),
308217309Snwhitehorn	DLG_KEYS_DATA( DLGK_FIELD_NEXT,	KEY_RIGHT ),
309217309Snwhitehorn	DLG_KEYS_DATA( DLGK_FIELD_NEXT,	TAB ),
310217309Snwhitehorn	DLG_KEYS_DATA( DLGK_FIELD_PREV,	KEY_BTAB ),
311217309Snwhitehorn	DLG_KEYS_DATA( DLGK_FIELD_PREV,	KEY_LEFT ),
312217309Snwhitehorn	DLG_KEYS_DATA( DLGK_ITEM_NEXT,	'+' ),
313217309Snwhitehorn	DLG_KEYS_DATA( DLGK_ITEM_NEXT,	KEY_DOWN ),
314217309Snwhitehorn	DLG_KEYS_DATA( DLGK_ITEM_NEXT,  CHR_NEXT ),
315217309Snwhitehorn	DLG_KEYS_DATA( DLGK_ITEM_PREV,	'-' ),
316217309Snwhitehorn	DLG_KEYS_DATA( DLGK_ITEM_PREV,	KEY_UP ),
317217309Snwhitehorn	DLG_KEYS_DATA( DLGK_ITEM_PREV,  CHR_PREVIOUS ),
318217309Snwhitehorn	DLG_KEYS_DATA( DLGK_PAGE_FIRST,	KEY_HOME ),
319217309Snwhitehorn	DLG_KEYS_DATA( DLGK_PAGE_LAST,	KEY_END ),
320217309Snwhitehorn	DLG_KEYS_DATA( DLGK_PAGE_LAST,	KEY_LL ),
321217309Snwhitehorn	DLG_KEYS_DATA( DLGK_PAGE_NEXT,	KEY_NPAGE ),
322217309Snwhitehorn	DLG_KEYS_DATA( DLGK_PAGE_PREV,	KEY_PPAGE ),
323217309Snwhitehorn	END_KEYS_BINDING
324217309Snwhitehorn    };
325217309Snwhitehorn    static DLG_KEYS_BINDING binding2[] = {
326217309Snwhitehorn	INPUTSTR_BINDINGS,
327224014Snwhitehorn	HELPKEY_BINDINGS,
328217309Snwhitehorn	ENTERKEY_BINDINGS,
329217309Snwhitehorn	END_KEYS_BINDING
330217309Snwhitehorn    };
331217309Snwhitehorn    /* *INDENT-ON* */
332217309Snwhitehorn
333217309Snwhitehorn#ifdef KEY_RESIZE
334217309Snwhitehorn    int old_height = height;
335217309Snwhitehorn    int old_width = width;
336217309Snwhitehorn#endif
337251843Sbapt    ALL_DATA all;
338251843Sbapt    int i, j, x, y, cur_x, cur_y;
339217309Snwhitehorn    int key = 0, fkey;
340251843Sbapt    int button = dialog_state.visit_items ? -1 : dlg_default_button();
341217309Snwhitehorn    int choice = dlg_default_listitem(items);
342217309Snwhitehorn    int result = DLG_EXIT_UNKNOWN;
343217309Snwhitehorn    int scrollamt = 0;
344251843Sbapt    int max_choice;
345217309Snwhitehorn    int found;
346251843Sbapt    int use_width, name_width, text_width, list_width;
347217309Snwhitehorn    WINDOW *dialog, *menu;
348217309Snwhitehorn    char *prompt = dlg_strclone(cprompt);
349217309Snwhitehorn    const char **buttons = dlg_ok_labels();
350251843Sbapt    bool is_inputmenu = ((rename_menutext != 0)
351251843Sbapt			 && (rename_menutext != dlg_dummy_menutext));
352217309Snwhitehorn
353251843Sbapt    all.items = items;
354251843Sbapt    all.item_no = item_no;
355251843Sbapt
356217309Snwhitehorn    dlg_does_output();
357217309Snwhitehorn    dlg_tab_correct_str(prompt);
358217309Snwhitehorn
359217309Snwhitehorn#ifdef KEY_RESIZE
360217309Snwhitehorn  retry:
361217309Snwhitehorn#endif
362217309Snwhitehorn
363251843Sbapt    all.menu_height = menu_height;
364251843Sbapt    use_width = dlg_calc_list_width(item_no, items) + 10;
365251843Sbapt    use_width = MAX(26, use_width);
366251843Sbapt    if (all.menu_height == 0) {
367217309Snwhitehorn	/* calculate height without items (4) */
368251843Sbapt	dlg_auto_size(title, prompt, &height, &width, MIN_HIGH, use_width);
369251843Sbapt	dlg_calc_listh(&height, &all.menu_height, item_no);
370217309Snwhitehorn    } else {
371251843Sbapt	dlg_auto_size(title, prompt,
372251843Sbapt		      &height, &width,
373251843Sbapt		      MIN_HIGH + all.menu_height, use_width);
374217309Snwhitehorn    }
375217309Snwhitehorn    dlg_button_layout(buttons, &width);
376217309Snwhitehorn    dlg_print_size(height, width);
377217309Snwhitehorn    dlg_ctl_size(height, width);
378217309Snwhitehorn
379217309Snwhitehorn    x = dlg_box_x_ordinate(width);
380217309Snwhitehorn    y = dlg_box_y_ordinate(height);
381217309Snwhitehorn
382217309Snwhitehorn    dialog = dlg_new_window(height, width, y, x);
383251843Sbapt    all.dialog = dialog;
384251843Sbapt
385217309Snwhitehorn    dlg_register_window(dialog, "menubox", binding);
386217309Snwhitehorn    dlg_register_buttons(dialog, "menubox", buttons);
387217309Snwhitehorn
388217309Snwhitehorn    dlg_mouse_setbase(x, y);
389217309Snwhitehorn
390251843Sbapt    dlg_draw_box2(dialog, 0, 0, height, width, dialog_attr, border_attr, border2_attr);
391251843Sbapt    dlg_draw_bottom_box2(dialog, border_attr, border2_attr, dialog_attr);
392217309Snwhitehorn    dlg_draw_title(dialog, title);
393217309Snwhitehorn
394251843Sbapt    (void) wattrset(dialog, dialog_attr);
395217309Snwhitehorn    dlg_print_autowrap(dialog, prompt, height, width);
396217309Snwhitehorn
397251843Sbapt    all.menu_width = width - 6;
398217309Snwhitehorn    getyx(dialog, cur_y, cur_x);
399251843Sbapt    all.box_y = cur_y + 1;
400251843Sbapt    all.box_x = (width - all.menu_width) / 2 - 1;
401217309Snwhitehorn
402217309Snwhitehorn    /*
403217309Snwhitehorn     * After displaying the prompt, we know how much space we really have.
404217309Snwhitehorn     * Limit the list to avoid overwriting the ok-button.
405217309Snwhitehorn     */
406251843Sbapt    if (all.menu_height + MIN_HIGH > height - cur_y)
407251843Sbapt	all.menu_height = height - MIN_HIGH - cur_y;
408251843Sbapt    if (all.menu_height <= 0)
409251843Sbapt	all.menu_height = 1;
410217309Snwhitehorn
411217309Snwhitehorn    /* Find out maximal number of displayable items at once. */
412251843Sbapt    max_choice = MIN(all.menu_height,
413217309Snwhitehorn		     RowHeight(item_no));
414217309Snwhitehorn    if (is_inputmenu)
415217309Snwhitehorn	max_choice /= INPUT_ROWS;
416217309Snwhitehorn
417217309Snwhitehorn    /* create new window for the menu */
418251843Sbapt    menu = dlg_sub_window(dialog, all.menu_height, all.menu_width,
419251843Sbapt			  y + all.box_y + 1,
420251843Sbapt			  x + all.box_x + 1);
421251843Sbapt    all.menu = menu;
422251843Sbapt
423217309Snwhitehorn    dlg_register_window(menu, "menu", binding2);
424217309Snwhitehorn    dlg_register_buttons(menu, "menu", buttons);
425217309Snwhitehorn
426217309Snwhitehorn    /* draw a box around the menu items */
427251843Sbapt    dlg_draw_box(dialog,
428251843Sbapt		 all.box_y, all.box_x,
429251843Sbapt		 all.menu_height + 2, all.menu_width + 2,
430251843Sbapt		 menubox_border_attr, menubox_border2_attr);
431217309Snwhitehorn
432217309Snwhitehorn    name_width = 0;
433217309Snwhitehorn    text_width = 0;
434217309Snwhitehorn
435217309Snwhitehorn    /* Find length of longest item to center menu  *
436217309Snwhitehorn     * only if --menu was given, using --inputmenu *
437217309Snwhitehorn     * won't be centered.                         */
438217309Snwhitehorn    for (i = 0; i < item_no; i++) {
439217309Snwhitehorn	name_width = MAX(name_width, dlg_count_columns(items[i].name));
440217309Snwhitehorn	text_width = MAX(text_width, dlg_count_columns(items[i].text));
441217309Snwhitehorn    }
442217309Snwhitehorn
443217309Snwhitehorn    /* If the name+text is wider than the list is allowed, then truncate
444217309Snwhitehorn     * one or both of them.  If the name is no wider than 30% of the list,
445217309Snwhitehorn     * leave it intact.
446217309Snwhitehorn     *
447217309Snwhitehorn     * FIXME: the gutter width and name/list ratio should be configurable.
448217309Snwhitehorn     */
449251843Sbapt    use_width = (all.menu_width - GUTTER);
450251843Sbapt    if (dialog_vars.no_tags) {
451251843Sbapt	list_width = MIN(use_width, text_width);
452251843Sbapt    } else if (dialog_vars.no_items) {
453251843Sbapt	list_width = MIN(use_width, name_width);
454251843Sbapt    } else {
455251843Sbapt	if (text_width >= 0
456251843Sbapt	    && name_width >= 0
457251843Sbapt	    && use_width > 0
458251843Sbapt	    && text_width + name_width > use_width) {
459251843Sbapt	    int need = (int) (0.30 * use_width);
460251843Sbapt	    if (name_width > need) {
461251843Sbapt		int want = (int) (use_width
462251843Sbapt				  * ((double) name_width)
463251843Sbapt				  / (text_width + name_width));
464251843Sbapt		name_width = (want > need) ? want : need;
465251843Sbapt	    }
466251843Sbapt	    text_width = use_width - name_width;
467217309Snwhitehorn	}
468251843Sbapt	list_width = (text_width + name_width);
469217309Snwhitehorn    }
470217309Snwhitehorn
471251843Sbapt    all.tag_x = (is_inputmenu
472251843Sbapt		 ? 0
473251843Sbapt		 : (use_width - list_width) / 2);
474251843Sbapt    all.item_x = ((dialog_vars.no_tags
475251843Sbapt		   ? 0
476251843Sbapt		   : (dialog_vars.no_items
477251843Sbapt		      ? 0
478251843Sbapt		      : (GUTTER + name_width)))
479251843Sbapt		  + all.tag_x);
480217309Snwhitehorn
481217309Snwhitehorn    if (choice - scrollamt >= max_choice) {
482217309Snwhitehorn	scrollamt = choice - (max_choice - 1);
483217309Snwhitehorn	choice = max_choice - 1;
484217309Snwhitehorn    }
485217309Snwhitehorn
486251843Sbapt    print_menu(&all, choice, scrollamt, max_choice, is_inputmenu);
487217309Snwhitehorn
488217309Snwhitehorn    /* register the new window, along with its borders */
489251843Sbapt    dlg_mouse_mkbigregion(all.box_y + 1, all.box_x,
490251843Sbapt			  all.menu_height + 2, all.menu_width + 2,
491217309Snwhitehorn			  KEY_MAX, 1, 1, 1 /* by lines */ );
492217309Snwhitehorn
493217309Snwhitehorn    dlg_draw_buttons(dialog, height - 2, 0, buttons, button, FALSE, width);
494217309Snwhitehorn
495251843Sbapt    dlg_trace_win(dialog);
496217309Snwhitehorn    while (result == DLG_EXIT_UNKNOWN) {
497217309Snwhitehorn	if (button < 0)		/* --visit-items */
498251843Sbapt	    wmove(dialog,
499251843Sbapt		  all.box_y + ItemToRow(choice) + 1,
500251843Sbapt		  all.box_x + all.tag_x + 1);
501217309Snwhitehorn
502217309Snwhitehorn	key = dlg_mouse_wgetch(dialog, &fkey);
503217309Snwhitehorn	if (dlg_result_key(key, fkey, &result))
504217309Snwhitehorn	    break;
505217309Snwhitehorn
506217309Snwhitehorn	found = FALSE;
507217309Snwhitehorn	if (fkey) {
508217309Snwhitehorn	    /*
509217309Snwhitehorn	     * Allow a mouse-click on a box to switch selection to that box.
510217309Snwhitehorn	     * Handling a button click is a little more complicated, since we
511217309Snwhitehorn	     * push a KEY_ENTER back onto the input stream so we'll put the
512217309Snwhitehorn	     * cursor at the right place before handling the "keypress".
513217309Snwhitehorn	     */
514217309Snwhitehorn	    if (key >= DLGK_MOUSE(KEY_MAX)) {
515217309Snwhitehorn		key -= DLGK_MOUSE(KEY_MAX);
516217309Snwhitehorn		i = RowToItem(key);
517217309Snwhitehorn		if (i < max_choice) {
518217309Snwhitehorn		    found = TRUE;
519217309Snwhitehorn		} else {
520217309Snwhitehorn		    beep();
521217309Snwhitehorn		    continue;
522217309Snwhitehorn		}
523217309Snwhitehorn	    } else if (is_DLGK_MOUSE(key)
524217309Snwhitehorn		       && dlg_ok_buttoncode(key - M_EVENT) >= 0) {
525217309Snwhitehorn		button = (key - M_EVENT);
526217309Snwhitehorn		ungetch('\n');
527217309Snwhitehorn		continue;
528217309Snwhitehorn	    }
529217309Snwhitehorn	} else {
530217309Snwhitehorn	    /*
531217309Snwhitehorn	     * Check if key pressed matches first character of any item tag in
532217309Snwhitehorn	     * list.  If there is more than one match, we will cycle through
533217309Snwhitehorn	     * each one as the same key is pressed repeatedly.
534217309Snwhitehorn	     */
535217309Snwhitehorn	    if (button < 0 || !dialog_state.visit_items) {
536217309Snwhitehorn		for (j = scrollamt + choice + 1; j < item_no; j++) {
537251843Sbapt		    if (check_hotkey(items, j)) {
538217309Snwhitehorn			found = TRUE;
539217309Snwhitehorn			i = j - scrollamt;
540217309Snwhitehorn			break;
541217309Snwhitehorn		    }
542217309Snwhitehorn		}
543217309Snwhitehorn		if (!found) {
544217309Snwhitehorn		    for (j = 0; j <= scrollamt + choice; j++) {
545251843Sbapt			if (check_hotkey(items, j)) {
546217309Snwhitehorn			    found = TRUE;
547217309Snwhitehorn			    i = j - scrollamt;
548217309Snwhitehorn			    break;
549217309Snwhitehorn			}
550217309Snwhitehorn		    }
551217309Snwhitehorn		}
552217309Snwhitehorn		if (found)
553217309Snwhitehorn		    dlg_flush_getc();
554217309Snwhitehorn	    } else if ((j = dlg_char_to_button(key, buttons)) >= 0) {
555217309Snwhitehorn		button = j;
556217309Snwhitehorn		ungetch('\n');
557217309Snwhitehorn		continue;
558217309Snwhitehorn	    }
559217309Snwhitehorn
560217309Snwhitehorn	    /*
561217309Snwhitehorn	     * A single digit (1-9) positions the selection to that line in the
562217309Snwhitehorn	     * current screen.
563217309Snwhitehorn	     */
564217309Snwhitehorn	    if (!found
565217309Snwhitehorn		&& (key <= '9')
566217309Snwhitehorn		&& (key > '0')
567217309Snwhitehorn		&& (key - '1' < max_choice)) {
568217309Snwhitehorn		found = TRUE;
569217309Snwhitehorn		i = key - '1';
570217309Snwhitehorn	    }
571217309Snwhitehorn	}
572217309Snwhitehorn
573217309Snwhitehorn	if (!found && fkey) {
574217309Snwhitehorn	    found = TRUE;
575217309Snwhitehorn	    switch (key) {
576217309Snwhitehorn	    case DLGK_PAGE_FIRST:
577217309Snwhitehorn		i = -scrollamt;
578217309Snwhitehorn		break;
579217309Snwhitehorn	    case DLGK_PAGE_LAST:
580217309Snwhitehorn		i = item_no - 1 - scrollamt;
581217309Snwhitehorn		break;
582217309Snwhitehorn	    case DLGK_MOUSE(KEY_PPAGE):
583217309Snwhitehorn	    case DLGK_PAGE_PREV:
584217309Snwhitehorn		if (choice)
585217309Snwhitehorn		    i = 0;
586217309Snwhitehorn		else if (scrollamt != 0)
587217309Snwhitehorn		    i = -MIN(scrollamt, max_choice);
588217309Snwhitehorn		else
589217309Snwhitehorn		    continue;
590217309Snwhitehorn		break;
591217309Snwhitehorn	    case DLGK_MOUSE(KEY_NPAGE):
592217309Snwhitehorn	    case DLGK_PAGE_NEXT:
593217309Snwhitehorn		i = MIN(choice + max_choice, item_no - scrollamt - 1);
594217309Snwhitehorn		break;
595217309Snwhitehorn	    case DLGK_ITEM_PREV:
596217309Snwhitehorn		i = choice - 1;
597217309Snwhitehorn		if (choice == 0 && scrollamt == 0)
598217309Snwhitehorn		    continue;
599217309Snwhitehorn		break;
600217309Snwhitehorn	    case DLGK_ITEM_NEXT:
601217309Snwhitehorn		i = choice + 1;
602217309Snwhitehorn		if (scrollamt + choice >= item_no - 1)
603217309Snwhitehorn		    continue;
604217309Snwhitehorn		break;
605217309Snwhitehorn	    default:
606217309Snwhitehorn		found = FALSE;
607217309Snwhitehorn		break;
608217309Snwhitehorn	    }
609217309Snwhitehorn	}
610217309Snwhitehorn
611217309Snwhitehorn	if (found) {
612217309Snwhitehorn	    if (i != choice) {
613217309Snwhitehorn		getyx(dialog, cur_y, cur_x);
614217309Snwhitehorn		if (i < 0 || i >= max_choice) {
615251843Sbapt		    if (i < 0) {
616251843Sbapt			scrollamt += i;
617251843Sbapt			choice = 0;
618251843Sbapt		    } else {
619251843Sbapt			choice = max_choice - 1;
620251843Sbapt			scrollamt += (i - max_choice + 1);
621217309Snwhitehorn		    }
622251843Sbapt		    print_menu(&all, choice, scrollamt, max_choice, is_inputmenu);
623217309Snwhitehorn		} else {
624217309Snwhitehorn		    choice = i;
625251843Sbapt		    print_menu(&all, choice, scrollamt, max_choice, is_inputmenu);
626217309Snwhitehorn		    (void) wmove(dialog, cur_y, cur_x);
627217309Snwhitehorn		    wrefresh(dialog);
628217309Snwhitehorn		}
629217309Snwhitehorn	    }
630217309Snwhitehorn	    continue;		/* wait for another key press */
631217309Snwhitehorn	}
632217309Snwhitehorn
633217309Snwhitehorn	if (fkey) {
634217309Snwhitehorn	    switch (key) {
635217309Snwhitehorn	    case DLGK_FIELD_PREV:
636217309Snwhitehorn		button = dlg_prev_button(buttons, button);
637217309Snwhitehorn		dlg_draw_buttons(dialog, height - 2, 0, buttons, button,
638217309Snwhitehorn				 FALSE, width);
639217309Snwhitehorn		break;
640217309Snwhitehorn	    case DLGK_FIELD_NEXT:
641217309Snwhitehorn		button = dlg_next_button(buttons, button);
642217309Snwhitehorn		dlg_draw_buttons(dialog, height - 2, 0, buttons, button,
643217309Snwhitehorn				 FALSE, width);
644217309Snwhitehorn		break;
645217309Snwhitehorn	    case DLGK_ENTER:
646251843Sbapt		if (is_inputmenu)
647251843Sbapt		    result = dlg_ok_buttoncode(button);
648251843Sbapt		else
649251843Sbapt		    result = dlg_enter_buttoncode(button);
650217309Snwhitehorn
651217309Snwhitehorn		/*
652217309Snwhitehorn		 * If dlg_menu() is called from dialog_menu(), we want to
653251843Sbapt		 * capture the results into dialog_vars.input_result.
654217309Snwhitehorn		 */
655217309Snwhitehorn		if (result == DLG_EXIT_ERROR) {
656217309Snwhitehorn		    result = DLG_EXIT_UNKNOWN;
657217309Snwhitehorn		} else if (is_inputmenu
658217309Snwhitehorn			   || rename_menutext == dlg_dummy_menutext) {
659217309Snwhitehorn		    result = handle_button(result,
660217309Snwhitehorn					   items,
661217309Snwhitehorn					   scrollamt + choice);
662217309Snwhitehorn		}
663217309Snwhitehorn
664217309Snwhitehorn		/*
665217309Snwhitehorn		 * If we have a rename_menutext function, interpret the Extra
666217309Snwhitehorn		 * button as a request to rename the menu's text.  If that
667217309Snwhitehorn		 * function doesn't return "Unknown", we will exit from this
668217309Snwhitehorn		 * function.  Usually that is done for dialog_menu(), so the
669217309Snwhitehorn		 * shell script can use the updated value.  If it does return
670217309Snwhitehorn		 * "Unknown", update the list item only.  A direct caller of
671217309Snwhitehorn		 * dlg_menu() can free the renamed value - we cannot.
672217309Snwhitehorn		 */
673217309Snwhitehorn		if (is_inputmenu && result == DLG_EXIT_EXTRA) {
674217309Snwhitehorn		    char *tmp;
675217309Snwhitehorn
676251843Sbapt		    if (input_menu_edit(&all,
677217309Snwhitehorn					&items[scrollamt + choice],
678217309Snwhitehorn					choice,
679217309Snwhitehorn					&tmp)) {
680217309Snwhitehorn			result = rename_menutext(items, scrollamt + choice, tmp);
681217309Snwhitehorn			if (result == DLG_EXIT_UNKNOWN) {
682217309Snwhitehorn			    items[scrollamt + choice].text = tmp;
683217309Snwhitehorn			} else {
684217309Snwhitehorn			    free(tmp);
685217309Snwhitehorn			}
686217309Snwhitehorn		    } else {
687217309Snwhitehorn			result = DLG_EXIT_UNKNOWN;
688251843Sbapt			print_item(&all,
689251843Sbapt				   menu,
690217309Snwhitehorn				   &items[scrollamt + choice],
691217309Snwhitehorn				   choice,
692217309Snwhitehorn				   Selected,
693217309Snwhitehorn				   is_inputmenu);
694217309Snwhitehorn			(void) wnoutrefresh(menu);
695217309Snwhitehorn			free(tmp);
696217309Snwhitehorn		    }
697217309Snwhitehorn
698217309Snwhitehorn		    if (result == DLG_EXIT_UNKNOWN) {
699217309Snwhitehorn			dlg_draw_buttons(dialog, height - 2, 0,
700217309Snwhitehorn					 buttons, button, FALSE, width);
701217309Snwhitehorn		    }
702217309Snwhitehorn		}
703217309Snwhitehorn		break;
704217309Snwhitehorn#ifdef KEY_RESIZE
705217309Snwhitehorn	    case KEY_RESIZE:
706217309Snwhitehorn		/* reset data */
707217309Snwhitehorn		height = old_height;
708217309Snwhitehorn		width = old_width;
709217309Snwhitehorn		/* repaint */
710217309Snwhitehorn		dlg_clear();
711217309Snwhitehorn		dlg_del_window(dialog);
712217309Snwhitehorn		refresh();
713217309Snwhitehorn		dlg_mouse_free_regions();
714217309Snwhitehorn		goto retry;
715217309Snwhitehorn#endif
716217309Snwhitehorn	    default:
717217309Snwhitehorn		flash();
718217309Snwhitehorn		break;
719217309Snwhitehorn	    }
720217309Snwhitehorn	}
721217309Snwhitehorn    }
722217309Snwhitehorn
723217309Snwhitehorn    dlg_mouse_free_regions();
724217309Snwhitehorn    dlg_unregister_window(menu);
725217309Snwhitehorn    dlg_del_window(dialog);
726217309Snwhitehorn    free(prompt);
727217309Snwhitehorn
728217309Snwhitehorn    *current_item = scrollamt + choice;
729217309Snwhitehorn    return result;
730217309Snwhitehorn}
731217309Snwhitehorn
732217309Snwhitehorn/*
733217309Snwhitehorn * Display a menu for choosing among a number of options
734217309Snwhitehorn */
735217309Snwhitehornint
736217309Snwhitehorndialog_menu(const char *title,
737217309Snwhitehorn	    const char *cprompt,
738217309Snwhitehorn	    int height,
739217309Snwhitehorn	    int width,
740217309Snwhitehorn	    int menu_height,
741217309Snwhitehorn	    int item_no,
742217309Snwhitehorn	    char **items)
743217309Snwhitehorn{
744217309Snwhitehorn    int result;
745217309Snwhitehorn    int choice;
746251843Sbapt    int i, j;
747217309Snwhitehorn    DIALOG_LISTITEM *listitems;
748217309Snwhitehorn
749217309Snwhitehorn    listitems = dlg_calloc(DIALOG_LISTITEM, (size_t) item_no + 1);
750217309Snwhitehorn    assert_ptr(listitems, "dialog_menu");
751217309Snwhitehorn
752251843Sbapt    for (i = j = 0; i < item_no; ++i) {
753251843Sbapt	listitems[i].name = items[j++];
754251843Sbapt	listitems[i].text = (dialog_vars.no_items
755251843Sbapt			     ? dlg_strempty()
756251843Sbapt			     : items[j++]);
757217309Snwhitehorn	listitems[i].help = ((dialog_vars.item_help)
758251843Sbapt			     ? items[j++]
759217309Snwhitehorn			     : dlg_strempty());
760217309Snwhitehorn    }
761217309Snwhitehorn    dlg_align_columns(&listitems[0].text, sizeof(DIALOG_LISTITEM), item_no);
762217309Snwhitehorn
763217309Snwhitehorn    result = dlg_menu(title,
764217309Snwhitehorn		      cprompt,
765217309Snwhitehorn		      height,
766217309Snwhitehorn		      width,
767217309Snwhitehorn		      menu_height,
768217309Snwhitehorn		      item_no,
769217309Snwhitehorn		      listitems,
770217309Snwhitehorn		      &choice,
771251843Sbapt		      (dialog_vars.input_menu
772251843Sbapt		       ? dlg_renamed_menutext
773251843Sbapt		       : dlg_dummy_menutext));
774217309Snwhitehorn
775217309Snwhitehorn    dlg_free_columns(&listitems[0].text, sizeof(DIALOG_LISTITEM), item_no);
776217309Snwhitehorn    free(listitems);
777217309Snwhitehorn    return result;
778217309Snwhitehorn}
779