]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/bsddialog/lib/lib_util.c
MFV: file 5.45.
[FreeBSD/FreeBSD.git] / contrib / bsddialog / lib / lib_util.c
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 }