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