1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2021-2024 Alfonso Sabato Siciliano
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28 #include <curses.h>
29 #include <limits.h>
30 #include <stdlib.h>
31 #include <string.h>
32
33 #include "bsddialog.h"
34 #include "bsddialog_theme.h"
35 #include "lib_util.h"
36
37 enum field_action {
38 MOVE_CURSOR_BEGIN,
39 MOVE_CURSOR_END,
40 MOVE_CURSOR_RIGHT,
41 MOVE_CURSOR_LEFT,
42 DEL_LETTER
43 };
44
45 struct privateitem {
46 const char *label; /* formitem.label */
47 unsigned int ylabel; /* formitem.ylabel */
48 unsigned int xlabel; /* formitem.xlabel */
49 unsigned int yfield; /* formitem.yfield */
50 unsigned int xfield; /* formitem.xfield */
51 bool secure; /* formitem.flags & BSDDIALOG_FIELDHIDDEN */
52 bool readonly; /* formitem.flags & BSDDIALOG_FIELDREADONLY */
53 bool fieldnocolor; /* formitem.flags & BSDDIALOG_FIELDNOCOLOR */
54 bool extendfield; /* formitem.flags & BSDDIALOG_FIELDEXTEND */
55 bool fieldonebyte; /* formitem.flags & BSDDIALOG_FIELDSINGLEBYTE */
56 bool cursorend; /* formitem.flags & BSDDIALOG_FIELDCURSOREND */
57 bool cursor; /* field cursor visibility */
58 const char *bottomdesc; /* formitem.bottomdesc */
59
60 wchar_t *privwbuf; /* formitem.value */
61 wchar_t *pubwbuf; /* string for drawitem() */
62 unsigned int maxletters; /* formitem.maxvaluelen, [priv|pub]wbuf size */
63 unsigned int nletters; /* letters in privwbuf and pubwbuf */
64 unsigned int pos; /* pos in privwbuf and pubwbuf */
65 unsigned int fieldcols; /* formitem.fieldlen */
66 unsigned int xcursor; /* position in fieldcols [0 - fieldcols-1] */
67 unsigned int xposdraw; /* first pubwbuf index to draw */
68 };
69
70 struct privateform {
71 WINDOW *box; /* window to draw borders */
72 WINDOW *pad;
73 unsigned int h; /* only to create pad */
74 unsigned int w; /* only to create pad */
75 unsigned int wmin; /* to refresh, w can change for FIELDEXTEND */
76 unsigned int ys; /* to refresh */
77 unsigned int ye; /* to refresh */
78 unsigned int xs; /* to refresh */
79 unsigned int xe; /* to refresh */
80 unsigned int y; /* changes moving focus around items */
81 unsigned int formheight; /* API formheight */
82 unsigned int viewrows; /* visible rows, real formheight */
83 unsigned int minviewrows; /* min viewrows, ylabel != yfield */
84 wchar_t securewch; /* wide char of conf.form.secure[mb]ch */
85 unsigned int nitems; /* like API nitems */
86 struct privateitem *pritems;
87 int sel; /* selected item in pritem, can be -1 */
88 bool hasbottomdesc; /* some item has bottomdesc */
89 };
90
91 static int
build_privateform(struct bsddialog_conf * conf,unsigned int nitems,struct bsddialog_formitem * items,struct privateform * f)92 build_privateform(struct bsddialog_conf*conf, unsigned int nitems,
93 struct bsddialog_formitem *items, struct privateform *f)
94 {
95 bool insecurecursor;
96 int mbchsize;
97 unsigned int i, j, itemybeg, itemxbeg, tmp;
98 wchar_t *winit;
99 struct privateitem *item;
100
101 /* checks */
102 CHECK_ARRAY(nitems, items);
103 for (i = 0; i < nitems; i++) {
104 if (items[i].fieldlen == 0)
105 RETURN_FMTERROR("item %u [0-%u] fieldlen = 0",
106 i, nitems);
107 if (items[i].maxvaluelen == 0)
108 RETURN_FMTERROR("item %u [0-%u] maxvaluelen = 0",
109 i, nitems);
110 }
111 f->nitems = nitems;
112
113 /* insecure ch */
114 insecurecursor = false;
115 if (conf->form.securembch != NULL) {
116 mbchsize = mblen(conf->form.securembch, MB_LEN_MAX);
117 if (mbtowc(&f->securewch, conf->form.securembch, mbchsize) < 0)
118 RETURN_ERROR("Cannot convert securembch to wchar_t");
119 insecurecursor = true;
120 } else if (conf->form.securech != '\0') {
121 f->securewch = btowc(conf->form.securech);
122 insecurecursor = true;
123 } else {
124 f->securewch = L' ';
125 }
126
127 /* alloc and set private items */
128 f->pritems = malloc(f->nitems * sizeof(struct privateitem));
129 if (f->pritems == NULL)
130 RETURN_ERROR("Cannot allocate internal form.pritems");
131 f->hasbottomdesc = false;
132 f->h = f->w = f->minviewrows = 0;
133 for (i = 0; i < f->nitems; i++) {
134 item = &f->pritems[i];
135 item->label = CHECK_STR(items[i].label);
136 item->ylabel = items[i].ylabel;
137 item->xlabel = items[i].xlabel;
138 item->yfield = items[i].yfield;
139 item->xfield = items[i].xfield;
140 item->secure = items[i].flags & BSDDIALOG_FIELDHIDDEN;
141 item->readonly = items[i].flags & BSDDIALOG_FIELDREADONLY;
142 item->fieldnocolor = items[i].flags & BSDDIALOG_FIELDNOCOLOR;
143 item->extendfield = items[i].flags & BSDDIALOG_FIELDEXTEND;
144 item->fieldonebyte = items[i].flags &
145 BSDDIALOG_FIELDSINGLEBYTE;
146 item->cursorend = items[i].flags & BSDDIALOG_FIELDCURSOREND;
147 item->bottomdesc = CHECK_STR(items[i].bottomdesc);
148 if (items[i].bottomdesc != NULL)
149 f->hasbottomdesc = true;
150 if (item->readonly || (item->secure && !insecurecursor))
151 item->cursor = false;
152 else
153 item->cursor = true;
154
155 item->maxletters = items[i].maxvaluelen;
156 item->privwbuf = calloc(item->maxletters + 1, sizeof(wchar_t));
157 if (item->privwbuf == NULL)
158 RETURN_ERROR("Cannot allocate item private buffer");
159 memset(item->privwbuf, 0, item->maxletters + 1);
160 item->pubwbuf = calloc(item->maxletters + 1, sizeof(wchar_t));
161 if (item->pubwbuf == NULL)
162 RETURN_ERROR("Cannot allocate item private buffer");
163 memset(item->pubwbuf, 0, item->maxletters + 1);
164
165 if ((winit = alloc_mbstows(CHECK_STR(items[i].init))) == NULL)
166 RETURN_ERROR("Cannot allocate item.init in wchar_t*");
167 wcsncpy(item->privwbuf, winit, item->maxletters);
168 wcsncpy(item->pubwbuf, winit, item->maxletters);
169 free(winit);
170 item->nletters = wcslen(item->pubwbuf);
171 if (item->secure) {
172 for (j = 0; j < item->nletters; j++)
173 item->pubwbuf[j] = f->securewch;
174 }
175
176 item->fieldcols = items[i].fieldlen;
177 item->xposdraw = 0;
178 item->xcursor = 0;
179 item->pos = 0;
180
181 /* size and position */
182 f->h = MAX(f->h, item->ylabel);
183 f->h = MAX(f->h, item->yfield);
184 f->w = MAX(f->w, item->xlabel + strcols(item->label));
185 f->w = MAX(f->w, item->xfield + item->fieldcols);
186 if (i == 0) {
187 itemybeg = MIN(item->ylabel, item->yfield);
188 itemxbeg = MIN(item->xlabel, item->xfield);
189 } else {
190 tmp = MIN(item->ylabel, item->yfield);
191 itemybeg = MIN(itemybeg, tmp);
192 tmp = MIN(item->xlabel, item->xfield);
193 itemxbeg = MIN(itemxbeg, tmp);
194 }
195 tmp = abs((int)item->ylabel - (int)item->yfield);
196 f->minviewrows = MAX(f->minviewrows, tmp);
197 }
198 if (f->nitems > 0) {
199 f->h = f->h + 1 - itemybeg;
200 f->w -= itemxbeg;
201 f->minviewrows += 1;
202 }
203 f->wmin = f->w;
204 for (i = 0; i < f->nitems; i++) {
205 f->pritems[i].ylabel -= itemybeg;
206 f->pritems[i].yfield -= itemybeg;
207 f->pritems[i].xlabel -= itemxbeg;
208 f->pritems[i].xfield -= itemxbeg;
209 }
210
211 return (0);
212 }
213
fieldctl(struct privateitem * item,enum field_action act)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
insertch(struct privateitem * item,wchar_t wch,wchar_t securewch)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
alloc_wstomb(wchar_t * wstr)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
return_values(struct bsddialog_conf * conf,struct privateform * f,struct bsddialog_formitem * items)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
set_first_with_default(struct privateform * f,int * focusitem)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
firstitem(unsigned int nitems,struct privateitem * items)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
lastitem(unsigned int nitems,struct privateitem * items)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
previtem(unsigned int nitems,struct privateitem * items,int curritem)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
nextitem(unsigned int nitems,struct privateitem * items,int curritem)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
redrawbuttons(struct dialog * d,bool focus,bool shortcut)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
drawitem(struct privateform * f,int idx,bool focus)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
update_formbox(struct bsddialog_conf * conf,struct privateform * f)528 static void update_formbox(struct bsddialog_conf *conf, struct privateform *f)
529 {
530 int h, w;
531
532 getmaxyx(f->box, h, w);
533 draw_borders(conf, f->box, LOWERED);
534
535 if (f->viewrows < f->h) {
536 wattron(f->box, t.dialog.arrowcolor);
537 if (f->y > 0)
538 mvwhline(f->box, 0, (w / 2) - 2, UARROW(conf), 5);
539
540 if (f->y + f->viewrows < f->h)
541 mvwhline(f->box, h-1, (w / 2) - 2, DARROW(conf), 5);
542 wattroff(f->box, t.dialog.arrowcolor);
543 }
544 }
545
curriteminview(struct privateform * f,struct privateitem * item)546 static void curriteminview(struct privateform *f, struct privateitem *item)
547 {
548 unsigned int yup, ydown;
549
550 yup = MIN(item->ylabel, item->yfield);
551 ydown = MAX(item->ylabel, item->yfield);
552
553 /* selected item in view */
554 if (f->y > yup && f->y > 0)
555 f->y = yup;
556 if ((int)(f->y + f->viewrows) - 1 < (int)ydown)
557 f->y = ydown - f->viewrows + 1;
558 /* lower pad after a terminal expansion */
559 if (f->y > 0 && (f->h - f->y) < f->viewrows)
560 f->y = f->h - f->viewrows;
561 }
562
form_size_position(struct dialog * d,struct privateform * f)563 static int form_size_position(struct dialog *d, struct privateform *f)
564 {
565 int htext, hform;
566
567 if (set_widget_size(d->conf, d->rows, d->cols, &d->h, &d->w) != 0)
568 return (BSDDIALOG_ERROR);
569
570 /* autosize */
571 hform = (int) f->viewrows;
572 if (f->viewrows == BSDDIALOG_AUTOSIZE)
573 hform = MAX(f->h, f->minviewrows);
574 hform += 2; /* formborders */
575
576 if (set_widget_autosize(d->conf, d->rows, d->cols, &d->h, &d->w,
577 d->text, &htext, &d->bs, hform, f->w + 4) != 0)
578 return (BSDDIALOG_ERROR);
579 /* formheight: avoid overflow, "at most" and at least minviewrows */
580 if (d->h - BORDERS - htext - HBUTTONS < 2 + (int)f->minviewrows) {
581 f->viewrows = f->minviewrows; /* for widget_checksize() */
582 } else if (f->viewrows == BSDDIALOG_AUTOSIZE) {
583 f->viewrows = MIN(d->h - BORDERS - htext - HBUTTONS, hform) - 2;
584 f->viewrows = MAX(f->viewrows, f->minviewrows);
585 } else {
586 f->viewrows = MIN(d->h - BORDERS - htext - HBUTTONS, hform) - 2;
587 }
588
589 /* checksize */
590 if (f->viewrows < f->minviewrows)
591 RETURN_FMTERROR("formheight, current: %u needed at least %u",
592 f->viewrows, f->minviewrows);
593 if (widget_checksize(d->h, d->w, &d->bs,
594 2 /* borders */ + f->minviewrows, f->w + 4) != 0)
595 return (BSDDIALOG_ERROR);
596
597 if (set_widget_position(d->conf, &d->y, &d->x, d->h, d->w) != 0)
598 return (BSDDIALOG_ERROR);
599
600 return (0);
601 }
602
603 static int
form_redraw(struct dialog * d,struct privateform * f,bool focusinform)604 form_redraw(struct dialog *d, struct privateform *f, bool focusinform)
605 {
606 unsigned int i;
607
608 if (d->built) {
609 hide_dialog(d);
610 refresh(); /* Important for decreasing screen */
611 }
612 f->viewrows = f->formheight;
613 f->w = f->wmin;
614 if (form_size_position(d, f) != 0)
615 return (BSDDIALOG_ERROR);
616 if (draw_dialog(d) != 0)
617 return (BSDDIALOG_ERROR);
618 if (d->built)
619 refresh(); /* Important to fix grey lines expanding screen */
620 TEXTPAD(d, 2 /* box borders */ + f->viewrows + HBUTTONS);
621
622 update_box(d->conf, f->box, d->y + d->h - 5 - f->viewrows, d->x + 2,
623 f->viewrows + 2, d->w - 4, LOWERED);
624
625 for (i = 0; i < f->nitems; i++) {
626 fieldctl(&f->pritems[i], MOVE_CURSOR_BEGIN);
627 if (f->pritems[i].extendfield) {
628 f->w = d->w - 6;
629 f->pritems[i].fieldcols = f->w - f->pritems[i].xfield;
630 }
631 if (f->pritems[i].cursorend)
632 fieldctl(&f->pritems[i], MOVE_CURSOR_END);
633 }
634
635 wresize(f->pad, f->h, f->w);
636 for (i = 0; i < f->nitems; i++)
637 drawitem(f, i, false);
638
639 f->ys = d->y + d->h - 5 - f->viewrows + 1;
640 f->ye = d->y + d->h - 5 ;
641 if ((int)f->w >= d->w - 6) { /* left */
642 f->xs = d->x + 3;
643 f->xe = f->xs + d->w - 7;
644 } else { /* center */
645 f->xs = d->x + 3 + (d->w - 6)/2 - f->w/2;
646 f->xe = f->xs + d->w - 5;
647 }
648
649 if (f->sel != -1) { /* at least 1 writable item */
650 redrawbuttons(d,
651 d->conf->button.always_active || !focusinform,
652 !focusinform);
653 wnoutrefresh(d->widget);
654 curriteminview(f, &f->pritems[f->sel]);
655 update_formbox(d->conf, f);
656 wnoutrefresh(f->box);
657 DRAWITEM_TRICK(f, f->sel, focusinform);
658 } else if (f->sel == -1 && f->nitems > 0) { /* all read only */
659 redrawbuttons(d, true, true);
660 wnoutrefresh(d->widget);
661 update_formbox(d->conf, f);
662 wnoutrefresh(f->box);
663 DRAWITEM_TRICK(f, 0, false); /* to refresh pad*/
664 } else { /* no item */
665 wnoutrefresh(f->box);
666 }
667
668 return (0);
669 }
670
671 /* API */
672 int
bsddialog_form(struct bsddialog_conf * conf,const char * text,int rows,int cols,unsigned int formheight,unsigned int nitems,struct bsddialog_formitem * items,int * focusitem)673 bsddialog_form(struct bsddialog_conf *conf, const char *text, int rows,
674 int cols, unsigned int formheight, unsigned int nitems,
675 struct bsddialog_formitem *items, int *focusitem)
676 {
677 bool switchfocus, changeitem, focusinform, loop;
678 int next, retval, wchtype;
679 unsigned int i;
680 wint_t input;
681 struct privateitem *item;
682 struct privateform form;
683 struct dialog d;
684
685 if (prepare_dialog(conf, text, rows, cols, &d) != 0)
686 return (BSDDIALOG_ERROR);
687 set_buttons(&d, true, OK_LABEL, CANCEL_LABEL);
688
689 if (build_privateform(conf, nitems, items, &form) != 0)
690 return (BSDDIALOG_ERROR);
691
692 if ((form.box = newwin(1, 1, 1, 1)) == NULL)
693 RETURN_ERROR("Cannot build WINDOW form box");
694 wbkgd(form.box, t.dialog.color);
695 if ((form.pad = newpad(1, 1)) == NULL)
696 RETURN_ERROR("Cannot build WINDOW form pad");
697 wbkgd(form.pad, t.dialog.color);
698
699 set_first_with_default(&form, focusitem);
700 if (form.sel != -1) {
701 focusinform = true;
702 form.y = 0;
703 item = &form.pritems[form.sel];
704 } else {
705 item = NULL;
706 focusinform = false;
707 }
708
709 form.formheight = formheight;
710 if (form_redraw(&d, &form, focusinform) != 0)
711 return (BSDDIALOG_ERROR);
712
713 changeitem = switchfocus = false;
714 loop = true;
715 while (loop) {
716 doupdate();
717 if ((wchtype = get_wch(&input)) == ERR)
718 continue;
719 switch(input) {
720 case KEY_ENTER:
721 case 10: /* Enter */
722 if (focusinform && conf->button.always_active == false)
723 break;
724 retval = BUTTONVALUE(d.bs);
725 loop = false;
726 break;
727 case 27: /* Esc */
728 if (conf->key.enable_esc) {
729 retval = BSDDIALOG_ESC;
730 loop = false;
731 }
732 break;
733 case '\t': /* TAB */
734 if (focusinform) {
735 switchfocus = true;
736 } else {
737 if (d.bs.curr + 1 < (int)d.bs.nbuttons) {
738 d.bs.curr++;
739 } else {
740 d.bs.curr = 0;
741 if (form.sel != -1) {
742 switchfocus = true;
743 }
744 }
745 redrawbuttons(&d, true, true);
746 wnoutrefresh(d.widget);
747 }
748 break;
749 case KEY_LEFT:
750 if (focusinform) {
751 if (fieldctl(item, MOVE_CURSOR_LEFT))
752 DRAWITEM_TRICK(&form, form.sel, true);
753 } else if (d.bs.curr > 0) {
754 d.bs.curr--;
755 redrawbuttons(&d, true, true);
756 wnoutrefresh(d.widget);
757 } else if (form.sel != -1) {
758 switchfocus = true;
759 }
760 break;
761 case KEY_RIGHT:
762 if (focusinform) {
763 if (fieldctl(item, MOVE_CURSOR_RIGHT))
764 DRAWITEM_TRICK(&form, form.sel, true);
765 } else if (d.bs.curr < (int) d.bs.nbuttons - 1) {
766 d.bs.curr++;
767 redrawbuttons(&d, true, true);
768 wnoutrefresh(d.widget);
769 } else if (form.sel != -1) {
770 switchfocus = true;
771 }
772 break;
773 case KEY_CTRL('p'):
774 case KEY_UP:
775 if (focusinform) {
776 next = previtem(form.nitems, form.pritems,
777 form.sel);
778 changeitem = form.sel != next;
779 } else if (form.sel != -1) {
780 switchfocus = true;
781 }
782 break;
783 case KEY_CTRL('n'):
784 case KEY_DOWN:
785 if (focusinform == false)
786 break;
787 if (form.nitems == 1) {
788 switchfocus = true;
789 } else {
790 next = nextitem(form.nitems, form.pritems,
791 form.sel);
792 changeitem = form.sel != next;
793 }
794 break;
795 case KEY_PPAGE:
796 if (focusinform) {
797 next = firstitem(form.nitems, form.pritems);
798 changeitem = form.sel != next;
799 }
800 break;
801 case KEY_NPAGE:
802 if (focusinform) {
803 next = lastitem(form.nitems, form.pritems);
804 changeitem = form.sel != next;
805 }
806 break;
807 case KEY_BACKSPACE:
808 case 127: /* Backspace */
809 if (focusinform == false)
810 break;
811 if (fieldctl(item, MOVE_CURSOR_LEFT))
812 if (fieldctl(item, DEL_LETTER))
813 DRAWITEM_TRICK(&form, form.sel, true);
814 break;
815 case KEY_DC:
816 if (focusinform == false)
817 break;
818 if (fieldctl(item, DEL_LETTER))
819 DRAWITEM_TRICK(&form, form.sel, true);
820 break;
821 case KEY_HOME:
822 if (focusinform == false)
823 break;
824 if (fieldctl(item, MOVE_CURSOR_BEGIN))
825 DRAWITEM_TRICK(&form, form.sel, true);
826 break;
827 case KEY_END:
828 if (focusinform == false)
829 break;
830 if (fieldctl(item, MOVE_CURSOR_END))
831 DRAWITEM_TRICK(&form, form.sel, true);
832 break;
833 case KEY_F(1):
834 if (conf->key.f1_file == NULL &&
835 conf->key.f1_message == NULL)
836 break;
837 curs_set(0);
838 if (f1help_dialog(conf) != 0) {
839 retval = BSDDIALOG_ERROR;
840 loop = false;
841 }
842 if (form_redraw(&d, &form, focusinform) != 0)
843 return (BSDDIALOG_ERROR);
844 break;
845 case KEY_CTRL('l'):
846 case KEY_RESIZE:
847 if (form_redraw(&d, &form, focusinform) != 0)
848 return (BSDDIALOG_ERROR);
849 break;
850 default:
851 if (wchtype == KEY_CODE_YES)
852 break;
853 if (focusinform) {
854 if (item->fieldonebyte && wctob(input) == EOF)
855 break;
856 /*
857 * MOVE_CURSOR_RIGHT manages new positions
858 * because the cursor remains on the new letter,
859 * "if" and "while" update the positions.
860 */
861 if (insertch(item, input, form.securewch)) {
862 fieldctl(item, MOVE_CURSOR_RIGHT);
863 /*
864 * no if (fieldctl), update always
865 * because it fails with maxletters.
866 */
867 DRAWITEM_TRICK(&form, form.sel, true);
868 }
869 } else {
870 if (shortcut_buttons(input, &d.bs)) {
871 DRAW_BUTTONS(d);
872 doupdate();
873 retval = BUTTONVALUE(d.bs);
874 loop = false;
875 }
876 }
877 break;
878 } /* end switch get_wch() */
879
880 if (switchfocus) {
881 focusinform = !focusinform;
882 d.bs.curr = 0;
883 redrawbuttons(&d,
884 conf->button.always_active || !focusinform,
885 !focusinform);
886 wnoutrefresh(d.widget);
887 DRAWITEM_TRICK(&form, form.sel, focusinform);
888 switchfocus = false;
889 }
890
891 if (changeitem) {
892 DRAWITEM_TRICK(&form, form.sel, false);
893 form.sel = next;
894 item = &form.pritems[form.sel];
895 curriteminview(&form, item);
896 update_formbox(conf, &form);
897 wnoutrefresh(form.box);
898 DRAWITEM_TRICK(&form, form.sel, true);
899 changeitem = false;
900 }
901 } /* end while (loop) */
902
903 curs_set(0);
904
905 if (return_values(conf, &form, items) == BSDDIALOG_ERROR)
906 return (BSDDIALOG_ERROR);
907
908 if (focusitem != NULL)
909 *focusitem = form.sel;
910
911 if (form.hasbottomdesc && conf->clear) {
912 move(SCREENLINES - 1, 2);
913 clrtoeol();
914 }
915 for (i = 0; i < form.nitems; i++) {
916 free(form.pritems[i].privwbuf);
917 free(form.pritems[i].pubwbuf);
918 }
919 delwin(form.pad);
920 delwin(form.box);
921 end_dialog(&d);
922
923 return (retval);
924 }
925