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 <curses.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 DEPTHSPACE 4 40 #define MIN_HEIGHT VBORDERS + 6 /* 2 buttons 1 text 3 menu */ 41 42 extern struct bsddialog_theme t; 43 44 enum menumode { 45 CHECKLISTMODE, 46 MENUMODE, 47 MIXEDLISTMODE, 48 RADIOLISTMODE, 49 SEPARATORMODE 50 }; 51 52 struct lineposition { 53 unsigned int maxsepstr; 54 unsigned int maxprefix; 55 unsigned int xselector; 56 unsigned int selectorlen; 57 unsigned int maxdepth; 58 unsigned int xname; 59 unsigned int maxname; 60 unsigned int xdesc; 61 unsigned int maxdesc; 62 unsigned int line; 63 }; 64 65 struct privateitem { 66 bool on; 67 int group; 68 int index; 69 enum menumode type; 70 struct bsddialog_menuitem *item; 71 }; 72 73 static void 74 set_on_output(struct bsddialog_conf *conf, int output, int ngroups, 75 struct bsddialog_menugroup *groups, struct privateitem *pritems) 76 { 77 int i, j, abs; 78 79 if (output != BSDDIALOG_OK && !conf->menu.on_without_ok) 80 return; 81 82 for(i = abs = 0; i < ngroups; i++) { 83 if (groups[i].type == BSDDIALOG_SEPARATOR) { 84 abs += groups[i].nitems; 85 continue; 86 } 87 88 for(j = 0; j < (int)groups[i].nitems; j++) { 89 groups[i].items[j].on = pritems[abs].on; 90 abs++; 91 } 92 } 93 } 94 95 static int getprev(struct privateitem *pritems, int abs) 96 { 97 int i; 98 99 for (i = abs - 1; i >= 0; i--) { 100 if (pritems[i].type == SEPARATORMODE) 101 continue; 102 return (i); 103 } 104 105 return (abs); 106 } 107 108 static int getnext(int npritems, struct privateitem *pritems, int abs) 109 { 110 int i; 111 112 for (i = abs + 1; i < npritems; i++) { 113 if (pritems[i].type == SEPARATORMODE) 114 continue; 115 return (i); 116 } 117 118 return (abs); 119 } 120 121 static int 122 getfirst_with_default(int npritems, struct privateitem *pritems, int ngroups, 123 struct bsddialog_menugroup *groups, int *focusgroup, int *focusitem) 124 { 125 int i, abs; 126 127 if ((abs = getnext(npritems, pritems, -1)) < 0) 128 return (abs); 129 130 if (focusgroup == NULL || focusitem == NULL) 131 return (abs); 132 if (*focusgroup < 0 || *focusgroup >= ngroups) 133 return (abs); 134 if (groups[*focusgroup].type == BSDDIALOG_SEPARATOR) 135 return (abs); 136 if (*focusitem < 0 || *focusitem >= (int)groups[*focusgroup].nitems) 137 return (abs); 138 139 for (i = abs; i < npritems; i++) { 140 if (pritems[i].group == *focusgroup && 141 pritems[i].index == *focusitem) 142 return (i); 143 } 144 145 return (abs); 146 } 147 148 static int 149 getfastnext(int menurows, int npritems, struct privateitem *pritems, int abs) 150 { 151 int a, start, i; 152 153 start = abs; 154 i = menurows; 155 do { 156 a = abs; 157 abs = getnext(npritems, pritems, abs); 158 i--; 159 } while (abs != a && abs < start + menurows && i > 0); 160 161 return (abs); 162 } 163 164 static int 165 getfastprev(int menurows, struct privateitem *pritems, int abs) 166 { 167 int a, start, i; 168 169 start = abs; 170 i = menurows; 171 do { 172 a = abs; 173 abs = getprev(pritems, abs); 174 i--; 175 } while (abs != a && abs > start - menurows && i > 0); 176 177 return (abs); 178 } 179 180 static int 181 getnextshortcut(struct bsddialog_conf *conf, int npritems, 182 struct privateitem *pritems, int abs, int key) 183 { 184 int i, ch, next; 185 186 next = -1; 187 for (i = 0; i < npritems; i++) { 188 if (pritems[i].type == SEPARATORMODE) 189 continue; 190 191 if (conf->menu.no_name) 192 ch = pritems[i].item->desc[0]; 193 else 194 ch = pritems[i].item->name[0]; 195 196 if (ch == key) { 197 if (i > abs) 198 return (i); 199 200 if (i < abs && next == -1) 201 next = i; 202 } 203 } 204 205 return (next != -1 ? next : abs); 206 } 207 208 static enum menumode 209 getmode(enum menumode mode, struct bsddialog_menugroup group) 210 { 211 if (mode == MIXEDLISTMODE) { 212 if (group.type == BSDDIALOG_SEPARATOR) 213 mode = SEPARATORMODE; 214 else if (group.type == BSDDIALOG_RADIOLIST) 215 mode = RADIOLISTMODE; 216 else if (group.type == BSDDIALOG_CHECKLIST) 217 mode = CHECKLISTMODE; 218 } 219 220 return (mode); 221 } 222 223 static void 224 drawitem(struct bsddialog_conf *conf, WINDOW *pad, int y, 225 struct lineposition pos, struct privateitem *pritem, bool focus) 226 { 227 int colordesc, colorname, colorshortcut, linech; 228 unsigned int depth; 229 enum menumode mode; 230 const char *prefix, *name, *desc, *bottomdesc, *shortcut; 231 232 prefix = pritem->item->prefix; 233 name = pritem->item->name; 234 depth = pritem->item->depth; 235 desc = pritem->item->desc; 236 bottomdesc = pritem->item->bottomdesc; 237 238 mode = pritem->type; 239 240 if (mode == SEPARATORMODE) { 241 if (conf->no_lines == false) { 242 wattron(pad, t.menu.desccolor); 243 linech = conf->ascii_lines ? '-' : ACS_HLINE; 244 mvwhline(pad, y, 0, linech, pos.line); 245 wattroff(pad, t.menu.desccolor); 246 } 247 wmove(pad, y, 248 pos.line/2 - (strlen(name) + strlen(desc)) / 2 ); 249 wattron(pad, t.menu.namesepcolor); 250 waddstr(pad, name); 251 wattroff(pad, t.menu.namesepcolor); 252 if (strlen(name) > 0 && strlen(desc) > 0) 253 waddch(pad, ' '); 254 wattron(pad, t.menu.descsepcolor); 255 waddstr(pad, desc); 256 wattroff(pad, t.menu.descsepcolor); 257 return; 258 } 259 260 /* prefix */ 261 if (prefix != NULL && prefix[0] != '\0') 262 mvwaddstr(pad, y, 0, prefix); 263 264 /* selector */ 265 wmove(pad, y, pos.xselector); 266 wattron(pad, t.menu.selectorcolor); 267 if (mode == CHECKLISTMODE) 268 wprintw(pad, "[%c]", pritem->on ? 'X' : ' '); 269 if (mode == RADIOLISTMODE) 270 wprintw(pad, "(%c)", pritem->on ? '*' : ' '); 271 wattroff(pad, t.menu.selectorcolor); 272 273 /* name */ 274 colorname = focus ? t.menu.f_namecolor : t.menu.namecolor; 275 if (conf->menu.no_name == false) { 276 wattron(pad, colorname); 277 mvwaddstr(pad, y, pos.xname + depth * DEPTHSPACE, name); 278 wattroff(pad, colorname); 279 } 280 281 /* description */ 282 if (conf->menu.no_name) 283 colordesc = focus ? t.menu.f_namecolor : t.menu.namecolor; 284 else 285 colordesc = focus ? t.menu.f_desccolor : t.menu.desccolor; 286 287 if (conf->menu.no_desc == false) { 288 wattron(pad, colordesc); 289 if (conf->menu.no_name) 290 mvwaddstr(pad, y, pos.xname + depth * DEPTHSPACE, desc); 291 else 292 mvwaddstr(pad, y, pos.xdesc, desc); 293 wattroff(pad, colordesc); 294 } 295 296 /* shortcut */ 297 if (conf->menu.shortcut_buttons == false) { 298 colorshortcut = focus ? 299 t.menu.f_shortcutcolor : t.menu.shortcutcolor; 300 wattron(pad, colorshortcut); 301 302 if (conf->menu.no_name) 303 shortcut = desc; 304 else 305 shortcut = name; 306 wmove(pad, y, pos.xname + depth * DEPTHSPACE); 307 if (shortcut != NULL && shortcut[0] != '\0') 308 waddch(pad, shortcut[0]); 309 wattroff(pad, colorshortcut); 310 } 311 312 /* bottom description */ 313 move(SCREENLINES - 1, 2); 314 clrtoeol(); 315 if (bottomdesc != NULL && focus) { 316 addstr(bottomdesc); 317 refresh(); 318 } 319 } 320 321 static int 322 menu_autosize(struct bsddialog_conf *conf, int rows, int cols, int *h, int *w, 323 const char *text, int linelen, unsigned int *menurows, int nitems, 324 struct buttons bs) 325 { 326 int htext, wtext, menusize, notext; 327 328 notext = 2; 329 if (*menurows == BSDDIALOG_AUTOSIZE) { 330 /* algo 1): grows vertically */ 331 /* notext = 1; */ 332 /* algo 2): grows horizontally, better with little terminals */ 333 notext += nitems; 334 notext = MIN(notext, widget_max_height(conf) - HBORDERS - 3); 335 } else 336 notext += *menurows; 337 338 if (cols == BSDDIALOG_AUTOSIZE || rows == BSDDIALOG_AUTOSIZE) { 339 if (text_size(conf, rows, cols, text, &bs, notext, linelen + 6, 340 &htext, &wtext) != 0) 341 return (BSDDIALOG_ERROR); 342 } 343 344 if (cols == BSDDIALOG_AUTOSIZE) 345 *w = widget_min_width(conf, wtext, linelen + 6, &bs); 346 347 if (rows == BSDDIALOG_AUTOSIZE) { 348 if (*menurows == 0) { 349 menusize = widget_max_height(conf) - HBORDERS - 350 2 /*buttons*/ - htext; 351 menusize = MIN(menusize, nitems + 2); 352 *menurows = menusize - 2 < 0 ? 0 : menusize - 2; 353 } 354 else /* h autosize with fixed menurows */ 355 menusize = *menurows + 2; 356 357 *h = widget_min_height(conf, htext, menusize, true); 358 /* 359 * avoid menurows overflow and 360 * with rows=AUTOSIZE menurows!=0 becomes max-menurows 361 */ 362 *menurows = MIN(*h - 6 - htext, (int)*menurows); 363 } else { 364 if (*menurows == 0) 365 *menurows = MIN(rows-6-htext, nitems); 366 } 367 368 return (0); 369 } 370 371 static int 372 menu_checksize(int rows, int cols, const char *text, int menurows, int nitems, 373 struct buttons bs) 374 { 375 int mincols, textrow, menusize; 376 377 mincols = VBORDERS; 378 /* buttons */ 379 mincols += bs.nbuttons * bs.sizebutton; 380 mincols += bs.nbuttons > 0 ? (bs.nbuttons-1) * t.button.space : 0; 381 /* 382 * linelen check, comment to allow some hidden col otherwise portconfig 383 * could not show big menus like www/apache24 384 */ 385 /* mincols = MAX(mincols, linelen); */ 386 387 if (cols < mincols) 388 RETURN_ERROR("Few cols, width < size buttons or " 389 "name + descripion of the items"); 390 391 textrow = text != NULL && strlen(text) > 0 ? 1 : 0; 392 393 if (nitems > 0 && menurows == 0) 394 RETURN_ERROR("items > 0 but menurows == 0, probably terminal " 395 "too small"); 396 397 menusize = nitems > 0 ? 3 : 0; 398 if (rows < 2 + 2 + menusize + textrow) 399 RETURN_ERROR("Few lines for this menus"); 400 401 return (0); 402 } 403 404 /* the caller has to call prefresh(menupad, ymenupad, 0, ys, xs, ye, xe); */ 405 static void 406 update_menuwin(struct bsddialog_conf *conf, WINDOW *menuwin, int h, int w, 407 int totnitems, unsigned int menurows, int ymenupad) 408 { 409 draw_borders(conf, menuwin, h, w, LOWERED); 410 411 if (totnitems > (int) menurows) { 412 wattron(menuwin, t.menu.arrowcolor); 413 414 if (ymenupad > 0) 415 mvwprintw(menuwin, 0, 2, "^^^"); 416 417 if ((int) (ymenupad + menurows) < totnitems) 418 mvwprintw(menuwin, h-1, 2, "vvv"); 419 420 wattroff(menuwin, t.menu.arrowcolor); 421 422 mvwprintw(menuwin, h-1, w-10, "%3d%%", 423 100 * (ymenupad + menurows) / totnitems); 424 } 425 } 426 427 static int 428 do_mixedlist(struct bsddialog_conf *conf, const char *text, int rows, int cols, 429 unsigned int menurows, enum menumode mode, unsigned int ngroups, 430 struct bsddialog_menugroup *groups, int *focuslist, int *focusitem) 431 { 432 bool loop, onetrue, movefocus, automenurows, shortcut_butts; 433 int i, j, y, x, h, w, output, input; 434 int ymenupad, ys, ye, xs, xe, abs, next, totnitems; 435 WINDOW *shadow, *widget, *textpad, *menuwin, *menupad; 436 struct buttons bs; 437 struct lineposition pos = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; 438 struct bsddialog_menuitem *item; 439 struct privateitem *pritems; 440 441 shortcut_butts = conf->menu.shortcut_buttons; 442 443 automenurows = menurows == BSDDIALOG_AUTOSIZE ? true : false; 444 445 totnitems = 0; 446 for (i = 0; i < (int)ngroups; i++) { 447 if (getmode(mode, groups[i]) == RADIOLISTMODE || 448 getmode(mode, groups[i]) == CHECKLISTMODE) 449 pos.selectorlen = 3; 450 451 for (j = 0; j < (int)groups[i].nitems; j++) { 452 totnitems++; 453 item = &groups[i].items[j]; 454 455 if (groups[i].type == BSDDIALOG_SEPARATOR) { 456 pos.maxsepstr = MAX(pos.maxsepstr, 457 strlen(item->name) + strlen(item->desc)); 458 continue; 459 } 460 461 pos.maxprefix = MAX(pos.maxprefix,strlen(item->prefix)); 462 pos.maxdepth = MAX(pos.maxdepth, item->depth); 463 pos.maxname = MAX(pos.maxname, strlen(item->name)); 464 pos.maxdesc = MAX(pos.maxdesc, strlen(item->desc)); 465 } 466 } 467 pos.maxname = conf->menu.no_name ? 0 : pos.maxname; 468 pos.maxdesc = conf->menu.no_desc ? 0 : pos.maxdesc; 469 pos.maxdepth *= DEPTHSPACE; 470 471 pos.xselector = pos.maxprefix + (pos.maxprefix != 0 ? 1 : 0); 472 pos.xname = pos.xselector + pos.selectorlen + 473 (pos.selectorlen > 0 ? 1 : 0); 474 pos.xdesc = pos.maxdepth + pos.xname + pos.maxname; 475 pos.xdesc += (pos.maxname != 0 ? 1 : 0); 476 pos.line = MAX(pos.maxsepstr + 3, pos.xdesc + pos.maxdesc); 477 478 479 get_buttons(conf, &bs, BUTTON_OK_LABEL, BUTTON_CANCEL_LABEL); 480 481 if (set_widget_size(conf, rows, cols, &h, &w) != 0) 482 return (BSDDIALOG_ERROR); 483 if (menu_autosize(conf, rows, cols, &h, &w, text, pos.line, &menurows, 484 totnitems, bs) != 0) 485 return (BSDDIALOG_ERROR); 486 if (menu_checksize(h, w, text, menurows, totnitems, bs) != 0) 487 return (BSDDIALOG_ERROR); 488 if (set_widget_position(conf, &y, &x, h, w) != 0) 489 return (BSDDIALOG_ERROR); 490 491 if (new_dialog(conf, &shadow, &widget, y, x, h, w, &textpad, text, &bs, 492 shortcut_butts) != 0) 493 return (BSDDIALOG_ERROR); 494 495 doupdate(); 496 497 prefresh(textpad, 0, 0, y + 1, x + 1 + TEXTHMARGIN, 498 y + h - menurows, x + 1 + w - TEXTHMARGIN); 499 500 menuwin = new_boxed_window(conf, y + h - 5 - menurows, x + 2, 501 menurows+2, w-4, LOWERED); 502 503 menupad = newpad(totnitems, pos.line); 504 wbkgd(menupad, t.dialog.color); 505 506 if ((pritems = calloc(totnitems, sizeof (struct privateitem))) == NULL) 507 RETURN_ERROR("Cannot allocate memory for internal menu items"); 508 509 abs = 0; 510 for (i = 0; i < (int)ngroups; i++) { 511 onetrue = false; 512 for (j = 0; j < (int)groups[i].nitems; j++) { 513 item = &groups[i].items[j]; 514 515 if (getmode(mode, groups[i]) == MENUMODE) { 516 pritems[abs].on = false; 517 } else if (getmode(mode, groups[i]) == RADIOLISTMODE) { 518 pritems[abs].on = onetrue ? false : item->on; 519 if (pritems[abs].on) 520 onetrue = true; 521 } else { 522 pritems[abs].on = item->on; 523 } 524 pritems[abs].group = i; 525 pritems[abs].index = j; 526 pritems[abs].type = getmode(mode, groups[i]); 527 pritems[abs].item = item; 528 529 drawitem(conf, menupad, abs, pos, &pritems[abs], false); 530 abs++; 531 } 532 } 533 abs = getfirst_with_default(totnitems, pritems, ngroups, groups, 534 focuslist, focusitem); 535 if (abs >= 0) 536 drawitem(conf, menupad, abs, pos, &pritems[abs], true); 537 538 ys = y + h - 5 - menurows + 1; 539 ye = y + h - 5 ; 540 if (conf->menu.align_left || (int)pos.line > w - 6) { 541 xs = x + 3; 542 xe = xs + w - 7; 543 } else { /* center */ 544 xs = x + 3 + (w-6)/2 - pos.line/2; 545 xe = xs + w - 5; 546 } 547 548 ymenupad = 0; 549 if ((int)(ymenupad + menurows) - 1 < abs) 550 ymenupad = abs - menurows + 1; 551 update_menuwin(conf, menuwin, menurows+2, w-4, totnitems, menurows, 552 ymenupad); 553 wrefresh(menuwin); 554 prefresh(menupad, ymenupad, 0, ys, xs, ye, xe); 555 556 movefocus = false; 557 loop = true; 558 while (loop) { 559 input = getch(); 560 switch(input) { 561 case KEY_ENTER: 562 case 10: /* Enter */ 563 output = bs.value[bs.curr]; 564 if (abs >= 0 && pritems[abs].type == MENUMODE) 565 pritems[abs].on = true; 566 set_on_output(conf, output, ngroups, groups, pritems); 567 loop = false; 568 break; 569 case 27: /* Esc */ 570 if (conf->key.enable_esc) { 571 output = BSDDIALOG_ESC; 572 if (abs >= 0 && pritems[abs].type == MENUMODE) 573 pritems[abs].on = true; 574 set_on_output(conf, output, ngroups, groups, 575 pritems); 576 loop = false; 577 } 578 break; 579 case '\t': /* TAB */ 580 bs.curr = (bs.curr + 1) % bs.nbuttons; 581 draw_buttons(widget, bs, shortcut_butts); 582 wrefresh(widget); 583 break; 584 case KEY_LEFT: 585 if (bs.curr > 0) { 586 bs.curr--; 587 draw_buttons(widget, bs, shortcut_butts); 588 wrefresh(widget); 589 } 590 break; 591 case KEY_RIGHT: 592 if (bs.curr < (int) bs.nbuttons - 1) { 593 bs.curr++; 594 draw_buttons(widget, bs, shortcut_butts); 595 wrefresh(widget); 596 } 597 break; 598 case KEY_F(1): 599 if (conf->f1_file == NULL && conf->f1_message == NULL) 600 break; 601 if (f1help(conf) != 0) 602 return (BSDDIALOG_ERROR); 603 /* No break, screen size can change */ 604 case KEY_RESIZE: 605 /* Important for decreasing screen */ 606 hide_widget(y, x, h, w, conf->shadow); 607 refresh(); 608 609 if (set_widget_size(conf, rows, cols, &h, &w) != 0) 610 return (BSDDIALOG_ERROR); 611 menurows = automenurows ? 0 : menurows; 612 if (menu_autosize(conf, rows, cols, &h, &w, text, 613 pos.line, &menurows, totnitems, bs) != 0) 614 return (BSDDIALOG_ERROR); 615 if (menu_checksize(h, w, text, menurows, totnitems, 616 bs) != 0) 617 return (BSDDIALOG_ERROR); 618 if (set_widget_position(conf, &y, &x, h, w) != 0) 619 return (BSDDIALOG_ERROR); 620 621 if (update_dialog(conf, shadow, widget, y, x, h, w, 622 textpad, text, &bs, shortcut_butts) != 0) 623 return (BSDDIALOG_ERROR); 624 625 doupdate(); 626 627 prefresh(textpad, 0, 0, y + 1, x + 1 + TEXTHMARGIN, 628 y + h - menurows, x + 1 + w - TEXTHMARGIN); 629 630 wclear(menuwin); 631 mvwin(menuwin, y + h - 5 - menurows, x + 2); 632 wresize(menuwin,menurows+2, w-4); 633 update_menuwin(conf, menuwin, menurows+2, w-4, 634 totnitems, menurows, ymenupad); 635 wrefresh(menuwin); 636 637 ys = y + h - 5 - menurows + 1; 638 ye = y + h - 5 ; 639 if (conf->menu.align_left || (int)pos.line > w - 6) { 640 xs = x + 3; 641 xe = xs + w - 7; 642 } else { /* center */ 643 xs = x + 3 + (w-6)/2 - pos.line/2; 644 xe = xs + w - 5; 645 } 646 647 if ((int)(ymenupad + menurows) - 1 < abs) 648 ymenupad = abs - menurows + 1; 649 prefresh(menupad, ymenupad, 0, ys, xs, ye, xe); 650 651 refresh(); 652 653 break; 654 } 655 656 if (abs < 0) 657 continue; 658 switch(input) { 659 case KEY_HOME: 660 next = getnext(totnitems, pritems, -1); 661 movefocus = next != abs; 662 break; 663 case KEY_UP: 664 next = getprev(pritems, abs); 665 movefocus = next != abs; 666 break; 667 case KEY_PPAGE: 668 next = getfastprev(menurows, pritems, abs); 669 movefocus = next != abs; 670 break; 671 case KEY_END: 672 next = getprev(pritems, totnitems); 673 movefocus = next != abs; 674 break; 675 case KEY_DOWN: 676 next = getnext(totnitems, pritems, abs); 677 movefocus = next != abs; 678 break; 679 case KEY_NPAGE: 680 next = getfastnext(menurows, totnitems, pritems, abs); 681 movefocus = next != abs; 682 break; 683 case ' ': /* Space */ 684 if (pritems[abs].type == MENUMODE) 685 break; 686 else if (pritems[abs].type == CHECKLISTMODE) 687 pritems[abs].on = !pritems[abs].on; 688 else { /* RADIOLISTMODE */ 689 for (i = abs - pritems[abs].index; 690 i < totnitems && 691 pritems[i].group == pritems[abs].group; 692 i++) { 693 if (i != abs && pritems[i].on) { 694 pritems[i].on = false; 695 drawitem(conf, menupad, i, pos, 696 &pritems[i], false); 697 } 698 } 699 pritems[abs].on = !pritems[abs].on; 700 } 701 drawitem(conf, menupad, abs, pos, &pritems[abs], true); 702 prefresh(menupad, ymenupad, 0, ys, xs, ye, xe); 703 break; 704 default: 705 if (shortcut_butts) { 706 if (shortcut_buttons(input, &bs)) { 707 output = bs.value[bs.curr]; 708 if (pritems[abs].type == MENUMODE) 709 pritems[abs].on = true; 710 set_on_output(conf, output, ngroups, 711 groups, pritems); 712 loop = false; 713 } 714 break; 715 } 716 717 /* shourtcut items */ 718 next = getnextshortcut(conf, totnitems, pritems, abs, 719 input); 720 movefocus = next != abs; 721 } 722 723 if (movefocus) { 724 drawitem(conf, menupad, abs, pos, &pritems[abs], false); 725 abs = next; 726 drawitem(conf, menupad, abs, pos, &pritems[abs], true); 727 if (ymenupad > abs && ymenupad > 0) 728 ymenupad = abs; 729 if ((int)(ymenupad + menurows) <= abs) 730 ymenupad = abs - menurows + 1; 731 update_menuwin(conf, menuwin, menurows+2, w-4, 732 totnitems, menurows, ymenupad); 733 wrefresh(menuwin); 734 prefresh(menupad, ymenupad, 0, ys, xs, ye, xe); 735 movefocus = false; 736 } 737 } 738 739 if (focuslist != NULL) 740 *focuslist = abs < 0 ? -1 : pritems[abs].group; 741 if (focusitem !=NULL) 742 *focusitem = abs < 0 ? -1 : pritems[abs].index; 743 744 delwin(menupad); 745 delwin(menuwin); 746 end_dialog(conf, shadow, widget, textpad); 747 free(pritems); 748 749 return (output); 750 } 751 752 /* API */ 753 int 754 bsddialog_mixedlist(struct bsddialog_conf *conf, const char *text, int rows, 755 int cols, unsigned int menurows, unsigned int ngroups, 756 struct bsddialog_menugroup *groups, int *focuslist, int *focusitem) 757 { 758 int output; 759 760 output = do_mixedlist(conf, text, rows, cols, menurows, MIXEDLISTMODE, 761 ngroups, groups, focuslist, focusitem); 762 763 return (output); 764 } 765 766 int 767 bsddialog_checklist(struct bsddialog_conf *conf, const char *text, int rows, 768 int cols, unsigned int menurows, unsigned int nitems, 769 struct bsddialog_menuitem *items, int *focusitem) 770 { 771 int output, focuslist = 0; 772 struct bsddialog_menugroup group = { 773 BSDDIALOG_CHECKLIST /* unused */, nitems, items}; 774 775 output = do_mixedlist(conf, text, rows, cols, menurows, CHECKLISTMODE, 776 1, &group, &focuslist, focusitem); 777 778 return (output); 779 } 780 781 int 782 bsddialog_menu(struct bsddialog_conf *conf, const char *text, int rows, 783 int cols, unsigned int menurows, unsigned int nitems, 784 struct bsddialog_menuitem *items, int *focusitem) 785 { 786 int output, focuslist = 0; 787 struct bsddialog_menugroup group = { 788 BSDDIALOG_CHECKLIST /* unused */, nitems, items}; 789 790 output = do_mixedlist(conf, text, rows, cols, menurows, MENUMODE, 1, 791 &group, &focuslist, focusitem); 792 793 return (output); 794 } 795 796 int 797 bsddialog_radiolist(struct bsddialog_conf *conf, const char *text, int rows, 798 int cols, unsigned int menurows, unsigned int nitems, 799 struct bsddialog_menuitem *items, int *focusitem) 800 { 801 int output, focuslist = 0; 802 struct bsddialog_menugroup group = { 803 BSDDIALOG_RADIOLIST /* unused */, nitems, items}; 804 805 output = do_mixedlist(conf, text, rows, cols, menurows, RADIOLISTMODE, 806 1, &group, &focuslist, focusitem); 807 808 return (output); 809 }