1
0
mirror of https://git.zap.org.au/git/trader.git synced 2024-07-07 15:54:14 -04:00

Add gettxline() to read a line of input from the keyboard

This commit is contained in:
John Zaitseff 2011-07-11 10:31:19 +10:00
parent 85e09ab4f2
commit dd281f795b
4 changed files with 703 additions and 6 deletions

View File

@ -37,7 +37,7 @@
* Module constants and type declarations *
************************************************************************/
#define OUTBUFSIZE (1024) /* Output string buffer size */
#define BUFSIZE (1024) /* Size of various string buffers */
typedef struct txwin {
@ -314,13 +314,13 @@ int center (WINDOW *win, int y, const bool clrline, const char *format, ...)
char *buf;
buf = malloc(OUTBUFSIZE);
buf = malloc(BUFSIZE);
if (buf == NULL) {
err_exit("out of memory");
}
va_start(args, format);
len = vsnprintf(buf, OUTBUFSIZE, format, args);
len = vsnprintf(buf, BUFSIZE, format, args);
va_end(args);
if (len < 0) {
return ERR;
@ -448,3 +448,660 @@ bool getanswer (WINDOW *win)
wrefresh(win);
return (key == 'Y');
}
/*-----------------------------------------------------------------------
Function: gettxline - Read a line from the keyboard (generic)
Arguments: win - Window to use
buf - Pointer to preallocated buffer
bufsize - Size of buffer
multifield - Allow <TAB>, etc, to move between fields
maxlen - Maximum length of string
emptyval - String used if an empty string is input
defaultval - Default string, used if KEY_DEFAULTVAL is
pressed as the first character
allowed - Characters allowed in the string
stripspc - Strip leading and trailing spaces from string
y, x - Start of field (line, col)
fieldsize - Size of field in characters
attr - Curses attribute to use for the field
Returns: int - Status code: OK, ERR or key code
This low-level function draws an input field fieldsize characters long
using attr as the window attribute, then reads a line of input from the
keyboard (of no more than maxlen characters) and places it into the
preallocated buffer buf[]. On entry, buf[] must contain the current
input string: this allows for resumed editing of an input line. On
exit, buf[] will contain the input string; this string is printed using
the original window attributes with A_BOLD added. Many EMACS/Bash-
style keyboard editing shortcuts are allowed.
If ENTER (or equivalent) is pressed, OK is returned. In this case,
leading and trailing spaces are stripped if stripspc is true; if an
empty string is entered, the string pointed to by emptyval (if not
NULL) is stored in buf[].
If CANCEL, ESC or ^G is pressed, ERR is returned. In this case, buf[]
contains the string as edited by the user: emptyval is NOT used, nor
are leading and trailing spaces stripped.
If multifield is true, the UP and DOWN arrow keys, as well as TAB,
Shift-TAB, ^P (Previous) and ^N (Next) return KEY_UP or KEY_DOWN as
appropriate. As with CANCEL etc., emptyval is NOT used, nor are
leading and trailing spaces stripped.
If KEY_DEFAULTVAL is pressed when the input line is empty, the string
pointed to by defaultval (if not NULL) is placed in the buffer as if
typed by the user. Editing is NOT terminated in this case.
If allowed points to a string (ie, is not NULL), only characters in
that string are allowed to be entered into the input line. For
example, if allowed points to "0123456789abcdefABCDEF", only those
characters would be allowed (allowing a hexadecimal number to be
entered).
*/
int gettxline (WINDOW *win, char *buf, int bufsize, bool multifield,
int maxlen, const char *emptyval, const char *defaultval,
const char *allowed, bool stripspc, int y, int x,
int fieldsize, int attr)
{
int i, len, pos, cpos, nb;
int oldattr;
bool done, redraw, first;
char c;
int key, key2, ret;
assert(buf != NULL);
assert(bufsize > 1);
assert(maxlen > 0);
assert(maxlen < bufsize);
assert(fieldsize > 1);
keypad(win, true);
meta(win, true);
wtimeout(win, -1);
oldattr = getbkgd(win) & ~A_CHARTEXT;
wattrset(win, attr & ~A_CHARTEXT);
curs_set(CURS_ON);
len = strlen(buf);
pos = len; // pos can be from 0 to strlen(buf)
cpos = MIN(len, fieldsize - 1);
redraw = true;
done = false;
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", fieldsize, fieldsize,
buf + pos - cpos);
nb = (len - (pos - cpos) > fieldsize) ?
0 : fieldsize - (len - (pos - cpos));
wmove(win, y, x + fieldsize - nb);
for (i = 0; i < nb; i++) {
waddch(win, ((attr & A_CHARTEXT) == 0) ? attr | ' ' : attr);
}
wmove(win, y, x + cpos);
wrefresh(win);
}
key = wgetch(win);
if (key == ERR) {
// Do nothing on ERR
;
} else if ((key == KEY_DEFAULTVAL) && (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, fieldsize - 1);
redraw = true;
} else if ((key < 0400) && (! iscntrl(key))) {
if ((len >= maxlen) ||
((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 < fieldsize - 1) {
cpos++;
}
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
i = 0;
while ((i < len) && isspace(buf[i])) {
i++;
}
memmove(buf, buf + i, len - i + 1);
len -= i;
// Strip trailing spaces
i = len;
while ((i > 0) && isspace(buf[i - 1])) {
i--;
}
buf[i] = '\0';
len = i;
}
if ((emptyval != NULL) && (len == 0)) {
strncpy(buf, emptyval, bufsize - 1);
buf[bufsize - 1] = '\0';
}
ret = OK;
done = true;
break;
case KEY_CANCEL:
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 < fieldsize - 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, fieldsize - 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 < fieldsize - 1) {
cpos++;
}
}
while ((pos < len) && isalnum(buf[pos])) {
pos++;
if (cpos < fieldsize - 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--;
}
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
redraw = true;
}
break;
case KEY_CLEAR:
// Delete the entire line
strcpy(buf, "");
len = 0;
pos = 0;
cpos = 0;
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;
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
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.
*/
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;
redraw = true;
}
break;
// Miscellaneous keys and events
case KEY_CTRL('T'):
// Transpose characters
if ((pos == 0) || (len <= 1)) {
beep();
} else if (pos == len) {
c = buf[pos - 1];
buf[pos - 1] = buf[pos - 2];
buf[pos - 2] = c;
redraw = true;
} else {
c = buf[pos];
buf[pos] = buf[pos - 1];
buf[pos - 1] = c;
pos++;
if (cpos < fieldsize - 1) {
cpos++;
}
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 < fieldsize - 1) {
cpos++;
}
}
while ((pos < len) && isalnum(buf[pos])) {
pos++;
if (cpos < fieldsize - 1) {
cpos++;
}
}
redraw = true;
break;
// Deletion keys
case KEY_BS:
case KEY_BACKSPACE:
case KEY_DEL:
// Delete the previous word (different from ^W)
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;
redraw = true;
break;
case 'D':
case 'd':
// Delete the next word
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
redraw = true;
break;
case '\\':
case ' ':
// Delete all surrounding spaces; if key2 == ' ',
// also insert one space
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 >= maxlen) || ((allowed != NULL) &&
(strchr(allowed, key) == NULL))) {
beep();
} else {
memmove(buf + pos + 1, buf + pos, len - pos + 1);
buf[pos] = ' ';
len++;
pos++;
if (cpos < fieldsize - 1) {
cpos++;
}
}
}
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 < fieldsize - 1) {
cpos++;
}
}
while ((pos < len) && isalnum(buf[pos])) {
buf[pos] = toupper(buf[pos]);
pos++;
if (cpos < fieldsize - 1) {
cpos++;
}
}
redraw = true;
break;
case 'L':
case 'l':
// Convert word (from cursor onwards) to lower case
while ((pos < len) && (! isalnum(buf[pos]))) {
pos++;
if (cpos < fieldsize - 1) {
cpos++;
}
}
while ((pos < len) && isalnum(buf[pos])) {
buf[pos] = tolower(buf[pos]);
pos++;
if (cpos < fieldsize - 1) {
cpos++;
}
}
redraw = true;
break;
case 'C':
case 'c':
// Convert current letter to upper case, following
// letters to lower case
first = true;
while ((pos < len) && (! isalnum(buf[pos]))) {
pos++;
if (cpos < fieldsize - 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 < fieldsize - 1) {
cpos++;
}
}
redraw = true;
break;
// Miscellaneous keys and events
case KEY_RESIZE:
case KEY_EVENT:
ret = key;
done = true;
break;
default:
beep();
break;
}
}
wtimeout(win, -1);
break;
case KEY_RESIZE:
case KEY_EVENT:
ret = key;
done = true;
break;
default:
beep();
break;
}
}
}
wattrset(win, oldattr);
curs_set(CURS_OFF);
wattron(win, A_BOLD);
mvwprintw(win, y, x, "%-*.*s", fieldsize, fieldsize, buf);
wattrset(win, oldattr);
wrefresh(win);
return OK;
}

