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