Console with moving cursor, selections, etc

This commit is contained in:
Inuyasha 2016-11-03 17:30:30 -07:00
parent bb20cfd6be
commit e245cdfcbf
3 changed files with 322 additions and 213 deletions

View file

@ -84,13 +84,16 @@ UINT32 con_scalefactor; // text size scale factor
// hold 32 last lines of input for history // hold 32 last lines of input for history
#define CON_MAXPROMPTCHARS 256 #define CON_MAXPROMPTCHARS 256
#define CON_PROMPTCHAR '>' #define CON_PROMPTCHAR '$'
static char inputlines[32][CON_MAXPROMPTCHARS]; // hold last 32 prompt lines static char inputlines[32][CON_MAXPROMPTCHARS]; // hold last 32 prompt lines
static INT32 inputline; // current input line number static INT32 inputline; // current input line number
static INT32 inputhist; // line number of history input line to restore static INT32 inputhist; // line number of history input line to restore
static size_t input_cx; // position in current input line static size_t input_cur; // position of cursor in line
static size_t input_sel; // position of selection marker (I.E.: anything between this and input_cur is "selected")
static size_t input_len; // length of current line, used to bound cursor and such
// notice: input does NOT include the "$" at the start of the line. - 11/3/16
// protos. // protos.
static void CON_InputInit(void); static void CON_InputInit(void);
@ -383,14 +386,10 @@ void CON_Init(void)
// //
static void CON_InputInit(void) static void CON_InputInit(void)
{ {
INT32 i;
// prepare the first prompt line // prepare the first prompt line
memset(inputlines, 0, sizeof (inputlines)); memset(inputlines, 0, sizeof (inputlines));
for (i = 0; i < 32; i++)
inputlines[i][0] = CON_PROMPTCHAR;
inputline = 0; inputline = 0;
input_cx = 1; input_cur = input_sel = input_len = 0;
} }
//====================================================================== //======================================================================
@ -615,6 +614,86 @@ void CON_Ticker(void)
} }
} }
//
// ----
//
// Shortcuts for adding and deleting characters, strings, and sections
// Necessary due to moving cursor
//
static inline void CON_InputClear(void)
{
memset(inputlines[inputline], 0, CON_MAXPROMPTCHARS);
input_cur = input_sel = input_len = 0;
}
static inline void CON_InputSetString(const char *c)
{
memset(inputlines[inputline], 0, CON_MAXPROMPTCHARS);
strcpy(inputlines[inputline], c);
input_cur = input_sel = input_len = strlen(c);
}
static inline void CON_InputAddString(const char *c)
{
size_t csize = strlen(c);
if (input_len + csize > CON_MAXPROMPTCHARS-1)
return;
if (input_cur != input_len)
memmove(&inputlines[inputline][input_cur+csize], &inputlines[inputline][input_cur], input_len-input_cur);
memcpy(&inputlines[inputline][input_cur], c, csize);
input_len += csize;
input_sel = (input_cur += csize);
}
static inline void CON_InputDelSelection(void)
{
size_t start, end, len;
if (input_cur > input_sel)
{
start = input_sel;
end = input_cur;
}
else
{
start = input_cur;
end = input_sel;
}
len = (end - start);
if (end != input_len)
memmove(&inputlines[inputline][start], &inputlines[inputline][end], input_len-input_cur);
memset(&inputlines[inputline][input_len - len], 0, len);
input_len -= len;
input_sel = input_cur = start;
}
static inline void CON_InputAddChar(char c)
{
if (input_len >= CON_MAXPROMPTCHARS-1)
return;
if (input_cur != input_len)
memmove(&inputlines[inputline][input_cur+1], &inputlines[inputline][input_cur], input_len-input_cur);
inputlines[inputline][input_cur++] = c;
inputlines[inputline][++input_len] = 0;
input_sel = input_cur;
}
static inline void CON_InputDelChar(void)
{
if (!input_cur)
return;
if (input_cur != input_len)
memmove(&inputlines[inputline][input_cur-1], &inputlines[inputline][input_cur], input_len-input_cur);
inputlines[inputline][--input_len] = 0;
input_sel = --input_cur;
}
//
// ----
//
// Handles console key input // Handles console key input
// //
boolean CON_Responder(event_t *ev) boolean CON_Responder(event_t *ev)
@ -689,12 +768,16 @@ boolean CON_Responder(event_t *ev)
// show all cvars/commands that match what we have inputted // show all cvars/commands that match what we have inputted
if (key == KEY_TAB) if (key == KEY_TAB)
{ {
size_t i, len = strlen(inputlines[inputline]+1); size_t i, len;
if (input_cx < 2 || len >= 80 || strchr(inputlines[inputline]+1, ' ')) if (!completion[0])
return true; {
if (!input_len || input_len >= 40 || strchr(inputlines[inputline], ' '))
strcpy(completion, inputlines[inputline]+1); return true;
strcpy(completion, inputlines[inputline]);
comskips = varskips = 0;
}
len = strlen(completion);
//first check commands //first check commands
CONS_Printf("\nCommands:\n"); CONS_Printf("\nCommands:\n");
@ -725,18 +808,38 @@ boolean CON_Responder(event_t *ev)
if (key == 'x' || key == 'X') if (key == 'x' || key == 'X')
{ {
CONS_Printf("Cut\n"); if (input_sel > input_cur)
I_ClipboardCopy(&inputlines[inputline][input_cur], input_sel-input_cur);
else
I_ClipboardCopy(&inputlines[inputline][input_sel], input_cur-input_sel);
CON_InputDelSelection();
completion[0] = 0;
return true; return true;
} }
else if (key == 'c' || key == 'C') else if (key == 'c' || key == 'C')
{ {
CONS_Printf("Copy\n"); if (input_sel > input_cur)
I_ClipboardCopy(inputlines[inputline]+1, strlen(inputlines[inputline]+1)); I_ClipboardCopy(&inputlines[inputline][input_cur], input_sel-input_cur);
else
I_ClipboardCopy(&inputlines[inputline][input_sel], input_cur-input_sel);
return true; return true;
} }
else if (key == 'v' || key == 'V') else if (key == 'v' || key == 'V')
{ {
CONS_Printf("Paste: %s\n", I_ClipboardPaste()); const char *paste = I_ClipboardPaste();
if (input_sel != input_cur)
CON_InputDelSelection();
if (paste != NULL)
CON_InputAddString(paste);
completion[0] = 0;
return true;
}
// Select all
if (key == 'a' || key == 'A')
{
input_sel = 0;
input_cur = input_len;
return true; return true;
} }
@ -750,13 +853,11 @@ boolean CON_Responder(event_t *ev)
// sequential command completion forward and backward // sequential command completion forward and backward
// remember typing for several completions (a-la-4dos) // remember typing for several completions (a-la-4dos)
if (inputlines[inputline][input_cx-1] != ' ') if (!completion[0])
{ {
if (strlen(inputlines[inputline]+1) < 80) if (!input_len || input_len >= 40 || strchr(inputlines[inputline], ' '))
strcpy(completion, inputlines[inputline]+1); return true;
else strcpy(completion, inputlines[inputline]);
completion[0] = 0;
comskips = varskips = 0; comskips = varskips = 0;
} }
else else
@ -768,37 +869,26 @@ boolean CON_Responder(event_t *ev)
if (--varskips < 0) if (--varskips < 0)
comskips = -comskips - 2; comskips = -comskips - 2;
} }
else if (comskips > 0) else if (comskips > 0) comskips--;
comskips--;
} }
else else
{ {
if (comskips < 0) if (comskips < 0) varskips++;
varskips++; else comskips++;
else
comskips++;
} }
} }
if (comskips >= 0) if (comskips >= 0)
{ {
cmd = COM_CompleteCommand(completion, comskips); cmd = COM_CompleteCommand(completion, comskips);
if (!cmd) if (!cmd) // dirty: make sure if comskips is zero, to have a neg value
// dirty: make sure if comskips is zero, to have a neg value
comskips = -comskips - 1; comskips = -comskips - 1;
} }
if (comskips < 0) if (comskips < 0)
cmd = CV_CompleteVar(completion, varskips); cmd = CV_CompleteVar(completion, varskips);
if (cmd) if (cmd)
{ CON_InputSetString(va("%s ", cmd));
memset(inputlines[inputline]+1, 0, CON_MAXPROMPTCHARS-1);
strcpy(inputlines[inputline]+1, cmd);
input_cx = strlen(cmd) + 1;
inputlines[inputline][input_cx] = ' ';
input_cx++;
inputlines[inputline][input_cx] = 0;
}
else else
{ {
if (comskips > 0) if (comskips > 0)
@ -824,38 +914,80 @@ boolean CON_Responder(event_t *ev)
return true; return true;
} }
if (key == KEY_LEFTARROW)
{
if (input_cur != 0)
--input_cur;
if (!shiftdown)
input_sel = input_cur;
return true;
}
else if (key == KEY_RIGHTARROW)
{
if (input_cur < input_len)
++input_cur;
if (!shiftdown)
input_sel = input_cur;
return true;
}
else if (key == KEY_HOME)
{
input_cur = 0;
if (!shiftdown)
input_sel = input_cur;
return true;
}
else if (key == KEY_END)
{
input_cur = input_len;
if (!shiftdown)
input_sel = input_cur;
return true;
}
// At this point we're messing with input
// Clear completion
completion[0] = 0;
// command enter // command enter
if (key == KEY_ENTER) if (key == KEY_ENTER)
{ {
if (input_cx < 2) if (!input_len)
return true; return true;
// push the command // push the command
COM_BufAddText(inputlines[inputline]+1); COM_BufAddText(inputlines[inputline]);
COM_BufAddText("\n"); COM_BufAddText("\n");
CONS_Printf("%s\n", inputlines[inputline]); CONS_Printf("\x86""%c""\x80""%s\n", CON_PROMPTCHAR, inputlines[inputline]);
inputline = (inputline+1) & 31; inputline = (inputline+1) & 31;
inputhist = inputline; inputhist = inputline;
CON_InputClear();
memset(inputlines[inputline], 0, CON_MAXPROMPTCHARS);
inputlines[inputline][0] = CON_PROMPTCHAR;
input_cx = 1;
return true; return true;
} }
// backspace command prompt // backspace and delete command prompt
if (key == KEY_BACKSPACE) if (input_sel != input_cur)
{ {
if (input_cx > 1) if (key == KEY_BACKSPACE || key == KEY_DEL)
{ {
input_cx--; CON_InputDelSelection();
inputlines[inputline][input_cx] = 0; return true;
} }
}
else if (key == KEY_BACKSPACE)
{
CON_InputDelChar();
return true;
}
else if (key == KEY_DEL)
{
if (input_cur == input_len)
return true;
++input_cur;
CON_InputDelChar();
return true; return true;
} }
@ -864,18 +996,15 @@ boolean CON_Responder(event_t *ev)
{ {
// copy one of the previous inputlines to the current // copy one of the previous inputlines to the current
do do
{
inputhist = (inputhist - 1) & 31; // cycle back inputhist = (inputhist - 1) & 31; // cycle back
} while (inputhist != inputline && !inputlines[inputhist][1]); while (inputhist != inputline && !inputlines[inputhist][0]);
// stop at the last history input line, which is the // stop at the last history input line, which is the
// current line + 1 because we cycle through the 32 input lines // current line + 1 because we cycle through the 32 input lines
if (inputhist == inputline) if (inputhist == inputline)
inputhist = (inputline + 1) & 31; inputhist = (inputline + 1) & 31;
M_Memcpy(inputlines[inputline], inputlines[inputhist], CON_MAXPROMPTCHARS); CON_InputSetString(inputlines[inputhist]);
input_cx = strlen(inputlines[inputline]);
return true; return true;
} }
@ -885,23 +1014,14 @@ boolean CON_Responder(event_t *ev)
if (inputhist == inputline) if (inputhist == inputline)
return true; return true;
do do
{
inputhist = (inputhist + 1) & 31; inputhist = (inputhist + 1) & 31;
} while (inputhist != inputline && !inputlines[inputhist][1]); while (inputhist != inputline && !inputlines[inputhist][0]);
memset(inputlines[inputline], 0, CON_MAXPROMPTCHARS);
// back to currentline // back to currentline
if (inputhist == inputline) if (inputhist == inputline)
{ CON_InputClear();
inputlines[inputline][0] = CON_PROMPTCHAR;
input_cx = 1;
}
else else
{ CON_InputSetString(inputlines[inputhist]);
strcpy(inputlines[inputline], inputlines[inputhist]);
input_cx = strlen(inputlines[inputline]);
}
return true; return true;
} }
@ -926,15 +1046,12 @@ boolean CON_Responder(event_t *ev)
return false; return false;
// add key to cmd line here // add key to cmd line here
if (input_cx < CON_MAXPROMPTCHARS) if (key >= 'A' && key <= 'Z' && !shiftdown) //this is only really necessary for dedicated servers
{ key = key + 'a' - 'A';
if (key >= 'A' && key <= 'Z' && !shiftdown) //this is only really necessary for dedicated servers
key = key + 'a' - 'A';
inputlines[inputline][input_cx] = (char)key; if (input_sel != input_cur)
inputlines[inputline][input_cx + 1] = 0; CON_InputDelSelection();
input_cx++; CON_InputAddChar(key);
}
return true; return true;
} }
@ -1218,26 +1335,84 @@ void CONS_Error(const char *msg)
// //
static void CON_DrawInput(void) static void CON_DrawInput(void)
{ {
char *p;
size_t c;
INT32 x, y;
INT32 charwidth = (INT32)con_scalefactor << 3; INT32 charwidth = (INT32)con_scalefactor << 3;
const char *p = inputlines[inputline];
// input line scrolls left if it gets too long size_t c, clen, cend;
p = inputlines[inputline]; UINT8 lellip = 0, rellip = 0;
if (input_cx >= con_width-11) INT32 x, y, i;
p += input_cx - (con_width-11) + 1;
y = con_curlines - 12 * con_scalefactor; y = con_curlines - 12 * con_scalefactor;
x = charwidth*2;
for (c = 0, x = charwidth; c < con_width-11; c++, x += charwidth) clen = con_width-12;
V_DrawCharacter(x, y, p[c] | cv_constextsize.value | V_NOSCALESTART, !cv_allcaps.value);
// draw the blinking cursor if (input_len <= clen)
// {
x = ((input_cx >= con_width-11) ? (INT32)(con_width-11) : (INT32)((input_cx + 1)) * charwidth); c = 0;
if (con_tick < 4) clen = input_len;
V_DrawCharacter(x, y, '_' | cv_constextsize.value | V_NOSCALESTART, !cv_allcaps.value); }
else // input line scrolls left if it gets too long
{
clen -= 2; // There will always be some extra truncation -- but where is what we'll find out
if (input_cur <= clen/2)
{
// Close enough to right edge to show all
c = 0;
// Always will truncate right side from this position, so always draw right ellipsis
rellip = 1;
}
else
{
// Cursor in the middle (or right side) of input
// Move over for the ellipsis
c = input_cur - (clen/2) + 2;
x += charwidth*2;
lellip = 1;
if (c + clen >= input_len)
{
// Cursor in the right side of input
// We were too far over, so move back
c = input_len - clen;
}
else
{
// Cursor in the middle -- ellipses on both sides
clen -= 2;
rellip = 1;
}
}
}
if (lellip)
{
for (i = 0, x -= charwidth*3; i < 3; ++i, x += charwidth)
V_DrawCharacter(x, y, '.' | cv_constextsize.value | V_GRAYMAP | V_NOSCALESTART, !cv_allcaps.value);
}
else
V_DrawCharacter(x-charwidth, y, CON_PROMPTCHAR | cv_constextsize.value | V_GRAYMAP | V_NOSCALESTART, !cv_allcaps.value);
for (cend = c + clen; c < cend; ++c, x += charwidth)
{
if ((input_sel > c && input_cur <= c) || (input_sel <= c && input_cur > c))
{
V_DrawFill(x, y, charwidth, (10 * con_scalefactor), 107 | V_NOSCALESTART);
V_DrawCharacter(x, y, p[c] | cv_constextsize.value | V_YELLOWMAP | V_NOSCALESTART, !cv_allcaps.value);
}
else
V_DrawCharacter(x, y, p[c] | cv_constextsize.value | V_NOSCALESTART, !cv_allcaps.value);
if (c == input_cur && con_tick >= 4)
V_DrawCharacter(x, y + (con_scalefactor*2), '_' | cv_constextsize.value | V_NOSCALESTART, !cv_allcaps.value);
}
if (cend == input_cur && con_tick >= 4)
V_DrawCharacter(x, y + (con_scalefactor*2), '_' | cv_constextsize.value | V_NOSCALESTART, !cv_allcaps.value);
if (rellip)
{
for (i = 0; i < 3; ++i, x += charwidth)
V_DrawCharacter(x, y, '.' | cv_constextsize.value | V_GRAYMAP | V_NOSCALESTART, !cv_allcaps.value);
}
} }
// draw the last lines of console text to the top of the screen // draw the last lines of console text to the top of the screen