View File

@ -51,9 +51,33 @@ typedef enum curs_type {
// Keycodes
#define KEY_TAB (011)
#define KEY_RETURN (012)
#define KEY_ESC (033)
#define KEY_BS (0010)
#define KEY_TAB (0011)
#define KEY_RETURN (0012)
#define KEY_ESC (0033)
#define KEY_DEL (0177)
#define KEY_CTRL(x) (x - 0100)
// Control-arrow key combinations
#ifndef KEY_CDOWN
# define KEY_CDOWN (01007)
#endif
#ifndef KEY_CUP
# define KEY_CUP (01060)
#endif
#ifndef KEY_CLEFT
# define KEY_CLEFT (01033)
#endif
#ifndef KEY_CRIGHT
# define KEY_CRIGHT (01052)
#endif
// Keycode for inserting the default value
#define KEY_DEFAULTVAL '='
// Timeout value (in ms) for Meta-X-style keyboard input
#define META_TIMEOUT (1000)
/*
@ -124,6 +148,10 @@ extern int attrpr (WINDOW *win, int attr, const char *format, ...)
extern int gettxchar (WINDOW *win);
extern bool getanswer (WINDOW *win);
extern int gettxline (WINDOW *win, char *buf, int bufsize, bool multifield,
int maxlen, const char *emptyval, const char *defaultval,
const char *allowed, bool stripspc, int y, int x,
int fieldsize, int attr);
#endif /* included_INTF_H */

View File

@ -47,6 +47,8 @@
#define _GNU_SOURCE 1
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>

View File

@ -40,6 +40,16 @@
#define GAME_FILENAME_BUFSIZE (16)
/************************************************************************
* Utility macro definitions *
************************************************************************/
// Type-unsafe minimum and maximum macros
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
/************************************************************************
* Utility function declarations *
************************************************************************/