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
getmode(enum menumode mode,struct bsddialog_menugroup group)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
build_privatemenu(struct bsddialog_conf * conf,struct privatemenu * m,enum menumode mode,unsigned int ngroups,struct bsddialog_menugroup * groups)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
set_return_on(struct privatemenu * m,struct bsddialog_menugroup * groups)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
getprev(struct privateitem * pritems,int abs)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
getnext(int npritems,struct privateitem * pritems,int abs)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
getfirst_with_default(int npritems,struct privateitem * pritems,int ngroups,struct bsddialog_menugroup * groups,int * focusgroup,int * focusitem)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
getfastnext(int menurows,int npritems,struct privateitem * pritems,int abs)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
getfastprev(int menurows,struct privateitem * pritems,int abs)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
getnextshortcut(int npritems,struct privateitem * pritems,int abs,wint_t key)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
drawseparators(struct bsddialog_conf * conf,struct privatemenu * m)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
drawitem(struct bsddialog_conf * conf,struct privatemenu * m,int y,bool focus)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
update_menubox(struct bsddialog_conf * conf,struct privatemenu * m)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
menu_size_position(struct dialog * d,struct privatemenu * m)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
mixedlist_redraw(struct dialog * d,struct privatemenu * m)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
do_mixedlist(struct bsddialog_conf * conf,const char * text,int rows,int cols,unsigned int menurows,enum menumode mode,unsigned int ngroups,struct bsddialog_menugroup * groups,int * focuslist,int * focusitem)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
bsddialog_mixedlist(struct bsddialog_conf * conf,const char * text,int rows,int cols,unsigned int menurows,unsigned int ngroups,struct bsddialog_menugroup * groups,int * focuslist,int * focusitem)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
bsddialog_checklist(struct bsddialog_conf * conf,const char * text,int rows,int cols,unsigned int menurows,unsigned int nitems,struct bsddialog_menuitem * items,int * focusitem)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
bsddialog_menu(struct bsddialog_conf * conf,const char * text,int rows,int cols,unsigned int menurows,unsigned int nitems,struct bsddialog_menuitem * items,int * focusitem)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
bsddialog_radiolist(struct bsddialog_conf * conf,const char * text,int rows,int cols,unsigned int menurows,unsigned int nitems,struct bsddialog_menuitem * items,int * focusitem)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