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
35enum menumode {
36	CHECKLISTMODE,
37	MENUMODE,
38	MIXEDLISTMODE,
39	RADIOLISTMODE,
40	SEPARATORMODE
41};
42
43struct 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
56struct 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
73static enum menumode
74getmode(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
88static int
89build_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
180static void
181set_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
194static 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
207static 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
220static int
221getfirst_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
247static int
248getfastnext(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
263static int
264getfastprev(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
279static int
280getnextshortcut(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
299static 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
331static void
332drawitem(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
399static 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
420static 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
457static 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
501static int
502do_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 */
695int
696bsddialog_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
708int
709bsddialog_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
724int
725bsddialog_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
740int
741bsddialog_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