xref: /freebsd/contrib/bsddialog/lib/timebox.c (revision 5b31cc94b10d4bb7109c6b27940a0fc76a44a331)
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 
30 #include "bsddialog.h"
31 #include "bsddialog_theme.h"
32 #include "lib_util.h"
33 
34 #define MINWTIME   14 /* 3 windows and their borders */
35 #define HBOX        3
36 #define WBOX        4
37 
38 struct clock {
39 	unsigned int max;
40 	unsigned int value;
41 	WINDOW *win;
42 };
43 
44 static void
45 drawsquare(struct bsddialog_conf *conf, WINDOW *win, unsigned int value,
46     bool focus)
47 {
48 	draw_borders(conf, win, LOWERED);
49 	if (focus) {
50 		wattron(win, t.dialog.arrowcolor);
51 		mvwhline(win, 0, 1, conf->ascii_lines ? '^' : ACS_UARROW, 2);
52 		mvwhline(win, 2, 1, conf->ascii_lines ? 'v' : ACS_DARROW, 2);
53 		wattroff(win, t.dialog.arrowcolor);
54 	}
55 
56 	if (focus)
57 		wattron(win, t.menu.f_namecolor);
58 	mvwprintw(win, 1, 1, "%02u", value);
59 	if (focus)
60 		wattroff(win, t.menu.f_namecolor);
61 
62 	wnoutrefresh(win);
63 }
64 
65 static int timebox_redraw(struct dialog *d, struct clock *c)
66 {
67 	int y, x;
68 
69 	if (d->built) {
70 		hide_dialog(d);
71 		refresh(); /* Important for decreasing screen */
72 	}
73 	if (dialog_size_position(d, HBOX, MINWTIME, NULL) != 0)
74 		return (BSDDIALOG_ERROR);
75 	if (draw_dialog(d) != 0)
76 		return (BSDDIALOG_ERROR);
77 	if (d->built)
78 		refresh(); /* Important to fix grey lines expanding screen */
79 	TEXTPAD(d, HBOX + HBUTTONS);
80 
81 	y = d->y + d->h - BORDER - HBUTTONS - HBOX;
82 	x = d->x + d->w/2 - 7;
83 	update_box(d->conf, c[0].win, y, x, HBOX, WBOX, LOWERED);
84 	mvwaddch(d->widget, d->h - 5, d->w/2 - 3, ':');
85 	update_box(d->conf, c[1].win, y, x += 5, HBOX, WBOX, LOWERED);
86 	mvwaddch(d->widget, d->h - 5, d->w/2 + 2, ':');
87 	update_box(d->conf, c[2].win, y, x + 5, HBOX, WBOX, LOWERED);
88 	wnoutrefresh(d->widget); /* for mvwaddch(':') */
89 
90 	return (0);
91 }
92 
93 /* API */
94 int
95 bsddialog_timebox(struct bsddialog_conf *conf, const char* text, int rows,
96     int cols, unsigned int *hh, unsigned int *mm, unsigned int *ss)
97 {
98 	bool loop, focusbuttons;
99 	int i, retval, sel;
100 	wint_t input;
101 	struct dialog d;
102 	struct clock c[3] = {
103 		{23, *hh, NULL},
104 		{59, *mm, NULL},
105 		{59, *ss, NULL}
106 	};
107 
108 	CHECK_PTR(hh);
109 	CHECK_PTR(mm);
110 	CHECK_PTR(ss);
111 	if (prepare_dialog(conf, text, rows, cols, &d) != 0)
112 		return (BSDDIALOG_ERROR);
113 	set_buttons(&d, true, OK_LABEL, CANCEL_LABEL);
114 	for (i=0; i<3; i++) {
115 		if ((c[i].win = newwin(1, 1, 1, 1)) == NULL)
116 			RETURN_FMTERROR("Cannot build WINDOW for time[%d]", i);
117 		wbkgd(c[i].win, t.dialog.color);
118 		c[i].value = MIN(c[i].value, c[i].max);
119 	}
120 	if (timebox_redraw(&d, c) != 0)
121 		return (BSDDIALOG_ERROR);
122 
123 	sel = -1;
124 	loop = focusbuttons = true;
125 	while (loop) {
126 		for (i = 0; i < 3; i++)
127 			drawsquare(conf, c[i].win, c[i].value, sel == i);
128 		doupdate();
129 		if (get_wch(&input) == ERR)
130 			continue;
131 		switch(input) {
132 		case KEY_ENTER:
133 		case 10: /* Enter */
134 			if (focusbuttons || conf->button.always_active) {
135 				retval = BUTTONVALUE(d.bs);
136 				loop = false;
137 			}
138 			break;
139 		case 27: /* Esc */
140 			if (conf->key.enable_esc) {
141 				retval = BSDDIALOG_ESC;
142 				loop = false;
143 			}
144 			break;
145 		case KEY_RIGHT:
146 		case '\t': /* TAB */
147 			if (focusbuttons) {
148 				d.bs.curr++;
149 				focusbuttons = d.bs.curr < (int)d.bs.nbuttons ?
150 				    true : false;
151 				if (focusbuttons == false) {
152 					sel = 0;
153 					d.bs.curr =
154 					    conf->button.always_active ? 0 : -1;
155 				}
156 			} else {
157 				sel++;
158 				focusbuttons = sel > 2 ? true : false;
159 				if (focusbuttons) {
160 					d.bs.curr = 0;
161 				}
162 			}
163 			DRAW_BUTTONS(d);
164 			break;
165 		case KEY_LEFT:
166 			if (focusbuttons) {
167 				d.bs.curr--;
168 				focusbuttons = d.bs.curr < 0 ? false : true;
169 				if (focusbuttons == false) {
170 					sel = 2;
171 					d.bs.curr =
172 					    conf->button.always_active ? 0 : -1;
173 				}
174 			} else {
175 				sel--;
176 				focusbuttons = sel < 0 ? true : false;
177 				if (focusbuttons)
178 					d.bs.curr = (int)d.bs.nbuttons - 1;
179 			}
180 			DRAW_BUTTONS(d);
181 			break;
182 		case KEY_UP:
183 			if (focusbuttons) {
184 				sel = 0;
185 				focusbuttons = false;
186 				d.bs.curr = conf->button.always_active ? 0 : -1;
187 				DRAW_BUTTONS(d);
188 			} else {
189 				c[sel].value = c[sel].value > 0 ?
190 				    c[sel].value - 1 : c[sel].max;
191 			}
192 			break;
193 		case KEY_DOWN:
194 			if (focusbuttons)
195 				break;
196 			c[sel].value = c[sel].value < c[sel].max ?
197 			    c[sel].value + 1 : 0;
198 			break;
199 		case KEY_F(1):
200 			if (conf->key.f1_file == NULL &&
201 			    conf->key.f1_message == NULL)
202 				break;
203 			if (f1help_dialog(conf) != 0)
204 				return (BSDDIALOG_ERROR);
205 			if (timebox_redraw(&d, c) != 0)
206 				return (BSDDIALOG_ERROR);
207 			break;
208 		case KEY_RESIZE:
209 			if (timebox_redraw(&d, c) != 0)
210 				return (BSDDIALOG_ERROR);
211 			break;
212 		default:
213 			if (shortcut_buttons(input, &d.bs)) {
214 				DRAW_BUTTONS(d);
215 				doupdate();
216 				retval = BUTTONVALUE(d.bs);
217 				loop = false;
218 			}
219 		}
220 	}
221 
222 	*hh = c[0].value;
223 	*mm = c[1].value;
224 	*ss = c[2].value;
225 
226 	for (i = 0; i < 3; i++)
227 		delwin(c[i].win);
228 	end_dialog(&d);
229 
230 	return (retval);
231 }
232