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