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