View file

@ -260,18 +260,6 @@ static char returnWadPath[256];
#include "../byteptr.h" #include "../byteptr.h"
#endif #endif
// FAKE_CLIPBOARD simulates system clipboard, but is just local to our app
#ifndef FAKE_CLIPBOARD
#include "SDL_syswm.h"
#if defined (_WIN32) && !defined (_XBOX)
#include <winuser.h>
#endif
// TODO: clipboard support for other OSes
#endif
/** \brief The JoyReset function /** \brief The JoyReset function
\param JoySet Joystick info to reset \param JoySet Joystick info to reset
@ -2659,86 +2647,30 @@ INT32 I_PutEnv(char *variable)
#endif #endif
} }
#ifdef FAKE_CLIPBOARD
static char __clipboard[512] = "";
#endif
INT32 I_ClipboardCopy(const char *data, size_t size) INT32 I_ClipboardCopy(const char *data, size_t size)
{ {
#ifdef FAKE_CLIPBOARD char storage[256];
if (size >= 512) size = 511; if (size > 255)
memcpy(__clipboard, data, size); size = 255;
__clipboard[size] = 0; memcpy(storage, data, size);
return 0; storage[size] = 0;
#else // !FAKE_CLIPBOARD
#if defined(_WIN32) && !defined(_XBOX) if (SDL_SetClipboardText(storage))
SDL_SysWMinfo syswm; return 0;
HGLOBAL *storage_ptr;
char *storage_raw;
SDL_VERSION(&syswm.version);
// Get window (HWND) information, use that to open the clipboard
if (!SDL_GetWindowWMInfo(window, &syswm) || !(OpenClipboard(syswm.info.win.window)))
return -1;
// Erase clipboard contents -- if we have nothing to copy, just leave them blank
EmptyClipboard();
if (size == 0)
goto clipboardend;
// Allocate movable global memory to store our text.
if (!(storage_ptr = GlobalAlloc(GMEM_MOVEABLE, size+1))
|| !(storage_raw = (char *)GlobalLock(storage_ptr))) // Lock our pointer (gives us access to its data)
goto clipboardfail;
memcpy(storage_raw, data, size);
storage_raw[size] = 0;
GlobalUnlock(storage_ptr); // Unlock it before sending it off
if (!SetClipboardData(CF_TEXT, storage_ptr)) // NOTE: this removes our permissions from the pointer
goto clipboardfail;
clipboardend:
CloseClipboard();
return 0; //OpenClipboard();
clipboardfail:
CloseClipboard(); // Don't leave me hanging
return -1; return -1;
#else
(void)data;
(void)size;
return -1;
#endif
#endif // FAKE_CLIPBOARD
} }
const char *I_ClipboardPaste(void) const char *I_ClipboardPaste(void)
{ {
#ifdef FAKE_CLIPBOARD static char clipboard_modified[256];
return (const char *)&__clipboard; char *clipboard_contents, *i = clipboard_modified;
#else // !FAKE_CLIPBOARD
#if defined(_WIN32) && !defined(_XBOX) if (!SDL_HasClipboardText())
HGLOBAL *clipboard_ptr;
const char *clipboard_raw;
static char clipboard_contents[512];
char *i = clipboard_contents;
if (!IsClipboardFormatAvailable(CF_TEXT))
return NULL; // Data either unavailable, or in wrong format
OpenClipboard(NULL); // NOTE: Don't need window pointer to get, only to set
if (!(clipboard_ptr = GetClipboardData(CF_TEXT))) // Get global pointer
return NULL; return NULL;
if (!(clipboard_raw = (const char *)GlobalLock(clipboard_ptr))) // Lock access -- gives us direct ptr to data clipboard_contents = SDL_GetClipboardText();
{ memcpy(clipboard_modified, clipboard_contents, 255);
CloseClipboard(); // Don't leave me hanging if we fail SDL_free(clipboard_contents);
return NULL; clipboard_modified[255] = 0;
}
memcpy(clipboard_contents, clipboard_raw, 511);
GlobalUnlock(clipboard_ptr); // Unlock for other apps
CloseClipboard(); // And make sure to close the clipboard for other apps too
clipboard_contents[511] = 0; // Done after unlock to cause as little interference as possible
while (*i) while (*i)
{ {
@ -2753,13 +2685,7 @@ const char *I_ClipboardPaste(void)
*i = '?'; // Nonprintable chars become question marks *i = '?'; // Nonprintable chars become question marks
++i; ++i;
} }
return (const char *)&clipboard_modified;
return (const char *)&clipboard_contents;
#else
return NULL;
#endif
#endif // FAKE_CLIPBOARD
} }
/** \brief The isWadPathOk function /** \brief The isWadPathOk function

View file

@ -774,43 +774,51 @@ void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
if (!screens[0]) if (!screens[0])
return; return;
if (x == 0 && y == 0 && w == BASEVIDWIDTH && h == BASEVIDHEIGHT) if (c & V_NOSCALESTART)
{ // Clear the entire screen, from dest to deststop. Yes, this really works.
memset(screens[0], (UINT8)(c&255), vid.width * vid.height * vid.bpp);
return;
}
dest = screens[0] + y*dupy*vid.width + x*dupx;
deststop = screens[0] + vid.rowbytes * vid.height;
if (w == BASEVIDWIDTH)
w = vid.width;
else
w *= dupx;
if (h == BASEVIDHEIGHT)
h = vid.height;
else
h *= dupy;
if (x && y && x + w < vid.width && y + h < vid.height)
{ {
// Center it if necessary dest = screens[0] + y*vid.width + x;
if (vid.width != BASEVIDWIDTH * dupx) deststop = screens[0] + vid.rowbytes * vid.height;
{ }
// dupx adjustments pretend that screen width is BASEVIDWIDTH * dupx, else
// so center this imaginary screen {
if (c & V_SNAPTORIGHT) if (x == 0 && y == 0 && w == BASEVIDWIDTH && h == BASEVIDHEIGHT)
dest += (vid.width - (BASEVIDWIDTH * dupx)); { // Clear the entire screen, from dest to deststop. Yes, this really works.
else if (!(c & V_SNAPTOLEFT)) memset(screens[0], (UINT8)(c&255), vid.width * vid.height * vid.bpp);
dest += (vid.width - (BASEVIDWIDTH * dupx)) / 2; return;
} }
if (vid.height != BASEVIDHEIGHT * dupy)
dest = screens[0] + y*dupy*vid.width + x*dupx;
deststop = screens[0] + vid.rowbytes * vid.height;
if (w == BASEVIDWIDTH)
w = vid.width;
else
w *= dupx;
if (h == BASEVIDHEIGHT)
h = vid.height;
else
h *= dupy;
if (x && y && x + w < vid.width && y + h < vid.height)
{ {
// same thing here // Center it if necessary
if (c & V_SNAPTOBOTTOM) if (vid.width != BASEVIDWIDTH * dupx)
dest += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width; {
else if (!(c & V_SNAPTOTOP)) // dupx adjustments pretend that screen width is BASEVIDWIDTH * dupx,
dest += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width / 2; // so center this imaginary screen
if (c & V_SNAPTORIGHT)
dest += (vid.width - (BASEVIDWIDTH * dupx));
else if (!(c & V_SNAPTOLEFT))
dest += (vid.width - (BASEVIDWIDTH * dupx)) / 2;
}
if (vid.height != BASEVIDHEIGHT * dupy)
{
// same thing here
if (c & V_SNAPTOBOTTOM)
dest += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width;
else if (!(c & V_SNAPTOTOP))
dest += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width / 2;
}
} }
} }