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