xref: /freebsd/contrib/bsddialog/lib/formbox.c (revision a6d8be451f62d425b71a4874f7d4e133b9fb393c)
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 
37 enum field_action {
38 	MOVE_CURSOR_BEGIN,
39 	MOVE_CURSOR_END,
40 	MOVE_CURSOR_RIGHT,
41 	MOVE_CURSOR_LEFT,
42 	DEL_LETTER
43 };
44 
45 struct 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 
70 struct 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 
91 static int
92 build_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 
214 static 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 
317 static 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 
338 static 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 
357 static int
358 return_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 
377 static 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 
394 static 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 
405 static 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 
416 static unsigned int
417 previtem(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 
432 static unsigned int
433 nextitem(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 
448 static 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 
460 static void
461 drawitem(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 
528 static 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 
546 static 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 
563 static 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 
603 static int
604 form_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 */
672 int
673 bsddialog_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