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
get_error_string(void)47 const char *get_error_string(void)
48 {
49 return (errorbuffer);
50 }
51
set_error_string(const char * str)52 void set_error_string(const char *str)
53 {
54 strncpy(errorbuffer, str, ERRBUFLEN-1);
55 }
56
57 /* Unicode */
alloc_mbstows(const char * mbstring)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
mvwaddwch(WINDOW * w,int y,int x,wchar_t wch)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
str_props(const char * mbstring,unsigned int * cols,bool * has_multi_col)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
strcols(const char * mbstring)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 */
hide_widget(int y,int x,int h,int w,bool withshadow)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 */
f1help(struct bsddialog_conf * conf)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
draw_button(WINDOW * window,int y,int x,int size,const char * text,wchar_t first,bool selected,bool shortcut)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
draw_buttons(WINDOW * window,struct buttons bs,bool shortcut)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
get_buttons(struct bsddialog_conf * conf,struct buttons * bs,const char * yesoklabel,const char * nocancellabel)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
buttons_min_width(struct buttons bs)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
shortcut_buttons(wint_t key,struct buttons * bs)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 */
is_wtext_attr(const wchar_t * wtext)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
check_set_wtext_attr(WINDOW * win,wchar_t * wtext)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
print_string(WINDOW * win,int * rows,int cols,int * y,int * x,wchar_t * str,bool color)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
print_textpad(struct bsddialog_conf * conf,WINDOW * pad,const char * text)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
text_properties(struct bsddialog_conf * conf,const char * text,struct textproperties * tp)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
text_autosize(struct bsddialog_conf * conf,struct textproperties * tp,int maxrows,int mincols,bool increasecols,int * h,int * w)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
text_size(struct bsddialog_conf * conf,int rows,int cols,const char * text,struct buttons * bs,int rowsnotext,int startwtext,int * htext,int * wtext)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 */
widget_max_height(struct bsddialog_conf * conf)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
widget_max_width(struct bsddialog_conf * conf)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
widget_min_height(struct bsddialog_conf * conf,int htext,int minwidget,bool withbuttons)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
widget_min_width(struct bsddialog_conf * conf,int wtext,int minwidget,struct buttons * bs)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
set_widget_size(struct bsddialog_conf * conf,int rows,int cols,int * h,int * w)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
set_widget_position(struct bsddialog_conf * conf,int * y,int * x,int h,int w)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
draw_borders(struct bsddialog_conf * conf,WINDOW * win,int rows,int cols,enum elevation elev)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 *
new_boxed_window(struct bsddialog_conf * conf,int y,int x,int rows,int cols,enum elevation elev)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
draw_dialog(struct bsddialog_conf * conf,WINDOW * shadow,WINDOW * widget,WINDOW * textpad,const char * text,struct buttons * bs,bool shortcutbuttons)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
update_dialog(struct bsddialog_conf * conf,WINDOW * shadow,WINDOW * widget,int y,int x,int h,int w,WINDOW * textpad,const char * text,struct buttons * bs,bool shortcutbuttons)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
new_dialog(struct bsddialog_conf * conf,WINDOW ** shadow,WINDOW ** widget,int y,int x,int h,int w,WINDOW ** textpad,const char * text,struct buttons * bs,bool shortcutbuttons)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
end_dialog(struct bsddialog_conf * conf,WINDOW * shadow,WINDOW * widget,WINDOW * textpad)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