1 /* 2 * $Id: util.c,v 1.289 2020/03/27 23:53:01 tom Exp $ 3 * 4 * util.c -- miscellaneous utilities for dialog 5 * 6 * Copyright 2000-2019,2020 Thomas E. Dickey 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU Lesser General Public License, version 2.1 10 * as published by the Free Software Foundation. 11 * 12 * This program is distributed in the hope that it will be useful, but 13 * WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with this program; if not, write to 19 * Free Software Foundation, Inc. 20 * 51 Franklin St., Fifth Floor 21 * Boston, MA 02110, USA. 22 * 23 * An earlier version of this program lists as authors 24 * Savio Lam (lam836@cs.cuhk.hk) 25 */ 26 27 #include <dialog.h> 28 #include <dlg_keys.h> 29 #include <dlg_internals.h> 30 31 #include <sys/time.h> 32 33 #ifdef HAVE_SETLOCALE 34 #include <locale.h> 35 #endif 36 37 #ifdef NEED_WCHAR_H 38 #include <wchar.h> 39 #endif 40 41 #ifdef NCURSES_VERSION 42 #if defined(HAVE_NCURSESW_TERM_H) 43 #include <ncursesw/term.h> 44 #elif defined(HAVE_NCURSES_TERM_H) 45 #include <ncurses/term.h> 46 #else 47 #include <term.h> 48 #endif 49 #endif 50 51 #if defined(HAVE_WCHGAT) 52 # if defined(NCURSES_VERSION_PATCH) 53 # if NCURSES_VERSION_PATCH >= 20060715 54 # define USE_WCHGAT 1 55 # else 56 # define USE_WCHGAT 0 57 # endif 58 # else 59 # define USE_WCHGAT 1 60 # endif 61 #else 62 # define USE_WCHGAT 0 63 #endif 64 65 /* globals */ 66 DIALOG_STATE dialog_state; 67 DIALOG_VARS dialog_vars; 68 69 #if !(defined(HAVE_WGETPARENT) && defined(HAVE_WINDOW__PARENT)) 70 #define NEED_WGETPARENT 1 71 #else 72 #undef NEED_WGETPARENT 73 #endif 74 75 #define concat(a,b) a##b 76 77 #ifdef HAVE_RC_FILE 78 #define RC_DATA(name,comment) , #name "_color", comment " color" 79 #else 80 #define RC_DATA(name,comment) /*nothing */ 81 #endif 82 83 #ifdef HAVE_COLOR 84 #include <dlg_colors.h> 85 #ifdef HAVE_RC_FILE2 86 #define COLOR_DATA(upr) , \ 87 concat(DLGC_FG_,upr), \ 88 concat(DLGC_BG_,upr), \ 89 concat(DLGC_HL_,upr), \ 90 concat(DLGC_UL_,upr), \ 91 concat(DLGC_RV_,upr) 92 #else /* HAVE_RC_FILE2 */ 93 #define COLOR_DATA(upr) , \ 94 concat(DLGC_FG_,upr), \ 95 concat(DLGC_BG_,upr), \ 96 concat(DLGC_HL_,upr) 97 #endif /* HAVE_RC_FILE2 */ 98 #else /* HAVE_COLOR */ 99 #define COLOR_DATA(upr) /*nothing */ 100 #endif /* HAVE_COLOR */ 101 102 #define UseShadow(dw) ((dw) != 0 && (dw)->normal != 0 && (dw)->shadow != 0) 103 104 /* 105 * Table of color and attribute values, default is for mono display. 106 * The order matches the DIALOG_ATR() values. 107 */ 108 #define DATA(atr,upr,lwr,cmt) { atr COLOR_DATA(upr) RC_DATA(lwr,cmt) } 109 /* *INDENT-OFF* */ 110 DIALOG_COLORS dlg_color_table[] = 111 { 112 DATA(A_NORMAL, SCREEN, screen, "Screen"), 113 DATA(A_NORMAL, SHADOW, shadow, "Shadow"), 114 DATA(A_REVERSE, DIALOG, dialog, "Dialog box"), 115 DATA(A_REVERSE, TITLE, title, "Dialog box title"), 116 DATA(A_REVERSE, BORDER, border, "Dialog box border"), 117 DATA(A_BOLD, BUTTON_ACTIVE, button_active, "Active button"), 118 DATA(A_DIM, BUTTON_INACTIVE, button_inactive, "Inactive button"), 119 DATA(A_UNDERLINE, BUTTON_KEY_ACTIVE, button_key_active, "Active button key"), 120 DATA(A_UNDERLINE, BUTTON_KEY_INACTIVE, button_key_inactive, "Inactive button key"), 121 DATA(A_NORMAL, BUTTON_LABEL_ACTIVE, button_label_active, "Active button label"), 122 DATA(A_NORMAL, BUTTON_LABEL_INACTIVE, button_label_inactive, "Inactive button label"), 123 DATA(A_REVERSE, INPUTBOX, inputbox, "Input box"), 124 DATA(A_REVERSE, INPUTBOX_BORDER, inputbox_border, "Input box border"), 125 DATA(A_REVERSE, SEARCHBOX, searchbox, "Search box"), 126 DATA(A_REVERSE, SEARCHBOX_TITLE, searchbox_title, "Search box title"), 127 DATA(A_REVERSE, SEARCHBOX_BORDER, searchbox_border, "Search box border"), 128 DATA(A_REVERSE, POSITION_INDICATOR, position_indicator, "File position indicator"), 129 DATA(A_REVERSE, MENUBOX, menubox, "Menu box"), 130 DATA(A_REVERSE, MENUBOX_BORDER, menubox_border, "Menu box border"), 131 DATA(A_REVERSE, ITEM, item, "Item"), 132 DATA(A_NORMAL, ITEM_SELECTED, item_selected, "Selected item"), 133 DATA(A_REVERSE, TAG, tag, "Tag"), 134 DATA(A_REVERSE, TAG_SELECTED, tag_selected, "Selected tag"), 135 DATA(A_NORMAL, TAG_KEY, tag_key, "Tag key"), 136 DATA(A_BOLD, TAG_KEY_SELECTED, tag_key_selected, "Selected tag key"), 137 DATA(A_REVERSE, CHECK, check, "Check box"), 138 DATA(A_REVERSE, CHECK_SELECTED, check_selected, "Selected check box"), 139 DATA(A_REVERSE, UARROW, uarrow, "Up arrow"), 140 DATA(A_REVERSE, DARROW, darrow, "Down arrow"), 141 DATA(A_NORMAL, ITEMHELP, itemhelp, "Item help-text"), 142 DATA(A_BOLD, FORM_ACTIVE_TEXT, form_active_text, "Active form text"), 143 DATA(A_REVERSE, FORM_TEXT, form_text, "Form text"), 144 DATA(A_NORMAL, FORM_ITEM_READONLY, form_item_readonly, "Readonly form item"), 145 DATA(A_REVERSE, GAUGE, gauge, "Dialog box gauge"), 146 DATA(A_REVERSE, BORDER2, border2, "Dialog box border2"), 147 DATA(A_REVERSE, INPUTBOX_BORDER2, inputbox_border2, "Input box border2"), 148 DATA(A_REVERSE, SEARCHBOX_BORDER2, searchbox_border2, "Search box border2"), 149 DATA(A_REVERSE, MENUBOX_BORDER2, menubox_border2, "Menu box border2") 150 }; 151 #undef DATA 152 /* *INDENT-ON* */ 153 154 /* 155 * Maintain a list of subwindows so that we can delete them to cleanup. 156 * More important, this provides a fallback when wgetparent() is not available. 157 */ 158 static void 159 add_subwindow(WINDOW *parent, WINDOW *child) 160 { 161 DIALOG_WINDOWS *p = dlg_calloc(DIALOG_WINDOWS, 1); 162 163 if (p != 0) { 164 p->normal = parent; 165 p->shadow = child; 166 p->getc_timeout = WTIMEOUT_OFF; 167 p->next = dialog_state.all_subwindows; 168 dialog_state.all_subwindows = p; 169 } 170 } 171 172 static void 173 del_subwindows(WINDOW *parent) 174 { 175 DIALOG_WINDOWS *p = dialog_state.all_subwindows; 176 DIALOG_WINDOWS *q = 0; 177 DIALOG_WINDOWS *r; 178 179 while (p != 0) { 180 if (p->normal == parent) { 181 delwin(p->shadow); 182 r = p->next; 183 if (q == 0) { 184 dialog_state.all_subwindows = r; 185 } else { 186 q->next = r; 187 } 188 free(p); 189 p = r; 190 } else { 191 q = p; 192 p = p->next; 193 } 194 } 195 } 196 197 /* 198 * Display background title if it exists ... 199 */ 200 void 201 dlg_put_backtitle(void) 202 { 203 204 if (dialog_vars.backtitle != NULL) { 205 chtype attr = A_NORMAL; 206 int backwidth = dlg_count_columns(dialog_vars.backtitle); 207 int i; 208 209 dlg_attrset(stdscr, screen_attr); 210 (void) wmove(stdscr, 0, 1); 211 dlg_print_text(stdscr, dialog_vars.backtitle, COLS - 2, &attr); 212 for (i = 0; i < COLS - backwidth; i++) 213 (void) waddch(stdscr, ' '); 214 (void) wmove(stdscr, 1, 1); 215 for (i = 0; i < COLS - 2; i++) 216 (void) waddch(stdscr, dlg_boxchar(ACS_HLINE)); 217 } 218 219 (void) wnoutrefresh(stdscr); 220 } 221 222 /* 223 * Set window to attribute 'attr'. There are more efficient ways to do this, 224 * but will not work on older/buggy ncurses versions. 225 */ 226 void 227 dlg_attr_clear(WINDOW *win, int height, int width, chtype attr) 228 { 229 int i, j; 230 231 dlg_attrset(win, attr); 232 for (i = 0; i < height; i++) { 233 (void) wmove(win, i, 0); 234 for (j = 0; j < width; j++) 235 (void) waddch(win, ' '); 236 } 237 (void) touchwin(win); 238 } 239 240 void 241 dlg_clear(void) 242 { 243 dlg_attr_clear(stdscr, LINES, COLS, screen_attr); 244 } 245 246 #ifdef KEY_RESIZE 247 void 248 _dlg_resize_cleanup(WINDOW *w) 249 { 250 dlg_clear(); 251 dlg_put_backtitle(); 252 dlg_del_window(w); 253 dlg_mouse_free_regions(); 254 } 255 #endif /* KEY_RESIZE */ 256 257 #define isprivate(s) ((s) != 0 && strstr(s, "\033[?") != 0) 258 259 #define TTY_DEVICE "/dev/tty" 260 261 /* 262 * If $DIALOG_TTY exists, allow the program to try to open the terminal 263 * directly when stdout is redirected. By default we require the "--stdout" 264 * option to be given, but some scripts were written making use of the 265 * behavior of dialog which tried opening the terminal anyway. 266 */ 267 static char * 268 dialog_tty(void) 269 { 270 char *result = getenv("DIALOG_TTY"); 271 if (result != 0 && atoi(result) == 0) 272 result = 0; 273 return result; 274 } 275 276 /* 277 * Open the terminal directly. If one of stdin, stdout or stderr really points 278 * to a tty, use it. Otherwise give up and open /dev/tty. 279 */ 280 static int 281 open_terminal(char **result, int mode) 282 { 283 const char *device = TTY_DEVICE; 284 if (!isatty(fileno(stderr)) 285 || (device = ttyname(fileno(stderr))) == 0) { 286 if (!isatty(fileno(stdout)) 287 || (device = ttyname(fileno(stdout))) == 0) { 288 if (!isatty(fileno(stdin)) 289 || (device = ttyname(fileno(stdin))) == 0) { 290 device = TTY_DEVICE; 291 } 292 } 293 } 294 *result = dlg_strclone(device); 295 return open(device, mode); 296 } 297 298 #ifdef NCURSES_VERSION 299 static int 300 my_putc(int ch) 301 { 302 char buffer[2]; 303 int fd = fileno(dialog_state.screen_output); 304 305 buffer[0] = (char) ch; 306 return (int) write(fd, buffer, (size_t) 1); 307 } 308 #endif 309 310 /* 311 * Do some initialization for dialog. 312 * 313 * 'input' is the real tty input of dialog. Usually it is stdin, but if 314 * --input-fd option is used, it may be anything. 315 * 316 * 'output' is where dialog will send its result. Usually it is stderr, but 317 * if --stdout or --output-fd is used, it may be anything. We are concerned 318 * mainly with the case where it happens to be the same as stdout. 319 */ 320 void 321 init_dialog(FILE *input, FILE *output) 322 { 323 int fd1, fd2; 324 char *device = 0; 325 326 setlocale(LC_ALL, ""); 327 328 dialog_state.output = output; 329 dialog_state.tab_len = TAB_LEN; 330 dialog_state.aspect_ratio = DEFAULT_ASPECT_RATIO; 331 #ifdef HAVE_COLOR 332 dialog_state.use_colors = USE_COLORS; /* use colors by default? */ 333 dialog_state.use_shadow = USE_SHADOW; /* shadow dialog boxes by default? */ 334 #endif 335 336 #ifdef HAVE_RC_FILE 337 if (dlg_parse_rc() == -1) /* Read the configuration file */ 338 dlg_exiterr("init_dialog: dlg_parse_rc"); 339 #endif 340 341 /* 342 * Some widgets (such as gauge) may read from the standard input. Pipes 343 * only connect stdout/stdin, so there is not much choice. But reading a 344 * pipe would get in the way of curses' normal reading stdin for getch. 345 * 346 * As in the --stdout (see below), reopening the terminal does not always 347 * work properly. dialog provides a --pipe-fd option for this purpose. We 348 * test that case first (differing fileno's for input/stdin). If the 349 * fileno's are equal, but we're not reading from a tty, see if we can open 350 * /dev/tty. 351 */ 352 dialog_state.pipe_input = stdin; 353 if (fileno(input) != fileno(stdin)) { 354 if ((fd1 = dup(fileno(input))) >= 0 355 && (fd2 = dup(fileno(stdin))) >= 0) { 356 (void) dup2(fileno(input), fileno(stdin)); 357 dialog_state.pipe_input = fdopen(fd2, "r"); 358 if (fileno(stdin) != 0) /* some functions may read fd #0 */ 359 (void) dup2(fileno(stdin), 0); 360 } else { 361 dlg_exiterr("cannot open tty-input"); 362 } 363 close(fd1); 364 } else if (!isatty(fileno(stdin))) { 365 if ((fd1 = open_terminal(&device, O_RDONLY)) >= 0) { 366 if ((fd2 = dup(fileno(stdin))) >= 0) { 367 dialog_state.pipe_input = fdopen(fd2, "r"); 368 if (freopen(device, "r", stdin) == 0) 369 dlg_exiterr("cannot open tty-input"); 370 if (fileno(stdin) != 0) /* some functions may read fd #0 */ 371 (void) dup2(fileno(stdin), 0); 372 } 373 close(fd1); 374 } 375 free(device); 376 } 377 378 /* 379 * If stdout is not a tty and dialog is called with the --stdout option, we 380 * have to provide for a way to write to the screen. 381 * 382 * The curses library normally writes its output to stdout, leaving stderr 383 * free for scripting. Scripts are simpler when stdout is redirected. The 384 * newterm function is useful; it allows us to specify where the output 385 * goes. Reopening the terminal is not portable since several 386 * configurations do not allow this to work properly: 387 * 388 * a) some getty implementations (and possibly broken tty drivers, e.g., on 389 * HPUX 10 and 11) cause stdin to act as if it is still in cooked mode 390 * even though results from ioctl's state that it is successfully 391 * altered to raw mode. Broken is the proper term. 392 * 393 * b) the user may not have permissions on the device, e.g., if one su's 394 * from the login user to another non-privileged user. 395 */ 396 if (!isatty(fileno(stdout)) 397 && (fileno(stdout) == fileno(output) || dialog_tty())) { 398 if ((fd1 = open_terminal(&device, O_WRONLY)) >= 0 399 && (dialog_state.screen_output = fdopen(fd1, "w")) != 0) { 400 if (newterm(NULL, dialog_state.screen_output, stdin) == 0) { 401 dlg_exiterr("cannot initialize curses"); 402 } 403 free(device); 404 } else { 405 dlg_exiterr("cannot open tty-output"); 406 } 407 } else { 408 dialog_state.screen_output = stdout; 409 (void) initscr(); 410 } 411 #ifdef NCURSES_VERSION 412 /* 413 * Cancel xterm's alternate-screen mode. 414 */ 415 if (!dialog_vars.keep_tite 416 && (fileno(dialog_state.screen_output) != fileno(stdout) 417 || isatty(fileno(dialog_state.screen_output))) 418 && key_mouse != 0 /* xterm and kindred */ 419 && isprivate(enter_ca_mode) 420 && isprivate(exit_ca_mode)) { 421 /* 422 * initscr() or newterm() already wrote enter_ca_mode as a side 423 * effect of initializing the screen. It would be nice to not even 424 * do that, but we do not really have access to the correct copy of 425 * the terminfo description until those functions have been invoked. 426 */ 427 (void) refresh(); 428 (void) tputs(exit_ca_mode, 0, my_putc); 429 (void) tputs(clear_screen, 0, my_putc); 430 /* 431 * Prevent ncurses from switching "back" to the normal screen when 432 * exiting from dialog. That would move the cursor to the original 433 * location saved in xterm. Normally curses sets the cursor position 434 * to the first line after the display, but the alternate screen 435 * switching is done after that point. 436 * 437 * Cancelling the strings altogether also works around the buggy 438 * implementation of alternate-screen in rxvt, etc., which clear 439 * more of the display than they should. 440 */ 441 enter_ca_mode = 0; 442 exit_ca_mode = 0; 443 } 444 #endif 445 #ifdef HAVE_FLUSHINP 446 (void) flushinp(); 447 #endif 448 (void) keypad(stdscr, TRUE); 449 (void) cbreak(); 450 (void) noecho(); 451 452 if (!dialog_state.no_mouse) { 453 mouse_open(); 454 } 455 456 dialog_state.screen_initialized = TRUE; 457 458 #ifdef HAVE_COLOR 459 if (dialog_state.use_colors || dialog_state.use_shadow) 460 dlg_color_setup(); /* Set up colors */ 461 #endif 462 463 /* Set screen to screen attribute */ 464 dlg_clear(); 465 } 466 467 #ifdef HAVE_COLOR 468 static int defined_colors = 1; /* pair-0 is reserved */ 469 /* 470 * Setup for color display 471 */ 472 void 473 dlg_color_setup(void) 474 { 475 if (has_colors()) { /* Terminal supports color? */ 476 unsigned i; 477 478 (void) start_color(); 479 480 #if defined(HAVE_USE_DEFAULT_COLORS) 481 use_default_colors(); 482 #endif 483 484 #if defined(__NetBSD__) && defined(_CURSES_) 485 #define C_ATTR(x,y) (((x) != 0 ? A_BOLD : 0) | COLOR_PAIR((y))) 486 /* work around bug in NetBSD curses */ 487 for (i = 0; i < sizeof(dlg_color_table) / 488 sizeof(dlg_color_table[0]); i++) { 489 490 /* Initialize color pairs */ 491 (void) init_pair(i + 1, 492 dlg_color_table[i].fg, 493 dlg_color_table[i].bg); 494 495 /* Setup color attributes */ 496 dlg_color_table[i].atr = C_ATTR(dlg_color_table[i].hilite, i + 1); 497 } 498 defined_colors = i + 1; 499 #else 500 for (i = 0; i < sizeof(dlg_color_table) / 501 sizeof(dlg_color_table[0]); i++) { 502 503 /* Initialize color pairs */ 504 chtype atr = dlg_color_pair(dlg_color_table[i].fg, 505 dlg_color_table[i].bg); 506 507 atr |= (dlg_color_table[i].hilite ? A_BOLD : 0); 508 #ifdef HAVE_RC_FILE2 509 atr |= (dlg_color_table[i].ul ? A_UNDERLINE : 0); 510 atr |= (dlg_color_table[i].rv ? A_REVERSE : 0); 511 #endif /* HAVE_RC_FILE2 */ 512 513 dlg_color_table[i].atr = atr; 514 } 515 #endif 516 } else { 517 dialog_state.use_colors = FALSE; 518 dialog_state.use_shadow = FALSE; 519 } 520 } 521 522 int 523 dlg_color_count(void) 524 { 525 return TableSize(dlg_color_table); 526 } 527 528 /* 529 * Wrapper for getattrs(), or the more cumbersome X/Open wattr_get(). 530 */ 531 chtype 532 dlg_get_attrs(WINDOW *win) 533 { 534 chtype result; 535 #ifdef HAVE_GETATTRS 536 result = (chtype) getattrs(win); 537 #else 538 attr_t my_result; 539 short my_pair; 540 wattr_get(win, &my_result, &my_pair, NULL); 541 result = my_result; 542 #endif 543 return result; 544 } 545 546 /* 547 * Reuse color pairs (they are limited), returning a COLOR_PAIR() value if we 548 * have (or can) define a pair with the given color as foreground on the 549 * window's defined background. 550 */ 551 chtype 552 dlg_color_pair(int foreground, int background) 553 { 554 chtype result = 0; 555 int pair; 556 short fg, bg; 557 bool found = FALSE; 558 559 for (pair = 1; pair < defined_colors; ++pair) { 560 if (pair_content((short) pair, &fg, &bg) != ERR 561 && fg == foreground 562 && bg == background) { 563 result = (chtype) COLOR_PAIR(pair); 564 found = TRUE; 565 break; 566 } 567 } 568 if (!found && (defined_colors + 1) < COLOR_PAIRS) { 569 pair = defined_colors++; 570 (void) init_pair((short) pair, (short) foreground, (short) background); 571 result = (chtype) COLOR_PAIR(pair); 572 } 573 return result; 574 } 575 576 /* 577 * Reuse color pairs (they are limited), returning a COLOR_PAIR() value if we 578 * have (or can) define a pair with the given color as foreground on the 579 * window's defined background. 580 */ 581 static chtype 582 define_color(WINDOW *win, int foreground) 583 { 584 short fg, bg, background; 585 if (dialog_state.text_only) { 586 background = COLOR_BLACK; 587 } else { 588 chtype attrs = dlg_get_attrs(win); 589 int pair; 590 591 if ((pair = PAIR_NUMBER(attrs)) != 0 592 && pair_content((short) pair, &fg, &bg) != ERR) { 593 background = bg; 594 } else { 595 background = COLOR_BLACK; 596 } 597 } 598 return dlg_color_pair(foreground, background); 599 } 600 #endif 601 602 /* 603 * End using dialog functions. 604 */ 605 void 606 end_dialog(void) 607 { 608 if (dialog_state.screen_initialized) { 609 dialog_state.screen_initialized = FALSE; 610 mouse_close(); 611 (void) endwin(); 612 (void) fflush(stdout); 613 } 614 } 615 616 #define ESCAPE_LEN 3 617 #define isOurEscape(p) (((p)[0] == '\\') && ((p)[1] == 'Z') && ((p)[2] != 0)) 618 619 int 620 dlg_count_real_columns(const char *text) 621 { 622 int result = 0; 623 if (*text) { 624 result = dlg_count_columns(text); 625 if (result && dialog_vars.colors) { 626 int hidden = 0; 627 while (*text) { 628 if (dialog_vars.colors && isOurEscape(text)) { 629 hidden += ESCAPE_LEN; 630 text += ESCAPE_LEN; 631 } else { 632 ++text; 633 } 634 } 635 result -= hidden; 636 } 637 } 638 return result; 639 } 640 641 static int 642 centered(int width, const char *string) 643 { 644 int need = dlg_count_real_columns(string); 645 int left; 646 647 left = (width - need) / 2 - 1; 648 if (left < 0) 649 left = 0; 650 return left; 651 } 652 653 #ifdef USE_WIDE_CURSES 654 static bool 655 is_combining(const char *txt, int *combined) 656 { 657 bool result = FALSE; 658 659 if (*combined == 0) { 660 if (UCH(*txt) >= 128) { 661 wchar_t wch; 662 mbstate_t state; 663 size_t given = strlen(txt); 664 size_t len; 665 666 memset(&state, 0, sizeof(state)); 667 len = mbrtowc(&wch, txt, given, &state); 668 if ((int) len > 0 && wcwidth(wch) == 0) { 669 *combined = (int) len - 1; 670 result = TRUE; 671 } 672 } 673 } else { 674 result = TRUE; 675 *combined -= 1; 676 } 677 return result; 678 } 679 #endif 680 681 /* 682 * Print the name (tag) or text from a DIALOG_LISTITEM, highlighting the 683 * first character if selected. 684 */ 685 void 686 dlg_print_listitem(WINDOW *win, 687 const char *text, 688 int climit, 689 bool first, 690 int selected) 691 { 692 chtype attr = A_NORMAL; 693 int limit; 694 chtype attrs[4]; 695 696 if (text == 0) 697 text = ""; 698 699 if (first) { 700 const int *indx = dlg_index_wchars(text); 701 attrs[3] = tag_key_selected_attr; 702 attrs[2] = tag_key_attr; 703 attrs[1] = tag_selected_attr; 704 attrs[0] = tag_attr; 705 706 dlg_attrset(win, selected ? attrs[3] : attrs[2]); 707 if (*text != '\0') { 708 (void) waddnstr(win, text, indx[1]); 709 710 if ((int) strlen(text) > indx[1]) { 711 limit = dlg_limit_columns(text, climit, 1); 712 if (limit > 1) { 713 dlg_attrset(win, selected ? attrs[1] : attrs[0]); 714 (void) waddnstr(win, 715 text + indx[1], 716 indx[limit] - indx[1]); 717 } 718 } 719 } 720 } else { 721 const int *cols; 722 723 attrs[1] = item_selected_attr; 724 attrs[0] = item_attr; 725 726 cols = dlg_index_columns(text); 727 limit = dlg_limit_columns(text, climit, 0); 728 729 if (limit > 0) { 730 dlg_attrset(win, selected ? attrs[1] : attrs[0]); 731 dlg_print_text(win, text, cols[limit], &attr); 732 } 733 } 734 } 735 736 /* 737 * Print up to 'cols' columns from 'text', optionally rendering our escape 738 * sequence for attributes and color. 739 */ 740 void 741 dlg_print_text(WINDOW *win, const char *txt, int cols, chtype *attr) 742 { 743 int y_origin, x_origin; 744 int y_before, x_before = 0; 745 int y_after, x_after; 746 int tabbed = 0; 747 bool ended = FALSE; 748 #ifdef USE_WIDE_CURSES 749 int combined = 0; 750 #endif 751 752 if (dialog_state.text_only) { 753 y_origin = y_after = 0; 754 x_origin = x_after = 0; 755 } else { 756 y_after = 0; 757 x_after = 0; 758 getyx(win, y_origin, x_origin); 759 } 760 while (cols > 0 && (*txt != '\0')) { 761 bool thisTab; 762 chtype useattr; 763 764 if (dialog_vars.colors) { 765 while (isOurEscape(txt)) { 766 int code; 767 768 txt += 2; 769 switch (code = CharOf(*txt)) { 770 #ifdef HAVE_COLOR 771 case '0': 772 case '1': 773 case '2': 774 case '3': 775 case '4': 776 case '5': 777 case '6': 778 case '7': 779 *attr &= ~A_COLOR; 780 *attr |= define_color(win, code - '0'); 781 break; 782 #endif 783 case 'B': 784 *attr &= ~A_BOLD; 785 break; 786 case 'b': 787 *attr |= A_BOLD; 788 break; 789 case 'R': 790 *attr &= ~A_REVERSE; 791 break; 792 case 'r': 793 *attr |= A_REVERSE; 794 break; 795 case 'U': 796 *attr &= ~A_UNDERLINE; 797 break; 798 case 'u': 799 *attr |= A_UNDERLINE; 800 break; 801 case 'n': 802 *attr = A_NORMAL; 803 break; 804 default: 805 break; 806 } 807 ++txt; 808 } 809 } 810 if (ended || *txt == '\n' || *txt == '\0') 811 break; 812 useattr = (*attr) & A_ATTRIBUTES; 813 #ifdef HAVE_COLOR 814 /* 815 * Prevent this from making text invisible when the foreground and 816 * background colors happen to be the same, and there's no bold 817 * attribute. 818 */ 819 if ((useattr & A_COLOR) != 0 && (useattr & A_BOLD) == 0) { 820 short pair = (short) PAIR_NUMBER(useattr); 821 short fg, bg; 822 if (pair_content(pair, &fg, &bg) != ERR 823 && fg == bg) { 824 useattr &= ~A_COLOR; 825 useattr |= dlg_color_pair(fg, ((bg == COLOR_BLACK) 826 ? COLOR_WHITE 827 : COLOR_BLACK)); 828 } 829 } 830 #endif 831 /* 832 * Write the character, using curses to tell exactly how wide it 833 * is. If it is a tab, discount that, since the caller thinks 834 * tabs are nonprinting, and curses will expand tabs to one or 835 * more blanks. 836 */ 837 thisTab = (CharOf(*txt) == TAB); 838 if (dialog_state.text_only) { 839 y_before = y_after; 840 x_before = x_after; 841 } else { 842 if (thisTab) { 843 getyx(win, y_before, x_before); 844 (void) y_before; 845 } 846 } 847 if (dialog_state.text_only) { 848 int ch = CharOf(*txt++); 849 if (thisTab) { 850 while ((x_after++) % 8) { 851 fputc(' ', dialog_state.output); 852 } 853 } else { 854 fputc(ch, dialog_state.output); 855 x_after++; /* FIXME: handle meta per locale */ 856 } 857 } else { 858 (void) waddch(win, CharOf(*txt++) | useattr); 859 getyx(win, y_after, x_after); 860 } 861 if (thisTab && (y_after == y_origin)) 862 tabbed += (x_after - x_before); 863 if ((y_after != y_origin) || 864 (x_after >= (cols + tabbed + x_origin) 865 #ifdef USE_WIDE_CURSES 866 && !is_combining(txt, &combined) 867 #endif 868 )) { 869 ended = TRUE; 870 } 871 } 872 if (dialog_state.text_only) { 873 fputc('\n', dialog_state.output); 874 } 875 } 876 877 /* 878 * Print one line of the prompt in the window within the limits of the 879 * specified right margin. The line will end on a word boundary and a pointer 880 * to the start of the next line is returned, or a NULL pointer if the end of 881 * *prompt is reached. 882 */ 883 const char * 884 dlg_print_line(WINDOW *win, 885 chtype *attr, 886 const char *prompt, 887 int lm, int rm, int *x) 888 { 889 const char *wrap_ptr; 890 const char *test_ptr; 891 const char *hide_ptr = 0; 892 const int *cols = dlg_index_columns(prompt); 893 const int *indx = dlg_index_wchars(prompt); 894 int wrap_inx = 0; 895 int test_inx = 0; 896 int cur_x = lm; 897 int hidden = 0; 898 int limit = dlg_count_wchars(prompt); 899 int n; 900 int tabbed = 0; 901 902 *x = 1; 903 904 /* 905 * Set *test_ptr to the end of the line or the right margin (rm), whichever 906 * is less, and set wrap_ptr to the end of the last word in the line. 907 */ 908 for (n = 0; n < limit; ++n) { 909 int ch = *(test_ptr = prompt + indx[test_inx]); 910 if (ch == '\n' || ch == '\0' || cur_x >= (rm + hidden)) 911 break; 912 if (ch == TAB && n == 0) { 913 tabbed = 8; /* workaround for leading tabs */ 914 } else if (isblank(UCH(ch)) 915 && n != 0 916 && !isblank(UCH(prompt[indx[n - 1]]))) { 917 wrap_inx = n; 918 *x = cur_x; 919 } else if (dialog_vars.colors && isOurEscape(test_ptr)) { 920 hide_ptr = test_ptr; 921 hidden += ESCAPE_LEN; 922 n += (ESCAPE_LEN - 1); 923 } 924 cur_x = lm + tabbed + cols[n + 1]; 925 if (cur_x > (rm + hidden)) 926 break; 927 test_inx = n + 1; 928 } 929 930 /* 931 * If the line doesn't reach the right margin in the middle of a word, then 932 * we don't have to wrap it at the end of the previous word. 933 */ 934 test_ptr = prompt + indx[test_inx]; 935 if (*test_ptr == '\n' || isblank(UCH(*test_ptr)) || *test_ptr == '\0') { 936 wrap_inx = test_inx; 937 while (wrap_inx > 0 && isblank(UCH(prompt[indx[wrap_inx - 1]]))) { 938 wrap_inx--; 939 } 940 *x = lm + indx[wrap_inx]; 941 } else if (*x == 1 && cur_x >= rm) { 942 /* 943 * If the line has no spaces, then wrap it anyway at the right margin 944 */ 945 *x = rm; 946 wrap_inx = test_inx; 947 } 948 wrap_ptr = prompt + indx[wrap_inx]; 949 #ifdef USE_WIDE_CURSES 950 if (UCH(*wrap_ptr) >= 128) { 951 int combined = 0; 952 while (is_combining(wrap_ptr, &combined)) { 953 ++wrap_ptr; 954 } 955 } 956 #endif 957 958 /* 959 * If we found hidden text past the last point that we will display, 960 * discount that from the displayed length. 961 */ 962 if ((hide_ptr != 0) && (hide_ptr >= wrap_ptr)) { 963 hidden -= ESCAPE_LEN; 964 test_ptr = wrap_ptr; 965 while (test_ptr < wrap_ptr) { 966 if (dialog_vars.colors && isOurEscape(test_ptr)) { 967 hidden -= ESCAPE_LEN; 968 test_ptr += ESCAPE_LEN; 969 } else { 970 ++test_ptr; 971 } 972 } 973 } 974 975 /* 976 * Print the line if we have a window pointer. Otherwise this routine 977 * is just being called for sizing the window. 978 */ 979 if (dialog_state.text_only || win) { 980 dlg_print_text(win, prompt, (cols[wrap_inx] - hidden), attr); 981 } 982 983 /* *x tells the calling function how long the line was */ 984 if (*x == 1) { 985 *x = rm; 986 } 987 988 *x -= hidden; 989 990 /* Find the start of the next line and return a pointer to it */ 991 test_ptr = wrap_ptr; 992 while (isblank(UCH(*test_ptr))) 993 test_ptr++; 994 if (*test_ptr == '\n') 995 test_ptr++; 996 dlg_finish_string(prompt); 997 return (test_ptr); 998 } 999 1000 static void 1001 justify_text(WINDOW *win, 1002 const char *prompt, 1003 int limit_y, 1004 int limit_x, 1005 int *high, int *wide) 1006 { 1007 chtype attr = A_NORMAL; 1008 int x; 1009 int y = MARGIN; 1010 int max_x = 2; 1011 int lm = (2 * MARGIN); /* left margin (box-border plus a space) */ 1012 int rm = limit_x; /* right margin */ 1013 int bm = limit_y; /* bottom margin */ 1014 int last_y = 0, last_x = 0; 1015 1016 dialog_state.text_height = 0; 1017 dialog_state.text_width = 0; 1018 if (dialog_state.text_only || win) { 1019 rm -= (2 * MARGIN); 1020 bm -= (2 * MARGIN); 1021 } 1022 if (prompt == 0) 1023 prompt = ""; 1024 1025 if (win != 0) 1026 getyx(win, last_y, last_x); 1027 while (y <= bm && *prompt) { 1028 x = lm; 1029 1030 if (*prompt == '\n') { 1031 while (*prompt == '\n' && y < bm) { 1032 if (*(prompt + 1) != '\0') { 1033 ++y; 1034 if (win != 0) 1035 (void) wmove(win, y, lm); 1036 } 1037 prompt++; 1038 } 1039 } else if (win != 0) 1040 (void) wmove(win, y, lm); 1041 1042 if (*prompt) { 1043 prompt = dlg_print_line(win, &attr, prompt, lm, rm, &x); 1044 if (win != 0) 1045 getyx(win, last_y, last_x); 1046 } 1047 if (*prompt) { 1048 ++y; 1049 if (win != 0) 1050 (void) wmove(win, y, lm); 1051 } 1052 max_x = MAX(max_x, x); 1053 } 1054 /* Move back to the last position after drawing prompt, for msgbox. */ 1055 if (win != 0) 1056 (void) wmove(win, last_y, last_x); 1057 1058 /* Set the final height and width for the calling function */ 1059 if (high != 0) 1060 *high = y; 1061 if (wide != 0) 1062 *wide = max_x; 1063 } 1064 1065 /* 1066 * Print a string of text in a window, automatically wrap around to the next 1067 * line if the string is too long to fit on one line. Note that the string may 1068 * contain embedded newlines. 1069 */ 1070 void 1071 dlg_print_autowrap(WINDOW *win, const char *prompt, int height, int width) 1072 { 1073 justify_text(win, prompt, 1074 height, 1075 width, 1076 (int *) 0, (int *) 0); 1077 } 1078 1079 /* 1080 * Display the message in a scrollable window. Actually the way it works is 1081 * that we create a "tall" window of the proper width, let the text wrap within 1082 * that, and copy a slice of the result to the dialog. 1083 * 1084 * It works for ncurses. Other curses implementations show only blanks (Tru64) 1085 * or garbage (NetBSD). 1086 */ 1087 int 1088 dlg_print_scrolled(WINDOW *win, 1089 const char *prompt, 1090 int offset, 1091 int height, 1092 int width, 1093 int pauseopt) 1094 { 1095 int oldy, oldx; 1096 int last = 0; 1097 1098 (void) pauseopt; /* used only for ncurses */ 1099 1100 getyx(win, oldy, oldx); 1101 #ifdef NCURSES_VERSION 1102 if (pauseopt) { 1103 int wide = width - (2 * MARGIN); 1104 int high = LINES; 1105 int len; 1106 WINDOW *dummy; 1107 1108 #if defined(NCURSES_VERSION_PATCH) && NCURSES_VERSION_PATCH >= 20040417 1109 /* 1110 * If we're not limited by the screensize, allow text to possibly be 1111 * one character per line. 1112 */ 1113 if ((len = dlg_count_columns(prompt)) > high) 1114 high = len; 1115 #endif 1116 dummy = newwin(high, width, 0, 0); 1117 if (dummy == 0) { 1118 dlg_attrset(win, dialog_attr); 1119 dlg_print_autowrap(win, prompt, height + 1 + (3 * MARGIN), width); 1120 last = 0; 1121 } else { 1122 int y, x; 1123 1124 wbkgdset(dummy, dialog_attr | ' '); 1125 dlg_attrset(dummy, dialog_attr); 1126 werase(dummy); 1127 dlg_print_autowrap(dummy, prompt, high, width); 1128 getyx(dummy, y, x); 1129 (void) x; 1130 1131 copywin(dummy, /* srcwin */ 1132 win, /* dstwin */ 1133 offset + MARGIN, /* sminrow */ 1134 MARGIN, /* smincol */ 1135 MARGIN, /* dminrow */ 1136 MARGIN, /* dmincol */ 1137 height, /* dmaxrow */ 1138 wide, /* dmaxcol */ 1139 FALSE); 1140 1141 delwin(dummy); 1142 1143 /* if the text is incomplete, or we have scrolled, show the percentage */ 1144 if (y > 0 && wide > 4) { 1145 int percent = (int) ((height + offset) * 100.0 / y); 1146 1147 if (percent < 0) 1148 percent = 0; 1149 if (percent > 100) 1150 percent = 100; 1151 1152 if (offset != 0 || percent != 100) { 1153 char buffer[5]; 1154 1155 dlg_attrset(win, position_indicator_attr); 1156 (void) wmove(win, MARGIN + height, wide - 4); 1157 (void) sprintf(buffer, "%d%%", percent); 1158 (void) waddstr(win, buffer); 1159 if ((len = (int) strlen(buffer)) < 4) { 1160 dlg_attrset(win, border_attr); 1161 whline(win, dlg_boxchar(ACS_HLINE), 4 - len); 1162 } 1163 } 1164 } 1165 last = (y - height); 1166 } 1167 } else 1168 #endif 1169 { 1170 (void) offset; 1171 dlg_attrset(win, dialog_attr); 1172 dlg_print_autowrap(win, prompt, height + 1 + (3 * MARGIN), width); 1173 last = 0; 1174 } 1175 wmove(win, oldy, oldx); 1176 return last; 1177 } 1178 1179 int 1180 dlg_check_scrolled(int key, int last, int page, bool * show, int *offset) 1181 { 1182 int code = 0; 1183 1184 *show = FALSE; 1185 1186 switch (key) { 1187 case DLGK_PAGE_FIRST: 1188 if (*offset > 0) { 1189 *offset = 0; 1190 *show = TRUE; 1191 } 1192 break; 1193 case DLGK_PAGE_LAST: 1194 if (*offset < last) { 1195 *offset = last; 1196 *show = TRUE; 1197 } 1198 break; 1199 case DLGK_GRID_UP: 1200 if (*offset > 0) { 1201 --(*offset); 1202 *show = TRUE; 1203 } 1204 break; 1205 case DLGK_GRID_DOWN: 1206 if (*offset < last) { 1207 ++(*offset); 1208 *show = TRUE; 1209 } 1210 break; 1211 case DLGK_PAGE_PREV: 1212 if (*offset > 0) { 1213 *offset -= page; 1214 if (*offset < 0) 1215 *offset = 0; 1216 *show = TRUE; 1217 } 1218 break; 1219 case DLGK_PAGE_NEXT: 1220 if (*offset < last) { 1221 *offset += page; 1222 if (*offset > last) 1223 *offset = last; 1224 *show = TRUE; 1225 } 1226 break; 1227 default: 1228 code = -1; 1229 break; 1230 } 1231 return code; 1232 } 1233 1234 /* 1235 * Calculate the window size for preformatted text. This will calculate box 1236 * dimensions that are at or close to the specified aspect ratio for the prompt 1237 * string with all spaces and newlines preserved and additional newlines added 1238 * as necessary. 1239 */ 1240 static void 1241 auto_size_preformatted(const char *prompt, int *height, int *width) 1242 { 1243 int high = 0, wide = 0; 1244 float car; /* Calculated Aspect Ratio */ 1245 int max_y = SLINES - 1; 1246 int max_x = SCOLS - 2; 1247 int max_width = max_x; 1248 int ar = dialog_state.aspect_ratio; 1249 1250 /* Get the initial dimensions */ 1251 justify_text((WINDOW *) 0, prompt, max_y, max_x, &high, &wide); 1252 car = (float) (wide / high); 1253 1254 /* 1255 * If the aspect ratio is greater than it should be, then decrease the 1256 * width proportionately. 1257 */ 1258 if (car > ar) { 1259 float diff = car / (float) ar; 1260 max_x = (int) ((float) wide / diff + 4); 1261 justify_text((WINDOW *) 0, prompt, max_y, max_x, &high, &wide); 1262 car = (float) wide / (float) high; 1263 } 1264 1265 /* 1266 * If the aspect ratio is too small after decreasing the width, then 1267 * incrementally increase the width until the aspect ratio is equal to or 1268 * greater than the specified aspect ratio. 1269 */ 1270 while (car < ar && max_x < max_width) { 1271 max_x += 4; 1272 justify_text((WINDOW *) 0, prompt, max_y, max_x, &high, &wide); 1273 car = (float) (wide / high); 1274 } 1275 1276 *height = high; 1277 *width = wide; 1278 } 1279 1280 /* 1281 * Find the length of the longest "word" in the given string. By setting the 1282 * widget width at least this long, we can avoid splitting a word on the 1283 * margin. 1284 */ 1285 static int 1286 longest_word(const char *string) 1287 { 1288 int result = 0; 1289 1290 while (*string != '\0') { 1291 int length = 0; 1292 while (*string != '\0' && !isspace(UCH(*string))) { 1293 length++; 1294 string++; 1295 } 1296 result = MAX(result, length); 1297 if (*string != '\0') 1298 string++; 1299 } 1300 return result; 1301 } 1302 1303 /* 1304 * if (height or width == -1) Maximize() 1305 * if (height or width == 0), justify and return actual limits. 1306 */ 1307 static void 1308 real_auto_size(const char *title, 1309 const char *prompt, 1310 int *height, int *width, 1311 int boxlines, int mincols) 1312 { 1313 int x = (dialog_vars.begin_set ? dialog_vars.begin_x : 2); 1314 int y = (dialog_vars.begin_set ? dialog_vars.begin_y : 1); 1315 int title_length = title ? dlg_count_columns(title) : 0; 1316 int high; 1317 int save_high = *height; 1318 int save_wide = *width; 1319 int max_high; 1320 int max_wide; 1321 1322 if (prompt == 0) { 1323 if (*height == 0) 1324 *height = -1; 1325 if (*width == 0) 1326 *width = -1; 1327 } 1328 1329 max_high = (*height < 0); 1330 max_wide = (*width < 0); 1331 1332 if (*height > 0) { 1333 high = *height; 1334 } else { 1335 high = SLINES - y; 1336 } 1337 1338 if (*width <= 0) { 1339 int wide; 1340 1341 if (prompt != 0) { 1342 wide = MAX(title_length, mincols); 1343 if (strchr(prompt, '\n') == 0) { 1344 double val = (dialog_state.aspect_ratio * 1345 dlg_count_real_columns(prompt)); 1346 double xxx = sqrt(val); 1347 int tmp = (int) xxx; 1348 wide = MAX(wide, tmp); 1349 wide = MAX(wide, longest_word(prompt)); 1350 justify_text((WINDOW *) 0, prompt, high, wide, height, width); 1351 } else { 1352 auto_size_preformatted(prompt, height, width); 1353 } 1354 } else { 1355 wide = SCOLS - x; 1356 justify_text((WINDOW *) 0, prompt, high, wide, height, width); 1357 } 1358 } 1359 1360 if (*width < title_length) { 1361 justify_text((WINDOW *) 0, prompt, high, title_length, height, width); 1362 *width = title_length; 1363 } 1364 1365 dialog_state.text_height = *height; 1366 dialog_state.text_width = *width; 1367 1368 if (*width < mincols && save_wide == 0) 1369 *width = mincols; 1370 if (prompt != 0) { 1371 *width += ((2 * MARGIN) + SHADOW_COLS); 1372 *height += boxlines + (2 * MARGIN); 1373 } 1374 1375 if (save_high > 0) 1376 *height = save_high; 1377 if (save_wide > 0) 1378 *width = save_wide; 1379 1380 if (max_high) 1381 *height = SLINES - (dialog_vars.begin_set ? dialog_vars.begin_y : 0); 1382 if (max_wide) 1383 *width = SCOLS - (dialog_vars.begin_set ? dialog_vars.begin_x : 0); 1384 } 1385 1386 /* End of real_auto_size() */ 1387 1388 void 1389 dlg_auto_size(const char *title, 1390 const char *prompt, 1391 int *height, 1392 int *width, 1393 int boxlines, 1394 int mincols) 1395 { 1396 DLG_TRACE(("# dlg_auto_size(%d,%d) limits %d,%d\n", 1397 *height, *width, 1398 boxlines, mincols)); 1399 1400 real_auto_size(title, prompt, height, width, boxlines, mincols); 1401 1402 if (*width > SCOLS) { 1403 (*height)++; 1404 *width = SCOLS; 1405 } 1406 1407 if (*height > SLINES) { 1408 *height = SLINES; 1409 } 1410 DLG_TRACE(("# ...dlg_auto_size(%d,%d) also %d,%d\n", 1411 *height, *width, 1412 dialog_state.text_height, dialog_state.text_width)); 1413 } 1414 1415 /* 1416 * if (height or width == -1) Maximize() 1417 * if (height or width == 0) 1418 * height=MIN(SLINES, num.lines in fd+n); 1419 * width=MIN(SCOLS, MAX(longer line+n, mincols)); 1420 */ 1421 void 1422 dlg_auto_sizefile(const char *title, 1423 const char *file, 1424 int *height, 1425 int *width, 1426 int boxlines, 1427 int mincols) 1428 { 1429 int count = 0; 1430 int len = title ? dlg_count_columns(title) : 0; 1431 int nc = 4; 1432 int numlines = 2; 1433 FILE *fd; 1434 1435 /* Open input file for reading */ 1436 if ((fd = fopen(file, "rb")) == NULL) 1437 dlg_exiterr("dlg_auto_sizefile: Cannot open input file %s", file); 1438 1439 if ((*height == -1) || (*width == -1)) { 1440 *height = SLINES - (dialog_vars.begin_set ? dialog_vars.begin_y : 0); 1441 *width = SCOLS - (dialog_vars.begin_set ? dialog_vars.begin_x : 0); 1442 } 1443 if ((*height != 0) && (*width != 0)) { 1444 (void) fclose(fd); 1445 if (*width > SCOLS) 1446 *width = SCOLS; 1447 if (*height > SLINES) 1448 *height = SLINES; 1449 return; 1450 } 1451 1452 while (!feof(fd)) { 1453 int ch; 1454 long offset; 1455 1456 if (ferror(fd)) 1457 break; 1458 1459 offset = 0; 1460 while (((ch = getc(fd)) != '\n') && !feof(fd)) { 1461 if ((ch == TAB) && (dialog_vars.tab_correct)) { 1462 offset += dialog_state.tab_len - (offset % dialog_state.tab_len); 1463 } else { 1464 offset++; 1465 } 1466 } 1467 1468 if (offset > len) 1469 len = (int) offset; 1470 1471 count++; 1472 } 1473 1474 /* now 'count' has the number of lines of fd and 'len' the max length */ 1475 1476 *height = MIN(SLINES, count + numlines + boxlines); 1477 *width = MIN(SCOLS, MAX((len + nc), mincols)); 1478 /* here width and height can be maximized if > SCOLS|SLINES because 1479 textbox-like widgets don't put all <file> on the screen. 1480 Msgbox-like widget instead have to put all <text> correctly. */ 1481 1482 (void) fclose(fd); 1483 } 1484 1485 /* 1486 * Draw a rectangular box with line drawing characters. 1487 * 1488 * borderchar is used to color the upper/left edges. 1489 * 1490 * boxchar is used to color the right/lower edges. It also is fill-color used 1491 * for the box contents. 1492 * 1493 * Normally, if you are drawing a scrollable box, use menubox_border_attr for 1494 * boxchar, and menubox_attr for borderchar since the scroll-arrows are drawn 1495 * with menubox_attr at the top, and menubox_border_attr at the bottom. That 1496 * also (given the default color choices) produces a recessed effect. 1497 * 1498 * If you want a raised effect (and are not going to use the scroll-arrows), 1499 * reverse this choice. 1500 */ 1501 void 1502 dlg_draw_box2(WINDOW *win, int y, int x, int height, int width, 1503 chtype boxchar, chtype borderchar, chtype borderchar2) 1504 { 1505 int i, j; 1506 chtype save = dlg_get_attrs(win); 1507 1508 dlg_attrset(win, 0); 1509 for (i = 0; i < height; i++) { 1510 (void) wmove(win, y + i, x); 1511 for (j = 0; j < width; j++) 1512 if (!i && !j) 1513 (void) waddch(win, borderchar | dlg_boxchar(ACS_ULCORNER)); 1514 else if (i == height - 1 && !j) 1515 (void) waddch(win, borderchar | dlg_boxchar(ACS_LLCORNER)); 1516 else if (!i && j == width - 1) 1517 (void) waddch(win, borderchar2 | dlg_boxchar(ACS_URCORNER)); 1518 else if (i == height - 1 && j == width - 1) 1519 (void) waddch(win, borderchar2 | dlg_boxchar(ACS_LRCORNER)); 1520 else if (!i) 1521 (void) waddch(win, borderchar | dlg_boxchar(ACS_HLINE)); 1522 else if (i == height - 1) 1523 (void) waddch(win, borderchar2 | dlg_boxchar(ACS_HLINE)); 1524 else if (!j) 1525 (void) waddch(win, borderchar | dlg_boxchar(ACS_VLINE)); 1526 else if (j == width - 1) 1527 (void) waddch(win, borderchar2 | dlg_boxchar(ACS_VLINE)); 1528 else 1529 (void) waddch(win, boxchar | ' '); 1530 } 1531 dlg_attrset(win, save); 1532 } 1533 1534 void 1535 dlg_draw_box(WINDOW *win, int y, int x, int height, int width, 1536 chtype boxchar, chtype borderchar) 1537 { 1538 dlg_draw_box2(win, y, x, height, width, boxchar, borderchar, boxchar); 1539 } 1540 1541 static DIALOG_WINDOWS * 1542 find_window(WINDOW *win) 1543 { 1544 DIALOG_WINDOWS *result = 0; 1545 DIALOG_WINDOWS *p; 1546 1547 for (p = dialog_state.all_windows; p != 0; p = p->next) { 1548 if (p->normal == win) { 1549 result = p; 1550 break; 1551 } 1552 } 1553 return result; 1554 } 1555 1556 #ifdef HAVE_COLOR 1557 /* 1558 * If we have wchgat(), use that for updating shadow attributes, to work with 1559 * wide-character data. 1560 */ 1561 1562 /* 1563 * Check if the given point is "in" the given window. If so, return the window 1564 * pointer, otherwise null. 1565 */ 1566 static WINDOW * 1567 in_window(WINDOW *win, int y, int x) 1568 { 1569 WINDOW *result = 0; 1570 int y_base = getbegy(win); 1571 int x_base = getbegx(win); 1572 int y_last = getmaxy(win) + y_base; 1573 int x_last = getmaxx(win) + x_base; 1574 1575 if (y >= y_base && y <= y_last && x >= x_base && x <= x_last) 1576 result = win; 1577 return result; 1578 } 1579 1580 static WINDOW * 1581 window_at_cell(DIALOG_WINDOWS * dw, int y, int x) 1582 { 1583 WINDOW *result = 0; 1584 DIALOG_WINDOWS *p; 1585 int y_want = y + getbegy(dw->shadow); 1586 int x_want = x + getbegx(dw->shadow); 1587 1588 for (p = dialog_state.all_windows; p != 0; p = p->next) { 1589 if (dw->normal != p->normal 1590 && dw->shadow != p->normal 1591 && (result = in_window(p->normal, y_want, x_want)) != 0) { 1592 break; 1593 } 1594 } 1595 if (result == 0) { 1596 result = stdscr; 1597 } 1598 return result; 1599 } 1600 1601 static bool 1602 in_shadow(WINDOW *normal, WINDOW *shadow, int y, int x) 1603 { 1604 bool result = FALSE; 1605 int ybase = getbegy(normal); 1606 int ylast = getmaxy(normal) + ybase; 1607 int xbase = getbegx(normal); 1608 int xlast = getmaxx(normal) + xbase; 1609 1610 y += getbegy(shadow); 1611 x += getbegx(shadow); 1612 1613 if (y >= ybase + SHADOW_ROWS 1614 && y < ylast + SHADOW_ROWS 1615 && x >= xlast 1616 && x < xlast + SHADOW_COLS) { 1617 /* in the right-side */ 1618 result = TRUE; 1619 } else if (y >= ylast 1620 && y < ylast + SHADOW_ROWS 1621 && x >= ybase + SHADOW_COLS 1622 && x < ylast + SHADOW_COLS) { 1623 /* check the bottom */ 1624 result = TRUE; 1625 } 1626 1627 return result; 1628 } 1629 1630 /* 1631 * When erasing a shadow, check each cell to make sure that it is not part of 1632 * another box's shadow. This is a little complicated since most shadows are 1633 * merged onto stdscr. 1634 */ 1635 static bool 1636 last_shadow(DIALOG_WINDOWS * dw, int y, int x) 1637 { 1638 DIALOG_WINDOWS *p; 1639 bool result = TRUE; 1640 1641 for (p = dialog_state.all_windows; p != 0; p = p->next) { 1642 if (p->normal != dw->normal 1643 && in_shadow(p->normal, dw->shadow, y, x)) { 1644 result = FALSE; 1645 break; 1646 } 1647 } 1648 return result; 1649 } 1650 1651 static void 1652 repaint_cell(DIALOG_WINDOWS * dw, bool draw, int y, int x) 1653 { 1654 WINDOW *win = dw->shadow; 1655 WINDOW *cellwin; 1656 int y2, x2; 1657 1658 if ((cellwin = window_at_cell(dw, y, x)) != 0 1659 && (draw || last_shadow(dw, y, x)) 1660 && (y2 = (y + getbegy(win) - getbegy(cellwin))) >= 0 1661 && (x2 = (x + getbegx(win) - getbegx(cellwin))) >= 0 1662 && wmove(cellwin, y2, x2) != ERR) { 1663 chtype the_cell = dlg_get_attrs(cellwin); 1664 chtype the_attr = (draw ? shadow_attr : the_cell); 1665 1666 if (winch(cellwin) & A_ALTCHARSET) { 1667 the_attr |= A_ALTCHARSET; 1668 } 1669 #if USE_WCHGAT 1670 wchgat(cellwin, 1, 1671 the_attr & (chtype) (~A_COLOR), 1672 (short) PAIR_NUMBER(the_attr), 1673 NULL); 1674 #else 1675 { 1676 chtype the_char = ((winch(cellwin) & A_CHARTEXT) | the_attr); 1677 (void) waddch(cellwin, the_char); 1678 } 1679 #endif 1680 wnoutrefresh(cellwin); 1681 } 1682 } 1683 1684 #define RepaintCell(dw, draw, y, x) repaint_cell(dw, draw, y, x) 1685 1686 static void 1687 repaint_shadow(DIALOG_WINDOWS * dw, bool draw, int y, int x, int height, int width) 1688 { 1689 if (UseShadow(dw)) { 1690 int i, j; 1691 1692 #if !USE_WCHGAT 1693 chtype save = dlg_get_attrs(dw->shadow); 1694 dlg_attrset(dw->shadow, draw ? shadow_attr : screen_attr); 1695 #endif 1696 for (i = 0; i < SHADOW_ROWS; ++i) { 1697 for (j = 0; j < width; ++j) { 1698 RepaintCell(dw, draw, i + y + height, j + x + SHADOW_COLS); 1699 } 1700 } 1701 for (i = 0; i < height; i++) { 1702 for (j = 0; j < SHADOW_COLS; ++j) { 1703 RepaintCell(dw, draw, i + y + SHADOW_ROWS, j + x + width); 1704 } 1705 } 1706 (void) wnoutrefresh(dw->shadow); 1707 #if !USE_WCHGAT 1708 dlg_attrset(dw->shadow, save); 1709 #endif 1710 } 1711 } 1712 1713 /* 1714 * Draw a shadow on the parent window corresponding to the right- and 1715 * bottom-edge of the child window, to give a 3-dimensional look. 1716 */ 1717 static void 1718 draw_childs_shadow(DIALOG_WINDOWS * dw) 1719 { 1720 if (UseShadow(dw)) { 1721 repaint_shadow(dw, 1722 TRUE, 1723 getbegy(dw->normal) - getbegy(dw->shadow), 1724 getbegx(dw->normal) - getbegx(dw->shadow), 1725 getmaxy(dw->normal), 1726 getmaxx(dw->normal)); 1727 } 1728 } 1729 1730 /* 1731 * Erase a shadow on the parent window corresponding to the right- and 1732 * bottom-edge of the child window. 1733 */ 1734 static void 1735 erase_childs_shadow(DIALOG_WINDOWS * dw) 1736 { 1737 if (UseShadow(dw)) { 1738 repaint_shadow(dw, 1739 FALSE, 1740 getbegy(dw->normal) - getbegy(dw->shadow), 1741 getbegx(dw->normal) - getbegx(dw->shadow), 1742 getmaxy(dw->normal), 1743 getmaxx(dw->normal)); 1744 } 1745 } 1746 1747 /* 1748 * Draw shadows along the right and bottom edge to give a more 3D look 1749 * to the boxes. 1750 */ 1751 void 1752 dlg_draw_shadow(WINDOW *win, int y, int x, int height, int width) 1753 { 1754 repaint_shadow(find_window(win), TRUE, y, x, height, width); 1755 } 1756 #endif /* HAVE_COLOR */ 1757 1758 /* 1759 * Allow shell scripts to remap the exit codes so they can distinguish ESC 1760 * from ERROR. 1761 */ 1762 void 1763 dlg_exit(int code) 1764 { 1765 /* *INDENT-OFF* */ 1766 static const struct { 1767 int code; 1768 const char *name; 1769 } table[] = { 1770 { DLG_EXIT_CANCEL, "DIALOG_CANCEL" }, 1771 { DLG_EXIT_ERROR, "DIALOG_ERROR" }, 1772 { DLG_EXIT_ESC, "DIALOG_ESC" }, 1773 { DLG_EXIT_EXTRA, "DIALOG_EXTRA" }, 1774 { DLG_EXIT_HELP, "DIALOG_HELP" }, 1775 { DLG_EXIT_OK, "DIALOG_OK" }, 1776 { DLG_EXIT_ITEM_HELP, "DIALOG_ITEM_HELP" }, 1777 }; 1778 /* *INDENT-ON* */ 1779 1780 unsigned n; 1781 char *name; 1782 char *temp; 1783 long value; 1784 bool overridden = FALSE; 1785 1786 retry: 1787 for (n = 0; n < TableSize(table); n++) { 1788 if (table[n].code == code) { 1789 if ((name = getenv(table[n].name)) != 0) { 1790 value = strtol(name, &temp, 0); 1791 if (temp != 0 && temp != name && *temp == '\0') { 1792 code = (int) value; 1793 overridden = TRUE; 1794 } 1795 } 1796 break; 1797 } 1798 } 1799 1800 /* 1801 * Prior to 2004/12/19, a widget using --item-help would exit with "OK" 1802 * if the help button were selected. Now we want to exit with "HELP", 1803 * but allow the environment variable to override. 1804 */ 1805 if (code == DLG_EXIT_ITEM_HELP && !overridden) { 1806 code = DLG_EXIT_HELP; 1807 goto retry; 1808 } 1809 #ifdef HAVE_DLG_TRACE 1810 dlg_trace((const char *) 0); /* close it */ 1811 #endif 1812 1813 #ifdef NO_LEAKS 1814 _dlg_inputstr_leaks(); 1815 #if defined(NCURSES_VERSION) && (defined(HAVE_CURSES_EXIT) || defined(HAVE__NC_FREE_AND_EXIT)) 1816 curses_exit(code); 1817 #endif 1818 #endif 1819 1820 if (dialog_state.input == stdin) { 1821 exit(code); 1822 } else { 1823 /* 1824 * Just in case of using --input-fd option, do not 1825 * call atexit functions of ncurses which may hang. 1826 */ 1827 if (dialog_state.input) { 1828 fclose(dialog_state.input); 1829 dialog_state.input = 0; 1830 } 1831 if (dialog_state.pipe_input) { 1832 if (dialog_state.pipe_input != stdin) { 1833 fclose(dialog_state.pipe_input); 1834 dialog_state.pipe_input = 0; 1835 } 1836 } 1837 _exit(code); 1838 } 1839 } 1840 1841 #define DATA(name) { DLG_EXIT_ ## name, #name } 1842 /* *INDENT-OFF* */ 1843 static struct { 1844 int code; 1845 const char *name; 1846 } exit_codenames[] = { 1847 DATA(ESC), 1848 DATA(UNKNOWN), 1849 DATA(ERROR), 1850 DATA(OK), 1851 DATA(CANCEL), 1852 DATA(HELP), 1853 DATA(EXTRA), 1854 DATA(ITEM_HELP), 1855 }; 1856 #undef DATA 1857 /* *INDENT-ON* */ 1858 1859 const char * 1860 dlg_exitcode2s(int code) 1861 { 1862 const char *result = "?"; 1863 size_t n; 1864 1865 for (n = 0; n < TableSize(exit_codenames); ++n) { 1866 if (exit_codenames[n].code == code) { 1867 result = exit_codenames[n].name; 1868 break; 1869 } 1870 } 1871 return result; 1872 } 1873 1874 int 1875 dlg_exitname2n(const char *name) 1876 { 1877 int result = DLG_EXIT_UNKNOWN; 1878 size_t n; 1879 1880 for (n = 0; n < TableSize(exit_codenames); ++n) { 1881 if (!dlg_strcmp(exit_codenames[n].name, name)) { 1882 result = exit_codenames[n].code; 1883 break; 1884 } 1885 } 1886 return result; 1887 } 1888 1889 /* quit program killing all tailbg */ 1890 void 1891 dlg_exiterr(const char *fmt, ...) 1892 { 1893 int retval; 1894 va_list ap; 1895 1896 end_dialog(); 1897 1898 (void) fputc('\n', stderr); 1899 va_start(ap, fmt); 1900 (void) vfprintf(stderr, fmt, ap); 1901 va_end(ap); 1902 (void) fputc('\n', stderr); 1903 1904 #ifdef HAVE_DLG_TRACE 1905 va_start(ap, fmt); 1906 dlg_trace_msg("## Error: "); 1907 dlg_trace_va_msg(fmt, ap); 1908 va_end(ap); 1909 #endif 1910 1911 dlg_killall_bg(&retval); 1912 1913 (void) fflush(stderr); 1914 (void) fflush(stdout); 1915 dlg_exit(DLG_EXIT_ERROR); 1916 } 1917 1918 void 1919 dlg_beeping(void) 1920 { 1921 if (dialog_vars.beep_signal) { 1922 (void) beep(); 1923 dialog_vars.beep_signal = 0; 1924 } 1925 } 1926 1927 void 1928 dlg_print_size(int height, int width) 1929 { 1930 if (dialog_vars.print_siz) { 1931 fprintf(dialog_state.output, "Size: %d, %d\n", height, width); 1932 DLG_TRACE(("# print size: %dx%d\n", height, width)); 1933 } 1934 } 1935 1936 void 1937 dlg_ctl_size(int height, int width) 1938 { 1939 if (dialog_vars.size_err) { 1940 if ((width > COLS) || (height > LINES)) { 1941 dlg_exiterr("Window too big. (height, width) = (%d, %d). Max allowed (%d, %d).", 1942 height, width, LINES, COLS); 1943 } 1944 #ifdef HAVE_COLOR 1945 else if ((dialog_state.use_shadow) 1946 && ((width > SCOLS || height > SLINES))) { 1947 if ((width <= COLS) && (height <= LINES)) { 1948 /* try again, without shadows */ 1949 dialog_state.use_shadow = 0; 1950 } else { 1951 dlg_exiterr("Window+Shadow too big. (height, width) = (%d, %d). Max allowed (%d, %d).", 1952 height, width, SLINES, SCOLS); 1953 } 1954 } 1955 #endif 1956 } 1957 } 1958 1959 /* 1960 * If the --tab-correct was not selected, convert tabs to single spaces. 1961 */ 1962 void 1963 dlg_tab_correct_str(char *prompt) 1964 { 1965 char *ptr; 1966 1967 if (dialog_vars.tab_correct) { 1968 while ((ptr = strchr(prompt, TAB)) != NULL) { 1969 *ptr = ' '; 1970 prompt = ptr; 1971 } 1972 } 1973 } 1974 1975 void 1976 dlg_calc_listh(int *height, int *list_height, int item_no) 1977 { 1978 /* calculate new height and list_height */ 1979 int rows = SLINES - (dialog_vars.begin_set ? dialog_vars.begin_y : 0); 1980 if (rows - (*height) > 0) { 1981 if (rows - (*height) > item_no) 1982 *list_height = item_no; 1983 else 1984 *list_height = rows - (*height); 1985 } 1986 (*height) += (*list_height); 1987 } 1988 1989 /* obsolete */ 1990 int 1991 dlg_calc_listw(int item_no, char **items, int group) 1992 { 1993 int i, len1 = 0, len2 = 0; 1994 1995 for (i = 0; i < (item_no * group); i += group) { 1996 int n; 1997 1998 if ((n = dlg_count_columns(items[i])) > len1) 1999 len1 = n; 2000 if ((n = dlg_count_columns(items[i + 1])) > len2) 2001 len2 = n; 2002 } 2003 return len1 + len2; 2004 } 2005 2006 int 2007 dlg_calc_list_width(int item_no, DIALOG_LISTITEM * items) 2008 { 2009 int n, i, len1 = 0, len2 = 0; 2010 int bits = ((dialog_vars.no_tags ? 1 : 0) 2011 + (dialog_vars.no_items ? 2 : 0)); 2012 2013 for (i = 0; i < item_no; ++i) { 2014 switch (bits) { 2015 case 0: 2016 /* FALLTHRU */ 2017 case 1: 2018 if ((n = dlg_count_columns(items[i].name)) > len1) 2019 len1 = n; 2020 if ((n = dlg_count_columns(items[i].text)) > len2) 2021 len2 = n; 2022 break; 2023 case 2: 2024 /* FALLTHRU */ 2025 case 3: 2026 if ((n = dlg_count_columns(items[i].name)) > len1) 2027 len1 = n; 2028 break; 2029 } 2030 } 2031 return len1 + len2; 2032 } 2033 2034 char * 2035 dlg_strempty(void) 2036 { 2037 static char empty[] = ""; 2038 return empty; 2039 } 2040 2041 char * 2042 dlg_strclone(const char *cprompt) 2043 { 2044 char *prompt = 0; 2045 if (cprompt != 0) { 2046 prompt = dlg_malloc(char, strlen(cprompt) + 1); 2047 assert_ptr(prompt, "dlg_strclone"); 2048 strcpy(prompt, cprompt); 2049 } 2050 return prompt; 2051 } 2052 2053 chtype 2054 dlg_asciibox(chtype ch) 2055 { 2056 chtype result = 0; 2057 2058 if (ch == ACS_ULCORNER) 2059 result = '+'; 2060 else if (ch == ACS_LLCORNER) 2061 result = '+'; 2062 else if (ch == ACS_URCORNER) 2063 result = '+'; 2064 else if (ch == ACS_LRCORNER) 2065 result = '+'; 2066 else if (ch == ACS_HLINE) 2067 result = '-'; 2068 else if (ch == ACS_VLINE) 2069 result = '|'; 2070 else if (ch == ACS_LTEE) 2071 result = '+'; 2072 else if (ch == ACS_RTEE) 2073 result = '+'; 2074 else if (ch == ACS_UARROW) 2075 result = '^'; 2076 else if (ch == ACS_DARROW) 2077 result = 'v'; 2078 2079 return result; 2080 } 2081 2082 chtype 2083 dlg_boxchar(chtype ch) 2084 { 2085 chtype result = dlg_asciibox(ch); 2086 2087 if (result != 0) { 2088 if (dialog_vars.ascii_lines) 2089 ch = result; 2090 else if (dialog_vars.no_lines) 2091 ch = ' '; 2092 } 2093 return ch; 2094 } 2095 2096 int 2097 dlg_box_x_ordinate(int width) 2098 { 2099 int x; 2100 2101 if (dialog_vars.begin_set == 1) { 2102 x = dialog_vars.begin_x; 2103 } else { 2104 /* center dialog box on screen unless --begin-set */ 2105 x = (SCOLS - width) / 2; 2106 } 2107 return x; 2108 } 2109 2110 int 2111 dlg_box_y_ordinate(int height) 2112 { 2113 int y; 2114 2115 if (dialog_vars.begin_set == 1) { 2116 y = dialog_vars.begin_y; 2117 } else { 2118 /* center dialog box on screen unless --begin-set */ 2119 y = (SLINES - height) / 2; 2120 } 2121 return y; 2122 } 2123 2124 void 2125 dlg_draw_title(WINDOW *win, const char *title) 2126 { 2127 if (title != NULL) { 2128 chtype attr = A_NORMAL; 2129 chtype save = dlg_get_attrs(win); 2130 int x = centered(getmaxx(win), title); 2131 2132 dlg_attrset(win, title_attr); 2133 wmove(win, 0, x); 2134 dlg_print_text(win, title, getmaxx(win) - x, &attr); 2135 dlg_attrset(win, save); 2136 dlg_finish_string(title); 2137 } 2138 } 2139 2140 void 2141 dlg_draw_bottom_box2(WINDOW *win, chtype on_left, chtype on_right, chtype on_inside) 2142 { 2143 int width = getmaxx(win); 2144 int height = getmaxy(win); 2145 int i; 2146 2147 dlg_attrset(win, on_left); 2148 (void) wmove(win, height - 3, 0); 2149 (void) waddch(win, dlg_boxchar(ACS_LTEE)); 2150 for (i = 0; i < width - 2; i++) 2151 (void) waddch(win, dlg_boxchar(ACS_HLINE)); 2152 dlg_attrset(win, on_right); 2153 (void) waddch(win, dlg_boxchar(ACS_RTEE)); 2154 dlg_attrset(win, on_inside); 2155 (void) wmove(win, height - 2, 1); 2156 for (i = 0; i < width - 2; i++) 2157 (void) waddch(win, ' '); 2158 } 2159 2160 void 2161 dlg_draw_bottom_box(WINDOW *win) 2162 { 2163 dlg_draw_bottom_box2(win, border_attr, dialog_attr, dialog_attr); 2164 } 2165 2166 /* 2167 * Remove a window, repainting everything else. This would be simpler if we 2168 * used the panel library, but that is not _always_ available. 2169 */ 2170 void 2171 dlg_del_window(WINDOW *win) 2172 { 2173 DIALOG_WINDOWS *p, *q, *r; 2174 2175 /* 2176 * If --keep-window was set, do not delete/repaint the windows. 2177 */ 2178 if (dialog_vars.keep_window) 2179 return; 2180 2181 /* Leave the main window untouched if there are no background windows. 2182 * We do this so the current window will not be cleared on exit, allowing 2183 * things like the infobox demo to run without flicker. 2184 */ 2185 if (dialog_state.getc_callbacks != 0) { 2186 touchwin(stdscr); 2187 wnoutrefresh(stdscr); 2188 } 2189 2190 for (p = dialog_state.all_windows, q = r = 0; p != 0; r = p, p = p->next) { 2191 if (p->normal == win) { 2192 q = p; /* found a match - should be only one */ 2193 if (r == 0) { 2194 dialog_state.all_windows = p->next; 2195 } else { 2196 r->next = p->next; 2197 } 2198 } else { 2199 if (p->shadow != 0) { 2200 touchwin(p->shadow); 2201 wnoutrefresh(p->shadow); 2202 } 2203 touchwin(p->normal); 2204 wnoutrefresh(p->normal); 2205 } 2206 } 2207 2208 if (q) { 2209 if (dialog_state.all_windows != 0) 2210 erase_childs_shadow(q); 2211 del_subwindows(q->normal); 2212 dlg_unregister_window(q->normal); 2213 delwin(q->normal); 2214 free(q); 2215 } 2216 doupdate(); 2217 } 2218 2219 /* 2220 * Create a window, optionally with a shadow. 2221 */ 2222 WINDOW * 2223 dlg_new_window(int height, int width, int y, int x) 2224 { 2225 return dlg_new_modal_window(stdscr, height, width, y, x); 2226 } 2227 2228 /* 2229 * "Modal" windows differ from normal ones by having a shadow in a window 2230 * separate from the standard screen. 2231 */ 2232 WINDOW * 2233 dlg_new_modal_window(WINDOW *parent, int height, int width, int y, int x) 2234 { 2235 WINDOW *win; 2236 DIALOG_WINDOWS *p = dlg_calloc(DIALOG_WINDOWS, 1); 2237 2238 (void) parent; 2239 if (p == 0 2240 || (win = newwin(height, width, y, x)) == 0) { 2241 dlg_exiterr("Can't make new window at (%d,%d), size (%d,%d).\n", 2242 y, x, height, width); 2243 } 2244 p->next = dialog_state.all_windows; 2245 p->normal = win; 2246 p->getc_timeout = WTIMEOUT_OFF; 2247 dialog_state.all_windows = p; 2248 #ifdef HAVE_COLOR 2249 if (dialog_state.use_shadow) { 2250 p->shadow = parent; 2251 draw_childs_shadow(p); 2252 } 2253 #endif 2254 2255 (void) keypad(win, TRUE); 2256 return win; 2257 } 2258 2259 /* 2260 * dlg_getc() uses the return-value to determine how to handle an ERR return 2261 * from a non-blocking read: 2262 * a) if greater than zero, there was an expired timeout (blocking for a short 2263 * time), or 2264 * b) if zero, it was a non-blocking read, or 2265 * c) if negative, an error occurred on a blocking read. 2266 */ 2267 int 2268 dlg_set_timeout(WINDOW *win, bool will_getc) 2269 { 2270 DIALOG_WINDOWS *p; 2271 int result = 0; 2272 int interval; 2273 2274 if ((p = find_window(win)) != NULL) { 2275 interval = (dialog_vars.timeout_secs * 1000); 2276 2277 if (will_getc) { 2278 interval = WTIMEOUT_VAL; 2279 } else { 2280 result = interval; 2281 if (interval <= 0) { 2282 interval = WTIMEOUT_OFF; 2283 } 2284 } 2285 wtimeout(win, interval); 2286 p->getc_timeout = interval; 2287 } 2288 return result; 2289 } 2290 2291 void 2292 dlg_reset_timeout(WINDOW *win) 2293 { 2294 DIALOG_WINDOWS *p; 2295 2296 if ((p = find_window(win)) != NULL) { 2297 wtimeout(win, p->getc_timeout); 2298 } else { 2299 wtimeout(win, WTIMEOUT_OFF); 2300 } 2301 } 2302 2303 /* 2304 * Move/Resize a window, optionally with a shadow. 2305 */ 2306 #ifdef KEY_RESIZE 2307 void 2308 dlg_move_window(WINDOW *win, int height, int width, int y, int x) 2309 { 2310 if (win != 0) { 2311 DIALOG_WINDOWS *p; 2312 2313 dlg_ctl_size(height, width); 2314 2315 if ((p = find_window(win)) != 0) { 2316 (void) wresize(win, height, width); 2317 (void) mvwin(win, y, x); 2318 #ifdef HAVE_COLOR 2319 if (p->shadow != 0) { 2320 if (dialog_state.use_shadow) { 2321 (void) mvwin(p->shadow, y + SHADOW_ROWS, x + SHADOW_COLS); 2322 } else { 2323 p->shadow = 0; 2324 } 2325 } 2326 #endif 2327 (void) refresh(); 2328 2329 #ifdef HAVE_COLOR 2330 draw_childs_shadow(p); 2331 #endif 2332 } 2333 } 2334 } 2335 2336 /* 2337 * Having just received a KEY_RESIZE, wait a short time to ignore followup 2338 * KEY_RESIZE events. 2339 */ 2340 void 2341 dlg_will_resize(WINDOW *win) 2342 { 2343 int n, base; 2344 int caught = 0; 2345 2346 dialog_state.had_resize = TRUE; 2347 dlg_trace_win(win); 2348 wtimeout(win, WTIMEOUT_VAL * 5); 2349 2350 for (n = base = 0; n < base + 10; ++n) { 2351 int ch; 2352 2353 if ((ch = wgetch(win)) != ERR) { 2354 if (ch == KEY_RESIZE) { 2355 base = n; 2356 ++caught; 2357 } else if (ch != ERR) { 2358 ungetch(ch); 2359 break; 2360 } 2361 } 2362 } 2363 dlg_reset_timeout(win); 2364 DLG_TRACE(("# caught %d KEY_RESIZE key%s\n", 2365 1 + caught, 2366 caught == 1 ? "" : "s")); 2367 } 2368 #endif /* KEY_RESIZE */ 2369 2370 WINDOW * 2371 dlg_sub_window(WINDOW *parent, int height, int width, int y, int x) 2372 { 2373 WINDOW *win; 2374 2375 if ((win = subwin(parent, height, width, y, x)) == 0) { 2376 dlg_exiterr("Can't make sub-window at (%d,%d), size (%d,%d).\n", 2377 y, x, height, width); 2378 } 2379 2380 add_subwindow(parent, win); 2381 (void) keypad(win, TRUE); 2382 return win; 2383 } 2384 2385 /* obsolete */ 2386 int 2387 dlg_default_item(char **items, int llen) 2388 { 2389 int result = 0; 2390 2391 if (dialog_vars.default_item != 0) { 2392 int count = 0; 2393 while (*items != 0) { 2394 if (!strcmp(dialog_vars.default_item, *items)) { 2395 result = count; 2396 break; 2397 } 2398 items += llen; 2399 count++; 2400 } 2401 } 2402 return result; 2403 } 2404 2405 int 2406 dlg_default_listitem(DIALOG_LISTITEM * items) 2407 { 2408 int result = 0; 2409 2410 if (dialog_vars.default_item != 0) { 2411 int count = 0; 2412 while (items->name != 0) { 2413 if (!strcmp(dialog_vars.default_item, items->name)) { 2414 result = count; 2415 break; 2416 } 2417 ++items; 2418 count++; 2419 } 2420 } 2421 return result; 2422 } 2423 2424 /* 2425 * Draw the string for item_help 2426 */ 2427 void 2428 dlg_item_help(const char *txt) 2429 { 2430 if (USE_ITEM_HELP(txt)) { 2431 chtype attr = A_NORMAL; 2432 2433 dlg_attrset(stdscr, itemhelp_attr); 2434 (void) wmove(stdscr, LINES - 1, 0); 2435 (void) wclrtoeol(stdscr); 2436 (void) addch(' '); 2437 dlg_print_text(stdscr, txt, COLS - 1, &attr); 2438 2439 if (itemhelp_attr & A_COLOR) { 2440 int y, x; 2441 /* fill the remainder of the line with the window's attributes */ 2442 getyx(stdscr, y, x); 2443 (void) y; 2444 while (x < COLS) { 2445 (void) addch(' '); 2446 ++x; 2447 } 2448 } 2449 (void) wnoutrefresh(stdscr); 2450 } 2451 } 2452 2453 #ifndef HAVE_STRCASECMP 2454 int 2455 dlg_strcmp(const char *a, const char *b) 2456 { 2457 int ac, bc, cmp; 2458 2459 for (;;) { 2460 ac = UCH(*a++); 2461 bc = UCH(*b++); 2462 if (isalpha(ac) && islower(ac)) 2463 ac = _toupper(ac); 2464 if (isalpha(bc) && islower(bc)) 2465 bc = _toupper(bc); 2466 cmp = ac - bc; 2467 if (ac == 0 || bc == 0 || cmp != 0) 2468 break; 2469 } 2470 return cmp; 2471 } 2472 #endif 2473 2474 /* 2475 * Returns true if 'dst' points to a blank which follows another blank which 2476 * is not a leading blank on a line. 2477 */ 2478 static bool 2479 trim_blank(char *base, char *dst) 2480 { 2481 int count = !!isblank(UCH(*dst)); 2482 2483 while (dst-- != base) { 2484 if (*dst == '\n') { 2485 break; 2486 } else if (isblank(UCH(*dst))) { 2487 count++; 2488 } else { 2489 break; 2490 } 2491 } 2492 return (count > 1); 2493 } 2494 2495 /* 2496 * Change embedded "\n" substrings to '\n' characters and tabs to single 2497 * spaces. If there are no "\n"s, it will strip all extra spaces, for 2498 * justification. If it has "\n"'s, it will preserve extra spaces. If cr_wrap 2499 * is set, it will preserve '\n's. 2500 */ 2501 void 2502 dlg_trim_string(char *s) 2503 { 2504 char *base = s; 2505 char *p1; 2506 char *p = s; 2507 int has_newlines = !dialog_vars.no_nl_expand && (strstr(s, "\\n") != 0); 2508 2509 while (*p != '\0') { 2510 if (*p == TAB && !dialog_vars.nocollapse) 2511 *p = ' '; 2512 2513 if (has_newlines) { /* If prompt contains "\n" strings */ 2514 if (*p == '\\' && *(p + 1) == 'n') { 2515 *s++ = '\n'; 2516 p += 2; 2517 p1 = p; 2518 /* 2519 * Handle end of lines intelligently. If '\n' follows "\n" 2520 * then ignore the '\n'. This eliminates the need to escape 2521 * the '\n' character (no need to use "\n\"). 2522 */ 2523 while (isblank(UCH(*p1))) 2524 p1++; 2525 if (*p1 == '\n') 2526 p = p1 + 1; 2527 } else if (*p == '\n') { 2528 if (dialog_vars.cr_wrap) 2529 *s++ = *p++; 2530 else { 2531 /* Replace the '\n' with a space if cr_wrap is not set */ 2532 if (!trim_blank(base, p)) 2533 *s++ = ' '; 2534 p++; 2535 } 2536 } else /* If *p != '\n' */ 2537 *s++ = *p++; 2538 } else if (dialog_vars.trim_whitespace) { 2539 if (isblank(UCH(*p))) { 2540 if (!isblank(UCH(*(s - 1)))) { 2541 *s++ = ' '; 2542 p++; 2543 } else 2544 p++; 2545 } else if (*p == '\n') { 2546 if (dialog_vars.cr_wrap) 2547 *s++ = *p++; 2548 else if (!isblank(UCH(*(s - 1)))) { 2549 /* Strip '\n's if cr_wrap is not set. */ 2550 *s++ = ' '; 2551 p++; 2552 } else 2553 p++; 2554 } else 2555 *s++ = *p++; 2556 } else { /* If there are no "\n" strings */ 2557 if (isblank(UCH(*p)) && !dialog_vars.nocollapse) { 2558 if (!trim_blank(base, p)) 2559 *s++ = *p; 2560 p++; 2561 } else 2562 *s++ = *p++; 2563 } 2564 } 2565 2566 *s = '\0'; 2567 } 2568 2569 void 2570 dlg_set_focus(WINDOW *parent, WINDOW *win) 2571 { 2572 if (win != 0) { 2573 (void) wmove(parent, 2574 getpary(win) + getcury(win), 2575 getparx(win) + getcurx(win)); 2576 (void) wnoutrefresh(win); 2577 (void) doupdate(); 2578 } 2579 } 2580 2581 /* 2582 * Returns the nominal maximum buffer size. 2583 */ 2584 int 2585 dlg_max_input(int max_len) 2586 { 2587 if (dialog_vars.max_input != 0 && dialog_vars.max_input < MAX_LEN) 2588 max_len = dialog_vars.max_input; 2589 2590 return max_len; 2591 } 2592 2593 /* 2594 * Free storage used for the result buffer. 2595 */ 2596 void 2597 dlg_clr_result(void) 2598 { 2599 if (dialog_vars.input_length) { 2600 dialog_vars.input_length = 0; 2601 if (dialog_vars.input_result) 2602 free(dialog_vars.input_result); 2603 } 2604 dialog_vars.input_result = 0; 2605 } 2606 2607 /* 2608 * Setup a fixed-buffer for the result. 2609 */ 2610 char * 2611 dlg_set_result(const char *string) 2612 { 2613 unsigned need = string ? (unsigned) strlen(string) + 1 : 0; 2614 2615 /* inputstr.c needs a fixed buffer */ 2616 if (need < MAX_LEN) 2617 need = MAX_LEN; 2618 2619 /* 2620 * If the buffer is not big enough, allocate a new one. 2621 */ 2622 if (dialog_vars.input_length != 0 2623 || dialog_vars.input_result == 0 2624 || need > MAX_LEN) { 2625 2626 dlg_clr_result(); 2627 2628 dialog_vars.input_length = need; 2629 dialog_vars.input_result = dlg_malloc(char, need); 2630 assert_ptr(dialog_vars.input_result, "dlg_set_result"); 2631 } 2632 2633 strcpy(dialog_vars.input_result, string ? string : ""); 2634 2635 return dialog_vars.input_result; 2636 } 2637 2638 /* 2639 * Accumulate results in dynamically allocated buffer. 2640 * If input_length is zero, it is a MAX_LEN buffer belonging to the caller. 2641 */ 2642 void 2643 dlg_add_result(const char *string) 2644 { 2645 unsigned have = (dialog_vars.input_result 2646 ? (unsigned) strlen(dialog_vars.input_result) 2647 : 0); 2648 unsigned want = (unsigned) strlen(string) + 1 + have; 2649 2650 if ((want >= MAX_LEN) 2651 || (dialog_vars.input_length != 0) 2652 || (dialog_vars.input_result == 0)) { 2653 2654 if (dialog_vars.input_length == 0 2655 || dialog_vars.input_result == 0) { 2656 2657 char *save_result = dialog_vars.input_result; 2658 2659 dialog_vars.input_length = want * 2; 2660 dialog_vars.input_result = dlg_malloc(char, dialog_vars.input_length); 2661 assert_ptr(dialog_vars.input_result, "dlg_add_result malloc"); 2662 dialog_vars.input_result[0] = '\0'; 2663 if (save_result != 0) 2664 strcpy(dialog_vars.input_result, save_result); 2665 } else if (want >= dialog_vars.input_length) { 2666 dialog_vars.input_length = want * 2; 2667 dialog_vars.input_result = dlg_realloc(char, 2668 dialog_vars.input_length, 2669 dialog_vars.input_result); 2670 assert_ptr(dialog_vars.input_result, "dlg_add_result realloc"); 2671 } 2672 } 2673 strcat(dialog_vars.input_result, string); 2674 } 2675 2676 /* 2677 * These are characters that (aside from the quote-delimiter) will have to 2678 * be escaped in a single- or double-quoted string. 2679 */ 2680 #define FIX_SINGLE "\n\\" 2681 #define FIX_DOUBLE FIX_SINGLE "[]{}?*;`~#$^&()|<>" 2682 2683 /* 2684 * Returns the quote-delimiter. 2685 */ 2686 static const char * 2687 quote_delimiter(void) 2688 { 2689 return dialog_vars.single_quoted ? "'" : "\""; 2690 } 2691 2692 /* 2693 * Returns true if we should quote the given string. 2694 */ 2695 static bool 2696 must_quote(char *string) 2697 { 2698 bool code = FALSE; 2699 2700 if (*string != '\0') { 2701 size_t len = strlen(string); 2702 if (strcspn(string, quote_delimiter()) != len) 2703 code = TRUE; 2704 else if (strcspn(string, "\n\t ") != len) 2705 code = TRUE; 2706 else 2707 code = (strcspn(string, FIX_DOUBLE) != len); 2708 } else { 2709 code = TRUE; 2710 } 2711 2712 return code; 2713 } 2714 2715 /* 2716 * Add a quoted string to the result buffer. 2717 */ 2718 void 2719 dlg_add_quoted(char *string) 2720 { 2721 char temp[2]; 2722 const char *my_quote = quote_delimiter(); 2723 const char *must_fix = (dialog_vars.single_quoted 2724 ? FIX_SINGLE 2725 : FIX_DOUBLE); 2726 2727 if (must_quote(string)) { 2728 temp[1] = '\0'; 2729 dlg_add_result(my_quote); 2730 while (*string != '\0') { 2731 temp[0] = *string++; 2732 if ((strchr) (my_quote, *temp) || (strchr) (must_fix, *temp)) 2733 dlg_add_result("\\"); 2734 dlg_add_result(temp); 2735 } 2736 dlg_add_result(my_quote); 2737 } else { 2738 dlg_add_result(string); 2739 } 2740 } 2741 2742 /* 2743 * When adding a result, make that depend on whether "--quoted" is used. 2744 */ 2745 void 2746 dlg_add_string(char *string) 2747 { 2748 if (dialog_vars.quoted) { 2749 dlg_add_quoted(string); 2750 } else { 2751 dlg_add_result(string); 2752 } 2753 } 2754 2755 bool 2756 dlg_need_separator(void) 2757 { 2758 bool result = FALSE; 2759 2760 if (dialog_vars.output_separator) { 2761 result = TRUE; 2762 } else if (dialog_vars.input_result && *(dialog_vars.input_result)) { 2763 result = TRUE; 2764 } 2765 return result; 2766 } 2767 2768 void 2769 dlg_add_separator(void) 2770 { 2771 const char *separator = (dialog_vars.separate_output) ? "\n" : " "; 2772 2773 if (dialog_vars.output_separator) 2774 separator = dialog_vars.output_separator; 2775 2776 dlg_add_result(separator); 2777 } 2778 2779 #define HELP_PREFIX "HELP " 2780 2781 void 2782 dlg_add_help_listitem(int *result, char **tag, DIALOG_LISTITEM * item) 2783 { 2784 dlg_add_result(HELP_PREFIX); 2785 if (USE_ITEM_HELP(item->help)) { 2786 *tag = dialog_vars.help_tags ? item->name : item->help; 2787 *result = DLG_EXIT_ITEM_HELP; 2788 } else { 2789 *tag = item->name; 2790 } 2791 } 2792 2793 void 2794 dlg_add_help_formitem(int *result, char **tag, DIALOG_FORMITEM * item) 2795 { 2796 dlg_add_result(HELP_PREFIX); 2797 if (USE_ITEM_HELP(item->help)) { 2798 *tag = dialog_vars.help_tags ? item->name : item->help; 2799 *result = DLG_EXIT_ITEM_HELP; 2800 } else { 2801 *tag = item->name; 2802 } 2803 } 2804 2805 /* 2806 * Some widgets support only one value of a given variable - save/restore the 2807 * global dialog_vars so we can override it consistently. 2808 */ 2809 void 2810 dlg_save_vars(DIALOG_VARS * vars) 2811 { 2812 *vars = dialog_vars; 2813 } 2814 2815 /* 2816 * Most of the data in DIALOG_VARS is normally set by command-line options. 2817 * The input_result member is an exception; it is normally set by the dialog 2818 * library to return result values. 2819 */ 2820 void 2821 dlg_restore_vars(DIALOG_VARS * vars) 2822 { 2823 char *save_result = dialog_vars.input_result; 2824 unsigned save_length = dialog_vars.input_length; 2825 2826 dialog_vars = *vars; 2827 dialog_vars.input_result = save_result; 2828 dialog_vars.input_length = save_length; 2829 } 2830 2831 /* 2832 * Called each time a widget is invoked which may do output, increment a count. 2833 */ 2834 void 2835 dlg_does_output(void) 2836 { 2837 dialog_state.output_count += 1; 2838 } 2839 2840 /* 2841 * Compatibility for different versions of curses. 2842 */ 2843 #if !(defined(HAVE_GETBEGX) && defined(HAVE_GETBEGY)) 2844 int 2845 dlg_getbegx(WINDOW *win) 2846 { 2847 int y, x; 2848 getbegyx(win, y, x); 2849 return x; 2850 } 2851 int 2852 dlg_getbegy(WINDOW *win) 2853 { 2854 int y, x; 2855 getbegyx(win, y, x); 2856 return y; 2857 } 2858 #endif 2859 2860 #if !(defined(HAVE_GETCURX) && defined(HAVE_GETCURY)) 2861 int 2862 dlg_getcurx(WINDOW *win) 2863 { 2864 int y, x; 2865 getyx(win, y, x); 2866 return x; 2867 } 2868 int 2869 dlg_getcury(WINDOW *win) 2870 { 2871 int y, x; 2872 getyx(win, y, x); 2873 return y; 2874 } 2875 #endif 2876 2877 #if !(defined(HAVE_GETMAXX) && defined(HAVE_GETMAXY)) 2878 int 2879 dlg_getmaxx(WINDOW *win) 2880 { 2881 int y, x; 2882 getmaxyx(win, y, x); 2883 return x; 2884 } 2885 int 2886 dlg_getmaxy(WINDOW *win) 2887 { 2888 int y, x; 2889 getmaxyx(win, y, x); 2890 return y; 2891 } 2892 #endif 2893 2894 #if !(defined(HAVE_GETPARX) && defined(HAVE_GETPARY)) 2895 int 2896 dlg_getparx(WINDOW *win) 2897 { 2898 int y, x; 2899 getparyx(win, y, x); 2900 return x; 2901 } 2902 int 2903 dlg_getpary(WINDOW *win) 2904 { 2905 int y, x; 2906 getparyx(win, y, x); 2907 return y; 2908 } 2909 #endif 2910 2911 #ifdef NEED_WGETPARENT 2912 WINDOW * 2913 dlg_wgetparent(WINDOW *win) 2914 { 2915 #undef wgetparent 2916 WINDOW *result = 0; 2917 DIALOG_WINDOWS *p; 2918 2919 for (p = dialog_state.all_subwindows; p != 0; p = p->next) { 2920 if (p->shadow == win) { 2921 result = p->normal; 2922 break; 2923 } 2924 } 2925 return result; 2926 } 2927 #endif 2928