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