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