2 * $Id: checklist.c,v 1.154 2015/01/25 23:53:06 tom Exp $
4 * checklist.c -- implements the checklist box
6 * Copyright 2000-2013,2015 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 License, version 2.1
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)
25 * Stuart Herbert - S.Herbert@sheffield.ac.uk: radiolist extension
26 * Alessandro Rubini - rubini@ipvvis.unipv.it: merged the two
32 #define MIN_HIGH (1 + (5 * MARGIN))
35 /* the outer-window */
44 /* the inner-window */
46 DIALOG_LISTITEM
*items
;
52 * Print list item. The 'selected' parameter is true if 'choice' is the
53 * current item. That one is colored differently from the other items.
56 print_item(ALL_DATA
* data
,
58 DIALOG_LISTITEM
* item
,
63 chtype save
= dlg_get_attrs(win
);
65 bool both
= (!dialog_vars
.no_tags
&& !dialog_vars
.no_items
);
67 int climit
= (getmaxx(win
) - data
->check_x
+ 1);
68 const char *show
= (dialog_vars
.no_items
72 /* Clear 'residue' of last item */
73 (void) wattrset(win
, menubox_attr
);
74 (void) wmove(win
, choice
, 0);
75 for (i
= 0; i
< data
->use_width
; i
++)
76 (void) waddch(win
, ' ');
78 (void) wmove(win
, choice
, data
->check_x
);
79 (void) wattrset(win
, selected
? check_selected_attr
: check_attr
);
81 (data
->checkflag
== FLAG_CHECK
) ? "[%c]" : "(%c)",
83 (void) wattrset(win
, menubox_attr
);
84 (void) waddch(win
, ' ');
87 dlg_print_listitem(win
, item
->name
, climit
, first
, selected
);
91 (void) wmove(win
, choice
, data
->item_x
);
92 dlg_print_listitem(win
, show
, climit
, first
, selected
);
95 dlg_item_help(item
->help
);
97 (void) wattrset(win
, save
);
101 print_list(ALL_DATA
* data
, int choice
, int scrollamt
, int max_choice
)
106 getyx(data
->dialog
, cur_y
, cur_x
);
107 for (i
= 0; i
< max_choice
; i
++) {
110 &data
->items
[i
+ scrollamt
],
114 (void) wnoutrefresh(data
->list
);
116 dlg_draw_scrollbar(data
->dialog
,
119 (long) (scrollamt
+ max_choice
),
120 (long) (data
->item_no
),
121 data
->box_x
+ data
->check_x
,
122 data
->box_x
+ data
->use_width
,
124 data
->box_y
+ data
->use_height
+ 1,
125 menubox_border2_attr
,
126 menubox_border_attr
);
128 (void) wmove(data
->dialog
, cur_y
, cur_x
);
132 check_hotkey(DIALOG_LISTITEM
* items
, int choice
)
136 if (dlg_match_char(dlg_last_getc(),
139 : items
[choice
].name
))) {
146 * This is an alternate interface to 'checklist' which allows the application
147 * to read the list item states back directly without putting them in the
148 * output buffer. It also provides for more than two states over which the
149 * check/radio box can display.
152 dlg_checklist(const char *title
,
158 DIALOG_LISTITEM
* items
,
164 static DLG_KEYS_BINDING binding
[] = {
167 DLG_KEYS_DATA( DLGK_FIELD_NEXT
, KEY_RIGHT
),
168 DLG_KEYS_DATA( DLGK_FIELD_NEXT
, TAB
),
169 DLG_KEYS_DATA( DLGK_FIELD_PREV
, KEY_BTAB
),
170 DLG_KEYS_DATA( DLGK_FIELD_PREV
, KEY_LEFT
),
171 DLG_KEYS_DATA( DLGK_ITEM_FIRST
, KEY_HOME
),
172 DLG_KEYS_DATA( DLGK_ITEM_LAST
, KEY_END
),
173 DLG_KEYS_DATA( DLGK_ITEM_LAST
, KEY_LL
),
174 DLG_KEYS_DATA( DLGK_ITEM_NEXT
, '+' ),
175 DLG_KEYS_DATA( DLGK_ITEM_NEXT
, KEY_DOWN
),
176 DLG_KEYS_DATA( DLGK_ITEM_NEXT
, CHR_NEXT
),
177 DLG_KEYS_DATA( DLGK_ITEM_PREV
, '-' ),
178 DLG_KEYS_DATA( DLGK_ITEM_PREV
, KEY_UP
),
179 DLG_KEYS_DATA( DLGK_ITEM_PREV
, CHR_PREVIOUS
),
180 DLG_KEYS_DATA( DLGK_PAGE_NEXT
, KEY_NPAGE
),
181 DLG_KEYS_DATA( DLGK_PAGE_NEXT
, DLGK_MOUSE(KEY_NPAGE
) ),
182 DLG_KEYS_DATA( DLGK_PAGE_PREV
, KEY_PPAGE
),
183 DLG_KEYS_DATA( DLGK_PAGE_PREV
, DLGK_MOUSE(KEY_PPAGE
) ),
189 int old_height
= height
;
190 int old_width
= width
;
193 int i
, j
, key2
, found
, x
, y
, cur_x
, cur_y
;
195 int button
= dialog_state
.visit_items
? -1 : dlg_default_button();
196 int choice
= dlg_default_listitem(items
);
200 int use_width
, list_width
, name_width
, text_width
;
201 int result
= DLG_EXIT_UNKNOWN
;
204 char *prompt
= dlg_strclone(cprompt
);
205 const char **buttons
= dlg_ok_labels();
206 const char *widget_name
;
208 dialog_state
.plain_buttons
= TRUE
;
210 memset(&all
, 0, sizeof(all
));
212 all
.item_no
= item_no
;
215 dlg_tab_correct_str(prompt
);
218 * If this is a radiobutton list, ensure that no more than one item is
219 * selected initially. Allow none to be selected, since some users may
220 * wish to provide this flavor.
222 if (flag
== FLAG_RADIO
) {
225 for (i
= 0; i
< item_no
; i
++) {
226 if (items
[i
].state
) {
234 widget_name
= "radiolist";
236 widget_name
= "checklist";
242 all
.use_height
= list_height
;
243 use_width
= dlg_calc_list_width(item_no
, items
) + 10;
244 use_width
= MAX(26, use_width
);
245 if (all
.use_height
== 0) {
246 /* calculate height without items (4) */
247 dlg_auto_size(title
, prompt
, &height
, &width
, MIN_HIGH
, use_width
);
248 dlg_calc_listh(&height
, &all
.use_height
, item_no
);
250 dlg_auto_size(title
, prompt
,
252 MIN_HIGH
+ all
.use_height
, use_width
);
254 dlg_button_layout(buttons
, &width
);
255 dlg_print_size(height
, width
);
256 dlg_ctl_size(height
, width
);
258 /* we need at least two states */
259 if (states
== 0 || strlen(states
) < 2)
261 num_states
= (int) strlen(states
);
264 all
.checkflag
= flag
;
266 x
= dlg_box_x_ordinate(width
);
267 y
= dlg_box_y_ordinate(height
);
269 dialog
= dlg_new_window(height
, width
, y
, x
);
271 dlg_register_window(dialog
, widget_name
, binding
);
272 dlg_register_buttons(dialog
, widget_name
, buttons
);
274 dlg_mouse_setbase(x
, y
);
276 dlg_draw_box2(dialog
, 0, 0, height
, width
, dialog_attr
, border_attr
, border2_attr
);
277 dlg_draw_bottom_box2(dialog
, border_attr
, border2_attr
, dialog_attr
);
278 dlg_draw_title(dialog
, title
);
280 (void) wattrset(dialog
, dialog_attr
);
281 dlg_print_autowrap(dialog
, prompt
, height
, width
);
283 all
.use_width
= width
- 6;
284 getyx(dialog
, cur_y
, cur_x
);
285 all
.box_y
= cur_y
+ 1;
286 all
.box_x
= (width
- all
.use_width
) / 2 - 1;
289 * After displaying the prompt, we know how much space we really have.
290 * Limit the list to avoid overwriting the ok-button.
292 if (all
.use_height
+ MIN_HIGH
> height
- cur_y
)
293 all
.use_height
= height
- MIN_HIGH
- cur_y
;
294 if (all
.use_height
<= 0)
297 max_choice
= MIN(all
.use_height
, item_no
);
298 max_choice
= MAX(max_choice
, 1);
300 /* create new window for the list */
301 all
.list
= dlg_sub_window(dialog
, all
.use_height
, all
.use_width
,
302 y
+ all
.box_y
+ 1, x
+ all
.box_x
+ 1);
304 /* draw a box around the list items */
305 dlg_draw_box(dialog
, all
.box_y
, all
.box_x
,
306 all
.use_height
+ 2 * MARGIN
,
307 all
.use_width
+ 2 * MARGIN
,
308 menubox_border_attr
, menubox_border2_attr
);
312 /* Find length of longest item to center checklist */
313 for (i
= 0; i
< item_no
; i
++) {
314 text_width
= MAX(text_width
, dlg_count_columns(items
[i
].text
));
315 name_width
= MAX(name_width
, dlg_count_columns(items
[i
].name
));
318 /* If the name+text is wider than the list is allowed, then truncate
319 * one or both of them. If the name is no wider than 1/4 of the list,
322 use_width
= (all
.use_width
- 6);
323 if (dialog_vars
.no_tags
) {
324 list_width
= MIN(all
.use_width
, text_width
);
325 } else if (dialog_vars
.no_items
) {
326 list_width
= MIN(all
.use_width
, name_width
);
331 && text_width
+ name_width
> use_width
) {
332 int need
= (int) (0.25 * use_width
);
333 if (name_width
> need
) {
334 int want
= (int) (use_width
* ((double) name_width
) /
335 (text_width
+ name_width
));
336 name_width
= (want
> need
) ? want
: need
;
338 text_width
= use_width
- name_width
;
340 list_width
= (text_width
+ name_width
);
343 all
.check_x
= (use_width
- list_width
) / 2;
344 all
.item_x
= ((dialog_vars
.no_tags
346 : (dialog_vars
.no_items
351 /* ensure we are scrolled to show the current choice */
352 scrollamt
= MIN(scrollamt
, max_choice
+ item_no
- 1);
353 if (choice
>= (max_choice
+ scrollamt
- 1)) {
354 scrollamt
= MAX(0, choice
- max_choice
+ 1);
355 choice
= max_choice
- 1;
357 print_list(&all
, choice
, scrollamt
, max_choice
);
359 /* register the new window, along with its borders */
360 dlg_mouse_mkbigregion(all
.box_y
+ 1, all
.box_x
,
361 all
.use_height
, all
.use_width
+ 2,
362 KEY_MAX
, 1, 1, 1 /* by lines */ );
364 dlg_draw_buttons(dialog
, height
- 2, 0, buttons
, button
, FALSE
, width
);
366 dlg_trace_win(dialog
);
367 while (result
== DLG_EXIT_UNKNOWN
) {
368 if (button
< 0) /* --visit-items */
369 wmove(dialog
, all
.box_y
+ choice
+ 1, all
.box_x
+ all
.check_x
+ 2);
371 key
= dlg_mouse_wgetch(dialog
, &fkey
);
372 if (dlg_result_key(key
, fkey
, &result
))
375 was_mouse
= (fkey
&& is_DLGK_MOUSE(key
));
379 if (was_mouse
&& (key
>= KEY_MAX
)) {
380 getyx(dialog
, cur_y
, cur_x
);
382 if (i
< max_choice
) {
383 choice
= (key
- KEY_MAX
);
384 print_list(&all
, choice
, scrollamt
, max_choice
);
386 key
= ' '; /* force the selected item to toggle */
392 } else if (was_mouse
&& key
>= KEY_MIN
) {
393 key
= dlg_lookup_key(dialog
, key
, &fkey
);
397 * A space toggles the item status. We handle either a checklist
398 * (any number of items can be selected) or radio list (zero or one
399 * items can be selected).
402 int current
= scrollamt
+ choice
;
403 int next
= items
[current
].state
+ 1;
405 if (next
>= num_states
)
408 if (flag
== FLAG_CHECK
) { /* checklist? */
409 getyx(dialog
, cur_y
, cur_x
);
410 items
[current
].state
= next
;
411 print_item(&all
, all
.list
,
412 &items
[scrollamt
+ choice
],
415 (void) wnoutrefresh(all
.list
);
416 (void) wmove(dialog
, cur_y
, cur_x
);
417 } else { /* radiolist */
418 for (i
= 0; i
< item_no
; i
++) {
423 if (items
[current
].state
) {
424 getyx(dialog
, cur_y
, cur_x
);
425 items
[current
].state
= next
? next
: 1;
426 print_item(&all
, all
.list
,
430 (void) wnoutrefresh(all
.list
);
431 (void) wmove(dialog
, cur_y
, cur_x
);
433 items
[current
].state
= 1;
434 print_list(&all
, choice
, scrollamt
, max_choice
);
437 continue; /* wait for another key press */
441 * Check if key pressed matches first character of any item tag in
442 * list. If there is more than one match, we will cycle through
443 * each one as the same key is pressed repeatedly.
447 if (button
< 0 || !dialog_state
.visit_items
) {
448 for (j
= scrollamt
+ choice
+ 1; j
< item_no
; j
++) {
449 if (check_hotkey(items
, j
)) {
456 for (j
= 0; j
<= scrollamt
+ choice
; j
++) {
457 if (check_hotkey(items
, j
)) {
466 } else if ((j
= dlg_char_to_button(key
, buttons
)) >= 0) {
474 * A single digit (1-9) positions the selection to that line in the
480 && (key
- '1' < max_choice
)) {
489 case DLGK_ITEM_FIRST
:
493 i
= item_no
- 1 - scrollamt
;
498 else if (scrollamt
!= 0)
499 i
= -MIN(scrollamt
, max_choice
);
504 i
= MIN(choice
+ max_choice
, item_no
- scrollamt
- 1);
508 if (choice
== 0 && scrollamt
== 0)
513 if (scrollamt
+ choice
>= item_no
- 1)
525 getyx(dialog
, cur_y
, cur_x
);
526 if (i
< 0 || i
>= max_choice
) {
531 choice
= max_choice
- 1;
532 scrollamt
+= (i
- max_choice
+ 1);
534 print_list(&all
, choice
, scrollamt
, max_choice
);
537 print_list(&all
, choice
, scrollamt
, max_choice
);
540 continue; /* wait for another key press */
546 result
= dlg_enter_buttoncode(button
);
548 case DLGK_FIELD_PREV
:
549 button
= dlg_prev_button(buttons
, button
);
550 dlg_draw_buttons(dialog
, height
- 2, 0, buttons
, button
,
553 case DLGK_FIELD_NEXT
:
554 button
= dlg_next_button(buttons
, button
);
555 dlg_draw_buttons(dialog
, height
- 2, 0, buttons
, button
,
565 dlg_del_window(dialog
);
567 dlg_mouse_free_regions();
572 if ((key2
= dlg_ok_buttoncode(key
)) >= 0) {
584 dlg_del_window(dialog
);
585 dlg_mouse_free_regions();
587 *current_item
= (scrollamt
+ choice
);
592 * Display a dialog box with a list of options that can be turned on or off
593 * The `flag' parameter is used to select between radiolist and checklist.
596 dialog_checklist(const char *title
,
607 DIALOG_LISTITEM
*listitems
;
608 bool separate_output
= ((flag
== FLAG_CHECK
)
609 && (dialog_vars
.separate_output
));
610 bool show_status
= FALSE
;
614 listitems
= dlg_calloc(DIALOG_LISTITEM
, (size_t) item_no
+ 1);
615 assert_ptr(listitems
, "dialog_checklist");
617 for (i
= j
= 0; i
< item_no
; ++i
) {
618 listitems
[i
].name
= items
[j
++];
619 listitems
[i
].text
= (dialog_vars
.no_items
622 listitems
[i
].state
= !dlg_strcmp(items
[j
++], "on");
623 listitems
[i
].help
= ((dialog_vars
.item_help
)
627 dlg_align_columns(&listitems
[0].text
, (int) sizeof(DIALOG_LISTITEM
), item_no
);
629 result
= dlg_checklist(title
,
641 case DLG_EXIT_OK
: /* FALLTHRU */
646 dlg_add_help_listitem(&result
, &help_result
, &listitems
[current
]);
647 if ((show_status
= dialog_vars
.help_status
)) {
648 if (separate_output
) {
649 dlg_add_string(help_result
);
652 dlg_add_quoted(help_result
);
655 dlg_add_string(help_result
);
661 for (i
= 0; i
< item_no
; i
++) {
662 if (listitems
[i
].state
) {
663 if (separate_output
) {
664 dlg_add_string(listitems
[i
].name
);
667 if (dlg_need_separator())
669 if (flag
== FLAG_CHECK
)
670 dlg_add_quoted(listitems
[i
].name
);
672 dlg_add_string(listitems
[i
].name
);
676 dlg_add_last_key(separate_output
);
679 dlg_free_columns(&listitems
[0].text
, (int) sizeof(DIALOG_LISTITEM
), item_no
);