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