xref: /freebsd/contrib/bsddialog/lib/lib_util.c (revision c7046f76)
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 <ctype.h>
31 #include <curses.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
35 #include <wchar.h>
36 #include <wctype.h>
37 
38 #include "bsddialog.h"
39 #include "bsddialog_theme.h"
40 #include "lib_util.h"
41 
42 #define ERRBUFLEN    1024 /* Error buffer len */
43 
44 /* Error */
45 static char errorbuffer[ERRBUFLEN];
46 
47 const char *get_error_string(void)
48 {
49 	return (errorbuffer);
50 }
51 
52 void set_error_string(const char *str)
53 {
54 	strncpy(errorbuffer, str, ERRBUFLEN-1);
55 }
56 
57 /* Unicode */
58 wchar_t* alloc_mbstows(const char *mbstring)
59 {
60 	size_t charlen, nchar;
61 	mbstate_t mbs;
62 	const char *pmbstring;
63 	wchar_t *wstring;
64 
65 	nchar = 1;
66 	pmbstring = mbstring;
67 	memset(&mbs, 0, sizeof(mbs));
68 	while ((charlen = mbrlen(pmbstring, MB_CUR_MAX, &mbs)) != 0 &&
69 	    charlen != (size_t)-1 && charlen != (size_t)-2) {
70 		pmbstring += charlen;
71 		nchar++;
72 	}
73 
74 	if ((wstring = calloc(nchar, sizeof(wchar_t))) == NULL)
75 		return (NULL);
76 	mbstowcs(wstring, mbstring, nchar);
77 
78 	return (wstring);
79 }
80 
81 void mvwaddwch(WINDOW *w, int y, int x, wchar_t wch)
82 {
83 	wchar_t ws[2];
84 
85 	ws[0] = wch;
86 	ws[1] = L'\0';
87 	mvwaddwstr(w, y, x, ws);
88 
89 }
90 
91 int str_props(const char *mbstring, unsigned int *cols, bool *has_multi_col)
92 {
93 	bool multicol;
94 	int w;
95 	unsigned int ncol;
96 	size_t charlen, mb_cur_max;
97 	wchar_t wch;
98 	mbstate_t mbs;
99 
100 	multicol = false;
101 	mb_cur_max = MB_CUR_MAX;
102 	ncol = 0;
103 	memset(&mbs, 0, sizeof(mbs));
104 	while ((charlen = mbrlen(mbstring, mb_cur_max, &mbs)) != 0 &&
105 	    charlen != (size_t)-1 && charlen != (size_t)-2) {
106 		if (mbtowc(&wch, mbstring, mb_cur_max) < 0)
107 			return (-1);
108 		w = (wch == L'\t') ? TABSIZE : wcwidth(wch);
109 		ncol += (w < 0) ? 0 : w;
110 		if (w > 1 && wch != L'\t')
111 			multicol = true;
112 		mbstring += charlen;
113 	}
114 
115 	if (cols != NULL)
116 		*cols = ncol;
117 	if (has_multi_col != NULL)
118 		*has_multi_col = multicol;
119 
120 	return (0);
121 }
122 
123 unsigned int strcols(const char *mbstring)
124 {
125 	int w;
126 	unsigned int ncol;
127 	size_t charlen, mb_cur_max;
128 	wchar_t wch;
129 	mbstate_t mbs;
130 
131 	mb_cur_max = MB_CUR_MAX;
132 	ncol = 0;
133 	memset(&mbs, 0, sizeof(mbs));
134 	while ((charlen = mbrlen(mbstring, mb_cur_max, &mbs)) != 0 &&
135 	    charlen != (size_t)-1 && charlen != (size_t)-2) {
136 		if (mbtowc(&wch, mbstring, mb_cur_max) < 0)
137 			return (0);
138 		w = (wch == L'\t') ? TABSIZE : wcwidth(wch);
139 		ncol += (w < 0) ? 0 : w;
140 		mbstring += charlen;
141 	}
142 
143 	return (ncol);
144 }
145 
146 /* Clear */
147 int hide_widget(int y, int x, int h, int w, bool withshadow)
148 {
149 	WINDOW *clear;
150 
151 	if ((clear = newwin(h, w, y + t.shadow.y, x + t.shadow.x)) == NULL)
152 		RETURN_ERROR("Cannot hide the widget");
153 	wbkgd(clear, t.screen.color);
154 
155 	if (withshadow)
156 		wrefresh(clear);
157 
158 	mvwin(clear, y, x);
159 	wrefresh(clear);
160 
161 	delwin(clear);
162 
163 	return (0);
164 }
165 
166 /* F1 help */
167 int f1help(struct bsddialog_conf *conf)
168 {
169 	int output;
170 	struct bsddialog_conf hconf;
171 
172 	bsddialog_initconf(&hconf);
173 	hconf.title           = "HELP";
174 	hconf.button.ok_label = "EXIT";
175 	hconf.clear           = true;
176 	hconf.ascii_lines     = conf->ascii_lines;
177 	hconf.no_lines        = conf->no_lines;
178 	hconf.shadow          = conf->shadow;
179 	hconf.text.highlight  = conf->text.highlight;
180 
181 	output = BSDDIALOG_OK;
182 	if (conf->key.f1_message != NULL)
183 		output = bsddialog_msgbox(&hconf, conf->key.f1_message, 0, 0);
184 
185 	if (output != BSDDIALOG_ERROR && conf->key.f1_file != NULL)
186 		output = bsddialog_textbox(&hconf, conf->key.f1_file, 0, 0);
187 
188 	return (output == BSDDIALOG_ERROR ? BSDDIALOG_ERROR : 0);
189 }
190 
191 /* Buttons */
192 static void
193 draw_button(WINDOW *window, int y, int x, int size, const char *text,
194     wchar_t first, bool selected, bool shortcut)
195 {
196 	int i, color_arrows, color_shortkey, color_button;
197 
198 	if (selected) {
199 		color_arrows = t.button.f_delimcolor;
200 		color_shortkey = t.button.f_shortcutcolor;
201 		color_button = t.button.f_color;
202 	} else {
203 		color_arrows = t.button.delimcolor;
204 		color_shortkey = t.button.shortcutcolor;
205 		color_button = t.button.color;
206 	}
207 
208 	wattron(window, color_arrows);
209 	mvwaddch(window, y, x, t.button.leftdelim);
210 	wattroff(window, color_arrows);
211 	wattron(window, color_button);
212 	for (i = 1; i < size - 1; i++)
213 		waddch(window, ' ');
214 	wattroff(window, color_button);
215 	wattron(window, color_arrows);
216 	mvwaddch(window, y, x + i, t.button.rightdelim);
217 	wattroff(window, color_arrows);
218 
219 	x = x + 1 + ((size - 2 - strcols(text))/2);
220 	wattron(window, color_button);
221 	mvwaddstr(window, y, x, text);
222 	wattroff(window, color_button);
223 
224 	if (shortcut) {
225 		wattron(window, color_shortkey);
226 		mvwaddwch(window, y, x, first);
227 		wattroff(window, color_shortkey);
228 	}
229 }
230 
231 void
232 draw_buttons(WINDOW *window, struct buttons bs, bool shortcut)
233 {
234 	int i, x, startx, y, rows, cols;
235 	unsigned int newmargin, margin, wbuttons;
236 
237 	getmaxyx(window, rows, cols);
238 	y = rows - 2;
239 
240 	newmargin = cols - VBORDERS - (bs.nbuttons * bs.sizebutton);
241 	newmargin /= (bs.nbuttons + 1);
242 	newmargin = MIN(newmargin, t.button.maxmargin);
243 	if (newmargin == 0) {
244 		margin = t.button.minmargin;
245 		wbuttons = buttons_min_width(bs);
246 	} else {
247 		margin = newmargin;
248 		wbuttons = bs.nbuttons * bs.sizebutton;
249 		wbuttons += (bs.nbuttons + 1) * margin;
250 	}
251 
252 	startx = (cols)/2 - wbuttons/2 + newmargin;
253 	for (i = 0; i < (int)bs.nbuttons; i++) {
254 		x = i * (bs.sizebutton + margin);
255 		draw_button(window, y, startx + x, bs.sizebutton, bs.label[i],
256 		    bs.first[i],  i == bs.curr, shortcut);
257 	}
258 }
259 
260 void
261 get_buttons(struct bsddialog_conf *conf, struct buttons *bs,
262     const char *yesoklabel, const char *nocancellabel)
263 {
264 	int i;
265 #define SIZEBUTTON              8
266 #define DEFAULT_BUTTON_LABEL	BUTTON_OK_LABEL
267 #define DEFAULT_BUTTON_VALUE	BSDDIALOG_OK
268 	wchar_t first;
269 
270 	bs->nbuttons = 0;
271 	bs->curr = 0;
272 	bs->sizebutton = 0;
273 
274 	if (yesoklabel != NULL && conf->button.without_ok == false) {
275 		bs->label[0] = conf->button.ok_label != NULL ?
276 		    conf->button.ok_label : yesoklabel;
277 		bs->value[0] = BSDDIALOG_OK;
278 		bs->nbuttons += 1;
279 	}
280 
281 	if (conf->button.with_extra) {
282 		bs->label[bs->nbuttons] = conf->button.extra_label != NULL ?
283 		    conf->button.extra_label : "Extra";
284 		bs->value[bs->nbuttons] = BSDDIALOG_EXTRA;
285 		bs->nbuttons += 1;
286 	}
287 
288 	if (nocancellabel != NULL && conf->button.without_cancel == false) {
289 		bs->label[bs->nbuttons] = conf->button.cancel_label ?
290 		    conf->button.cancel_label : nocancellabel;
291 		bs->value[bs->nbuttons] = BSDDIALOG_CANCEL;
292 		if (conf->button.default_cancel)
293 			bs->curr = bs->nbuttons;
294 		bs->nbuttons += 1;
295 	}
296 
297 	if (conf->button.with_help) {
298 		bs->label[bs->nbuttons] = conf->button.help_label != NULL ?
299 		    conf->button.help_label : "Help";
300 		bs->value[bs->nbuttons] = BSDDIALOG_HELP;
301 		bs->nbuttons += 1;
302 	}
303 
304 	if (conf->button.generic1_label != NULL) {
305 		bs->label[bs->nbuttons] = conf->button.generic1_label;
306 		bs->value[bs->nbuttons] = BSDDIALOG_GENERIC1;
307 		bs->nbuttons += 1;
308 	}
309 
310 	if (conf->button.generic2_label != NULL) {
311 		bs->label[bs->nbuttons] = conf->button.generic2_label;
312 		bs->value[bs->nbuttons] = BSDDIALOG_GENERIC2;
313 		bs->nbuttons += 1;
314 	}
315 
316 	if (bs->nbuttons == 0) {
317 		bs->label[0] = DEFAULT_BUTTON_LABEL;
318 		bs->value[0] = DEFAULT_BUTTON_VALUE;
319 		bs->nbuttons = 1;
320 	}
321 
322 	for (i = 0; i < (int)bs->nbuttons; i++) {
323 		mbtowc(&first, bs->label[i], MB_CUR_MAX);
324 		bs->first[i] = first;
325 	}
326 
327 	if (conf->button.default_label != NULL) {
328 		for (i = 0; i < (int)bs->nbuttons; i++) {
329 			if (strcmp(conf->button.default_label,
330 			    bs->label[i]) == 0)
331 				bs->curr = i;
332 		}
333 	}
334 
335 	bs->sizebutton = MAX(SIZEBUTTON - 2, strcols(bs->label[0]));
336 	for (i = 1; i < (int)bs->nbuttons; i++)
337 		bs->sizebutton = MAX(bs->sizebutton, strcols(bs->label[i]));
338 	bs->sizebutton += 2;
339 }
340 
341 int buttons_min_width(struct buttons bs)
342 {
343 	unsigned int width;
344 
345 	width = bs.nbuttons * bs.sizebutton;
346 	if (bs.nbuttons > 0)
347 		width += (bs.nbuttons - 1) * t.button.minmargin;
348 
349 	return (width);
350 }
351 
352 bool shortcut_buttons(wint_t key, struct buttons *bs)
353 {
354 	bool match;
355 	unsigned int i;
356 
357 	match = false;
358 	for (i = 0; i < bs->nbuttons; i++) {
359 		if (towlower(key) == towlower(bs->first[i])) {
360 			bs->curr = i;
361 			match = true;
362 			break;
363 		}
364 	}
365 
366 	return (match);
367 }
368 
369 /* Text */
370 static bool is_wtext_attr(const wchar_t *wtext)
371 {
372 	if (wcsnlen(wtext, 3) < 3)
373 		return (false);
374 
375 	if (wtext[0] != L'\\' || wtext[1] != L'Z')
376 		return (false);
377 
378 	return (wcschr(L"nbBrRuU01234567", wtext[2]) == NULL ? false : true);
379 }
380 
381 static bool check_set_wtext_attr(WINDOW *win, wchar_t *wtext)
382 {
383 	enum bsddialog_color bg;
384 
385 	if (is_wtext_attr(wtext) == false)
386 		return (false);
387 
388 	if ((wtext[2] - L'0') >= 0 && (wtext[2] - L'0') < 8) {
389 		bsddialog_color_attrs(t.dialog.color, NULL, &bg, NULL);
390 		wattron(win, bsddialog_color(wtext[2] - L'0', bg, 0));
391 		return (true);
392 	}
393 
394 	switch (wtext[2]) {
395 	case L'n':
396 		wattron(win, t.dialog.color);
397 		wattrset(win, A_NORMAL);
398 		break;
399 	case L'b':
400 		wattron(win, A_BOLD);
401 		break;
402 	case L'B':
403 		wattroff(win, A_BOLD);
404 		break;
405 	case L'r':
406 		wattron(win, A_REVERSE);
407 		break;
408 	case L'R':
409 		wattroff(win, A_REVERSE);
410 		break;
411 	case L'u':
412 		wattron(win, A_UNDERLINE);
413 		break;
414 	case L'U':
415 		wattroff(win, A_UNDERLINE);
416 		break;
417 	}
418 
419 	return (true);
420 }
421 
422 /* Word Wrapping */
423 static void
424 print_string(WINDOW *win, int *rows, int cols, int *y, int *x, wchar_t *str,
425     bool color)
426 {
427 	int i, j, len, reallen, wc;
428 	wchar_t ws[2];
429 
430 	ws[1] = L'\0';
431 
432 	len = wcslen(str);
433 	if (color) {
434 		reallen = 0;
435 		i=0;
436 		while (i < len) {
437 			if (is_wtext_attr(str+i) == false)
438 				reallen += wcwidth(str[i]);
439 			i++;
440 		}
441 	} else
442 		reallen = wcswidth(str, len);
443 
444 	i = 0;
445 	while (i < len) {
446 		if (*x + reallen > cols) {
447 			*y = (*x != 0 ? *y+1 : *y);
448 			if (*y >= *rows) {
449 				*rows = *y + 1;
450 				wresize(win, *rows, cols);
451 			}
452 			*x = 0;
453 		}
454 		j = *x;
455 		while (j < cols && i < len) {
456 			if (color && check_set_wtext_attr(win, str+i)) {
457 				i += 3;
458 			} else if (j + wcwidth(str[i]) > cols) {
459 				break;
460 			} else {
461 				/* inline mvwaddwch() for efficiency */
462 				ws[0] = str[i];
463 				mvwaddwstr(win, *y, j, ws);
464 				wc = wcwidth(str[i]);;
465 				reallen -= wc;
466 				j += wc;
467 				i++;
468 				*x = j;
469 			}
470 		}
471 	}
472 }
473 
474 static int
475 print_textpad(struct bsddialog_conf *conf, WINDOW *pad, const char *text)
476 {
477 	bool loop;
478 	int i, j, z, rows, cols, x, y, tablen;
479 	wchar_t *wtext, *string;
480 
481 	if ((wtext = alloc_mbstows(text)) == NULL)
482 		RETURN_ERROR("Cannot allocate/print text in wchar_t*");
483 
484 	if ((string = calloc(wcslen(wtext) + 1, sizeof(wchar_t))) == NULL)
485 		RETURN_ERROR("Cannot build (analyze) text");
486 
487 	getmaxyx(pad, rows, cols);
488 	tablen = (conf->text.tablen == 0) ? TABSIZE : (int)conf->text.tablen;
489 
490 	i = j = x = y = 0;
491 	loop = true;
492 	while (loop) {
493 		string[j] = wtext[i];
494 
495 		if (wcschr(L"\n\t  ", string[j]) != NULL || string[j] == L'\0') {
496 			string[j] = L'\0';
497 			print_string(pad, &rows, cols, &y, &x, string,
498 			    conf->text.highlight);
499 		}
500 
501 		switch (wtext[i]) {
502 		case L'\0':
503 			loop = false;
504 			break;
505 		case L'\n':
506 			x = 0;
507 			y++;
508 			j = -1;
509 			break;
510 		case L'\t':
511 			for (z = 0; z < tablen; z++) {
512 				if (x >= cols) {
513 					x = 0;
514 					y++;
515 				}
516 				x++;
517 			}
518 			j = -1;
519 			break;
520 		case L' ':
521 			x++;
522 			if (x >= cols) {
523 				x = 0;
524 				y++;
525 			}
526 			j = -1;
527 		}
528 
529 		if (y >= rows) {
530 			rows = y + 1;
531 			wresize(pad, rows, cols);
532 		}
533 
534 		j++;
535 		i++;
536 	}
537 
538 	free(wtext);
539 	free(string);
540 
541 	return (0);
542 }
543 
544 /* Text Autosize */
545 #define NL  -1
546 #define WS  -2
547 #define TB  -3
548 
549 struct textproperties {
550 	int nword;
551 	int *words;
552 	uint8_t *wletters;
553 	int maxwordcols;
554 	int maxline;
555 	bool hasnewline;
556 };
557 
558 static int
559 text_properties(struct bsddialog_conf *conf, const char *text,
560     struct textproperties *tp)
561 {
562 	int i, l, currlinecols, maxwords, wtextlen, tablen, wordcols;
563 	wchar_t *wtext;
564 
565 	tablen = (conf->text.tablen == 0) ? TABSIZE : (int)conf->text.tablen;
566 
567 	maxwords = 1024;
568 	if ((tp->words = calloc(maxwords, sizeof(int))) == NULL)
569 		RETURN_ERROR("Cannot alloc memory for text autosize");
570 
571 	if ((wtext = alloc_mbstows(text)) == NULL)
572 		RETURN_ERROR("Cannot allocate/autosize text in wchar_t*");
573 	wtextlen = wcslen(wtext);
574 	if ((tp->wletters = calloc(wtextlen, sizeof(uint8_t))) == NULL)
575 		RETURN_ERROR("Cannot allocate wletters for text autosizing");
576 
577 	tp->nword = 0;
578 	tp->maxline = 0;
579 	tp->maxwordcols = 0;
580 	tp->hasnewline = false;
581 	currlinecols = 0;
582 	wordcols = 0;
583 	l = 0;
584 	for (i = 0; i < wtextlen; i++) {
585 		if (conf->text.highlight && is_wtext_attr(wtext + i)) {
586 			i += 2; /* +1 for update statement */
587 			continue;
588 		}
589 
590 		if (tp->nword + 1 >= maxwords) {
591 			maxwords += 1024;
592 			tp->words = realloc(tp->words, maxwords * sizeof(int));
593 			if (tp->words == NULL)
594 				RETURN_ERROR("Cannot realloc memory for text "
595 				    "autosize");
596 		}
597 
598 		if (wcschr(L"\t\n  ", wtext[i]) != NULL) {
599 			tp->maxwordcols = MAX(wordcols, tp->maxwordcols);
600 
601 			if (wordcols != 0) {
602 				/* line */
603 				currlinecols += wordcols;
604 				/* word */
605 				tp->words[tp->nword] = wordcols;
606 				tp->nword += 1;
607 				wordcols = 0;
608 			}
609 
610 			switch (wtext[i]) {
611 			case L'\t':
612 				/* line */
613 				currlinecols += tablen;
614 				/* word */
615 				tp->words[tp->nword] = TB;
616 				break;
617 			case L'\n':
618 				/* line */
619 				tp->hasnewline = true;
620 				tp->maxline = MAX(tp->maxline, currlinecols);
621 				currlinecols = 0;
622 				/* word */
623 				tp->words[tp->nword] = NL;
624 				break;
625 			case L' ':
626 				/* line */
627 				currlinecols += 1;
628 				/* word */
629 				tp->words[tp->nword] = WS;
630 				break;
631 			}
632 			tp->nword += 1;
633 		} else {
634 			tp->wletters[l] = wcwidth(wtext[i]);
635 			wordcols += tp->wletters[l];
636 			l++;
637 		}
638 	}
639 	/* word */
640 	if (wordcols != 0) {
641 		tp->words[tp->nword] = wordcols;
642 		tp->nword += 1;
643 		tp->maxwordcols = MAX(wordcols, tp->maxwordcols);
644 	}
645 	/* line */
646 	tp->maxline = MAX(tp->maxline, currlinecols);
647 
648 	free(wtext);
649 
650 	return (0);
651 }
652 
653 
654 static int
655 text_autosize(struct bsddialog_conf *conf, struct textproperties *tp,
656     int maxrows, int mincols, bool increasecols, int *h, int *w)
657 {
658 	int i, j, x, y, z, l, line, maxwidth, tablen;
659 
660 	maxwidth = widget_max_width(conf) - HBORDERS - TEXTHMARGINS;
661 	tablen = (conf->text.tablen == 0) ? TABSIZE : (int)conf->text.tablen;
662 
663 	if (increasecols) {
664 		mincols = MAX(mincols, tp->maxwordcols);
665 		mincols = MAX(mincols,
666 		    (int)conf->auto_minwidth - HBORDERS - TEXTHMARGINS);
667 		mincols = MIN(mincols, maxwidth);
668 	}
669 
670 	while (true) {
671 		x = 0;
672 		y = 1;
673 		line=0;
674 		l = 0;
675 		for (i = 0; i < tp->nword; i++) {
676 			switch (tp->words[i]) {
677 			case TB:
678 				for (j = 0; j < tablen; j++) {
679 					if (x >= mincols) {
680 						x = 0;
681 						y++;
682 					}
683 				x++;
684 				}
685 				break;
686 			case NL:
687 				y++;
688 				x = 0;
689 				break;
690 			case WS:
691 				x++;
692 				if (x >= mincols) {
693 					x = 0;
694 					y++;
695 				}
696 				break;
697 			default:
698 				if (tp->words[i] + x <= mincols) {
699 					x += tp->words[i];
700 					for (z = 0 ; z != tp->words[i]; l++ )
701 						z += tp->wletters[l];
702 				} else if (tp->words[i] <= mincols) {
703 					y++;
704 					x = tp->words[i];
705 					for (z = 0 ; z != tp->words[i]; l++ )
706 						z += tp->wletters[l];
707 				} else {
708 					for (j = tp->words[i]; j > 0; ) {
709 						y = (x == 0) ? y : y + 1;
710 						z = 0;
711 						while (z != j && z < mincols) {
712 							z += tp->wletters[l];
713 							l++;
714 						}
715 						x = z;
716 						line = MAX(line, x);
717 						j -= z;
718 					}
719 				}
720 			}
721 			line = MAX(line, x);
722 		}
723 
724 		if (increasecols == false)
725 			break;
726 		if (mincols >= maxwidth)
727 			break;
728 		if (line >= y * (int)conf->text.cols_per_row && y <= maxrows)
729 			break;
730 		mincols++;
731 	}
732 
733 	*h = (tp->nword == 0) ? 0 : y;
734 	*w = MIN(mincols, line); /* wtext can be less than mincols */
735 
736 	return (0);
737 }
738 
739 int
740 text_size(struct bsddialog_conf *conf, int rows, int cols, const char *text,
741     struct buttons *bs, int rowsnotext, int startwtext, int *htext, int *wtext)
742 {
743 	bool changewtext;
744 	int wbuttons, maxhtext;
745 	struct textproperties tp;
746 
747 	wbuttons = 0;
748 	if (bs != NULL)
749 		wbuttons = buttons_min_width(*bs);
750 
751 	/* Rows */
752 	if (rows == BSDDIALOG_AUTOSIZE || rows == BSDDIALOG_FULLSCREEN) {
753 		maxhtext = widget_max_height(conf) - VBORDERS - rowsnotext;
754 	} else { /* fixed */
755 		maxhtext = rows - VBORDERS - rowsnotext;
756 	}
757 	if (bs != NULL)
758 		maxhtext -= 2;
759 	if (maxhtext <= 0)
760 		maxhtext = 1; /* text_autosize() computes always htext */
761 
762 	/* Cols */
763 	if (cols == BSDDIALOG_AUTOSIZE) {
764 		startwtext = MAX(startwtext, wbuttons - TEXTHMARGINS);
765 		changewtext = true;
766 	} else if (cols == BSDDIALOG_FULLSCREEN) {
767 		startwtext = widget_max_width(conf) - VBORDERS - TEXTHMARGINS;
768 		changewtext = false;
769 	} else { /* fixed */
770 		startwtext = cols - VBORDERS - TEXTHMARGINS;
771 		changewtext = false;
772 	}
773 
774 	if (startwtext <= 0 && changewtext)
775 		startwtext = 1;
776 	if (startwtext <= 0)
777 		RETURN_ERROR("Fullscreen or fixed cols to print text <=0");
778 
779 	/* Sizing calculation */
780 	if (text_properties(conf, text, &tp) != 0)
781 		return (BSDDIALOG_ERROR);
782 	if (text_autosize(conf, &tp, maxhtext, startwtext, changewtext, htext,
783 	    wtext) != 0)
784 		return (BSDDIALOG_ERROR);
785 
786 	free(tp.words);
787 	free(tp.wletters);
788 
789 	return (0);
790 }
791 
792 /* Widget size and position */
793 int widget_max_height(struct bsddialog_conf *conf)
794 {
795 	int maxheight;
796 
797 	maxheight = conf->shadow ? SCREENLINES - (int)t.shadow.y : SCREENLINES;
798 	if (maxheight <= 0)
799 		RETURN_ERROR("Terminal too small, screen lines - shadow <= 0");
800 
801 	if (conf->y != BSDDIALOG_CENTER && conf->auto_topmargin > 0)
802 		RETURN_ERROR("conf.y > 0 and conf->auto_topmargin > 0");
803 	else if (conf->y == BSDDIALOG_CENTER) {
804 		maxheight -= conf->auto_topmargin;
805 		if (maxheight <= 0)
806 			RETURN_ERROR("Terminal too small, screen lines - top "
807 			    "margins <= 0");
808 	} else if (conf->y > 0) {
809 		maxheight -= conf->y;
810 		if (maxheight <= 0)
811 			RETURN_ERROR("Terminal too small, screen lines - "
812 			    "shadow - y <= 0");
813 	}
814 
815 	maxheight -= conf->auto_downmargin;
816 	if (maxheight <= 0)
817 		RETURN_ERROR("Terminal too small, screen lines - Down margins "
818 		    "<= 0");
819 
820 	return (maxheight);
821 }
822 
823 int widget_max_width(struct bsddialog_conf *conf)
824 {
825 	int maxwidth;
826 
827 	maxwidth = conf->shadow ? SCREENCOLS - (int)t.shadow.x : SCREENCOLS;
828 	if (maxwidth <= 0)
829 		RETURN_ERROR("Terminal too small, screen cols - shadow <= 0");
830 
831 	if (conf->x > 0) {
832 		maxwidth -= conf->x;
833 		if (maxwidth <= 0)
834 			RETURN_ERROR("Terminal too small, screen cols - shadow "
835 			    "- x <= 0");
836 	}
837 
838 	return (maxwidth);
839 }
840 
841 int
842 widget_min_height(struct bsddialog_conf *conf, int htext, int minwidget,
843     bool withbuttons)
844 {
845 	int min;
846 
847 	min = 0;
848 
849 	/* buttons */
850 	if (withbuttons)
851 		min += 2; /* buttons and border */
852 
853 	/* text */
854 	min += htext;
855 
856 	/* specific widget min height */
857 	min += minwidget;
858 
859 	/* dialog borders */
860 	min += HBORDERS;
861 	/* conf.auto_minheight */
862 	min = MAX(min, (int)conf->auto_minheight);
863 	/* avoid terminal overflow */
864 	min = MIN(min, widget_max_height(conf));
865 
866 	return (min);
867 }
868 
869 int
870 widget_min_width(struct bsddialog_conf *conf, int wtext, int minwidget,
871     struct buttons *bs)
872 
873 {
874 	int min, delimtitle, wbottomtitle, wtitle;
875 
876 	min = 0;
877 
878 	/* buttons */
879 	if (bs != NULL)
880 		min += buttons_min_width(*bs);
881 
882 	/* text */
883 	if (wtext > 0)
884 		min = MAX(min, wtext + TEXTHMARGINS);
885 
886 	/* specific widget min width */
887 	min = MAX(min, minwidget);
888 
889 	/* title */
890 	if (conf->title != NULL) {
891 		delimtitle = t.dialog.delimtitle ? 2 : 0;
892 		wtitle = strcols(conf->title);
893 		min = MAX(min, wtitle + 2 + delimtitle);
894 	}
895 
896 	/* bottom title */
897 	if (conf->bottomtitle != NULL) {
898 		wbottomtitle = strcols(conf->bottomtitle);
899 		min = MAX(min, wbottomtitle + 4);
900 	}
901 
902 	/* dialog borders */
903 	min += VBORDERS;
904 	/* conf.auto_minwidth */
905 	min = MAX(min, (int)conf->auto_minwidth);
906 	/* avoid terminal overflow */
907 	min = MIN(min, widget_max_width(conf));
908 
909 	return (min);
910 }
911 
912 int
913 set_widget_size(struct bsddialog_conf *conf, int rows, int cols, int *h, int *w)
914 {
915 	int maxheight, maxwidth;
916 
917 	if ((maxheight = widget_max_height(conf)) == BSDDIALOG_ERROR)
918 		return (BSDDIALOG_ERROR);
919 
920 	if (rows == BSDDIALOG_FULLSCREEN)
921 		*h = maxheight;
922 	else if (rows < BSDDIALOG_FULLSCREEN)
923 		RETURN_ERROR("Negative (less than -1) height");
924 	else if (rows > BSDDIALOG_AUTOSIZE) {
925 		if ((*h = rows) > maxheight)
926 			RETURN_ERROR("Height too big (> terminal height - "
927 			    "shadow)");
928 	}
929 	/* rows == AUTOSIZE: each widget has to set its size */
930 
931 	if ((maxwidth = widget_max_width(conf)) == BSDDIALOG_ERROR)
932 		return (BSDDIALOG_ERROR);
933 
934 	if (cols == BSDDIALOG_FULLSCREEN)
935 		*w = maxwidth;
936 	else if (cols < BSDDIALOG_FULLSCREEN)
937 		RETURN_ERROR("Negative (less than -1) width");
938 	else if (cols > BSDDIALOG_AUTOSIZE) {
939 		if ((*w = cols) > maxwidth)
940 			RETURN_ERROR("Width too big (> terminal width - "
941 			    "shadow)");
942 	}
943 	/* cols == AUTOSIZE: each widget has to set its size */
944 
945 	return (0);
946 }
947 
948 int
949 set_widget_position(struct bsddialog_conf *conf, int *y, int *x, int h, int w)
950 {
951 	int hshadow = conf->shadow ? (int)t.shadow.y : 0;
952 	int wshadow = conf->shadow ? (int)t.shadow.x : 0;
953 
954 	if (conf->y == BSDDIALOG_CENTER) {
955 		*y = SCREENLINES/2 - (h + hshadow)/2;
956 		if (*y < (int)conf->auto_topmargin)
957 			*y = conf->auto_topmargin;
958 		if (*y + h + hshadow > SCREENLINES - (int)conf->auto_downmargin)
959 			*y = SCREENLINES - h - hshadow - conf->auto_downmargin;
960 	}
961 	else if (conf->y < BSDDIALOG_CENTER)
962 		RETURN_ERROR("Negative begin y (less than -1)");
963 	else if (conf->y >= SCREENLINES)
964 		RETURN_ERROR("Begin Y under the terminal");
965 	else
966 		*y = conf->y;
967 
968 	if (*y + h + hshadow > SCREENLINES)
969 		RETURN_ERROR("The lower of the box under the terminal "
970 		    "(begin Y + height (+ shadow) > terminal lines)");
971 
972 
973 	if (conf->x == BSDDIALOG_CENTER)
974 		*x = SCREENCOLS/2 - (w + wshadow)/2;
975 	else if (conf->x < BSDDIALOG_CENTER)
976 		RETURN_ERROR("Negative begin x (less than -1)");
977 	else if (conf->x >= SCREENCOLS)
978 		RETURN_ERROR("Begin X over the right of the terminal");
979 	else
980 		*x = conf->x;
981 
982 	if ((*x + w + wshadow) > SCREENCOLS)
983 		RETURN_ERROR("The right of the box over the terminal "
984 		    "(begin X + width (+ shadow) > terminal cols)");
985 
986 	return (0);
987 }
988 
989 /* Widgets build, update, destroy */
990 void
991 draw_borders(struct bsddialog_conf *conf, WINDOW *win, int rows, int cols,
992     enum elevation elev)
993 {
994 	int leftcolor, rightcolor;
995 	int ls, rs, ts, bs, tl, tr, bl, br, ltee, rtee;
996 
997 	if (conf->no_lines)
998 		return;
999 
1000 	if (conf->ascii_lines) {
1001 		ls = rs = '|';
1002 		ts = bs = '-';
1003 		tl = tr = bl = br = ltee = rtee = '+';
1004 	} else {
1005 		ls = rs = ACS_VLINE;
1006 		ts = bs = ACS_HLINE;
1007 		tl = ACS_ULCORNER;
1008 		tr = ACS_URCORNER;
1009 		bl = ACS_LLCORNER;
1010 		br = ACS_LRCORNER;
1011 		ltee = ACS_LTEE;
1012 		rtee = ACS_RTEE;
1013 	}
1014 
1015 	leftcolor = elev == RAISED ?
1016 	    t.dialog.lineraisecolor : t.dialog.linelowercolor;
1017 	rightcolor = elev == RAISED ?
1018 	    t.dialog.linelowercolor : t.dialog.lineraisecolor;
1019 	wattron(win, leftcolor);
1020 	wborder(win, ls, rs, ts, bs, tl, tr, bl, br);
1021 	wattroff(win, leftcolor);
1022 
1023 	wattron(win, rightcolor);
1024 	mvwaddch(win, 0, cols-1, tr);
1025 	mvwvline(win, 1, cols-1, rs, rows-2);
1026 	mvwaddch(win, rows-1, cols-1, br);
1027 	mvwhline(win, rows-1, 1, bs, cols-2);
1028 	wattroff(win, rightcolor);
1029 }
1030 
1031 WINDOW *
1032 new_boxed_window(struct bsddialog_conf *conf, int y, int x, int rows, int cols,
1033     enum elevation elev)
1034 {
1035 	WINDOW *win;
1036 
1037 	if ((win = newwin(rows, cols, y, x)) == NULL) {
1038 		set_error_string("Cannot build boxed window");
1039 		return (NULL);
1040 	}
1041 
1042 	wbkgd(win, t.dialog.color);
1043 
1044 	draw_borders(conf, win, rows, cols, elev);
1045 
1046 	return (win);
1047 }
1048 
1049 static int
1050 draw_dialog(struct bsddialog_conf *conf, WINDOW *shadow, WINDOW *widget,
1051     WINDOW *textpad, const char *text, struct buttons *bs, bool shortcutbuttons)
1052 {
1053 	int h, w, wtitle, wbottomtitle, ts, ltee, rtee;
1054 
1055 	ts = conf->ascii_lines ? '-' : ACS_HLINE;
1056 	ltee = conf->ascii_lines ? '+' : ACS_LTEE;
1057 	rtee = conf->ascii_lines ? '+' : ACS_RTEE;
1058 
1059 	getmaxyx(widget, h, w);
1060 
1061 	if (conf->shadow)
1062 		wnoutrefresh(shadow);
1063 
1064 	draw_borders(conf, widget, h, w, RAISED);
1065 
1066 	if (conf->title != NULL) {
1067 		if ((wtitle = strcols(conf->title)) < 0)
1068 			return (BSDDIALOG_ERROR);
1069 		if (t.dialog.delimtitle && conf->no_lines == false) {
1070 			wattron(widget, t.dialog.lineraisecolor);
1071 			mvwaddch(widget, 0, w/2 - wtitle/2 -1, rtee);
1072 			wattroff(widget, t.dialog.lineraisecolor);
1073 		}
1074 		wattron(widget, t.dialog.titlecolor);
1075 		mvwaddstr(widget, 0, w/2 - wtitle/2, conf->title);
1076 		wattroff(widget, t.dialog.titlecolor);
1077 		if (t.dialog.delimtitle && conf->no_lines == false) {
1078 			wattron(widget, t.dialog.lineraisecolor);
1079 			waddch(widget, ltee);
1080 			wattroff(widget, t.dialog.lineraisecolor);
1081 		}
1082 	}
1083 
1084 	if (bs != NULL) {
1085 		if (conf->no_lines == false) {
1086 			wattron(widget, t.dialog.lineraisecolor);
1087 			mvwaddch(widget, h-3, 0, ltee);
1088 			mvwhline(widget, h-3, 1, ts, w-2);
1089 			wattroff(widget, t.dialog.lineraisecolor);
1090 
1091 			wattron(widget, t.dialog.linelowercolor);
1092 			mvwaddch(widget, h-3, w-1, rtee);
1093 			wattroff(widget, t.dialog.linelowercolor);
1094 		}
1095 		draw_buttons(widget, *bs, shortcutbuttons);
1096 	}
1097 
1098 	if (conf->bottomtitle != NULL) {
1099 		if ((wbottomtitle = strcols(conf->bottomtitle)) < 0)
1100 			return (BSDDIALOG_ERROR);
1101 		wattron(widget, t.dialog.bottomtitlecolor);
1102 		wmove(widget, h - 1, w/2 - wbottomtitle/2 - 1);
1103 		waddch(widget, ' ');
1104 		waddstr(widget, conf->bottomtitle);
1105 		waddch(widget, ' ');
1106 		wattroff(widget, t.dialog.bottomtitlecolor);
1107 	}
1108 
1109 	wnoutrefresh(widget);
1110 
1111 	if (textpad != NULL && text != NULL) /* textbox */
1112 		if (print_textpad(conf, textpad, text) !=0)
1113 			return (BSDDIALOG_ERROR);
1114 
1115 	return (0);
1116 }
1117 
1118 int
1119 update_dialog(struct bsddialog_conf *conf, WINDOW *shadow, WINDOW *widget,
1120     int y, int x, int h, int w, WINDOW *textpad, const char *text,
1121     struct buttons *bs, bool shortcutbuttons)
1122 {
1123 	int error;
1124 
1125 	if (conf->shadow) {
1126 		wclear(shadow);
1127 		mvwin(shadow, y + t.shadow.y, x + t.shadow.x);
1128 		wresize(shadow, h, w);
1129 	}
1130 
1131 	wclear(widget);
1132 	mvwin(widget, y, x);
1133 	wresize(widget, h, w);
1134 
1135 	if (textpad != NULL) {
1136 		wclear(textpad);
1137 		wresize(textpad, 1, w - HBORDERS - TEXTHMARGINS);
1138 	}
1139 
1140 	error = draw_dialog(conf, shadow, widget, textpad, text, bs,
1141 	    shortcutbuttons);
1142 
1143 	return (error);
1144 }
1145 
1146 int
1147 new_dialog(struct bsddialog_conf *conf, WINDOW **shadow, WINDOW **widget, int y,
1148     int x, int h, int w, WINDOW **textpad, const char *text, struct buttons *bs,
1149     bool shortcutbuttons)
1150 {
1151 	int error;
1152 
1153 	if (conf->shadow) {
1154 		*shadow = newwin(h, w, y + t.shadow.y, x + t.shadow.x);
1155 		if (*shadow == NULL)
1156 			RETURN_ERROR("Cannot build shadow");
1157 		wbkgd(*shadow, t.shadow.color);
1158 	}
1159 
1160 	if ((*widget = new_boxed_window(conf, y, x, h, w, RAISED)) == NULL) {
1161 		if (conf->shadow)
1162 			delwin(*shadow);
1163 		return (BSDDIALOG_ERROR);
1164 	}
1165 
1166 	if (textpad != NULL && text != NULL) { /* textbox */
1167 		*textpad = newpad(1, w - HBORDERS - TEXTHMARGINS);
1168 		if (*textpad == NULL) {
1169 			delwin(*widget);
1170 			if (conf->shadow)
1171 				delwin(*shadow);
1172 			RETURN_ERROR("Cannot build the pad window for text");
1173 		}
1174 		wbkgd(*textpad, t.dialog.color);
1175 	}
1176 
1177 	error = draw_dialog(conf, *shadow, *widget,
1178 	    textpad == NULL ? NULL : *textpad, text, bs, shortcutbuttons);
1179 
1180 	return (error);
1181 }
1182 
1183 void
1184 end_dialog(struct bsddialog_conf *conf, WINDOW *shadow, WINDOW *widget,
1185     WINDOW *textpad)
1186 {
1187 	int y, x, h, w;
1188 
1189 	getbegyx(widget, y, x);
1190 	getmaxyx(widget, h, w);
1191 
1192 	if (conf->sleep > 0)
1193 		sleep(conf->sleep);
1194 
1195 	if (textpad != NULL)
1196 		delwin(textpad);
1197 
1198 	delwin(widget);
1199 
1200 	if (conf->shadow)
1201 		delwin(shadow);
1202 
1203 	if (conf->clear)
1204 		hide_widget(y, x, h, w, conf->shadow);
1205 
1206 	if (conf->get_height != NULL)
1207 		*conf->get_height = h;
1208 	if (conf->get_width != NULL)
1209 		*conf->get_width = w;
1210 }
1211