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