xref: /freebsd/contrib/bsddialog/lib/barbox.c (revision 18054d0220cfc8df9c9568c437bd6fbb59d53c3c)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2021-2022 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 <curses.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <time.h>
35 #include <unistd.h>
36 
37 #include "bsddialog.h"
38 #include "bsddialog_progressview.h"
39 #include "bsddialog_theme.h"
40 #include "lib_util.h"
41 
42 #define BARPADDING     2
43 #define MINBARLEN      15
44 #define MINBARWIDTH    (2 + 2 * BARPADDING + MINBARLEN)
45 #define MINMGBARLEN    18
46 #define MINMGBARWIDTH  (2 + 2 * BARPADDING + MINMGBARLEN)
47 
48 bool bsddialog_interruptprogview;
49 bool bsddialog_abortprogview;
50 int  bsddialog_total_progview;
51 
52 static void
53 draw_bar(WINDOW *win, int y, int x, int barlen, int perc, bool withlabel,
54     int label)
55 {
56 	int i, blue_x, color, stringlen;
57 	char labelstr[128];
58 
59 	blue_x = perc > 0 ? (perc * barlen) / 100 : -1;
60 
61 	wmove(win, y, x);
62 	for (i = 0; i < barlen; i++) {
63 		color = (i <= blue_x) ? t.bar.f_color : t.bar.color;
64 		wattron(win, color);
65 		waddch(win, ' ');
66 		wattroff(win, color);
67 	}
68 
69 	if (withlabel)
70 		sprintf(labelstr, "%d", label);
71 	else
72 		sprintf(labelstr, "%3d%%", perc);
73 	stringlen = (int)strlen(labelstr);
74 	wmove(win, y, x + barlen/2 - stringlen/2);
75 	for (i = 0; i < stringlen; i++) {
76 		color = (blue_x + 1 <= barlen/2 - stringlen/2 + i ) ?
77 		    t.bar.color : t.bar.f_color;
78 		wattron(win, color);
79 		waddch(win, labelstr[i]);
80 		wattroff(win, color);
81 	}
82 }
83 
84 static int
85 bar_autosize(struct bsddialog_conf *conf, int rows, int cols, int *h, int *w,
86     const char *text, struct buttons *bs)
87 {
88 	int htext, wtext;
89 
90 	if (cols == BSDDIALOG_AUTOSIZE || rows == BSDDIALOG_AUTOSIZE) {
91 		if (text_size(conf, rows, cols, text, bs, 3, MINBARWIDTH,
92 		    &htext, &wtext) != 0)
93 			return (BSDDIALOG_ERROR);
94 	}
95 
96 	if (cols == BSDDIALOG_AUTOSIZE)
97 		*w = widget_min_width(conf, wtext, MINBARWIDTH, bs);
98 
99 	if (rows == BSDDIALOG_AUTOSIZE)
100 		*h = widget_min_height(conf, htext, 3 /* bar */, bs != NULL);
101 
102 	return (0);
103 }
104 
105 static int
106 bar_checksize(int rows, int cols, struct buttons *bs)
107 {
108 	int minheight, minwidth;
109 
110 	minwidth = 0;
111 	if (bs != NULL) /* gauge has not buttons */
112 		minwidth = buttons_width(*bs);
113 
114 	minwidth = MAX(minwidth, MINBARWIDTH);
115 	minwidth += VBORDERS;
116 
117 	if (cols < minwidth)
118 		RETURN_ERROR("Few cols to draw bar and/or buttons");
119 
120 	minheight = HBORDERS + 3;
121 	if (bs != NULL)
122 		minheight += 2;
123 	if (rows < minheight)
124 		RETURN_ERROR("Few rows to draw bar");
125 
126 	return (0);
127 }
128 
129 int
130 bsddialog_gauge(struct bsddialog_conf *conf, const char *text, int rows,
131     int cols, unsigned int perc, int fd, const char *sep)
132 {
133 	bool mainloop;
134 	int y, x, h, w, fd2;
135 	FILE *input;
136 	WINDOW *widget, *textpad, *bar, *shadow;
137 	char inputbuf[2048], ntext[2048], *pntext;
138 
139 	if (set_widget_size(conf, rows, cols, &h, &w) != 0)
140 		return (BSDDIALOG_ERROR);
141 	if (bar_autosize(conf, rows, cols, &h, &w, text, NULL) != 0)
142 		return (BSDDIALOG_ERROR);
143 	if (bar_checksize(h, w, NULL) != 0)
144 		return (BSDDIALOG_ERROR);
145 	if (set_widget_position(conf, &y, &x, h, w) != 0)
146 		return (BSDDIALOG_ERROR);
147 
148 	if (new_dialog(conf, &shadow, &widget, y, x, h, w, &textpad, text, NULL,
149 	    false) != 0)
150 		return (BSDDIALOG_ERROR);
151 
152 	bar = new_boxed_window(conf, y+h-4, x+3, 3, w-6, RAISED);
153 
154 	mainloop = (fd < 0) ? false : true;
155 
156 	if (mainloop) {
157 		fd2 = dup(fd);
158 		input = fdopen(fd2, "r");
159 		if (input == NULL)
160 			RETURN_ERROR("Cannot build FILE* from fd");
161 	} else
162 		input = NULL;
163 
164 	while (mainloop) {
165 		wrefresh(widget);
166 		prefresh(textpad, 0, 0, y+1, x+1+TEXTHMARGIN, y+h-4,
167 		    x+w-1-TEXTHMARGIN);
168 		draw_borders(conf, bar, 3, w-6, RAISED);
169 		draw_bar(bar, 1, 1, w-8, perc, false, -1 /*unused*/);
170 		wrefresh(bar);
171 
172 		while (true) {
173 			fscanf(input, "%s", inputbuf);
174 			if (strcmp(inputbuf,"EOF") == 0) {
175 				mainloop = false;
176 				break;
177 			}
178 			if (strcmp(inputbuf, sep) == 0)
179 				break;
180 		}
181 		if (mainloop == false)
182 			break;
183 		fscanf(input, "%d", &perc);
184 		perc = perc > 100 ? 100 : perc;
185 		pntext = &ntext[0];
186 		ntext[0] = '\0';
187 		while (true) {
188 			fscanf(input, "%s", inputbuf);
189 			if (strcmp(inputbuf,"EOF") == 0) {
190 				mainloop = false;
191 				break;
192 			}
193 			if (strcmp(inputbuf, sep) == 0)
194 				break;
195 			strcpy(pntext, inputbuf);
196 			pntext += strlen(inputbuf);
197 			pntext[0] = ' ';
198 			pntext++;
199 		}
200 		if (update_dialog(conf, shadow, widget, y, x, h, w, textpad,
201 		    ntext, NULL, false) != 0)
202 			return (BSDDIALOG_ERROR);
203 	}
204 
205 	if (input != NULL)
206 		fclose(input);
207 	delwin(bar);
208 	end_dialog(conf, shadow, widget, textpad);
209 
210 	return (BSDDIALOG_OK);
211 }
212 
213 /* Mixedgauge */
214 static int
215 do_mixedgauge(struct bsddialog_conf *conf, const char *text, int rows, int cols,
216     unsigned int mainperc, unsigned int nminibars, const char **minilabels,
217     int *minipercs, bool color)
218 {
219 	int i, output, miniperc, y, x, h, w, ypad, max_minbarlen;
220 	int htextpad, htext, wtext;
221 	int colorperc, red, green;
222 	WINDOW *widget, *textpad, *bar, *shadow;
223 	char states[12][14] = {
224 		"  Succeeded  ", /* -1  */
225 		"   Failed    ", /* -2  */
226 		"   Passed    ", /* -3  */
227 		"  Completed  ", /* -4  */
228 		"   Checked   ", /* -5  */
229 		"    Done     ", /* -6  */
230 		"   Skipped   ", /* -7  */
231 		" In Progress ", /* -8  */
232 		"(blank)      ", /* -9  */
233 		"     N/A     ", /* -10 */
234 		"   Pending   ", /* -11 */
235 		"   UNKNOWN   ", /* < -11, no API */
236 	};
237 
238 	red   = bsddialog_color(BSDDIALOG_WHITE,BSDDIALOG_RED,  BSDDIALOG_BOLD);
239 	green = bsddialog_color(BSDDIALOG_WHITE,BSDDIALOG_GREEN,BSDDIALOG_BOLD);
240 
241 	max_minbarlen = 0;
242 	for (i = 0; i < (int)nminibars; i++)
243 		max_minbarlen = MAX(max_minbarlen, (int)strlen(minilabels[i]));
244 	max_minbarlen += 3 + 16; /* seps + [...] */
245 	max_minbarlen = MAX(max_minbarlen, MINMGBARWIDTH); /* mainbar */
246 
247 	if (set_widget_size(conf, rows, cols, &h, &w) != 0)
248 		return (BSDDIALOG_ERROR);
249 
250 	/* mixedgauge autosize */
251 	if (cols == BSDDIALOG_AUTOSIZE || rows == BSDDIALOG_AUTOSIZE) {
252 		if (text_size(conf, rows, cols, text, NULL, nminibars + 3,
253 		    max_minbarlen, &htext, &wtext) != 0)
254 			return (BSDDIALOG_ERROR);
255 	}
256 	if (cols == BSDDIALOG_AUTOSIZE)
257 		w = widget_min_width(conf, wtext, max_minbarlen, NULL);
258 	if (rows == BSDDIALOG_AUTOSIZE)
259 		h = widget_min_height(conf, htext, nminibars + 3, false);
260 
261 	/* mixedgauge checksize */
262 	if (w < max_minbarlen + 2)
263 		RETURN_ERROR("Few cols for this mixedgauge");
264 	if (h < 5 + (int)nminibars)
265 		RETURN_ERROR("Few rows for this mixedgauge");
266 
267 	if (set_widget_position(conf, &y, &x, h, w) != 0)
268 		return (BSDDIALOG_ERROR);
269 
270 	output = new_dialog(conf, &shadow, &widget, y, x, h, w, &textpad, text,
271 	    NULL, false);
272 	if (output == BSDDIALOG_ERROR)
273 		return (output);
274 
275 	/* mini bars */
276 	for (i = 0; i < (int)nminibars; i++) {
277 		miniperc = minipercs[i];
278 		if (miniperc == BSDDIALOG_MG_BLANK)
279 			continue;
280 		/* label */
281 		if (color && (miniperc >= 0))
282 			wattron(widget, A_BOLD);
283 		mvwaddstr(widget, i+1, 2, minilabels[i]);
284 			wattroff(widget, A_BOLD);
285 		/* perc */
286 		if (miniperc < -11)
287 			mvwaddstr(widget, i+1, w-2-15, states[11]);
288 		else if (miniperc < 0) {
289 			mvwaddstr(widget, i+1, w-2-15, "[             ]");
290 			colorperc = -1;
291 			if (color && miniperc == BSDDIALOG_MG_FAILED)
292 				colorperc = red;
293 			if (color && miniperc == BSDDIALOG_MG_DONE)
294 				colorperc = green;
295 			if (colorperc != -1)
296 				wattron(widget, colorperc);
297 			miniperc = abs(miniperc + 1);
298 			mvwaddstr(widget, i+1, 1+w-2-15, states[miniperc]);
299 			if (colorperc != -1)
300 				wattroff(widget, colorperc);
301 		}
302 		else { /* miniperc >= 0 */
303 			if (miniperc > 100)
304 				miniperc = 100;
305 			mvwaddstr(widget, i+1, w-2-15, "[             ]");
306 			draw_bar(widget, i+1, 1+w-2-15, 13, miniperc, false,
307 			    -1 /*unused*/);
308 		}
309 	}
310 
311 	wrefresh(widget);
312 	getmaxyx(textpad, htextpad, i /* unused */);
313 	ypad =  y + h - 4 - htextpad;
314 	ypad = ypad < y+(int)nminibars ? y+(int)nminibars : ypad;
315 	prefresh(textpad, 0, 0, ypad, x+2, y+h-4, x+w-2);
316 
317 	/* main bar */
318 	bar = new_boxed_window(conf, y+h -4, x+3, 3, w-6, RAISED);
319 
320 	draw_bar(bar, 1, 1, w-8, mainperc, false, -1 /*unused*/);
321 
322 	wattron(bar, t.bar.color);
323 	mvwaddstr(bar, 0, 2, "Overall Progress");
324 	wattroff(bar, t.bar.color);
325 
326 	wrefresh(bar);
327 
328 	/* getch(); port ncurses shows nothing */
329 
330 	delwin(bar);
331 	end_dialog(conf, shadow, widget, textpad);
332 
333 	return (BSDDIALOG_OK);
334 }
335 
336 int
337 bsddialog_mixedgauge(struct bsddialog_conf *conf, const char *text, int rows,
338     int cols, unsigned int mainperc, unsigned int nminibars,
339     const char **minilabels, int *minipercs)
340 {
341 	int output;
342 
343 	output = do_mixedgauge(conf, text, rows, cols, mainperc, nminibars,
344 	    minilabels, minipercs, false);
345 
346 	return (output);
347 }
348 
349 int
350 bsddialog_progressview (struct bsddialog_conf *conf, const char *text, int rows,
351     int cols, struct bsddialog_progviewconf *pvconf, unsigned int nminibar,
352     struct bsddialog_fileminibar *minibar)
353 {
354 	bool update;
355 	int perc, output, *minipercs;
356 	unsigned int i, mainperc, totaltodo;
357 	float readforsec;
358 	const char **minilabels;
359 	time_t tstart, told, tnew, refresh;
360 
361 	if ((minilabels = calloc(nminibar, sizeof(char*))) == NULL)
362 		RETURN_ERROR("Cannot allocate memory for minilabels");
363 	if ((minipercs = calloc(nminibar, sizeof(int))) == NULL)
364 		RETURN_ERROR("Cannot allocate memory for minipercs");
365 
366 	totaltodo = 0;
367 	for (i = 0; i < nminibar; i++) {
368 		totaltodo += minibar[i].size;
369 		minilabels[i] = minibar[i].label;
370 		minipercs[i] = minibar[i].status;
371 	}
372 
373 	refresh = pvconf->refresh == 0 ? 0 : pvconf->refresh - 1;
374 	output = BSDDIALOG_OK;
375 	i = 0;
376 	update = true;
377 	time(&told);
378 	tstart = told;
379 	while (!(bsddialog_interruptprogview || bsddialog_abortprogview)) {
380 		if (bsddialog_total_progview == 0 || totaltodo == 0)
381 			mainperc = 0;
382 		else
383 			mainperc = (bsddialog_total_progview * 100) / totaltodo;
384 
385 		time(&tnew);
386 		if (update || tnew > told + refresh) {
387 			output = do_mixedgauge(conf, text, rows, cols, mainperc,
388 			    nminibar, minilabels, minipercs, true);
389 			if (output == BSDDIALOG_ERROR)
390 				return (BSDDIALOG_ERROR);
391 
392 			move(SCREENLINES - 1, 2);
393 			clrtoeol();
394 			readforsec = ((tnew - tstart) == 0) ? 0 :
395 			    bsddialog_total_progview / (float)(tnew - tstart);
396 			printw(pvconf->fmtbottomstr, bsddialog_total_progview,
397 			    readforsec);
398 			refresh();
399 
400 			time(&told);
401 			update = false;
402 		}
403 
404 		if (i >= nminibar)
405 			break;
406 		if (minibar[i].status == BSDDIALOG_MG_FAILED)
407 			break;
408 
409 		perc = pvconf->callback(&minibar[i]);
410 
411 		if (minibar[i].status == BSDDIALOG_MG_DONE) { /*||perc >= 100)*/
412 			minipercs[i] = BSDDIALOG_MG_DONE;
413 			update = true;
414 			i++;
415 		} else if (minibar[i].status == BSDDIALOG_MG_FAILED || perc < 0) {
416 			minipercs[i] = BSDDIALOG_MG_FAILED;
417 			update = true;
418 		} else /* perc >= 0 */
419 			minipercs[i] = perc;
420 	}
421 
422 	free(minilabels);
423 	free(minipercs);
424 	return (output);
425 }
426 
427 int
428 bsddialog_rangebox(struct bsddialog_conf *conf, const char *text, int rows,
429     int cols, int min, int max, int *value)
430 {
431 	bool loop, buttupdate, barupdate;
432 	int y, x, h, w;
433 	int input, currvalue, output, sizebar, bigchange, positions;
434 	float perc;
435 	WINDOW *widget, *textpad, *bar, *shadow;
436 	struct buttons bs;
437 
438 	if (value == NULL)
439 		RETURN_ERROR("*value cannot be NULL");
440 
441 	if (min >= max)
442 		RETURN_ERROR("min >= max");
443 
444 	currvalue = *value;
445 	positions = max - min + 1;
446 
447 	get_buttons(conf, &bs, BUTTON_OK_LABEL, BUTTON_CANCEL_LABEL);
448 
449 	if (set_widget_size(conf, rows, cols, &h, &w) != 0)
450 		return (BSDDIALOG_ERROR);
451 	if (bar_autosize(conf, rows, cols, &h, &w, text, &bs) != 0)
452 		return (BSDDIALOG_ERROR);
453 	if (bar_checksize(h, w, &bs) != 0)
454 		return (BSDDIALOG_ERROR);
455 	if (set_widget_position(conf, &y, &x, h, w) != 0)
456 		return (BSDDIALOG_ERROR);
457 
458 	if (new_dialog(conf, &shadow, &widget, y, x, h, w, &textpad, text, &bs,
459 	    true) != 0)
460 		return (BSDDIALOG_ERROR);
461 
462 	doupdate();
463 
464 	prefresh(textpad, 0, 0, y+1, x+1+TEXTHMARGIN, y+h-7, x+w-1-TEXTHMARGIN);
465 
466 	sizebar = w - HBORDERS - (2 * BARPADDING) - 2;
467 	bigchange = MAX(1, sizebar/10);
468 
469 	bar = new_boxed_window(conf, y + h - 6, x + 1 + BARPADDING, 3,
470 	    sizebar + 2, RAISED);
471 
472 	loop = buttupdate = barupdate = true;
473 	while (loop) {
474 		if (buttupdate) {
475 			draw_buttons(widget, bs, true);
476 			wrefresh(widget);
477 			buttupdate = false;
478 		}
479 		if (barupdate) {
480 			perc = ((float)(currvalue - min)*100) / (positions-1);
481 			draw_bar(bar, 1, 1, sizebar, perc, true, currvalue);
482 			barupdate = false;
483 			wrefresh(bar);
484 		}
485 
486 		input = getch();
487 		switch(input) {
488 		case KEY_ENTER:
489 		case 10: /* Enter */
490 			output = bs.value[bs.curr];
491 			*value = currvalue;
492 			loop = false;
493 			break;
494 		case 27: /* Esc */
495 			if (conf->key.enable_esc) {
496 				output = BSDDIALOG_ESC;
497 				loop = false;
498 			}
499 			break;
500 		case '\t': /* TAB */
501 			bs.curr = (bs.curr + 1) % bs.nbuttons;
502 			buttupdate = true;
503 			break;
504 		case KEY_LEFT:
505 			if (bs.curr > 0) {
506 				bs.curr--;
507 				buttupdate = true;
508 			}
509 			break;
510 		case KEY_RIGHT:
511 			if (bs.curr < (int) bs.nbuttons - 1) {
512 				bs.curr++;
513 				buttupdate = true;
514 			}
515 			break;
516 		case KEY_HOME:
517 			currvalue = max;
518 			barupdate = true;
519 			break;
520 		case KEY_END:
521 			currvalue = min;
522 			barupdate = true;
523 			break;
524 		case KEY_NPAGE:
525 			currvalue -= bigchange;
526 			if (currvalue < min)
527 				currvalue = min;
528 			barupdate = true;
529 			break;
530 		case KEY_PPAGE:
531 			currvalue += bigchange;
532 			if (currvalue > max)
533 				currvalue = max;
534 			barupdate = true;
535 			break;
536 		case KEY_UP:
537 			if (currvalue < max) {
538 				currvalue++;
539 				barupdate = true;
540 			}
541 			break;
542 		case KEY_DOWN:
543 			if (currvalue > min) {
544 				currvalue--;
545 				barupdate = true;
546 			}
547 			break;
548 		case KEY_F(1):
549 			if (conf->key.f1_file == NULL &&
550 			    conf->key.f1_message == NULL)
551 				break;
552 			if (f1help(conf) != 0)
553 				return (BSDDIALOG_ERROR);
554 			/* No break, screen size can change */
555 		case KEY_RESIZE:
556 			/* Important for decreasing screen */
557 			hide_widget(y, x, h, w, conf->shadow);
558 			refresh();
559 
560 			if (set_widget_size(conf, rows, cols, &h, &w) != 0)
561 				return (BSDDIALOG_ERROR);
562 			if (bar_autosize(conf, rows, cols, &h, &w, text,
563 			    &bs) != 0)
564 				return (BSDDIALOG_ERROR);
565 			if (bar_checksize(h, w, &bs) != 0)
566 				return (BSDDIALOG_ERROR);
567 			if (set_widget_position(conf, &y, &x, h, w) != 0)
568 				return (BSDDIALOG_ERROR);
569 
570 			if (update_dialog(conf, shadow, widget,y, x, h, w,
571 			    textpad, text, &bs, true) != 0)
572 				return (BSDDIALOG_ERROR);
573 
574 			doupdate();
575 
576 			sizebar = w - HBORDERS - (2 * BARPADDING) - 2;
577 			bigchange = MAX(1, sizebar/10);
578 			wclear(bar);
579 			mvwin(bar, y + h - 6, x + 1 + BARPADDING);
580 			wresize(bar, 3, sizebar + 2);
581 			draw_borders(conf, bar, 3, sizebar+2, RAISED);
582 
583 			prefresh(textpad, 0, 0, y+1, x+1+TEXTHMARGIN, y+h-7,
584 			    x+w-1-TEXTHMARGIN);
585 
586 			barupdate = true;
587 			break;
588 		default:
589 			if (shortcut_buttons(input, &bs)) {
590 				output = bs.value[bs.curr];
591 				loop = false;
592 			}
593 		}
594 	}
595 
596 	delwin(bar);
597 	end_dialog(conf, shadow, widget, textpad);
598 
599 	return (output);
600 }
601 
602 int
603 bsddialog_pause(struct bsddialog_conf *conf, const char *text, int rows,
604     int cols, unsigned int sec)
605 {
606 	bool loop, buttupdate, barupdate;
607 	int output, y, x, h, w, input, tout, sizebar;
608 	float perc;
609 	WINDOW *widget, *textpad, *bar, *shadow;
610 	struct buttons bs;
611 
612 	get_buttons(conf, &bs, BUTTON_OK_LABEL, BUTTON_CANCEL_LABEL);
613 
614 	if (set_widget_size(conf, rows, cols, &h, &w) != 0)
615 		return (BSDDIALOG_ERROR);
616 	if (bar_autosize(conf, rows, cols, &h, &w, text, &bs) != 0)
617 		return (BSDDIALOG_ERROR);
618 	if (bar_checksize(h, w, &bs) != 0)
619 		return (BSDDIALOG_ERROR);
620 	if (set_widget_position(conf, &y, &x, h, w) != 0)
621 		return (BSDDIALOG_ERROR);
622 
623 	if (new_dialog(conf, &shadow, &widget, y, x, h, w, &textpad, text, &bs,
624 	    true) != 0)
625 		return (BSDDIALOG_ERROR);
626 
627 	doupdate();
628 
629 	prefresh(textpad, 0, 0, y+1, x+1+TEXTHMARGIN, y+h-7, x+w-1-TEXTHMARGIN);
630 
631 	sizebar = w - HBORDERS - (2 * BARPADDING) - 2;
632 	bar = new_boxed_window(conf, y + h - 6, x + 1 + BARPADDING, 3,
633 	    sizebar + 2, RAISED);
634 
635 	tout = sec;
636 	nodelay(stdscr, TRUE);
637 	timeout(1000);
638 	loop = buttupdate = barupdate = true;
639 	while (loop) {
640 		if (barupdate) {
641 			perc = (float)tout * 100 / sec;
642 			draw_bar(bar, 1, 1, sizebar, perc, true, tout);
643 			barupdate = false;
644 			wrefresh(bar);
645 		}
646 
647 		if (buttupdate) {
648 			draw_buttons(widget, bs, true);
649 			wrefresh(widget);
650 			buttupdate = false;
651 		}
652 
653 		input = getch();
654 		if (input < 0) { /* timeout */
655 			tout--;
656 			if (tout < 0) {
657 				output = BSDDIALOG_TIMEOUT;
658 				break;
659 			}
660 			else {
661 				barupdate = true;
662 				continue;
663 			}
664 		}
665 		switch(input) {
666 		case KEY_ENTER:
667 		case 10: /* Enter */
668 			output = bs.value[bs.curr];
669 			loop = false;
670 			break;
671 		case 27: /* Esc */
672 			if (conf->key.enable_esc) {
673 				output = BSDDIALOG_ESC;
674 				loop = false;
675 			}
676 			break;
677 		case '\t': /* TAB */
678 			bs.curr = (bs.curr + 1) % bs.nbuttons;
679 			buttupdate = true;
680 			break;
681 		case KEY_LEFT:
682 			if (bs.curr > 0) {
683 				bs.curr--;
684 				buttupdate = true;
685 			}
686 			break;
687 		case KEY_RIGHT:
688 			if (bs.curr < (int) bs.nbuttons - 1) {
689 				bs.curr++;
690 				buttupdate = true;
691 			}
692 			break;
693 		case KEY_F(1):
694 			if (conf->key.f1_file == NULL &&
695 			    conf->key.f1_message == NULL)
696 				break;
697 			if (f1help(conf) != 0)
698 				return (BSDDIALOG_ERROR);
699 			/* No break, screen size can change */
700 		case KEY_RESIZE:
701 			/* Important for decreasing screen */
702 			hide_widget(y, x, h, w, conf->shadow);
703 			refresh();
704 
705 			if (set_widget_size(conf, rows, cols, &h, &w) != 0)
706 				return (BSDDIALOG_ERROR);
707 			if (bar_autosize(conf, rows, cols, &h, &w, text,
708 			    &bs) != 0)
709 				return (BSDDIALOG_ERROR);
710 			if (bar_checksize(h, w, &bs) != 0)
711 				return (BSDDIALOG_ERROR);
712 			if (set_widget_position(conf, &y, &x, h, w) != 0)
713 				return (BSDDIALOG_ERROR);
714 
715 			if (update_dialog(conf, shadow, widget,y, x, h, w,
716 			    textpad, text, &bs, true) != 0)
717 				return (BSDDIALOG_ERROR);
718 
719 			doupdate();
720 
721 			sizebar = w - HBORDERS - (2 * BARPADDING) - 2;
722 			wclear(bar);
723 			mvwin(bar, y + h - 6, x + 1 + BARPADDING);
724 			wresize(bar, 3, sizebar + 2);
725 			draw_borders(conf, bar, 3, sizebar+2, LOWERED);
726 
727 			prefresh(textpad, 0, 0, y+1, x+1+TEXTHMARGIN, y+h-7,
728 			    x+w-1-TEXTHMARGIN);
729 
730 			barupdate = true;
731 			break;
732 		default:
733 			if (shortcut_buttons(input, &bs)) {
734 				output = bs.value[bs.curr];
735 				loop = false;
736 			}
737 		}
738 	}
739 
740 	nodelay(stdscr, FALSE);
741 
742 	delwin(bar);
743 	end_dialog(conf, shadow, widget, textpad);
744 
745 	return (output);
746 }