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