xref: /freebsd/contrib/dialog/editbox.c (revision 8881d206f4e68b564c2c5f50fc717086fc3e827a)
1 /*
2  *  $Id: editbox.c,v 1.80 2020/11/23 00:27:21 tom Exp $
3  *
4  *  editbox.c -- implements the edit box
5  *
6  *  Copyright 2007-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 <dialog.h>
25 #include <dlg_keys.h>
26 
27 #include <sys/stat.h>
28 
29 #define sTEXT -1
30 
31 static void
32 fail_list(void)
33 {
34     dlg_exiterr("File too large");
35 }
36 
37 static void
38 grow_list(char ***list, int *have, int want)
39 {
40     if (want > *have) {
41 	size_t last = (size_t) *have;
42 	size_t need = (size_t) (want | 31) + 3;
43 	*have = (int) need;
44 	(*list) = dlg_realloc(char *, need, *list);
45 	if ((*list) == 0) {
46 	    fail_list();
47 	} else {
48 	    while (++last < need) {
49 		(*list)[last] = 0;
50 	    }
51 	}
52     }
53 }
54 
55 static void
56 load_list(const char *file, char ***list, int *rows)
57 {
58     char *blob = 0;
59     struct stat sb;
60     size_t size;
61 
62     *list = 0;
63     *rows = 0;
64 
65     if (stat(file, &sb) < 0 ||
66 	(sb.st_mode & S_IFMT) != S_IFREG)
67 	dlg_exiterr("Not a file: %s", file);
68 
69     size = (size_t) sb.st_size;
70     if ((blob = dlg_malloc(char, size + 2)) == 0) {
71 	fail_list();
72     } else {
73 	FILE *fp;
74 	unsigned n, pass;
75 
76 	blob[size] = '\0';
77 
78 	if ((fp = fopen(file, "r")) == 0)
79 	    dlg_exiterr("Cannot open: %s", file);
80 	size = fread(blob, sizeof(char), size, fp);
81 	fclose(fp);
82 
83 	/*
84 	 * If the file is not empty, ensure that it ends with a newline.
85 	 */
86 	if (size != 0 && blob[size - 1] != '\n') {
87 	    blob[++size - 1] = '\n';
88 	    blob[size] = '\0';
89 	}
90 
91 	for (pass = 0; pass < 2; ++pass) {
92 	    int first = TRUE;
93 	    unsigned need = 0;
94 
95 	    for (n = 0; n < size; ++n) {
96 		if (first && pass) {
97 		    (*list)[need] = blob + n;
98 		    first = FALSE;
99 		}
100 		if (blob[n] == '\n') {
101 		    first = TRUE;
102 		    ++need;
103 		    if (pass)
104 			blob[n] = '\0';
105 		}
106 	    }
107 	    if (pass) {
108 		if (need == 0) {
109 		    (*list)[0] = dlg_strclone("");
110 		    (*list)[1] = 0;
111 		} else {
112 		    for (n = 0; n < need; ++n) {
113 			(*list)[n] = dlg_strclone((*list)[n]);
114 		    }
115 		    (*list)[need] = 0;
116 		}
117 	    } else {
118 		grow_list(list, rows, (int) need + 1);
119 	    }
120 	}
121 	free(blob);
122     }
123 }
124 
125 static void
126 free_list(char ***list, int *rows)
127 {
128     if (*list != 0) {
129 	int n;
130 	for (n = 0; n < (*rows); ++n) {
131 	    if ((*list)[n] != 0)
132 		free((*list)[n]);
133 	}
134 	free(*list);
135 	*list = 0;
136     }
137     *rows = 0;
138 }
139 
140 /*
141  * Display a single row in the editing window:
142  * thisrow is the actual row number that's being displayed.
143  * show_row is the row number that's highlighted for edit.
144  * base_row is the first row number in the window
145  */
146 static bool
147 display_one(WINDOW *win,
148 	    char *text,
149 	    int thisrow,
150 	    int show_row,
151 	    int base_row,
152 	    int chr_offset)
153 {
154     bool result;
155 
156     if (text != 0) {
157 	dlg_show_string(win,
158 			text,
159 			chr_offset,
160 			((thisrow == show_row)
161 			 ? form_active_text_attr
162 			 : form_text_attr),
163 			thisrow - base_row,
164 			0,
165 			getmaxx(win),
166 			FALSE,
167 			FALSE);
168 	result = TRUE;
169     } else {
170 	result = FALSE;
171     }
172     return result;
173 }
174 
175 static void
176 display_all(WINDOW *win,
177 	    char **list,
178 	    int show_row,
179 	    int firstrow,
180 	    int lastrow,
181 	    int chr_offset)
182 {
183     int limit = getmaxy(win);
184     int row;
185 
186     dlg_attr_clear(win, getmaxy(win), getmaxx(win), dialog_attr);
187     if (lastrow - firstrow >= limit)
188 	lastrow = firstrow + limit;
189     for (row = firstrow; row < lastrow; ++row) {
190 	if (!display_one(win, list[row],
191 			 row, show_row, firstrow,
192 			 (row == show_row) ? chr_offset : 0))
193 	    break;
194     }
195 }
196 
197 static int
198 size_list(char **list)
199 {
200     int result = 0;
201 
202     if (list != 0) {
203 	while (*list++ != 0) {
204 	    ++result;
205 	}
206     }
207     return result;
208 }
209 
210 static bool
211 scroll_to(int pagesize, int rows, int *base_row, int *this_row, int target)
212 {
213     bool result = FALSE;
214 
215     if (target < *base_row) {
216 	if (target < 0) {
217 	    if (*base_row == 0 && *this_row == 0) {
218 		beep();
219 	    } else {
220 		*this_row = 0;
221 		*base_row = 0;
222 		result = TRUE;
223 	    }
224 	} else {
225 	    *this_row = target;
226 	    *base_row = target;
227 	    result = TRUE;
228 	}
229     } else if (target >= rows) {
230 	if (*this_row < rows - 1) {
231 	    *this_row = rows - 1;
232 	    *base_row = rows - 1;
233 	    result = TRUE;
234 	} else {
235 	    beep();
236 	}
237     } else if (target >= *base_row + pagesize) {
238 	*this_row = target;
239 	*base_row = target;
240 	result = TRUE;
241     } else {
242 	*this_row = target;
243 	result = FALSE;
244     }
245     if (pagesize < rows) {
246 	if (*base_row + pagesize >= rows) {
247 	    *base_row = rows - pagesize;
248 	}
249     } else {
250 	*base_row = 0;
251     }
252     return result;
253 }
254 
255 static int
256 col_to_chr_offset(const char *text, int col)
257 {
258     const int *cols = dlg_index_columns(text);
259     const int *indx = dlg_index_wchars(text);
260     bool found = FALSE;
261     int result = 0;
262     unsigned n;
263     unsigned len = (unsigned) dlg_count_wchars(text);
264 
265     for (n = 0; n < len; ++n) {
266 	if (cols[n] <= col && cols[n + 1] > col) {
267 	    result = indx[n];
268 	    found = TRUE;
269 	    break;
270 	}
271     }
272     if (!found && len && cols[len] == col) {
273 	result = indx[len];
274     }
275     return result;
276 }
277 
278 #define Scroll_To(target) scroll_to(pagesize, listsize, &base_row, &thisrow, target)
279 #define SCROLL_TO(target) show_all = Scroll_To(target)
280 
281 #define PREV_ROW (*list)[thisrow - 1]
282 #define THIS_ROW (*list)[thisrow]
283 #define NEXT_ROW (*list)[thisrow + 1]
284 
285 #define UPDATE_COL(input) col_offset = dlg_edit_offset(input, chr_offset, box_width)
286 
287 static int
288 widest_line(char **list)
289 {
290     int result = MAX_LEN;
291 
292     if (list != 0) {
293 	char *value;
294 
295 	while ((value = *list++) != 0) {
296 	    int check = (int) strlen(value);
297 	    if (check > result)
298 		result = check;
299 	}
300     }
301     return result;
302 }
303 
304 #define NAVIGATE_BINDINGS \
305 	DLG_KEYS_DATA( DLGK_GRID_DOWN,	KEY_DOWN ), \
306 	DLG_KEYS_DATA( DLGK_GRID_RIGHT,	KEY_RIGHT ), \
307 	DLG_KEYS_DATA( DLGK_GRID_LEFT,	KEY_LEFT ), \
308 	DLG_KEYS_DATA( DLGK_GRID_UP,	KEY_UP ), \
309 	DLG_KEYS_DATA( DLGK_FIELD_NEXT,	TAB ), \
310 	DLG_KEYS_DATA( DLGK_FIELD_PREV,	KEY_BTAB ), \
311 	DLG_KEYS_DATA( DLGK_PAGE_FIRST,	KEY_HOME ), \
312 	DLG_KEYS_DATA( DLGK_PAGE_LAST,	KEY_END ), \
313 	DLG_KEYS_DATA( DLGK_PAGE_LAST,	KEY_LL ), \
314 	DLG_KEYS_DATA( DLGK_PAGE_NEXT,	KEY_NPAGE ), \
315 	DLG_KEYS_DATA( DLGK_PAGE_NEXT,	DLGK_MOUSE(KEY_NPAGE) ), \
316 	DLG_KEYS_DATA( DLGK_PAGE_PREV,	KEY_PPAGE ), \
317 	DLG_KEYS_DATA( DLGK_PAGE_PREV,	DLGK_MOUSE(KEY_PPAGE) )
318 /*
319  * Display a dialog box for editing a copy of a file
320  */
321 int
322 dlg_editbox(const char *title,
323 	    char ***list,
324 	    int *rows,
325 	    int height,
326 	    int width)
327 {
328     /* *INDENT-OFF* */
329     static DLG_KEYS_BINDING binding[] = {
330 	HELPKEY_BINDINGS,
331 	ENTERKEY_BINDINGS,
332 	NAVIGATE_BINDINGS,
333 	TOGGLEKEY_BINDINGS,
334 	END_KEYS_BINDING
335     };
336     static DLG_KEYS_BINDING binding2[] = {
337 	INPUTSTR_BINDINGS,
338 	HELPKEY_BINDINGS,
339 	ENTERKEY_BINDINGS,
340 	NAVIGATE_BINDINGS,
341 	/* no TOGGLEKEY_BINDINGS, since that includes space... */
342 	END_KEYS_BINDING
343     };
344     /* *INDENT-ON* */
345 
346 #ifdef KEY_RESIZE
347     int old_height = height;
348     int old_width = width;
349 #endif
350     int x, y, box_y, box_x, box_height, box_width;
351     int show_buttons;
352     int thisrow, base_row, lastrow;
353     int goal_col = -1;
354     int col_offset = 0;
355     int chr_offset = 0;
356     int key, fkey, code;
357     int pagesize;
358     int listsize = size_list(*list);
359     int result = DLG_EXIT_UNKNOWN;
360     int state;
361     size_t max_len = (size_t) dlg_max_input(widest_line(*list));
362     char *buffer;
363     bool show_all, show_one;
364     bool first_trace = TRUE;
365     WINDOW *dialog;
366     WINDOW *editing;
367     DIALOG_VARS save_vars;
368     const char **buttons = dlg_ok_labels();
369     int mincols = (3 * COLS / 4);
370 
371     DLG_TRACE(("# editbox args:\n"));
372     DLG_TRACE2S("title", title);
373     /* FIXME dump the rows & list */
374     DLG_TRACE2N("height", height);
375     DLG_TRACE2N("width", width);
376 
377     dlg_save_vars(&save_vars);
378     dialog_vars.separate_output = TRUE;
379 
380     dlg_does_output();
381 
382     buffer = dlg_malloc(char, max_len + 1);
383     assert_ptr(buffer, "dlg_editbox");
384 
385     thisrow = base_row = lastrow = 0;
386 
387 #ifdef KEY_RESIZE
388   retry:
389 #endif
390     show_buttons = TRUE;
391     state = dialog_vars.default_button >= 0 ? dlg_default_button() : sTEXT;
392     fkey = 0;
393 
394     dlg_button_layout(buttons, &mincols);
395     dlg_auto_size(title, "", &height, &width, 3 * LINES / 4, mincols);
396     dlg_print_size(height, width);
397     dlg_ctl_size(height, width);
398 
399     x = dlg_box_x_ordinate(width);
400     y = dlg_box_y_ordinate(height);
401 
402     dialog = dlg_new_window(height, width, y, x);
403     dlg_register_window(dialog, "editbox", binding);
404     dlg_register_buttons(dialog, "editbox", buttons);
405 
406     dlg_mouse_setbase(x, y);
407 
408     dlg_draw_box2(dialog, 0, 0, height, width, dialog_attr, border_attr, border2_attr);
409     dlg_draw_bottom_box2(dialog, border_attr, border2_attr, dialog_attr);
410     dlg_draw_title(dialog, title);
411 
412     dlg_attrset(dialog, dialog_attr);
413 
414     /* Draw the editing field in a box */
415     box_y = MARGIN + 0;
416     box_x = MARGIN + 1;
417     box_width = width - 2 - (2 * MARGIN);
418     box_height = height - (4 * MARGIN);
419 
420     dlg_draw_box(dialog,
421 		 box_y,
422 		 box_x,
423 		 box_height,
424 		 box_width,
425 		 border_attr, border2_attr);
426     dlg_mouse_mkbigregion(box_y + MARGIN,
427 			  box_x + MARGIN,
428 			  box_height - (2 * MARGIN),
429 			  box_width - (2 * MARGIN),
430 			  KEY_MAX, 1, 1, 3);
431     editing = dlg_sub_window(dialog,
432 			     box_height - (2 * MARGIN),
433 			     box_width - (2 * MARGIN),
434 			     getbegy(dialog) + box_y + 1,
435 			     getbegx(dialog) + box_x + 1);
436     dlg_register_window(editing, "editbox2", binding2);
437 
438     show_all = TRUE;
439     show_one = FALSE;
440     pagesize = getmaxy(editing);
441 
442     while (result == DLG_EXIT_UNKNOWN) {
443 	bool was_mouse;
444 	char *input;
445 
446 	if (show_all) {
447 	    display_all(editing, *list, thisrow, base_row, listsize, chr_offset);
448 	    display_one(editing, THIS_ROW,
449 			thisrow, thisrow, base_row, chr_offset);
450 	    show_all = FALSE;
451 	    show_one = TRUE;
452 	} else {
453 	    if (thisrow != lastrow) {
454 		display_one(editing, (*list)[lastrow],
455 			    lastrow, thisrow, base_row, 0);
456 		show_one = TRUE;
457 	    }
458 	}
459 	if (show_one) {
460 	    display_one(editing, THIS_ROW,
461 			thisrow, thisrow, base_row, chr_offset);
462 	    getyx(editing, y, x);
463 	    dlg_draw_scrollbar(dialog,
464 			       base_row,
465 			       base_row,
466 			       base_row + pagesize,
467 			       listsize,
468 			       box_x,
469 			       box_x + getmaxx(editing),
470 			       box_y + 0,
471 			       box_y + getmaxy(editing) + 1,
472 			       border2_attr,
473 			       border_attr);
474 	    wmove(editing, y, x);
475 	    show_one = FALSE;
476 	}
477 	lastrow = thisrow;
478 	input = THIS_ROW;
479 
480 	/*
481 	 * The last field drawn determines where the cursor is shown:
482 	 */
483 	if (show_buttons) {
484 	    show_buttons = FALSE;
485 	    UPDATE_COL(input);
486 	    if (state != sTEXT) {
487 		display_one(editing, input, thisrow,
488 			    -1, base_row, 0);
489 		wrefresh(editing);
490 	    }
491 	    dlg_draw_buttons(dialog,
492 			     height - 2,
493 			     0,
494 			     buttons,
495 			     (state != sTEXT) ? state : 99,
496 			     FALSE,
497 			     width);
498 	    if (state == sTEXT) {
499 		display_one(editing, input, thisrow,
500 			    thisrow, base_row, chr_offset);
501 	    }
502 	}
503 
504 	if (first_trace) {
505 	    first_trace = FALSE;
506 	    dlg_trace_win(dialog);
507 	}
508 
509 	key = dlg_mouse_wgetch((state == sTEXT) ? editing : dialog, &fkey);
510 	if (key == ERR) {
511 	    result = DLG_EXIT_ERROR;
512 	    break;
513 	} else if (key == ESC) {
514 	    result = DLG_EXIT_ESC;
515 	    break;
516 	}
517 	if (state != sTEXT) {
518 	    if (dlg_result_key(key, fkey, &result)) {
519 		if (!dlg_button_key(result, &code, &key, &fkey))
520 		    break;
521 	    }
522 	}
523 
524 	was_mouse = (fkey && is_DLGK_MOUSE(key));
525 	if (was_mouse)
526 	    key -= M_EVENT;
527 
528 	/*
529 	 * Handle mouse clicks first, since we want to know if this is a
530 	 * button, or something that dlg_edit_string() should handle.
531 	 */
532 	if (fkey
533 	    && was_mouse
534 	    && (code = dlg_ok_buttoncode(key)) >= 0) {
535 	    result = code;
536 	    continue;
537 	}
538 
539 	if (was_mouse
540 	    && (key >= KEY_MAX)) {
541 	    int wide = getmaxx(editing);
542 	    int cell = key - KEY_MAX;
543 	    int check = (cell / wide) + base_row;
544 	    if (check < listsize) {
545 		thisrow = check;
546 		col_offset = (cell % wide);
547 		chr_offset = col_to_chr_offset(THIS_ROW, col_offset);
548 		show_one = TRUE;
549 		if (state != sTEXT) {
550 		    state = sTEXT;
551 		    show_buttons = TRUE;
552 		}
553 	    } else {
554 		beep();
555 	    }
556 	    continue;
557 	} else if (was_mouse && key >= KEY_MIN) {
558 	    key = dlg_lookup_key(dialog, key, &fkey);
559 	}
560 
561 	if (state == sTEXT) {	/* editing box selected */
562 	    int edit = 0;
563 
564 	    /*
565 	     * Intercept scrolling keys that dlg_edit_string() does not
566 	     * understand.
567 	     */
568 	    if (fkey) {
569 		bool moved = TRUE;
570 
571 		switch (key) {
572 		case DLGK_GRID_UP:
573 		    SCROLL_TO(thisrow - 1);
574 		    break;
575 		case DLGK_GRID_DOWN:
576 		    SCROLL_TO(thisrow + 1);
577 		    break;
578 		case DLGK_PAGE_FIRST:
579 		    SCROLL_TO(0);
580 		    break;
581 		case DLGK_PAGE_LAST:
582 		    SCROLL_TO(listsize);
583 		    break;
584 		case DLGK_PAGE_NEXT:
585 		    SCROLL_TO(base_row + pagesize);
586 		    break;
587 		case DLGK_PAGE_PREV:
588 		    if (thisrow > base_row) {
589 			SCROLL_TO(base_row);
590 		    } else {
591 			SCROLL_TO(base_row - pagesize);
592 		    }
593 		    break;
594 		case DLGK_DELETE_LEFT:
595 		    if (chr_offset == 0) {
596 			if (thisrow == 0) {
597 			    beep();
598 			} else {
599 			    size_t len = (strlen(THIS_ROW) +
600 					  strlen(PREV_ROW) + 1);
601 			    char *tmp = dlg_malloc(char, len);
602 
603 			    assert_ptr(tmp, "dlg_editbox");
604 
605 			    chr_offset = dlg_count_wchars(PREV_ROW);
606 			    UPDATE_COL(PREV_ROW);
607 			    goal_col = col_offset;
608 
609 			    sprintf(tmp, "%s%s", PREV_ROW, THIS_ROW);
610 			    if (len > max_len)
611 				tmp[max_len] = '\0';
612 
613 			    free(PREV_ROW);
614 			    PREV_ROW = tmp;
615 			    for (y = thisrow; y < listsize; ++y) {
616 				(*list)[y] = (*list)[y + 1];
617 			    }
618 			    --listsize;
619 			    --thisrow;
620 			    (void) Scroll_To(thisrow);
621 
622 			    show_all = TRUE;
623 			}
624 		    } else {
625 			/* dlg_edit_string() can handle this case */
626 			moved = FALSE;
627 		    }
628 		    break;
629 		default:
630 		    moved = FALSE;
631 		    break;
632 		}
633 		if (moved) {
634 		    if (thisrow != lastrow) {
635 			if (goal_col < 0)
636 			    goal_col = col_offset;
637 			chr_offset = col_to_chr_offset(THIS_ROW, goal_col);
638 		    } else {
639 			UPDATE_COL(THIS_ROW);
640 		    }
641 		    continue;
642 		}
643 	    }
644 	    strncpy(buffer, input, max_len - 1)[max_len - 1] = '\0';
645 	    edit = dlg_edit_string(buffer, &chr_offset, key, fkey, FALSE);
646 
647 	    if (edit) {
648 		goal_col = UPDATE_COL(input);
649 		if (strcmp(input, buffer)) {
650 		    free(input);
651 		    THIS_ROW = dlg_strclone(buffer);
652 		    input = THIS_ROW;
653 		}
654 		display_one(editing, input, thisrow,
655 			    thisrow, base_row, chr_offset);
656 		continue;
657 	    }
658 	}
659 
660 	/* handle non-functionkeys */
661 	if (!fkey && (code = dlg_char_to_button(key, buttons)) >= 0) {
662 	    dlg_del_window(dialog);
663 	    result = dlg_ok_buttoncode(code);
664 	    continue;
665 	}
666 
667 	/* handle functionkeys */
668 	if (fkey) {
669 	    switch (key) {
670 	    case DLGK_GRID_UP:
671 	    case DLGK_GRID_LEFT:
672 	    case DLGK_FIELD_PREV:
673 		show_buttons = TRUE;
674 		state = dlg_prev_ok_buttonindex(state, sTEXT);
675 		break;
676 	    case DLGK_GRID_RIGHT:
677 	    case DLGK_GRID_DOWN:
678 	    case DLGK_FIELD_NEXT:
679 		show_buttons = TRUE;
680 		state = dlg_next_ok_buttonindex(state, sTEXT);
681 		break;
682 	    case DLGK_ENTER:
683 		if (state == sTEXT) {
684 		    const int *indx = dlg_index_wchars(THIS_ROW);
685 		    int split = indx[chr_offset];
686 		    char *tmp = dlg_strclone(THIS_ROW + split);
687 
688 		    assert_ptr(tmp, "dlg_editbox");
689 		    grow_list(list, rows, listsize + 1);
690 		    ++listsize;
691 		    for (y = listsize; y > thisrow; --y) {
692 			(*list)[y] = (*list)[y - 1];
693 		    }
694 		    THIS_ROW[split] = '\0';
695 		    ++thisrow;
696 		    chr_offset = 0;
697 		    col_offset = 0;
698 		    THIS_ROW = tmp;
699 		    (void) Scroll_To(thisrow);
700 		    show_all = TRUE;
701 		} else {
702 		    result = dlg_enter_buttoncode(state);
703 		}
704 		break;
705 	    case DLGK_LEAVE:
706 		if (state >= 0)
707 		    result = dlg_ok_buttoncode(state);
708 		break;
709 #ifdef KEY_RESIZE
710 	    case KEY_RESIZE:
711 		dlg_will_resize(dialog);
712 		/* reset data */
713 		height = old_height;
714 		width = old_width;
715 		/* repaint */
716 		dlg_del_window(editing);
717 		dlg_unregister_window(editing);
718 		_dlg_resize_cleanup(dialog);
719 		goto retry;
720 #endif
721 	    case DLGK_TOGGLE:
722 		if (state != sTEXT) {
723 		    result = dlg_ok_buttoncode(state);
724 		} else {
725 		    beep();
726 		}
727 		break;
728 	    default:
729 		beep();
730 		break;
731 	    }
732 	} else if (key > 0) {
733 	    beep();
734 	}
735     }
736 
737     dlg_unregister_window(editing);
738     dlg_del_window(editing);
739     dlg_del_window(dialog);
740     dlg_mouse_free_regions();
741 
742     /*
743      * The caller's copy of the (*list)[] array has been updated, but for
744      * consistency with the other widgets, we put the "real" result in
745      * the output buffer.
746      */
747     if (result == DLG_EXIT_OK) {
748 	int n;
749 	for (n = 0; n < listsize; ++n) {
750 	    dlg_add_result((*list)[n]);
751 	    dlg_add_separator();
752 	}
753 	dlg_add_last_key(-1);
754     }
755     free(buffer);
756     dlg_restore_vars(&save_vars);
757     return result;
758 }
759 
760 int
761 dialog_editbox(const char *title, const char *file, int height, int width)
762 {
763     int result;
764     char **list;
765     int rows;
766 
767     load_list(file, &list, &rows);
768     result = dlg_editbox(title, &list, &rows, height, width);
769     free_list(&list, &rows);
770     return result;
771 }
772