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