]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/bsddialog/lib/formbox.c
MFV: file 5.45.
[FreeBSD/FreeBSD.git] / contrib / bsddialog / lib / formbox.c
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 }