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