1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2021-2024 Alfonso Sabato Siciliano
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28#include <curses.h>
29#include <limits.h>
30#include <stdlib.h>
31#include <string.h>
32
33#include "bsddialog.h"
34#include "bsddialog_theme.h"
35#include "lib_util.h"
36
37enum field_action {
38	MOVE_CURSOR_BEGIN,
39	MOVE_CURSOR_END,
40	MOVE_CURSOR_RIGHT,
41	MOVE_CURSOR_LEFT,
42	DEL_LETTER
43};
44
45struct privateitem {
46	const char *label;      /* formitem.label */
47	unsigned int ylabel;    /* formitem.ylabel */
48	unsigned int xlabel;    /* formitem.xlabel */
49	unsigned int yfield;    /* formitem.yfield */
50	unsigned int xfield;    /* formitem.xfield */
51	bool secure;            /* formitem.flags & BSDDIALOG_FIELDHIDDEN */
52	bool readonly;          /* formitem.flags & BSDDIALOG_FIELDREADONLY */
53	bool fieldnocolor;      /* formitem.flags & BSDDIALOG_FIELDNOCOLOR */
54	bool extendfield;       /* formitem.flags & BSDDIALOG_FIELDEXTEND */
55	bool fieldonebyte;      /* formitem.flags & BSDDIALOG_FIELDSINGLEBYTE */
56	bool cursorend;         /* formitem.flags & BSDDIALOG_FIELDCURSOREND */
57	bool cursor;            /* field cursor visibility */
58	const char *bottomdesc; /* formitem.bottomdesc */
59
60	wchar_t *privwbuf;       /* formitem.value */
61	wchar_t *pubwbuf;        /* string for drawitem() */
62	unsigned int maxletters; /* formitem.maxvaluelen, [priv|pub]wbuf size */
63	unsigned int nletters;   /* letters in privwbuf and pubwbuf */
64	unsigned int pos;        /* pos in privwbuf and pubwbuf */
65	unsigned int fieldcols;  /* formitem.fieldlen */
66	unsigned int xcursor;    /* position in fieldcols [0 - fieldcols-1] */
67	unsigned int xposdraw;   /* first pubwbuf index to draw */
68};
69
70struct privateform {
71	WINDOW *box;         /* window to draw borders */
72	WINDOW *pad;
73	unsigned int h;      /* only to create pad */
74	unsigned int w;      /* only to create pad */
75	unsigned int wmin;   /* to refresh, w can change for FIELDEXTEND */
76	unsigned int ys;     /* to refresh */
77	unsigned int ye;     /* to refresh */
78	unsigned int xs;     /* to refresh */
79	unsigned int xe;     /* to refresh */
80	unsigned int y;      /* changes moving focus around items */
81	unsigned int formheight;  /* API formheight */
82	unsigned int viewrows;    /* visible rows, real formheight */
83	unsigned int minviewrows; /* min viewrows, ylabel != yfield */
84	wchar_t securewch;   /* wide char of conf.form.secure[mb]ch */
85	unsigned int nitems; /* like API nitems */
86	struct privateitem *pritems;
87	int sel;             /* selected item in pritem, can be -1 */
88	bool hasbottomdesc;  /* some item has bottomdesc */
89};
90
91static int
92build_privateform(struct bsddialog_conf*conf, unsigned int nitems,
93    struct bsddialog_formitem *items, struct privateform *f)
94{
95	bool insecurecursor;
96	int mbchsize;
97	unsigned int i, j, itemybeg, itemxbeg, tmp;
98	wchar_t *winit;
99	struct privateitem *item;
100
101	/* checks */
102	CHECK_ARRAY(nitems, items);
103	for (i = 0; i < nitems; i++) {
104		if (items[i].fieldlen == 0)
105			RETURN_FMTERROR("item %u [0-%u] fieldlen = 0",
106			    i, nitems);
107		if (items[i].maxvaluelen == 0)
108			RETURN_FMTERROR("item %u [0-%u] maxvaluelen = 0",
109			    i, nitems);
110	}
111	f->nitems = nitems;
112
113	/* insecure ch */
114	insecurecursor = false;
115	if (conf->form.securembch != NULL) {
116		mbchsize = mblen(conf->form.securembch, MB_LEN_MAX);
117		if (mbtowc(&f->securewch, conf->form.securembch, mbchsize) < 0)
118			RETURN_ERROR("Cannot convert securembch to wchar_t");
119		insecurecursor = true;
120	} else if (conf->form.securech != '\0') {
121		f->securewch = btowc(conf->form.securech);
122		insecurecursor = true;
123	} else {
124		f->securewch = L' ';
125	}
126
127	/* alloc and set private items */
128	f->pritems = malloc(f->nitems * sizeof(struct privateitem));
129	if (f->pritems == NULL)
130		RETURN_ERROR("Cannot allocate internal form.pritems");
131	f->hasbottomdesc = false;
132	f->h = f->w = f->minviewrows = 0;
133	for (i = 0; i < f->nitems; i++) {
134		item = &f->pritems[i];
135		item->label = CHECK_STR(items[i].label);
136		item->ylabel = items[i].ylabel;
137		item->xlabel = items[i].xlabel;
138		item->yfield = items[i].yfield;
139		item->xfield = items[i].xfield;
140		item->secure = items[i].flags & BSDDIALOG_FIELDHIDDEN;
141		item->readonly = items[i].flags & BSDDIALOG_FIELDREADONLY;
142		item->fieldnocolor = items[i].flags & BSDDIALOG_FIELDNOCOLOR;
143		item->extendfield = items[i].flags & BSDDIALOG_FIELDEXTEND;
144		item->fieldonebyte = items[i].flags &
145		    BSDDIALOG_FIELDSINGLEBYTE;
146		item->cursorend = items[i].flags & BSDDIALOG_FIELDCURSOREND;
147		item->bottomdesc = CHECK_STR(items[i].bottomdesc);
148		if (items[i].bottomdesc != NULL)
149			f->hasbottomdesc = true;
150		if (item->readonly || (item->secure && !insecurecursor))
151			item->cursor = false;
152		else
153			item->cursor = true;
154
155		item->maxletters = items[i].maxvaluelen;
156		item->privwbuf = calloc(item->maxletters + 1, sizeof(wchar_t));
157		if (item->privwbuf == NULL)
158			RETURN_ERROR("Cannot allocate item private buffer");
159		memset(item->privwbuf, 0, item->maxletters + 1);
160		item->pubwbuf = calloc(item->maxletters + 1, sizeof(wchar_t));
161		if (item->pubwbuf == NULL)
162			RETURN_ERROR("Cannot allocate item private buffer");
163		memset(item->pubwbuf, 0, item->maxletters + 1);
164
165		if ((winit = alloc_mbstows(CHECK_STR(items[i].init))) == NULL)
166			RETURN_ERROR("Cannot allocate item.init in wchar_t*");
167		wcsncpy(item->privwbuf, winit, item->maxletters);
168		wcsncpy(item->pubwbuf, winit, item->maxletters);
169		free(winit);
170		item->nletters = wcslen(item->pubwbuf);
171		if (item->secure) {
172			for (j = 0; j < item->nletters; j++)
173				item->pubwbuf[j] = f->securewch;
174		}
175
176		item->fieldcols = items[i].fieldlen;
177		item->xposdraw = 0;
178		item->xcursor = 0;
179		item->pos = 0;
180
181		/* size and position */
182		f->h = MAX(f->h, item->ylabel);
183		f->h = MAX(f->h, item->yfield);
184		f->w = MAX(f->w, item->xlabel + strcols(item->label));
185		f->w = MAX(f->w, item->xfield + item->fieldcols);
186		if (i == 0) {
187			itemybeg = MIN(item->ylabel, item->yfield);
188			itemxbeg = MIN(item->xlabel, item->xfield);
189		} else {
190			tmp = MIN(item->ylabel, item->yfield);
191			itemybeg = MIN(itemybeg, tmp);
192			tmp = MIN(item->xlabel, item->xfield);
193			itemxbeg = MIN(itemxbeg, tmp);
194		}
195		tmp = abs((int)item->ylabel - (int)item->yfield);
196		f->minviewrows = MAX(f->minviewrows, tmp);
197	}
198	if (f->nitems > 0) {
199		f->h = f->h + 1 - itemybeg;
200		f->w -= itemxbeg;
201		f->minviewrows += 1;
202	}
203	f->wmin = f->w;
204	for (i = 0; i < f->nitems; i++) {
205		f->pritems[i].ylabel -= itemybeg;
206		f->pritems[i].yfield -= itemybeg;
207		f->pritems[i].xlabel -= itemxbeg;
208		f->pritems[i].xfield -= itemxbeg;
209	}
210
211	return (0);
212}
213
214static bool fieldctl(struct privateitem *item, enum field_action act)
215{
216	bool change;
217	int width, oldwidth, nextwidth, cols;
218	unsigned int i;
219
220	change = false;
221	switch (act){
222	case MOVE_CURSOR_BEGIN:
223		if (item->pos == 0 && item->xcursor == 0)
224			break;
225		/* here the cursor is changed */
226		change = true;
227		item->pos = 0;
228		item->xcursor = 0;
229		item->xposdraw = 0;
230		break;
231	case MOVE_CURSOR_END:
232		while (fieldctl(item, MOVE_CURSOR_RIGHT))
233			change = true;
234		break;
235	case MOVE_CURSOR_LEFT:
236		if (item->pos == 0)
237			break;
238		/* check redundant by item->pos == 0 because of 'while' below */
239		if (item->xcursor == 0 && item->xposdraw == 0)
240			break;
241		/* here some letter to left */
242		change = true;
243		item->pos -= 1;
244		width = wcwidth(item->pubwbuf[item->pos]);
245		if (((int)item->xcursor) - width < 0) {
246			item->xcursor = 0;
247			item->xposdraw -= 1;
248		} else
249			item->xcursor -= width;
250
251		while (true) {
252			if (item->xposdraw == 0)
253				break;
254			if (item->xcursor >= item->fieldcols / 2)
255				break;
256			if (wcwidth(item->pubwbuf[item->xposdraw - 1]) +
257			    item->xcursor + width > item->fieldcols)
258				break;
259
260			item->xposdraw -= 1;
261			item->xcursor +=
262			    wcwidth(item->pubwbuf[item->xposdraw]);
263		}
264		break;
265	case DEL_LETTER:
266		if (item->nletters == 0)
267			break;
268		if (item->pos == item->nletters)
269			break;
270		/* here a letter under the cursor */
271		change = true;
272		for (i = item->pos; i < item->nletters; i++) {
273			item->privwbuf[i] = item->privwbuf[i+1];
274			item->pubwbuf[i] = item->pubwbuf[i+1];
275		}
276		item->nletters -= 1;
277		item->privwbuf[i] = L'\0';
278		item->pubwbuf[i] = L'\0';
279		break;
280	case MOVE_CURSOR_RIGHT: /* used also by "insert", see handler loop */
281		if (item->pos + 1 == item->maxletters)
282			break;
283		if (item->pos == item->nletters)
284			break;
285		/* here a change to right */
286		change = true;
287		oldwidth = wcwidth(item->pubwbuf[item->pos]);
288		item->pos += 1;
289		if (item->pos == item->nletters) { /* empty column */
290			nextwidth = 1;
291		} else { /* a letter to right */
292			nextwidth = wcwidth(item->pubwbuf[item->pos]);
293		}
294		if (item->xcursor + oldwidth + nextwidth - 1 >= item->fieldcols) {
295			cols = nextwidth;
296			item->xposdraw = item->pos;
297			while (item->xposdraw != 0) {
298				cols += wcwidth(item->pubwbuf[item->xposdraw - 1]);
299				if (cols > (int)item->fieldcols)
300					break;
301				item->xposdraw -= 1;
302			}
303			item->xcursor = 0;
304			for (i = item->xposdraw; i < item->pos ; i++)
305				item->xcursor += wcwidth(item->pubwbuf[i]);
306		}
307		else {
308			item->xcursor += oldwidth;
309		}
310
311		break;
312	}
313
314	return (change);
315}
316
317static bool insertch(struct privateitem *item, wchar_t wch, wchar_t securewch)
318{
319	int i;
320
321	if (item->nletters >= item->maxletters)
322		return (false);
323
324	for (i = (int)item->nletters - 1; i >= (int)item->pos; i--) {
325		item->privwbuf[i+1] = item->privwbuf[i];
326		item->pubwbuf[i+1] = item->pubwbuf[i];
327	}
328
329	item->privwbuf[item->pos] = wch;
330	item->pubwbuf[item->pos] = item->secure ? securewch : wch;
331	item->nletters += 1;
332	item->privwbuf[item->nletters] = L'\0';
333	item->pubwbuf[item->nletters] = L'\0';
334
335	return (true);
336}
337
338static char* alloc_wstomb(wchar_t *wstr)
339{
340	int len, nbytes, i;
341	char mbch[MB_LEN_MAX], *mbstr;
342
343	nbytes = MB_LEN_MAX; /* to ensure a null terminated string */
344	len = wcslen(wstr);
345	for (i = 0; i < len; i++) {
346		wctomb(mbch, wstr[i]);
347		nbytes += mblen(mbch, MB_LEN_MAX);
348	}
349	if ((mbstr = malloc(nbytes)) == NULL)
350		return (NULL);
351
352	wcstombs(mbstr,	wstr, nbytes);
353
354	return (mbstr);
355}
356
357static int
358return_values(struct bsddialog_conf *conf, struct privateform *f,
359    struct bsddialog_formitem *items)
360{
361	unsigned int i;
362
363	for (i = 0; i < f->nitems; i++) {
364		if (conf->form.value_wchar)
365			items[i].value = (char*)wcsdup(f->pritems[i].privwbuf);
366		else
367			items[i].value = alloc_wstomb(f->pritems[i].privwbuf);
368
369		if (items[i].value == NULL)
370			RETURN_FMTERROR(
371			    "Cannot allocate memory for item[%d].value", i);
372	}
373
374	return (0);
375}
376
377static void set_first_with_default(struct privateform *f, int *focusitem)
378{
379	unsigned int i;
380
381	f->sel = -1;
382	if (focusitem != NULL && *focusitem >=0 && *focusitem < (int)f->nitems)
383		if (f->pritems[*focusitem].readonly == false) {
384			f->sel = *focusitem;
385			return;
386		}
387	for (i = 0 ; i < f->nitems; i++)
388		if (f->pritems[i].readonly == false) {
389			f->sel = i;
390			break;
391		}
392}
393
394static unsigned int firstitem(unsigned int nitems, struct privateitem *items)
395{
396	int i;
397
398	for (i = 0; i < (int)nitems; i++)
399		if (items[i].readonly == false)
400			break;
401
402	return (i);
403}
404
405static unsigned int lastitem(unsigned int nitems, struct privateitem *items)
406{
407	int i;
408
409	for (i = nitems - 1; i >= 0 ; i--)
410		if (items[i].readonly == false)
411			break;
412
413	return (i);
414}
415
416static unsigned int
417previtem(unsigned int nitems, struct privateitem *items, int curritem)
418{
419	int i;
420
421	for (i = curritem - 1; i >= 0; i--)
422		if (items[i].readonly == false)
423			return(i);
424
425	for (i = nitems - 1; i > curritem - 1; i--)
426		if (items[i].readonly == false)
427			return(i);
428
429	return (curritem);
430}
431
432static unsigned int
433nextitem(unsigned int nitems, struct privateitem *items, int curritem)
434{
435	int i;
436
437	for (i = curritem + 1; i < (int)nitems; i++)
438		if (items[i].readonly == false)
439			return(i);
440
441	for (i = 0; i < curritem; i++)
442		if (items[i].readonly == false)
443			return(i);
444
445	return (curritem);
446}
447
448static void redrawbuttons(struct dialog *d, bool focus, bool shortcut)
449{
450	int selected;
451
452	selected = d->bs.curr;
453	if (focus == false)
454		d->bs.curr = -1;
455	d->bs.shortcut = shortcut;
456	draw_buttons(d);
457	d->bs.curr = selected;
458}
459
460static void
461drawitem(struct privateform *f, int idx, bool focus)
462{
463	int color;
464	unsigned int n, cols;
465	struct privateitem *item;
466
467	item = &f->pritems[idx];
468
469	/* Label */
470	wattron(f->pad, t.dialog.color);
471	mvwaddstr(f->pad, item->ylabel, item->xlabel, item->label);
472	wattroff(f->pad, t.dialog.color);
473
474	/* Field */
475	if (item->readonly)
476		color = t.form.readonlycolor;
477	else if (item->fieldnocolor)
478		color = t.dialog.color;
479	else
480		color = focus ? t.form.f_fieldcolor : t.form.fieldcolor;
481	wattron(f->pad, color);
482	mvwhline(f->pad, item->yfield, item->xfield, ' ', item->fieldcols);
483	n = 0;
484	cols = wcwidth(item->pubwbuf[item->xposdraw]);
485	while (cols <= item->fieldcols &&
486	    item->xposdraw + n < wcslen(item->pubwbuf)) {
487		n++;
488		cols += wcwidth(item->pubwbuf[item->xposdraw + n]);
489
490	}
491	mvwaddnwstr(f->pad, item->yfield, item->xfield,
492	    &item->pubwbuf[item->xposdraw], n);
493	wattroff(f->pad, color);
494
495	/* Bottom Desc */
496	if (f->hasbottomdesc) {
497		move(SCREENLINES - 1, 2);
498		clrtoeol();
499		if (item->bottomdesc != NULL && focus) {
500			attron(t.form.bottomdesccolor);
501			addstr(item->bottomdesc);
502			attroff(t.form.bottomdesccolor);
503			refresh();
504		}
505	}
506
507	/* Cursor */
508	curs_set((focus && item->cursor) ? 1 : 0);
509	wmove(f->pad, item->yfield, item->xfield + item->xcursor);
510}
511
512/*
513 * Trick: draw 2 times an item switching focus.
514 * Problem: curses tries to optimize the rendering but sometimes it misses some
515 * updates or draws old stuff. libformw has a similar problem fixed by the
516 * same trick.
517 * Case 1: KEY_DC and KEY_BACKSPACE, deleted multicolumn letters are drawn
518 * again. It seems fixed by new items pad and prefresh(), previously WINDOW.
519 * Case2: some terminal, tmux and ssh does not show the cursor.
520 */
521#define DRAWITEM_TRICK(f, idx, focus) do {                                     \
522	drawitem(f, idx, !focus);                                              \
523	prefresh((f)->pad, (f)->y, 0, (f)->ys, (f)->xs, (f)->ye, (f)->xe);     \
524	drawitem(f, idx, focus);                                               \
525	prefresh((f)->pad, (f)->y, 0, (f)->ys, (f)->xs, (f)->ye, (f)->xe);     \
526} while (0)
527
528static void update_formbox(struct bsddialog_conf *conf, struct privateform *f)
529{
530	int h, w;
531
532	getmaxyx(f->box, h, w);
533	draw_borders(conf, f->box, LOWERED);
534
535	if (f->viewrows < f->h) {
536		wattron(f->box, t.dialog.arrowcolor);
537		if (f->y > 0)
538			mvwhline(f->box, 0, (w / 2) - 2, UARROW(conf), 5);
539
540		if (f->y + f->viewrows < f->h)
541			mvwhline(f->box, h-1, (w / 2) - 2, DARROW(conf), 5);
542		wattroff(f->box, t.dialog.arrowcolor);
543	}
544}
545
546static void curriteminview(struct privateform *f, struct privateitem *item)
547{
548	unsigned int yup, ydown;
549
550	yup = MIN(item->ylabel, item->yfield);
551	ydown = MAX(item->ylabel, item->yfield);
552
553	/* selected item in view */
554	if (f->y > yup && f->y > 0)
555		f->y = yup;
556	if ((int)(f->y + f->viewrows) - 1 < (int)ydown)
557		f->y = ydown - f->viewrows + 1;
558	/* lower pad after a terminal expansion */
559	if (f->y > 0 && (f->h - f->y) < f->viewrows)
560		f->y = f->h - f->viewrows;
561}
562
563static int form_size_position(struct dialog *d, struct privateform *f)
564{
565	int htext, hform;
566
567	if (set_widget_size(d->conf, d->rows, d->cols, &d->h, &d->w) != 0)
568		return (BSDDIALOG_ERROR);
569
570	/* autosize */
571	hform = (int) f->viewrows;
572	if (f->viewrows == BSDDIALOG_AUTOSIZE)
573		hform = MAX(f->h, f->minviewrows);
574	hform += 2; /* formborders */
575
576	if (set_widget_autosize(d->conf, d->rows, d->cols, &d->h, &d->w,
577	    d->text, &htext, &d->bs, hform, f->w + 4) != 0)
578		return (BSDDIALOG_ERROR);
579	/* formheight: avoid overflow, "at most" and at least minviewrows */
580	if (d->h - BORDERS - htext - HBUTTONS < 2 + (int)f->minviewrows) {
581		f->viewrows = f->minviewrows; /* for widget_checksize() */
582	} else if (f->viewrows == BSDDIALOG_AUTOSIZE) {
583		f->viewrows = MIN(d->h - BORDERS - htext - HBUTTONS, hform) - 2;
584		f->viewrows = MAX(f->viewrows, f->minviewrows);
585	} else {
586		f->viewrows = MIN(d->h - BORDERS - htext - HBUTTONS, hform) - 2;
587	}
588
589	/* checksize */
590	if (f->viewrows < f->minviewrows)
591		RETURN_FMTERROR("formheight, current: %u needed at least %u",
592		    f->viewrows, f->minviewrows);
593	if (widget_checksize(d->h, d->w, &d->bs,
594	    2 /* borders */ + f->minviewrows, f->w + 4) != 0)
595		return (BSDDIALOG_ERROR);
596
597	if (set_widget_position(d->conf, &d->y, &d->x, d->h, d->w) != 0)
598		return (BSDDIALOG_ERROR);
599
600	return (0);
601}
602
603static int
604form_redraw(struct dialog *d, struct privateform *f, bool focusinform)
605{
606	unsigned int i;
607
608	if (d->built) {
609		hide_dialog(d);
610		refresh(); /* Important for decreasing screen */
611	}
612	f->viewrows = f->formheight;
613	f->w = f->wmin;
614	if (form_size_position(d, f) != 0)
615		return (BSDDIALOG_ERROR);
616	if (draw_dialog(d) != 0)
617		return (BSDDIALOG_ERROR);
618	if (d->built)
619		refresh(); /* Important to fix grey lines expanding screen */
620	TEXTPAD(d, 2 /* box borders */ + f->viewrows + HBUTTONS);
621
622	update_box(d->conf, f->box, d->y + d->h - 5 - f->viewrows, d->x + 2,
623	    f->viewrows + 2, d->w - 4, LOWERED);
624
625	for (i = 0; i < f->nitems; i++) {
626		fieldctl(&f->pritems[i], MOVE_CURSOR_BEGIN);
627		if (f->pritems[i].extendfield) {
628			f->w = d->w - 6;
629			f->pritems[i].fieldcols = f->w - f->pritems[i].xfield;
630		}
631		if (f->pritems[i].cursorend)
632			fieldctl(&f->pritems[i], MOVE_CURSOR_END);
633	}
634
635	wresize(f->pad, f->h, f->w);
636	for (i = 0; i < f->nitems; i++)
637		drawitem(f, i, false);
638
639	f->ys = d->y + d->h - 5 - f->viewrows + 1;
640	f->ye = d->y + d->h - 5 ;
641	if ((int)f->w >= d->w - 6) { /* left */
642		f->xs = d->x + 3;
643		f->xe = f->xs + d->w - 7;
644	} else { /* center */
645		f->xs = d->x + 3 + (d->w - 6)/2 - f->w/2;
646		f->xe = f->xs + d->w - 5;
647	}
648
649	if (f->sel != -1) { /* at least 1 writable item */
650		redrawbuttons(d,
651		    d->conf->button.always_active || !focusinform,
652		    !focusinform);
653		wnoutrefresh(d->widget);
654		curriteminview(f, &f->pritems[f->sel]);
655		update_formbox(d->conf, f);
656		wnoutrefresh(f->box);
657		DRAWITEM_TRICK(f, f->sel, focusinform);
658	} else if (f->sel == -1 && f->nitems > 0) { /* all read only */
659		redrawbuttons(d, true, true);
660		wnoutrefresh(d->widget);
661		update_formbox(d->conf, f);
662		wnoutrefresh(f->box);
663		DRAWITEM_TRICK(f, 0, false); /* to refresh pad*/
664	} else { /* no item */
665		wnoutrefresh(f->box);
666	}
667
668	return (0);
669}
670
671/* API */
672int
673bsddialog_form(struct bsddialog_conf *conf, const char *text, int rows,
674    int cols, unsigned int formheight, unsigned int nitems,
675    struct bsddialog_formitem *items, int *focusitem)
676{
677	bool switchfocus, changeitem, focusinform, loop;
678	int next, retval, wchtype;
679	unsigned int i;
680	wint_t input;
681	struct privateitem *item;
682	struct privateform form;
683	struct dialog d;
684
685	if (prepare_dialog(conf, text, rows, cols, &d) != 0)
686		return (BSDDIALOG_ERROR);
687	set_buttons(&d, true, OK_LABEL, CANCEL_LABEL);
688
689	if (build_privateform(conf, nitems, items, &form) != 0)
690		return (BSDDIALOG_ERROR);
691
692	if ((form.box = newwin(1, 1, 1, 1)) == NULL)
693		RETURN_ERROR("Cannot build WINDOW form box");
694	wbkgd(form.box, t.dialog.color);
695	if ((form.pad = newpad(1, 1)) == NULL)
696		RETURN_ERROR("Cannot build WINDOW form pad");
697	wbkgd(form.pad, t.dialog.color);
698
699	set_first_with_default(&form, focusitem);
700	if (form.sel != -1) {
701		focusinform = true;
702		form.y = 0;
703		item = &form.pritems[form.sel];
704	} else {
705		item = NULL;
706		focusinform = false;
707	}
708
709	form.formheight = formheight;
710	if (form_redraw(&d, &form, focusinform) != 0)
711		return (BSDDIALOG_ERROR);
712
713	changeitem = switchfocus = false;
714	loop = true;
715	while (loop) {
716		doupdate();
717		if ((wchtype = get_wch(&input)) == ERR)
718			continue;
719		switch(input) {
720		case KEY_ENTER:
721		case 10: /* Enter */
722			if (focusinform && conf->button.always_active == false)
723				break;
724			retval = BUTTONVALUE(d.bs);
725			loop = false;
726			break;
727		case 27: /* Esc */
728			if (conf->key.enable_esc) {
729				retval = BSDDIALOG_ESC;
730				loop = false;
731			}
732			break;
733		case '\t': /* TAB */
734			if (focusinform) {
735				switchfocus = true;
736			} else {
737				if (d.bs.curr + 1 < (int)d.bs.nbuttons) {
738					d.bs.curr++;
739				} else {
740					d.bs.curr = 0;
741					if (form.sel != -1) {
742						switchfocus = true;
743					}
744				}
745				redrawbuttons(&d, true, true);
746				wnoutrefresh(d.widget);
747			}
748			break;
749		case KEY_LEFT:
750			if (focusinform) {
751				if (fieldctl(item, MOVE_CURSOR_LEFT))
752					DRAWITEM_TRICK(&form, form.sel, true);
753			} else if (d.bs.curr > 0) {
754				d.bs.curr--;
755				redrawbuttons(&d, true, true);
756				wnoutrefresh(d.widget);
757			} else if (form.sel != -1) {
758				switchfocus = true;
759			}
760			break;
761		case KEY_RIGHT:
762			if (focusinform) {
763				if (fieldctl(item, MOVE_CURSOR_RIGHT))
764					DRAWITEM_TRICK(&form, form.sel, true);
765			} else if (d.bs.curr < (int) d.bs.nbuttons - 1) {
766				d.bs.curr++;
767				redrawbuttons(&d, true, true);
768				wnoutrefresh(d.widget);
769			} else if (form.sel != -1) {
770				switchfocus = true;
771			}
772			break;
773		case KEY_CTRL('p'):
774		case KEY_UP:
775			if (focusinform) {
776				next = previtem(form.nitems, form.pritems,
777				    form.sel);
778				changeitem = form.sel != next;
779			} else if (form.sel != -1) {
780				switchfocus = true;
781			}
782			break;
783		case KEY_CTRL('n'):
784		case KEY_DOWN:
785			if (focusinform == false)
786				break;
787			if (form.nitems == 1) {
788				switchfocus = true;
789			} else {
790				next = nextitem(form.nitems, form.pritems,
791				    form.sel);
792				changeitem = form.sel != next;
793			}
794			break;
795		case KEY_PPAGE:
796			if (focusinform) {
797				next = firstitem(form.nitems, form.pritems);
798				changeitem = form.sel != next;
799			}
800			break;
801		case KEY_NPAGE:
802			if (focusinform) {
803				next = lastitem(form.nitems, form.pritems);
804				changeitem = form.sel != next;
805			}
806			break;
807		case KEY_BACKSPACE:
808		case 127: /* Backspace */
809			if (focusinform == false)
810				break;
811			if (fieldctl(item, MOVE_CURSOR_LEFT))
812				if (fieldctl(item, DEL_LETTER))
813					DRAWITEM_TRICK(&form, form.sel, true);
814			break;
815		case KEY_DC:
816			if (focusinform == false)
817				break;
818			if (fieldctl(item, DEL_LETTER))
819				DRAWITEM_TRICK(&form, form.sel, true);
820			break;
821		case KEY_HOME:
822			if (focusinform == false)
823				break;
824			if (fieldctl(item, MOVE_CURSOR_BEGIN))
825				DRAWITEM_TRICK(&form, form.sel, true);
826			break;
827		case KEY_END:
828			if (focusinform == false)
829				break;
830			if (fieldctl(item, MOVE_CURSOR_END))
831				DRAWITEM_TRICK(&form, form.sel, true);
832			break;
833		case KEY_F(1):
834			if (conf->key.f1_file == NULL &&
835			    conf->key.f1_message == NULL)
836				break;
837			curs_set(0);
838			if (f1help_dialog(conf) != 0) {
839				retval = BSDDIALOG_ERROR;
840				loop = false;
841			}
842			if (form_redraw(&d, &form, focusinform) != 0)
843				return (BSDDIALOG_ERROR);
844			break;
845		case KEY_CTRL('l'):
846		case KEY_RESIZE:
847			if (form_redraw(&d, &form, focusinform) != 0)
848				return (BSDDIALOG_ERROR);
849			break;
850		default:
851			if (wchtype == KEY_CODE_YES)
852				break;
853			if (focusinform) {
854				if (item->fieldonebyte && wctob(input) == EOF)
855					break;
856				/*
857				 * MOVE_CURSOR_RIGHT manages new positions
858				 * because the cursor remains on the new letter,
859				 * "if" and "while" update the positions.
860				 */
861				if (insertch(item, input, form.securewch)) {
862					fieldctl(item, MOVE_CURSOR_RIGHT);
863					/*
864					 * no if (fieldctl), update always
865					 * because it fails with maxletters.
866					 */
867					DRAWITEM_TRICK(&form, form.sel, true);
868				}
869			} else {
870				if (shortcut_buttons(input, &d.bs)) {
871					DRAW_BUTTONS(d);
872					doupdate();
873					retval = BUTTONVALUE(d.bs);
874					loop = false;
875				}
876			}
877			break;
878		} /* end switch get_wch() */
879
880		if (switchfocus) {
881			focusinform = !focusinform;
882			d.bs.curr = 0;
883			redrawbuttons(&d,
884			    conf->button.always_active || !focusinform,
885			    !focusinform);
886			wnoutrefresh(d.widget);
887			DRAWITEM_TRICK(&form, form.sel, focusinform);
888			switchfocus = false;
889		}
890
891		if (changeitem) {
892			DRAWITEM_TRICK(&form, form.sel, false);
893			form.sel = next;
894			item = &form.pritems[form.sel];
895			curriteminview(&form, item);
896			update_formbox(conf, &form);
897			wnoutrefresh(form.box);
898			DRAWITEM_TRICK(&form, form.sel, true);
899			changeitem = false;
900		}
901	} /* end while (loop) */
902
903	curs_set(0);
904
905	if (return_values(conf, &form, items) == BSDDIALOG_ERROR)
906		return (BSDDIALOG_ERROR);
907
908	if (focusitem != NULL)
909		*focusitem = form.sel;
910
911	if (form.hasbottomdesc && conf->clear) {
912		move(SCREENLINES - 1, 2);
913		clrtoeol();
914	}
915	for (i = 0; i < form.nitems; i++) {
916		free(form.pritems[i].privwbuf);
917		free(form.pritems[i].pubwbuf);
918	}
919	delwin(form.pad);
920	delwin(form.box);
921	end_dialog(&d);
922
923	return (retval);
924}
925