2 * $Id: util.c,v 1.300 2021/01/17 22:10:56 tom Exp $
4 * util.c -- miscellaneous utilities for dialog
6 * Copyright 2000-2020,2021 Thomas E. Dickey
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.
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.
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.
23 * An earlier version of this program lists as authors
24 * Savio Lam (lam836@cs.cuhk.hk)
29 #include <dlg_internals.h>
41 #ifdef HAVE_SYS_PARAM_H
44 #include <sys/param.h>
47 #if defined(NCURSES_VERSION)
48 #define CAN_KEEP_TITE 1
49 #elif defined(__NetBSD_Version__) && (__NetBSD_Version__ >= 800000000)
50 #define CAN_KEEP_TITE 1
52 #define CAN_KEEP_TITE 0
56 #if defined(NCURSES_VERSION) && defined(HAVE_NCURSESW_TERM_H)
57 #include <ncursesw/term.h>
58 #elif defined(NCURSES_VERSION) && defined(HAVE_NCURSES_TERM_H)
59 #include <ncurses/term.h>
65 #if defined(HAVE_WCHGAT)
66 # if defined(NCURSES_VERSION_PATCH)
67 # if NCURSES_VERSION_PATCH >= 20060715
80 DIALOG_STATE dialog_state;
81 DIALOG_VARS dialog_vars;
83 #if !(defined(HAVE_WGETPARENT) && defined(HAVE_WINDOW__PARENT))
84 #define NEED_WGETPARENT 1
86 #undef NEED_WGETPARENT
89 #define concat(a,b) a##b
92 #define RC_DATA(name,comment) , #name "_color", comment " color"
94 #define RC_DATA(name,comment) /*nothing */
98 #include <dlg_colors.h>
100 #define COLOR_DATA(upr) , \
101 concat(DLGC_FG_,upr), \
102 concat(DLGC_BG_,upr), \
103 concat(DLGC_HL_,upr), \
104 concat(DLGC_UL_,upr), \
106 #else /* HAVE_RC_FILE2 */
107 #define COLOR_DATA(upr) , \
108 concat(DLGC_FG_,upr), \
109 concat(DLGC_BG_,upr), \
111 #endif /* HAVE_RC_FILE2 */
112 #else /* HAVE_COLOR */
113 #define COLOR_DATA(upr) /*nothing */
114 #endif /* HAVE_COLOR */
116 #define UseShadow(dw) ((dw) != 0 && (dw)->normal != 0 && (dw)->shadow != 0)
119 * Table of color and attribute values, default is for mono display.
120 * The order matches the DIALOG_ATR() values.
122 #define DATA(atr,upr,lwr,cmt) { atr COLOR_DATA(upr) RC_DATA(lwr,cmt) }
124 DIALOG_COLORS dlg_color_table[] =
126 DATA(A_NORMAL, SCREEN, screen, "Screen"),
127 DATA(A_NORMAL, SHADOW, shadow, "Shadow"),
128 DATA(A_REVERSE, DIALOG, dialog, "Dialog box"),
129 DATA(A_REVERSE, TITLE, title, "Dialog box title"),
130 DATA(A_REVERSE, BORDER, border, "Dialog box border"),
131 DATA(A_BOLD, BUTTON_ACTIVE, button_active, "Active button"),
132 DATA(A_DIM, BUTTON_INACTIVE, button_inactive, "Inactive button"),
133 DATA(A_UNDERLINE, BUTTON_KEY_ACTIVE, button_key_active, "Active button key"),
134 DATA(A_UNDERLINE, BUTTON_KEY_INACTIVE, button_key_inactive, "Inactive button key"),
135 DATA(A_NORMAL, BUTTON_LABEL_ACTIVE, button_label_active, "Active button label"),
136 DATA(A_NORMAL, BUTTON_LABEL_INACTIVE, button_label_inactive, "Inactive button label"),
137 DATA(A_REVERSE, INPUTBOX, inputbox, "Input box"),
138 DATA(A_REVERSE, INPUTBOX_BORDER, inputbox_border, "Input box border"),
139 DATA(A_REVERSE, SEARCHBOX, searchbox, "Search box"),
140 DATA(A_REVERSE, SEARCHBOX_TITLE, searchbox_title, "Search box title"),
141 DATA(A_REVERSE, SEARCHBOX_BORDER, searchbox_border, "Search box border"),
142 DATA(A_REVERSE, POSITION_INDICATOR, position_indicator, "File position indicator"),
143 DATA(A_REVERSE, MENUBOX, menubox, "Menu box"),
144 DATA(A_REVERSE, MENUBOX_BORDER, menubox_border, "Menu box border"),
145 DATA(A_REVERSE, ITEM, item, "Item"),
146 DATA(A_NORMAL, ITEM_SELECTED, item_selected, "Selected item"),
147 DATA(A_REVERSE, TAG, tag, "Tag"),
148 DATA(A_REVERSE, TAG_SELECTED, tag_selected, "Selected tag"),
149 DATA(A_NORMAL, TAG_KEY, tag_key, "Tag key"),
150 DATA(A_BOLD, TAG_KEY_SELECTED, tag_key_selected, "Selected tag key"),
151 DATA(A_REVERSE, CHECK, check, "Check box"),
152 DATA(A_REVERSE, CHECK_SELECTED, check_selected, "Selected check box"),
153 DATA(A_REVERSE, UARROW, uarrow, "Up arrow"),
154 DATA(A_REVERSE, DARROW, darrow, "Down arrow"),
155 DATA(A_NORMAL, ITEMHELP, itemhelp, "Item help-text"),
156 DATA(A_BOLD, FORM_ACTIVE_TEXT, form_active_text, "Active form text"),
157 DATA(A_REVERSE, FORM_TEXT, form_text, "Form text"),
158 DATA(A_NORMAL, FORM_ITEM_READONLY, form_item_readonly, "Readonly form item"),
159 DATA(A_REVERSE, GAUGE, gauge, "Dialog box gauge"),
160 DATA(A_REVERSE, BORDER2, border2, "Dialog box border2"),
161 DATA(A_REVERSE, INPUTBOX_BORDER2, inputbox_border2, "Input box border2"),
162 DATA(A_REVERSE, SEARCHBOX_BORDER2, searchbox_border2, "Search box border2"),
163 DATA(A_REVERSE, MENUBOX_BORDER2, menubox_border2, "Menu box border2")
169 * Maintain a list of subwindows so that we can delete them to cleanup.
170 * More important, this provides a fallback when wgetparent() is not available.
173 add_subwindow(WINDOW *parent, WINDOW *child)
175 DIALOG_WINDOWS *p = dlg_calloc(DIALOG_WINDOWS, 1);
180 p->getc_timeout = WTIMEOUT_OFF;
181 p->next = dialog_state.all_subwindows;
182 dialog_state.all_subwindows = p;
187 del_subwindows(WINDOW *parent)
189 DIALOG_WINDOWS *p = dialog_state.all_subwindows;
190 DIALOG_WINDOWS *q = 0;
194 if (p->normal == parent) {
198 dialog_state.all_subwindows = r;
212 * Display background title if it exists ...
215 dlg_put_backtitle(void)
218 if (dialog_vars.backtitle != NULL) {
219 chtype attr = A_NORMAL;
220 int backwidth = dlg_count_columns(dialog_vars.backtitle);
223 dlg_attrset(stdscr, screen_attr);
224 (void) wmove(stdscr, 0, 1);
225 dlg_print_text(stdscr, dialog_vars.backtitle, COLS - 2, &attr);
226 for (i = 0; i < COLS - backwidth; i++)
227 (void) waddch(stdscr, ' ');
228 (void) wmove(stdscr, 1, 1);
229 for (i = 0; i < COLS - 2; i++)
230 (void) waddch(stdscr, dlg_boxchar(ACS_HLINE));
233 (void) wnoutrefresh(stdscr);
237 * Set window to attribute 'attr'. There are more efficient ways to do this,
238 * but will not work on older/buggy ncurses versions.
241 dlg_attr_clear(WINDOW *win, int height, int width, chtype attr)
245 dlg_attrset(win, attr);
246 for (i = 0; i < height; i++) {
247 (void) wmove(win, i, 0);
248 for (j = 0; j < width; j++)
249 (void) waddch(win, ' ');
251 (void) touchwin(win);
257 dlg_attr_clear(stdscr, LINES, COLS, screen_attr);
262 _dlg_resize_cleanup(WINDOW *w)
267 dlg_mouse_free_regions();
269 #endif /* KEY_RESIZE */
271 #define isprivate(s) ((s) != 0 && strstr(s, "\033[?") != 0)
273 #define TTY_DEVICE "/dev/tty"
276 * If $DIALOG_TTY exists, allow the program to try to open the terminal
277 * directly when stdout is redirected. By default we require the "--stdout"
278 * option to be given, but some scripts were written making use of the
279 * behavior of dialog which tried opening the terminal anyway.
281 #define dialog_tty() (dlg_getenv_num("DIALOG_TTY", (int *)0) > 0)
284 * Open the terminal directly. If one of stdin, stdout or stderr really points
285 * to a tty, use it. Otherwise give up and open /dev/tty.
288 open_terminal(char **result, int mode)
290 const char *device = TTY_DEVICE;
291 if (!isatty(fileno(stderr))
292 || (device = ttyname(fileno(stderr))) == 0) {
293 if (!isatty(fileno(stdout))
294 || (device = ttyname(fileno(stdout))) == 0) {
295 if (!isatty(fileno(stdin))
296 || (device = ttyname(fileno(stdin))) == 0) {
301 *result = dlg_strclone(device);
302 return open(device, mode);
310 int fd = fileno(dialog_state.screen_output);
312 buffer[0] = (char) ch;
313 return (int) write(fd, buffer, (size_t) 1);
318 * Do some initialization for dialog.
320 * 'input' is the real tty input of dialog. Usually it is stdin, but if
321 * --input-fd option is used, it may be anything.
323 * 'output' is where dialog will send its result. Usually it is stderr, but
324 * if --stdout or --output-fd is used, it may be anything. We are concerned
325 * mainly with the case where it happens to be the same as stdout.
328 init_dialog(FILE *input, FILE *output)
333 setlocale(LC_ALL, "");
335 dialog_state.output = output;
336 if (dialog_state.tab_len == 0)
337 dialog_state.tab_len = TAB_LEN;
338 if (dialog_state.aspect_ratio == 0)
339 dialog_state.aspect_ratio = DEFAULT_ASPECT_RATIO;
341 dialog_state.use_colors = USE_COLORS; /* use colors by default? */
342 dialog_state.use_shadow = USE_SHADOW; /* shadow dialog boxes by default? */
346 if (dlg_parse_rc() == -1) /* Read the configuration file */
347 dlg_exiterr("init_dialog: dlg_parse_rc");
351 * Some widgets (such as gauge) may read from the standard input. Pipes
352 * only connect stdout/stdin, so there is not much choice. But reading a
353 * pipe would get in the way of curses' normal reading stdin for getch.
355 * As in the --stdout (see below), reopening the terminal does not always
356 * work properly. dialog provides a --pipe-fd option for this purpose. We
357 * test that case first (differing fileno's for input/stdin). If the
358 * fileno's are equal, but we're not reading from a tty, see if we can open
361 dialog_state.pipe_input = stdin;
362 if (fileno(input) != fileno(stdin)) {
363 if ((fd1 = dup(fileno(input))) >= 0
364 && (fd2 = dup(fileno(stdin))) >= 0) {
365 (void) dup2(fileno(input), fileno(stdin));
366 dialog_state.pipe_input = fdopen(fd2, "r");
367 if (fileno(stdin) != 0) /* some functions may read fd #0 */
368 (void) dup2(fileno(stdin), 0);
370 dlg_exiterr("cannot open tty-input");
373 } else if (!isatty(fileno(stdin))) {
374 if ((fd1 = open_terminal(&device, O_RDONLY)) >= 0) {
375 if ((fd2 = dup(fileno(stdin))) >= 0) {
376 dialog_state.pipe_input = fdopen(fd2, "r");
377 if (freopen(device, "r", stdin) == 0)
378 dlg_exiterr("cannot open tty-input");
379 if (fileno(stdin) != 0) /* some functions may read fd #0 */
380 (void) dup2(fileno(stdin), 0);
388 * If stdout is not a tty and dialog is called with the --stdout option, we
389 * have to provide for a way to write to the screen.
391 * The curses library normally writes its output to stdout, leaving stderr
392 * free for scripting. Scripts are simpler when stdout is redirected. The
393 * newterm function is useful; it allows us to specify where the output
394 * goes. Reopening the terminal is not portable since several
395 * configurations do not allow this to work properly:
397 * a) some getty implementations (and possibly broken tty drivers, e.g., on
398 * HPUX 10 and 11) cause stdin to act as if it is still in cooked mode
399 * even though results from ioctl's state that it is successfully
400 * altered to raw mode. Broken is the proper term.
402 * b) the user may not have permissions on the device, e.g., if one su's
403 * from the login user to another non-privileged user.
405 if (!isatty(fileno(stdout))
406 && (fileno(stdout) == fileno(output) || dialog_tty())) {
407 if ((fd1 = open_terminal(&device, O_WRONLY)) >= 0
408 && (dialog_state.screen_output = fdopen(fd1, "w")) != 0) {
409 if (newterm(NULL, dialog_state.screen_output, stdin) == 0) {
410 dlg_exiterr("cannot initialize curses");
414 dlg_exiterr("cannot open tty-output");
417 dialog_state.screen_output = stdout;
420 dlg_keep_tite(dialog_state.screen_output);
424 (void) keypad(stdscr, TRUE);
428 if (!dialog_state.no_mouse) {
432 dialog_state.screen_initialized = TRUE;
435 if (dialog_state.use_colors || dialog_state.use_shadow)
436 dlg_color_setup(); /* Set up colors */
439 /* Set screen to screen attribute */
444 dlg_keep_tite(FILE *output)
446 if (!dialog_vars.keep_tite) {
449 * Cancel xterm's alternate-screen mode.
451 if ((fileno(output) != fileno(stdout)
452 || isatty(fileno(output)))
453 && key_mouse != 0 /* xterm and kindred */
454 && isprivate(enter_ca_mode)
455 && isprivate(exit_ca_mode)) {
456 FILE *save = dialog_state.screen_output;
459 * initscr() or newterm() already wrote enter_ca_mode as a side
460 * effect of initializing the screen. It would be nice to not even
461 * do that, but we do not really have access to the correct copy of
462 * the terminfo description until those functions have been
466 dialog_state.screen_output = output;
467 (void) tputs(exit_ca_mode, 0, my_putc);
468 (void) tputs(clear_screen, 0, my_putc);
469 dialog_state.screen_output = save;
472 * Prevent ncurses from switching "back" to the normal screen when
473 * exiting from dialog. That would move the cursor to the original
474 * location saved in xterm. Normally curses sets the cursor
475 * position to the first line after the display, but the alternate
476 * screen switching is done after that point.
478 * Cancelling the strings altogether also works around the buggy
479 * implementation of alternate-screen in rxvt, etc., which clear
480 * more of the display than they should.
487 * For other implementations, there are no useful answers:
488 * + SVr4 curses "could" support a similar approach, but the clue about
489 * xterm is absent from its terminal database.
490 * + PDCurses does not provide terminfo.
498 static int defined_colors = 1; /* pair-0 is reserved */
500 * Setup for color display
503 dlg_color_setup(void)
505 if (has_colors()) { /* Terminal supports color? */
508 (void) start_color();
510 #if defined(HAVE_USE_DEFAULT_COLORS)
511 use_default_colors();
514 #if defined(__NetBSD__) && defined(_CURSES_)
515 #define C_ATTR(x,y) (((x) != 0 ? A_BOLD : 0) | COLOR_PAIR((y)))
516 /* work around bug in NetBSD curses */
517 for (i = 0; i < sizeof(dlg_color_table) /
518 sizeof(dlg_color_table[0]); i++) {
520 /* Initialize color pairs */
521 (void) init_pair(i + 1,
522 dlg_color_table[i].fg,
523 dlg_color_table[i].bg);
525 /* Setup color attributes */
526 dlg_color_table[i].atr = C_ATTR(dlg_color_table[i].hilite, i + 1);
528 defined_colors = i + 1;
530 for (i = 0; i < sizeof(dlg_color_table) /
531 sizeof(dlg_color_table[0]); i++) {
533 /* Initialize color pairs */
534 chtype atr = dlg_color_pair(dlg_color_table[i].fg,
535 dlg_color_table[i].bg);
537 atr |= (dlg_color_table[i].hilite ? A_BOLD : 0);
539 atr |= (dlg_color_table[i].ul ? A_UNDERLINE : 0);
540 atr |= (dlg_color_table[i].rv ? A_REVERSE : 0);
541 #endif /* HAVE_RC_FILE2 */
543 dlg_color_table[i].atr = atr;
547 dialog_state.use_colors = FALSE;
548 dialog_state.use_shadow = FALSE;
553 dlg_color_count(void)
555 return TableSize(dlg_color_table);
559 * Wrapper for getattrs(), or the more cumbersome X/Open wattr_get().
562 dlg_get_attrs(WINDOW *win)
566 result = (chtype) getattrs(win);
570 wattr_get(win, &my_result, &my_pair, NULL);
577 * Reuse color pairs (they are limited), returning a COLOR_PAIR() value if we
578 * have (or can) define a pair with the given color as foreground on the
579 * window's defined background.
582 dlg_color_pair(int foreground, int background)
589 for (pair = 1; pair < defined_colors; ++pair) {
590 if (pair_content((short) pair, &fg, &bg) != ERR
592 && bg == background) {
593 result = (chtype) COLOR_PAIR(pair);
598 if (!found && (defined_colors + 1) < COLOR_PAIRS) {
599 pair = defined_colors++;
600 (void) init_pair((short) pair, (short) foreground, (short) background);
601 result = (chtype) COLOR_PAIR(pair);
607 * Reuse color pairs (they are limited), returning a COLOR_PAIR() value if we
608 * have (or can) define a pair with the given color as foreground on the
609 * window's defined background.
612 define_color(WINDOW *win, int foreground)
614 short fg, bg, background;
615 if (dialog_state.text_only) {
616 background = COLOR_BLACK;
618 chtype attrs = dlg_get_attrs(win);
621 if ((pair = PAIR_NUMBER(attrs)) != 0
622 && pair_content((short) pair, &fg, &bg) != ERR) {
625 background = COLOR_BLACK;
628 return dlg_color_pair(foreground, background);
633 * End using dialog functions.
638 if (dialog_state.screen_initialized) {
639 dialog_state.screen_initialized = FALSE;
640 if (dialog_vars.erase_on_exit) {
642 * Clear the screen to the native background color, and leave the
643 * terminal cursor at the lower-left corner of the screen.
650 (void) fflush(stdout);
655 #define isOurEscape(p) (((p)[0] == '\\') && ((p)[1] == 'Z') && ((p)[2] != 0))
658 dlg_count_real_columns(const char *text)
662 result = dlg_count_columns(text);
663 if (result && dialog_vars.colors) {
666 if (dialog_vars.colors && isOurEscape(text)) {
667 hidden += ESCAPE_LEN;
680 centered(int width, const char *string)
682 int need = dlg_count_real_columns(string);
685 left = (width - need) / 2 - 1;
691 #ifdef USE_WIDE_CURSES
693 is_combining(const char *txt, int *combined)
697 if (*combined == 0) {
698 if (UCH(*txt) >= 128) {
701 size_t given = strlen(txt);
704 memset(&state, 0, sizeof(state));
705 len = mbrtowc(&wch, txt, given, &state);
706 if ((int) len > 0 && wcwidth(wch) == 0) {
707 *combined = (int) len - 1;
720 * Print the name (tag) or text from a DIALOG_LISTITEM, highlighting the
721 * first character if selected.
724 dlg_print_listitem(WINDOW *win,
730 chtype attr = A_NORMAL;
737 if (first && !dialog_vars.no_hot_list) {
738 const int *indx = dlg_index_wchars(text);
739 attrs[3] = tag_key_selected_attr;
740 attrs[2] = tag_key_attr;
741 attrs[1] = tag_selected_attr;
744 dlg_attrset(win, selected ? attrs[3] : attrs[2]);
746 (void) waddnstr(win, text, indx[1]);
748 if ((int) strlen(text) > indx[1]) {
749 limit = dlg_limit_columns(text, climit, 1);
751 dlg_attrset(win, selected ? attrs[1] : attrs[0]);
754 indx[limit] - indx[1]);
761 attrs[1] = item_selected_attr;
762 attrs[0] = item_attr;
764 cols = dlg_index_columns(text);
765 limit = dlg_limit_columns(text, climit, 0);
768 dlg_attrset(win, selected ? attrs[1] : attrs[0]);
769 dlg_print_text(win, text, cols[limit], &attr);
775 * Print up to 'cols' columns from 'text', optionally rendering our escape
776 * sequence for attributes and color.
779 dlg_print_text(WINDOW *win, const char *txt, int cols, chtype *attr)
781 int y_origin, x_origin;
782 int y_before, x_before = 0;
783 int y_after, x_after;
786 #ifdef USE_WIDE_CURSES
790 if (dialog_state.text_only) {
791 y_origin = y_after = 0;
792 x_origin = x_after = 0;
796 getyx(win, y_origin, x_origin);
798 while (cols > 0 && (*txt != '\0')) {
802 if (dialog_vars.colors) {
803 while (isOurEscape(txt)) {
807 switch (code = CharOf(*txt)) {
818 *attr |= define_color(win, code - '0');
834 *attr &= ~A_UNDERLINE;
837 *attr |= A_UNDERLINE;
848 if (ended || *txt == '\n' || *txt == '\0')
850 useattr = (*attr) & A_ATTRIBUTES;
853 * Prevent this from making text invisible when the foreground and
854 * background colors happen to be the same, and there's no bold
857 if ((useattr & A_COLOR) != 0 && (useattr & A_BOLD) == 0) {
858 short pair = (short) PAIR_NUMBER(useattr);
860 if (pair_content(pair, &fg, &bg) != ERR
863 useattr |= dlg_color_pair(fg, ((bg == COLOR_BLACK)
870 * Write the character, using curses to tell exactly how wide it
871 * is. If it is a tab, discount that, since the caller thinks
872 * tabs are nonprinting, and curses will expand tabs to one or
875 thisTab = (CharOf(*txt) == TAB);
876 if (dialog_state.text_only) {
880 getyx(win, y_before, x_before);
884 if (dialog_state.text_only) {
885 int ch = CharOf(*txt++);
887 while ((x_after++) % 8) {
888 fputc(' ', dialog_state.output);
891 fputc(ch, dialog_state.output);
892 x_after++; /* FIXME: handle meta per locale */
895 (void) waddch(win, CharOf(*txt++) | useattr);
896 getyx(win, y_after, x_after);
898 if (thisTab && (y_after == y_origin))
899 tabbed += (x_after - x_before);
900 if ((y_after != y_origin) ||
901 (x_after >= (cols + tabbed + x_origin)
902 #ifdef USE_WIDE_CURSES
903 && !is_combining(txt, &combined)
909 if (dialog_state.text_only) {
910 fputc('\n', dialog_state.output);
915 * Print one line of the prompt in the window within the limits of the
916 * specified right margin. The line will end on a word boundary and a pointer
917 * to the start of the next line is returned, or a NULL pointer if the end of
918 * *prompt is reached.
921 dlg_print_line(WINDOW *win,
924 int lm, int rm, int *x)
926 const char *wrap_ptr;
927 const char *test_ptr;
928 const char *hide_ptr = 0;
929 const int *cols = dlg_index_columns(prompt);
930 const int *indx = dlg_index_wchars(prompt);
935 int limit = dlg_count_wchars(prompt);
942 * Set *test_ptr to the end of the line or the right margin (rm), whichever
943 * is less, and set wrap_ptr to the end of the last word in the line.
945 for (n = 0; n < limit; ++n) {
946 int ch = *(test_ptr = prompt + indx[test_inx]);
947 if (ch == '\n' || ch == '\0' || cur_x >= (rm + hidden))
949 if (ch == TAB && n == 0) {
950 tabbed = 8; /* workaround for leading tabs */
951 } else if (isblank(UCH(ch))
953 && !isblank(UCH(prompt[indx[n - 1]]))) {
956 } else if (dialog_vars.colors && isOurEscape(test_ptr)) {
958 hidden += ESCAPE_LEN;
959 n += (ESCAPE_LEN - 1);
961 cur_x = lm + tabbed + cols[n + 1];
962 if (cur_x > (rm + hidden))
968 * If the line doesn't reach the right margin in the middle of a word, then
969 * we don't have to wrap it at the end of the previous word.
971 test_ptr = prompt + indx[test_inx];
972 if (*test_ptr == '\n' || isblank(UCH(*test_ptr)) || *test_ptr == '\0') {
974 while (wrap_inx > 0 && isblank(UCH(prompt[indx[wrap_inx - 1]]))) {
977 *x = lm + indx[wrap_inx];
978 } else if (*x == 1 && cur_x >= rm) {
980 * If the line has no spaces, then wrap it anyway at the right margin
985 wrap_ptr = prompt + indx[wrap_inx];
986 #ifdef USE_WIDE_CURSES
987 if (UCH(*wrap_ptr) >= 128) {
989 while (is_combining(wrap_ptr, &combined)) {
996 * If we found hidden text past the last point that we will display,
997 * discount that from the displayed length.
999 if ((hide_ptr != 0) && (hide_ptr >= wrap_ptr)) {
1000 hidden -= ESCAPE_LEN;
1001 test_ptr = wrap_ptr;
1002 while (test_ptr < wrap_ptr) {
1003 if (dialog_vars.colors && isOurEscape(test_ptr)) {
1004 hidden -= ESCAPE_LEN;
1005 test_ptr += ESCAPE_LEN;
1013 * Print the line if we have a window pointer. Otherwise this routine
1014 * is just being called for sizing the window.
1016 if (dialog_state.text_only || win) {
1017 dlg_print_text(win, prompt, (cols[wrap_inx] - hidden), attr);
1020 /* *x tells the calling function how long the line was */
1027 /* Find the start of the next line and return a pointer to it */
1028 test_ptr = wrap_ptr;
1029 while (isblank(UCH(*test_ptr)))
1031 if (*test_ptr == '\n')
1033 dlg_finish_string(prompt);
1038 justify_text(WINDOW *win,
1042 int *high, int *wide)
1044 chtype attr = A_NORMAL;
1048 int lm = (2 * MARGIN); /* left margin (box-border plus a space) */
1049 int rm = limit_x; /* right margin */
1050 int bm = limit_y; /* bottom margin */
1051 int last_y = 0, last_x = 0;
1053 dialog_state.text_height = 0;
1054 dialog_state.text_width = 0;
1055 if (dialog_state.text_only || win) {
1063 getyx(win, last_y, last_x);
1064 while (y <= bm && *prompt) {
1067 if (*prompt == '\n') {
1068 while (*prompt == '\n' && y < bm) {
1069 if (*(prompt + 1) != '\0') {
1072 (void) wmove(win, y, lm);
1076 } else if (win != 0)
1077 (void) wmove(win, y, lm);
1080 prompt = dlg_print_line(win, &attr, prompt, lm, rm, &x);
1082 getyx(win, last_y, last_x);
1087 (void) wmove(win, y, lm);
1089 max_x = MAX(max_x, x);
1091 /* Move back to the last position after drawing prompt, for msgbox. */
1093 (void) wmove(win, last_y, last_x);
1095 /* Set the final height and width for the calling function */
1103 * Print a string of text in a window, automatically wrap around to the next
1104 * line if the string is too long to fit on one line. Note that the string may
1105 * contain embedded newlines.
1108 dlg_print_autowrap(WINDOW *win, const char *prompt, int height, int width)
1110 justify_text(win, prompt,
1113 (int *) 0, (int *) 0);
1117 * Display the message in a scrollable window. Actually the way it works is
1118 * that we create a "tall" window of the proper width, let the text wrap within
1119 * that, and copy a slice of the result to the dialog.
1121 * It works for ncurses. Other curses implementations show only blanks (Tru64)
1122 * or garbage (NetBSD).
1125 dlg_print_scrolled(WINDOW *win,
1135 (void) pauseopt; /* used only for ncurses */
1137 getyx(win, oldy, oldx);
1138 #ifdef NCURSES_VERSION
1140 int wide = width - (2 * MARGIN);
1145 #if defined(NCURSES_VERSION_PATCH) && NCURSES_VERSION_PATCH >= 20040417
1147 * If we're not limited by the screensize, allow text to possibly be
1148 * one character per line.
1150 if ((len = dlg_count_columns(prompt)) > high)
1153 dummy = newwin(high, width, 0, 0);
1155 dlg_attrset(win, dialog_attr);
1156 dlg_print_autowrap(win, prompt, height + 1 + (3 * MARGIN), width);
1161 wbkgdset(dummy, dialog_attr | ' ');
1162 dlg_attrset(dummy, dialog_attr);
1164 dlg_print_autowrap(dummy, prompt, high, width);
1168 copywin(dummy, /* srcwin */
1170 offset + MARGIN, /* sminrow */
1171 MARGIN, /* smincol */
1172 MARGIN, /* dminrow */
1173 MARGIN, /* dmincol */
1174 height, /* dmaxrow */
1180 /* if the text is incomplete, or we have scrolled, show the percentage */
1181 if (y > 0 && wide > 4) {
1182 int percent = (int) ((height + offset) * 100.0 / y);
1189 if (offset != 0 || percent != 100) {
1192 dlg_attrset(win, position_indicator_attr);
1193 (void) wmove(win, MARGIN + height, wide - 4);
1194 (void) sprintf(buffer, "%d%%", percent);
1195 (void) waddstr(win, buffer);
1196 if ((len = (int) strlen(buffer)) < 4) {
1197 dlg_attrset(win, border_attr);
1198 whline(win, dlg_boxchar(ACS_HLINE), 4 - len);
1202 last = (y - height);
1208 dlg_attrset(win, dialog_attr);
1209 dlg_print_autowrap(win, prompt, height + 1 + (3 * MARGIN), width);
1212 wmove(win, oldy, oldx);
1217 dlg_check_scrolled(int key, int last, int page, bool * show, int *offset)
1224 case DLGK_PAGE_FIRST:
1230 case DLGK_PAGE_LAST:
1231 if (*offset < last) {
1242 case DLGK_GRID_DOWN:
1243 if (*offset < last) {
1248 case DLGK_PAGE_PREV:
1256 case DLGK_PAGE_NEXT:
1257 if (*offset < last) {
1272 * Calculate the window size for preformatted text. This will calculate box
1273 * dimensions that are at or close to the specified aspect ratio for the prompt
1274 * string with all spaces and newlines preserved and additional newlines added
1278 auto_size_preformatted(const char *prompt, int *height, int *width)
1280 int high = 0, wide = 0;
1281 float car; /* Calculated Aspect Ratio */
1282 int max_y = SLINES - 1;
1283 int max_x = SCOLS - 2;
1284 int max_width = max_x;
1285 int ar = dialog_state.aspect_ratio;
1287 /* Get the initial dimensions */
1288 justify_text((WINDOW *) 0, prompt, max_y, max_x, &high, &wide);
1289 car = (float) (wide / high);
1292 * If the aspect ratio is greater than it should be, then decrease the
1293 * width proportionately.
1296 float diff = car / (float) ar;
1297 max_x = (int) ((float) wide / diff + 4);
1298 justify_text((WINDOW *) 0, prompt, max_y, max_x, &high, &wide);
1299 car = (float) wide / (float) high;
1303 * If the aspect ratio is too small after decreasing the width, then
1304 * incrementally increase the width until the aspect ratio is equal to or
1305 * greater than the specified aspect ratio.
1307 while (car < ar && max_x < max_width) {
1309 justify_text((WINDOW *) 0, prompt, max_y, max_x, &high, &wide);
1310 car = (float) (wide / high);
1318 * Find the length of the longest "word" in the given string. By setting the
1319 * widget width at least this long, we can avoid splitting a word on the
1323 longest_word(const char *string)
1327 while (*string != '\0') {
1329 while (*string != '\0' && !isspace(UCH(*string))) {
1333 result = MAX(result, length);
1334 if (*string != '\0')
1341 * if (height or width == -1) Maximize()
1342 * if (height or width == 0), justify and return actual limits.
1345 real_auto_size(const char *title,
1347 int *height, int *width,
1348 int boxlines, int mincols)
1350 int x = (dialog_vars.begin_set ? dialog_vars.begin_x : 2);
1351 int y = (dialog_vars.begin_set ? dialog_vars.begin_y : 1);
1352 int title_length = title ? dlg_count_columns(title) : 0;
1354 int save_high = *height;
1355 int save_wide = *width;
1366 max_high = (*height < 0);
1367 max_wide = (*width < 0);
1379 wide = MAX(title_length, mincols);
1380 if (strchr(prompt, '\n') == 0) {
1381 double val = (dialog_state.aspect_ratio *
1382 dlg_count_real_columns(prompt));
1383 double xxx = sqrt(val);
1384 int tmp = (int) xxx;
1385 wide = MAX(wide, tmp);
1386 wide = MAX(wide, longest_word(prompt));
1387 justify_text((WINDOW *) 0, prompt, high, wide, height, width);
1389 auto_size_preformatted(prompt, height, width);
1393 justify_text((WINDOW *) 0, prompt, high, wide, height, width);
1397 if (*width < title_length) {
1398 justify_text((WINDOW *) 0, prompt, high, title_length, height, width);
1399 *width = title_length;
1402 dialog_state.text_height = *height;
1403 dialog_state.text_width = *width;
1405 if (*width < mincols && save_wide == 0)
1408 *width += ((2 * MARGIN) + SHADOW_COLS);
1409 *height += boxlines + (2 * MARGIN);
1413 *height = save_high;
1418 *height = SLINES - (dialog_vars.begin_set ? dialog_vars.begin_y : 0);
1420 *width = SCOLS - (dialog_vars.begin_set ? dialog_vars.begin_x : 0);
1423 /* End of real_auto_size() */
1426 dlg_auto_size(const char *title,
1433 DLG_TRACE(("# dlg_auto_size(%d,%d) limits %d,%d\n",
1435 boxlines, mincols));
1437 real_auto_size(title, prompt, height, width, boxlines, mincols);
1439 if (*width > SCOLS) {
1444 if (*height > SLINES) {
1447 DLG_TRACE(("# ...dlg_auto_size(%d,%d) also %d,%d\n",
1449 dialog_state.text_height, dialog_state.text_width));
1453 * if (height or width == -1) Maximize()
1454 * if (height or width == 0)
1455 * height=MIN(SLINES, num.lines in fd+n);
1456 * width=MIN(SCOLS, MAX(longer line+n, mincols));
1459 dlg_auto_sizefile(const char *title,
1467 int len = title ? dlg_count_columns(title) : 0;
1472 /* Open input file for reading */
1473 if ((fd = fopen(file, "rb")) == NULL)
1474 dlg_exiterr("dlg_auto_sizefile: Cannot open input file %s", file);
1476 if ((*height == -1) || (*width == -1)) {
1477 *height = SLINES - (dialog_vars.begin_set ? dialog_vars.begin_y : 0);
1478 *width = SCOLS - (dialog_vars.begin_set ? dialog_vars.begin_x : 0);
1480 if ((*height != 0) && (*width != 0)) {
1484 if (*height > SLINES)
1497 while (((ch = getc(fd)) != '\n') && !feof(fd)) {
1498 if ((ch == TAB) && (dialog_vars.tab_correct)) {
1499 offset += dialog_state.tab_len - (offset % dialog_state.tab_len);
1511 /* now 'count' has the number of lines of fd and 'len' the max length */
1513 *height = MIN(SLINES, count + numlines + boxlines);
1514 *width = MIN(SCOLS, MAX((len + nc), mincols));
1515 /* here width and height can be maximized if > SCOLS|SLINES because
1516 textbox-like widgets don't put all <file> on the screen.
1517 Msgbox-like widget instead have to put all <text> correctly. */
1523 * Draw a rectangular box with line drawing characters.
1525 * borderchar is used to color the upper/left edges.
1527 * boxchar is used to color the right/lower edges. It also is fill-color used
1528 * for the box contents.
1530 * Normally, if you are drawing a scrollable box, use menubox_border_attr for
1531 * boxchar, and menubox_attr for borderchar since the scroll-arrows are drawn
1532 * with menubox_attr at the top, and menubox_border_attr at the bottom. That
1533 * also (given the default color choices) produces a recessed effect.
1535 * If you want a raised effect (and are not going to use the scroll-arrows),
1536 * reverse this choice.
1539 dlg_draw_box2(WINDOW *win, int y, int x, int height, int width,
1540 chtype boxchar, chtype borderchar, chtype borderchar2)
1543 chtype save = dlg_get_attrs(win);
1545 dlg_attrset(win, 0);
1546 for (i = 0; i < height; i++) {
1547 (void) wmove(win, y + i, x);
1548 for (j = 0; j < width; j++)
1550 (void) waddch(win, borderchar | dlg_boxchar(ACS_ULCORNER));
1551 else if (i == height - 1 && !j)
1552 (void) waddch(win, borderchar | dlg_boxchar(ACS_LLCORNER));
1553 else if (!i && j == width - 1)
1554 (void) waddch(win, borderchar2 | dlg_boxchar(ACS_URCORNER));
1555 else if (i == height - 1 && j == width - 1)
1556 (void) waddch(win, borderchar2 | dlg_boxchar(ACS_LRCORNER));
1558 (void) waddch(win, borderchar | dlg_boxchar(ACS_HLINE));
1559 else if (i == height - 1)
1560 (void) waddch(win, borderchar2 | dlg_boxchar(ACS_HLINE));
1562 (void) waddch(win, borderchar | dlg_boxchar(ACS_VLINE));
1563 else if (j == width - 1)
1564 (void) waddch(win, borderchar2 | dlg_boxchar(ACS_VLINE));
1566 (void) waddch(win, boxchar | ' ');
1568 dlg_attrset(win, save);
1572 dlg_draw_box(WINDOW *win, int y, int x, int height, int width,
1573 chtype boxchar, chtype borderchar)
1575 dlg_draw_box2(win, y, x, height, width, boxchar, borderchar, boxchar);
1579 * Search the given 'list' for the given window 'win'. Typically 'win' is an
1580 * input-window, i.e., a window where we might use wgetch.
1582 * The all-windows list has normal- and shadow-windows. Since we never use the
1583 * shadow as an input window, normally we just look for the normal-window.
1585 * However, the all-subwindows list stores parent/child windows rather than
1586 * normal/shadow windows. When searching that list, we look for the child
1587 * window (in the .shadow field).
1589 static DIALOG_WINDOWS *
1590 find_window(DIALOG_WINDOWS * list, WINDOW *win, bool normal)
1592 DIALOG_WINDOWS *result = 0;
1595 for (p = list; p != 0; p = p->next) {
1596 WINDOW *check = normal ? p->normal : p->shadow;
1605 #define SearchTopWindows(win) find_window(dialog_state.all_windows, win, TRUE)
1606 #define SearchSubWindows(win) find_window(dialog_state.all_subwindows, win, FALSE)
1609 * Check for the existence of a window, e.g., when used for input or updating
1610 * the display. This is used in dlg_getc() and related functions, to guard
1611 * against an asynchronous window-deletion that might invalidate the input
1612 * window used in dlg_getc().
1615 _dlg_find_window(WINDOW *win)
1617 DIALOG_WINDOWS *result = 0;
1619 if ((result = SearchTopWindows(win)) == NULL)
1620 result = SearchSubWindows(win);
1626 * If we have wchgat(), use that for updating shadow attributes, to work with
1627 * wide-character data.
1631 * Check if the given point is "in" the given window. If so, return the window
1632 * pointer, otherwise null.
1635 in_window(WINDOW *win, int y, int x)
1638 int y_base = getbegy(win);
1639 int x_base = getbegx(win);
1640 int y_last = getmaxy(win) + y_base;
1641 int x_last = getmaxx(win) + x_base;
1643 if (y >= y_base && y <= y_last && x >= x_base && x <= x_last)
1649 window_at_cell(DIALOG_WINDOWS * dw, int y, int x)
1653 int y_want = y + getbegy(dw->shadow);
1654 int x_want = x + getbegx(dw->shadow);
1656 for (p = dialog_state.all_windows; p != 0; p = p->next) {
1657 if (dw->normal != p->normal
1658 && dw->shadow != p->normal
1659 && (result = in_window(p->normal, y_want, x_want)) != 0) {
1670 in_shadow(WINDOW *normal, WINDOW *shadow, int y, int x)
1672 bool result = FALSE;
1673 int ybase = getbegy(normal);
1674 int ylast = getmaxy(normal) + ybase;
1675 int xbase = getbegx(normal);
1676 int xlast = getmaxx(normal) + xbase;
1678 y += getbegy(shadow);
1679 x += getbegx(shadow);
1681 if (y >= ybase + SHADOW_ROWS
1682 && y < ylast + SHADOW_ROWS
1684 && x < xlast + SHADOW_COLS) {
1685 /* in the right-side */
1687 } else if (y >= ylast
1688 && y < ylast + SHADOW_ROWS
1689 && x >= ybase + SHADOW_COLS
1690 && x < ylast + SHADOW_COLS) {
1691 /* check the bottom */
1699 * When erasing a shadow, check each cell to make sure that it is not part of
1700 * another box's shadow. This is a little complicated since most shadows are
1701 * merged onto stdscr.
1704 last_shadow(DIALOG_WINDOWS * dw, int y, int x)
1709 for (p = dialog_state.all_windows; p != 0; p = p->next) {
1710 if (p->normal != dw->normal
1711 && in_shadow(p->normal, dw->shadow, y, x)) {
1720 repaint_cell(DIALOG_WINDOWS * dw, bool draw, int y, int x)
1722 WINDOW *win = dw->shadow;
1726 if ((cellwin = window_at_cell(dw, y, x)) != 0
1727 && (draw || last_shadow(dw, y, x))
1728 && (y2 = (y + getbegy(win) - getbegy(cellwin))) >= 0
1729 && (x2 = (x + getbegx(win) - getbegx(cellwin))) >= 0
1730 && wmove(cellwin, y2, x2) != ERR) {
1731 chtype the_cell = dlg_get_attrs(cellwin);
1732 chtype the_attr = (draw ? shadow_attr : the_cell);
1734 if (winch(cellwin) & A_ALTCHARSET) {
1735 the_attr |= A_ALTCHARSET;
1739 the_attr & (chtype) (~A_COLOR),
1740 (short) PAIR_NUMBER(the_attr),
1744 chtype the_char = ((winch(cellwin) & A_CHARTEXT) | the_attr);
1745 (void) waddch(cellwin, the_char);
1748 wnoutrefresh(cellwin);
1752 #define RepaintCell(dw, draw, y, x) repaint_cell(dw, draw, y, x)
1755 repaint_shadow(DIALOG_WINDOWS * dw, bool draw, int y, int x, int height, int width)
1757 if (UseShadow(dw)) {
1761 chtype save = dlg_get_attrs(dw->shadow);
1762 dlg_attrset(dw->shadow, draw ? shadow_attr : screen_attr);
1764 for (i = 0; i < SHADOW_ROWS; ++i) {
1765 for (j = 0; j < width; ++j) {
1766 RepaintCell(dw, draw, i + y + height, j + x + SHADOW_COLS);
1769 for (i = 0; i < height; i++) {
1770 for (j = 0; j < SHADOW_COLS; ++j) {
1771 RepaintCell(dw, draw, i + y + SHADOW_ROWS, j + x + width);
1774 (void) wnoutrefresh(dw->shadow);
1776 dlg_attrset(dw->shadow, save);
1782 * Draw a shadow on the parent window corresponding to the right- and
1783 * bottom-edge of the child window, to give a 3-dimensional look.
1786 draw_childs_shadow(DIALOG_WINDOWS * dw)
1788 if (UseShadow(dw)) {
1791 getbegy(dw->normal) - getbegy(dw->shadow),
1792 getbegx(dw->normal) - getbegx(dw->shadow),
1793 getmaxy(dw->normal),
1794 getmaxx(dw->normal));
1799 * Erase a shadow on the parent window corresponding to the right- and
1800 * bottom-edge of the child window.
1803 erase_childs_shadow(DIALOG_WINDOWS * dw)
1805 if (UseShadow(dw)) {
1808 getbegy(dw->normal) - getbegy(dw->shadow),
1809 getbegx(dw->normal) - getbegx(dw->shadow),
1810 getmaxy(dw->normal),
1811 getmaxx(dw->normal));
1816 * Draw shadows along the right and bottom edge to give a more 3D look
1820 dlg_draw_shadow(WINDOW *win, int y, int x, int height, int width)
1822 repaint_shadow(SearchTopWindows(win), TRUE, y, x, height, width);
1824 #endif /* HAVE_COLOR */
1827 * Allow shell scripts to remap the exit codes so they can distinguish ESC
1834 static const struct {
1838 { DLG_EXIT_CANCEL, "DIALOG_CANCEL" },
1839 { DLG_EXIT_ERROR, "DIALOG_ERROR" },
1840 { DLG_EXIT_ESC, "DIALOG_ESC" },
1841 { DLG_EXIT_EXTRA, "DIALOG_EXTRA" },
1842 { DLG_EXIT_HELP, "DIALOG_HELP" },
1843 { DLG_EXIT_OK, "DIALOG_OK" },
1844 { DLG_EXIT_ITEM_HELP, "DIALOG_ITEM_HELP" },
1845 { DLG_EXIT_TIMEOUT, "DIALOG_TIMEOUT" },
1850 bool overridden = FALSE;
1853 for (n = 0; n < TableSize(table); n++) {
1854 if (table[n].code == code) {
1855 if (dlg_getenv_num(table[n].name, &code)) {
1863 * Prior to 2004/12/19, a widget using --item-help would exit with "OK"
1864 * if the help button were selected. Now we want to exit with "HELP",
1865 * but allow the environment variable to override.
1867 if (code == DLG_EXIT_ITEM_HELP && !overridden) {
1868 code = DLG_EXIT_HELP;
1871 #ifdef HAVE_DLG_TRACE
1872 dlg_trace((const char *) 0); /* close it */
1876 _dlg_inputstr_leaks();
1877 #if defined(NCURSES_VERSION) && (defined(HAVE_CURSES_EXIT) || defined(HAVE__NC_FREE_AND_EXIT))
1882 if (dialog_state.input == stdin) {
1886 * Just in case of using --input-fd option, do not
1887 * call atexit functions of ncurses which may hang.
1889 if (dialog_state.input) {
1890 fclose(dialog_state.input);
1891 dialog_state.input = 0;
1893 if (dialog_state.pipe_input) {
1894 if (dialog_state.pipe_input != stdin) {
1895 fclose(dialog_state.pipe_input);
1896 dialog_state.pipe_input = 0;
1903 #define DATA(name) { DLG_EXIT_ ## name, #name }
1908 } exit_codenames[] = {
1922 dlg_exitcode2s(int code)
1924 const char *result = "?";
1927 for (n = 0; n < TableSize(exit_codenames); ++n) {
1928 if (exit_codenames[n].code == code) {
1929 result = exit_codenames[n].name;
1937 dlg_exitname2n(const char *name)
1939 int result = DLG_EXIT_UNKNOWN;
1942 for (n = 0; n < TableSize(exit_codenames); ++n) {
1943 if (!dlg_strcmp(exit_codenames[n].name, name)) {
1944 result = exit_codenames[n].code;
1951 /* quit program killing all tailbg */
1953 dlg_exiterr(const char *fmt, ...)
1960 (void) fputc('\n', stderr);
1962 (void) vfprintf(stderr, fmt, ap);
1964 (void) fputc('\n', stderr);
1966 #ifdef HAVE_DLG_TRACE
1968 dlg_trace_msg("## Error: ");
1969 dlg_trace_va_msg(fmt, ap);
1973 dlg_killall_bg(&retval);
1975 (void) fflush(stderr);
1976 (void) fflush(stdout);
1977 dlg_exit(strcmp(fmt, "timeout") == 0 ? DLG_EXIT_TIMEOUT : DLG_EXIT_ERROR);
1981 * Get a string from the environment, rejecting those which are entirely blank.
1984 dlg_getenv_str(const char *name)
1986 char *result = getenv(name);
1987 if (result != NULL) {
1988 while (*result != '\0' && isspace(UCH(*result)))
1990 if (*result == '\0')
1997 * Get a number from the environment:
1998 * + If the caller provides a pointer in the second parameter, return
1999 * success/failure for the function return, and the actual value via the
2000 * pointer. Use this for decoding arbitrary numbers, e.g., negative or zero.
2001 * + If the caller does not provide a pointer, return the decoded value for
2002 * the function-return. Use this when only values greater than zero are
2006 dlg_getenv_num(const char *name, int *value)
2009 char *data = getenv(name);
2012 long check = strtol(data, &temp, 0);
2013 if (temp != 0 && temp != data && *temp == '\0') {
2014 result = (int) check;
2015 if (value != NULL) {
2027 if (dialog_vars.beep_signal) {
2029 dialog_vars.beep_signal = 0;
2034 dlg_print_size(int height, int width)
2036 if (dialog_vars.print_siz) {
2037 fprintf(dialog_state.output, "Size: %d, %d\n", height, width);
2038 DLG_TRACE(("# print size: %dx%d\n", height, width));
2043 dlg_ctl_size(int height, int width)
2045 if (dialog_vars.size_err) {
2046 if ((width > COLS) || (height > LINES)) {
2047 dlg_exiterr("Window too big. (height, width) = (%d, %d). Max allowed (%d, %d).",
2048 height, width, LINES, COLS);
2051 else if ((dialog_state.use_shadow)
2052 && ((width > SCOLS || height > SLINES))) {
2053 if ((width <= COLS) && (height <= LINES)) {
2054 /* try again, without shadows */
2055 dialog_state.use_shadow = 0;
2057 dlg_exiterr("Window+Shadow too big. (height, width) = (%d, %d). Max allowed (%d, %d).",
2058 height, width, SLINES, SCOLS);
2066 * If the --tab-correct was not selected, convert tabs to single spaces.
2069 dlg_tab_correct_str(char *prompt)
2073 if (dialog_vars.tab_correct) {
2074 while ((ptr = strchr(prompt, TAB)) != NULL) {
2082 dlg_calc_listh(int *height, int *list_height, int item_no)
2084 /* calculate new height and list_height */
2085 int rows = SLINES - (dialog_vars.begin_set ? dialog_vars.begin_y : 0);
2086 if (rows - (*height) > 0) {
2087 if (rows - (*height) > item_no)
2088 *list_height = item_no;
2090 *list_height = rows - (*height);
2092 (*height) += (*list_height);
2097 dlg_calc_listw(int item_no, char **items, int group)
2099 int i, len1 = 0, len2 = 0;
2101 for (i = 0; i < (item_no * group); i += group) {
2104 if ((n = dlg_count_columns(items[i])) > len1)
2106 if ((n = dlg_count_columns(items[i + 1])) > len2)
2113 dlg_calc_list_width(int item_no, DIALOG_LISTITEM * items)
2115 int n, i, len1 = 0, len2 = 0;
2116 int bits = ((dialog_vars.no_tags ? 1 : 0)
2117 + (dialog_vars.no_items ? 2 : 0));
2119 for (i = 0; i < item_no; ++i) {
2124 if ((n = dlg_count_columns(items[i].name)) > len1)
2126 if ((n = dlg_count_columns(items[i].text)) > len2)
2132 if ((n = dlg_count_columns(items[i].name)) > len1)
2143 static char empty[] = "";
2148 dlg_strclone(const char *cprompt)
2152 prompt = dlg_malloc(char, strlen(cprompt) + 1);
2153 assert_ptr(prompt, "dlg_strclone");
2154 strcpy(prompt, cprompt);
2160 dlg_asciibox(chtype ch)
2164 if (ch == ACS_ULCORNER)
2166 else if (ch == ACS_LLCORNER)
2168 else if (ch == ACS_URCORNER)
2170 else if (ch == ACS_LRCORNER)
2172 else if (ch == ACS_HLINE)
2174 else if (ch == ACS_VLINE)
2176 else if (ch == ACS_LTEE)
2178 else if (ch == ACS_RTEE)
2180 else if (ch == ACS_UARROW)
2182 else if (ch == ACS_DARROW)
2189 dlg_boxchar(chtype ch)
2191 chtype result = dlg_asciibox(ch);
2194 if (dialog_vars.ascii_lines)
2196 else if (dialog_vars.no_lines)
2203 dlg_box_x_ordinate(int width)
2207 if (dialog_vars.begin_set == 1) {
2208 x = dialog_vars.begin_x;
2210 /* center dialog box on screen unless --begin-set */
2211 x = (SCOLS - width) / 2;
2217 dlg_box_y_ordinate(int height)
2221 if (dialog_vars.begin_set == 1) {
2222 y = dialog_vars.begin_y;
2224 /* center dialog box on screen unless --begin-set */
2225 y = (SLINES - height) / 2;
2231 dlg_draw_title(WINDOW *win, const char *title)
2233 if (title != NULL) {
2234 chtype attr = A_NORMAL;
2235 chtype save = dlg_get_attrs(win);
2236 int x = centered(getmaxx(win), title);
2238 dlg_attrset(win, title_attr);
2240 dlg_print_text(win, title, getmaxx(win) - x, &attr);
2241 dlg_attrset(win, save);
2242 dlg_finish_string(title);
2247 dlg_draw_bottom_box2(WINDOW *win, chtype on_left, chtype on_right, chtype on_inside)
2249 int width = getmaxx(win);
2250 int height = getmaxy(win);
2253 dlg_attrset(win, on_left);
2254 (void) wmove(win, height - 3, 0);
2255 (void) waddch(win, dlg_boxchar(ACS_LTEE));
2256 for (i = 0; i < width - 2; i++)
2257 (void) waddch(win, dlg_boxchar(ACS_HLINE));
2258 dlg_attrset(win, on_right);
2259 (void) waddch(win, dlg_boxchar(ACS_RTEE));
2260 dlg_attrset(win, on_inside);
2261 (void) wmove(win, height - 2, 1);
2262 for (i = 0; i < width - 2; i++)
2263 (void) waddch(win, ' ');
2267 dlg_draw_bottom_box(WINDOW *win)
2269 dlg_draw_bottom_box2(win, border_attr, dialog_attr, dialog_attr);
2273 * Remove a window, repainting everything else. This would be simpler if we
2274 * used the panel library, but that is not _always_ available.
2277 dlg_del_window(WINDOW *win)
2279 DIALOG_WINDOWS *p, *q, *r;
2282 * If --keep-window was set, do not delete/repaint the windows.
2284 if (dialog_vars.keep_window)
2287 /* Leave the main window untouched if there are no background windows.
2288 * We do this so the current window will not be cleared on exit, allowing
2289 * things like the infobox demo to run without flicker.
2291 if (dialog_state.getc_callbacks != 0) {
2293 wnoutrefresh(stdscr);
2296 for (p = dialog_state.all_windows, q = r = 0; p != 0; r = p, p = p->next) {
2297 if (p->normal == win) {
2298 q = p; /* found a match - should be only one */
2300 dialog_state.all_windows = p->next;
2305 if (p->shadow != 0) {
2306 touchwin(p->shadow);
2307 wnoutrefresh(p->shadow);
2309 touchwin(p->normal);
2310 wnoutrefresh(p->normal);
2315 if (dialog_state.all_windows != 0)
2316 erase_childs_shadow(q);
2317 del_subwindows(q->normal);
2318 dlg_unregister_window(q->normal);
2326 * Create a window, optionally with a shadow.
2329 dlg_new_window(int height, int width, int y, int x)
2331 return dlg_new_modal_window(stdscr, height, width, y, x);
2335 * "Modal" windows differ from normal ones by having a shadow in a window
2336 * separate from the standard screen.
2339 dlg_new_modal_window(WINDOW *parent, int height, int width, int y, int x)
2342 DIALOG_WINDOWS *p = dlg_calloc(DIALOG_WINDOWS, 1);
2346 || (win = newwin(height, width, y, x)) == 0) {
2347 dlg_exiterr("Can't make new window at (%d,%d), size (%d,%d).\n",
2348 y, x, height, width);
2350 p->next = dialog_state.all_windows;
2352 p->getc_timeout = WTIMEOUT_OFF;
2353 dialog_state.all_windows = p;
2355 if (dialog_state.use_shadow) {
2357 draw_childs_shadow(p);
2361 (void) keypad(win, TRUE);
2366 * dlg_getc() uses the return-value to determine how to handle an ERR return
2367 * from a non-blocking read:
2368 * a) if greater than zero, there was an expired timeout (blocking for a short
2370 * b) if zero, it was a non-blocking read, or
2371 * c) if negative, an error occurred on a blocking read.
2374 dlg_set_timeout(WINDOW *win, bool will_getc)
2379 if ((p = SearchTopWindows(win)) != NULL) {
2380 int interval = (dialog_vars.timeout_secs * 1000);
2382 if (will_getc || dialog_vars.pause_secs) {
2383 interval = WTIMEOUT_VAL;
2386 if (interval <= 0) {
2387 interval = WTIMEOUT_OFF;
2390 wtimeout(win, interval);
2391 p->getc_timeout = interval;
2397 dlg_reset_timeout(WINDOW *win)
2401 if ((p = SearchTopWindows(win)) != NULL) {
2402 wtimeout(win, p->getc_timeout);
2404 wtimeout(win, WTIMEOUT_OFF);
2409 * Move/Resize a window, optionally with a shadow.
2413 dlg_move_window(WINDOW *win, int height, int width, int y, int x)
2418 dlg_ctl_size(height, width);
2420 if ((p = SearchTopWindows(win)) != 0) {
2421 (void) wresize(win, height, width);
2422 (void) mvwin(win, y, x);
2424 if (p->shadow != 0) {
2425 if (dialog_state.use_shadow) {
2426 (void) mvwin(p->shadow, y + SHADOW_ROWS, x + SHADOW_COLS);
2435 draw_childs_shadow(p);
2442 * Having just received a KEY_RESIZE, wait a short time to ignore followup
2443 * KEY_RESIZE events.
2446 dlg_will_resize(WINDOW *win)
2451 dialog_state.had_resize = TRUE;
2453 wtimeout(win, WTIMEOUT_VAL * 5);
2455 for (n = base = 0; n < base + 10; ++n) {
2458 if ((ch = wgetch(win)) != ERR) {
2459 if (ch == KEY_RESIZE) {
2462 } else if (ch != ERR) {
2468 dlg_reset_timeout(win);
2469 DLG_TRACE(("# caught %d KEY_RESIZE key%s\n",
2471 caught == 1 ? "" : "s"));
2473 #endif /* KEY_RESIZE */
2476 dlg_der_window(WINDOW *parent, int height, int width, int y, int x)
2480 /* existing uses of derwin are (almost) guaranteed to succeed, and the
2481 * caller has to allow for failure.
2483 if ((win = derwin(parent, height, width, y, x)) != 0) {
2484 add_subwindow(parent, win);
2485 (void) keypad(win, TRUE);
2491 dlg_sub_window(WINDOW *parent, int height, int width, int y, int x)
2495 if ((win = subwin(parent, height, width, y, x)) == 0) {
2496 dlg_exiterr("Can't make sub-window at (%d,%d), size (%d,%d).\n",
2497 y, x, height, width);
2500 add_subwindow(parent, win);
2501 (void) keypad(win, TRUE);
2507 dlg_default_item(char **items, int llen)
2511 if (dialog_vars.default_item != 0) {
2513 while (*items != 0) {
2514 if (!strcmp(dialog_vars.default_item, *items)) {
2526 dlg_default_listitem(DIALOG_LISTITEM * items)
2530 if (dialog_vars.default_item != 0) {
2532 while (items->name != 0) {
2533 if (!strcmp(dialog_vars.default_item, items->name)) {
2545 * Draw the string for item_help
2548 dlg_item_help(const char *txt)
2550 if (USE_ITEM_HELP(txt)) {
2551 chtype attr = A_NORMAL;
2553 dlg_attrset(stdscr, itemhelp_attr);
2554 (void) wmove(stdscr, LINES - 1, 0);
2555 (void) wclrtoeol(stdscr);
2557 dlg_print_text(stdscr, txt, COLS - 1, &attr);
2559 if (itemhelp_attr & A_COLOR) {
2561 /* fill the remainder of the line with the window's attributes */
2562 getyx(stdscr, y, x);
2569 (void) wnoutrefresh(stdscr);
2573 #ifndef HAVE_STRCASECMP
2575 dlg_strcmp(const char *a, const char *b)
2582 if (isalpha(ac) && islower(ac))
2584 if (isalpha(bc) && islower(bc))
2587 if (ac == 0 || bc == 0 || cmp != 0)
2595 * Returns true if 'dst' points to a blank which follows another blank which
2596 * is not a leading blank on a line.
2599 trim_blank(char *base, char *dst)
2601 int count = !!isblank(UCH(*dst));
2603 while (dst-- != base) {
2606 } else if (isblank(UCH(*dst))) {
2616 * Change embedded "\n" substrings to '\n' characters and tabs to single
2617 * spaces. If there are no "\n"s, it will strip all extra spaces, for
2618 * justification. If it has "\n"'s, it will preserve extra spaces. If cr_wrap
2619 * is set, it will preserve '\n's.
2622 dlg_trim_string(char *s)
2627 int has_newlines = !dialog_vars.no_nl_expand && (strstr(s, "\\n") != 0);
2629 while (*p != '\0') {
2630 if (*p == TAB && !dialog_vars.nocollapse)
2633 if (has_newlines) { /* If prompt contains "\n" strings */
2634 if (*p == '\\' && *(p + 1) == 'n') {
2639 * Handle end of lines intelligently. If '\n' follows "\n"
2640 * then ignore the '\n'. This eliminates the need to escape
2641 * the '\n' character (no need to use "\n\").
2643 while (isblank(UCH(*p1)))
2647 } else if (*p == '\n') {
2648 if (dialog_vars.cr_wrap)
2651 /* Replace the '\n' with a space if cr_wrap is not set */
2652 if (!trim_blank(base, p))
2656 } else /* If *p != '\n' */
2658 } else if (dialog_vars.trim_whitespace) {
2659 if (isblank(UCH(*p))) {
2660 if (!isblank(UCH(*(s - 1)))) {
2665 } else if (*p == '\n') {
2666 if (dialog_vars.cr_wrap)
2668 else if (!isblank(UCH(*(s - 1)))) {
2669 /* Strip '\n's if cr_wrap is not set. */
2676 } else { /* If there are no "\n" strings */
2677 if (isblank(UCH(*p)) && !dialog_vars.nocollapse) {
2678 if (!trim_blank(base, p))
2690 dlg_set_focus(WINDOW *parent, WINDOW *win)
2693 (void) wmove(parent,
2694 getpary(win) + getcury(win),
2695 getparx(win) + getcurx(win));
2696 (void) wnoutrefresh(win);
2702 * Returns the nominal maximum buffer size.
2705 dlg_max_input(int max_len)
2707 if (dialog_vars.max_input != 0 && dialog_vars.max_input < MAX_LEN)
2708 max_len = dialog_vars.max_input;
2714 * Free storage used for the result buffer.
2717 dlg_clr_result(void)
2719 if (dialog_vars.input_length) {
2720 dialog_vars.input_length = 0;
2721 if (dialog_vars.input_result)
2722 free(dialog_vars.input_result);
2724 dialog_vars.input_result = 0;
2728 * Setup a fixed-buffer for the result.
2731 dlg_set_result(const char *string)
2733 unsigned need = string ? (unsigned) strlen(string) + 1 : 0;
2735 /* inputstr.c needs a fixed buffer */
2740 * If the buffer is not big enough, allocate a new one.
2742 if (dialog_vars.input_length != 0
2743 || dialog_vars.input_result == 0
2744 || need > MAX_LEN) {
2748 dialog_vars.input_length = need;
2749 dialog_vars.input_result = dlg_malloc(char, need);
2750 assert_ptr(dialog_vars.input_result, "dlg_set_result");
2753 strcpy(dialog_vars.input_result, string ? string : "");
2755 return dialog_vars.input_result;
2759 * Accumulate results in dynamically allocated buffer.
2760 * If input_length is zero, it is a MAX_LEN buffer belonging to the caller.
2763 dlg_add_result(const char *string)
2765 unsigned have = (dialog_vars.input_result
2766 ? (unsigned) strlen(dialog_vars.input_result)
2768 unsigned want = (unsigned) strlen(string) + 1 + have;
2770 if ((want >= MAX_LEN)
2771 || (dialog_vars.input_length != 0)
2772 || (dialog_vars.input_result == 0)) {
2774 if (dialog_vars.input_length == 0
2775 || dialog_vars.input_result == 0) {
2777 char *save_result = dialog_vars.input_result;
2779 dialog_vars.input_length = want * 2;
2780 dialog_vars.input_result = dlg_malloc(char, dialog_vars.input_length);
2781 assert_ptr(dialog_vars.input_result, "dlg_add_result malloc");
2782 dialog_vars.input_result[0] = '\0';
2783 if (save_result != 0)
2784 strcpy(dialog_vars.input_result, save_result);
2785 } else if (want >= dialog_vars.input_length) {
2786 dialog_vars.input_length = want * 2;
2787 dialog_vars.input_result = dlg_realloc(char,
2788 dialog_vars.input_length,
2789 dialog_vars.input_result);
2790 assert_ptr(dialog_vars.input_result, "dlg_add_result realloc");
2793 strcat(dialog_vars.input_result, string);
2797 * These are characters that (aside from the quote-delimiter) will have to
2798 * be escaped in a single- or double-quoted string.
2800 #define FIX_SINGLE "\n\\"
2801 #define FIX_DOUBLE FIX_SINGLE "[]{}?*;`~#$^&()|<>"
2804 * Returns the quote-delimiter.
2807 quote_delimiter(void)
2809 return dialog_vars.single_quoted ? "'" : "\"";
2813 * Returns true if we should quote the given string.
2816 must_quote(char *string)
2820 if (*string != '\0') {
2821 size_t len = strlen(string);
2822 if (strcspn(string, quote_delimiter()) != len)
2824 else if (strcspn(string, "\n\t ") != len)
2827 code = (strcspn(string, FIX_DOUBLE) != len);
2836 * Add a quoted string to the result buffer.
2839 dlg_add_quoted(char *string)
2842 const char *my_quote = quote_delimiter();
2843 const char *must_fix = (dialog_vars.single_quoted
2847 if (must_quote(string)) {
2849 dlg_add_result(my_quote);
2850 while (*string != '\0') {
2851 temp[0] = *string++;
2852 if ((strchr) (my_quote, *temp) || (strchr) (must_fix, *temp))
2853 dlg_add_result("\\");
2854 dlg_add_result(temp);
2856 dlg_add_result(my_quote);
2858 dlg_add_result(string);
2863 * When adding a result, make that depend on whether "--quoted" is used.
2866 dlg_add_string(char *string)
2868 if (dialog_vars.quoted) {
2869 dlg_add_quoted(string);
2871 dlg_add_result(string);
2876 dlg_need_separator(void)
2878 bool result = FALSE;
2880 if (dialog_vars.output_separator) {
2882 } else if (dialog_vars.input_result && *(dialog_vars.input_result)) {
2889 dlg_add_separator(void)
2891 const char *separator = (dialog_vars.separate_output) ? "\n" : " ";
2893 if (dialog_vars.output_separator)
2894 separator = dialog_vars.output_separator;
2896 dlg_add_result(separator);
2899 #define HELP_PREFIX "HELP "
2902 dlg_add_help_listitem(int *result, char **tag, DIALOG_LISTITEM * item)
2904 dlg_add_result(HELP_PREFIX);
2905 if (USE_ITEM_HELP(item->help)) {
2906 *tag = dialog_vars.help_tags ? item->name : item->help;
2907 *result = DLG_EXIT_ITEM_HELP;
2914 dlg_add_help_formitem(int *result, char **tag, DIALOG_FORMITEM * item)
2916 dlg_add_result(HELP_PREFIX);
2917 if (USE_ITEM_HELP(item->help)) {
2918 *tag = dialog_vars.help_tags ? item->name : item->help;
2919 *result = DLG_EXIT_ITEM_HELP;
2926 * Some widgets support only one value of a given variable - save/restore the
2927 * global dialog_vars so we can override it consistently.
2930 dlg_save_vars(DIALOG_VARS * vars)
2932 *vars = dialog_vars;
2936 * Most of the data in DIALOG_VARS is normally set by command-line options.
2937 * The input_result member is an exception; it is normally set by the dialog
2938 * library to return result values.
2941 dlg_restore_vars(DIALOG_VARS * vars)
2943 char *save_result = dialog_vars.input_result;
2944 unsigned save_length = dialog_vars.input_length;
2946 dialog_vars = *vars;
2947 dialog_vars.input_result = save_result;
2948 dialog_vars.input_length = save_length;
2952 * Called each time a widget is invoked which may do output, increment a count.
2955 dlg_does_output(void)
2957 dialog_state.output_count += 1;
2961 * Compatibility for different versions of curses.
2963 #if !(defined(HAVE_GETBEGX) && defined(HAVE_GETBEGY))
2965 dlg_getbegx(WINDOW *win)
2968 getbegyx(win, y, x);
2972 dlg_getbegy(WINDOW *win)
2975 getbegyx(win, y, x);
2980 #if !(defined(HAVE_GETCURX) && defined(HAVE_GETCURY))
2982 dlg_getcurx(WINDOW *win)
2989 dlg_getcury(WINDOW *win)
2997 #if !(defined(HAVE_GETMAXX) && defined(HAVE_GETMAXY))
2999 dlg_getmaxx(WINDOW *win)
3002 getmaxyx(win, y, x);
3006 dlg_getmaxy(WINDOW *win)
3009 getmaxyx(win, y, x);
3014 #if !(defined(HAVE_GETPARX) && defined(HAVE_GETPARY))
3016 dlg_getparx(WINDOW *win)
3019 getparyx(win, y, x);
3023 dlg_getpary(WINDOW *win)
3026 getparyx(win, y, x);
3031 #ifdef NEED_WGETPARENT
3033 dlg_wgetparent(WINDOW *win)
3039 for (p = dialog_state.all_subwindows; p != 0; p = p->next) {
3040 if (p->shadow == win) {