xref: /freebsd-src/contrib/bsddialog/lib/messagebox.c (revision 8c4f402881b3a926f1bafdf275b015c6d76a31b2)
1c76f0793SBaptiste Daroussin /*-
2c76f0793SBaptiste Daroussin  * SPDX-License-Identifier: BSD-2-Clause
3c76f0793SBaptiste Daroussin  *
4c76f0793SBaptiste Daroussin  * Copyright (c) 2021 Alfonso Sabato Siciliano
5c76f0793SBaptiste Daroussin  *
6c76f0793SBaptiste Daroussin  * Redistribution and use in source and binary forms, with or without
7c76f0793SBaptiste Daroussin  * modification, are permitted provided that the following conditions
8c76f0793SBaptiste Daroussin  * are met:
9c76f0793SBaptiste Daroussin  * 1. Redistributions of source code must retain the above copyright
10c76f0793SBaptiste Daroussin  *    notice, this list of conditions and the following disclaimer.
11c76f0793SBaptiste Daroussin  * 2. Redistributions in binary form must reproduce the above copyright
12c76f0793SBaptiste Daroussin  *    notice, this list of conditions and the following disclaimer in the
13c76f0793SBaptiste Daroussin  *    documentation and/or other materials provided with the distribution.
14c76f0793SBaptiste Daroussin  *
15c76f0793SBaptiste Daroussin  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16c76f0793SBaptiste Daroussin  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17c76f0793SBaptiste Daroussin  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18c76f0793SBaptiste Daroussin  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19c76f0793SBaptiste Daroussin  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20c76f0793SBaptiste Daroussin  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21c76f0793SBaptiste Daroussin  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22c76f0793SBaptiste Daroussin  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23c76f0793SBaptiste Daroussin  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24c76f0793SBaptiste Daroussin  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25c76f0793SBaptiste Daroussin  * SUCH DAMAGE.
26c76f0793SBaptiste Daroussin  */
27c76f0793SBaptiste Daroussin 
28c76f0793SBaptiste Daroussin #include <sys/param.h>
29c76f0793SBaptiste Daroussin 
30c76f0793SBaptiste Daroussin #include <ctype.h>
31c76f0793SBaptiste Daroussin #include <string.h>
32c76f0793SBaptiste Daroussin 
33c76f0793SBaptiste Daroussin #ifdef PORTNCURSES
34*8c4f4028SBaptiste Daroussin #include <ncurses/ncurses.h>
35c76f0793SBaptiste Daroussin #else
36*8c4f4028SBaptiste Daroussin #include <ncurses.h>
37c76f0793SBaptiste Daroussin #endif
38c76f0793SBaptiste Daroussin 
39c76f0793SBaptiste Daroussin #include "bsddialog.h"
40c76f0793SBaptiste Daroussin #include "lib_util.h"
41c76f0793SBaptiste Daroussin #include "bsddialog_theme.h"
42c76f0793SBaptiste Daroussin 
43c76f0793SBaptiste Daroussin /* "Message": msgbox - yesno */
44c76f0793SBaptiste Daroussin 
45c76f0793SBaptiste Daroussin #define AUTO_WIDTH	(COLS / 3U)
46c76f0793SBaptiste Daroussin /*
47c76f0793SBaptiste Daroussin  * Min height = 5: 2 up & down borders + 2 label & up border buttons + 1 line
48c76f0793SBaptiste Daroussin  * for text, at least 1 line is important for widget_withtextpad_init() to avoid
49c76f0793SBaptiste Daroussin  * "Cannot build the pad window for text".
50c76f0793SBaptiste Daroussin  */
51c76f0793SBaptiste Daroussin #define MIN_HEIGHT	5
52c76f0793SBaptiste Daroussin 
53c76f0793SBaptiste Daroussin extern struct bsddialog_theme t;
54c76f0793SBaptiste Daroussin 
55c76f0793SBaptiste Daroussin static int
56f499134dSBaptiste Daroussin message_autosize(struct bsddialog_conf *conf, int rows, int cols, int *h, int *w,
57c76f0793SBaptiste Daroussin     char *text, struct buttons bs)
58c76f0793SBaptiste Daroussin {
59c76f0793SBaptiste Daroussin 	int maxword, maxline, nlines, line;
60c76f0793SBaptiste Daroussin 
61c76f0793SBaptiste Daroussin 	if (get_text_properties(conf, text, &maxword, &maxline, &nlines) != 0)
62c76f0793SBaptiste Daroussin 		return BSDDIALOG_ERROR;
63c76f0793SBaptiste Daroussin 
64c76f0793SBaptiste Daroussin 	if (cols == BSDDIALOG_AUTOSIZE) {
65c76f0793SBaptiste Daroussin 		*w = VBORDERS;
66c76f0793SBaptiste Daroussin 		/* buttons size */
67c76f0793SBaptiste Daroussin 		*w += bs.nbuttons * bs.sizebutton;
68f499134dSBaptiste Daroussin 		*w += bs.nbuttons > 0 ? (bs.nbuttons-1) * t.button.space : 0;
69c76f0793SBaptiste Daroussin 		/* text size */
70f499134dSBaptiste Daroussin 		line = MIN(maxline + VBORDERS + t.text.hmargin * 2, AUTO_WIDTH);
71f499134dSBaptiste Daroussin 		line = MAX(line, (int) (maxword + VBORDERS + t.text.hmargin * 2));
72c76f0793SBaptiste Daroussin 		*w = MAX(*w, line);
73*8c4f4028SBaptiste Daroussin 		/* conf.auto_minwidth */
74*8c4f4028SBaptiste Daroussin 		*w = MAX(*w, (int)conf->auto_minwidth);
75c76f0793SBaptiste Daroussin 		/* avoid terminal overflow */
76c76f0793SBaptiste Daroussin 		*w = MIN(*w, widget_max_width(conf));
77c76f0793SBaptiste Daroussin 	}
78c76f0793SBaptiste Daroussin 
79c76f0793SBaptiste Daroussin 	if (rows == BSDDIALOG_AUTOSIZE) {
80c76f0793SBaptiste Daroussin 		*h = MIN_HEIGHT - 1;
81c76f0793SBaptiste Daroussin 		if (maxword > 0)
82*8c4f4028SBaptiste Daroussin 			*h += MAX(nlines, (int)(*w / GET_ASPECT_RATIO(conf)));
83c76f0793SBaptiste Daroussin 		*h = MAX(*h, MIN_HEIGHT);
84*8c4f4028SBaptiste Daroussin 		/* conf.auto_minheight */
85*8c4f4028SBaptiste Daroussin 		*h = MAX(*h, (int)conf->auto_minheight);
86c76f0793SBaptiste Daroussin 		/* avoid terminal overflow */
87c76f0793SBaptiste Daroussin 		*h = MIN(*h, widget_max_height(conf));
88c76f0793SBaptiste Daroussin 	}
89c76f0793SBaptiste Daroussin 
90c76f0793SBaptiste Daroussin 	return 0;
91c76f0793SBaptiste Daroussin }
92c76f0793SBaptiste Daroussin 
93c76f0793SBaptiste Daroussin static int message_checksize(int rows, int cols, struct buttons bs)
94c76f0793SBaptiste Daroussin {
95c76f0793SBaptiste Daroussin 	int mincols;
96c76f0793SBaptiste Daroussin 
97c76f0793SBaptiste Daroussin 	mincols = VBORDERS;
98c76f0793SBaptiste Daroussin 	mincols += bs.nbuttons * bs.sizebutton;
99f499134dSBaptiste Daroussin 	mincols += bs.nbuttons > 0 ? (bs.nbuttons-1) * t.button.space : 0;
100c76f0793SBaptiste Daroussin 
101c76f0793SBaptiste Daroussin 	if (cols < mincols)
102c76f0793SBaptiste Daroussin 		RETURN_ERROR("Few cols, Msgbox and Yesno need at least width "\
103c76f0793SBaptiste Daroussin 		    "for borders, buttons and spaces between buttons");
104c76f0793SBaptiste Daroussin 
105c76f0793SBaptiste Daroussin 	if (rows < MIN_HEIGHT)
106c76f0793SBaptiste Daroussin 		RETURN_ERROR("Msgbox and Yesno need at least height 5");
107c76f0793SBaptiste Daroussin 
108c76f0793SBaptiste Daroussin 	return 0;
109c76f0793SBaptiste Daroussin }
110c76f0793SBaptiste Daroussin 
111c76f0793SBaptiste Daroussin static void
112c76f0793SBaptiste Daroussin buttonsupdate(WINDOW *widget, int h, int w, struct buttons bs, bool shortkey)
113c76f0793SBaptiste Daroussin {
114c76f0793SBaptiste Daroussin 	draw_buttons(widget, h-2, w, bs, shortkey);
115c76f0793SBaptiste Daroussin 	wnoutrefresh(widget);
116c76f0793SBaptiste Daroussin }
117c76f0793SBaptiste Daroussin 
118c76f0793SBaptiste Daroussin static void
119c76f0793SBaptiste Daroussin textupdate(WINDOW *widget, int y, int x, int h, int w, WINDOW *textpad,
120c76f0793SBaptiste Daroussin     int htextpad, int textrow)
121c76f0793SBaptiste Daroussin {
122c76f0793SBaptiste Daroussin 
123c76f0793SBaptiste Daroussin 	if (htextpad > h - 4) {
124c76f0793SBaptiste Daroussin 		mvwprintw(widget, h-3, w-6, "%3d%%",
125c76f0793SBaptiste Daroussin 		    100 * (textrow+h-4)/ htextpad);
126c76f0793SBaptiste Daroussin 		wnoutrefresh(widget);
127c76f0793SBaptiste Daroussin 	}
128c76f0793SBaptiste Daroussin 
129c76f0793SBaptiste Daroussin 	pnoutrefresh(textpad, textrow, 0, y+1, x+2, y+h-4, x+w-2);
130c76f0793SBaptiste Daroussin }
131c76f0793SBaptiste Daroussin 
132c76f0793SBaptiste Daroussin static int
133f499134dSBaptiste Daroussin do_widget(struct bsddialog_conf *conf, char *text, int rows, int cols,
134c76f0793SBaptiste Daroussin     struct buttons bs, bool shortkey)
135c76f0793SBaptiste Daroussin {
136c76f0793SBaptiste Daroussin 	WINDOW *widget, *textpad, *shadow;
137c76f0793SBaptiste Daroussin 	bool loop;
138c76f0793SBaptiste Daroussin 	int i, y, x, h, w, input, output, htextpad, textrow;
139c76f0793SBaptiste Daroussin 
140c76f0793SBaptiste Daroussin 	if (set_widget_size(conf, rows, cols, &h, &w) != 0)
141c76f0793SBaptiste Daroussin 		return BSDDIALOG_ERROR;
142c76f0793SBaptiste Daroussin 	if (message_autosize(conf, rows, cols, &h, &w, text, bs) != 0)
143c76f0793SBaptiste Daroussin 		return BSDDIALOG_ERROR;
144c76f0793SBaptiste Daroussin 	if (message_checksize(h, w, bs) != 0)
145c76f0793SBaptiste Daroussin 		return BSDDIALOG_ERROR;
146c76f0793SBaptiste Daroussin 	if (set_widget_position(conf, &y, &x, h, w) != 0)
147c76f0793SBaptiste Daroussin 		return BSDDIALOG_ERROR;
148c76f0793SBaptiste Daroussin 
149c76f0793SBaptiste Daroussin 	if (new_widget_withtextpad(conf, &shadow, &widget, y, x, h, w, RAISED,
150c76f0793SBaptiste Daroussin 	    &textpad, &htextpad, text, true) != 0)
151c76f0793SBaptiste Daroussin 		return BSDDIALOG_ERROR;
152c76f0793SBaptiste Daroussin 
153c76f0793SBaptiste Daroussin 	textrow = 0;
154c76f0793SBaptiste Daroussin 	loop = true;
155c76f0793SBaptiste Daroussin 	buttonsupdate(widget, h, w, bs, shortkey);
156c76f0793SBaptiste Daroussin 	textupdate(widget, y, x, h, w, textpad, htextpad, textrow);
157c76f0793SBaptiste Daroussin 	while(loop) {
158c76f0793SBaptiste Daroussin 		doupdate();
159c76f0793SBaptiste Daroussin 		input = getch();
160c76f0793SBaptiste Daroussin 		switch (input) {
161c76f0793SBaptiste Daroussin 		case 10: /* Enter */
162c76f0793SBaptiste Daroussin 			output = bs.value[bs.curr];
163c76f0793SBaptiste Daroussin 			loop = false;
164c76f0793SBaptiste Daroussin 			break;
165c76f0793SBaptiste Daroussin 		case 27: /* Esc */
166c76f0793SBaptiste Daroussin 			output = BSDDIALOG_ESC;
167c76f0793SBaptiste Daroussin 			loop = false;
168c76f0793SBaptiste Daroussin 			break;
169c76f0793SBaptiste Daroussin 		case '\t': /* TAB */
170c76f0793SBaptiste Daroussin 			bs.curr = (bs.curr + 1) % bs.nbuttons;
171c76f0793SBaptiste Daroussin 			buttonsupdate(widget, h, w, bs, shortkey);
172c76f0793SBaptiste Daroussin 			break;
173c76f0793SBaptiste Daroussin 		case KEY_F(1):
174*8c4f4028SBaptiste Daroussin 			if (conf->f1_file == NULL && conf->f1_message == NULL)
175c76f0793SBaptiste Daroussin 				break;
176c76f0793SBaptiste Daroussin 			if (f1help(conf) != 0)
177c76f0793SBaptiste Daroussin 				return BSDDIALOG_ERROR;
178c76f0793SBaptiste Daroussin 			/* No break! the terminal size can change */
179c76f0793SBaptiste Daroussin 		case KEY_RESIZE:
180f499134dSBaptiste Daroussin 			hide_widget(y, x, h, w,conf->shadow);
181c76f0793SBaptiste Daroussin 
182c76f0793SBaptiste Daroussin 			/*
183c76f0793SBaptiste Daroussin 			 * Unnecessary, but, when the columns decrease the
184c76f0793SBaptiste Daroussin 			 * following "refresh" seem not work
185c76f0793SBaptiste Daroussin 			 */
186c76f0793SBaptiste Daroussin 			refresh();
187c76f0793SBaptiste Daroussin 
188c76f0793SBaptiste Daroussin 			if (set_widget_size(conf, rows, cols, &h, &w) != 0)
189c76f0793SBaptiste Daroussin 				return BSDDIALOG_ERROR;
190c76f0793SBaptiste Daroussin 			if (message_autosize(conf, rows, cols, &h, &w, text, bs) != 0)
191c76f0793SBaptiste Daroussin 				return BSDDIALOG_ERROR;
192c76f0793SBaptiste Daroussin 			if (message_checksize(h, w, bs) != 0)
193c76f0793SBaptiste Daroussin 				return BSDDIALOG_ERROR;
194c76f0793SBaptiste Daroussin 			if (set_widget_position(conf, &y, &x, h, w) != 0)
195c76f0793SBaptiste Daroussin 				return BSDDIALOG_ERROR;
196c76f0793SBaptiste Daroussin 
197c76f0793SBaptiste Daroussin 			wclear(shadow);
198f499134dSBaptiste Daroussin 			mvwin(shadow, y + t.shadow.h, x + t.shadow.w);
199c76f0793SBaptiste Daroussin 			wresize(shadow, h, w);
200c76f0793SBaptiste Daroussin 
201c76f0793SBaptiste Daroussin 			wclear(widget);
202c76f0793SBaptiste Daroussin 			mvwin(widget, y, x);
203c76f0793SBaptiste Daroussin 			wresize(widget, h, w);
204c76f0793SBaptiste Daroussin 
205c76f0793SBaptiste Daroussin 			htextpad = 1;
206c76f0793SBaptiste Daroussin 			wclear(textpad);
207f499134dSBaptiste Daroussin 			wresize(textpad, 1, w - HBORDERS - t.text.hmargin * 2);
208c76f0793SBaptiste Daroussin 
209c76f0793SBaptiste Daroussin 			if(update_widget_withtextpad(conf, shadow, widget, h, w,
210c76f0793SBaptiste Daroussin 			    RAISED, textpad, &htextpad, text, true) != 0)
211c76f0793SBaptiste Daroussin 				return BSDDIALOG_ERROR;
212c76f0793SBaptiste Daroussin 
213c76f0793SBaptiste Daroussin 			buttonsupdate(widget, h, w, bs, shortkey);
214c76f0793SBaptiste Daroussin 			textupdate(widget, y, x, h, w, textpad, htextpad, textrow);
215c76f0793SBaptiste Daroussin 
216c76f0793SBaptiste Daroussin 			/* Important to fix grey lines expanding screen */
217c76f0793SBaptiste Daroussin 			refresh();
218c76f0793SBaptiste Daroussin 			break;
219c76f0793SBaptiste Daroussin 		case KEY_UP:
220c76f0793SBaptiste Daroussin 			if (textrow == 0)
221c76f0793SBaptiste Daroussin 				break;
222c76f0793SBaptiste Daroussin 			textrow--;
223c76f0793SBaptiste Daroussin 			textupdate(widget, y, x, h, w, textpad, htextpad, textrow);
224c76f0793SBaptiste Daroussin 			break;
225c76f0793SBaptiste Daroussin 		case KEY_DOWN:
226c76f0793SBaptiste Daroussin 			if (textrow + h - 4 >= htextpad)
227c76f0793SBaptiste Daroussin 				break;
228c76f0793SBaptiste Daroussin 			textrow++;
229c76f0793SBaptiste Daroussin 			textupdate(widget, y, x, h, w, textpad, htextpad, textrow);
230c76f0793SBaptiste Daroussin 			break;
231c76f0793SBaptiste Daroussin 		case KEY_LEFT:
232c76f0793SBaptiste Daroussin 			if (bs.curr > 0) {
233c76f0793SBaptiste Daroussin 				bs.curr--;
234c76f0793SBaptiste Daroussin 				buttonsupdate(widget, h, w, bs, shortkey);
235c76f0793SBaptiste Daroussin 			}
236c76f0793SBaptiste Daroussin 			break;
237c76f0793SBaptiste Daroussin 		case KEY_RIGHT:
238c76f0793SBaptiste Daroussin 			if (bs.curr < (int) bs.nbuttons - 1) {
239c76f0793SBaptiste Daroussin 				bs.curr++;
240c76f0793SBaptiste Daroussin 				buttonsupdate(widget, h, w, bs, shortkey);
241c76f0793SBaptiste Daroussin 			}
242c76f0793SBaptiste Daroussin 			break;
243c76f0793SBaptiste Daroussin 		default:
244c76f0793SBaptiste Daroussin 			if (shortkey == false)
245c76f0793SBaptiste Daroussin 				break;
246c76f0793SBaptiste Daroussin 
247c76f0793SBaptiste Daroussin 			for (i = 0; i < (int) bs.nbuttons; i++)
248c76f0793SBaptiste Daroussin 				if (tolower(input) == tolower((bs.label[i])[0])) {
249c76f0793SBaptiste Daroussin 					output = bs.value[i];
250c76f0793SBaptiste Daroussin 					loop = false;
251c76f0793SBaptiste Daroussin 			}
252c76f0793SBaptiste Daroussin 		}
253c76f0793SBaptiste Daroussin 	}
254c76f0793SBaptiste Daroussin 
255c76f0793SBaptiste Daroussin 	end_widget_withtextpad(conf, widget, h, w, textpad, shadow);
256c76f0793SBaptiste Daroussin 
257c76f0793SBaptiste Daroussin 	return output;
258c76f0793SBaptiste Daroussin }
259c76f0793SBaptiste Daroussin 
260c76f0793SBaptiste Daroussin /* API */
261c76f0793SBaptiste Daroussin 
262c76f0793SBaptiste Daroussin int
263f499134dSBaptiste Daroussin bsddialog_msgbox(struct bsddialog_conf *conf, char* text, int rows, int cols)
264c76f0793SBaptiste Daroussin {
265c76f0793SBaptiste Daroussin 	struct buttons bs;
266c76f0793SBaptiste Daroussin 
267c76f0793SBaptiste Daroussin 	get_buttons(conf, &bs, BUTTONLABEL(ok_label), BUTTONLABEL(extra_label),
268c76f0793SBaptiste Daroussin 	    NULL /* nocancel */, BUTTONLABEL(help_label));
269c76f0793SBaptiste Daroussin 
270c76f0793SBaptiste Daroussin 	return (do_widget(conf, text, rows, cols, bs, true));
271c76f0793SBaptiste Daroussin }
272c76f0793SBaptiste Daroussin 
273c76f0793SBaptiste Daroussin int
274f499134dSBaptiste Daroussin bsddialog_yesno(struct bsddialog_conf *conf, char* text, int rows, int cols)
275c76f0793SBaptiste Daroussin {
276c76f0793SBaptiste Daroussin 	struct buttons bs;
277c76f0793SBaptiste Daroussin 
278*8c4f4028SBaptiste Daroussin 	get_buttons(conf, &bs,
279*8c4f4028SBaptiste Daroussin 	    conf->button.ok_label == NULL ? "Yes" : conf->button.ok_label,
280*8c4f4028SBaptiste Daroussin 	    BUTTONLABEL(extra_label),
281*8c4f4028SBaptiste Daroussin 	    conf->button.cancel_label == NULL ? "No" : conf->button.cancel_label,
282*8c4f4028SBaptiste Daroussin 	    BUTTONLABEL(help_label));
283c76f0793SBaptiste Daroussin 
284c76f0793SBaptiste Daroussin 	return (do_widget(conf, text, rows, cols, bs, true));
285c76f0793SBaptiste Daroussin }
286