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