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 }