xref: /freebsd/contrib/bsddialog/lib/barbox.c (revision a6d8be451f62d425b71a4874f7d4e133b9fb393c)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2021-2024 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  /* Dialog 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 /* Mixedgauge main bar */
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 
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 
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
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 void
210 mvwaddcstr(WINDOW *win, int y, int x, const char *mbstring, unsigned int cols)
211 {
212 	size_t charlen, n, w;
213 	mbstate_t mbs;
214 	const char *pmbstring;
215 	wchar_t wch;
216 
217 	w = n = 0;
218 	pmbstring = mbstring;
219 	memset(&mbs, 0, sizeof(mbs));
220 	while ((charlen = mbrlen(pmbstring, MB_CUR_MAX, &mbs)) != 0 &&
221 	    charlen != (size_t)-1 && charlen != (size_t)-2) {
222 		mbtowc(&wch, pmbstring, charlen);
223 		w += (wch == L'\t') ? TABSIZE : wcwidth(wch);
224 		if (w > cols)
225 			break;
226 		pmbstring += charlen;
227 		n += charlen;
228 	}
229 	mvwaddnstr(win, y, x, mbstring, n);
230 	if(w > cols)
231 		mvwaddstr(win, y, (x + cols) - 3, "...");
232 }
233 
234 static int
235 mixedgauge_size_position(struct dialog *d, int nminibars,
236     const char **minilabels, int *htext)
237 {
238 	int i, max_minibarlen;
239 
240 	max_minibarlen = 0;
241 	for (i = 0; i < (int)nminibars; i++)
242 		max_minibarlen = MAX(max_minibarlen,
243 		    (int)strcols(CHECK_STR(minilabels[i])));
244 	max_minibarlen += 18; /* ' '<max_minibarlen>' ['13'] ' */
245 	max_minibarlen = MAX(max_minibarlen, MIN_WMGBOX); /* mainbar */
246 
247 	if (set_widget_size(d->conf, d->rows, d->cols, &d->h, &d->w) != 0)
248 		return (BSDDIALOG_ERROR);
249 	if (set_widget_autosize(d->conf, d->rows, d->cols, &d->h, &d->w,
250 	    d->text, htext, &d->bs, nminibars + HBOX, max_minibarlen) != 0)
251 		return (BSDDIALOG_ERROR);
252 	if (widget_checksize(d->h, d->w, &d->bs, nminibars + HBOX,
253 	    MIN_WMGBOX) != 0)
254 		return (BSDDIALOG_ERROR);
255 	if (set_widget_position(d->conf, &d->y, &d->x, d->h, d->w) != 0)
256 		return (BSDDIALOG_ERROR);
257 
258 	return (0);
259 }
260 
261 static int
262 do_mixedgauge(struct bsddialog_conf *conf, const char *text, int rows, int cols,
263     unsigned int mainperc, unsigned int nminibars, const char **minilabels,
264     int *minipercs, bool color)
265 {
266 	int i, miniperc;
267 	int ystext, htext;
268 	int minicolor, red, green;
269 	struct bar b;
270 	struct dialog d;
271 
272 	CHECK_ARRAY(nminibars, minilabels);
273 	CHECK_ARRAY(nminibars, minipercs);
274 
275 	red   = bsddialog_color(BSDDIALOG_WHITE,BSDDIALOG_RED,  BSDDIALOG_BOLD);
276 	green = bsddialog_color(BSDDIALOG_WHITE,BSDDIALOG_GREEN,BSDDIALOG_BOLD);
277 
278 	if (prepare_dialog(conf, text, rows, cols, &d) != 0)
279 		return (BSDDIALOG_ERROR);
280 	if (mixedgauge_size_position(&d, nminibars, minilabels, &htext) != 0)
281 		return (BSDDIALOG_ERROR);
282 	if (draw_dialog(&d) != 0)
283 		return (BSDDIALOG_ERROR);
284 
285 	/* mini bars */
286 	b.win = d.widget;
287 	b.x = 1 + d.w - 2 - 15;
288 	b.w = 13;
289 	b.fmt = "%3d%%";
290 	b.toupdate = false;
291 	for (i = 0; i < (int)nminibars; i++) {
292 		miniperc = minipercs[i];
293 		/* label */
294 		if (color && miniperc >= 0)
295 			wattron(d.widget, A_BOLD);
296 		mvwaddcstr(d.widget, i+1, 2, CHECK_STR(minilabels[i]), d.w-20);
297 		if (color && miniperc >= 0)
298 			wattroff(d.widget, A_BOLD);
299 		/* perc */
300 		if (miniperc == BSDDIALOG_MG_BLANK)
301 			continue;
302 		mvwaddstr(d.widget, i+1, d.w-2-15, "[             ]");
303 		if (miniperc >= 0) {
304 			b.y = i + 1;
305 			b.perc = b.label = MIN(miniperc, 100);
306 			draw_bar(&b);
307 		} else { /* miniperc < 0 */
308 			if (miniperc < BSDDIALOG_MG_PENDING)
309 				miniperc = -12; /* UNKNOWN */
310 			minicolor = t.dialog.color;
311 			if (color && miniperc == BSDDIALOG_MG_FAILED)
312 				minicolor = red;
313 			else if (color && miniperc == BSDDIALOG_MG_DONE)
314 				minicolor = green;
315 			wattron(d.widget, minicolor);
316 			miniperc = abs(miniperc + 1);
317 			mvwaddstr(d.widget, i+1, 1+d.w-2-15, states[miniperc]);
318 			wattroff(d.widget, minicolor);
319 		}
320 	}
321 	wnoutrefresh(d.widget);
322 
323 	/* text */
324 	ystext = MAX(d.h - BORDERS - htext - HBOX, (int)nminibars);
325 	rtextpad(&d, 0, 0, ystext, HBOX);
326 
327 	/* main bar */
328 	if ((b.win = newwin(1, 1, 1, 1)) == NULL)
329 		RETURN_ERROR("Cannot build WINDOW bar");
330 	update_barbox(&d, &b, false);
331 	wattron(b.win, t.bar.color);
332 	mvwaddstr(b.win, 0, 2, "Overall Progress");
333 	wattroff(b.win, t.bar.color);
334 
335 	b.y = b.x = 1;
336 	b.w = WBAR(&d);
337 	b.fmt = "%3d%%";
338 	b.perc = b.label = MIN(mainperc, 100);
339 	b.toupdate = true;
340 	draw_bar(&b);
341 
342 	doupdate();
343 	/* getch(); to test with "alternate mode" */
344 
345 	delwin(b.win);
346 	end_dialog(&d);
347 
348 	return (BSDDIALOG_OK);
349 }
350 
351 int
352 bsddialog_mixedgauge(struct bsddialog_conf *conf, const char *text, int rows,
353     int cols, unsigned int mainperc, unsigned int nminibars,
354     const char **minilabels, int *minipercs)
355 {
356 	int retval;
357 
358 	retval = do_mixedgauge(conf, text, rows, cols, mainperc, nminibars,
359 	    minilabels, minipercs, false);
360 
361 	return (retval);
362 }
363 
364 int
365 bsddialog_progressview (struct bsddialog_conf *conf, const char *text, int rows,
366     int cols, struct bsddialog_progviewconf *pvconf, unsigned int nminibar,
367     struct bsddialog_fileminibar *minibar)
368 {
369 	bool update;
370 	int perc, retval, *minipercs;
371 	unsigned int i, mainperc, totaltodo;
372 	float readforsec;
373 	const char **minilabels;
374 	time_t tstart, told, tnew, refresh;
375 
376 	if ((minilabels = calloc(nminibar, sizeof(char*))) == NULL)
377 		RETURN_ERROR("Cannot allocate memory for minilabels");
378 	if ((minipercs = calloc(nminibar, sizeof(int))) == NULL)
379 		RETURN_ERROR("Cannot allocate memory for minipercs");
380 
381 	totaltodo = 0;
382 	for (i = 0; i < nminibar; i++) {
383 		totaltodo += minibar[i].size;
384 		minilabels[i] = minibar[i].label;
385 		minipercs[i] = minibar[i].status;
386 	}
387 
388 	refresh = pvconf->refresh == 0 ? 0 : pvconf->refresh - 1;
389 	retval = BSDDIALOG_OK;
390 	i = 0;
391 	update = true;
392 	time(&told);
393 	tstart = told;
394 	while (!(bsddialog_interruptprogview || bsddialog_abortprogview)) {
395 		if (bsddialog_total_progview == 0 || totaltodo == 0)
396 			mainperc = 0;
397 		else
398 			mainperc = (bsddialog_total_progview * 100) / totaltodo;
399 
400 		time(&tnew);
401 		if (update || tnew > told + refresh) {
402 			retval = do_mixedgauge(conf, text, rows, cols, mainperc,
403 			    nminibar, minilabels, minipercs, true);
404 			if (retval == BSDDIALOG_ERROR)
405 				return (BSDDIALOG_ERROR);
406 
407 			move(SCREENLINES - 1, 2);
408 			clrtoeol();
409 			readforsec = ((tnew - tstart) == 0) ? 0 :
410 			    bsddialog_total_progview / (float)(tnew - tstart);
411 			printw(pvconf->fmtbottomstr, bsddialog_total_progview,
412 			    readforsec);
413 			refresh();
414 
415 			time(&told);
416 			update = false;
417 		}
418 
419 		if (i >= nminibar)
420 			break;
421 		if (minibar[i].status == BSDDIALOG_MG_FAILED)
422 			break;
423 
424 		perc = pvconf->callback(&minibar[i]);
425 
426 		if (minibar[i].status == BSDDIALOG_MG_DONE) { /*||perc >= 100)*/
427 			minipercs[i] = BSDDIALOG_MG_DONE;
428 			update = true;
429 			i++;
430 		} else if (minibar[i].status == BSDDIALOG_MG_FAILED ||
431 		    perc < 0) {
432 			minipercs[i] = BSDDIALOG_MG_FAILED;
433 			update = true;
434 		} else /* perc >= 0 */
435 			minipercs[i] = perc;
436 	}
437 
438 	free(minilabels);
439 	free(minipercs);
440 	return (retval);
441 }
442 
443 static int rangebox_redraw(struct dialog *d, struct bar *b, int *bigchange)
444 {
445 	if (d->built) {
446 		hide_dialog(d);
447 		refresh(); /* Important for decreasing screen */
448 	}
449 	if (dialog_size_position(d, HBOX, MIN_WBOX, NULL) != 0)
450 		return (BSDDIALOG_ERROR);
451 	if (draw_dialog(d) != 0)
452 		return (BSDDIALOG_ERROR);
453 	if (d->built)
454 		refresh(); /* Important to fix grey lines expanding screen */
455 	TEXTPAD(d, HBOX + HBUTTONS);
456 
457 	b->w = WBAR(d);
458 	*bigchange = MAX(1, b->w  / 10);
459 	update_barbox(d, b, true);
460 	b->toupdate = true;
461 
462 	return (0);
463 }
464 
465 int
466 bsddialog_rangebox(struct bsddialog_conf *conf, const char *text, int rows,
467     int cols, int min, int max, int *value)
468 {
469 	bool loop;
470 	int currvalue, retval, bigchange, positions;
471 	wint_t input;
472 	struct bar b;
473 	struct dialog d;
474 
475 	CHECK_PTR(value);
476 	if (min >= max)
477 		RETURN_FMTERROR("min (%d) >= max (%d)", min, max);
478 	if (*value < min)
479 		RETURN_FMTERROR("value (%d) < min (%d)", *value, min);
480 	if (*value > max)
481 		RETURN_FMTERROR("value (%d) > max (%d)", *value, max);
482 
483 	currvalue = *value;
484 	positions = max - min + 1;
485 
486 	if (prepare_dialog(conf, text, rows, cols, &d) != 0)
487 		return (BSDDIALOG_ERROR);
488 	set_buttons(&d, true, OK_LABEL, CANCEL_LABEL);
489 	if ((b.win = newwin(1, 1, 1, 1)) == NULL)
490 		RETURN_ERROR("Cannot build WINDOW bar");
491 	b.y = b.x = 1;
492 	b.fmt = "%d";
493 	if (rangebox_redraw(&d, &b, &bigchange) != 0)
494 		return (BSDDIALOG_ERROR);
495 
496 	loop = true;
497 	while (loop) {
498 		if (b.toupdate) {
499 			b.perc = ((float)(currvalue - min)*100) / (positions-1);
500 			b.label = currvalue;
501 			draw_bar(&b);
502 		}
503 		doupdate();
504 		if (get_wch(&input) == ERR)
505 			continue;
506 		switch(input) {
507 		case KEY_ENTER:
508 		case 10: /* Enter */
509 			retval = BUTTONVALUE(d.bs);
510 			loop = false;
511 			break;
512 		case 27: /* Esc */
513 			if (conf->key.enable_esc) {
514 				retval = BSDDIALOG_ESC;
515 				loop = false;
516 			}
517 			break;
518 		case '\t': /* TAB */
519 		case KEY_CTRL('n'):
520 		case KEY_RIGHT:
521 			d.bs.curr = (d.bs.curr + 1) % d.bs.nbuttons;
522 			DRAW_BUTTONS(d);
523 			break;
524 		case KEY_CTRL('p'):
525 		case KEY_LEFT:
526 			d.bs.curr--;
527 			if (d.bs.curr < 0)
528 				 d.bs.curr = d.bs.nbuttons - 1;
529 			DRAW_BUTTONS(d);
530 			break;
531 		case KEY_HOME:
532 			currvalue = max;
533 			b.toupdate = true;
534 			break;
535 		case KEY_END:
536 			currvalue = min;
537 			b.toupdate = true;
538 			break;
539 		case KEY_NPAGE:
540 			currvalue -= bigchange;
541 			if (currvalue < min)
542 				currvalue = min;
543 			b.toupdate = true;
544 			break;
545 		case KEY_PPAGE:
546 			currvalue += bigchange;
547 			if (currvalue > max)
548 				currvalue = max;
549 			b.toupdate = true;
550 			break;
551 		case '-':
552 		case KEY_UP:
553 			if (currvalue > min) {
554 				currvalue--;
555 				b.toupdate = true;
556 			}
557 			break;
558 		case '+':
559 		case KEY_DOWN:
560 			if (currvalue < max) {
561 				currvalue++;
562 				b.toupdate = true;
563 			}
564 			break;
565 		case KEY_F(1):
566 			if (conf->key.f1_file == NULL &&
567 			    conf->key.f1_message == NULL)
568 				break;
569 			if (f1help_dialog(conf) != 0)
570 				return (BSDDIALOG_ERROR);
571 			if (rangebox_redraw(&d, &b, &bigchange) != 0)
572 				return (BSDDIALOG_ERROR);
573 			break;
574 		case KEY_CTRL('l'):
575 		case KEY_RESIZE:
576 			if (rangebox_redraw(&d, &b, &bigchange) != 0)
577 				return (BSDDIALOG_ERROR);
578 			break;
579 		default:
580 			if (shortcut_buttons(input, &d.bs)) {
581 				DRAW_BUTTONS(d);
582 				doupdate();
583 				retval = BUTTONVALUE(d.bs);
584 				loop = false;
585 			}
586 		}
587 	}
588 
589 	*value = currvalue;
590 
591 	delwin(b.win);
592 	end_dialog(&d);
593 
594 	return (retval);
595 }
596 
597 static int pause_redraw(struct dialog *d, struct bar *b)
598 {
599 	if (d->built) {
600 		hide_dialog(d);
601 		refresh(); /* Important for decreasing screen */
602 	}
603 	if (dialog_size_position(d, HBOX, MIN_WBOX, NULL) != 0)
604 		return (BSDDIALOG_ERROR);
605 	if (draw_dialog(d) != 0)
606 		return (BSDDIALOG_ERROR);
607 	if (d->built)
608 		refresh(); /* Important to fix grey lines expanding screen */
609 	TEXTPAD(d, HBOX + HBUTTONS);
610 
611 	b->w = WBAR(d);
612 	update_barbox(d, b, true);
613 	b->toupdate = true;
614 
615 	return (0);
616 }
617 
618 int
619 bsddialog_pause(struct bsddialog_conf *conf, const char *text, int rows,
620     int cols, unsigned int *seconds)
621 {
622 	bool loop;
623 	int retval, tout;
624 	wint_t input;
625 	struct bar b;
626 	struct dialog d;
627 
628 	CHECK_PTR(seconds);
629 	if (prepare_dialog(conf, text, rows, cols, &d) != 0)
630 		return (BSDDIALOG_ERROR);
631 	set_buttons(&d, true, OK_LABEL, CANCEL_LABEL);
632 	if ((b.win = newwin(1, 1, 1, 1)) == NULL)
633 		RETURN_ERROR("Cannot build WINDOW bar");
634 	b.y = b.x = 1;
635 	b.fmt = "%d";
636 	if (pause_redraw(&d, &b) != 0)
637 		return (BSDDIALOG_ERROR);
638 
639 	tout = *seconds;
640 	nodelay(stdscr, TRUE);
641 	timeout(1000);
642 	loop = true;
643 	while (loop) {
644 		if (b.toupdate) {
645 			b.perc = (float)tout * 100 / *seconds;
646 			b.label = tout;
647 			draw_bar(&b);
648 		}
649 		doupdate();
650 		if (get_wch(&input) == ERR) { /* timeout */
651 			tout--;
652 			if (tout < 0) {
653 				retval = BSDDIALOG_TIMEOUT;
654 				break;
655 			}
656 			else {
657 				b.toupdate = true;
658 				continue;
659 			}
660 		}
661 		switch(input) {
662 		case KEY_ENTER:
663 		case 10: /* Enter */
664 			retval = BUTTONVALUE(d.bs);
665 			loop = false;
666 			break;
667 		case 27: /* Esc */
668 			if (conf->key.enable_esc) {
669 				retval = BSDDIALOG_ESC;
670 				loop = false;
671 			}
672 			break;
673 		case '\t': /* TAB */
674 		case KEY_RIGHT:
675 			d.bs.curr = (d.bs.curr + 1) % d.bs.nbuttons;
676 			DRAW_BUTTONS(d);
677 			break;
678 		case KEY_LEFT:
679 			d.bs.curr--;
680 			if (d.bs.curr < 0)
681 				 d.bs.curr = d.bs.nbuttons - 1;
682 			DRAW_BUTTONS(d);
683 			break;
684 		case KEY_F(1):
685 			if (conf->key.f1_file == NULL &&
686 			    conf->key.f1_message == NULL)
687 				break;
688 			if (f1help_dialog(conf) != 0)
689 				return (BSDDIALOG_ERROR);
690 			if (pause_redraw(&d, &b) != 0)
691 				return (BSDDIALOG_ERROR);
692 			break;
693 		case KEY_CTRL('l'):
694 		case KEY_RESIZE:
695 			if (pause_redraw(&d, &b) != 0)
696 				return (BSDDIALOG_ERROR);
697 			break;
698 		default:
699 			if (shortcut_buttons(input, &d.bs)) {
700 				DRAW_BUTTONS(d);
701 				doupdate();
702 				retval = BUTTONVALUE(d.bs);
703 				loop = false;
704 			}
705 		}
706 	}
707 	nodelay(stdscr, FALSE);
708 
709 	*seconds = MAX(tout, 0);
710 
711 	delwin(b.win);
712 	end_dialog(&d);
713 
714 	return (retval);
715 }
716