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