1 /* 2 * $Id: util.c,v 1.211 2011/01/19 00:31:43 tom Exp $ 3 * 4 * util.c -- miscellaneous utilities for dialog 5 * 6 * Copyright 2000-2010,2011 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 30 #ifdef NCURSES_VERSION 31 #if defined(HAVE_NCURSESW_TERM_H) 32 #include <ncursesw/term.h> 33 #elif defined(HAVE_NCURSES_TERM_H) 34 #include <ncurses/term.h> 35 #else 36 #include <term.h> 37 #endif 38 #endif 39 40 /* globals */ 41 DIALOG_STATE dialog_state; 42 DIALOG_VARS dialog_vars; 43 44 #define concat(a,b) a##b 45 46 #ifdef HAVE_RC_FILE 47 #define RC_DATA(name,comment) , #name "_color", comment " color" 48 #else 49 #define RC_DATA(name,comment) /*nothing */ 50 #endif 51 52 #ifdef HAVE_COLOR 53 #include <dlg_colors.h> 54 #define COLOR_DATA(upr) , \ 55 concat(DLGC_FG_,upr), \ 56 concat(DLGC_BG_,upr), \ 57 concat(DLGC_HL_,upr) 58 #else 59 #define COLOR_DATA(upr) /*nothing */ 60 #endif 61 62 #define DATA(atr,upr,lwr,cmt) { atr COLOR_DATA(upr) RC_DATA(lwr,cmt) } 63 64 /* 65 * Table of color and attribute values, default is for mono display. 66 */ 67 /* *INDENT-OFF* */ 68 DIALOG_COLORS dlg_color_table[] = 69 { 70 DATA(A_NORMAL, SCREEN, screen, "Screen"), 71 DATA(A_NORMAL, SHADOW, shadow, "Shadow"), 72 DATA(A_REVERSE, DIALOG, dialog, "Dialog box"), 73 DATA(A_REVERSE, TITLE, title, "Dialog box title"), 74 DATA(A_REVERSE, BORDER, border, "Dialog box border"), 75 DATA(A_BOLD, BUTTON_ACTIVE, button_active, "Active button"), 76 DATA(A_DIM, BUTTON_INACTIVE, button_inactive, "Inactive button"), 77 DATA(A_UNDERLINE, BUTTON_KEY_ACTIVE, button_key_active, "Active button key"), 78 DATA(A_UNDERLINE, BUTTON_KEY_INACTIVE, button_key_inactive, "Inactive button key"), 79 DATA(A_NORMAL, BUTTON_LABEL_ACTIVE, button_label_active, "Active button label"), 80 DATA(A_NORMAL, BUTTON_LABEL_INACTIVE, button_label_inactive, "Inactive button label"), 81 DATA(A_REVERSE, INPUTBOX, inputbox, "Input box"), 82 DATA(A_REVERSE, INPUTBOX_BORDER, inputbox_border, "Input box border"), 83 DATA(A_REVERSE, SEARCHBOX, searchbox, "Search box"), 84 DATA(A_REVERSE, SEARCHBOX_TITLE, searchbox_title, "Search box title"), 85 DATA(A_REVERSE, SEARCHBOX_BORDER, searchbox_border, "Search box border"), 86 DATA(A_REVERSE, POSITION_INDICATOR, position_indicator, "File position indicator"), 87 DATA(A_REVERSE, MENUBOX, menubox, "Menu box"), 88 DATA(A_REVERSE, MENUBOX_BORDER, menubox_border, "Menu box border"), 89 DATA(A_REVERSE, ITEM, item, "Item"), 90 DATA(A_NORMAL, ITEM_SELECTED, item_selected, "Selected item"), 91 DATA(A_REVERSE, TAG, tag, "Tag"), 92 DATA(A_REVERSE, TAG_SELECTED, tag_selected, "Selected tag"), 93 DATA(A_NORMAL, TAG_KEY, tag_key, "Tag key"), 94 DATA(A_BOLD, TAG_KEY_SELECTED, tag_key_selected, "Selected tag key"), 95 DATA(A_REVERSE, CHECK, check, "Check box"), 96 DATA(A_REVERSE, CHECK_SELECTED, check_selected, "Selected check box"), 97 DATA(A_REVERSE, UARROW, uarrow, "Up arrow"), 98 DATA(A_REVERSE, DARROW, darrow, "Down arrow"), 99 DATA(A_NORMAL, ITEMHELP, itemhelp, "Item help-text"), 100 DATA(A_BOLD, FORM_ACTIVE_TEXT, form_active_text, "Active form text"), 101 DATA(A_REVERSE, FORM_TEXT, form_text, "Form text"), 102 DATA(A_NORMAL, FORM_ITEM_READONLY, form_item_readonly, "Readonly form item"), 103 DATA(A_REVERSE, GAUGE, gauge, "Dialog box gauge") 104 }; 105 /* *INDENT-ON* */ 106 107 /* 108 * Display background title if it exists ... 109 */ 110 void 111 dlg_put_backtitle(void) 112 { 113 int i; 114 115 if (dialog_vars.backtitle != NULL) { 116 chtype attr = A_NORMAL; 117 int backwidth = dlg_count_columns(dialog_vars.backtitle); 118 119 wattrset(stdscr, screen_attr); 120 (void) wmove(stdscr, 0, 1); 121 dlg_print_text(stdscr, dialog_vars.backtitle, COLS - 2, &attr); 122 for (i = 0; i < COLS - backwidth; i++) 123 (void) waddch(stdscr, ' '); 124 (void) wmove(stdscr, 1, 1); 125 for (i = 0; i < COLS - 2; i++) 126 (void) waddch(stdscr, dlg_boxchar(ACS_HLINE)); 127 } 128 129 (void) wnoutrefresh(stdscr); 130 } 131 132 /* 133 * Set window to attribute 'attr'. There are more efficient ways to do this, 134 * but will not work on older/buggy ncurses versions. 135 */ 136 void 137 dlg_attr_clear(WINDOW *win, int height, int width, chtype attr) 138 { 139 int i, j; 140 141 wattrset(win, attr); 142 for (i = 0; i < height; i++) { 143 (void) wmove(win, i, 0); 144 for (j = 0; j < width; j++) 145 (void) waddch(win, ' '); 146 } 147 (void) touchwin(win); 148 } 149 150 void 151 dlg_clear(void) 152 { 153 dlg_attr_clear(stdscr, LINES, COLS, screen_attr); 154 } 155 156 #define isprivate(s) ((s) != 0 && strstr(s, "\033[?") != 0) 157 158 #define TTY_DEVICE "/dev/tty" 159 160 /* 161 * If $DIALOG_TTY exists, allow the program to try to open the terminal 162 * directly when stdout is redirected. By default we require the "--stdout" 163 * option to be given, but some scripts were written making use of the 164 * behavior of dialog which tried opening the terminal anyway. 165 */ 166 static char * 167 dialog_tty(void) 168 { 169 char *result = getenv("DIALOG_TTY"); 170 if (result != 0 && atoi(result) == 0) 171 result = 0; 172 return result; 173 } 174 175 /* 176 * Open the terminal directly. If one of stdin, stdout or stderr really points 177 * to a tty, use it. Otherwise give up and open /dev/tty. 178 */ 179 static int 180 open_terminal(char **result, int mode) 181 { 182 const char *device = TTY_DEVICE; 183 if (!isatty(fileno(stderr)) 184 || (device = ttyname(fileno(stderr))) == 0) { 185 if (!isatty(fileno(stdout)) 186 || (device = ttyname(fileno(stdout))) == 0) { 187 if (!isatty(fileno(stdin)) 188 || (device = ttyname(fileno(stdin))) == 0) { 189 device = TTY_DEVICE; 190 } 191 } 192 } 193 *result = dlg_strclone(device); 194 return open(device, mode); 195 } 196 197 /* 198 * Do some initialization for dialog. 199 * 200 * 'input' is the real tty input of dialog. Usually it is stdin, but if 201 * --input-fd option is used, it may be anything. 202 * 203 * 'output' is where dialog will send its result. Usually it is stderr, but 204 * if --stdout or --output-fd is used, it may be anything. We are concerned 205 * mainly with the case where it happens to be the same as stdout. 206 */ 207 void 208 init_dialog(FILE *input, FILE *output) 209 { 210 int fd1, fd2; 211 char *device = 0; 212 213 dialog_state.output = output; 214 dialog_state.tab_len = TAB_LEN; 215 dialog_state.aspect_ratio = DEFAULT_ASPECT_RATIO; 216 #ifdef HAVE_COLOR 217 dialog_state.use_colors = USE_COLORS; /* use colors by default? */ 218 dialog_state.use_shadow = USE_SHADOW; /* shadow dialog boxes by default? */ 219 #endif 220 221 #ifdef HAVE_RC_FILE 222 if (dlg_parse_rc() == -1) /* Read the configuration file */ 223 dlg_exiterr("init_dialog: dlg_parse_rc"); 224 #endif 225 226 /* 227 * Some widgets (such as gauge) may read from the standard input. Pipes 228 * only connect stdout/stdin, so there is not much choice. But reading a 229 * pipe would get in the way of curses' normal reading stdin for getch. 230 * 231 * As in the --stdout (see below), reopening the terminal does not always 232 * work properly. dialog provides a --pipe-fd option for this purpose. We 233 * test that case first (differing fileno's for input/stdin). If the 234 * fileno's are equal, but we're not reading from a tty, see if we can open 235 * /dev/tty. 236 */ 237 dialog_state.pipe_input = stdin; 238 if (fileno(input) != fileno(stdin)) { 239 if ((fd1 = dup(fileno(input))) >= 0 240 && (fd2 = dup(fileno(stdin))) >= 0) { 241 (void) dup2(fileno(input), fileno(stdin)); 242 dialog_state.pipe_input = fdopen(fd2, "r"); 243 if (fileno(stdin) != 0) /* some functions may read fd #0 */ 244 (void) dup2(fileno(stdin), 0); 245 } else 246 dlg_exiterr("cannot open tty-input"); 247 } else if (!isatty(fileno(stdin))) { 248 if ((fd1 = open_terminal(&device, O_RDONLY)) >= 0 249 && (fd2 = dup(fileno(stdin))) >= 0) { 250 dialog_state.pipe_input = fdopen(fd2, "r"); 251 if (freopen(device, "r", stdin) == 0) 252 dlg_exiterr("cannot open tty-input"); 253 if (fileno(stdin) != 0) /* some functions may read fd #0 */ 254 (void) dup2(fileno(stdin), 0); 255 } 256 free(device); 257 } 258 259 /* 260 * If stdout is not a tty and dialog is called with the --stdout option, we 261 * have to provide for a way to write to the screen. 262 * 263 * The curses library normally writes its output to stdout, leaving stderr 264 * free for scripting. Scripts are simpler when stdout is redirected. The 265 * newterm function is useful; it allows us to specify where the output 266 * goes. Reopening the terminal is not portable since several 267 * configurations do not allow this to work properly: 268 * 269 * a) some getty implementations (and possibly broken tty drivers, e.g., on 270 * HPUX 10 and 11) cause stdin to act as if it is still in cooked mode 271 * even though results from ioctl's state that it is successfully 272 * altered to raw mode. Broken is the proper term. 273 * 274 * b) the user may not have permissions on the device, e.g., if one su's 275 * from the login user to another non-privileged user. 276 */ 277 if (!isatty(fileno(stdout)) 278 && (fileno(stdout) == fileno(output) || dialog_tty())) { 279 if ((fd1 = open_terminal(&device, O_WRONLY)) >= 0 280 && (dialog_state.screen_output = fdopen(fd1, "w")) != 0) { 281 if (newterm(NULL, dialog_state.screen_output, stdin) == 0) { 282 dlg_exiterr("cannot initialize curses"); 283 } 284 free(device); 285 } else { 286 dlg_exiterr("cannot open tty-output"); 287 } 288 } else { 289 dialog_state.screen_output = stdout; 290 (void) initscr(); 291 } 292 #ifdef NCURSES_VERSION 293 /* 294 * Cancel xterm's alternate-screen mode. 295 */ 296 if (!dialog_vars.keep_tite 297 && (dialog_state.screen_output != stdout 298 || isatty(fileno(dialog_state.screen_output))) 299 && key_mouse != 0 /* xterm and kindred */ 300 && isprivate(enter_ca_mode) 301 && isprivate(exit_ca_mode)) { 302 /* 303 * initscr() or newterm() already did putp(enter_ca_mode) as a side 304 * effect of initializing the screen. It would be nice to not even 305 * do that, but we do not really have access to the correct copy of 306 * the terminfo description until those functions have been invoked. 307 */ 308 (void) putp(exit_ca_mode); 309 (void) putp(clear_screen); 310 /* 311 * Prevent ncurses from switching "back" to the normal screen when 312 * exiting from dialog. That would move the cursor to the original 313 * location saved in xterm. Normally curses sets the cursor position 314 * to the first line after the display, but the alternate screen 315 * switching is done after that point. 316 * 317 * Cancelling the strings altogether also works around the buggy 318 * implementation of alternate-screen in rxvt, etc., which clear 319 * more of the display than they should. 320 */ 321 enter_ca_mode = 0; 322 exit_ca_mode = 0; 323 } 324 #endif 325 #ifdef HAVE_FLUSHINP 326 (void) flushinp(); 327 #endif 328 (void) keypad(stdscr, TRUE); 329 (void) cbreak(); 330 (void) noecho(); 331 332 if (!dialog_state.no_mouse) { 333 mouse_open(); 334 } 335 336 dialog_state.screen_initialized = TRUE; 337 338 #ifdef HAVE_COLOR 339 if (dialog_state.use_colors || dialog_state.use_shadow) 340 dlg_color_setup(); /* Set up colors */ 341 #endif 342 343 /* Set screen to screen attribute */ 344 dlg_clear(); 345 } 346 347 #ifdef HAVE_COLOR 348 static int defined_colors = 1; /* pair-0 is reserved */ 349 /* 350 * Setup for color display 351 */ 352 void 353 dlg_color_setup(void) 354 { 355 unsigned i; 356 357 if (has_colors()) { /* Terminal supports color? */ 358 (void) start_color(); 359 360 #if defined(HAVE_USE_DEFAULT_COLORS) 361 use_default_colors(); 362 #endif 363 364 #if defined(__NetBSD__) && defined(_CURSES_) 365 #define C_ATTR(x,y) (((x) != 0 ? A_BOLD : 0) | COLOR_PAIR((y))) 366 /* work around bug in NetBSD curses */ 367 for (i = 0; i < sizeof(dlg_color_table) / 368 sizeof(dlg_color_table[0]); i++) { 369 370 /* Initialize color pairs */ 371 (void) init_pair(i + 1, 372 dlg_color_table[i].fg, 373 dlg_color_table[i].bg); 374 375 /* Setup color attributes */ 376 dlg_color_table[i].atr = C_ATTR(dlg_color_table[i].hilite, i + 1); 377 } 378 defined_colors = i + 1; 379 #else 380 for (i = 0; i < sizeof(dlg_color_table) / 381 sizeof(dlg_color_table[0]); i++) { 382 383 /* Initialize color pairs */ 384 chtype color = dlg_color_pair(dlg_color_table[i].fg, 385 dlg_color_table[i].bg); 386 387 /* Setup color attributes */ 388 dlg_color_table[i].atr = ((dlg_color_table[i].hilite 389 ? A_BOLD 390 : 0) 391 | color); 392 } 393 #endif 394 } else { 395 dialog_state.use_colors = FALSE; 396 dialog_state.use_shadow = FALSE; 397 } 398 } 399 400 int 401 dlg_color_count(void) 402 { 403 return sizeof(dlg_color_table) / sizeof(dlg_color_table[0]); 404 } 405 406 /* 407 * Wrapper for getattrs(), or the more cumbersome X/Open wattr_get(). 408 */ 409 chtype 410 dlg_get_attrs(WINDOW *win) 411 { 412 chtype result; 413 #ifdef HAVE_GETATTRS 414 result = getattrs(win); 415 #else 416 attr_t my_result; 417 short my_pair; 418 wattr_get(win, &my_result, &my_pair, NULL); 419 result = my_result; 420 #endif 421 return result; 422 } 423 424 /* 425 * Reuse color pairs (they are limited), returning a COLOR_PAIR() value if we 426 * have (or can) define a pair with the given color as foreground on the 427 * window's defined background. 428 */ 429 chtype 430 dlg_color_pair(int foreground, int background) 431 { 432 chtype result = 0; 433 int pair; 434 short fg, bg; 435 bool found = FALSE; 436 437 for (pair = 1; pair < defined_colors; ++pair) { 438 if (pair_content((short) pair, &fg, &bg) != ERR 439 && fg == foreground 440 && bg == background) { 441 result = (chtype) COLOR_PAIR(pair); 442 found = TRUE; 443 break; 444 } 445 } 446 if (!found && (defined_colors + 1) < COLOR_PAIRS) { 447 pair = defined_colors++; 448 (void) init_pair((short) pair, (short) foreground, (short) background); 449 result = (chtype) COLOR_PAIR(pair); 450 } 451 return result; 452 } 453 454 /* 455 * Reuse color pairs (they are limited), returning a COLOR_PAIR() value if we 456 * have (or can) define a pair with the given color as foreground on the 457 * window's defined background. 458 */ 459 static chtype 460 define_color(WINDOW *win, int foreground) 461 { 462 chtype attrs = dlg_get_attrs(win); 463 int pair; 464 short fg, bg, background; 465 466 if ((pair = PAIR_NUMBER(attrs)) != 0 467 && pair_content((short) pair, &fg, &bg) != ERR) { 468 background = bg; 469 } else { 470 background = COLOR_BLACK; 471 } 472 return dlg_color_pair(foreground, background); 473 } 474 #endif 475 476 /* 477 * End using dialog functions. 478 */ 479 void 480 end_dialog(void) 481 { 482 if (dialog_state.screen_initialized) { 483 dialog_state.screen_initialized = FALSE; 484 mouse_close(); 485 (void) endwin(); 486 (void) fflush(stdout); 487 } 488 } 489 490 #define isOurEscape(p) (((p)[0] == '\\') && ((p)[1] == 'Z') && ((p)[2] != 0)) 491 492 static int 493 centered(int width, const char *string) 494 { 495 int len = dlg_count_columns(string); 496 int left; 497 int hide = 0; 498 int n; 499 500 if (dialog_vars.colors) { 501 for (n = 0; n < len; ++n) { 502 if (isOurEscape(string + n)) { 503 hide += 3; 504 } 505 } 506 } 507 left = (width - (len - hide)) / 2 - 1; 508 if (left < 0) 509 left = 0; 510 return left; 511 } 512 513 #ifdef USE_WIDE_CURSES 514 static bool 515 is_combining(const char *txt, int *combined) 516 { 517 bool result = FALSE; 518 519 if (*combined == 0) { 520 if (UCH(*txt) >= 128) { 521 wchar_t wch; 522 mbstate_t state; 523 size_t given = strlen(txt); 524 size_t len; 525 526 memset(&state, 0, sizeof(state)); 527 len = mbrtowc(&wch, txt, given, &state); 528 if ((int) len > 0 && wcwidth(wch) == 0) { 529 *combined = (int) len - 1; 530 result = TRUE; 531 } 532 } 533 } else { 534 result = TRUE; 535 *combined -= 1; 536 } 537 return result; 538 } 539 #endif 540 541 /* 542 * Print up to 'cols' columns from 'text', optionally rendering our escape 543 * sequence for attributes and color. 544 */ 545 void 546 dlg_print_text(WINDOW *win, const char *txt, int cols, chtype *attr) 547 { 548 int y_origin, x_origin; 549 int y_before, x_before = 0; 550 int y_after, x_after; 551 int tabbed = 0; 552 bool thisTab; 553 bool ended = FALSE; 554 chtype useattr; 555 #ifdef USE_WIDE_CURSES 556 int combined = 0; 557 #endif 558 559 getyx(win, y_origin, x_origin); 560 while (cols > 0 && (*txt != '\0')) { 561 if (dialog_vars.colors) { 562 while (isOurEscape(txt)) { 563 int code; 564 565 txt += 2; 566 switch (code = CharOf(*txt)) { 567 #ifdef HAVE_COLOR 568 case '0': 569 case '1': 570 case '2': 571 case '3': 572 case '4': 573 case '5': 574 case '6': 575 case '7': 576 *attr &= ~A_COLOR; 577 *attr |= define_color(win, code - '0'); 578 break; 579 #endif 580 case 'B': 581 *attr &= ~A_BOLD; 582 break; 583 case 'b': 584 *attr |= A_BOLD; 585 break; 586 case 'R': 587 *attr &= ~A_REVERSE; 588 break; 589 case 'r': 590 *attr |= A_REVERSE; 591 break; 592 case 'U': 593 *attr &= ~A_UNDERLINE; 594 break; 595 case 'u': 596 *attr |= A_UNDERLINE; 597 break; 598 case 'n': 599 *attr = A_NORMAL; 600 break; 601 } 602 ++txt; 603 } 604 } 605 if (ended || *txt == '\n' || *txt == '\0') 606 break; 607 useattr = (*attr) & A_ATTRIBUTES; 608 #ifdef HAVE_COLOR 609 /* 610 * Prevent this from making text invisible when the foreground and 611 * background colors happen to be the same, and there's no bold 612 * attribute. 613 */ 614 if ((useattr & A_COLOR) != 0 && (useattr & A_BOLD) == 0) { 615 short pair = (short) PAIR_NUMBER(useattr); 616 short fg, bg; 617 if (pair_content(pair, &fg, &bg) != ERR 618 && fg == bg) { 619 useattr &= ~A_COLOR; 620 useattr |= dlg_color_pair(fg, ((bg == COLOR_BLACK) 621 ? COLOR_WHITE 622 : COLOR_BLACK)); 623 } 624 } 625 #endif 626 /* 627 * Write the character, using curses to tell exactly how wide it 628 * is. If it is a tab, discount that, since the caller thinks 629 * tabs are nonprinting, and curses will expand tabs to one or 630 * more blanks. 631 */ 632 thisTab = (CharOf(*txt) == TAB); 633 if (thisTab) 634 getyx(win, y_before, x_before); 635 (void) waddch(win, CharOf(*txt++) | useattr); 636 getyx(win, y_after, x_after); 637 if (thisTab && (y_after == y_origin)) 638 tabbed += (x_after - x_before); 639 if ((y_after != y_origin) || 640 (x_after >= (cols + tabbed + x_origin) 641 #ifdef USE_WIDE_CURSES 642 && !is_combining(txt, &combined) 643 #endif 644 )) { 645 ended = TRUE; 646 } 647 } 648 } 649 650 /* 651 * Print one line of the prompt in the window within the limits of the 652 * specified right margin. The line will end on a word boundary and a pointer 653 * to the start of the next line is returned, or a NULL pointer if the end of 654 * *prompt is reached. 655 */ 656 const char * 657 dlg_print_line(WINDOW *win, 658 chtype *attr, 659 const char *prompt, 660 int lm, int rm, int *x) 661 { 662 const char *wrap_ptr = prompt; 663 const char *test_ptr = prompt; 664 const int *cols = dlg_index_columns(prompt); 665 const int *indx = dlg_index_wchars(prompt); 666 int wrap_inx = 0; 667 int test_inx = 0; 668 int cur_x = lm; 669 int hidden = 0; 670 int limit = dlg_count_wchars(prompt); 671 int n; 672 int tabbed = 0; 673 674 *x = 1; 675 676 /* 677 * Set *test_ptr to the end of the line or the right margin (rm), whichever 678 * is less, and set wrap_ptr to the end of the last word in the line. 679 */ 680 for (n = 0; n < limit; ++n) { 681 test_ptr = prompt + indx[test_inx]; 682 if (*test_ptr == '\n' || *test_ptr == '\0' || cur_x >= (rm + hidden)) 683 break; 684 if (*test_ptr == TAB && n == 0) { 685 tabbed = 8; /* workaround for leading tabs */ 686 } else if (*test_ptr == ' ' && n != 0 && prompt[indx[n - 1]] != ' ') { 687 wrap_inx = n; 688 *x = cur_x; 689 } else if (isOurEscape(test_ptr)) { 690 hidden += 3; 691 n += 2; 692 } 693 cur_x = lm + tabbed + cols[n + 1]; 694 if (cur_x > (rm + hidden)) 695 break; 696 test_inx = n + 1; 697 } 698 699 /* 700 * If the line doesn't reach the right margin in the middle of a word, then 701 * we don't have to wrap it at the end of the previous word. 702 */ 703 test_ptr = prompt + indx[test_inx]; 704 if (*test_ptr == '\n' || *test_ptr == ' ' || *test_ptr == '\0') { 705 wrap_inx = test_inx; 706 while (wrap_inx > 0 && prompt[indx[wrap_inx - 1]] == ' ') { 707 wrap_inx--; 708 } 709 *x = lm + indx[wrap_inx]; 710 } else if (*x == 1 && cur_x >= rm) { 711 /* 712 * If the line has no spaces, then wrap it anyway at the right margin 713 */ 714 *x = rm; 715 wrap_inx = test_inx; 716 } 717 wrap_ptr = prompt + indx[wrap_inx]; 718 #ifdef USE_WIDE_CURSES 719 if (UCH(*wrap_ptr) >= 128) { 720 int combined = 0; 721 while (is_combining(wrap_ptr, &combined)) { 722 ++wrap_ptr; 723 } 724 } 725 #endif 726 727 /* 728 * Print the line if we have a window pointer. Otherwise this routine 729 * is just being called for sizing the window. 730 */ 731 if (win) { 732 dlg_print_text(win, prompt, (cols[wrap_inx] - hidden), attr); 733 } 734 735 /* *x tells the calling function how long the line was */ 736 if (*x == 1) 737 *x = rm; 738 739 /* Find the start of the next line and return a pointer to it */ 740 test_ptr = wrap_ptr; 741 while (*test_ptr == ' ') 742 test_ptr++; 743 if (*test_ptr == '\n') 744 test_ptr++; 745 return (test_ptr); 746 } 747 748 static void 749 justify_text(WINDOW *win, 750 const char *prompt, 751 int limit_y, 752 int limit_x, 753 int *high, int *wide) 754 { 755 chtype attr = A_NORMAL; 756 int x = (2 * MARGIN); 757 int y = MARGIN; 758 int max_x = 2; 759 int lm = (2 * MARGIN); /* left margin (box-border plus a space) */ 760 int rm = limit_x; /* right margin */ 761 int bm = limit_y; /* bottom margin */ 762 int last_y = 0, last_x = 0; 763 764 if (win) { 765 rm -= (2 * MARGIN); 766 bm -= (2 * MARGIN); 767 } 768 if (prompt == 0) 769 prompt = ""; 770 771 if (win != 0) 772 getyx(win, last_y, last_x); 773 while (y <= bm && *prompt) { 774 x = lm; 775 776 if (*prompt == '\n') { 777 while (*prompt == '\n' && y < bm) { 778 if (*(prompt + 1) != '\0') { 779 ++y; 780 if (win != 0) 781 (void) wmove(win, y, lm); 782 } 783 prompt++; 784 } 785 } else if (win != 0) 786 (void) wmove(win, y, lm); 787 788 if (*prompt) { 789 prompt = dlg_print_line(win, &attr, prompt, lm, rm, &x); 790 if (win != 0) 791 getyx(win, last_y, last_x); 792 } 793 if (*prompt) { 794 ++y; 795 if (win != 0) 796 (void) wmove(win, y, lm); 797 } 798 max_x = MAX(max_x, x); 799 } 800 /* Move back to the last position after drawing prompt, for msgbox. */ 801 if (win != 0) 802 (void) wmove(win, last_y, last_x); 803 804 /* Set the final height and width for the calling function */ 805 if (high != 0) 806 *high = y; 807 if (wide != 0) 808 *wide = max_x; 809 } 810 811 /* 812 * Print a string of text in a window, automatically wrap around to the next 813 * line if the string is too long to fit on one line. Note that the string may 814 * contain embedded newlines. 815 */ 816 void 817 dlg_print_autowrap(WINDOW *win, const char *prompt, int height, int width) 818 { 819 justify_text(win, prompt, 820 height, 821 width, 822 (int *) 0, (int *) 0); 823 } 824 825 /* 826 * Display the message in a scrollable window. Actually the way it works is 827 * that we create a "tall" window of the proper width, let the text wrap within 828 * that, and copy a slice of the result to the dialog. 829 * 830 * It works for ncurses. Other curses implementations show only blanks (Tru64) 831 * or garbage (NetBSD). 832 */ 833 int 834 dlg_print_scrolled(WINDOW *win, 835 const char *prompt, 836 int offset, 837 int height, 838 int width, 839 int pauseopt) 840 { 841 int oldy, oldx; 842 int last = 0; 843 844 (void) pauseopt; /* used only for ncurses */ 845 846 getyx(win, oldy, oldx); 847 #ifdef NCURSES_VERSION 848 if (pauseopt) { 849 int wide = width - (2 * MARGIN); 850 int high = LINES; 851 int y, x; 852 int len; 853 int percent; 854 WINDOW *dummy; 855 char buffer[5]; 856 857 #if defined(NCURSES_VERSION_PATCH) && NCURSES_VERSION_PATCH >= 20040417 858 /* 859 * If we're not limited by the screensize, allow text to possibly be 860 * one character per line. 861 */ 862 if ((len = dlg_count_columns(prompt)) > high) 863 high = len; 864 #endif 865 dummy = newwin(high, width, 0, 0); 866 wbkgdset(dummy, dialog_attr | ' '); 867 wattrset(dummy, dialog_attr); 868 werase(dummy); 869 dlg_print_autowrap(dummy, prompt, high, width); 870 getyx(dummy, y, x); 871 872 copywin(dummy, /* srcwin */ 873 win, /* dstwin */ 874 offset + MARGIN, /* sminrow */ 875 MARGIN, /* smincol */ 876 MARGIN, /* dminrow */ 877 MARGIN, /* dmincol */ 878 height, /* dmaxrow */ 879 wide, /* dmaxcol */ 880 FALSE); 881 882 delwin(dummy); 883 884 /* if the text is incomplete, or we have scrolled, show the percentage */ 885 if (y > 0 && wide > 4) { 886 percent = (int) ((height + offset) * 100.0 / y); 887 if (percent < 0) 888 percent = 0; 889 if (percent > 100) 890 percent = 100; 891 if (offset != 0 || percent != 100) { 892 (void) wattrset(win, position_indicator_attr); 893 (void) wmove(win, MARGIN + height, wide - 4); 894 (void) sprintf(buffer, "%d%%", percent); 895 (void) waddstr(win, buffer); 896 if ((len = (int) strlen(buffer)) < 4) { 897 wattrset(win, border_attr); 898 whline(win, dlg_boxchar(ACS_HLINE), 4 - len); 899 } 900 } 901 } 902 last = (y - height); 903 } else 904 #endif 905 { 906 (void) offset; 907 wattrset(win, dialog_attr); 908 dlg_print_autowrap(win, prompt, height + 1 + (3 * MARGIN), width); 909 last = 0; 910 } 911 wmove(win, oldy, oldx); 912 return last; 913 } 914 915 int 916 dlg_check_scrolled(int key, int last, int page, bool * show, int *offset) 917 { 918 int code = 0; 919 920 *show = FALSE; 921 922 switch (key) { 923 case DLGK_PAGE_FIRST: 924 if (*offset > 0) { 925 *offset = 0; 926 *show = TRUE; 927 } 928 break; 929 case DLGK_PAGE_LAST: 930 if (*offset < last) { 931 *offset = last; 932 *show = TRUE; 933 } 934 break; 935 case DLGK_GRID_UP: 936 if (*offset > 0) { 937 --(*offset); 938 *show = TRUE; 939 } 940 break; 941 case DLGK_GRID_DOWN: 942 if (*offset < last) { 943 ++(*offset); 944 *show = TRUE; 945 } 946 break; 947 case DLGK_PAGE_PREV: 948 if (*offset > 0) { 949 *offset -= page; 950 if (*offset < 0) 951 *offset = 0; 952 *show = TRUE; 953 } 954 break; 955 case DLGK_PAGE_NEXT: 956 if (*offset < last) { 957 *offset += page; 958 if (*offset > last) 959 *offset = last; 960 *show = TRUE; 961 } 962 break; 963 default: 964 code = -1; 965 break; 966 } 967 return code; 968 } 969 970 /* 971 * Calculate the window size for preformatted text. This will calculate box 972 * dimensions that are at or close to the specified aspect ratio for the prompt 973 * string with all spaces and newlines preserved and additional newlines added 974 * as necessary. 975 */ 976 static void 977 auto_size_preformatted(const char *prompt, int *height, int *width) 978 { 979 int high = 0, wide = 0; 980 float car; /* Calculated Aspect Ratio */ 981 float diff; 982 int max_y = SLINES - 1; 983 int max_x = SCOLS - 2; 984 int max_width = max_x; 985 int ar = dialog_state.aspect_ratio; 986 987 /* Get the initial dimensions */ 988 justify_text((WINDOW *) 0, prompt, max_y, max_x, &high, &wide); 989 car = (float) (wide / high); 990 991 /* 992 * If the aspect ratio is greater than it should be, then decrease the 993 * width proportionately. 994 */ 995 if (car > ar) { 996 diff = car / (float) ar; 997 max_x = (int) ((float) wide / diff + 4); 998 justify_text((WINDOW *) 0, prompt, max_y, max_x, &high, &wide); 999 car = (float) wide / (float) high; 1000 } 1001 1002 /* 1003 * If the aspect ratio is too small after decreasing the width, then 1004 * incrementally increase the width until the aspect ratio is equal to or 1005 * greater than the specified aspect ratio. 1006 */ 1007 while (car < ar && max_x < max_width) { 1008 max_x += 4; 1009 justify_text((WINDOW *) 0, prompt, max_y, max_x, &high, &wide); 1010 car = (float) (wide / high); 1011 } 1012 1013 *height = high; 1014 *width = wide; 1015 } 1016 1017 /* 1018 * Find the length of the longest "word" in the given string. By setting the 1019 * widget width at least this long, we can avoid splitting a word on the 1020 * margin. 1021 */ 1022 static int 1023 longest_word(const char *string) 1024 { 1025 int length, result = 0; 1026 1027 while (*string != '\0') { 1028 length = 0; 1029 while (*string != '\0' && !isspace(UCH(*string))) { 1030 length++; 1031 string++; 1032 } 1033 result = MAX(result, length); 1034 if (*string != '\0') 1035 string++; 1036 } 1037 return result; 1038 } 1039 1040 /* 1041 * if (height or width == -1) Maximize() 1042 * if (height or width == 0), justify and return actual limits. 1043 */ 1044 static void 1045 real_auto_size(const char *title, 1046 const char *prompt, 1047 int *height, int *width, 1048 int boxlines, int mincols) 1049 { 1050 int x = (dialog_vars.begin_set ? dialog_vars.begin_x : 2); 1051 int y = (dialog_vars.begin_set ? dialog_vars.begin_y : 1); 1052 int title_length = title ? dlg_count_columns(title) : 0; 1053 int nc = 4; 1054 int high; 1055 int wide; 1056 int save_high = *height; 1057 int save_wide = *width; 1058 1059 if (prompt == 0) { 1060 if (*height == 0) 1061 *height = -1; 1062 if (*width == 0) 1063 *width = -1; 1064 } 1065 1066 if (*height > 0) { 1067 high = *height; 1068 } else { 1069 high = SLINES - y; 1070 } 1071 1072 if (*width > 0) { 1073 wide = *width; 1074 } else if (prompt != 0) { 1075 wide = MAX(title_length, mincols); 1076 if (strchr(prompt, '\n') == 0) { 1077 double val = dialog_state.aspect_ratio * dlg_count_columns(prompt); 1078 double xxx = sqrt(val); 1079 int tmp = (int) xxx; 1080 wide = MAX(wide, tmp); 1081 wide = MAX(wide, longest_word(prompt)); 1082 justify_text((WINDOW *) 0, prompt, high, wide, height, width); 1083 } else { 1084 auto_size_preformatted(prompt, height, width); 1085 } 1086 } else { 1087 wide = SCOLS - x; 1088 justify_text((WINDOW *) 0, prompt, high, wide, height, width); 1089 } 1090 1091 if (*width < title_length) { 1092 justify_text((WINDOW *) 0, prompt, high, title_length, height, width); 1093 *width = title_length; 1094 } 1095 1096 if (*width < mincols && save_wide == 0) 1097 *width = mincols; 1098 if (prompt != 0) { 1099 *width += nc; 1100 *height += boxlines + 2; 1101 } 1102 if (save_high > 0) 1103 *height = save_high; 1104 if (save_wide > 0) 1105 *width = save_wide; 1106 } 1107 1108 /* End of real_auto_size() */ 1109 1110 void 1111 dlg_auto_size(const char *title, 1112 const char *prompt, 1113 int *height, 1114 int *width, 1115 int boxlines, 1116 int mincols) 1117 { 1118 real_auto_size(title, prompt, height, width, boxlines, mincols); 1119 1120 if (*width > SCOLS) { 1121 (*height)++; 1122 *width = SCOLS; 1123 } 1124 1125 if (*height > SLINES) 1126 *height = SLINES; 1127 } 1128 1129 /* 1130 * if (height or width == -1) Maximize() 1131 * if (height or width == 0) 1132 * height=MIN(SLINES, num.lines in fd+n); 1133 * width=MIN(SCOLS, MAX(longer line+n, mincols)); 1134 */ 1135 void 1136 dlg_auto_sizefile(const char *title, 1137 const char *file, 1138 int *height, 1139 int *width, 1140 int boxlines, 1141 int mincols) 1142 { 1143 int count = 0; 1144 int len = title ? dlg_count_columns(title) : 0; 1145 int nc = 4; 1146 int numlines = 2; 1147 long offset; 1148 int ch; 1149 FILE *fd; 1150 1151 /* Open input file for reading */ 1152 if ((fd = fopen(file, "rb")) == NULL) 1153 dlg_exiterr("dlg_auto_sizefile: Cannot open input file %s", file); 1154 1155 if ((*height == -1) || (*width == -1)) { 1156 *height = SLINES - (dialog_vars.begin_set ? dialog_vars.begin_y : 0); 1157 *width = SCOLS - (dialog_vars.begin_set ? dialog_vars.begin_x : 0); 1158 } 1159 if ((*height != 0) && (*width != 0)) { 1160 (void) fclose(fd); 1161 if (*width > SCOLS) 1162 *width = SCOLS; 1163 if (*height > SLINES) 1164 *height = SLINES; 1165 return; 1166 } 1167 1168 while (!feof(fd)) { 1169 offset = 0; 1170 while (((ch = getc(fd)) != '\n') && !feof(fd)) 1171 if ((ch == TAB) && (dialog_vars.tab_correct)) 1172 offset += dialog_state.tab_len - (offset % dialog_state.tab_len); 1173 else 1174 offset++; 1175 1176 if (offset > len) 1177 len = (int) offset; 1178 1179 count++; 1180 } 1181 1182 /* now 'count' has the number of lines of fd and 'len' the max length */ 1183 1184 *height = MIN(SLINES, count + numlines + boxlines); 1185 *width = MIN(SCOLS, MAX((len + nc), mincols)); 1186 /* here width and height can be maximized if > SCOLS|SLINES because 1187 textbox-like widgets don't put all <file> on the screen. 1188 Msgbox-like widget instead have to put all <text> correctly. */ 1189 1190 (void) fclose(fd); 1191 } 1192 1193 /* 1194 * Draw a rectangular box with line drawing characters. 1195 * 1196 * borderchar is used to color the upper/left edges. 1197 * 1198 * boxchar is used to color the right/lower edges. It also is fill-color used 1199 * for the box contents. 1200 * 1201 * Normally, if you are drawing a scrollable box, use menubox_border_attr for 1202 * boxchar, and menubox_attr for borderchar since the scroll-arrows are drawn 1203 * with menubox_attr at the top, and menubox_border_attr at the bottom. That 1204 * also (given the default color choices) produces a recessed effect. 1205 * 1206 * If you want a raised effect (and are not going to use the scroll-arrows), 1207 * reverse this choice. 1208 */ 1209 void 1210 dlg_draw_box(WINDOW *win, int y, int x, int height, int width, 1211 chtype boxchar, chtype borderchar) 1212 { 1213 int i, j; 1214 chtype save = dlg_get_attrs(win); 1215 1216 wattrset(win, 0); 1217 for (i = 0; i < height; i++) { 1218 (void) wmove(win, y + i, x); 1219 for (j = 0; j < width; j++) 1220 if (!i && !j) 1221 (void) waddch(win, borderchar | dlg_boxchar(ACS_ULCORNER)); 1222 else if (i == height - 1 && !j) 1223 (void) waddch(win, borderchar | dlg_boxchar(ACS_LLCORNER)); 1224 else if (!i && j == width - 1) 1225 (void) waddch(win, boxchar | dlg_boxchar(ACS_URCORNER)); 1226 else if (i == height - 1 && j == width - 1) 1227 (void) waddch(win, boxchar | dlg_boxchar(ACS_LRCORNER)); 1228 else if (!i) 1229 (void) waddch(win, borderchar | dlg_boxchar(ACS_HLINE)); 1230 else if (i == height - 1) 1231 (void) waddch(win, boxchar | dlg_boxchar(ACS_HLINE)); 1232 else if (!j) 1233 (void) waddch(win, borderchar | dlg_boxchar(ACS_VLINE)); 1234 else if (j == width - 1) 1235 (void) waddch(win, boxchar | dlg_boxchar(ACS_VLINE)); 1236 else 1237 (void) waddch(win, boxchar | ' '); 1238 } 1239 wattrset(win, save); 1240 } 1241 1242 #ifdef HAVE_COLOR 1243 /* 1244 * Draw a shadow on the parent window corresponding to the right- and 1245 * bottom-edge of the child window, to give a 3-dimensional look. 1246 */ 1247 static void 1248 draw_childs_shadow(WINDOW *parent, WINDOW *child) 1249 { 1250 if (has_colors()) { /* Whether terminal supports color? */ 1251 chtype save = dlg_get_attrs(parent); 1252 1253 dlg_draw_shadow(parent, 1254 getbegy(child) - getbegy(parent), 1255 getbegx(child) - getbegx(parent), 1256 getmaxy(child), 1257 getmaxx(child)); 1258 wattrset(parent, save); 1259 } 1260 } 1261 1262 /* 1263 * Draw shadows along the right and bottom edge to give a more 3D look 1264 * to the boxes 1265 */ 1266 void 1267 dlg_draw_shadow(WINDOW *win, int y, int x, int height, int width) 1268 { 1269 int i, j; 1270 1271 if (has_colors()) { /* Whether terminal supports color? */ 1272 wattrset(win, shadow_attr); 1273 for (i = 0; i < SHADOW_ROWS; ++i) { 1274 for (j = 0; j < width; ++j) { 1275 if (wmove(win, i + y + height, j + x + SHADOW_COLS) != ERR) { 1276 (void) waddch(win, winch(win) & (chtype) (~A_COLOR)); 1277 } 1278 } 1279 } 1280 for (i = 0; i < height; i++) { 1281 for (j = 0; j < SHADOW_COLS; ++j) { 1282 if (wmove(win, i + y + SHADOW_ROWS, j + x + width) != ERR) { 1283 (void) waddch(win, winch(win) & (chtype) (~A_COLOR)); 1284 } 1285 } 1286 } 1287 (void) wnoutrefresh(win); 1288 } 1289 } 1290 #endif /* HAVE_COLOR */ 1291 1292 /* 1293 * Allow shell scripts to remap the exit codes so they can distinguish ESC 1294 * from ERROR. 1295 */ 1296 void 1297 dlg_exit(int code) 1298 { 1299 /* *INDENT-OFF* */ 1300 static const struct { 1301 int code; 1302 const char *name; 1303 } table[] = { 1304 { DLG_EXIT_CANCEL, "DIALOG_CANCEL" }, 1305 { DLG_EXIT_ERROR, "DIALOG_ERROR" }, 1306 { DLG_EXIT_ESC, "DIALOG_ESC" }, 1307 { DLG_EXIT_EXTRA, "DIALOG_EXTRA" }, 1308 { DLG_EXIT_HELP, "DIALOG_HELP" }, 1309 { DLG_EXIT_OK, "DIALOG_OK" }, 1310 { DLG_EXIT_ITEM_HELP, "DIALOG_ITEM_HELP" }, 1311 }; 1312 /* *INDENT-ON* */ 1313 1314 unsigned n; 1315 char *name; 1316 char *temp; 1317 long value; 1318 bool overridden = FALSE; 1319 1320 retry: 1321 for (n = 0; n < sizeof(table) / sizeof(table[0]); n++) { 1322 if (table[n].code == code) { 1323 if ((name = getenv(table[n].name)) != 0) { 1324 value = strtol(name, &temp, 0); 1325 if (temp != 0 && temp != name && *temp == '\0') { 1326 code = (int) value; 1327 overridden = TRUE; 1328 } 1329 } 1330 break; 1331 } 1332 } 1333 1334 /* 1335 * Prior to 2004/12/19, a widget using --item-help would exit with "OK" 1336 * if the help button were selected. Now we want to exit with "HELP", 1337 * but allow the environment variable to override. 1338 */ 1339 if (code == DLG_EXIT_ITEM_HELP && !overridden) { 1340 code = DLG_EXIT_HELP; 1341 goto retry; 1342 } 1343 #ifdef NO_LEAKS 1344 _dlg_inputstr_leaks(); 1345 #if defined(NCURSES_VERSION) && defined(HAVE__NC_FREE_AND_EXIT) 1346 _nc_free_and_exit(code); 1347 #endif 1348 #endif 1349 1350 if (dialog_state.input == stdin) { 1351 exit(code); 1352 } else { 1353 /* 1354 * Just in case of using --input-fd option, do not 1355 * call atexit functions of ncurses which may hang. 1356 */ 1357 if (dialog_state.input) { 1358 fclose(dialog_state.input); 1359 dialog_state.input = 0; 1360 } 1361 if (dialog_state.pipe_input) { 1362 if (dialog_state.pipe_input != stdin) { 1363 fclose(dialog_state.pipe_input); 1364 dialog_state.pipe_input = 0; 1365 } 1366 } 1367 _exit(code); 1368 } 1369 } 1370 1371 /* quit program killing all tailbg */ 1372 void 1373 dlg_exiterr(const char *fmt,...) 1374 { 1375 int retval; 1376 va_list ap; 1377 1378 end_dialog(); 1379 1380 (void) fputc('\n', stderr); 1381 va_start(ap, fmt); 1382 (void) vfprintf(stderr, fmt, ap); 1383 va_end(ap); 1384 (void) fputc('\n', stderr); 1385 1386 dlg_killall_bg(&retval); 1387 1388 (void) fflush(stderr); 1389 (void) fflush(stdout); 1390 dlg_exit(DLG_EXIT_ERROR); 1391 } 1392 1393 void 1394 dlg_beeping(void) 1395 { 1396 if (dialog_vars.beep_signal) { 1397 (void) beep(); 1398 dialog_vars.beep_signal = 0; 1399 } 1400 } 1401 1402 void 1403 dlg_print_size(int height, int width) 1404 { 1405 if (dialog_vars.print_siz) 1406 fprintf(dialog_state.output, "Size: %d, %d\n", height, width); 1407 } 1408 1409 void 1410 dlg_ctl_size(int height, int width) 1411 { 1412 if (dialog_vars.size_err) { 1413 if ((width > COLS) || (height > LINES)) { 1414 dlg_exiterr("Window too big. (height, width) = (%d, %d). Max allowed (%d, %d).", 1415 height, width, LINES, COLS); 1416 } 1417 #ifdef HAVE_COLOR 1418 else if ((dialog_state.use_shadow) 1419 && ((width > SCOLS || height > SLINES))) { 1420 if ((width <= COLS) && (height <= LINES)) { 1421 /* try again, without shadows */ 1422 dialog_state.use_shadow = 0; 1423 } else { 1424 dlg_exiterr("Window+Shadow too big. (height, width) = (%d, %d). Max allowed (%d, %d).", 1425 height, width, SLINES, SCOLS); 1426 } 1427 } 1428 #endif 1429 } 1430 } 1431 1432 /* 1433 * If the --tab-correct was not selected, convert tabs to single spaces. 1434 */ 1435 void 1436 dlg_tab_correct_str(char *prompt) 1437 { 1438 char *ptr; 1439 1440 if (dialog_vars.tab_correct) { 1441 while ((ptr = strchr(prompt, TAB)) != NULL) { 1442 *ptr = ' '; 1443 prompt = ptr; 1444 } 1445 } 1446 } 1447 1448 void 1449 dlg_calc_listh(int *height, int *list_height, int item_no) 1450 { 1451 /* calculate new height and list_height */ 1452 int rows = SLINES - (dialog_vars.begin_set ? dialog_vars.begin_y : 0); 1453 if (rows - (*height) > 0) { 1454 if (rows - (*height) > item_no) 1455 *list_height = item_no; 1456 else 1457 *list_height = rows - (*height); 1458 } 1459 (*height) += (*list_height); 1460 } 1461 1462 /* obsolete */ 1463 int 1464 dlg_calc_listw(int item_no, char **items, int group) 1465 { 1466 int n, i, len1 = 0, len2 = 0; 1467 for (i = 0; i < (item_no * group); i += group) { 1468 if ((n = dlg_count_columns(items[i])) > len1) 1469 len1 = n; 1470 if ((n = dlg_count_columns(items[i + 1])) > len2) 1471 len2 = n; 1472 } 1473 return len1 + len2; 1474 } 1475 1476 int 1477 dlg_calc_list_width(int item_no, DIALOG_LISTITEM * items) 1478 { 1479 int n, i, len1 = 0, len2 = 0; 1480 for (i = 0; i < item_no; ++i) { 1481 if ((n = dlg_count_columns(items[i].name)) > len1) 1482 len1 = n; 1483 if ((n = dlg_count_columns(items[i].text)) > len2) 1484 len2 = n; 1485 } 1486 return len1 + len2; 1487 } 1488 1489 char * 1490 dlg_strempty(void) 1491 { 1492 static char empty[] = ""; 1493 return empty; 1494 } 1495 1496 char * 1497 dlg_strclone(const char *cprompt) 1498 { 1499 char *prompt = dlg_malloc(char, strlen(cprompt) + 1); 1500 assert_ptr(prompt, "dlg_strclone"); 1501 strcpy(prompt, cprompt); 1502 return prompt; 1503 } 1504 1505 chtype 1506 dlg_asciibox(chtype ch) 1507 { 1508 chtype result = 0; 1509 1510 if (ch == ACS_ULCORNER) 1511 result = '+'; 1512 else if (ch == ACS_LLCORNER) 1513 result = '+'; 1514 else if (ch == ACS_URCORNER) 1515 result = '+'; 1516 else if (ch == ACS_LRCORNER) 1517 result = '+'; 1518 else if (ch == ACS_HLINE) 1519 result = '-'; 1520 else if (ch == ACS_VLINE) 1521 result = '|'; 1522 else if (ch == ACS_LTEE) 1523 result = '+'; 1524 else if (ch == ACS_RTEE) 1525 result = '+'; 1526 else if (ch == ACS_UARROW) 1527 result = '^'; 1528 else if (ch == ACS_DARROW) 1529 result = 'v'; 1530 1531 return result; 1532 } 1533 1534 chtype 1535 dlg_boxchar(chtype ch) 1536 { 1537 chtype result = dlg_asciibox(ch); 1538 1539 if (result != 0) { 1540 if (dialog_vars.ascii_lines) 1541 ch = result; 1542 else if (dialog_vars.no_lines) 1543 ch = ' '; 1544 } 1545 return ch; 1546 } 1547 1548 int 1549 dlg_box_x_ordinate(int width) 1550 { 1551 int x; 1552 1553 if (dialog_vars.begin_set == 1) { 1554 x = dialog_vars.begin_x; 1555 } else { 1556 /* center dialog box on screen unless --begin-set */ 1557 x = (SCOLS - width) / 2; 1558 } 1559 return x; 1560 } 1561 1562 int 1563 dlg_box_y_ordinate(int height) 1564 { 1565 int y; 1566 1567 if (dialog_vars.begin_set == 1) { 1568 y = dialog_vars.begin_y; 1569 } else { 1570 /* center dialog box on screen unless --begin-set */ 1571 y = (SLINES - height) / 2; 1572 } 1573 return y; 1574 } 1575 1576 void 1577 dlg_draw_title(WINDOW *win, const char *title) 1578 { 1579 if (title != NULL) { 1580 chtype attr = A_NORMAL; 1581 chtype save = dlg_get_attrs(win); 1582 int x = centered(getmaxx(win), title); 1583 1584 wattrset(win, title_attr); 1585 wmove(win, 0, x); 1586 dlg_print_text(win, title, getmaxx(win) - x, &attr); 1587 wattrset(win, save); 1588 } 1589 } 1590 1591 void 1592 dlg_draw_bottom_box(WINDOW *win) 1593 { 1594 int width = getmaxx(win); 1595 int height = getmaxy(win); 1596 int i; 1597 1598 wattrset(win, border_attr); 1599 (void) wmove(win, height - 3, 0); 1600 (void) waddch(win, dlg_boxchar(ACS_LTEE)); 1601 for (i = 0; i < width - 2; i++) 1602 (void) waddch(win, dlg_boxchar(ACS_HLINE)); 1603 wattrset(win, dialog_attr); 1604 (void) waddch(win, dlg_boxchar(ACS_RTEE)); 1605 (void) wmove(win, height - 2, 1); 1606 for (i = 0; i < width - 2; i++) 1607 (void) waddch(win, ' '); 1608 } 1609 1610 /* 1611 * Remove a window, repainting everything else. This would be simpler if we 1612 * used the panel library, but that is not _always_ available. 1613 */ 1614 void 1615 dlg_del_window(WINDOW *win) 1616 { 1617 DIALOG_WINDOWS *p, *q, *r; 1618 1619 /* 1620 * If --keep-window was set, do not delete/repaint the windows. 1621 */ 1622 if (dialog_vars.keep_window) 1623 return; 1624 1625 /* Leave the main window untouched if there are no background windows. 1626 * We do this so the current window will not be cleared on exit, allowing 1627 * things like the infobox demo to run without flicker. 1628 */ 1629 if (dialog_state.getc_callbacks != 0) { 1630 touchwin(stdscr); 1631 wnoutrefresh(stdscr); 1632 } 1633 1634 for (p = dialog_state.all_windows, q = r = 0; p != 0; r = p, p = p->next) { 1635 if (p->normal == win) { 1636 q = p; /* found a match - should be only one */ 1637 if (r == 0) { 1638 dialog_state.all_windows = p->next; 1639 } else { 1640 r->next = p->next; 1641 } 1642 } else { 1643 if (p->shadow != 0) { 1644 touchwin(p->shadow); 1645 wnoutrefresh(p->shadow); 1646 } 1647 touchwin(p->normal); 1648 wnoutrefresh(p->normal); 1649 } 1650 } 1651 1652 if (q) { 1653 delwin(q->normal); 1654 dlg_unregister_window(q->normal); 1655 free(q); 1656 } 1657 doupdate(); 1658 } 1659 1660 /* 1661 * Create a window, optionally with a shadow. 1662 */ 1663 WINDOW * 1664 dlg_new_window(int height, int width, int y, int x) 1665 { 1666 WINDOW *win; 1667 DIALOG_WINDOWS *p = dlg_calloc(DIALOG_WINDOWS, 1); 1668 1669 if ((win = newwin(height, width, y, x)) == 0) { 1670 dlg_exiterr("Can't make new window at (%d,%d), size (%d,%d).\n", 1671 y, x, height, width); 1672 } 1673 p->next = dialog_state.all_windows; 1674 p->normal = win; 1675 dialog_state.all_windows = p; 1676 #ifdef HAVE_COLOR 1677 if (dialog_state.use_shadow) { 1678 draw_childs_shadow(p->shadow = stdscr, win); 1679 } 1680 #endif 1681 1682 (void) keypad(win, TRUE); 1683 return win; 1684 } 1685 1686 WINDOW * 1687 dlg_new_modal_window(WINDOW *parent, int height, int width, int y, int x) 1688 { 1689 WINDOW *win; 1690 DIALOG_WINDOWS *p = dlg_calloc(DIALOG_WINDOWS, 1); 1691 1692 (void) parent; 1693 if ((win = newwin(height, width, y, x)) == 0) { 1694 dlg_exiterr("Can't make new window at (%d,%d), size (%d,%d).\n", 1695 y, x, height, width); 1696 } 1697 p->next = dialog_state.all_windows; 1698 p->normal = win; 1699 dialog_state.all_windows = p; 1700 #ifdef HAVE_COLOR 1701 if (dialog_state.use_shadow) { 1702 draw_childs_shadow(p->shadow = parent, win); 1703 } 1704 #endif 1705 1706 (void) keypad(win, TRUE); 1707 return win; 1708 } 1709 1710 /* 1711 * Move/Resize a window, optionally with a shadow. 1712 */ 1713 #ifdef KEY_RESIZE 1714 void 1715 dlg_move_window(WINDOW *win, int height, int width, int y, int x) 1716 { 1717 DIALOG_WINDOWS *p, *q; 1718 1719 if (win != 0) { 1720 dlg_ctl_size(height, width); 1721 1722 for (p = dialog_state.all_windows; p != 0; p = q) { 1723 q = p->next; 1724 if (p->normal == win) { 1725 break; 1726 } 1727 } 1728 1729 if (p != 0) { 1730 (void) wresize(win, height, width); 1731 (void) mvwin(win, y, x); 1732 #ifdef HAVE_COLOR 1733 if (p->shadow != 0) { 1734 if (dialog_state.use_shadow) { 1735 (void) mvwin(p->shadow, y + SHADOW_ROWS, x + SHADOW_COLS); 1736 } else { 1737 p->shadow = 0; 1738 } 1739 } 1740 #endif 1741 (void) refresh(); 1742 1743 #ifdef HAVE_COLOR 1744 if (p->shadow) 1745 draw_childs_shadow(p->shadow, win); 1746 #endif 1747 } 1748 } 1749 } 1750 #endif /* KEY_RESIZE */ 1751 1752 WINDOW * 1753 dlg_sub_window(WINDOW *parent, int height, int width, int y, int x) 1754 { 1755 WINDOW *win; 1756 1757 if ((win = subwin(parent, height, width, y, x)) == 0) { 1758 dlg_exiterr("Can't make sub-window at (%d,%d), size (%d,%d).\n", 1759 y, x, height, width); 1760 } 1761 1762 (void) keypad(win, TRUE); 1763 return win; 1764 } 1765 1766 /* obsolete */ 1767 int 1768 dlg_default_item(char **items, int llen) 1769 { 1770 int result = 0; 1771 1772 if (dialog_vars.default_item != 0) { 1773 int count = 0; 1774 while (*items != 0) { 1775 if (!strcmp(dialog_vars.default_item, *items)) { 1776 result = count; 1777 break; 1778 } 1779 items += llen; 1780 count++; 1781 } 1782 } 1783 return result; 1784 } 1785 1786 int 1787 dlg_default_listitem(DIALOG_LISTITEM * items) 1788 { 1789 int result = 0; 1790 1791 if (dialog_vars.default_item != 0) { 1792 int count = 0; 1793 while (items->name != 0) { 1794 if (!strcmp(dialog_vars.default_item, items->name)) { 1795 result = count; 1796 break; 1797 } 1798 ++items; 1799 count++; 1800 } 1801 } 1802 return result; 1803 } 1804 1805 /* 1806 * Draw the string for item_help 1807 */ 1808 void 1809 dlg_item_help(const char *txt) 1810 { 1811 if (USE_ITEM_HELP(txt)) { 1812 chtype attr = A_NORMAL; 1813 int y, x; 1814 1815 wattrset(stdscr, itemhelp_attr); 1816 (void) wmove(stdscr, LINES - 1, 0); 1817 (void) wclrtoeol(stdscr); 1818 (void) addch(' '); 1819 dlg_print_text(stdscr, txt, COLS - 1, &attr); 1820 if (itemhelp_attr & A_COLOR) { 1821 /* fill the remainder of the line with the window's attributes */ 1822 getyx(stdscr, y, x); 1823 while (x < COLS) { 1824 (void) addch(' '); 1825 ++x; 1826 } 1827 } 1828 (void) wnoutrefresh(stdscr); 1829 } 1830 } 1831 1832 #ifndef HAVE_STRCASECMP 1833 int 1834 dlg_strcmp(const char *a, const char *b) 1835 { 1836 int ac, bc, cmp; 1837 1838 for (;;) { 1839 ac = UCH(*a++); 1840 bc = UCH(*b++); 1841 if (isalpha(ac) && islower(ac)) 1842 ac = _toupper(ac); 1843 if (isalpha(bc) && islower(bc)) 1844 bc = _toupper(bc); 1845 cmp = ac - bc; 1846 if (ac == 0 || bc == 0 || cmp != 0) 1847 break; 1848 } 1849 return cmp; 1850 } 1851 #endif 1852 1853 /* 1854 * Returns true if 'dst' points to a blank which follows another blank which 1855 * is not a leading blank on a line. 1856 */ 1857 static bool 1858 trim_blank(char *base, char *dst) 1859 { 1860 int count = 0; 1861 1862 while (dst-- != base) { 1863 if (*dst == '\n') { 1864 return FALSE; 1865 } else if (*dst != ' ') { 1866 return (count > 1); 1867 } else { 1868 count++; 1869 } 1870 } 1871 return FALSE; 1872 } 1873 1874 /* 1875 * Change embedded "\n" substrings to '\n' characters and tabs to single 1876 * spaces. If there are no "\n"s, it will strip all extra spaces, for 1877 * justification. If it has "\n"'s, it will preserve extra spaces. If cr_wrap 1878 * is set, it will preserve '\n's. 1879 */ 1880 void 1881 dlg_trim_string(char *s) 1882 { 1883 char *base = s; 1884 char *p1; 1885 char *p = s; 1886 int has_newlines = (strstr(s, "\\n") != 0); 1887 1888 while (*p != '\0') { 1889 if (*p == TAB && !dialog_vars.nocollapse) 1890 *p = ' '; 1891 1892 if (has_newlines) { /* If prompt contains "\n" strings */ 1893 if (*p == '\\' && *(p + 1) == 'n') { 1894 *s++ = '\n'; 1895 p += 2; 1896 p1 = p; 1897 /* 1898 * Handle end of lines intelligently. If '\n' follows "\n" 1899 * then ignore the '\n'. This eliminates the need to escape 1900 * the '\n' character (no need to use "\n\"). 1901 */ 1902 while (*p1 == ' ') 1903 p1++; 1904 if (*p1 == '\n') 1905 p = p1 + 1; 1906 } else if (*p == '\n') { 1907 if (dialog_vars.cr_wrap) 1908 *s++ = *p++; 1909 else { 1910 /* Replace the '\n' with a space if cr_wrap is not set */ 1911 if (!trim_blank(base, s)) 1912 *s++ = ' '; 1913 p++; 1914 } 1915 } else /* If *p != '\n' */ 1916 *s++ = *p++; 1917 } else if (dialog_vars.trim_whitespace) { 1918 if (*p == ' ') { 1919 if (*(s - 1) != ' ') { 1920 *s++ = ' '; 1921 p++; 1922 } else 1923 p++; 1924 } else if (*p == '\n') { 1925 if (dialog_vars.cr_wrap) 1926 *s++ = *p++; 1927 else if (*(s - 1) != ' ') { 1928 /* Strip '\n's if cr_wrap is not set. */ 1929 *s++ = ' '; 1930 p++; 1931 } else 1932 p++; 1933 } else 1934 *s++ = *p++; 1935 } else { /* If there are no "\n" strings */ 1936 if (*p == ' ' && !dialog_vars.nocollapse) { 1937 if (!trim_blank(base, s)) 1938 *s++ = *p; 1939 p++; 1940 } else 1941 *s++ = *p++; 1942 } 1943 } 1944 1945 *s = '\0'; 1946 } 1947 1948 void 1949 dlg_set_focus(WINDOW *parent, WINDOW *win) 1950 { 1951 if (win != 0) { 1952 (void) wmove(parent, 1953 getpary(win) + getcury(win), 1954 getparx(win) + getcurx(win)); 1955 (void) wnoutrefresh(win); 1956 (void) doupdate(); 1957 } 1958 } 1959 1960 /* 1961 * Returns the nominal maximum buffer size. 1962 */ 1963 int 1964 dlg_max_input(int max_len) 1965 { 1966 if (dialog_vars.max_input != 0 && dialog_vars.max_input < MAX_LEN) 1967 max_len = dialog_vars.max_input; 1968 1969 return max_len; 1970 } 1971 1972 /* 1973 * Free storage used for the result buffer. 1974 */ 1975 void 1976 dlg_clr_result(void) 1977 { 1978 if (dialog_vars.input_length) { 1979 dialog_vars.input_length = 0; 1980 if (dialog_vars.input_result) 1981 free(dialog_vars.input_result); 1982 } 1983 dialog_vars.input_result = 0; 1984 } 1985 1986 /* 1987 * Setup a fixed-buffer for the result. 1988 */ 1989 char * 1990 dlg_set_result(const char *string) 1991 { 1992 unsigned need = string ? (unsigned) strlen(string) + 1 : 0; 1993 1994 /* inputstr.c needs a fixed buffer */ 1995 if (need < MAX_LEN) 1996 need = MAX_LEN; 1997 1998 /* 1999 * If the buffer is not big enough, allocate a new one. 2000 */ 2001 if (dialog_vars.input_length != 0 2002 || dialog_vars.input_result == 0 2003 || need > MAX_LEN) { 2004 2005 dlg_clr_result(); 2006 2007 dialog_vars.input_length = need; 2008 dialog_vars.input_result = dlg_malloc(char, need); 2009 assert_ptr(dialog_vars.input_result, "dlg_set_result"); 2010 } 2011 2012 strcpy(dialog_vars.input_result, string ? string : ""); 2013 2014 return dialog_vars.input_result; 2015 } 2016 2017 /* 2018 * Accumulate results in dynamically allocated buffer. 2019 * If input_length is zero, it is a MAX_LEN buffer belonging to the caller. 2020 */ 2021 void 2022 dlg_add_result(const char *string) 2023 { 2024 unsigned have = (dialog_vars.input_result 2025 ? (unsigned) strlen(dialog_vars.input_result) 2026 : 0); 2027 unsigned want = (unsigned) strlen(string) + 1 + have; 2028 2029 if ((want >= MAX_LEN) 2030 || (dialog_vars.input_length != 0) 2031 || (dialog_vars.input_result == 0)) { 2032 2033 if (dialog_vars.input_length == 0 2034 || dialog_vars.input_result == 0) { 2035 2036 char *save_result = dialog_vars.input_result; 2037 2038 dialog_vars.input_length = want * 2; 2039 dialog_vars.input_result = dlg_malloc(char, dialog_vars.input_length); 2040 assert_ptr(dialog_vars.input_result, "dlg_add_result malloc"); 2041 dialog_vars.input_result[0] = '\0'; 2042 if (save_result != 0) 2043 strcpy(dialog_vars.input_result, save_result); 2044 } else if (want >= dialog_vars.input_length) { 2045 dialog_vars.input_length = want * 2; 2046 dialog_vars.input_result = dlg_realloc(char, 2047 dialog_vars.input_length, 2048 dialog_vars.input_result); 2049 assert_ptr(dialog_vars.input_result, "dlg_add_result realloc"); 2050 } 2051 } 2052 strcat(dialog_vars.input_result, string); 2053 } 2054 2055 /* 2056 * These are characters that (aside from the quote-delimiter) will have to 2057 * be escaped in a single- or double-quoted string. 2058 */ 2059 #define FIX_SINGLE "\n\\" 2060 #define FIX_DOUBLE FIX_SINGLE "[]{}?*;`~#$^&()|<>" 2061 2062 /* 2063 * Returns the quote-delimiter. 2064 */ 2065 static const char * 2066 quote_delimiter(void) 2067 { 2068 return dialog_vars.single_quoted ? "'" : "\""; 2069 } 2070 2071 /* 2072 * Returns true if we should quote the given string. 2073 */ 2074 static bool 2075 must_quote(char *string) 2076 { 2077 bool code = FALSE; 2078 2079 if (*string != '\0') { 2080 size_t len = strlen(string); 2081 if (strcspn(string, quote_delimiter()) != len) 2082 code = TRUE; 2083 else if (strcspn(string, "\n\t ") != len) 2084 code = TRUE; 2085 else 2086 code = (strcspn(string, FIX_DOUBLE) != len); 2087 } else { 2088 code = TRUE; 2089 } 2090 2091 return code; 2092 } 2093 2094 /* 2095 * Add a quoted string to the result buffer. 2096 */ 2097 void 2098 dlg_add_quoted(char *string) 2099 { 2100 char temp[2]; 2101 const char *my_quote = quote_delimiter(); 2102 const char *must_fix = (dialog_vars.single_quoted 2103 ? FIX_SINGLE 2104 : FIX_DOUBLE); 2105 2106 if (dialog_vars.quoted || must_quote(string)) { 2107 temp[1] = '\0'; 2108 dlg_add_result(my_quote); 2109 while (*string != '\0') { 2110 temp[0] = *string++; 2111 if (strchr(my_quote, *temp) || strchr(must_fix, *temp)) 2112 dlg_add_result("\\"); 2113 dlg_add_result(temp); 2114 } 2115 dlg_add_result(my_quote); 2116 } else { 2117 dlg_add_result(string); 2118 } 2119 } 2120 2121 /* 2122 * When adding a result, make that depend on whether "--quoted" is used. 2123 */ 2124 void 2125 dlg_add_string(char *string) 2126 { 2127 if (dialog_vars.quoted) { 2128 dlg_add_quoted(string); 2129 } else { 2130 dlg_add_result(string); 2131 } 2132 } 2133 2134 bool 2135 dlg_need_separator(void) 2136 { 2137 bool result = FALSE; 2138 2139 if (dialog_vars.output_separator) { 2140 result = TRUE; 2141 } else if (dialog_vars.input_result && *(dialog_vars.input_result)) { 2142 result = TRUE; 2143 } 2144 return result; 2145 } 2146 2147 void 2148 dlg_add_separator(void) 2149 { 2150 const char *separator = (dialog_vars.separate_output) ? "\n" : " "; 2151 2152 if (dialog_vars.output_separator) 2153 separator = dialog_vars.output_separator; 2154 2155 dlg_add_result(separator); 2156 } 2157 2158 /* 2159 * Some widgets support only one value of a given variable - save/restore the 2160 * global dialog_vars so we can override it consistently. 2161 */ 2162 void 2163 dlg_save_vars(DIALOG_VARS * vars) 2164 { 2165 *vars = dialog_vars; 2166 } 2167 2168 /* 2169 * Most of the data in DIALOG_VARS is normally set by command-line options. 2170 * The input_result member is an exception; it is normally set by the dialog 2171 * library to return result values. 2172 */ 2173 void 2174 dlg_restore_vars(DIALOG_VARS * vars) 2175 { 2176 char *save_result = dialog_vars.input_result; 2177 unsigned save_length = dialog_vars.input_length; 2178 2179 dialog_vars = *vars; 2180 dialog_vars.input_result = save_result; 2181 dialog_vars.input_length = save_length; 2182 } 2183 2184 /* 2185 * Called each time a widget is invoked which may do output, increment a count. 2186 */ 2187 void 2188 dlg_does_output(void) 2189 { 2190 dialog_state.output_count += 1; 2191 } 2192 2193 /* 2194 * Compatibility for different versions of curses. 2195 */ 2196 #if !(defined(HAVE_GETBEGX) && defined(HAVE_GETBEGY)) 2197 int 2198 getbegx(WINDOW *win) 2199 { 2200 int y, x; 2201 getbegyx(win, y, x); 2202 return x; 2203 } 2204 int 2205 getbegy(WINDOW *win) 2206 { 2207 int y, x; 2208 getbegyx(win, y, x); 2209 return y; 2210 } 2211 #endif 2212 2213 #if !(defined(HAVE_GETCURX) && defined(HAVE_GETCURY)) 2214 int 2215 getcurx(WINDOW *win) 2216 { 2217 int y, x; 2218 getyx(win, y, x); 2219 return x; 2220 } 2221 int 2222 getcury(WINDOW *win) 2223 { 2224 int y, x; 2225 getyx(win, y, x); 2226 return y; 2227 } 2228 #endif 2229 2230 #if !(defined(HAVE_GETMAXX) && defined(HAVE_GETMAXY)) 2231 int 2232 getmaxx(WINDOW *win) 2233 { 2234 int y, x; 2235 getmaxyx(win, y, x); 2236 return x; 2237 } 2238 int 2239 getmaxy(WINDOW *win) 2240 { 2241 int y, x; 2242 getmaxyx(win, y, x); 2243 return y; 2244 } 2245 #endif 2246 2247 #if !(defined(HAVE_GETPARX) && defined(HAVE_GETPARY)) 2248 int 2249 getparx(WINDOW *win) 2250 { 2251 int y, x; 2252 getparyx(win, y, x); 2253 return x; 2254 } 2255 int 2256 getpary(WINDOW *win) 2257 { 2258 int y, x; 2259 getparyx(win, y, x); 2260 return y; 2261 } 2262 #endif 2263