xref: /freebsd/contrib/dialog/util.c (revision 2e3f49888ec8851bafb22011533217487764fdb0)
1 /*
2  *  $Id: util.c,v 1.300 2021/01/17 22:10:56 tom Exp $
3  *
4  *  util.c -- miscellaneous utilities for dialog
5  *
6  *  Copyright 2000-2020,2021	Thomas E. Dickey
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU Lesser General Public License, version 2.1
10  *  as published by the Free Software Foundation.
11  *
12  *  This program is distributed in the hope that it will be useful, but
13  *  WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  *  Lesser General Public License for more details.
16  *
17  *  You should have received a copy of the GNU Lesser General Public
18  *  License along with this program; if not, write to
19  *	Free Software Foundation, Inc.
20  *	51 Franklin St., Fifth Floor
21  *	Boston, MA 02110, USA.
22  *
23  *  An earlier version of this program lists as authors
24  *	Savio Lam (lam836@cs.cuhk.hk)
25  */
26 
27 #include <dialog.h>
28 #include <dlg_keys.h>
29 #include <dlg_internals.h>
30 
31 #include <sys/time.h>
32 
33 #ifdef HAVE_SETLOCALE
34 #include <locale.h>
35 #endif
36 
37 #ifdef NEED_WCHAR_H
38 #include <wchar.h>
39 #endif
40 
41 #ifdef HAVE_SYS_PARAM_H
42 #undef MIN
43 #undef MAX
44 #include <sys/param.h>
45 #endif
46 
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
51 #else
52 #define CAN_KEEP_TITE 0
53 #endif
54 
55 #if CAN_KEEP_TITE
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>
60 #else
61 #include <term.h>
62 #endif
63 #endif
64 
65 #if defined(HAVE_WCHGAT)
66 #  if defined(NCURSES_VERSION_PATCH)
67 #    if NCURSES_VERSION_PATCH >= 20060715
68 #      define USE_WCHGAT 1
69 #    else
70 #      define USE_WCHGAT 0
71 #    endif
72 #  else
73 #    define USE_WCHGAT 1
74 #  endif
75 #else
76 #  define USE_WCHGAT 0
77 #endif
78 
79 /* globals */
80 DIALOG_STATE dialog_state;
81 DIALOG_VARS dialog_vars;
82 
83 #if !(defined(HAVE_WGETPARENT) && defined(HAVE_WINDOW__PARENT))
84 #define NEED_WGETPARENT 1
85 #else
86 #undef NEED_WGETPARENT
87 #endif
88 
89 #define concat(a,b) a##b
90 
91 #ifdef HAVE_RC_FILE
92 #define RC_DATA(name,comment) , #name "_color", comment " color"
93 #else
94 #define RC_DATA(name,comment)	/*nothing */
95 #endif
96 
97 #ifdef HAVE_COLOR
98 #include <dlg_colors.h>
99 #ifdef HAVE_RC_FILE2
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), \
105 	concat(DLGC_RV_,upr)
106 #else /* HAVE_RC_FILE2 */
107 #define COLOR_DATA(upr) , \
108 	concat(DLGC_FG_,upr), \
109 	concat(DLGC_BG_,upr), \
110 	concat(DLGC_HL_,upr)
111 #endif /* HAVE_RC_FILE2 */
112 #else /* HAVE_COLOR */
113 #define COLOR_DATA(upr)		/*nothing */
114 #endif /* HAVE_COLOR */
115 
116 #define UseShadow(dw) ((dw) != 0 && (dw)->normal != 0 && (dw)->shadow != 0)
117 
118 /*
119  * Table of color and attribute values, default is for mono display.
120  * The order matches the DIALOG_ATR() values.
121  */
122 #define DATA(atr,upr,lwr,cmt) { atr COLOR_DATA(upr) RC_DATA(lwr,cmt) }
123 /* *INDENT-OFF* */
124 DIALOG_COLORS dlg_color_table[] =
125 {
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")
164 };
165 #undef DATA
166 /* *INDENT-ON* */
167 
168 /*
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.
171  */
172 static void
173 add_subwindow(WINDOW *parent, WINDOW *child)
174 {
175     DIALOG_WINDOWS *p = dlg_calloc(DIALOG_WINDOWS, 1);
176 
177     if (p != 0) {
178 	p->normal = parent;
179 	p->shadow = child;
180 	p->getc_timeout = WTIMEOUT_OFF;
181 	p->next = dialog_state.all_subwindows;
182 	dialog_state.all_subwindows = p;
183     }
184 }
185 
186 static void
187 del_subwindows(WINDOW *parent)
188 {
189     DIALOG_WINDOWS *p = dialog_state.all_subwindows;
190     DIALOG_WINDOWS *q = 0;
191     DIALOG_WINDOWS *r;
192 
193     while (p != 0) {
194 	if (p->normal == parent) {
195 	    delwin(p->shadow);
196 	    r = p->next;
197 	    if (q == 0) {
198 		dialog_state.all_subwindows = r;
199 	    } else {
200 		q->next = r;
201 	    }
202 	    free(p);
203 	    p = r;
204 	} else {
205 	    q = p;
206 	    p = p->next;
207 	}
208     }
209 }
210 
211 /*
212  * Display background title if it exists ...
213  */
214 void
215 dlg_put_backtitle(void)
216 {
217 
218     if (dialog_vars.backtitle != NULL) {
219 	chtype attr = A_NORMAL;
220 	int backwidth = dlg_count_columns(dialog_vars.backtitle);
221 	int i;
222 
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));
231     }
232 
233     (void) wnoutrefresh(stdscr);
234 }
235 
236 /*
237  * Set window to attribute 'attr'.  There are more efficient ways to do this,
238  * but will not work on older/buggy ncurses versions.
239  */
240 void
241 dlg_attr_clear(WINDOW *win, int height, int width, chtype attr)
242 {
243     int i, j;
244 
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, ' ');
250     }
251     (void) touchwin(win);
252 }
253 
254 void
255 dlg_clear(void)
256 {
257     dlg_attr_clear(stdscr, LINES, COLS, screen_attr);
258 }
259 
260 #ifdef KEY_RESIZE
261 void
262 _dlg_resize_cleanup(WINDOW *w)
263 {
264     dlg_clear();
265     dlg_put_backtitle();
266     dlg_del_window(w);
267     dlg_mouse_free_regions();
268 }
269 #endif /* KEY_RESIZE */
270 
271 #define isprivate(s) ((s) != 0 && strstr(s, "\033[?") != 0)
272 
273 #define TTY_DEVICE "/dev/tty"
274 
275 /*
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.
280  */
281 #define dialog_tty() (dlg_getenv_num("DIALOG_TTY", (int *)0) > 0)
282 
283 /*
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.
286  */
287 static int
288 open_terminal(char **result, int mode)
289 {
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) {
297 		device = TTY_DEVICE;
298 	    }
299 	}
300     }
301     *result = dlg_strclone(device);
302     return open(device, mode);
303 }
304 
305 #if CAN_KEEP_TITE
306 static int
307 my_putc(int ch)
308 {
309     char buffer[2];
310     int fd = fileno(dialog_state.screen_output);
311 
312     buffer[0] = (char) ch;
313     return (int) write(fd, buffer, (size_t) 1);
314 }
315 #endif
316 
317 /*
318  * Do some initialization for dialog.
319  *
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.
322  *
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.
326  */
327 void
328 init_dialog(FILE *input, FILE *output)
329 {
330     int fd1, fd2;
331     char *device = 0;
332 
333     setlocale(LC_ALL, "");
334 
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;
340 #ifdef HAVE_COLOR
341     dialog_state.use_colors = USE_COLORS;	/* use colors by default? */
342     dialog_state.use_shadow = USE_SHADOW;	/* shadow dialog boxes by default? */
343 #endif
344 
345 #ifdef HAVE_RC_FILE
346     if (dlg_parse_rc() == -1)	/* Read the configuration file */
347 	dlg_exiterr("init_dialog: dlg_parse_rc");
348 #endif
349 
350     /*
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.
354      *
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
359      * /dev/tty.
360      */
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);
369 	} else {
370 	    dlg_exiterr("cannot open tty-input");
371 	}
372 	close(fd1);
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);
381 	    }
382 	    close(fd1);
383 	}
384 	free(device);
385     }
386 
387     /*
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.
390      *
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:
396      *
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.
401      *
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.
404      */
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");
411 	    }
412 	    free(device);
413 	} else {
414 	    dlg_exiterr("cannot open tty-output");
415 	}
416     } else {
417 	dialog_state.screen_output = stdout;
418 	(void) initscr();
419     }
420     dlg_keep_tite(dialog_state.screen_output);
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 void
444 dlg_keep_tite(FILE *output)
445 {
446     if (!dialog_vars.keep_tite) {
447 #if CAN_KEEP_TITE
448 	/*
449 	 * Cancel xterm's alternate-screen mode.
450 	 */
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;
457 
458 	    /*
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
463 	     * invoked.
464 	     */
465 	    (void) refresh();
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;
470 
471 	    /*
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.
477 	     *
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.
481 	     */
482 	    enter_ca_mode = 0;
483 	    exit_ca_mode = 0;
484 	}
485 #else
486 	/*
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.
491 	 */
492 	(void) output;
493 #endif
494     }
495 }
496 
497 #ifdef HAVE_COLOR
498 static int defined_colors = 1;	/* pair-0 is reserved */
499 /*
500  * Setup for color display
501  */
502 void
503 dlg_color_setup(void)
504 {
505     if (has_colors()) {		/* Terminal supports color? */
506 	unsigned i;
507 
508 	(void) start_color();
509 
510 #if defined(HAVE_USE_DEFAULT_COLORS)
511 	use_default_colors();
512 #endif
513 
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++) {
519 
520 	    /* Initialize color pairs */
521 	    (void) init_pair(i + 1,
522 			     dlg_color_table[i].fg,
523 			     dlg_color_table[i].bg);
524 
525 	    /* Setup color attributes */
526 	    dlg_color_table[i].atr = C_ATTR(dlg_color_table[i].hilite, i + 1);
527 	}
528 	defined_colors = i + 1;
529 #else
530 	for (i = 0; i < sizeof(dlg_color_table) /
531 	     sizeof(dlg_color_table[0]); i++) {
532 
533 	    /* Initialize color pairs */
534 	    chtype atr = dlg_color_pair(dlg_color_table[i].fg,
535 					dlg_color_table[i].bg);
536 
537 	    atr |= (dlg_color_table[i].hilite ? A_BOLD : 0);
538 #ifdef HAVE_RC_FILE2
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 */
542 
543 	    dlg_color_table[i].atr = atr;
544 	}
545 #endif
546     } else {
547 	dialog_state.use_colors = FALSE;
548 	dialog_state.use_shadow = FALSE;
549     }
550 }
551 
552 int
553 dlg_color_count(void)
554 {
555     return TableSize(dlg_color_table);
556 }
557 
558 /*
559  * Wrapper for getattrs(), or the more cumbersome X/Open wattr_get().
560  */
561 chtype
562 dlg_get_attrs(WINDOW *win)
563 {
564     chtype result;
565 #ifdef HAVE_GETATTRS
566     result = (chtype) getattrs(win);
567 #else
568     attr_t my_result;
569     short my_pair;
570     wattr_get(win, &my_result, &my_pair, NULL);
571     result = my_result;
572 #endif
573     return result;
574 }
575 
576 /*
577  * Reuse color pairs (they are limited), returning a COLOR_PAIR() value if we
578  * have (or can) define a pair with the given color as foreground on the
579  * window's defined background.
580  */
581 chtype
582 dlg_color_pair(int foreground, int background)
583 {
584     chtype result = 0;
585     int pair;
586     short fg, bg;
587     bool found = FALSE;
588 
589     for (pair = 1; pair < defined_colors; ++pair) {
590 	if (pair_content((short) pair, &fg, &bg) != ERR
591 	    && fg == foreground
592 	    && bg == background) {
593 	    result = (chtype) COLOR_PAIR(pair);
594 	    found = TRUE;
595 	    break;
596 	}
597     }
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);
602     }
603     return result;
604 }
605 
606 /*
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.
610  */
611 static chtype
612 define_color(WINDOW *win, int foreground)
613 {
614     short fg, bg, background;
615     if (dialog_state.text_only) {
616 	background = COLOR_BLACK;
617     } else {
618 	chtype attrs = dlg_get_attrs(win);
619 	int pair;
620 
621 	if ((pair = PAIR_NUMBER(attrs)) != 0
622 	    && pair_content((short) pair, &fg, &bg) != ERR) {
623 	    background = bg;
624 	} else {
625 	    background = COLOR_BLACK;
626 	}
627     }
628     return dlg_color_pair(foreground, background);
629 }
630 #endif
631 
632 /*
633  * End using dialog functions.
634  */
635 void
636 end_dialog(void)
637 {
638     if (dialog_state.screen_initialized) {
639 	dialog_state.screen_initialized = FALSE;
640 	if (dialog_vars.erase_on_exit) {
641 	    /*
642 	     * Clear the screen to the native background color, and leave the
643 	     * terminal cursor at the lower-left corner of the screen.
644 	     */
645 	    werase(stdscr);
646 	    wrefresh(stdscr);
647 	}
648 	mouse_close();
649 	(void) endwin();
650 	(void) fflush(stdout);
651     }
652 }
653 
654 #define ESCAPE_LEN 3
655 #define isOurEscape(p) (((p)[0] == '\\') && ((p)[1] == 'Z') && ((p)[2] != 0))
656 
657 int
658 dlg_count_real_columns(const char *text)
659 {
660     int result = 0;
661     if (*text) {
662 	result = dlg_count_columns(text);
663 	if (result && dialog_vars.colors) {
664 	    int hidden = 0;
665 	    while (*text) {
666 		if (dialog_vars.colors && isOurEscape(text)) {
667 		    hidden += ESCAPE_LEN;
668 		    text += ESCAPE_LEN;
669 		} else {
670 		    ++text;
671 		}
672 	    }
673 	    result -= hidden;
674 	}
675     }
676     return result;
677 }
678 
679 static int
680 centered(int width, const char *string)
681 {
682     int need = dlg_count_real_columns(string);
683     int left;
684 
685     left = (width - need) / 2 - 1;
686     if (left < 0)
687 	left = 0;
688     return left;
689 }
690 
691 #ifdef USE_WIDE_CURSES
692 static bool
693 is_combining(const char *txt, int *combined)
694 {
695     bool result = FALSE;
696 
697     if (*combined == 0) {
698 	if (UCH(*txt) >= 128) {
699 	    wchar_t wch;
700 	    mbstate_t state;
701 	    size_t given = strlen(txt);
702 	    size_t len;
703 
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;
708 		result = TRUE;
709 	    }
710 	}
711     } else {
712 	result = TRUE;
713 	*combined -= 1;
714     }
715     return result;
716 }
717 #endif
718 
719 /*
720  * Print the name (tag) or text from a DIALOG_LISTITEM, highlighting the
721  * first character if selected.
722  */
723 void
724 dlg_print_listitem(WINDOW *win,
725 		   const char *text,
726 		   int climit,
727 		   bool first,
728 		   int selected)
729 {
730     chtype attr = A_NORMAL;
731     int limit;
732     chtype attrs[4];
733 
734     if (text == 0)
735 	text = "";
736 
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;
742 	attrs[0] = tag_attr;
743 
744 	dlg_attrset(win, selected ? attrs[3] : attrs[2]);
745 	if (*text != '\0') {
746 	    (void) waddnstr(win, text, indx[1]);
747 
748 	    if ((int) strlen(text) > indx[1]) {
749 		limit = dlg_limit_columns(text, climit, 1);
750 		if (limit > 1) {
751 		    dlg_attrset(win, selected ? attrs[1] : attrs[0]);
752 		    (void) waddnstr(win,
753 				    text + indx[1],
754 				    indx[limit] - indx[1]);
755 		}
756 	    }
757 	}
758     } else {
759 	const int *cols;
760 
761 	attrs[1] = item_selected_attr;
762 	attrs[0] = item_attr;
763 
764 	cols = dlg_index_columns(text);
765 	limit = dlg_limit_columns(text, climit, 0);
766 
767 	if (limit > 0) {
768 	    dlg_attrset(win, selected ? attrs[1] : attrs[0]);
769 	    dlg_print_text(win, text, cols[limit], &attr);
770 	}
771     }
772 }
773 
774 /*
775  * Print up to 'cols' columns from 'text', optionally rendering our escape
776  * sequence for attributes and color.
777  */
778 void
779 dlg_print_text(WINDOW *win, const char *txt, int cols, chtype *attr)
780 {
781     int y_origin, x_origin;
782     int y_before, x_before = 0;
783     int y_after, x_after;
784     int tabbed = 0;
785     bool ended = FALSE;
786 #ifdef USE_WIDE_CURSES
787     int combined = 0;
788 #endif
789 
790     if (dialog_state.text_only) {
791 	y_origin = y_after = 0;
792 	x_origin = x_after = 0;
793     } else {
794 	y_after = 0;
795 	x_after = 0;
796 	getyx(win, y_origin, x_origin);
797     }
798     while (cols > 0 && (*txt != '\0')) {
799 	bool thisTab;
800 	chtype useattr;
801 
802 	if (dialog_vars.colors) {
803 	    while (isOurEscape(txt)) {
804 		int code;
805 
806 		txt += 2;
807 		switch (code = CharOf(*txt)) {
808 #ifdef HAVE_COLOR
809 		case '0':
810 		case '1':
811 		case '2':
812 		case '3':
813 		case '4':
814 		case '5':
815 		case '6':
816 		case '7':
817 		    *attr &= ~A_COLOR;
818 		    *attr |= define_color(win, code - '0');
819 		    break;
820 #endif
821 		case 'B':
822 		    *attr &= ~A_BOLD;
823 		    break;
824 		case 'b':
825 		    *attr |= A_BOLD;
826 		    break;
827 		case 'R':
828 		    *attr &= ~A_REVERSE;
829 		    break;
830 		case 'r':
831 		    *attr |= A_REVERSE;
832 		    break;
833 		case 'U':
834 		    *attr &= ~A_UNDERLINE;
835 		    break;
836 		case 'u':
837 		    *attr |= A_UNDERLINE;
838 		    break;
839 		case 'n':
840 		    *attr = A_NORMAL;
841 		    break;
842 		default:
843 		    break;
844 		}
845 		++txt;
846 	    }
847 	}
848 	if (ended || *txt == '\n' || *txt == '\0')
849 	    break;
850 	useattr = (*attr) & A_ATTRIBUTES;
851 #ifdef HAVE_COLOR
852 	/*
853 	 * Prevent this from making text invisible when the foreground and
854 	 * background colors happen to be the same, and there's no bold
855 	 * attribute.
856 	 */
857 	if ((useattr & A_COLOR) != 0 && (useattr & A_BOLD) == 0) {
858 	    short pair = (short) PAIR_NUMBER(useattr);
859 	    short fg, bg;
860 	    if (pair_content(pair, &fg, &bg) != ERR
861 		&& fg == bg) {
862 		useattr &= ~A_COLOR;
863 		useattr |= dlg_color_pair(fg, ((bg == COLOR_BLACK)
864 					       ? COLOR_WHITE
865 					       : COLOR_BLACK));
866 	    }
867 	}
868 #endif
869 	/*
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
873 	 * more blanks.
874 	 */
875 	thisTab = (CharOf(*txt) == TAB);
876 	if (dialog_state.text_only) {
877 	    x_before = x_after;
878 	} else {
879 	    if (thisTab) {
880 		getyx(win, y_before, x_before);
881 		(void) y_before;
882 	    }
883 	}
884 	if (dialog_state.text_only) {
885 	    int ch = CharOf(*txt++);
886 	    if (thisTab) {
887 		while ((x_after++) % 8) {
888 		    fputc(' ', dialog_state.output);
889 		}
890 	    } else {
891 		fputc(ch, dialog_state.output);
892 		x_after++;	/* FIXME: handle meta per locale */
893 	    }
894 	} else {
895 	    (void) waddch(win, CharOf(*txt++) | useattr);
896 	    getyx(win, y_after, x_after);
897 	}
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)
904 #endif
905 	    )) {
906 	    ended = TRUE;
907 	}
908     }
909     if (dialog_state.text_only) {
910 	fputc('\n', dialog_state.output);
911     }
912 }
913 
914 /*
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.
919  */
920 const char *
921 dlg_print_line(WINDOW *win,
922 	       chtype *attr,
923 	       const char *prompt,
924 	       int lm, int rm, int *x)
925 {
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);
931     int wrap_inx = 0;
932     int test_inx = 0;
933     int cur_x = lm;
934     int hidden = 0;
935     int limit = dlg_count_wchars(prompt);
936     int n;
937     int tabbed = 0;
938 
939     *x = 1;
940 
941     /*
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.
944      */
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))
948 	    break;
949 	if (ch == TAB && n == 0) {
950 	    tabbed = 8;		/* workaround for leading tabs */
951 	} else if (isblank(UCH(ch))
952 		   && n != 0
953 		   && !isblank(UCH(prompt[indx[n - 1]]))) {
954 	    wrap_inx = n;
955 	    *x = cur_x;
956 	} else if (dialog_vars.colors && isOurEscape(test_ptr)) {
957 	    hide_ptr = test_ptr;
958 	    hidden += ESCAPE_LEN;
959 	    n += (ESCAPE_LEN - 1);
960 	}
961 	cur_x = lm + tabbed + cols[n + 1];
962 	if (cur_x > (rm + hidden))
963 	    break;
964 	test_inx = n + 1;
965     }
966 
967     /*
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.
970      */
971     test_ptr = prompt + indx[test_inx];
972     if (*test_ptr == '\n' || isblank(UCH(*test_ptr)) || *test_ptr == '\0') {
973 	wrap_inx = test_inx;
974 	while (wrap_inx > 0 && isblank(UCH(prompt[indx[wrap_inx - 1]]))) {
975 	    wrap_inx--;
976 	}
977 	*x = lm + indx[wrap_inx];
978     } else if (*x == 1 && cur_x >= rm) {
979 	/*
980 	 * If the line has no spaces, then wrap it anyway at the right margin
981 	 */
982 	*x = rm;
983 	wrap_inx = test_inx;
984     }
985     wrap_ptr = prompt + indx[wrap_inx];
986 #ifdef USE_WIDE_CURSES
987     if (UCH(*wrap_ptr) >= 128) {
988 	int combined = 0;
989 	while (is_combining(wrap_ptr, &combined)) {
990 	    ++wrap_ptr;
991 	}
992     }
993 #endif
994 
995     /*
996      * If we found hidden text past the last point that we will display,
997      * discount that from the displayed length.
998      */
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;
1006 	    } else {
1007 		++test_ptr;
1008 	    }
1009 	}
1010     }
1011 
1012     /*
1013      * Print the line if we have a window pointer.  Otherwise this routine
1014      * is just being called for sizing the window.
1015      */
1016     if (dialog_state.text_only || win) {
1017 	dlg_print_text(win, prompt, (cols[wrap_inx] - hidden), attr);
1018     }
1019 
1020     /* *x tells the calling function how long the line was */
1021     if (*x == 1) {
1022 	*x = rm;
1023     }
1024 
1025     *x -= hidden;
1026 
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)))
1030 	test_ptr++;
1031     if (*test_ptr == '\n')
1032 	test_ptr++;
1033     dlg_finish_string(prompt);
1034     return (test_ptr);
1035 }
1036 
1037 static void
1038 justify_text(WINDOW *win,
1039 	     const char *prompt,
1040 	     int limit_y,
1041 	     int limit_x,
1042 	     int *high, int *wide)
1043 {
1044     chtype attr = A_NORMAL;
1045     int x;
1046     int y = MARGIN;
1047     int max_x = 2;
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;
1052 
1053     dialog_state.text_height = 0;
1054     dialog_state.text_width = 0;
1055     if (dialog_state.text_only || win) {
1056 	rm -= (2 * MARGIN);
1057 	bm -= (2 * MARGIN);
1058     }
1059     if (prompt == 0)
1060 	prompt = "";
1061 
1062     if (win != 0)
1063 	getyx(win, last_y, last_x);
1064     while (y <= bm && *prompt) {
1065 	x = lm;
1066 
1067 	if (*prompt == '\n') {
1068 	    while (*prompt == '\n' && y < bm) {
1069 		if (*(prompt + 1) != '\0') {
1070 		    ++y;
1071 		    if (win != 0)
1072 			(void) wmove(win, y, lm);
1073 		}
1074 		prompt++;
1075 	    }
1076 	} else if (win != 0)
1077 	    (void) wmove(win, y, lm);
1078 
1079 	if (*prompt) {
1080 	    prompt = dlg_print_line(win, &attr, prompt, lm, rm, &x);
1081 	    if (win != 0)
1082 		getyx(win, last_y, last_x);
1083 	}
1084 	if (*prompt) {
1085 	    ++y;
1086 	    if (win != 0)
1087 		(void) wmove(win, y, lm);
1088 	}
1089 	max_x = MAX(max_x, x);
1090     }
1091     /* Move back to the last position after drawing prompt, for msgbox. */
1092     if (win != 0)
1093 	(void) wmove(win, last_y, last_x);
1094 
1095     /* Set the final height and width for the calling function */
1096     if (high != 0)
1097 	*high = y;
1098     if (wide != 0)
1099 	*wide = max_x;
1100 }
1101 
1102 /*
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.
1106  */
1107 void
1108 dlg_print_autowrap(WINDOW *win, const char *prompt, int height, int width)
1109 {
1110     justify_text(win, prompt,
1111 		 height,
1112 		 width,
1113 		 (int *) 0, (int *) 0);
1114 }
1115 
1116 /*
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.
1120  *
1121  * It works for ncurses.  Other curses implementations show only blanks (Tru64)
1122  * or garbage (NetBSD).
1123  */
1124 int
1125 dlg_print_scrolled(WINDOW *win,
1126 		   const char *prompt,
1127 		   int offset,
1128 		   int height,
1129 		   int width,
1130 		   int pauseopt)
1131 {
1132     int oldy, oldx;
1133     int last = 0;
1134 
1135     (void) pauseopt;		/* used only for ncurses */
1136 
1137     getyx(win, oldy, oldx);
1138 #ifdef NCURSES_VERSION
1139     if (pauseopt) {
1140 	int wide = width - (2 * MARGIN);
1141 	int high = LINES;
1142 	int len;
1143 	WINDOW *dummy;
1144 
1145 #if defined(NCURSES_VERSION_PATCH) && NCURSES_VERSION_PATCH >= 20040417
1146 	/*
1147 	 * If we're not limited by the screensize, allow text to possibly be
1148 	 * one character per line.
1149 	 */
1150 	if ((len = dlg_count_columns(prompt)) > high)
1151 	    high = len;
1152 #endif
1153 	dummy = newwin(high, width, 0, 0);
1154 	if (dummy == 0) {
1155 	    dlg_attrset(win, dialog_attr);
1156 	    dlg_print_autowrap(win, prompt, height + 1 + (3 * MARGIN), width);
1157 	    last = 0;
1158 	} else {
1159 	    int y, x;
1160 
1161 	    wbkgdset(dummy, dialog_attr | ' ');
1162 	    dlg_attrset(dummy, dialog_attr);
1163 	    werase(dummy);
1164 	    dlg_print_autowrap(dummy, prompt, high, width);
1165 	    getyx(dummy, y, x);
1166 	    (void) x;
1167 
1168 	    copywin(dummy,	/* srcwin */
1169 		    win,	/* dstwin */
1170 		    offset + MARGIN,	/* sminrow */
1171 		    MARGIN,	/* smincol */
1172 		    MARGIN,	/* dminrow */
1173 		    MARGIN,	/* dmincol */
1174 		    height,	/* dmaxrow */
1175 		    wide,	/* dmaxcol */
1176 		    FALSE);
1177 
1178 	    delwin(dummy);
1179 
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);
1183 
1184 		if (percent < 0)
1185 		    percent = 0;
1186 		if (percent > 100)
1187 		    percent = 100;
1188 
1189 		if (offset != 0 || percent != 100) {
1190 		    char buffer[5];
1191 
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);
1199 		    }
1200 		}
1201 	    }
1202 	    last = (y - height);
1203 	}
1204     } else
1205 #endif
1206     {
1207 	(void) offset;
1208 	dlg_attrset(win, dialog_attr);
1209 	dlg_print_autowrap(win, prompt, height + 1 + (3 * MARGIN), width);
1210 	last = 0;
1211     }
1212     wmove(win, oldy, oldx);
1213     return last;
1214 }
1215 
1216 int
1217 dlg_check_scrolled(int key, int last, int page, bool * show, int *offset)
1218 {
1219     int code = 0;
1220 
1221     *show = FALSE;
1222 
1223     switch (key) {
1224     case DLGK_PAGE_FIRST:
1225 	if (*offset > 0) {
1226 	    *offset = 0;
1227 	    *show = TRUE;
1228 	}
1229 	break;
1230     case DLGK_PAGE_LAST:
1231 	if (*offset < last) {
1232 	    *offset = last;
1233 	    *show = TRUE;
1234 	}
1235 	break;
1236     case DLGK_GRID_UP:
1237 	if (*offset > 0) {
1238 	    --(*offset);
1239 	    *show = TRUE;
1240 	}
1241 	break;
1242     case DLGK_GRID_DOWN:
1243 	if (*offset < last) {
1244 	    ++(*offset);
1245 	    *show = TRUE;
1246 	}
1247 	break;
1248     case DLGK_PAGE_PREV:
1249 	if (*offset > 0) {
1250 	    *offset -= page;
1251 	    if (*offset < 0)
1252 		*offset = 0;
1253 	    *show = TRUE;
1254 	}
1255 	break;
1256     case DLGK_PAGE_NEXT:
1257 	if (*offset < last) {
1258 	    *offset += page;
1259 	    if (*offset > last)
1260 		*offset = last;
1261 	    *show = TRUE;
1262 	}
1263 	break;
1264     default:
1265 	code = -1;
1266 	break;
1267     }
1268     return code;
1269 }
1270 
1271 /*
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
1275  * as necessary.
1276  */
1277 static void
1278 auto_size_preformatted(const char *prompt, int *height, int *width)
1279 {
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;
1286 
1287     /* Get the initial dimensions */
1288     justify_text((WINDOW *) 0, prompt, max_y, max_x, &high, &wide);
1289     car = (float) (wide / high);
1290 
1291     /*
1292      * If the aspect ratio is greater than it should be, then decrease the
1293      * width proportionately.
1294      */
1295     if (car > ar) {
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;
1300     }
1301 
1302     /*
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.
1306      */
1307     while (car < ar && max_x < max_width) {
1308 	max_x += 4;
1309 	justify_text((WINDOW *) 0, prompt, max_y, max_x, &high, &wide);
1310 	car = (float) (wide / high);
1311     }
1312 
1313     *height = high;
1314     *width = wide;
1315 }
1316 
1317 /*
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
1320  * margin.
1321  */
1322 static int
1323 longest_word(const char *string)
1324 {
1325     int result = 0;
1326 
1327     while (*string != '\0') {
1328 	int length = 0;
1329 	while (*string != '\0' && !isspace(UCH(*string))) {
1330 	    length++;
1331 	    string++;
1332 	}
1333 	result = MAX(result, length);
1334 	if (*string != '\0')
1335 	    string++;
1336     }
1337     return result;
1338 }
1339 
1340 /*
1341  * if (height or width == -1) Maximize()
1342  * if (height or width == 0), justify and return actual limits.
1343  */
1344 static void
1345 real_auto_size(const char *title,
1346 	       const char *prompt,
1347 	       int *height, int *width,
1348 	       int boxlines, int mincols)
1349 {
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;
1353     int high;
1354     int save_high = *height;
1355     int save_wide = *width;
1356     int max_high;
1357     int max_wide;
1358 
1359     if (prompt == 0) {
1360 	if (*height == 0)
1361 	    *height = -1;
1362 	if (*width == 0)
1363 	    *width = -1;
1364     }
1365 
1366     max_high = (*height < 0);
1367     max_wide = (*width < 0);
1368 
1369     if (*height > 0) {
1370 	high = *height;
1371     } else {
1372 	high = SLINES - y;
1373     }
1374 
1375     if (*width <= 0) {
1376 	int wide;
1377 
1378 	if (prompt != 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);
1388 	    } else {
1389 		auto_size_preformatted(prompt, height, width);
1390 	    }
1391 	} else {
1392 	    wide = SCOLS - x;
1393 	    justify_text((WINDOW *) 0, prompt, high, wide, height, width);
1394 	}
1395     }
1396 
1397     if (*width < title_length) {
1398 	justify_text((WINDOW *) 0, prompt, high, title_length, height, width);
1399 	*width = title_length;
1400     }
1401 
1402     dialog_state.text_height = *height;
1403     dialog_state.text_width = *width;
1404 
1405     if (*width < mincols && save_wide == 0)
1406 	*width = mincols;
1407     if (prompt != 0) {
1408 	*width += ((2 * MARGIN) + SHADOW_COLS);
1409 	*height += boxlines + (2 * MARGIN);
1410     }
1411 
1412     if (save_high > 0)
1413 	*height = save_high;
1414     if (save_wide > 0)
1415 	*width = save_wide;
1416 
1417     if (max_high)
1418 	*height = SLINES - (dialog_vars.begin_set ? dialog_vars.begin_y : 0);
1419     if (max_wide)
1420 	*width = SCOLS - (dialog_vars.begin_set ? dialog_vars.begin_x : 0);
1421 }
1422 
1423 /* End of real_auto_size() */
1424 
1425 void
1426 dlg_auto_size(const char *title,
1427 	      const char *prompt,
1428 	      int *height,
1429 	      int *width,
1430 	      int boxlines,
1431 	      int mincols)
1432 {
1433     DLG_TRACE(("# dlg_auto_size(%d,%d) limits %d,%d\n",
1434 	       *height, *width,
1435 	       boxlines, mincols));
1436 
1437     real_auto_size(title, prompt, height, width, boxlines, mincols);
1438 
1439     if (*width > SCOLS) {
1440 	(*height)++;
1441 	*width = SCOLS;
1442     }
1443 
1444     if (*height > SLINES) {
1445 	*height = SLINES;
1446     }
1447     DLG_TRACE(("# ...dlg_auto_size(%d,%d) also %d,%d\n",
1448 	       *height, *width,
1449 	       dialog_state.text_height, dialog_state.text_width));
1450 }
1451 
1452 /*
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));
1457  */
1458 void
1459 dlg_auto_sizefile(const char *title,
1460 		  const char *file,
1461 		  int *height,
1462 		  int *width,
1463 		  int boxlines,
1464 		  int mincols)
1465 {
1466     int count = 0;
1467     int len = title ? dlg_count_columns(title) : 0;
1468     int nc = 4;
1469     int numlines = 2;
1470     FILE *fd;
1471 
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);
1475 
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);
1479     }
1480     if ((*height != 0) && (*width != 0)) {
1481 	(void) fclose(fd);
1482 	if (*width > SCOLS)
1483 	    *width = SCOLS;
1484 	if (*height > SLINES)
1485 	    *height = SLINES;
1486 	return;
1487     }
1488 
1489     while (!feof(fd)) {
1490 	int ch;
1491 	long offset;
1492 
1493 	if (ferror(fd))
1494 	    break;
1495 
1496 	offset = 0;
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);
1500 	    } else {
1501 		offset++;
1502 	    }
1503 	}
1504 
1505 	if (offset > len)
1506 	    len = (int) offset;
1507 
1508 	count++;
1509     }
1510 
1511     /* now 'count' has the number of lines of fd and 'len' the max length */
1512 
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. */
1518 
1519     (void) fclose(fd);
1520 }
1521 
1522 /*
1523  * Draw a rectangular box with line drawing characters.
1524  *
1525  * borderchar is used to color the upper/left edges.
1526  *
1527  * boxchar is used to color the right/lower edges.  It also is fill-color used
1528  * for the box contents.
1529  *
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.
1534  *
1535  * If you want a raised effect (and are not going to use the scroll-arrows),
1536  * reverse this choice.
1537  */
1538 void
1539 dlg_draw_box2(WINDOW *win, int y, int x, int height, int width,
1540 	      chtype boxchar, chtype borderchar, chtype borderchar2)
1541 {
1542     int i, j;
1543     chtype save = dlg_get_attrs(win);
1544 
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++)
1549 	    if (!i && !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));
1557 	    else if (!i)
1558 		(void) waddch(win, borderchar | dlg_boxchar(ACS_HLINE));
1559 	    else if (i == height - 1)
1560 		(void) waddch(win, borderchar2 | dlg_boxchar(ACS_HLINE));
1561 	    else if (!j)
1562 		(void) waddch(win, borderchar | dlg_boxchar(ACS_VLINE));
1563 	    else if (j == width - 1)
1564 		(void) waddch(win, borderchar2 | dlg_boxchar(ACS_VLINE));
1565 	    else
1566 		(void) waddch(win, boxchar | ' ');
1567     }
1568     dlg_attrset(win, save);
1569 }
1570 
1571 void
1572 dlg_draw_box(WINDOW *win, int y, int x, int height, int width,
1573 	     chtype boxchar, chtype borderchar)
1574 {
1575     dlg_draw_box2(win, y, x, height, width, boxchar, borderchar, boxchar);
1576 }
1577 
1578 /*
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.
1581  *
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.
1584  *
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).
1588  */
1589 static DIALOG_WINDOWS *
1590 find_window(DIALOG_WINDOWS * list, WINDOW *win, bool normal)
1591 {
1592     DIALOG_WINDOWS *result = 0;
1593     DIALOG_WINDOWS *p;
1594 
1595     for (p = list; p != 0; p = p->next) {
1596 	WINDOW *check = normal ? p->normal : p->shadow;
1597 	if (check == win) {
1598 	    result = p;
1599 	    break;
1600 	}
1601     }
1602     return result;
1603 }
1604 
1605 #define SearchTopWindows(win) find_window(dialog_state.all_windows, win, TRUE)
1606 #define SearchSubWindows(win) find_window(dialog_state.all_subwindows, win, FALSE)
1607 
1608 /*
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().
1613  */
1614 DIALOG_WINDOWS *
1615 _dlg_find_window(WINDOW *win)
1616 {
1617     DIALOG_WINDOWS *result = 0;
1618 
1619     if ((result = SearchTopWindows(win)) == NULL)
1620 	result = SearchSubWindows(win);
1621     return result;
1622 }
1623 
1624 #ifdef HAVE_COLOR
1625 /*
1626  * If we have wchgat(), use that for updating shadow attributes, to work with
1627  * wide-character data.
1628  */
1629 
1630 /*
1631  * Check if the given point is "in" the given window.  If so, return the window
1632  * pointer, otherwise null.
1633  */
1634 static WINDOW *
1635 in_window(WINDOW *win, int y, int x)
1636 {
1637     WINDOW *result = 0;
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;
1642 
1643     if (y >= y_base && y <= y_last && x >= x_base && x <= x_last)
1644 	result = win;
1645     return result;
1646 }
1647 
1648 static WINDOW *
1649 window_at_cell(DIALOG_WINDOWS * dw, int y, int x)
1650 {
1651     WINDOW *result = 0;
1652     DIALOG_WINDOWS *p;
1653     int y_want = y + getbegy(dw->shadow);
1654     int x_want = x + getbegx(dw->shadow);
1655 
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) {
1660 	    break;
1661 	}
1662     }
1663     if (result == 0) {
1664 	result = stdscr;
1665     }
1666     return result;
1667 }
1668 
1669 static bool
1670 in_shadow(WINDOW *normal, WINDOW *shadow, int y, int x)
1671 {
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;
1677 
1678     y += getbegy(shadow);
1679     x += getbegx(shadow);
1680 
1681     if (y >= ybase + SHADOW_ROWS
1682 	&& y < ylast + SHADOW_ROWS
1683 	&& x >= xlast
1684 	&& x < xlast + SHADOW_COLS) {
1685 	/* in the right-side */
1686 	result = TRUE;
1687     } else if (y >= ylast
1688 	       && y < ylast + SHADOW_ROWS
1689 	       && x >= ybase + SHADOW_COLS
1690 	       && x < ylast + SHADOW_COLS) {
1691 	/* check the bottom */
1692 	result = TRUE;
1693     }
1694 
1695     return result;
1696 }
1697 
1698 /*
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.
1702  */
1703 static bool
1704 last_shadow(DIALOG_WINDOWS * dw, int y, int x)
1705 {
1706     DIALOG_WINDOWS *p;
1707     bool result = TRUE;
1708 
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)) {
1712 	    result = FALSE;
1713 	    break;
1714 	}
1715     }
1716     return result;
1717 }
1718 
1719 static void
1720 repaint_cell(DIALOG_WINDOWS * dw, bool draw, int y, int x)
1721 {
1722     WINDOW *win = dw->shadow;
1723     WINDOW *cellwin;
1724     int y2, x2;
1725 
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);
1733 
1734 	if (winch(cellwin) & A_ALTCHARSET) {
1735 	    the_attr |= A_ALTCHARSET;
1736 	}
1737 #if USE_WCHGAT
1738 	wchgat(cellwin, 1,
1739 	       the_attr & (chtype) (~A_COLOR),
1740 	       (short) PAIR_NUMBER(the_attr),
1741 	       NULL);
1742 #else
1743 	{
1744 	    chtype the_char = ((winch(cellwin) & A_CHARTEXT) | the_attr);
1745 	    (void) waddch(cellwin, the_char);
1746 	}
1747 #endif
1748 	wnoutrefresh(cellwin);
1749     }
1750 }
1751 
1752 #define RepaintCell(dw, draw, y, x) repaint_cell(dw, draw, y, x)
1753 
1754 static void
1755 repaint_shadow(DIALOG_WINDOWS * dw, bool draw, int y, int x, int height, int width)
1756 {
1757     if (UseShadow(dw)) {
1758 	int i, j;
1759 
1760 #if !USE_WCHGAT
1761 	chtype save = dlg_get_attrs(dw->shadow);
1762 	dlg_attrset(dw->shadow, draw ? shadow_attr : screen_attr);
1763 #endif
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);
1767 	    }
1768 	}
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);
1772 	    }
1773 	}
1774 	(void) wnoutrefresh(dw->shadow);
1775 #if !USE_WCHGAT
1776 	dlg_attrset(dw->shadow, save);
1777 #endif
1778     }
1779 }
1780 
1781 /*
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.
1784  */
1785 static void
1786 draw_childs_shadow(DIALOG_WINDOWS * dw)
1787 {
1788     if (UseShadow(dw)) {
1789 	repaint_shadow(dw,
1790 		       TRUE,
1791 		       getbegy(dw->normal) - getbegy(dw->shadow),
1792 		       getbegx(dw->normal) - getbegx(dw->shadow),
1793 		       getmaxy(dw->normal),
1794 		       getmaxx(dw->normal));
1795     }
1796 }
1797 
1798 /*
1799  * Erase a shadow on the parent window corresponding to the right- and
1800  * bottom-edge of the child window.
1801  */
1802 static void
1803 erase_childs_shadow(DIALOG_WINDOWS * dw)
1804 {
1805     if (UseShadow(dw)) {
1806 	repaint_shadow(dw,
1807 		       FALSE,
1808 		       getbegy(dw->normal) - getbegy(dw->shadow),
1809 		       getbegx(dw->normal) - getbegx(dw->shadow),
1810 		       getmaxy(dw->normal),
1811 		       getmaxx(dw->normal));
1812     }
1813 }
1814 
1815 /*
1816  * Draw shadows along the right and bottom edge to give a more 3D look
1817  * to the boxes.
1818  */
1819 void
1820 dlg_draw_shadow(WINDOW *win, int y, int x, int height, int width)
1821 {
1822     repaint_shadow(SearchTopWindows(win), TRUE, y, x, height, width);
1823 }
1824 #endif /* HAVE_COLOR */
1825 
1826 /*
1827  * Allow shell scripts to remap the exit codes so they can distinguish ESC
1828  * from ERROR.
1829  */
1830 void
1831 dlg_exit(int code)
1832 {
1833     /* *INDENT-OFF* */
1834     static const struct {
1835 	int code;
1836 	const char *name;
1837     } table[] = {
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" },
1846     };
1847     /* *INDENT-ON* */
1848 
1849     unsigned n;
1850     bool overridden = FALSE;
1851 
1852   retry:
1853     for (n = 0; n < TableSize(table); n++) {
1854 	if (table[n].code == code) {
1855 	    if (dlg_getenv_num(table[n].name, &code)) {
1856 		overridden = TRUE;
1857 	    }
1858 	    break;
1859 	}
1860     }
1861 
1862     /*
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.
1866      */
1867     if (code == DLG_EXIT_ITEM_HELP && !overridden) {
1868 	code = DLG_EXIT_HELP;
1869 	goto retry;
1870     }
1871 #ifdef HAVE_DLG_TRACE
1872     dlg_trace((const char *) 0);	/* close it */
1873 #endif
1874 
1875 #ifdef NO_LEAKS
1876     _dlg_inputstr_leaks();
1877 #if defined(NCURSES_VERSION) && (defined(HAVE_CURSES_EXIT) || defined(HAVE__NC_FREE_AND_EXIT))
1878     curses_exit(code);
1879 #endif
1880 #endif
1881 
1882     if (dialog_state.input == stdin) {
1883 	exit(code);
1884     } else {
1885 	/*
1886 	 * Just in case of using --input-fd option, do not
1887 	 * call atexit functions of ncurses which may hang.
1888 	 */
1889 	if (dialog_state.input) {
1890 	    fclose(dialog_state.input);
1891 	    dialog_state.input = 0;
1892 	}
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;
1897 	    }
1898 	}
1899 	_exit(code);
1900     }
1901 }
1902 
1903 #define DATA(name) { DLG_EXIT_ ## name, #name }
1904 /* *INDENT-OFF* */
1905 static struct {
1906     int code;
1907     const char *name;
1908 } exit_codenames[] = {
1909     DATA(ESC),
1910     DATA(UNKNOWN),
1911     DATA(ERROR),
1912     DATA(OK),
1913     DATA(CANCEL),
1914     DATA(HELP),
1915     DATA(EXTRA),
1916     DATA(ITEM_HELP),
1917 };
1918 #undef DATA
1919 /* *INDENT-ON* */
1920 
1921 const char *
1922 dlg_exitcode2s(int code)
1923 {
1924     const char *result = "?";
1925     size_t n;
1926 
1927     for (n = 0; n < TableSize(exit_codenames); ++n) {
1928 	if (exit_codenames[n].code == code) {
1929 	    result = exit_codenames[n].name;
1930 	    break;
1931 	}
1932     }
1933     return result;
1934 }
1935 
1936 int
1937 dlg_exitname2n(const char *name)
1938 {
1939     int result = DLG_EXIT_UNKNOWN;
1940     size_t n;
1941 
1942     for (n = 0; n < TableSize(exit_codenames); ++n) {
1943 	if (!dlg_strcmp(exit_codenames[n].name, name)) {
1944 	    result = exit_codenames[n].code;
1945 	    break;
1946 	}
1947     }
1948     return result;
1949 }
1950 
1951 /* quit program killing all tailbg */
1952 void
1953 dlg_exiterr(const char *fmt, ...)
1954 {
1955     int retval;
1956     va_list ap;
1957 
1958     end_dialog();
1959 
1960     (void) fputc('\n', stderr);
1961     va_start(ap, fmt);
1962     (void) vfprintf(stderr, fmt, ap);
1963     va_end(ap);
1964     (void) fputc('\n', stderr);
1965 
1966 #ifdef HAVE_DLG_TRACE
1967     va_start(ap, fmt);
1968     dlg_trace_msg("## Error: ");
1969     dlg_trace_va_msg(fmt, ap);
1970     va_end(ap);
1971 #endif
1972 
1973     dlg_killall_bg(&retval);
1974 
1975     (void) fflush(stderr);
1976     (void) fflush(stdout);
1977     dlg_exit(strcmp(fmt, "timeout") == 0 ? DLG_EXIT_TIMEOUT : DLG_EXIT_ERROR);
1978 }
1979 
1980 /*
1981  * Get a string from the environment, rejecting those which are entirely blank.
1982  */
1983 char *
1984 dlg_getenv_str(const char *name)
1985 {
1986     char *result = getenv(name);
1987     if (result != NULL) {
1988 	while (*result != '\0' && isspace(UCH(*result)))
1989 	    ++result;
1990 	if (*result == '\0')
1991 	    result = NULL;
1992     }
1993     return result;
1994 }
1995 
1996 /*
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
2003  *   useful.
2004  */
2005 int
2006 dlg_getenv_num(const char *name, int *value)
2007 {
2008     int result = 0;
2009     char *data = getenv(name);
2010     if (data != NULL) {
2011 	char *temp = NULL;
2012 	long check = strtol(data, &temp, 0);
2013 	if (temp != 0 && temp != data && *temp == '\0') {
2014 	    result = (int) check;
2015 	    if (value != NULL) {
2016 		*value = result;
2017 		result = 1;
2018 	    }
2019 	}
2020     }
2021     return result;
2022 }
2023 
2024 void
2025 dlg_beeping(void)
2026 {
2027     if (dialog_vars.beep_signal) {
2028 	(void) beep();
2029 	dialog_vars.beep_signal = 0;
2030     }
2031 }
2032 
2033 void
2034 dlg_print_size(int height, int width)
2035 {
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));
2039     }
2040 }
2041 
2042 void
2043 dlg_ctl_size(int height, int width)
2044 {
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);
2049 	}
2050 #ifdef HAVE_COLOR
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;
2056 	    } else {
2057 		dlg_exiterr("Window+Shadow too big. (height, width) = (%d, %d). Max allowed (%d, %d).",
2058 			    height, width, SLINES, SCOLS);
2059 	    }
2060 	}
2061 #endif
2062     }
2063 }
2064 
2065 /*
2066  * If the --tab-correct was not selected, convert tabs to single spaces.
2067  */
2068 void
2069 dlg_tab_correct_str(char *prompt)
2070 {
2071     char *ptr;
2072 
2073     if (dialog_vars.tab_correct) {
2074 	while ((ptr = strchr(prompt, TAB)) != NULL) {
2075 	    *ptr = ' ';
2076 	    prompt = ptr;
2077 	}
2078     }
2079 }
2080 
2081 void
2082 dlg_calc_listh(int *height, int *list_height, int item_no)
2083 {
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;
2089 	else
2090 	    *list_height = rows - (*height);
2091     }
2092     (*height) += (*list_height);
2093 }
2094 
2095 /* obsolete */
2096 int
2097 dlg_calc_listw(int item_no, char **items, int group)
2098 {
2099     int i, len1 = 0, len2 = 0;
2100 
2101     for (i = 0; i < (item_no * group); i += group) {
2102 	int n;
2103 
2104 	if ((n = dlg_count_columns(items[i])) > len1)
2105 	    len1 = n;
2106 	if ((n = dlg_count_columns(items[i + 1])) > len2)
2107 	    len2 = n;
2108     }
2109     return len1 + len2;
2110 }
2111 
2112 int
2113 dlg_calc_list_width(int item_no, DIALOG_LISTITEM * items)
2114 {
2115     int n, i, len1 = 0, len2 = 0;
2116     int bits = ((dialog_vars.no_tags ? 1 : 0)
2117 		+ (dialog_vars.no_items ? 2 : 0));
2118 
2119     for (i = 0; i < item_no; ++i) {
2120 	switch (bits) {
2121 	case 0:
2122 	    /* FALLTHRU */
2123 	case 1:
2124 	    if ((n = dlg_count_columns(items[i].name)) > len1)
2125 		len1 = n;
2126 	    if ((n = dlg_count_columns(items[i].text)) > len2)
2127 		len2 = n;
2128 	    break;
2129 	case 2:
2130 	    /* FALLTHRU */
2131 	case 3:
2132 	    if ((n = dlg_count_columns(items[i].name)) > len1)
2133 		len1 = n;
2134 	    break;
2135 	}
2136     }
2137     return len1 + len2;
2138 }
2139 
2140 char *
2141 dlg_strempty(void)
2142 {
2143     static char empty[] = "";
2144     return empty;
2145 }
2146 
2147 char *
2148 dlg_strclone(const char *cprompt)
2149 {
2150     char *prompt = 0;
2151     if (cprompt != 0) {
2152 	prompt = dlg_malloc(char, strlen(cprompt) + 1);
2153 	assert_ptr(prompt, "dlg_strclone");
2154 	strcpy(prompt, cprompt);
2155     }
2156     return prompt;
2157 }
2158 
2159 chtype
2160 dlg_asciibox(chtype ch)
2161 {
2162     chtype result = 0;
2163 
2164     if (ch == ACS_ULCORNER)
2165 	result = '+';
2166     else if (ch == ACS_LLCORNER)
2167 	result = '+';
2168     else if (ch == ACS_URCORNER)
2169 	result = '+';
2170     else if (ch == ACS_LRCORNER)
2171 	result = '+';
2172     else if (ch == ACS_HLINE)
2173 	result = '-';
2174     else if (ch == ACS_VLINE)
2175 	result = '|';
2176     else if (ch == ACS_LTEE)
2177 	result = '+';
2178     else if (ch == ACS_RTEE)
2179 	result = '+';
2180     else if (ch == ACS_UARROW)
2181 	result = '^';
2182     else if (ch == ACS_DARROW)
2183 	result = 'v';
2184 
2185     return result;
2186 }
2187 
2188 chtype
2189 dlg_boxchar(chtype ch)
2190 {
2191     chtype result = dlg_asciibox(ch);
2192 
2193     if (result != 0) {
2194 	if (dialog_vars.ascii_lines)
2195 	    ch = result;
2196 	else if (dialog_vars.no_lines)
2197 	    ch = ' ';
2198     }
2199     return ch;
2200 }
2201 
2202 int
2203 dlg_box_x_ordinate(int width)
2204 {
2205     int x;
2206 
2207     if (dialog_vars.begin_set == 1) {
2208 	x = dialog_vars.begin_x;
2209     } else {
2210 	/* center dialog box on screen unless --begin-set */
2211 	x = (SCOLS - width) / 2;
2212     }
2213     return x;
2214 }
2215 
2216 int
2217 dlg_box_y_ordinate(int height)
2218 {
2219     int y;
2220 
2221     if (dialog_vars.begin_set == 1) {
2222 	y = dialog_vars.begin_y;
2223     } else {
2224 	/* center dialog box on screen unless --begin-set */
2225 	y = (SLINES - height) / 2;
2226     }
2227     return y;
2228 }
2229 
2230 void
2231 dlg_draw_title(WINDOW *win, const char *title)
2232 {
2233     if (title != NULL) {
2234 	chtype attr = A_NORMAL;
2235 	chtype save = dlg_get_attrs(win);
2236 	int x = centered(getmaxx(win), title);
2237 
2238 	dlg_attrset(win, title_attr);
2239 	wmove(win, 0, x);
2240 	dlg_print_text(win, title, getmaxx(win) - x, &attr);
2241 	dlg_attrset(win, save);
2242 	dlg_finish_string(title);
2243     }
2244 }
2245 
2246 void
2247 dlg_draw_bottom_box2(WINDOW *win, chtype on_left, chtype on_right, chtype on_inside)
2248 {
2249     int width = getmaxx(win);
2250     int height = getmaxy(win);
2251     int i;
2252 
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, ' ');
2264 }
2265 
2266 void
2267 dlg_draw_bottom_box(WINDOW *win)
2268 {
2269     dlg_draw_bottom_box2(win, border_attr, dialog_attr, dialog_attr);
2270 }
2271 
2272 /*
2273  * Remove a window, repainting everything else.  This would be simpler if we
2274  * used the panel library, but that is not _always_ available.
2275  */
2276 void
2277 dlg_del_window(WINDOW *win)
2278 {
2279     DIALOG_WINDOWS *p, *q, *r;
2280 
2281     /*
2282      * If --keep-window was set, do not delete/repaint the windows.
2283      */
2284     if (dialog_vars.keep_window)
2285 	return;
2286 
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.
2290      */
2291     if (dialog_state.getc_callbacks != 0) {
2292 	touchwin(stdscr);
2293 	wnoutrefresh(stdscr);
2294     }
2295 
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 */
2299 	    if (r == 0) {
2300 		dialog_state.all_windows = p->next;
2301 	    } else {
2302 		r->next = p->next;
2303 	    }
2304 	} else {
2305 	    if (p->shadow != 0) {
2306 		touchwin(p->shadow);
2307 		wnoutrefresh(p->shadow);
2308 	    }
2309 	    touchwin(p->normal);
2310 	    wnoutrefresh(p->normal);
2311 	}
2312     }
2313 
2314     if (q) {
2315 	if (dialog_state.all_windows != 0)
2316 	    erase_childs_shadow(q);
2317 	del_subwindows(q->normal);
2318 	dlg_unregister_window(q->normal);
2319 	delwin(q->normal);
2320 	free(q);
2321     }
2322     doupdate();
2323 }
2324 
2325 /*
2326  * Create a window, optionally with a shadow.
2327  */
2328 WINDOW *
2329 dlg_new_window(int height, int width, int y, int x)
2330 {
2331     return dlg_new_modal_window(stdscr, height, width, y, x);
2332 }
2333 
2334 /*
2335  * "Modal" windows differ from normal ones by having a shadow in a window
2336  * separate from the standard screen.
2337  */
2338 WINDOW *
2339 dlg_new_modal_window(WINDOW *parent, int height, int width, int y, int x)
2340 {
2341     WINDOW *win;
2342     DIALOG_WINDOWS *p = dlg_calloc(DIALOG_WINDOWS, 1);
2343 
2344     (void) parent;
2345     if (p == 0
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);
2349     }
2350     p->next = dialog_state.all_windows;
2351     p->normal = win;
2352     p->getc_timeout = WTIMEOUT_OFF;
2353     dialog_state.all_windows = p;
2354 #ifdef HAVE_COLOR
2355     if (dialog_state.use_shadow) {
2356 	p->shadow = parent;
2357 	draw_childs_shadow(p);
2358     }
2359 #endif
2360 
2361     (void) keypad(win, TRUE);
2362     return win;
2363 }
2364 
2365 /*
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
2369  *    time), or
2370  * b) if zero, it was a non-blocking read, or
2371  * c) if negative, an error occurred on a blocking read.
2372  */
2373 int
2374 dlg_set_timeout(WINDOW *win, bool will_getc)
2375 {
2376     DIALOG_WINDOWS *p;
2377     int result = 0;
2378 
2379     if ((p = SearchTopWindows(win)) != NULL) {
2380 	int interval = (dialog_vars.timeout_secs * 1000);
2381 
2382 	if (will_getc || dialog_vars.pause_secs) {
2383 	    interval = WTIMEOUT_VAL;
2384 	} else {
2385 	    result = interval;
2386 	    if (interval <= 0) {
2387 		interval = WTIMEOUT_OFF;
2388 	    }
2389 	}
2390 	wtimeout(win, interval);
2391 	p->getc_timeout = interval;
2392     }
2393     return result;
2394 }
2395 
2396 void
2397 dlg_reset_timeout(WINDOW *win)
2398 {
2399     DIALOG_WINDOWS *p;
2400 
2401     if ((p = SearchTopWindows(win)) != NULL) {
2402 	wtimeout(win, p->getc_timeout);
2403     } else {
2404 	wtimeout(win, WTIMEOUT_OFF);
2405     }
2406 }
2407 
2408 /*
2409  * Move/Resize a window, optionally with a shadow.
2410  */
2411 #ifdef KEY_RESIZE
2412 void
2413 dlg_move_window(WINDOW *win, int height, int width, int y, int x)
2414 {
2415     if (win != 0) {
2416 	DIALOG_WINDOWS *p;
2417 
2418 	dlg_ctl_size(height, width);
2419 
2420 	if ((p = SearchTopWindows(win)) != 0) {
2421 	    (void) wresize(win, height, width);
2422 	    (void) mvwin(win, y, x);
2423 #ifdef HAVE_COLOR
2424 	    if (p->shadow != 0) {
2425 		if (dialog_state.use_shadow) {
2426 		    (void) mvwin(p->shadow, y + SHADOW_ROWS, x + SHADOW_COLS);
2427 		} else {
2428 		    p->shadow = 0;
2429 		}
2430 	    }
2431 #endif
2432 	    (void) refresh();
2433 
2434 #ifdef HAVE_COLOR
2435 	    draw_childs_shadow(p);
2436 #endif
2437 	}
2438     }
2439 }
2440 
2441 /*
2442  * Having just received a KEY_RESIZE, wait a short time to ignore followup
2443  * KEY_RESIZE events.
2444  */
2445 void
2446 dlg_will_resize(WINDOW *win)
2447 {
2448     int n, base;
2449     int caught = 0;
2450 
2451     dialog_state.had_resize = TRUE;
2452     dlg_trace_win(win);
2453     wtimeout(win, WTIMEOUT_VAL * 5);
2454 
2455     for (n = base = 0; n < base + 10; ++n) {
2456 	int ch;
2457 
2458 	if ((ch = wgetch(win)) != ERR) {
2459 	    if (ch == KEY_RESIZE) {
2460 		base = n;
2461 		++caught;
2462 	    } else if (ch != ERR) {
2463 		ungetch(ch);
2464 		break;
2465 	    }
2466 	}
2467     }
2468     dlg_reset_timeout(win);
2469     DLG_TRACE(("# caught %d KEY_RESIZE key%s\n",
2470 	       1 + caught,
2471 	       caught == 1 ? "" : "s"));
2472 }
2473 #endif /* KEY_RESIZE */
2474 
2475 WINDOW *
2476 dlg_der_window(WINDOW *parent, int height, int width, int y, int x)
2477 {
2478     WINDOW *win;
2479 
2480     /* existing uses of derwin are (almost) guaranteed to succeed, and the
2481      * caller has to allow for failure.
2482      */
2483     if ((win = derwin(parent, height, width, y, x)) != 0) {
2484 	add_subwindow(parent, win);
2485 	(void) keypad(win, TRUE);
2486     }
2487     return win;
2488 }
2489 
2490 WINDOW *
2491 dlg_sub_window(WINDOW *parent, int height, int width, int y, int x)
2492 {
2493     WINDOW *win;
2494 
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);
2498     }
2499 
2500     add_subwindow(parent, win);
2501     (void) keypad(win, TRUE);
2502     return win;
2503 }
2504 
2505 /* obsolete */
2506 int
2507 dlg_default_item(char **items, int llen)
2508 {
2509     int result = 0;
2510 
2511     if (dialog_vars.default_item != 0) {
2512 	int count = 0;
2513 	while (*items != 0) {
2514 	    if (!strcmp(dialog_vars.default_item, *items)) {
2515 		result = count;
2516 		break;
2517 	    }
2518 	    items += llen;
2519 	    count++;
2520 	}
2521     }
2522     return result;
2523 }
2524 
2525 int
2526 dlg_default_listitem(DIALOG_LISTITEM * items)
2527 {
2528     int result = 0;
2529 
2530     if (dialog_vars.default_item != 0) {
2531 	int count = 0;
2532 	while (items->name != 0) {
2533 	    if (!strcmp(dialog_vars.default_item, items->name)) {
2534 		result = count;
2535 		break;
2536 	    }
2537 	    ++items;
2538 	    count++;
2539 	}
2540     }
2541     return result;
2542 }
2543 
2544 /*
2545  * Draw the string for item_help
2546  */
2547 void
2548 dlg_item_help(const char *txt)
2549 {
2550     if (USE_ITEM_HELP(txt)) {
2551 	chtype attr = A_NORMAL;
2552 
2553 	dlg_attrset(stdscr, itemhelp_attr);
2554 	(void) wmove(stdscr, LINES - 1, 0);
2555 	(void) wclrtoeol(stdscr);
2556 	(void) addch(' ');
2557 	dlg_print_text(stdscr, txt, COLS - 1, &attr);
2558 
2559 	if (itemhelp_attr & A_COLOR) {
2560 	    int y, x;
2561 	    /* fill the remainder of the line with the window's attributes */
2562 	    getyx(stdscr, y, x);
2563 	    (void) y;
2564 	    while (x < COLS) {
2565 		(void) addch(' ');
2566 		++x;
2567 	    }
2568 	}
2569 	(void) wnoutrefresh(stdscr);
2570     }
2571 }
2572 
2573 #ifndef HAVE_STRCASECMP
2574 int
2575 dlg_strcmp(const char *a, const char *b)
2576 {
2577     int ac, bc, cmp;
2578 
2579     for (;;) {
2580 	ac = UCH(*a++);
2581 	bc = UCH(*b++);
2582 	if (isalpha(ac) && islower(ac))
2583 	    ac = _toupper(ac);
2584 	if (isalpha(bc) && islower(bc))
2585 	    bc = _toupper(bc);
2586 	cmp = ac - bc;
2587 	if (ac == 0 || bc == 0 || cmp != 0)
2588 	    break;
2589     }
2590     return cmp;
2591 }
2592 #endif
2593 
2594 /*
2595  * Returns true if 'dst' points to a blank which follows another blank which
2596  * is not a leading blank on a line.
2597  */
2598 static bool
2599 trim_blank(char *base, char *dst)
2600 {
2601     int count = !!isblank(UCH(*dst));
2602 
2603     while (dst-- != base) {
2604 	if (*dst == '\n') {
2605 	    break;
2606 	} else if (isblank(UCH(*dst))) {
2607 	    count++;
2608 	} else {
2609 	    break;
2610 	}
2611     }
2612     return (count > 1);
2613 }
2614 
2615 /*
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.
2620  */
2621 void
2622 dlg_trim_string(char *s)
2623 {
2624     char *base = s;
2625     char *p1;
2626     char *p = s;
2627     int has_newlines = !dialog_vars.no_nl_expand && (strstr(s, "\\n") != 0);
2628 
2629     while (*p != '\0') {
2630 	if (*p == TAB && !dialog_vars.nocollapse)
2631 	    *p = ' ';
2632 
2633 	if (has_newlines) {	/* If prompt contains "\n" strings */
2634 	    if (*p == '\\' && *(p + 1) == 'n') {
2635 		*s++ = '\n';
2636 		p += 2;
2637 		p1 = p;
2638 		/*
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\").
2642 		 */
2643 		while (isblank(UCH(*p1)))
2644 		    p1++;
2645 		if (*p1 == '\n')
2646 		    p = p1 + 1;
2647 	    } else if (*p == '\n') {
2648 		if (dialog_vars.cr_wrap)
2649 		    *s++ = *p++;
2650 		else {
2651 		    /* Replace the '\n' with a space if cr_wrap is not set */
2652 		    if (!trim_blank(base, p))
2653 			*s++ = ' ';
2654 		    p++;
2655 		}
2656 	    } else		/* If *p != '\n' */
2657 		*s++ = *p++;
2658 	} else if (dialog_vars.trim_whitespace) {
2659 	    if (isblank(UCH(*p))) {
2660 		if (!isblank(UCH(*(s - 1)))) {
2661 		    *s++ = ' ';
2662 		    p++;
2663 		} else
2664 		    p++;
2665 	    } else if (*p == '\n') {
2666 		if (dialog_vars.cr_wrap)
2667 		    *s++ = *p++;
2668 		else if (!isblank(UCH(*(s - 1)))) {
2669 		    /* Strip '\n's if cr_wrap is not set. */
2670 		    *s++ = ' ';
2671 		    p++;
2672 		} else
2673 		    p++;
2674 	    } else
2675 		*s++ = *p++;
2676 	} else {		/* If there are no "\n" strings */
2677 	    if (isblank(UCH(*p)) && !dialog_vars.nocollapse) {
2678 		if (!trim_blank(base, p))
2679 		    *s++ = *p;
2680 		p++;
2681 	    } else
2682 		*s++ = *p++;
2683 	}
2684     }
2685 
2686     *s = '\0';
2687 }
2688 
2689 void
2690 dlg_set_focus(WINDOW *parent, WINDOW *win)
2691 {
2692     if (win != 0) {
2693 	(void) wmove(parent,
2694 		     getpary(win) + getcury(win),
2695 		     getparx(win) + getcurx(win));
2696 	(void) wnoutrefresh(win);
2697 	(void) doupdate();
2698     }
2699 }
2700 
2701 /*
2702  * Returns the nominal maximum buffer size.
2703  */
2704 int
2705 dlg_max_input(int max_len)
2706 {
2707     if (dialog_vars.max_input != 0 && dialog_vars.max_input < MAX_LEN)
2708 	max_len = dialog_vars.max_input;
2709 
2710     return max_len;
2711 }
2712 
2713 /*
2714  * Free storage used for the result buffer.
2715  */
2716 void
2717 dlg_clr_result(void)
2718 {
2719     if (dialog_vars.input_length) {
2720 	dialog_vars.input_length = 0;
2721 	if (dialog_vars.input_result)
2722 	    free(dialog_vars.input_result);
2723     }
2724     dialog_vars.input_result = 0;
2725 }
2726 
2727 /*
2728  * Setup a fixed-buffer for the result.
2729  */
2730 char *
2731 dlg_set_result(const char *string)
2732 {
2733     unsigned need = string ? (unsigned) strlen(string) + 1 : 0;
2734 
2735     /* inputstr.c needs a fixed buffer */
2736     if (need < MAX_LEN)
2737 	need = MAX_LEN;
2738 
2739     /*
2740      * If the buffer is not big enough, allocate a new one.
2741      */
2742     if (dialog_vars.input_length != 0
2743 	|| dialog_vars.input_result == 0
2744 	|| need > MAX_LEN) {
2745 
2746 	dlg_clr_result();
2747 
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");
2751     }
2752 
2753     strcpy(dialog_vars.input_result, string ? string : "");
2754 
2755     return dialog_vars.input_result;
2756 }
2757 
2758 /*
2759  * Accumulate results in dynamically allocated buffer.
2760  * If input_length is zero, it is a MAX_LEN buffer belonging to the caller.
2761  */
2762 void
2763 dlg_add_result(const char *string)
2764 {
2765     unsigned have = (dialog_vars.input_result
2766 		     ? (unsigned) strlen(dialog_vars.input_result)
2767 		     : 0);
2768     unsigned want = (unsigned) strlen(string) + 1 + have;
2769 
2770     if ((want >= MAX_LEN)
2771 	|| (dialog_vars.input_length != 0)
2772 	|| (dialog_vars.input_result == 0)) {
2773 
2774 	if (dialog_vars.input_length == 0
2775 	    || dialog_vars.input_result == 0) {
2776 
2777 	    char *save_result = dialog_vars.input_result;
2778 
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");
2791 	}
2792     }
2793     strcat(dialog_vars.input_result, string);
2794 }
2795 
2796 /*
2797  * These are characters that (aside from the quote-delimiter) will have to
2798  * be escaped in a single- or double-quoted string.
2799  */
2800 #define FIX_SINGLE "\n\\"
2801 #define FIX_DOUBLE FIX_SINGLE "[]{}?*;`~#$^&()|<>"
2802 
2803 /*
2804  * Returns the quote-delimiter.
2805  */
2806 static const char *
2807 quote_delimiter(void)
2808 {
2809     return dialog_vars.single_quoted ? "'" : "\"";
2810 }
2811 
2812 /*
2813  * Returns true if we should quote the given string.
2814  */
2815 static bool
2816 must_quote(char *string)
2817 {
2818     bool code = FALSE;
2819 
2820     if (*string != '\0') {
2821 	size_t len = strlen(string);
2822 	if (strcspn(string, quote_delimiter()) != len)
2823 	    code = TRUE;
2824 	else if (strcspn(string, "\n\t ") != len)
2825 	    code = TRUE;
2826 	else
2827 	    code = (strcspn(string, FIX_DOUBLE) != len);
2828     } else {
2829 	code = TRUE;
2830     }
2831 
2832     return code;
2833 }
2834 
2835 /*
2836  * Add a quoted string to the result buffer.
2837  */
2838 void
2839 dlg_add_quoted(char *string)
2840 {
2841     char temp[2];
2842     const char *my_quote = quote_delimiter();
2843     const char *must_fix = (dialog_vars.single_quoted
2844 			    ? FIX_SINGLE
2845 			    : FIX_DOUBLE);
2846 
2847     if (must_quote(string)) {
2848 	temp[1] = '\0';
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);
2855 	}
2856 	dlg_add_result(my_quote);
2857     } else {
2858 	dlg_add_result(string);
2859     }
2860 }
2861 
2862 /*
2863  * When adding a result, make that depend on whether "--quoted" is used.
2864  */
2865 void
2866 dlg_add_string(char *string)
2867 {
2868     if (dialog_vars.quoted) {
2869 	dlg_add_quoted(string);
2870     } else {
2871 	dlg_add_result(string);
2872     }
2873 }
2874 
2875 bool
2876 dlg_need_separator(void)
2877 {
2878     bool result = FALSE;
2879 
2880     if (dialog_vars.output_separator) {
2881 	result = TRUE;
2882     } else if (dialog_vars.input_result && *(dialog_vars.input_result)) {
2883 	result = TRUE;
2884     }
2885     return result;
2886 }
2887 
2888 void
2889 dlg_add_separator(void)
2890 {
2891     const char *separator = (dialog_vars.separate_output) ? "\n" : " ";
2892 
2893     if (dialog_vars.output_separator)
2894 	separator = dialog_vars.output_separator;
2895 
2896     dlg_add_result(separator);
2897 }
2898 
2899 #define HELP_PREFIX		"HELP "
2900 
2901 void
2902 dlg_add_help_listitem(int *result, char **tag, DIALOG_LISTITEM * item)
2903 {
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;
2908     } else {
2909 	*tag = item->name;
2910     }
2911 }
2912 
2913 void
2914 dlg_add_help_formitem(int *result, char **tag, DIALOG_FORMITEM * item)
2915 {
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;
2920     } else {
2921 	*tag = item->name;
2922     }
2923 }
2924 
2925 /*
2926  * Some widgets support only one value of a given variable - save/restore the
2927  * global dialog_vars so we can override it consistently.
2928  */
2929 void
2930 dlg_save_vars(DIALOG_VARS * vars)
2931 {
2932     *vars = dialog_vars;
2933 }
2934 
2935 /*
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.
2939  */
2940 void
2941 dlg_restore_vars(DIALOG_VARS * vars)
2942 {
2943     char *save_result = dialog_vars.input_result;
2944     unsigned save_length = dialog_vars.input_length;
2945 
2946     dialog_vars = *vars;
2947     dialog_vars.input_result = save_result;
2948     dialog_vars.input_length = save_length;
2949 }
2950 
2951 /*
2952  * Called each time a widget is invoked which may do output, increment a count.
2953  */
2954 void
2955 dlg_does_output(void)
2956 {
2957     dialog_state.output_count += 1;
2958 }
2959 
2960 /*
2961  * Compatibility for different versions of curses.
2962  */
2963 #if !(defined(HAVE_GETBEGX) && defined(HAVE_GETBEGY))
2964 int
2965 dlg_getbegx(WINDOW *win)
2966 {
2967     int y, x;
2968     getbegyx(win, y, x);
2969     return x;
2970 }
2971 int
2972 dlg_getbegy(WINDOW *win)
2973 {
2974     int y, x;
2975     getbegyx(win, y, x);
2976     return y;
2977 }
2978 #endif
2979 
2980 #if !(defined(HAVE_GETCURX) && defined(HAVE_GETCURY))
2981 int
2982 dlg_getcurx(WINDOW *win)
2983 {
2984     int y, x;
2985     getyx(win, y, x);
2986     return x;
2987 }
2988 int
2989 dlg_getcury(WINDOW *win)
2990 {
2991     int y, x;
2992     getyx(win, y, x);
2993     return y;
2994 }
2995 #endif
2996 
2997 #if !(defined(HAVE_GETMAXX) && defined(HAVE_GETMAXY))
2998 int
2999 dlg_getmaxx(WINDOW *win)
3000 {
3001     int y, x;
3002     getmaxyx(win, y, x);
3003     return x;
3004 }
3005 int
3006 dlg_getmaxy(WINDOW *win)
3007 {
3008     int y, x;
3009     getmaxyx(win, y, x);
3010     return y;
3011 }
3012 #endif
3013 
3014 #if !(defined(HAVE_GETPARX) && defined(HAVE_GETPARY))
3015 int
3016 dlg_getparx(WINDOW *win)
3017 {
3018     int y, x;
3019     getparyx(win, y, x);
3020     return x;
3021 }
3022 int
3023 dlg_getpary(WINDOW *win)
3024 {
3025     int y, x;
3026     getparyx(win, y, x);
3027     return y;
3028 }
3029 #endif
3030 
3031 #ifdef NEED_WGETPARENT
3032 WINDOW *
3033 dlg_wgetparent(WINDOW *win)
3034 {
3035 #undef wgetparent
3036     WINDOW *result = 0;
3037     DIALOG_WINDOWS *p;
3038 
3039     for (p = dialog_state.all_subwindows; p != 0; p = p->next) {
3040 	if (p->shadow == win) {
3041 	    result = p->normal;
3042 	    break;
3043 	}
3044     }
3045     return result;
3046 }
3047 #endif
3048