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 <string.h> 32 33 #ifdef PORTNCURSES 34 #include <ncurses/curses.h> 35 #else 36 #include <curses.h> 37 #endif 38 39 #include "bsddialog.h" 40 #include "lib_util.h" 41 #include "bsddialog_theme.h" 42 43 /* "Menu": checklist - menu - mixedlist - radiolist - treeview - 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 enum menumode 275 getmode(enum menumode mode, struct bsddialog_menugroup group) 276 { 277 278 if (mode == MIXEDLISTMODE) { 279 if (group.type == BSDDIALOG_SEPARATOR) 280 mode = SEPARATORMODE; 281 else if (group.type == BSDDIALOG_RADIOLIST) 282 mode = RADIOLISTMODE; 283 else if (group.type == BSDDIALOG_CHECKLIST) 284 mode = CHECKLISTMODE; 285 } 286 287 return mode; 288 } 289 290 static void 291 drawitem(struct bsddialog_conf conf, WINDOW *pad, int y, 292 struct bsddialog_menuitem item, enum menumode mode, struct lineposition pos, 293 bool curr) 294 { 295 int color, colorname, linech; 296 297 color = curr ? t.curritemcolor : t.itemcolor; 298 colorname = curr ? t.currtagcolor : t.tagcolor; 299 300 if (mode == SEPARATORMODE) { 301 if (conf.no_lines == false) { 302 wattron(pad, t.itemcolor); 303 linech = conf.ascii_lines ? '-' : ACS_HLINE; 304 mvwhline(pad, y, 0, linech, pos.line); 305 wattroff(pad, t.itemcolor); 306 } 307 wmove(pad, y, pos.line/2 - (strlen(item.name)+strlen(item.desc))/2); 308 wattron(pad, t.namesepcolor); 309 waddstr(pad, item.name); 310 wattroff(pad, t.namesepcolor); 311 if (strlen(item.name) > 0 && strlen(item.desc) > 0) 312 waddch(pad, ' '); 313 wattron(pad, t.descsepcolor); 314 waddstr(pad, item.desc); 315 wattroff(pad, t.descsepcolor); 316 return; 317 } 318 319 /* prefix */ 320 if (item.prefix != NULL && item.prefix[0] != '\0') 321 mvwaddstr(pad, y, 0, item.prefix); 322 323 /* selector */ 324 wmove(pad, y, pos.xselector); 325 wattron(pad, color); 326 if (mode == CHECKLISTMODE) 327 wprintw(pad, "[%c]", item.on ? 'X' : ' '); 328 if (mode == RADIOLISTMODE) 329 wprintw(pad, "(%c)", item.on ? '*' : ' '); 330 wattroff(pad, color); 331 332 /* name */ 333 if (mode != BUILDLISTMODE && conf.menu.no_tags == false) { 334 wattron(pad, colorname); 335 mvwaddstr(pad, y, pos.xname + item.depth * DEPTHSPACE, item.name); 336 wattroff(pad, colorname); 337 } 338 339 /* description */ 340 if (conf.menu.no_items == false) { 341 if ((mode == BUILDLISTMODE || conf.menu.no_tags) && curr == false) 342 color = item.on ? t.tagcolor : t.itemcolor; 343 wattron(pad, color); 344 if (conf.menu.no_tags) 345 mvwaddstr(pad, y, pos.xname + item.depth * DEPTHSPACE, item.desc); 346 else 347 mvwaddstr(pad, y, pos.xdesc, item.desc); 348 wattroff(pad, color); 349 } 350 351 /* bottom desc (item help) */ 352 if (item.bottomdesc != NULL && item.bottomdesc[0] != '\0') { 353 move(LINES-1, 2); 354 clrtoeol(); 355 addstr(item.bottomdesc); 356 357 refresh(); 358 } 359 } 360 361 static void 362 menu_autosize(struct bsddialog_conf conf, int rows, int cols, int *h, int *w, 363 char *text, int linelen, unsigned int *menurows, int nitems, 364 struct buttons bs) 365 { 366 int textrow, menusize; 367 368 textrow = text != NULL && strlen(text) > 0 ? 1 : 0; 369 370 if (cols == BSDDIALOG_AUTOSIZE) { 371 *w = VBORDERS; 372 /* buttons size */ 373 *w += bs.nbuttons * bs.sizebutton; 374 *w += bs.nbuttons > 0 ? (bs.nbuttons-1) * t.buttonspace : 0; 375 /* line size */ 376 *w = MAX(*w, linelen + 6); 377 /* 378 * avoid terminal overflow, 379 * -1 fix false negative with big menu over the terminal and 380 * autosize, for example "portconfig /usr/ports/www/apache24/". 381 */ 382 *w = MIN(*w, widget_max_width(conf)-1); 383 } 384 385 if (rows == BSDDIALOG_AUTOSIZE) { 386 *h = HBORDERS + 2 /* buttons */ + textrow; 387 388 if (*menurows == 0) { 389 *h += nitems + 2; 390 *h = MIN(*h, widget_max_height(conf)); 391 menusize = MIN(nitems + 2, *h - (HBORDERS + 2 + textrow)); 392 menusize -=2; 393 *menurows = menusize < 0 ? 0 : menusize; 394 } 395 else /* h autosize with a fixed menurows */ 396 *h = *h + *menurows + 2; 397 398 /* avoid terminal overflow */ 399 *h = MIN(*h, widget_max_height(conf)); 400 } 401 else { 402 if (*menurows == 0) 403 *menurows = MIN(rows-6-textrow, nitems); 404 } 405 } 406 407 static int 408 menu_checksize(int rows, int cols, char *text, int menurows, int nitems, 409 struct buttons bs) 410 { 411 int mincols, textrow, menusize; 412 413 mincols = VBORDERS; 414 /* buttons */ 415 mincols += bs.nbuttons * bs.sizebutton; 416 mincols += bs.nbuttons > 0 ? (bs.nbuttons-1) * t.buttonspace : 0; 417 /* line, comment to permet some cols hidden */ 418 /* mincols = MAX(mincols, linelen); */ 419 420 if (cols < mincols) 421 RETURN_ERROR("Few cols, width < size buttons or "\ 422 "name+descripion of the items"); 423 424 textrow = text != NULL && strlen(text) > 0 ? 1 : 0; 425 426 if (nitems > 0 && menurows == 0) 427 RETURN_ERROR("items > 0 but menurows == 0, probably terminal "\ 428 "too small"); 429 430 menusize = nitems > 0 ? 3 : 0; 431 if (rows < 2 + 2 + menusize + textrow) 432 RETURN_ERROR("Few lines for this menus"); 433 434 return 0; 435 } 436 437 /* the caller has to call prefresh(menupad, ymenupad, 0, ys, xs, ye, xe); */ 438 static void 439 update_menuwin(struct bsddialog_conf conf, WINDOW *menuwin, int h, int w, 440 int totnitems, unsigned int menurows, int ymenupad) 441 { 442 443 if (totnitems > (int) menurows) { 444 draw_borders(conf, menuwin, h, w, LOWERED); 445 446 if (ymenupad > 0) { 447 wattron(menuwin, t.lineraisecolor); 448 mvwprintw(menuwin, 0, 2, "^^"); 449 wattroff(menuwin, t.lineraisecolor); 450 } 451 if ((int) (ymenupad + menurows) < totnitems) { 452 wattron(menuwin, t.linelowercolor); 453 mvwprintw(menuwin, h-1, 2, "vv"); 454 wattroff(menuwin, t.linelowercolor); 455 } 456 457 mvwprintw(menuwin, h-1, w-10, "%3d%%", 458 100 * (ymenupad + menurows) / totnitems); 459 } 460 } 461 462 static int 463 do_mixedlist(struct bsddialog_conf conf, char* text, int rows, int cols, 464 unsigned int menurows, enum menumode mode, int ngroups, 465 struct bsddialog_menugroup *groups, int *focuslist, int *focusitem) 466 { 467 WINDOW *shadow, *widget, *textpad, *menuwin, *menupad; 468 int i, j, y, x, h, w, htextpad, output, input; 469 int ymenupad, ys, ye, xs, xe, abs, g, rel, totnitems; 470 bool loop, automenurows; 471 struct buttons bs; 472 struct bsddialog_menuitem *item; 473 enum menumode currmode; 474 struct lineposition pos = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; 475 476 automenurows = menurows == BSDDIALOG_AUTOSIZE ? true : false; 477 478 totnitems = 0; 479 for (i=0; i < ngroups; i++) { 480 currmode = getmode(mode, groups[i]); 481 if (currmode == RADIOLISTMODE) 482 checkradiolist(groups[i].nitems, groups[i].items); 483 484 if (currmode == MENUMODE) 485 checkmenu(groups[i].nitems, groups[i].items); 486 487 if (currmode == RADIOLISTMODE || currmode == CHECKLISTMODE) 488 pos.selectorlen = 3; 489 490 for (j=0; j < (int) groups[i].nitems; j++) { 491 totnitems++; 492 item = &groups[i].items[j]; 493 494 if (groups[i].type == BSDDIALOG_SEPARATOR) { 495 pos.maxsepstr = MAX(pos.maxsepstr, 496 strlen(item->name) + strlen(item->desc)); 497 continue; 498 } 499 500 pos.maxprefix = MAX(pos.maxprefix, strlen(item->prefix)); 501 pos.maxdepth = MAX((int) pos.maxdepth, item->depth); 502 pos.maxname = MAX(pos.maxname, strlen(item->name)); 503 pos.maxdesc = MAX(pos.maxdesc, strlen(item->desc)); 504 } 505 } 506 pos.maxname = conf.menu.no_tags ? 0 : pos.maxname; 507 pos.maxdesc = conf.menu.no_items ? 0 : pos.maxdesc; 508 pos.maxdepth *= DEPTHSPACE; 509 510 pos.xselector = pos.maxprefix + (pos.maxprefix != 0 ? 1 : 0); 511 pos.xname = pos.xselector + pos.selectorlen + (pos.selectorlen > 0 ? 1 : 0); 512 pos.xdesc = pos.maxdepth + pos.xname + pos.maxname; 513 pos.xdesc += (pos.maxname != 0 ? 1 : 0); 514 pos.line = MAX(pos.maxsepstr + 3, pos.xdesc + pos.maxdesc); 515 516 517 get_buttons(conf, &bs, BUTTONLABEL(ok_label), BUTTONLABEL(extra_label), 518 BUTTONLABEL(cancel_label), BUTTONLABEL(help_label)); 519 520 if (set_widget_size(conf, rows, cols, &h, &w) != 0) 521 return BSDDIALOG_ERROR; 522 menu_autosize(conf, rows, cols, &h, &w, text, pos.line, &menurows, 523 totnitems, bs); 524 if (menu_checksize(h, w, text, menurows, totnitems, bs) != 0) 525 return BSDDIALOG_ERROR; 526 if (set_widget_position(conf, &y, &x, h, w) != 0) 527 return BSDDIALOG_ERROR; 528 529 if (new_widget_withtextpad(conf, &shadow, &widget, y, x, h, w, RAISED, 530 &textpad, &htextpad, text, true) != 0) 531 return BSDDIALOG_ERROR; 532 533 prefresh(textpad, 0, 0, y + 1, x + 1 + t.texthmargin, 534 y + h - menurows, x + 1 + w - t.texthmargin); 535 536 menuwin = new_boxed_window(conf, y + h - 5 - menurows, x + 2, 537 menurows+2, w-4, LOWERED); 538 539 menupad = newpad(totnitems, pos.line); 540 wbkgd(menupad, t.widgetcolor); 541 542 getfirst_with_default(conf, ngroups, groups, &abs, &g, &rel); 543 ymenupad = 0; 544 for (i=0; i<ngroups; i++) { 545 currmode = getmode(mode, groups[i]); 546 for (j=0; j < (int) groups[i].nitems; j++) { 547 item = &groups[i].items[j]; 548 drawitem(conf, menupad, ymenupad, *item, currmode, 549 pos, ymenupad == abs); 550 ymenupad++; 551 } 552 } 553 554 ys = y + h - 5 - menurows + 1; 555 ye = y + h - 5 ; 556 if (conf.menu.align_left || (int)pos.line > w - 6) { 557 xs = x + 3; 558 xe = xs + w - 7; 559 } 560 else { /* center */ 561 xs = x + 3 + (w-6)/2 - pos.line/2; 562 xe = xs + w - 5; 563 } 564 565 ymenupad = 0; /* now ymenupad is pminrow for prefresh() */ 566 if ((int)(ymenupad + menurows) - 1 < abs) 567 ymenupad = abs - menurows + 1; 568 update_menuwin(conf, menuwin, menurows+2, w-4, totnitems, menurows, ymenupad); 569 wrefresh(menuwin); 570 prefresh(menupad, ymenupad, 0, ys, xs, ye, xe); 571 572 draw_buttons(widget, h-2, w, bs, true); 573 wrefresh(widget); 574 575 item = &groups[g].items[rel]; 576 currmode = getmode(mode, groups[g]); 577 loop = true; 578 while(loop) { 579 input = getch(); 580 switch(input) { 581 case KEY_ENTER: 582 case 10: /* Enter */ 583 output = bs.value[bs.curr]; 584 if (currmode == MENUMODE) 585 item->on = true; 586 loop = false; 587 break; 588 case 27: /* Esc */ 589 output = BSDDIALOG_ESC; 590 loop = false; 591 break; 592 case '\t': /* TAB */ 593 bs.curr = (bs.curr + 1) % bs.nbuttons; 594 draw_buttons(widget, h-2, w, bs, true); 595 wrefresh(widget); 596 break; 597 case KEY_LEFT: 598 if (bs.curr > 0) { 599 bs.curr--; 600 draw_buttons(widget, h-2, w, bs, true); 601 wrefresh(widget); 602 } 603 break; 604 case KEY_RIGHT: 605 if (bs.curr < (int) bs.nbuttons - 1) { 606 bs.curr++; 607 draw_buttons(widget, h-2, w, bs, true); 608 wrefresh(widget); 609 } 610 break; 611 case KEY_CTRL('E'): /* add conf.menu.extrahelpkey ? */ 612 case KEY_F(1): 613 if (conf.hfile == NULL) 614 break; 615 if (f1help(conf) != 0) 616 return BSDDIALOG_ERROR; 617 /* No break! the terminal size can change */ 618 case KEY_RESIZE: 619 hide_widget(y, x, h, w,conf.shadow); 620 621 /* 622 * Unnecessary, but, when the columns decrease the 623 * following "refresh" seem not work 624 */ 625 refresh(); 626 627 if (set_widget_size(conf, rows, cols, &h, &w) != 0) 628 return BSDDIALOG_ERROR; 629 menurows = automenurows ? 0 : menurows; 630 menu_autosize(conf, rows, cols, &h, &w, text, pos.line, 631 &menurows, totnitems, bs); 632 if (menu_checksize(h, w, text, menurows, totnitems, bs) != 0) 633 return BSDDIALOG_ERROR; 634 if (set_widget_position(conf, &y, &x, h, w) != 0) 635 return BSDDIALOG_ERROR; 636 637 wclear(shadow); 638 mvwin(shadow, y + t.shadowrows, x + t.shadowcols); 639 wresize(shadow, h, w); 640 641 wclear(widget); 642 mvwin(widget, y, x); 643 wresize(widget, h, w); 644 645 htextpad = 1; 646 wclear(textpad); 647 wresize(textpad, 1, w - HBORDERS - t.texthmargin * 2); 648 649 if(update_widget_withtextpad(conf, shadow, widget, h, w, 650 RAISED, textpad, &htextpad, text, true) != 0) 651 return BSDDIALOG_ERROR; 652 653 draw_buttons(widget, h-2, w, bs, true); 654 wrefresh(widget); 655 656 prefresh(textpad, 0, 0, y + 1, x + 1 + t.texthmargin, 657 y + h - menurows, x + 1 + w - t.texthmargin); 658 659 wclear(menuwin); 660 mvwin(menuwin, y + h - 5 - menurows, x + 2); 661 wresize(menuwin,menurows+2, w-4); 662 update_menuwin(conf, menuwin, menurows+2, w-4, totnitems, 663 menurows, ymenupad); 664 wrefresh(menuwin); 665 666 ys = y + h - 5 - menurows + 1; 667 ye = y + h - 5 ; 668 if (conf.menu.align_left || (int)pos.line > w - 6) { 669 xs = x + 3; 670 xe = xs + w - 7; 671 } 672 else { /* center */ 673 xs = x + 3 + (w-6)/2 - pos.line/2; 674 xe = xs + w - 5; 675 } 676 677 if ((int)(ymenupad + menurows) - 1 < abs) 678 ymenupad = abs - menurows + 1; 679 prefresh(menupad, ymenupad, 0, ys, xs, ye, xe); 680 681 refresh(); 682 683 break; 684 default: 685 for (i = 0; i < (int) bs.nbuttons; i++) 686 if (tolower(input) == tolower((bs.label[i])[0])) { 687 output = bs.value[i]; 688 loop = false; 689 } 690 691 } 692 693 if (abs < 0) 694 continue; 695 switch(input) { 696 case KEY_HOME: 697 case KEY_UP: 698 case KEY_PPAGE: 699 if (abs == 0) /* useless, just to save cpu refresh */ 700 break; 701 drawitem(conf, menupad, abs, *item, currmode, pos, false); 702 if (input == KEY_HOME) 703 getfirst(ngroups, groups, &abs, &g, &rel); 704 else if (input == KEY_UP) 705 getprev(groups, &abs, &g, &rel); 706 else /* input == KEY_PPAGE*/ 707 getfastprev(menurows, groups, &abs, &g, &rel); 708 item = &groups[g].items[rel]; 709 currmode= getmode(mode, groups[g]); 710 drawitem(conf, menupad, abs, *item, currmode, pos, true); 711 if (ymenupad > abs && ymenupad > 0) 712 ymenupad = abs; 713 update_menuwin(conf, menuwin, menurows+2, w-4, totnitems, 714 menurows, ymenupad); 715 wrefresh(menuwin); 716 prefresh(menupad, ymenupad, 0, ys, xs, ye, xe); 717 break; 718 case KEY_END: 719 case KEY_DOWN: 720 case KEY_NPAGE: 721 if (abs == totnitems -1) 722 break; /* useless, just to save cpu refresh */ 723 drawitem(conf, menupad, abs, *item, currmode, pos, false); 724 if (input == KEY_END) 725 getlast(totnitems, ngroups, groups, &abs, &g, &rel); 726 else if (input == KEY_DOWN) 727 getnext(ngroups, groups, &abs, &g, &rel); 728 else /* input == KEY_NPAGE*/ 729 getfastnext(menurows, ngroups, groups, &abs, &g, &rel); 730 item = &groups[g].items[rel]; 731 currmode= getmode(mode, groups[g]); 732 drawitem(conf, menupad, abs, *item, currmode, pos, true); 733 if ((int)(ymenupad + menurows) <= abs) 734 ymenupad = abs - menurows + 1; 735 update_menuwin(conf, menuwin, menurows+2, w-4, totnitems, 736 menurows, ymenupad); 737 wrefresh(menuwin); 738 prefresh(menupad, ymenupad, 0, ys, xs, ye, xe); 739 break; 740 case ' ': /* Space */ 741 if (currmode == MENUMODE) 742 break; 743 else if (currmode == CHECKLISTMODE) 744 item->on = !item->on; 745 else { /* RADIOLISTMODE */ 746 if (item->on == true) 747 break; 748 for (i=0; i < (int) groups[g].nitems; i++) 749 if (groups[g].items[i].on == true) { 750 groups[g].items[i].on = false; 751 drawitem(conf, menupad, 752 abs - rel + i, groups[g].items[i], 753 currmode, pos, false); 754 } 755 item->on = true; 756 } 757 drawitem(conf, menupad, abs, *item, currmode, pos, true); 758 prefresh(menupad, ymenupad, 0, ys, xs, ye, xe); 759 } 760 } 761 762 if (focuslist != NULL) 763 *focuslist = g; 764 if (focusitem !=NULL) 765 *focusitem = rel; 766 767 delwin(menupad); 768 delwin(menuwin); 769 end_widget_withtextpad(conf, widget, h, w, textpad, shadow); 770 771 return output; 772 } 773 774 /* 775 * API 776 */ 777 778 int bsddialog_mixedlist(struct bsddialog_conf conf, char* text, int rows, int cols, 779 unsigned int menurows, int ngroups, struct bsddialog_menugroup *groups, 780 int *focuslist, int *focusitem) 781 { 782 int output; 783 784 output = do_mixedlist(conf, text, rows, cols, menurows, MIXEDLISTMODE, 785 ngroups, groups, focuslist, focusitem); 786 787 return output; 788 } 789 790 int 791 bsddialog_checklist(struct bsddialog_conf conf, char* text, int rows, int cols, 792 unsigned int menurows, int nitems, struct bsddialog_menuitem *items, 793 int *focusitem) 794 { 795 int output; 796 struct bsddialog_menugroup group = { 797 BSDDIALOG_CHECKLIST /* unused */, nitems, items}; 798 799 output = do_mixedlist(conf, text, rows, cols, menurows, CHECKLISTMODE, 800 1, &group, NULL, focusitem); 801 802 return output; 803 } 804 805 int 806 bsddialog_menu(struct bsddialog_conf conf, char* text, int rows, int cols, 807 unsigned int menurows, int nitems, struct bsddialog_menuitem *items, 808 int *focusitem) 809 { 810 int output; 811 struct bsddialog_menugroup group = { 812 BSDDIALOG_CHECKLIST /* unused */, nitems, items}; 813 814 output = do_mixedlist(conf, text, rows, cols, menurows, MENUMODE, 1, 815 &group, NULL, focusitem); 816 817 return output; 818 } 819 820 int 821 bsddialog_radiolist(struct bsddialog_conf conf, char* text, int rows, int cols, 822 unsigned int menurows, int nitems, struct bsddialog_menuitem *items, 823 int *focusitem) 824 { 825 int output; 826 struct bsddialog_menugroup group = { 827 BSDDIALOG_RADIOLIST /* unused */, nitems, items}; 828 829 output = do_mixedlist(conf, text, rows, cols, menurows, RADIOLISTMODE, 830 1, &group, NULL, focusitem); 831 832 return output; 833 } 834 835 int 836 bsddialog_treeview(struct bsddialog_conf conf, char* text, int rows, int cols, 837 unsigned int menurows, int nitems, struct bsddialog_menuitem *items, 838 int *focusitem) 839 { 840 int output; 841 struct bsddialog_menugroup group = { 842 BSDDIALOG_RADIOLIST /* unused */, nitems, items}; 843 844 conf.menu.no_tags = true; 845 conf.menu.align_left = true; 846 847 output = do_mixedlist(conf, text, rows, cols, menurows, RADIOLISTMODE, 848 1, &group, NULL, focusitem); 849 850 return output; 851 } 852 853 int 854 bsddialog_buildlist(struct bsddialog_conf conf, char* text, int rows, int cols, 855 unsigned int menurows, int nitems, struct bsddialog_menuitem *items, 856 int *focusitem) 857 { 858 WINDOW *widget, *leftwin, *leftpad, *rightwin, *rightpad, *shadow; 859 int output, i, x, y, input; 860 bool loop, buttupdate, padsupdate, startleft; 861 int nlefts, nrights, leftwinx, rightwinx, winsy, padscols, curr; 862 enum side {LEFT, RIGHT} currV; 863 int currH; 864 struct buttons bs; 865 struct lineposition pos = {0,0,0,0,0,0,0,0,0,0}; 866 867 startleft = false; 868 for (i=0; i<nitems; i++) { 869 pos.line = MAX(pos.line, strlen(items[i].desc)); 870 if (items[i].on == false) 871 startleft = true; 872 } 873 874 if (new_widget(conf, &widget, &y, &x, text, &rows, &cols, &shadow, 875 true) <0) 876 return -1; 877 878 winsy = y + rows - 5 - menurows; 879 leftwinx = x+2; 880 leftwin = new_boxed_window(conf, winsy, leftwinx, menurows+2, (cols-5)/2, 881 LOWERED); 882 rightwinx = x + cols - 2 -(cols-5)/2; 883 rightwin = new_boxed_window(conf, winsy, rightwinx, menurows+2, 884 (cols-5)/2, LOWERED); 885 886 wrefresh(leftwin); 887 wrefresh(rightwin); 888 889 padscols = (cols-5)/2 - 2; 890 leftpad = newpad(nitems, pos.line); 891 rightpad = newpad(nitems, pos.line); 892 wbkgd(leftpad, t.widgetcolor); 893 wbkgd(rightpad, t.widgetcolor); 894 895 get_buttons(conf, &bs, BUTTONLABEL(ok_label), BUTTONLABEL(extra_label), 896 BUTTONLABEL(cancel_label), BUTTONLABEL(help_label)); 897 898 currH = 0; 899 currV = startleft ? LEFT : RIGHT; 900 loop = buttupdate = padsupdate = true; 901 while(loop) { 902 if (buttupdate) { 903 draw_buttons(widget, rows-2, cols, bs, true); 904 wrefresh(widget); 905 buttupdate = false; 906 } 907 908 if (padsupdate) { 909 werase(leftpad); 910 werase(rightpad); 911 curr = -1; 912 nlefts = nrights = 0; 913 for (i=0; i<nitems; i++) { 914 if (items[i].on == false) { 915 if (currV == LEFT && currH == nlefts) 916 curr = i; 917 drawitem(conf, leftpad, nlefts, items[i], 918 BUILDLISTMODE, pos, curr == i); 919 nlefts++; 920 } else { 921 if (currV == RIGHT && currH == nrights) 922 curr = i; 923 drawitem(conf, rightpad, nrights, items[i], 924 BUILDLISTMODE, pos, curr == i); 925 nrights++; 926 } 927 } 928 prefresh(leftpad, 0, 0, winsy+1, leftwinx+1, 929 winsy+1+menurows, leftwinx + 1 + padscols); 930 prefresh(rightpad, 0, 0, winsy+1, rightwinx+1, 931 winsy+1+menurows, rightwinx + 1 + padscols); 932 padsupdate = false; 933 } 934 935 input = getch(); 936 switch(input) { 937 case 10: // Enter 938 output = bs.value[bs.curr]; // -> buttvalues[selbutton] 939 loop = false; 940 break; 941 case 27: // Esc 942 output = BSDDIALOG_ERROR; 943 loop = false; 944 break; 945 case '\t': // TAB 946 bs.curr = (bs.curr + 1) % bs.nbuttons; 947 buttupdate = true; 948 break; 949 } 950 951 if (nitems <= 0) 952 continue; 953 954 switch(input) { 955 case KEY_LEFT: 956 if (currV == RIGHT && nrights > 0) { 957 currV = LEFT; 958 currH = 0; 959 padsupdate = true; 960 } 961 break; 962 case KEY_RIGHT: 963 if (currV == LEFT && nrights > 0) { 964 currV = RIGHT; 965 currH = 0; 966 padsupdate = true; 967 } 968 break; 969 case KEY_UP: 970 currH = (currH > 0) ? currH - 1 : 0; 971 padsupdate = true; 972 break; 973 case KEY_DOWN: 974 if (currV == LEFT) 975 currH = (currH < nlefts-1) ? currH +1 : currH; 976 else 977 currH = (currH < nrights-1)? currH +1 : currH; 978 padsupdate = true; 979 break; 980 case ' ': // Space 981 items[curr].on = ! items[curr].on; 982 if (currV == LEFT) { 983 if (nlefts > 1) 984 currH = currH > 0 ? currH-1 : 0; 985 else { 986 currH = 0; 987 currV = RIGHT; 988 } 989 } else { 990 if (nrights > 1) 991 currH = currH > 0 ? currH-1 : 0; 992 else { 993 currH = 0; 994 currV = LEFT; 995 } 996 } 997 padsupdate = true; 998 break; 999 default: 1000 1001 break; 1002 } 1003 } 1004 1005 if(focusitem != NULL) 1006 *focusitem = curr; 1007 1008 delwin(leftpad); 1009 delwin(leftwin); 1010 delwin(rightpad); 1011 delwin(rightwin); 1012 end_widget(conf, widget, rows, cols, shadow); 1013 1014 return output; 1015 } 1016