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