xref: /freebsd/contrib/bsddialog/lib/messagebox.c (revision ec0ea6efa1ad229d75c394c1a9b9cac33af2b1d3)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2021 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 <string.h>
32 
33 #ifdef PORTNCURSES
34 #include <ncurses/ncurses.h>
35 #else
36 #include <ncurses.h>
37 #endif
38 
39 #include "bsddialog.h"
40 #include "lib_util.h"
41 #include "bsddialog_theme.h"
42 
43 /* "Message": msgbox - yesno */
44 
45 #define AUTO_WIDTH	(COLS / 3U)
46 /*
47  * Min height = 5: 2 up & down borders + 2 label & up border buttons + 1 line
48  * for text, at least 1 line is important for widget_withtextpad_init() to avoid
49  * "Cannot build the pad window for text".
50  */
51 #define MIN_HEIGHT	5
52 
53 extern struct bsddialog_theme t;
54 
55 static int
56 message_autosize(struct bsddialog_conf *conf, int rows, int cols, int *h, int *w,
57     char *text, struct buttons bs)
58 {
59 	int maxword, maxline, nlines, line;
60 
61 	if (get_text_properties(conf, text, &maxword, &maxline, &nlines) != 0)
62 		return BSDDIALOG_ERROR;
63 
64 	if (cols == BSDDIALOG_AUTOSIZE) {
65 		*w = VBORDERS;
66 		/* buttons size */
67 		*w += bs.nbuttons * bs.sizebutton;
68 		*w += bs.nbuttons > 0 ? (bs.nbuttons-1) * t.button.space : 0;
69 		/* text size */
70 		line = MIN(maxline + VBORDERS + t.text.hmargin * 2, AUTO_WIDTH);
71 		line = MAX(line, (int) (maxword + VBORDERS + t.text.hmargin * 2));
72 		*w = MAX(*w, line);
73 		/* conf.auto_minwidth */
74 		*w = MAX(*w, (int)conf->auto_minwidth);
75 		/* avoid terminal overflow */
76 		*w = MIN(*w, widget_max_width(conf));
77 	}
78 
79 	if (rows == BSDDIALOG_AUTOSIZE) {
80 		*h = MIN_HEIGHT - 1;
81 		if (maxword > 0)
82 			*h += MAX(nlines, (int)(*w / GET_ASPECT_RATIO(conf)));
83 		*h = MAX(*h, MIN_HEIGHT);
84 		/* conf.auto_minheight */
85 		*h = MAX(*h, (int)conf->auto_minheight);
86 		/* avoid terminal overflow */
87 		*h = MIN(*h, widget_max_height(conf));
88 	}
89 
90 	return 0;
91 }
92 
93 static int message_checksize(int rows, int cols, struct buttons bs)
94 {
95 	int mincols;
96 
97 	mincols = VBORDERS;
98 	mincols += bs.nbuttons * bs.sizebutton;
99 	mincols += bs.nbuttons > 0 ? (bs.nbuttons-1) * t.button.space : 0;
100 
101 	if (cols < mincols)
102 		RETURN_ERROR("Few cols, Msgbox and Yesno need at least width "\
103 		    "for borders, buttons and spaces between buttons");
104 
105 	if (rows < MIN_HEIGHT)
106 		RETURN_ERROR("Msgbox and Yesno need at least height 5");
107 
108 	return 0;
109 }
110 
111 static void
112 buttonsupdate(WINDOW *widget, int h, int w, struct buttons bs, bool shortkey)
113 {
114 	draw_buttons(widget, h-2, w, bs, shortkey);
115 	wnoutrefresh(widget);
116 }
117 
118 static void
119 textupdate(WINDOW *widget, int y, int x, int h, int w, WINDOW *textpad,
120     int htextpad, int textrow)
121 {
122 
123 	if (htextpad > h - 4) {
124 		mvwprintw(widget, h-3, w-6, "%3d%%",
125 		    100 * (textrow+h-4)/ htextpad);
126 		wnoutrefresh(widget);
127 	}
128 
129 	pnoutrefresh(textpad, textrow, 0, y+1, x+2, y+h-4, x+w-2);
130 }
131 
132 static int
133 do_widget(struct bsddialog_conf *conf, char *text, int rows, int cols,
134     struct buttons bs, bool shortkey)
135 {
136 	WINDOW *widget, *textpad, *shadow;
137 	bool loop;
138 	int i, y, x, h, w, input, output, htextpad, textrow;
139 
140 	if (set_widget_size(conf, rows, cols, &h, &w) != 0)
141 		return BSDDIALOG_ERROR;
142 	if (message_autosize(conf, rows, cols, &h, &w, text, bs) != 0)
143 		return BSDDIALOG_ERROR;
144 	if (message_checksize(h, w, bs) != 0)
145 		return BSDDIALOG_ERROR;
146 	if (set_widget_position(conf, &y, &x, h, w) != 0)
147 		return BSDDIALOG_ERROR;
148 
149 	if (new_widget_withtextpad(conf, &shadow, &widget, y, x, h, w, RAISED,
150 	    &textpad, &htextpad, text, true) != 0)
151 		return BSDDIALOG_ERROR;
152 
153 	textrow = 0;
154 	loop = true;
155 	buttonsupdate(widget, h, w, bs, shortkey);
156 	textupdate(widget, y, x, h, w, textpad, htextpad, textrow);
157 	while(loop) {
158 		doupdate();
159 		input = getch();
160 		switch (input) {
161 		case 10: /* Enter */
162 			output = bs.value[bs.curr];
163 			loop = false;
164 			break;
165 		case 27: /* Esc */
166 			output = BSDDIALOG_ESC;
167 			loop = false;
168 			break;
169 		case '\t': /* TAB */
170 			bs.curr = (bs.curr + 1) % bs.nbuttons;
171 			buttonsupdate(widget, h, w, bs, shortkey);
172 			break;
173 		case KEY_F(1):
174 			if (conf->f1_file == NULL && conf->f1_message == NULL)
175 				break;
176 			if (f1help(conf) != 0)
177 				return BSDDIALOG_ERROR;
178 			/* No break! the terminal size can change */
179 		case KEY_RESIZE:
180 			hide_widget(y, x, h, w,conf->shadow);
181 
182 			/*
183 			 * Unnecessary, but, when the columns decrease the
184 			 * following "refresh" seem not work
185 			 */
186 			refresh();
187 
188 			if (set_widget_size(conf, rows, cols, &h, &w) != 0)
189 				return BSDDIALOG_ERROR;
190 			if (message_autosize(conf, rows, cols, &h, &w, text, bs) != 0)
191 				return BSDDIALOG_ERROR;
192 			if (message_checksize(h, w, bs) != 0)
193 				return BSDDIALOG_ERROR;
194 			if (set_widget_position(conf, &y, &x, h, w) != 0)
195 				return BSDDIALOG_ERROR;
196 
197 			wclear(shadow);
198 			mvwin(shadow, y + t.shadow.h, x + t.shadow.w);
199 			wresize(shadow, h, w);
200 
201 			wclear(widget);
202 			mvwin(widget, y, x);
203 			wresize(widget, h, w);
204 
205 			htextpad = 1;
206 			wclear(textpad);
207 			wresize(textpad, 1, w - HBORDERS - t.text.hmargin * 2);
208 
209 			if(update_widget_withtextpad(conf, shadow, widget, h, w,
210 			    RAISED, textpad, &htextpad, text, true) != 0)
211 				return BSDDIALOG_ERROR;
212 
213 			buttonsupdate(widget, h, w, bs, shortkey);
214 			textupdate(widget, y, x, h, w, textpad, htextpad, textrow);
215 
216 			/* Important to fix grey lines expanding screen */
217 			refresh();
218 			break;
219 		case KEY_UP:
220 			if (textrow == 0)
221 				break;
222 			textrow--;
223 			textupdate(widget, y, x, h, w, textpad, htextpad, textrow);
224 			break;
225 		case KEY_DOWN:
226 			if (textrow + h - 4 >= htextpad)
227 				break;
228 			textrow++;
229 			textupdate(widget, y, x, h, w, textpad, htextpad, textrow);
230 			break;
231 		case KEY_LEFT:
232 			if (bs.curr > 0) {
233 				bs.curr--;
234 				buttonsupdate(widget, h, w, bs, shortkey);
235 			}
236 			break;
237 		case KEY_RIGHT:
238 			if (bs.curr < (int) bs.nbuttons - 1) {
239 				bs.curr++;
240 				buttonsupdate(widget, h, w, bs, shortkey);
241 			}
242 			break;
243 		default:
244 			if (shortkey == false)
245 				break;
246 
247 			for (i = 0; i < (int) bs.nbuttons; i++)
248 				if (tolower(input) == tolower((bs.label[i])[0])) {
249 					output = bs.value[i];
250 					loop = false;
251 			}
252 		}
253 	}
254 
255 	end_widget_withtextpad(conf, widget, h, w, textpad, shadow);
256 
257 	return output;
258 }
259 
260 /* API */
261 
262 int
263 bsddialog_msgbox(struct bsddialog_conf *conf, char* text, int rows, int cols)
264 {
265 	struct buttons bs;
266 
267 	get_buttons(conf, &bs, BUTTONLABEL(ok_label), BUTTONLABEL(extra_label),
268 	    NULL /* nocancel */, BUTTONLABEL(help_label));
269 
270 	return (do_widget(conf, text, rows, cols, bs, true));
271 }
272 
273 int
274 bsddialog_yesno(struct bsddialog_conf *conf, char* text, int rows, int cols)
275 {
276 	struct buttons bs;
277 
278 	get_buttons(conf, &bs,
279 	    conf->button.ok_label == NULL ? "Yes" : conf->button.ok_label,
280 	    BUTTONLABEL(extra_label),
281 	    conf->button.cancel_label == NULL ? "No" : conf->button.cancel_label,
282 	    BUTTONLABEL(help_label));
283 
284 	return (do_widget(conf, text, rows, cols, bs, true));
285 }
286