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