2 * $Id: treeview.c,v 1.25 2015/01/25 23:54:02 tom Exp $
4 * treeview.c -- implements the treeview dialog
6 * Copyright 2012-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.
28 #define MIN_HIGH (1 + (5 * MARGIN))
31 /* the outer-window */
40 /* the inner-window */
42 DIALOG_LISTITEM
*items
;
49 * Print list item. The 'selected' parameter is true if 'choice' is the
50 * current item. That one is colored differently from the other items.
53 print_item(ALL_DATA
* data
,
54 DIALOG_LISTITEM
* item
,
60 WINDOW
*win
= data
->list
;
61 chtype save
= dlg_get_attrs(win
);
64 int climit
= (getmaxx(win
) - data
->check_x
+ 1);
65 const char *show
= (dialog_vars
.no_items
69 /* Clear 'residue' of last item */
70 (void) wattrset(win
, menubox_attr
);
71 (void) wmove(win
, choice
, 0);
72 for (i
= 0; i
< data
->use_width
; i
++)
73 (void) waddch(win
, ' ');
75 (void) wmove(win
, choice
, data
->check_x
);
76 (void) wattrset(win
, selected
? check_selected_attr
: check_attr
);
78 data
->is_check
? "[%c]" : "(%c)",
80 (void) wattrset(win
, menubox_attr
);
82 (void) wattrset(win
, selected
? item_selected_attr
: item_attr
);
83 for (i
= 0; i
< depths
; ++i
) {
85 (void) wmove(win
, choice
, data
->item_x
+ INDENT
* i
);
86 (void) waddch(win
, ACS_VLINE
);
87 for (j
= INDENT
- 1; j
> 0; --j
)
88 (void) waddch(win
, ' ');
90 (void) wmove(win
, choice
, data
->item_x
+ INDENT
* depths
);
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
,
109 getyx(data
->dialog
, cur_y
, cur_x
);
111 for (i
= 0; i
< max_choice
; i
++) {
113 &data
->items
[scrollamt
+ i
],
115 data
->depths
[scrollamt
+ i
],
118 (void) wnoutrefresh(data
->list
);
120 dlg_draw_scrollbar(data
->dialog
,
123 (long) (scrollamt
+ max_choice
),
124 (long) (data
->item_no
),
125 data
->box_x
+ data
->check_x
,
126 data
->box_x
+ data
->use_width
,
128 data
->box_y
+ data
->use_height
+ 1,
129 menubox_border2_attr
,
130 menubox_border_attr
);
132 (void) wmove(data
->dialog
, cur_y
, cur_x
);
136 check_hotkey(DIALOG_LISTITEM
* items
, int choice
)
140 if (dlg_match_char(dlg_last_getc(),
143 : items
[choice
].name
))) {
150 * This is an alternate interface to 'treeview' which allows the application
151 * to read the list item states back directly without putting them in the
155 dlg_treeview(const char *title
,
161 DIALOG_LISTITEM
* items
,
168 static DLG_KEYS_BINDING binding
[] = {
171 DLG_KEYS_DATA( DLGK_FIELD_NEXT
, KEY_RIGHT
),
172 DLG_KEYS_DATA( DLGK_FIELD_NEXT
, TAB
),
173 DLG_KEYS_DATA( DLGK_FIELD_PREV
, KEY_BTAB
),
174 DLG_KEYS_DATA( DLGK_FIELD_PREV
, KEY_LEFT
),
175 DLG_KEYS_DATA( DLGK_ITEM_FIRST
, KEY_HOME
),
176 DLG_KEYS_DATA( DLGK_ITEM_LAST
, KEY_END
),
177 DLG_KEYS_DATA( DLGK_ITEM_LAST
, KEY_LL
),
178 DLG_KEYS_DATA( DLGK_ITEM_NEXT
, '+' ),
179 DLG_KEYS_DATA( DLGK_ITEM_NEXT
, KEY_DOWN
),
180 DLG_KEYS_DATA( DLGK_ITEM_NEXT
, CHR_NEXT
),
181 DLG_KEYS_DATA( DLGK_ITEM_PREV
, '-' ),
182 DLG_KEYS_DATA( DLGK_ITEM_PREV
, KEY_UP
),
183 DLG_KEYS_DATA( DLGK_ITEM_PREV
, CHR_PREVIOUS
),
184 DLG_KEYS_DATA( DLGK_PAGE_NEXT
, KEY_NPAGE
),
185 DLG_KEYS_DATA( DLGK_PAGE_NEXT
, DLGK_MOUSE(KEY_NPAGE
) ),
186 DLG_KEYS_DATA( DLGK_PAGE_PREV
, KEY_PPAGE
),
187 DLG_KEYS_DATA( DLGK_PAGE_PREV
, DLGK_MOUSE(KEY_PPAGE
) ),
193 int old_height
= height
;
194 int old_width
= width
;
197 int i
, j
, key2
, found
, x
, y
, cur_y
, box_x
, box_y
;
199 int button
= dialog_state
.visit_items
? -1 : dlg_default_button();
200 int choice
= dlg_default_listitem(items
);
205 int use_width
, name_width
, text_width
, tree_width
;
206 int result
= DLG_EXIT_UNKNOWN
;
208 WINDOW
*dialog
, *list
;
209 char *prompt
= dlg_strclone(cprompt
);
210 const char **buttons
= dlg_ok_labels();
211 const char *widget_name
;
213 /* we need at least two states */
214 if (states
== 0 || strlen(states
) < 2)
216 num_states
= (int) strlen(states
);
218 dialog_state
.plain_buttons
= TRUE
;
220 memset(&all
, 0, sizeof(all
));
222 all
.item_no
= item_no
;
227 dlg_tab_correct_str(prompt
);
230 * If this is a radiobutton list, ensure that no more than one item is
231 * selected initially. Allow none to be selected, since some users may
232 * wish to provide this flavor.
234 if (flag
== FLAG_RADIO
) {
237 for (i
= 0; i
< item_no
; i
++) {
238 if (items
[i
].state
) {
249 widget_name
= "treeview";
254 use_height
= list_height
;
255 use_width
= dlg_calc_list_width(item_no
, items
) + 10;
256 use_width
= MAX(26, use_width
);
257 if (use_height
== 0) {
258 /* calculate height without items (4) */
259 dlg_auto_size(title
, prompt
, &height
, &width
, MIN_HIGH
, use_width
);
260 dlg_calc_listh(&height
, &use_height
, item_no
);
262 dlg_auto_size(title
, prompt
, &height
, &width
, MIN_HIGH
+ use_height
, use_width
);
264 dlg_button_layout(buttons
, &width
);
265 dlg_print_size(height
, width
);
266 dlg_ctl_size(height
, width
);
268 x
= dlg_box_x_ordinate(width
);
269 y
= dlg_box_y_ordinate(height
);
271 dialog
= dlg_new_window(height
, width
, y
, x
);
272 dlg_register_window(dialog
, widget_name
, binding
);
273 dlg_register_buttons(dialog
, widget_name
, buttons
);
275 dlg_mouse_setbase(x
, y
);
277 dlg_draw_box2(dialog
, 0, 0, height
, width
, dialog_attr
, border_attr
, border2_attr
);
278 dlg_draw_bottom_box2(dialog
, border_attr
, border2_attr
, dialog_attr
);
279 dlg_draw_title(dialog
, title
);
281 (void) wattrset(dialog
, dialog_attr
);
282 dlg_print_autowrap(dialog
, prompt
, height
, width
);
284 all
.use_width
= width
- 4;
285 cur_y
= getcury(dialog
);
287 box_x
= (width
- all
.use_width
) / 2 - 1;
290 * After displaying the prompt, we know how much space we really have.
291 * Limit the list to avoid overwriting the ok-button.
293 if (use_height
+ MIN_HIGH
> height
- cur_y
)
294 use_height
= height
- MIN_HIGH
- cur_y
;
298 max_choice
= MIN(use_height
, item_no
);
300 /* create new window for the list */
301 list
= dlg_sub_window(dialog
, use_height
, all
.use_width
,
302 y
+ box_y
+ 1, x
+ box_x
+ 1);
304 /* draw a box around the list items */
305 dlg_draw_box(dialog
, box_y
, box_x
,
306 use_height
+ 2 * MARGIN
,
307 all
.use_width
+ 2 * MARGIN
,
308 menubox_border_attr
, menubox_border2_attr
);
313 /* Find length of longest item to center treeview */
314 for (i
= 0; i
< item_no
; i
++) {
315 tree_width
= MAX(tree_width
, INDENT
* depths
[i
]);
316 text_width
= MAX(text_width
, dlg_count_columns(items
[i
].text
));
317 name_width
= MAX(name_width
, dlg_count_columns(items
[i
].name
));
319 if (dialog_vars
.no_tags
&& !dialog_vars
.no_items
) {
320 tree_width
+= text_width
;
321 } else if (dialog_vars
.no_items
) {
322 tree_width
+= name_width
;
324 tree_width
+= (text_width
+ name_width
);
327 use_width
= (all
.use_width
- 4);
328 tree_width
= MIN(tree_width
, all
.use_width
);
330 all
.check_x
= (use_width
- tree_width
) / 2;
331 all
.item_x
= ((dialog_vars
.no_tags
333 : (dialog_vars
.no_items
338 /* ensure we are scrolled to show the current choice */
339 if (choice
>= (max_choice
+ scrollamt
)) {
340 scrollamt
= choice
- max_choice
+ 1;
341 choice
= max_choice
- 1;
344 /* register the new window, along with its borders */
345 dlg_mouse_mkbigregion(box_y
+ 1, box_x
,
346 use_height
, all
.use_width
+ 2,
347 KEY_MAX
, 1, 1, 1 /* by lines */ );
352 all
.use_height
= use_height
;
354 print_list(&all
, choice
, scrollamt
, max_choice
);
356 dlg_draw_buttons(dialog
, height
- 2, 0, buttons
, button
, FALSE
, width
);
358 dlg_trace_win(dialog
);
359 while (result
== DLG_EXIT_UNKNOWN
) {
360 if (button
< 0) /* --visit-items */
361 wmove(dialog
, box_y
+ choice
+ 1, box_x
+ all
.check_x
+ 2);
363 key
= dlg_mouse_wgetch(dialog
, &fkey
);
364 if (dlg_result_key(key
, fkey
, &result
))
367 was_mouse
= (fkey
&& is_DLGK_MOUSE(key
));
371 if (was_mouse
&& (key
>= KEY_MAX
)) {
373 if (i
< max_choice
) {
374 choice
= (key
- KEY_MAX
);
375 print_list(&all
, choice
, scrollamt
, max_choice
);
377 key
= ' '; /* force the selected item to toggle */
383 } else if (was_mouse
&& key
>= KEY_MIN
) {
384 key
= dlg_lookup_key(dialog
, key
, &fkey
);
388 * A space toggles the item status.
391 int current
= scrollamt
+ choice
;
392 int next
= items
[current
].state
+ 1;
394 if (next
>= num_states
)
397 if (flag
== FLAG_CHECK
) { /* checklist? */
398 items
[current
].state
= next
;
400 for (i
= 0; i
< item_no
; i
++) {
405 if (items
[current
].state
) {
406 items
[current
].state
= next
? next
: 1;
408 items
[current
].state
= 1;
411 print_list(&all
, choice
, scrollamt
, max_choice
);
412 continue; /* wait for another key press */
416 * Check if key pressed matches first character of any item tag in
417 * list. If there is more than one match, we will cycle through
418 * each one as the same key is pressed repeatedly.
422 if (button
< 0 || !dialog_state
.visit_items
) {
423 for (j
= scrollamt
+ choice
+ 1; j
< item_no
; j
++) {
424 if (check_hotkey(items
, j
)) {
431 for (j
= 0; j
<= scrollamt
+ choice
; j
++) {
432 if (check_hotkey(items
, j
)) {
441 } else if ((j
= dlg_char_to_button(key
, buttons
)) >= 0) {
449 * A single digit (1-9) positions the selection to that line in the
455 && (key
- '1' < max_choice
)) {
464 case DLGK_ITEM_FIRST
:
468 i
= item_no
- 1 - scrollamt
;
473 else if (scrollamt
!= 0)
474 i
= -MIN(scrollamt
, max_choice
);
479 i
= MIN(choice
+ max_choice
, item_no
- scrollamt
- 1);
483 if (choice
== 0 && scrollamt
== 0)
488 if (scrollamt
+ choice
>= item_no
- 1)
500 if (i
< 0 || i
>= max_choice
) {
505 choice
= max_choice
- 1;
506 scrollamt
+= (i
- max_choice
+ 1);
508 print_list(&all
, choice
, scrollamt
, max_choice
);
511 print_list(&all
, choice
, scrollamt
, max_choice
);
514 continue; /* wait for another key press */
520 result
= dlg_enter_buttoncode(button
);
522 case DLGK_FIELD_PREV
:
523 button
= dlg_prev_button(buttons
, button
);
524 dlg_draw_buttons(dialog
, height
- 2, 0, buttons
, button
,
527 case DLGK_FIELD_NEXT
:
528 button
= dlg_next_button(buttons
, button
);
529 dlg_draw_buttons(dialog
, height
- 2, 0, buttons
, button
,
539 dlg_del_window(dialog
);
541 dlg_mouse_free_regions();
546 if ((key2
= dlg_ok_buttoncode(key
)) >= 0) {
558 dlg_del_window(dialog
);
559 dlg_mouse_free_regions();
561 *current_item
= (scrollamt
+ choice
);
566 * Display a set of items as a tree.
569 dialog_treeview(const char *title
,
580 DIALOG_LISTITEM
*listitems
;
582 bool show_status
= FALSE
;
586 listitems
= dlg_calloc(DIALOG_LISTITEM
, (size_t) item_no
+ 1);
587 assert_ptr(listitems
, "dialog_treeview");
589 depths
= dlg_calloc(int, (size_t) item_no
+ 1);
590 assert_ptr(depths
, "dialog_treeview");
592 for (i
= j
= 0; i
< item_no
; ++i
) {
593 listitems
[i
].name
= items
[j
++];
594 listitems
[i
].text
= (dialog_vars
.no_items
597 listitems
[i
].state
= !dlg_strcmp(items
[j
++], "on");
598 depths
[i
] = atoi(items
[j
++]);
599 listitems
[i
].help
= ((dialog_vars
.item_help
)
603 dlg_align_columns(&listitems
[0].text
, (int) sizeof(DIALOG_LISTITEM
), item_no
);
605 result
= dlg_treeview(title
,
618 case DLG_EXIT_OK
: /* FALLTHRU */
623 dlg_add_help_listitem(&result
, &help_result
, &listitems
[current
]);
624 if ((show_status
= dialog_vars
.help_status
)) {
625 if (dialog_vars
.separate_output
) {
626 dlg_add_string(help_result
);
629 dlg_add_quoted(help_result
);
632 dlg_add_string(help_result
);
638 for (i
= 0; i
< item_no
; i
++) {
639 if (listitems
[i
].state
) {
640 if (dialog_vars
.separate_output
) {
641 dlg_add_string(listitems
[i
].name
);
644 if (dlg_need_separator())
646 if (flag
== FLAG_CHECK
)
647 dlg_add_quoted(listitems
[i
].name
);
649 dlg_add_string(listitems
[i
].name
);
653 dlg_add_last_key(-1);
656 dlg_free_columns(&listitems
[0].text
, (int) sizeof(DIALOG_LISTITEM
), item_no
);