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