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