1/*
2 *  $Id: util.c,v 1.258 2013/09/22 00:41:40 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 = 0;
593    if (*text) {
594	result = dlg_count_columns(text);
595	if (result && dialog_vars.colors) {
596	    int hidden = 0;
597	    while (*text) {
598		if (dialog_vars.colors && isOurEscape(text)) {
599		    hidden += ESCAPE_LEN;
600		    text += ESCAPE_LEN;
601		} else {
602		    ++text;
603		}
604	    }
605	    result -= hidden;
606	}
607    }
608    return result;
609}
610
611static int
612centered(int width, const char *string)
613{
614    int need = dlg_count_real_columns(string);
615    int left;
616
617    left = (width - need) / 2 - 1;
618    if (left < 0)
619	left = 0;
620    return left;
621}
622
623#ifdef USE_WIDE_CURSES
624static bool
625is_combining(const char *txt, int *combined)
626{
627    bool result = FALSE;
628
629    if (*combined == 0) {
630	if (UCH(*txt) >= 128) {
631	    wchar_t wch;
632	    mbstate_t state;
633	    size_t given = strlen(txt);
634	    size_t len;
635
636	    memset(&state, 0, sizeof(state));
637	    len = mbrtowc(&wch, txt, given, &state);
638	    if ((int) len > 0 && wcwidth(wch) == 0) {
639		*combined = (int) len - 1;
640		result = TRUE;
641	    }
642	}
643    } else {
644	result = TRUE;
645	*combined -= 1;
646    }
647    return result;
648}
649#endif
650
651/*
652 * Print the name (tag) or text from a DIALOG_LISTITEM, highlighting the
653 * first character if selected.
654 */
655void
656dlg_print_listitem(WINDOW *win,
657		   const char *text,
658		   int climit,
659		   bool first,
660		   int selected)
661{
662    chtype attr = A_NORMAL;
663    int limit;
664    const int *cols;
665    chtype attrs[4];
666
667    if (text == 0)
668	text = "";
669
670    if (first) {
671	const int *indx = dlg_index_wchars(text);
672	attrs[3] = tag_key_selected_attr;
673	attrs[2] = tag_key_attr;
674	attrs[1] = tag_selected_attr;
675	attrs[0] = tag_attr;
676
677	(void) wattrset(win, selected ? attrs[3] : attrs[2]);
678	(void) waddnstr(win, text, indx[1]);
679
680	if ((int) strlen(text) > indx[1]) {
681	    limit = dlg_limit_columns(text, climit, 1);
682	    if (limit > 1) {
683		(void) wattrset(win, selected ? attrs[1] : attrs[0]);
684		(void) waddnstr(win,
685				text + indx[1],
686				indx[limit] - indx[1]);
687	    }
688	}
689    } else {
690	attrs[1] = item_selected_attr;
691	attrs[0] = item_attr;
692
693	cols = dlg_index_columns(text);
694	limit = dlg_limit_columns(text, climit, 0);
695
696	if (limit > 0) {
697	    (void) wattrset(win, selected ? attrs[1] : attrs[0]);
698	    dlg_print_text(win, text, cols[limit], &attr);
699	}
700    }
701}
702
703/*
704 * Print up to 'cols' columns from 'text', optionally rendering our escape
705 * sequence for attributes and color.
706 */
707void
708dlg_print_text(WINDOW *win, const char *txt, int cols, chtype *attr)
709{
710    int y_origin, x_origin;
711    int y_before, x_before = 0;
712    int y_after, x_after;
713    int tabbed = 0;
714    bool thisTab;
715    bool ended = FALSE;
716    chtype useattr;
717#ifdef USE_WIDE_CURSES
718    int combined = 0;
719#endif
720
721    getyx(win, y_origin, x_origin);
722    while (cols > 0 && (*txt != '\0')) {
723	if (dialog_vars.colors) {
724	    while (isOurEscape(txt)) {
725		int code;
726
727		txt += 2;
728		switch (code = CharOf(*txt)) {
729#ifdef HAVE_COLOR
730		case '0':
731		case '1':
732		case '2':
733		case '3':
734		case '4':
735		case '5':
736		case '6':
737		case '7':
738		    *attr &= ~A_COLOR;
739		    *attr |= define_color(win, code - '0');
740		    break;
741#endif
742		case 'B':
743		    *attr &= ~A_BOLD;
744		    break;
745		case 'b':
746		    *attr |= A_BOLD;
747		    break;
748		case 'R':
749		    *attr &= ~A_REVERSE;
750		    break;
751		case 'r':
752		    *attr |= A_REVERSE;
753		    break;
754		case 'U':
755		    *attr &= ~A_UNDERLINE;
756		    break;
757		case 'u':
758		    *attr |= A_UNDERLINE;
759		    break;
760		case 'n':
761		    *attr = A_NORMAL;
762		    break;
763		}
764		++txt;
765	    }
766	}
767	if (ended || *txt == '\n' || *txt == '\0')
768	    break;
769	useattr = (*attr) & A_ATTRIBUTES;
770#ifdef HAVE_COLOR
771	/*
772	 * Prevent this from making text invisible when the foreground and
773	 * background colors happen to be the same, and there's no bold
774	 * attribute.
775	 */
776	if ((useattr & A_COLOR) != 0 && (useattr & A_BOLD) == 0) {
777	    short pair = (short) PAIR_NUMBER(useattr);
778	    short fg, bg;
779	    if (pair_content(pair, &fg, &bg) != ERR
780		&& fg == bg) {
781		useattr &= ~A_COLOR;
782		useattr |= dlg_color_pair(fg, ((bg == COLOR_BLACK)
783					       ? COLOR_WHITE
784					       : COLOR_BLACK));
785	    }
786	}
787#endif
788	/*
789	 * Write the character, using curses to tell exactly how wide it
790	 * is.  If it is a tab, discount that, since the caller thinks
791	 * tabs are nonprinting, and curses will expand tabs to one or
792	 * more blanks.
793	 */
794	thisTab = (CharOf(*txt) == TAB);
795	if (thisTab) {
796	    getyx(win, y_before, x_before);
797	    (void) y_before;
798	}
799	(void) waddch(win, CharOf(*txt++) | useattr);
800	getyx(win, y_after, x_after);
801	if (thisTab && (y_after == y_origin))
802	    tabbed += (x_after - x_before);
803	if ((y_after != y_origin) ||
804	    (x_after >= (cols + tabbed + x_origin)
805#ifdef USE_WIDE_CURSES
806	     && !is_combining(txt, &combined)
807#endif
808	    )) {
809	    ended = TRUE;
810	}
811    }
812}
813
814/*
815 * Print one line of the prompt in the window within the limits of the
816 * specified right margin.  The line will end on a word boundary and a pointer
817 * to the start of the next line is returned, or a NULL pointer if the end of
818 * *prompt is reached.
819 */
820const char *
821dlg_print_line(WINDOW *win,
822	       chtype *attr,
823	       const char *prompt,
824	       int lm, int rm, int *x)
825{
826    const char *wrap_ptr;
827    const char *test_ptr;
828    const char *hide_ptr = 0;
829    const int *cols = dlg_index_columns(prompt);
830    const int *indx = dlg_index_wchars(prompt);
831    int wrap_inx = 0;
832    int test_inx = 0;
833    int cur_x = lm;
834    int hidden = 0;
835    int limit = dlg_count_wchars(prompt);
836    int n;
837    int tabbed = 0;
838
839    *x = 1;
840
841    /*
842     * Set *test_ptr to the end of the line or the right margin (rm), whichever
843     * is less, and set wrap_ptr to the end of the last word in the line.
844     */
845    for (n = 0; n < limit; ++n) {
846	test_ptr = prompt + indx[test_inx];
847	if (*test_ptr == '\n' || *test_ptr == '\0' || cur_x >= (rm + hidden))
848	    break;
849	if (*test_ptr == TAB && n == 0) {
850	    tabbed = 8;		/* workaround for leading tabs */
851	} else if (*test_ptr == ' ' && n != 0 && prompt[indx[n - 1]] != ' ') {
852	    wrap_inx = n;
853	    *x = cur_x;
854	} else if (dialog_vars.colors && isOurEscape(test_ptr)) {
855	    hide_ptr = test_ptr;
856	    hidden += ESCAPE_LEN;
857	    n += (ESCAPE_LEN - 1);
858	}
859	cur_x = lm + tabbed + cols[n + 1];
860	if (cur_x > (rm + hidden))
861	    break;
862	test_inx = n + 1;
863    }
864
865    /*
866     * If the line doesn't reach the right margin in the middle of a word, then
867     * we don't have to wrap it at the end of the previous word.
868     */
869    test_ptr = prompt + indx[test_inx];
870    if (*test_ptr == '\n' || *test_ptr == ' ' || *test_ptr == '\0') {
871	wrap_inx = test_inx;
872	while (wrap_inx > 0 && prompt[indx[wrap_inx - 1]] == ' ') {
873	    wrap_inx--;
874	}
875	*x = lm + indx[wrap_inx];
876    } else if (*x == 1 && cur_x >= rm) {
877	/*
878	 * If the line has no spaces, then wrap it anyway at the right margin
879	 */
880	*x = rm;
881	wrap_inx = test_inx;
882    }
883    wrap_ptr = prompt + indx[wrap_inx];
884#ifdef USE_WIDE_CURSES
885    if (UCH(*wrap_ptr) >= 128) {
886	int combined = 0;
887	while (is_combining(wrap_ptr, &combined)) {
888	    ++wrap_ptr;
889	}
890    }
891#endif
892
893    /*
894     * If we found hidden text past the last point that we will display,
895     * discount that from the displayed length.
896     */
897    if ((hide_ptr != 0) && (hide_ptr >= wrap_ptr)) {
898	hidden -= ESCAPE_LEN;
899	test_ptr = wrap_ptr;
900	while (test_ptr < wrap_ptr) {
901	    if (dialog_vars.colors && isOurEscape(test_ptr)) {
902		hidden -= ESCAPE_LEN;
903		test_ptr += ESCAPE_LEN;
904	    } else {
905		++test_ptr;
906	    }
907	}
908    }
909
910    /*
911     * Print the line if we have a window pointer.  Otherwise this routine
912     * is just being called for sizing the window.
913     */
914    if (win) {
915	dlg_print_text(win, prompt, (cols[wrap_inx] - hidden), attr);
916    }
917
918    /* *x tells the calling function how long the line was */
919    if (*x == 1)
920	*x = rm;
921
922    *x -= hidden;
923
924    /* Find the start of the next line and return a pointer to it */
925    test_ptr = wrap_ptr;
926    while (*test_ptr == ' ')
927	test_ptr++;
928    if (*test_ptr == '\n')
929	test_ptr++;
930    dlg_finish_string(prompt);
931    return (test_ptr);
932}
933
934static void
935justify_text(WINDOW *win,
936	     const char *prompt,
937	     int limit_y,
938	     int limit_x,
939	     int *high, int *wide)
940{
941    chtype attr = A_NORMAL;
942    int x = (2 * MARGIN);
943    int y = MARGIN;
944    int max_x = 2;
945    int lm = (2 * MARGIN);	/* left margin (box-border plus a space) */
946    int rm = limit_x;		/* right margin */
947    int bm = limit_y;		/* bottom margin */
948    int last_y = 0, last_x = 0;
949
950    if (win) {
951	rm -= (2 * MARGIN);
952	bm -= (2 * MARGIN);
953    }
954    if (prompt == 0)
955	prompt = "";
956
957    if (win != 0)
958	getyx(win, last_y, last_x);
959    while (y <= bm && *prompt) {
960	x = lm;
961
962	if (*prompt == '\n') {
963	    while (*prompt == '\n' && y < bm) {
964		if (*(prompt + 1) != '\0') {
965		    ++y;
966		    if (win != 0)
967			(void) wmove(win, y, lm);
968		}
969		prompt++;
970	    }
971	} else if (win != 0)
972	    (void) wmove(win, y, lm);
973
974	if (*prompt) {
975	    prompt = dlg_print_line(win, &attr, prompt, lm, rm, &x);
976	    if (win != 0)
977		getyx(win, last_y, last_x);
978	}
979	if (*prompt) {
980	    ++y;
981	    if (win != 0)
982		(void) wmove(win, y, lm);
983	}
984	max_x = MAX(max_x, x);
985    }
986    /* Move back to the last position after drawing prompt, for msgbox. */
987    if (win != 0)
988	(void) wmove(win, last_y, last_x);
989
990    /* Set the final height and width for the calling function */
991    if (high != 0)
992	*high = y;
993    if (wide != 0)
994	*wide = max_x;
995}
996
997/*
998 * Print a string of text in a window, automatically wrap around to the next
999 * line if the string is too long to fit on one line.  Note that the string may
1000 * contain embedded newlines.
1001 */
1002void
1003dlg_print_autowrap(WINDOW *win, const char *prompt, int height, int width)
1004{
1005    justify_text(win, prompt,
1006		 height,
1007		 width,
1008		 (int *) 0, (int *) 0);
1009}
1010
1011/*
1012 * Display the message in a scrollable window.  Actually the way it works is
1013 * that we create a "tall" window of the proper width, let the text wrap within
1014 * that, and copy a slice of the result to the dialog.
1015 *
1016 * It works for ncurses.  Other curses implementations show only blanks (Tru64)
1017 * or garbage (NetBSD).
1018 */
1019int
1020dlg_print_scrolled(WINDOW *win,
1021		   const char *prompt,
1022		   int offset,
1023		   int height,
1024		   int width,
1025		   int pauseopt)
1026{
1027    int oldy, oldx;
1028    int last = 0;
1029
1030    (void) pauseopt;		/* used only for ncurses */
1031
1032    getyx(win, oldy, oldx);
1033#ifdef NCURSES_VERSION
1034    if (pauseopt) {
1035	int wide = width - (2 * MARGIN);
1036	int high = LINES;
1037	int y, x;
1038	int len;
1039	int percent;
1040	WINDOW *dummy;
1041	char buffer[5];
1042
1043#if defined(NCURSES_VERSION_PATCH) && NCURSES_VERSION_PATCH >= 20040417
1044	/*
1045	 * If we're not limited by the screensize, allow text to possibly be
1046	 * one character per line.
1047	 */
1048	if ((len = dlg_count_columns(prompt)) > high)
1049	    high = len;
1050#endif
1051	dummy = newwin(high, width, 0, 0);
1052	if (dummy == 0) {
1053	    (void) wattrset(win, dialog_attr);
1054	    dlg_print_autowrap(win, prompt, height + 1 + (3 * MARGIN), width);
1055	    last = 0;
1056	} else {
1057	    wbkgdset(dummy, dialog_attr | ' ');
1058	    (void) wattrset(dummy, dialog_attr);
1059	    werase(dummy);
1060	    dlg_print_autowrap(dummy, prompt, high, width);
1061	    getyx(dummy, y, x);
1062	    (void) x;
1063
1064	    copywin(dummy,	/* srcwin */
1065		    win,	/* dstwin */
1066		    offset + MARGIN,	/* sminrow */
1067		    MARGIN,	/* smincol */
1068		    MARGIN,	/* dminrow */
1069		    MARGIN,	/* dmincol */
1070		    height,	/* dmaxrow */
1071		    wide,	/* dmaxcol */
1072		    FALSE);
1073
1074	    delwin(dummy);
1075
1076	    /* if the text is incomplete, or we have scrolled, show the percentage */
1077	    if (y > 0 && wide > 4) {
1078		percent = (int) ((height + offset) * 100.0 / y);
1079		if (percent < 0)
1080		    percent = 0;
1081		if (percent > 100)
1082		    percent = 100;
1083		if (offset != 0 || percent != 100) {
1084		    (void) wattrset(win, position_indicator_attr);
1085		    (void) wmove(win, MARGIN + height, wide - 4);
1086		    (void) sprintf(buffer, "%d%%", percent);
1087		    (void) waddstr(win, buffer);
1088		    if ((len = (int) strlen(buffer)) < 4) {
1089			(void) wattrset(win, border_attr);
1090			whline(win, dlg_boxchar(ACS_HLINE), 4 - len);
1091		    }
1092		}
1093	    }
1094	    last = (y - height);
1095	}
1096    } else
1097#endif
1098    {
1099	(void) offset;
1100	(void) wattrset(win, dialog_attr);
1101	dlg_print_autowrap(win, prompt, height + 1 + (3 * MARGIN), width);
1102	last = 0;
1103    }
1104    wmove(win, oldy, oldx);
1105    return last;
1106}
1107
1108int
1109dlg_check_scrolled(int key, int last, int page, bool * show, int *offset)
1110{
1111    int code = 0;
1112
1113    *show = FALSE;
1114
1115    switch (key) {
1116    case DLGK_PAGE_FIRST:
1117	if (*offset > 0) {
1118	    *offset = 0;
1119	    *show = TRUE;
1120	}
1121	break;
1122    case DLGK_PAGE_LAST:
1123	if (*offset < last) {
1124	    *offset = last;
1125	    *show = TRUE;
1126	}
1127	break;
1128    case DLGK_GRID_UP:
1129	if (*offset > 0) {
1130	    --(*offset);
1131	    *show = TRUE;
1132	}
1133	break;
1134    case DLGK_GRID_DOWN:
1135	if (*offset < last) {
1136	    ++(*offset);
1137	    *show = TRUE;
1138	}
1139	break;
1140    case DLGK_PAGE_PREV:
1141	if (*offset > 0) {
1142	    *offset -= page;
1143	    if (*offset < 0)
1144		*offset = 0;
1145	    *show = TRUE;
1146	}
1147	break;
1148    case DLGK_PAGE_NEXT:
1149	if (*offset < last) {
1150	    *offset += page;
1151	    if (*offset > last)
1152		*offset = last;
1153	    *show = TRUE;
1154	}
1155	break;
1156    default:
1157	code = -1;
1158	break;
1159    }
1160    return code;
1161}
1162
1163/*
1164 * Calculate the window size for preformatted text.  This will calculate box
1165 * dimensions that are at or close to the specified aspect ratio for the prompt
1166 * string with all spaces and newlines preserved and additional newlines added
1167 * as necessary.
1168 */
1169static void
1170auto_size_preformatted(const char *prompt, int *height, int *width)
1171{
1172    int high = 0, wide = 0;
1173    float car;			/* Calculated Aspect Ratio */
1174    float diff;
1175    int max_y = SLINES - 1;
1176    int max_x = SCOLS - 2;
1177    int max_width = max_x;
1178    int ar = dialog_state.aspect_ratio;
1179
1180    /* Get the initial dimensions */
1181    justify_text((WINDOW *) 0, prompt, max_y, max_x, &high, &wide);
1182    car = (float) (wide / high);
1183
1184    /*
1185     * If the aspect ratio is greater than it should be, then decrease the
1186     * width proportionately.
1187     */
1188    if (car > ar) {
1189	diff = car / (float) ar;
1190	max_x = (int) ((float) wide / diff + 4);
1191	justify_text((WINDOW *) 0, prompt, max_y, max_x, &high, &wide);
1192	car = (float) wide / (float) high;
1193    }
1194
1195    /*
1196     * If the aspect ratio is too small after decreasing the width, then
1197     * incrementally increase the width until the aspect ratio is equal to or
1198     * greater than the specified aspect ratio.
1199     */
1200    while (car < ar && max_x < max_width) {
1201	max_x += 4;
1202	justify_text((WINDOW *) 0, prompt, max_y, max_x, &high, &wide);
1203	car = (float) (wide / high);
1204    }
1205
1206    *height = high;
1207    *width = wide;
1208}
1209
1210/*
1211 * Find the length of the longest "word" in the given string.  By setting the
1212 * widget width at least this long, we can avoid splitting a word on the
1213 * margin.
1214 */
1215static int
1216longest_word(const char *string)
1217{
1218    int length, result = 0;
1219
1220    while (*string != '\0') {
1221	length = 0;
1222	while (*string != '\0' && !isspace(UCH(*string))) {
1223	    length++;
1224	    string++;
1225	}
1226	result = MAX(result, length);
1227	if (*string != '\0')
1228	    string++;
1229    }
1230    return result;
1231}
1232
1233/*
1234 * if (height or width == -1) Maximize()
1235 * if (height or width == 0), justify and return actual limits.
1236 */
1237static void
1238real_auto_size(const char *title,
1239	       const char *prompt,
1240	       int *height, int *width,
1241	       int boxlines, int mincols)
1242{
1243    int x = (dialog_vars.begin_set ? dialog_vars.begin_x : 2);
1244    int y = (dialog_vars.begin_set ? dialog_vars.begin_y : 1);
1245    int title_length = title ? dlg_count_columns(title) : 0;
1246    int nc = 4;
1247    int high;
1248    int wide;
1249    int save_high = *height;
1250    int save_wide = *width;
1251
1252    if (prompt == 0) {
1253	if (*height == 0)
1254	    *height = -1;
1255	if (*width == 0)
1256	    *width = -1;
1257    }
1258
1259    if (*height > 0) {
1260	high = *height;
1261    } else {
1262	high = SLINES - y;
1263    }
1264
1265    if (*width <= 0) {
1266	if (prompt != 0) {
1267	    wide = MAX(title_length, mincols);
1268	    if (strchr(prompt, '\n') == 0) {
1269		double val = (dialog_state.aspect_ratio *
1270			      dlg_count_real_columns(prompt));
1271		double xxx = sqrt(val);
1272		int tmp = (int) xxx;
1273		wide = MAX(wide, tmp);
1274		wide = MAX(wide, longest_word(prompt));
1275		justify_text((WINDOW *) 0, prompt, high, wide, height, width);
1276	    } else {
1277		auto_size_preformatted(prompt, height, width);
1278	    }
1279	} else {
1280	    wide = SCOLS - x;
1281	    justify_text((WINDOW *) 0, prompt, high, wide, height, width);
1282	}
1283    }
1284
1285    if (*width < title_length) {
1286	justify_text((WINDOW *) 0, prompt, high, title_length, height, width);
1287	*width = title_length;
1288    }
1289
1290    if (*width < mincols && save_wide == 0)
1291	*width = mincols;
1292    if (prompt != 0) {
1293	*width += nc;
1294	*height += boxlines + 2;
1295    }
1296    if (save_high > 0)
1297	*height = save_high;
1298    if (save_wide > 0)
1299	*width = save_wide;
1300}
1301
1302/* End of real_auto_size() */
1303
1304void
1305dlg_auto_size(const char *title,
1306	      const char *prompt,
1307	      int *height,
1308	      int *width,
1309	      int boxlines,
1310	      int mincols)
1311{
1312    real_auto_size(title, prompt, height, width, boxlines, mincols);
1313
1314    if (*width > SCOLS) {
1315	(*height)++;
1316	*width = SCOLS;
1317    }
1318
1319    if (*height > SLINES)
1320	*height = SLINES;
1321}
1322
1323/*
1324 * if (height or width == -1) Maximize()
1325 * if (height or width == 0)
1326 *    height=MIN(SLINES, num.lines in fd+n);
1327 *    width=MIN(SCOLS, MAX(longer line+n, mincols));
1328 */
1329void
1330dlg_auto_sizefile(const char *title,
1331		  const char *file,
1332		  int *height,
1333		  int *width,
1334		  int boxlines,
1335		  int mincols)
1336{
1337    int count = 0;
1338    int len = title ? dlg_count_columns(title) : 0;
1339    int nc = 4;
1340    int numlines = 2;
1341    long offset;
1342    int ch;
1343    FILE *fd;
1344
1345    /* Open input file for reading */
1346    if ((fd = fopen(file, "rb")) == NULL)
1347	dlg_exiterr("dlg_auto_sizefile: Cannot open input file %s", file);
1348
1349    if ((*height == -1) || (*width == -1)) {
1350	*height = SLINES - (dialog_vars.begin_set ? dialog_vars.begin_y : 0);
1351	*width = SCOLS - (dialog_vars.begin_set ? dialog_vars.begin_x : 0);
1352    }
1353    if ((*height != 0) && (*width != 0)) {
1354	(void) fclose(fd);
1355	if (*width > SCOLS)
1356	    *width = SCOLS;
1357	if (*height > SLINES)
1358	    *height = SLINES;
1359	return;
1360    }
1361
1362    while (!feof(fd)) {
1363	offset = 0;
1364	while (((ch = getc(fd)) != '\n') && !feof(fd))
1365	    if ((ch == TAB) && (dialog_vars.tab_correct))
1366		offset += dialog_state.tab_len - (offset % dialog_state.tab_len);
1367	    else
1368		offset++;
1369
1370	if (offset > len)
1371	    len = (int) offset;
1372
1373	count++;
1374    }
1375
1376    /* now 'count' has the number of lines of fd and 'len' the max length */
1377
1378    *height = MIN(SLINES, count + numlines + boxlines);
1379    *width = MIN(SCOLS, MAX((len + nc), mincols));
1380    /* here width and height can be maximized if > SCOLS|SLINES because
1381       textbox-like widgets don't put all <file> on the screen.
1382       Msgbox-like widget instead have to put all <text> correctly. */
1383
1384    (void) fclose(fd);
1385}
1386
1387static chtype
1388dlg_get_cell_attrs(WINDOW *win)
1389{
1390    chtype result;
1391#ifdef USE_WIDE_CURSES
1392    cchar_t wch;
1393    wchar_t cc;
1394    attr_t attrs;
1395    short pair;
1396    if (win_wch(win, &wch) == OK
1397	&& getcchar(&wch, &cc, &attrs, &pair, NULL) == OK) {
1398	result = attrs;
1399    } else {
1400	result = 0;
1401    }
1402#else
1403    result = winch(win) & (A_ATTRIBUTES & ~A_COLOR);
1404#endif
1405    return result;
1406}
1407
1408/*
1409 * Draw a rectangular box with line drawing characters.
1410 *
1411 * borderchar is used to color the upper/left edges.
1412 *
1413 * boxchar is used to color the right/lower edges.  It also is fill-color used
1414 * for the box contents.
1415 *
1416 * Normally, if you are drawing a scrollable box, use menubox_border_attr for
1417 * boxchar, and menubox_attr for borderchar since the scroll-arrows are drawn
1418 * with menubox_attr at the top, and menubox_border_attr at the bottom.  That
1419 * also (given the default color choices) produces a recessed effect.
1420 *
1421 * If you want a raised effect (and are not going to use the scroll-arrows),
1422 * reverse this choice.
1423 */
1424void
1425dlg_draw_box2(WINDOW *win, int y, int x, int height, int width,
1426	      chtype boxchar, chtype borderchar, chtype borderchar2)
1427{
1428    int i, j;
1429    chtype save = dlg_get_attrs(win);
1430
1431    (void) wattrset(win, 0);
1432    for (i = 0; i < height; i++) {
1433	(void) wmove(win, y + i, x);
1434	for (j = 0; j < width; j++)
1435	    if (!i && !j)
1436		(void) waddch(win, borderchar | dlg_boxchar(ACS_ULCORNER));
1437	    else if (i == height - 1 && !j)
1438		(void) waddch(win, borderchar | dlg_boxchar(ACS_LLCORNER));
1439	    else if (!i && j == width - 1)
1440		(void) waddch(win, borderchar2 | dlg_boxchar(ACS_URCORNER));
1441	    else if (i == height - 1 && j == width - 1)
1442		(void) waddch(win, borderchar2 | dlg_boxchar(ACS_LRCORNER));
1443	    else if (!i)
1444		(void) waddch(win, borderchar | dlg_boxchar(ACS_HLINE));
1445	    else if (i == height - 1)
1446		(void) waddch(win, borderchar2 | dlg_boxchar(ACS_HLINE));
1447	    else if (!j)
1448		(void) waddch(win, borderchar | dlg_boxchar(ACS_VLINE));
1449	    else if (j == width - 1)
1450		(void) waddch(win, borderchar2 | dlg_boxchar(ACS_VLINE));
1451	    else
1452		(void) waddch(win, boxchar | ' ');
1453    }
1454    (void) wattrset(win, save);
1455}
1456
1457void
1458dlg_draw_box(WINDOW *win, int y, int x, int height, int width,
1459	     chtype boxchar, chtype borderchar)
1460{
1461    dlg_draw_box2(win, y, x, height, width, boxchar, borderchar, boxchar);
1462}
1463
1464static DIALOG_WINDOWS *
1465find_window(WINDOW *win)
1466{
1467    DIALOG_WINDOWS *result = 0;
1468    DIALOG_WINDOWS *p;
1469
1470    for (p = dialog_state.all_windows; p != 0; p = p->next) {
1471	if (p->normal == win) {
1472	    result = p;
1473	    break;
1474	}
1475    }
1476    return result;
1477}
1478
1479#ifdef HAVE_COLOR
1480/*
1481 * If we have wchgat(), use that for updating shadow attributes, to work with
1482 * wide-character data.
1483 */
1484
1485/*
1486 * Check if the given point is "in" the given window.  If so, return the window
1487 * pointer, otherwise null.
1488 */
1489static WINDOW *
1490in_window(WINDOW *win, int y, int x)
1491{
1492    WINDOW *result = 0;
1493    int y_base = getbegy(win);
1494    int x_base = getbegx(win);
1495    int y_last = getmaxy(win) + y_base;
1496    int x_last = getmaxx(win) + x_base;
1497
1498    if (y >= y_base && y <= y_last && x >= x_base && x <= x_last)
1499	result = win;
1500    return result;
1501}
1502
1503static WINDOW *
1504window_at_cell(DIALOG_WINDOWS * dw, int y, int x)
1505{
1506    WINDOW *result = 0;
1507    DIALOG_WINDOWS *p;
1508    int y_want = y + getbegy(dw->shadow);
1509    int x_want = x + getbegx(dw->shadow);
1510
1511    for (p = dialog_state.all_windows; p != 0; p = p->next) {
1512	if (dw->normal != p->normal
1513	    && dw->shadow != p->normal
1514	    && (result = in_window(p->normal, y_want, x_want)) != 0) {
1515	    break;
1516	}
1517    }
1518    if (result == 0) {
1519	result = stdscr;
1520    }
1521    return result;
1522}
1523
1524static bool
1525in_shadow(WINDOW *normal, WINDOW *shadow, int y, int x)
1526{
1527    bool result = FALSE;
1528    int ybase = getbegy(normal);
1529    int ylast = getmaxy(normal) + ybase;
1530    int xbase = getbegx(normal);
1531    int xlast = getmaxx(normal) + xbase;
1532
1533    y += getbegy(shadow);
1534    x += getbegx(shadow);
1535
1536    if (y >= ybase + SHADOW_ROWS
1537	&& y < ylast + SHADOW_ROWS
1538	&& x >= xlast
1539	&& x < xlast + SHADOW_COLS) {
1540	/* in the right-side */
1541	result = TRUE;
1542    } else if (y >= ylast
1543	       && y < ylast + SHADOW_ROWS
1544	       && x >= ybase + SHADOW_COLS
1545	       && x < ylast + SHADOW_COLS) {
1546	/* check the bottom */
1547	result = TRUE;
1548    }
1549
1550    return result;
1551}
1552
1553/*
1554 * When erasing a shadow, check each cell to make sure that it is not part of
1555 * another box's shadow.  This is a little complicated since most shadows are
1556 * merged onto stdscr.
1557 */
1558static bool
1559last_shadow(DIALOG_WINDOWS * dw, int y, int x)
1560{
1561    DIALOG_WINDOWS *p;
1562    bool result = TRUE;
1563
1564    for (p = dialog_state.all_windows; p != 0; p = p->next) {
1565	if (p->normal != dw->normal
1566	    && in_shadow(p->normal, dw->shadow, y, x)) {
1567	    result = FALSE;
1568	    break;
1569	}
1570    }
1571    return result;
1572}
1573
1574static void
1575repaint_cell(DIALOG_WINDOWS * dw, bool draw, int y, int x)
1576{
1577    WINDOW *win = dw->shadow;
1578    WINDOW *cellwin;
1579    int y2, x2;
1580
1581    if ((cellwin = window_at_cell(dw, y, x)) != 0
1582	&& (draw || last_shadow(dw, y, x))
1583	&& (y2 = (y + getbegy(win) - getbegy(cellwin))) >= 0
1584	&& (x2 = (x + getbegx(win) - getbegx(cellwin))) >= 0
1585	&& wmove(cellwin, y2, x2) != ERR) {
1586	chtype the_cell = dlg_get_attrs(cellwin);
1587	chtype the_attr = (draw ? shadow_attr : the_cell);
1588
1589	if (dlg_get_cell_attrs(cellwin) & A_ALTCHARSET) {
1590	    the_attr |= A_ALTCHARSET;
1591	}
1592#if USE_WCHGAT
1593	wchgat(cellwin, 1,
1594	       the_attr & (chtype) (~A_COLOR),
1595	       (short) PAIR_NUMBER(the_attr),
1596	       NULL);
1597#else
1598	{
1599	    chtype the_char = ((winch(cellwin) & A_CHARTEXT) | the_attr);
1600	    (void) waddch(cellwin, the_char);
1601	}
1602#endif
1603	wnoutrefresh(cellwin);
1604    }
1605}
1606
1607#define RepaintCell(dw, draw, y, x) repaint_cell(dw, draw, y, x)
1608
1609static void
1610repaint_shadow(DIALOG_WINDOWS * dw, bool draw, int y, int x, int height, int width)
1611{
1612    int i, j;
1613
1614    if (UseShadow(dw)) {
1615#if !USE_WCHGAT
1616	chtype save = dlg_get_attrs(dw->shadow);
1617	(void) wattrset(dw->shadow, draw ? shadow_attr : screen_attr);
1618#endif
1619	for (i = 0; i < SHADOW_ROWS; ++i) {
1620	    for (j = 0; j < width; ++j) {
1621		RepaintCell(dw, draw, i + y + height, j + x + SHADOW_COLS);
1622	    }
1623	}
1624	for (i = 0; i < height; i++) {
1625	    for (j = 0; j < SHADOW_COLS; ++j) {
1626		RepaintCell(dw, draw, i + y + SHADOW_ROWS, j + x + width);
1627	    }
1628	}
1629	(void) wnoutrefresh(dw->shadow);
1630#if !USE_WCHGAT
1631	(void) wattrset(dw->shadow, save);
1632#endif
1633    }
1634}
1635
1636/*
1637 * Draw a shadow on the parent window corresponding to the right- and
1638 * bottom-edge of the child window, to give a 3-dimensional look.
1639 */
1640static void
1641draw_childs_shadow(DIALOG_WINDOWS * dw)
1642{
1643    if (UseShadow(dw)) {
1644	repaint_shadow(dw,
1645		       TRUE,
1646		       getbegy(dw->normal) - getbegy(dw->shadow),
1647		       getbegx(dw->normal) - getbegx(dw->shadow),
1648		       getmaxy(dw->normal),
1649		       getmaxx(dw->normal));
1650    }
1651}
1652
1653/*
1654 * Erase a shadow on the parent window corresponding to the right- and
1655 * bottom-edge of the child window.
1656 */
1657static void
1658erase_childs_shadow(DIALOG_WINDOWS * dw)
1659{
1660    if (UseShadow(dw)) {
1661	repaint_shadow(dw,
1662		       FALSE,
1663		       getbegy(dw->normal) - getbegy(dw->shadow),
1664		       getbegx(dw->normal) - getbegx(dw->shadow),
1665		       getmaxy(dw->normal),
1666		       getmaxx(dw->normal));
1667    }
1668}
1669
1670/*
1671 * Draw shadows along the right and bottom edge to give a more 3D look
1672 * to the boxes.
1673 */
1674void
1675dlg_draw_shadow(WINDOW *win, int y, int x, int height, int width)
1676{
1677    repaint_shadow(find_window(win), TRUE, y, x, height, width);
1678}
1679#endif /* HAVE_COLOR */
1680
1681/*
1682 * Allow shell scripts to remap the exit codes so they can distinguish ESC
1683 * from ERROR.
1684 */
1685void
1686dlg_exit(int code)
1687{
1688    /* *INDENT-OFF* */
1689    static const struct {
1690	int code;
1691	const char *name;
1692    } table[] = {
1693	{ DLG_EXIT_CANCEL, 	"DIALOG_CANCEL" },
1694	{ DLG_EXIT_ERROR,  	"DIALOG_ERROR" },
1695	{ DLG_EXIT_ESC,	   	"DIALOG_ESC" },
1696	{ DLG_EXIT_EXTRA,  	"DIALOG_EXTRA" },
1697	{ DLG_EXIT_HELP,   	"DIALOG_HELP" },
1698	{ DLG_EXIT_OK,	   	"DIALOG_OK" },
1699	{ DLG_EXIT_ITEM_HELP,	"DIALOG_ITEM_HELP" },
1700    };
1701    /* *INDENT-ON* */
1702
1703    unsigned n;
1704    char *name;
1705    char *temp;
1706    long value;
1707    bool overridden = FALSE;
1708
1709  retry:
1710    for (n = 0; n < sizeof(table) / sizeof(table[0]); n++) {
1711	if (table[n].code == code) {
1712	    if ((name = getenv(table[n].name)) != 0) {
1713		value = strtol(name, &temp, 0);
1714		if (temp != 0 && temp != name && *temp == '\0') {
1715		    code = (int) value;
1716		    overridden = TRUE;
1717		}
1718	    }
1719	    break;
1720	}
1721    }
1722
1723    /*
1724     * Prior to 2004/12/19, a widget using --item-help would exit with "OK"
1725     * if the help button were selected.  Now we want to exit with "HELP",
1726     * but allow the environment variable to override.
1727     */
1728    if (code == DLG_EXIT_ITEM_HELP && !overridden) {
1729	code = DLG_EXIT_HELP;
1730	goto retry;
1731    }
1732#ifdef HAVE_DLG_TRACE
1733    dlg_trace((const char *) 0);	/* close it */
1734#endif
1735
1736#ifdef NO_LEAKS
1737    _dlg_inputstr_leaks();
1738#if defined(NCURSES_VERSION) && defined(HAVE__NC_FREE_AND_EXIT)
1739    _nc_free_and_exit(code);
1740#endif
1741#endif
1742
1743    if (dialog_state.input == stdin) {
1744	exit(code);
1745    } else {
1746	/*
1747	 * Just in case of using --input-fd option, do not
1748	 * call atexit functions of ncurses which may hang.
1749	 */
1750	if (dialog_state.input) {
1751	    fclose(dialog_state.input);
1752	    dialog_state.input = 0;
1753	}
1754	if (dialog_state.pipe_input) {
1755	    if (dialog_state.pipe_input != stdin) {
1756		fclose(dialog_state.pipe_input);
1757		dialog_state.pipe_input = 0;
1758	    }
1759	}
1760	_exit(code);
1761    }
1762}
1763
1764/* quit program killing all tailbg */
1765void
1766dlg_exiterr(const char *fmt,...)
1767{
1768    int retval;
1769    va_list ap;
1770
1771    end_dialog();
1772
1773    (void) fputc('\n', stderr);
1774    va_start(ap, fmt);
1775    (void) vfprintf(stderr, fmt, ap);
1776    va_end(ap);
1777    (void) fputc('\n', stderr);
1778
1779    dlg_killall_bg(&retval);
1780
1781    (void) fflush(stderr);
1782    (void) fflush(stdout);
1783    dlg_exit(DLG_EXIT_ERROR);
1784}
1785
1786void
1787dlg_beeping(void)
1788{
1789    if (dialog_vars.beep_signal) {
1790	(void) beep();
1791	dialog_vars.beep_signal = 0;
1792    }
1793}
1794
1795void
1796dlg_print_size(int height, int width)
1797{
1798    if (dialog_vars.print_siz)
1799	fprintf(dialog_state.output, "Size: %d, %d\n", height, width);
1800}
1801
1802void
1803dlg_ctl_size(int height, int width)
1804{
1805    if (dialog_vars.size_err) {
1806	if ((width > COLS) || (height > LINES)) {
1807	    dlg_exiterr("Window too big. (height, width) = (%d, %d). Max allowed (%d, %d).",
1808			height, width, LINES, COLS);
1809	}
1810#ifdef HAVE_COLOR
1811	else if ((dialog_state.use_shadow)
1812		 && ((width > SCOLS || height > SLINES))) {
1813	    if ((width <= COLS) && (height <= LINES)) {
1814		/* try again, without shadows */
1815		dialog_state.use_shadow = 0;
1816	    } else {
1817		dlg_exiterr("Window+Shadow too big. (height, width) = (%d, %d). Max allowed (%d, %d).",
1818			    height, width, SLINES, SCOLS);
1819	    }
1820	}
1821#endif
1822    }
1823}
1824
1825/*
1826 * If the --tab-correct was not selected, convert tabs to single spaces.
1827 */
1828void
1829dlg_tab_correct_str(char *prompt)
1830{
1831    char *ptr;
1832
1833    if (dialog_vars.tab_correct) {
1834	while ((ptr = strchr(prompt, TAB)) != NULL) {
1835	    *ptr = ' ';
1836	    prompt = ptr;
1837	}
1838    }
1839}
1840
1841void
1842dlg_calc_listh(int *height, int *list_height, int item_no)
1843{
1844    /* calculate new height and list_height */
1845    int rows = SLINES - (dialog_vars.begin_set ? dialog_vars.begin_y : 0);
1846    if (rows - (*height) > 0) {
1847	if (rows - (*height) > item_no)
1848	    *list_height = item_no;
1849	else
1850	    *list_height = rows - (*height);
1851    }
1852    (*height) += (*list_height);
1853}
1854
1855/* obsolete */
1856int
1857dlg_calc_listw(int item_no, char **items, int group)
1858{
1859    int n, i, len1 = 0, len2 = 0;
1860    for (i = 0; i < (item_no * group); i += group) {
1861	if ((n = dlg_count_columns(items[i])) > len1)
1862	    len1 = n;
1863	if ((n = dlg_count_columns(items[i + 1])) > len2)
1864	    len2 = n;
1865    }
1866    return len1 + len2;
1867}
1868
1869int
1870dlg_calc_list_width(int item_no, DIALOG_LISTITEM * items)
1871{
1872    int n, i, len1 = 0, len2 = 0;
1873    int bits = ((dialog_vars.no_tags ? 1 : 0)
1874		+ (dialog_vars.no_items ? 2 : 0));
1875
1876    for (i = 0; i < item_no; ++i) {
1877	switch (bits) {
1878	case 0:
1879	    /* FALLTHRU */
1880	case 1:
1881	    if ((n = dlg_count_columns(items[i].name)) > len1)
1882		len1 = n;
1883	    if ((n = dlg_count_columns(items[i].text)) > len2)
1884		len2 = n;
1885	    break;
1886	case 2:
1887	    /* FALLTHRU */
1888	case 3:
1889	    if ((n = dlg_count_columns(items[i].name)) > len1)
1890		len1 = n;
1891	    break;
1892	}
1893    }
1894    return len1 + len2;
1895}
1896
1897char *
1898dlg_strempty(void)
1899{
1900    static char empty[] = "";
1901    return empty;
1902}
1903
1904char *
1905dlg_strclone(const char *cprompt)
1906{
1907    char *prompt = dlg_malloc(char, strlen(cprompt) + 1);
1908    assert_ptr(prompt, "dlg_strclone");
1909    strcpy(prompt, cprompt);
1910    return prompt;
1911}
1912
1913chtype
1914dlg_asciibox(chtype ch)
1915{
1916    chtype result = 0;
1917
1918    if (ch == ACS_ULCORNER)
1919	result = '+';
1920    else if (ch == ACS_LLCORNER)
1921	result = '+';
1922    else if (ch == ACS_URCORNER)
1923	result = '+';
1924    else if (ch == ACS_LRCORNER)
1925	result = '+';
1926    else if (ch == ACS_HLINE)
1927	result = '-';
1928    else if (ch == ACS_VLINE)
1929	result = '|';
1930    else if (ch == ACS_LTEE)
1931	result = '+';
1932    else if (ch == ACS_RTEE)
1933	result = '+';
1934    else if (ch == ACS_UARROW)
1935	result = '^';
1936    else if (ch == ACS_DARROW)
1937	result = 'v';
1938
1939    return result;
1940}
1941
1942chtype
1943dlg_boxchar(chtype ch)
1944{
1945    chtype result = dlg_asciibox(ch);
1946
1947    if (result != 0) {
1948	if (dialog_vars.ascii_lines)
1949	    ch = result;
1950	else if (dialog_vars.no_lines)
1951	    ch = ' ';
1952    }
1953    return ch;
1954}
1955
1956int
1957dlg_box_x_ordinate(int width)
1958{
1959    int x;
1960
1961    if (dialog_vars.begin_set == 1) {
1962	x = dialog_vars.begin_x;
1963    } else {
1964	/* center dialog box on screen unless --begin-set */
1965	x = (SCOLS - width) / 2;
1966    }
1967    return x;
1968}
1969
1970int
1971dlg_box_y_ordinate(int height)
1972{
1973    int y;
1974
1975    if (dialog_vars.begin_set == 1) {
1976	y = dialog_vars.begin_y;
1977    } else {
1978	/* center dialog box on screen unless --begin-set */
1979	y = (SLINES - height) / 2;
1980    }
1981    return y;
1982}
1983
1984void
1985dlg_draw_title(WINDOW *win, const char *title)
1986{
1987    if (title != NULL) {
1988	chtype attr = A_NORMAL;
1989	chtype save = dlg_get_attrs(win);
1990	int x = centered(getmaxx(win), title);
1991
1992	(void) wattrset(win, title_attr);
1993	wmove(win, 0, x);
1994	dlg_print_text(win, title, getmaxx(win) - x, &attr);
1995	(void) wattrset(win, save);
1996	dlg_finish_string(title);
1997    }
1998}
1999
2000void
2001dlg_draw_bottom_box2(WINDOW *win, chtype on_left, chtype on_right, chtype on_inside)
2002{
2003    int width = getmaxx(win);
2004    int height = getmaxy(win);
2005    int i;
2006
2007    (void) wattrset(win, on_left);
2008    (void) wmove(win, height - 3, 0);
2009    (void) waddch(win, dlg_boxchar(ACS_LTEE));
2010    for (i = 0; i < width - 2; i++)
2011	(void) waddch(win, dlg_boxchar(ACS_HLINE));
2012    (void) wattrset(win, on_right);
2013    (void) waddch(win, dlg_boxchar(ACS_RTEE));
2014    (void) wattrset(win, on_inside);
2015    (void) wmove(win, height - 2, 1);
2016    for (i = 0; i < width - 2; i++)
2017	(void) waddch(win, ' ');
2018}
2019
2020void
2021dlg_draw_bottom_box(WINDOW *win)
2022{
2023    dlg_draw_bottom_box2(win, border_attr, dialog_attr, dialog_attr);
2024}
2025
2026/*
2027 * Remove a window, repainting everything else.  This would be simpler if we
2028 * used the panel library, but that is not _always_ available.
2029 */
2030void
2031dlg_del_window(WINDOW *win)
2032{
2033    DIALOG_WINDOWS *p, *q, *r;
2034
2035    /*
2036     * If --keep-window was set, do not delete/repaint the windows.
2037     */
2038    if (dialog_vars.keep_window)
2039	return;
2040
2041    /* Leave the main window untouched if there are no background windows.
2042     * We do this so the current window will not be cleared on exit, allowing
2043     * things like the infobox demo to run without flicker.
2044     */
2045    if (dialog_state.getc_callbacks != 0) {
2046	touchwin(stdscr);
2047	wnoutrefresh(stdscr);
2048    }
2049
2050    for (p = dialog_state.all_windows, q = r = 0; p != 0; r = p, p = p->next) {
2051	if (p->normal == win) {
2052	    q = p;		/* found a match - should be only one */
2053	    if (r == 0) {
2054		dialog_state.all_windows = p->next;
2055	    } else {
2056		r->next = p->next;
2057	    }
2058	} else {
2059	    if (p->shadow != 0) {
2060		touchwin(p->shadow);
2061		wnoutrefresh(p->shadow);
2062	    }
2063	    touchwin(p->normal);
2064	    wnoutrefresh(p->normal);
2065	}
2066    }
2067
2068    if (q) {
2069	if (dialog_state.all_windows != 0)
2070	    erase_childs_shadow(q);
2071	del_subwindows(q->normal);
2072	dlg_unregister_window(q->normal);
2073	delwin(q->normal);
2074	free(q);
2075    }
2076    doupdate();
2077}
2078
2079/*
2080 * Create a window, optionally with a shadow.
2081 */
2082WINDOW *
2083dlg_new_window(int height, int width, int y, int x)
2084{
2085    return dlg_new_modal_window(stdscr, height, width, y, x);
2086}
2087
2088/*
2089 * "Modal" windows differ from normal ones by having a shadow in a window
2090 * separate from the standard screen.
2091 */
2092WINDOW *
2093dlg_new_modal_window(WINDOW *parent, int height, int width, int y, int x)
2094{
2095    WINDOW *win;
2096    DIALOG_WINDOWS *p = dlg_calloc(DIALOG_WINDOWS, 1);
2097
2098    (void) parent;
2099    if (p == 0
2100	|| (win = newwin(height, width, y, x)) == 0) {
2101	dlg_exiterr("Can't make new window at (%d,%d), size (%d,%d).\n",
2102		    y, x, height, width);
2103    }
2104    p->next = dialog_state.all_windows;
2105    p->normal = win;
2106    dialog_state.all_windows = p;
2107#ifdef HAVE_COLOR
2108    if (dialog_state.use_shadow) {
2109	p->shadow = parent;
2110	draw_childs_shadow(p);
2111    }
2112#endif
2113
2114    (void) keypad(win, TRUE);
2115    return win;
2116}
2117
2118/*
2119 * Move/Resize a window, optionally with a shadow.
2120 */
2121#ifdef KEY_RESIZE
2122void
2123dlg_move_window(WINDOW *win, int height, int width, int y, int x)
2124{
2125    DIALOG_WINDOWS *p;
2126
2127    if (win != 0) {
2128	dlg_ctl_size(height, width);
2129
2130	if ((p = find_window(win)) != 0) {
2131	    (void) wresize(win, height, width);
2132	    (void) mvwin(win, y, x);
2133#ifdef HAVE_COLOR
2134	    if (p->shadow != 0) {
2135		if (dialog_state.use_shadow) {
2136		    (void) mvwin(p->shadow, y + SHADOW_ROWS, x + SHADOW_COLS);
2137		} else {
2138		    p->shadow = 0;
2139		}
2140	    }
2141#endif
2142	    (void) refresh();
2143
2144#ifdef HAVE_COLOR
2145	    draw_childs_shadow(p);
2146#endif
2147	}
2148    }
2149}
2150#endif /* KEY_RESIZE */
2151
2152WINDOW *
2153dlg_sub_window(WINDOW *parent, int height, int width, int y, int x)
2154{
2155    WINDOW *win;
2156
2157    if ((win = subwin(parent, height, width, y, x)) == 0) {
2158	dlg_exiterr("Can't make sub-window at (%d,%d), size (%d,%d).\n",
2159		    y, x, height, width);
2160    }
2161
2162    add_subwindow(parent, win);
2163    (void) keypad(win, TRUE);
2164    return win;
2165}
2166
2167/* obsolete */
2168int
2169dlg_default_item(char **items, int llen)
2170{
2171    int result = 0;
2172
2173    if (dialog_vars.default_item != 0) {
2174	int count = 0;
2175	while (*items != 0) {
2176	    if (!strcmp(dialog_vars.default_item, *items)) {
2177		result = count;
2178		break;
2179	    }
2180	    items += llen;
2181	    count++;
2182	}
2183    }
2184    return result;
2185}
2186
2187int
2188dlg_default_listitem(DIALOG_LISTITEM * items)
2189{
2190    int result = 0;
2191
2192    if (dialog_vars.default_item != 0) {
2193	int count = 0;
2194	while (items->name != 0) {
2195	    if (!strcmp(dialog_vars.default_item, items->name)) {
2196		result = count;
2197		break;
2198	    }
2199	    ++items;
2200	    count++;
2201	}
2202    }
2203    return result;
2204}
2205
2206/*
2207 * Draw the string for item_help
2208 */
2209void
2210dlg_item_help(const char *txt)
2211{
2212    if (USE_ITEM_HELP(txt)) {
2213	chtype attr = A_NORMAL;
2214	int y, x;
2215
2216	(void) wattrset(stdscr, itemhelp_attr);
2217	(void) wmove(stdscr, LINES - 1, 0);
2218	(void) wclrtoeol(stdscr);
2219	(void) addch(' ');
2220	dlg_print_text(stdscr, txt, COLS - 1, &attr);
2221	if (itemhelp_attr & A_COLOR) {
2222	    /* fill the remainder of the line with the window's attributes */
2223	    getyx(stdscr, y, x);
2224	    (void) y;
2225	    while (x < COLS) {
2226		(void) addch(' ');
2227		++x;
2228	    }
2229	}
2230	(void) wnoutrefresh(stdscr);
2231    }
2232}
2233
2234#ifndef HAVE_STRCASECMP
2235int
2236dlg_strcmp(const char *a, const char *b)
2237{
2238    int ac, bc, cmp;
2239
2240    for (;;) {
2241	ac = UCH(*a++);
2242	bc = UCH(*b++);
2243	if (isalpha(ac) && islower(ac))
2244	    ac = _toupper(ac);
2245	if (isalpha(bc) && islower(bc))
2246	    bc = _toupper(bc);
2247	cmp = ac - bc;
2248	if (ac == 0 || bc == 0 || cmp != 0)
2249	    break;
2250    }
2251    return cmp;
2252}
2253#endif
2254
2255/*
2256 * Returns true if 'dst' points to a blank which follows another blank which
2257 * is not a leading blank on a line.
2258 */
2259static bool
2260trim_blank(char *base, char *dst)
2261{
2262    int count = 0;
2263
2264    while (dst-- != base) {
2265	if (*dst == '\n') {
2266	    return FALSE;
2267	} else if (*dst != ' ') {
2268	    return (count > 1);
2269	} else {
2270	    count++;
2271	}
2272    }
2273    return FALSE;
2274}
2275
2276/*
2277 * Change embedded "\n" substrings to '\n' characters and tabs to single
2278 * spaces.  If there are no "\n"s, it will strip all extra spaces, for
2279 * justification.  If it has "\n"'s, it will preserve extra spaces.  If cr_wrap
2280 * is set, it will preserve '\n's.
2281 */
2282void
2283dlg_trim_string(char *s)
2284{
2285    char *base = s;
2286    char *p1;
2287    char *p = s;
2288    int has_newlines = !dialog_vars.no_nl_expand && (strstr(s, "\\n") != 0);
2289
2290    while (*p != '\0') {
2291	if (*p == TAB && !dialog_vars.nocollapse)
2292	    *p = ' ';
2293
2294	if (has_newlines) {	/* If prompt contains "\n" strings */
2295	    if (*p == '\\' && *(p + 1) == 'n') {
2296		*s++ = '\n';
2297		p += 2;
2298		p1 = p;
2299		/*
2300		 * Handle end of lines intelligently.  If '\n' follows "\n"
2301		 * then ignore the '\n'.  This eliminates the need to escape
2302		 * the '\n' character (no need to use "\n\").
2303		 */
2304		while (*p1 == ' ')
2305		    p1++;
2306		if (*p1 == '\n')
2307		    p = p1 + 1;
2308	    } else if (*p == '\n') {
2309		if (dialog_vars.cr_wrap)
2310		    *s++ = *p++;
2311		else {
2312		    /* Replace the '\n' with a space if cr_wrap is not set */
2313		    if (!trim_blank(base, s))
2314			*s++ = ' ';
2315		    p++;
2316		}
2317	    } else		/* If *p != '\n' */
2318		*s++ = *p++;
2319	} else if (dialog_vars.trim_whitespace) {
2320	    if (*p == ' ') {
2321		if (*(s - 1) != ' ') {
2322		    *s++ = ' ';
2323		    p++;
2324		} else
2325		    p++;
2326	    } else if (*p == '\n') {
2327		if (dialog_vars.cr_wrap)
2328		    *s++ = *p++;
2329		else if (*(s - 1) != ' ') {
2330		    /* Strip '\n's if cr_wrap is not set. */
2331		    *s++ = ' ';
2332		    p++;
2333		} else
2334		    p++;
2335	    } else
2336		*s++ = *p++;
2337	} else {		/* If there are no "\n" strings */
2338	    if (*p == ' ' && !dialog_vars.nocollapse) {
2339		if (!trim_blank(base, s))
2340		    *s++ = *p;
2341		p++;
2342	    } else
2343		*s++ = *p++;
2344	}
2345    }
2346
2347    *s = '\0';
2348}
2349
2350void
2351dlg_set_focus(WINDOW *parent, WINDOW *win)
2352{
2353    if (win != 0) {
2354	(void) wmove(parent,
2355		     getpary(win) + getcury(win),
2356		     getparx(win) + getcurx(win));
2357	(void) wnoutrefresh(win);
2358	(void) doupdate();
2359    }
2360}
2361
2362/*
2363 * Returns the nominal maximum buffer size.
2364 */
2365int
2366dlg_max_input(int max_len)
2367{
2368    if (dialog_vars.max_input != 0 && dialog_vars.max_input < MAX_LEN)
2369	max_len = dialog_vars.max_input;
2370
2371    return max_len;
2372}
2373
2374/*
2375 * Free storage used for the result buffer.
2376 */
2377void
2378dlg_clr_result(void)
2379{
2380    if (dialog_vars.input_length) {
2381	dialog_vars.input_length = 0;
2382	if (dialog_vars.input_result)
2383	    free(dialog_vars.input_result);
2384    }
2385    dialog_vars.input_result = 0;
2386}
2387
2388/*
2389 * Setup a fixed-buffer for the result.
2390 */
2391char *
2392dlg_set_result(const char *string)
2393{
2394    unsigned need = string ? (unsigned) strlen(string) + 1 : 0;
2395
2396    /* inputstr.c needs a fixed buffer */
2397    if (need < MAX_LEN)
2398	need = MAX_LEN;
2399
2400    /*
2401     * If the buffer is not big enough, allocate a new one.
2402     */
2403    if (dialog_vars.input_length != 0
2404	|| dialog_vars.input_result == 0
2405	|| need > MAX_LEN) {
2406
2407	dlg_clr_result();
2408
2409	dialog_vars.input_length = need;
2410	dialog_vars.input_result = dlg_malloc(char, need);
2411	assert_ptr(dialog_vars.input_result, "dlg_set_result");
2412    }
2413
2414    strcpy(dialog_vars.input_result, string ? string : "");
2415
2416    return dialog_vars.input_result;
2417}
2418
2419/*
2420 * Accumulate results in dynamically allocated buffer.
2421 * If input_length is zero, it is a MAX_LEN buffer belonging to the caller.
2422 */
2423void
2424dlg_add_result(const char *string)
2425{
2426    unsigned have = (dialog_vars.input_result
2427		     ? (unsigned) strlen(dialog_vars.input_result)
2428		     : 0);
2429    unsigned want = (unsigned) strlen(string) + 1 + have;
2430
2431    if ((want >= MAX_LEN)
2432	|| (dialog_vars.input_length != 0)
2433	|| (dialog_vars.input_result == 0)) {
2434
2435	if (dialog_vars.input_length == 0
2436	    || dialog_vars.input_result == 0) {
2437
2438	    char *save_result = dialog_vars.input_result;
2439
2440	    dialog_vars.input_length = want * 2;
2441	    dialog_vars.input_result = dlg_malloc(char, dialog_vars.input_length);
2442	    assert_ptr(dialog_vars.input_result, "dlg_add_result malloc");
2443	    dialog_vars.input_result[0] = '\0';
2444	    if (save_result != 0)
2445		strcpy(dialog_vars.input_result, save_result);
2446	} else if (want >= dialog_vars.input_length) {
2447	    dialog_vars.input_length = want * 2;
2448	    dialog_vars.input_result = dlg_realloc(char,
2449						   dialog_vars.input_length,
2450						   dialog_vars.input_result);
2451	    assert_ptr(dialog_vars.input_result, "dlg_add_result realloc");
2452	}
2453    }
2454    strcat(dialog_vars.input_result, string);
2455}
2456
2457/*
2458 * These are characters that (aside from the quote-delimiter) will have to
2459 * be escaped in a single- or double-quoted string.
2460 */
2461#define FIX_SINGLE "\n\\"
2462#define FIX_DOUBLE FIX_SINGLE "[]{}?*;`~#$^&()|<>"
2463
2464/*
2465 * Returns the quote-delimiter.
2466 */
2467static const char *
2468quote_delimiter(void)
2469{
2470    return dialog_vars.single_quoted ? "'" : "\"";
2471}
2472
2473/*
2474 * Returns true if we should quote the given string.
2475 */
2476static bool
2477must_quote(char *string)
2478{
2479    bool code = FALSE;
2480
2481    if (*string != '\0') {
2482	size_t len = strlen(string);
2483	if (strcspn(string, quote_delimiter()) != len)
2484	    code = TRUE;
2485	else if (strcspn(string, "\n\t ") != len)
2486	    code = TRUE;
2487	else
2488	    code = (strcspn(string, FIX_DOUBLE) != len);
2489    } else {
2490	code = TRUE;
2491    }
2492
2493    return code;
2494}
2495
2496/*
2497 * Add a quoted string to the result buffer.
2498 */
2499void
2500dlg_add_quoted(char *string)
2501{
2502    char temp[2];
2503    const char *my_quote = quote_delimiter();
2504    const char *must_fix = (dialog_vars.single_quoted
2505			    ? FIX_SINGLE
2506			    : FIX_DOUBLE);
2507
2508    if (must_quote(string)) {
2509	temp[1] = '\0';
2510	dlg_add_result(my_quote);
2511	while (*string != '\0') {
2512	    temp[0] = *string++;
2513	    if (strchr(my_quote, *temp) || strchr(must_fix, *temp))
2514		dlg_add_result("\\");
2515	    dlg_add_result(temp);
2516	}
2517	dlg_add_result(my_quote);
2518    } else {
2519	dlg_add_result(string);
2520    }
2521}
2522
2523/*
2524 * When adding a result, make that depend on whether "--quoted" is used.
2525 */
2526void
2527dlg_add_string(char *string)
2528{
2529    if (dialog_vars.quoted) {
2530	dlg_add_quoted(string);
2531    } else {
2532	dlg_add_result(string);
2533    }
2534}
2535
2536bool
2537dlg_need_separator(void)
2538{
2539    bool result = FALSE;
2540
2541    if (dialog_vars.output_separator) {
2542	result = TRUE;
2543    } else if (dialog_vars.input_result && *(dialog_vars.input_result)) {
2544	result = TRUE;
2545    }
2546    return result;
2547}
2548
2549void
2550dlg_add_separator(void)
2551{
2552    const char *separator = (dialog_vars.separate_output) ? "\n" : " ";
2553
2554    if (dialog_vars.output_separator)
2555	separator = dialog_vars.output_separator;
2556
2557    dlg_add_result(separator);
2558}
2559
2560#define HELP_PREFIX		"HELP "
2561
2562void
2563dlg_add_help_listitem(int *result, char **tag, DIALOG_LISTITEM * item)
2564{
2565    dlg_add_result(HELP_PREFIX);
2566    if (USE_ITEM_HELP(item->help)) {
2567	*tag = dialog_vars.help_tags ? item->name : item->help;
2568	*result = DLG_EXIT_ITEM_HELP;
2569    } else {
2570	*tag = item->name;
2571    }
2572}
2573
2574void
2575dlg_add_help_formitem(int *result, char **tag, DIALOG_FORMITEM * item)
2576{
2577    dlg_add_result(HELP_PREFIX);
2578    if (USE_ITEM_HELP(item->help)) {
2579	*tag = dialog_vars.help_tags ? item->name : item->help;
2580	*result = DLG_EXIT_ITEM_HELP;
2581    } else {
2582	*tag = item->name;
2583    }
2584}
2585
2586/*
2587 * Some widgets support only one value of a given variable - save/restore the
2588 * global dialog_vars so we can override it consistently.
2589 */
2590void
2591dlg_save_vars(DIALOG_VARS * vars)
2592{
2593    *vars = dialog_vars;
2594}
2595
2596/*
2597 * Most of the data in DIALOG_VARS is normally set by command-line options.
2598 * The input_result member is an exception; it is normally set by the dialog
2599 * library to return result values.
2600 */
2601void
2602dlg_restore_vars(DIALOG_VARS * vars)
2603{
2604    char *save_result = dialog_vars.input_result;
2605    unsigned save_length = dialog_vars.input_length;
2606
2607    dialog_vars = *vars;
2608    dialog_vars.input_result = save_result;
2609    dialog_vars.input_length = save_length;
2610}
2611
2612/*
2613 * Called each time a widget is invoked which may do output, increment a count.
2614 */
2615void
2616dlg_does_output(void)
2617{
2618    dialog_state.output_count += 1;
2619}
2620
2621/*
2622 * Compatibility for different versions of curses.
2623 */
2624#if !(defined(HAVE_GETBEGX) && defined(HAVE_GETBEGY))
2625int
2626dlg_getbegx(WINDOW *win)
2627{
2628    int y, x;
2629    getbegyx(win, y, x);
2630    return x;
2631}
2632int
2633dlg_getbegy(WINDOW *win)
2634{
2635    int y, x;
2636    getbegyx(win, y, x);
2637    return y;
2638}
2639#endif
2640
2641#if !(defined(HAVE_GETCURX) && defined(HAVE_GETCURY))
2642int
2643dlg_getcurx(WINDOW *win)
2644{
2645    int y, x;
2646    getyx(win, y, x);
2647    return x;
2648}
2649int
2650dlg_getcury(WINDOW *win)
2651{
2652    int y, x;
2653    getyx(win, y, x);
2654    return y;
2655}
2656#endif
2657
2658#if !(defined(HAVE_GETMAXX) && defined(HAVE_GETMAXY))
2659int
2660dlg_getmaxx(WINDOW *win)
2661{
2662    int y, x;
2663    getmaxyx(win, y, x);
2664    return x;
2665}
2666int
2667dlg_getmaxy(WINDOW *win)
2668{
2669    int y, x;
2670    getmaxyx(win, y, x);
2671    return y;
2672}
2673#endif
2674
2675#if !(defined(HAVE_GETPARX) && defined(HAVE_GETPARY))
2676int
2677dlg_getparx(WINDOW *win)
2678{
2679    int y, x;
2680    getparyx(win, y, x);
2681    return x;
2682}
2683int
2684dlg_getpary(WINDOW *win)
2685{
2686    int y, x;
2687    getparyx(win, y, x);
2688    return y;
2689}
2690#endif
2691
2692#ifdef NEED_WGETPARENT
2693WINDOW *
2694dlg_wgetparent(WINDOW *win)
2695{
2696#undef wgetparent
2697    WINDOW *result = 0;
2698    DIALOG_WINDOWS *p;
2699
2700    for (p = dialog_state.all_subwindows; p != 0; p = p->next) {
2701	if (p->shadow == win) {
2702	    result = p->normal;
2703	    break;
2704	}
2705    }
2706    return result;
2707}
2708#endif
2709