xref: /freebsd/contrib/bsddialog/lib/messagebox.c (revision 7be9a3b45356747f9fcb6d69a722c1c95f8060bf)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2021-2022 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 <curses.h>
31 #include <string.h>
32 
33 #include "bsddialog.h"
34 #include "bsddialog_theme.h"
35 #include "lib_util.h"
36 
37 extern struct bsddialog_theme t;
38 
39 static int
40 message_autosize(struct bsddialog_conf *conf, int rows, int cols, int *h,
41     int *w, const char *text, struct buttons bs)
42 {
43 	int htext, wtext;
44 
45 	if (cols == BSDDIALOG_AUTOSIZE || rows == BSDDIALOG_AUTOSIZE) {
46 		if (text_size(conf, rows, cols, text, &bs, 0, SCREENCOLS/2,
47 		    &htext, &wtext) != 0)
48 			return (BSDDIALOG_ERROR);
49 	}
50 
51 	if (cols == BSDDIALOG_AUTOSIZE)
52 		*w = widget_min_width(conf, wtext, 0, &bs);
53 
54 	if (rows == BSDDIALOG_AUTOSIZE)
55 		*h = widget_min_height(conf, htext, 0, true);
56 
57 	return (0);
58 }
59 
60 static int message_checksize(int rows, int cols, struct buttons bs)
61 {
62 	int mincols;
63 
64 	mincols = VBORDERS;
65 	mincols += bs.nbuttons * bs.sizebutton;
66 	mincols += bs.nbuttons > 0 ? (bs.nbuttons-1) * t.button.space : 0;
67 
68 	if (cols < mincols)
69 		RETURN_ERROR("Few cols, Msgbox and Yesno need at least width "
70 		    "for borders, buttons and spaces between buttons");
71 
72 	if (rows < HBORDERS + 2 /*buttons*/)
73 		RETURN_ERROR("Msgbox and Yesno need at least height 4");
74 
75 	return (0);
76 }
77 
78 static void
79 textupdate(WINDOW *widget, WINDOW *textpad, int htextpad, int ytextpad)
80 {
81 	int y, x, h, w;
82 
83 	getbegyx(widget, y, x);
84 	getmaxyx(widget, h, w);
85 
86 	if (htextpad > h - 4) {
87 		mvwprintw(widget, h-3, w-6, "%3d%%",
88 		    100 * (ytextpad+h-4)/ htextpad);
89 		wnoutrefresh(widget);
90 	}
91 
92 	pnoutrefresh(textpad, ytextpad, 0, y+1, x+2, y+h-4, x+w-2);
93 }
94 
95 static int
96 do_message(struct bsddialog_conf *conf, const char *text, int rows, int cols,
97     struct buttons bs)
98 {
99 	bool loop;
100 	int y, x, h, w, input, output, ytextpad, htextpad, unused;
101 	WINDOW *widget, *textpad, *shadow;
102 
103 	if (set_widget_size(conf, rows, cols, &h, &w) != 0)
104 		return (BSDDIALOG_ERROR);
105 	if (message_autosize(conf, rows, cols, &h, &w, text, bs) != 0)
106 		return (BSDDIALOG_ERROR);
107 	if (message_checksize(h, w, bs) != 0)
108 		return (BSDDIALOG_ERROR);
109 	if (set_widget_position(conf, &y, &x, h, w) != 0)
110 		return (BSDDIALOG_ERROR);
111 
112 	if (new_dialog(conf, &shadow, &widget, y, x, h, w, &textpad, text, &bs,
113 	    true) != 0)
114 		return (BSDDIALOG_ERROR);
115 
116 	ytextpad = 0;
117 	getmaxyx(textpad, htextpad, unused);
118 	unused++; /* fix unused error */
119 	textupdate(widget, textpad, htextpad, ytextpad);
120 	loop = true;
121 	while (loop) {
122 		doupdate();
123 		input = getch();
124 		switch (input) {
125 		case KEY_ENTER:
126 		case 10: /* Enter */
127 			output = bs.value[bs.curr];
128 			loop = false;
129 			break;
130 		case 27: /* Esc */
131 			if (conf->key.enable_esc) {
132 				output = BSDDIALOG_ESC;
133 				loop = false;
134 			}
135 			break;
136 		case '\t': /* TAB */
137 			bs.curr = (bs.curr + 1) % bs.nbuttons;
138 			draw_buttons(widget, bs, true);
139 			wnoutrefresh(widget);
140 			break;
141 		case KEY_LEFT:
142 			if (bs.curr > 0) {
143 				bs.curr--;
144 				draw_buttons(widget, bs, true);
145 				wnoutrefresh(widget);
146 			}
147 			break;
148 		case KEY_RIGHT:
149 			if (bs.curr < (int)bs.nbuttons - 1) {
150 				bs.curr++;
151 				draw_buttons(widget, bs, true);
152 				wnoutrefresh(widget);
153 			}
154 			break;
155 		case KEY_F(1):
156 			if (conf->f1_file == NULL && conf->f1_message == NULL)
157 				break;
158 			if (f1help(conf) != 0)
159 				return (BSDDIALOG_ERROR);
160 			/* No break, screen size can change */
161 		case KEY_RESIZE:
162 			/* Important for decreasing screen */
163 			hide_widget(y, x, h, w, conf->shadow);
164 			refresh();
165 
166 			if (set_widget_size(conf, rows, cols, &h, &w) != 0)
167 				return (BSDDIALOG_ERROR);
168 			if (message_autosize(conf, rows, cols, &h, &w, text,
169 			    bs) != 0)
170 				return (BSDDIALOG_ERROR);
171 			if (message_checksize(h, w, bs) != 0)
172 				return (BSDDIALOG_ERROR);
173 			if (set_widget_position(conf, &y, &x, h, w) != 0)
174 				return (BSDDIALOG_ERROR);
175 
176 			if (update_dialog(conf, shadow, widget, y, x, h, w,
177 			    textpad, text, &bs, true) != 0)
178 				return (BSDDIALOG_ERROR);
179 
180 			getmaxyx(textpad, htextpad, unused);
181 			textupdate(widget, textpad, htextpad, ytextpad);
182 
183 			/* Important to fix grey lines expanding screen */
184 			refresh();
185 			break;
186 		case KEY_UP:
187 			if (ytextpad == 0)
188 				break;
189 			ytextpad--;
190 			textupdate(widget, textpad, htextpad, ytextpad);
191 			break;
192 		case KEY_DOWN:
193 			if (ytextpad + h - 4 >= htextpad)
194 				break;
195 			ytextpad++;
196 			textupdate(widget, textpad, htextpad, ytextpad);
197 			break;
198 		default:
199 			if (shortcut_buttons(input, &bs)) {
200 				output = bs.value[bs.curr];
201 				loop = false;
202 			}
203 		}
204 	}
205 
206 	end_dialog(conf, shadow, widget, textpad);
207 
208 	return (output);
209 }
210 
211 /* API */
212 int
213 bsddialog_msgbox(struct bsddialog_conf *conf, const char *text, int rows,
214     int cols)
215 {
216 	struct buttons bs;
217 
218 	get_buttons(conf, &bs, BUTTON_OK_LABEL, NULL);
219 
220 	return (do_message(conf, text, rows, cols, bs));
221 }
222 
223 int
224 bsddialog_yesno(struct bsddialog_conf *conf, const char *text, int rows,
225     int cols)
226 {
227 	struct buttons bs;
228 
229 	get_buttons(conf, &bs, "Yes", "No");
230 
231 	return (do_message(conf, text, rows, cols, bs));
232 }