xref: /freebsd/contrib/bsddialog/lib/timebox.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 
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, UARROW(conf), 2);
52 		mvwhline(win, 2, 1, DARROW(conf), 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 '\t': /* TAB */
146 		case KEY_CTRL('n'):
147 		case KEY_RIGHT:
148 			if (focusbuttons) {
149 				d.bs.curr++;
150 				focusbuttons = d.bs.curr < (int)d.bs.nbuttons ?
151 				    true : false;
152 				if (focusbuttons == false) {
153 					sel = 0;
154 					d.bs.curr =
155 					    conf->button.always_active ? 0 : -1;
156 				}
157 			} else {
158 				sel++;
159 				focusbuttons = sel > 2 ? true : false;
160 				if (focusbuttons) {
161 					d.bs.curr = 0;
162 				}
163 			}
164 			DRAW_BUTTONS(d);
165 			break;
166 		case KEY_CTRL('p'):
167 		case KEY_LEFT:
168 			if (focusbuttons) {
169 				d.bs.curr--;
170 				focusbuttons = d.bs.curr < 0 ? false : true;
171 				if (focusbuttons == false) {
172 					sel = 2;
173 					d.bs.curr =
174 					    conf->button.always_active ? 0 : -1;
175 				}
176 			} else {
177 				sel--;
178 				focusbuttons = sel < 0 ? true : false;
179 				if (focusbuttons)
180 					d.bs.curr = (int)d.bs.nbuttons - 1;
181 			}
182 			DRAW_BUTTONS(d);
183 			break;
184 		case '-':
185 			if (focusbuttons == false)
186 				c[sel].value = c[sel].value > 0 ?
187 				    c[sel].value - 1 : c[sel].max;
188 			break;
189 		case KEY_UP:
190 			if (focusbuttons) {
191 				sel = 0;
192 				focusbuttons = false;
193 				d.bs.curr = conf->button.always_active ? 0 : -1;
194 				DRAW_BUTTONS(d);
195 			} else {
196 				c[sel].value = c[sel].value > 0 ?
197 				    c[sel].value - 1 : c[sel].max;
198 			}
199 			break;
200 		case '+':
201 		case KEY_DOWN:
202 			if (focusbuttons)
203 				break;
204 			c[sel].value = c[sel].value < c[sel].max ?
205 			    c[sel].value + 1 : 0;
206 			break;
207 		case KEY_F(1):
208 			if (conf->key.f1_file == NULL &&
209 			    conf->key.f1_message == NULL)
210 				break;
211 			if (f1help_dialog(conf) != 0)
212 				return (BSDDIALOG_ERROR);
213 			if (timebox_redraw(&d, c) != 0)
214 				return (BSDDIALOG_ERROR);
215 			break;
216 		case KEY_CTRL('l'):
217 		case KEY_RESIZE:
218 			if (timebox_redraw(&d, c) != 0)
219 				return (BSDDIALOG_ERROR);
220 			break;
221 		default:
222 			if (shortcut_buttons(input, &d.bs)) {
223 				DRAW_BUTTONS(d);
224 				doupdate();
225 				retval = BUTTONVALUE(d.bs);
226 				loop = false;
227 			}
228 		}
229 	}
230 
231 	*hh = c[0].value;
232 	*mm = c[1].value;
233 	*ss = c[2].value;
234 
235 	for (i = 0; i < 3; i++)
236 		delwin(c[i].win);
237 	end_dialog(&d);
238 
239 	return (retval);
240 }
241