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