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