xref: /freebsd/contrib/bsddialog/lib/menubox.c (revision 963f5dc7a30624e95d72fb7f87b8892651164e46)
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 #ifdef PORTNCURSES
32 #include <ncurses/ncurses.h>
33 #else
34 #include <ncurses.h>
35 #endif
36 #include <string.h>
37 
38 
39 #include "bsddialog.h"
40 #include "lib_util.h"
41 #include "bsddialog_theme.h"
42 
43 /* "Menu": checklist - menu - mixedlist - radiolist - 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 bool
275 getnextshortcut(struct bsddialog_conf *conf, enum menumode mode, int ngroups,
276     struct bsddialog_menugroup *groups, int *abs, int *group, int *rel,
277     int key)
278 {
279 	int i, j, a, ch, ng, nr, na;
280 	bool mainloop;
281 
282 	if (*abs < 0 || ngroups < 0 || *rel < 0 || mode == BUILDLISTMODE)
283 		return false;
284 
285 	na = a = -1;
286 	mainloop = true;
287 	for (i = 0; i < ngroups && mainloop; i++) {
288 		if (groups[i].type == BSDDIALOG_SEPARATOR) {
289 			a += groups[i].nitems;
290 			continue;
291 		}
292 		for (j = 0; j < (int)groups[i].nitems; j++) {
293 			a++;
294 			if (a == *abs)
295 				continue;
296 
297 			if (conf->menu.no_name)
298 				ch = groups[i].items[j].desc[0];
299 			else
300 				ch = groups[i].items[j].name[0];
301 
302 			if (ch == key) {
303 				if (a < *abs && na == -1) {
304 					na = a;
305 					ng = i;
306 					nr = j;
307 				}
308 				if (a > *abs) {
309 					na = a;
310 					ng = i;
311 					nr = j;
312 					mainloop = false;
313 					break;
314 				}
315 			}
316 		}
317 	}
318 
319 	if (na != -1) {
320 		*abs = na;
321 		*group = ng;
322 		*rel = nr;
323 		return (true);
324 	}
325 
326 	return (false);
327 }
328 
329 static enum menumode
330 getmode(enum menumode mode, struct bsddialog_menugroup group)
331 {
332 
333 	if (mode == MIXEDLISTMODE) {
334 		if (group.type == BSDDIALOG_SEPARATOR)
335 			mode = SEPARATORMODE;
336 		else if (group.type == BSDDIALOG_RADIOLIST)
337 			mode = RADIOLISTMODE;
338 		else if (group.type == BSDDIALOG_CHECKLIST)
339 			mode = CHECKLISTMODE;
340 	}
341 
342 	return mode;
343 }
344 
345 static void
346 drawitem(struct bsddialog_conf *conf, WINDOW *pad, int y,
347     struct bsddialog_menuitem item, enum menumode mode, struct lineposition pos,
348     bool curr)
349 {
350 	int colordesc, colorname, colorshortcut, linech;
351 	char *shortcut;
352 
353 	if (mode == SEPARATORMODE) {
354 		if (conf->no_lines == false) {
355 			wattron(pad, t.menu.desccolor);
356 			linech = conf->ascii_lines ? '-' : ACS_HLINE;
357 			mvwhline(pad, y, 0, linech, pos.line);
358 			wattroff(pad, t.menu.desccolor);
359 		}
360 		wmove(pad, y, pos.line/2 - (strlen(item.name)+strlen(item.desc))/2);
361 		wattron(pad, t.menu.namesepcolor);
362 		waddstr(pad, item.name);
363 		wattroff(pad, t.menu.namesepcolor);
364 		if (strlen(item.name) > 0 && strlen(item.desc) > 0)
365 			waddch(pad, ' ');
366 		wattron(pad, t.menu.descsepcolor);
367 		waddstr(pad, item.desc);
368 		wattroff(pad, t.menu.descsepcolor);
369 		return;
370 	}
371 
372 	/* prefix */
373 	if (item.prefix != NULL && item.prefix[0] != '\0')
374 		mvwaddstr(pad, y, 0, item.prefix);
375 
376 	/* selector */
377 	wmove(pad, y, pos.xselector);
378 	wattron(pad, t.menu.selectorcolor);
379 	if (mode == CHECKLISTMODE)
380 		wprintw(pad, "[%c]", item.on ? 'X' : ' ');
381 	if (mode == RADIOLISTMODE)
382 		wprintw(pad, "(%c)", item.on ? '*' : ' ');
383 	wattroff(pad, t.menu.selectorcolor);
384 
385 	/* name */
386 	colorname = curr ? t.menu.f_namecolor : t.menu.namecolor;
387 	if (mode != BUILDLISTMODE && conf->menu.no_name == false) {
388 		wattron(pad, colorname);
389 		mvwaddstr(pad, y, pos.xname + item.depth * DEPTHSPACE, item.name);
390 		wattroff(pad, colorname);
391 	}
392 
393 	/* description */
394 	if (mode == BUILDLISTMODE) {
395 		if (curr == false)
396 			colordesc = item.on ? t.menu.namecolor : t.menu.desccolor;
397 		else
398 			colordesc = t.menu.f_namecolor;
399 	}
400 	else {
401 		if (conf->menu.no_name)
402 			colordesc = curr ? t.menu.f_namecolor : t.menu.namecolor;
403 		else
404 			colordesc = curr ? t.menu.f_desccolor : t.menu.desccolor;
405 	}
406 	if (mode == BUILDLISTMODE || conf->menu.no_desc == false) {
407 		wattron(pad, colordesc);
408 		if (conf->menu.no_name)
409 			mvwaddstr(pad, y, pos.xname + item.depth * DEPTHSPACE, item.desc);
410 		else
411 			mvwaddstr(pad, y, pos.xdesc, item.desc);
412 		wattroff(pad, colordesc);
413 	}
414 
415 	/* shortcut */
416 	if (mode != BUILDLISTMODE && conf->menu.shortcut_buttons == false) {
417 		colorshortcut = curr ? t.menu.f_shortcutcolor : t.menu.shortcutcolor;
418 		wattron(pad, colorshortcut);
419 
420 		if (conf->menu.no_name)
421 			shortcut = item.desc;
422 		else
423 			shortcut = item.name;
424 		wmove(pad, y, pos.xname + item.depth * DEPTHSPACE);
425 		if (shortcut != NULL && shortcut[0] != '\0')
426 			waddch(pad, shortcut[0]);
427 	wattroff(pad, colorshortcut);
428 }
429 
430 	/* bottom description */
431 	move(LINES-1, 2);
432 	clrtoeol();
433 	if (item.bottomdesc != NULL) {
434 		addstr(item.bottomdesc);
435 		refresh();
436 	}
437 }
438 
439 static void
440 menu_autosize(struct bsddialog_conf *conf, int rows, int cols, int *h, int *w,
441     char *text, int linelen, unsigned int *menurows, int nitems,
442     struct buttons bs)
443 {
444 	int textrow, menusize;
445 
446 	textrow = text != NULL && strlen(text) > 0 ? 1 : 0;
447 
448 	if (cols == BSDDIALOG_AUTOSIZE) {
449 		*w = VBORDERS;
450 		/* buttons size */
451 		*w += bs.nbuttons * bs.sizebutton;
452 		*w += bs.nbuttons > 0 ? (bs.nbuttons-1) * t.button.space : 0;
453 		/* line size */
454 		*w = MAX(*w, linelen + 6);
455 		/* conf.auto_minwidth */
456 		*w = MAX(*w, (int)conf->auto_minwidth);
457 		/*
458 		* avoid terminal overflow,
459 		* -1 fix false negative with big menu over the terminal and
460 		* autosize, for example "portconfig /usr/ports/www/apache24/".
461 		*/
462 		*w = MIN(*w, widget_max_width(conf)-1);
463 	}
464 
465 	if (rows == BSDDIALOG_AUTOSIZE) {
466 		*h = HBORDERS + 2 /* buttons */ + textrow;
467 
468 		if (*menurows == 0) {
469 			*h += nitems + 2;
470 			*h = MIN(*h, widget_max_height(conf));
471 			menusize = MIN(nitems + 2, *h - (HBORDERS + 2 + textrow));
472 			menusize -=2;
473 			*menurows = menusize < 0 ? 0 : menusize;
474 		}
475 		else /* h autosize with a fixed menurows */
476 			*h = *h + *menurows + 2;
477 
478 		/* conf.auto_minheight */
479 		*h = MAX(*h, (int)conf->auto_minheight);
480 		/* avoid terminal overflow */
481 		*h = MIN(*h, widget_max_height(conf));
482 		/* avoid menurows overflow */
483 		/* manual: with rows=autosize menurows!=0 is maxmenurows */
484 		*menurows = MIN(*h - 6 - textrow, (int)*menurows);
485 	}
486 	else {
487 		if (*menurows == 0)
488 			*menurows = MIN(rows-6-textrow, nitems);
489 	}
490 }
491 
492 static int
493 menu_checksize(int rows, int cols, char *text, int menurows, int nitems,
494     struct buttons bs)
495 {
496 	int mincols, textrow, menusize;
497 
498 	mincols = VBORDERS;
499 	/* buttons */
500 	mincols += bs.nbuttons * bs.sizebutton;
501 	mincols += bs.nbuttons > 0 ? (bs.nbuttons-1) * t.button.space : 0;
502 	/* line, comment to permet some cols hidden */
503 	/* mincols = MAX(mincols, linelen); */
504 
505 	if (cols < mincols)
506 		RETURN_ERROR("Few cols, width < size buttons or "\
507 		    "name+descripion of the items");
508 
509 	textrow = text != NULL && strlen(text) > 0 ? 1 : 0;
510 
511 	if (nitems > 0 && menurows == 0)
512 		RETURN_ERROR("items > 0 but menurows == 0, probably terminal "\
513 		    "too small");
514 
515 	menusize = nitems > 0 ? 3 : 0;
516 	if (rows < 2  + 2 + menusize + textrow)
517 		RETURN_ERROR("Few lines for this menus");
518 
519 	return 0;
520 }
521 
522 /* the caller has to call prefresh(menupad, ymenupad, 0, ys, xs, ye, xe); */
523 static void
524 update_menuwin(struct bsddialog_conf *conf, WINDOW *menuwin, int h, int w,
525     int totnitems, unsigned int menurows, int ymenupad)
526 {
527 
528 	draw_borders(conf, menuwin, h, w, LOWERED);
529 
530 	if (totnitems > (int) menurows) {
531 		wattron(menuwin, t.menu.arrowcolor);
532 
533 		if (ymenupad > 0)
534 			mvwprintw(menuwin, 0, 2, "^^^");
535 
536 		if ((int) (ymenupad + menurows) < totnitems)
537 			mvwprintw(menuwin, h-1, 2, "vvv");
538 
539 		wattroff(menuwin, t.menu.arrowcolor);
540 
541 		mvwprintw(menuwin, h-1, w-10, "%3d%%",
542 		    100 * (ymenupad + menurows) / totnitems);
543 	}
544 }
545 
546 static int
547 do_mixedlist(struct bsddialog_conf *conf, char* text, int rows, int cols,
548     unsigned int menurows, enum menumode mode, int ngroups,
549     struct bsddialog_menugroup *groups, int *focuslist, int *focusitem)
550 {
551 	WINDOW  *shadow, *widget, *textpad, *menuwin, *menupad;
552 	int i, j, y, x, h, w, htextpad, output, input;
553 	int ymenupad, ys, ye, xs, xe, abs, g, rel, totnitems;
554 	bool loop, automenurows, shortcut_buttons;
555 	struct buttons bs;
556 	struct bsddialog_menuitem *item;
557 	enum menumode currmode;
558 	struct lineposition pos = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
559 
560 	shortcut_buttons = conf->menu.shortcut_buttons;
561 
562 	automenurows = menurows == BSDDIALOG_AUTOSIZE ? true : false;
563 
564 	totnitems = 0;
565 	for (i=0; i < ngroups; i++) {
566 		currmode = getmode(mode, groups[i]);
567 		if (currmode == RADIOLISTMODE)
568 			checkradiolist(groups[i].nitems, groups[i].items);
569 
570 		if (currmode == MENUMODE)
571 			checkmenu(groups[i].nitems, groups[i].items);
572 
573 		if (currmode == RADIOLISTMODE || currmode == CHECKLISTMODE)
574 			pos.selectorlen = 3;
575 
576 		for (j=0; j < (int) groups[i].nitems; j++) {
577 			totnitems++;
578 			item = &groups[i].items[j];
579 
580 			if (groups[i].type == BSDDIALOG_SEPARATOR) {
581 				pos.maxsepstr = MAX(pos.maxsepstr,
582 				    strlen(item->name) + strlen(item->desc));
583 				continue;
584 			}
585 
586 			pos.maxprefix = MAX(pos.maxprefix, strlen(item->prefix));
587 			pos.maxdepth  = MAX(pos.maxdepth, item->depth);
588 			pos.maxname   = MAX(pos.maxname, strlen(item->name));
589 			pos.maxdesc   = MAX(pos.maxdesc, strlen(item->desc));
590 		}
591 	}
592 	pos.maxname = conf->menu.no_name ? 0 : pos.maxname;
593 	pos.maxdesc = conf->menu.no_desc ? 0 : pos.maxdesc;
594 	pos.maxdepth *= DEPTHSPACE;
595 
596 	pos.xselector = pos.maxprefix + (pos.maxprefix != 0 ? 1 : 0);
597 	pos.xname = pos.xselector + pos.selectorlen + (pos.selectorlen > 0 ? 1 : 0);
598 	pos.xdesc = pos.maxdepth + pos.xname + pos.maxname;
599 	pos.xdesc += (pos.maxname != 0 ? 1 : 0);
600 	pos.line = MAX(pos.maxsepstr + 3, pos.xdesc + pos.maxdesc);
601 
602 
603 	get_buttons(conf, &bs, BUTTONLABEL(ok_label), BUTTONLABEL(extra_label),
604 	    BUTTONLABEL(cancel_label), BUTTONLABEL(help_label));
605 
606 	if (set_widget_size(conf, rows, cols, &h, &w) != 0)
607 		return BSDDIALOG_ERROR;
608 	menu_autosize(conf, rows, cols, &h, &w, text, pos.line, &menurows,
609 	    totnitems, bs);
610 	if (menu_checksize(h, w, text, menurows, totnitems, bs) != 0)
611 		return BSDDIALOG_ERROR;
612 	if (set_widget_position(conf, &y, &x, h, w) != 0)
613 		return BSDDIALOG_ERROR;
614 
615 	if (new_widget_withtextpad(conf, &shadow, &widget, y, x, h, w, RAISED,
616 	    &textpad, &htextpad, text, true) != 0)
617 		return BSDDIALOG_ERROR;
618 
619 	prefresh(textpad, 0, 0, y + 1, x + 1 + t.text.hmargin,
620 	    y + h - menurows, x + 1 + w - t.text.hmargin);
621 
622 	menuwin = new_boxed_window(conf, y + h - 5 - menurows, x + 2,
623 	    menurows+2, w-4, LOWERED);
624 
625 	menupad = newpad(totnitems, pos.line);
626 	wbkgd(menupad, t.dialog.color);
627 
628 	ymenupad = 0;
629 	for (i=0; i<ngroups; i++) {
630 		currmode = getmode(mode, groups[i]);
631 		for (j=0; j < (int) groups[i].nitems; j++) {
632 			item = &groups[i].items[j];
633 			drawitem(conf, menupad, ymenupad, *item, currmode, pos,
634 			    false);
635 			ymenupad++;
636 		}
637 	}
638 	getfirst_with_default(conf, ngroups, groups, &abs, &g, &rel);
639 	currmode = getmode(mode, groups[g]);
640 	item = &groups[g].items[rel];
641 	drawitem(conf, menupad, abs, *item, currmode, pos, true);
642 
643 	ys = y + h - 5 - menurows + 1;
644 	ye = y + h - 5 ;
645 	if (conf->menu.align_left || (int)pos.line > w - 6) {
646 		xs = x + 3;
647 		xe = xs + w - 7;
648 	}
649 	else { /* center */
650 		xs = x + 3 + (w-6)/2 - pos.line/2;
651 		xe = xs + w - 5;
652 	}
653 
654 	ymenupad = 0; /* now ymenupad is pminrow for prefresh() */
655 	if ((int)(ymenupad + menurows) - 1 < abs)
656 		ymenupad = abs - menurows + 1;
657 	update_menuwin(conf, menuwin, menurows+2, w-4, totnitems, menurows, ymenupad);
658 	wrefresh(menuwin);
659 	prefresh(menupad, ymenupad, 0, ys, xs, ye, xe);
660 
661 	draw_buttons(widget, h-2, w, bs, shortcut_buttons);
662 	wrefresh(widget);
663 
664 	loop = true;
665 	while(loop) {
666 		input = getch();
667 		switch(input) {
668 		case KEY_ENTER:
669 		case 10: /* Enter */
670 			output = bs.value[bs.curr];
671 			if (currmode == MENUMODE)
672 				item->on = true;
673 			loop = false;
674 			break;
675 		case 27: /* Esc */
676 			output = BSDDIALOG_ESC;
677 			loop = false;
678 			break;
679 		case '\t': /* TAB */
680 			bs.curr = (bs.curr + 1) % bs.nbuttons;
681 			draw_buttons(widget, h-2, w, bs, shortcut_buttons);
682 			wrefresh(widget);
683 			break;
684 		case KEY_LEFT:
685 			if (bs.curr > 0) {
686 				bs.curr--;
687 				draw_buttons(widget, h-2, w, bs, shortcut_buttons);
688 				wrefresh(widget);
689 			}
690 			break;
691 		case KEY_RIGHT:
692 			if (bs.curr < (int) bs.nbuttons - 1) {
693 				bs.curr++;
694 				draw_buttons(widget, h-2, w, bs, shortcut_buttons);
695 				wrefresh(widget);
696 			}
697 			break;
698 		case KEY_CTRL('E'): /* add conf->menu.extrahelpkey ? */
699 		case KEY_F(1):
700 			if (conf->f1_file == NULL && conf->f1_message == NULL)
701 				break;
702 			if (f1help(conf) != 0)
703 				return BSDDIALOG_ERROR;
704 			/* No break! the terminal size can change */
705 		case KEY_RESIZE:
706 			hide_widget(y, x, h, w,conf->shadow);
707 
708 			/*
709 			 * Unnecessary, but, when the columns decrease the
710 			 * following "refresh" seem not work
711 			 */
712 			refresh();
713 
714 			if (set_widget_size(conf, rows, cols, &h, &w) != 0)
715 				return BSDDIALOG_ERROR;
716 			menurows = automenurows ? 0 : menurows;
717 			menu_autosize(conf, rows, cols, &h, &w, text, pos.line,
718 			    &menurows, totnitems, bs);
719 			if (menu_checksize(h, w, text, menurows, totnitems, bs) != 0)
720 				return BSDDIALOG_ERROR;
721 			if (set_widget_position(conf, &y, &x, h, w) != 0)
722 				return BSDDIALOG_ERROR;
723 
724 			wclear(shadow);
725 			mvwin(shadow, y + t.shadow.h, x + t.shadow.w);
726 			wresize(shadow, h, w);
727 
728 			wclear(widget);
729 			mvwin(widget, y, x);
730 			wresize(widget, h, w);
731 
732 			htextpad = 1;
733 			wclear(textpad);
734 			wresize(textpad, 1, w - HBORDERS - t.text.hmargin * 2);
735 
736 			if(update_widget_withtextpad(conf, shadow, widget, h, w,
737 			    RAISED, textpad, &htextpad, text, true) != 0)
738 			return BSDDIALOG_ERROR;
739 
740 			draw_buttons(widget, h-2, w, bs, shortcut_buttons);
741 			wrefresh(widget);
742 
743 			prefresh(textpad, 0, 0, y + 1, x + 1 + t.text.hmargin,
744 			    y + h - menurows, x + 1 + w - t.text.hmargin);
745 
746 			wclear(menuwin);
747 			mvwin(menuwin, y + h - 5 - menurows, x + 2);
748 			wresize(menuwin,menurows+2, w-4);
749 			update_menuwin(conf, menuwin, menurows+2, w-4, totnitems,
750 			    menurows, ymenupad);
751 			wrefresh(menuwin);
752 
753 			ys = y + h - 5 - menurows + 1;
754 			ye = y + h - 5 ;
755 			if (conf->menu.align_left || (int)pos.line > w - 6) {
756 				xs = x + 3;
757 				xe = xs + w - 7;
758 			}
759 			else { /* center */
760 				xs = x + 3 + (w-6)/2 - pos.line/2;
761 				xe = xs + w - 5;
762 			}
763 
764 			if ((int)(ymenupad + menurows) - 1 < abs)
765 				ymenupad = abs - menurows + 1;
766 			prefresh(menupad, ymenupad, 0, ys, xs, ye, xe);
767 
768 			refresh();
769 
770 			break;
771 		}
772 
773 		if (abs < 0)
774 			continue;
775 		switch(input) {
776 		case KEY_HOME:
777 		case KEY_UP:
778 		case KEY_PPAGE:
779 			if (abs == 0) /* useless, just to save cpu refresh */
780 				break;
781 			drawitem(conf, menupad, abs, *item, currmode, pos, false);
782 			if (input == KEY_HOME)
783 				getfirst(ngroups, groups, &abs, &g, &rel);
784 			else if (input == KEY_UP)
785 				getprev(groups, &abs, &g, &rel);
786 			else /* input == KEY_PPAGE*/
787 				getfastprev(menurows, groups, &abs, &g, &rel);
788 			item = &groups[g].items[rel];
789 			currmode= getmode(mode, groups[g]);
790 			drawitem(conf, menupad, abs, *item, currmode, pos, true);
791 			if (ymenupad > abs && ymenupad > 0)
792 				ymenupad = abs;
793 			update_menuwin(conf, menuwin, menurows+2, w-4, totnitems,
794 			    menurows, ymenupad);
795 			wrefresh(menuwin);
796 			prefresh(menupad, ymenupad, 0, ys, xs, ye, xe);
797 			break;
798 		case KEY_END:
799 		case KEY_DOWN:
800 		case KEY_NPAGE:
801 			if (abs == totnitems -1)
802 				break; /* useless, just to save cpu refresh */
803 			drawitem(conf, menupad, abs, *item, currmode, pos, false);
804 			if (input == KEY_END)
805 				getlast(totnitems, ngroups, groups, &abs, &g, &rel);
806 			else if (input == KEY_DOWN)
807 				getnext(ngroups, groups, &abs, &g, &rel);
808 			else /* input == KEY_NPAGE*/
809 				getfastnext(menurows, ngroups, groups, &abs, &g, &rel);
810 			item = &groups[g].items[rel];
811 			currmode= getmode(mode, groups[g]);
812 			drawitem(conf, menupad, abs, *item, currmode, pos, true);
813 			if ((int)(ymenupad + menurows) <= abs)
814 				ymenupad = abs - menurows + 1;
815 			update_menuwin(conf, menuwin, menurows+2, w-4, totnitems,
816 			    menurows, ymenupad);
817 			wrefresh(menuwin);
818 			prefresh(menupad, ymenupad, 0, ys, xs, ye, xe);
819 			break;
820 		case ' ': /* Space */
821 			if (currmode == MENUMODE)
822 				break;
823 			else if (currmode == CHECKLISTMODE)
824 				item->on = !item->on;
825 			else { /* RADIOLISTMODE */
826 				for (i=0; i < (int) groups[g].nitems; i++)
827 					if (groups[g].items[i].on == true && i != rel) {
828 						groups[g].items[i].on = false;
829 						drawitem(conf, menupad,
830 						    abs - rel + i, groups[g].items[i],
831 						    currmode, pos, false);
832 					}
833 				item->on = !item->on;
834 			}
835 			drawitem(conf, menupad, abs, *item, currmode, pos, true);
836 			prefresh(menupad, ymenupad, 0, ys, xs, ye, xe);
837 		default:
838 			if (shortcut_buttons) {
839 				for (i = 0; i < (int) bs.nbuttons; i++)
840 					if (tolower(input) == tolower((bs.label[i])[0])) {
841 						output = bs.value[i];
842 						if (currmode == MENUMODE)
843 							item->on = true;
844 						loop = false;
845 					}
846 				break;
847 			}
848 
849 			drawitem(conf, menupad, abs, *item, currmode, pos, false);
850 			getnextshortcut(conf, currmode, ngroups, groups, &abs,
851 			    &g, &rel, input);
852 			item = &groups[g].items[rel];
853 			currmode = getmode(mode, groups[g]);
854 			drawitem(conf, menupad, abs, *item, currmode, pos, true);
855 			if (ymenupad > abs && ymenupad > 0)
856 				ymenupad = abs;
857 			if ((int)(ymenupad + menurows) <= abs)
858 				ymenupad = abs - menurows + 1;
859 			update_menuwin(conf, menuwin, menurows+2, w-4, totnitems,
860 			    menurows, ymenupad);
861 			wrefresh(menuwin);
862 			prefresh(menupad, ymenupad, 0, ys, xs, ye, xe);
863 		}
864 	}
865 
866 	if (focuslist != NULL)
867 		*focuslist = g;
868 	if (focusitem !=NULL)
869 		*focusitem = rel;
870 
871 	delwin(menupad);
872 	delwin(menuwin);
873 	end_widget_withtextpad(conf, widget, h, w, textpad, shadow);
874 
875 	return output;
876 }
877 
878 /*
879  * API
880  */
881 
882 int bsddialog_mixedlist(struct bsddialog_conf *conf, char* text, int rows, int cols,
883     unsigned int menurows, int ngroups, struct bsddialog_menugroup *groups,
884     int *focuslist, int *focusitem)
885 {
886 	int output;
887 
888 	output = do_mixedlist(conf, text, rows, cols, menurows, MIXEDLISTMODE,
889 	    ngroups, groups, focuslist, focusitem);
890 
891 	return output;
892 }
893 
894 int
895 bsddialog_checklist(struct bsddialog_conf *conf, char* text, int rows, int cols,
896     unsigned int menurows, int nitems, struct bsddialog_menuitem *items,
897     int *focusitem)
898 {
899 	int output;
900 	struct bsddialog_menugroup group = {
901 	    BSDDIALOG_CHECKLIST /* unused */, nitems, items};
902 
903 	output = do_mixedlist(conf, text, rows, cols, menurows, CHECKLISTMODE,
904 	    1, &group, NULL, focusitem);
905 
906 	return output;
907 }
908 
909 int
910 bsddialog_menu(struct bsddialog_conf *conf, char* text, int rows, int cols,
911     unsigned int menurows, int nitems, struct bsddialog_menuitem *items,
912     int *focusitem)
913 {
914 	int output;
915 	struct bsddialog_menugroup group = {
916 	    BSDDIALOG_CHECKLIST /* unused */, nitems, items};
917 
918 	output = do_mixedlist(conf, text, rows, cols, menurows, MENUMODE, 1,
919 	    &group, NULL, focusitem);
920 
921 	return output;
922 }
923 
924 int
925 bsddialog_radiolist(struct bsddialog_conf *conf, char* text, int rows, int cols,
926     unsigned int menurows, int nitems, struct bsddialog_menuitem *items,
927     int *focusitem)
928 {
929 	int output;
930 	struct bsddialog_menugroup group = {
931 	    BSDDIALOG_RADIOLIST /* unused */, nitems, items};
932 
933 	output = do_mixedlist(conf, text, rows, cols, menurows, RADIOLISTMODE,
934 	    1, &group, NULL, focusitem);
935 
936 	return output;
937 }
938 
939 /* todo */
940 static int buildlist_autosize(int rows, int cols)
941 {
942 
943 	if (cols == BSDDIALOG_AUTOSIZE)
944 		RETURN_ERROR("Unimplemented cols autosize for buildlist");
945 
946 	if (rows == BSDDIALOG_AUTOSIZE)
947 		RETURN_ERROR("Unimplemented rows autosize for buildlist");
948 
949 	return 0;
950 }
951 
952 /* to improve */
953 static int
954 buildlist_checksize(int rows, int cols, char *text, int menurows, int nitems,
955     struct buttons bs)
956 {
957 	int mincols, textrow, menusize;
958 
959 	mincols = VBORDERS;
960 	/* buttons */
961 	mincols += bs.nbuttons * bs.sizebutton;
962 	mincols += bs.nbuttons > 0 ? (bs.nbuttons-1) * t.button.space : 0;
963 	/* line, comment to permet some cols hidden */
964 	/* mincols = MAX(mincols, linelen); */
965 
966 	if (cols < mincols)
967 		RETURN_ERROR("Few cols, width < size buttons or "\
968 		    "name+descripion of the items");
969 
970 	textrow = text != NULL && strlen(text) > 0 ? 1 : 0;
971 
972 	if (nitems > 0 && menurows == 0)
973 		RETURN_ERROR("items > 0 but menurows == 0, probably terminal "\
974 		    "too small");
975 
976 	menusize = nitems > 0 ? 3 : 0;
977 	if (rows < 2 + 2 + menusize + textrow)
978 		RETURN_ERROR("Few lines for this menus");
979 
980 	return 0;
981 }
982 
983 int
984 bsddialog_buildlist(struct bsddialog_conf *conf, char* text, int rows, int cols,
985     unsigned int menurows, int nitems, struct bsddialog_menuitem *items,
986     int *focusitem)
987 {
988 	WINDOW *widget, *textpad, *leftwin, *leftpad, *rightwin, *rightpad, *shadow;
989 	int output, i, x, y, h, w, htextpad, input;
990 	bool loop, buttupdate, padsupdate, startleft;
991 	int nlefts, nrights, leftwinx, rightwinx, winsy, padscols, curr;
992 	enum side {LEFT, RIGHT} currV;
993 	int currH;
994 	struct buttons bs;
995 	struct lineposition pos = {0,0,0,0,0,0,0,0,0,0};
996 
997 	startleft = false;
998 	for (i=0; i<nitems; i++) {
999 		pos.line = MAX(pos.line, strlen(items[i].desc));
1000 		if (items[i].on == false)
1001 			startleft = true;
1002 	}
1003 
1004 	get_buttons(conf, &bs, BUTTONLABEL(ok_label), BUTTONLABEL(extra_label),
1005 	    BUTTONLABEL(cancel_label), BUTTONLABEL(help_label));
1006 
1007 	if (set_widget_size(conf, rows, cols, &h, &w) != 0)
1008 		return BSDDIALOG_ERROR;
1009 	if (buildlist_autosize(rows, cols) != 0)
1010 		return BSDDIALOG_ERROR;
1011 	if (buildlist_checksize(h, w, text, menurows, nitems, bs) != 0)
1012 		return BSDDIALOG_ERROR;
1013 	if (set_widget_position(conf, &y, &x, h, w) != 0)
1014 		return BSDDIALOG_ERROR;
1015 
1016 	if (new_widget_withtextpad(conf, &shadow, &widget, y, x, h, w, RAISED,
1017 	    &textpad, &htextpad, text, true) != 0)
1018 		return BSDDIALOG_ERROR;
1019 
1020 	prefresh(textpad, 0, 0, y + 1, x + 1 + t.text.hmargin,
1021 	    y + h - menurows, x + 1 + w - t.text.hmargin);
1022 
1023 	winsy = y + h - 5 - menurows;
1024 	leftwinx = x+2;
1025 	leftwin = new_boxed_window(conf, winsy, leftwinx, menurows+2, (w-5)/2,
1026 	    LOWERED);
1027 	rightwinx = x + w - 2 -(w-5)/2;
1028 	rightwin = new_boxed_window(conf, winsy, rightwinx, menurows+2,
1029 	    (w-5)/2, LOWERED);
1030 
1031 	wrefresh(leftwin);
1032 	wrefresh(rightwin);
1033 
1034 	padscols = (w-5)/2 - 2;
1035 	leftpad  = newpad(nitems, pos.line);
1036 	rightpad = newpad(nitems, pos.line);
1037 	wbkgd(leftpad, t.dialog.color);
1038 	wbkgd(rightpad, t.dialog.color);
1039 
1040 	currH = 0;
1041 	currV = startleft ? LEFT : RIGHT;
1042 	loop = buttupdate = padsupdate = true;
1043 	while(loop) {
1044 		if (buttupdate) {
1045 			draw_buttons(widget, h-2, w, bs, true);
1046 			wrefresh(widget);
1047 			buttupdate = false;
1048 		}
1049 
1050 		if (padsupdate) {
1051 			werase(leftpad);
1052 			werase(rightpad);
1053 			curr = -1;
1054 			nlefts = nrights = 0;
1055 			for (i=0; i<nitems; i++) {
1056 				if (items[i].on == false) {
1057 					if (currV == LEFT && currH == nlefts)
1058 						curr = i;
1059 					drawitem(conf, leftpad, nlefts, items[i],
1060 					    BUILDLISTMODE, pos, curr == i);
1061 					nlefts++;
1062 				} else {
1063 					if (currV == RIGHT && currH == nrights)
1064 						curr = i;
1065 					drawitem(conf, rightpad, nrights, items[i],
1066 					    BUILDLISTMODE, pos, curr == i);
1067 					nrights++;
1068 				}
1069 			}
1070 			prefresh(leftpad, 0, 0, winsy+1, leftwinx+1,
1071 			    winsy+1+menurows, leftwinx + 1 + padscols);
1072 			prefresh(rightpad, 0, 0, winsy+1, rightwinx+1,
1073 			    winsy+1+menurows, rightwinx + 1 + padscols);
1074 			padsupdate = false;
1075 		}
1076 
1077 		input = getch();
1078 		switch(input) {
1079 		case KEY_ENTER:
1080 		case 10: /* Enter */
1081 			output = bs.value[bs.curr];
1082 			loop = false;
1083 			break;
1084 		case 27: /* Esc */
1085 			output = BSDDIALOG_ERROR;
1086 			loop = false;
1087 			break;
1088 		case '\t': /* TAB */
1089 			bs.curr = (bs.curr + 1) % bs.nbuttons;
1090 			buttupdate = true;
1091 			break;
1092 		}
1093 
1094 		if (nitems <= 0)
1095 			continue;
1096 
1097 		switch(input) {
1098 		case KEY_LEFT:
1099 			if (currV == RIGHT && nrights > 0) {
1100 				currV = LEFT;
1101 				currH = 0;
1102 				padsupdate = true;
1103 			}
1104 			break;
1105 		case KEY_RIGHT:
1106 			if (currV == LEFT && nrights > 0) {
1107 				currV = RIGHT;
1108 				currH = 0;
1109 				padsupdate = true;
1110 			}
1111 			break;
1112 		case KEY_UP:
1113 			currH = (currH > 0) ? currH - 1 : 0;
1114 			padsupdate = true;
1115 			break;
1116 		case KEY_DOWN:
1117 			if (currV == LEFT)
1118 				currH = (currH < nlefts-1) ? currH +1 : currH;
1119 			else
1120 				currH = (currH < nrights-1)? currH +1 : currH;
1121 			padsupdate = true;
1122 			break;
1123 		case ' ': /* Space */
1124 			items[curr].on = ! items[curr].on;
1125 			if (currV == LEFT) {
1126 				if (nlefts > 1)
1127 					currH = currH > 0 ? currH-1 : 0;
1128 				else {
1129 					currH = 0;
1130 					currV = RIGHT;
1131 				}
1132 			} else {
1133 				if (nrights > 1)
1134 					currH = currH > 0 ? currH-1 : 0;
1135 				else {
1136 					currH = 0;
1137 					currV = LEFT;
1138 				}
1139 			}
1140 			padsupdate = true;
1141 			break;
1142 		default:
1143 
1144 			break;
1145 		}
1146 	}
1147 
1148 	if(focusitem != NULL)
1149 		*focusitem = curr;
1150 
1151 	delwin(leftpad);
1152 	delwin(leftwin);
1153 	delwin(rightpad);
1154 	delwin(rightwin);
1155 	end_widget_withtextpad(conf, widget, h, w, textpad, shadow);
1156 
1157 	return output;
1158 }
1159