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/ncurses.h> 33 #else 34 #include <ncurses.h> 35 #endif 36 #include <string.h> 37 38 39 #include "bsddialog.h" 40 #include "lib_util.h" 41 #include "bsddialog_theme.h" 42 43 /* "Menu": checklist - menu - mixedlist - radiolist - buildlist */ 44 45 #define DEPTHSPACE 4 46 #define MIN_HEIGHT VBORDERS + 6 /* 2 buttons 1 text 3 menu */ 47 48 extern struct bsddialog_theme t; 49 50 enum menumode { 51 BUILDLISTMODE, 52 CHECKLISTMODE, 53 MENUMODE, 54 MIXEDLISTMODE, 55 RADIOLISTMODE, 56 SEPARATORMODE 57 }; 58 59 struct lineposition { 60 unsigned int maxsepstr; 61 unsigned int maxprefix; 62 unsigned int xselector; 63 unsigned int selectorlen; 64 unsigned int maxdepth; 65 unsigned int xname; 66 unsigned int maxname; 67 unsigned int xdesc; 68 unsigned int maxdesc; 69 unsigned int line; 70 }; 71 72 static int checkradiolist(int nitems, struct bsddialog_menuitem *items) 73 { 74 int i, error; 75 76 error = 0; 77 for (i=0; i<nitems; i++) { 78 if (error > 0) 79 items[i].on = false; 80 81 if (items[i].on == true) 82 error++; 83 } 84 85 return (error == 0 ? 0 : -1); 86 } 87 88 static int checkmenu(int nitems, struct bsddialog_menuitem *items) // useful? 89 { 90 int i, error; 91 92 error = 0; 93 for (i=0; i<nitems; i++) { 94 if (items[i].on == true) 95 error++; 96 97 items[i].on = false; 98 } 99 100 return (error == 0 ? 0 : -1); 101 } 102 103 static void 104 getfirst(int ngroups, struct bsddialog_menugroup *groups, int *abs, int *group, 105 int *rel) 106 { 107 int i, a; 108 109 *abs = *rel = *group = -1; 110 a = 0; 111 for (i=0; i<ngroups; i++) { 112 if (groups[i].type == BSDDIALOG_SEPARATOR) { 113 a += groups[i].nitems; 114 continue; 115 } 116 if (groups[i].nitems != 0) { 117 *group = i; 118 *abs = a; 119 *rel = 0; 120 break; 121 } 122 } 123 } 124 125 static void 126 getfirst_with_default(struct bsddialog_conf *conf, int ngroups, 127 struct bsddialog_menugroup *groups, int *abs, int *group, int *rel) 128 { 129 int i, j, a; 130 struct bsddialog_menuitem *item; 131 132 getfirst(ngroups, groups, abs, group, rel); 133 if (*abs < 0) 134 return; 135 136 a = *abs; 137 138 for (i=*group; i<ngroups; i++) { 139 if (groups[i].type == BSDDIALOG_SEPARATOR) { 140 a += groups[i].nitems; 141 continue; 142 } 143 for (j = 0; j < (int) groups[i].nitems; j++) { 144 item = &groups[i].items[j]; 145 if (conf->menu.default_item != NULL && item->name != NULL) { 146 if (strcmp(item->name, conf->menu.default_item) == 0) { 147 *abs = a; 148 *group = i; 149 *rel = j; 150 return; 151 } 152 } 153 a++; 154 } 155 } 156 } 157 158 static void 159 getlast(int totnitems, int ngroups, struct bsddialog_menugroup *groups, 160 int *abs, int *group, int *rel) 161 { 162 int i, a; 163 164 a = totnitems - 1; 165 for (i = ngroups-1; i>=0; i--) { 166 if (groups[i].type == BSDDIALOG_SEPARATOR) { 167 a -= groups[i].nitems; 168 continue; 169 } 170 if (groups[i].nitems != 0) { 171 *group = i; 172 *abs = a; 173 *rel = groups[i].nitems - 1; 174 break; 175 } 176 } 177 } 178 179 static void 180 getnext(int ngroups, struct bsddialog_menugroup *groups, int *abs, int *group, 181 int *rel) 182 { 183 int i, a; 184 185 if (*abs < 0 || *group < 0 || *rel < 0) 186 return; 187 188 if (*rel + 1 < (int) groups[*group].nitems) { 189 *rel = *rel + 1; 190 *abs = *abs + 1; 191 return; 192 } 193 194 if (*group + 1 > ngroups) 195 return; 196 197 a = *abs; 198 for (i = *group + 1; i < ngroups; i++) { 199 if (groups[i].type == BSDDIALOG_SEPARATOR) { 200 a += groups[i].nitems; 201 continue; 202 } 203 if (groups[i].nitems != 0) { 204 *group = i; 205 *abs = a + 1; 206 *rel = 0; 207 break; 208 } 209 } 210 } 211 212 static void 213 getfastnext(int menurows, int ngroups, struct bsddialog_menugroup *groups, 214 int *abs, int *group, int *rel) 215 { 216 int a, start, i; 217 218 start = *abs; 219 i = menurows; 220 do { 221 a = *abs; 222 getnext(ngroups, groups, abs, group, rel); 223 i--; 224 } while (*abs != a && *abs < start + menurows && i > 0); 225 } 226 227 static void 228 getprev(struct bsddialog_menugroup *groups, int *abs, int *group, int *rel) 229 { 230 int i, a; 231 232 if (*abs < 0 || *group < 0 || *rel < 0) 233 return; 234 235 if (*rel > 0) { 236 *rel = *rel - 1; 237 *abs = *abs - 1; 238 return; 239 } 240 241 if (*group - 1 < 0) 242 return; 243 244 a = *abs; 245 for (i = *group - 1; i >= 0; i--) { 246 if (groups[i].type == BSDDIALOG_SEPARATOR) { 247 a -= (int) groups[i].nitems; 248 continue; 249 } 250 if (groups[i].nitems != 0) { 251 *group = i; 252 *abs = a - 1; 253 *rel = (int) groups[i].nitems - 1; 254 break; 255 } 256 } 257 } 258 259 static void 260 getfastprev(int menurows, struct bsddialog_menugroup *groups, int *abs, 261 int *group, int *rel) 262 { 263 int a, start, i; 264 265 start = *abs; 266 i = menurows; 267 do { 268 a = *abs; 269 getprev(groups, abs, group, rel); 270 i--; 271 } while (*abs != a && *abs > start - menurows && i > 0); 272 } 273 274 static bool 275 getnextshortcut(struct bsddialog_conf *conf, enum menumode mode, int ngroups, 276 struct bsddialog_menugroup *groups, int *abs, int *group, int *rel, 277 int key) 278 { 279 int i, j, a, ch, ng, nr, na; 280 bool mainloop; 281 282 if (*abs < 0 || ngroups < 0 || *rel < 0 || mode == BUILDLISTMODE) 283 return false; 284 285 na = a = -1; 286 mainloop = true; 287 for (i = 0; i < ngroups && mainloop; i++) { 288 if (groups[i].type == BSDDIALOG_SEPARATOR) { 289 a += groups[i].nitems; 290 continue; 291 } 292 for (j = 0; j < (int)groups[i].nitems; j++) { 293 a++; 294 if (a == *abs) 295 continue; 296 297 if (conf->menu.no_name) 298 ch = groups[i].items[j].desc[0]; 299 else 300 ch = groups[i].items[j].name[0]; 301 302 if (ch == key) { 303 if (a < *abs && na == -1) { 304 na = a; 305 ng = i; 306 nr = j; 307 } 308 if (a > *abs) { 309 na = a; 310 ng = i; 311 nr = j; 312 mainloop = false; 313 break; 314 } 315 } 316 } 317 } 318 319 if (na != -1) { 320 *abs = na; 321 *group = ng; 322 *rel = nr; 323 return (true); 324 } 325 326 return (false); 327 } 328 329 static enum menumode 330 getmode(enum menumode mode, struct bsddialog_menugroup group) 331 { 332 333 if (mode == MIXEDLISTMODE) { 334 if (group.type == BSDDIALOG_SEPARATOR) 335 mode = SEPARATORMODE; 336 else if (group.type == BSDDIALOG_RADIOLIST) 337 mode = RADIOLISTMODE; 338 else if (group.type == BSDDIALOG_CHECKLIST) 339 mode = CHECKLISTMODE; 340 } 341 342 return mode; 343 } 344 345 static void 346 drawitem(struct bsddialog_conf *conf, WINDOW *pad, int y, 347 struct bsddialog_menuitem item, enum menumode mode, struct lineposition pos, 348 bool curr) 349 { 350 int colordesc, colorname, colorshortcut, linech; 351 char *shortcut; 352 353 if (mode == SEPARATORMODE) { 354 if (conf->no_lines == false) { 355 wattron(pad, t.menu.desccolor); 356 linech = conf->ascii_lines ? '-' : ACS_HLINE; 357 mvwhline(pad, y, 0, linech, pos.line); 358 wattroff(pad, t.menu.desccolor); 359 } 360 wmove(pad, y, pos.line/2 - (strlen(item.name)+strlen(item.desc))/2); 361 wattron(pad, t.menu.namesepcolor); 362 waddstr(pad, item.name); 363 wattroff(pad, t.menu.namesepcolor); 364 if (strlen(item.name) > 0 && strlen(item.desc) > 0) 365 waddch(pad, ' '); 366 wattron(pad, t.menu.descsepcolor); 367 waddstr(pad, item.desc); 368 wattroff(pad, t.menu.descsepcolor); 369 return; 370 } 371 372 /* prefix */ 373 if (item.prefix != NULL && item.prefix[0] != '\0') 374 mvwaddstr(pad, y, 0, item.prefix); 375 376 /* selector */ 377 wmove(pad, y, pos.xselector); 378 wattron(pad, t.menu.selectorcolor); 379 if (mode == CHECKLISTMODE) 380 wprintw(pad, "[%c]", item.on ? 'X' : ' '); 381 if (mode == RADIOLISTMODE) 382 wprintw(pad, "(%c)", item.on ? '*' : ' '); 383 wattroff(pad, t.menu.selectorcolor); 384 385 /* name */ 386 colorname = curr ? t.menu.f_namecolor : t.menu.namecolor; 387 if (mode != BUILDLISTMODE && conf->menu.no_name == false) { 388 wattron(pad, colorname); 389 mvwaddstr(pad, y, pos.xname + item.depth * DEPTHSPACE, item.name); 390 wattroff(pad, colorname); 391 } 392 393 /* description */ 394 if (mode == BUILDLISTMODE) { 395 if (curr == false) 396 colordesc = item.on ? t.menu.namecolor : t.menu.desccolor; 397 else 398 colordesc = t.menu.f_namecolor; 399 } 400 else { 401 if (conf->menu.no_name) 402 colordesc = curr ? t.menu.f_namecolor : t.menu.namecolor; 403 else 404 colordesc = curr ? t.menu.f_desccolor : t.menu.desccolor; 405 } 406 if (mode == BUILDLISTMODE || conf->menu.no_desc == false) { 407 wattron(pad, colordesc); 408 if (conf->menu.no_name) 409 mvwaddstr(pad, y, pos.xname + item.depth * DEPTHSPACE, item.desc); 410 else 411 mvwaddstr(pad, y, pos.xdesc, item.desc); 412 wattroff(pad, colordesc); 413 } 414 415 /* shortcut */ 416 if (mode != BUILDLISTMODE && conf->menu.shortcut_buttons == false) { 417 colorshortcut = curr ? t.menu.f_shortcutcolor : t.menu.shortcutcolor; 418 wattron(pad, colorshortcut); 419 420 if (conf->menu.no_name) 421 shortcut = item.desc; 422 else 423 shortcut = item.name; 424 wmove(pad, y, pos.xname + item.depth * DEPTHSPACE); 425 if (shortcut != NULL && shortcut[0] != '\0') 426 waddch(pad, shortcut[0]); 427 wattroff(pad, colorshortcut); 428 } 429 430 /* bottom description */ 431 move(LINES-1, 2); 432 clrtoeol(); 433 if (item.bottomdesc != NULL) { 434 addstr(item.bottomdesc); 435 refresh(); 436 } 437 } 438 439 static void 440 menu_autosize(struct bsddialog_conf *conf, int rows, int cols, int *h, int *w, 441 char *text, int linelen, unsigned int *menurows, int nitems, 442 struct buttons bs) 443 { 444 int textrow, menusize; 445 446 textrow = text != NULL && strlen(text) > 0 ? 1 : 0; 447 448 if (cols == BSDDIALOG_AUTOSIZE) { 449 *w = VBORDERS; 450 /* buttons size */ 451 *w += bs.nbuttons * bs.sizebutton; 452 *w += bs.nbuttons > 0 ? (bs.nbuttons-1) * t.button.space : 0; 453 /* line size */ 454 *w = MAX(*w, linelen + 6); 455 /* conf.auto_minwidth */ 456 *w = MAX(*w, (int)conf->auto_minwidth); 457 /* 458 * avoid terminal overflow, 459 * -1 fix false negative with big menu over the terminal and 460 * autosize, for example "portconfig /usr/ports/www/apache24/". 461 */ 462 *w = MIN(*w, widget_max_width(conf)-1); 463 } 464 465 if (rows == BSDDIALOG_AUTOSIZE) { 466 *h = HBORDERS + 2 /* buttons */ + textrow; 467 468 if (*menurows == 0) { 469 *h += nitems + 2; 470 *h = MIN(*h, widget_max_height(conf)); 471 menusize = MIN(nitems + 2, *h - (HBORDERS + 2 + textrow)); 472 menusize -=2; 473 *menurows = menusize < 0 ? 0 : menusize; 474 } 475 else /* h autosize with a fixed menurows */ 476 *h = *h + *menurows + 2; 477 478 /* conf.auto_minheight */ 479 *h = MAX(*h, (int)conf->auto_minheight); 480 /* avoid terminal overflow */ 481 *h = MIN(*h, widget_max_height(conf)); 482 /* avoid menurows overflow */ 483 /* manual: with rows=autosize menurows!=0 is maxmenurows */ 484 *menurows = MIN(*h - 6 - textrow, (int)*menurows); 485 } 486 else { 487 if (*menurows == 0) 488 *menurows = MIN(rows-6-textrow, nitems); 489 } 490 } 491 492 static int 493 menu_checksize(int rows, int cols, char *text, int menurows, int nitems, 494 struct buttons bs) 495 { 496 int mincols, textrow, menusize; 497 498 mincols = VBORDERS; 499 /* buttons */ 500 mincols += bs.nbuttons * bs.sizebutton; 501 mincols += bs.nbuttons > 0 ? (bs.nbuttons-1) * t.button.space : 0; 502 /* line, comment to permet some cols hidden */ 503 /* mincols = MAX(mincols, linelen); */ 504 505 if (cols < mincols) 506 RETURN_ERROR("Few cols, width < size buttons or "\ 507 "name+descripion of the items"); 508 509 textrow = text != NULL && strlen(text) > 0 ? 1 : 0; 510 511 if (nitems > 0 && menurows == 0) 512 RETURN_ERROR("items > 0 but menurows == 0, probably terminal "\ 513 "too small"); 514 515 menusize = nitems > 0 ? 3 : 0; 516 if (rows < 2 + 2 + menusize + textrow) 517 RETURN_ERROR("Few lines for this menus"); 518 519 return 0; 520 } 521 522 /* the caller has to call prefresh(menupad, ymenupad, 0, ys, xs, ye, xe); */ 523 static void 524 update_menuwin(struct bsddialog_conf *conf, WINDOW *menuwin, int h, int w, 525 int totnitems, unsigned int menurows, int ymenupad) 526 { 527 528 draw_borders(conf, menuwin, h, w, LOWERED); 529 530 if (totnitems > (int) menurows) { 531 wattron(menuwin, t.menu.arrowcolor); 532 533 if (ymenupad > 0) 534 mvwprintw(menuwin, 0, 2, "^^^"); 535 536 if ((int) (ymenupad + menurows) < totnitems) 537 mvwprintw(menuwin, h-1, 2, "vvv"); 538 539 wattroff(menuwin, t.menu.arrowcolor); 540 541 mvwprintw(menuwin, h-1, w-10, "%3d%%", 542 100 * (ymenupad + menurows) / totnitems); 543 } 544 } 545 546 static int 547 do_mixedlist(struct bsddialog_conf *conf, char* text, int rows, int cols, 548 unsigned int menurows, enum menumode mode, int ngroups, 549 struct bsddialog_menugroup *groups, int *focuslist, int *focusitem) 550 { 551 WINDOW *shadow, *widget, *textpad, *menuwin, *menupad; 552 int i, j, y, x, h, w, htextpad, output, input; 553 int ymenupad, ys, ye, xs, xe, abs, g, rel, totnitems; 554 bool loop, automenurows, shortcut_buttons; 555 struct buttons bs; 556 struct bsddialog_menuitem *item; 557 enum menumode currmode; 558 struct lineposition pos = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; 559 560 shortcut_buttons = conf->menu.shortcut_buttons; 561 562 automenurows = menurows == BSDDIALOG_AUTOSIZE ? true : false; 563 564 totnitems = 0; 565 for (i=0; i < ngroups; i++) { 566 currmode = getmode(mode, groups[i]); 567 if (currmode == RADIOLISTMODE) 568 checkradiolist(groups[i].nitems, groups[i].items); 569 570 if (currmode == MENUMODE) 571 checkmenu(groups[i].nitems, groups[i].items); 572 573 if (currmode == RADIOLISTMODE || currmode == CHECKLISTMODE) 574 pos.selectorlen = 3; 575 576 for (j=0; j < (int) groups[i].nitems; j++) { 577 totnitems++; 578 item = &groups[i].items[j]; 579 580 if (groups[i].type == BSDDIALOG_SEPARATOR) { 581 pos.maxsepstr = MAX(pos.maxsepstr, 582 strlen(item->name) + strlen(item->desc)); 583 continue; 584 } 585 586 pos.maxprefix = MAX(pos.maxprefix, strlen(item->prefix)); 587 pos.maxdepth = MAX(pos.maxdepth, item->depth); 588 pos.maxname = MAX(pos.maxname, strlen(item->name)); 589 pos.maxdesc = MAX(pos.maxdesc, strlen(item->desc)); 590 } 591 } 592 pos.maxname = conf->menu.no_name ? 0 : pos.maxname; 593 pos.maxdesc = conf->menu.no_desc ? 0 : pos.maxdesc; 594 pos.maxdepth *= DEPTHSPACE; 595 596 pos.xselector = pos.maxprefix + (pos.maxprefix != 0 ? 1 : 0); 597 pos.xname = pos.xselector + pos.selectorlen + (pos.selectorlen > 0 ? 1 : 0); 598 pos.xdesc = pos.maxdepth + pos.xname + pos.maxname; 599 pos.xdesc += (pos.maxname != 0 ? 1 : 0); 600 pos.line = MAX(pos.maxsepstr + 3, pos.xdesc + pos.maxdesc); 601 602 603 get_buttons(conf, &bs, BUTTONLABEL(ok_label), BUTTONLABEL(extra_label), 604 BUTTONLABEL(cancel_label), BUTTONLABEL(help_label)); 605 606 if (set_widget_size(conf, rows, cols, &h, &w) != 0) 607 return BSDDIALOG_ERROR; 608 menu_autosize(conf, rows, cols, &h, &w, text, pos.line, &menurows, 609 totnitems, bs); 610 if (menu_checksize(h, w, text, menurows, totnitems, bs) != 0) 611 return BSDDIALOG_ERROR; 612 if (set_widget_position(conf, &y, &x, h, w) != 0) 613 return BSDDIALOG_ERROR; 614 615 if (new_widget_withtextpad(conf, &shadow, &widget, y, x, h, w, RAISED, 616 &textpad, &htextpad, text, true) != 0) 617 return BSDDIALOG_ERROR; 618 619 prefresh(textpad, 0, 0, y + 1, x + 1 + t.text.hmargin, 620 y + h - menurows, x + 1 + w - t.text.hmargin); 621 622 menuwin = new_boxed_window(conf, y + h - 5 - menurows, x + 2, 623 menurows+2, w-4, LOWERED); 624 625 menupad = newpad(totnitems, pos.line); 626 wbkgd(menupad, t.dialog.color); 627 628 ymenupad = 0; 629 for (i=0; i<ngroups; i++) { 630 currmode = getmode(mode, groups[i]); 631 for (j=0; j < (int) groups[i].nitems; j++) { 632 item = &groups[i].items[j]; 633 drawitem(conf, menupad, ymenupad, *item, currmode, pos, 634 false); 635 ymenupad++; 636 } 637 } 638 getfirst_with_default(conf, ngroups, groups, &abs, &g, &rel); 639 currmode = getmode(mode, groups[g]); 640 item = &groups[g].items[rel]; 641 drawitem(conf, menupad, abs, *item, currmode, pos, true); 642 643 ys = y + h - 5 - menurows + 1; 644 ye = y + h - 5 ; 645 if (conf->menu.align_left || (int)pos.line > w - 6) { 646 xs = x + 3; 647 xe = xs + w - 7; 648 } 649 else { /* center */ 650 xs = x + 3 + (w-6)/2 - pos.line/2; 651 xe = xs + w - 5; 652 } 653 654 ymenupad = 0; /* now ymenupad is pminrow for prefresh() */ 655 if ((int)(ymenupad + menurows) - 1 < abs) 656 ymenupad = abs - menurows + 1; 657 update_menuwin(conf, menuwin, menurows+2, w-4, totnitems, menurows, ymenupad); 658 wrefresh(menuwin); 659 prefresh(menupad, ymenupad, 0, ys, xs, ye, xe); 660 661 draw_buttons(widget, h-2, w, bs, shortcut_buttons); 662 wrefresh(widget); 663 664 loop = true; 665 while(loop) { 666 input = getch(); 667 switch(input) { 668 case KEY_ENTER: 669 case 10: /* Enter */ 670 output = bs.value[bs.curr]; 671 if (currmode == MENUMODE) 672 item->on = true; 673 loop = false; 674 break; 675 case 27: /* Esc */ 676 output = BSDDIALOG_ESC; 677 loop = false; 678 break; 679 case '\t': /* TAB */ 680 bs.curr = (bs.curr + 1) % bs.nbuttons; 681 draw_buttons(widget, h-2, w, bs, shortcut_buttons); 682 wrefresh(widget); 683 break; 684 case KEY_LEFT: 685 if (bs.curr > 0) { 686 bs.curr--; 687 draw_buttons(widget, h-2, w, bs, shortcut_buttons); 688 wrefresh(widget); 689 } 690 break; 691 case KEY_RIGHT: 692 if (bs.curr < (int) bs.nbuttons - 1) { 693 bs.curr++; 694 draw_buttons(widget, h-2, w, bs, shortcut_buttons); 695 wrefresh(widget); 696 } 697 break; 698 case KEY_CTRL('E'): /* add conf->menu.extrahelpkey ? */ 699 case KEY_F(1): 700 if (conf->f1_file == NULL && conf->f1_message == NULL) 701 break; 702 if (f1help(conf) != 0) 703 return BSDDIALOG_ERROR; 704 /* No break! the terminal size can change */ 705 case KEY_RESIZE: 706 hide_widget(y, x, h, w,conf->shadow); 707 708 /* 709 * Unnecessary, but, when the columns decrease the 710 * following "refresh" seem not work 711 */ 712 refresh(); 713 714 if (set_widget_size(conf, rows, cols, &h, &w) != 0) 715 return BSDDIALOG_ERROR; 716 menurows = automenurows ? 0 : menurows; 717 menu_autosize(conf, rows, cols, &h, &w, text, pos.line, 718 &menurows, totnitems, bs); 719 if (menu_checksize(h, w, text, menurows, totnitems, bs) != 0) 720 return BSDDIALOG_ERROR; 721 if (set_widget_position(conf, &y, &x, h, w) != 0) 722 return BSDDIALOG_ERROR; 723 724 wclear(shadow); 725 mvwin(shadow, y + t.shadow.h, x + t.shadow.w); 726 wresize(shadow, h, w); 727 728 wclear(widget); 729 mvwin(widget, y, x); 730 wresize(widget, h, w); 731 732 htextpad = 1; 733 wclear(textpad); 734 wresize(textpad, 1, w - HBORDERS - t.text.hmargin * 2); 735 736 if(update_widget_withtextpad(conf, shadow, widget, h, w, 737 RAISED, textpad, &htextpad, text, true) != 0) 738 return BSDDIALOG_ERROR; 739 740 draw_buttons(widget, h-2, w, bs, shortcut_buttons); 741 wrefresh(widget); 742 743 prefresh(textpad, 0, 0, y + 1, x + 1 + t.text.hmargin, 744 y + h - menurows, x + 1 + w - t.text.hmargin); 745 746 wclear(menuwin); 747 mvwin(menuwin, y + h - 5 - menurows, x + 2); 748 wresize(menuwin,menurows+2, w-4); 749 update_menuwin(conf, menuwin, menurows+2, w-4, totnitems, 750 menurows, ymenupad); 751 wrefresh(menuwin); 752 753 ys = y + h - 5 - menurows + 1; 754 ye = y + h - 5 ; 755 if (conf->menu.align_left || (int)pos.line > w - 6) { 756 xs = x + 3; 757 xe = xs + w - 7; 758 } 759 else { /* center */ 760 xs = x + 3 + (w-6)/2 - pos.line/2; 761 xe = xs + w - 5; 762 } 763 764 if ((int)(ymenupad + menurows) - 1 < abs) 765 ymenupad = abs - menurows + 1; 766 prefresh(menupad, ymenupad, 0, ys, xs, ye, xe); 767 768 refresh(); 769 770 break; 771 } 772 773 if (abs < 0) 774 continue; 775 switch(input) { 776 case KEY_HOME: 777 case KEY_UP: 778 case KEY_PPAGE: 779 if (abs == 0) /* useless, just to save cpu refresh */ 780 break; 781 drawitem(conf, menupad, abs, *item, currmode, pos, false); 782 if (input == KEY_HOME) 783 getfirst(ngroups, groups, &abs, &g, &rel); 784 else if (input == KEY_UP) 785 getprev(groups, &abs, &g, &rel); 786 else /* input == KEY_PPAGE*/ 787 getfastprev(menurows, groups, &abs, &g, &rel); 788 item = &groups[g].items[rel]; 789 currmode= getmode(mode, groups[g]); 790 drawitem(conf, menupad, abs, *item, currmode, pos, true); 791 if (ymenupad > abs && ymenupad > 0) 792 ymenupad = abs; 793 update_menuwin(conf, menuwin, menurows+2, w-4, totnitems, 794 menurows, ymenupad); 795 wrefresh(menuwin); 796 prefresh(menupad, ymenupad, 0, ys, xs, ye, xe); 797 break; 798 case KEY_END: 799 case KEY_DOWN: 800 case KEY_NPAGE: 801 if (abs == totnitems -1) 802 break; /* useless, just to save cpu refresh */ 803 drawitem(conf, menupad, abs, *item, currmode, pos, false); 804 if (input == KEY_END) 805 getlast(totnitems, ngroups, groups, &abs, &g, &rel); 806 else if (input == KEY_DOWN) 807 getnext(ngroups, groups, &abs, &g, &rel); 808 else /* input == KEY_NPAGE*/ 809 getfastnext(menurows, ngroups, groups, &abs, &g, &rel); 810 item = &groups[g].items[rel]; 811 currmode= getmode(mode, groups[g]); 812 drawitem(conf, menupad, abs, *item, currmode, pos, true); 813 if ((int)(ymenupad + menurows) <= abs) 814 ymenupad = abs - menurows + 1; 815 update_menuwin(conf, menuwin, menurows+2, w-4, totnitems, 816 menurows, ymenupad); 817 wrefresh(menuwin); 818 prefresh(menupad, ymenupad, 0, ys, xs, ye, xe); 819 break; 820 case ' ': /* Space */ 821 if (currmode == MENUMODE) 822 break; 823 else if (currmode == CHECKLISTMODE) 824 item->on = !item->on; 825 else { /* RADIOLISTMODE */ 826 for (i=0; i < (int) groups[g].nitems; i++) 827 if (groups[g].items[i].on == true && i != rel) { 828 groups[g].items[i].on = false; 829 drawitem(conf, menupad, 830 abs - rel + i, groups[g].items[i], 831 currmode, pos, false); 832 } 833 item->on = !item->on; 834 } 835 drawitem(conf, menupad, abs, *item, currmode, pos, true); 836 prefresh(menupad, ymenupad, 0, ys, xs, ye, xe); 837 default: 838 if (shortcut_buttons) { 839 for (i = 0; i < (int) bs.nbuttons; i++) 840 if (tolower(input) == tolower((bs.label[i])[0])) { 841 output = bs.value[i]; 842 if (currmode == MENUMODE) 843 item->on = true; 844 loop = false; 845 } 846 break; 847 } 848 849 drawitem(conf, menupad, abs, *item, currmode, pos, false); 850 getnextshortcut(conf, currmode, ngroups, groups, &abs, 851 &g, &rel, input); 852 item = &groups[g].items[rel]; 853 currmode = getmode(mode, groups[g]); 854 drawitem(conf, menupad, abs, *item, currmode, pos, true); 855 if (ymenupad > abs && ymenupad > 0) 856 ymenupad = abs; 857 if ((int)(ymenupad + menurows) <= abs) 858 ymenupad = abs - menurows + 1; 859 update_menuwin(conf, menuwin, menurows+2, w-4, totnitems, 860 menurows, ymenupad); 861 wrefresh(menuwin); 862 prefresh(menupad, ymenupad, 0, ys, xs, ye, xe); 863 } 864 } 865 866 if (focuslist != NULL) 867 *focuslist = g; 868 if (focusitem !=NULL) 869 *focusitem = rel; 870 871 delwin(menupad); 872 delwin(menuwin); 873 end_widget_withtextpad(conf, widget, h, w, textpad, shadow); 874 875 return output; 876 } 877 878 /* 879 * API 880 */ 881 882 int bsddialog_mixedlist(struct bsddialog_conf *conf, char* text, int rows, int cols, 883 unsigned int menurows, int ngroups, struct bsddialog_menugroup *groups, 884 int *focuslist, int *focusitem) 885 { 886 int output; 887 888 output = do_mixedlist(conf, text, rows, cols, menurows, MIXEDLISTMODE, 889 ngroups, groups, focuslist, focusitem); 890 891 return output; 892 } 893 894 int 895 bsddialog_checklist(struct bsddialog_conf *conf, char* text, int rows, int cols, 896 unsigned int menurows, int nitems, struct bsddialog_menuitem *items, 897 int *focusitem) 898 { 899 int output; 900 struct bsddialog_menugroup group = { 901 BSDDIALOG_CHECKLIST /* unused */, nitems, items}; 902 903 output = do_mixedlist(conf, text, rows, cols, menurows, CHECKLISTMODE, 904 1, &group, NULL, focusitem); 905 906 return output; 907 } 908 909 int 910 bsddialog_menu(struct bsddialog_conf *conf, char* text, int rows, int cols, 911 unsigned int menurows, int nitems, struct bsddialog_menuitem *items, 912 int *focusitem) 913 { 914 int output; 915 struct bsddialog_menugroup group = { 916 BSDDIALOG_CHECKLIST /* unused */, nitems, items}; 917 918 output = do_mixedlist(conf, text, rows, cols, menurows, MENUMODE, 1, 919 &group, NULL, focusitem); 920 921 return output; 922 } 923 924 int 925 bsddialog_radiolist(struct bsddialog_conf *conf, char* text, int rows, int cols, 926 unsigned int menurows, int nitems, struct bsddialog_menuitem *items, 927 int *focusitem) 928 { 929 int output; 930 struct bsddialog_menugroup group = { 931 BSDDIALOG_RADIOLIST /* unused */, nitems, items}; 932 933 output = do_mixedlist(conf, text, rows, cols, menurows, RADIOLISTMODE, 934 1, &group, NULL, focusitem); 935 936 return output; 937 } 938 939 /* todo */ 940 static int buildlist_autosize(int rows, int cols) 941 { 942 943 if (cols == BSDDIALOG_AUTOSIZE) 944 RETURN_ERROR("Unimplemented cols autosize for buildlist"); 945 946 if (rows == BSDDIALOG_AUTOSIZE) 947 RETURN_ERROR("Unimplemented rows autosize for buildlist"); 948 949 return 0; 950 } 951 952 /* to improve */ 953 static int 954 buildlist_checksize(int rows, int cols, char *text, int menurows, int nitems, 955 struct buttons bs) 956 { 957 int mincols, textrow, menusize; 958 959 mincols = VBORDERS; 960 /* buttons */ 961 mincols += bs.nbuttons * bs.sizebutton; 962 mincols += bs.nbuttons > 0 ? (bs.nbuttons-1) * t.button.space : 0; 963 /* line, comment to permet some cols hidden */ 964 /* mincols = MAX(mincols, linelen); */ 965 966 if (cols < mincols) 967 RETURN_ERROR("Few cols, width < size buttons or "\ 968 "name+descripion of the items"); 969 970 textrow = text != NULL && strlen(text) > 0 ? 1 : 0; 971 972 if (nitems > 0 && menurows == 0) 973 RETURN_ERROR("items > 0 but menurows == 0, probably terminal "\ 974 "too small"); 975 976 menusize = nitems > 0 ? 3 : 0; 977 if (rows < 2 + 2 + menusize + textrow) 978 RETURN_ERROR("Few lines for this menus"); 979 980 return 0; 981 } 982 983 int 984 bsddialog_buildlist(struct bsddialog_conf *conf, char* text, int rows, int cols, 985 unsigned int menurows, int nitems, struct bsddialog_menuitem *items, 986 int *focusitem) 987 { 988 WINDOW *widget, *textpad, *leftwin, *leftpad, *rightwin, *rightpad, *shadow; 989 int output, i, x, y, h, w, htextpad, input; 990 bool loop, buttupdate, padsupdate, startleft; 991 int nlefts, nrights, leftwinx, rightwinx, winsy, padscols, curr; 992 enum side {LEFT, RIGHT} currV; 993 int currH; 994 struct buttons bs; 995 struct lineposition pos = {0,0,0,0,0,0,0,0,0,0}; 996 997 startleft = false; 998 for (i=0; i<nitems; i++) { 999 pos.line = MAX(pos.line, strlen(items[i].desc)); 1000 if (items[i].on == false) 1001 startleft = true; 1002 } 1003 1004 get_buttons(conf, &bs, BUTTONLABEL(ok_label), BUTTONLABEL(extra_label), 1005 BUTTONLABEL(cancel_label), BUTTONLABEL(help_label)); 1006 1007 if (set_widget_size(conf, rows, cols, &h, &w) != 0) 1008 return BSDDIALOG_ERROR; 1009 if (buildlist_autosize(rows, cols) != 0) 1010 return BSDDIALOG_ERROR; 1011 if (buildlist_checksize(h, w, text, menurows, nitems, bs) != 0) 1012 return BSDDIALOG_ERROR; 1013 if (set_widget_position(conf, &y, &x, h, w) != 0) 1014 return BSDDIALOG_ERROR; 1015 1016 if (new_widget_withtextpad(conf, &shadow, &widget, y, x, h, w, RAISED, 1017 &textpad, &htextpad, text, true) != 0) 1018 return BSDDIALOG_ERROR; 1019 1020 prefresh(textpad, 0, 0, y + 1, x + 1 + t.text.hmargin, 1021 y + h - menurows, x + 1 + w - t.text.hmargin); 1022 1023 winsy = y + h - 5 - menurows; 1024 leftwinx = x+2; 1025 leftwin = new_boxed_window(conf, winsy, leftwinx, menurows+2, (w-5)/2, 1026 LOWERED); 1027 rightwinx = x + w - 2 -(w-5)/2; 1028 rightwin = new_boxed_window(conf, winsy, rightwinx, menurows+2, 1029 (w-5)/2, LOWERED); 1030 1031 wrefresh(leftwin); 1032 wrefresh(rightwin); 1033 1034 padscols = (w-5)/2 - 2; 1035 leftpad = newpad(nitems, pos.line); 1036 rightpad = newpad(nitems, pos.line); 1037 wbkgd(leftpad, t.dialog.color); 1038 wbkgd(rightpad, t.dialog.color); 1039 1040 currH = 0; 1041 currV = startleft ? LEFT : RIGHT; 1042 loop = buttupdate = padsupdate = true; 1043 while(loop) { 1044 if (buttupdate) { 1045 draw_buttons(widget, h-2, w, bs, true); 1046 wrefresh(widget); 1047 buttupdate = false; 1048 } 1049 1050 if (padsupdate) { 1051 werase(leftpad); 1052 werase(rightpad); 1053 curr = -1; 1054 nlefts = nrights = 0; 1055 for (i=0; i<nitems; i++) { 1056 if (items[i].on == false) { 1057 if (currV == LEFT && currH == nlefts) 1058 curr = i; 1059 drawitem(conf, leftpad, nlefts, items[i], 1060 BUILDLISTMODE, pos, curr == i); 1061 nlefts++; 1062 } else { 1063 if (currV == RIGHT && currH == nrights) 1064 curr = i; 1065 drawitem(conf, rightpad, nrights, items[i], 1066 BUILDLISTMODE, pos, curr == i); 1067 nrights++; 1068 } 1069 } 1070 prefresh(leftpad, 0, 0, winsy+1, leftwinx+1, 1071 winsy+1+menurows, leftwinx + 1 + padscols); 1072 prefresh(rightpad, 0, 0, winsy+1, rightwinx+1, 1073 winsy+1+menurows, rightwinx + 1 + padscols); 1074 padsupdate = false; 1075 } 1076 1077 input = getch(); 1078 switch(input) { 1079 case KEY_ENTER: 1080 case 10: /* Enter */ 1081 output = bs.value[bs.curr]; 1082 loop = false; 1083 break; 1084 case 27: /* Esc */ 1085 output = BSDDIALOG_ERROR; 1086 loop = false; 1087 break; 1088 case '\t': /* TAB */ 1089 bs.curr = (bs.curr + 1) % bs.nbuttons; 1090 buttupdate = true; 1091 break; 1092 } 1093 1094 if (nitems <= 0) 1095 continue; 1096 1097 switch(input) { 1098 case KEY_LEFT: 1099 if (currV == RIGHT && nrights > 0) { 1100 currV = LEFT; 1101 currH = 0; 1102 padsupdate = true; 1103 } 1104 break; 1105 case KEY_RIGHT: 1106 if (currV == LEFT && nrights > 0) { 1107 currV = RIGHT; 1108 currH = 0; 1109 padsupdate = true; 1110 } 1111 break; 1112 case KEY_UP: 1113 currH = (currH > 0) ? currH - 1 : 0; 1114 padsupdate = true; 1115 break; 1116 case KEY_DOWN: 1117 if (currV == LEFT) 1118 currH = (currH < nlefts-1) ? currH +1 : currH; 1119 else 1120 currH = (currH < nrights-1)? currH +1 : currH; 1121 padsupdate = true; 1122 break; 1123 case ' ': /* Space */ 1124 items[curr].on = ! items[curr].on; 1125 if (currV == LEFT) { 1126 if (nlefts > 1) 1127 currH = currH > 0 ? currH-1 : 0; 1128 else { 1129 currH = 0; 1130 currV = RIGHT; 1131 } 1132 } else { 1133 if (nrights > 1) 1134 currH = currH > 0 ? currH-1 : 0; 1135 else { 1136 currH = 0; 1137 currV = LEFT; 1138 } 1139 } 1140 padsupdate = true; 1141 break; 1142 default: 1143 1144 break; 1145 } 1146 } 1147 1148 if(focusitem != NULL) 1149 *focusitem = curr; 1150 1151 delwin(leftpad); 1152 delwin(leftwin); 1153 delwin(rightpad); 1154 delwin(rightwin); 1155 end_widget_withtextpad(conf, widget, h, w, textpad, shadow); 1156 1157 return output; 1158 } 1159