checklist.c revision 217309
1153838Sdfr/*
2153838Sdfr *  $Id: checklist.c,v 1.121 2010/01/17 19:32:17 tom Exp $
3153838Sdfr *
4153838Sdfr *  checklist.c -- implements the checklist box
5153838Sdfr *
6153838Sdfr *  Copyright 2000-2009,2010	Thomas E. Dickey
7153838Sdfr *
8153838Sdfr *  This program is free software; you can redistribute it and/or modify
9153838Sdfr *  it under the terms of the GNU Lesser General Public License, version 2.1
10153838Sdfr *  as published by the Free Software Foundation.
11153838Sdfr *
12153838Sdfr *  This program is distributed in the hope that it will be useful, but
13153838Sdfr *  WITHOUT ANY WARRANTY; without even the implied warranty of
14153838Sdfr *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15153838Sdfr *  Lesser General Public License for more details.
16153838Sdfr *
17153838Sdfr *  You should have received a copy of the GNU Lesser General Public
18153838Sdfr *  License along with this program; if not, write to
19153838Sdfr *	Free Software Foundation, Inc.
20153838Sdfr *	51 Franklin St., Fifth Floor
21153838Sdfr *	Boston, MA 02110, USA.
22153838Sdfr *
23153838Sdfr *  An earlier version of this program lists as authors:
24153838Sdfr *	Savio Lam (lam836@cs.cuhk.hk)
25153838Sdfr *	Stuart Herbert - S.Herbert@sheffield.ac.uk: radiolist extension
26153838Sdfr *	Alessandro Rubini - rubini@ipvvis.unipv.it: merged the two
27153838Sdfr */
28153838Sdfr
29153838Sdfr#include <dialog.h>
30153838Sdfr#include <dlg_keys.h>
31153838Sdfr
32153838Sdfrstatic int list_width, check_x, item_x, checkflag;
33153838Sdfr
34153838Sdfr#define MIN_HIGH  (1 + (5 * MARGIN))
35153838Sdfr
36153838Sdfr#define LLEN(n) ((n) * CHECKBOX_TAGS)
37153838Sdfr#define ItemData(i)    &items[LLEN(i)]
38153838Sdfr#define ItemName(i)    items[LLEN(i)]
39153838Sdfr#define ItemText(i)    items[LLEN(i) + 1]
40153838Sdfr#define ItemStatus(i)  items[LLEN(i) + 2]
41153838Sdfr#define ItemHelp(i)    items[LLEN(i) + 3]
42153838Sdfr
43153838Sdfrstatic void
44153838Sdfrprint_arrows(WINDOW *win,
45153838Sdfr	     int box_x,
46153838Sdfr	     int box_y,
47153838Sdfr	     int scrollamt,
48153838Sdfr	     int choice,
49153838Sdfr	     int item_no,
50153838Sdfr	     int list_height)
51153838Sdfr{
52153838Sdfr    dlg_draw_scrollbar(win,
53153838Sdfr		       scrollamt,
54153838Sdfr		       scrollamt,
55153838Sdfr		       scrollamt + choice,
56153838Sdfr		       item_no,
57153838Sdfr		       box_x + check_x,
58153838Sdfr		       box_x + list_width,
59153838Sdfr		       box_y,
60153838Sdfr		       box_y + list_height + 1,
61153838Sdfr		       menubox_attr,
62153838Sdfr		       menubox_border_attr);
63153838Sdfr}
64153838Sdfr
65153838Sdfr/*
66153838Sdfr * Print list item.  The 'selected' parameter is true if 'choice' is the
67153838Sdfr * current item.  That one is colored differently from the other items.
68153838Sdfr */
69153838Sdfrstatic void
70153838Sdfrprint_item(WINDOW *win,
71153838Sdfr	   DIALOG_LISTITEM * item,
72153838Sdfr	   const char *states,
73153838Sdfr	   int choice,
74153838Sdfr	   int selected)
75153838Sdfr{
76153838Sdfr    chtype save = getattrs(win);
77153838Sdfr    int i;
78153838Sdfr    chtype attr = A_NORMAL;
79153838Sdfr    const int *cols;
80153838Sdfr    const int *indx;
81153838Sdfr    int limit;
82153838Sdfr
83153838Sdfr    /* Clear 'residue' of last item */
84153838Sdfr    wattrset(win, menubox_attr);
85153838Sdfr    (void) wmove(win, choice, 0);
86153838Sdfr    for (i = 0; i < list_width; i++)
87153838Sdfr	(void) waddch(win, ' ');
88153838Sdfr
89153838Sdfr    (void) wmove(win, choice, check_x);
90153838Sdfr    wattrset(win, selected ? check_selected_attr : check_attr);
91153838Sdfr    (void) wprintw(win,
92153838Sdfr		   (checkflag == FLAG_CHECK) ? "[%c]" : "(%c)",
93153838Sdfr		   states[item->state]);
94153838Sdfr    wattrset(win, menubox_attr);
95153838Sdfr    (void) waddch(win, ' ');
96153838Sdfr
97153838Sdfr    if (strlen(item->name) != 0) {
98153838Sdfr
99153838Sdfr	indx = dlg_index_wchars(item->name);
100153838Sdfr
101153838Sdfr	wattrset(win, selected ? tag_key_selected_attr : tag_key_attr);
102153838Sdfr	(void) waddnstr(win, item->name, indx[1]);
103153838Sdfr
104153838Sdfr	if ((int) strlen(item->name) > indx[1]) {
105153838Sdfr	    limit = dlg_limit_columns(item->name, (item_x - check_x - 6), 1);
106153838Sdfr	    if (limit > 1) {
107153838Sdfr		wattrset(win, selected ? tag_selected_attr : tag_attr);
108153838Sdfr		(void) waddnstr(win,
109153838Sdfr				item->name + indx[1],
110153838Sdfr				indx[limit] - indx[1]);
111153838Sdfr	    }
112153838Sdfr	}
113153838Sdfr    }
114153838Sdfr
115153838Sdfr    if (strlen(item->text) != 0) {
116153838Sdfr	cols = dlg_index_columns(item->text);
117153838Sdfr	limit = dlg_limit_columns(item->text, (getmaxx(win) - item_x + 1), 0);
118153838Sdfr
119153838Sdfr	if (limit > 0) {
120153838Sdfr	    (void) wmove(win, choice, item_x);
121153838Sdfr	    wattrset(win, selected ? item_selected_attr : item_attr);
122153838Sdfr	    dlg_print_text(win, item->text, cols[limit], &attr);
123153838Sdfr	}
124153838Sdfr    }
125153838Sdfr
126153838Sdfr    if (selected) {
127153838Sdfr	dlg_item_help(item->help);
128153838Sdfr    }
129153838Sdfr    wattrset(win, save);
130153838Sdfr}
131153838Sdfr
132153838Sdfr/*
133153838Sdfr * This is an alternate interface to 'checklist' which allows the application
134153838Sdfr * to read the list item states back directly without putting them in the
135153838Sdfr * output buffer.  It also provides for more than two states over which the
136153838Sdfr * check/radio box can display.
137153838Sdfr */
138153838Sdfrint
139153838Sdfrdlg_checklist(const char *title,
140153838Sdfr	      const char *cprompt,
141153838Sdfr	      int height,
142153838Sdfr	      int width,
143153838Sdfr	      int list_height,
144153838Sdfr	      int item_no,
145153838Sdfr	      DIALOG_LISTITEM * items,
146153838Sdfr	      const char *states,
147153838Sdfr	      int flag,
148153838Sdfr	      int *current_item)
149153838Sdfr{
150153838Sdfr    /* *INDENT-OFF* */
151153838Sdfr    static DLG_KEYS_BINDING binding[] = {
152153838Sdfr	ENTERKEY_BINDINGS,
153153838Sdfr	DLG_KEYS_DATA( DLGK_FIELD_NEXT, KEY_RIGHT ),
154153838Sdfr	DLG_KEYS_DATA( DLGK_FIELD_NEXT, TAB ),
155153838Sdfr	DLG_KEYS_DATA( DLGK_FIELD_PREV, KEY_BTAB ),
156153838Sdfr	DLG_KEYS_DATA( DLGK_FIELD_PREV, KEY_LEFT ),
157153838Sdfr	DLG_KEYS_DATA( DLGK_ITEM_FIRST, KEY_HOME ),
158153838Sdfr	DLG_KEYS_DATA( DLGK_ITEM_LAST,	KEY_END ),
159153838Sdfr	DLG_KEYS_DATA( DLGK_ITEM_LAST,	KEY_LL ),
160153838Sdfr	DLG_KEYS_DATA( DLGK_ITEM_NEXT,	'+' ),
161153838Sdfr	DLG_KEYS_DATA( DLGK_ITEM_NEXT,	KEY_DOWN ),
162153838Sdfr	DLG_KEYS_DATA( DLGK_ITEM_NEXT,  CHR_NEXT ),
163153838Sdfr	DLG_KEYS_DATA( DLGK_ITEM_PREV,	'-' ),
164153838Sdfr	DLG_KEYS_DATA( DLGK_ITEM_PREV,	KEY_UP ),
165153838Sdfr	DLG_KEYS_DATA( DLGK_ITEM_PREV,  CHR_PREVIOUS ),
166153838Sdfr	DLG_KEYS_DATA( DLGK_PAGE_NEXT,	KEY_NPAGE ),
167153838Sdfr	DLG_KEYS_DATA( DLGK_PAGE_NEXT,	DLGK_MOUSE(KEY_NPAGE) ),
168153838Sdfr	DLG_KEYS_DATA( DLGK_PAGE_PREV,	KEY_PPAGE ),
169153838Sdfr	DLG_KEYS_DATA( DLGK_PAGE_PREV,	DLGK_MOUSE(KEY_PPAGE) ),
170153838Sdfr	END_KEYS_BINDING
171153838Sdfr    };
172153838Sdfr    /* *INDENT-ON* */
173153838Sdfr
174153838Sdfr#ifdef KEY_RESIZE
175153838Sdfr    int old_height = height;
176153838Sdfr    int old_width = width;
177153838Sdfr#endif
178153838Sdfr    int i, j, key2, found, x, y, cur_x, cur_y, box_x, box_y;
179153838Sdfr    int key = 0, fkey;
180153838Sdfr    int button = dialog_state.visit_items ? -1 : dlg_defaultno_button();
181153838Sdfr    int choice = dlg_default_listitem(items);
182153838Sdfr    int scrollamt = 0;
183153838Sdfr    int max_choice;
184153838Sdfr    int was_mouse;
185153838Sdfr    int use_height;
186153838Sdfr    int use_width, name_width, text_width;
187153838Sdfr    int result = DLG_EXIT_UNKNOWN;
188153838Sdfr    int num_states;
189153838Sdfr    WINDOW *dialog, *list;
190153838Sdfr    char *prompt = dlg_strclone(cprompt);
191153838Sdfr    const char **buttons = dlg_ok_labels();
192153838Sdfr
193153838Sdfr    dlg_does_output();
194153838Sdfr    dlg_tab_correct_str(prompt);
195153838Sdfr
196153838Sdfr#ifdef KEY_RESIZE
197153838Sdfr  retry:
198153838Sdfr#endif
199153838Sdfr
200153838Sdfr    use_height = list_height;
201153838Sdfr    if (use_height == 0) {
202153838Sdfr	use_width = dlg_calc_list_width(item_no, items) + 10;
203153838Sdfr	/* calculate height without items (4) */
204153838Sdfr	dlg_auto_size(title, prompt, &height, &width, MIN_HIGH, MAX(26, use_width));
205153838Sdfr	dlg_calc_listh(&height, &use_height, item_no);
206153838Sdfr    } else {
207153838Sdfr	dlg_auto_size(title, prompt, &height, &width, MIN_HIGH + use_height, 26);
208153838Sdfr    }
209153838Sdfr    dlg_button_layout(buttons, &width);
210153838Sdfr    dlg_print_size(height, width);
211153838Sdfr    dlg_ctl_size(height, width);
212153838Sdfr
213153838Sdfr    /* we need at least two states */
214153838Sdfr    if (states == 0 || strlen(states) < 2)
215153838Sdfr	states = " *";
216153838Sdfr    num_states = (int) strlen(states);
217153838Sdfr
218153838Sdfr    checkflag = flag;
219153838Sdfr
220153838Sdfr    x = dlg_box_x_ordinate(width);
221153838Sdfr    y = dlg_box_y_ordinate(height);
222153838Sdfr
223153838Sdfr    dialog = dlg_new_window(height, width, y, x);
224153838Sdfr    dlg_register_window(dialog, "checklist", binding);
225153838Sdfr    dlg_register_buttons(dialog, "checklist", buttons);
226153838Sdfr
227153838Sdfr    dlg_mouse_setbase(x, y);
228153838Sdfr
229153838Sdfr    dlg_draw_box(dialog, 0, 0, height, width, dialog_attr, border_attr);
230153838Sdfr    dlg_draw_bottom_box(dialog);
231153838Sdfr    dlg_draw_title(dialog, title);
232153838Sdfr
233153838Sdfr    wattrset(dialog, dialog_attr);
234153838Sdfr    dlg_print_autowrap(dialog, prompt, height, width);
235153838Sdfr
236153838Sdfr    list_width = width - 6;
237153838Sdfr    getyx(dialog, cur_y, cur_x);
238153838Sdfr    box_y = cur_y + 1;
239153838Sdfr    box_x = (width - list_width) / 2 - 1;
240153838Sdfr
241153838Sdfr    /*
242153838Sdfr     * After displaying the prompt, we know how much space we really have.
243153838Sdfr     * Limit the list to avoid overwriting the ok-button.
244153838Sdfr     */
245153838Sdfr    if (use_height + MIN_HIGH > height - cur_y)
246153838Sdfr	use_height = height - MIN_HIGH - cur_y;
247153838Sdfr    if (use_height <= 0)
248153838Sdfr	use_height = 1;
249153838Sdfr
250153838Sdfr    max_choice = MIN(use_height, item_no);
251153838Sdfr
252153838Sdfr    /* create new window for the list */
253153838Sdfr    list = dlg_sub_window(dialog, use_height, list_width,
254			  y + box_y + 1, x + box_x + 1);
255
256    /* draw a box around the list items */
257    dlg_draw_box(dialog, box_y, box_x,
258		 use_height + 2 * MARGIN,
259		 list_width + 2 * MARGIN,
260		 menubox_border_attr, menubox_attr);
261
262    text_width = 0;
263    name_width = 0;
264    /* Find length of longest item to center checklist */
265    for (i = 0; i < item_no; i++) {
266	text_width = MAX(text_width, dlg_count_columns(items[i].text));
267	name_width = MAX(name_width, dlg_count_columns(items[i].name));
268    }
269
270    /* If the name+text is wider than the list is allowed, then truncate
271     * one or both of them.  If the name is no wider than 1/4 of the list,
272     * leave it intact.
273     */
274    use_width = (list_width - 6);
275    if (text_width + name_width > use_width) {
276	int need = (int) (0.25 * use_width);
277	if (name_width > need) {
278	    int want = (int) (use_width * ((double) name_width) /
279			      (text_width + name_width));
280	    name_width = (want > need) ? want : need;
281	}
282	text_width = use_width - name_width;
283    }
284
285    check_x = (use_width - (text_width + name_width)) / 2;
286    item_x = name_width + check_x + 6;
287
288    /* ensure we are scrolled to show the current choice */
289    if (choice >= (max_choice + scrollamt)) {
290	scrollamt = choice - max_choice + 1;
291	choice = max_choice - 1;
292    }
293    /* Print the list */
294    for (i = 0; i < max_choice; i++)
295	print_item(list,
296		   &items[i + scrollamt],
297		   states,
298		   i, i == choice);
299    (void) wnoutrefresh(list);
300
301    /* register the new window, along with its borders */
302    dlg_mouse_mkbigregion(box_y + 1, box_x, use_height, list_width + 2,
303			  KEY_MAX, 1, 1, 1 /* by lines */ );
304
305    print_arrows(dialog,
306		 box_x, box_y,
307		 scrollamt, max_choice, item_no, use_height);
308
309    dlg_draw_buttons(dialog, height - 2, 0, buttons, button, FALSE, width);
310
311    while (result == DLG_EXIT_UNKNOWN) {
312	if (button < 0)		/* --visit-items */
313	    wmove(dialog, box_y + choice + 1, box_x + check_x + 2);
314
315	key = dlg_mouse_wgetch(dialog, &fkey);
316	if (dlg_result_key(key, fkey, &result))
317	    break;
318
319	was_mouse = (fkey && is_DLGK_MOUSE(key));
320	if (was_mouse)
321	    key -= M_EVENT;
322
323	if (was_mouse && (key >= KEY_MAX)) {
324	    getyx(dialog, cur_y, cur_x);
325	    i = (key - KEY_MAX);
326	    if (i < max_choice) {
327		/* De-highlight current item */
328		print_item(list,
329			   &items[scrollamt + choice],
330			   states,
331			   choice, FALSE);
332		/* Highlight new item */
333		choice = (key - KEY_MAX);
334		print_item(list,
335			   &items[scrollamt + choice],
336			   states,
337			   choice, TRUE);
338		(void) wnoutrefresh(list);
339		(void) wmove(dialog, cur_y, cur_x);
340
341		key = ' ';	/* force the selected item to toggle */
342	    } else {
343		beep();
344		continue;
345	    }
346	    fkey = FALSE;
347	} else if (was_mouse && key >= KEY_MIN) {
348	    key = dlg_lookup_key(dialog, key, &fkey);
349	}
350
351	/*
352	 * A space toggles the item status.  We handle either a checklist
353	 * (any number of items can be selected) or radio list (zero or one
354	 * items can be selected).
355	 */
356	if (key == ' ') {
357	    int current = scrollamt + choice;
358	    int next = items[current].state + 1;
359
360	    if (next >= num_states)
361		next = 0;
362
363	    getyx(dialog, cur_y, cur_x);
364	    if (flag == FLAG_CHECK) {	/* checklist? */
365		items[current].state = next;
366		print_item(list,
367			   &items[scrollamt + choice],
368			   states,
369			   choice, TRUE);
370	    } else {		/* radiolist */
371		for (i = 0; i < item_no; i++) {
372		    if (i != current) {
373			items[i].state = 0;
374		    }
375		}
376		if (items[current].state) {
377		    items[current].state = next ? next : 1;
378		    print_item(list,
379			       &items[current],
380			       states,
381			       choice, TRUE);
382		} else {
383		    items[current].state = 1;
384		    for (i = 0; i < max_choice; i++)
385			print_item(list,
386				   &items[scrollamt + i],
387				   states,
388				   i, i == choice);
389		}
390	    }
391	    (void) wnoutrefresh(list);
392	    (void) wmove(dialog, cur_y, cur_x);
393	    wrefresh(dialog);
394	    continue;		/* wait for another key press */
395	}
396
397	/*
398	 * Check if key pressed matches first character of any item tag in
399	 * list.  If there is more than one match, we will cycle through
400	 * each one as the same key is pressed repeatedly.
401	 */
402	found = FALSE;
403	if (!fkey) {
404	    if (button < 0 || !dialog_state.visit_items) {
405		for (j = scrollamt + choice + 1; j < item_no; j++) {
406		    if (dlg_match_char(dlg_last_getc(), items[j].name)) {
407			found = TRUE;
408			i = j - scrollamt;
409			break;
410		    }
411		}
412		if (!found) {
413		    for (j = 0; j <= scrollamt + choice; j++) {
414			if (dlg_match_char(dlg_last_getc(), items[j].name)) {
415			    found = TRUE;
416			    i = j - scrollamt;
417			    break;
418			}
419		    }
420		}
421		if (found)
422		    dlg_flush_getc();
423	    } else if ((j = dlg_char_to_button(key, buttons)) >= 0) {
424		button = j;
425		ungetch('\n');
426		continue;
427	    }
428	}
429
430	/*
431	 * A single digit (1-9) positions the selection to that line in the
432	 * current screen.
433	 */
434	if (!found
435	    && (key <= '9')
436	    && (key > '0')
437	    && (key - '1' < max_choice)) {
438	    found = TRUE;
439	    i = key - '1';
440	}
441
442	if (!found) {
443	    if (fkey) {
444		found = TRUE;
445		switch (key) {
446		case DLGK_ITEM_FIRST:
447		    i = -scrollamt;
448		    break;
449		case DLGK_ITEM_LAST:
450		    i = item_no - 1 - scrollamt;
451		    break;
452		case DLGK_PAGE_PREV:
453		    if (choice)
454			i = 0;
455		    else if (scrollamt != 0)
456			i = -MIN(scrollamt, max_choice);
457		    else
458			continue;
459		    break;
460		case DLGK_PAGE_NEXT:
461		    i = MIN(choice + max_choice, item_no - scrollamt - 1);
462		    break;
463		case DLGK_ITEM_PREV:
464		    i = choice - 1;
465		    if (choice == 0 && scrollamt == 0)
466			continue;
467		    break;
468		case DLGK_ITEM_NEXT:
469		    i = choice + 1;
470		    if (scrollamt + choice >= item_no - 1)
471			continue;
472		    break;
473		default:
474		    found = FALSE;
475		    break;
476		}
477	    }
478	}
479
480	if (found) {
481	    if (i != choice) {
482		getyx(dialog, cur_y, cur_x);
483		if (i < 0 || i >= max_choice) {
484#if defined(NCURSES_VERSION_MAJOR) && NCURSES_VERSION_MAJOR < 5
485		    /*
486		     * Using wscrl to assist ncurses scrolling is not needed
487		     * in version 5.x
488		     */
489		    if (i == -1) {
490			if (use_height > 1) {
491			    /* De-highlight current first item */
492			    print_item(list,
493				       &items[scrollamt],
494				       states,
495				       0, FALSE);
496			    scrollok(list, TRUE);
497			    wscrl(list, -1);
498			    scrollok(list, FALSE);
499			}
500			scrollamt--;
501			print_item(list,
502				   &items[scrollamt],
503				   states,
504				   0, TRUE);
505		    } else if (i == max_choice) {
506			if (use_height > 1) {
507			    /* De-highlight current last item before scrolling up */
508			    print_item(list,
509				       &items[scrollamt + max_choice - 1],
510				       states,
511				       max_choice - 1, FALSE);
512			    scrollok(list, TRUE);
513			    wscrl(list, 1);
514			    scrollok(list, FALSE);
515			}
516			scrollamt++;
517			print_item(list,
518				   &items[scrollamt + max_choice - 1],
519				   states,
520				   max_choice - 1, TRUE);
521		    } else
522#endif
523		    {
524			if (i < 0) {
525			    scrollamt += i;
526			    choice = 0;
527			} else {
528			    choice = max_choice - 1;
529			    scrollamt += (i - max_choice + 1);
530			}
531			for (i = 0; i < max_choice; i++) {
532			    print_item(list,
533				       &items[scrollamt + i],
534				       states,
535				       i, i == choice);
536			}
537		    }
538		    (void) wnoutrefresh(list);
539		    print_arrows(dialog,
540				 box_x, box_y,
541				 scrollamt, max_choice, item_no, use_height);
542		} else {
543		    /* De-highlight current item */
544		    print_item(list,
545			       &items[scrollamt + choice],
546			       states,
547			       choice, FALSE);
548		    /* Highlight new item */
549		    choice = i;
550		    print_item(list,
551			       &items[scrollamt + choice],
552			       states,
553			       choice, TRUE);
554		    (void) wnoutrefresh(list);
555		    print_arrows(dialog,
556				 box_x, box_y,
557				 scrollamt, max_choice, item_no, use_height);
558		    (void) wmove(dialog, cur_y, cur_x);
559		    wrefresh(dialog);
560		}
561	    }
562	    continue;		/* wait for another key press */
563	}
564
565	if (fkey) {
566	    switch (key) {
567	    case DLGK_ENTER:
568		result = dlg_ok_buttoncode(button);
569		break;
570	    case DLGK_FIELD_PREV:
571		button = dlg_prev_button(buttons, button);
572		dlg_draw_buttons(dialog, height - 2, 0, buttons, button,
573				 FALSE, width);
574		break;
575	    case DLGK_FIELD_NEXT:
576		button = dlg_next_button(buttons, button);
577		dlg_draw_buttons(dialog, height - 2, 0, buttons, button,
578				 FALSE, width);
579		break;
580#ifdef KEY_RESIZE
581	    case KEY_RESIZE:
582		/* reset data */
583		height = old_height;
584		width = old_width;
585		/* repaint */
586		dlg_clear();
587		dlg_del_window(dialog);
588		refresh();
589		dlg_mouse_free_regions();
590		goto retry;
591#endif
592	    default:
593		if (was_mouse) {
594		    if ((key2 = dlg_ok_buttoncode(key)) >= 0) {
595			result = key2;
596			break;
597		    }
598		    beep();
599		}
600	    }
601	} else {
602	    beep();
603	}
604    }
605
606    dlg_del_window(dialog);
607    dlg_mouse_free_regions();
608    free(prompt);
609    *current_item = (scrollamt + choice);
610    return result;
611}
612
613/*
614 * Display a dialog box with a list of options that can be turned on or off
615 * The `flag' parameter is used to select between radiolist and checklist.
616 */
617int
618dialog_checklist(const char *title,
619		 const char *cprompt,
620		 int height,
621		 int width,
622		 int list_height,
623		 int item_no,
624		 char **items,
625		 int flag)
626{
627    int result;
628    int i;
629    DIALOG_LISTITEM *listitems;
630    bool separate_output = ((flag == FLAG_CHECK)
631			    && (dialog_vars.separate_output));
632    bool show_status = FALSE;
633    int current = 0;
634
635    listitems = dlg_calloc(DIALOG_LISTITEM, (size_t) item_no + 1);
636    assert_ptr(listitems, "dialog_checklist");
637
638    for (i = 0; i < item_no; ++i) {
639	listitems[i].name = ItemName(i);
640	listitems[i].text = ItemText(i);
641	listitems[i].help = ((dialog_vars.item_help)
642			     ? ItemHelp(i)
643			     : dlg_strempty());
644	listitems[i].state = !dlg_strcmp(ItemStatus(i), "on");
645    }
646    dlg_align_columns(&listitems[0].text, sizeof(DIALOG_LISTITEM), item_no);
647
648    result = dlg_checklist(title,
649			   cprompt,
650			   height,
651			   width,
652			   list_height,
653			   item_no,
654			   listitems,
655			   NULL,
656			   flag,
657			   &current);
658
659    switch (result) {
660    case DLG_EXIT_OK:		/* FALLTHRU */
661    case DLG_EXIT_EXTRA:
662	show_status = TRUE;
663	break;
664    case DLG_EXIT_HELP:
665	dlg_add_result("HELP ");
666	show_status = dialog_vars.help_status;
667	if (USE_ITEM_HELP(listitems[current].help)) {
668	    if (show_status) {
669		if (separate_output) {
670		    dlg_add_string(listitems[current].help);
671		    dlg_add_separator();
672		} else {
673		    dlg_add_quoted(listitems[current].help);
674		}
675	    } else {
676		dlg_add_string(listitems[current].help);
677	    }
678	    result = DLG_EXIT_ITEM_HELP;
679	} else {
680	    if (show_status) {
681		if (separate_output) {
682		    dlg_add_string(listitems[current].name);
683		    dlg_add_separator();
684		} else {
685		    dlg_add_quoted(listitems[current].name);
686		}
687	    } else {
688		dlg_add_string(listitems[current].name);
689	    }
690	}
691	break;
692    }
693
694    if (show_status) {
695	for (i = 0; i < item_no; i++) {
696	    if (listitems[i].state) {
697		if (separate_output) {
698		    dlg_add_string(listitems[i].name);
699		    dlg_add_separator();
700		} else {
701		    if (dlg_need_separator())
702			dlg_add_separator();
703		    dlg_add_string(listitems[i].name);
704		}
705	    }
706	}
707    }
708
709    dlg_free_columns(&listitems[0].text, sizeof(DIALOG_LISTITEM), item_no);
710    free(listitems);
711    return result;
712}
713