1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2021-2024 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 <curses.h> 29 #include <stdlib.h> 30 31 #include "bsddialog.h" 32 #include "bsddialog_theme.h" 33 #include "lib_util.h" 34 35 enum menumode { 36 CHECKLISTMODE, 37 MENUMODE, 38 MIXEDLISTMODE, 39 RADIOLISTMODE, 40 SEPARATORMODE 41 }; 42 43 struct privateitem { 44 const char *prefix; 45 bool on; /* menu changes, not API on */ 46 unsigned int depth; 47 const char *name; 48 const char *desc; 49 const char *bottomdesc; 50 int group; /* index menu in menugroup */ 51 int index; /* real item index inside its menu */ 52 enum menumode type; 53 wchar_t shortcut; 54 }; 55 56 struct privatemenu { 57 WINDOW *box; /* only for borders */ 58 WINDOW *pad; /* pad for the private items */ 59 int ypad; /* start pad line */ 60 int ys, ye, xs, xe; /* pad pos */ 61 unsigned int xselector; /* [] */ 62 unsigned int xname; /* real x: xname + item.depth */ 63 unsigned int xdesc; /* real x: xdesc + item.depth */ 64 unsigned int line; /* wpad: prefix [] depth name desc */ 65 unsigned int apimenurows; 66 unsigned int menurows; /* real menurows after menu_size_position() */ 67 int nitems; /* total nitems (all groups * all items) */ 68 struct privateitem *pritems; 69 int sel; /* current focus item, can be -1 */ 70 bool hasbottomdesc; 71 }; 72 73 static enum menumode 74 getmode(enum menumode mode, struct bsddialog_menugroup group) 75 { 76 if (mode == MIXEDLISTMODE) { 77 if (group.type == BSDDIALOG_SEPARATOR) 78 mode = SEPARATORMODE; 79 else if (group.type == BSDDIALOG_RADIOLIST) 80 mode = RADIOLISTMODE; 81 else if (group.type == BSDDIALOG_CHECKLIST) 82 mode = CHECKLISTMODE; 83 } 84 85 return (mode); 86 } 87 88 static int 89 build_privatemenu(struct bsddialog_conf *conf, struct privatemenu *m, 90 enum menumode mode, unsigned int ngroups, 91 struct bsddialog_menugroup *groups) 92 { 93 bool onetrue; 94 int i, j, abs; 95 unsigned int maxsepstr, maxprefix, selectorlen, maxdepth; 96 unsigned int maxname, maxdesc; 97 struct bsddialog_menuitem *item; 98 struct privateitem *pritem; 99 100 /* nitems and fault checks */ 101 CHECK_ARRAY(ngroups, groups); 102 m->nitems = 0; 103 for (i = 0; i < (int)ngroups; i++) { 104 CHECK_ARRAY(groups[i].nitems, groups[i].items); 105 m->nitems += (int)groups[i].nitems; 106 } 107 108 /* alloc and set private items */ 109 m->pritems = calloc(m->nitems, sizeof (struct privateitem)); 110 if (m->pritems == NULL) 111 RETURN_ERROR("Cannot allocate memory for internal menu items"); 112 m->hasbottomdesc = false; 113 abs = 0; 114 for (i = 0; i < (int)ngroups; i++) { 115 onetrue = false; 116 for (j = 0; j < (int)groups[i].nitems; j++) { 117 item = &groups[i].items[j]; 118 pritem = &m->pritems[abs]; 119 120 if (getmode(mode, groups[i]) == MENUMODE) { 121 m->pritems[abs].on = false; 122 } else if (getmode(mode, groups[i]) == RADIOLISTMODE) { 123 m->pritems[abs].on = onetrue ? false : item->on; 124 if (m->pritems[abs].on) 125 onetrue = true; 126 } else { /* CHECKLISTMODE */ 127 m->pritems[abs].on = item->on; 128 } 129 pritem->group = i; 130 pritem->index = j; 131 pritem->type = getmode(mode, groups[i]); 132 133 pritem->prefix = CHECK_STR(item->prefix); 134 pritem->depth = item->depth; 135 pritem->name = CHECK_STR(item->name); 136 pritem->desc = CHECK_STR(item->desc); 137 pritem->bottomdesc = CHECK_STR(item->bottomdesc); 138 if (item->bottomdesc != NULL) 139 m->hasbottomdesc = true; 140 141 mbtowc(&pritem->shortcut, conf->menu.no_name ? 142 pritem->desc : pritem->name, MB_CUR_MAX); 143 144 abs++; 145 } 146 } 147 148 /* positions */ 149 m->xselector = m->xname = m->xdesc = m->line = 0; 150 maxsepstr = maxprefix = selectorlen = maxdepth = maxname = maxdesc = 0; 151 for (i = 0; i < m->nitems; i++) { 152 if (m->pritems[i].type == RADIOLISTMODE || 153 m->pritems[i].type == CHECKLISTMODE) 154 selectorlen = 4; 155 156 if (m->pritems[i].type == SEPARATORMODE) { 157 maxsepstr = MAX(maxsepstr, 158 strcols(m->pritems[i].name) + 159 strcols(m->pritems[i].desc)); 160 continue; 161 } 162 163 maxprefix = MAX(maxprefix, strcols(m->pritems[i].prefix)); 164 maxdepth = MAX(maxdepth, m->pritems[i].depth); 165 maxname = MAX(maxname, strcols(m->pritems[i].name)); 166 maxdesc = MAX(maxdesc, strcols(m->pritems[i].desc)); 167 } 168 maxname = conf->menu.no_name ? 0 : maxname; 169 maxdesc = conf->menu.no_desc ? 0 : maxdesc; 170 171 m->xselector = maxprefix + (maxprefix != 0 ? 1 : 0); 172 m->xname = m->xselector + selectorlen; 173 m->xdesc = maxdepth + m->xname + maxname; 174 m->xdesc += (maxname != 0 ? 1 : 0); 175 m->line = MAX(maxsepstr + 3, m->xdesc + maxdesc); 176 177 return (0); 178 } 179 180 static void 181 set_return_on(struct privatemenu *m, struct bsddialog_menugroup *groups) 182 { 183 int i; 184 struct privateitem *pritem; 185 186 for (i = 0; i < m->nitems; i++) { 187 if (m->pritems[i].type == SEPARATORMODE) 188 continue; 189 pritem = &m->pritems[i]; 190 groups[pritem->group].items[pritem->index].on = pritem->on; 191 } 192 } 193 194 static int getprev(struct privateitem *pritems, int abs) 195 { 196 int i; 197 198 for (i = abs - 1; i >= 0; i--) { 199 if (pritems[i].type == SEPARATORMODE) 200 continue; 201 return (i); 202 } 203 204 return (abs); 205 } 206 207 static int getnext(int npritems, struct privateitem *pritems, int abs) 208 { 209 int i; 210 211 for (i = abs + 1; i < npritems; i++) { 212 if (pritems[i].type == SEPARATORMODE) 213 continue; 214 return (i); 215 } 216 217 return (abs); 218 } 219 220 static int 221 getfirst_with_default(int npritems, struct privateitem *pritems, int ngroups, 222 struct bsddialog_menugroup *groups, int *focusgroup, int *focusitem) 223 { 224 int i, abs; 225 226 if ((abs = getnext(npritems, pritems, -1)) < 0) 227 return (abs); 228 229 if (focusgroup == NULL || focusitem == NULL) 230 return (abs); 231 if (*focusgroup < 0 || *focusgroup >= ngroups) 232 return (abs); 233 if (groups[*focusgroup].type == BSDDIALOG_SEPARATOR) 234 return (abs); 235 if (*focusitem < 0 || *focusitem >= (int)groups[*focusgroup].nitems) 236 return (abs); 237 238 for (i = abs; i < npritems; i++) { 239 if (pritems[i].group == *focusgroup && 240 pritems[i].index == *focusitem) 241 return (i); 242 } 243 244 return (abs); 245 } 246 247 static int 248 getfastnext(int menurows, int npritems, struct privateitem *pritems, int abs) 249 { 250 int a, start, i; 251 252 start = abs; 253 i = menurows; 254 do { 255 a = abs; 256 abs = getnext(npritems, pritems, abs); 257 i--; 258 } while (abs != a && abs < start + menurows && i > 0); 259 260 return (abs); 261 } 262 263 static int 264 getfastprev(int menurows, struct privateitem *pritems, int abs) 265 { 266 int a, start, i; 267 268 start = abs; 269 i = menurows; 270 do { 271 a = abs; 272 abs = getprev(pritems, abs); 273 i--; 274 } while (abs != a && abs > start - menurows && i > 0); 275 276 return (abs); 277 } 278 279 static int 280 getnextshortcut(int npritems, struct privateitem *pritems, int abs, wint_t key) 281 { 282 int i, next; 283 284 next = -1; 285 for (i = 0; i < npritems; i++) { 286 if (pritems[i].type == SEPARATORMODE) 287 continue; 288 if (pritems[i].shortcut == (wchar_t)key) { 289 if (i > abs) 290 return (i); 291 if (i < abs && next == -1) 292 next = i; 293 } 294 } 295 296 return (next != -1 ? next : abs); 297 } 298 299 static void drawseparators(struct bsddialog_conf *conf, struct privatemenu *m) 300 { 301 int i, realw, labellen; 302 const char *desc, *name; 303 304 for (i = 0; i < m->nitems; i++) { 305 if (m->pritems[i].type != SEPARATORMODE) 306 continue; 307 if (conf->no_lines == false) { 308 wattron(m->pad, t.menu.desccolor); 309 if (conf->ascii_lines) 310 mvwhline(m->pad, i, 0, '-', m->line); 311 else 312 mvwhline_set(m->pad, i, 0, WACS_HLINE, m->line); 313 wattroff(m->pad, t.menu.desccolor); 314 } 315 name = m->pritems[i].name; 316 desc = m->pritems[i].desc; 317 realw = m->xe - m->xs; 318 labellen = strcols(name) + strcols(desc) + 1; 319 wmove(m->pad, i, (labellen < realw) ? realw/2 - labellen/2 : 0); 320 wattron(m->pad, t.menu.sepnamecolor); 321 waddstr(m->pad, name); 322 wattroff(m->pad, t.menu.sepnamecolor); 323 if (strcols(name) > 0 && strcols(desc) > 0) 324 waddch(m->pad, ' '); 325 wattron(m->pad, t.menu.sepdesccolor); 326 waddstr(m->pad, desc); 327 wattroff(m->pad, t.menu.sepdesccolor); 328 } 329 } 330 331 static void 332 drawitem(struct bsddialog_conf *conf, struct privatemenu *m, int y, bool focus) 333 { 334 int colordesc, colorname, colorshortcut; 335 struct privateitem *pritem; 336 337 pritem = &m->pritems[y]; 338 339 /* prefix */ 340 wattron(m->pad, focus ? t.menu.f_prefixcolor : t.menu.prefixcolor); 341 mvwaddstr(m->pad, y, 0, pritem->prefix); 342 wattroff(m->pad, focus ? t.menu.f_prefixcolor : t.menu.prefixcolor); 343 344 /* selector */ 345 wmove(m->pad, y, m->xselector); 346 wattron(m->pad, focus ? t.menu.f_selectorcolor : t.menu.selectorcolor); 347 if (pritem->type == CHECKLISTMODE) 348 wprintw(m->pad, "[%c]", pritem->on ? 'X' : ' '); 349 if (pritem->type == RADIOLISTMODE) 350 wprintw(m->pad, "(%c)", pritem->on ? '*' : ' '); 351 wattroff(m->pad, focus ? t.menu.f_selectorcolor : t.menu.selectorcolor); 352 353 /* name */ 354 colorname = focus ? t.menu.f_namecolor : t.menu.namecolor; 355 if (conf->menu.no_name == false) { 356 wattron(m->pad, colorname); 357 mvwaddstr(m->pad, y, m->xname + pritem->depth, pritem->name); 358 wattroff(m->pad, colorname); 359 } 360 361 /* description */ 362 if (conf->menu.no_name) 363 colordesc = focus ? t.menu.f_namecolor : t.menu.namecolor; 364 else 365 colordesc = focus ? t.menu.f_desccolor : t.menu.desccolor; 366 367 if (conf->menu.no_desc == false) { 368 wattron(m->pad, colordesc); 369 if (conf->menu.no_name) 370 mvwaddstr(m->pad, y, m->xname + pritem->depth, 371 pritem->desc); 372 else 373 mvwaddstr(m->pad, y, m->xdesc, pritem->desc); 374 wattroff(m->pad, colordesc); 375 } 376 377 /* shortcut */ 378 if (conf->menu.shortcut_buttons == false) { 379 colorshortcut = focus ? 380 t.menu.f_shortcutcolor : t.menu.shortcutcolor; 381 wattron(m->pad, colorshortcut); 382 mvwaddwch(m->pad, y, m->xname + pritem->depth, pritem->shortcut); 383 wattroff(m->pad, colorshortcut); 384 } 385 386 /* bottom description */ 387 if (m->hasbottomdesc) { 388 move(SCREENLINES - 1, 2); 389 clrtoeol(); 390 if (focus) { 391 attron(t.menu.bottomdesccolor); 392 addstr(pritem->bottomdesc); 393 attroff(t.menu.bottomdesccolor); 394 refresh(); 395 } 396 } 397 } 398 399 static void update_menubox(struct bsddialog_conf *conf, struct privatemenu *m) 400 { 401 int h, w; 402 403 draw_borders(conf, m->box, LOWERED); 404 getmaxyx(m->box, h, w); 405 406 if (m->nitems > (int)m->menurows) { 407 wattron(m->box, t.dialog.arrowcolor); 408 if (m->ypad > 0) 409 mvwhline(m->box, 0, 2, UARROW(conf), 3); 410 411 if ((m->ypad + (int)m->menurows) < m->nitems) 412 mvwhline(m->box, h-1, 2, DARROW(conf), 3); 413 414 mvwprintw(m->box, h-1, w-6, "%3d%%", 415 100 * (m->ypad + m->menurows) / m->nitems); 416 wattroff(m->box, t.dialog.arrowcolor); 417 } 418 } 419 420 static int menu_size_position(struct dialog *d, struct privatemenu *m) 421 { 422 int htext, hmenu; 423 424 if (set_widget_size(d->conf, d->rows, d->cols, &d->h, &d->w) != 0) 425 return (BSDDIALOG_ERROR); 426 427 hmenu = (int)(m->menurows == BSDDIALOG_AUTOSIZE) ? 428 (int)m->nitems : (int)m->menurows; 429 hmenu += 2; /* menu borders */ 430 /* 431 * algo 1: notext = 1 (grows vertically). 432 * algo 2: notext = hmenu (grows horizontally, better for little term). 433 */ 434 if (set_widget_autosize(d->conf, d->rows, d->cols, &d->h, &d->w, 435 d->text, &htext, &d->bs, hmenu, m->line + 4) != 0) 436 return (BSDDIALOG_ERROR); 437 /* avoid menurows overflow and menurows becomes "at most menurows" */ 438 if (d->h - BORDERS - htext - HBUTTONS <= 2 /* menuborders */) 439 m->menurows = (m->nitems > 0) ? 1 : 0; /* widget_checksize() */ 440 else 441 m->menurows = MIN(d->h - BORDERS - htext - HBUTTONS, hmenu) - 2; 442 443 /* 444 * no minw=linelen to avoid big menu fault, then some col can be 445 * hidden (example portconfig www/apache24). 446 */ 447 if (widget_checksize(d->h, d->w, &d->bs, 448 2 /* border box */ + MIN(m->menurows, 1), 0) != 0) 449 return (BSDDIALOG_ERROR); 450 451 if (set_widget_position(d->conf, &d->y, &d->x, d->h, d->w) != 0) 452 return (BSDDIALOG_ERROR); 453 454 return (0); 455 } 456 457 static int mixedlist_redraw(struct dialog *d, struct privatemenu *m) 458 { 459 if (d->built) { 460 hide_dialog(d); 461 refresh(); /* Important for decreasing screen */ 462 } 463 m->menurows = m->apimenurows; 464 if (menu_size_position(d, m) != 0) 465 return (BSDDIALOG_ERROR); 466 if (draw_dialog(d) != 0) 467 return (BSDDIALOG_ERROR); 468 if (d->built) 469 refresh(); /* Important to fix grey lines expanding screen */ 470 TEXTPAD(d, 2/*bmenu*/ + m->menurows + HBUTTONS); 471 472 /* selected item in view*/ 473 if (m->ypad > m->sel && m->ypad > 0) 474 m->ypad = m->sel; 475 if ((int)(m->ypad + m->menurows) <= m->sel) 476 m->ypad = m->sel - m->menurows + 1; 477 /* lower pad after a terminal expansion */ 478 if (m->ypad > 0 && (m->nitems - m->ypad) < (int)m->menurows) 479 m->ypad = m->nitems - m->menurows; 480 481 update_box(d->conf, m->box, d->y + d->h - 5 - m->menurows, d->x + 2, 482 m->menurows+2, d->w-4, LOWERED); 483 update_menubox(d->conf, m); 484 wnoutrefresh(m->box); 485 486 m->ys = d->y + d->h - 5 - m->menurows + 1; 487 m->ye = d->y + d->h - 5 ; 488 if (d->conf->menu.align_left || (int)m->line > d->w - 6) { 489 m->xs = d->x + 3; 490 m->xe = m->xs + d->w - 7; 491 } else { /* center */ 492 m->xs = d->x + 3 + (d->w-6)/2 - m->line/2; 493 m->xe = m->xs + d->w - 5; 494 } 495 drawseparators(d->conf, m); /* uses xe - xs */ 496 pnoutrefresh(m->pad, m->ypad, 0, m->ys, m->xs, m->ye, m->xe); 497 498 return (0); 499 } 500 501 static int 502 do_mixedlist(struct bsddialog_conf *conf, const char *text, int rows, int cols, 503 unsigned int menurows, enum menumode mode, unsigned int ngroups, 504 struct bsddialog_menugroup *groups, int *focuslist, int *focusitem) 505 { 506 bool loop, changeitem; 507 int i, next, retval; 508 wint_t input; 509 struct privatemenu m; 510 struct dialog d; 511 512 if (prepare_dialog(conf, text, rows, cols, &d) != 0) 513 return (BSDDIALOG_ERROR); 514 set_buttons(&d, conf->menu.shortcut_buttons, OK_LABEL, CANCEL_LABEL); 515 if (d.conf->menu.no_name && d.conf->menu.no_desc) 516 RETURN_ERROR("Both conf.menu.no_name and conf.menu.no_desc"); 517 518 if (build_privatemenu(conf, &m, mode, ngroups, groups) != 0) 519 return (BSDDIALOG_ERROR); 520 521 if ((m.box = newwin(1, 1, 1, 1)) == NULL) 522 RETURN_ERROR("Cannot build WINDOW box menu"); 523 wbkgd(m.box, t.dialog.color); 524 m.pad = newpad(m.nitems, m.line); 525 wbkgd(m.pad, t.dialog.color); 526 527 for (i = 0; i < m.nitems; i++) 528 drawitem(conf, &m, i, false); 529 m.sel = getfirst_with_default(m.nitems, m.pritems, ngroups, groups, 530 focuslist, focusitem); 531 if (m.sel >= 0) 532 drawitem(d.conf, &m, m.sel, true); 533 m.ypad = 0; 534 m.apimenurows = menurows; 535 if (mixedlist_redraw(&d, &m) != 0) 536 return (BSDDIALOG_ERROR); 537 538 changeitem = false; 539 loop = true; 540 while (loop) { 541 doupdate(); 542 if (get_wch(&input) == ERR) 543 continue; 544 switch(input) { 545 case KEY_ENTER: 546 case 10: /* Enter */ 547 retval = BUTTONVALUE(d.bs); 548 if (m.sel >= 0 && m.pritems[m.sel].type == MENUMODE) 549 m.pritems[m.sel].on = true; 550 loop = false; 551 break; 552 case 27: /* Esc */ 553 if (conf->key.enable_esc) { 554 retval = BSDDIALOG_ESC; 555 if (m.sel >= 0 && 556 m.pritems[m.sel].type == MENUMODE) 557 m.pritems[m.sel].on = true; 558 loop = false; 559 } 560 break; 561 case '\t': /* TAB */ 562 case KEY_RIGHT: 563 d.bs.curr = (d.bs.curr + 1) % d.bs.nbuttons; 564 DRAW_BUTTONS(d); 565 break; 566 case KEY_LEFT: 567 d.bs.curr--; 568 if (d.bs.curr < 0) 569 d.bs.curr = d.bs.nbuttons - 1; 570 DRAW_BUTTONS(d); 571 break; 572 case KEY_F(1): 573 if (conf->key.f1_file == NULL && 574 conf->key.f1_message == NULL) 575 break; 576 if (f1help_dialog(conf) != 0) 577 return (BSDDIALOG_ERROR); 578 if (mixedlist_redraw(&d, &m) != 0) 579 return (BSDDIALOG_ERROR); 580 break; 581 case KEY_CTRL('l'): 582 case KEY_RESIZE: 583 if (mixedlist_redraw(&d, &m) != 0) 584 return (BSDDIALOG_ERROR); 585 break; 586 } 587 588 if (m.sel < 0) 589 continue; 590 switch(input) { 591 case KEY_HOME: 592 next = getnext(m.nitems, m.pritems, -1); 593 changeitem = next != m.sel; 594 break; 595 case '-': 596 case KEY_CTRL('p'): 597 case KEY_UP: 598 next = getprev(m.pritems, m.sel); 599 changeitem = next != m.sel; 600 break; 601 case KEY_PPAGE: 602 next = getfastprev(m.menurows, m.pritems, m.sel); 603 changeitem = next != m.sel; 604 break; 605 case KEY_END: 606 next = getprev(m.pritems, m.nitems); 607 changeitem = next != m.sel; 608 break; 609 case '+': 610 case KEY_CTRL('n'): 611 case KEY_DOWN: 612 next = getnext(m.nitems, m.pritems, m.sel); 613 changeitem = next != m.sel; 614 break; 615 case KEY_NPAGE: 616 next = getfastnext(m.menurows, m.nitems, m.pritems, m.sel); 617 changeitem = next != m.sel; 618 break; 619 case ' ': /* Space */ 620 if (m.pritems[m.sel].type == MENUMODE) { 621 retval = BUTTONVALUE(d.bs); 622 m.pritems[m.sel].on = true; 623 loop = false; 624 } else if (m.pritems[m.sel].type == CHECKLISTMODE) { 625 m.pritems[m.sel].on = !m.pritems[m.sel].on; 626 } else { /* RADIOLISTMODE */ 627 for (i = m.sel - m.pritems[m.sel].index; 628 i < m.nitems && 629 m.pritems[i].group == m.pritems[m.sel].group; 630 i++) { 631 if (i != m.sel && m.pritems[i].on) { 632 m.pritems[i].on = false; 633 drawitem(conf, &m, i, false); 634 } 635 } 636 m.pritems[m.sel].on = !m.pritems[m.sel].on; 637 } 638 drawitem(conf, &m, m.sel, true); 639 pnoutrefresh(m.pad, m.ypad, 0, m.ys, m.xs, m.ye, m.xe); 640 break; 641 default: 642 if (conf->menu.shortcut_buttons) { 643 if (shortcut_buttons(input, &d.bs)) { 644 DRAW_BUTTONS(d); 645 doupdate(); 646 retval = BUTTONVALUE(d.bs); 647 if (m.pritems[m.sel].type == MENUMODE) 648 m.pritems[m.sel].on = true; 649 loop = false; 650 } 651 break; 652 } 653 654 /* shourtcut items */ 655 next = getnextshortcut(m.nitems, m.pritems, m.sel, 656 input); 657 changeitem = next != m.sel; 658 } /* end switch get_wch() */ 659 660 if (changeitem) { 661 drawitem(conf, &m, m.sel, false); 662 m.sel = next; 663 drawitem(conf, &m, m.sel, true); 664 if (m.ypad > m.sel && m.ypad > 0) 665 m.ypad = m.sel; 666 if ((int)(m.ypad + m.menurows) <= m.sel) 667 m.ypad = m.sel - m.menurows + 1; 668 update_menubox(conf, &m); 669 wnoutrefresh(m.box); 670 pnoutrefresh(m.pad, m.ypad, 0, m.ys, m.xs, m.ye, m.xe); 671 changeitem = false; 672 } 673 } /* end while (loop) */ 674 675 set_return_on(&m, groups); 676 677 if (focuslist != NULL) 678 *focuslist = m.sel < 0 ? -1 : m.pritems[m.sel].group; 679 if (focusitem !=NULL) 680 *focusitem = m.sel < 0 ? -1 : m.pritems[m.sel].index; 681 682 if (m.hasbottomdesc && conf->clear) { 683 move(SCREENLINES - 1, 2); 684 clrtoeol(); 685 } 686 delwin(m.pad); 687 delwin(m.box); 688 end_dialog(&d); 689 free(m.pritems); 690 691 return (retval); 692 } 693 694 /* API */ 695 int 696 bsddialog_mixedlist(struct bsddialog_conf *conf, const char *text, int rows, 697 int cols, unsigned int menurows, unsigned int ngroups, 698 struct bsddialog_menugroup *groups, int *focuslist, int *focusitem) 699 { 700 int retval; 701 702 retval = do_mixedlist(conf, text, rows, cols, menurows, MIXEDLISTMODE, 703 ngroups, groups, focuslist, focusitem); 704 705 return (retval); 706 } 707 708 int 709 bsddialog_checklist(struct bsddialog_conf *conf, const char *text, int rows, 710 int cols, unsigned int menurows, unsigned int nitems, 711 struct bsddialog_menuitem *items, int *focusitem) 712 { 713 int retval, focuslist = 0; 714 struct bsddialog_menugroup group = { 715 BSDDIALOG_CHECKLIST /* unused */, nitems, items, 0}; 716 717 CHECK_ARRAY(nitems, items); /* efficiency, avoid do_mixedlist() */ 718 retval = do_mixedlist(conf, text, rows, cols, menurows, CHECKLISTMODE, 719 1, &group, &focuslist, focusitem); 720 721 return (retval); 722 } 723 724 int 725 bsddialog_menu(struct bsddialog_conf *conf, const char *text, int rows, 726 int cols, unsigned int menurows, unsigned int nitems, 727 struct bsddialog_menuitem *items, int *focusitem) 728 { 729 int retval, focuslist = 0; 730 struct bsddialog_menugroup group = { 731 BSDDIALOG_CHECKLIST /* unused */, nitems, items, 0}; 732 733 CHECK_ARRAY(nitems, items); /* efficiency, avoid do_mixedlist() */ 734 retval = do_mixedlist(conf, text, rows, cols, menurows, MENUMODE, 1, 735 &group, &focuslist, focusitem); 736 737 return (retval); 738 } 739 740 int 741 bsddialog_radiolist(struct bsddialog_conf *conf, const char *text, int rows, 742 int cols, unsigned int menurows, unsigned int nitems, 743 struct bsddialog_menuitem *items, int *focusitem) 744 { 745 int retval, focuslist = 0; 746 struct bsddialog_menugroup group = { 747 BSDDIALOG_RADIOLIST /* unused */, nitems, items, 0}; 748 749 CHECK_ARRAY(nitems, items); /* efficiency, avoid do_mixedlist() */ 750 retval = do_mixedlist(conf, text, rows, cols, menurows, RADIOLISTMODE, 751 1, &group, &focuslist, focusitem); 752 753 return (retval); 754 } 755