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