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 <ctype.h> 31 #include <form.h> 32 #include <stdlib.h> 33 #include <string.h> 34 35 #include "bsddialog.h" 36 #include "bsddialog_theme.h" 37 #include "lib_util.h" 38 39 #define ISFIELDHIDDEN(item) (item.flags & BSDDIALOG_FIELDHIDDEN) 40 #define ISFIELDREADONLY(item) (item.flags & BSDDIALOG_FIELDREADONLY) 41 #define REDRAWFORM 19860214 /* magic number */ 42 43 /* field_userptr for private buffer and view options */ 44 struct myfield { 45 int buflen; 46 wchar_t *buf; 47 int pos; 48 int maxpos; 49 bool secure; 50 int securech; 51 const char *bottomdesc; 52 }; 53 #define GETMYFIELD(field) ((struct myfield*)field_userptr(field)) 54 #define GETMYFIELD2(form) ((struct myfield*)field_userptr(current_field(form))) 55 56 static void insertch(struct myfield *mf, int ch) 57 { 58 int i; 59 60 if (mf->buflen > mf->maxpos) 61 return; 62 63 for (i = mf->buflen; i >= mf->pos; i--) { 64 mf->buf[i+1] = mf->buf[i]; 65 } 66 67 mf->buf[mf->pos] = ch; 68 mf->pos += 1; 69 if (mf->pos > mf->maxpos) 70 mf->pos = mf->maxpos; 71 mf->buflen += 1; 72 mf->buf[mf->buflen] = '\0'; 73 } 74 75 static void shiftleft(struct myfield *mf) 76 { 77 int i, last; 78 79 for (i = mf->pos; i < mf->buflen -1; i++) { 80 mf->buf[i] = mf->buf[i+1]; 81 } 82 83 last = mf->buflen > 0 ? mf->buflen -1 : 0; 84 mf->buf[last] = '\0'; 85 mf->buflen = last; 86 } 87 88 static void print_bottomdesc(struct myfield *mf) 89 { 90 move(SCREENLINES - 1, 2); 91 clrtoeol(); 92 if (mf->bottomdesc != NULL) { 93 addstr(mf->bottomdesc); 94 refresh(); 95 } 96 } 97 98 static char *w2c(wchar_t *string) 99 { 100 int i, len; 101 char *value; 102 103 len = wcslen(string); 104 if ((value = calloc(len + 1, sizeof(char))) == NULL) 105 return NULL; 106 107 for (i = 0; i < len; i++) 108 value[i] = string[i]; 109 value[i] = '\0'; 110 111 return value; 112 } 113 114 static int 115 return_values(struct bsddialog_conf *conf, int output, int nitems, 116 struct bsddialog_formitem *items, FORM *form, FIELD **cfield) 117 { 118 int i; 119 struct myfield *mf; 120 121 if (output != BSDDIALOG_OK && conf->form.value_without_ok == false) 122 return (output); 123 124 form_driver_w(form, KEY_CODE_YES, REQ_NEXT_FIELD); 125 form_driver_w(form, KEY_CODE_YES, REQ_PREV_FIELD); 126 for (i = 0; i < nitems; i++) { 127 mf = GETMYFIELD(cfield[i]); 128 if (conf->form.enable_wchar) { 129 items[i].value = (char*)wcsdup(mf->buf); 130 } else { 131 items[i].value = w2c(mf->buf); 132 } 133 if (items[i].value == NULL) 134 RETURN_ERROR("Cannot allocate memory for form value"); 135 } 136 137 return (output); 138 } 139 140 static int 141 form_handler(struct bsddialog_conf *conf, WINDOW *widget, struct buttons bs, 142 WINDOW *formwin, FORM *form, FIELD **cfield, int nitems, 143 struct bsddialog_formitem *items) 144 { 145 bool loop, buttupdate, informwin; 146 int i, chtype, output; 147 wint_t input; 148 struct myfield *mf; 149 150 mf = GETMYFIELD2(form); 151 print_bottomdesc(mf); 152 pos_form_cursor(form); 153 form_driver_w(form, KEY_CODE_YES, REQ_END_LINE); 154 mf->pos = MIN(mf->buflen, mf->maxpos); 155 curs_set(1); 156 informwin = true; 157 158 bs.curr = -1; 159 buttupdate = true; 160 161 loop = true; 162 while (loop) { 163 if (buttupdate) { 164 draw_buttons(widget, bs, !informwin); 165 wrefresh(widget); 166 buttupdate = false; 167 } 168 wrefresh(formwin); 169 chtype = get_wch(&input); 170 if (chtype != KEY_CODE_YES && input > 127 && 171 conf->form.enable_wchar == false) 172 continue; 173 switch(input) { 174 case KEY_HOME: 175 case KEY_PPAGE: 176 case KEY_END: 177 case KEY_NPAGE: 178 /* disabled keys */ 179 break; 180 case KEY_ENTER: 181 case 10: /* Enter */ 182 if (informwin) 183 break; 184 output = return_values(conf, bs.value[bs.curr], nitems, 185 items, form, cfield); 186 loop = false; 187 break; 188 case 27: /* Esc */ 189 if (conf->key.enable_esc) { 190 output = return_values(conf, BSDDIALOG_ESC, 191 nitems, items, form, cfield); 192 loop = false; 193 } 194 break; 195 case '\t': /* TAB */ 196 if (informwin) { 197 bs.curr = 0; 198 informwin = false; 199 curs_set(0); 200 } else { 201 bs.curr++; 202 informwin = bs.curr >= (int)bs.nbuttons ? 203 true : false; 204 if (informwin) { 205 curs_set(1); 206 pos_form_cursor(form); 207 } 208 } 209 buttupdate = true; 210 break; 211 case KEY_LEFT: 212 if (informwin) { 213 form_driver_w(form, KEY_CODE_YES, REQ_PREV_CHAR); 214 mf = GETMYFIELD2(form); 215 if (mf->pos > 0) 216 mf->pos -= 1; 217 } else { 218 if (bs.curr > 0) { 219 bs.curr--; 220 buttupdate = true; 221 } 222 } 223 break; 224 case KEY_RIGHT: 225 if (informwin) { 226 mf = GETMYFIELD2(form); 227 if (mf->pos >= mf->buflen) 228 break; 229 mf->pos += 1; 230 form_driver_w(form, KEY_CODE_YES, REQ_NEXT_CHAR); 231 } else { 232 if (bs.curr < (int) bs.nbuttons - 1) { 233 bs.curr++; 234 buttupdate = true; 235 } 236 } 237 break; 238 case KEY_UP: 239 if (nitems < 2) 240 break; 241 set_field_fore(current_field(form), t.form.fieldcolor); 242 set_field_back(current_field(form), t.form.fieldcolor); 243 form_driver_w(form, KEY_CODE_YES, REQ_PREV_FIELD); 244 form_driver_w(form, KEY_CODE_YES, REQ_END_LINE); 245 mf = GETMYFIELD2(form); 246 print_bottomdesc(mf); 247 mf->pos = MIN(mf->buflen, mf->maxpos); 248 set_field_fore(current_field(form), t.form.f_fieldcolor); 249 set_field_back(current_field(form), t.form.f_fieldcolor); 250 break; 251 case KEY_DOWN: 252 if (nitems < 2) 253 break; 254 set_field_fore(current_field(form), t.form.fieldcolor); 255 set_field_back(current_field(form), t.form.fieldcolor); 256 form_driver_w(form, KEY_CODE_YES, REQ_NEXT_FIELD); 257 form_driver_w(form, KEY_CODE_YES, REQ_END_LINE); 258 mf = GETMYFIELD2(form); 259 print_bottomdesc(mf); 260 mf->pos = MIN(mf->buflen, mf->maxpos); 261 set_field_fore(current_field(form), t.form.f_fieldcolor); 262 set_field_back(current_field(form), t.form.f_fieldcolor); 263 break; 264 case KEY_BACKSPACE: 265 case 127: /* Backspace */ 266 mf = GETMYFIELD2(form); 267 if (mf->pos <= 0) 268 break; 269 form_driver_w(form, KEY_CODE_YES, REQ_DEL_PREV); 270 form_driver_w(form, KEY_CODE_YES, REQ_BEG_LINE); 271 mf->pos = mf->pos - 1; 272 for (i = 0; i < mf->pos; i++) 273 form_driver_w(form, KEY_CODE_YES, REQ_NEXT_CHAR); 274 shiftleft(mf); 275 break; 276 case KEY_DC: 277 form_driver_w(form, KEY_CODE_YES, REQ_DEL_CHAR); 278 mf = GETMYFIELD2(form); 279 if (mf->pos < mf->buflen) 280 shiftleft(mf); 281 break; 282 case KEY_F(1): 283 if (conf->key.f1_file == NULL && 284 conf->key.f1_message == NULL) 285 break; 286 if (f1help(conf) != 0) 287 return (BSDDIALOG_ERROR); 288 /* No Break */ 289 case KEY_RESIZE: 290 output = REDRAWFORM; 291 loop = false; 292 break; 293 default: 294 if (informwin) { 295 if (chtype == KEY_CODE_YES) 296 break; 297 mf = GETMYFIELD2(form); 298 if (mf->secure) 299 form_driver_w(form, chtype, mf->securech); 300 else 301 form_driver_w(form, chtype, input); 302 insertch(mf, input); 303 } 304 else { 305 if (shortcut_buttons(input, &bs)) { 306 output = return_values(conf, 307 bs.value[bs.curr], nitems, items, 308 form, cfield); 309 loop = false; 310 } 311 } 312 break; 313 } 314 } 315 316 curs_set(0); 317 318 return (output); 319 } 320 321 static int 322 form_autosize(struct bsddialog_conf *conf, int rows, int cols, int *h, int *w, 323 const char *text, int linelen, unsigned int *formheight, int nitems, 324 struct buttons bs) 325 { 326 int htext, wtext, menusize; 327 328 if (cols == BSDDIALOG_AUTOSIZE || rows == BSDDIALOG_AUTOSIZE) { 329 if (text_size(conf, rows, cols, text, &bs, *formheight + 2, 330 linelen + 2, &htext, &wtext) != 0) 331 return (BSDDIALOG_ERROR); 332 } 333 334 if (cols == BSDDIALOG_AUTOSIZE) 335 *w = widget_min_width(conf, wtext, linelen + 2, &bs); 336 337 if (rows == BSDDIALOG_AUTOSIZE) { 338 if (*formheight == 0) { 339 menusize = widget_max_height(conf) - HBORDERS - 340 2 /*buttons*/ - htext; 341 menusize = MIN(menusize, nitems + 2); 342 *formheight = menusize - 2 < 0 ? 0 : menusize - 2; 343 } 344 else /* h autosize with fixed formheight */ 345 menusize = *formheight + 2; 346 347 *h = widget_min_height(conf, htext, menusize, true); 348 } else { 349 if (*formheight == 0) 350 *formheight = MIN(rows-6-htext, nitems); 351 } 352 353 return (0); 354 } 355 356 static int 357 form_checksize(int rows, int cols, const char *text, int formheight, int nitems, 358 unsigned int linelen, struct buttons bs) 359 { 360 int mincols, textrow, formrows; 361 362 mincols = VBORDERS; 363 /* buttons */ 364 mincols += buttons_width(bs); 365 mincols = MAX(mincols, (int)linelen + 4); 366 367 if (cols < mincols) 368 RETURN_ERROR("Few cols, width < size buttons or " 369 "forms (label + field)"); 370 371 textrow = text != NULL && strlen(text) > 0 ? 1 : 0; 372 373 if (nitems > 0 && formheight == 0) 374 RETURN_ERROR("fields > 0 but formheight == 0, probably " 375 "terminal too small"); 376 377 formrows = nitems > 0 ? 3 : 0; 378 if (rows < 2 + 2 + formrows + textrow) 379 RETURN_ERROR("Few lines for this menus"); 380 381 return (0); 382 } 383 384 int 385 bsddialog_form(struct bsddialog_conf *conf, const char *text, int rows, 386 int cols, unsigned int formheight, unsigned int nitems, 387 struct bsddialog_formitem *items) 388 { 389 int i, output, color, y, x, h, w; 390 unsigned long j, maxline, mybufsize; 391 struct buttons bs; 392 struct myfield *myfields; 393 FIELD **cfield; 394 FORM *form; 395 WINDOW *widget, *formwin, *textpad, *shadow; 396 397 /* disable form scrolling */ 398 if (formheight < nitems) 399 formheight = nitems; 400 401 for (i = 0; i < (int)nitems; i++) { 402 if (items[i].maxvaluelen == 0) 403 RETURN_ERROR("maxvaluelen cannot be zero"); 404 if (items[i].fieldlen == 0) 405 RETURN_ERROR("fieldlen cannot be zero"); 406 if (items[i].fieldlen > items[i].maxvaluelen) 407 RETURN_ERROR("fieldlen cannot be > maxvaluelen"); 408 } 409 410 maxline = 0; 411 myfields = malloc(nitems * sizeof(struct myfield)); 412 cfield = calloc(nitems + 1, sizeof(FIELD*)); 413 for (i = 0; i < (int)nitems; i++) { 414 cfield[i] = new_field(1, items[i].fieldlen, items[i].yfield-1, 415 items[i].xfield-1, 0, 0); 416 field_opts_off(cfield[i], O_STATIC); 417 set_max_field(cfield[i], items[i].maxvaluelen); 418 /* setlocale() should handle set_field_buffer() */ 419 set_field_buffer(cfield[i], 0, items[i].init); 420 421 mybufsize = (items[i].maxvaluelen + 1) * sizeof(wchar_t); 422 myfields[i].buf = malloc(mybufsize); 423 memset(myfields[i].buf, 0, mybufsize); 424 for (j = 0; j < items[i].maxvaluelen && j < strlen(items[i].init); 425 j++) 426 myfields[i].buf[j] = items[i].init[j]; 427 428 myfields[i].buflen = wcslen(myfields[i].buf); 429 430 myfields[i].maxpos = items[i].maxvaluelen -1; 431 myfields[i].pos = MIN(myfields[i].buflen, myfields[i].maxpos); 432 433 myfields[i].bottomdesc = items[i].bottomdesc; 434 set_field_userptr(cfield[i], &myfields[i]); 435 436 field_opts_off(cfield[i], O_AUTOSKIP); 437 field_opts_off(cfield[i], O_BLANK); 438 439 if (ISFIELDHIDDEN(items[i])) { 440 myfields[i].secure = true; 441 myfields[i].securech = ' '; 442 if (conf->form.securech != '\0') 443 myfields[i].securech = conf->form.securech; 444 } 445 else 446 myfields[i].secure = false; 447 448 if (ISFIELDREADONLY(items[i])) { 449 field_opts_off(cfield[i], O_EDIT); 450 field_opts_off(cfield[i], O_ACTIVE); 451 color = t.form.readonlycolor; 452 } else { 453 color = i == 0 ? t.form.f_fieldcolor : t.form.fieldcolor; 454 } 455 set_field_fore(cfield[i], color); 456 set_field_back(cfield[i], color); 457 458 maxline = MAX(maxline, items[i].xlabel + strlen(items[i].label)); 459 maxline = MAX(maxline, items[i].xfield + items[i].fieldlen - 1); 460 } 461 cfield[i] = NULL; 462 463 /* disable focus with 1 item (inputbox or passwordbox) */ 464 if (formheight == 1 && nitems == 1 && strlen(items[0].label) == 0 && 465 items[0].xfield == 1 ) { 466 set_field_fore(cfield[0], t.dialog.color); 467 set_field_back(cfield[0], t.dialog.color); 468 } 469 470 get_buttons(conf, &bs, BUTTON_OK_LABEL, BUTTON_CANCEL_LABEL); 471 472 if (set_widget_size(conf, rows, cols, &h, &w) != 0) 473 return (BSDDIALOG_ERROR); 474 if (form_autosize(conf, rows, cols, &h, &w, text, maxline, &formheight, 475 nitems, bs) != 0) 476 return (BSDDIALOG_ERROR); 477 if (form_checksize(h, w, text, formheight, nitems, maxline, bs) != 0) 478 return (BSDDIALOG_ERROR); 479 if (set_widget_position(conf, &y, &x, h, w) != 0) 480 return (BSDDIALOG_ERROR); 481 482 if (new_dialog(conf, &shadow, &widget, y, x, h, w, &textpad, text, &bs, 483 true) != 0) 484 return (BSDDIALOG_ERROR); 485 486 prefresh(textpad, 0, 0, y + 1, x + 1 + TEXTHMARGIN, 487 y + h - formheight, x + 1 + w - TEXTHMARGIN); 488 489 formwin = new_boxed_window(conf, y + h - 3 - formheight -2, x +1, 490 formheight+2, w-2, LOWERED); 491 492 form = new_form(cfield); 493 set_form_win(form, formwin); 494 /* should be formheight */ 495 set_form_sub(form, derwin(formwin, nitems, w-4, 1, 1)); 496 post_form(form); 497 498 for (i = 0; i < (int)nitems; i++) 499 mvwaddstr(formwin, items[i].ylabel, items[i].xlabel, 500 items[i].label); 501 502 wrefresh(formwin); 503 504 do { 505 output = form_handler(conf, widget, bs, formwin, form, cfield, 506 nitems, items); 507 508 if (update_dialog(conf, shadow, widget, y, x, h, w, textpad, 509 text, &bs, true) != 0) 510 return (BSDDIALOG_ERROR); 511 512 doupdate(); 513 wrefresh(widget); 514 515 prefresh(textpad, 0, 0, y + 1, x + 1 + TEXTHMARGIN, 516 y + h - formheight, x + 1 + w - TEXTHMARGIN); 517 518 draw_borders(conf, formwin, formheight+2, w-2, LOWERED); 519 wrefresh(formwin); 520 521 refresh(); 522 } while (output == REDRAWFORM); 523 524 unpost_form(form); 525 free_form(form); 526 for (i = 0; i < (int)nitems; i++) { 527 free_field(cfield[i]); 528 free(myfields[i].buf); 529 } 530 free(cfield); 531 free(myfields); 532 533 delwin(formwin); 534 end_dialog(conf, shadow, widget, textpad); 535 536 return (output); 537 } 538