xref: /freebsd/contrib/bsddialog/lib/barbox.c (revision 61ba55bc)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2021-2023 Alfonso Sabato Siciliano
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <curses.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <time.h>
32 #include <unistd.h>
33 
34 #include "bsddialog.h"
35 #include "bsddialog_progressview.h"
36 #include "bsddialog_theme.h"
37 #include "lib_util.h"
38 
39 #define BARPADDING   2  /* widget border | BARPADDING | box bar */
40 #define BOXBORDERS   2
41 #define MIN_WBAR     15
42 #define MIN_WBOX     (BARPADDING + BOXBORDERS + MIN_WBAR + BARPADDING)
43 #define MIN_WMGBAR   18
44 #define MIN_WMGBOX   (BARPADDING + BOXBORDERS + MIN_WMGBAR + BARPADDING)
45 #define HBOX         3
46 #define WBOX(d)      ((d)->w - BORDERS - BARPADDING - BARPADDING)
47 #define WBAR(d)      (WBOX(d) - BOXBORDERS)
48 
49 bool bsddialog_interruptprogview;
50 bool bsddialog_abortprogview;
51 long long int bsddialog_total_progview;
52 
53 static const char states[12][14] = {
54 	"  Succeeded  ", /* -1  */
55 	"   Failed    ", /* -2  */
56 	"   Passed    ", /* -3  */
57 	"  Completed  ", /* -4  */
58 	"   Checked   ", /* -5  */
59 	"    Done     ", /* -6  */
60 	"   Skipped   ", /* -7  */
61 	" In Progress ", /* -8  */
62 	"(blank)      ", /* -9  */
63 	"     N/A     ", /* -10 */
64 	"   Pending   ", /* -11 */
65 	"   UNKNOWN   ", /* < -11, no API */
66 };
67 
68 struct bar {
69 	bool toupdate;
70 	WINDOW *win;
71 	int y;           /* bar y in win */
72 	int x;           /* bar x in win */
73 	int w;           /* width in win */
74 	int perc;        /* barlen = (w * perc) / 100 */
75 	const char* fmt; /* format for label */
76 	int label;       /* rangebox and pause perc!=label */
77 };
78 
draw_bar(struct bar * b)79 static void draw_bar(struct bar *b)
80 {
81 	int barlen, xlabel;
82 	chtype ch;
83 	char label[128];
84 
85 	barlen = b->perc > 0 ? (b->perc * b->w) / 100 : 0;
86 
87 	ch = ' ' | t.bar.f_color;
88 	mvwhline(b->win, b->y, b->x, ch, barlen);
89 	ch = ' ' | t.bar.color;
90 	mvwhline(b->win, b->y, b->x + barlen, ch, b->w - barlen);
91 
92 	sprintf(label, b->fmt, b->label);
93 	xlabel = b->x + b->w/2 - (int)strlen(label)/2; /* 1-byte-char string */
94 	wattron(b->win, t.bar.color);   /* x+barlen < xlabel */
95 	mvwaddstr(b->win, b->y, xlabel, label);
96 	wattroff(b->win, t.bar.color);
97 	wattron(b->win, t.bar.f_color); /* x+barlen >= xlabel */
98 	mvwaddnstr(b->win, b->y, xlabel, label, MAX((b->x+barlen) - xlabel, 0));
99 	wattroff(b->win, t.bar.f_color);
100 
101 	if (b->toupdate)
102 		wnoutrefresh(b->win);
103 	b->toupdate = false;
104 }
105 
update_barbox(struct dialog * d,struct bar * b,bool buttons)106 static void update_barbox(struct dialog *d, struct bar *b, bool buttons)
107 {
108 	int y;
109 
110 	y = d->y + d->h - BORDER - HBOX;
111 	if (buttons)
112 		y -= HBUTTONS;
113 	update_box(d->conf, b->win, y, d->x + BORDER + BARPADDING, HBOX,
114 	    WBOX(d), RAISED);
115 }
116 
117 int
bsddialog_gauge(struct bsddialog_conf * conf,const char * text,int rows,int cols,unsigned int perc,int fd,const char * sep,const char * end)118 bsddialog_gauge(struct bsddialog_conf *conf, const char *text, int rows,
119     int cols, unsigned int perc, int fd, const char *sep, const char *end)
120 {
121 	bool mainloop;
122 	int fd2;
123 	FILE *input;
124 	char inputbuf[2048], ntext[2048], *pntext;
125 	struct bar b;
126 	struct dialog d;
127 
128 	if (prepare_dialog(conf, text, rows, cols, &d) != 0)
129 		return (BSDDIALOG_ERROR);
130 	if ((b.win = newwin(1, 1, 1, 1)) == NULL)
131 		RETURN_ERROR("Cannot build WINDOW bar");
132 	b.y = b.x = 1;
133 	b.fmt = "%3d%%";
134 
135 	input = NULL;
136 	if (fd >= 0) {
137 		CHECK_PTR(sep);
138 		CHECK_PTR(end);
139 
140 		fd2 = dup(fd);
141 		if ((input = fdopen(fd2, "r")) == NULL)
142 			RETURN_FMTERROR("Cannot build FILE* from fd %d", fd);
143 	}
144 
145 	perc = MIN(perc, 100);
146 	mainloop = true;
147 	while (mainloop) {
148 		if (d.built) {
149 			hide_dialog(&d);
150 			refresh(); /* Important for decreasing screen */
151 		}
152 		if (dialog_size_position(&d, HBOX, MIN_WBOX, NULL) != 0)
153 			return (BSDDIALOG_ERROR);
154 		if (draw_dialog(&d))
155 			return (BSDDIALOG_ERROR);
156 		if (d.built)
157 			refresh(); /* fix grey lines expanding screen */
158 		TEXTPAD(&d, HBOX);
159 		update_barbox(&d, &b, false);
160 		b.w = WBAR(&d);
161 		b.perc = b.label = perc;
162 		b.toupdate = true;
163 		draw_bar(&b);
164 		doupdate();
165 		if (input == NULL) /* that is fd < 0 */
166 			break;
167 
168 		while (true) {
169 			fscanf(input, "%s", inputbuf);
170 			if (strcmp(inputbuf, end) == 0) {
171 				mainloop = false;
172 				break;
173 			}
174 			if (strcmp(inputbuf, sep) == 0)
175 				break;
176 		}
177 		if (mainloop == false)
178 			break;
179 		fscanf(input, "%d", &perc);
180 		perc = MIN(perc, 100);
181 		pntext = &ntext[0];
182 		ntext[0] = '\0';
183 		while (true) {
184 			fscanf(input, "%s", inputbuf);
185 			if (strcmp(inputbuf, end) == 0) {
186 				mainloop = false;
187 				break;
188 			}
189 			if (strcmp(inputbuf, sep) == 0)
190 				break;
191 			strcpy(pntext, inputbuf);
192 			pntext += strlen(inputbuf); /* end string, no strlen */
193 			pntext[0] = ' ';
194 			pntext++;
195 		}
196 		pntext[0] = '\0';
197 		d.text = ntext;
198 	}
199 
200 	if (input != NULL)
201 		fclose(input);
202 	delwin(b.win);
203 	end_dialog(&d);
204 
205 	return (BSDDIALOG_OK);
206 }
207 
208 /* Mixedgauge */
209 static int
do_mixedgauge(struct bsddialog_conf * conf,const char * text,int rows,int cols,unsigned int mainperc,unsigned int nminibars,const char ** minilabels,int * minipercs,bool color)210 do_mixedgauge(struct bsddialog_conf *conf, const char *text, int rows, int cols,
211     unsigned int mainperc, unsigned int nminibars, const char **minilabels,
212     int *minipercs, bool color)
213 {
214 	int i, miniperc, max_minibarlen;
215 	int ystext, htext;
216 	int minicolor, red, green;
217 	struct bar b;
218 	struct dialog d;
219 
220 	CHECK_ARRAY(nminibars, minilabels);
221 	CHECK_ARRAY(nminibars, minipercs);
222 
223 	red   = bsddialog_color(BSDDIALOG_WHITE,BSDDIALOG_RED,  BSDDIALOG_BOLD);
224 	green = bsddialog_color(BSDDIALOG_WHITE,BSDDIALOG_GREEN,BSDDIALOG_BOLD);
225 
226 	max_minibarlen = 0;
227 	for (i = 0; i < (int)nminibars; i++)
228 		max_minibarlen = MAX(max_minibarlen,
229 		    (int)strcols(CHECK_STR(minilabels[i])));
230 	max_minibarlen += 3 + 16; /* seps + [...] */
231 	max_minibarlen = MAX(max_minibarlen, MIN_WMGBOX); /* mainbar */
232 
233 	if (prepare_dialog(conf, text, rows, cols, &d) != 0)
234 		return (BSDDIALOG_ERROR);
235 	if (dialog_size_position(&d, nminibars + HBOX, max_minibarlen,
236 	    &htext) != 0)
237 		return (BSDDIALOG_ERROR);
238 	if (draw_dialog(&d) != 0)
239 		return (BSDDIALOG_ERROR);
240 
241 	/* mini bars */
242 	b.win = d.widget;
243 	b.x = 1 + d.w - 2 - 15;
244 	b.w = 13;
245 	b.fmt = "%3d%%";
246 	b.toupdate = false;
247 	for (i = 0; i < (int)nminibars; i++) {
248 		miniperc = minipercs[i];
249 		/* label */
250 		if (color && miniperc >= 0)
251 			wattron(d.widget, A_BOLD);
252 		mvwaddstr(d.widget, i+1, 2, CHECK_STR(minilabels[i]));
253 		if (color && miniperc >= 0)
254 			wattroff(d.widget, A_BOLD);
255 		/* perc */
256 		if (miniperc == BSDDIALOG_MG_BLANK)
257 			continue;
258 		mvwaddstr(d.widget, i+1, d.w-2-15, "[             ]");
259 		if (miniperc >= 0) {
260 			b.y = i + 1;
261 			b.perc = b.label = MIN(miniperc, 100);
262 			draw_bar(&b);
263 		} else { /* miniperc < 0 */
264 			if (miniperc < BSDDIALOG_MG_PENDING)
265 				miniperc = -12; /* UNKNOWN */
266 			minicolor = t.dialog.color;
267 			if (color && miniperc == BSDDIALOG_MG_FAILED)
268 				minicolor = red;
269 			else if (color && miniperc == BSDDIALOG_MG_DONE)
270 				minicolor = green;
271 			wattron(d.widget, minicolor);
272 			miniperc = abs(miniperc + 1);
273 			mvwaddstr(d.widget, i+1, 1+d.w-2-15, states[miniperc]);
274 			wattroff(d.widget, minicolor);
275 		}
276 	}
277 	wnoutrefresh(d.widget);
278 
279 	/* text */
280 	ystext = MAX(d.h - BORDERS - htext - HBOX, (int)nminibars);
281 	rtextpad(&d, 0, 0, ystext, HBOX);
282 
283 	/* main bar */
284 	if ((b.win = newwin(1, 1, 1, 1)) == NULL)
285 		RETURN_ERROR("Cannot build WINDOW bar");
286 	update_barbox(&d, &b, false);
287 	wattron(b.win, t.bar.color);
288 	mvwaddstr(b.win, 0, 2, "Overall Progress");
289 	wattroff(b.win, t.bar.color);
290 
291 	b.y = b.x = 1;
292 	b.w = WBAR(&d);
293 	b.fmt = "%3d%%";
294 	b.perc = b.label = MIN(mainperc, 100);
295 	b.toupdate = true;
296 	draw_bar(&b);
297 
298 	doupdate();
299 	/* getch(); to test with "alternate mode" */
300 
301 	delwin(b.win);
302 	end_dialog(&d);
303 
304 	return (BSDDIALOG_OK);
305 }
306 
307 int
bsddialog_mixedgauge(struct bsddialog_conf * conf,const char * text,int rows,int cols,unsigned int mainperc,unsigned int nminibars,const char ** minilabels,int * minipercs)308 bsddialog_mixedgauge(struct bsddialog_conf *conf, const char *text, int rows,
309     int cols, unsigned int mainperc, unsigned int nminibars,
310     const char **minilabels, int *minipercs)
311 {
312 	int retval;
313 
314 	retval = do_mixedgauge(conf, text, rows, cols, mainperc, nminibars,
315 	    minilabels, minipercs, false);
316 
317 	return (retval);
318 }
319 
320 int
bsddialog_progressview(struct bsddialog_conf * conf,const char * text,int rows,int cols,struct bsddialog_progviewconf * pvconf,unsigned int nminibar,struct bsddialog_fileminibar * minibar)321 bsddialog_progressview (struct bsddialog_conf *conf, const char *text, int rows,
322     int cols, struct bsddialog_progviewconf *pvconf, unsigned int nminibar,
323     struct bsddialog_fileminibar *minibar)
324 {
325 	bool update;
326 	int perc, retval, *minipercs;
327 	unsigned int i, mainperc, totaltodo;
328 	float readforsec;
329 	const char **minilabels;
330 	time_t tstart, told, tnew, refresh;
331 
332 	if ((minilabels = calloc(nminibar, sizeof(char*))) == NULL)
333 		RETURN_ERROR("Cannot allocate memory for minilabels");
334 	if ((minipercs = calloc(nminibar, sizeof(int))) == NULL)
335 		RETURN_ERROR("Cannot allocate memory for minipercs");
336 
337 	totaltodo = 0;
338 	for (i = 0; i < nminibar; i++) {
339 		totaltodo += minibar[i].size;
340 		minilabels[i] = minibar[i].label;
341 		minipercs[i] = minibar[i].status;
342 	}
343 
344 	refresh = pvconf->refresh == 0 ? 0 : pvconf->refresh - 1;
345 	retval = BSDDIALOG_OK;
346 	i = 0;
347 	update = true;
348 	time(&told);
349 	tstart = told;
350 	while (!(bsddialog_interruptprogview || bsddialog_abortprogview)) {
351 		if (bsddialog_total_progview == 0 || totaltodo == 0)
352 			mainperc = 0;
353 		else
354 			mainperc = (bsddialog_total_progview * 100) / totaltodo;
355 
356 		time(&tnew);
357 		if (update || tnew > told + refresh) {
358 			retval = do_mixedgauge(conf, text, rows, cols, mainperc,
359 			    nminibar, minilabels, minipercs, true);
360 			if (retval == BSDDIALOG_ERROR)
361 				return (BSDDIALOG_ERROR);
362 
363 			move(SCREENLINES - 1, 2);
364 			clrtoeol();
365 			readforsec = ((tnew - tstart) == 0) ? 0 :
366 			    bsddialog_total_progview / (float)(tnew - tstart);
367 			printw(pvconf->fmtbottomstr, bsddialog_total_progview,
368 			    readforsec);
369 			refresh();
370 
371 			time(&told);
372 			update = false;
373 		}
374 
375 		if (i >= nminibar)
376 			break;
377 		if (minibar[i].status == BSDDIALOG_MG_FAILED)
378 			break;
379 
380 		perc = pvconf->callback(&minibar[i]);
381 
382 		if (minibar[i].status == BSDDIALOG_MG_DONE) { /*||perc >= 100)*/
383 			minipercs[i] = BSDDIALOG_MG_DONE;
384 			update = true;
385 			i++;
386 		} else if (minibar[i].status == BSDDIALOG_MG_FAILED ||
387 		    perc < 0) {
388 			minipercs[i] = BSDDIALOG_MG_FAILED;
389 			update = true;
390 		} else /* perc >= 0 */
391 			minipercs[i] = perc;
392 	}
393 
394 	free(minilabels);
395 	free(minipercs);
396 	return (retval);
397 }
398 
rangebox_redraw(struct dialog * d,struct bar * b,int * bigchange)399 static int rangebox_redraw(struct dialog *d, struct bar *b, int *bigchange)
400 {
401 	if (d->built) {
402 		hide_dialog(d);
403 		refresh(); /* Important for decreasing screen */
404 	}
405 	if (dialog_size_position(d, HBOX, MIN_WBOX, NULL) != 0)
406 		return (BSDDIALOG_ERROR);
407 	if (draw_dialog(d) != 0)
408 		return (BSDDIALOG_ERROR);
409 	if (d->built)
410 		refresh(); /* Important to fix grey lines expanding screen */
411 	TEXTPAD(d, HBOX + HBUTTONS);
412 
413 	b->w = WBAR(d);
414 	*bigchange = MAX(1, b->w  / 10);
415 	update_barbox(d, b, true);
416 	b->toupdate = true;
417 
418 	return (0);
419 }
420 
421 int
bsddialog_rangebox(struct bsddialog_conf * conf,const char * text,int rows,int cols,int min,int max,int * value)422 bsddialog_rangebox(struct bsddialog_conf *conf, const char *text, int rows,
423     int cols, int min, int max, int *value)
424 {
425 	bool loop;
426 	int currvalue, retval, bigchange, positions;
427 	wint_t input;
428 	struct bar b;
429 	struct dialog d;
430 
431 	CHECK_PTR(value);
432 	if (min >= max)
433 		RETURN_FMTERROR("min (%d) >= max (%d)", min, max);
434 	if (*value < min)
435 		RETURN_FMTERROR("value (%d) < min (%d)", *value, min);
436 	if (*value > max)
437 		RETURN_FMTERROR("value (%d) > max (%d)", *value, max);
438 
439 	currvalue = *value;
440 	positions = max - min + 1;
441 
442 	if (prepare_dialog(conf, text, rows, cols, &d) != 0)
443 		return (BSDDIALOG_ERROR);
444 	set_buttons(&d, true, OK_LABEL, CANCEL_LABEL);
445 	if ((b.win = newwin(1, 1, 1, 1)) == NULL)
446 		RETURN_ERROR("Cannot build WINDOW bar");
447 	b.y = b.x = 1;
448 	b.fmt = "%d";
449 	if (rangebox_redraw(&d, &b, &bigchange) != 0)
450 		return (BSDDIALOG_ERROR);
451 
452 	loop = true;
453 	while (loop) {
454 		if (b.toupdate) {
455 			b.perc = ((float)(currvalue - min)*100) / (positions-1);
456 			b.label = currvalue;
457 			draw_bar(&b);
458 		}
459 		doupdate();
460 		if (get_wch(&input) == ERR)
461 			continue;
462 		switch(input) {
463 		case KEY_ENTER:
464 		case 10: /* Enter */
465 			retval = BUTTONVALUE(d.bs);
466 			loop = false;
467 			break;
468 		case 27: /* Esc */
469 			if (conf->key.enable_esc) {
470 				retval = BSDDIALOG_ESC;
471 				loop = false;
472 			}
473 			break;
474 		case '\t': /* TAB */
475 		case KEY_RIGHT:
476 			d.bs.curr = (d.bs.curr + 1) % d.bs.nbuttons;
477 			DRAW_BUTTONS(d);
478 			break;
479 		case KEY_LEFT:
480 			d.bs.curr--;
481 			if (d.bs.curr < 0)
482 				 d.bs.curr = d.bs.nbuttons - 1;
483 			DRAW_BUTTONS(d);
484 			break;
485 		case KEY_HOME:
486 			currvalue = max;
487 			b.toupdate = true;
488 			break;
489 		case KEY_END:
490 			currvalue = min;
491 			b.toupdate = true;
492 			break;
493 		case KEY_NPAGE:
494 			currvalue -= bigchange;
495 			if (currvalue < min)
496 				currvalue = min;
497 			b.toupdate = true;
498 			break;
499 		case KEY_PPAGE:
500 			currvalue += bigchange;
501 			if (currvalue > max)
502 				currvalue = max;
503 			b.toupdate = true;
504 			break;
505 		case KEY_UP:
506 			if (currvalue < max) {
507 				currvalue++;
508 				b.toupdate = true;
509 			}
510 			break;
511 		case KEY_DOWN:
512 			if (currvalue > min) {
513 				currvalue--;
514 				b.toupdate = true;
515 			}
516 			break;
517 		case KEY_F(1):
518 			if (conf->key.f1_file == NULL &&
519 			    conf->key.f1_message == NULL)
520 				break;
521 			if (f1help_dialog(conf) != 0)
522 				return (BSDDIALOG_ERROR);
523 			if (rangebox_redraw(&d, &b, &bigchange) != 0)
524 				return (BSDDIALOG_ERROR);
525 			break;
526 		case KEY_RESIZE:
527 			if (rangebox_redraw(&d, &b, &bigchange) != 0)
528 				return (BSDDIALOG_ERROR);
529 			break;
530 		default:
531 			if (shortcut_buttons(input, &d.bs)) {
532 				DRAW_BUTTONS(d);
533 				doupdate();
534 				retval = BUTTONVALUE(d.bs);
535 				loop = false;
536 			}
537 		}
538 	}
539 
540 	*value = currvalue;
541 
542 	delwin(b.win);
543 	end_dialog(&d);
544 
545 	return (retval);
546 }
547 
pause_redraw(struct dialog * d,struct bar * b)548 static int pause_redraw(struct dialog *d, struct bar *b)
549 {
550 	if (d->built) {
551 		hide_dialog(d);
552 		refresh(); /* Important for decreasing screen */
553 	}
554 	if (dialog_size_position(d, HBOX, MIN_WBOX, NULL) != 0)
555 		return (BSDDIALOG_ERROR);
556 	if (draw_dialog(d) != 0)
557 		return (BSDDIALOG_ERROR);
558 	if (d->built)
559 		refresh(); /* Important to fix grey lines expanding screen */
560 	TEXTPAD(d, HBOX + HBUTTONS);
561 
562 	b->w = WBAR(d);
563 	update_barbox(d, b, true);
564 	b->toupdate = true;
565 
566 	return (0);
567 }
568 
569 int
bsddialog_pause(struct bsddialog_conf * conf,const char * text,int rows,int cols,unsigned int * seconds)570 bsddialog_pause(struct bsddialog_conf *conf, const char *text, int rows,
571     int cols, unsigned int *seconds)
572 {
573 	bool loop;
574 	int retval, tout;
575 	wint_t input;
576 	struct bar b;
577 	struct dialog d;
578 
579 	CHECK_PTR(seconds);
580 	if (prepare_dialog(conf, text, rows, cols, &d) != 0)
581 		return (BSDDIALOG_ERROR);
582 	set_buttons(&d, true, OK_LABEL, CANCEL_LABEL);
583 	if ((b.win = newwin(1, 1, 1, 1)) == NULL)
584 		RETURN_ERROR("Cannot build WINDOW bar");
585 	b.y = b.x = 1;
586 	b.fmt = "%d";
587 	if (pause_redraw(&d, &b) != 0)
588 		return (BSDDIALOG_ERROR);
589 
590 	tout = *seconds;
591 	nodelay(stdscr, TRUE);
592 	timeout(1000);
593 	loop = true;
594 	while (loop) {
595 		if (b.toupdate) {
596 			b.perc = (float)tout * 100 / *seconds;
597 			b.label = tout;
598 			draw_bar(&b);
599 		}
600 		doupdate();
601 		if (get_wch(&input) == ERR) { /* timeout */
602 			tout--;
603 			if (tout < 0) {
604 				retval = BSDDIALOG_TIMEOUT;
605 				break;
606 			}
607 			else {
608 				b.toupdate = true;
609 				continue;
610 			}
611 		}
612 		switch(input) {
613 		case KEY_ENTER:
614 		case 10: /* Enter */
615 			retval = BUTTONVALUE(d.bs);
616 			loop = false;
617 			break;
618 		case 27: /* Esc */
619 			if (conf->key.enable_esc) {
620 				retval = BSDDIALOG_ESC;
621 				loop = false;
622 			}
623 			break;
624 		case '\t': /* TAB */
625 		case KEY_RIGHT:
626 			d.bs.curr = (d.bs.curr + 1) % d.bs.nbuttons;
627 			DRAW_BUTTONS(d);
628 			break;
629 		case KEY_LEFT:
630 			d.bs.curr--;
631 			if (d.bs.curr < 0)
632 				 d.bs.curr = d.bs.nbuttons - 1;
633 			DRAW_BUTTONS(d);
634 			break;
635 		case KEY_F(1):
636 			if (conf->key.f1_file == NULL &&
637 			    conf->key.f1_message == NULL)
638 				break;
639 			if (f1help_dialog(conf) != 0)
640 				return (BSDDIALOG_ERROR);
641 			if (pause_redraw(&d, &b) != 0)
642 				return (BSDDIALOG_ERROR);
643 			break;
644 		case KEY_RESIZE:
645 			if (pause_redraw(&d, &b) != 0)
646 				return (BSDDIALOG_ERROR);
647 			break;
648 		default:
649 			if (shortcut_buttons(input, &d.bs)) {
650 				DRAW_BUTTONS(d);
651 				doupdate();
652 				retval = BUTTONVALUE(d.bs);
653 				loop = false;
654 			}
655 		}
656 	}
657 	nodelay(stdscr, FALSE);
658 
659 	*seconds = MAX(tout, 0);
660 
661 	delwin(b.win);
662 	end_dialog(&d);
663 
664 	return (retval);
665 }
666