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