1/*
2 *  $Id: formbox.c,v 1.103 2021/01/17 22:19:05 tom Exp $
3 *
4 *  formbox.c -- implements the form (i.e., some pairs label/editbox)
5 *
6 *  Copyright 2003-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 *  This is adapted from source contributed by
24 *	Valery Reznic (valery_reznic@users.sourceforge.net)
25 */
26
27#include <dialog.h>
28#include <dlg_keys.h>
29
30#define LLEN(n) ((n) * FORMBOX_TAGS)
31
32#define ItemName(i)     items[LLEN(i) + 0]
33#define ItemNameY(i)    items[LLEN(i) + 1]
34#define ItemNameX(i)    items[LLEN(i) + 2]
35#define ItemText(i)     items[LLEN(i) + 3]
36#define ItemTextY(i)    items[LLEN(i) + 4]
37#define ItemTextX(i)    items[LLEN(i) + 5]
38#define ItemTextFLen(i) items[LLEN(i) + 6]
39#define ItemTextILen(i) items[LLEN(i) + 7]
40#define ItemHelp(i)     (dialog_vars.item_help ? items[LLEN(i) + 8] : dlg_strempty())
41
42static bool
43is_readonly(DIALOG_FORMITEM * item)
44{
45    return ((item->type & 2) != 0) || (item->text_flen <= 0);
46}
47
48static bool
49is_hidden(DIALOG_FORMITEM * item)
50{
51    return ((item->type & 1) != 0);
52}
53
54static bool
55in_window(WINDOW *win, int scrollamt, int y)
56{
57    return (y >= scrollamt && y - scrollamt < getmaxy(win));
58}
59
60static bool
61ok_move(WINDOW *win, int scrollamt, int y, int x)
62{
63    return in_window(win, scrollamt, y)
64	&& (wmove(win, y - scrollamt, x) != ERR);
65}
66
67static void
68move_past(WINDOW *win, int y, int x)
69{
70    if (wmove(win, y, x) == ERR)
71	wmove(win, y, getmaxx(win) - 1);
72}
73
74/*
75 * Print form item
76 */
77static int
78print_item(WINDOW *win, DIALOG_FORMITEM * item, int scrollamt, bool choice)
79{
80    int count = 0;
81    int len;
82
83    if (ok_move(win, scrollamt, item->name_y, item->name_x)) {
84	len = item->name_len;
85	len = MIN(len, getmaxx(win) - item->name_x);
86	if (len > 0) {
87	    dlg_show_string(win,
88			    item->name,
89			    0,
90			    menubox_attr,
91			    item->name_y - scrollamt,
92			    item->name_x,
93			    len,
94			    FALSE,
95			    FALSE);
96	    move_past(win, item->name_y - scrollamt, item->name_x + len);
97	    count = 1;
98	}
99    }
100    if (item->text_len && ok_move(win, scrollamt, item->text_y, item->text_x)) {
101	chtype this_item_attribute;
102
103	len = item->text_len;
104	len = MIN(len, getmaxx(win) - item->text_x);
105
106	if (!is_readonly(item)) {
107	    this_item_attribute = choice
108		? form_active_text_attr
109		: form_text_attr;
110	} else {
111	    this_item_attribute = form_item_readonly_attr;
112	}
113
114	if (len > 0) {
115	    dlg_show_string(win,
116			    item->text,
117			    0,
118			    this_item_attribute,
119			    item->text_y - scrollamt,
120			    item->text_x,
121			    len,
122			    is_hidden(item),
123			    FALSE);
124	    move_past(win, item->text_y - scrollamt, item->text_x + len);
125	    count = 1;
126	}
127    }
128    return count;
129}
130
131/*
132 * Print the entire form.
133 */
134static void
135print_form(WINDOW *win, DIALOG_FORMITEM * item, int total, int scrollamt, int choice)
136{
137    int n;
138    int count = 0;
139
140    for (n = 0; n < total; ++n) {
141	count += print_item(win, item + n, scrollamt, n == choice);
142    }
143    if (count) {
144	wbkgdset(win, menubox_attr | ' ');
145	wclrtobot(win);
146	(void) wnoutrefresh(win);
147    }
148}
149
150static int
151set_choice(DIALOG_FORMITEM item[], int choice, int item_no, bool * noneditable)
152{
153    int result = -1;
154
155    *noneditable = FALSE;
156    if (!is_readonly(&item[choice])) {
157	result = choice;
158    } else {
159	int i;
160
161	for (i = 0; i < item_no; i++) {
162	    if (!is_readonly(&(item[i]))) {
163		result = i;
164		break;
165	    }
166	}
167	if (result < 0) {
168	    *noneditable = TRUE;
169	    result = 0;
170	}
171    }
172    return result;
173}
174
175/*
176 * Find the last y-value in the form.
177 */
178static int
179form_limit(DIALOG_FORMITEM item[])
180{
181    int n;
182    int limit = 0;
183    for (n = 0; item[n].name != 0; ++n) {
184	if (limit < item[n].name_y)
185	    limit = item[n].name_y;
186	if (limit < item[n].text_y)
187	    limit = item[n].text_y;
188    }
189    return limit;
190}
191
192static int
193is_first_field(DIALOG_FORMITEM item[], int choice)
194{
195    int count = 0;
196    while (choice >= 0) {
197	if (item[choice].text_flen > 0) {
198	    ++count;
199	}
200	--choice;
201    }
202
203    return (count == 1);
204}
205
206static int
207is_last_field(DIALOG_FORMITEM item[], int choice, int item_no)
208{
209    int count = 0;
210    while (choice < item_no) {
211	if (item[choice].text_flen > 0) {
212	    ++count;
213	}
214	++choice;
215    }
216
217    return (count == 1);
218}
219
220/*
221 * Tab to the next field.
222 */
223static bool
224tab_next(WINDOW *win,
225	 DIALOG_FORMITEM item[],
226	 int item_no,
227	 int stepsize,
228	 int *choice,
229	 int *scrollamt)
230{
231    int old_choice = *choice;
232    int old_scroll = *scrollamt;
233    bool wrapped = FALSE;
234
235    do {
236	do {
237	    *choice += stepsize;
238	    if (*choice < 0) {
239		*choice = item_no - 1;
240		wrapped = TRUE;
241	    } else if (*choice >= item_no) {
242		*choice = 0;
243		wrapped = TRUE;
244	    }
245	} while ((*choice != old_choice) && is_readonly(&(item[*choice])));
246
247	if (item[*choice].text_flen > 0) {
248	    int lo = MIN(item[*choice].name_y, item[*choice].text_y);
249	    int hi = MAX(item[*choice].name_y, item[*choice].text_y);
250
251	    if (old_choice == *choice)
252		break;
253	    print_item(win, item + old_choice, *scrollamt, FALSE);
254
255	    if (*scrollamt < lo + 1 - getmaxy(win))
256		*scrollamt = lo + 1 - getmaxy(win);
257	    if (*scrollamt > hi)
258		*scrollamt = hi;
259	    /*
260	     * If we have to scroll to show a wrap-around, it does get
261	     * confusing.  Just give up rather than scroll.  Tab'ing to the
262	     * next field in a multi-column form is a different matter.  Scroll
263	     * for that.
264	     */
265	    if (*scrollamt != old_scroll) {
266		if (wrapped) {
267		    beep();
268		    *scrollamt = old_scroll;
269		    *choice = old_choice;
270		} else {
271		    scrollok(win, TRUE);
272		    wscrl(win, *scrollamt - old_scroll);
273		    scrollok(win, FALSE);
274		}
275	    }
276	    break;
277	}
278    } while (*choice != old_choice);
279
280    return (old_choice != *choice) || (old_scroll != *scrollamt);
281}
282
283/*
284 * Scroll to the next page, putting the choice at the first editable field
285 * in that page.  Note that fields are not necessarily in top-to-bottom order,
286 * nor is there necessarily a field on each row of the window.
287 */
288static bool
289scroll_next(WINDOW *win, DIALOG_FORMITEM item[], int stepsize, int *choice, int *scrollamt)
290{
291    bool result = TRUE;
292    int old_choice = *choice;
293    int old_scroll = *scrollamt;
294    int old_row = MIN(item[old_choice].text_y, item[old_choice].name_y);
295    int target = old_scroll + stepsize;
296
297    if (stepsize < 0) {
298	if (old_row != old_scroll)
299	    target = old_scroll;
300	else
301	    target = old_scroll + stepsize;
302	if (target < 0) {
303	    result = FALSE;
304	}
305    } else {
306	if (target > form_limit(item)) {
307	    result = FALSE;
308	}
309    }
310
311    if (result) {
312	int n;
313
314	for (n = 0; item[n].name != 0; ++n) {
315	    if (item[n].text_flen > 0) {
316		int new_row = MIN(item[n].text_y, item[n].name_y);
317		if (abs(new_row - target) < abs(old_row - target)) {
318		    old_row = new_row;
319		    *choice = n;
320		}
321	    }
322	}
323
324	if (old_choice != *choice)
325	    print_item(win, item + old_choice, *scrollamt, FALSE);
326
327	*scrollamt = *choice;
328	if (*scrollamt != old_scroll) {
329	    scrollok(win, TRUE);
330	    wscrl(win, *scrollamt - old_scroll);
331	    scrollok(win, FALSE);
332	}
333	result = (old_choice != *choice) || (old_scroll != *scrollamt);
334    }
335    if (!result)
336	beep();
337    return result;
338}
339
340/*
341 * Do a sanity check on the field length, and return the "right" value.
342 */
343static int
344real_length(DIALOG_FORMITEM * item)
345{
346    return (item->text_flen > 0
347	    ? item->text_flen
348	    : (item->text_flen < 0
349	       ? -item->text_flen
350	       : item->text_len));
351}
352
353/*
354 * Compute the form size, setup field buffers.
355 */
356static void
357make_FORM_ELTs(DIALOG_FORMITEM * item,
358	       int item_no,
359	       int *min_height,
360	       int *min_width)
361{
362    int i;
363    int min_w = 0;
364    int min_h = 0;
365
366    for (i = 0; i < item_no; ++i) {
367	int real_len = real_length(item + i);
368
369	/*
370	 * Special value '0' for text_flen: no input allowed
371	 * Special value '0' for text_ilen: 'be the same as text_flen'
372	 */
373	if (item[i].text_ilen == 0)
374	    item[i].text_ilen = real_len;
375
376	min_h = MAX(min_h, item[i].name_y + 1);
377	min_h = MAX(min_h, item[i].text_y + 1);
378	min_w = MAX(min_w, item[i].name_x + 1 + item[i].name_len);
379	min_w = MAX(min_w, item[i].text_x + 1 + real_len);
380
381	item[i].text_len = real_length(item + i);
382
383	/*
384	 * We do not know the actual length of .text, so we allocate it here
385	 * to ensure it is big enough.
386	 */
387	if (item[i].text_flen > 0) {
388	    int max_len = dlg_max_input(MAX(item[i].text_ilen + 1, MAX_LEN));
389	    char *old_text = item[i].text;
390
391	    item[i].text = dlg_malloc(char, (size_t) max_len + 1);
392	    assert_ptr(item[i].text, "make_FORM_ELTs");
393
394	    sprintf(item[i].text, "%.*s", item[i].text_ilen, old_text);
395
396	    if (item[i].text_free) {
397		free(old_text);
398	    }
399	    item[i].text_free = TRUE;
400	}
401    }
402
403    *min_height = min_h;
404    *min_width = min_w;
405}
406
407int
408dlg_default_formitem(DIALOG_FORMITEM * items)
409{
410    int result = 0;
411
412    if (dialog_vars.default_item != 0) {
413	int count = 0;
414	while (items->name != 0) {
415	    if (!strcmp(dialog_vars.default_item, items->name)) {
416		result = count;
417		break;
418	    }
419	    ++items;
420	    count++;
421	}
422    }
423    return result;
424}
425
426#define sTEXT -1
427
428static int
429next_valid_buttonindex(int state, int extra, bool non_editable)
430{
431    state = dlg_next_ok_buttonindex(state, extra);
432    while (non_editable && state == sTEXT)
433	state = dlg_next_ok_buttonindex(state, sTEXT);
434    return state;
435}
436
437static int
438prev_valid_buttonindex(int state, int extra, bool non_editable)
439{
440    state = dlg_prev_ok_buttonindex(state, extra);
441    while (non_editable && state == sTEXT)
442	state = dlg_prev_ok_buttonindex(state, sTEXT);
443    return state;
444}
445
446#define NAVIGATE_BINDINGS \
447	DLG_KEYS_DATA( DLGK_FIELD_NEXT, TAB ), \
448	DLG_KEYS_DATA( DLGK_FIELD_PREV, KEY_BTAB ), \
449	DLG_KEYS_DATA( DLGK_ITEM_NEXT,  CHR_NEXT ), \
450	DLG_KEYS_DATA( DLGK_ITEM_NEXT,  KEY_DOWN ), \
451	DLG_KEYS_DATA( DLGK_ITEM_NEXT,  KEY_RIGHT ), \
452	DLG_KEYS_DATA( DLGK_ITEM_NEXT,  KEY_NEXT ), \
453	DLG_KEYS_DATA( DLGK_ITEM_PREV,  CHR_PREVIOUS ), \
454	DLG_KEYS_DATA( DLGK_ITEM_PREV,  KEY_PREVIOUS ), \
455	DLG_KEYS_DATA( DLGK_ITEM_PREV,  KEY_LEFT ), \
456	DLG_KEYS_DATA( DLGK_ITEM_PREV,  KEY_UP ), \
457	DLG_KEYS_DATA( DLGK_PAGE_NEXT,  KEY_NPAGE ), \
458	DLG_KEYS_DATA( DLGK_PAGE_PREV,  KEY_PPAGE )
459/*
460 * Display a form for entering a number of fields
461 */
462int
463dlg_form(const char *title,
464	 const char *cprompt,
465	 int height,
466	 int width,
467	 int form_height,
468	 int item_no,
469	 DIALOG_FORMITEM * items,
470	 int *current_item)
471{
472    /* *INDENT-OFF* */
473    static DLG_KEYS_BINDING binding[] = {
474	HELPKEY_BINDINGS,
475	ENTERKEY_BINDINGS,
476	NAVIGATE_BINDINGS,
477	TOGGLEKEY_BINDINGS,
478	END_KEYS_BINDING
479    };
480    static DLG_KEYS_BINDING binding2[] = {
481	INPUTSTR_BINDINGS,
482	HELPKEY_BINDINGS,
483	ENTERKEY_BINDINGS,
484	NAVIGATE_BINDINGS,
485	/* no TOGGLEKEY_BINDINGS, since that includes space... */
486	END_KEYS_BINDING
487    };
488    /* *INDENT-ON* */
489
490#ifdef KEY_RESIZE
491    int old_height = height;
492    int old_width = width;
493#endif
494
495    int form_width;
496    bool first = TRUE;
497    bool first_trace = TRUE;
498    int chr_offset = 0;
499    int state = (dialog_vars.default_button >= 0
500		 ? dlg_default_button()
501		 : sTEXT);
502    int x, y, cur_x, cur_y, box_x, box_y;
503    int code;
504    int fkey;
505    int choice = dlg_default_formitem(items);
506    int new_choice, new_scroll;
507    int scrollamt = 0;
508    int result = DLG_EXIT_UNKNOWN;
509    int min_width = 0, min_height = 0;
510    bool was_autosize = (height == 0 || width == 0);
511    bool show_buttons = FALSE;
512    bool scroll_changed = FALSE;
513    bool field_changed = FALSE;
514    bool non_editable = FALSE;
515    WINDOW *dialog, *form;
516    char *prompt;
517    const char **buttons = dlg_ok_labels();
518    DIALOG_FORMITEM *current;
519
520    DLG_TRACE(("# %sform args:\n", (dialog_vars.formitem_type
521				    ? "password"
522				    : "")));
523    DLG_TRACE2S("title", title);
524    DLG_TRACE2S("message", cprompt);
525    DLG_TRACE2N("height", height);
526    DLG_TRACE2N("width", width);
527    DLG_TRACE2N("lheight", form_height);
528    DLG_TRACE2N("llength", item_no);
529    /* FIXME dump the items[][] too */
530    DLG_TRACE2N("current", *current_item);
531
532    make_FORM_ELTs(items, item_no, &min_height, &min_width);
533    dlg_button_layout(buttons, &min_width);
534    dlg_does_output();
535
536#ifdef KEY_RESIZE
537  retry:
538#endif
539
540    prompt = dlg_strclone(cprompt);
541    dlg_tab_correct_str(prompt);
542    dlg_auto_size(title, prompt, &height, &width,
543		  1 + 3 * MARGIN,
544		  MAX(26, 2 + min_width));
545
546    if (form_height == 0)
547	form_height = min_height;
548
549    if (was_autosize) {
550	form_height = MIN(SLINES - height, form_height);
551	height += form_height;
552    } else {
553	int thigh = 0;
554	int twide = 0;
555	dlg_auto_size(title, prompt, &thigh, &twide, 0, width);
556	thigh = SLINES - (height - (thigh + 1 + 3 * MARGIN));
557	form_height = MIN(thigh, form_height);
558    }
559
560    dlg_print_size(height, width);
561    dlg_ctl_size(height, width);
562
563    x = dlg_box_x_ordinate(width);
564    y = dlg_box_y_ordinate(height);
565
566    dialog = dlg_new_window(height, width, y, x);
567    dlg_register_window(dialog, "formbox", binding);
568    dlg_register_buttons(dialog, "formbox", buttons);
569
570    dlg_mouse_setbase(x, y);
571
572    dlg_draw_box2(dialog, 0, 0, height, width, dialog_attr, border_attr, border2_attr);
573    dlg_draw_bottom_box2(dialog, border_attr, border2_attr, dialog_attr);
574    dlg_draw_title(dialog, title);
575
576    dlg_attrset(dialog, dialog_attr);
577    dlg_print_autowrap(dialog, prompt, height, width);
578
579    form_width = width - 6;
580    getyx(dialog, cur_y, cur_x);
581    (void) cur_x;
582    box_y = cur_y + 1;
583    box_x = (width - form_width) / 2 - 1;
584
585    /* create new window for the form */
586    form = dlg_sub_window(dialog, form_height, form_width, y + box_y + 1,
587			  x + box_x + 1);
588    dlg_register_window(form, "formfield", binding2);
589
590    /* draw a box around the form items */
591    dlg_draw_box(dialog, box_y, box_x, form_height + 2, form_width + 2,
592		 menubox_border_attr, menubox_border2_attr);
593
594    /* register the new window, along with its borders */
595    dlg_mouse_mkbigregion(getbegy(form) - getbegy(dialog),
596			  getbegx(form) - getbegx(dialog),
597			  getmaxy(form),
598			  getmaxx(form),
599			  KEY_MAX, 1, 1, 3 /* by cells */ );
600
601    show_buttons = TRUE;
602    scroll_changed = TRUE;
603
604    choice = set_choice(items, choice, item_no, &non_editable);
605    current = &items[choice];
606    if (non_editable)
607	state = next_valid_buttonindex(state, sTEXT, non_editable);
608
609    while (result == DLG_EXIT_UNKNOWN) {
610	int edit = FALSE;
611	int key;
612
613	if (scroll_changed) {
614	    print_form(form, items, item_no, scrollamt, choice);
615	    dlg_draw_scrollbar(dialog,
616			       scrollamt,
617			       scrollamt,
618			       scrollamt + form_height + 1,
619			       min_height,
620			       box_x + 1,
621			       box_x + form_width,
622			       box_y,
623			       box_y + form_height + 1,
624			       menubox_border2_attr,
625			       menubox_border_attr);
626	    scroll_changed = FALSE;
627	}
628
629	if (show_buttons) {
630	    dlg_item_help("");
631	    dlg_draw_buttons(dialog, height - 2, 0, buttons,
632			     ((state < 0)
633			      ? 1000	/* no such button, not highlighted */
634			      : state),
635			     FALSE, width);
636	    show_buttons = FALSE;
637	}
638
639	if (first_trace) {
640	    first_trace = FALSE;
641	    dlg_trace_win(dialog);
642	}
643
644	if (field_changed || state == sTEXT) {
645	    if (field_changed)
646		chr_offset = 0;
647	    current = &items[choice];
648	    dialog_vars.max_input = current->text_ilen;
649	    dlg_item_help(current->help);
650	    dlg_show_string(form, current->text, chr_offset,
651			    form_active_text_attr,
652			    current->text_y - scrollamt,
653			    current->text_x,
654			    current->text_len,
655			    is_hidden(current), first);
656	    wsyncup(form);
657	    wcursyncup(form);
658	    field_changed = FALSE;
659	}
660
661	key = dlg_mouse_wgetch((state == sTEXT) ? form : dialog, &fkey);
662	if (dlg_result_key(key, fkey, &result)) {
663	    break;
664	}
665
666	/* handle non-functionkeys */
667	if (!fkey) {
668	    if (state != sTEXT) {
669		code = dlg_char_to_button(key, buttons);
670		if (code >= 0) {
671		    dlg_del_window(dialog);
672		    result = dlg_ok_buttoncode(code);
673		    continue;
674		}
675	    }
676	}
677
678	/* handle functionkeys */
679	if (fkey) {
680	    bool do_scroll = FALSE;
681	    bool do_tab = FALSE;
682	    int move_by = 0;
683
684	    switch (key) {
685	    case DLGK_MOUSE(KEY_PPAGE):
686	    case DLGK_PAGE_PREV:
687		do_scroll = TRUE;
688		move_by = -form_height;
689		break;
690
691	    case DLGK_MOUSE(KEY_NPAGE):
692	    case DLGK_PAGE_NEXT:
693		do_scroll = TRUE;
694		move_by = form_height;
695		break;
696
697	    case DLGK_TOGGLE:
698	    case DLGK_ENTER:
699		dlg_del_window(dialog);
700		result = (state >= 0) ? dlg_enter_buttoncode(state) : DLG_EXIT_OK;
701		continue;
702	    case DLGK_LEAVE:
703		if (state >= 0)
704		    result = dlg_ok_buttoncode(state);
705		break;
706
707	    case DLGK_GRID_LEFT:
708		if (state == sTEXT)
709		    break;
710		/* FALLTHRU */
711	    case DLGK_ITEM_PREV:
712		if (state == sTEXT) {
713		    do_tab = TRUE;
714		    move_by = -1;
715		    break;
716		} else {
717		    state = prev_valid_buttonindex(state, 0, non_editable);
718		    show_buttons = TRUE;
719		    continue;
720		}
721
722	    case DLGK_FORM_PREV:
723		if (state == sTEXT && !is_first_field(items, choice)) {
724		    do_tab = TRUE;
725		    move_by = -1;
726		    break;
727		} else {
728		    int old_state = state;
729		    state = prev_valid_buttonindex(state, sTEXT, non_editable);
730		    show_buttons = TRUE;
731		    if (old_state >= 0 && state == sTEXT) {
732			new_choice = item_no - 1;
733			if (choice != new_choice) {
734			    print_item(form, items + choice, scrollamt, FALSE);
735			    choice = new_choice;
736			}
737		    }
738		    continue;
739		}
740
741	    case DLGK_FIELD_PREV:
742		state = prev_valid_buttonindex(state, sTEXT, non_editable);
743		show_buttons = TRUE;
744		continue;
745
746	    case DLGK_FIELD_NEXT:
747		state = next_valid_buttonindex(state, sTEXT, non_editable);
748		show_buttons = TRUE;
749		continue;
750
751	    case DLGK_GRID_RIGHT:
752		if (state == sTEXT)
753		    break;
754		/* FALLTHRU */
755
756	    case DLGK_ITEM_NEXT:
757		if (state == sTEXT) {
758		    do_tab = TRUE;
759		    move_by = 1;
760		    break;
761		} else {
762		    state = next_valid_buttonindex(state, 0, non_editable);
763		    show_buttons = TRUE;
764		    continue;
765		}
766
767	    case DLGK_FORM_NEXT:
768		if (state == sTEXT && !is_last_field(items, choice, item_no)) {
769		    do_tab = TRUE;
770		    move_by = 1;
771		    break;
772		} else {
773		    state = next_valid_buttonindex(state, sTEXT, non_editable);
774		    show_buttons = TRUE;
775		    if (state == sTEXT && choice) {
776			print_item(form, items + choice, scrollamt, FALSE);
777			choice = 0;
778		    }
779		    continue;
780		}
781
782#ifdef KEY_RESIZE
783	    case KEY_RESIZE:
784		dlg_will_resize(dialog);
785		/* reset data */
786		height = old_height;
787		width = old_width;
788		free(prompt);
789		_dlg_resize_cleanup(dialog);
790		dlg_unregister_window(form);
791		/* repaint */
792		goto retry;
793#endif
794	    default:
795#if USE_MOUSE
796		if (is_DLGK_MOUSE(key)) {
797		    if (key >= DLGK_MOUSE(KEY_MAX)) {
798			int cell = key - DLGK_MOUSE(KEY_MAX);
799			int row = (cell / getmaxx(form)) + scrollamt;
800			int col = (cell % getmaxx(form));
801			int n;
802
803			for (n = 0; n < item_no; ++n) {
804			    if (items[n].name_y == row
805				&& items[n].name_x <= col
806				&& (items[n].name_x + items[n].name_len > col
807				    || (items[n].name_y == items[n].text_y
808					&& items[n].text_x > col))) {
809				if (!is_readonly(&(items[n]))) {
810				    field_changed = TRUE;
811				    break;
812				}
813			    }
814			    if (items[n].text_y == row
815				&& items[n].text_x <= col
816				&& items[n].text_x + items[n].text_ilen > col) {
817				if (!is_readonly(&(items[n]))) {
818				    field_changed = TRUE;
819				    break;
820				}
821			    }
822			}
823			if (field_changed) {
824			    print_item(form, items + choice, scrollamt, FALSE);
825			    choice = n;
826			    continue;
827			}
828			beep();
829		    } else if ((code = dlg_ok_buttoncode(key - M_EVENT)) >= 0) {
830			result = code;
831		    }
832		    continue;
833		}
834#endif
835		break;
836	    }
837
838	    new_scroll = scrollamt;
839	    new_choice = choice;
840	    if (do_scroll) {
841		if (scroll_next(form, items, move_by, &new_choice, &new_scroll)) {
842		    if (choice != new_choice) {
843			choice = new_choice;
844			field_changed = TRUE;
845		    }
846		    if (scrollamt != new_scroll) {
847			scrollamt = new_scroll;
848			scroll_changed = TRUE;
849		    }
850		}
851		continue;
852	    }
853	    if (do_tab) {
854		if (tab_next(form, items, item_no, move_by, &new_choice, &new_scroll)) {
855		    if (choice != new_choice) {
856			choice = new_choice;
857			field_changed = TRUE;
858		    }
859		    if (scrollamt != new_scroll) {
860			scrollamt = new_scroll;
861			scroll_changed = TRUE;
862		    }
863		}
864		continue;
865	    }
866	}
867
868	if (state == sTEXT) {	/* Input box selected */
869	    if (!is_readonly(current))
870		edit = dlg_edit_string(current->text, &chr_offset, key,
871				       fkey, first);
872	    if (edit) {
873		dlg_show_string(form, current->text, chr_offset,
874				form_active_text_attr,
875				current->text_y - scrollamt,
876				current->text_x,
877				current->text_len,
878				is_hidden(current), first);
879		continue;
880	    }
881	}
882
883    }
884
885    dlg_mouse_free_regions();
886    dlg_unregister_window(form);
887    dlg_del_window(dialog);
888    free(prompt);
889
890    *current_item = choice;
891    return result;
892}
893
894/*
895 * Free memory owned by a list of DIALOG_FORMITEM's.
896 */
897void
898dlg_free_formitems(DIALOG_FORMITEM * items)
899{
900    int n;
901    for (n = 0; items[n].name != 0; ++n) {
902	if (items[n].name_free)
903	    free(items[n].name);
904	if (items[n].text_free)
905	    free(items[n].text);
906	if (items[n].help_free && items[n].help != dlg_strempty())
907	    free(items[n].help);
908    }
909    free(items);
910}
911
912/*
913 * The script accepts values beginning at 1, while curses starts at 0.
914 */
915int
916dlg_ordinate(const char *s)
917{
918    int result = atoi(s);
919    if (result > 0)
920	--result;
921    else
922	result = 0;
923    return result;
924}
925
926int
927dialog_form(const char *title,
928	    const char *cprompt,
929	    int height,
930	    int width,
931	    int form_height,
932	    int item_no,
933	    char **items)
934{
935    int result;
936    int choice = 0;
937    int i;
938    DIALOG_FORMITEM *listitems;
939    DIALOG_VARS save_vars;
940    bool show_status = FALSE;
941    char *help_result;
942
943    dlg_save_vars(&save_vars);
944    dialog_vars.separate_output = TRUE;
945
946    listitems = dlg_calloc(DIALOG_FORMITEM, (size_t) item_no + 1);
947    assert_ptr(listitems, "dialog_form");
948
949    for (i = 0; i < item_no; ++i) {
950	listitems[i].type = dialog_vars.formitem_type;
951	listitems[i].name = ItemName(i);
952	listitems[i].name_len = (int) strlen(ItemName(i));
953	listitems[i].name_y = dlg_ordinate(ItemNameY(i));
954	listitems[i].name_x = dlg_ordinate(ItemNameX(i));
955	listitems[i].text = ItemText(i);
956	listitems[i].text_len = (int) strlen(ItemText(i));
957	listitems[i].text_y = dlg_ordinate(ItemTextY(i));
958	listitems[i].text_x = dlg_ordinate(ItemTextX(i));
959	listitems[i].text_flen = atoi(ItemTextFLen(i));
960	listitems[i].text_ilen = atoi(ItemTextILen(i));
961	listitems[i].help = ((dialog_vars.item_help)
962			     ? ItemHelp(i)
963			     : dlg_strempty());
964    }
965
966    result = dlg_form(title,
967		      cprompt,
968		      height,
969		      width,
970		      form_height,
971		      item_no,
972		      listitems,
973		      &choice);
974
975    switch (result) {
976    case DLG_EXIT_OK:		/* FALLTHRU */
977    case DLG_EXIT_EXTRA:
978	show_status = TRUE;
979	break;
980    case DLG_EXIT_HELP:
981	dlg_add_help_formitem(&result, &help_result, &listitems[choice]);
982	show_status = dialog_vars.help_status;
983	dlg_add_string(help_result);
984	if (show_status)
985	    dlg_add_separator();
986	break;
987    }
988    if (show_status) {
989	for (i = 0; i < item_no; i++) {
990	    if (listitems[i].text_flen > 0) {
991		dlg_add_string(listitems[i].text);
992		dlg_add_separator();
993	    }
994	}
995	dlg_add_last_key(-1);
996    }
997
998    dlg_free_formitems(listitems);
999    dlg_restore_vars(&save_vars);
1000
1001    return result;
1002}
1003