xref: /freebsd/contrib/bsddialog/lib/lib_util.c (revision 5d3e7166)
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) /* fixed rows */
925 		*h = MIN(rows, maxheight); /* rows is at most maxheight */
926 	/* rows == AUTOSIZE: each widget has to set its size */
927 
928 	if ((maxwidth = widget_max_width(conf)) == BSDDIALOG_ERROR)
929 		return (BSDDIALOG_ERROR);
930 
931 	if (cols == BSDDIALOG_FULLSCREEN)
932 		*w = maxwidth;
933 	else if (cols < BSDDIALOG_FULLSCREEN)
934 		RETURN_ERROR("Negative (less than -1) width");
935 	else if (cols > BSDDIALOG_AUTOSIZE) /* fixed cols */
936 		*w = MIN(cols, maxwidth); /* cols is at most maxwidth */
937 	/* cols == AUTOSIZE: each widget has to set its size */
938 
939 	return (0);
940 }
941 
942 int
943 set_widget_position(struct bsddialog_conf *conf, int *y, int *x, int h, int w)
944 {
945 	int hshadow = conf->shadow ? (int)t.shadow.y : 0;
946 	int wshadow = conf->shadow ? (int)t.shadow.x : 0;
947 
948 	if (conf->y == BSDDIALOG_CENTER) {
949 		*y = SCREENLINES/2 - (h + hshadow)/2;
950 		if (*y < (int)conf->auto_topmargin)
951 			*y = conf->auto_topmargin;
952 		if (*y + h + hshadow > SCREENLINES - (int)conf->auto_downmargin)
953 			*y = SCREENLINES - h - hshadow - conf->auto_downmargin;
954 	}
955 	else if (conf->y < BSDDIALOG_CENTER)
956 		RETURN_ERROR("Negative begin y (less than -1)");
957 	else if (conf->y >= SCREENLINES)
958 		RETURN_ERROR("Begin Y under the terminal");
959 	else
960 		*y = conf->y;
961 
962 	if (*y + h + hshadow > SCREENLINES)
963 		RETURN_ERROR("The lower of the box under the terminal "
964 		    "(begin Y + height (+ shadow) > terminal lines)");
965 
966 
967 	if (conf->x == BSDDIALOG_CENTER)
968 		*x = SCREENCOLS/2 - (w + wshadow)/2;
969 	else if (conf->x < BSDDIALOG_CENTER)
970 		RETURN_ERROR("Negative begin x (less than -1)");
971 	else if (conf->x >= SCREENCOLS)
972 		RETURN_ERROR("Begin X over the right of the terminal");
973 	else
974 		*x = conf->x;
975 
976 	if ((*x + w + wshadow) > SCREENCOLS)
977 		RETURN_ERROR("The right of the box over the terminal "
978 		    "(begin X + width (+ shadow) > terminal cols)");
979 
980 	return (0);
981 }
982 
983 /* Widgets build, update, destroy */
984 void
985 draw_borders(struct bsddialog_conf *conf, WINDOW *win, int rows, int cols,
986     enum elevation elev)
987 {
988 	int leftcolor, rightcolor;
989 	int ls, rs, ts, bs, tl, tr, bl, br, ltee, rtee;
990 
991 	if (conf->no_lines)
992 		return;
993 
994 	if (conf->ascii_lines) {
995 		ls = rs = '|';
996 		ts = bs = '-';
997 		tl = tr = bl = br = ltee = rtee = '+';
998 	} else {
999 		ls = rs = ACS_VLINE;
1000 		ts = bs = ACS_HLINE;
1001 		tl = ACS_ULCORNER;
1002 		tr = ACS_URCORNER;
1003 		bl = ACS_LLCORNER;
1004 		br = ACS_LRCORNER;
1005 		ltee = ACS_LTEE;
1006 		rtee = ACS_RTEE;
1007 	}
1008 
1009 	leftcolor = elev == RAISED ?
1010 	    t.dialog.lineraisecolor : t.dialog.linelowercolor;
1011 	rightcolor = elev == RAISED ?
1012 	    t.dialog.linelowercolor : t.dialog.lineraisecolor;
1013 	wattron(win, leftcolor);
1014 	wborder(win, ls, rs, ts, bs, tl, tr, bl, br);
1015 	wattroff(win, leftcolor);
1016 
1017 	wattron(win, rightcolor);
1018 	mvwaddch(win, 0, cols-1, tr);
1019 	mvwvline(win, 1, cols-1, rs, rows-2);
1020 	mvwaddch(win, rows-1, cols-1, br);
1021 	mvwhline(win, rows-1, 1, bs, cols-2);
1022 	wattroff(win, rightcolor);
1023 }
1024 
1025 WINDOW *
1026 new_boxed_window(struct bsddialog_conf *conf, int y, int x, int rows, int cols,
1027     enum elevation elev)
1028 {
1029 	WINDOW *win;
1030 
1031 	if ((win = newwin(rows, cols, y, x)) == NULL) {
1032 		set_error_string("Cannot build boxed window");
1033 		return (NULL);
1034 	}
1035 
1036 	wbkgd(win, t.dialog.color);
1037 
1038 	draw_borders(conf, win, rows, cols, elev);
1039 
1040 	return (win);
1041 }
1042 
1043 static int
1044 draw_dialog(struct bsddialog_conf *conf, WINDOW *shadow, WINDOW *widget,
1045     WINDOW *textpad, const char *text, struct buttons *bs, bool shortcutbuttons)
1046 {
1047 	int h, w, wtitle, wbottomtitle, ts, ltee, rtee;
1048 
1049 	ts = conf->ascii_lines ? '-' : ACS_HLINE;
1050 	ltee = conf->ascii_lines ? '+' : ACS_LTEE;
1051 	rtee = conf->ascii_lines ? '+' : ACS_RTEE;
1052 
1053 	getmaxyx(widget, h, w);
1054 
1055 	if (conf->shadow)
1056 		wnoutrefresh(shadow);
1057 
1058 	draw_borders(conf, widget, h, w, RAISED);
1059 
1060 	if (conf->title != NULL) {
1061 		if ((wtitle = strcols(conf->title)) < 0)
1062 			return (BSDDIALOG_ERROR);
1063 		if (t.dialog.delimtitle && conf->no_lines == false) {
1064 			wattron(widget, t.dialog.lineraisecolor);
1065 			mvwaddch(widget, 0, w/2 - wtitle/2 -1, rtee);
1066 			wattroff(widget, t.dialog.lineraisecolor);
1067 		}
1068 		wattron(widget, t.dialog.titlecolor);
1069 		mvwaddstr(widget, 0, w/2 - wtitle/2, conf->title);
1070 		wattroff(widget, t.dialog.titlecolor);
1071 		if (t.dialog.delimtitle && conf->no_lines == false) {
1072 			wattron(widget, t.dialog.lineraisecolor);
1073 			waddch(widget, ltee);
1074 			wattroff(widget, t.dialog.lineraisecolor);
1075 		}
1076 	}
1077 
1078 	if (bs != NULL) {
1079 		if (conf->no_lines == false) {
1080 			wattron(widget, t.dialog.lineraisecolor);
1081 			mvwaddch(widget, h-3, 0, ltee);
1082 			mvwhline(widget, h-3, 1, ts, w-2);
1083 			wattroff(widget, t.dialog.lineraisecolor);
1084 
1085 			wattron(widget, t.dialog.linelowercolor);
1086 			mvwaddch(widget, h-3, w-1, rtee);
1087 			wattroff(widget, t.dialog.linelowercolor);
1088 		}
1089 		draw_buttons(widget, *bs, shortcutbuttons);
1090 	}
1091 
1092 	if (conf->bottomtitle != NULL) {
1093 		if ((wbottomtitle = strcols(conf->bottomtitle)) < 0)
1094 			return (BSDDIALOG_ERROR);
1095 		wattron(widget, t.dialog.bottomtitlecolor);
1096 		wmove(widget, h - 1, w/2 - wbottomtitle/2 - 1);
1097 		waddch(widget, ' ');
1098 		waddstr(widget, conf->bottomtitle);
1099 		waddch(widget, ' ');
1100 		wattroff(widget, t.dialog.bottomtitlecolor);
1101 	}
1102 
1103 	wnoutrefresh(widget);
1104 
1105 	if (textpad != NULL && text != NULL) /* textbox */
1106 		if (print_textpad(conf, textpad, text) !=0)
1107 			return (BSDDIALOG_ERROR);
1108 
1109 	return (0);
1110 }
1111 
1112 int
1113 update_dialog(struct bsddialog_conf *conf, WINDOW *shadow, WINDOW *widget,
1114     int y, int x, int h, int w, WINDOW *textpad, const char *text,
1115     struct buttons *bs, bool shortcutbuttons)
1116 {
1117 	int error;
1118 
1119 	if (conf->shadow) {
1120 		wclear(shadow);
1121 		mvwin(shadow, y + t.shadow.y, x + t.shadow.x);
1122 		wresize(shadow, h, w);
1123 	}
1124 
1125 	wclear(widget);
1126 	mvwin(widget, y, x);
1127 	wresize(widget, h, w);
1128 
1129 	if (textpad != NULL) {
1130 		wclear(textpad);
1131 		wresize(textpad, 1, w - HBORDERS - TEXTHMARGINS);
1132 	}
1133 
1134 	error = draw_dialog(conf, shadow, widget, textpad, text, bs,
1135 	    shortcutbuttons);
1136 
1137 	return (error);
1138 }
1139 
1140 int
1141 new_dialog(struct bsddialog_conf *conf, WINDOW **shadow, WINDOW **widget, int y,
1142     int x, int h, int w, WINDOW **textpad, const char *text, struct buttons *bs,
1143     bool shortcutbuttons)
1144 {
1145 	int error;
1146 
1147 	if (conf->shadow) {
1148 		*shadow = newwin(h, w, y + t.shadow.y, x + t.shadow.x);
1149 		if (*shadow == NULL)
1150 			RETURN_ERROR("Cannot build shadow");
1151 		wbkgd(*shadow, t.shadow.color);
1152 	}
1153 
1154 	if ((*widget = new_boxed_window(conf, y, x, h, w, RAISED)) == NULL) {
1155 		if (conf->shadow)
1156 			delwin(*shadow);
1157 		return (BSDDIALOG_ERROR);
1158 	}
1159 
1160 	if (textpad != NULL && text != NULL) { /* textbox */
1161 		*textpad = newpad(1, w - HBORDERS - TEXTHMARGINS);
1162 		if (*textpad == NULL) {
1163 			delwin(*widget);
1164 			if (conf->shadow)
1165 				delwin(*shadow);
1166 			RETURN_ERROR("Cannot build the pad window for text");
1167 		}
1168 		wbkgd(*textpad, t.dialog.color);
1169 	}
1170 
1171 	error = draw_dialog(conf, *shadow, *widget,
1172 	    textpad == NULL ? NULL : *textpad, text, bs, shortcutbuttons);
1173 
1174 	return (error);
1175 }
1176 
1177 void
1178 end_dialog(struct bsddialog_conf *conf, WINDOW *shadow, WINDOW *widget,
1179     WINDOW *textpad)
1180 {
1181 	int y, x, h, w;
1182 
1183 	getbegyx(widget, y, x);
1184 	getmaxyx(widget, h, w);
1185 
1186 	if (conf->sleep > 0)
1187 		sleep(conf->sleep);
1188 
1189 	if (textpad != NULL)
1190 		delwin(textpad);
1191 
1192 	delwin(widget);
1193 
1194 	if (conf->shadow)
1195 		delwin(shadow);
1196 
1197 	if (conf->clear)
1198 		hide_widget(y, x, h, w, conf->shadow);
1199 
1200 	if (conf->get_height != NULL)
1201 		*conf->get_height = h;
1202 	if (conf->get_width != NULL)
1203 		*conf->get_width = w;
1204 }
1205