mirror of
https://git.FreeBSD.org/src.git
synced 2024-12-27 11:55:06 +00:00
2593 lines
60 KiB
C
2593 lines
60 KiB
C
/*
|
|
* $Id: util.c,v 1.243 2012/06/30 12:58:04 tom Exp $
|
|
*
|
|
* util.c -- miscellaneous utilities for dialog
|
|
*
|
|
* Copyright 2000-2011,2012 Thomas E. Dickey
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License, version 2.1
|
|
* as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this program; if not, write to
|
|
* Free Software Foundation, Inc.
|
|
* 51 Franklin St., Fifth Floor
|
|
* Boston, MA 02110, USA.
|
|
*
|
|
* An earlier version of this program lists as authors
|
|
* Savio Lam (lam836@cs.cuhk.hk)
|
|
*/
|
|
|
|
#include <dialog.h>
|
|
#include <dlg_keys.h>
|
|
|
|
#ifdef HAVE_SETLOCALE
|
|
#include <locale.h>
|
|
#endif
|
|
|
|
#ifdef NEED_WCHAR_H
|
|
#include <wchar.h>
|
|
#endif
|
|
|
|
#ifdef NCURSES_VERSION
|
|
#if defined(HAVE_NCURSESW_TERM_H)
|
|
#include <ncursesw/term.h>
|
|
#elif defined(HAVE_NCURSES_TERM_H)
|
|
#include <ncurses/term.h>
|
|
#else
|
|
#include <term.h>
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(HAVE_WCHGAT)
|
|
# if defined(NCURSES_VERSION_PATCH)
|
|
# if NCURSES_VERSION_PATCH >= 20060715
|
|
# define USE_WCHGAT 1
|
|
# else
|
|
# define USE_WCHGAT 0
|
|
# endif
|
|
# else
|
|
# define USE_WCHGAT 1
|
|
# endif
|
|
#else
|
|
# define USE_WCHGAT 0
|
|
#endif
|
|
|
|
/* globals */
|
|
DIALOG_STATE dialog_state;
|
|
DIALOG_VARS dialog_vars;
|
|
|
|
#if !(defined(HAVE_WGETPARENT) && defined(HAVE_WINDOW__PARENT))
|
|
#define NEED_WGETPARENT 1
|
|
#else
|
|
#undef NEED_WGETPARENT
|
|
#endif
|
|
|
|
#define concat(a,b) a##b
|
|
|
|
#ifdef HAVE_RC_FILE
|
|
#define RC_DATA(name,comment) , #name "_color", comment " color"
|
|
#else
|
|
#define RC_DATA(name,comment) /*nothing */
|
|
#endif
|
|
|
|
#ifdef HAVE_COLOR
|
|
#include <dlg_colors.h>
|
|
#define COLOR_DATA(upr) , \
|
|
concat(DLGC_FG_,upr), \
|
|
concat(DLGC_BG_,upr), \
|
|
concat(DLGC_HL_,upr)
|
|
#else
|
|
#define COLOR_DATA(upr) /*nothing */
|
|
#endif
|
|
|
|
#define DATA(atr,upr,lwr,cmt) { atr COLOR_DATA(upr) RC_DATA(lwr,cmt) }
|
|
|
|
#define UseShadow(dw) ((dw) != 0 && (dw)->normal != 0 && (dw)->shadow != 0)
|
|
|
|
/*
|
|
* Table of color and attribute values, default is for mono display.
|
|
* The order matches the DIALOG_ATR() values.
|
|
*/
|
|
/* *INDENT-OFF* */
|
|
DIALOG_COLORS dlg_color_table[] =
|
|
{
|
|
DATA(A_NORMAL, SCREEN, screen, "Screen"),
|
|
DATA(A_NORMAL, SHADOW, shadow, "Shadow"),
|
|
DATA(A_REVERSE, DIALOG, dialog, "Dialog box"),
|
|
DATA(A_REVERSE, TITLE, title, "Dialog box title"),
|
|
DATA(A_REVERSE, BORDER, border, "Dialog box border"),
|
|
DATA(A_BOLD, BUTTON_ACTIVE, button_active, "Active button"),
|
|
DATA(A_DIM, BUTTON_INACTIVE, button_inactive, "Inactive button"),
|
|
DATA(A_UNDERLINE, BUTTON_KEY_ACTIVE, button_key_active, "Active button key"),
|
|
DATA(A_UNDERLINE, BUTTON_KEY_INACTIVE, button_key_inactive, "Inactive button key"),
|
|
DATA(A_NORMAL, BUTTON_LABEL_ACTIVE, button_label_active, "Active button label"),
|
|
DATA(A_NORMAL, BUTTON_LABEL_INACTIVE, button_label_inactive, "Inactive button label"),
|
|
DATA(A_REVERSE, INPUTBOX, inputbox, "Input box"),
|
|
DATA(A_REVERSE, INPUTBOX_BORDER, inputbox_border, "Input box border"),
|
|
DATA(A_REVERSE, SEARCHBOX, searchbox, "Search box"),
|
|
DATA(A_REVERSE, SEARCHBOX_TITLE, searchbox_title, "Search box title"),
|
|
DATA(A_REVERSE, SEARCHBOX_BORDER, searchbox_border, "Search box border"),
|
|
DATA(A_REVERSE, POSITION_INDICATOR, position_indicator, "File position indicator"),
|
|
DATA(A_REVERSE, MENUBOX, menubox, "Menu box"),
|
|
DATA(A_REVERSE, MENUBOX_BORDER, menubox_border, "Menu box border"),
|
|
DATA(A_REVERSE, ITEM, item, "Item"),
|
|
DATA(A_NORMAL, ITEM_SELECTED, item_selected, "Selected item"),
|
|
DATA(A_REVERSE, TAG, tag, "Tag"),
|
|
DATA(A_REVERSE, TAG_SELECTED, tag_selected, "Selected tag"),
|
|
DATA(A_NORMAL, TAG_KEY, tag_key, "Tag key"),
|
|
DATA(A_BOLD, TAG_KEY_SELECTED, tag_key_selected, "Selected tag key"),
|
|
DATA(A_REVERSE, CHECK, check, "Check box"),
|
|
DATA(A_REVERSE, CHECK_SELECTED, check_selected, "Selected check box"),
|
|
DATA(A_REVERSE, UARROW, uarrow, "Up arrow"),
|
|
DATA(A_REVERSE, DARROW, darrow, "Down arrow"),
|
|
DATA(A_NORMAL, ITEMHELP, itemhelp, "Item help-text"),
|
|
DATA(A_BOLD, FORM_ACTIVE_TEXT, form_active_text, "Active form text"),
|
|
DATA(A_REVERSE, FORM_TEXT, form_text, "Form text"),
|
|
DATA(A_NORMAL, FORM_ITEM_READONLY, form_item_readonly, "Readonly form item"),
|
|
DATA(A_REVERSE, GAUGE, gauge, "Dialog box gauge"),
|
|
DATA(A_REVERSE, BORDER2, border2, "Dialog box border2"),
|
|
DATA(A_REVERSE, INPUTBOX_BORDER2, inputbox_border2, "Input box border2"),
|
|
DATA(A_REVERSE, SEARCHBOX_BORDER2, searchbox_border2, "Search box border2"),
|
|
DATA(A_REVERSE, MENUBOX_BORDER2, menubox_border2, "Menu box border2")
|
|
};
|
|
/* *INDENT-ON* */
|
|
|
|
/*
|
|
* Maintain a list of subwindows so that we can delete them to cleanup.
|
|
* More important, this provides a fallback when wgetparent() is not available.
|
|
*/
|
|
static void
|
|
add_subwindow(WINDOW *parent, WINDOW *child)
|
|
{
|
|
DIALOG_WINDOWS *p = dlg_calloc(DIALOG_WINDOWS, 1);
|
|
|
|
if (p != 0) {
|
|
p->normal = parent;
|
|
p->shadow = child;
|
|
p->next = dialog_state.all_subwindows;
|
|
dialog_state.all_subwindows = p;
|
|
}
|
|
}
|
|
|
|
static void
|
|
del_subwindows(WINDOW *parent)
|
|
{
|
|
DIALOG_WINDOWS *p = dialog_state.all_subwindows;
|
|
DIALOG_WINDOWS *q = 0;
|
|
DIALOG_WINDOWS *r;
|
|
|
|
while (p != 0) {
|
|
if (p->normal == parent) {
|
|
delwin(p->shadow);
|
|
r = p->next;
|
|
if (q == 0) {
|
|
dialog_state.all_subwindows = r;
|
|
} else {
|
|
q->next = r;
|
|
}
|
|
free(p);
|
|
p = r;
|
|
} else {
|
|
q = p;
|
|
p = p->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Display background title if it exists ...
|
|
*/
|
|
void
|
|
dlg_put_backtitle(void)
|
|
{
|
|
int i;
|
|
|
|
if (dialog_vars.backtitle != NULL) {
|
|
chtype attr = A_NORMAL;
|
|
int backwidth = dlg_count_columns(dialog_vars.backtitle);
|
|
|
|
wattrset(stdscr, screen_attr);
|
|
(void) wmove(stdscr, 0, 1);
|
|
dlg_print_text(stdscr, dialog_vars.backtitle, COLS - 2, &attr);
|
|
for (i = 0; i < COLS - backwidth; i++)
|
|
(void) waddch(stdscr, ' ');
|
|
(void) wmove(stdscr, 1, 1);
|
|
for (i = 0; i < COLS - 2; i++)
|
|
(void) waddch(stdscr, dlg_boxchar(ACS_HLINE));
|
|
}
|
|
|
|
(void) wnoutrefresh(stdscr);
|
|
}
|
|
|
|
/*
|
|
* Set window to attribute 'attr'. There are more efficient ways to do this,
|
|
* but will not work on older/buggy ncurses versions.
|
|
*/
|
|
void
|
|
dlg_attr_clear(WINDOW *win, int height, int width, chtype attr)
|
|
{
|
|
int i, j;
|
|
|
|
wattrset(win, attr);
|
|
for (i = 0; i < height; i++) {
|
|
(void) wmove(win, i, 0);
|
|
for (j = 0; j < width; j++)
|
|
(void) waddch(win, ' ');
|
|
}
|
|
(void) touchwin(win);
|
|
}
|
|
|
|
void
|
|
dlg_clear(void)
|
|
{
|
|
dlg_attr_clear(stdscr, LINES, COLS, screen_attr);
|
|
}
|
|
|
|
#define isprivate(s) ((s) != 0 && strstr(s, "\033[?") != 0)
|
|
|
|
#define TTY_DEVICE "/dev/tty"
|
|
|
|
/*
|
|
* If $DIALOG_TTY exists, allow the program to try to open the terminal
|
|
* directly when stdout is redirected. By default we require the "--stdout"
|
|
* option to be given, but some scripts were written making use of the
|
|
* behavior of dialog which tried opening the terminal anyway.
|
|
*/
|
|
static char *
|
|
dialog_tty(void)
|
|
{
|
|
char *result = getenv("DIALOG_TTY");
|
|
if (result != 0 && atoi(result) == 0)
|
|
result = 0;
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Open the terminal directly. If one of stdin, stdout or stderr really points
|
|
* to a tty, use it. Otherwise give up and open /dev/tty.
|
|
*/
|
|
static int
|
|
open_terminal(char **result, int mode)
|
|
{
|
|
const char *device = TTY_DEVICE;
|
|
if (!isatty(fileno(stderr))
|
|
|| (device = ttyname(fileno(stderr))) == 0) {
|
|
if (!isatty(fileno(stdout))
|
|
|| (device = ttyname(fileno(stdout))) == 0) {
|
|
if (!isatty(fileno(stdin))
|
|
|| (device = ttyname(fileno(stdin))) == 0) {
|
|
device = TTY_DEVICE;
|
|
}
|
|
}
|
|
}
|
|
*result = dlg_strclone(device);
|
|
return open(device, mode);
|
|
}
|
|
|
|
/*
|
|
* Do some initialization for dialog.
|
|
*
|
|
* 'input' is the real tty input of dialog. Usually it is stdin, but if
|
|
* --input-fd option is used, it may be anything.
|
|
*
|
|
* 'output' is where dialog will send its result. Usually it is stderr, but
|
|
* if --stdout or --output-fd is used, it may be anything. We are concerned
|
|
* mainly with the case where it happens to be the same as stdout.
|
|
*/
|
|
void
|
|
init_dialog(FILE *input, FILE *output)
|
|
{
|
|
int fd1, fd2;
|
|
char *device = 0;
|
|
|
|
setlocale(LC_ALL, "");
|
|
|
|
dialog_state.output = output;
|
|
dialog_state.tab_len = TAB_LEN;
|
|
dialog_state.aspect_ratio = DEFAULT_ASPECT_RATIO;
|
|
#ifdef HAVE_COLOR
|
|
dialog_state.use_colors = USE_COLORS; /* use colors by default? */
|
|
dialog_state.use_shadow = USE_SHADOW; /* shadow dialog boxes by default? */
|
|
#endif
|
|
|
|
#ifdef HAVE_RC_FILE
|
|
if (dlg_parse_rc() == -1) /* Read the configuration file */
|
|
dlg_exiterr("init_dialog: dlg_parse_rc");
|
|
#endif
|
|
|
|
/*
|
|
* Some widgets (such as gauge) may read from the standard input. Pipes
|
|
* only connect stdout/stdin, so there is not much choice. But reading a
|
|
* pipe would get in the way of curses' normal reading stdin for getch.
|
|
*
|
|
* As in the --stdout (see below), reopening the terminal does not always
|
|
* work properly. dialog provides a --pipe-fd option for this purpose. We
|
|
* test that case first (differing fileno's for input/stdin). If the
|
|
* fileno's are equal, but we're not reading from a tty, see if we can open
|
|
* /dev/tty.
|
|
*/
|
|
dialog_state.pipe_input = stdin;
|
|
if (fileno(input) != fileno(stdin)) {
|
|
if (dup(fileno(input)) >= 0
|
|
&& (fd2 = dup(fileno(stdin))) >= 0) {
|
|
(void) dup2(fileno(input), fileno(stdin));
|
|
dialog_state.pipe_input = fdopen(fd2, "r");
|
|
if (fileno(stdin) != 0) /* some functions may read fd #0 */
|
|
(void) dup2(fileno(stdin), 0);
|
|
} else
|
|
dlg_exiterr("cannot open tty-input");
|
|
} else if (!isatty(fileno(stdin))) {
|
|
if (open_terminal(&device, O_RDONLY) >= 0
|
|
&& (fd2 = dup(fileno(stdin))) >= 0) {
|
|
dialog_state.pipe_input = fdopen(fd2, "r");
|
|
if (freopen(device, "r", stdin) == 0)
|
|
dlg_exiterr("cannot open tty-input");
|
|
if (fileno(stdin) != 0) /* some functions may read fd #0 */
|
|
(void) dup2(fileno(stdin), 0);
|
|
}
|
|
free(device);
|
|
}
|
|
|
|
/*
|
|
* If stdout is not a tty and dialog is called with the --stdout option, we
|
|
* have to provide for a way to write to the screen.
|
|
*
|
|
* The curses library normally writes its output to stdout, leaving stderr
|
|
* free for scripting. Scripts are simpler when stdout is redirected. The
|
|
* newterm function is useful; it allows us to specify where the output
|
|
* goes. Reopening the terminal is not portable since several
|
|
* configurations do not allow this to work properly:
|
|
*
|
|
* a) some getty implementations (and possibly broken tty drivers, e.g., on
|
|
* HPUX 10 and 11) cause stdin to act as if it is still in cooked mode
|
|
* even though results from ioctl's state that it is successfully
|
|
* altered to raw mode. Broken is the proper term.
|
|
*
|
|
* b) the user may not have permissions on the device, e.g., if one su's
|
|
* from the login user to another non-privileged user.
|
|
*/
|
|
if (!isatty(fileno(stdout))
|
|
&& (fileno(stdout) == fileno(output) || dialog_tty())) {
|
|
if ((fd1 = open_terminal(&device, O_WRONLY)) >= 0
|
|
&& (dialog_state.screen_output = fdopen(fd1, "w")) != 0) {
|
|
if (newterm(NULL, dialog_state.screen_output, stdin) == 0) {
|
|
dlg_exiterr("cannot initialize curses");
|
|
}
|
|
free(device);
|
|
} else {
|
|
dlg_exiterr("cannot open tty-output");
|
|
}
|
|
} else {
|
|
dialog_state.screen_output = stdout;
|
|
(void) initscr();
|
|
}
|
|
#ifdef NCURSES_VERSION
|
|
/*
|
|
* Cancel xterm's alternate-screen mode.
|
|
*/
|
|
if (!dialog_vars.keep_tite
|
|
&& (dialog_state.screen_output != stdout
|
|
|| isatty(fileno(dialog_state.screen_output)))
|
|
&& key_mouse != 0 /* xterm and kindred */
|
|
&& isprivate(enter_ca_mode)
|
|
&& isprivate(exit_ca_mode)) {
|
|
/*
|
|
* initscr() or newterm() already did putp(enter_ca_mode) as a side
|
|
* effect of initializing the screen. It would be nice to not even
|
|
* do that, but we do not really have access to the correct copy of
|
|
* the terminfo description until those functions have been invoked.
|
|
*/
|
|
(void) putp(exit_ca_mode);
|
|
(void) putp(clear_screen);
|
|
/*
|
|
* Prevent ncurses from switching "back" to the normal screen when
|
|
* exiting from dialog. That would move the cursor to the original
|
|
* location saved in xterm. Normally curses sets the cursor position
|
|
* to the first line after the display, but the alternate screen
|
|
* switching is done after that point.
|
|
*
|
|
* Cancelling the strings altogether also works around the buggy
|
|
* implementation of alternate-screen in rxvt, etc., which clear
|
|
* more of the display than they should.
|
|
*/
|
|
enter_ca_mode = 0;
|
|
exit_ca_mode = 0;
|
|
}
|
|
#endif
|
|
#ifdef HAVE_FLUSHINP
|
|
(void) flushinp();
|
|
#endif
|
|
(void) keypad(stdscr, TRUE);
|
|
(void) cbreak();
|
|
(void) noecho();
|
|
|
|
if (!dialog_state.no_mouse) {
|
|
mouse_open();
|
|
}
|
|
|
|
dialog_state.screen_initialized = TRUE;
|
|
|
|
#ifdef HAVE_COLOR
|
|
if (dialog_state.use_colors || dialog_state.use_shadow)
|
|
dlg_color_setup(); /* Set up colors */
|
|
#endif
|
|
|
|
/* Set screen to screen attribute */
|
|
dlg_clear();
|
|
}
|
|
|
|
#ifdef HAVE_COLOR
|
|
static int defined_colors = 1; /* pair-0 is reserved */
|
|
/*
|
|
* Setup for color display
|
|
*/
|
|
void
|
|
dlg_color_setup(void)
|
|
{
|
|
unsigned i;
|
|
|
|
if (has_colors()) { /* Terminal supports color? */
|
|
(void) start_color();
|
|
|
|
#if defined(HAVE_USE_DEFAULT_COLORS)
|
|
use_default_colors();
|
|
#endif
|
|
|
|
#if defined(__NetBSD__) && defined(_CURSES_)
|
|
#define C_ATTR(x,y) (((x) != 0 ? A_BOLD : 0) | COLOR_PAIR((y)))
|
|
/* work around bug in NetBSD curses */
|
|
for (i = 0; i < sizeof(dlg_color_table) /
|
|
sizeof(dlg_color_table[0]); i++) {
|
|
|
|
/* Initialize color pairs */
|
|
(void) init_pair(i + 1,
|
|
dlg_color_table[i].fg,
|
|
dlg_color_table[i].bg);
|
|
|
|
/* Setup color attributes */
|
|
dlg_color_table[i].atr = C_ATTR(dlg_color_table[i].hilite, i + 1);
|
|
}
|
|
defined_colors = i + 1;
|
|
#else
|
|
for (i = 0; i < sizeof(dlg_color_table) /
|
|
sizeof(dlg_color_table[0]); i++) {
|
|
|
|
/* Initialize color pairs */
|
|
chtype color = dlg_color_pair(dlg_color_table[i].fg,
|
|
dlg_color_table[i].bg);
|
|
|
|
/* Setup color attributes */
|
|
dlg_color_table[i].atr = ((dlg_color_table[i].hilite
|
|
? A_BOLD
|
|
: 0)
|
|
| color);
|
|
}
|
|
#endif
|
|
} else {
|
|
dialog_state.use_colors = FALSE;
|
|
dialog_state.use_shadow = FALSE;
|
|
}
|
|
}
|
|
|
|
int
|
|
dlg_color_count(void)
|
|
{
|
|
return sizeof(dlg_color_table) / sizeof(dlg_color_table[0]);
|
|
}
|
|
|
|
/*
|
|
* Wrapper for getattrs(), or the more cumbersome X/Open wattr_get().
|
|
*/
|
|
chtype
|
|
dlg_get_attrs(WINDOW *win)
|
|
{
|
|
chtype result;
|
|
#ifdef HAVE_GETATTRS
|
|
result = (chtype) getattrs(win);
|
|
#else
|
|
attr_t my_result;
|
|
short my_pair;
|
|
wattr_get(win, &my_result, &my_pair, NULL);
|
|
result = my_result;
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Reuse color pairs (they are limited), returning a COLOR_PAIR() value if we
|
|
* have (or can) define a pair with the given color as foreground on the
|
|
* window's defined background.
|
|
*/
|
|
chtype
|
|
dlg_color_pair(int foreground, int background)
|
|
{
|
|
chtype result = 0;
|
|
int pair;
|
|
short fg, bg;
|
|
bool found = FALSE;
|
|
|
|
for (pair = 1; pair < defined_colors; ++pair) {
|
|
if (pair_content((short) pair, &fg, &bg) != ERR
|
|
&& fg == foreground
|
|
&& bg == background) {
|
|
result = (chtype) COLOR_PAIR(pair);
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if (!found && (defined_colors + 1) < COLOR_PAIRS) {
|
|
pair = defined_colors++;
|
|
(void) init_pair((short) pair, (short) foreground, (short) background);
|
|
result = (chtype) COLOR_PAIR(pair);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Reuse color pairs (they are limited), returning a COLOR_PAIR() value if we
|
|
* have (or can) define a pair with the given color as foreground on the
|
|
* window's defined background.
|
|
*/
|
|
static chtype
|
|
define_color(WINDOW *win, int foreground)
|
|
{
|
|
chtype attrs = dlg_get_attrs(win);
|
|
int pair;
|
|
short fg, bg, background;
|
|
|
|
if ((pair = PAIR_NUMBER(attrs)) != 0
|
|
&& pair_content((short) pair, &fg, &bg) != ERR) {
|
|
background = bg;
|
|
} else {
|
|
background = COLOR_BLACK;
|
|
}
|
|
return dlg_color_pair(foreground, background);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* End using dialog functions.
|
|
*/
|
|
void
|
|
end_dialog(void)
|
|
{
|
|
if (dialog_state.screen_initialized) {
|
|
dialog_state.screen_initialized = FALSE;
|
|
mouse_close();
|
|
(void) endwin();
|
|
(void) fflush(stdout);
|
|
}
|
|
}
|
|
|
|
#define ESCAPE_LEN 3
|
|
#define isOurEscape(p) (((p)[0] == '\\') && ((p)[1] == 'Z') && ((p)[2] != 0))
|
|
|
|
int
|
|
dlg_count_real_columns(const char *text)
|
|
{
|
|
int result = dlg_count_columns(text);
|
|
if (result && dialog_vars.colors) {
|
|
int hidden = 0;
|
|
while (*text) {
|
|
if (dialog_vars.colors && isOurEscape(text)) {
|
|
hidden += ESCAPE_LEN;
|
|
text += ESCAPE_LEN;
|
|
} else {
|
|
++text;
|
|
}
|
|
}
|
|
result -= hidden;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static int
|
|
centered(int width, const char *string)
|
|
{
|
|
int need = dlg_count_real_columns(string);
|
|
int left;
|
|
|
|
left = (width - need) / 2 - 1;
|
|
if (left < 0)
|
|
left = 0;
|
|
return left;
|
|
}
|
|
|
|
#ifdef USE_WIDE_CURSES
|
|
static bool
|
|
is_combining(const char *txt, int *combined)
|
|
{
|
|
bool result = FALSE;
|
|
|
|
if (*combined == 0) {
|
|
if (UCH(*txt) >= 128) {
|
|
wchar_t wch;
|
|
mbstate_t state;
|
|
size_t given = strlen(txt);
|
|
size_t len;
|
|
|
|
memset(&state, 0, sizeof(state));
|
|
len = mbrtowc(&wch, txt, given, &state);
|
|
if ((int) len > 0 && wcwidth(wch) == 0) {
|
|
*combined = (int) len - 1;
|
|
result = TRUE;
|
|
}
|
|
}
|
|
} else {
|
|
result = TRUE;
|
|
*combined -= 1;
|
|
}
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Print up to 'cols' columns from 'text', optionally rendering our escape
|
|
* sequence for attributes and color.
|
|
*/
|
|
void
|
|
dlg_print_text(WINDOW *win, const char *txt, int cols, chtype *attr)
|
|
{
|
|
int y_origin, x_origin;
|
|
int y_before, x_before = 0;
|
|
int y_after, x_after;
|
|
int tabbed = 0;
|
|
bool thisTab;
|
|
bool ended = FALSE;
|
|
chtype useattr;
|
|
#ifdef USE_WIDE_CURSES
|
|
int combined = 0;
|
|
#endif
|
|
|
|
getyx(win, y_origin, x_origin);
|
|
while (cols > 0 && (*txt != '\0')) {
|
|
if (dialog_vars.colors) {
|
|
while (isOurEscape(txt)) {
|
|
int code;
|
|
|
|
txt += 2;
|
|
switch (code = CharOf(*txt)) {
|
|
#ifdef HAVE_COLOR
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
*attr &= ~A_COLOR;
|
|
*attr |= define_color(win, code - '0');
|
|
break;
|
|
#endif
|
|
case 'B':
|
|
*attr &= ~A_BOLD;
|
|
break;
|
|
case 'b':
|
|
*attr |= A_BOLD;
|
|
break;
|
|
case 'R':
|
|
*attr &= ~A_REVERSE;
|
|
break;
|
|
case 'r':
|
|
*attr |= A_REVERSE;
|
|
break;
|
|
case 'U':
|
|
*attr &= ~A_UNDERLINE;
|
|
break;
|
|
case 'u':
|
|
*attr |= A_UNDERLINE;
|
|
break;
|
|
case 'n':
|
|
*attr = A_NORMAL;
|
|
break;
|
|
}
|
|
++txt;
|
|
}
|
|
}
|
|
if (ended || *txt == '\n' || *txt == '\0')
|
|
break;
|
|
useattr = (*attr) & A_ATTRIBUTES;
|
|
#ifdef HAVE_COLOR
|
|
/*
|
|
* Prevent this from making text invisible when the foreground and
|
|
* background colors happen to be the same, and there's no bold
|
|
* attribute.
|
|
*/
|
|
if ((useattr & A_COLOR) != 0 && (useattr & A_BOLD) == 0) {
|
|
short pair = (short) PAIR_NUMBER(useattr);
|
|
short fg, bg;
|
|
if (pair_content(pair, &fg, &bg) != ERR
|
|
&& fg == bg) {
|
|
useattr &= ~A_COLOR;
|
|
useattr |= dlg_color_pair(fg, ((bg == COLOR_BLACK)
|
|
? COLOR_WHITE
|
|
: COLOR_BLACK));
|
|
}
|
|
}
|
|
#endif
|
|
/*
|
|
* Write the character, using curses to tell exactly how wide it
|
|
* is. If it is a tab, discount that, since the caller thinks
|
|
* tabs are nonprinting, and curses will expand tabs to one or
|
|
* more blanks.
|
|
*/
|
|
thisTab = (CharOf(*txt) == TAB);
|
|
if (thisTab) {
|
|
getyx(win, y_before, x_before);
|
|
(void) y_before;
|
|
}
|
|
(void) waddch(win, CharOf(*txt++) | useattr);
|
|
getyx(win, y_after, x_after);
|
|
if (thisTab && (y_after == y_origin))
|
|
tabbed += (x_after - x_before);
|
|
if ((y_after != y_origin) ||
|
|
(x_after >= (cols + tabbed + x_origin)
|
|
#ifdef USE_WIDE_CURSES
|
|
&& !is_combining(txt, &combined)
|
|
#endif
|
|
)) {
|
|
ended = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Print one line of the prompt in the window within the limits of the
|
|
* specified right margin. The line will end on a word boundary and a pointer
|
|
* to the start of the next line is returned, or a NULL pointer if the end of
|
|
* *prompt is reached.
|
|
*/
|
|
const char *
|
|
dlg_print_line(WINDOW *win,
|
|
chtype *attr,
|
|
const char *prompt,
|
|
int lm, int rm, int *x)
|
|
{
|
|
const char *wrap_ptr = prompt;
|
|
const char *test_ptr = prompt;
|
|
const char *hide_ptr = 0;
|
|
const int *cols = dlg_index_columns(prompt);
|
|
const int *indx = dlg_index_wchars(prompt);
|
|
int wrap_inx = 0;
|
|
int test_inx = 0;
|
|
int cur_x = lm;
|
|
int hidden = 0;
|
|
int limit = dlg_count_wchars(prompt);
|
|
int n;
|
|
int tabbed = 0;
|
|
|
|
*x = 1;
|
|
|
|
/*
|
|
* Set *test_ptr to the end of the line or the right margin (rm), whichever
|
|
* is less, and set wrap_ptr to the end of the last word in the line.
|
|
*/
|
|
for (n = 0; n < limit; ++n) {
|
|
test_ptr = prompt + indx[test_inx];
|
|
if (*test_ptr == '\n' || *test_ptr == '\0' || cur_x >= (rm + hidden))
|
|
break;
|
|
if (*test_ptr == TAB && n == 0) {
|
|
tabbed = 8; /* workaround for leading tabs */
|
|
} else if (*test_ptr == ' ' && n != 0 && prompt[indx[n - 1]] != ' ') {
|
|
wrap_inx = n;
|
|
*x = cur_x;
|
|
} else if (dialog_vars.colors && isOurEscape(test_ptr)) {
|
|
hide_ptr = test_ptr;
|
|
hidden += ESCAPE_LEN;
|
|
n += (ESCAPE_LEN - 1);
|
|
}
|
|
cur_x = lm + tabbed + cols[n + 1];
|
|
if (cur_x > (rm + hidden))
|
|
break;
|
|
test_inx = n + 1;
|
|
}
|
|
|
|
/*
|
|
* If the line doesn't reach the right margin in the middle of a word, then
|
|
* we don't have to wrap it at the end of the previous word.
|
|
*/
|
|
test_ptr = prompt + indx[test_inx];
|
|
if (*test_ptr == '\n' || *test_ptr == ' ' || *test_ptr == '\0') {
|
|
wrap_inx = test_inx;
|
|
while (wrap_inx > 0 && prompt[indx[wrap_inx - 1]] == ' ') {
|
|
wrap_inx--;
|
|
}
|
|
*x = lm + indx[wrap_inx];
|
|
} else if (*x == 1 && cur_x >= rm) {
|
|
/*
|
|
* If the line has no spaces, then wrap it anyway at the right margin
|
|
*/
|
|
*x = rm;
|
|
wrap_inx = test_inx;
|
|
}
|
|
wrap_ptr = prompt + indx[wrap_inx];
|
|
#ifdef USE_WIDE_CURSES
|
|
if (UCH(*wrap_ptr) >= 128) {
|
|
int combined = 0;
|
|
while (is_combining(wrap_ptr, &combined)) {
|
|
++wrap_ptr;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* If we found hidden text past the last point that we will display,
|
|
* discount that from the displayed length.
|
|
*/
|
|
if ((hide_ptr != 0) && (hide_ptr >= wrap_ptr)) {
|
|
hidden -= ESCAPE_LEN;
|
|
test_ptr = wrap_ptr;
|
|
while (test_ptr < wrap_ptr) {
|
|
if (dialog_vars.colors && isOurEscape(test_ptr)) {
|
|
hidden -= ESCAPE_LEN;
|
|
test_ptr += ESCAPE_LEN;
|
|
} else {
|
|
++test_ptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Print the line if we have a window pointer. Otherwise this routine
|
|
* is just being called for sizing the window.
|
|
*/
|
|
if (win) {
|
|
dlg_print_text(win, prompt, (cols[wrap_inx] - hidden), attr);
|
|
}
|
|
|
|
/* *x tells the calling function how long the line was */
|
|
if (*x == 1)
|
|
*x = rm;
|
|
|
|
*x -= hidden;
|
|
|
|
/* Find the start of the next line and return a pointer to it */
|
|
test_ptr = wrap_ptr;
|
|
while (*test_ptr == ' ')
|
|
test_ptr++;
|
|
if (*test_ptr == '\n')
|
|
test_ptr++;
|
|
return (test_ptr);
|
|
}
|
|
|
|
static void
|
|
justify_text(WINDOW *win,
|
|
const char *prompt,
|
|
int limit_y,
|
|
int limit_x,
|
|
int *high, int *wide)
|
|
{
|
|
chtype attr = A_NORMAL;
|
|
int x = (2 * MARGIN);
|
|
int y = MARGIN;
|
|
int max_x = 2;
|
|
int lm = (2 * MARGIN); /* left margin (box-border plus a space) */
|
|
int rm = limit_x; /* right margin */
|
|
int bm = limit_y; /* bottom margin */
|
|
int last_y = 0, last_x = 0;
|
|
|
|
if (win) {
|
|
rm -= (2 * MARGIN);
|
|
bm -= (2 * MARGIN);
|
|
}
|
|
if (prompt == 0)
|
|
prompt = "";
|
|
|
|
if (win != 0)
|
|
getyx(win, last_y, last_x);
|
|
while (y <= bm && *prompt) {
|
|
x = lm;
|
|
|
|
if (*prompt == '\n') {
|
|
while (*prompt == '\n' && y < bm) {
|
|
if (*(prompt + 1) != '\0') {
|
|
++y;
|
|
if (win != 0)
|
|
(void) wmove(win, y, lm);
|
|
}
|
|
prompt++;
|
|
}
|
|
} else if (win != 0)
|
|
(void) wmove(win, y, lm);
|
|
|
|
if (*prompt) {
|
|
prompt = dlg_print_line(win, &attr, prompt, lm, rm, &x);
|
|
if (win != 0)
|
|
getyx(win, last_y, last_x);
|
|
}
|
|
if (*prompt) {
|
|
++y;
|
|
if (win != 0)
|
|
(void) wmove(win, y, lm);
|
|
}
|
|
max_x = MAX(max_x, x);
|
|
}
|
|
/* Move back to the last position after drawing prompt, for msgbox. */
|
|
if (win != 0)
|
|
(void) wmove(win, last_y, last_x);
|
|
|
|
/* Set the final height and width for the calling function */
|
|
if (high != 0)
|
|
*high = y;
|
|
if (wide != 0)
|
|
*wide = max_x;
|
|
}
|
|
|
|
/*
|
|
* Print a string of text in a window, automatically wrap around to the next
|
|
* line if the string is too long to fit on one line. Note that the string may
|
|
* contain embedded newlines.
|
|
*/
|
|
void
|
|
dlg_print_autowrap(WINDOW *win, const char *prompt, int height, int width)
|
|
{
|
|
justify_text(win, prompt,
|
|
height,
|
|
width,
|
|
(int *) 0, (int *) 0);
|
|
}
|
|
|
|
/*
|
|
* Display the message in a scrollable window. Actually the way it works is
|
|
* that we create a "tall" window of the proper width, let the text wrap within
|
|
* that, and copy a slice of the result to the dialog.
|
|
*
|
|
* It works for ncurses. Other curses implementations show only blanks (Tru64)
|
|
* or garbage (NetBSD).
|
|
*/
|
|
int
|
|
dlg_print_scrolled(WINDOW *win,
|
|
const char *prompt,
|
|
int offset,
|
|
int height,
|
|
int width,
|
|
int pauseopt)
|
|
{
|
|
int oldy, oldx;
|
|
int last = 0;
|
|
|
|
(void) pauseopt; /* used only for ncurses */
|
|
|
|
getyx(win, oldy, oldx);
|
|
#ifdef NCURSES_VERSION
|
|
if (pauseopt) {
|
|
int wide = width - (2 * MARGIN);
|
|
int high = LINES;
|
|
int y, x;
|
|
int len;
|
|
int percent;
|
|
WINDOW *dummy;
|
|
char buffer[5];
|
|
|
|
#if defined(NCURSES_VERSION_PATCH) && NCURSES_VERSION_PATCH >= 20040417
|
|
/*
|
|
* If we're not limited by the screensize, allow text to possibly be
|
|
* one character per line.
|
|
*/
|
|
if ((len = dlg_count_columns(prompt)) > high)
|
|
high = len;
|
|
#endif
|
|
dummy = newwin(high, width, 0, 0);
|
|
if (dummy == 0) {
|
|
wattrset(win, dialog_attr);
|
|
dlg_print_autowrap(win, prompt, height + 1 + (3 * MARGIN), width);
|
|
last = 0;
|
|
} else {
|
|
wbkgdset(dummy, dialog_attr | ' ');
|
|
wattrset(dummy, dialog_attr);
|
|
werase(dummy);
|
|
dlg_print_autowrap(dummy, prompt, high, width);
|
|
getyx(dummy, y, x);
|
|
(void) x;
|
|
|
|
copywin(dummy, /* srcwin */
|
|
win, /* dstwin */
|
|
offset + MARGIN, /* sminrow */
|
|
MARGIN, /* smincol */
|
|
MARGIN, /* dminrow */
|
|
MARGIN, /* dmincol */
|
|
height, /* dmaxrow */
|
|
wide, /* dmaxcol */
|
|
FALSE);
|
|
|
|
delwin(dummy);
|
|
|
|
/* if the text is incomplete, or we have scrolled, show the percentage */
|
|
if (y > 0 && wide > 4) {
|
|
percent = (int) ((height + offset) * 100.0 / y);
|
|
if (percent < 0)
|
|
percent = 0;
|
|
if (percent > 100)
|
|
percent = 100;
|
|
if (offset != 0 || percent != 100) {
|
|
(void) wattrset(win, position_indicator_attr);
|
|
(void) wmove(win, MARGIN + height, wide - 4);
|
|
(void) sprintf(buffer, "%d%%", percent);
|
|
(void) waddstr(win, buffer);
|
|
if ((len = (int) strlen(buffer)) < 4) {
|
|
wattrset(win, border_attr);
|
|
whline(win, dlg_boxchar(ACS_HLINE), 4 - len);
|
|
}
|
|
}
|
|
}
|
|
last = (y - height);
|
|
}
|
|
} else
|
|
#endif
|
|
{
|
|
(void) offset;
|
|
wattrset(win, dialog_attr);
|
|
dlg_print_autowrap(win, prompt, height + 1 + (3 * MARGIN), width);
|
|
last = 0;
|
|
}
|
|
wmove(win, oldy, oldx);
|
|
return last;
|
|
}
|
|
|
|
int
|
|
dlg_check_scrolled(int key, int last, int page, bool * show, int *offset)
|
|
{
|
|
int code = 0;
|
|
|
|
*show = FALSE;
|
|
|
|
switch (key) {
|
|
case DLGK_PAGE_FIRST:
|
|
if (*offset > 0) {
|
|
*offset = 0;
|
|
*show = TRUE;
|
|
}
|
|
break;
|
|
case DLGK_PAGE_LAST:
|
|
if (*offset < last) {
|
|
*offset = last;
|
|
*show = TRUE;
|
|
}
|
|
break;
|
|
case DLGK_GRID_UP:
|
|
if (*offset > 0) {
|
|
--(*offset);
|
|
*show = TRUE;
|
|
}
|
|
break;
|
|
case DLGK_GRID_DOWN:
|
|
if (*offset < last) {
|
|
++(*offset);
|
|
*show = TRUE;
|
|
}
|
|
break;
|
|
case DLGK_PAGE_PREV:
|
|
if (*offset > 0) {
|
|
*offset -= page;
|
|
if (*offset < 0)
|
|
*offset = 0;
|
|
*show = TRUE;
|
|
}
|
|
break;
|
|
case DLGK_PAGE_NEXT:
|
|
if (*offset < last) {
|
|
*offset += page;
|
|
if (*offset > last)
|
|
*offset = last;
|
|
*show = TRUE;
|
|
}
|
|
break;
|
|
default:
|
|
code = -1;
|
|
break;
|
|
}
|
|
return code;
|
|
}
|
|
|
|
/*
|
|
* Calculate the window size for preformatted text. This will calculate box
|
|
* dimensions that are at or close to the specified aspect ratio for the prompt
|
|
* string with all spaces and newlines preserved and additional newlines added
|
|
* as necessary.
|
|
*/
|
|
static void
|
|
auto_size_preformatted(const char *prompt, int *height, int *width)
|
|
{
|
|
int high = 0, wide = 0;
|
|
float car; /* Calculated Aspect Ratio */
|
|
float diff;
|
|
int max_y = SLINES - 1;
|
|
int max_x = SCOLS - 2;
|
|
int max_width = max_x;
|
|
int ar = dialog_state.aspect_ratio;
|
|
|
|
/* Get the initial dimensions */
|
|
justify_text((WINDOW *) 0, prompt, max_y, max_x, &high, &wide);
|
|
car = (float) (wide / high);
|
|
|
|
/*
|
|
* If the aspect ratio is greater than it should be, then decrease the
|
|
* width proportionately.
|
|
*/
|
|
if (car > ar) {
|
|
diff = car / (float) ar;
|
|
max_x = (int) ((float) wide / diff + 4);
|
|
justify_text((WINDOW *) 0, prompt, max_y, max_x, &high, &wide);
|
|
car = (float) wide / (float) high;
|
|
}
|
|
|
|
/*
|
|
* If the aspect ratio is too small after decreasing the width, then
|
|
* incrementally increase the width until the aspect ratio is equal to or
|
|
* greater than the specified aspect ratio.
|
|
*/
|
|
while (car < ar && max_x < max_width) {
|
|
max_x += 4;
|
|
justify_text((WINDOW *) 0, prompt, max_y, max_x, &high, &wide);
|
|
car = (float) (wide / high);
|
|
}
|
|
|
|
*height = high;
|
|
*width = wide;
|
|
}
|
|
|
|
/*
|
|
* Find the length of the longest "word" in the given string. By setting the
|
|
* widget width at least this long, we can avoid splitting a word on the
|
|
* margin.
|
|
*/
|
|
static int
|
|
longest_word(const char *string)
|
|
{
|
|
int length, result = 0;
|
|
|
|
while (*string != '\0') {
|
|
length = 0;
|
|
while (*string != '\0' && !isspace(UCH(*string))) {
|
|
length++;
|
|
string++;
|
|
}
|
|
result = MAX(result, length);
|
|
if (*string != '\0')
|
|
string++;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* if (height or width == -1) Maximize()
|
|
* if (height or width == 0), justify and return actual limits.
|
|
*/
|
|
static void
|
|
real_auto_size(const char *title,
|
|
const char *prompt,
|
|
int *height, int *width,
|
|
int boxlines, int mincols)
|
|
{
|
|
int x = (dialog_vars.begin_set ? dialog_vars.begin_x : 2);
|
|
int y = (dialog_vars.begin_set ? dialog_vars.begin_y : 1);
|
|
int title_length = title ? dlg_count_columns(title) : 0;
|
|
int nc = 4;
|
|
int high;
|
|
int wide;
|
|
int save_high = *height;
|
|
int save_wide = *width;
|
|
|
|
if (prompt == 0) {
|
|
if (*height == 0)
|
|
*height = -1;
|
|
if (*width == 0)
|
|
*width = -1;
|
|
}
|
|
|
|
if (*height > 0) {
|
|
high = *height;
|
|
} else {
|
|
high = SLINES - y;
|
|
}
|
|
|
|
if (*width <= 0) {
|
|
if (prompt != 0) {
|
|
wide = MAX(title_length, mincols);
|
|
if (strchr(prompt, '\n') == 0) {
|
|
double val = (dialog_state.aspect_ratio *
|
|
dlg_count_real_columns(prompt));
|
|
double xxx = sqrt(val);
|
|
int tmp = (int) xxx;
|
|
wide = MAX(wide, tmp);
|
|
wide = MAX(wide, longest_word(prompt));
|
|
justify_text((WINDOW *) 0, prompt, high, wide, height, width);
|
|
} else {
|
|
auto_size_preformatted(prompt, height, width);
|
|
}
|
|
} else {
|
|
wide = SCOLS - x;
|
|
justify_text((WINDOW *) 0, prompt, high, wide, height, width);
|
|
}
|
|
}
|
|
|
|
if (*width < title_length) {
|
|
justify_text((WINDOW *) 0, prompt, high, title_length, height, width);
|
|
*width = title_length;
|
|
}
|
|
|
|
if (*width < mincols && save_wide == 0)
|
|
*width = mincols;
|
|
if (prompt != 0) {
|
|
*width += nc;
|
|
*height += boxlines + 2;
|
|
}
|
|
if (save_high > 0)
|
|
*height = save_high;
|
|
if (save_wide > 0)
|
|
*width = save_wide;
|
|
}
|
|
|
|
/* End of real_auto_size() */
|
|
|
|
void
|
|
dlg_auto_size(const char *title,
|
|
const char *prompt,
|
|
int *height,
|
|
int *width,
|
|
int boxlines,
|
|
int mincols)
|
|
{
|
|
real_auto_size(title, prompt, height, width, boxlines, mincols);
|
|
|
|
if (*width > SCOLS) {
|
|
(*height)++;
|
|
*width = SCOLS;
|
|
}
|
|
|
|
if (*height > SLINES)
|
|
*height = SLINES;
|
|
}
|
|
|
|
/*
|
|
* if (height or width == -1) Maximize()
|
|
* if (height or width == 0)
|
|
* height=MIN(SLINES, num.lines in fd+n);
|
|
* width=MIN(SCOLS, MAX(longer line+n, mincols));
|
|
*/
|
|
void
|
|
dlg_auto_sizefile(const char *title,
|
|
const char *file,
|
|
int *height,
|
|
int *width,
|
|
int boxlines,
|
|
int mincols)
|
|
{
|
|
int count = 0;
|
|
int len = title ? dlg_count_columns(title) : 0;
|
|
int nc = 4;
|
|
int numlines = 2;
|
|
long offset;
|
|
int ch;
|
|
FILE *fd;
|
|
|
|
/* Open input file for reading */
|
|
if ((fd = fopen(file, "rb")) == NULL)
|
|
dlg_exiterr("dlg_auto_sizefile: Cannot open input file %s", file);
|
|
|
|
if ((*height == -1) || (*width == -1)) {
|
|
*height = SLINES - (dialog_vars.begin_set ? dialog_vars.begin_y : 0);
|
|
*width = SCOLS - (dialog_vars.begin_set ? dialog_vars.begin_x : 0);
|
|
}
|
|
if ((*height != 0) && (*width != 0)) {
|
|
(void) fclose(fd);
|
|
if (*width > SCOLS)
|
|
*width = SCOLS;
|
|
if (*height > SLINES)
|
|
*height = SLINES;
|
|
return;
|
|
}
|
|
|
|
while (!feof(fd)) {
|
|
offset = 0;
|
|
while (((ch = getc(fd)) != '\n') && !feof(fd))
|
|
if ((ch == TAB) && (dialog_vars.tab_correct))
|
|
offset += dialog_state.tab_len - (offset % dialog_state.tab_len);
|
|
else
|
|
offset++;
|
|
|
|
if (offset > len)
|
|
len = (int) offset;
|
|
|
|
count++;
|
|
}
|
|
|
|
/* now 'count' has the number of lines of fd and 'len' the max length */
|
|
|
|
*height = MIN(SLINES, count + numlines + boxlines);
|
|
*width = MIN(SCOLS, MAX((len + nc), mincols));
|
|
/* here width and height can be maximized if > SCOLS|SLINES because
|
|
textbox-like widgets don't put all <file> on the screen.
|
|
Msgbox-like widget instead have to put all <text> correctly. */
|
|
|
|
(void) fclose(fd);
|
|
}
|
|
|
|
static chtype
|
|
dlg_get_cell_attrs(WINDOW *win)
|
|
{
|
|
chtype result;
|
|
#ifdef USE_WIDE_CURSES
|
|
cchar_t wch;
|
|
wchar_t cc;
|
|
attr_t attrs;
|
|
short pair;
|
|
if (win_wch(win, &wch) == OK
|
|
&& getcchar(&wch, &cc, &attrs, &pair, NULL) == OK) {
|
|
result = attrs;
|
|
} else {
|
|
result = 0;
|
|
}
|
|
#else
|
|
result = winch(win) & (A_ATTRIBUTES & ~A_COLOR);
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Draw a rectangular box with line drawing characters.
|
|
*
|
|
* borderchar is used to color the upper/left edges.
|
|
*
|
|
* boxchar is used to color the right/lower edges. It also is fill-color used
|
|
* for the box contents.
|
|
*
|
|
* Normally, if you are drawing a scrollable box, use menubox_border_attr for
|
|
* boxchar, and menubox_attr for borderchar since the scroll-arrows are drawn
|
|
* with menubox_attr at the top, and menubox_border_attr at the bottom. That
|
|
* also (given the default color choices) produces a recessed effect.
|
|
*
|
|
* If you want a raised effect (and are not going to use the scroll-arrows),
|
|
* reverse this choice.
|
|
*/
|
|
void
|
|
dlg_draw_box2(WINDOW *win, int y, int x, int height, int width,
|
|
chtype boxchar, chtype borderchar, chtype borderchar2)
|
|
{
|
|
int i, j;
|
|
chtype save = dlg_get_attrs(win);
|
|
|
|
wattrset(win, 0);
|
|
for (i = 0; i < height; i++) {
|
|
(void) wmove(win, y + i, x);
|
|
for (j = 0; j < width; j++)
|
|
if (!i && !j)
|
|
(void) waddch(win, borderchar | dlg_boxchar(ACS_ULCORNER));
|
|
else if (i == height - 1 && !j)
|
|
(void) waddch(win, borderchar | dlg_boxchar(ACS_LLCORNER));
|
|
else if (!i && j == width - 1)
|
|
(void) waddch(win, borderchar2 | dlg_boxchar(ACS_URCORNER));
|
|
else if (i == height - 1 && j == width - 1)
|
|
(void) waddch(win, borderchar2 | dlg_boxchar(ACS_LRCORNER));
|
|
else if (!i)
|
|
(void) waddch(win, borderchar | dlg_boxchar(ACS_HLINE));
|
|
else if (i == height - 1)
|
|
(void) waddch(win, borderchar2 | dlg_boxchar(ACS_HLINE));
|
|
else if (!j)
|
|
(void) waddch(win, borderchar | dlg_boxchar(ACS_VLINE));
|
|
else if (j == width - 1)
|
|
(void) waddch(win, borderchar2 | dlg_boxchar(ACS_VLINE));
|
|
else
|
|
(void) waddch(win, boxchar | ' ');
|
|
}
|
|
wattrset(win, save);
|
|
}
|
|
|
|
void
|
|
dlg_draw_box(WINDOW *win, int y, int x, int height, int width,
|
|
chtype boxchar, chtype borderchar)
|
|
{
|
|
dlg_draw_box2(win, y, x, height, width, boxchar, borderchar, boxchar);
|
|
}
|
|
|
|
static DIALOG_WINDOWS *
|
|
find_window(WINDOW *win)
|
|
{
|
|
DIALOG_WINDOWS *result = 0;
|
|
DIALOG_WINDOWS *p;
|
|
|
|
for (p = dialog_state.all_windows; p != 0; p = p->next) {
|
|
if (p->normal == win) {
|
|
result = p;
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#ifdef HAVE_COLOR
|
|
/*
|
|
* If we have wchgat(), use that for updating shadow attributes, to work with
|
|
* wide-character data.
|
|
*/
|
|
|
|
/*
|
|
* Check if the given point is "in" the given window. If so, return the window
|
|
* pointer, otherwise null.
|
|
*/
|
|
static WINDOW *
|
|
in_window(WINDOW *win, int y, int x)
|
|
{
|
|
WINDOW *result = 0;
|
|
int y_base = getbegy(win);
|
|
int x_base = getbegx(win);
|
|
int y_last = getmaxy(win) + y_base;
|
|
int x_last = getmaxx(win) + x_base;
|
|
|
|
if (y >= y_base && y <= y_last && x >= x_base && x <= x_last)
|
|
result = win;
|
|
return result;
|
|
}
|
|
|
|
static WINDOW *
|
|
window_at_cell(DIALOG_WINDOWS * dw, int y, int x)
|
|
{
|
|
WINDOW *result = 0;
|
|
DIALOG_WINDOWS *p;
|
|
int y_want = y + getbegy(dw->shadow);
|
|
int x_want = x + getbegx(dw->shadow);
|
|
|
|
for (p = dialog_state.all_windows; p != 0; p = p->next) {
|
|
if (dw->normal != p->normal
|
|
&& dw->shadow != p->normal
|
|
&& (result = in_window(p->normal, y_want, x_want)) != 0) {
|
|
break;
|
|
}
|
|
}
|
|
if (result == 0) {
|
|
result = stdscr;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static bool
|
|
in_shadow(WINDOW *normal, WINDOW *shadow, int y, int x)
|
|
{
|
|
bool result = FALSE;
|
|
int ybase = getbegy(normal);
|
|
int ylast = getmaxy(normal) + ybase;
|
|
int xbase = getbegx(normal);
|
|
int xlast = getmaxx(normal) + xbase;
|
|
|
|
y += getbegy(shadow);
|
|
x += getbegx(shadow);
|
|
|
|
if (y >= ybase + SHADOW_ROWS
|
|
&& y < ylast + SHADOW_ROWS
|
|
&& x >= xlast
|
|
&& x < xlast + SHADOW_COLS) {
|
|
/* in the right-side */
|
|
result = TRUE;
|
|
} else if (y >= ylast
|
|
&& y < ylast + SHADOW_ROWS
|
|
&& x >= ybase + SHADOW_COLS
|
|
&& x < ylast + SHADOW_COLS) {
|
|
/* check the bottom */
|
|
result = TRUE;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* When erasing a shadow, check each cell to make sure that it is not part of
|
|
* another box's shadow. This is a little complicated since most shadows are
|
|
* merged onto stdscr.
|
|
*/
|
|
static bool
|
|
last_shadow(DIALOG_WINDOWS * dw, int y, int x)
|
|
{
|
|
DIALOG_WINDOWS *p;
|
|
bool result = TRUE;
|
|
|
|
for (p = dialog_state.all_windows; p != 0; p = p->next) {
|
|
if (p->normal != dw->normal
|
|
&& in_shadow(p->normal, dw->shadow, y, x)) {
|
|
result = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
repaint_cell(DIALOG_WINDOWS * dw, bool draw, int y, int x)
|
|
{
|
|
WINDOW *win = dw->shadow;
|
|
WINDOW *cellwin;
|
|
int y2, x2;
|
|
|
|
if ((cellwin = window_at_cell(dw, y, x)) != 0
|
|
&& (draw || last_shadow(dw, y, x))
|
|
&& (y2 = (y + getbegy(win) - getbegy(cellwin))) >= 0
|
|
&& (x2 = (x + getbegx(win) - getbegx(cellwin))) >= 0
|
|
&& wmove(cellwin, y2, x2) != ERR) {
|
|
chtype the_cell = dlg_get_attrs(cellwin);
|
|
chtype the_attr = (draw ? shadow_attr : the_cell);
|
|
|
|
if (dlg_get_cell_attrs(cellwin) & A_ALTCHARSET) {
|
|
the_attr |= A_ALTCHARSET;
|
|
}
|
|
#if USE_WCHGAT
|
|
wchgat(cellwin, 1,
|
|
the_attr & (chtype) (~A_COLOR),
|
|
(short) PAIR_NUMBER(the_attr),
|
|
NULL);
|
|
#else
|
|
{
|
|
chtype the_char = ((winch(cellwin) & A_CHARTEXT) | the_attr);
|
|
(void) waddch(cellwin, the_char);
|
|
}
|
|
#endif
|
|
wnoutrefresh(cellwin);
|
|
}
|
|
}
|
|
|
|
#define RepaintCell(dw, draw, y, x) repaint_cell(dw, draw, y, x)
|
|
|
|
static void
|
|
repaint_shadow(DIALOG_WINDOWS * dw, bool draw, int y, int x, int height, int width)
|
|
{
|
|
int i, j;
|
|
|
|
if (UseShadow(dw)) {
|
|
#if !USE_WCHGAT
|
|
chtype save = dlg_get_attrs(dw->shadow);
|
|
wattrset(dw->shadow, draw ? shadow_attr : screen_attr);
|
|
#endif
|
|
for (i = 0; i < SHADOW_ROWS; ++i) {
|
|
for (j = 0; j < width; ++j) {
|
|
RepaintCell(dw, draw, i + y + height, j + x + SHADOW_COLS);
|
|
}
|
|
}
|
|
for (i = 0; i < height; i++) {
|
|
for (j = 0; j < SHADOW_COLS; ++j) {
|
|
RepaintCell(dw, draw, i + y + SHADOW_ROWS, j + x + width);
|
|
}
|
|
}
|
|
(void) wnoutrefresh(dw->shadow);
|
|
#if !USE_WCHGAT
|
|
wattrset(dw->shadow, save);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Draw a shadow on the parent window corresponding to the right- and
|
|
* bottom-edge of the child window, to give a 3-dimensional look.
|
|
*/
|
|
static void
|
|
draw_childs_shadow(DIALOG_WINDOWS * dw)
|
|
{
|
|
if (UseShadow(dw)) {
|
|
repaint_shadow(dw,
|
|
TRUE,
|
|
getbegy(dw->normal) - getbegy(dw->shadow),
|
|
getbegx(dw->normal) - getbegx(dw->shadow),
|
|
getmaxy(dw->normal),
|
|
getmaxx(dw->normal));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Erase a shadow on the parent window corresponding to the right- and
|
|
* bottom-edge of the child window.
|
|
*/
|
|
static void
|
|
erase_childs_shadow(DIALOG_WINDOWS * dw)
|
|
{
|
|
if (UseShadow(dw)) {
|
|
repaint_shadow(dw,
|
|
FALSE,
|
|
getbegy(dw->normal) - getbegy(dw->shadow),
|
|
getbegx(dw->normal) - getbegx(dw->shadow),
|
|
getmaxy(dw->normal),
|
|
getmaxx(dw->normal));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Draw shadows along the right and bottom edge to give a more 3D look
|
|
* to the boxes.
|
|
*/
|
|
void
|
|
dlg_draw_shadow(WINDOW *win, int y, int x, int height, int width)
|
|
{
|
|
repaint_shadow(find_window(win), TRUE, y, x, height, width);
|
|
}
|
|
#endif /* HAVE_COLOR */
|
|
|
|
/*
|
|
* Allow shell scripts to remap the exit codes so they can distinguish ESC
|
|
* from ERROR.
|
|
*/
|
|
void
|
|
dlg_exit(int code)
|
|
{
|
|
/* *INDENT-OFF* */
|
|
static const struct {
|
|
int code;
|
|
const char *name;
|
|
} table[] = {
|
|
{ DLG_EXIT_CANCEL, "DIALOG_CANCEL" },
|
|
{ DLG_EXIT_ERROR, "DIALOG_ERROR" },
|
|
{ DLG_EXIT_ESC, "DIALOG_ESC" },
|
|
{ DLG_EXIT_EXTRA, "DIALOG_EXTRA" },
|
|
{ DLG_EXIT_HELP, "DIALOG_HELP" },
|
|
{ DLG_EXIT_OK, "DIALOG_OK" },
|
|
{ DLG_EXIT_ITEM_HELP, "DIALOG_ITEM_HELP" },
|
|
};
|
|
/* *INDENT-ON* */
|
|
|
|
unsigned n;
|
|
char *name;
|
|
char *temp;
|
|
long value;
|
|
bool overridden = FALSE;
|
|
|
|
retry:
|
|
for (n = 0; n < sizeof(table) / sizeof(table[0]); n++) {
|
|
if (table[n].code == code) {
|
|
if ((name = getenv(table[n].name)) != 0) {
|
|
value = strtol(name, &temp, 0);
|
|
if (temp != 0 && temp != name && *temp == '\0') {
|
|
code = (int) value;
|
|
overridden = TRUE;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Prior to 2004/12/19, a widget using --item-help would exit with "OK"
|
|
* if the help button were selected. Now we want to exit with "HELP",
|
|
* but allow the environment variable to override.
|
|
*/
|
|
if (code == DLG_EXIT_ITEM_HELP && !overridden) {
|
|
code = DLG_EXIT_HELP;
|
|
goto retry;
|
|
}
|
|
#ifdef HAVE_DLG_TRACE
|
|
dlg_trace((const char *) 0); /* close it */
|
|
#endif
|
|
|
|
#ifdef NO_LEAKS
|
|
_dlg_inputstr_leaks();
|
|
#if defined(NCURSES_VERSION) && defined(HAVE__NC_FREE_AND_EXIT)
|
|
_nc_free_and_exit(code);
|
|
#endif
|
|
#endif
|
|
|
|
if (dialog_state.input == stdin) {
|
|
exit(code);
|
|
} else {
|
|
/*
|
|
* Just in case of using --input-fd option, do not
|
|
* call atexit functions of ncurses which may hang.
|
|
*/
|
|
if (dialog_state.input) {
|
|
fclose(dialog_state.input);
|
|
dialog_state.input = 0;
|
|
}
|
|
if (dialog_state.pipe_input) {
|
|
if (dialog_state.pipe_input != stdin) {
|
|
fclose(dialog_state.pipe_input);
|
|
dialog_state.pipe_input = 0;
|
|
}
|
|
}
|
|
_exit(code);
|
|
}
|
|
}
|
|
|
|
/* quit program killing all tailbg */
|
|
void
|
|
dlg_exiterr(const char *fmt,...)
|
|
{
|
|
int retval;
|
|
va_list ap;
|
|
|
|
end_dialog();
|
|
|
|
(void) fputc('\n', stderr);
|
|
va_start(ap, fmt);
|
|
(void) vfprintf(stderr, fmt, ap);
|
|
va_end(ap);
|
|
(void) fputc('\n', stderr);
|
|
|
|
dlg_killall_bg(&retval);
|
|
|
|
(void) fflush(stderr);
|
|
(void) fflush(stdout);
|
|
dlg_exit(DLG_EXIT_ERROR);
|
|
}
|
|
|
|
void
|
|
dlg_beeping(void)
|
|
{
|
|
if (dialog_vars.beep_signal) {
|
|
(void) beep();
|
|
dialog_vars.beep_signal = 0;
|
|
}
|
|
}
|
|
|
|
void
|
|
dlg_print_size(int height, int width)
|
|
{
|
|
if (dialog_vars.print_siz)
|
|
fprintf(dialog_state.output, "Size: %d, %d\n", height, width);
|
|
}
|
|
|
|
void
|
|
dlg_ctl_size(int height, int width)
|
|
{
|
|
if (dialog_vars.size_err) {
|
|
if ((width > COLS) || (height > LINES)) {
|
|
dlg_exiterr("Window too big. (height, width) = (%d, %d). Max allowed (%d, %d).",
|
|
height, width, LINES, COLS);
|
|
}
|
|
#ifdef HAVE_COLOR
|
|
else if ((dialog_state.use_shadow)
|
|
&& ((width > SCOLS || height > SLINES))) {
|
|
if ((width <= COLS) && (height <= LINES)) {
|
|
/* try again, without shadows */
|
|
dialog_state.use_shadow = 0;
|
|
} else {
|
|
dlg_exiterr("Window+Shadow too big. (height, width) = (%d, %d). Max allowed (%d, %d).",
|
|
height, width, SLINES, SCOLS);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If the --tab-correct was not selected, convert tabs to single spaces.
|
|
*/
|
|
void
|
|
dlg_tab_correct_str(char *prompt)
|
|
{
|
|
char *ptr;
|
|
|
|
if (dialog_vars.tab_correct) {
|
|
while ((ptr = strchr(prompt, TAB)) != NULL) {
|
|
*ptr = ' ';
|
|
prompt = ptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
dlg_calc_listh(int *height, int *list_height, int item_no)
|
|
{
|
|
/* calculate new height and list_height */
|
|
int rows = SLINES - (dialog_vars.begin_set ? dialog_vars.begin_y : 0);
|
|
if (rows - (*height) > 0) {
|
|
if (rows - (*height) > item_no)
|
|
*list_height = item_no;
|
|
else
|
|
*list_height = rows - (*height);
|
|
}
|
|
(*height) += (*list_height);
|
|
}
|
|
|
|
/* obsolete */
|
|
int
|
|
dlg_calc_listw(int item_no, char **items, int group)
|
|
{
|
|
int n, i, len1 = 0, len2 = 0;
|
|
for (i = 0; i < (item_no * group); i += group) {
|
|
if ((n = dlg_count_columns(items[i])) > len1)
|
|
len1 = n;
|
|
if ((n = dlg_count_columns(items[i + 1])) > len2)
|
|
len2 = n;
|
|
}
|
|
return len1 + len2;
|
|
}
|
|
|
|
int
|
|
dlg_calc_list_width(int item_no, DIALOG_LISTITEM * items)
|
|
{
|
|
int n, i, len1 = 0, len2 = 0;
|
|
for (i = 0; i < item_no; ++i) {
|
|
if ((n = dlg_count_columns(items[i].name)) > len1)
|
|
len1 = n;
|
|
if ((n = dlg_count_columns(items[i].text)) > len2)
|
|
len2 = n;
|
|
}
|
|
return len1 + len2;
|
|
}
|
|
|
|
char *
|
|
dlg_strempty(void)
|
|
{
|
|
static char empty[] = "";
|
|
return empty;
|
|
}
|
|
|
|
char *
|
|
dlg_strclone(const char *cprompt)
|
|
{
|
|
char *prompt = dlg_malloc(char, strlen(cprompt) + 1);
|
|
assert_ptr(prompt, "dlg_strclone");
|
|
strcpy(prompt, cprompt);
|
|
return prompt;
|
|
}
|
|
|
|
chtype
|
|
dlg_asciibox(chtype ch)
|
|
{
|
|
chtype result = 0;
|
|
|
|
if (ch == ACS_ULCORNER)
|
|
result = '+';
|
|
else if (ch == ACS_LLCORNER)
|
|
result = '+';
|
|
else if (ch == ACS_URCORNER)
|
|
result = '+';
|
|
else if (ch == ACS_LRCORNER)
|
|
result = '+';
|
|
else if (ch == ACS_HLINE)
|
|
result = '-';
|
|
else if (ch == ACS_VLINE)
|
|
result = '|';
|
|
else if (ch == ACS_LTEE)
|
|
result = '+';
|
|
else if (ch == ACS_RTEE)
|
|
result = '+';
|
|
else if (ch == ACS_UARROW)
|
|
result = '^';
|
|
else if (ch == ACS_DARROW)
|
|
result = 'v';
|
|
|
|
return result;
|
|
}
|
|
|
|
chtype
|
|
dlg_boxchar(chtype ch)
|
|
{
|
|
chtype result = dlg_asciibox(ch);
|
|
|
|
if (result != 0) {
|
|
if (dialog_vars.ascii_lines)
|
|
ch = result;
|
|
else if (dialog_vars.no_lines)
|
|
ch = ' ';
|
|
}
|
|
return ch;
|
|
}
|
|
|
|
int
|
|
dlg_box_x_ordinate(int width)
|
|
{
|
|
int x;
|
|
|
|
if (dialog_vars.begin_set == 1) {
|
|
x = dialog_vars.begin_x;
|
|
} else {
|
|
/* center dialog box on screen unless --begin-set */
|
|
x = (SCOLS - width) / 2;
|
|
}
|
|
return x;
|
|
}
|
|
|
|
int
|
|
dlg_box_y_ordinate(int height)
|
|
{
|
|
int y;
|
|
|
|
if (dialog_vars.begin_set == 1) {
|
|
y = dialog_vars.begin_y;
|
|
} else {
|
|
/* center dialog box on screen unless --begin-set */
|
|
y = (SLINES - height) / 2;
|
|
}
|
|
return y;
|
|
}
|
|
|
|
void
|
|
dlg_draw_title(WINDOW *win, const char *title)
|
|
{
|
|
if (title != NULL) {
|
|
chtype attr = A_NORMAL;
|
|
chtype save = dlg_get_attrs(win);
|
|
int x = centered(getmaxx(win), title);
|
|
|
|
wattrset(win, title_attr);
|
|
wmove(win, 0, x);
|
|
dlg_print_text(win, title, getmaxx(win) - x, &attr);
|
|
wattrset(win, save);
|
|
}
|
|
}
|
|
|
|
void
|
|
dlg_draw_bottom_box2(WINDOW *win, chtype on_left, chtype on_right, chtype on_inside)
|
|
{
|
|
int width = getmaxx(win);
|
|
int height = getmaxy(win);
|
|
int i;
|
|
|
|
wattrset(win, on_left);
|
|
(void) wmove(win, height - 3, 0);
|
|
(void) waddch(win, dlg_boxchar(ACS_LTEE));
|
|
for (i = 0; i < width - 2; i++)
|
|
(void) waddch(win, dlg_boxchar(ACS_HLINE));
|
|
wattrset(win, on_right);
|
|
(void) waddch(win, dlg_boxchar(ACS_RTEE));
|
|
wattrset(win, on_inside);
|
|
(void) wmove(win, height - 2, 1);
|
|
for (i = 0; i < width - 2; i++)
|
|
(void) waddch(win, ' ');
|
|
}
|
|
|
|
void
|
|
dlg_draw_bottom_box(WINDOW *win)
|
|
{
|
|
dlg_draw_bottom_box2(win, border_attr, dialog_attr, dialog_attr);
|
|
}
|
|
|
|
/*
|
|
* Remove a window, repainting everything else. This would be simpler if we
|
|
* used the panel library, but that is not _always_ available.
|
|
*/
|
|
void
|
|
dlg_del_window(WINDOW *win)
|
|
{
|
|
DIALOG_WINDOWS *p, *q, *r;
|
|
|
|
/*
|
|
* If --keep-window was set, do not delete/repaint the windows.
|
|
*/
|
|
if (dialog_vars.keep_window)
|
|
return;
|
|
|
|
/* Leave the main window untouched if there are no background windows.
|
|
* We do this so the current window will not be cleared on exit, allowing
|
|
* things like the infobox demo to run without flicker.
|
|
*/
|
|
if (dialog_state.getc_callbacks != 0) {
|
|
touchwin(stdscr);
|
|
wnoutrefresh(stdscr);
|
|
}
|
|
|
|
for (p = dialog_state.all_windows, q = r = 0; p != 0; r = p, p = p->next) {
|
|
if (p->normal == win) {
|
|
q = p; /* found a match - should be only one */
|
|
if (r == 0) {
|
|
dialog_state.all_windows = p->next;
|
|
} else {
|
|
r->next = p->next;
|
|
}
|
|
} else {
|
|
if (p->shadow != 0) {
|
|
touchwin(p->shadow);
|
|
wnoutrefresh(p->shadow);
|
|
}
|
|
touchwin(p->normal);
|
|
wnoutrefresh(p->normal);
|
|
}
|
|
}
|
|
|
|
if (q) {
|
|
if (dialog_state.all_windows != 0)
|
|
erase_childs_shadow(q);
|
|
del_subwindows(q->normal);
|
|
delwin(q->normal);
|
|
dlg_unregister_window(q->normal);
|
|
free(q);
|
|
}
|
|
doupdate();
|
|
}
|
|
|
|
/*
|
|
* Create a window, optionally with a shadow.
|
|
*/
|
|
WINDOW *
|
|
dlg_new_window(int height, int width, int y, int x)
|
|
{
|
|
return dlg_new_modal_window(stdscr, height, width, y, x);
|
|
}
|
|
|
|
/*
|
|
* "Modal" windows differ from normal ones by having a shadow in a window
|
|
* separate from the standard screen.
|
|
*/
|
|
WINDOW *
|
|
dlg_new_modal_window(WINDOW *parent, int height, int width, int y, int x)
|
|
{
|
|
WINDOW *win;
|
|
DIALOG_WINDOWS *p = dlg_calloc(DIALOG_WINDOWS, 1);
|
|
|
|
(void) parent;
|
|
if ((win = newwin(height, width, y, x)) == 0) {
|
|
dlg_exiterr("Can't make new window at (%d,%d), size (%d,%d).\n",
|
|
y, x, height, width);
|
|
}
|
|
p->next = dialog_state.all_windows;
|
|
p->normal = win;
|
|
dialog_state.all_windows = p;
|
|
#ifdef HAVE_COLOR
|
|
if (dialog_state.use_shadow) {
|
|
p->shadow = parent;
|
|
draw_childs_shadow(p);
|
|
}
|
|
#endif
|
|
|
|
(void) keypad(win, TRUE);
|
|
return win;
|
|
}
|
|
|
|
/*
|
|
* Move/Resize a window, optionally with a shadow.
|
|
*/
|
|
#ifdef KEY_RESIZE
|
|
void
|
|
dlg_move_window(WINDOW *win, int height, int width, int y, int x)
|
|
{
|
|
DIALOG_WINDOWS *p;
|
|
|
|
if (win != 0) {
|
|
dlg_ctl_size(height, width);
|
|
|
|
if ((p = find_window(win)) != 0) {
|
|
(void) wresize(win, height, width);
|
|
(void) mvwin(win, y, x);
|
|
#ifdef HAVE_COLOR
|
|
if (p->shadow != 0) {
|
|
if (dialog_state.use_shadow) {
|
|
(void) mvwin(p->shadow, y + SHADOW_ROWS, x + SHADOW_COLS);
|
|
} else {
|
|
p->shadow = 0;
|
|
}
|
|
}
|
|
#endif
|
|
(void) refresh();
|
|
|
|
#ifdef HAVE_COLOR
|
|
draw_childs_shadow(p);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
#endif /* KEY_RESIZE */
|
|
|
|
WINDOW *
|
|
dlg_sub_window(WINDOW *parent, int height, int width, int y, int x)
|
|
{
|
|
WINDOW *win;
|
|
|
|
if ((win = subwin(parent, height, width, y, x)) == 0) {
|
|
dlg_exiterr("Can't make sub-window at (%d,%d), size (%d,%d).\n",
|
|
y, x, height, width);
|
|
}
|
|
|
|
add_subwindow(parent, win);
|
|
(void) keypad(win, TRUE);
|
|
return win;
|
|
}
|
|
|
|
/* obsolete */
|
|
int
|
|
dlg_default_item(char **items, int llen)
|
|
{
|
|
int result = 0;
|
|
|
|
if (dialog_vars.default_item != 0) {
|
|
int count = 0;
|
|
while (*items != 0) {
|
|
if (!strcmp(dialog_vars.default_item, *items)) {
|
|
result = count;
|
|
break;
|
|
}
|
|
items += llen;
|
|
count++;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int
|
|
dlg_default_listitem(DIALOG_LISTITEM * items)
|
|
{
|
|
int result = 0;
|
|
|
|
if (dialog_vars.default_item != 0) {
|
|
int count = 0;
|
|
while (items->name != 0) {
|
|
if (!strcmp(dialog_vars.default_item, items->name)) {
|
|
result = count;
|
|
break;
|
|
}
|
|
++items;
|
|
count++;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Draw the string for item_help
|
|
*/
|
|
void
|
|
dlg_item_help(const char *txt)
|
|
{
|
|
if (USE_ITEM_HELP(txt)) {
|
|
chtype attr = A_NORMAL;
|
|
int y, x;
|
|
|
|
wattrset(stdscr, itemhelp_attr);
|
|
(void) wmove(stdscr, LINES - 1, 0);
|
|
(void) wclrtoeol(stdscr);
|
|
(void) addch(' ');
|
|
dlg_print_text(stdscr, txt, COLS - 1, &attr);
|
|
if (itemhelp_attr & A_COLOR) {
|
|
/* fill the remainder of the line with the window's attributes */
|
|
getyx(stdscr, y, x);
|
|
(void) y;
|
|
while (x < COLS) {
|
|
(void) addch(' ');
|
|
++x;
|
|
}
|
|
}
|
|
(void) wnoutrefresh(stdscr);
|
|
}
|
|
}
|
|
|
|
#ifndef HAVE_STRCASECMP
|
|
int
|
|
dlg_strcmp(const char *a, const char *b)
|
|
{
|
|
int ac, bc, cmp;
|
|
|
|
for (;;) {
|
|
ac = UCH(*a++);
|
|
bc = UCH(*b++);
|
|
if (isalpha(ac) && islower(ac))
|
|
ac = _toupper(ac);
|
|
if (isalpha(bc) && islower(bc))
|
|
bc = _toupper(bc);
|
|
cmp = ac - bc;
|
|
if (ac == 0 || bc == 0 || cmp != 0)
|
|
break;
|
|
}
|
|
return cmp;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Returns true if 'dst' points to a blank which follows another blank which
|
|
* is not a leading blank on a line.
|
|
*/
|
|
static bool
|
|
trim_blank(char *base, char *dst)
|
|
{
|
|
int count = 0;
|
|
|
|
while (dst-- != base) {
|
|
if (*dst == '\n') {
|
|
return FALSE;
|
|
} else if (*dst != ' ') {
|
|
return (count > 1);
|
|
} else {
|
|
count++;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Change embedded "\n" substrings to '\n' characters and tabs to single
|
|
* spaces. If there are no "\n"s, it will strip all extra spaces, for
|
|
* justification. If it has "\n"'s, it will preserve extra spaces. If cr_wrap
|
|
* is set, it will preserve '\n's.
|
|
*/
|
|
void
|
|
dlg_trim_string(char *s)
|
|
{
|
|
char *base = s;
|
|
char *p1;
|
|
char *p = s;
|
|
int has_newlines = !dialog_vars.no_nl_expand && (strstr(s, "\\n") != 0);
|
|
|
|
while (*p != '\0') {
|
|
if (*p == TAB && !dialog_vars.nocollapse)
|
|
*p = ' ';
|
|
|
|
if (has_newlines) { /* If prompt contains "\n" strings */
|
|
if (*p == '\\' && *(p + 1) == 'n') {
|
|
*s++ = '\n';
|
|
p += 2;
|
|
p1 = p;
|
|
/*
|
|
* Handle end of lines intelligently. If '\n' follows "\n"
|
|
* then ignore the '\n'. This eliminates the need to escape
|
|
* the '\n' character (no need to use "\n\").
|
|
*/
|
|
while (*p1 == ' ')
|
|
p1++;
|
|
if (*p1 == '\n')
|
|
p = p1 + 1;
|
|
} else if (*p == '\n') {
|
|
if (dialog_vars.cr_wrap)
|
|
*s++ = *p++;
|
|
else {
|
|
/* Replace the '\n' with a space if cr_wrap is not set */
|
|
if (!trim_blank(base, s))
|
|
*s++ = ' ';
|
|
p++;
|
|
}
|
|
} else /* If *p != '\n' */
|
|
*s++ = *p++;
|
|
} else if (dialog_vars.trim_whitespace) {
|
|
if (*p == ' ') {
|
|
if (*(s - 1) != ' ') {
|
|
*s++ = ' ';
|
|
p++;
|
|
} else
|
|
p++;
|
|
} else if (*p == '\n') {
|
|
if (dialog_vars.cr_wrap)
|
|
*s++ = *p++;
|
|
else if (*(s - 1) != ' ') {
|
|
/* Strip '\n's if cr_wrap is not set. */
|
|
*s++ = ' ';
|
|
p++;
|
|
} else
|
|
p++;
|
|
} else
|
|
*s++ = *p++;
|
|
} else { /* If there are no "\n" strings */
|
|
if (*p == ' ' && !dialog_vars.nocollapse) {
|
|
if (!trim_blank(base, s))
|
|
*s++ = *p;
|
|
p++;
|
|
} else
|
|
*s++ = *p++;
|
|
}
|
|
}
|
|
|
|
*s = '\0';
|
|
}
|
|
|
|
void
|
|
dlg_set_focus(WINDOW *parent, WINDOW *win)
|
|
{
|
|
if (win != 0) {
|
|
(void) wmove(parent,
|
|
getpary(win) + getcury(win),
|
|
getparx(win) + getcurx(win));
|
|
(void) wnoutrefresh(win);
|
|
(void) doupdate();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Returns the nominal maximum buffer size.
|
|
*/
|
|
int
|
|
dlg_max_input(int max_len)
|
|
{
|
|
if (dialog_vars.max_input != 0 && dialog_vars.max_input < MAX_LEN)
|
|
max_len = dialog_vars.max_input;
|
|
|
|
return max_len;
|
|
}
|
|
|
|
/*
|
|
* Free storage used for the result buffer.
|
|
*/
|
|
void
|
|
dlg_clr_result(void)
|
|
{
|
|
if (dialog_vars.input_length) {
|
|
dialog_vars.input_length = 0;
|
|
if (dialog_vars.input_result)
|
|
free(dialog_vars.input_result);
|
|
}
|
|
dialog_vars.input_result = 0;
|
|
}
|
|
|
|
/*
|
|
* Setup a fixed-buffer for the result.
|
|
*/
|
|
char *
|
|
dlg_set_result(const char *string)
|
|
{
|
|
unsigned need = string ? (unsigned) strlen(string) + 1 : 0;
|
|
|
|
/* inputstr.c needs a fixed buffer */
|
|
if (need < MAX_LEN)
|
|
need = MAX_LEN;
|
|
|
|
/*
|
|
* If the buffer is not big enough, allocate a new one.
|
|
*/
|
|
if (dialog_vars.input_length != 0
|
|
|| dialog_vars.input_result == 0
|
|
|| need > MAX_LEN) {
|
|
|
|
dlg_clr_result();
|
|
|
|
dialog_vars.input_length = need;
|
|
dialog_vars.input_result = dlg_malloc(char, need);
|
|
assert_ptr(dialog_vars.input_result, "dlg_set_result");
|
|
}
|
|
|
|
strcpy(dialog_vars.input_result, string ? string : "");
|
|
|
|
return dialog_vars.input_result;
|
|
}
|
|
|
|
/*
|
|
* Accumulate results in dynamically allocated buffer.
|
|
* If input_length is zero, it is a MAX_LEN buffer belonging to the caller.
|
|
*/
|
|
void
|
|
dlg_add_result(const char *string)
|
|
{
|
|
unsigned have = (dialog_vars.input_result
|
|
? (unsigned) strlen(dialog_vars.input_result)
|
|
: 0);
|
|
unsigned want = (unsigned) strlen(string) + 1 + have;
|
|
|
|
if ((want >= MAX_LEN)
|
|
|| (dialog_vars.input_length != 0)
|
|
|| (dialog_vars.input_result == 0)) {
|
|
|
|
if (dialog_vars.input_length == 0
|
|
|| dialog_vars.input_result == 0) {
|
|
|
|
char *save_result = dialog_vars.input_result;
|
|
|
|
dialog_vars.input_length = want * 2;
|
|
dialog_vars.input_result = dlg_malloc(char, dialog_vars.input_length);
|
|
assert_ptr(dialog_vars.input_result, "dlg_add_result malloc");
|
|
dialog_vars.input_result[0] = '\0';
|
|
if (save_result != 0)
|
|
strcpy(dialog_vars.input_result, save_result);
|
|
} else if (want >= dialog_vars.input_length) {
|
|
dialog_vars.input_length = want * 2;
|
|
dialog_vars.input_result = dlg_realloc(char,
|
|
dialog_vars.input_length,
|
|
dialog_vars.input_result);
|
|
assert_ptr(dialog_vars.input_result, "dlg_add_result realloc");
|
|
}
|
|
}
|
|
strcat(dialog_vars.input_result, string);
|
|
}
|
|
|
|
/*
|
|
* These are characters that (aside from the quote-delimiter) will have to
|
|
* be escaped in a single- or double-quoted string.
|
|
*/
|
|
#define FIX_SINGLE "\n\\"
|
|
#define FIX_DOUBLE FIX_SINGLE "[]{}?*;`~#$^&()|<>"
|
|
|
|
/*
|
|
* Returns the quote-delimiter.
|
|
*/
|
|
static const char *
|
|
quote_delimiter(void)
|
|
{
|
|
return dialog_vars.single_quoted ? "'" : "\"";
|
|
}
|
|
|
|
/*
|
|
* Returns true if we should quote the given string.
|
|
*/
|
|
static bool
|
|
must_quote(char *string)
|
|
{
|
|
bool code = FALSE;
|
|
|
|
if (*string != '\0') {
|
|
size_t len = strlen(string);
|
|
if (strcspn(string, quote_delimiter()) != len)
|
|
code = TRUE;
|
|
else if (strcspn(string, "\n\t ") != len)
|
|
code = TRUE;
|
|
else
|
|
code = (strcspn(string, FIX_DOUBLE) != len);
|
|
} else {
|
|
code = TRUE;
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
/*
|
|
* Add a quoted string to the result buffer.
|
|
*/
|
|
void
|
|
dlg_add_quoted(char *string)
|
|
{
|
|
char temp[2];
|
|
const char *my_quote = quote_delimiter();
|
|
const char *must_fix = (dialog_vars.single_quoted
|
|
? FIX_SINGLE
|
|
: FIX_DOUBLE);
|
|
|
|
if (must_quote(string)) {
|
|
temp[1] = '\0';
|
|
dlg_add_result(my_quote);
|
|
while (*string != '\0') {
|
|
temp[0] = *string++;
|
|
if (strchr(my_quote, *temp) || strchr(must_fix, *temp))
|
|
dlg_add_result("\\");
|
|
dlg_add_result(temp);
|
|
}
|
|
dlg_add_result(my_quote);
|
|
} else {
|
|
dlg_add_result(string);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* When adding a result, make that depend on whether "--quoted" is used.
|
|
*/
|
|
void
|
|
dlg_add_string(char *string)
|
|
{
|
|
if (dialog_vars.quoted) {
|
|
dlg_add_quoted(string);
|
|
} else {
|
|
dlg_add_result(string);
|
|
}
|
|
}
|
|
|
|
bool
|
|
dlg_need_separator(void)
|
|
{
|
|
bool result = FALSE;
|
|
|
|
if (dialog_vars.output_separator) {
|
|
result = TRUE;
|
|
} else if (dialog_vars.input_result && *(dialog_vars.input_result)) {
|
|
result = TRUE;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void
|
|
dlg_add_separator(void)
|
|
{
|
|
const char *separator = (dialog_vars.separate_output) ? "\n" : " ";
|
|
|
|
if (dialog_vars.output_separator)
|
|
separator = dialog_vars.output_separator;
|
|
|
|
dlg_add_result(separator);
|
|
}
|
|
|
|
/*
|
|
* Some widgets support only one value of a given variable - save/restore the
|
|
* global dialog_vars so we can override it consistently.
|
|
*/
|
|
void
|
|
dlg_save_vars(DIALOG_VARS * vars)
|
|
{
|
|
*vars = dialog_vars;
|
|
}
|
|
|
|
/*
|
|
* Most of the data in DIALOG_VARS is normally set by command-line options.
|
|
* The input_result member is an exception; it is normally set by the dialog
|
|
* library to return result values.
|
|
*/
|
|
void
|
|
dlg_restore_vars(DIALOG_VARS * vars)
|
|
{
|
|
char *save_result = dialog_vars.input_result;
|
|
unsigned save_length = dialog_vars.input_length;
|
|
|
|
dialog_vars = *vars;
|
|
dialog_vars.input_result = save_result;
|
|
dialog_vars.input_length = save_length;
|
|
}
|
|
|
|
/*
|
|
* Called each time a widget is invoked which may do output, increment a count.
|
|
*/
|
|
void
|
|
dlg_does_output(void)
|
|
{
|
|
dialog_state.output_count += 1;
|
|
}
|
|
|
|
/*
|
|
* Compatibility for different versions of curses.
|
|
*/
|
|
#if !(defined(HAVE_GETBEGX) && defined(HAVE_GETBEGY))
|
|
int
|
|
dlg_getbegx(WINDOW *win)
|
|
{
|
|
int y, x;
|
|
getbegyx(win, y, x);
|
|
return x;
|
|
}
|
|
int
|
|
dlg_getbegy(WINDOW *win)
|
|
{
|
|
int y, x;
|
|
getbegyx(win, y, x);
|
|
return y;
|
|
}
|
|
#endif
|
|
|
|
#if !(defined(HAVE_GETCURX) && defined(HAVE_GETCURY))
|
|
int
|
|
dlg_getcurx(WINDOW *win)
|
|
{
|
|
int y, x;
|
|
getyx(win, y, x);
|
|
return x;
|
|
}
|
|
int
|
|
dlg_getcury(WINDOW *win)
|
|
{
|
|
int y, x;
|
|
getyx(win, y, x);
|
|
return y;
|
|
}
|
|
#endif
|
|
|
|
#if !(defined(HAVE_GETMAXX) && defined(HAVE_GETMAXY))
|
|
int
|
|
dlg_getmaxx(WINDOW *win)
|
|
{
|
|
int y, x;
|
|
getmaxyx(win, y, x);
|
|
return x;
|
|
}
|
|
int
|
|
dlg_getmaxy(WINDOW *win)
|
|
{
|
|
int y, x;
|
|
getmaxyx(win, y, x);
|
|
return y;
|
|
}
|
|
#endif
|
|
|
|
#if !(defined(HAVE_GETPARX) && defined(HAVE_GETPARY))
|
|
int
|
|
dlg_getparx(WINDOW *win)
|
|
{
|
|
int y, x;
|
|
getparyx(win, y, x);
|
|
return x;
|
|
}
|
|
int
|
|
dlg_getpary(WINDOW *win)
|
|
{
|
|
int y, x;
|
|
getparyx(win, y, x);
|
|
return y;
|
|
}
|
|
#endif
|
|
|
|
#ifdef NEED_WGETPARENT
|
|
WINDOW *
|
|
dlg_wgetparent(WINDOW *win)
|
|
{
|
|
#undef wgetparent
|
|
WINDOW *result = 0;
|
|
DIALOG_WINDOWS *p;
|
|
|
|
for (p = dialog_state.all_subwindows; p != 0; p = p->next) {
|
|
if (p->shadow == win) {
|
|
result = p->normal;
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
#endif
|