1 /*
2 * $Id: fselect.c,v 1.115 2021/01/16 17:19:15 tom Exp $
3 *
4 * fselect.c -- implements the file-selector box
5 *
6 * Copyright 2000-2020,2021 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 #include <sys/types.h>
28 #include <sys/stat.h>
29
30 #if HAVE_DIRENT_H
31 # include <dirent.h>
32 # define NAMLEN(dirent) strlen((dirent)->d_name)
33 #else
34 # define dirent direct
35 # define NAMLEN(dirent) (dirent)->d_namlen
36 # if HAVE_SYS_NDIR_H
37 # include <sys/ndir.h>
38 # endif
39 # if HAVE_SYS_DIR_H
40 # include <sys/dir.h>
41 # endif
42 # if HAVE_NDIR_H
43 # include <ndir.h>
44 # endif
45 #endif
46
47 # if defined(_FILE_OFFSET_BITS) && defined(HAVE_STRUCT_DIRENT64)
48 # if !defined(_LP64) && (_FILE_OFFSET_BITS == 64)
49 # define DIRENT struct dirent64
50 # else
51 # define DIRENT struct dirent
52 # endif
53 # else
54 # define DIRENT struct dirent
55 # endif
56
57 #define EXT_WIDE 1
58 #define HDR_HIGH 1
59 #define BTN_HIGH (1 + 2 * MARGIN) /* Ok/Cancel, also input-box */
60 #define MIN_HIGH (HDR_HIGH - MARGIN + (BTN_HIGH * 2) + 4 * MARGIN)
61 #define MIN_WIDE (2 * MAX(dlg_count_columns(d_label), dlg_count_columns(f_label)) + 6 * MARGIN + 2 * EXT_WIDE)
62
63 #define MOUSE_D (KEY_MAX + 0)
64 #define MOUSE_F (KEY_MAX + 10000)
65 #define MOUSE_T (KEY_MAX + 20000)
66
67 typedef enum {
68 sDIRS = -3
69 ,sFILES = -2
70 ,sTEXT = -1
71 } STATES;
72
73 typedef struct {
74 WINDOW *par; /* parent window */
75 WINDOW *win; /* this window */
76 int length; /* length of the data[] array */
77 int offset; /* index of first item on screen */
78 int choice; /* index of the selection */
79 int mousex; /* base of mouse-code return-values */
80 unsigned allocd;
81 char **data;
82 } LIST;
83
84 typedef struct {
85 int length;
86 char **data;
87 } MATCH;
88
89 static void
init_list(LIST * list,WINDOW * par,WINDOW * win,int mousex)90 init_list(LIST * list, WINDOW *par, WINDOW *win, int mousex)
91 {
92 list->par = par;
93 list->win = win;
94 list->length = 0;
95 list->offset = 0;
96 list->choice = 0;
97 list->mousex = mousex;
98 list->allocd = 0;
99 list->data = 0;
100 dlg_mouse_mkbigregion(getbegy(win), getbegx(win),
101 getmaxy(win), getmaxx(win),
102 mousex, 1, 1, 1 /* by lines */ );
103 }
104
105 static char *
leaf_of(char * path)106 leaf_of(char *path)
107 {
108 char *leaf = strrchr(path, '/');
109 if (leaf != 0)
110 leaf++;
111 else
112 leaf = path;
113 return leaf;
114 }
115
116 static char *
data_of(LIST * list)117 data_of(LIST * list)
118 {
119 if (list != 0
120 && list->data != 0)
121 return list->data[list->choice];
122 return 0;
123 }
124
125 static void
free_list(LIST * list,int reinit)126 free_list(LIST * list, int reinit)
127 {
128 if (list->data != 0) {
129 int n;
130
131 for (n = 0; list->data[n] != 0; n++)
132 free(list->data[n]);
133 free(list->data);
134 list->data = 0;
135 }
136 if (reinit)
137 init_list(list, list->par, list->win, list->mousex);
138 }
139
140 static void
add_to_list(LIST * list,char * text)141 add_to_list(LIST * list, char *text)
142 {
143 unsigned need;
144
145 need = (unsigned) (list->length + 1);
146 if (need + 1 > list->allocd) {
147 list->allocd = 2 * (need + 1);
148 if (list->data == 0) {
149 list->data = dlg_malloc(char *, list->allocd);
150 } else {
151 list->data = dlg_realloc(char *, list->allocd, list->data);
152 }
153 assert_ptr(list->data, "add_to_list");
154 }
155 list->data[list->length++] = dlg_strclone(text);
156 list->data[list->length] = 0;
157 }
158
159 static void
keep_visible(LIST * list)160 keep_visible(LIST * list)
161 {
162 int high = getmaxy(list->win);
163
164 if (list->choice < list->offset) {
165 list->offset = list->choice;
166 }
167 if (list->choice - list->offset >= high)
168 list->offset = list->choice - high + 1;
169 }
170
171 #define Value(c) (int)((c) & 0xff)
172
173 static int
find_choice(char * target,LIST * list)174 find_choice(char *target, LIST * list)
175 {
176 int choice = list->choice;
177
178 if (*target == 0) {
179 list->choice = 0;
180 } else {
181 int n;
182 int len_1, cmp_1;
183
184 /* find the match with the longest length. If more than one has the
185 * same length, choose the one with the closest match of the final
186 * character.
187 */
188 len_1 = 0;
189 cmp_1 = 256;
190 for (n = 0; n < list->length; n++) {
191 char *a = target;
192 char *b = list->data[n];
193 int len_2, cmp_2;
194
195 len_2 = 0;
196 while ((*a != 0) && (*b != 0) && (*a == *b)) {
197 a++;
198 b++;
199 len_2++;
200 }
201 cmp_2 = Value(*a) - Value(*b);
202 if (cmp_2 < 0)
203 cmp_2 = -cmp_2;
204 if ((len_2 > len_1)
205 || (len_1 == len_2 && cmp_2 < cmp_1)) {
206 len_1 = len_2;
207 cmp_1 = cmp_2;
208 list->choice = n;
209 }
210 }
211 }
212 if (choice != list->choice) {
213 keep_visible(list);
214 }
215 return (choice != list->choice);
216 }
217
218 static void
display_list(LIST * list)219 display_list(LIST * list)
220 {
221 if (list->win != 0) {
222 int n;
223 int x;
224 int y;
225 int top;
226 int bottom;
227
228 dlg_attr_clear(list->win, getmaxy(list->win), getmaxx(list->win), item_attr);
229 for (n = list->offset; n < list->length && list->data[n]; n++) {
230 y = n - list->offset;
231 if (y >= getmaxy(list->win))
232 break;
233 (void) wmove(list->win, y, 0);
234 if (n == list->choice)
235 dlg_attrset(list->win, item_selected_attr);
236 (void) waddstr(list->win, list->data[n]);
237 dlg_attrset(list->win, item_attr);
238 }
239 dlg_attrset(list->win, item_attr);
240
241 getparyx(list->win, y, x);
242
243 top = y - 1;
244 bottom = y + getmaxy(list->win);
245 dlg_draw_scrollbar(list->par,
246 (long) list->offset,
247 (long) list->offset,
248 (long) (list->offset + getmaxy(list->win)),
249 (long) (list->length),
250 x + 1,
251 x + getmaxx(list->win),
252 top,
253 bottom,
254 menubox_border2_attr,
255 menubox_border_attr);
256
257 (void) wmove(list->win, list->choice - list->offset, 0);
258 (void) wnoutrefresh(list->win);
259 }
260 }
261
262 /* FIXME: see arrows.c
263 * This workaround is used to allow two lists to have scroll-tabs at the same
264 * time, by reassigning their return-values to be different. Just for
265 * readability, we use the names of keys with similar connotations, though all
266 * that is really required is that they're distinct, so we can put them in a
267 * switch statement.
268 */
269 #if USE_MOUSE
270 static void
fix_arrows(LIST * list)271 fix_arrows(LIST * list)
272 {
273 if (list->win != 0) {
274 int x;
275 int y;
276 int top;
277 int right;
278 int bottom;
279
280 getparyx(list->win, y, x);
281 top = y - 1;
282 right = getmaxx(list->win);
283 bottom = y + getmaxy(list->win);
284
285 mouse_mkbutton(top, x, right,
286 ((list->mousex == MOUSE_D)
287 ? KEY_PREVIOUS
288 : KEY_PPAGE));
289 mouse_mkbutton(bottom, x, right,
290 ((list->mousex == MOUSE_D)
291 ? KEY_NEXT
292 : KEY_NPAGE));
293 }
294 }
295
296 #else
297 #define fix_arrows(list) /* nothing */
298 #endif
299
300 static bool
show_list(char * target,LIST * list,bool keep)301 show_list(char *target, LIST * list, bool keep)
302 {
303 bool changed = keep || find_choice(target, list);
304 display_list(list);
305 return changed;
306 }
307
308 /*
309 * Highlight the closest match to 'target' in the given list, setting offset
310 * to match.
311 */
312 static bool
show_both_lists(char * input,LIST * d_list,LIST * f_list,bool keep)313 show_both_lists(char *input, LIST * d_list, LIST * f_list, bool keep)
314 {
315 char *leaf = leaf_of(input);
316
317 return show_list(leaf, d_list, keep) || show_list(leaf, f_list, keep);
318 }
319
320 /*
321 * Move up/down in the given list
322 */
323 static bool
change_list(int choice,LIST * list)324 change_list(int choice, LIST * list)
325 {
326 if (data_of(list) != 0) {
327 int last = list->length - 1;
328
329 choice += list->choice;
330 if (choice < 0)
331 choice = 0;
332 if (choice > last)
333 choice = last;
334 list->choice = choice;
335 keep_visible(list);
336 display_list(list);
337 return TRUE;
338 }
339 return FALSE;
340 }
341
342 static void
scroll_list(int direction,LIST * list)343 scroll_list(int direction, LIST * list)
344 {
345 if (data_of(list) != 0) {
346 int length = getmaxy(list->win);
347 if (change_list(direction * length, list))
348 return;
349 }
350 beep();
351 }
352
353 static int
compar(const void * a,const void * b)354 compar(const void *a, const void *b)
355 {
356 return strcmp(*(const char *const *) a, *(const char *const *) b);
357 }
358
359 static void
match(char * name,LIST * d_list,LIST * f_list,MATCH * match_list)360 match(char *name, LIST * d_list, LIST * f_list, MATCH * match_list)
361 {
362 char *test = leaf_of(name);
363 size_t test_len = strlen(test);
364 char **matches = dlg_malloc(char *, (size_t) (d_list->length + f_list->length));
365 size_t data_len = 0;
366
367 if (matches != 0) {
368 int i;
369 char **new_ptr;
370
371 for (i = 2; i < d_list->length; i++) {
372 if (strncmp(test, d_list->data[i], test_len) == 0) {
373 matches[data_len++] = d_list->data[i];
374 }
375 }
376 for (i = 0; i < f_list->length; i++) {
377 if (strncmp(test, f_list->data[i], test_len) == 0) {
378 matches[data_len++] = f_list->data[i];
379 }
380 }
381 if ((new_ptr = dlg_realloc(char *, data_len + 1, matches)) != 0) {
382 matches = new_ptr;
383 } else {
384 free(matches);
385 matches = 0;
386 data_len = 0;
387 }
388 }
389 match_list->data = matches;
390 match_list->length = (int) data_len;
391 }
392
393 static void
free_match(MATCH * match_list)394 free_match(MATCH * match_list)
395 {
396 free(match_list->data);
397 match_list->length = 0;
398 }
399
400 static int
complete(char * name,LIST * d_list,LIST * f_list,char ** buff_ptr)401 complete(char *name, LIST * d_list, LIST * f_list, char **buff_ptr)
402 {
403 MATCH match_list;
404 char *test;
405 size_t test_len;
406 size_t i;
407 char *buff;
408
409 match(name, d_list, f_list, &match_list);
410 if (match_list.length == 0) {
411 free(match_list.data);
412 *buff_ptr = NULL;
413 return 0;
414 }
415
416 test = match_list.data[0];
417 test_len = strlen(test);
418 buff = dlg_malloc(char, test_len + 2);
419 if (match_list.length == 1) {
420 strcpy(buff, test);
421 i = test_len;
422 if (test == data_of(d_list)) {
423 buff[test_len] = '/';
424 i++;
425 }
426 } else {
427 int j;
428
429 for (i = 0; i < test_len; i++) {
430 char test_char = test[i];
431 if (test_char == '\0')
432 break;
433 for (j = 0; j < match_list.length; j++) {
434 if (match_list.data[j][i] != test_char) {
435 break;
436 }
437 }
438 if (j == match_list.length) {
439 (buff)[i] = test_char;
440 } else
441 break;
442 }
443 buff = dlg_realloc(char, i + 1, buff);
444 }
445 free_match(&match_list);
446 buff[i] = '\0';
447 *buff_ptr = buff;
448 return (i != 0);
449 }
450
451 static bool
fill_lists(char * current,char * input,LIST * d_list,LIST * f_list,bool keep)452 fill_lists(char *current, char *input, LIST * d_list, LIST * f_list, bool keep)
453 {
454 bool result = TRUE;
455 bool rescan = FALSE;
456 struct stat sb;
457 int n;
458 char path[MAX_LEN + 1];
459
460 /* check if we've updated the lists */
461 for (n = 0; current[n] && input[n]; n++) {
462 if (current[n] != input[n])
463 break;
464 }
465
466 if (current[n] == input[n]) {
467 result = FALSE;
468 rescan = (n == 0 && d_list->length == 0);
469 } else if (strchr(current + n, '/') == 0
470 && strchr(input + n, '/') == 0) {
471 result = show_both_lists(input, d_list, f_list, keep);
472 } else {
473 rescan = TRUE;
474 }
475
476 if (rescan) {
477 DIR *dp;
478 size_t have = strlen(input);
479 char *leaf;
480
481 if (have > MAX_LEN)
482 have = MAX_LEN;
483 memcpy(current, input, have);
484 current[have] = '\0';
485
486 /* refill the lists */
487 free_list(d_list, TRUE);
488 free_list(f_list, TRUE);
489 memcpy(path, current, have);
490 path[have] = '\0';
491 if ((leaf = strrchr(path, '/')) != 0) {
492 *++leaf = 0;
493 } else {
494 strcpy(path, "./");
495 leaf = path + strlen(path);
496 }
497 DLG_TRACE(("opendir '%s'\n", path));
498 if ((dp = opendir(path)) != 0) {
499 DIRENT *de;
500
501 while ((de = readdir(dp)) != 0) {
502 size_t len = NAMLEN(de);
503 if (len == 0 || (len + have + 2) >= MAX_LEN)
504 continue;
505 memcpy(leaf, de->d_name, len);
506 leaf[len] = '\0';
507 if (stat(path, &sb) == 0) {
508 if ((sb.st_mode & S_IFMT) == S_IFDIR)
509 add_to_list(d_list, leaf);
510 else if (f_list->win)
511 add_to_list(f_list, leaf);
512 }
513 }
514 (void) closedir(dp);
515 /* sort the lists */
516 if (d_list->data != 0 && d_list->length > 1) {
517 qsort(d_list->data,
518 (size_t) d_list->length,
519 sizeof(d_list->data[0]),
520 compar);
521 }
522 if (f_list->data != 0 && f_list->length > 1) {
523 qsort(f_list->data,
524 (size_t) f_list->length,
525 sizeof(f_list->data[0]),
526 compar);
527 }
528 }
529
530 (void) show_both_lists(input, d_list, f_list, FALSE);
531 d_list->offset = d_list->choice;
532 f_list->offset = f_list->choice;
533 result = TRUE;
534 }
535 return result;
536 }
537
538 static bool
usable_state(int state,LIST * dirs,LIST * files)539 usable_state(int state, LIST * dirs, LIST * files)
540 {
541 bool result;
542
543 switch (state) {
544 case sDIRS:
545 result = (dirs->win != 0) && (data_of(dirs) != 0);
546 break;
547 case sFILES:
548 result = (files->win != 0) && (data_of(files) != 0);
549 break;
550 default:
551 result = TRUE;
552 break;
553 }
554 return result;
555 }
556
557 #define which_list() ((state == sFILES) \
558 ? &f_list \
559 : ((state == sDIRS) \
560 ? &d_list \
561 : 0))
562 #define NAVIGATE_BINDINGS \
563 DLG_KEYS_DATA( DLGK_FIELD_NEXT, KEY_RIGHT ), \
564 DLG_KEYS_DATA( DLGK_FIELD_NEXT, TAB ), \
565 DLG_KEYS_DATA( DLGK_FIELD_PREV, KEY_BTAB ), \
566 DLG_KEYS_DATA( DLGK_ITEM_NEXT, KEY_DOWN ), \
567 DLG_KEYS_DATA( DLGK_ITEM_NEXT, CHR_NEXT ), \
568 DLG_KEYS_DATA( DLGK_ITEM_NEXT, KEY_NEXT ), \
569 DLG_KEYS_DATA( DLGK_ITEM_PREV, CHR_PREVIOUS ), \
570 DLG_KEYS_DATA( DLGK_ITEM_PREV, KEY_UP ), \
571 DLG_KEYS_DATA( DLGK_PAGE_NEXT, KEY_NPAGE ), \
572 DLG_KEYS_DATA( DLGK_PAGE_PREV, KEY_PPAGE )
573
574 /*
575 * Display a dialog box for entering a filename
576 */
577 static int
dlg_fselect(const char * title,const char * path,int height,int width,int dselect)578 dlg_fselect(const char *title, const char *path, int height, int width, int dselect)
579 {
580 /* *INDENT-OFF* */
581 static DLG_KEYS_BINDING binding[] = {
582 HELPKEY_BINDINGS,
583 ENTERKEY_BINDINGS,
584 NAVIGATE_BINDINGS,
585 TOGGLEKEY_BINDINGS,
586 END_KEYS_BINDING
587 };
588 static DLG_KEYS_BINDING binding2[] = {
589 INPUTSTR_BINDINGS,
590 HELPKEY_BINDINGS,
591 ENTERKEY_BINDINGS,
592 NAVIGATE_BINDINGS,
593 TOGGLEKEY_BINDINGS,
594 END_KEYS_BINDING
595 };
596 /* *INDENT-ON* */
597
598 #ifdef KEY_RESIZE
599 int old_height = height;
600 int old_width = width;
601 bool resized = FALSE;
602 #endif
603 int tbox_y, tbox_x, tbox_width, tbox_height;
604 int dbox_y, dbox_x, dbox_width, dbox_height;
605 int fbox_y, fbox_x, fbox_width, fbox_height;
606 int show_buttons = TRUE;
607 int offset = 0;
608 int key = 0;
609 int fkey = FALSE;
610 int code;
611 int result = DLG_EXIT_UNKNOWN;
612 int state = dialog_vars.default_button >= 0 ? dlg_default_button() : sTEXT;
613 int button;
614 bool first = (state == sTEXT);
615 bool first_trace = TRUE;
616 char *input;
617 char *completed;
618 char current[MAX_LEN + 1];
619 WINDOW *dialog = 0;
620 WINDOW *w_text = 0;
621 WINDOW *w_work = 0;
622 const char **buttons = dlg_ok_labels();
623 const char *d_label = _("Directories");
624 const char *f_label = _("Files");
625 char *partial = 0;
626 int min_wide = MIN_WIDE;
627 int min_items = height ? 0 : 4;
628 LIST d_list, f_list;
629
630 DLG_TRACE(("# %s args:\n", dselect ? "dselect" : "fselect"));
631 DLG_TRACE2S("title", title);
632 DLG_TRACE2S("path", path);
633 DLG_TRACE2N("height", height);
634 DLG_TRACE2N("width", width);
635
636 dlg_does_output();
637
638 /* Set up the initial value */
639 input = dlg_set_result(path);
640 offset = (int) strlen(input);
641 *current = 0;
642
643 dlg_button_layout(buttons, &min_wide);
644
645 #ifdef KEY_RESIZE
646 retry:
647 #endif
648 dlg_auto_size(title, "", &height, &width, MIN_HIGH + min_items, min_wide);
649
650 dlg_print_size(height, width);
651 dlg_ctl_size(height, width);
652
653 dialog = dlg_new_window(height, width,
654 dlg_box_y_ordinate(height),
655 dlg_box_x_ordinate(width));
656 dlg_register_window(dialog, "fselect", binding);
657 dlg_register_buttons(dialog, "fselect", buttons);
658
659 dlg_mouse_setbase(0, 0);
660
661 dlg_draw_box2(dialog, 0, 0, height, width, dialog_attr, border_attr, border2_attr);
662 dlg_draw_bottom_box2(dialog, border_attr, border2_attr, dialog_attr);
663 dlg_draw_title(dialog, title);
664
665 dlg_attrset(dialog, dialog_attr);
666
667 /* Draw the input field box */
668 tbox_height = 1;
669 tbox_width = width - (4 * MARGIN + 2);
670 tbox_y = height - (BTN_HIGH * 2) + MARGIN;
671 tbox_x = (width - tbox_width) / 2;
672
673 w_text = dlg_der_window(dialog, tbox_height, tbox_width, tbox_y, tbox_x);
674 if (w_text == 0) {
675 result = DLG_EXIT_ERROR;
676 goto finish;
677 }
678
679 dlg_draw_box(dialog, tbox_y - MARGIN, tbox_x - MARGIN,
680 (2 * MARGIN + 1), tbox_width + (MARGIN + EXT_WIDE),
681 menubox_border_attr, menubox_border2_attr);
682 dlg_mouse_mkbigregion(getbegy(dialog) + tbox_y - MARGIN,
683 getbegx(dialog) + tbox_x - MARGIN,
684 1 + (2 * MARGIN),
685 tbox_width + (MARGIN + EXT_WIDE),
686 MOUSE_T, 1, 1, 3 /* doesn't matter */ );
687
688 dlg_register_window(w_text, "fselect2", binding2);
689
690 /* Draw the directory listing box */
691 if (dselect)
692 dbox_width = (width - (6 * MARGIN));
693 else
694 dbox_width = (width - (6 * MARGIN + 2 * EXT_WIDE)) / 2;
695 dbox_height = height - MIN_HIGH;
696 dbox_y = (2 * MARGIN + 1);
697 dbox_x = tbox_x;
698
699 w_work = dlg_der_window(dialog, dbox_height, dbox_width, dbox_y, dbox_x);
700 if (w_work == 0) {
701 result = DLG_EXIT_ERROR;
702 goto finish;
703 }
704
705 (void) mvwaddstr(dialog, dbox_y - (MARGIN + 1), dbox_x - MARGIN, d_label);
706 dlg_draw_box(dialog,
707 dbox_y - MARGIN, dbox_x - MARGIN,
708 dbox_height + (MARGIN + 1), dbox_width + (MARGIN + 1),
709 menubox_border_attr, menubox_border2_attr);
710 init_list(&d_list, dialog, w_work, MOUSE_D);
711
712 if (!dselect) {
713 /* Draw the filename listing box */
714 fbox_height = dbox_height;
715 fbox_width = dbox_width;
716 fbox_y = dbox_y;
717 fbox_x = tbox_x + dbox_width + (2 * MARGIN);
718
719 w_work = dlg_der_window(dialog, fbox_height, fbox_width, fbox_y, fbox_x);
720 if (w_work == 0) {
721 result = DLG_EXIT_ERROR;
722 goto finish;
723 }
724
725 (void) mvwaddstr(dialog, fbox_y - (MARGIN + 1), fbox_x - MARGIN, f_label);
726 dlg_draw_box(dialog,
727 fbox_y - MARGIN, fbox_x - MARGIN,
728 fbox_height + (MARGIN + 1), fbox_width + (MARGIN + 1),
729 menubox_border_attr, menubox_border2_attr);
730 init_list(&f_list, dialog, w_work, MOUSE_F);
731 } else {
732 memset(&f_list, 0, sizeof(f_list));
733 }
734
735 while (result == DLG_EXIT_UNKNOWN) {
736
737 if (fill_lists(current, input, &d_list, &f_list, state < sTEXT))
738 show_buttons = TRUE;
739
740 #ifdef KEY_RESIZE
741 if (resized) {
742 resized = FALSE;
743 dlg_show_string(w_text, input, offset, inputbox_attr,
744 0, 0, tbox_width, FALSE, first);
745 }
746 #endif
747
748 /*
749 * The last field drawn determines where the cursor is shown:
750 */
751 if (show_buttons) {
752 show_buttons = FALSE;
753 button = (state < 0) ? 0 : state;
754 dlg_draw_buttons(dialog, height - 2, 0, buttons, button, FALSE, width);
755 }
756
757 if (first_trace) {
758 first_trace = FALSE;
759 dlg_trace_win(dialog);
760 }
761
762 if (state < 0) {
763 switch (state) {
764 case sTEXT:
765 dlg_set_focus(dialog, w_text);
766 break;
767 case sFILES:
768 dlg_set_focus(dialog, f_list.win);
769 break;
770 case sDIRS:
771 dlg_set_focus(dialog, d_list.win);
772 break;
773 }
774 }
775
776 if (first) {
777 (void) wrefresh(dialog);
778 } else {
779 fix_arrows(&d_list);
780 fix_arrows(&f_list);
781 key = dlg_mouse_wgetch((state == sTEXT) ? w_text : dialog, &fkey);
782 if (dlg_result_key(key, fkey, &result)) {
783 if (!dlg_button_key(result, &button, &key, &fkey))
784 break;
785 }
786 }
787
788 if (key == DLGK_TOGGLE) {
789 key = DLGK_SELECT;
790 fkey = TRUE;
791 }
792
793 if (fkey) {
794 switch (key) {
795 case DLGK_MOUSE(KEY_PREVIOUS):
796 state = sDIRS;
797 scroll_list(-1, which_list());
798 continue;
799 case DLGK_MOUSE(KEY_NEXT):
800 state = sDIRS;
801 scroll_list(1, which_list());
802 continue;
803 case DLGK_MOUSE(KEY_PPAGE):
804 state = sFILES;
805 scroll_list(-1, which_list());
806 continue;
807 case DLGK_MOUSE(KEY_NPAGE):
808 state = sFILES;
809 scroll_list(1, which_list());
810 continue;
811 case DLGK_PAGE_PREV:
812 scroll_list(-1, which_list());
813 continue;
814 case DLGK_PAGE_NEXT:
815 scroll_list(1, which_list());
816 continue;
817 case DLGK_ITEM_PREV:
818 if (change_list(-1, which_list()))
819 continue;
820 /* FALLTHRU */
821 case DLGK_FIELD_PREV:
822 show_buttons = TRUE;
823 do {
824 state = dlg_prev_ok_buttonindex(state, sDIRS);
825 } while (!usable_state(state, &d_list, &f_list));
826 continue;
827 case DLGK_ITEM_NEXT:
828 if (change_list(1, which_list()))
829 continue;
830 /* FALLTHRU */
831 case DLGK_FIELD_NEXT:
832 show_buttons = TRUE;
833 do {
834 state = dlg_next_ok_buttonindex(state, sDIRS);
835 } while (!usable_state(state, &d_list, &f_list));
836 continue;
837 case DLGK_SELECT:
838 completed = 0;
839 if (partial != 0) {
840 free(partial);
841 partial = 0;
842 }
843 if (state == sFILES && !dselect) {
844 completed = data_of(&f_list);
845 } else if (state == sDIRS) {
846 completed = data_of(&d_list);
847 } else {
848 if (complete(input, &d_list, &f_list, &partial)) {
849 completed = partial;
850 }
851 }
852 if (completed != 0) {
853 state = sTEXT;
854 show_buttons = TRUE;
855 strcpy(leaf_of(input), completed);
856 offset = (int) strlen(input);
857 dlg_show_string(w_text, input, offset, inputbox_attr,
858 0, 0, tbox_width, 0, first);
859 if (partial != NULL) {
860 free(partial);
861 partial = 0;
862 }
863 continue;
864 } else { /* if (state < sTEXT) */
865 (void) beep();
866 continue;
867 }
868 /* FALLTHRU */
869 case DLGK_ENTER:
870 result = (state > 0) ? dlg_enter_buttoncode(state) : DLG_EXIT_OK;
871 continue;
872 case DLGK_LEAVE:
873 if (state >= 0)
874 result = dlg_ok_buttoncode(state);
875 break;
876 #ifdef KEY_RESIZE
877 case KEY_RESIZE:
878 dlg_will_resize(dialog);
879 /* reset data */
880 height = old_height;
881 width = old_width;
882 show_buttons = TRUE;
883 *current = 0;
884 resized = TRUE;
885 /* repaint */
886 free_list(&d_list, FALSE);
887 free_list(&f_list, FALSE);
888 _dlg_resize_cleanup(dialog);
889 goto retry;
890 #endif
891 default:
892 if (key >= DLGK_MOUSE(MOUSE_T)) {
893 state = sTEXT;
894 continue;
895 } else if (key >= DLGK_MOUSE(MOUSE_F)) {
896 if (f_list.win != 0) {
897 state = sFILES;
898 f_list.choice = (key - DLGK_MOUSE(MOUSE_F)) + f_list.offset;
899 display_list(&f_list);
900 }
901 continue;
902 } else if (key >= DLGK_MOUSE(MOUSE_D)) {
903 if (d_list.win != 0) {
904 state = sDIRS;
905 d_list.choice = (key - DLGK_MOUSE(MOUSE_D)) + d_list.offset;
906 display_list(&d_list);
907 }
908 continue;
909 } else if (is_DLGK_MOUSE(key)
910 && (code = dlg_ok_buttoncode(key - M_EVENT)) >= 0) {
911 result = code;
912 continue;
913 }
914 break;
915 }
916 }
917
918 if (state < 0) { /* Input box selected if we're editing */
919 int edit = dlg_edit_string(input, &offset, key, fkey, first);
920
921 if (edit) {
922 dlg_show_string(w_text, input, offset, inputbox_attr,
923 0, 0, tbox_width, 0, first);
924 first = FALSE;
925 state = sTEXT;
926 }
927 } else if ((code = dlg_char_to_button(key, buttons)) >= 0) {
928 result = dlg_ok_buttoncode(code);
929 break;
930 }
931 }
932 AddLastKey();
933
934 dlg_unregister_window(w_text);
935 dlg_del_window(dialog);
936 dlg_mouse_free_regions();
937 free_list(&d_list, FALSE);
938 free_list(&f_list, FALSE);
939
940 finish:
941 if (partial != 0)
942 free(partial);
943 return result;
944 }
945
946 /*
947 * Display a dialog box for entering a filename
948 */
949 int
dialog_fselect(const char * title,const char * path,int height,int width)950 dialog_fselect(const char *title, const char *path, int height, int width)
951 {
952 return dlg_fselect(title, path, height, width, FALSE);
953 }
954
955 /*
956 * Display a dialog box for entering a directory
957 */
958 int
dialog_dselect(const char * title,const char * path,int height,int width)959 dialog_dselect(const char *title, const char *path, int height, int width)
960 {
961 return dlg_fselect(title, path, height, width, TRUE);
962 }
963