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