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