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