xref: /freebsd/contrib/bsddialog/lib/barbox.c (revision 84823cc70824c8d842f503d8c2e6d7b0c2d95b61)
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); /* number, always 1-byte-ch string */
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_min_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 	input = NULL;
155 	if (fd >= 0) {
156 		fd2 = dup(fd);
157 		if ((input = fdopen(fd2, "r")) == NULL)
158 			RETURN_ERROR("Cannot build FILE* from fd");
159 	}
160 
161 	mainloop = true;
162 	while (mainloop) {
163 		wrefresh(widget);
164 		prefresh(textpad, 0, 0, y+1, x+1+TEXTHMARGIN, y+h-4,
165 		    x+w-1-TEXTHMARGIN);
166 		draw_borders(conf, bar, 3, w-6, RAISED);
167 		draw_bar(bar, 1, 1, w-8, perc, false, -1 /*unused*/);
168 		wrefresh(bar);
169 		if (input == NULL) /* that is fd < 0 */
170 			break;
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); /* end string, no strlen */
197 			pntext[0] = ' ';
198 			pntext++;
199 		}
200 		pntext[0] = '\0';
201 		if (update_dialog(conf, shadow, widget, y, x, h, w, textpad,
202 		    ntext, NULL, false) != 0)
203 			return (BSDDIALOG_ERROR);
204 	}
205 
206 	if (input != NULL)
207 		fclose(input);
208 	delwin(bar);
209 	end_dialog(conf, shadow, widget, textpad);
210 
211 	return (BSDDIALOG_OK);
212 }
213 
214 /* Mixedgauge */
215 static int
216 do_mixedgauge(struct bsddialog_conf *conf, const char *text, int rows, int cols,
217     unsigned int mainperc, unsigned int nminibars, const char **minilabels,
218     int *minipercs, bool color)
219 {
220 	int i, retval, miniperc, y, x, h, w, ypad, max_minbarlen;
221 	int htextpad, htext, wtext;
222 	int colorperc, red, green;
223 	WINDOW *widget, *textpad, *bar, *shadow;
224 	char states[12][14] = {
225 		"  Succeeded  ", /* -1  */
226 		"   Failed    ", /* -2  */
227 		"   Passed    ", /* -3  */
228 		"  Completed  ", /* -4  */
229 		"   Checked   ", /* -5  */
230 		"    Done     ", /* -6  */
231 		"   Skipped   ", /* -7  */
232 		" In Progress ", /* -8  */
233 		"(blank)      ", /* -9  */
234 		"     N/A     ", /* -10 */
235 		"   Pending   ", /* -11 */
236 		"   UNKNOWN   ", /* < -11, no API */
237 	};
238 
239 	red   = bsddialog_color(BSDDIALOG_WHITE,BSDDIALOG_RED,  BSDDIALOG_BOLD);
240 	green = bsddialog_color(BSDDIALOG_WHITE,BSDDIALOG_GREEN,BSDDIALOG_BOLD);
241 
242 	max_minbarlen = 0;
243 	for (i = 0; i < (int)nminibars; i++)
244 		max_minbarlen = MAX(max_minbarlen, (int)strcols(minilabels[i]));
245 	max_minbarlen += 3 + 16; /* seps + [...] */
246 	max_minbarlen = MAX(max_minbarlen, MINMGBARWIDTH); /* mainbar */
247 
248 	if (set_widget_size(conf, rows, cols, &h, &w) != 0)
249 		return (BSDDIALOG_ERROR);
250 
251 	/* mixedgauge autosize */
252 	if (cols == BSDDIALOG_AUTOSIZE || rows == BSDDIALOG_AUTOSIZE) {
253 		if (text_size(conf, rows, cols, text, NULL, nminibars + 3,
254 		    max_minbarlen, &htext, &wtext) != 0)
255 			return (BSDDIALOG_ERROR);
256 	}
257 	if (cols == BSDDIALOG_AUTOSIZE)
258 		w = widget_min_width(conf, wtext, max_minbarlen, NULL);
259 	if (rows == BSDDIALOG_AUTOSIZE)
260 		h = widget_min_height(conf, htext, nminibars + 3, false);
261 
262 	/* mixedgauge checksize */
263 	if (w < max_minbarlen + 2)
264 		RETURN_ERROR("Few cols for this mixedgauge");
265 	if (h < 5 + (int)nminibars)
266 		RETURN_ERROR("Few rows for this mixedgauge");
267 
268 	if (set_widget_position(conf, &y, &x, h, w) != 0)
269 		return (BSDDIALOG_ERROR);
270 
271 	retval = new_dialog(conf, &shadow, &widget, y, x, h, w, &textpad, text,
272 	    NULL, false);
273 	if (retval == BSDDIALOG_ERROR)
274 		return (retval);
275 
276 	/* mini bars */
277 	for (i = 0; i < (int)nminibars; i++) {
278 		miniperc = minipercs[i];
279 		if (miniperc == BSDDIALOG_MG_BLANK)
280 			continue;
281 		/* label */
282 		if (color && (miniperc >= 0))
283 			wattron(widget, A_BOLD);
284 		mvwaddstr(widget, i+1, 2, minilabels[i]);
285 		if (color && (miniperc >= 0))
286 			wattroff(widget, A_BOLD);
287 		/* perc */
288 		if (miniperc < -11)
289 			mvwaddstr(widget, i+1, w-2-15, states[11]);
290 		else if (miniperc < 0) {
291 			mvwaddstr(widget, i+1, w-2-15, "[             ]");
292 			colorperc = -1;
293 			if (color && miniperc == BSDDIALOG_MG_FAILED)
294 				colorperc = red;
295 			if (color && miniperc == BSDDIALOG_MG_DONE)
296 				colorperc = green;
297 			if (colorperc != -1)
298 				wattron(widget, colorperc);
299 			miniperc = abs(miniperc + 1);
300 			mvwaddstr(widget, i+1, 1+w-2-15, states[miniperc]);
301 			if (colorperc != -1)
302 				wattroff(widget, colorperc);
303 		}
304 		else { /* miniperc >= 0 */
305 			if (miniperc > 100)
306 				miniperc = 100;
307 			mvwaddstr(widget, i+1, w-2-15, "[             ]");
308 			draw_bar(widget, i+1, 1+w-2-15, 13, miniperc, false,
309 			    -1 /*unused*/);
310 		}
311 	}
312 
313 	wrefresh(widget);
314 	getmaxyx(textpad, htextpad, i /* unused */);
315 	ypad =  y + h - 4 - htextpad;
316 	ypad = ypad < y+(int)nminibars ? y+(int)nminibars : ypad;
317 	prefresh(textpad, 0, 0, ypad, x+2, y+h-4, x+w-2);
318 
319 	/* main bar */
320 	bar = new_boxed_window(conf, y+h -4, x+3, 3, w-6, RAISED);
321 
322 	draw_bar(bar, 1, 1, w-8, mainperc, false, -1 /*unused*/);
323 
324 	wattron(bar, t.bar.color);
325 	mvwaddstr(bar, 0, 2, "Overall Progress");
326 	wattroff(bar, t.bar.color);
327 
328 	wrefresh(bar);
329 
330 	/* getch(); alternate mode (port devel/ncurses) shows nothing */
331 
332 	delwin(bar);
333 	end_dialog(conf, shadow, widget, textpad);
334 
335 	return (BSDDIALOG_OK);
336 }
337 
338 int
339 bsddialog_mixedgauge(struct bsddialog_conf *conf, const char *text, int rows,
340     int cols, unsigned int mainperc, unsigned int nminibars,
341     const char **minilabels, int *minipercs)
342 {
343 	int retval;
344 
345 	retval = do_mixedgauge(conf, text, rows, cols, mainperc, nminibars,
346 	    minilabels, minipercs, false);
347 
348 	return (retval);
349 }
350 
351 int
352 bsddialog_progressview (struct bsddialog_conf *conf, const char *text, int rows,
353     int cols, struct bsddialog_progviewconf *pvconf, unsigned int nminibar,
354     struct bsddialog_fileminibar *minibar)
355 {
356 	bool update;
357 	int perc, retval, *minipercs;
358 	unsigned int i, mainperc, totaltodo;
359 	float readforsec;
360 	const char **minilabels;
361 	time_t tstart, told, tnew, refresh;
362 
363 	if ((minilabels = calloc(nminibar, sizeof(char*))) == NULL)
364 		RETURN_ERROR("Cannot allocate memory for minilabels");
365 	if ((minipercs = calloc(nminibar, sizeof(int))) == NULL)
366 		RETURN_ERROR("Cannot allocate memory for minipercs");
367 
368 	totaltodo = 0;
369 	for (i = 0; i < nminibar; i++) {
370 		totaltodo += minibar[i].size;
371 		minilabels[i] = minibar[i].label;
372 		minipercs[i] = minibar[i].status;
373 	}
374 
375 	refresh = pvconf->refresh == 0 ? 0 : pvconf->refresh - 1;
376 	retval = BSDDIALOG_OK;
377 	i = 0;
378 	update = true;
379 	time(&told);
380 	tstart = told;
381 	while (!(bsddialog_interruptprogview || bsddialog_abortprogview)) {
382 		if (bsddialog_total_progview == 0 || totaltodo == 0)
383 			mainperc = 0;
384 		else
385 			mainperc = (bsddialog_total_progview * 100) / totaltodo;
386 
387 		time(&tnew);
388 		if (update || tnew > told + refresh) {
389 			retval = do_mixedgauge(conf, text, rows, cols, mainperc,
390 			    nminibar, minilabels, minipercs, true);
391 			if (retval == BSDDIALOG_ERROR)
392 				return (BSDDIALOG_ERROR);
393 
394 			move(SCREENLINES - 1, 2);
395 			clrtoeol();
396 			readforsec = ((tnew - tstart) == 0) ? 0 :
397 			    bsddialog_total_progview / (float)(tnew - tstart);
398 			printw(pvconf->fmtbottomstr, bsddialog_total_progview,
399 			    readforsec);
400 			refresh();
401 
402 			time(&told);
403 			update = false;
404 		}
405 
406 		if (i >= nminibar)
407 			break;
408 		if (minibar[i].status == BSDDIALOG_MG_FAILED)
409 			break;
410 
411 		perc = pvconf->callback(&minibar[i]);
412 
413 		if (minibar[i].status == BSDDIALOG_MG_DONE) { /*||perc >= 100)*/
414 			minipercs[i] = BSDDIALOG_MG_DONE;
415 			update = true;
416 			i++;
417 		} else if (minibar[i].status == BSDDIALOG_MG_FAILED || perc < 0) {
418 			minipercs[i] = BSDDIALOG_MG_FAILED;
419 			update = true;
420 		} else /* perc >= 0 */
421 			minipercs[i] = perc;
422 	}
423 
424 	free(minilabels);
425 	free(minipercs);
426 	return (retval);
427 }
428 
429 int
430 bsddialog_rangebox(struct bsddialog_conf *conf, const char *text, int rows,
431     int cols, int min, int max, int *value)
432 {
433 	bool loop, buttupdate, barupdate;
434 	int y, x, h, w;
435 	int currvalue, retval, sizebar, bigchange, positions;
436 	wint_t input;
437 	float perc;
438 	WINDOW *widget, *textpad, *bar, *shadow;
439 	struct buttons bs;
440 
441 	if (value == NULL)
442 		RETURN_ERROR("*value cannot be NULL");
443 
444 	if (min >= max)
445 		RETURN_ERROR("min >= max");
446 
447 	currvalue = *value;
448 	positions = max - min + 1;
449 
450 	get_buttons(conf, &bs, BUTTON_OK_LABEL, BUTTON_CANCEL_LABEL);
451 
452 	if (set_widget_size(conf, rows, cols, &h, &w) != 0)
453 		return (BSDDIALOG_ERROR);
454 	if (bar_autosize(conf, rows, cols, &h, &w, text, &bs) != 0)
455 		return (BSDDIALOG_ERROR);
456 	if (bar_checksize(h, w, &bs) != 0)
457 		return (BSDDIALOG_ERROR);
458 	if (set_widget_position(conf, &y, &x, h, w) != 0)
459 		return (BSDDIALOG_ERROR);
460 
461 	if (new_dialog(conf, &shadow, &widget, y, x, h, w, &textpad, text, &bs,
462 	    true) != 0)
463 		return (BSDDIALOG_ERROR);
464 
465 	doupdate();
466 
467 	prefresh(textpad, 0, 0, y+1, x+1+TEXTHMARGIN, y+h-7, x+w-1-TEXTHMARGIN);
468 
469 	sizebar = w - HBORDERS - (2 * BARPADDING) - 2;
470 	bigchange = MAX(1, sizebar/10);
471 
472 	bar = new_boxed_window(conf, y + h - 6, x + 1 + BARPADDING, 3,
473 	    sizebar + 2, RAISED);
474 
475 	loop = buttupdate = barupdate = true;
476 	while (loop) {
477 		if (buttupdate) {
478 			draw_buttons(widget, bs, true);
479 			wrefresh(widget);
480 			buttupdate = false;
481 		}
482 		if (barupdate) {
483 			perc = ((float)(currvalue - min)*100) / (positions-1);
484 			draw_bar(bar, 1, 1, sizebar, perc, true, currvalue);
485 			barupdate = false;
486 			wrefresh(bar);
487 		}
488 
489 		if (get_wch(&input) == ERR)
490 			continue;
491 		switch(input) {
492 		case KEY_ENTER:
493 		case 10: /* Enter */
494 			retval = bs.value[bs.curr];
495 			*value = currvalue;
496 			loop = false;
497 			break;
498 		case 27: /* Esc */
499 			if (conf->key.enable_esc) {
500 				retval = BSDDIALOG_ESC;
501 				loop = false;
502 			}
503 			break;
504 		case '\t': /* TAB */
505 			bs.curr = (bs.curr + 1) % bs.nbuttons;
506 			buttupdate = true;
507 			break;
508 		case KEY_LEFT:
509 			if (bs.curr > 0) {
510 				bs.curr--;
511 				buttupdate = true;
512 			}
513 			break;
514 		case KEY_RIGHT:
515 			if (bs.curr < (int) bs.nbuttons - 1) {
516 				bs.curr++;
517 				buttupdate = true;
518 			}
519 			break;
520 		case KEY_HOME:
521 			currvalue = max;
522 			barupdate = true;
523 			break;
524 		case KEY_END:
525 			currvalue = min;
526 			barupdate = true;
527 			break;
528 		case KEY_NPAGE:
529 			currvalue -= bigchange;
530 			if (currvalue < min)
531 				currvalue = min;
532 			barupdate = true;
533 			break;
534 		case KEY_PPAGE:
535 			currvalue += bigchange;
536 			if (currvalue > max)
537 				currvalue = max;
538 			barupdate = true;
539 			break;
540 		case KEY_UP:
541 			if (currvalue < max) {
542 				currvalue++;
543 				barupdate = true;
544 			}
545 			break;
546 		case KEY_DOWN:
547 			if (currvalue > min) {
548 				currvalue--;
549 				barupdate = true;
550 			}
551 			break;
552 		case KEY_F(1):
553 			if (conf->key.f1_file == NULL &&
554 			    conf->key.f1_message == NULL)
555 				break;
556 			if (f1help(conf) != 0)
557 				return (BSDDIALOG_ERROR);
558 			/* No break, screen size can change */
559 		case KEY_RESIZE:
560 			/* Important for decreasing screen */
561 			hide_widget(y, x, h, w, conf->shadow);
562 			refresh();
563 
564 			if (set_widget_size(conf, rows, cols, &h, &w) != 0)
565 				return (BSDDIALOG_ERROR);
566 			if (bar_autosize(conf, rows, cols, &h, &w, text,
567 			    &bs) != 0)
568 				return (BSDDIALOG_ERROR);
569 			if (bar_checksize(h, w, &bs) != 0)
570 				return (BSDDIALOG_ERROR);
571 			if (set_widget_position(conf, &y, &x, h, w) != 0)
572 				return (BSDDIALOG_ERROR);
573 
574 			if (update_dialog(conf, shadow, widget,y, x, h, w,
575 			    textpad, text, &bs, true) != 0)
576 				return (BSDDIALOG_ERROR);
577 
578 			doupdate();
579 
580 			sizebar = w - HBORDERS - (2 * BARPADDING) - 2;
581 			bigchange = MAX(1, sizebar/10);
582 			wclear(bar);
583 			mvwin(bar, y + h - 6, x + 1 + BARPADDING);
584 			wresize(bar, 3, sizebar + 2);
585 			draw_borders(conf, bar, 3, sizebar+2, RAISED);
586 
587 			prefresh(textpad, 0, 0, y+1, x+1+TEXTHMARGIN, y+h-7,
588 			    x+w-1-TEXTHMARGIN);
589 
590 			barupdate = true;
591 			break;
592 		default:
593 			if (shortcut_buttons(input, &bs)) {
594 				retval = bs.value[bs.curr];
595 				loop = false;
596 			}
597 		}
598 	}
599 
600 	delwin(bar);
601 	end_dialog(conf, shadow, widget, textpad);
602 
603 	return (retval);
604 }
605 
606 int
607 bsddialog_pause(struct bsddialog_conf *conf, const char *text, int rows,
608     int cols, unsigned int sec)
609 {
610 	bool loop, buttupdate, barupdate;
611 	int retval, y, x, h, w, tout, sizebar;
612 	wint_t input;
613 	float perc;
614 	WINDOW *widget, *textpad, *bar, *shadow;
615 	struct buttons bs;
616 
617 	get_buttons(conf, &bs, BUTTON_OK_LABEL, BUTTON_CANCEL_LABEL);
618 
619 	if (set_widget_size(conf, rows, cols, &h, &w) != 0)
620 		return (BSDDIALOG_ERROR);
621 	if (bar_autosize(conf, rows, cols, &h, &w, text, &bs) != 0)
622 		return (BSDDIALOG_ERROR);
623 	if (bar_checksize(h, w, &bs) != 0)
624 		return (BSDDIALOG_ERROR);
625 	if (set_widget_position(conf, &y, &x, h, w) != 0)
626 		return (BSDDIALOG_ERROR);
627 
628 	if (new_dialog(conf, &shadow, &widget, y, x, h, w, &textpad, text, &bs,
629 	    true) != 0)
630 		return (BSDDIALOG_ERROR);
631 
632 	doupdate();
633 
634 	prefresh(textpad, 0, 0, y+1, x+1+TEXTHMARGIN, y+h-7, x+w-1-TEXTHMARGIN);
635 
636 	sizebar = w - HBORDERS - (2 * BARPADDING) - 2;
637 	bar = new_boxed_window(conf, y + h - 6, x + 1 + BARPADDING, 3,
638 	    sizebar + 2, RAISED);
639 
640 	tout = sec;
641 	nodelay(stdscr, TRUE);
642 	timeout(1000);
643 	loop = buttupdate = barupdate = true;
644 	while (loop) {
645 		if (barupdate) {
646 			perc = (float)tout * 100 / sec;
647 			draw_bar(bar, 1, 1, sizebar, perc, true, tout);
648 			barupdate = false;
649 			wrefresh(bar);
650 		}
651 
652 		if (buttupdate) {
653 			draw_buttons(widget, bs, true);
654 			wrefresh(widget);
655 			buttupdate = false;
656 		}
657 
658 		if (get_wch(&input) == ERR) { /* timeout */
659 			tout--;
660 			if (tout < 0) {
661 				retval = BSDDIALOG_TIMEOUT;
662 				break;
663 			}
664 			else {
665 				barupdate = true;
666 				continue;
667 			}
668 		}
669 		switch(input) {
670 		case KEY_ENTER:
671 		case 10: /* Enter */
672 			retval = bs.value[bs.curr];
673 			loop = false;
674 			break;
675 		case 27: /* Esc */
676 			if (conf->key.enable_esc) {
677 				retval = BSDDIALOG_ESC;
678 				loop = false;
679 			}
680 			break;
681 		case '\t': /* TAB */
682 			bs.curr = (bs.curr + 1) % bs.nbuttons;
683 			buttupdate = true;
684 			break;
685 		case KEY_LEFT:
686 			if (bs.curr > 0) {
687 				bs.curr--;
688 				buttupdate = true;
689 			}
690 			break;
691 		case KEY_RIGHT:
692 			if (bs.curr < (int) bs.nbuttons - 1) {
693 				bs.curr++;
694 				buttupdate = true;
695 			}
696 			break;
697 		case KEY_F(1):
698 			if (conf->key.f1_file == NULL &&
699 			    conf->key.f1_message == NULL)
700 				break;
701 			if (f1help(conf) != 0)
702 				return (BSDDIALOG_ERROR);
703 			/* No break, screen size can change */
704 		case KEY_RESIZE:
705 			/* Important for decreasing screen */
706 			hide_widget(y, x, h, w, conf->shadow);
707 			refresh();
708 
709 			if (set_widget_size(conf, rows, cols, &h, &w) != 0)
710 				return (BSDDIALOG_ERROR);
711 			if (bar_autosize(conf, rows, cols, &h, &w, text,
712 			    &bs) != 0)
713 				return (BSDDIALOG_ERROR);
714 			if (bar_checksize(h, w, &bs) != 0)
715 				return (BSDDIALOG_ERROR);
716 			if (set_widget_position(conf, &y, &x, h, w) != 0)
717 				return (BSDDIALOG_ERROR);
718 
719 			if (update_dialog(conf, shadow, widget,y, x, h, w,
720 			    textpad, text, &bs, true) != 0)
721 				return (BSDDIALOG_ERROR);
722 
723 			doupdate();
724 
725 			sizebar = w - HBORDERS - (2 * BARPADDING) - 2;
726 			wclear(bar);
727 			mvwin(bar, y + h - 6, x + 1 + BARPADDING);
728 			wresize(bar, 3, sizebar + 2);
729 			draw_borders(conf, bar, 3, sizebar+2, LOWERED);
730 
731 			prefresh(textpad, 0, 0, y+1, x+1+TEXTHMARGIN, y+h-7,
732 			    x+w-1-TEXTHMARGIN);
733 
734 			barupdate = true;
735 			break;
736 		default:
737 			if (shortcut_buttons(input, &bs)) {
738 				retval = bs.value[bs.curr];
739 				loop = false;
740 			}
741 		}
742 	}
743 
744 	nodelay(stdscr, FALSE);
745 
746 	delwin(bar);
747 	end_dialog(conf, shadow, widget, textpad);
748 
749 	return (retval);
750 }