xref: /freebsd/contrib/bsddialog/lib/textbox.c (revision 559a218c9b257775fb249b67945fe4a05b7a6b9f)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2021-2023 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 <curses.h>
29 
30 #include "bsddialog.h"
31 #include "bsddialog_theme.h"
32 #include "lib_util.h"
33 
34 struct scrolltext {
35 	WINDOW *pad;
36 	int ypad;
37 	int xpad;
38 	int ys;
39 	int ye;
40 	int xs;
41 	int xe;
42 	int hpad;
43 	int wpad;
44 	int margin;    /* 2 with multicolumn char, 0 otherwise */
45 	int printrows; /* d.h - BORDERS - HBUTTONS */
46 };
47 
48 static void updateborders(struct dialog *d, struct scrolltext *st)
49 {
50 	chtype arrowch, borderch;
51 
52 	if (d->conf->no_lines)
53 		borderch = ' ';
54 	else if (d->conf->ascii_lines)
55 		borderch = '|';
56 	else
57 		borderch = ACS_VLINE;
58 
59 	if (st->xpad > 0) {
60 		arrowch = d->conf->ascii_lines ? '<' : ACS_LARROW;
61 		arrowch |= t.dialog.arrowcolor;
62 	} else {
63 		arrowch = borderch;
64 		arrowch |= t.dialog.lineraisecolor;
65 	}
66 	mvwvline(d->widget, (d->h / 2) - 2, 0, arrowch, 4);
67 
68 	if (st->xpad + d->w - 2 - st->margin < st->wpad) {
69 		arrowch = d->conf->ascii_lines ? '>' : ACS_RARROW;
70 		arrowch |= t.dialog.arrowcolor;
71 	} else {
72 		arrowch = borderch;
73 		arrowch |= t.dialog.linelowercolor;
74 	}
75 	mvwvline(d->widget, (d->h / 2) - 2, d->w - 1, arrowch, 4);
76 
77 	if (st->hpad > d->h - 4) {
78 		wattron(d->widget, t.dialog.arrowcolor);
79 		mvwprintw(d->widget, d->h - 3, d->w - 6,
80 		    "%3d%%", 100 * (st->ypad + d->h - 4) / st->hpad);
81 		wattroff(d->widget, t.dialog.arrowcolor);
82 	}
83 }
84 
85 static int textbox_size_position(struct dialog *d, struct scrolltext *st)
86 {
87 	int minw;
88 
89 	if (set_widget_size(d->conf, d->rows, d->cols, &d->h, &d->w) != 0)
90 		return (BSDDIALOG_ERROR);
91 	if (set_widget_autosize(d->conf, d->rows, d->cols, &d->h, &d->w,
92 	    d->text, NULL, &d->bs, st->hpad, st->wpad + st->margin) != 0)
93 		return (BSDDIALOG_ERROR);
94 	minw = (st->wpad > 0) ? 2 /*multicolumn char*/ + st->margin : 0 ;
95 	if (widget_checksize(d->h, d->w, &d->bs, MIN(st->hpad, 1), minw) != 0)
96 		return (BSDDIALOG_ERROR);
97 	if (set_widget_position(d->conf, &d->y, &d->x, d->h, d->w) != 0)
98 		return (BSDDIALOG_ERROR);
99 
100 	return (0);
101 }
102 
103 static int textbox_draw(struct dialog *d, struct scrolltext *st)
104 {
105 	if (d->built) {
106 		hide_dialog(d);
107 		refresh(); /* Important for decreasing screen */
108 	}
109 	if (textbox_size_position(d, st) != 0)
110 		return (BSDDIALOG_ERROR);
111 	if (draw_dialog(d) != 0)
112 		return (BSDDIALOG_ERROR);
113 	if (d->built)
114 		refresh(); /* Important to fix grey lines expanding screen */
115 
116 	st->ys = d->y + 1;
117 	st->xs = (st->margin == 0) ? d->x + 1 : d->x + 2;
118 	st->ye = st->ys + d->h - 5;
119 	st->xe = st->xs + d->w - 3 - st->margin;
120 	st->ypad = st->xpad = 0;
121 	st->printrows = d->h-4;
122 
123 	return (0);
124 }
125 
126 /* API */
127 int
128 bsddialog_textbox(struct bsddialog_conf *conf, const char *file, int rows,
129     int cols)
130 {
131 	bool loop, has_multicol_ch;
132 	int i, retval;
133 	unsigned int defaulttablen, linecols;
134 	wint_t input;
135 	char buf[BUFSIZ];
136 	FILE *fp;
137 	struct scrolltext st;
138 	struct dialog d;
139 
140 	if (file == NULL)
141 		RETURN_ERROR("*file is NULL");
142 	if ((fp = fopen(file, "r")) == NULL)
143 		RETURN_FMTERROR("Cannot open file \"%s\"", file);
144 
145 	if (prepare_dialog(conf, "" /* fake */, rows, cols, &d) != 0)
146 		return (BSDDIALOG_ERROR);
147 	set_buttons(&d, true, "EXIT", NULL);
148 
149 	defaulttablen = TABSIZE;
150 	if (conf->text.tablen > 0)
151 		set_tabsize(conf->text.tablen);
152 	st.hpad = 1;
153 	st.wpad = 1;
154 	st.pad = newpad(st.hpad, st.wpad);
155 	wbkgd(st.pad, t.dialog.color);
156 	st.margin = 0;
157 	i = 0;
158 	while (fgets(buf, BUFSIZ, fp) != NULL) {
159 		if (str_props(buf, &linecols, &has_multicol_ch) != 0)
160 			continue;
161 		if ((int)linecols > st.wpad) {
162 			st.wpad = linecols;
163 			wresize(st.pad, st.hpad, st.wpad);
164 		}
165 		if (i > st.hpad-1) {
166 			st.hpad++;
167 			wresize(st.pad, st.hpad, st.wpad);
168 		}
169 		mvwaddstr(st.pad, i, 0, buf);
170 		i++;
171 		if (has_multicol_ch)
172 			st.margin = 2;
173 	}
174 	fclose(fp);
175 	set_tabsize(defaulttablen); /* reset because it is curses global */
176 
177 	if (textbox_draw(&d, &st) != 0)
178 		return (BSDDIALOG_ERROR);
179 
180 	loop = true;
181 	while (loop) {
182 		updateborders(&d, &st);
183 		/*
184 		 * Overflow multicolumn charchter right border:
185 		 * wnoutrefresh(widget);
186 		 * pnoutrefresh(pad, ypad, xpad, ys, xs, ye, xe);
187 		 * doupdate();
188 		 */
189 		wrefresh(d.widget);
190 		prefresh(st.pad, st.ypad, st.xpad, st.ys, st.xs, st.ye, st.xe);
191 		if (get_wch(&input) == ERR)
192 			continue;
193 		if (shortcut_buttons(input, &d.bs)) {
194 			DRAW_BUTTONS(d);
195 			doupdate();
196 			retval = BUTTONVALUE(d.bs);
197 			break; /* loop */
198 		}
199 		switch(input) {
200 		case KEY_ENTER:
201 		case 10: /* Enter */
202 			retval = BSDDIALOG_OK;
203 			loop = false;
204 			break;
205 		case 27: /* Esc */
206 			if (conf->key.enable_esc) {
207 				retval = BSDDIALOG_ESC;
208 				loop = false;
209 			}
210 			break;
211 		case '\t': /* TAB */
212 			d.bs.curr = (d.bs.curr + 1) % d.bs.nbuttons;
213 			DRAW_BUTTONS(d);
214 			break;
215 		case KEY_HOME:
216 			st.ypad = 0;
217 			break;
218 		case KEY_END:
219 			st.ypad = MAX(st.hpad - st.printrows, 0);
220 			break;
221 		case KEY_PPAGE:
222 			st.ypad = MAX(st.ypad - st.printrows, 0);
223 			break;
224 		case KEY_NPAGE:
225 			st.ypad += st.printrows;
226 			if (st.ypad + st.printrows > st.hpad)
227 				st.ypad = st.hpad - st.printrows;
228 			break;
229 		case '0':
230 			st.xpad = 0;
231 			break;
232 		case KEY_LEFT:
233 		case 'h':
234 			st.xpad = MAX(st.xpad - 1, 0);
235 			break;
236 		case KEY_RIGHT:
237 		case 'l':
238 			if (st.xpad + d.w - 2 - st.margin < st.wpad)
239 				st.xpad++;
240 			break;
241 		case KEY_UP:
242 		case 'k':
243 			st.ypad = MAX(st.ypad - 1, 0);
244 			break;
245 		case KEY_DOWN:
246 		case'j':
247 			if (st.ypad + st.printrows <= st.hpad -1)
248 				st.ypad++;
249 			break;
250 		case KEY_F(1):
251 			if (conf->key.f1_file == NULL &&
252 			    conf->key.f1_message == NULL)
253 				break;
254 			if (f1help_dialog(conf) != 0)
255 				return (BSDDIALOG_ERROR);
256 			if (textbox_draw(&d, &st) != 0)
257 				return (BSDDIALOG_ERROR);
258 			break;
259 		case KEY_RESIZE:
260 			if (textbox_draw(&d, &st) != 0)
261 				return (BSDDIALOG_ERROR);
262 			break;
263 		}
264 	}
265 
266 	delwin(st.pad);
267 	end_dialog(&d);
268 
269 	return (retval);
270 }
271