]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/bsddialog/lib/lib_util.c
Merge one true awk from 2024-01-22 for the Awk Second Edition support
[FreeBSD/FreeBSD.git] / contrib / bsddialog / lib / lib_util.c
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2021-2023 Alfonso Sabato Siciliano
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27
28 #include <curses.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32 #include <wctype.h>
33
34 #include "bsddialog.h"
35 #include "bsddialog_theme.h"
36 #include "lib_util.h"
37
38 /*
39  * -1- Error and diagnostic
40  *
41  *      get_error_string();
42  *      set_error_string();
43  *      set_fmt_error_string();
44  *
45  * ----------------------------------------------------
46  * -2- (Unicode) Multicolumn character strings
47  *
48  *      alloc_mbstows();
49  *      mvwaddwch();
50  *      str_props();
51  *      strcols();
52  *
53  * ----------------------------------------------------
54  * -3- Buttons
55  *
56  * [static] buttons_min_width();
57  * [static] draw_button();
58  *          draw_buttons();
59  *          set_buttons(); (to call 1 time after prepare_dialog()).
60  *          shortcut_buttons();
61  *
62  * ----------------------------------------------------
63  * -4- (Auto) Sizing and (Auto) Position
64  *
65  * [static] widget_max_height(conf);
66  * [static] widget_max_width(struct bsddialog_conf *conf)
67  * [static] is_wtext_attr();
68  * [static] text_properties();
69  * [static] text_autosize();
70  * [static] text_size();
71  * [static] widget_min_height(conf, htext, hnotext, bool buttons);
72  * [static] widget_min_width(conf, wtext, minw, buttons);
73  *          set_widget_size();
74  *          set_widget_autosize();  (not for all dialogs).
75  *          widget_checksize();     (not for all dialogs).
76  *          set_widget_position();
77  *          dialog_size_position(struct dialog); (not for all dialogs).
78  *
79  * ----------------------------------------------------
80  * -5- (Dialog) Widget components and utils
81  *
82  *      hide_dialog(struct dialog);
83  *      f1help_dialog(conf);
84  *      draw_borders(conf, win, elev);
85  *      update_box(conf, win, y, x, h, w, elev);
86  *      rtextpad(); (helper for pnoutrefresh(textpad)).
87  *
88  * ----------------------------------------------------
89  * -6- Dialog init/build, update/draw, destroy
90  *
91  *          end_dialog(struct dialog);
92  * [static] check_set_wtext_attr();
93  * [static] print_string(); (word wrapping).
94  * [static] print_textpad();
95  *          draw_dialog(struct dialog);
96  *          prepare_dialog(struct dialog);
97  */
98
99 /*
100  * -1- Error and diagnostic
101  */
102 #define ERRBUFLEN    1024
103
104 static char errorbuffer[ERRBUFLEN];
105
106 const char *get_error_string(void)
107 {
108         return (errorbuffer);
109 }
110
111 void set_error_string(const char *str)
112 {
113         strncpy(errorbuffer, str, ERRBUFLEN-1);
114 }
115
116 void set_fmt_error_string(const char *fmt, ...)
117 {
118    va_list arg_ptr;
119
120    va_start(arg_ptr, fmt);
121    vsnprintf(errorbuffer, ERRBUFLEN-1, fmt, arg_ptr);
122    va_end(arg_ptr);
123 }
124
125 /*
126  * -2- (Unicode) Multicolumn character strings
127  */
128 wchar_t* alloc_mbstows(const char *mbstring)
129 {
130         size_t charlen, nchar;
131         mbstate_t mbs;
132         const char *pmbstring;
133         wchar_t *wstring;
134
135         nchar = 1;
136         pmbstring = mbstring;
137         memset(&mbs, 0, sizeof(mbs));
138         while ((charlen = mbrlen(pmbstring, MB_CUR_MAX, &mbs)) != 0 &&
139             charlen != (size_t)-1 && charlen != (size_t)-2) {
140                 pmbstring += charlen;
141                 nchar++;
142         }
143
144         if ((wstring = calloc(nchar, sizeof(wchar_t))) == NULL)
145                 return (NULL);
146         mbstowcs(wstring, mbstring, nchar);
147
148         return (wstring);
149 }
150
151 void mvwaddwch(WINDOW *w, int y, int x, wchar_t wch)
152 {
153         wchar_t ws[2];
154
155         ws[0] = wch;
156         ws[1] = L'\0';
157         mvwaddwstr(w, y, x, ws);
158 }
159
160 int str_props(const char *mbstring, unsigned int *cols, bool *has_multi_col)
161 {
162         bool multicol;
163         int w;
164         unsigned int ncol;
165         size_t charlen, mb_cur_max;
166         wchar_t wch;
167         mbstate_t mbs;
168
169         multicol = false;
170         mb_cur_max = MB_CUR_MAX;
171         ncol = 0;
172         memset(&mbs, 0, sizeof(mbs));
173         while ((charlen = mbrlen(mbstring, mb_cur_max, &mbs)) != 0 &&
174             charlen != (size_t)-1 && charlen != (size_t)-2) {
175                 if (mbtowc(&wch, mbstring, mb_cur_max) < 0)
176                         return (-1);
177                 w = (wch == L'\t') ? TABSIZE : wcwidth(wch);
178                 ncol += (w < 0) ? 0 : w;
179                 if (w > 1 && wch != L'\t')
180                         multicol = true;
181                 mbstring += charlen;
182         }
183
184         if (cols != NULL)
185                 *cols = ncol;
186         if (has_multi_col != NULL)
187                 *has_multi_col = multicol;
188
189         return (0);
190 }
191
192 unsigned int strcols(const char *mbstring)
193 {
194         int w;
195         unsigned int ncol;
196         size_t charlen, mb_cur_max;
197         wchar_t wch;
198         mbstate_t mbs;
199
200         mb_cur_max = MB_CUR_MAX;
201         ncol = 0;
202         memset(&mbs, 0, sizeof(mbs));
203         while ((charlen = mbrlen(mbstring, mb_cur_max, &mbs)) != 0 &&
204             charlen != (size_t)-1 && charlen != (size_t)-2) {
205                 if (mbtowc(&wch, mbstring, mb_cur_max) < 0)
206                         return (0);
207                 w = (wch == L'\t') ? TABSIZE : wcwidth(wch);
208                 ncol += (w < 0) ? 0 : w;
209                 mbstring += charlen;
210         }
211
212         return (ncol);
213 }
214
215 /*
216  * -3- Buttons
217  */
218 static int buttons_min_width(struct buttons *bs)
219 {
220         unsigned int width;
221
222         width = bs->nbuttons * bs->sizebutton;
223         if (bs->nbuttons > 0)
224                 width += (bs->nbuttons - 1) * t.button.minmargin;
225
226         return (width);
227 }
228
229 static void
230 draw_button(WINDOW *window, int y, int x, int size, const char *text,
231     wchar_t first, bool selected, bool shortcut)
232 {
233         int i, color_arrows, color_shortkey, color_button;
234
235         if (selected) {
236                 color_arrows = t.button.f_delimcolor;
237                 color_shortkey = t.button.f_shortcutcolor;
238                 color_button = t.button.f_color;
239         } else {
240                 color_arrows = t.button.delimcolor;
241                 color_shortkey = t.button.shortcutcolor;
242                 color_button = t.button.color;
243         }
244
245         wattron(window, color_arrows);
246         mvwaddch(window, y, x, t.button.leftdelim);
247         wattroff(window, color_arrows);
248         wattron(window, color_button);
249         for (i = 1; i < size - 1; i++)
250                 waddch(window, ' ');
251         wattroff(window, color_button);
252         wattron(window, color_arrows);
253         mvwaddch(window, y, x + i, t.button.rightdelim);
254         wattroff(window, color_arrows);
255
256         x = x + 1 + ((size - 2 - strcols(text))/2);
257         wattron(window, color_button);
258         mvwaddstr(window, y, x, text);
259         wattroff(window, color_button);
260
261         if (shortcut) {
262                 wattron(window, color_shortkey);
263                 mvwaddwch(window, y, x, first);
264                 wattroff(window, color_shortkey);
265         }
266 }
267
268 void draw_buttons(struct dialog *d)
269 {
270         int i, x, startx, y;
271         unsigned int newmargin, margin, wbuttons;
272
273         y = d->h - 2;
274
275         newmargin = d->w - BORDERS - (d->bs.nbuttons * d->bs.sizebutton);
276         newmargin /= (d->bs.nbuttons + 1);
277         newmargin = MIN(newmargin, t.button.maxmargin);
278         if (newmargin == 0) {
279                 margin = t.button.minmargin;
280                 wbuttons = buttons_min_width(&d->bs);
281         } else {
282                 margin = newmargin;
283                 wbuttons = d->bs.nbuttons * d->bs.sizebutton;
284                 wbuttons += (d->bs.nbuttons + 1) * margin;
285         }
286
287         startx = d->w/2 - wbuttons/2 + newmargin;
288         for (i = 0; i < (int)d->bs.nbuttons; i++) {
289                 x = i * (d->bs.sizebutton + margin);
290                 draw_button(d->widget, y, startx + x, d->bs.sizebutton,
291                     d->bs.label[i], d->bs.first[i],  i == d->bs.curr,
292                     d->bs.shortcut);
293         }
294 }
295
296 void
297 set_buttons(struct dialog *d, bool shortcut, const char *oklabel,
298     const char *cancellabel)
299 {
300         int i;
301 #define SIZEBUTTON              8
302 #define DEFAULT_BUTTON_LABEL    OK_LABEL
303 #define DEFAULT_BUTTON_VALUE    BSDDIALOG_OK
304         wchar_t first;
305
306         d->bs.nbuttons = 0;
307         d->bs.curr = 0;
308         d->bs.sizebutton = 0;
309         d->bs.shortcut = shortcut;
310
311         if (d->conf->button.left1_label != NULL) {
312                 d->bs.label[d->bs.nbuttons] = d->conf->button.left1_label;
313                 d->bs.value[d->bs.nbuttons] = BSDDIALOG_LEFT1;
314                 d->bs.nbuttons += 1;
315         }
316
317         if (d->conf->button.left2_label != NULL) {
318                 d->bs.label[d->bs.nbuttons] = d->conf->button.left2_label;
319                 d->bs.value[d->bs.nbuttons] = BSDDIALOG_LEFT2;
320                 d->bs.nbuttons += 1;
321         }
322
323         if (d->conf->button.left3_label != NULL) {
324                 d->bs.label[d->bs.nbuttons] = d->conf->button.left3_label;
325                 d->bs.value[d->bs.nbuttons] = BSDDIALOG_LEFT3;
326                 d->bs.nbuttons += 1;
327         }
328
329         if (oklabel != NULL && d->conf->button.without_ok == false) {
330                 d->bs.label[d->bs.nbuttons] = d->conf->button.ok_label != NULL ?
331                     d->conf->button.ok_label : oklabel;
332                 d->bs.value[d->bs.nbuttons] = BSDDIALOG_OK;
333                 d->bs.nbuttons += 1;
334         }
335
336         if (d->conf->button.with_extra) {
337                 d->bs.label[d->bs.nbuttons] = d->conf->button.extra_label != NULL ?
338                     d->conf->button.extra_label : "Extra";
339                 d->bs.value[d->bs.nbuttons] = BSDDIALOG_EXTRA;
340                 d->bs.nbuttons += 1;
341         }
342
343         if (cancellabel != NULL && d->conf->button.without_cancel == false) {
344                 d->bs.label[d->bs.nbuttons] = d->conf->button.cancel_label ?
345                     d->conf->button.cancel_label : cancellabel;
346                 d->bs.value[d->bs.nbuttons] = BSDDIALOG_CANCEL;
347                 if (d->conf->button.default_cancel)
348                         d->bs.curr = d->bs.nbuttons;
349                 d->bs.nbuttons += 1;
350         }
351
352         if (d->conf->button.with_help) {
353                 d->bs.label[d->bs.nbuttons] = d->conf->button.help_label != NULL ?
354                     d->conf->button.help_label : "Help";
355                 d->bs.value[d->bs.nbuttons] = BSDDIALOG_HELP;
356                 d->bs.nbuttons += 1;
357         }
358
359         if (d->conf->button.right1_label != NULL) {
360                 d->bs.label[d->bs.nbuttons] = d->conf->button.right1_label;
361                 d->bs.value[d->bs.nbuttons] = BSDDIALOG_RIGHT1;
362                 d->bs.nbuttons += 1;
363         }
364
365         if (d->conf->button.right2_label != NULL) {
366                 d->bs.label[d->bs.nbuttons] = d->conf->button.right2_label;
367                 d->bs.value[d->bs.nbuttons] = BSDDIALOG_RIGHT2;
368                 d->bs.nbuttons += 1;
369         }
370
371         if (d->conf->button.right3_label != NULL) {
372                 d->bs.label[d->bs.nbuttons] = d->conf->button.right3_label;
373                 d->bs.value[d->bs.nbuttons] = BSDDIALOG_RIGHT3;
374                 d->bs.nbuttons += 1;
375         }
376
377         if (d->bs.nbuttons == 0) {
378                 d->bs.label[0] = DEFAULT_BUTTON_LABEL;
379                 d->bs.value[0] = DEFAULT_BUTTON_VALUE;
380                 d->bs.nbuttons = 1;
381         }
382
383         for (i = 0; i < (int)d->bs.nbuttons; i++) {
384                 mbtowc(&first, d->bs.label[i], MB_CUR_MAX);
385                 d->bs.first[i] = first;
386         }
387
388         if (d->conf->button.default_label != NULL) {
389                 for (i = 0; i < (int)d->bs.nbuttons; i++) {
390                         if (strcmp(d->conf->button.default_label,
391                             d->bs.label[i]) == 0)
392                                 d->bs.curr = i;
393                 }
394         }
395
396         d->bs.sizebutton = MAX(SIZEBUTTON - 2, strcols(d->bs.label[0]));
397         for (i = 1; i < (int)d->bs.nbuttons; i++)
398                 d->bs.sizebutton = MAX(d->bs.sizebutton, strcols(d->bs.label[i]));
399         d->bs.sizebutton += 2;
400 }
401
402 bool shortcut_buttons(wint_t key, struct buttons *bs)
403 {
404         bool match;
405         unsigned int i;
406
407         match = false;
408         for (i = 0; i < bs->nbuttons; i++) {
409                 if (towlower(key) == towlower(bs->first[i])) {
410                         bs->curr = i;
411                         match = true;
412                         break;
413                 }
414         }
415
416         return (match);
417 }
418
419 /*
420  * -4- (Auto) Sizing and (Auto) Position
421  */
422 static int widget_max_height(struct bsddialog_conf *conf)
423 {
424         int maxheight;
425
426         maxheight = conf->shadow ? SCREENLINES - (int)t.shadow.y : SCREENLINES;
427         if (maxheight <= 0)
428                 RETURN_ERROR("Terminal too small, screen lines - shadow <= 0");
429
430         if (conf->y != BSDDIALOG_CENTER && conf->auto_topmargin > 0)
431                 RETURN_ERROR("conf.y > 0 and conf->auto_topmargin > 0");
432         else if (conf->y == BSDDIALOG_CENTER) {
433                 maxheight -= conf->auto_topmargin;
434                 if (maxheight <= 0)
435                         RETURN_ERROR("Terminal too small, screen lines - top "
436                             "margins <= 0");
437         } else if (conf->y > 0) {
438                 maxheight -= conf->y;
439                 if (maxheight <= 0)
440                         RETURN_ERROR("Terminal too small, screen lines - "
441                             "shadow - y <= 0");
442         }
443
444         maxheight -= conf->auto_downmargin;
445         if (maxheight <= 0)
446                 RETURN_ERROR("Terminal too small, screen lines - Down margins "
447                     "<= 0");
448
449         return (maxheight);
450 }
451
452 static int widget_max_width(struct bsddialog_conf *conf)
453 {
454         int maxwidth;
455
456         maxwidth = conf->shadow ? SCREENCOLS - (int)t.shadow.x : SCREENCOLS;
457         if (maxwidth <= 0)
458                 RETURN_ERROR("Terminal too small, screen cols - shadow <= 0");
459
460         if (conf->x > 0) {
461                 maxwidth -= conf->x;
462                 if (maxwidth <= 0)
463                         RETURN_ERROR("Terminal too small, screen cols - shadow "
464                             "- x <= 0");
465         }
466
467         return (maxwidth);
468 }
469
470 static bool is_wtext_attr(const wchar_t *wtext)
471 {
472         bool att;
473
474         if (wcsnlen(wtext, 3) < 3)
475                 return (false);
476         if (wtext[0] != L'\\' || wtext[1] != L'Z')
477                 return (false);
478
479         att = wcschr(L"nbBdDkKrRsSuU01234567", wtext[2]) == NULL ? false : true;
480
481         return (att);
482 }
483
484 #define NL  -1
485 #define WS  -2
486 #define TB  -3
487
488 struct textproperties {
489         int nword;
490         int *words;
491         uint8_t *wletters;
492         int maxwordcols;
493         int maxline;
494         bool hasnewline;
495 };
496
497 static int
498 text_properties(struct bsddialog_conf *conf, const char *text,
499     struct textproperties *tp)
500 {
501         int i, l, currlinecols, maxwords, wtextlen, tablen, wordcols;
502         wchar_t *wtext;
503
504         tablen = (conf->text.tablen == 0) ? TABSIZE : (int)conf->text.tablen;
505
506         maxwords = 1024;
507         if ((tp->words = calloc(maxwords, sizeof(int))) == NULL)
508                 RETURN_ERROR("Cannot alloc memory for text autosize");
509
510         if ((wtext = alloc_mbstows(text)) == NULL)
511                 RETURN_ERROR("Cannot allocate/autosize text in wchar_t*");
512         wtextlen = wcslen(wtext);
513         if ((tp->wletters = calloc(wtextlen, sizeof(uint8_t))) == NULL)
514                 RETURN_ERROR("Cannot allocate wletters for text autosizing");
515
516         tp->nword = 0;
517         tp->maxline = 0;
518         tp->maxwordcols = 0;
519         tp->hasnewline = false;
520         currlinecols = 0;
521         wordcols = 0;
522         l = 0;
523         for (i = 0; i < wtextlen; i++) {
524                 if (conf->text.escape && is_wtext_attr(wtext + i)) {
525                         i += 2; /* +1 for update statement */
526                         continue;
527                 }
528
529                 if (tp->nword + 1 >= maxwords) {
530                         maxwords += 1024;
531                         tp->words = realloc(tp->words, maxwords * sizeof(int));
532                         if (tp->words == NULL)
533                                 RETURN_ERROR("Cannot realloc memory for text "
534                                     "autosize");
535                 }
536
537                 if (wcschr(L"\t\n  ", wtext[i]) != NULL) {
538                         tp->maxwordcols = MAX(wordcols, tp->maxwordcols);
539
540                         if (wordcols != 0) {
541                                 /* line */
542                                 currlinecols += wordcols;
543                                 /* word */
544                                 tp->words[tp->nword] = wordcols;
545                                 tp->nword += 1;
546                                 wordcols = 0;
547                         }
548
549                         switch (wtext[i]) {
550                         case L'\t':
551                                 /* line */
552                                 currlinecols += tablen;
553                                 /* word */
554                                 tp->words[tp->nword] = TB;
555                                 break;
556                         case L'\n':
557                                 /* line */
558                                 tp->hasnewline = true;
559                                 tp->maxline = MAX(tp->maxline, currlinecols);
560                                 currlinecols = 0;
561                                 /* word */
562                                 tp->words[tp->nword] = NL;
563                                 break;
564                         case L' ':
565                                 /* line */
566                                 currlinecols += 1;
567                                 /* word */
568                                 tp->words[tp->nword] = WS;
569                                 break;
570                         }
571                         tp->nword += 1;
572                 } else {
573                         tp->wletters[l] = wcwidth(wtext[i]);
574                         wordcols += tp->wletters[l];
575                         l++;
576                 }
577         }
578         /* word */
579         if (wordcols != 0) {
580                 tp->words[tp->nword] = wordcols;
581                 tp->nword += 1;
582                 tp->maxwordcols = MAX(wordcols, tp->maxwordcols);
583         }
584         /* line */
585         tp->maxline = MAX(tp->maxline, currlinecols);
586
587         free(wtext);
588
589         return (0);
590 }
591
592 static int
593 text_autosize(struct bsddialog_conf *conf, struct textproperties *tp,
594     int maxrows, int mincols, bool increasecols, int *h, int *w)
595 {
596         int i, j, x, y, z, l, line, maxwidth, tablen;
597
598         maxwidth = widget_max_width(conf) - BORDERS - TEXTHMARGINS;
599         tablen = (conf->text.tablen == 0) ? TABSIZE : (int)conf->text.tablen;
600
601         if (increasecols) {
602                 mincols = MAX(mincols, tp->maxwordcols);
603                 mincols = MAX(mincols,
604                     (int)conf->auto_minwidth - BORDERS - TEXTHMARGINS);
605                 mincols = MIN(mincols, maxwidth);
606         }
607
608         while (true) {
609                 x = 0;
610                 y = 1;
611                 line=0;
612                 l = 0;
613                 for (i = 0; i < tp->nword; i++) {
614                         switch (tp->words[i]) {
615                         case TB:
616                                 for (j = 0; j < tablen; j++) {
617                                         if (x >= mincols) {
618                                                 x = 0;
619                                                 y++;
620                                         }
621                                 x++;
622                                 }
623                                 break;
624                         case NL:
625                                 y++;
626                                 x = 0;
627                                 break;
628                         case WS:
629                                 x++;
630                                 if (x >= mincols) {
631                                         x = 0;
632                                         y++;
633                                 }
634                                 break;
635                         default:
636                                 if (tp->words[i] + x <= mincols) {
637                                         x += tp->words[i];
638                                         for (z = 0 ; z != tp->words[i]; l++ )
639                                                 z += tp->wletters[l];
640                                 } else if (tp->words[i] <= mincols) {
641                                         y++;
642                                         x = tp->words[i];
643                                         for (z = 0 ; z != tp->words[i]; l++ )
644                                                 z += tp->wletters[l];
645                                 } else {
646                                         for (j = tp->words[i]; j > 0; ) {
647                                                 y = (x == 0) ? y : y + 1;
648                                                 z = 0;
649                                                 while (z != j && z < mincols) {
650                                                         z += tp->wletters[l];
651                                                         l++;
652                                                 }
653                                                 x = z;
654                                                 line = MAX(line, x);
655                                                 j -= z;
656                                         }
657                                 }
658                         }
659                         line = MAX(line, x);
660                 }
661
662                 if (increasecols == false)
663                         break;
664                 if (mincols >= maxwidth)
665                         break;
666                 if (line >= y * (int)conf->text.cols_per_row && y <= maxrows)
667                         break;
668                 mincols++;
669         }
670
671         *h = (tp->nword == 0) ? 0 : y;
672         *w = MIN(mincols, line); /* wtext can be less than mincols */
673
674         return (0);
675 }
676
677 static int
678 text_size(struct bsddialog_conf *conf, int rows, int cols, const char *text,
679     struct buttons *bs, int rowsnotext, int startwtext, int *htext, int *wtext)
680 {
681         bool changewtext;
682         int wbuttons, maxhtext;
683         struct textproperties tp;
684
685         wbuttons = 0;
686         if (bs->nbuttons > 0)
687                 wbuttons = buttons_min_width(bs);
688
689         /* Rows */
690         if (rows == BSDDIALOG_AUTOSIZE || rows == BSDDIALOG_FULLSCREEN) {
691                 maxhtext = widget_max_height(conf) - BORDERS - rowsnotext;
692         } else { /* fixed */
693                 maxhtext = rows - BORDERS - rowsnotext;
694         }
695         if (bs->nbuttons > 0)
696                 maxhtext -= 2;
697         if (maxhtext <= 0)
698                 maxhtext = 1; /* text_autosize() computes always htext */
699
700         /* Cols */
701         if (cols == BSDDIALOG_AUTOSIZE) {
702                 startwtext = MAX(startwtext, wbuttons - TEXTHMARGINS);
703                 changewtext = true;
704         } else if (cols == BSDDIALOG_FULLSCREEN) {
705                 startwtext = widget_max_width(conf) - BORDERS - TEXTHMARGINS;
706                 changewtext = false;
707         } else { /* fixed */
708                 startwtext = cols - BORDERS - TEXTHMARGINS;
709                 changewtext = false;
710         }
711
712         if (startwtext <= 0 && changewtext)
713                 startwtext = 1;
714
715         /* Sizing calculation */
716         if (text_properties(conf, text, &tp) != 0)
717                 return (BSDDIALOG_ERROR);
718         if (tp.nword > 0 && startwtext <= 0)
719                 RETURN_FMTERROR("(fixed cols or fullscreen) "
720                     "needed at least %d cols to draw text",
721                     BORDERS + TEXTHMARGINS + 1);
722         if (text_autosize(conf, &tp, maxhtext, startwtext, changewtext, htext,
723             wtext) != 0)
724                 return (BSDDIALOG_ERROR);
725
726         free(tp.words);
727         free(tp.wletters);
728
729         return (0);
730 }
731
732 static int
733 widget_min_height(struct bsddialog_conf *conf, int htext, int hnotext,
734     bool withbuttons)
735 {
736         int min;
737
738         /* dialog borders */
739         min = BORDERS;
740
741         /* text */
742         min += htext;
743
744         /* specific widget lines without text */
745         min += hnotext;
746
747         /* buttons */
748         if (withbuttons)
749                 min += HBUTTONS; /* buttons and their up-border */
750
751         /* conf.auto_minheight */
752         min = MAX(min, (int)conf->auto_minheight);
753
754         return (min);
755 }
756
757 static int
758 widget_min_width(struct bsddialog_conf *conf, int wtext, int minwidget,
759     struct buttons *bs)
760
761 {
762         int min, delimtitle, wbottomtitle, wtitle;
763
764         min = 0;
765
766         /* buttons */
767         if (bs->nbuttons > 0)
768                 min += buttons_min_width(bs);
769
770         /* text */
771         if (wtext > 0)
772                 min = MAX(min, wtext + TEXTHMARGINS);
773
774         /* specific widget min width */
775         min = MAX(min, minwidget);
776
777         /* title */
778         if (conf->title != NULL) {
779                 delimtitle = t.dialog.delimtitle ? 2 : 0;
780                 wtitle = strcols(conf->title);
781                 min = MAX(min, wtitle + 2 + delimtitle);
782         }
783
784         /* bottom title */
785         if (conf->bottomtitle != NULL) {
786                 wbottomtitle = strcols(conf->bottomtitle);
787                 min = MAX(min, wbottomtitle + 4);
788         }
789
790         /* dialog borders */
791         min += BORDERS;
792         /* conf.auto_minwidth */
793         min = MAX(min, (int)conf->auto_minwidth);
794
795         return (min);
796 }
797
798 int
799 set_widget_size(struct bsddialog_conf *conf, int rows, int cols, int *h, int *w)
800 {
801         int maxheight, maxwidth;
802
803         if ((maxheight = widget_max_height(conf)) == BSDDIALOG_ERROR)
804                 return (BSDDIALOG_ERROR);
805
806         if (rows == BSDDIALOG_FULLSCREEN)
807                 *h = maxheight;
808         else if (rows < BSDDIALOG_FULLSCREEN)
809                 RETURN_ERROR("Negative (less than -1) height");
810         else if (rows > BSDDIALOG_AUTOSIZE) /* fixed rows */
811                 *h = MIN(rows, maxheight); /* rows is at most maxheight */
812         /* rows == AUTOSIZE: each widget has to set its size */
813
814         if ((maxwidth = widget_max_width(conf)) == BSDDIALOG_ERROR)
815                 return (BSDDIALOG_ERROR);
816
817         if (cols == BSDDIALOG_FULLSCREEN)
818                 *w = maxwidth;
819         else if (cols < BSDDIALOG_FULLSCREEN)
820                 RETURN_ERROR("Negative (less than -1) width");
821         else if (cols > BSDDIALOG_AUTOSIZE) /* fixed cols */
822                 *w = MIN(cols, maxwidth); /* cols is at most maxwidth */
823         /* cols == AUTOSIZE: each widget has to set its size */
824
825         return (0);
826 }
827
828 int
829 set_widget_autosize(struct bsddialog_conf *conf, int rows, int cols, int *h,
830     int *w, const char *text, int *rowstext, struct buttons *bs, int hnotext,
831     int minw)
832 {
833         int htext, wtext;
834
835         if (rows == BSDDIALOG_AUTOSIZE || cols == BSDDIALOG_AUTOSIZE ||
836             rowstext != NULL) {
837                 if (text_size(conf, rows, cols, text, bs, hnotext, minw,
838                     &htext, &wtext) != 0)
839                         return (BSDDIALOG_ERROR);
840                 if (rowstext != NULL)
841                         *rowstext = htext;
842         }
843
844         if (rows == BSDDIALOG_AUTOSIZE) {
845                 *h = widget_min_height(conf, htext, hnotext, bs->nbuttons > 0);
846                 *h = MIN(*h, widget_max_height(conf));
847         }
848
849         if (cols == BSDDIALOG_AUTOSIZE) {
850                 *w = widget_min_width(conf, wtext, minw, bs);
851                 *w = MIN(*w, widget_max_width(conf));
852         }
853
854         return (0);
855 }
856
857 int widget_checksize(int h, int w, struct buttons *bs, int hnotext, int minw)
858 {
859         int minheight, minwidth;
860
861         minheight = BORDERS + hnotext;
862         if (bs->nbuttons > 0)
863                 minheight += HBUTTONS;
864         if (h < minheight)
865                 RETURN_FMTERROR("Current rows: %d, needed at least: %d",
866                     h, minheight);
867
868         minwidth = 0;
869         if (bs->nbuttons > 0)
870                 minwidth = buttons_min_width(bs);
871         minwidth = MAX(minwidth, minw);
872         minwidth += BORDERS;
873         if (w < minwidth)
874                 RETURN_FMTERROR("Current cols: %d, nedeed at least %d",
875                     w, minwidth);
876
877         return (0);
878 }
879
880 int
881 set_widget_position(struct bsddialog_conf *conf, int *y, int *x, int h, int w)
882 {
883         int hshadow = conf->shadow ? (int)t.shadow.y : 0;
884         int wshadow = conf->shadow ? (int)t.shadow.x : 0;
885
886         if (conf->y == BSDDIALOG_CENTER) {
887                 *y = SCREENLINES/2 - (h + hshadow)/2;
888                 if (*y < (int)conf->auto_topmargin)
889                         *y = conf->auto_topmargin;
890                 if (*y + h + hshadow > SCREENLINES - (int)conf->auto_downmargin)
891                         *y = SCREENLINES - h - hshadow - conf->auto_downmargin;
892         }
893         else if (conf->y < BSDDIALOG_CENTER)
894                 RETURN_ERROR("Negative begin y (less than -1)");
895         else if (conf->y >= SCREENLINES)
896                 RETURN_ERROR("Begin Y under the terminal");
897         else
898                 *y = conf->y;
899
900         if (*y + h + hshadow > SCREENLINES)
901                 RETURN_ERROR("The lower of the box under the terminal "
902                     "(begin Y + height (+ shadow) > terminal lines)");
903
904
905         if (conf->x == BSDDIALOG_CENTER)
906                 *x = SCREENCOLS/2 - (w + wshadow)/2;
907         else if (conf->x < BSDDIALOG_CENTER)
908                 RETURN_ERROR("Negative begin x (less than -1)");
909         else if (conf->x >= SCREENCOLS)
910                 RETURN_ERROR("Begin X over the right of the terminal");
911         else
912                 *x = conf->x;
913
914         if ((*x + w + wshadow) > SCREENCOLS)
915                 RETURN_ERROR("The right of the box over the terminal "
916                     "(begin X + width (+ shadow) > terminal cols)");
917
918         return (0);
919 }
920
921 int dialog_size_position(struct dialog *d, int hnotext, int minw, int *htext)
922 {
923         if (set_widget_size(d->conf, d->rows, d->cols, &d->h, &d->w) != 0)
924                 return (BSDDIALOG_ERROR);
925         if (set_widget_autosize(d->conf, d->rows, d->cols, &d->h, &d->w,
926             d->text, htext, &d->bs, hnotext, minw) != 0)
927                 return (BSDDIALOG_ERROR);
928         if (widget_checksize(d->h, d->w, &d->bs, hnotext, minw) != 0)
929                 return (BSDDIALOG_ERROR);
930         if (set_widget_position(d->conf, &d->y, &d->x, d->h, d->w) != 0)
931                 return (BSDDIALOG_ERROR);
932
933         return (0);
934 }
935
936 /*
937  * -5- Widget components and utilities
938  */
939 int hide_dialog(struct dialog *d)
940 {
941         WINDOW *clear;
942
943         if ((clear = newwin(d->h, d->w, d->y, d->x)) == NULL)
944                 RETURN_ERROR("Cannot hide the widget");
945         wbkgd(clear, t.screen.color);
946         wrefresh(clear);
947
948         if (d->conf->shadow) {
949                 mvwin(clear, d->y + t.shadow.y, d->x + t.shadow.x);
950                 wrefresh(clear);
951         }
952
953         delwin(clear);
954
955         return (0);
956 }
957
958 int f1help_dialog(struct bsddialog_conf *conf)
959 {
960         int output;
961         struct bsddialog_conf hconf;
962
963         bsddialog_initconf(&hconf);
964         hconf.title           = "HELP";
965         hconf.button.ok_label = "EXIT";
966         hconf.clear           = true;
967         hconf.ascii_lines     = conf->ascii_lines;
968         hconf.no_lines        = conf->no_lines;
969         hconf.shadow          = conf->shadow;
970         hconf.text.escape     = conf->text.escape;
971
972         output = BSDDIALOG_OK;
973         if (conf->key.f1_message != NULL)
974                 output = bsddialog_msgbox(&hconf, conf->key.f1_message, 0, 0);
975
976         if (output != BSDDIALOG_ERROR && conf->key.f1_file != NULL)
977                 output = bsddialog_textbox(&hconf, conf->key.f1_file, 0, 0);
978
979         return (output == BSDDIALOG_ERROR ? BSDDIALOG_ERROR : 0);
980 }
981
982 void draw_borders(struct bsddialog_conf *conf, WINDOW *win, enum elevation elev)
983 {
984         int h, w;
985         int leftcolor, rightcolor;
986         int ls, rs, ts, bs, tl, tr, bl, br, ltee, rtee;
987
988         if (conf->no_lines)
989                 return;
990
991         if (conf->ascii_lines) {
992                 ls = rs = '|';
993                 ts = bs = '-';
994                 tl = tr = bl = br = ltee = rtee = '+';
995         } else {
996                 ls = rs = ACS_VLINE;
997                 ts = bs = ACS_HLINE;
998                 tl = ACS_ULCORNER;
999                 tr = ACS_URCORNER;
1000                 bl = ACS_LLCORNER;
1001                 br = ACS_LRCORNER;
1002                 ltee = ACS_LTEE;
1003                 rtee = ACS_RTEE;
1004         }
1005
1006         getmaxyx(win, h, w);
1007         leftcolor = elev == RAISED ?
1008             t.dialog.lineraisecolor : t.dialog.linelowercolor;
1009         rightcolor = elev == RAISED ?
1010             t.dialog.linelowercolor : t.dialog.lineraisecolor;
1011
1012         wattron(win, leftcolor);
1013         wborder(win, ls, rs, ts, bs, tl, tr, bl, br);
1014         wattroff(win, leftcolor);
1015
1016         wattron(win, rightcolor);
1017         mvwaddch(win, 0, w-1, tr);
1018         mvwvline(win, 1, w-1, rs, h-2);
1019         mvwaddch(win, h-1, w-1, br);
1020         mvwhline(win, h-1, 1, bs, w-2);
1021         wattroff(win, rightcolor);
1022 }
1023
1024 void
1025 update_box(struct bsddialog_conf *conf, WINDOW *win, int y, int x, int h, int w,
1026     enum elevation elev)
1027 {
1028         wclear(win);
1029         wresize(win, h, w);
1030         mvwin(win, y, x);
1031         draw_borders(conf, win, elev);
1032 }
1033
1034 void
1035 rtextpad(struct dialog *d, int ytext, int xtext, int upnotext, int downnotext)
1036 {
1037         pnoutrefresh(d->textpad, ytext, xtext,
1038             d->y + BORDER + upnotext,
1039             d->x + BORDER + TEXTHMARGIN,
1040             d->y + d->h - 1 - downnotext - BORDER,
1041             d->x + d->w - TEXTHMARGIN - BORDER);
1042 }
1043
1044 /*
1045  * -6- Dialog init/build, update/draw, destroy
1046  */
1047 void end_dialog(struct dialog *d)
1048 {
1049         if (d->conf->sleep > 0)
1050                 sleep(d->conf->sleep);
1051
1052         delwin(d->textpad);
1053         delwin(d->widget);
1054         if (d->conf->shadow)
1055                 delwin(d->shadow);
1056
1057         if (d->conf->clear)
1058                 hide_dialog(d);
1059
1060         if (d->conf->get_height != NULL)
1061                 *d->conf->get_height = d->h;
1062         if (d->conf->get_width != NULL)
1063                 *d->conf->get_width = d->w;
1064 }
1065
1066 static bool check_set_wtext_attr(WINDOW *win, wchar_t *wtext)
1067 {
1068         enum bsddialog_color bg;
1069
1070         if (is_wtext_attr(wtext) == false)
1071                 return (false);
1072
1073         if ((wtext[2] >= L'0') && (wtext[2] <= L'7')) {
1074                 bsddialog_color_attrs(t.dialog.color, NULL, &bg, NULL);
1075                 wattron(win, bsddialog_color(wtext[2] - L'0', bg, 0));
1076                 return (true);
1077         }
1078
1079         switch (wtext[2]) {
1080         case L'n':
1081                 wattron(win, t.dialog.color);
1082                 wattrset(win, A_NORMAL);
1083                 break;
1084         case L'b':
1085                 wattron(win, A_BOLD);
1086                 break;
1087         case L'B':
1088                 wattroff(win, A_BOLD);
1089                 break;
1090         case L'd':
1091                 wattron(win, A_DIM);
1092                 break;
1093         case L'D':
1094                 wattroff(win, A_DIM);
1095                 break;
1096         case L'k':
1097                 wattron(win, A_BLINK);
1098                 break;
1099         case L'K':
1100                 wattroff(win, A_BLINK);
1101                 break;
1102         case L'r':
1103                 wattron(win, A_REVERSE);
1104                 break;
1105         case L'R':
1106                 wattroff(win, A_REVERSE);
1107                 break;
1108         case L's':
1109                 wattron(win, A_STANDOUT);
1110                 break;
1111         case L'S':
1112                 wattroff(win, A_STANDOUT);
1113                 break;
1114         case L'u':
1115                 wattron(win, A_UNDERLINE);
1116                 break;
1117         case L'U':
1118                 wattroff(win, A_UNDERLINE);
1119                 break;
1120         }
1121
1122         return (true);
1123 }
1124
1125 static void
1126 print_string(WINDOW *win, int *rows, int cols, int *y, int *x, wchar_t *str,
1127     bool color)
1128 {
1129         int i, j, len, reallen, wc;
1130         wchar_t ws[2];
1131
1132         ws[1] = L'\0';
1133
1134         len = wcslen(str);
1135         if (color) {
1136                 reallen = 0;
1137                 i=0;
1138                 while (i < len) {
1139                         if (is_wtext_attr(str+i) == false) {
1140                                 reallen += wcwidth(str[i]);
1141                                 i++;
1142                         } else {
1143                                 i +=3 ;
1144                         }
1145                 }
1146         } else
1147                 reallen = wcswidth(str, len);
1148
1149         i = 0;
1150         while (i < len) {
1151                 if (*x + reallen > cols) {
1152                         *y = (*x != 0 ? *y+1 : *y);
1153                         if (*y >= *rows) {
1154                                 *rows = *y + 1;
1155                                 wresize(win, *rows, cols);
1156                         }
1157                         *x = 0;
1158                 }
1159                 j = *x;
1160                 while (j < cols && i < len) {
1161                         if (color && check_set_wtext_attr(win, str+i)) {
1162                                 i += 3;
1163                         } else if (j + wcwidth(str[i]) > cols) {
1164                                 break;
1165                         } else {
1166                                 /* inline mvwaddwch() for efficiency */
1167                                 ws[0] = str[i];
1168                                 mvwaddwstr(win, *y, j, ws);
1169                                 wc = wcwidth(str[i]);;
1170                                 reallen -= wc;
1171                                 j += wc;
1172                                 i++;
1173                                 *x = j;
1174                         }
1175                 }
1176         }
1177 }
1178
1179 static int
1180 print_textpad(struct bsddialog_conf *conf, WINDOW *pad, const char *text)
1181 {
1182         bool loop;
1183         int i, j, z, rows, cols, x, y, tablen;
1184         wchar_t *wtext, *string;
1185
1186         if ((wtext = alloc_mbstows(text)) == NULL)
1187                 RETURN_ERROR("Cannot allocate/print text in wchar_t*");
1188
1189         if ((string = calloc(wcslen(wtext) + 1, sizeof(wchar_t))) == NULL)
1190                 RETURN_ERROR("Cannot build (analyze) text");
1191
1192         getmaxyx(pad, rows, cols);
1193         tablen = (conf->text.tablen == 0) ? TABSIZE : (int)conf->text.tablen;
1194
1195         i = j = x = y = 0;
1196         loop = true;
1197         while (loop) {
1198                 string[j] = wtext[i];
1199
1200                 if (wcschr(L"\n\t  ", string[j]) != NULL || string[j] == L'\0') {
1201                         string[j] = L'\0';
1202                         print_string(pad, &rows, cols, &y, &x, string,
1203                             conf->text.escape);
1204                 }
1205
1206                 switch (wtext[i]) {
1207                 case L'\0':
1208                         loop = false;
1209                         break;
1210                 case L'\n':
1211                         x = 0;
1212                         y++;
1213                         j = -1;
1214                         break;
1215                 case L'\t':
1216                         for (z = 0; z < tablen; z++) {
1217                                 if (x >= cols) {
1218                                         x = 0;
1219                                         y++;
1220                                 }
1221                                 x++;
1222                         }
1223                         j = -1;
1224                         break;
1225                 case L' ':
1226                         x++;
1227                         if (x >= cols) {
1228                                 x = 0;
1229                                 y++;
1230                         }
1231                         j = -1;
1232                 }
1233
1234                 if (y >= rows) {
1235                         rows = y + 1;
1236                         wresize(pad, rows, cols);
1237                 }
1238
1239                 j++;
1240                 i++;
1241         }
1242
1243         free(wtext);
1244         free(string);
1245
1246         return (0);
1247 }
1248
1249 int draw_dialog(struct dialog *d)
1250 {
1251         int wtitle, wbottomtitle, ts, ltee, rtee;
1252
1253         ts   = d->conf->ascii_lines ? '-' : ACS_HLINE;
1254         ltee = d->conf->ascii_lines ? '+' : ACS_LTEE;
1255         rtee = d->conf->ascii_lines ? '+' : ACS_RTEE;
1256
1257         if (d->conf->shadow) {
1258                 wclear(d->shadow);
1259                 wresize(d->shadow, d->h, d->w);
1260                 mvwin(d->shadow, d->y + t.shadow.y, d->x + t.shadow.x);
1261                 wnoutrefresh(d->shadow);
1262         }
1263
1264         wclear(d->widget);
1265         wresize(d->widget, d->h, d->w);
1266         mvwin(d->widget, d->y, d->x);
1267         draw_borders(d->conf, d->widget, RAISED);
1268
1269         if (d->conf->title != NULL) {
1270                 if ((wtitle = strcols(d->conf->title)) < 0)
1271                         return (BSDDIALOG_ERROR);
1272                 if (t.dialog.delimtitle && d->conf->no_lines == false) {
1273                         wattron(d->widget, t.dialog.lineraisecolor);
1274                         mvwaddch(d->widget, 0, d->w/2 - wtitle/2 -1, rtee);
1275                         wattroff(d->widget, t.dialog.lineraisecolor);
1276                 }
1277                 wattron(d->widget, t.dialog.titlecolor);
1278                 mvwaddstr(d->widget, 0, d->w/2 - wtitle/2, d->conf->title);
1279                 wattroff(d->widget, t.dialog.titlecolor);
1280                 if (t.dialog.delimtitle && d->conf->no_lines == false) {
1281                         wattron(d->widget, t.dialog.lineraisecolor);
1282                         waddch(d->widget, ltee);
1283                         wattroff(d->widget, t.dialog.lineraisecolor);
1284                 }
1285         }
1286
1287         if (d->bs.nbuttons > 0) {
1288                 if (d->conf->no_lines == false) {
1289                         wattron(d->widget, t.dialog.lineraisecolor);
1290                         mvwaddch(d->widget, d->h-3, 0, ltee);
1291                         mvwhline(d->widget, d->h-3, 1, ts, d->w-2);
1292                         wattroff(d->widget, t.dialog.lineraisecolor);
1293
1294                         wattron(d->widget, t.dialog.linelowercolor);
1295                         mvwaddch(d->widget, d->h-3, d->w-1, rtee);
1296                         wattroff(d->widget, t.dialog.linelowercolor);
1297                 }
1298                 draw_buttons(d);
1299         }
1300
1301         if (d->conf->bottomtitle != NULL) {
1302                 if ((wbottomtitle = strcols(d->conf->bottomtitle)) < 0)
1303                         return (BSDDIALOG_ERROR);
1304                 wattron(d->widget, t.dialog.bottomtitlecolor);
1305                 wmove(d->widget, d->h - 1, d->w/2 - wbottomtitle/2 - 1);
1306                 waddch(d->widget, ' ');
1307                 waddstr(d->widget, d->conf->bottomtitle);
1308                 waddch(d->widget, ' ');
1309                 wattroff(d->widget, t.dialog.bottomtitlecolor);
1310         }
1311
1312         wnoutrefresh(d->widget);
1313
1314         wclear(d->textpad);
1315         /* `infobox "" 0 2` fails but text is empty and textpad remains 1 1 */
1316         wresize(d->textpad, 1, d->w - BORDERS - TEXTHMARGINS);
1317
1318         if (print_textpad(d->conf, d->textpad, d->text) != 0)
1319                 return (BSDDIALOG_ERROR);
1320
1321         d->built = true;
1322
1323         return (0);
1324 }
1325
1326 int
1327 prepare_dialog(struct bsddialog_conf *conf, const char *text, int rows,
1328     int cols, struct dialog *d)
1329 {
1330         CHECK_PTR(conf);
1331
1332         d->built = false;
1333         d->conf = conf;
1334         d->rows = rows;
1335         d->cols = cols;
1336         d->text = CHECK_STR(text);
1337         d->bs.nbuttons = 0;
1338
1339         if (d->conf->shadow) {
1340                 if ((d->shadow = newwin(1, 1, 1, 1)) == NULL)
1341                         RETURN_ERROR("Cannot build WINDOW shadow");
1342                 wbkgd(d->shadow, t.shadow.color);
1343         }
1344
1345         if ((d->widget = newwin(1, 1, 1, 1)) == NULL)
1346                 RETURN_ERROR("Cannot build WINDOW widget");
1347         wbkgd(d->widget, t.dialog.color);
1348
1349         /* fake for textpad */
1350         if ((d->textpad = newpad(1, 1)) == NULL)
1351                 RETURN_ERROR("Cannot build the pad WINDOW for text");
1352         wbkgd(d->textpad, t.dialog.color);
1353
1354         return (0);
1355 }