mirror of
https://git.zap.org.au/git/trader.git
synced 2025-02-02 15:08:13 -05:00
2559 lines
57 KiB
C
2559 lines
57 KiB
C
/************************************************************************
|
|
* *
|
|
* Star Traders: A Game of Interstellar Trading *
|
|
* Copyright (C) 1990-2011, John Zaitseff *
|
|
* *
|
|
************************************************************************/
|
|
|
|
/*
|
|
Author: John Zaitseff <J.Zaitseff@zap.org.au>
|
|
$Id$
|
|
|
|
This file, intf.c, contains the actual implementation of basic text
|
|
input/output routines as used in Star Traders.
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify it
|
|
under the terms of the GNU General Public License as published by the
|
|
Free Software Foundation, either version 3 of the License, or (at your
|
|
option) any later version.
|
|
|
|
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
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see http://www.gnu.org/licenses/.
|
|
*/
|
|
|
|
|
|
#include "trader.h"
|
|
|
|
|
|
/************************************************************************
|
|
* Module-specific constants and type declarations *
|
|
************************************************************************/
|
|
|
|
typedef struct txwin {
|
|
WINDOW *win; // Pointer to window structure
|
|
struct txwin *next; // Next window in stack
|
|
struct txwin *prev; // Previous window in stack
|
|
} txwin_t;
|
|
|
|
|
|
// Declarations for argument processing in prepstr()
|
|
|
|
#define MAXFMTARGS 8 // Maximum number of positional arguments
|
|
|
|
enum argument_type {
|
|
TYPE_NONE, // No type yet assigned
|
|
TYPE_INT, // int
|
|
TYPE_LONGINT, // long int
|
|
TYPE_DOUBLE, // double
|
|
TYPE_STRING // const char *
|
|
};
|
|
|
|
struct argument {
|
|
enum argument_type a_type;
|
|
union a {
|
|
int a_int;
|
|
long int a_longint;
|
|
double a_double;
|
|
const char *a_string;
|
|
} a;
|
|
};
|
|
|
|
|
|
/************************************************************************
|
|
* Global variable definitions *
|
|
************************************************************************/
|
|
|
|
WINDOW *curwin = NULL; // Top-most (current) window
|
|
|
|
|
|
// Character renditions (attributes) used by Star Traders
|
|
|
|
chtype attr_root_window; // Root window (behind all others)
|
|
chtype attr_game_title; // One-line game title at top
|
|
|
|
chtype attr_normal_window; // Normal window background
|
|
chtype attr_title; // Normal window title
|
|
chtype attr_subtitle; // Normal window subtitle
|
|
chtype attr_normal; // Normal window text
|
|
chtype attr_highlight; // Normal window highlighted string
|
|
chtype attr_blink; // Blinking text in normal window
|
|
chtype attr_keycode; // Make keycodes like <1> stand out
|
|
chtype attr_choice; // Make map/company choices stand out
|
|
chtype attr_input_field; // Background for input text field
|
|
chtype attr_waitforkey; // "Press any key", normal window
|
|
|
|
chtype attr_map_window; // Map window background
|
|
chtype attr_mapwin_title; // Map window title (player name, turn)
|
|
chtype attr_mapwin_highlight; // Map window title highlight
|
|
chtype attr_mapwin_blink; // Map window title blinking text
|
|
chtype attr_map_empty; // On map, empty space
|
|
chtype attr_map_outpost; // On map, outpost
|
|
chtype attr_map_star; // On map, star
|
|
chtype attr_map_company; // On map, company
|
|
chtype attr_map_choice; // On map, a choice of moves
|
|
|
|
chtype attr_status_window; // Status window background
|
|
|
|
chtype attr_error_window; // Error message window background
|
|
chtype attr_error_title; // Error window title
|
|
chtype attr_error_normal; // Error window ordinary text
|
|
chtype attr_error_highlight; // Error window highlighted string
|
|
chtype attr_error_waitforkey; // "Press any key", error window
|
|
|
|
|
|
/************************************************************************
|
|
* Module-specific variables *
|
|
************************************************************************/
|
|
|
|
txwin_t *topwin = NULL; // Top-most txwin structure
|
|
txwin_t *firstwin = NULL; // First (bottom-most) txwin structure
|
|
|
|
|
|
/************************************************************************
|
|
* Module-specific function prototypes *
|
|
************************************************************************/
|
|
|
|
/*
|
|
Function: init_title - Draw the main window title
|
|
Parameters: (none)
|
|
Returns: (nothing)
|
|
|
|
This function draws the main window game title, "Star Traders", and
|
|
clears the rest of the screen. It does NOT call wrefresh().
|
|
*/
|
|
static void init_title (void);
|
|
|
|
|
|
/*
|
|
Function: sigterm_handler - Handle program termination signals
|
|
Parameters: sig - Signal number
|
|
Returns: (nothing)
|
|
|
|
This function handles termination signals (like SIGINT, SIGTERM and
|
|
SIGQUIT) by clearing the screen, uninstalling itself and reraising the
|
|
signal.
|
|
*/
|
|
static void sigterm_handler (int sig);
|
|
|
|
|
|
/*
|
|
Function: txresize - Handle a terminal resize event
|
|
Parameters: (none)
|
|
Returns: (nothing)
|
|
|
|
This function handles a SIGWINCH (terminal window size changed) event
|
|
by refreshing Curses windows as appropriate.
|
|
*/
|
|
#ifdef HANDLE_RESIZE_EVENTS
|
|
static void txresize (void);
|
|
#endif
|
|
|
|
|
|
/*
|
|
Function: prepstr_addch - Add a character to the prepstr buffer
|
|
Parameters: chbuf - Pointer to chtype pointer in which to store string
|
|
chbufsize - Pointer to number of chtype elements in chbuf
|
|
attr - Character rendition to use
|
|
maxlines - Maximum number of screen lines to use
|
|
maxwidth - Maximum width of each line, in chars
|
|
line - Pointer to current line number
|
|
width - Pointer to current line width
|
|
lastspc - Pointer to const char * pointer to last space
|
|
widthspc - Pointer to width just before last space
|
|
widthbuf - Pointer to buffer to store widths of each line
|
|
widthbufsize - Number of int elements in widthbuf
|
|
str - Pointer to const char * pointer to string
|
|
Returns: int - -1 on error (with errno set), 0 otherwise
|
|
|
|
This helper function adds the character **str to **chbuf, using attr as
|
|
the character rendition (attributes), incrementing both *str and *chbuf
|
|
and decrementing *chbufsize. If a string is too long for the current
|
|
line, a previous space in the current line is converted to a new line
|
|
(if possible), else a new line is inserted into the current location
|
|
(if not on the last line). *line, *width, *lastspc, *widthspc and
|
|
widthbuf[] are all updated appropriately.
|
|
*/
|
|
static int prepstr_addch (chtype *restrict *restrict chbuf,
|
|
int *restrict chbufsize, chtype attr,
|
|
int maxlines, int maxwidth, int *restrict line,
|
|
int *restrict width,
|
|
chtype *restrict *restrict lastspc,
|
|
int *restrict widthspc, int *restrict widthbuf,
|
|
int widthbufsize,
|
|
const char *restrict *restrict str);
|
|
|
|
|
|
/*
|
|
Function: txinput_fixup - Copy strings with fixup
|
|
Parameters: dest - Destination buffer of size BUFSIZE
|
|
src - Source buffer of size BUFSIZE
|
|
isfloat - True if src contains a floating point number
|
|
Returns: (nothing)
|
|
|
|
This helper function copies the string in src to dest, performing
|
|
certain fixups along the way. In particular, thousands separators are
|
|
removed and (if isfloat is true) the monetary radix (decimal point) is
|
|
replaced by the normal one.
|
|
|
|
This function is used by gettxdouble() and gettxlong() to share some
|
|
common code.
|
|
*/
|
|
static void txinput_fixup (char *restrict dest, char *restrict src,
|
|
bool isfloat);
|
|
|
|
|
|
/************************************************************************
|
|
* Basic text input/output function definitions *
|
|
************************************************************************/
|
|
|
|
/* These functions are documented either in the file "intf.h" or in the
|
|
comments above. */
|
|
|
|
|
|
/***********************************************************************/
|
|
// init_screen: Initialise the screen (terminal display)
|
|
|
|
void init_screen (void)
|
|
{
|
|
struct sigaction sa;
|
|
|
|
|
|
// Initialise signal handlers
|
|
sa.sa_handler = sigterm_handler;
|
|
sa.sa_flags = 0;
|
|
sigemptyset(&sa.sa_mask);
|
|
sigaddset(&sa.sa_mask, SIGINT);
|
|
sigaddset(&sa.sa_mask, SIGTERM);
|
|
sigaddset(&sa.sa_mask, SIGQUIT);
|
|
|
|
if (sigaction(SIGINT, &sa, NULL) == -1) {
|
|
errno_exit("sigaction(SIGINT)");
|
|
}
|
|
if (sigaction(SIGTERM, &sa, NULL) == -1) {
|
|
errno_exit("sigaction(SIGTERM)");
|
|
}
|
|
if (sigaction(SIGQUIT, &sa, NULL) == -1) {
|
|
errno_exit("sigaction(SIGQUIT)");
|
|
}
|
|
|
|
// Initialise the screen
|
|
initscr();
|
|
|
|
if (COLS < MIN_COLS || LINES < MIN_LINES) {
|
|
err_exit(_("terminal size is too small (%d x %d required)"),
|
|
MIN_COLS, MIN_LINES);
|
|
}
|
|
|
|
// Initialise variables controlling the stack of windows
|
|
curwin = stdscr;
|
|
topwin = NULL;
|
|
firstwin = NULL;
|
|
|
|
noecho();
|
|
curs_set(CURS_OFF);
|
|
raw();
|
|
|
|
// Initialise all character renditions used in the game
|
|
if (! option_no_color && has_colors()) {
|
|
start_color();
|
|
|
|
init_pair(1, COLOR_BLACK, COLOR_WHITE);
|
|
init_pair(2, COLOR_BLUE, COLOR_BLACK);
|
|
init_pair(3, COLOR_GREEN, COLOR_BLACK);
|
|
init_pair(4, COLOR_CYAN, COLOR_BLUE);
|
|
init_pair(5, COLOR_RED, COLOR_BLACK);
|
|
init_pair(6, COLOR_YELLOW, COLOR_BLACK);
|
|
init_pair(7, COLOR_YELLOW, COLOR_BLUE);
|
|
init_pair(8, COLOR_YELLOW, COLOR_CYAN);
|
|
init_pair(9, COLOR_WHITE, COLOR_BLACK);
|
|
init_pair(10, COLOR_WHITE, COLOR_BLUE);
|
|
init_pair(11, COLOR_WHITE, COLOR_RED);
|
|
|
|
attr_root_window = COLOR_PAIR(9);
|
|
attr_game_title = COLOR_PAIR(8) | A_BOLD;
|
|
|
|
attr_normal_window = COLOR_PAIR(10);
|
|
attr_title = COLOR_PAIR(6) | A_BOLD;
|
|
attr_subtitle = COLOR_PAIR(9);
|
|
attr_normal = attr_normal_window;
|
|
attr_highlight = COLOR_PAIR(7) | A_BOLD;
|
|
attr_blink = COLOR_PAIR(7) | A_BOLD | A_BLINK;
|
|
attr_keycode = COLOR_PAIR(6) | A_BOLD;
|
|
attr_choice = COLOR_PAIR(11) | A_BOLD;
|
|
attr_input_field = COLOR_PAIR(9);
|
|
attr_waitforkey = COLOR_PAIR(4);
|
|
|
|
attr_map_window = COLOR_PAIR(9);
|
|
attr_mapwin_title = COLOR_PAIR(10);
|
|
attr_mapwin_highlight = COLOR_PAIR(7) | A_BOLD;
|
|
attr_mapwin_blink = COLOR_PAIR(7) | A_BOLD | A_BLINK;
|
|
attr_map_empty = COLOR_PAIR(2) | A_BOLD;
|
|
attr_map_outpost = COLOR_PAIR(3) | A_BOLD;
|
|
attr_map_star = COLOR_PAIR(6) | A_BOLD;
|
|
attr_map_company = COLOR_PAIR(5) | A_BOLD;
|
|
attr_map_choice = COLOR_PAIR(11) | A_BOLD;
|
|
|
|
attr_status_window = COLOR_PAIR(1);
|
|
|
|
attr_error_window = COLOR_PAIR(11);
|
|
attr_error_title = COLOR_PAIR(6) | A_BOLD;
|
|
attr_error_normal = attr_error_window;
|
|
attr_error_highlight = COLOR_PAIR(11) | A_BOLD;
|
|
attr_error_waitforkey = COLOR_PAIR(11);
|
|
|
|
} else {
|
|
// No colour is to be used
|
|
|
|
attr_root_window = A_NORMAL;
|
|
attr_game_title = A_REVERSE | A_BOLD;
|
|
|
|
attr_normal_window = A_NORMAL;
|
|
attr_title = A_REVERSE;
|
|
attr_subtitle = A_REVERSE;
|
|
attr_normal = attr_normal_window;
|
|
attr_highlight = A_BOLD;
|
|
attr_blink = A_BOLD | A_BLINK;
|
|
attr_keycode = A_REVERSE;
|
|
attr_choice = A_REVERSE;
|
|
attr_input_field = A_BOLD | '_';
|
|
attr_waitforkey = A_NORMAL;
|
|
|
|
attr_map_window = A_NORMAL;
|
|
attr_mapwin_title = A_NORMAL;
|
|
attr_mapwin_highlight = A_BOLD;
|
|
attr_mapwin_blink = A_BOLD | A_BLINK;
|
|
attr_map_empty = A_NORMAL;
|
|
attr_map_outpost = A_NORMAL;
|
|
attr_map_star = A_BOLD;
|
|
attr_map_company = A_BOLD;
|
|
attr_map_choice = A_REVERSE;
|
|
|
|
attr_status_window = A_REVERSE;
|
|
|
|
attr_error_window = A_REVERSE;
|
|
attr_error_title = A_BOLD;
|
|
attr_error_normal = attr_error_window;
|
|
attr_error_highlight = A_REVERSE;
|
|
attr_error_waitforkey = A_REVERSE;
|
|
}
|
|
|
|
init_title();
|
|
refresh();
|
|
}
|
|
|
|
|
|
/***********************************************************************/
|
|
// end_screen: Deinitialise the screen (terminal display)
|
|
|
|
void end_screen (void)
|
|
{
|
|
delalltxwin();
|
|
|
|
curs_set(CURS_ON);
|
|
clear();
|
|
refresh();
|
|
endwin();
|
|
|
|
curwin = NULL;
|
|
topwin = NULL;
|
|
firstwin = NULL;
|
|
}
|
|
|
|
|
|
/***********************************************************************/
|
|
// init_title: Draw the main window title
|
|
|
|
void init_title (void)
|
|
{
|
|
chtype *chbuf;
|
|
int width;
|
|
int lines;
|
|
|
|
|
|
bkgd(attr_root_window);
|
|
clear();
|
|
|
|
move(0, 0);
|
|
for (int i = 0; i < COLS; i++) {
|
|
addch(attr_game_title | ' ');
|
|
}
|
|
|
|
chbuf = malloc(BUFSIZE * sizeof(chtype));
|
|
if (chbuf == NULL) {
|
|
err_exit_nomem();
|
|
}
|
|
|
|
lines = prepstr(chbuf, BUFSIZE, attr_game_title, 0, 0, 1, COLS,
|
|
&width, 1, "%s", _("Star Traders"));
|
|
if (lines < 0) {
|
|
errno_exit("init_title");
|
|
}
|
|
|
|
pr_center(stdscr, 0, 0, chbuf, lines, &width);
|
|
attrset(attr_root_window);
|
|
free(chbuf);
|
|
}
|
|
|
|
|
|
/***********************************************************************/
|
|
// sigterm_handler: Handle program termination signals
|
|
|
|
void sigterm_handler (int sig)
|
|
{
|
|
struct sigaction sa;
|
|
|
|
|
|
/* The following Curses functions are NOT async-signal-safe (ie, are
|
|
not reentrant) as they may well call malloc() or free(). However,
|
|
it does allow us to terminate with the correct signal without
|
|
having convoluted code in the main program. */
|
|
|
|
curs_set(CURS_ON);
|
|
clear();
|
|
refresh();
|
|
endwin();
|
|
|
|
// Reraise the same signal, using the system-default handler
|
|
sa.sa_handler = SIG_DFL;
|
|
sa.sa_flags = 0;
|
|
sigemptyset(&sa.sa_mask);
|
|
|
|
sigaction(sig, &sa, NULL);
|
|
raise(sig);
|
|
}
|
|
|
|
|
|
/***********************************************************************/
|
|
// newtxwin: Create a new window, inserted into window stack
|
|
|
|
WINDOW *newtxwin (int nlines, int ncols, int begin_y, int begin_x,
|
|
bool dofill, chtype bkgd_attr)
|
|
{
|
|
WINDOW *win;
|
|
txwin_t *nw;
|
|
|
|
|
|
// Centre the window, if required
|
|
if (begin_y == WCENTER) {
|
|
begin_y = (nlines == 0) ? 0 : (LINES - nlines) / 2;
|
|
}
|
|
if (begin_x == WCENTER) {
|
|
begin_x = (ncols == 0) ? 0 : (COLS - ncols) / 2;
|
|
}
|
|
|
|
// Create the new window
|
|
|
|
win = newwin(nlines, ncols, begin_y, begin_x);
|
|
if (win == NULL) {
|
|
err_exit_nomem();
|
|
}
|
|
|
|
nw = malloc(sizeof(txwin_t));
|
|
if (nw == NULL) {
|
|
err_exit_nomem();
|
|
}
|
|
|
|
// Insert the new window into the txwin stack
|
|
|
|
nw->win = win;
|
|
nw->next = NULL;
|
|
nw->prev = topwin;
|
|
|
|
if (topwin != NULL) {
|
|
topwin->next = nw;
|
|
}
|
|
|
|
topwin = nw;
|
|
curwin = win;
|
|
|
|
if (firstwin == NULL) {
|
|
firstwin = nw;
|
|
}
|
|
|
|
// Paint the background and border, if required
|
|
|
|
if (dofill) {
|
|
wbkgd(win, bkgd_attr);
|
|
box(win, 0, 0);
|
|
}
|
|
|
|
return win;
|
|
}
|
|
|
|
|
|
/***********************************************************************/
|
|
// deltxwin: Delete the top-most window in the window stack
|
|
|
|
int deltxwin (void)
|
|
{
|
|
txwin_t *cur, *prev;
|
|
int ret;
|
|
|
|
|
|
if (topwin == NULL) {
|
|
return ERR;
|
|
}
|
|
|
|
// Remove window from the txwin stack
|
|
|
|
cur = topwin;
|
|
prev = topwin->prev;
|
|
topwin = prev;
|
|
|
|
if (prev != NULL) {
|
|
prev->next = NULL;
|
|
curwin = prev->win;
|
|
} else {
|
|
firstwin = NULL;
|
|
curwin = stdscr;
|
|
}
|
|
|
|
ret = delwin(cur->win);
|
|
free(cur);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/***********************************************************************/
|
|
// delalltxwin: Delete all windows in the window stack
|
|
|
|
int delalltxwin (void)
|
|
{
|
|
while (topwin != NULL) {
|
|
deltxwin();
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
|
|
/***********************************************************************/
|
|
// txrefresh: Redraw all windows in the window stack
|
|
|
|
int txrefresh (void)
|
|
{
|
|
touchwin(stdscr);
|
|
wnoutrefresh(stdscr);
|
|
|
|
for (txwin_t *p = firstwin; p != NULL; p = p->next) {
|
|
touchwin(p->win);
|
|
wnoutrefresh(p->win);
|
|
}
|
|
|
|
return doupdate();
|
|
}
|
|
|
|
|
|
/***********************************************************************/
|
|
// txresize: Handle a terminal resize event
|
|
|
|
#ifdef HANDLE_RESIZE_EVENTS
|
|
|
|
void txresize (void)
|
|
{
|
|
/* The current implementation cannot resize windows per se: a given
|
|
window would have to be destroyed and recreated in the new
|
|
location, then redrawn, most likely via a call-back function.
|
|
We just redraw the game title, refresh all windows and hope for
|
|
the best! */
|
|
|
|
init_title();
|
|
txrefresh();
|
|
}
|
|
|
|
#endif // HANDLE_RESIZE_EVENTS
|
|
|
|
|
|
/***********************************************************************/
|
|
// txdlgbox: Display a dialog box and wait for any key
|
|
|
|
int txdlgbox (int maxlines, int ncols, int begin_y, int begin_x,
|
|
chtype bkgd_attr, chtype title_attr, chtype norm_attr,
|
|
chtype alt1_attr, chtype alt2_attr, chtype keywait_attr,
|
|
const char *restrict boxtitle, const char *restrict format, ...)
|
|
{
|
|
bool usetitle = (boxtitle != NULL);
|
|
|
|
chtype *chbuf;
|
|
int *widthbuf;
|
|
int lines;
|
|
va_list args;
|
|
|
|
|
|
assert(maxlines > 0);
|
|
|
|
chbuf = malloc(BUFSIZE * sizeof(chtype));
|
|
if (chbuf == NULL) {
|
|
err_exit_nomem();
|
|
}
|
|
|
|
widthbuf = malloc(maxlines * sizeof(int));
|
|
if (widthbuf == NULL) {
|
|
err_exit_nomem();
|
|
}
|
|
|
|
va_start(args, format);
|
|
lines = vprepstr(chbuf, BUFSIZE, norm_attr, alt1_attr, alt2_attr, maxlines,
|
|
ncols - 4, widthbuf, maxlines, format, args);
|
|
va_end(args);
|
|
|
|
if (lines < 0) {
|
|
errno_exit(_("txdlgbox: `%s'"), format);
|
|
}
|
|
|
|
newtxwin(usetitle ? lines + 6 : lines + 5, ncols, begin_y, begin_x,
|
|
true, bkgd_attr);
|
|
|
|
if (usetitle) {
|
|
chtype *titlebuf;
|
|
int titlewidth;
|
|
int titlelines;
|
|
|
|
titlebuf = malloc(BUFSIZE * sizeof(chtype));
|
|
if (titlebuf == NULL) {
|
|
err_exit_nomem();
|
|
}
|
|
|
|
titlelines = prepstr(titlebuf, BUFSIZE, title_attr, bkgd_attr,
|
|
norm_attr, 1, ncols - 4, &titlewidth, 1,
|
|
"%s", boxtitle);
|
|
if (titlelines < 0) {
|
|
errno_exit(_("txdlgbox: `%s'"), boxtitle);
|
|
}
|
|
|
|
pr_center(curwin, 1, 0, titlebuf, titlelines, &titlewidth);
|
|
|
|
free(titlebuf);
|
|
}
|
|
|
|
pr_center(curwin, usetitle ? 3 : 2, 0, chbuf, lines, widthbuf);
|
|
wait_for_key(curwin, getmaxy(curwin) - 2, keywait_attr);
|
|
deltxwin();
|
|
|
|
free(widthbuf);
|
|
free(chbuf);
|
|
return OK;
|
|
}
|
|
|
|
|
|
/***********************************************************************/
|
|
// prepstr: Prepare a string for printing to screen
|
|
|
|
int prepstr (chtype *restrict chbuf, int chbufsize, chtype attr_norm,
|
|
chtype attr_alt1, chtype attr_alt2, int maxlines, int maxwidth,
|
|
int *restrict widthbuf, int widthbufsize,
|
|
const char *restrict format, ...)
|
|
{
|
|
va_list args;
|
|
int lines;
|
|
|
|
|
|
va_start(args, format);
|
|
lines = vprepstr(chbuf, chbufsize, attr_norm, attr_alt1, attr_alt2,
|
|
maxlines, maxwidth, widthbuf, widthbufsize, format,
|
|
args);
|
|
va_end(args);
|
|
return lines;
|
|
}
|
|
|
|
|
|
/***********************************************************************/
|
|
// prepstr_addch: Add a character to the prepstr buffer
|
|
|
|
int prepstr_addch (chtype *restrict *restrict chbuf, int *restrict chbufsize,
|
|
chtype attr, int maxlines, int maxwidth,
|
|
int *restrict line, int *restrict width,
|
|
chtype *restrict *restrict lastspc, int *restrict widthspc,
|
|
int *restrict widthbuf, int widthbufsize,
|
|
const char *restrict *restrict str)
|
|
{
|
|
if (*line < 0) {
|
|
// First character in buffer: start line 0
|
|
*line = 0;
|
|
}
|
|
|
|
if (**str == '\n') {
|
|
// Start a new line
|
|
|
|
if (*line < maxlines - 1) {
|
|
*(*chbuf)++ = '\n';
|
|
(*chbufsize)--;
|
|
}
|
|
|
|
widthbuf[*line] = *width;
|
|
*width = 0;
|
|
|
|
*lastspc = NULL;
|
|
*widthspc = 0;
|
|
|
|
(*line)++;
|
|
(*str)++;
|
|
} else if (*width == maxwidth) {
|
|
// Current line is now too long
|
|
|
|
if (! isspace(**str) && *lastspc != NULL && *line < maxlines - 1) {
|
|
// Break on the last space in this line
|
|
**lastspc = '\n';
|
|
|
|
widthbuf[*line] = *widthspc;
|
|
*width -= *widthspc + 1;
|
|
|
|
*lastspc = NULL;
|
|
*widthspc = 0;
|
|
|
|
(*line)++;
|
|
} else {
|
|
// Insert a new-line character (if not on last line)
|
|
if (*line < maxlines - 1) {
|
|
*(*chbuf)++ = '\n';
|
|
(*chbufsize)--;
|
|
}
|
|
|
|
widthbuf[*line] = *width;
|
|
*width = 0;
|
|
|
|
*lastspc = NULL;
|
|
*widthspc = 0;
|
|
|
|
(*line)++;
|
|
|
|
// Skip any following spaces
|
|
while (isspace(**str)) {
|
|
if (*(*str)++ == '\n') {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Insert an ordinary character into the output string
|
|
|
|
if (isspace(**str)) {
|
|
*lastspc = *chbuf;
|
|
*widthspc = *width;
|
|
}
|
|
|
|
*(*chbuf)++ = (unsigned char) **str | attr;
|
|
(*chbufsize)--;
|
|
(*width)++;
|
|
(*str)++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/***********************************************************************/
|
|
// vprepstr: Prepare a string for printing to screen
|
|
|
|
int vprepstr (chtype *restrict chbuf, int chbufsize, chtype attr_norm,
|
|
chtype attr_alt1, chtype attr_alt2, int maxlines, int maxwidth,
|
|
int *restrict widthbuf, int widthbufsize,
|
|
const char *restrict format, va_list args)
|
|
{
|
|
struct argument format_arg[MAXFMTARGS];
|
|
int num_format_args, arg_num;
|
|
const char *orig_format;
|
|
int line, width;
|
|
chtype *lastspc;
|
|
int widthspc;
|
|
chtype curattr;
|
|
int saved_errno;
|
|
|
|
|
|
assert(chbuf != NULL);
|
|
assert(chbufsize > 0);
|
|
assert(maxlines > 0);
|
|
assert(maxwidth > 0);
|
|
assert(widthbuf != NULL);
|
|
assert(widthbufsize >= maxlines);
|
|
assert(format != NULL);
|
|
|
|
/* Do a preliminary scan through the format parameter to determine
|
|
the types of each positional argument (conversion specifier). If
|
|
we did not support "%m$"-style specifiers, this would not be
|
|
necessary. */
|
|
|
|
orig_format = format;
|
|
memset(format_arg, 0, sizeof(format_arg));
|
|
num_format_args = 0;
|
|
arg_num = 0;
|
|
|
|
while (*format != '\0') {
|
|
switch (*format++) {
|
|
case '^':
|
|
// Switch to a different character rendition
|
|
if (*format == '\0') {
|
|
errno = EINVAL;
|
|
return -1;
|
|
} else {
|
|
format++;
|
|
}
|
|
break;
|
|
|
|
case '%':
|
|
// Process a conversion specifier
|
|
if (*format == '\0') {
|
|
errno = EINVAL;
|
|
return -1;
|
|
} else if (*format == '%') {
|
|
format++;
|
|
} else {
|
|
enum argument_type arg_type = TYPE_NONE;
|
|
bool inspec = true;
|
|
bool flag_posn = false;
|
|
bool flag_long = false;
|
|
int count = 0;
|
|
|
|
while (inspec && *format != '\0') {
|
|
char c = *format++;
|
|
switch (c) {
|
|
case '0':
|
|
// Zero flag, or part of numeric count
|
|
if (count == 0) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
count *= 10;
|
|
break;
|
|
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
// Part of some numeric count
|
|
count = count * 10 + (c - '0');
|
|
break;
|
|
|
|
case '$':
|
|
// Fixed-position argument
|
|
if (flag_posn || count == 0) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
if (count > MAXFMTARGS) {
|
|
errno = E2BIG;
|
|
return -1;
|
|
}
|
|
|
|
flag_posn = true;
|
|
arg_num = count - 1;
|
|
count = 0;
|
|
break;
|
|
|
|
case '\'':
|
|
// Use locale-specific thousands separator
|
|
break;
|
|
|
|
case 'l':
|
|
// Long length modifier
|
|
if (flag_long) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
flag_long = true;
|
|
break;
|
|
|
|
case 'd':
|
|
// Insert an integer (int or long int)
|
|
arg_type = flag_long ? TYPE_LONGINT : TYPE_INT;
|
|
goto handlefmt;
|
|
|
|
case 'N':
|
|
// Insert a monetary amount (double)
|
|
if (flag_long) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
arg_type = TYPE_DOUBLE;
|
|
goto handlefmt;
|
|
|
|
case 's':
|
|
// Insert a string (const char *)
|
|
if (flag_long) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
arg_type = TYPE_STRING;
|
|
|
|
handlefmt:
|
|
if (arg_num >= MAXFMTARGS) {
|
|
errno = E2BIG;
|
|
return -1;
|
|
}
|
|
|
|
if (format_arg[arg_num].a_type == TYPE_NONE) {
|
|
format_arg[arg_num].a_type = arg_type;
|
|
} else if (format_arg[arg_num].a_type != arg_type) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
arg_num++;
|
|
num_format_args = MAX(num_format_args, arg_num);
|
|
|
|
inspec = false;
|
|
break;
|
|
|
|
default:
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
}
|
|
if (inspec) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// Process an ordinary character: do nothing for now
|
|
;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < num_format_args; i++) {
|
|
switch (format_arg[i].a_type) {
|
|
case TYPE_INT:
|
|
format_arg[i].a.a_int = va_arg(args, int);
|
|
break;
|
|
|
|
case TYPE_LONGINT:
|
|
format_arg[i].a.a_longint = va_arg(args, long int);
|
|
break;
|
|
|
|
case TYPE_DOUBLE:
|
|
format_arg[i].a.a_double = va_arg(args, double);
|
|
break;
|
|
|
|
case TYPE_STRING:
|
|
format_arg[i].a.a_string = va_arg(args, const char *);
|
|
break;
|
|
|
|
default:
|
|
/* Cannot allow unused arguments, as we have no way of
|
|
knowing how much space they take (cf. int vs. long long
|
|
int). */
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// Actually process the format parameter string
|
|
|
|
format = orig_format;
|
|
arg_num = 0;
|
|
|
|
curattr = attr_norm;
|
|
line = -1; // Current line number (0 = first)
|
|
width = 0; // Width of the current line
|
|
lastspc = NULL; // Pointer to last space in line
|
|
widthspc = 0; // Width of line before last space
|
|
|
|
while (*format != '\0' && chbufsize > 1 && line < maxlines) {
|
|
switch (*format) {
|
|
case '^':
|
|
// Switch to a different character rendition
|
|
if (*++format == '\0') {
|
|
errno = EINVAL;
|
|
return -1;
|
|
} else {
|
|
switch (*format) {
|
|
case '^':
|
|
if (prepstr_addch(&chbuf, &chbufsize, curattr, maxlines,
|
|
maxwidth, &line, &width, &lastspc,
|
|
&widthspc, widthbuf, widthbufsize,
|
|
&format) < 0) {
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case '{':
|
|
curattr = attr_alt1;
|
|
format++;
|
|
break;
|
|
|
|
case '[':
|
|
curattr = attr_alt2;
|
|
format++;
|
|
break;
|
|
|
|
case '}':
|
|
case ']':
|
|
curattr = attr_norm;
|
|
format++;
|
|
break;
|
|
|
|
default:
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case '%':
|
|
// Process a conversion specifier
|
|
if (*++format == '\0') {
|
|
errno = EINVAL;
|
|
return -1;
|
|
} else if (*format == '%') {
|
|
if (prepstr_addch(&chbuf, &chbufsize, curattr, maxlines,
|
|
maxwidth, &line, &width, &lastspc, &widthspc,
|
|
widthbuf, widthbufsize, &format) < 0) {
|
|
return -1;
|
|
}
|
|
} else {
|
|
bool inspec = true;
|
|
bool flag_posn = false;
|
|
bool flag_long = false;
|
|
bool flag_thou = false;
|
|
int count = 0;
|
|
const char *str;
|
|
|
|
char *buf = malloc(BUFSIZE);
|
|
if (buf == NULL) {
|
|
err_exit_nomem();
|
|
}
|
|
|
|
while (inspec && *format != '\0') {
|
|
char c = *format++;
|
|
switch (c) {
|
|
case '0':
|
|
// Zero flag, or part of numeric count
|
|
if (count == 0) {
|
|
// Zero flag is not supported
|
|
free(buf);
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
count *= 10;
|
|
break;
|
|
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
// Part of some numeric count
|
|
count = count * 10 + (c - '0');
|
|
break;
|
|
|
|
case '$':
|
|
// Fixed-position argument
|
|
if (flag_posn || count == 0) {
|
|
free(buf);
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
if (count > MAXFMTARGS) {
|
|
free(buf);
|
|
errno = E2BIG;
|
|
return -1;
|
|
}
|
|
|
|
flag_posn = true;
|
|
arg_num = count - 1;
|
|
count = 0;
|
|
break;
|
|
|
|
case '\'':
|
|
// Use locale-specific thousands separator
|
|
if (flag_thou) {
|
|
free(buf);
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
flag_thou = true;
|
|
break;
|
|
|
|
case 'l':
|
|
// Long length modifier
|
|
if (flag_long) {
|
|
free(buf);
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
flag_long = true;
|
|
break;
|
|
|
|
case 'd':
|
|
// Insert an integer (int or long int) into the output
|
|
if (count != 0) {
|
|
free(buf);
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
if (arg_num >= MAXFMTARGS) {
|
|
free(buf);
|
|
errno = E2BIG;
|
|
return -1;
|
|
}
|
|
|
|
if (flag_long) {
|
|
if (snprintf(buf, BUFSIZE, flag_thou ? "%'ld" : "%ld",
|
|
format_arg[arg_num].a.a_longint) < 0) {
|
|
saved_errno = errno;
|
|
free(buf);
|
|
errno = saved_errno;
|
|
return -1;
|
|
}
|
|
} else {
|
|
if (snprintf(buf, BUFSIZE, flag_thou ? "%'d" : "%d",
|
|
format_arg[arg_num].a.a_int) < 0) {
|
|
saved_errno = errno;
|
|
free(buf);
|
|
errno = saved_errno;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
str = buf;
|
|
goto insertstr;
|
|
|
|
case 'N':
|
|
// Insert a monetary amount (double) into the output
|
|
if (count != 0 || flag_thou || flag_long) {
|
|
free(buf);
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
if (arg_num >= MAXFMTARGS) {
|
|
free(buf);
|
|
errno = E2BIG;
|
|
return -1;
|
|
}
|
|
|
|
if (l_strfmon(buf, BUFSIZE, "%n",
|
|
format_arg[arg_num].a.a_double) < 0) {
|
|
saved_errno = errno;
|
|
free(buf);
|
|
errno = saved_errno;
|
|
return -1;
|
|
}
|
|
|
|
str = buf;
|
|
goto insertstr;
|
|
|
|
case 's':
|
|
// Insert a string (const char *) into the output
|
|
if (count != 0 || flag_thou || flag_long) {
|
|
free(buf);
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
if (arg_num >= MAXFMTARGS) {
|
|
free(buf);
|
|
errno = E2BIG;
|
|
return -1;
|
|
}
|
|
|
|
str = format_arg[arg_num].a.a_string;
|
|
|
|
if (str == NULL) {
|
|
str = "(null)"; // As per GNU printf()
|
|
}
|
|
|
|
insertstr:
|
|
// Insert the string pointed to by str
|
|
while (*str != '\0' && chbufsize > 1 && line < maxlines) {
|
|
if (prepstr_addch(&chbuf, &chbufsize, curattr,
|
|
maxlines, maxwidth, &line, &width,
|
|
&lastspc, &widthspc, widthbuf,
|
|
widthbufsize, &str) < 0) {
|
|
saved_errno = errno;
|
|
free(buf);
|
|
errno = saved_errno;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
arg_num++;
|
|
inspec = false;
|
|
break;
|
|
|
|
default:
|
|
free(buf);
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
}
|
|
free(buf);
|
|
if (inspec) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// Process an ordinary character (including new-line)
|
|
if (prepstr_addch(&chbuf, &chbufsize, curattr, maxlines, maxwidth,
|
|
&line, &width, &lastspc, &widthspc, widthbuf,
|
|
widthbufsize, &format) < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
*chbuf = 0; // Terminating NUL byte
|
|
|
|
if (line >= 0 && line < maxlines) {
|
|
widthbuf[line] = width;
|
|
} else if (line >= maxlines) {
|
|
line = maxlines - 1;
|
|
}
|
|
|
|
return line + 1;
|
|
}
|
|
|
|
|
|
/***********************************************************************/
|
|
// chbufdup: Duplicate a chtype buffer
|
|
|
|
chtype *chbufdup (const chtype *restrict chbuf, int chbufsize)
|
|
{
|
|
const chtype *p;
|
|
int len;
|
|
chtype *ret;
|
|
|
|
|
|
// Determine chbuf length, including ending NUL
|
|
for (len = 1, p = chbuf; *p != '\0' && len <= chbufsize; p++, len++)
|
|
;
|
|
|
|
ret = malloc(len * sizeof(chtype));
|
|
if (ret == NULL) {
|
|
err_exit_nomem();
|
|
}
|
|
|
|
memcpy(ret, chbuf, len * sizeof(chtype));
|
|
ret[len - 1] = '\0'; // Terminating NUL, just in case not present
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/***********************************************************************/
|
|
// pr_left: Print strings in chbuf left-aligned
|
|
|
|
int pr_left (WINDOW *win, int y, int x, const chtype *restrict chbuf,
|
|
int lines, const int *restrict widthbuf)
|
|
{
|
|
assert(win != NULL);
|
|
assert(chbuf != NULL);
|
|
assert(lines > 0);
|
|
assert(widthbuf != NULL);
|
|
|
|
wmove(win, y, x);
|
|
for ( ; *chbuf != '\0'; chbuf++) {
|
|
if (*chbuf == '\n') {
|
|
wmove(win, getcury(win) + 1, x);
|
|
} else {
|
|
waddch(win, *chbuf);
|
|
}
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
|
|
/***********************************************************************/
|
|
// pr_center: Print strings in chbuf centred in window
|
|
|
|
int pr_center (WINDOW *win, int y, int offset, const chtype *restrict chbuf,
|
|
int lines, const int *restrict widthbuf)
|
|
{
|
|
int ln = 0;
|
|
|
|
|
|
assert(win != NULL);
|
|
assert(chbuf != NULL);
|
|
assert(lines > 0);
|
|
assert(widthbuf != NULL);
|
|
|
|
wmove(win, y, (getmaxx(win) - widthbuf[ln]) / 2 + offset);
|
|
for ( ; *chbuf != '\0'; chbuf++) {
|
|
if (*chbuf == '\n') {
|
|
if (ln++ >= lines) {
|
|
return ERR;
|
|
} else {
|
|
wmove(win, getcury(win) + 1,
|
|
(getmaxx(win) - widthbuf[ln]) / 2 + offset);
|
|
}
|
|
} else {
|
|
waddch(win, *chbuf);
|
|
}
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
|
|
/***********************************************************************/
|
|
// pr_right: Print strings in chbuf right-aligned
|
|
|
|
int pr_right (WINDOW *win, int y, int x, const chtype *restrict chbuf,
|
|
int lines, const int *restrict widthbuf)
|
|
{
|
|
int ln = 0;
|
|
|
|
|
|
assert(win != NULL);
|
|
assert(chbuf != NULL);
|
|
assert(lines > 0);
|
|
assert(widthbuf != NULL);
|
|
|
|
wmove(win, y, x - widthbuf[ln]);
|
|
for ( ; *chbuf != '\0'; chbuf++) {
|
|
if (*chbuf == '\n') {
|
|
if (ln++ >= lines) {
|
|
return ERR;
|
|
} else {
|
|
wmove(win, getcury(win) + 1, x - widthbuf[ln]);
|
|
}
|
|
} else {
|
|
waddch(win, *chbuf);
|
|
}
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
|
|
/***********************************************************************/
|
|
// attrpr: Print a string with a particular character rendition
|
|
|
|
int attrpr (WINDOW *win, chtype attr, const char *restrict format, ...)
|
|
{
|
|
va_list args;
|
|
int ret;
|
|
|
|
|
|
assert(win != NULL);
|
|
assert(format != NULL);
|
|
|
|
chtype oldattr = getattrs(win);
|
|
chtype oldbkgd = getbkgd(win);
|
|
|
|
/* Note that wattrset() will override parts of wbkgdset() and vice
|
|
versa: don't swap the order of these two lines! */
|
|
wbkgdset(win, (oldbkgd & A_COLOR) | A_NORMAL);
|
|
wattrset(win, attr);
|
|
|
|
va_start(args, format);
|
|
ret = vw_printw(win, format, args);
|
|
va_end(args);
|
|
|
|
wbkgdset(win, oldbkgd);
|
|
wattrset(win, oldattr);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/***********************************************************************/
|
|
// center: Centre a string in a given window
|
|
|
|
int center (WINDOW *win, int y, chtype attr, const char *restrict format, ...)
|
|
{
|
|
va_list args;
|
|
int ret, len, x;
|
|
char *buf;
|
|
|
|
|
|
assert(win != NULL);
|
|
assert(format != NULL);
|
|
|
|
buf = malloc(BUFSIZE);
|
|
if (buf == NULL) {
|
|
err_exit_nomem();
|
|
}
|
|
|
|
chtype oldattr = getattrs(win);
|
|
chtype oldbkgd = getbkgd(win);
|
|
|
|
// Order is important: see attrpr()
|
|
wbkgdset(win, (oldbkgd & A_COLOR) | A_NORMAL);
|
|
wattrset(win, attr);
|
|
|
|
va_start(args, format);
|
|
len = vsnprintf(buf, BUFSIZE, format, args);
|
|
va_end(args);
|
|
|
|
if (len < 0) {
|
|
ret = ERR;
|
|
} else if (len == 0) {
|
|
ret = OK;
|
|
} else {
|
|
x = (getmaxx(win) - len) / 2;
|
|
ret = mvwprintw(win, y, MAX(x, 2), "%1.*s", getmaxx(win) - 4, buf);
|
|
}
|
|
|
|
wbkgdset(win, oldbkgd);
|
|
wattrset(win, oldattr);
|
|
|
|
free(buf);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/***********************************************************************/
|
|
// center2: Centre two strings in a given window
|
|
|
|
int center2 (WINDOW *win, int y, chtype attr1, chtype attr2,
|
|
const char *initial, const char *restrict format, ...)
|
|
{
|
|
va_list args;
|
|
int ret, len1, len2, x;
|
|
char *buf;
|
|
|
|
|
|
assert(win != NULL);
|
|
assert(initial != NULL);
|
|
assert(format != NULL);
|
|
|
|
buf = malloc(BUFSIZE);
|
|
if (buf == NULL) {
|
|
err_exit_nomem();
|
|
}
|
|
|
|
chtype oldattr = getattrs(win);
|
|
chtype oldbkgd = getbkgd(win);
|
|
|
|
wbkgdset(win, (oldbkgd & A_COLOR) | A_NORMAL);
|
|
|
|
len1 = strlen(initial);
|
|
|
|
va_start(args, format);
|
|
len2 = vsnprintf(buf, BUFSIZE, format, args);
|
|
va_end(args);
|
|
|
|
if (len2 < 0) {
|
|
ret = ERR;
|
|
} else if (len1 + len2 == 0) {
|
|
ret = OK;
|
|
} else {
|
|
x = (getmaxx(win) - (len1 + len2)) / 2;
|
|
wattrset(win, attr1);
|
|
mvwprintw(win, y, MAX(x, 2), "%s", initial);
|
|
wattrset(win, attr2);
|
|
ret = wprintw(win, "%1.*s", getmaxx(win) - len1 - 4, buf);
|
|
}
|
|
|
|
wbkgdset(win, oldbkgd);
|
|
wattrset(win, oldattr);
|
|
|
|
free(buf);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/***********************************************************************/
|
|
// center3: Centre three strings in a given window
|
|
|
|
int center3 (WINDOW *win, int y, chtype attr1, chtype attr3, chtype attr2,
|
|
const char *initial, const char *final,
|
|
const char *restrict format, ...)
|
|
{
|
|
va_list args;
|
|
int len1, len2, len3;
|
|
int ret, x;
|
|
char *buf;
|
|
|
|
|
|
assert(win != NULL);
|
|
assert(initial != NULL);
|
|
assert(final != NULL);
|
|
assert(format != NULL);
|
|
|
|
buf = malloc(BUFSIZE);
|
|
if (buf == NULL) {
|
|
err_exit_nomem();
|
|
}
|
|
|
|
chtype oldattr = getattrs(win);
|
|
chtype oldbkgd = getbkgd(win);
|
|
|
|
wbkgdset(win, (oldbkgd & A_COLOR) | A_NORMAL);
|
|
|
|
len1 = strlen(initial);
|
|
len3 = strlen(final);
|
|
|
|
va_start(args, format);
|
|
len2 = vsnprintf(buf, BUFSIZE, format, args);
|
|
va_end(args);
|
|
|
|
if (len2 < 0) {
|
|
ret = ERR;
|
|
} else if (len1 + len2 + len3 == 0) {
|
|
ret = OK;
|
|
} else {
|
|
x = (getmaxx(win) - (len1 + len2 + len3)) / 2;
|
|
wattrset(win, attr1);
|
|
mvwprintw(win, y, MAX(x, 2), "%s", initial);
|
|
wattrset(win, attr2);
|
|
ret = wprintw(win, "%1.*s", getmaxx(win) - len1 - len3 - 4, buf);
|
|
wattrset(win, attr3);
|
|
wprintw(win, "%s", final);
|
|
}
|
|
|
|
wbkgdset(win, oldbkgd);
|
|
wattrset(win, oldattr);
|
|
|
|
free(buf);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/***********************************************************************/
|
|
// gettxchar: Read a character from the keyboard
|
|
|
|
int gettxchar (WINDOW *win)
|
|
{
|
|
int key;
|
|
bool done;
|
|
|
|
|
|
keypad(win, true);
|
|
meta(win, true);
|
|
wtimeout(win, -1);
|
|
|
|
done = false;
|
|
while (! done) {
|
|
key = wgetch(win);
|
|
switch (key) {
|
|
case ERR:
|
|
beep();
|
|
break;
|
|
|
|
#ifdef HANDLE_RESIZE_EVENTS
|
|
case KEY_RESIZE:
|
|
txresize();
|
|
break;
|
|
#endif // HANDLE_RESIZE_EVENTS
|
|
|
|
default:
|
|
done = true;
|
|
}
|
|
}
|
|
|
|
return key;
|
|
}
|
|
|
|
|
|
/***********************************************************************/
|
|
// gettxline: Read a line from the keyboard (low-level)
|
|
|
|
int gettxline (WINDOW *win, char *buf, int bufsize, bool *restrict modified,
|
|
bool multifield, const char *emptyval, const char *defaultval,
|
|
const char *allowed, bool stripspc, int y, int x, int width,
|
|
chtype attr)
|
|
{
|
|
int len, pos, cpos, nb, ret;
|
|
bool done, redraw, mod;
|
|
int key, key2;
|
|
|
|
|
|
assert(win != NULL);
|
|
assert(buf != NULL);
|
|
assert(bufsize > 2);
|
|
assert(width > 1);
|
|
|
|
keypad(win, true);
|
|
meta(win, true);
|
|
wtimeout(win, -1);
|
|
|
|
chtype oldattr = getattrs(win);
|
|
chtype oldbkgd = getbkgd(win);
|
|
|
|
/* Note that wattrset() will override parts of wbkgdset() and vice
|
|
versa: don't swap the order of these two lines! */
|
|
wbkgdset(win, (oldbkgd & A_COLOR) | A_NORMAL);
|
|
wattrset(win, attr & ~A_CHARTEXT);
|
|
curs_set(CURS_ON);
|
|
|
|
len = strlen(buf);
|
|
pos = len; // pos (string position) is from 0 to len
|
|
cpos = MIN(len, width - 1); // cpos (cursor position) is from 0 to width-1
|
|
redraw = true;
|
|
done = false;
|
|
mod = false;
|
|
ret = OK;
|
|
|
|
while (! done) {
|
|
if (redraw) {
|
|
/*
|
|
Redisplay the visible part of the current input string.
|
|
Blanks at the end of the input area are replaced with
|
|
"attr", which may contain a '_' for non-colour mode.
|
|
*/
|
|
mvwprintw(win, y, x, "%-*.*s", width, width, buf + pos - cpos);
|
|
|
|
// nb = number of blanks
|
|
nb = (len - (pos - cpos) > width) ? 0 : width - (len - (pos - cpos));
|
|
wmove(win, y, x + width - nb);
|
|
|
|
chtype ch = ((attr & A_CHARTEXT) == 0) ? attr | ' ' : attr;
|
|
for (int i = 0; i < nb; i++) {
|
|
waddch(win, ch);
|
|
}
|
|
|
|
wmove(win, y, x + cpos);
|
|
wrefresh(win);
|
|
}
|
|
|
|
key = wgetch(win);
|
|
if (key == ERR) {
|
|
// Do nothing on ERR
|
|
;
|
|
} else if ((key == KEY_DEFAULTVAL1 || key == KEY_DEFAULTVAL2)
|
|
&& defaultval != NULL && len == 0) {
|
|
// Initialise buffer with the default value
|
|
|
|
strncpy(buf, defaultval, bufsize - 1);
|
|
buf[bufsize - 1] = '\0';
|
|
|
|
len = strlen(buf);
|
|
pos = len;
|
|
cpos = MIN(len, width - 1);
|
|
mod = true;
|
|
redraw = true;
|
|
} else if (key < 0400 && ! iscntrl(key)) {
|
|
if (len >= bufsize - 1
|
|
|| (allowed != NULL && strchr(allowed, key) == NULL)) {
|
|
beep();
|
|
} else {
|
|
// Process ordinary key press: insert it into the string
|
|
|
|
memmove(buf + pos + 1, buf + pos, len - pos + 1);
|
|
buf[pos] = (char) key;
|
|
len++;
|
|
pos++;
|
|
if (cpos < width - 1) {
|
|
cpos++;
|
|
}
|
|
mod = true;
|
|
redraw = true;
|
|
}
|
|
} else {
|
|
switch (key) {
|
|
|
|
// Terminating keys
|
|
|
|
case KEY_RETURN:
|
|
case KEY_ENTER:
|
|
case KEY_CTRL('M'):
|
|
// Finish entering the string
|
|
|
|
if (stripspc) {
|
|
// Strip leading spaces
|
|
int i;
|
|
|
|
for (i = 0; i < len && isspace(buf[i]); i++)
|
|
;
|
|
if (i > 0) {
|
|
memmove(buf, buf + i, len - i + 1);
|
|
len -= i;
|
|
mod = true;
|
|
}
|
|
|
|
// Strip trailing spaces
|
|
for (i = len; i > 0 && isspace(buf[i - 1]); i--)
|
|
;
|
|
if (i < len) {
|
|
buf[i] = '\0';
|
|
len = i;
|
|
mod = true;
|
|
}
|
|
}
|
|
|
|
if (emptyval != NULL && len == 0) {
|
|
strncpy(buf, emptyval, bufsize - 1);
|
|
buf[bufsize - 1] = '\0';
|
|
mod = true;
|
|
}
|
|
|
|
ret = OK;
|
|
done = true;
|
|
break;
|
|
|
|
case KEY_CANCEL:
|
|
case KEY_EXIT:
|
|
case KEY_CTRL('G'):
|
|
case KEY_CTRL('C'):
|
|
case KEY_CTRL('\\'):
|
|
// Cancel entering the string
|
|
ret = ERR;
|
|
done = true;
|
|
break;
|
|
|
|
case KEY_UP:
|
|
case KEY_BTAB:
|
|
case KEY_CTRL('P'):
|
|
// Finish entering the string, if multifield is true
|
|
if (multifield) {
|
|
ret = KEY_UP;
|
|
done = true;
|
|
} else {
|
|
beep();
|
|
}
|
|
break;
|
|
|
|
case KEY_DOWN:
|
|
case KEY_TAB:
|
|
case KEY_CTRL('N'):
|
|
// Finish entering the string, if multifield is true
|
|
if (multifield) {
|
|
ret = KEY_DOWN;
|
|
done = true;
|
|
} else {
|
|
beep();
|
|
}
|
|
break;
|
|
|
|
// Cursor movement keys
|
|
|
|
case KEY_LEFT:
|
|
case KEY_CTRL('B'):
|
|
// Move cursor back one character
|
|
if (pos == 0) {
|
|
beep();
|
|
} else {
|
|
pos--;
|
|
if (cpos > 0) {
|
|
cpos--;
|
|
}
|
|
redraw = true;
|
|
}
|
|
break;
|
|
|
|
case KEY_RIGHT:
|
|
case KEY_CTRL('F'):
|
|
// Move cursor forward one character
|
|
if (pos == len) {
|
|
beep();
|
|
} else {
|
|
pos++;
|
|
if (cpos < width - 1) {
|
|
cpos++;
|
|
}
|
|
redraw = true;
|
|
}
|
|
break;
|
|
|
|
case KEY_HOME:
|
|
case KEY_CTRL('A'):
|
|
// Move cursor to start of string
|
|
pos = 0;
|
|
cpos = 0;
|
|
redraw = true;
|
|
break;
|
|
|
|
case KEY_END:
|
|
case KEY_CTRL('E'):
|
|
// Move cursor to end of string
|
|
pos = len;
|
|
cpos = MIN(pos, width - 1);
|
|
redraw = true;
|
|
break;
|
|
|
|
case KEY_CLEFT:
|
|
// Move cursor to start of current or previous word
|
|
while (pos > 0 && ! isalnum(buf[pos - 1])) {
|
|
pos--;
|
|
if (cpos > 0) {
|
|
cpos--;
|
|
}
|
|
}
|
|
while (pos > 0 && isalnum(buf[pos - 1])) {
|
|
pos--;
|
|
if (cpos > 0) {
|
|
cpos--;
|
|
}
|
|
}
|
|
redraw = true;
|
|
break;
|
|
|
|
case KEY_CRIGHT:
|
|
// Move cursor to end of current or next word
|
|
while (pos < len && ! isalnum(buf[pos])) {
|
|
pos++;
|
|
if (cpos < width - 1) {
|
|
cpos++;
|
|
}
|
|
}
|
|
while (pos < len && isalnum(buf[pos])) {
|
|
pos++;
|
|
if (cpos < width - 1) {
|
|
cpos++;
|
|
}
|
|
}
|
|
redraw = true;
|
|
break;
|
|
|
|
// Deletion keys
|
|
|
|
case KEY_BS:
|
|
case KEY_BACKSPACE:
|
|
case KEY_DEL:
|
|
// Delete previous character
|
|
if (pos == 0) {
|
|
beep();
|
|
} else {
|
|
memmove(buf + pos - 1, buf + pos, len - pos + 1);
|
|
len--;
|
|
pos--;
|
|
if (cpos > 0) {
|
|
cpos--;
|
|
}
|
|
mod = true;
|
|
redraw = true;
|
|
}
|
|
break;
|
|
|
|
case KEY_DC:
|
|
case KEY_CTRL('D'):
|
|
// Delete character under cursor
|
|
if (pos == len) {
|
|
beep();
|
|
} else {
|
|
memmove(buf + pos, buf + pos + 1, len - pos);
|
|
len--;
|
|
// pos and cpos stay the same
|
|
mod = true;
|
|
redraw = true;
|
|
}
|
|
break;
|
|
|
|
case KEY_CLEAR:
|
|
// Delete the entire line
|
|
strcpy(buf, "");
|
|
len = 0;
|
|
pos = 0;
|
|
cpos = 0;
|
|
mod = true;
|
|
redraw = true;
|
|
break;
|
|
|
|
case KEY_CTRL('U'):
|
|
// Delete backwards to the start of the line
|
|
if (pos == 0) {
|
|
beep();
|
|
} else {
|
|
memmove(buf, buf + pos, len - pos + 1);
|
|
len -= pos;
|
|
pos = 0;
|
|
cpos = 0;
|
|
mod = true;
|
|
redraw = true;
|
|
}
|
|
break;
|
|
|
|
case KEY_CTRL('K'):
|
|
// Delete to the end of the line
|
|
if (pos == len) {
|
|
beep();
|
|
} else {
|
|
buf[pos] = '\0';
|
|
len = pos;
|
|
// pos and cpos stay the same
|
|
mod = true;
|
|
redraw = true;
|
|
}
|
|
break;
|
|
|
|
case KEY_CTRL('W'):
|
|
// Delete the previous word
|
|
if (pos == 0) {
|
|
beep();
|
|
} else {
|
|
/*
|
|
Note the use of isspace() instead of isalnum():
|
|
this makes ^W follow GNU Bash standards, which
|
|
behaves differently from Meta-DEL.
|
|
*/
|
|
int i = pos;
|
|
while (i > 0 && isspace(buf[i - 1])) {
|
|
i--;
|
|
if (cpos > 0) {
|
|
cpos--;
|
|
}
|
|
}
|
|
while (i > 0 && ! isspace(buf[i - 1])) {
|
|
i--;
|
|
if (cpos > 0) {
|
|
cpos--;
|
|
}
|
|
}
|
|
|
|
memmove(buf + i, buf + pos, len - pos + 1);
|
|
len -= pos - i;
|
|
pos = i;
|
|
mod = true;
|
|
redraw = true;
|
|
}
|
|
break;
|
|
|
|
// Miscellaneous keys and events
|
|
|
|
case KEY_CTRL('T'):
|
|
// Transpose characters
|
|
if (pos == 0 || len <= 1) {
|
|
beep();
|
|
} else if (pos == len) {
|
|
char c = buf[pos - 1];
|
|
buf[pos - 1] = buf[pos - 2];
|
|
buf[pos - 2] = c;
|
|
mod = true;
|
|
redraw = true;
|
|
} else {
|
|
char c = buf[pos];
|
|
buf[pos] = buf[pos - 1];
|
|
buf[pos - 1] = c;
|
|
|
|
pos++;
|
|
if (cpos < width - 1) {
|
|
cpos++;
|
|
}
|
|
mod = true;
|
|
redraw = true;
|
|
}
|
|
break;
|
|
|
|
case KEY_ESC:
|
|
// Handle Meta-X-style and other function key presses
|
|
wtimeout(win, META_TIMEOUT);
|
|
key2 = wgetch(win);
|
|
|
|
if (key2 == ERR) {
|
|
// <ESC> by itself: cancel entering the string
|
|
ret = ERR;
|
|
done = true;
|
|
} else if (key2 == 'O' || key2 == '[') {
|
|
// Swallow any unknown VT100-style function keys
|
|
key2 = wgetch(win);
|
|
while (key2 != ERR && isascii(key2)
|
|
&& strchr("0123456789;", key2) != NULL
|
|
&& strchr("~ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
"abcdefghijklmnopqrstuvwxyz", key2)
|
|
== NULL) {
|
|
key2 = wgetch(win);
|
|
}
|
|
beep();
|
|
} else {
|
|
// Handle Meta-X-style keypress
|
|
switch (key2) {
|
|
|
|
// Cursor movement keys
|
|
|
|
case 'B':
|
|
case 'b':
|
|
// Move cursor to start of current or previous word
|
|
while (pos > 0 && ! isalnum(buf[pos - 1])) {
|
|
pos--;
|
|
if (cpos > 0) {
|
|
cpos--;
|
|
}
|
|
}
|
|
while (pos > 0 && isalnum(buf[pos - 1])) {
|
|
pos--;
|
|
if (cpos > 0) {
|
|
cpos--;
|
|
}
|
|
}
|
|
redraw = true;
|
|
break;
|
|
|
|
case 'F':
|
|
case 'f':
|
|
// Move cursor to end of current or next word
|
|
while (pos < len && ! isalnum(buf[pos])) {
|
|
pos++;
|
|
if (cpos < width - 1) {
|
|
cpos++;
|
|
}
|
|
}
|
|
while (pos < len && isalnum(buf[pos])) {
|
|
pos++;
|
|
if (cpos < width - 1) {
|
|
cpos++;
|
|
}
|
|
}
|
|
redraw = true;
|
|
break;
|
|
|
|
// Deletion keys
|
|
|
|
case KEY_BS:
|
|
case KEY_BACKSPACE:
|
|
case KEY_DEL:
|
|
// Delete the previous word (different from ^W)
|
|
{
|
|
int i = pos;
|
|
while (i > 0 && ! isalnum(buf[i - 1])) {
|
|
i--;
|
|
if (cpos > 0) {
|
|
cpos--;
|
|
}
|
|
}
|
|
while (i > 0 && isalnum(buf[i - 1])) {
|
|
i--;
|
|
if (cpos > 0) {
|
|
cpos--;
|
|
}
|
|
}
|
|
|
|
memmove(buf + i, buf + pos, len - pos + 1);
|
|
len -= (pos - i);
|
|
pos = i;
|
|
mod = true;
|
|
redraw = true;
|
|
}
|
|
break;
|
|
|
|
case 'D':
|
|
case 'd':
|
|
// Delete the next word
|
|
{
|
|
int i = pos;
|
|
while (i < len && ! isalnum(buf[i])) {
|
|
i++;
|
|
}
|
|
while (i < len && isalnum(buf[i])) {
|
|
i++;
|
|
}
|
|
|
|
memmove(buf + pos, buf + i, len - i + 1);
|
|
len -= (i - pos);
|
|
// pos and cpos stay the same
|
|
mod = true;
|
|
redraw = true;
|
|
}
|
|
break;
|
|
|
|
case '\\':
|
|
case ' ':
|
|
// Delete all surrounding spaces; if key2 == ' ',
|
|
// also insert one space
|
|
{
|
|
int i = pos;
|
|
while (pos > 0 && isspace(buf[pos - 1])) {
|
|
pos--;
|
|
if (cpos > 0) {
|
|
cpos--;
|
|
}
|
|
}
|
|
while (i < len && isspace(buf[i])) {
|
|
i++;
|
|
}
|
|
|
|
memmove(buf + pos, buf + i, len - i + 1);
|
|
len -= (i - pos);
|
|
|
|
if (key2 == ' ') {
|
|
if (len >= bufsize - 1 || (allowed != NULL
|
|
&& strchr(allowed, key) == NULL)) {
|
|
beep();
|
|
} else {
|
|
memmove(buf + pos + 1, buf + pos,
|
|
len - pos + 1);
|
|
buf[pos] = ' ';
|
|
len++;
|
|
pos++;
|
|
if (cpos < width - 1) {
|
|
cpos++;
|
|
}
|
|
}
|
|
}
|
|
|
|
mod = true;
|
|
redraw = true;
|
|
}
|
|
break;
|
|
|
|
// Transformation keys
|
|
|
|
case 'U':
|
|
case 'u':
|
|
// Convert word (from cursor onwards) to upper case
|
|
while (pos < len && ! isalnum(buf[pos])) {
|
|
pos++;
|
|
if (cpos < width - 1) {
|
|
cpos++;
|
|
}
|
|
}
|
|
while (pos < len && isalnum(buf[pos])) {
|
|
buf[pos] = toupper(buf[pos]);
|
|
pos++;
|
|
if (cpos < width - 1) {
|
|
cpos++;
|
|
}
|
|
}
|
|
mod = true;
|
|
redraw = true;
|
|
break;
|
|
|
|
case 'L':
|
|
case 'l':
|
|
// Convert word (from cursor onwards) to lower case
|
|
while (pos < len && ! isalnum(buf[pos])) {
|
|
pos++;
|
|
if (cpos < width - 1) {
|
|
cpos++;
|
|
}
|
|
}
|
|
while (pos < len && isalnum(buf[pos])) {
|
|
buf[pos] = tolower(buf[pos]);
|
|
pos++;
|
|
if (cpos < width - 1) {
|
|
cpos++;
|
|
}
|
|
}
|
|
mod = true;
|
|
redraw = true;
|
|
break;
|
|
|
|
case 'C':
|
|
case 'c':
|
|
// Convert current letter to upper case, following
|
|
// letters to lower case
|
|
{
|
|
bool first = true;
|
|
while (pos < len && ! isalnum(buf[pos])) {
|
|
pos++;
|
|
if (cpos < width - 1) {
|
|
cpos++;
|
|
}
|
|
}
|
|
while (pos < len && isalnum(buf[pos])) {
|
|
if (first) {
|
|
buf[pos] = toupper(buf[pos]);
|
|
first = false;
|
|
} else {
|
|
buf[pos] = tolower(buf[pos]);
|
|
}
|
|
pos++;
|
|
if (cpos < width - 1) {
|
|
cpos++;
|
|
}
|
|
}
|
|
mod = true;
|
|
redraw = true;
|
|
}
|
|
break;
|
|
|
|
// Miscellaneous keys and events
|
|
|
|
#ifdef HANDLE_RESIZE_EVENTS
|
|
case KEY_RESIZE:
|
|
txresize();
|
|
break;
|
|
#endif // HANDLE_RESIZE_EVENTS
|
|
|
|
default:
|
|
beep();
|
|
}
|
|
}
|
|
|
|
wtimeout(win, -1);
|
|
break;
|
|
|
|
#ifdef HANDLE_RESIZE_EVENTS
|
|
case KEY_RESIZE:
|
|
txresize();
|
|
break;
|
|
#endif // HANDLE_RESIZE_EVENTS
|
|
|
|
default:
|
|
beep();
|
|
}
|
|
}
|
|
}
|
|
|
|
curs_set(CURS_OFF);
|
|
|
|
wattrset(win, oldattr | A_BOLD);
|
|
mvwprintw(win, y, x, "%-*.*s", width, width, buf);
|
|
|
|
wbkgdset(win, oldbkgd);
|
|
wattrset(win, oldattr);
|
|
wrefresh(win);
|
|
|
|
if (modified != NULL) {
|
|
*modified = mod;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
/***********************************************************************/
|
|
// gettxstr: Read a string from the keyboard
|
|
|
|
int gettxstr (WINDOW *win, char **bufptr, bool *restrict modified,
|
|
bool multifield, int y, int x, int width, chtype attr)
|
|
{
|
|
assert(bufptr != NULL);
|
|
|
|
|
|
// Allocate the result buffer if needed
|
|
if (*bufptr == NULL) {
|
|
*bufptr = malloc(BUFSIZE);
|
|
if (*bufptr == NULL) {
|
|
err_exit_nomem();
|
|
}
|
|
|
|
**bufptr = '\0';
|
|
}
|
|
|
|
return gettxline(win, *bufptr, BUFSIZE, modified, multifield, "", "",
|
|
NULL, true, y, x, width, attr);
|
|
}
|
|
|
|
|
|
/***********************************************************************/
|
|
// txinput_fixup: Copy strings with fixup
|
|
|
|
void txinput_fixup (char *restrict dest, char *restrict src, bool isfloat)
|
|
{
|
|
struct lconv *lc = &lconvinfo;
|
|
char *p;
|
|
|
|
|
|
assert(src != NULL);
|
|
assert(dest != NULL);
|
|
|
|
strncpy(dest, src, BUFSIZE - 1);
|
|
dest[BUFSIZE - 1] = '\0';
|
|
|
|
// Replace mon_decimal_point with decimal_point if these are different
|
|
if (isfloat) {
|
|
if (strcmp(lc->mon_decimal_point, "") != 0
|
|
&& strcmp(lc->decimal_point, "") != 0
|
|
&& strcmp(lc->mon_decimal_point, lc->decimal_point) != 0) {
|
|
while ((p = strstr(dest, lc->mon_decimal_point)) != NULL) {
|
|
char *pn;
|
|
int len1 = strlen(lc->mon_decimal_point);
|
|
int len2 = strlen(lc->decimal_point);
|
|
|
|
// Make space for lc->decimal_point, if needed
|
|
memmove(p + len2, p + len1, strlen(p) - (len2 - len1) + 1);
|
|
|
|
// Copy lc->decimal_point over p WITHOUT copying ending NUL
|
|
for (pn = lc->decimal_point; *pn != '\0'; pn++, p++) {
|
|
*p = *pn;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove thousands separators if required
|
|
if (strcmp(lc->thousands_sep, "") != 0) {
|
|
while ((p = strstr(dest, lc->thousands_sep)) != NULL) {
|
|
int len = strlen(lc->thousands_sep);
|
|
memmove(p, p + len, strlen(p) - len + 1);
|
|
}
|
|
}
|
|
if (strcmp(lc->mon_thousands_sep, "") != 0) {
|
|
while ((p = strstr(dest, lc->mon_thousands_sep)) != NULL) {
|
|
int len = strlen(lc->thousands_sep);
|
|
memmove(p, p + len, strlen(p) - len + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/***********************************************************************/
|
|
// gettxdouble: Read a floating-point number from the keyboard
|
|
|
|
int gettxdouble (WINDOW *win, double *restrict result, double min,
|
|
double max, double emptyval, double defaultval,
|
|
int y, int x, int width, chtype attr)
|
|
{
|
|
struct lconv *lc = &lconvinfo;
|
|
|
|
char *buf, *bufcopy;
|
|
char *allowed, *emptystr, *defaultstr;
|
|
double val;
|
|
bool done;
|
|
int ret;
|
|
|
|
|
|
assert(result != NULL);
|
|
assert(min <= max);
|
|
|
|
buf = malloc(BUFSIZE);
|
|
bufcopy = malloc(BUFSIZE);
|
|
allowed = malloc(BUFSIZE);
|
|
emptystr = malloc(BUFSIZE);
|
|
defaultstr = malloc(BUFSIZE);
|
|
|
|
if (buf == NULL || bufcopy == NULL || allowed == NULL
|
|
|| emptystr == NULL || defaultstr == NULL) {
|
|
err_exit_nomem();
|
|
}
|
|
|
|
*buf = '\0';
|
|
|
|
strcpy(allowed, "0123456789+-Ee");
|
|
strncat(allowed, lc->decimal_point, BUFSIZE - strlen(allowed) - 1);
|
|
strncat(allowed, lc->thousands_sep, BUFSIZE - strlen(allowed) - 1);
|
|
strncat(allowed, lc->mon_decimal_point, BUFSIZE - strlen(allowed) - 1);
|
|
strncat(allowed, lc->mon_thousands_sep, BUFSIZE - strlen(allowed) - 1);
|
|
|
|
snprintf(emptystr, BUFSIZE, "%'1.*f", lc->frac_digits, emptyval);
|
|
snprintf(defaultstr, BUFSIZE, "%'1.*f", lc->frac_digits, defaultval);
|
|
|
|
done = false;
|
|
while (! done) {
|
|
ret = gettxline(win, buf, BUFSIZE, NULL, false, emptystr, defaultstr,
|
|
allowed, true, y, x, width, attr);
|
|
|
|
if (ret == OK) {
|
|
char *p;
|
|
|
|
txinput_fixup(bufcopy, buf, true);
|
|
val = strtod(bufcopy, &p);
|
|
|
|
if (*p == '\0' && val >= min && val <= max) {
|
|
*result = val;
|
|
done = true;
|
|
} else {
|
|
beep();
|
|
}
|
|
} else {
|
|
done = true;
|
|
}
|
|
}
|
|
|
|
free(defaultstr);
|
|
free(emptystr);
|
|
free(allowed);
|
|
free(bufcopy);
|
|
free(buf);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/***********************************************************************/
|
|
// gettxlong: Read an integer number from the keyboard
|
|
|
|
int gettxlong (WINDOW *win, long int *restrict result, long int min,
|
|
long int max, long int emptyval, long int defaultval,
|
|
int y, int x, int width, chtype attr)
|
|
{
|
|
struct lconv *lc = &lconvinfo;
|
|
|
|
char *buf, *bufcopy;
|
|
char *allowed, *emptystr, *defaultstr;
|
|
long int val;
|
|
bool done;
|
|
int ret;
|
|
|
|
|
|
assert(result != NULL);
|
|
assert(min <= max);
|
|
|
|
buf = malloc(BUFSIZE);
|
|
bufcopy = malloc(BUFSIZE);
|
|
allowed = malloc(BUFSIZE);
|
|
emptystr = malloc(BUFSIZE);
|
|
defaultstr = malloc(BUFSIZE);
|
|
|
|
if (buf == NULL || bufcopy == NULL || allowed == NULL
|
|
|| emptystr == NULL || defaultstr == NULL) {
|
|
err_exit_nomem();
|
|
}
|
|
|
|
*buf = '\0';
|
|
|
|
strcpy(allowed, "0123456789+-");
|
|
strncat(allowed, lc->thousands_sep, BUFSIZE - strlen(allowed) - 1);
|
|
strncat(allowed, lc->mon_thousands_sep, BUFSIZE - strlen(allowed) - 1);
|
|
|
|
snprintf(emptystr, BUFSIZE, "%'1ld", emptyval);
|
|
snprintf(defaultstr, BUFSIZE, "%'1ld", defaultval);
|
|
|
|
done = false;
|
|
while (! done) {
|
|
ret = gettxline(win, buf, BUFSIZE, NULL, false, emptystr, defaultstr,
|
|
allowed, true, y, x, width, attr);
|
|
|
|
if (ret == OK) {
|
|
char *p;
|
|
|
|
txinput_fixup(bufcopy, buf, false);
|
|
val = strtol(bufcopy, &p, 10);
|
|
|
|
if (*p == '\0' && val >= min && val <= max) {
|
|
*result = val;
|
|
done = true;
|
|
} else {
|
|
beep();
|
|
}
|
|
} else {
|
|
done = true;
|
|
}
|
|
}
|
|
|
|
free(defaultstr);
|
|
free(emptystr);
|
|
free(allowed);
|
|
free(bufcopy);
|
|
free(buf);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/***********************************************************************/
|
|
// answer_yesno: Wait for a Yes/No answer
|
|
|
|
bool answer_yesno (WINDOW *win, chtype attr_keys)
|
|
{
|
|
int key;
|
|
bool done;
|
|
|
|
chtype oldattr = getattrs(win);
|
|
chtype oldbkgd = getbkgd(win);
|
|
|
|
|
|
keypad(win, true);
|
|
meta(win, true);
|
|
wtimeout(win, -1);
|
|
|
|
waddstr(curwin, " [");
|
|
attrpr(curwin, attr_keys, "Y");
|
|
waddstr(curwin, "/");
|
|
attrpr(curwin, attr_keys, "N");
|
|
waddstr(curwin, "] ");
|
|
|
|
curs_set(CURS_ON);
|
|
|
|
done = false;
|
|
while (! done) {
|
|
key = wgetch(win);
|
|
|
|
switch (key) {
|
|
case 'Y':
|
|
case 'y':
|
|
case 'N':
|
|
case 'n':
|
|
key = toupper(key);
|
|
done = true;
|
|
break;
|
|
|
|
#ifdef HANDLE_RESIZE_EVENTS
|
|
case KEY_RESIZE:
|
|
txresize();
|
|
break;
|
|
#endif // HANDLE_RESIZE_EVENTS
|
|
|
|
default:
|
|
beep();
|
|
}
|
|
}
|
|
|
|
curs_set(CURS_OFF);
|
|
wattron(win, A_BOLD);
|
|
|
|
if (key == 'Y') {
|
|
waddstr(win, "Yes");
|
|
} else {
|
|
waddstr(win, "No");
|
|
}
|
|
|
|
wbkgdset(win, oldbkgd);
|
|
wattrset(win, oldattr);
|
|
|
|
wrefresh(win);
|
|
return (key == 'Y');
|
|
}
|
|
|
|
|
|
/***********************************************************************/
|
|
// wait_for_key: Print a message and wait for any key
|
|
|
|
void wait_for_key (WINDOW *win, int y, chtype attr)
|
|
{
|
|
chtype *chbuf;
|
|
int width;
|
|
int lines;
|
|
|
|
int key;
|
|
bool done;
|
|
|
|
|
|
|
|
keypad(win, true);
|
|
meta(win, true);
|
|
wtimeout(win, -1);
|
|
|
|
chbuf = malloc(BUFSIZE * sizeof(chtype));
|
|
if (chbuf == NULL) {
|
|
err_exit_nomem();
|
|
}
|
|
|
|
lines = prepstr(chbuf, BUFSIZE, attr, 0, 0, 1, getmaxx(win) - 4,
|
|
&width, 1, _("[ Press <SPACE> to continue ] "));
|
|
if (lines < 0) {
|
|
errno_exit("wait_for_key");
|
|
}
|
|
|
|
pr_center(win, y, 0, chbuf, lines, &width);
|
|
wrefresh(win);
|
|
|
|
done = false;
|
|
while (! done) {
|
|
key = wgetch(win);
|
|
switch (key) {
|
|
case ERR:
|
|
beep();
|
|
break;
|
|
|
|
#ifdef HANDLE_RESIZE_EVENTS
|
|
case KEY_RESIZE:
|
|
txresize();
|
|
break;
|
|
#endif // HANDLE_RESIZE_EVENTS
|
|
|
|
default:
|
|
done = true;
|
|
}
|
|
}
|
|
|
|
free(chbuf);
|
|
}
|
|
|
|
|
|
/***********************************************************************/
|
|
// End of file
|