xref: /freebsd/contrib/bsddialog/lib/formbox.c (revision c7046f76)
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 	/* cols autosize, rows autosize, rows fullscreen, menu particularity */
428 	if (cols == BSDDIALOG_AUTOSIZE || rows <= BSDDIALOG_AUTOSIZE) {
429 		if (text_size(conf, rows, cols, text, &bs, notext, linelen + 4,
430 		    &htext, &wtext) != 0)
431 			return (BSDDIALOG_ERROR);
432 	}
433 
434 	if (cols == BSDDIALOG_AUTOSIZE)
435 		*w = widget_min_width(conf, wtext, linelen + 4, &bs);
436 
437 	if (rows == BSDDIALOG_AUTOSIZE) {
438 		if (*menurows == 0) {
439 			menusize = widget_max_height(conf) - HBORDERS -
440 			     2 /*buttons*/ - htext;
441 			menusize = MIN(menusize, nitems + 2);
442 			*menurows = menusize - 2 < 0 ? 0 : menusize - 2;
443 		}
444 		else /* h autosize with fixed menurows */
445 			menusize = *menurows + 2;
446 
447 		*h = widget_min_height(conf, htext, menusize, true);
448 		/*
449 		 * avoid menurows overflow and
450 		 * with rows=AUTOSIZE menurows!=0 becomes max-menurows
451 		 */
452 		*menurows = MIN(*h - 6 - htext, (int)*menurows);
453 	} else {
454 		if (*menurows == 0) {
455 			if (*h - 6 - htext <= 0)
456 				*menurows = 0; /* form_checksize() will check */
457 			else
458 				*menurows = MIN(*h-6-htext, nitems);
459 		}
460 	}
461 
462 	return (0);
463 }
464 
465 static int
466 form_checksize(int rows, int cols, const char *text, struct privateform *form,
467     int nitems, struct buttons bs)
468 {
469 	int mincols, textrow, menusize;
470 
471 	/* cols */
472 	mincols = VBORDERS;
473 	mincols += buttons_min_width(bs);
474 	mincols = MAX(mincols, (int)form->w + 6);
475 
476 	if (cols < mincols)
477 		RETURN_ERROR("Form width, cols < buttons or xlabels/xfields");
478 
479 	/* rows */
480 	if (nitems > 0 && form->viewrows == 0)
481 		RETURN_ERROR("items > 0 but viewrows == 0, if formheight = 0 "
482 		    "terminal too small");
483 
484 	if (form->viewrows < form->minviewrows)
485 		RETURN_ERROR("Few formheight rows, if formheight = 0 terminal "
486 		    "too small");
487 
488 	textrow = text != NULL && text[0] != '\0' ? 1 : 0;
489 	menusize = nitems > 0 ? 3 : 0;
490 	if (rows < 2  + 2 + menusize + textrow)
491 		RETURN_ERROR("Few lines for this form");
492 
493 	return (0);
494 }
495 
496 static void curriteminview(struct privateform *form, struct privateitem *item)
497 {
498 	unsigned int yup, ydown;
499 
500 	yup = MIN(item->ylabel, item->yfield);
501 	ydown = MAX(item->ylabel, item->yfield);
502 
503 	if (form->y > yup && form->y > 0)
504 		form->y = yup;
505 	if ((int)(form->y + form->viewrows) - 1 < (int)ydown)
506 		form->y = ydown - form->viewrows + 1;
507 }
508 
509 /* API */
510 int
511 bsddialog_form(struct bsddialog_conf *conf, const char *text, int rows,
512     int cols, unsigned int formheight, unsigned int nitems,
513     struct bsddialog_formitem *apiitems)
514 {
515 	bool switchfocus, changeitem, focusinform, insecurecursor, loop;
516 	int curritem, mbchsize, next, retval, y, x, h, w, wchtype;
517 	unsigned int i, j, itemybeg, itemxbeg, tmp;
518 	wchar_t *winit;
519 	wint_t input;
520 	WINDOW *widget, *textpad, *shadow;
521 	struct privateitem *items, *item;
522 	struct buttons bs;
523 	struct privateform form;
524 
525 	for (i = 0; i < nitems; i++) {
526 		if (apiitems[i].maxvaluelen == 0)
527 			RETURN_ERROR("maxvaluelen cannot be zero");
528 		if (apiitems[i].fieldlen == 0)
529 			RETURN_ERROR("fieldlen cannot be zero");
530 	}
531 
532 	insecurecursor = false;
533 	if (conf->form.securembch != NULL) {
534 		mbchsize = mblen(conf->form.securembch, MB_LEN_MAX);
535 		if(mbtowc(&form.securewch, conf->form.securembch, mbchsize) < 0)
536 			RETURN_ERROR("Cannot convert securembch to wchar_t");
537 		insecurecursor = true;
538 	} else if (conf->form.securech != '\0') {
539 		form.securewch = btowc(conf->form.securech);
540 		insecurecursor = true;
541 	} else {
542 		form.securewch = L' ';
543 	}
544 
545 	if ((items = malloc(nitems * sizeof(struct privateitem))) == NULL)
546 		RETURN_ERROR("Cannot allocate internal items");
547 	form.h = form.w = form.minviewrows = 0;
548 	for (i = 0; i < nitems; i++) {
549 		item = &items[i];
550 		item->label = apiitems[i].label;
551 		item->ylabel = apiitems[i].ylabel;
552 		item->xlabel = apiitems[i].xlabel;
553 		item->yfield = apiitems[i].yfield;
554 		item->xfield = apiitems[i].xfield;
555 		item->secure = apiitems[i].flags & BSDDIALOG_FIELDHIDDEN;
556 		item->readonly = apiitems[i].flags & BSDDIALOG_FIELDREADONLY;
557 		item->fieldnocolor = apiitems[i].flags & BSDDIALOG_FIELDNOCOLOR;
558 		item->extendfield = apiitems[i].flags & BSDDIALOG_FIELDEXTEND;
559 		item->fieldonebyte = apiitems[i].flags &
560 		    BSDDIALOG_FIELDSINGLEBYTE;
561 		item->cursorend = apiitems[i].flags & BSDDIALOG_FIELDCURSOREND;
562 		item->bottomdesc = apiitems[i].bottomdesc;
563 		if (item->readonly || (item->secure && !insecurecursor))
564 			item->cursor = false;
565 		else
566 			item->cursor = true;
567 
568 		item->maxletters = apiitems[i].maxvaluelen;
569 		item->privwbuf = calloc(item->maxletters + 1, sizeof(wchar_t));
570 		if (item->privwbuf == NULL)
571 			RETURN_ERROR("Cannot allocate item private buffer");
572 		memset(item->privwbuf, 0, item->maxletters + 1);
573 		item->pubwbuf = calloc(item->maxletters + 1, sizeof(wchar_t));
574 		if (item->pubwbuf == NULL)
575 			RETURN_ERROR("Cannot allocate item private buffer");
576 		memset(item->pubwbuf, 0, item->maxletters + 1);
577 
578 		if ((winit = alloc_mbstows(apiitems[i].init)) == NULL)
579 			RETURN_ERROR("Cannot allocate item.init in wchar_t*");
580 		wcsncpy(item->privwbuf, winit, item->maxletters);
581 		wcsncpy(item->pubwbuf, winit, item->maxletters);
582 		free(winit);
583 		item->nletters = wcslen(item->pubwbuf);
584 		if (item->secure) {
585 			for (j = 0; j < item->nletters; j++)
586 				item->pubwbuf[j] = form.securewch;
587 		}
588 
589 		item->fieldcols = apiitems[i].fieldlen;
590 		item->xposdraw = 0;
591 		item->xcursor = 0;
592 		item->pos = 0;
593 
594 		form.h = MAX(form.h, items[i].ylabel);
595 		form.h = MAX(form.h, items[i].yfield);
596 		form.w = MAX(form.w, items[i].xlabel + strcols(items[i].label));
597 		form.w = MAX(form.w, items[i].xfield + items[i].fieldcols);
598 		if (i == 0) {
599 			itemybeg = MIN(items[i].ylabel, items[i].yfield);
600 			itemxbeg = MIN(items[i].xlabel, items[i].xfield);
601 		} else {
602 			tmp = MIN(items[i].ylabel, items[i].yfield);
603 			itemybeg = MIN(itemybeg, tmp);
604 			tmp = MIN(items[i].xlabel, items[i].xfield);
605 			itemxbeg = MIN(itemxbeg, tmp);
606 		}
607 		tmp = abs((int)items[i].ylabel - (int)items[i].yfield);
608 		form.minviewrows = MAX(form.minviewrows, tmp);
609 	}
610 	if (nitems > 0) {
611 		form.h = form.h + 1 - itemybeg;
612 		form.w -= itemxbeg;
613 		form.minviewrows += 1;
614 	}
615 	form.wmin = form.w;
616 	for (i = 0; i < nitems; i++) {
617 		items[i].ylabel -= itemybeg;
618 		items[i].yfield -= itemybeg;
619 		items[i].xlabel -= itemxbeg;
620 		items[i].xfield -= itemxbeg;
621 	}
622 
623 	get_buttons(conf, &bs, BUTTON_OK_LABEL, BUTTON_CANCEL_LABEL);
624 	form.viewrows = formheight;
625 
626 	if (set_widget_size(conf, rows, cols, &h, &w) != 0)
627 		return (BSDDIALOG_ERROR);
628 	if (menu_autosize(conf, rows, cols, &h, &w, text, form.w,
629 	    &form.viewrows, form.h, bs) != 0)
630 		return (BSDDIALOG_ERROR);
631 	if (form_checksize(h, w, text, &form, nitems, bs) != 0)
632 		return (BSDDIALOG_ERROR);
633 	if (set_widget_position(conf, &y, &x, h, w) != 0)
634 		return (BSDDIALOG_ERROR);
635 
636 	if (new_dialog(conf, &shadow, &widget, y, x, h, w, &textpad, text, &bs,
637 	    true) != 0)
638 		return (BSDDIALOG_ERROR);
639 
640 	doupdate();
641 
642 	prefresh(textpad, 0, 0, y + 1, x + 1 + TEXTHMARGIN,
643 	    y + h - form.viewrows, x + 1 + w - TEXTHMARGIN);
644 
645 	form.border = new_boxed_window(conf, y + h - 5 - form.viewrows, x + 2,
646 	    form.viewrows + 2, w - 4, LOWERED);
647 
648 	for (i = 0; i < nitems; i++) {
649 		if (items[i].extendfield) {
650 			form.w = w - 6;
651 			items[i].fieldcols = form.w - items[i].xfield;
652 		}
653 		if (items[i].cursorend)
654 			fieldctl(item, MOVE_CURSOR_END);
655 	}
656 
657 	form.pad = newpad(form.h, form.w);
658 	wbkgd(form.pad, t.dialog.color);
659 
660 	form.ys = y + h - 5 - form.viewrows + 1;
661 	form.ye = y + h - 5 ;
662 	if ((int)form.w >= w - 6) { /* left */
663 		form.xs = x + 3;
664 		form.xe = form.xs + w - 7;
665 	} else { /* center */
666 		form.xs = x + 3 + (w-6)/2 - form.w/2;
667 		form.xe = form.xs + w - 5;
668 	}
669 
670 	curritem = -1;
671 	for (i=0 ; i < nitems; i++) {
672 		DRAWITEM_TRICK(&form, &items[i], false);
673 		if (curritem == -1 && items[i].readonly == false)
674 			curritem = i;
675 	}
676 	if (curritem != -1) {
677 		focusinform = true;
678 		redrawbuttons(widget, &bs, conf->button.always_active, false);
679 		form.y = 0;
680 		item = &items[curritem];
681 		curriteminview(&form, item);
682 		update_formborders(conf, &form);
683 		wrefresh(form.border);
684 		DRAWITEM_TRICK(&form, item, true);
685 	} else {
686 		item = NULL;
687 		focusinform = false;
688 		wrefresh(form.border);
689 	}
690 
691 	changeitem = switchfocus = false;
692 	loop = true;
693 	while (loop) {
694 		if ((wchtype = get_wch(&input)) == ERR)
695 			continue;
696 		switch(input) {
697 		case KEY_ENTER:
698 		case 10: /* Enter */
699 			if (focusinform && conf->button.always_active == false)
700 				break;
701 			retval = return_values(conf, bs.value[bs.curr],
702 			    nitems, apiitems, items);
703 			loop = false;
704 			break;
705 		case 27: /* Esc */
706 			if (conf->key.enable_esc) {
707 				retval = return_values(conf, BSDDIALOG_ESC,
708 				    nitems, apiitems, items);
709 				loop = false;
710 			}
711 			break;
712 		case '\t': /* TAB */
713 			if (focusinform) {
714 				switchfocus = true;
715 			} else {
716 				if (bs.curr + 1 < (int)bs.nbuttons) {
717 					bs.curr++;
718 				} else {
719 					bs.curr = 0;
720 					if (curritem != -1) {
721 						switchfocus = true;
722 					}
723 				}
724 				draw_buttons(widget, bs, true);
725 				wrefresh(widget);
726 			}
727 			break;
728 		case KEY_LEFT:
729 			if (focusinform) {
730 				if(fieldctl(item, MOVE_CURSOR_LEFT))
731 					DRAWITEM_TRICK(&form, item, true);
732 			} else if (bs.curr > 0) {
733 				bs.curr--;
734 				draw_buttons(widget, bs, true);
735 				wrefresh(widget);
736 			} else if (curritem != -1) {
737 				switchfocus = true;
738 			}
739 			break;
740 		case KEY_RIGHT:
741 			if (focusinform) {
742 				if(fieldctl(item, MOVE_CURSOR_RIGHT))
743 					DRAWITEM_TRICK(&form, item, true);
744 			} else if (bs.curr < (int) bs.nbuttons - 1) {
745 				bs.curr++;
746 				draw_buttons(widget, bs, true);
747 				wrefresh(widget);
748 			} else if (curritem != -1) {
749 				switchfocus = true;
750 			}
751 			break;
752 		case KEY_UP:
753 			if (focusinform) {
754 				next = previtem(nitems, items, curritem);
755 				changeitem = curritem != next;
756 			} else if (curritem != -1) {
757 				switchfocus = true;
758 			}
759 			break;
760 		case KEY_DOWN:
761 			if (focusinform == false)
762 				break;
763 			if (nitems == 1) {
764 				switchfocus = true;
765 			} else {
766 				next = nextitem(nitems, items, curritem);
767 				changeitem = curritem != next;
768 			}
769 			break;
770 		case KEY_PPAGE:
771 			if (focusinform) {
772 				next = firstitem(nitems, items);
773 				changeitem = curritem != next;
774 			}
775 			break;
776 		case KEY_NPAGE:
777 			if (focusinform) {
778 				next = lastitem(nitems, items);
779 				changeitem = curritem != next;
780 			}
781 			break;
782 		case KEY_BACKSPACE:
783 		case 127: /* Backspace */
784 			if (focusinform == false)
785 				break;
786 			if(fieldctl(item, MOVE_CURSOR_LEFT))
787 				if(fieldctl(item, DEL_LETTER))
788 					DRAWITEM_TRICK(&form, item, true);
789 			break;
790 		case KEY_DC:
791 			if (focusinform == false)
792 				break;
793 			if(fieldctl(item, DEL_LETTER))
794 				DRAWITEM_TRICK(&form, item, true);
795 			break;
796 		case KEY_HOME:
797 			if (focusinform == false)
798 				break;
799 			if(fieldctl(item, MOVE_CURSOR_BEGIN))
800 				DRAWITEM_TRICK(&form, item, true);
801 			break;
802 		case KEY_END:
803 			if (focusinform == false)
804 				break;
805 			if (fieldctl(item, MOVE_CURSOR_END))
806 				DRAWITEM_TRICK(&form, item, true);
807 			break;
808 		case KEY_F(1):
809 			if (conf->key.f1_file == NULL &&
810 			    conf->key.f1_message == NULL)
811 				break;
812 			curs_set(0);
813 			if (f1help(conf) != 0) {
814 				retval = BSDDIALOG_ERROR;
815 				loop = false;
816 			}
817 			/* No break, screen size can change */
818 		case KEY_RESIZE:
819 			/* Important for decreasing screen */
820 			hide_widget(y, x, h, w, conf->shadow);
821 			refresh();
822 
823 			form.viewrows = formheight;
824 			form.w = form.wmin;
825 			if (set_widget_size(conf, rows, cols, &h, &w) != 0)
826 				return (BSDDIALOG_ERROR);
827 			if (menu_autosize(conf, rows, cols, &h, &w, text, form.w,
828 			    &form.viewrows, form.h, bs) != 0)
829 				return (BSDDIALOG_ERROR);
830 			if (form_checksize(h, w, text, &form, nitems, bs) != 0)
831 				return (BSDDIALOG_ERROR);
832 			if (set_widget_position(conf, &y, &x, h, w) != 0)
833 				return (BSDDIALOG_ERROR);
834 
835 			if (update_dialog(conf, shadow, widget, y, x, h, w,
836 			    textpad, text, &bs, true) != 0)
837 			return (BSDDIALOG_ERROR);
838 
839 			doupdate();
840 
841 			prefresh(textpad, 0, 0, y + 1, x + 1 + TEXTHMARGIN,
842 			    y + h - form.viewrows, x + 1 + w - TEXTHMARGIN);
843 
844 			wclear(form.border);
845 			mvwin(form.border, y + h - 5 - form.viewrows, x + 2);
846 			wresize(form.border, form.viewrows + 2, w - 4);
847 
848 			for (i = 0; i < nitems; i++) {
849 				fieldctl(&items[i], MOVE_CURSOR_BEGIN);
850 				if (items[i].extendfield) {
851 					form.w = w - 6;
852 					items[i].fieldcols =
853 					    form.w - items[i].xfield;
854 				}
855 				if (items[i].cursorend)
856 					fieldctl(&items[i], MOVE_CURSOR_END);
857 			}
858 
859 			form.ys = y + h - 5 - form.viewrows + 1;
860 			form.ye = y + h - 5 ;
861 			if ((int)form.w >= w - 6) { /* left */
862 				form.xs = x + 3;
863 				form.xe = form.xs + w - 7;
864 			} else { /* center */
865 				form.xs = x + 3 + (w-6)/2 - form.w/2;
866 				form.xe = form.xs + w - 5;
867 			}
868 
869 			if (curritem != -1) {
870 				redrawbuttons(widget, &bs,
871 				    conf->button.always_active || !focusinform,
872 				    !focusinform);
873 				curriteminview(&form, item);
874 				update_formborders(conf, &form);
875 				wrefresh(form.border);
876 				/* drawitem just to prefresh() pad */
877 				DRAWITEM_TRICK(&form, item, focusinform);
878 			} else {
879 				wrefresh(form.border);
880 			}
881 			break;
882 		default:
883 			if (wchtype == KEY_CODE_YES)
884 				break;
885 			if (focusinform) {
886 				if (item->fieldonebyte && wctob(input) == EOF)
887 					break;
888 				/*
889 				 * MOVE_CURSOR_RIGHT manages new positions
890 				 * because the cursor remains on the new letter,
891 				 * "if" and "while" update the positions.
892 				 */
893 				if(insertch(&form, item, input)) {
894 					fieldctl(item, MOVE_CURSOR_RIGHT);
895 					/*
896 					 * no if(fieldctl), update always
897 					 * because it fails with maxletters.
898 					 */
899 					DRAWITEM_TRICK(&form, item, true);
900 				}
901 			} else {
902 				if (shortcut_buttons(input, &bs)) {
903 					retval = return_values(conf,
904 					    bs.value[bs.curr], nitems, apiitems,
905 					    items);
906 					loop = false;
907 				}
908 			}
909 			break;
910 		} /* end switch handler */
911 
912 		if (switchfocus) {
913 			focusinform = !focusinform;
914 			bs.curr = 0;
915 			redrawbuttons(widget, &bs,
916 			    conf->button.always_active || !focusinform,
917 			    !focusinform);
918 			DRAWITEM_TRICK(&form, item, focusinform);
919 			switchfocus = false;
920 		}
921 
922 		if (changeitem) {
923 			DRAWITEM_TRICK(&form, item, false);
924 			curritem = next;
925 			item = &items[curritem];
926 			curriteminview(&form, item);
927 			update_formborders(conf, &form);
928 			DRAWITEM_TRICK(&form, item, true);
929 			changeitem = false;
930 		}
931 	} /* end while handler */
932 
933 	curs_set(0);
934 
935 	delwin(form.pad);
936 	delwin(form.border);
937 	for (i = 0; i < nitems; i++) {
938 		free(items[i].privwbuf);
939 		free(items[i].pubwbuf);
940 	}
941 	end_dialog(conf, shadow, widget, textpad);
942 
943 	return (retval);
944 }
945