2 * $Id: buildlist.c,v 1.61 2015/01/25 23:52:45 tom Exp $
4 * buildlist.c -- implements the buildlist dialog
6 * Copyright 2012-2014,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 * Visually like menubox, but two columns.
34 #define KEY_TOGGLE ' '
35 #define KEY_LEFTCOL '^'
36 #define KEY_RIGHTCOL '$'
38 #define MIN_HIGH (1 + (5 * MARGIN))
49 DIALOG_LISTITEM *items;
50 int base_y; /* base for mouse coordinates */
52 int use_height; /* actual size of column box */
61 * Print list item. The 'selected' parameter is true if 'choice' is the
62 * current item. That one is colored differently from the other items.
65 print_item(ALL_DATA * data,
67 DIALOG_LISTITEM * item,
71 chtype save = dlg_get_attrs(win);
73 bool both = (!dialog_vars.no_tags && !dialog_vars.no_items);
75 int climit = (data->item_x - data->check_x - 1);
76 const char *show = (dialog_vars.no_items
80 /* Clear 'residue' of last item */
81 (void) wattrset(win, menubox_attr);
82 (void) wmove(win, choice, 0);
83 for (i = 0; i < getmaxx(win); i++)
84 (void) waddch(win, ' ');
86 (void) wmove(win, choice, data->check_x);
87 (void) wattrset(win, menubox_attr);
90 dlg_print_listitem(win, item->name, climit, first, selected);
91 (void) waddch(win, ' ');
95 (void) wmove(win, choice, data->item_x);
96 climit = (getmaxx(win) - data->item_x + 1);
97 dlg_print_listitem(win, show, climit, first, selected);
100 dlg_item_help(item->help);
102 (void) wattrset(win, save);
106 * Prints either the left (unselected) or right (selected) list.
109 print_1_list(ALL_DATA * data,
113 MY_DATA *moi = data->list + selected;
114 WINDOW *win = moi->win;
117 int max_rows = getmaxy(win);
119 for (i = j = 0; j < max_rows; i++) {
120 int ii = i + moi->top_index;
123 } else if (ii >= data->item_no) {
125 } else if (!(selected ^ (data->items[ii].state != 0))) {
133 if (wmove(win, last, 0) != ERR)
135 (void) wnoutrefresh(win);
139 * Return the previous item from the list, staying in the same column. If no
140 * further movement is possible, return the same choice as given.
143 prev_item(ALL_DATA * data, int choice, int selected)
148 for (n = choice - 1; n >= 0; --n) {
149 if ((data->items[n].state != 0) == selected) {
158 * Return true if the given choice is on the first page in the current column.
161 stop_prev(ALL_DATA * data, int choice, int selected)
163 return (prev_item(data, choice, selected) == choice);
167 check_hotkey(DIALOG_LISTITEM * items, int choice, int selected)
171 if ((items[choice].state != 0) == selected) {
172 if (dlg_match_char(dlg_last_getc(),
175 : items[choice].name))) {
183 * Return the next item from the list, staying in the same column. If no
184 * further movement is possible, return the same choice as given.
187 next_item(ALL_DATA * data, int choice, int selected)
192 for (n = choice + 1; n < data->item_no; ++n) {
193 if ((data->items[n].state != 0) == selected) {
198 dlg_trace_msg("next_item(%d) ->%d\n", choice, result);
203 * Translate a choice from items[] to a row-number in an unbounded column,
207 index2row(ALL_DATA * data, int choice, int selected)
211 for (n = 0; n < data->item_no; ++n) {
212 if ((data->items[n].state != 0) == selected) {
222 * Return the first choice from items[] for the given column.
225 first_item(ALL_DATA * data, int selected)
230 for (n = 0; n < data->item_no; ++n) {
231 if ((data->items[n].state != 0) == selected) {
240 * Return the last choice from items[] for the given column.
243 last_item(ALL_DATA * data, int selected)
248 for (n = data->item_no - 1; n >= 0; --n) {
249 if ((data->items[n].state != 0) == selected) {
258 * Convert a row-number back to an item number, i.e., index into items[].
261 row2index(ALL_DATA * data, int row, int selected)
265 for (n = 0; n < data->item_no; ++n) {
266 if ((data->items[n].state != 0) == selected) {
277 skip_rows(ALL_DATA * data, int row, int skip, int selected)
279 int choice = row2index(data, row, selected);
283 for (n = choice + 1; n < data->item_no; ++n) {
284 if ((data->items[n].state != 0) == selected) {
290 } else if (skip < 0) {
291 for (n = choice - 1; n >= 0; --n) {
292 if ((data->items[n].state != 0) == selected) {
303 * Find the closest item in the given column starting with the given choice.
306 closest_item(ALL_DATA * data, int choice, int selected)
313 for (n = choice; n >= 0; --n) {
314 if ((data->items[n].state != 0) == selected) {
319 for (n = choice; n < data->item_no; ++n) {
320 if ((data->items[n].state != 0) == selected) {
325 if (prev != choice) {
327 if (next != choice) {
328 if ((choice - prev) > (next - choice)) {
332 } else if (next != choice) {
339 print_both(ALL_DATA * data,
344 WINDOW *dialog = wgetparent(data->list[0].win);
346 getyx(dialog, cur_y, cur_x);
347 for (selected = 0; selected < 2; ++selected) {
348 MY_DATA *moi = data->list + selected;
349 WINDOW *win = moi->win;
350 int thumb_top = index2row(data, moi->top_index, selected);
351 int thumb_max = index2row(data, -1, selected);
352 int thumb_end = thumb_top + getmaxy(win);
354 print_1_list(data, choice, selected);
356 dlg_mouse_setcode(selected * KEY_MAX);
357 dlg_draw_scrollbar(dialog,
358 (long) (moi->top_index),
360 (long) MIN(thumb_end, thumb_max),
362 moi->box_x + data->check_x,
363 moi->box_x + getmaxx(win),
365 moi->box_y + getmaxy(win) + 1,
366 menubox_border2_attr,
367 menubox_border_attr);
369 (void) wmove(dialog, cur_y, cur_x);
370 dlg_mouse_setcode(0);
374 set_top_item(ALL_DATA * data, int value, int selected)
376 if (value != data->list[selected].top_index) {
377 dlg_trace_msg("set top of %s column to %d\n",
378 selected ? "right" : "left",
380 data->list[selected].top_index = value;
385 * Adjust the top-index as needed to ensure that it and the given item are
389 fix_top_item(ALL_DATA * data, int cur_item, int selected)
391 int top_item = data->list[selected].top_index;
392 int cur_row = index2row(data, cur_item, selected);
393 int top_row = index2row(data, top_item, selected);
395 if (cur_row < top_row) {
397 } else if ((cur_row - top_row) > data->use_height) {
398 top_item = row2index(data, cur_row + 1 - data->use_height, selected);
400 if (cur_row < data->use_height) {
401 top_item = row2index(data, 0, selected);
403 dlg_trace_msg("fix_top_item(cur_item %d, selected %d) ->top_item %d\n",
404 cur_item, selected, top_item);
405 set_top_item(data, top_item, selected);
409 * This is an alternate interface to 'buildlist' which allows the application
410 * to read the list item states back directly without putting them in the
414 dlg_buildlist(const char *title,
420 DIALOG_LISTITEM * items,
426 static DLG_KEYS_BINDING binding[] = {
429 DLG_KEYS_DATA( DLGK_FIELD_NEXT, KEY_RIGHT ),
430 DLG_KEYS_DATA( DLGK_FIELD_NEXT, TAB ),
431 DLG_KEYS_DATA( DLGK_FIELD_PREV, KEY_BTAB ),
432 DLG_KEYS_DATA( DLGK_FIELD_PREV, KEY_LEFT ),
433 DLG_KEYS_DATA( DLGK_ITEM_FIRST, KEY_HOME ),
434 DLG_KEYS_DATA( DLGK_ITEM_LAST, KEY_END ),
435 DLG_KEYS_DATA( DLGK_ITEM_LAST, KEY_LL ),
436 DLG_KEYS_DATA( DLGK_ITEM_NEXT, '+' ),
437 DLG_KEYS_DATA( DLGK_ITEM_NEXT, KEY_DOWN ),
438 DLG_KEYS_DATA( DLGK_ITEM_NEXT, CHR_NEXT ),
439 DLG_KEYS_DATA( DLGK_ITEM_PREV, '-' ),
440 DLG_KEYS_DATA( DLGK_ITEM_PREV, KEY_UP ),
441 DLG_KEYS_DATA( DLGK_ITEM_PREV, CHR_PREVIOUS ),
442 DLG_KEYS_DATA( DLGK_PAGE_NEXT, KEY_NPAGE ),
443 DLG_KEYS_DATA( DLGK_PAGE_NEXT, DLGK_MOUSE(KEY_NPAGE) ),
444 DLG_KEYS_DATA( DLGK_PAGE_NEXT, DLGK_MOUSE(KEY_NPAGE+KEY_MAX) ),
445 DLG_KEYS_DATA( DLGK_PAGE_PREV, KEY_PPAGE ),
446 DLG_KEYS_DATA( DLGK_PAGE_PREV, DLGK_MOUSE(KEY_PPAGE) ),
447 DLG_KEYS_DATA( DLGK_PAGE_PREV, DLGK_MOUSE(KEY_PPAGE+KEY_MAX) ),
448 DLG_KEYS_DATA( DLGK_GRID_LEFT, KEY_LEFTCOL ),
449 DLG_KEYS_DATA( DLGK_GRID_RIGHT, KEY_RIGHTCOL ),
455 int old_height = height;
456 int old_width = width;
459 MY_DATA *data = all.list;
460 int i, j, k, key2, found, x, y, cur_x, cur_y;
462 bool save_visit = dialog_state.visit_items;
466 int name_width, text_width, full_width, list_width;
467 int result = DLG_EXIT_UNKNOWN;
471 char *prompt = dlg_strclone(cprompt);
472 const char **buttons = dlg_ok_labels();
473 const char *widget_name = "buildlist";
477 dialog_state.plain_buttons = TRUE;
480 * Unlike other uses of --visit-items, we have two windows to visit.
482 if (dialog_state.visit_cols)
483 dialog_state.visit_cols = 2;
485 memset(&all, 0, sizeof(all));
487 all.item_no = item_no;
489 if (dialog_vars.default_item != 0) {
490 cur_item = dlg_default_listitem(items);
492 if ((cur_item = first_item(&all, 0)) < 0)
493 cur_item = first_item(&all, 1);
495 button = (dialog_state.visit_items
496 ? (items[cur_item].state ? sRIGHT : sLEFT)
497 : dlg_default_button());
500 dlg_tab_correct_str(prompt);
506 all.use_height = list_height;
507 all.use_width = (2 * (dlg_calc_list_width(item_no, items)
511 all.use_width = MAX(26, all.use_width);
512 if (all.use_height == 0) {
513 /* calculate height without items (4) */
514 dlg_auto_size(title, prompt, &height, &width, MIN_HIGH, all.use_width);
515 dlg_calc_listh(&height, &all.use_height, item_no);
517 dlg_auto_size(title, prompt,
519 MIN_HIGH + all.use_height, all.use_width);
521 dlg_button_layout(buttons, &width);
522 dlg_print_size(height, width);
523 dlg_ctl_size(height, width);
525 /* we need at least two states */
526 if (states == 0 || strlen(states) < 2)
528 num_states = (int) strlen(states);
530 x = dlg_box_x_ordinate(width);
531 y = dlg_box_y_ordinate(height);
533 dialog = dlg_new_window(height, width, y, x);
534 dlg_register_window(dialog, widget_name, binding);
535 dlg_register_buttons(dialog, widget_name, buttons);
537 dlg_mouse_setbase(all.base_x = x, all.base_y = y);
539 dlg_draw_box2(dialog, 0, 0, height, width, dialog_attr, border_attr, border2_attr);
540 dlg_draw_bottom_box2(dialog, border_attr, border2_attr, dialog_attr);
541 dlg_draw_title(dialog, title);
543 (void) wattrset(dialog, dialog_attr);
544 dlg_print_autowrap(dialog, prompt, height, width);
546 list_width = (width - 6 * MARGIN - 2) / 2;
547 getyx(dialog, cur_y, cur_x);
548 data[0].box_y = cur_y + 1;
549 data[0].box_x = MARGIN + 1;
550 data[1].box_y = cur_y + 1;
551 data[1].box_x = data[0].box_x + 1 + 2 * MARGIN + list_width;
554 * After displaying the prompt, we know how much space we really have.
555 * Limit the list to avoid overwriting the ok-button.
557 if (all.use_height + MIN_HIGH > height - cur_y)
558 all.use_height = height - MIN_HIGH - cur_y;
559 if (all.use_height <= 0)
562 for (k = 0; k < 2; ++k) {
563 /* create new window for the list */
564 data[k].win = dlg_sub_window(dialog, all.use_height, list_width,
565 y + data[k].box_y + 1,
566 x + data[k].box_x + 1);
568 /* draw a box around the list items */
569 dlg_draw_box(dialog, data[k].box_y, data[k].box_x,
570 all.use_height + 2 * MARGIN,
571 list_width + 2 * MARGIN,
572 menubox_border_attr, menubox_border2_attr);
577 /* Find length of longest item to center buildlist */
578 for (i = 0; i < item_no; i++) {
579 text_width = MAX(text_width, dlg_count_columns(items[i].text));
580 name_width = MAX(name_width, dlg_count_columns(items[i].name));
583 /* If the name+text is wider than the list is allowed, then truncate
584 * one or both of them. If the name is no wider than 1/4 of the list,
587 all.use_width = (list_width - 6 * MARGIN);
588 if (dialog_vars.no_tags && !dialog_vars.no_items) {
589 full_width = MIN(all.use_width, text_width);
590 } else if (dialog_vars.no_items) {
591 full_width = MIN(all.use_width, name_width);
596 && text_width + name_width > all.use_width) {
597 int need = (int) (0.25 * all.use_width);
598 if (name_width > need) {
599 int want = (int) (all.use_width * ((double) name_width) /
600 (text_width + name_width));
601 name_width = (want > need) ? want : need;
603 text_width = all.use_width - name_width;
605 full_width = text_width + name_width;
608 all.check_x = (all.use_width - full_width) / 2;
609 all.item_x = ((dialog_vars.no_tags
611 : (dialog_vars.no_items
616 /* ensure we are scrolled to show the current choice */
617 j = MIN(all.use_height, item_no);
618 for (i = 0; i < 2; ++i) {
620 if ((items[cur_item].state != 0) == i) {
621 top_item = cur_item - j + 1;
624 set_top_item(&all, top_item, i);
626 set_top_item(&all, 0, i);
630 /* register the new window, along with its borders */
631 for (i = 0; i < 2; ++i) {
632 dlg_mouse_mkbigregion(data[i].box_y + 1,
636 2 * KEY_MAX + (i * (1 + all.use_height)),
637 1, 1, 1 /* by lines */ );
640 dlg_draw_buttons(dialog, height - 2, 0, buttons, button, FALSE, width);
642 while (result == DLG_EXIT_UNKNOWN) {
643 int which = (items[cur_item].state != 0);
644 MY_DATA *moi = data + which;
645 int at_top = index2row(&all, moi->top_index, which);
646 int at_end = index2row(&all, -1, which);
647 int at_bot = skip_rows(&all, at_top, all.use_height, which);
649 dlg_trace_msg("\t** state %d:%d top %d (%d:%d:%d) %d\n",
650 cur_item, item_no - 1,
652 at_top, at_bot, at_end,
656 print_both(&all, cur_item);
657 dlg_trace_win(dialog);
661 if (button < 0) { /* --visit-items */
662 int cur_row = index2row(&all, cur_item, which);
663 cur_y = (data[which].box_y
668 cur_x = (data[which].box_x
670 dlg_trace_msg("\t...visit row %d (%d,%d)\n", cur_row, cur_y, cur_x);
671 wmove(dialog, cur_y, cur_x);
674 key = dlg_mouse_wgetch(dialog, &fkey);
675 if (dlg_result_key(key, fkey, &result))
678 was_mouse = (fkey && is_DLGK_MOUSE(key));
684 } else if (key >= 2 * KEY_MAX) {
685 i = (key - 2 * KEY_MAX) % (1 + all.use_height);
686 j = (key - 2 * KEY_MAX) / (1 + all.use_height);
687 k = row2index(&all, i + at_top, j);
688 dlg_trace_msg("MOUSE column %d, row %d ->item %d\n", j, i, k);
689 if (k >= 0 && j < 2) {
692 * Mouse click was in the other column.
695 fix_top_item(&all, k, j);
698 at_top = index2row(&all, moi->top_index, which);
699 at_bot = skip_rows(&all, at_top, all.use_height, which);
701 print_both(&all, cur_item);
702 key = KEY_TOGGLE; /* force the selected item to toggle */
708 } else if (key >= KEY_MIN) {
711 key = KEY_RIGHTCOL; /* switch to right-column */
718 key = KEY_LEFTCOL; /* switch to left-column */
722 key = dlg_lookup_key(dialog, key, &fkey);
726 * A space toggles the item status. Normally we put the cursor on
727 * the next available item in the same column. But if there are no
728 * more items in the column, move the cursor to the other column.
730 if (key == KEY_TOGGLE) {
732 int new_state = items[cur_item].state + 1;
734 if ((new_choice = next_item(&all, cur_item, which)) == cur_item) {
735 new_choice = prev_item(&all, cur_item, which);
737 dlg_trace_msg("cur_item %d, new_choice:%d\n", cur_item, new_choice);
738 if (new_state >= num_states)
741 items[cur_item].state = new_state;
742 if (cur_item == moi->top_index) {
743 set_top_item(&all, new_choice, which);
746 if (new_choice >= 0) {
747 fix_top_item(&all, cur_item, !which);
748 cur_item = new_choice;
750 print_both(&all, cur_item);
751 dlg_trace_win(dialog);
752 continue; /* wait for another key press */
756 * Check if key pressed matches first character of any item tag in
757 * list. If there is more than one match, we will cycle through
758 * each one as the same key is pressed repeatedly.
762 if (button < 0 || !dialog_state.visit_items) {
763 for (j = cur_item + 1; j < item_no; j++) {
764 if (check_hotkey(items, j, which)) {
771 for (j = 0; j <= cur_item; j++) {
772 if (check_hotkey(items, j, which)) {
781 } else if ((j = dlg_char_to_button(key, buttons)) >= 0) {
789 * A single digit (1-9) positions the selection to that line in the
795 && (key - '1' < at_bot)) {
800 if (!found && fkey) {
802 case DLGK_FIELD_PREV:
803 if ((button == sRIGHT) && dialog_state.visit_items) {
804 key = DLGK_GRID_LEFT;
807 button = dlg_prev_button(buttons, button);
808 dlg_draw_buttons(dialog, height - 2, 0, buttons, button,
810 if (button == sRIGHT) {
811 key = DLGK_GRID_RIGHT;
817 case DLGK_FIELD_NEXT:
818 if ((button == sLEFT) && dialog_state.visit_items) {
819 key = DLGK_GRID_RIGHT;
822 button = dlg_next_button(buttons, button);
823 dlg_draw_buttons(dialog, height - 2, 0, buttons, button,
825 if (button == sLEFT) {
826 key = DLGK_GRID_LEFT;
835 if (!found && fkey) {
840 i = closest_item(&all, cur_item, 0);
841 fix_top_item(&all, i, 0);
843 case DLGK_GRID_RIGHT:
844 i = closest_item(&all, cur_item, 1);
845 fix_top_item(&all, i, 1);
848 if (cur_item > moi->top_index) {
850 } else if (moi->top_index != 0) {
852 if ((temp -= all.use_height) < 0)
854 i = row2index(&all, temp, which);
858 if ((at_end - at_bot) < all.use_height) {
860 row2index(&all, at_end, which),
864 row2index(&all, at_bot, which),
869 row2index(&all, at_top, which),
872 at_bot = skip_rows(&all, at_top, all.use_height, which);
873 at_bot = MIN(at_bot, at_end);
876 case DLGK_ITEM_FIRST:
877 i = first_item(&all, which);
880 i = last_item(&all, which);
883 i = prev_item(&all, cur_item, which);
884 if (stop_prev(&all, cur_item, which))
888 i = next_item(&all, cur_item, which);
898 int now_at = index2row(&all, i, which);
902 dlg_trace_msg("<--CHOICE %d\n", i);
903 dlg_trace_msg("<--topITM %d\n", moi->top_index);
904 dlg_trace_msg("<--now_at %d\n", now_at);
905 dlg_trace_msg("<--at_top %d\n", at_top);
906 dlg_trace_msg("<--at_bot %d\n", at_bot);
908 if (now_at >= at_bot) {
909 while (now_at >= at_bot) {
910 if ((at_bot - at_top) >= all.use_height) {
912 next_item(&all, moi->top_index, which),
915 at_top = index2row(&all, moi->top_index, which);
916 at_bot = skip_rows(&all, at_top, all.use_height, which);
918 dlg_trace_msg("...at_bot %d (now %d vs %d)\n",
919 at_bot, now_at, at_end);
920 dlg_trace_msg("...topITM %d\n", moi->top_index);
921 dlg_trace_msg("...at_top %d (diff %d)\n", at_top,
924 if (at_bot >= at_end) {
926 * If we bumped into the end, move the top-item
927 * down by one line so that we can display the
928 * last item in the list.
930 if ((at_bot - at_top) > all.use_height) {
932 next_item(&all, moi->top_index, which),
934 } else if (at_top > 0 &&
935 (at_bot - at_top) >= all.use_height) {
937 next_item(&all, moi->top_index, which),
943 dlg_trace_msg("OOPS-forward\n");
947 } else if (now_at < at_top) {
948 while (now_at < at_top) {
949 old_item = moi->top_index;
951 prev_item(&all, moi->top_index, which),
953 at_top = index2row(&all, moi->top_index, which);
955 dlg_trace_msg("...at_top %d (now %d)\n", at_top, now_at);
956 dlg_trace_msg("...topITM %d\n", moi->top_index);
958 if (moi->top_index >= old_item)
960 if (at_top <= now_at)
963 dlg_trace_msg("OOPS-backward\n");
968 dlg_trace_msg("-->now_at %d\n", now_at);
970 print_both(&all, cur_item);
972 dlg_trace_win(dialog);
973 continue; /* wait for another key press */
979 result = dlg_enter_buttoncode(button);
988 dlg_del_window(dialog);
990 dlg_mouse_free_regions();
995 if ((key2 = dlg_ok_buttoncode(key)) >= 0) {
1007 dialog_state.visit_cols = save_visit;
1008 dlg_del_window(dialog);
1009 dlg_mouse_free_regions();
1011 *current_item = cur_item;
1016 * Display a dialog box with a list of options that can be turned on or off
1019 dialog_buildlist(const char *title,
1020 const char *cprompt,
1030 DIALOG_LISTITEM *listitems;
1031 bool separate_output = dialog_vars.separate_output;
1032 bool show_status = FALSE;
1036 listitems = dlg_calloc(DIALOG_LISTITEM, (size_t) item_no + 1);
1037 assert_ptr(listitems, "dialog_buildlist");
1039 for (i = j = 0; i < item_no; ++i) {
1040 listitems[i].name = items[j++];
1041 listitems[i].text = (dialog_vars.no_items
1044 listitems[i].state = !dlg_strcmp(items[j++], "on");
1045 listitems[i].help = ((dialog_vars.item_help)
1049 dlg_align_columns(&listitems[0].text, (int) sizeof(DIALOG_LISTITEM), item_no);
1051 result = dlg_buildlist(title,
1063 case DLG_EXIT_OK: /* FALLTHRU */
1064 case DLG_EXIT_EXTRA:
1068 dlg_add_help_listitem(&result, &help_result, &listitems[current]);
1069 if ((show_status = dialog_vars.help_status)) {
1070 if (separate_output) {
1071 dlg_add_string(help_result);
1072 dlg_add_separator();
1074 dlg_add_quoted(help_result);
1077 dlg_add_string(help_result);
1083 for (i = 0; i < item_no; i++) {
1084 if (listitems[i].state) {
1085 if (separate_output) {
1086 dlg_add_string(listitems[i].name);
1087 dlg_add_separator();
1089 if (dlg_need_separator())
1090 dlg_add_separator();
1091 dlg_add_quoted(listitems[i].name);
1095 dlg_add_last_key(-1);
1098 dlg_free_columns(&listitems[0].text, (int) sizeof(DIALOG_LISTITEM), item_no);