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