xref: /freebsd/contrib/bsddialog/lib/menubox.c (revision bf2fa8d9d11c9f2ceff09bacc406876fa37096be)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2021 Alfonso Sabato Siciliano
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <sys/param.h>
29 
30 #include <ctype.h>
31 #include <string.h>
32 
33 #ifdef PORTNCURSES
34 #include <ncurses/curses.h>
35 #else
36 #include <curses.h>
37 #endif
38 
39 #include "bsddialog.h"
40 #include "lib_util.h"
41 #include "bsddialog_theme.h"
42 
43 /* "Menu": checklist - menu - mixedlist - radiolist - treeview - buildlist */
44 
45 #define DEPTHSPACE	4
46 #define MIN_HEIGHT	VBORDERS + 6 /* 2 buttons 1 text 3 menu */
47 
48 extern struct bsddialog_theme t;
49 
50 enum menumode {
51 	BUILDLISTMODE,
52 	CHECKLISTMODE,
53 	MENUMODE,
54 	MIXEDLISTMODE,
55 	RADIOLISTMODE,
56 	SEPARATORMODE
57 };
58 
59 struct lineposition {
60 	unsigned int maxsepstr;
61 	unsigned int maxprefix;
62 	unsigned int xselector;
63 	unsigned int selectorlen;
64 	unsigned int maxdepth;
65 	unsigned int xname;
66 	unsigned int maxname;
67 	unsigned int xdesc;
68 	unsigned int maxdesc;
69 	unsigned int line;
70 };
71 
72 static int checkradiolist(int nitems, struct bsddialog_menuitem *items)
73 {
74 	int i, error;
75 
76 	error = 0;
77 	for (i=0; i<nitems; i++) {
78 		if (error > 0)
79 			items[i].on = false;
80 
81 		if (items[i].on == true)
82 			error++;
83 	}
84 
85 	return (error == 0 ? 0 : -1);
86 }
87 
88 static int checkmenu(int nitems, struct bsddialog_menuitem *items) // useful?
89 {
90 	int i, error;
91 
92 	error = 0;
93 	for (i=0; i<nitems; i++) {
94 		if (items[i].on == true)
95 			error++;
96 
97 		items[i].on = false;
98 	}
99 
100 	return (error == 0 ? 0 : -1);
101 }
102 
103 static void
104 getfirst(int ngroups, struct bsddialog_menugroup *groups, int *abs, int *group,
105     int *rel)
106 {
107 	int i, a;
108 
109 	*abs = *rel = *group = -1;
110 	a = 0;
111 	for (i=0; i<ngroups; i++) {
112 		if (groups[i].type == BSDDIALOG_SEPARATOR) {
113 			a += groups[i].nitems;
114 			continue;
115 		}
116 		if (groups[i].nitems != 0) {
117 			*group = i;
118 			*abs = a;
119 			*rel = 0;
120 			break;
121 		}
122 	}
123 }
124 
125 static void
126 getfirst_with_default(struct bsddialog_conf conf, int ngroups,
127     struct bsddialog_menugroup *groups, int *abs, int *group, int *rel)
128 {
129 	int i, j, a;
130 	struct bsddialog_menuitem *item;
131 
132 	getfirst(ngroups, groups, abs, group, rel);
133 	if (*abs < 0)
134 		return;
135 
136 	a = *abs;
137 
138 	for (i=*group; i<ngroups; i++) {
139 		if (groups[i].type == BSDDIALOG_SEPARATOR) {
140 			a += groups[i].nitems;
141 			continue;
142 		}
143 		for (j = 0; j < (int) groups[i].nitems; j++) {
144 			item = &groups[i].items[j];
145 			if (conf.menu.default_item != NULL && item->name != NULL) {
146 				if (strcmp(item->name, conf.menu.default_item) == 0) {
147 					*abs = a;
148 					*group = i;
149 					*rel = j;
150 					return;
151 				}
152 			}
153 			a++;
154 		}
155 	}
156 }
157 
158 static void
159 getlast(int totnitems, int ngroups, struct bsddialog_menugroup *groups,
160     int *abs, int *group, int *rel)
161 {
162 	int i, a;
163 
164 	a = totnitems - 1;
165 	for (i = ngroups-1; i>=0; i--) {
166 		if (groups[i].type == BSDDIALOG_SEPARATOR) {
167 			a -= groups[i].nitems;
168 			continue;
169 		}
170 		if (groups[i].nitems != 0) {
171 			*group = i;
172 			*abs = a;
173 			*rel = groups[i].nitems - 1;
174 			break;
175 		}
176 	}
177 }
178 
179 static void
180 getnext(int ngroups, struct bsddialog_menugroup *groups, int *abs, int *group,
181     int *rel)
182 {
183 	int i, a;
184 
185 	if (*abs < 0 || *group < 0 || *rel < 0)
186 		return;
187 
188 	if (*rel + 1 < (int) groups[*group].nitems) {
189 		*rel = *rel + 1;
190 		*abs = *abs + 1;
191 		return;
192 	}
193 
194 	if (*group + 1 > ngroups)
195 		return;
196 
197 	a = *abs;
198 	for (i = *group + 1; i < ngroups; i++) {
199 		if (groups[i].type == BSDDIALOG_SEPARATOR) {
200 			a += groups[i].nitems;
201 			continue;
202 		}
203 		if (groups[i].nitems != 0) {
204 			*group = i;
205 			*abs = a + 1;
206 			*rel = 0;
207 			break;
208 		}
209 	}
210 }
211 
212 static void
213 getfastnext(int menurows, int ngroups, struct bsddialog_menugroup *groups,
214     int *abs, int *group, int *rel)
215 {
216 	int a, start, i;
217 
218 	start = *abs;
219 	i = menurows;
220 	do {
221 		a = *abs;
222 		getnext(ngroups, groups, abs, group, rel);
223 		i--;
224 	} while (*abs != a && *abs < start + menurows && i > 0);
225 }
226 
227 static void
228 getprev(struct bsddialog_menugroup *groups, int *abs, int *group, int *rel)
229 {
230 	int i, a;
231 
232 	if (*abs < 0 || *group < 0 || *rel < 0)
233 		return;
234 
235 	if (*rel > 0) {
236 		*rel = *rel - 1;
237 		*abs = *abs - 1;
238 		return;
239 	}
240 
241 	if (*group - 1 < 0)
242 		return;
243 
244 	a = *abs;
245 	for (i = *group - 1; i >= 0; i--) {
246 		if (groups[i].type == BSDDIALOG_SEPARATOR) {
247 			a -= (int) groups[i].nitems;
248 			continue;
249 		}
250 		if (groups[i].nitems != 0) {
251 			*group = i;
252 			*abs = a - 1;
253 			*rel = (int) groups[i].nitems - 1;
254 			break;
255 		}
256 	}
257 }
258 
259 static void
260 getfastprev(int menurows, struct bsddialog_menugroup *groups, int *abs,
261     int *group, int *rel)
262 {
263 	int a, start, i;
264 
265 	start = *abs;
266 	i = menurows;
267 	do {
268 		a = *abs;
269 		getprev(groups, abs, group, rel);
270 		i--;
271 	} while (*abs != a && *abs > start - menurows && i > 0);
272 }
273 
274 static enum menumode
275 getmode(enum menumode mode, struct bsddialog_menugroup group)
276 {
277 
278 	if (mode == MIXEDLISTMODE) {
279 		if (group.type == BSDDIALOG_SEPARATOR)
280 			mode = SEPARATORMODE;
281 		else if (group.type == BSDDIALOG_RADIOLIST)
282 			mode = RADIOLISTMODE;
283 		else if (group.type == BSDDIALOG_CHECKLIST)
284 			mode = CHECKLISTMODE;
285 	}
286 
287 	return mode;
288 }
289 
290 static void
291 drawitem(struct bsddialog_conf conf, WINDOW *pad, int y,
292     struct bsddialog_menuitem item, enum menumode mode, struct lineposition pos,
293     bool curr)
294 {
295 	int color, colorname, linech;
296 
297 	color = curr ? t.curritemcolor : t.itemcolor;
298 	colorname = curr ? t.currtagcolor : t.tagcolor;
299 
300 	if (mode == SEPARATORMODE) {
301 		if (conf.no_lines == false) {
302 			wattron(pad, t.itemcolor);
303 			linech = conf.ascii_lines ? '-' : ACS_HLINE;
304 			mvwhline(pad, y, 0, linech, pos.line);
305 			wattroff(pad, t.itemcolor);
306 		}
307 		wmove(pad, y, pos.line/2 - (strlen(item.name)+strlen(item.desc))/2);
308 		wattron(pad, t.namesepcolor);
309 		waddstr(pad, item.name);
310 		wattroff(pad, t.namesepcolor);
311 		if (strlen(item.name) > 0 && strlen(item.desc) > 0)
312 			waddch(pad, ' ');
313 		wattron(pad, t.descsepcolor);
314 		waddstr(pad, item.desc);
315 		wattroff(pad, t.descsepcolor);
316 		return;
317 	}
318 
319 	/* prefix */
320 	if (item.prefix != NULL && item.prefix[0] != '\0')
321 		mvwaddstr(pad, y, 0, item.prefix);
322 
323 	/* selector */
324 	wmove(pad, y, pos.xselector);
325 	wattron(pad, color);
326 	if (mode == CHECKLISTMODE)
327 		wprintw(pad, "[%c]", item.on ? 'X' : ' ');
328 	if (mode == RADIOLISTMODE)
329 		wprintw(pad, "(%c)", item.on ? '*' : ' ');
330 	wattroff(pad, color);
331 
332 	/* name */
333 	if (mode != BUILDLISTMODE && conf.menu.no_tags == false) {
334 		wattron(pad, colorname);
335 		mvwaddstr(pad, y, pos.xname + item.depth * DEPTHSPACE, item.name);
336 		wattroff(pad, colorname);
337 	}
338 
339 	/* description */
340 	if (conf.menu.no_items == false) {
341 		if ((mode == BUILDLISTMODE || conf.menu.no_tags) && curr == false)
342 			color = item.on ? t.tagcolor : t.itemcolor;
343 		wattron(pad, color);
344 		if (conf.menu.no_tags)
345 			mvwaddstr(pad, y, pos.xname + item.depth * DEPTHSPACE, item.desc);
346 		else
347 			mvwaddstr(pad, y, pos.xdesc, item.desc);
348 		wattroff(pad, color);
349 	}
350 
351 	/* bottom desc (item help) */
352 	if (item.bottomdesc != NULL && item.bottomdesc[0] != '\0') {
353 		move(LINES-1, 2);
354 		clrtoeol();
355 		addstr(item.bottomdesc);
356 
357 		refresh();
358 	}
359 }
360 
361 static void
362 menu_autosize(struct bsddialog_conf conf, int rows, int cols, int *h, int *w,
363     char *text, int linelen, unsigned int *menurows, int nitems,
364     struct buttons bs)
365 {
366 	int textrow, menusize;
367 
368 	textrow = text != NULL && strlen(text) > 0 ? 1 : 0;
369 
370 	if (cols == BSDDIALOG_AUTOSIZE) {
371 		*w = VBORDERS;
372 		/* buttons size */
373 		*w += bs.nbuttons * bs.sizebutton;
374 		*w += bs.nbuttons > 0 ? (bs.nbuttons-1) * t.buttonspace : 0;
375 		/* line size */
376 		*w = MAX(*w, linelen + 6);
377 		/*
378 		* avoid terminal overflow,
379 		* -1 fix false negative with big menu over the terminal and
380 		* autosize, for example "portconfig /usr/ports/www/apache24/".
381 		*/
382 		*w = MIN(*w, widget_max_width(conf)-1);
383 	}
384 
385 	if (rows == BSDDIALOG_AUTOSIZE) {
386 		*h = HBORDERS + 2 /* buttons */ + textrow;
387 
388 		if (*menurows == 0) {
389 			*h += nitems + 2;
390 			*h = MIN(*h, widget_max_height(conf));
391 			menusize = MIN(nitems + 2, *h - (HBORDERS + 2 + textrow));
392 			menusize -=2;
393 			*menurows = menusize < 0 ? 0 : menusize;
394 		}
395 		else /* h autosize with a fixed menurows */
396 			*h = *h + *menurows + 2;
397 
398 		/* avoid terminal overflow */
399 		*h = MIN(*h, widget_max_height(conf));
400 	}
401 	else {
402 		if (*menurows == 0)
403 			*menurows = MIN(rows-6-textrow, nitems);
404 	}
405 }
406 
407 static int
408 menu_checksize(int rows, int cols, char *text, int menurows, int nitems,
409     struct buttons bs)
410 {
411 	int mincols, textrow, menusize;
412 
413 	mincols = VBORDERS;
414 	/* buttons */
415 	mincols += bs.nbuttons * bs.sizebutton;
416 	mincols += bs.nbuttons > 0 ? (bs.nbuttons-1) * t.buttonspace : 0;
417 	/* line, comment to permet some cols hidden */
418 	/* mincols = MAX(mincols, linelen); */
419 
420 	if (cols < mincols)
421 		RETURN_ERROR("Few cols, width < size buttons or "\
422 		    "name+descripion of the items");
423 
424 	textrow = text != NULL && strlen(text) > 0 ? 1 : 0;
425 
426 	if (nitems > 0 && menurows == 0)
427 		RETURN_ERROR("items > 0 but menurows == 0, probably terminal "\
428 		    "too small");
429 
430 	menusize = nitems > 0 ? 3 : 0;
431 	if (rows < 2  + 2 + menusize + textrow)
432 		RETURN_ERROR("Few lines for this menus");
433 
434 	return 0;
435 }
436 
437 /* the caller has to call prefresh(menupad, ymenupad, 0, ys, xs, ye, xe); */
438 static void
439 update_menuwin(struct bsddialog_conf conf, WINDOW *menuwin, int h, int w,
440     int totnitems, unsigned int menurows, int ymenupad)
441 {
442 
443 	if (totnitems > (int) menurows) {
444 		draw_borders(conf, menuwin, h, w, LOWERED);
445 
446 		if (ymenupad > 0) {
447 			wattron(menuwin, t.lineraisecolor);
448 			mvwprintw(menuwin, 0, 2, "^^");
449 			wattroff(menuwin, t.lineraisecolor);
450 		}
451 		if ((int) (ymenupad + menurows) < totnitems) {
452 			wattron(menuwin, t.linelowercolor);
453 			mvwprintw(menuwin, h-1, 2, "vv");
454 			wattroff(menuwin, t.linelowercolor);
455 		}
456 
457 		mvwprintw(menuwin, h-1, w-10, "%3d%%",
458 		    100 * (ymenupad + menurows) / totnitems);
459 	}
460 }
461 
462 static int
463 do_mixedlist(struct bsddialog_conf conf, char* text, int rows, int cols,
464     unsigned int menurows, enum menumode mode, int ngroups,
465     struct bsddialog_menugroup *groups, int *focuslist, int *focusitem)
466 {
467 	WINDOW  *shadow, *widget, *textpad, *menuwin, *menupad;
468 	int i, j, y, x, h, w, htextpad, output, input;
469 	int ymenupad, ys, ye, xs, xe, abs, g, rel, totnitems;
470 	bool loop, automenurows;
471 	struct buttons bs;
472 	struct bsddialog_menuitem *item;
473 	enum menumode currmode;
474 	struct lineposition pos = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
475 
476 	automenurows = menurows == BSDDIALOG_AUTOSIZE ? true : false;
477 
478 	totnitems = 0;
479 	for (i=0; i < ngroups; i++) {
480 		currmode = getmode(mode, groups[i]);
481 		if (currmode == RADIOLISTMODE)
482 			checkradiolist(groups[i].nitems, groups[i].items);
483 
484 		if (currmode == MENUMODE)
485 			checkmenu(groups[i].nitems, groups[i].items);
486 
487 		if (currmode == RADIOLISTMODE || currmode == CHECKLISTMODE)
488 			pos.selectorlen = 3;
489 
490 		for (j=0; j < (int) groups[i].nitems; j++) {
491 			totnitems++;
492 			item = &groups[i].items[j];
493 
494 			if (groups[i].type == BSDDIALOG_SEPARATOR) {
495 				pos.maxsepstr = MAX(pos.maxsepstr,
496 				    strlen(item->name) + strlen(item->desc));
497 				continue;
498 			}
499 
500 			pos.maxprefix = MAX(pos.maxprefix, strlen(item->prefix));
501 			pos.maxdepth  = MAX((int) pos.maxdepth, item->depth);
502 			pos.maxname   = MAX(pos.maxname, strlen(item->name));
503 			pos.maxdesc   = MAX(pos.maxdesc, strlen(item->desc));
504 		}
505 	}
506 	pos.maxname = conf.menu.no_tags ? 0 : pos.maxname;
507 	pos.maxdesc = conf.menu.no_items ? 0 : pos.maxdesc;
508 	pos.maxdepth *= DEPTHSPACE;
509 
510 	pos.xselector = pos.maxprefix + (pos.maxprefix != 0 ? 1 : 0);
511 	pos.xname = pos.xselector + pos.selectorlen + (pos.selectorlen > 0 ? 1 : 0);
512 	pos.xdesc = pos.maxdepth + pos.xname + pos.maxname;
513 	pos.xdesc += (pos.maxname != 0 ? 1 : 0);
514 	pos.line = MAX(pos.maxsepstr + 3, pos.xdesc + pos.maxdesc);
515 
516 
517 	get_buttons(conf, &bs, BUTTONLABEL(ok_label), BUTTONLABEL(extra_label),
518 	    BUTTONLABEL(cancel_label), BUTTONLABEL(help_label));
519 
520 	if (set_widget_size(conf, rows, cols, &h, &w) != 0)
521 		return BSDDIALOG_ERROR;
522 	menu_autosize(conf, rows, cols, &h, &w, text, pos.line, &menurows,
523 	    totnitems, bs);
524 	if (menu_checksize(h, w, text, menurows, totnitems, bs) != 0)
525 		return BSDDIALOG_ERROR;
526 	if (set_widget_position(conf, &y, &x, h, w) != 0)
527 		return BSDDIALOG_ERROR;
528 
529 	if (new_widget_withtextpad(conf, &shadow, &widget, y, x, h, w, RAISED,
530 	    &textpad, &htextpad, text, true) != 0)
531 		return BSDDIALOG_ERROR;
532 
533 	prefresh(textpad, 0, 0, y + 1, x + 1 + t.texthmargin,
534 	    y + h - menurows, x + 1 + w - t.texthmargin);
535 
536 	menuwin = new_boxed_window(conf, y + h - 5 - menurows, x + 2,
537 	    menurows+2, w-4, LOWERED);
538 
539 	menupad = newpad(totnitems, pos.line);
540 	wbkgd(menupad, t.widgetcolor);
541 
542 	getfirst_with_default(conf, ngroups, groups, &abs, &g, &rel);
543 	ymenupad = 0;
544 	for (i=0; i<ngroups; i++) {
545 		currmode = getmode(mode, groups[i]);
546 		for (j=0; j < (int) groups[i].nitems; j++) {
547 			item = &groups[i].items[j];
548 			drawitem(conf, menupad, ymenupad, *item, currmode,
549 			    pos, ymenupad == abs);
550 			ymenupad++;
551 		}
552 	}
553 
554 	ys = y + h - 5 - menurows + 1;
555 	ye = y + h - 5 ;
556 	if (conf.menu.align_left || (int)pos.line > w - 6) {
557 		xs = x + 3;
558 		xe = xs + w - 7;
559 	}
560 	else { /* center */
561 		xs = x + 3 + (w-6)/2 - pos.line/2;
562 		xe = xs + w - 5;
563 	}
564 
565 	ymenupad = 0; /* now ymenupad is pminrow for prefresh() */
566 	if ((int)(ymenupad + menurows) - 1 < abs)
567 		ymenupad = abs - menurows + 1;
568 	update_menuwin(conf, menuwin, menurows+2, w-4, totnitems, menurows, ymenupad);
569 	wrefresh(menuwin);
570 	prefresh(menupad, ymenupad, 0, ys, xs, ye, xe);
571 
572 	draw_buttons(widget, h-2, w, bs, true);
573 	wrefresh(widget);
574 
575 	item = &groups[g].items[rel];
576 	currmode = getmode(mode, groups[g]);
577 	loop = true;
578 	while(loop) {
579 		input = getch();
580 		switch(input) {
581 		case KEY_ENTER:
582 		case 10: /* Enter */
583 			output = bs.value[bs.curr];
584 			if (currmode == MENUMODE)
585 				item->on = true;
586 			loop = false;
587 			break;
588 		case 27: /* Esc */
589 			output = BSDDIALOG_ESC;
590 			loop = false;
591 			break;
592 		case '\t': /* TAB */
593 			bs.curr = (bs.curr + 1) % bs.nbuttons;
594 			draw_buttons(widget, h-2, w, bs, true);
595 			wrefresh(widget);
596 			break;
597 		case KEY_LEFT:
598 			if (bs.curr > 0) {
599 				bs.curr--;
600 				draw_buttons(widget, h-2, w, bs, true);
601 				wrefresh(widget);
602 			}
603 			break;
604 		case KEY_RIGHT:
605 			if (bs.curr < (int) bs.nbuttons - 1) {
606 				bs.curr++;
607 				draw_buttons(widget, h-2, w, bs, true);
608 				wrefresh(widget);
609 			}
610 			break;
611 		case KEY_CTRL('E'): /* add conf.menu.extrahelpkey ? */
612 		case KEY_F(1):
613 			if (conf.hfile == NULL)
614 				break;
615 			if (f1help(conf) != 0)
616 				return BSDDIALOG_ERROR;
617 			/* No break! the terminal size can change */
618 		case KEY_RESIZE:
619 			hide_widget(y, x, h, w,conf.shadow);
620 
621 			/*
622 			 * Unnecessary, but, when the columns decrease the
623 			 * following "refresh" seem not work
624 			 */
625 			refresh();
626 
627 			if (set_widget_size(conf, rows, cols, &h, &w) != 0)
628 				return BSDDIALOG_ERROR;
629 			menurows = automenurows ? 0 : menurows;
630 			menu_autosize(conf, rows, cols, &h, &w, text, pos.line,
631 			    &menurows, totnitems, bs);
632 			if (menu_checksize(h, w, text, menurows, totnitems, bs) != 0)
633 				return BSDDIALOG_ERROR;
634 			if (set_widget_position(conf, &y, &x, h, w) != 0)
635 				return BSDDIALOG_ERROR;
636 
637 			wclear(shadow);
638 			mvwin(shadow, y + t.shadowrows, x + t.shadowcols);
639 			wresize(shadow, h, w);
640 
641 			wclear(widget);
642 			mvwin(widget, y, x);
643 			wresize(widget, h, w);
644 
645 			htextpad = 1;
646 			wclear(textpad);
647 			wresize(textpad, 1, w - HBORDERS - t.texthmargin * 2);
648 
649 			if(update_widget_withtextpad(conf, shadow, widget, h, w,
650 			    RAISED, textpad, &htextpad, text, true) != 0)
651 			return BSDDIALOG_ERROR;
652 
653 			draw_buttons(widget, h-2, w, bs, true);
654 			wrefresh(widget);
655 
656 			prefresh(textpad, 0, 0, y + 1, x + 1 + t.texthmargin,
657 			    y + h - menurows, x + 1 + w - t.texthmargin);
658 
659 			wclear(menuwin);
660 			mvwin(menuwin, y + h - 5 - menurows, x + 2);
661 			wresize(menuwin,menurows+2, w-4);
662 			update_menuwin(conf, menuwin, menurows+2, w-4, totnitems,
663 			    menurows, ymenupad);
664 			wrefresh(menuwin);
665 
666 			ys = y + h - 5 - menurows + 1;
667 			ye = y + h - 5 ;
668 			if (conf.menu.align_left || (int)pos.line > w - 6) {
669 				xs = x + 3;
670 				xe = xs + w - 7;
671 			}
672 			else { /* center */
673 				xs = x + 3 + (w-6)/2 - pos.line/2;
674 				xe = xs + w - 5;
675 			}
676 
677 			if ((int)(ymenupad + menurows) - 1 < abs)
678 				ymenupad = abs - menurows + 1;
679 			prefresh(menupad, ymenupad, 0, ys, xs, ye, xe);
680 
681 			refresh();
682 
683 			break;
684 		default:
685 			for (i = 0; i < (int) bs.nbuttons; i++)
686 				if (tolower(input) == tolower((bs.label[i])[0])) {
687 					output = bs.value[i];
688 					loop = false;
689 			}
690 
691 		}
692 
693 		if (abs < 0)
694 			continue;
695 		switch(input) {
696 		case KEY_HOME:
697 		case KEY_UP:
698 		case KEY_PPAGE:
699 			if (abs == 0) /* useless, just to save cpu refresh */
700 				break;
701 			drawitem(conf, menupad, abs, *item, currmode, pos, false);
702 			if (input == KEY_HOME)
703 				getfirst(ngroups, groups, &abs, &g, &rel);
704 			else if (input == KEY_UP)
705 				getprev(groups, &abs, &g, &rel);
706 			else /* input == KEY_PPAGE*/
707 				getfastprev(menurows, groups, &abs, &g, &rel);
708 			item = &groups[g].items[rel];
709 			currmode= getmode(mode, groups[g]);
710 			drawitem(conf, menupad, abs, *item, currmode, pos, true);
711 			if (ymenupad > abs && ymenupad > 0)
712 				ymenupad = abs;
713 			update_menuwin(conf, menuwin, menurows+2, w-4, totnitems,
714 			    menurows, ymenupad);
715 			wrefresh(menuwin);
716 			prefresh(menupad, ymenupad, 0, ys, xs, ye, xe);
717 			break;
718 		case KEY_END:
719 		case KEY_DOWN:
720 		case KEY_NPAGE:
721 			if (abs == totnitems -1)
722 				break; /* useless, just to save cpu refresh */
723 			drawitem(conf, menupad, abs, *item, currmode, pos, false);
724 			if (input == KEY_END)
725 				getlast(totnitems, ngroups, groups, &abs, &g, &rel);
726 			else if (input == KEY_DOWN)
727 				getnext(ngroups, groups, &abs, &g, &rel);
728 			else /* input == KEY_NPAGE*/
729 				getfastnext(menurows, ngroups, groups, &abs, &g, &rel);
730 			item = &groups[g].items[rel];
731 			currmode= getmode(mode, groups[g]);
732 			drawitem(conf, menupad, abs, *item, currmode, pos, true);
733 			if ((int)(ymenupad + menurows) <= abs)
734 				ymenupad = abs - menurows + 1;
735 			update_menuwin(conf, menuwin, menurows+2, w-4, totnitems,
736 			    menurows, ymenupad);
737 			wrefresh(menuwin);
738 			prefresh(menupad, ymenupad, 0, ys, xs, ye, xe);
739 			break;
740 		case ' ': /* Space */
741 			if (currmode == MENUMODE)
742 				break;
743 			else if (currmode == CHECKLISTMODE)
744 				item->on = !item->on;
745 			else { /* RADIOLISTMODE */
746 				if (item->on == true)
747 					break;
748 				for (i=0; i < (int) groups[g].nitems; i++)
749 					if (groups[g].items[i].on == true) {
750 						groups[g].items[i].on = false;
751 						drawitem(conf, menupad,
752 						    abs - rel + i, groups[g].items[i],
753 						    currmode, pos, false);
754 					}
755 				item->on = true;
756 			}
757 			drawitem(conf, menupad, abs, *item, currmode, pos, true);
758 			prefresh(menupad, ymenupad, 0, ys, xs, ye, xe);
759 		}
760 	}
761 
762 	if (focuslist != NULL)
763 		*focuslist = g;
764 	if (focusitem !=NULL)
765 		*focusitem = rel;
766 
767 	delwin(menupad);
768 	delwin(menuwin);
769 	end_widget_withtextpad(conf, widget, h, w, textpad, shadow);
770 
771 	return output;
772 }
773 
774 /*
775  * API
776  */
777 
778 int bsddialog_mixedlist(struct bsddialog_conf conf, char* text, int rows, int cols,
779     unsigned int menurows, int ngroups, struct bsddialog_menugroup *groups,
780     int *focuslist, int *focusitem)
781 {
782 	int output;
783 
784 	output = do_mixedlist(conf, text, rows, cols, menurows, MIXEDLISTMODE,
785 	    ngroups, groups, focuslist, focusitem);
786 
787 	return output;
788 }
789 
790 int
791 bsddialog_checklist(struct bsddialog_conf conf, char* text, int rows, int cols,
792     unsigned int menurows, int nitems, struct bsddialog_menuitem *items,
793     int *focusitem)
794 {
795 	int output;
796 	struct bsddialog_menugroup group = {
797 	    BSDDIALOG_CHECKLIST /* unused */, nitems, items};
798 
799 	output = do_mixedlist(conf, text, rows, cols, menurows, CHECKLISTMODE,
800 	    1, &group, NULL, focusitem);
801 
802 	return output;
803 }
804 
805 int
806 bsddialog_menu(struct bsddialog_conf conf, char* text, int rows, int cols,
807     unsigned int menurows, int nitems, struct bsddialog_menuitem *items,
808     int *focusitem)
809 {
810 	int output;
811 	struct bsddialog_menugroup group = {
812 	    BSDDIALOG_CHECKLIST /* unused */, nitems, items};
813 
814 	output = do_mixedlist(conf, text, rows, cols, menurows, MENUMODE, 1,
815 	    &group, NULL, focusitem);
816 
817 	return output;
818 }
819 
820 int
821 bsddialog_radiolist(struct bsddialog_conf conf, char* text, int rows, int cols,
822     unsigned int menurows, int nitems, struct bsddialog_menuitem *items,
823     int *focusitem)
824 {
825 	int output;
826 	struct bsddialog_menugroup group = {
827 	    BSDDIALOG_RADIOLIST /* unused */, nitems, items};
828 
829 	output = do_mixedlist(conf, text, rows, cols, menurows, RADIOLISTMODE,
830 	    1, &group, NULL, focusitem);
831 
832 	return output;
833 }
834 
835 int
836 bsddialog_treeview(struct bsddialog_conf conf, char* text, int rows, int cols,
837     unsigned int menurows, int nitems, struct bsddialog_menuitem *items,
838     int *focusitem)
839 {
840 	int output;
841 	struct bsddialog_menugroup group = {
842 	    BSDDIALOG_RADIOLIST /* unused */, nitems, items};
843 
844 	conf.menu.no_tags = true;
845 	conf.menu.align_left = true;
846 
847 	output = do_mixedlist(conf, text, rows, cols, menurows, RADIOLISTMODE,
848 	    1, &group, NULL, focusitem);
849 
850 	return output;
851 }
852 
853 int
854 bsddialog_buildlist(struct bsddialog_conf conf, char* text, int rows, int cols,
855     unsigned int menurows, int nitems, struct bsddialog_menuitem *items,
856     int *focusitem)
857 {
858 	WINDOW *widget, *leftwin, *leftpad, *rightwin, *rightpad, *shadow;
859 	int output, i, x, y, input;
860 	bool loop, buttupdate, padsupdate, startleft;
861 	int nlefts, nrights, leftwinx, rightwinx, winsy, padscols, curr;
862 	enum side {LEFT, RIGHT} currV;
863 	int currH;
864 	struct buttons bs;
865 	struct lineposition pos = {0,0,0,0,0,0,0,0,0,0};
866 
867 	startleft = false;
868 	for (i=0; i<nitems; i++) {
869 		pos.line = MAX(pos.line, strlen(items[i].desc));
870 		if (items[i].on == false)
871 			startleft = true;
872 	}
873 
874 	if (new_widget(conf, &widget, &y, &x, text, &rows, &cols, &shadow,
875 	    true) <0)
876 		return -1;
877 
878 	winsy = y + rows - 5 - menurows;
879 	leftwinx = x+2;
880 	leftwin = new_boxed_window(conf, winsy, leftwinx, menurows+2, (cols-5)/2,
881 	    LOWERED);
882 	rightwinx = x + cols - 2 -(cols-5)/2;
883 	rightwin = new_boxed_window(conf, winsy, rightwinx, menurows+2,
884 	    (cols-5)/2, LOWERED);
885 
886 	wrefresh(leftwin);
887 	wrefresh(rightwin);
888 
889 	padscols = (cols-5)/2 - 2;
890 	leftpad  = newpad(nitems, pos.line);
891 	rightpad = newpad(nitems, pos.line);
892 	wbkgd(leftpad, t.widgetcolor);
893 	wbkgd(rightpad, t.widgetcolor);
894 
895 	get_buttons(conf, &bs, BUTTONLABEL(ok_label), BUTTONLABEL(extra_label),
896 	    BUTTONLABEL(cancel_label), BUTTONLABEL(help_label));
897 
898 	currH = 0;
899 	currV = startleft ? LEFT : RIGHT;
900 	loop = buttupdate = padsupdate = true;
901 	while(loop) {
902 		if (buttupdate) {
903 			draw_buttons(widget, rows-2, cols, bs, true);
904 			wrefresh(widget);
905 			buttupdate = false;
906 		}
907 
908 		if (padsupdate) {
909 			werase(leftpad);
910 			werase(rightpad);
911 			curr = -1;
912 			nlefts = nrights = 0;
913 			for (i=0; i<nitems; i++) {
914 				if (items[i].on == false) {
915 					if (currV == LEFT && currH == nlefts)
916 						curr = i;
917 					drawitem(conf, leftpad, nlefts, items[i],
918 					    BUILDLISTMODE, pos, curr == i);
919 					nlefts++;
920 				} else {
921 					if (currV == RIGHT && currH == nrights)
922 						curr = i;
923 					drawitem(conf, rightpad, nrights, items[i],
924 					    BUILDLISTMODE, pos, curr == i);
925 					nrights++;
926 				}
927 			}
928 			prefresh(leftpad, 0, 0, winsy+1, leftwinx+1,
929 			    winsy+1+menurows, leftwinx + 1 + padscols);
930 			prefresh(rightpad, 0, 0, winsy+1, rightwinx+1,
931 			    winsy+1+menurows, rightwinx + 1 + padscols);
932 			padsupdate = false;
933 		}
934 
935 		input = getch();
936 		switch(input) {
937 		case 10: // Enter
938 			output = bs.value[bs.curr]; // -> buttvalues[selbutton]
939 			loop = false;
940 			break;
941 		case 27: // Esc
942 			output = BSDDIALOG_ERROR;
943 			loop = false;
944 			break;
945 		case '\t': // TAB
946 			bs.curr = (bs.curr + 1) % bs.nbuttons;
947 			buttupdate = true;
948 			break;
949 		}
950 
951 		if (nitems <= 0)
952 			continue;
953 
954 		switch(input) {
955 		case KEY_LEFT:
956 			if (currV == RIGHT && nrights > 0) {
957 				currV = LEFT;
958 				currH = 0;
959 				padsupdate = true;
960 			}
961 			break;
962 		case KEY_RIGHT:
963 			if (currV == LEFT && nrights > 0) {
964 				currV = RIGHT;
965 				currH = 0;
966 				padsupdate = true;
967 			}
968 			break;
969 		case KEY_UP:
970 			currH = (currH > 0) ? currH - 1 : 0;
971 			padsupdate = true;
972 			break;
973 		case KEY_DOWN:
974 			if (currV == LEFT)
975 				currH = (currH < nlefts-1) ? currH +1 : currH;
976 			else
977 				currH = (currH < nrights-1)? currH +1 : currH;
978 			padsupdate = true;
979 			break;
980 		case ' ': // Space
981 			items[curr].on = ! items[curr].on;
982 			if (currV == LEFT) {
983 				if (nlefts > 1)
984 					currH = currH > 0 ? currH-1 : 0;
985 				else {
986 					currH = 0;
987 					currV = RIGHT;
988 				}
989 			} else {
990 				if (nrights > 1)
991 					currH = currH > 0 ? currH-1 : 0;
992 				else {
993 					currH = 0;
994 					currV = LEFT;
995 				}
996 			}
997 			padsupdate = true;
998 			break;
999 		default:
1000 
1001 			break;
1002 		}
1003 	}
1004 
1005 	if(focusitem != NULL)
1006 		*focusitem = curr;
1007 
1008 	delwin(leftpad);
1009 	delwin(leftwin);
1010 	delwin(rightpad);
1011 	delwin(rightwin);
1012 	end_widget(conf, widget, rows, cols, shadow);
1013 
1014 	return output;
1015 }
1016