xref: /freebsd/contrib/bsddialog/lib/menubox.c (revision a6d8be451f62d425b71a4874f7d4e133b9fb393c)
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