1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2021-2025 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 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 214 static bool fieldctl(struct privateitem *item, enum field_action act) 215 { 216 bool change; 217 int width, oldwidth, nextwidth, cols; 218 unsigned int i; 219 220 change = false; 221 switch (act){ 222 case MOVE_CURSOR_BEGIN: 223 if (item->pos == 0 && item->xcursor == 0) 224 break; 225 /* here the cursor is changed */ 226 change = true; 227 item->pos = 0; 228 item->xcursor = 0; 229 item->xposdraw = 0; 230 break; 231 case MOVE_CURSOR_END: 232 while (fieldctl(item, MOVE_CURSOR_RIGHT)) 233 change = true; 234 break; 235 case MOVE_CURSOR_LEFT: 236 if (item->pos == 0) 237 break; 238 /* check redundant by item->pos == 0 because of 'while' below */ 239 if (item->xcursor == 0 && item->xposdraw == 0) 240 break; 241 /* here some letter to left */ 242 change = true; 243 item->pos -= 1; 244 width = wcwidth(item->pubwbuf[item->pos]); 245 if (((int)item->xcursor) - width < 0) { 246 item->xcursor = 0; 247 item->xposdraw -= 1; 248 } else 249 item->xcursor -= width; 250 251 while (true) { 252 if (item->xposdraw == 0) 253 break; 254 if (item->xcursor >= item->fieldcols / 2) 255 break; 256 if (wcwidth(item->pubwbuf[item->xposdraw - 1]) + 257 item->xcursor + width > item->fieldcols) 258 break; 259 260 item->xposdraw -= 1; 261 item->xcursor += 262 wcwidth(item->pubwbuf[item->xposdraw]); 263 } 264 break; 265 case DEL_LETTER: 266 if (item->nletters == 0) 267 break; 268 if (item->pos == item->nletters) 269 break; 270 /* here a letter under the cursor */ 271 change = true; 272 for (i = item->pos; i < item->nletters; i++) { 273 item->privwbuf[i] = item->privwbuf[i+1]; 274 item->pubwbuf[i] = item->pubwbuf[i+1]; 275 } 276 item->nletters -= 1; 277 item->privwbuf[i] = L'\0'; 278 item->pubwbuf[i] = L'\0'; 279 break; 280 case MOVE_CURSOR_RIGHT: /* used also by "insert", see handler loop */ 281 if (item->pos + 1 == item->maxletters) 282 break; 283 if (item->pos == item->nletters) 284 break; 285 /* here a change to right */ 286 change = true; 287 oldwidth = wcwidth(item->pubwbuf[item->pos]); 288 item->pos += 1; 289 if (item->pos == item->nletters) { /* empty column */ 290 nextwidth = 1; 291 } else { /* a letter to right */ 292 nextwidth = wcwidth(item->pubwbuf[item->pos]); 293 } 294 if (item->xcursor + oldwidth + nextwidth - 1 >= item->fieldcols) { 295 cols = nextwidth; 296 item->xposdraw = item->pos; 297 while (item->xposdraw != 0) { 298 cols += wcwidth(item->pubwbuf[item->xposdraw - 1]); 299 if (cols > (int)item->fieldcols) 300 break; 301 item->xposdraw -= 1; 302 } 303 item->xcursor = 0; 304 for (i = item->xposdraw; i < item->pos ; i++) 305 item->xcursor += wcwidth(item->pubwbuf[i]); 306 } 307 else { 308 item->xcursor += oldwidth; 309 } 310 311 break; 312 } 313 314 return (change); 315 } 316 317 static bool insertch(struct privateitem *item, wchar_t wch, wchar_t securewch) 318 { 319 int i; 320 321 if (item->nletters >= item->maxletters) 322 return (false); 323 324 for (i = (int)item->nletters - 1; i >= (int)item->pos; i--) { 325 item->privwbuf[i+1] = item->privwbuf[i]; 326 item->pubwbuf[i+1] = item->pubwbuf[i]; 327 } 328 329 item->privwbuf[item->pos] = wch; 330 item->pubwbuf[item->pos] = item->secure ? securewch : wch; 331 item->nletters += 1; 332 item->privwbuf[item->nletters] = L'\0'; 333 item->pubwbuf[item->nletters] = L'\0'; 334 335 return (true); 336 } 337 338 static char* alloc_wstomb(wchar_t *wstr) 339 { 340 int len, nbytes, i; 341 char mbch[MB_LEN_MAX], *mbstr; 342 343 nbytes = MB_LEN_MAX; /* to ensure a null terminated string */ 344 len = wcslen(wstr); 345 for (i = 0; i < len; i++) { 346 wctomb(mbch, wstr[i]); 347 nbytes += mblen(mbch, MB_LEN_MAX); 348 } 349 if ((mbstr = malloc(nbytes)) == NULL) 350 return (NULL); 351 352 wcstombs(mbstr, wstr, nbytes); 353 354 return (mbstr); 355 } 356 357 static int 358 return_values(struct bsddialog_conf *conf, struct privateform *f, 359 struct bsddialog_formitem *items) 360 { 361 unsigned int i; 362 363 for (i = 0; i < f->nitems; i++) { 364 if (conf->form.value_wchar) 365 items[i].value = (char*)wcsdup(f->pritems[i].privwbuf); 366 else 367 items[i].value = alloc_wstomb(f->pritems[i].privwbuf); 368 369 if (items[i].value == NULL) 370 RETURN_FMTERROR( 371 "Cannot allocate memory for item[%d].value", i); 372 } 373 374 return (0); 375 } 376 377 static void set_first_with_default(struct privateform *f, int *focusitem) 378 { 379 unsigned int i; 380 381 f->sel = -1; 382 if (focusitem != NULL && *focusitem >=0 && *focusitem < (int)f->nitems) 383 if (f->pritems[*focusitem].readonly == false) { 384 f->sel = *focusitem; 385 return; 386 } 387 for (i = 0 ; i < f->nitems; i++) 388 if (f->pritems[i].readonly == false) { 389 f->sel = i; 390 break; 391 } 392 } 393 394 static unsigned int firstitem(unsigned int nitems, struct privateitem *items) 395 { 396 int i; 397 398 for (i = 0; i < (int)nitems; i++) 399 if (items[i].readonly == false) 400 break; 401 402 return (i); 403 } 404 405 static unsigned int lastitem(unsigned int nitems, struct privateitem *items) 406 { 407 int i; 408 409 for (i = nitems - 1; i >= 0 ; i--) 410 if (items[i].readonly == false) 411 break; 412 413 return (i); 414 } 415 416 static unsigned int 417 previtem(unsigned int nitems, struct privateitem *items, int curritem) 418 { 419 int i; 420 421 for (i = curritem - 1; i >= 0; i--) 422 if (items[i].readonly == false) 423 return(i); 424 425 for (i = nitems - 1; i > curritem - 1; i--) 426 if (items[i].readonly == false) 427 return(i); 428 429 return (curritem); 430 } 431 432 static unsigned int 433 nextitem(unsigned int nitems, struct privateitem *items, int curritem) 434 { 435 int i; 436 437 for (i = curritem + 1; i < (int)nitems; i++) 438 if (items[i].readonly == false) 439 return(i); 440 441 for (i = 0; i < curritem; i++) 442 if (items[i].readonly == false) 443 return(i); 444 445 return (curritem); 446 } 447 448 static void redrawbuttons(struct dialog *d, bool focus, bool shortcut) 449 { 450 int selected; 451 452 selected = d->bs.curr; 453 if (focus == false) 454 d->bs.curr = -1; 455 d->bs.shortcut = shortcut; 456 draw_buttons(d); 457 d->bs.curr = selected; 458 } 459 460 static void 461 drawitem(struct privateform *f, int idx, bool focus) 462 { 463 int color; 464 unsigned int n, cols; 465 struct privateitem *item; 466 467 item = &f->pritems[idx]; 468 469 /* Label */ 470 wattron(f->pad, t.dialog.color); 471 mvwaddstr(f->pad, item->ylabel, item->xlabel, item->label); 472 wattroff(f->pad, t.dialog.color); 473 474 /* Field */ 475 if (item->readonly) 476 color = t.form.readonlycolor; 477 else if (item->fieldnocolor) 478 color = t.dialog.color; 479 else 480 color = focus ? t.form.f_fieldcolor : t.form.fieldcolor; 481 wattron(f->pad, color); 482 mvwhline(f->pad, item->yfield, item->xfield, ' ', item->fieldcols); 483 n = 0; 484 cols = wcwidth(item->pubwbuf[item->xposdraw]); 485 while (cols <= item->fieldcols && 486 item->xposdraw + n < wcslen(item->pubwbuf)) { 487 n++; 488 cols += wcwidth(item->pubwbuf[item->xposdraw + n]); 489 490 } 491 mvwaddnwstr(f->pad, item->yfield, item->xfield, 492 &item->pubwbuf[item->xposdraw], n); 493 wattroff(f->pad, color); 494 495 /* Bottom Desc */ 496 if (f->hasbottomdesc) { 497 move(SCREENLINES - 1, 2); 498 clrtoeol(); 499 if (item->bottomdesc != NULL && focus) { 500 attron(t.form.bottomdesccolor); 501 addstr(item->bottomdesc); 502 attroff(t.form.bottomdesccolor); 503 refresh(); 504 } 505 } 506 507 /* Cursor */ 508 curs_set((focus && item->cursor) ? 1 : 0); 509 wmove(f->pad, item->yfield, item->xfield + item->xcursor); 510 } 511 512 /* 513 * Trick: draw 2 times an item switching focus. 514 * Problem: curses tries to optimize the rendering but sometimes it misses some 515 * updates or draws old stuff. libformw has a similar problem fixed by the 516 * same trick. 517 * Case 1: KEY_DC and KEY_BACKSPACE, deleted multicolumn letters are drawn 518 * again. It seems fixed by new items pad and prefresh(), previously WINDOW. 519 * Case2: some terminal, tmux and ssh does not show the cursor. 520 */ 521 #define DRAWITEM_TRICK(f, idx, focus) do { \ 522 drawitem(f, idx, !focus); \ 523 prefresh((f)->pad, (f)->y, 0, (f)->ys, (f)->xs, (f)->ye, (f)->xe); \ 524 drawitem(f, idx, focus); \ 525 prefresh((f)->pad, (f)->y, 0, (f)->ys, (f)->xs, (f)->ye, (f)->xe); \ 526 } while (0) 527 528 static void update_formbox(struct bsddialog_conf *conf, struct privateform *f) 529 { 530 int h, w; 531 532 getmaxyx(f->box, h, w); 533 draw_borders(conf, f->box, LOWERED); 534 535 if (f->viewrows < f->h) { 536 wattron(f->box, t.dialog.arrowcolor); 537 if (f->y > 0) 538 mvwhline(f->box, 0, (w / 2) - 2, 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 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 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 604 form_draw(struct dialog *d, bool redraw, struct privateform *f, bool focusinform) 605 { 606 unsigned int i; 607 608 if (redraw) { 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) /* doupdate() in main loop */ 617 return (BSDDIALOG_ERROR); 618 if (redraw) 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 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_draw(&d, false, &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 next = nextitem(form.nitems, form.pritems, form.sel); 724 if (next > form.sel) 725 changeitem = true; /* needs next */ 726 else 727 switchfocus = true; 728 } else { 729 retval = BUTTONVALUE(d.bs); 730 loop = false; 731 } 732 break; 733 case 27: /* Esc */ 734 if (conf->key.enable_esc) { 735 retval = BSDDIALOG_ESC; 736 loop = false; 737 } 738 break; 739 case '\t': /* TAB */ 740 if (focusinform) { 741 next = nextitem(form.nitems, form.pritems, 742 form.sel); 743 if (next > form.sel) 744 changeitem = true; /* needs next */ 745 else 746 switchfocus = true; 747 } else { 748 if (d.bs.curr + 1 < (int)d.bs.nbuttons) { 749 d.bs.curr++; 750 } else { 751 d.bs.curr = 0; 752 if (form.sel != -1) { 753 switchfocus = true; 754 } 755 } 756 redrawbuttons(&d, true, true); 757 wnoutrefresh(d.widget); 758 } 759 break; 760 case KEY_LEFT: 761 if (focusinform) { 762 if (fieldctl(item, MOVE_CURSOR_LEFT)) 763 DRAWITEM_TRICK(&form, form.sel, true); 764 } else if (d.bs.curr > 0) { 765 d.bs.curr--; 766 redrawbuttons(&d, true, true); 767 wnoutrefresh(d.widget); 768 } else if (form.sel != -1) { 769 switchfocus = true; 770 } 771 break; 772 case KEY_RIGHT: 773 if (focusinform) { 774 if (fieldctl(item, MOVE_CURSOR_RIGHT)) 775 DRAWITEM_TRICK(&form, form.sel, true); 776 } else if (d.bs.curr < (int) d.bs.nbuttons - 1) { 777 d.bs.curr++; 778 redrawbuttons(&d, true, true); 779 wnoutrefresh(d.widget); 780 } else if (form.sel != -1) { 781 switchfocus = true; 782 } 783 break; 784 case KEY_CTRL('p'): 785 case KEY_UP: 786 if (focusinform) { 787 next = previtem(form.nitems, form.pritems, 788 form.sel); 789 changeitem = form.sel != next; 790 } else if (form.sel != -1) { 791 switchfocus = true; 792 } 793 break; 794 case KEY_CTRL('n'): 795 case KEY_DOWN: 796 if (focusinform == false) 797 break; 798 if (form.nitems == 1) { 799 switchfocus = true; 800 } else { 801 next = nextitem(form.nitems, form.pritems, 802 form.sel); 803 changeitem = form.sel != next; 804 } 805 break; 806 case KEY_PPAGE: 807 if (focusinform) { 808 next = firstitem(form.nitems, form.pritems); 809 changeitem = form.sel != next; 810 } 811 break; 812 case KEY_NPAGE: 813 if (focusinform) { 814 next = lastitem(form.nitems, form.pritems); 815 changeitem = form.sel != next; 816 } 817 break; 818 case KEY_BACKSPACE: 819 case 127: /* Backspace */ 820 if (focusinform == false) 821 break; 822 if (fieldctl(item, MOVE_CURSOR_LEFT)) 823 if (fieldctl(item, DEL_LETTER)) 824 DRAWITEM_TRICK(&form, form.sel, true); 825 break; 826 case KEY_DC: 827 if (focusinform == false) 828 break; 829 if (fieldctl(item, DEL_LETTER)) 830 DRAWITEM_TRICK(&form, form.sel, true); 831 break; 832 case KEY_HOME: 833 if (focusinform == false) 834 break; 835 if (fieldctl(item, MOVE_CURSOR_BEGIN)) 836 DRAWITEM_TRICK(&form, form.sel, true); 837 break; 838 case KEY_END: 839 if (focusinform == false) 840 break; 841 if (fieldctl(item, MOVE_CURSOR_END)) 842 DRAWITEM_TRICK(&form, form.sel, true); 843 break; 844 case KEY_F(1): 845 if (conf->key.f1_file == NULL && 846 conf->key.f1_message == NULL) 847 break; 848 curs_set(0); 849 if (f1help_dialog(conf) != 0) { 850 retval = BSDDIALOG_ERROR; 851 loop = false; 852 } 853 if (form_draw(&d, true, &form, focusinform) != 0) 854 return (BSDDIALOG_ERROR); 855 break; 856 case KEY_CTRL('l'): 857 case KEY_RESIZE: 858 if (form_draw(&d, true, &form, focusinform) != 0) 859 return (BSDDIALOG_ERROR); 860 break; 861 default: 862 if (wchtype == KEY_CODE_YES) 863 break; 864 if (focusinform) { 865 if (item->fieldonebyte && wctob(input) == EOF) 866 break; 867 /* 868 * MOVE_CURSOR_RIGHT manages new positions 869 * because the cursor remains on the new letter, 870 * "if" and "while" update the positions. 871 */ 872 if (insertch(item, input, form.securewch)) { 873 fieldctl(item, MOVE_CURSOR_RIGHT); 874 /* 875 * no if (fieldctl), update always 876 * because it fails with maxletters. 877 */ 878 DRAWITEM_TRICK(&form, form.sel, true); 879 } 880 } else { 881 if (shortcut_buttons(input, &d.bs)) { 882 DRAW_BUTTONS(d); 883 doupdate(); 884 retval = BUTTONVALUE(d.bs); 885 loop = false; 886 } 887 } 888 break; 889 } /* end switch get_wch() */ 890 891 if (switchfocus) { 892 focusinform = !focusinform; 893 d.bs.curr = 0; 894 redrawbuttons(&d, 895 conf->button.always_active || !focusinform, 896 !focusinform); 897 wnoutrefresh(d.widget); 898 if (focusinform == false) 899 DRAWITEM_TRICK(&form, form.sel, false); 900 else { 901 next = firstitem(form.nitems, form.pritems); 902 if (next == form.sel) 903 DRAWITEM_TRICK(&form, form.sel, true); 904 else 905 changeitem = true; 906 } 907 switchfocus = false; 908 } 909 910 if (changeitem) { 911 /* useless after if(switchfocus) */ 912 DRAWITEM_TRICK(&form, form.sel, false); 913 form.sel = next; 914 item = &form.pritems[form.sel]; 915 curriteminview(&form, item); 916 update_formbox(conf, &form); 917 wnoutrefresh(form.box); 918 DRAWITEM_TRICK(&form, form.sel, true); 919 changeitem = false; 920 } 921 } /* end while (loop) */ 922 923 curs_set(0); 924 925 if (return_values(conf, &form, items) == BSDDIALOG_ERROR) 926 return (BSDDIALOG_ERROR); 927 928 if (focusitem != NULL) 929 *focusitem = form.sel; 930 931 if (form.hasbottomdesc && conf->clear) { 932 move(SCREENLINES - 1, 2); 933 clrtoeol(); 934 } 935 for (i = 0; i < form.nitems; i++) { 936 free(form.pritems[i].privwbuf); 937 free(form.pritems[i].pubwbuf); 938 } 939 delwin(form.pad); 940 delwin(form.box); 941 end_dialog(&d); 942 943 return (retval); 944 } 945