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