Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/dialog/buildlist.c
39475 views
1
/*
2
* $Id: buildlist.c,v 1.94 2020/11/23 00:37:17 tom Exp $
3
*
4
* buildlist.c -- implements the buildlist dialog
5
*
6
* Copyright 2012-2019,2020 Thomas E. Dickey
7
*
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.
11
*
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.
16
*
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.
22
*/
23
24
#include <dlg_internals.h>
25
#include <dlg_keys.h>
26
27
/*
28
* Visually like menubox, but two columns.
29
*/
30
31
#define sLEFT (-2)
32
#define sRIGHT (-1)
33
34
#define KEY_LEFTCOL '^'
35
#define KEY_RIGHTCOL '$'
36
37
#define MIN_HIGH (1 + (5 * MARGIN))
38
39
typedef struct {
40
WINDOW *win;
41
int box_y;
42
int box_x;
43
int top_index;
44
int cur_index;
45
DIALOG_LISTITEM **ip; /* pointers to items in this list */
46
} MY_DATA;
47
48
#if 0
49
#define TRACE(p) dlg_trace_msg p
50
#else
51
#define TRACE(p) /* nothing */
52
#endif
53
54
#define okIndex(all,index) ((index) >= 0 && (index) < (all)->item_no)
55
56
#define myItem(p,n) ((p)->ip)[n]
57
#define mySide(n) ((n)?"right":"left")
58
59
typedef struct {
60
DIALOG_LISTITEM *items; /* all items in the widget */
61
int base_y; /* base for mouse coordinates */
62
int base_x;
63
int use_height; /* actual size of column box */
64
int use_width;
65
int item_no;
66
int check_x;
67
int item_x;
68
MY_DATA list[2];
69
} ALL_DATA;
70
71
/*
72
* Translate a choice from items[] to a row-number in an unbounded column,
73
* starting at zero.
74
*/
75
static int
76
index2row(ALL_DATA * all, int choice, int selected)
77
{
78
MY_DATA *data = all->list + selected;
79
int result = -1;
80
81
if (okIndex(all, choice)) {
82
int row;
83
84
for (row = 0; row < all->item_no; ++row) {
85
TRACE(("!... choice %d: %p vs row %d: %p\n",
86
choice, all->items + choice,
87
row, myItem(data, row)));
88
if (myItem(data, row) == all->items + choice) {
89
result = row;
90
break;
91
}
92
}
93
}
94
TRACE(("! index2row(choice %d, %s) = %d\n", choice, mySide(selected), result));
95
return result;
96
}
97
98
/*
99
* Convert a row-number back to an item number, i.e., index into items[].
100
*/
101
static int
102
row2index(ALL_DATA * all, int row, int selected)
103
{
104
MY_DATA *data = all->list + selected;
105
int result = -1;
106
int n;
107
for (n = 0; n < all->item_no; ++n) {
108
TRACE(("!... row %d: %p vs choice %d: %p\n",
109
row, myItem(data, row),
110
n, all->items + n));
111
if (myItem(data, row) == all->items + n) {
112
result = n;
113
break;
114
}
115
}
116
TRACE(("! row2index(row %d, %s) = %d\n", row, mySide(selected), result));
117
return result;
118
}
119
120
/*
121
* Print list item. The 'selected' parameter is true if 'choice' is the
122
* current item. That one is colored differently from the other items.
123
*/
124
static void
125
print_item(ALL_DATA * all,
126
WINDOW *win,
127
DIALOG_LISTITEM * item,
128
int row,
129
int selected)
130
{
131
chtype save = dlg_get_attrs(win);
132
int i;
133
bool both = (!dialog_vars.no_tags && !dialog_vars.no_items);
134
bool first = TRUE;
135
int climit = (all->item_x - all->check_x - 1);
136
const char *show = (dialog_vars.no_items
137
? item->name
138
: item->text);
139
140
/* Clear 'residue' of last item */
141
dlg_attrset(win, menubox_attr);
142
(void) wmove(win, row, 0);
143
for (i = 0; i < getmaxx(win); i++)
144
(void) waddch(win, ' ');
145
146
(void) wmove(win, row, all->check_x);
147
dlg_attrset(win, menubox_attr);
148
149
if (both) {
150
dlg_print_listitem(win, item->name, climit, first, selected);
151
(void) waddch(win, ' ');
152
first = FALSE;
153
}
154
155
(void) wmove(win, row, all->item_x);
156
climit = (getmaxx(win) - all->item_x + 1);
157
dlg_print_listitem(win, show, climit, first, selected);
158
159
if (selected) {
160
dlg_item_help(item->help);
161
}
162
dlg_attrset(win, save);
163
}
164
165
/*
166
* Prints either the left (unselected) or right (selected) list.
167
*/
168
static void
169
print_1_list(ALL_DATA * all,
170
int choice,
171
int selected)
172
{
173
MY_DATA *data = all->list + selected;
174
DIALOG_LISTITEM *target = (okIndex(all, choice)
175
? all->items + choice
176
: 0);
177
WINDOW *win = data->win;
178
int i, j;
179
int last = 0;
180
int top_row = index2row(all, data->top_index, selected);
181
int max_rows = getmaxy(win);
182
183
TRACE(("! print_1_list %d %s, top %d\n", choice, mySide(selected), top_row));
184
for (i = j = 0; j < max_rows; i++) {
185
int ii = i + top_row;
186
if (ii < 0) {
187
continue;
188
} else if (myItem(data, ii)) {
189
print_item(all,
190
win,
191
myItem(data, ii),
192
j, myItem(data, ii) == target);
193
last = ++j;
194
} else {
195
break;
196
}
197
}
198
if (wmove(win, last, 0) != ERR) {
199
while (waddch(win, ' ') != ERR) {
200
;
201
}
202
}
203
(void) wnoutrefresh(win);
204
}
205
206
/*
207
* Return the previous item from the list, staying in the same column. If no
208
* further movement is possible, return the same choice as given.
209
*/
210
static int
211
prev_item(ALL_DATA * all, int choice, int selected)
212
{
213
int result = choice;
214
int row = index2row(all, choice, selected);
215
if (row > 0) {
216
row--;
217
result = row2index(all, row, selected);
218
}
219
TRACE(("! prev_item choice %d, %s = %d\n", choice, mySide(selected), result));
220
return result;
221
}
222
223
/*
224
* Return true if the given choice is on the first page in the current column.
225
*/
226
static bool
227
stop_prev(ALL_DATA * all, int choice, int selected)
228
{
229
return (prev_item(all, choice, selected) == choice);
230
}
231
232
static bool
233
check_hotkey(DIALOG_LISTITEM * items, int choice, int selected)
234
{
235
bool result = FALSE;
236
237
if ((items[choice].state != 0) == selected) {
238
if (dlg_match_char(dlg_last_getc(),
239
(dialog_vars.no_tags
240
? items[choice].text
241
: items[choice].name))) {
242
result = TRUE;
243
}
244
}
245
return result;
246
}
247
248
/*
249
* Return the next item from the list, staying in the same column. If no
250
* further movement is possible, return the same choice as given.
251
*/
252
static int
253
next_item(ALL_DATA * all, int choice, int selected)
254
{
255
MY_DATA *data = all->list + selected;
256
int result = choice;
257
int row = index2row(all, choice, selected);
258
TRACE(("! given item %d, testing next-item on row %d\n", choice, row + 1));
259
if (myItem(data, row + 1)) {
260
result = row2index(all, row + 1, selected);
261
}
262
TRACE(("! next_item(%d, %s) ->%d\n", choice, mySide(selected), result));
263
return result;
264
}
265
266
/*
267
* Return the first choice from items[] for the given column.
268
*/
269
static int
270
first_item(ALL_DATA * all, int selected)
271
{
272
MY_DATA *data = all->list + selected;
273
int result = -1;
274
275
if (myItem(data, 0) != 0) {
276
int n;
277
278
for (n = 0; n < all->item_no; ++n) {
279
if (myItem(data, 0) == &all->items[n]) {
280
result = n;
281
break;
282
}
283
}
284
}
285
TRACE(("! first_item %s = %d\n", mySide(selected), result));
286
return result;
287
}
288
289
/*
290
* Return the last choice from items[] for the given column.
291
*/
292
static int
293
last_item(ALL_DATA * all, int selected)
294
{
295
MY_DATA *data = all->list + selected;
296
int result = -1;
297
int n;
298
299
for (n = 0; myItem(data, n) != 0; ++n) {
300
result = n;
301
}
302
if (result >= 0) {
303
result = row2index(all, result, selected);
304
}
305
TRACE(("! last_item %s = %d\n", mySide(selected), result));
306
return result;
307
}
308
309
static int
310
skip_rows(ALL_DATA * all, int row, int skip, int selected)
311
{
312
MY_DATA *data = all->list + selected;
313
int result = row;
314
315
if (skip > 0) {
316
int n;
317
318
for (n = row + 1; (n < all->item_no) && (n <= row + skip); ++n) {
319
if (myItem(data, n) == 0)
320
break;
321
result = n;
322
}
323
} else if (skip < 0) {
324
result -= skip;
325
if (result < 0)
326
result = 0;
327
}
328
TRACE(("! skip_rows row %d, skip %d, %s = %d\n",
329
row, skip, mySide(selected), result));
330
return result;
331
}
332
333
/*
334
* Find the closest item in the given column starting with the given choice.
335
*/
336
static int
337
closest_item(ALL_DATA * all, int choice, int selected)
338
{
339
int prev = choice;
340
int next = choice;
341
int result = choice;
342
int n;
343
344
for (n = choice; n >= 0; --n) {
345
if ((all->items[n].state != 0) == selected) {
346
prev = n;
347
break;
348
}
349
}
350
for (n = choice; n < all->item_no; ++n) {
351
if ((all->items[n].state != 0) == selected) {
352
next = n;
353
break;
354
}
355
}
356
if (prev != choice) {
357
result = prev;
358
if (next != choice) {
359
if ((choice - prev) > (next - choice)) {
360
result = next;
361
}
362
}
363
} else if (next != choice) {
364
result = next;
365
}
366
TRACE(("! XXX closest item choice %d, %s = %d\n",
367
choice, mySide(selected), result));
368
return result;
369
}
370
371
static void
372
print_both(ALL_DATA * all,
373
int choice)
374
{
375
int selected;
376
int cur_y, cur_x;
377
WINDOW *dialog = wgetparent(all->list[0].win);
378
379
TRACE(("! print_both %d\n", choice));
380
getyx(dialog, cur_y, cur_x);
381
for (selected = 0; selected < 2; ++selected) {
382
MY_DATA *data = all->list + selected;
383
WINDOW *win = data->win;
384
int thumb_top = index2row(all, data->top_index, selected);
385
int thumb_max = index2row(all, -1, selected);
386
int thumb_end = thumb_top + getmaxy(win);
387
388
print_1_list(all, choice, selected);
389
390
dlg_mouse_setcode(selected * KEY_MAX);
391
dlg_draw_scrollbar(dialog,
392
(long) (data->top_index),
393
(long) (thumb_top),
394
(long) MIN(thumb_end, thumb_max),
395
(long) thumb_max,
396
data->box_x + all->check_x,
397
data->box_x + getmaxx(win),
398
data->box_y,
399
data->box_y + getmaxy(win) + 1,
400
menubox_border2_attr,
401
menubox_border_attr);
402
}
403
(void) wmove(dialog, cur_y, cur_x);
404
dlg_mouse_setcode(0);
405
}
406
407
static void
408
set_top_item(ALL_DATA * all, int choice, int selected)
409
{
410
if (choice != all->list[selected].top_index) {
411
DLG_TRACE(("# set top of %s column to %d\n",
412
mySide(selected),
413
choice));
414
all->list[selected].top_index = choice;
415
}
416
}
417
418
/*
419
* Adjust the top-index as needed to ensure that it and the given item are
420
* visible.
421
*/
422
static void
423
fix_top_item(ALL_DATA * all, int cur_item, int selected)
424
{
425
int top_item = all->list[selected].top_index;
426
int cur_row = index2row(all, cur_item, selected);
427
int top_row = index2row(all, top_item, selected);
428
429
if (cur_row < top_row) {
430
top_item = cur_item;
431
} else if ((cur_row - top_row) >= all->use_height) {
432
top_item = row2index(all, cur_row + 1 - all->use_height, selected);
433
}
434
if (cur_row < all->use_height) {
435
top_item = row2index(all, 0, selected);
436
}
437
DLG_TRACE(("# fix_top_item(cur_item %d, %s) ->top_item %d\n",
438
cur_item, mySide(selected), top_item));
439
set_top_item(all, top_item, selected);
440
}
441
442
static void
443
append_right_side(ALL_DATA * all, int choice)
444
{
445
MY_DATA *data = &all->list[1];
446
int j;
447
for (j = 0; j < all->item_no; ++j) {
448
if (myItem(data, j) == 0) {
449
myItem(data, j) = &all->items[choice];
450
break;
451
}
452
}
453
}
454
455
static void
456
amend_right_side(ALL_DATA * all, int choice)
457
{
458
MY_DATA *data = &all->list[1];
459
int j, k;
460
for (j = 0; j < all->item_no; ++j) {
461
if (myItem(data, j) == &all->items[choice]) {
462
for (k = j; k < all->item_no; ++k) {
463
if ((myItem(data, k) = myItem(data, k + 1)) == 0)
464
break;
465
}
466
break;
467
}
468
}
469
}
470
471
static void
472
fill_one_side(ALL_DATA * all, int selected)
473
{
474
int i, j;
475
MY_DATA *data = all->list + selected;
476
477
for (i = j = 0; j < all->item_no; ++j) {
478
myItem(data, i) = 0;
479
if ((all->items[j].state != 0) == selected) {
480
myItem(data, i) = all->items + j;
481
TRACE(("! %s item[%d] %p = all[%d] %p\n",
482
mySide(selected),
483
i, myItem(data, i),
484
j, all->items + j));
485
++i;
486
}
487
}
488
myItem(data, i) = 0;
489
}
490
491
static void
492
fill_both_sides(ALL_DATA * all)
493
{
494
int k;
495
496
for (k = 0; k < 2; ++k) {
497
fill_one_side(all, k);
498
}
499
}
500
501
/*
502
* This is an alternate interface to 'buildlist' which allows the application
503
* to read the list item states back directly without putting them in the
504
* output buffer.
505
*/
506
int
507
dlg_buildlist(const char *title,
508
const char *cprompt,
509
int height,
510
int width,
511
int list_height,
512
int item_no,
513
DIALOG_LISTITEM * items,
514
const char *states,
515
int order_mode,
516
int *current_item)
517
{
518
#define THIS_FUNC "dlg_buildlist"
519
/* *INDENT-OFF* */
520
static DLG_KEYS_BINDING binding[] = {
521
HELPKEY_BINDINGS,
522
ENTERKEY_BINDINGS,
523
DLG_KEYS_DATA( DLGK_FIELD_NEXT, KEY_RIGHT ),
524
DLG_KEYS_DATA( DLGK_FIELD_NEXT, TAB ),
525
DLG_KEYS_DATA( DLGK_FIELD_PREV, KEY_BTAB ),
526
DLG_KEYS_DATA( DLGK_FIELD_PREV, KEY_LEFT ),
527
DLG_KEYS_DATA( DLGK_ITEM_FIRST, KEY_HOME ),
528
DLG_KEYS_DATA( DLGK_ITEM_LAST, KEY_END ),
529
DLG_KEYS_DATA( DLGK_ITEM_LAST, KEY_LL ),
530
DLG_KEYS_DATA( DLGK_ITEM_NEXT, '+' ),
531
DLG_KEYS_DATA( DLGK_ITEM_NEXT, KEY_DOWN ),
532
DLG_KEYS_DATA( DLGK_ITEM_NEXT, CHR_NEXT ),
533
DLG_KEYS_DATA( DLGK_ITEM_PREV, '-' ),
534
DLG_KEYS_DATA( DLGK_ITEM_PREV, KEY_UP ),
535
DLG_KEYS_DATA( DLGK_ITEM_PREV, CHR_PREVIOUS ),
536
DLG_KEYS_DATA( DLGK_PAGE_NEXT, KEY_NPAGE ),
537
DLG_KEYS_DATA( DLGK_PAGE_NEXT, DLGK_MOUSE(KEY_NPAGE) ),
538
DLG_KEYS_DATA( DLGK_PAGE_NEXT, DLGK_MOUSE(KEY_NPAGE+KEY_MAX) ),
539
DLG_KEYS_DATA( DLGK_PAGE_PREV, KEY_PPAGE ),
540
DLG_KEYS_DATA( DLGK_PAGE_PREV, DLGK_MOUSE(KEY_PPAGE) ),
541
DLG_KEYS_DATA( DLGK_PAGE_PREV, DLGK_MOUSE(KEY_PPAGE+KEY_MAX) ),
542
DLG_KEYS_DATA( DLGK_GRID_LEFT, KEY_LEFTCOL ),
543
DLG_KEYS_DATA( DLGK_GRID_RIGHT, KEY_RIGHTCOL ),
544
TOGGLEKEY_BINDINGS,
545
END_KEYS_BINDING
546
};
547
/* *INDENT-ON* */
548
549
#ifdef KEY_RESIZE
550
int old_height = height;
551
int old_width = width;
552
#endif
553
ALL_DATA all;
554
MY_DATA *data = all.list;
555
int i, j, k, key2, found, x, y, cur_x, cur_y;
556
int key, fkey;
557
bool save_visit = dialog_state.visit_items;
558
int button;
559
int cur_item;
560
int name_width, text_width, full_width, list_width;
561
int result = DLG_EXIT_UNKNOWN;
562
int num_states;
563
bool first = TRUE;
564
WINDOW *dialog;
565
char *prompt;
566
const char **buttons = dlg_ok_labels();
567
const char *widget_name = "buildlist";
568
569
dialog_state.plain_buttons = TRUE;
570
571
/*
572
* Unlike other uses of --visit-items, we have two windows to visit.
573
*/
574
if (dialog_state.visit_cols)
575
dialog_state.visit_cols = 2;
576
577
memset(&all, 0, sizeof(all));
578
all.items = items;
579
all.item_no = item_no;
580
for (k = 0; k < 2; ++k) {
581
data[k].ip = dlg_calloc(DIALOG_LISTITEM *, (item_no + 2));
582
}
583
fill_both_sides(&all);
584
585
if (dialog_vars.default_item != 0) {
586
cur_item = dlg_default_listitem(items);
587
} else {
588
if ((cur_item = first_item(&all, 0)) < 0)
589
cur_item = first_item(&all, 1);
590
}
591
button = (dialog_state.visit_items
592
? (items[cur_item].state ? sRIGHT : sLEFT)
593
: dlg_default_button());
594
595
dlg_does_output();
596
597
#ifdef KEY_RESIZE
598
retry:
599
#endif
600
601
prompt = dlg_strclone(cprompt);
602
dlg_tab_correct_str(prompt);
603
604
all.use_height = list_height;
605
all.use_width = (2 * (dlg_calc_list_width(item_no, items)
606
+ 4
607
+ 2 * MARGIN)
608
+ 1);
609
all.use_width = MAX(26, all.use_width);
610
if (all.use_height == 0) {
611
/* calculate height without items (4) */
612
dlg_auto_size(title, prompt, &height, &width, MIN_HIGH, all.use_width);
613
dlg_calc_listh(&height, &all.use_height, item_no);
614
} else {
615
dlg_auto_size(title, prompt,
616
&height, &width,
617
MIN_HIGH + all.use_height, all.use_width);
618
}
619
dlg_button_layout(buttons, &width);
620
dlg_print_size(height, width);
621
dlg_ctl_size(height, width);
622
623
/* we need at least two states */
624
if (states == 0 || strlen(states) < 2)
625
states = " *";
626
num_states = (int) strlen(states);
627
628
x = dlg_box_x_ordinate(width);
629
y = dlg_box_y_ordinate(height);
630
631
dialog = dlg_new_window(height, width, y, x);
632
dlg_register_window(dialog, widget_name, binding);
633
dlg_register_buttons(dialog, widget_name, buttons);
634
635
dlg_mouse_setbase(all.base_x = x, all.base_y = y);
636
637
dlg_draw_box2(dialog, 0, 0, height, width, dialog_attr, border_attr, border2_attr);
638
dlg_draw_bottom_box2(dialog, border_attr, border2_attr, dialog_attr);
639
dlg_draw_title(dialog, title);
640
641
dlg_attrset(dialog, dialog_attr);
642
dlg_print_autowrap(dialog, prompt, height, width);
643
644
list_width = (width - 6 * MARGIN - 2) / 2;
645
getyx(dialog, cur_y, cur_x);
646
data[0].box_y = cur_y + 1;
647
data[0].box_x = MARGIN + 1;
648
data[1].box_y = cur_y + 1;
649
data[1].box_x = data[0].box_x + 1 + 2 * MARGIN + list_width;
650
651
/*
652
* After displaying the prompt, we know how much space we really have.
653
* Limit the list to avoid overwriting the ok-button.
654
*/
655
all.use_height = height - MIN_HIGH - cur_y;
656
if (all.use_height <= 0)
657
all.use_height = 1;
658
659
for (k = 0; k < 2; ++k) {
660
/* create new window for the list */
661
data[k].win = dlg_sub_window(dialog, all.use_height, list_width,
662
y + data[k].box_y + 1,
663
x + data[k].box_x + 1);
664
665
/* draw a box around the list items */
666
dlg_draw_box(dialog, data[k].box_y, data[k].box_x,
667
all.use_height + 2 * MARGIN,
668
list_width + 2 * MARGIN,
669
menubox_border_attr, menubox_border2_attr);
670
}
671
672
text_width = 0;
673
name_width = 0;
674
/* Find length of longest item to center buildlist */
675
for (i = 0; i < item_no; i++) {
676
text_width = MAX(text_width, dlg_count_columns(items[i].text));
677
name_width = MAX(name_width, dlg_count_columns(items[i].name));
678
}
679
680
/* If the name+text is wider than the list is allowed, then truncate
681
* one or both of them. If the name is no wider than 1/4 of the list,
682
* leave it intact.
683
*/
684
all.use_width = (list_width - 6 * MARGIN);
685
if (dialog_vars.no_tags && !dialog_vars.no_items) {
686
full_width = MIN(all.use_width, text_width);
687
} else if (dialog_vars.no_items) {
688
full_width = MIN(all.use_width, name_width);
689
} else {
690
if (text_width >= 0
691
&& name_width >= 0
692
&& all.use_width > 0
693
&& text_width + name_width > all.use_width) {
694
int need = (int) (0.25 * all.use_width);
695
if (name_width > need) {
696
int want = (int) (all.use_width * ((double) name_width) /
697
(text_width + name_width));
698
name_width = (want > need) ? want : need;
699
}
700
text_width = all.use_width - name_width;
701
}
702
full_width = text_width + name_width;
703
}
704
705
all.check_x = (all.use_width - full_width) / 2;
706
all.item_x = ((dialog_vars.no_tags
707
? 0
708
: (dialog_vars.no_items
709
? 0
710
: (name_width + 2)))
711
+ all.check_x);
712
713
/* ensure we are scrolled to show the current choice */
714
j = MIN(all.use_height, item_no);
715
for (i = 0; i < 2; ++i) {
716
if ((items[cur_item].state != 0) == i) {
717
int top_item = cur_item - j + 1;
718
if (top_item < 0)
719
top_item = 0;
720
while ((items[top_item].state != 0) != i)
721
++top_item;
722
set_top_item(&all, top_item, i);
723
} else {
724
set_top_item(&all, 0, i);
725
}
726
}
727
728
/* register the new window, along with its borders */
729
for (i = 0; i < 2; ++i) {
730
dlg_mouse_mkbigregion(data[i].box_y + 1,
731
data[i].box_x,
732
all.use_height,
733
list_width + 2,
734
2 * KEY_MAX + (i * (1 + all.use_height)),
735
1, 1, 1 /* by lines */ );
736
}
737
738
dlg_draw_buttons(dialog, height - 2, 0, buttons, button, FALSE, width);
739
740
while (result == DLG_EXIT_UNKNOWN) {
741
int which = (items[cur_item].state != 0);
742
MY_DATA *moi = data + which;
743
int at_top = index2row(&all, moi->top_index, which);
744
int at_end = index2row(&all, -1, which);
745
int at_bot = skip_rows(&all, at_top, all.use_height, which);
746
int was_mouse;
747
748
DLG_TRACE(("# ** state %d:%d top %d (%d:%d:%d) %s\n",
749
cur_item, item_no - 1,
750
moi->top_index,
751
at_top, at_bot, at_end,
752
mySide(which)));
753
754
if (first) {
755
print_both(&all, cur_item);
756
dlg_trace_win(dialog);
757
first = FALSE;
758
}
759
760
if (button < 0) { /* --visit-items */
761
int cur_row = index2row(&all, cur_item, which);
762
cur_y = (data[which].box_y
763
+ cur_row
764
+ 1);
765
if (at_top > 0)
766
cur_y -= at_top;
767
cur_x = (data[which].box_x
768
+ all.check_x + 1);
769
DLG_TRACE(("# ...visit row %d (%d,%d)\n", cur_row, cur_y, cur_x));
770
wmove(dialog, cur_y, cur_x);
771
}
772
773
key = dlg_mouse_wgetch(dialog, &fkey);
774
if (dlg_result_key(key, fkey, &result)) {
775
if (!dlg_button_key(result, &button, &key, &fkey))
776
break;
777
}
778
779
was_mouse = (fkey && is_DLGK_MOUSE(key));
780
if (was_mouse)
781
key -= M_EVENT;
782
783
if (!was_mouse) {
784
;
785
} else if (key >= 2 * KEY_MAX) {
786
i = (key - 2 * KEY_MAX) % (1 + all.use_height);
787
j = (key - 2 * KEY_MAX) / (1 + all.use_height);
788
k = row2index(&all, i + at_top, j);
789
DLG_TRACE(("# MOUSE column %d, row %d ->item %d\n", j, i, k));
790
if (k >= 0 && j < 2) {
791
if (j != which) {
792
/*
793
* Mouse click was in the other column.
794
*/
795
moi = data + j;
796
fix_top_item(&all, k, j);
797
}
798
which = j;
799
at_top = index2row(&all, moi->top_index, which);
800
at_bot = skip_rows(&all, at_top, all.use_height, which);
801
cur_item = k;
802
print_both(&all, cur_item);
803
key = DLGK_TOGGLE; /* force the selected item to toggle */
804
} else {
805
beep();
806
continue;
807
}
808
fkey = FALSE;
809
} else if (key >= KEY_MIN) {
810
if (key > KEY_MAX) {
811
if (which == 0) {
812
key = KEY_RIGHTCOL; /* switch to right-column */
813
fkey = FALSE;
814
} else {
815
key -= KEY_MAX;
816
}
817
} else {
818
if (which == 1) {
819
key = KEY_LEFTCOL; /* switch to left-column */
820
fkey = FALSE;
821
}
822
}
823
key = dlg_lookup_key(dialog, key, &fkey);
824
}
825
826
/*
827
* A space toggles the item status. Normally we put the cursor on
828
* the next available item in the same column. But if there are no
829
* more items in the column, move the cursor to the other column.
830
*/
831
if (key == DLGK_TOGGLE) {
832
int new_choice;
833
int new_state = items[cur_item].state + 1;
834
835
if ((new_choice = next_item(&all, cur_item, which)) == cur_item) {
836
new_choice = prev_item(&all, cur_item, which);
837
}
838
DLG_TRACE(("# cur_item %d, new_choice:%d\n", cur_item, new_choice));
839
/* FIXME - how to test and handle multiple states? */
840
if (new_state >= num_states)
841
new_state = 0;
842
843
items[cur_item].state = new_state;
844
if (order_mode) {
845
fill_one_side(&all, 0);
846
if (new_state) {
847
append_right_side(&all, cur_item);
848
} else {
849
amend_right_side(&all, cur_item);
850
}
851
} else {
852
fill_both_sides(&all);
853
}
854
if (cur_item == moi->top_index) {
855
set_top_item(&all, new_choice, which);
856
}
857
858
if (new_choice >= 0) {
859
fix_top_item(&all, cur_item, !which);
860
cur_item = new_choice;
861
}
862
print_both(&all, cur_item);
863
dlg_trace_win(dialog);
864
continue; /* wait for another key press */
865
}
866
867
/*
868
* Check if key pressed matches first character of any item tag in
869
* list. If there is more than one match, we will cycle through
870
* each one as the same key is pressed repeatedly.
871
*/
872
found = FALSE;
873
if (!fkey) {
874
if (button < 0 || !dialog_state.visit_items) {
875
for (j = cur_item + 1; j < item_no; j++) {
876
if (check_hotkey(items, j, which)) {
877
found = TRUE;
878
i = j;
879
break;
880
}
881
}
882
if (!found) {
883
for (j = 0; j <= cur_item; j++) {
884
if (check_hotkey(items, j, which)) {
885
found = TRUE;
886
i = j;
887
break;
888
}
889
}
890
}
891
if (found)
892
dlg_flush_getc();
893
} else if ((j = dlg_char_to_button(key, buttons)) >= 0) {
894
button = j;
895
ungetch('\n');
896
continue;
897
}
898
}
899
900
/*
901
* A single digit (1-9) positions the selection to that line in the
902
* current screen.
903
*/
904
if (!found
905
&& (key <= '9')
906
&& (key > '0')
907
&& (key - '1' < at_bot)) {
908
found = TRUE;
909
i = key - '1';
910
}
911
912
if (!found && fkey) {
913
switch (key) {
914
case DLGK_FIELD_PREV:
915
if ((button == sRIGHT) && dialog_state.visit_items) {
916
key = DLGK_GRID_LEFT;
917
button = sLEFT;
918
} else {
919
button = dlg_prev_button(buttons, button);
920
dlg_draw_buttons(dialog, height - 2, 0, buttons, button,
921
FALSE, width);
922
if (button == sRIGHT) {
923
key = DLGK_GRID_RIGHT;
924
} else {
925
continue;
926
}
927
}
928
break;
929
case DLGK_FIELD_NEXT:
930
if ((button == sLEFT) && dialog_state.visit_items) {
931
key = DLGK_GRID_RIGHT;
932
button = sRIGHT;
933
} else {
934
button = dlg_next_button(buttons, button);
935
dlg_draw_buttons(dialog, height - 2, 0, buttons, button,
936
FALSE, width);
937
if (button == sLEFT) {
938
key = DLGK_GRID_LEFT;
939
} else {
940
continue;
941
}
942
}
943
break;
944
}
945
}
946
947
if (!found && fkey) {
948
i = cur_item;
949
found = TRUE;
950
switch (key) {
951
case DLGK_GRID_LEFT:
952
i = closest_item(&all, cur_item, 0);
953
fix_top_item(&all, i, 0);
954
break;
955
case DLGK_GRID_RIGHT:
956
if (order_mode) {
957
i = last_item(&all, 1);
958
} else {
959
i = closest_item(&all, cur_item, 1);
960
}
961
fix_top_item(&all, i, 1);
962
break;
963
case DLGK_PAGE_PREV:
964
if (cur_item > moi->top_index) {
965
i = moi->top_index;
966
} else if (moi->top_index != 0) {
967
int temp = at_top;
968
if ((temp -= all.use_height) < 0)
969
temp = 0;
970
i = row2index(&all, temp, which);
971
}
972
break;
973
case DLGK_PAGE_NEXT:
974
if ((at_end - at_bot) < all.use_height) {
975
i = next_item(&all,
976
row2index(&all, at_end, which),
977
which);
978
} else {
979
i = next_item(&all,
980
row2index(&all, at_bot, which),
981
which);
982
at_top = at_bot;
983
set_top_item(&all,
984
next_item(&all,
985
row2index(&all, at_top, which),
986
which),
987
which);
988
at_bot = skip_rows(&all, at_top, all.use_height, which);
989
at_bot = MIN(at_bot, at_end);
990
}
991
break;
992
case DLGK_ITEM_FIRST:
993
i = first_item(&all, which);
994
break;
995
case DLGK_ITEM_LAST:
996
i = last_item(&all, which);
997
break;
998
case DLGK_ITEM_PREV:
999
i = prev_item(&all, cur_item, which);
1000
if (stop_prev(&all, cur_item, which))
1001
continue;
1002
break;
1003
case DLGK_ITEM_NEXT:
1004
i = next_item(&all, cur_item, which);
1005
break;
1006
default:
1007
found = FALSE;
1008
break;
1009
}
1010
}
1011
1012
if (found) {
1013
if (i != cur_item) {
1014
int now_at = index2row(&all, i, which);
1015
int oops = item_no;
1016
int old_item;
1017
1018
DLG_TRACE(("# <--CHOICE %d\n", i));
1019
DLG_TRACE(("# <--topITM %d\n", moi->top_index));
1020
DLG_TRACE(("# <--now_at %d\n", now_at));
1021
DLG_TRACE(("# <--at_top %d\n", at_top));
1022
DLG_TRACE(("# <--at_bot %d\n", at_bot));
1023
1024
if (now_at >= at_bot) {
1025
while (now_at >= at_bot) {
1026
if ((at_bot - at_top) >= all.use_height) {
1027
set_top_item(&all,
1028
next_item(&all, moi->top_index, which),
1029
which);
1030
}
1031
at_top = index2row(&all, moi->top_index, which);
1032
at_bot = skip_rows(&all, at_top, all.use_height, which);
1033
1034
DLG_TRACE(("# ...at_bot %d (now %d vs %d)\n",
1035
at_bot, now_at, at_end));
1036
DLG_TRACE(("# ...topITM %d\n", moi->top_index));
1037
DLG_TRACE(("# ...at_top %d (diff %d)\n", at_top,
1038
at_bot - at_top));
1039
1040
if (at_bot >= at_end) {
1041
/*
1042
* If we bumped into the end, move the top-item
1043
* down by one line so that we can display the
1044
* last item in the list.
1045
*/
1046
if ((at_bot - at_top) > all.use_height) {
1047
set_top_item(&all,
1048
next_item(&all, moi->top_index, which),
1049
which);
1050
} else if (at_top > 0 &&
1051
(at_bot - at_top) >= all.use_height) {
1052
set_top_item(&all,
1053
next_item(&all, moi->top_index, which),
1054
which);
1055
}
1056
break;
1057
}
1058
if (--oops < 0) {
1059
DLG_TRACE(("# OOPS-forward\n"));
1060
break;
1061
}
1062
}
1063
} else if (now_at < at_top) {
1064
while (now_at < at_top) {
1065
old_item = moi->top_index;
1066
set_top_item(&all,
1067
prev_item(&all, moi->top_index, which),
1068
which);
1069
at_top = index2row(&all, moi->top_index, which);
1070
1071
DLG_TRACE(("# ...at_top %d (now %d)\n", at_top, now_at));
1072
DLG_TRACE(("# ...topITM %d\n", moi->top_index));
1073
1074
if (moi->top_index >= old_item)
1075
break;
1076
if (at_top <= now_at)
1077
break;
1078
if (--oops < 0) {
1079
DLG_TRACE(("# OOPS-backward\n"));
1080
break;
1081
}
1082
}
1083
}
1084
DLG_TRACE(("# -->now_at %d\n", now_at));
1085
cur_item = i;
1086
print_both(&all, cur_item);
1087
}
1088
dlg_trace_win(dialog);
1089
continue; /* wait for another key press */
1090
}
1091
1092
if (fkey) {
1093
switch (key) {
1094
case DLGK_ENTER:
1095
result = dlg_enter_buttoncode(button);
1096
break;
1097
case DLGK_LEAVE:
1098
result = dlg_ok_buttoncode(button);
1099
break;
1100
#ifdef KEY_RESIZE
1101
case KEY_RESIZE:
1102
dlg_will_resize(dialog);
1103
/* reset data */
1104
height = old_height;
1105
width = old_width;
1106
free(prompt);
1107
_dlg_resize_cleanup(dialog);
1108
/* repaint */
1109
first = TRUE;
1110
goto retry;
1111
#endif
1112
default:
1113
if (was_mouse) {
1114
if ((key2 = dlg_ok_buttoncode(key)) >= 0) {
1115
result = key2;
1116
break;
1117
}
1118
beep();
1119
}
1120
}
1121
} else if (key > 0) {
1122
beep();
1123
}
1124
}
1125
1126
/*
1127
* If told to re-order the list, update it to reflect the current display:
1128
* a) The left-side will be at the beginning, without gaps.
1129
* b) The right-side will follow, in display-order.
1130
*/
1131
if (order_mode) {
1132
DIALOG_LISTITEM *redo;
1133
int row;
1134
int choice;
1135
int new_item = cur_item;
1136
1137
redo = dlg_calloc(DIALOG_LISTITEM, (size_t) item_no + 1);
1138
assert_ptr(redo, THIS_FUNC);
1139
1140
j = 0;
1141
for (k = 0; k < 2; ++k) {
1142
for (row = 0; row < item_no; ++row) {
1143
if (myItem(all.list + k, row) == 0)
1144
break;
1145
choice = row2index(&all, row, k);
1146
if (choice == cur_item)
1147
new_item = j;
1148
redo[j++] = items[choice];
1149
}
1150
}
1151
1152
cur_item = new_item;
1153
memcpy(items, redo, sizeof(DIALOG_LISTITEM) * (size_t) (item_no + 1));
1154
1155
free(redo);
1156
}
1157
1158
for (k = 0; k < 2; ++k) {
1159
free(data[k].ip);
1160
}
1161
1162
dialog_state.visit_cols = save_visit;
1163
dlg_del_window(dialog);
1164
dlg_mouse_free_regions();
1165
free(prompt);
1166
1167
*current_item = cur_item;
1168
return result;
1169
#undef THIS_FUNC
1170
}
1171
1172
/*
1173
* Display a dialog box with a list of options that can be turned on or off
1174
*/
1175
int
1176
dialog_buildlist(const char *title,
1177
const char *cprompt,
1178
int height,
1179
int width,
1180
int list_height,
1181
int item_no,
1182
char **items,
1183
int order_mode)
1184
{
1185
#define THIS_FUNC "dialog_buildlist"
1186
int result;
1187
int i, j;
1188
DIALOG_LISTITEM *listitems;
1189
bool separate_output = dialog_vars.separate_output;
1190
bool show_status = FALSE;
1191
int current = 0;
1192
char *help_result;
1193
1194
DLG_TRACE(("# buildlist args:\n"));
1195
DLG_TRACE2S("title", title);
1196
DLG_TRACE2S("message", cprompt);
1197
DLG_TRACE2N("height", height);
1198
DLG_TRACE2N("width", width);
1199
DLG_TRACE2N("lheight", list_height);
1200
DLG_TRACE2N("llength", item_no);
1201
/* FIXME dump the items[][] too */
1202
DLG_TRACE2N("order", order_mode != 0);
1203
1204
listitems = dlg_calloc(DIALOG_LISTITEM, (size_t) item_no + 1);
1205
assert_ptr(listitems, THIS_FUNC);
1206
1207
for (i = j = 0; i < item_no; ++i) {
1208
listitems[i].name = items[j++];
1209
listitems[i].text = (dialog_vars.no_items
1210
? dlg_strempty()
1211
: items[j++]);
1212
listitems[i].state = !dlg_strcmp(items[j++], "on");
1213
listitems[i].help = ((dialog_vars.item_help)
1214
? items[j++]
1215
: dlg_strempty());
1216
}
1217
dlg_align_columns(&listitems[0].text, (int) sizeof(DIALOG_LISTITEM), item_no);
1218
1219
result = dlg_buildlist(title,
1220
cprompt,
1221
height,
1222
width,
1223
list_height,
1224
item_no,
1225
listitems,
1226
NULL,
1227
order_mode,
1228
&current);
1229
1230
switch (result) {
1231
case DLG_EXIT_OK: /* FALLTHRU */
1232
case DLG_EXIT_EXTRA:
1233
show_status = TRUE;
1234
break;
1235
case DLG_EXIT_HELP:
1236
dlg_add_help_listitem(&result, &help_result, &listitems[current]);
1237
if ((show_status = dialog_vars.help_status)) {
1238
if (separate_output) {
1239
dlg_add_string(help_result);
1240
} else {
1241
dlg_add_quoted(help_result);
1242
}
1243
} else {
1244
dlg_add_string(help_result);
1245
}
1246
break;
1247
}
1248
1249
if (show_status) {
1250
for (i = 0; i < item_no; i++) {
1251
if (listitems[i].state) {
1252
if (dlg_need_separator())
1253
dlg_add_separator();
1254
if (separate_output) {
1255
dlg_add_string(listitems[i].name);
1256
} else {
1257
dlg_add_quoted(listitems[i].name);
1258
}
1259
}
1260
}
1261
AddLastKey();
1262
}
1263
1264
dlg_free_columns(&listitems[0].text, (int) sizeof(DIALOG_LISTITEM), item_no);
1265
free(listitems);
1266
return result;
1267
#undef THIS_FUNC
1268
}
1269
1270