534 lines
13 KiB
C
534 lines
13 KiB
C
#include "m_textinput.h"
|
|
#include "m_menu.h" // MAXSTRINGLENGTH
|
|
#include "v_video.h"
|
|
#include "r_main.h" // renderisnewtic
|
|
#include "i_system.h"
|
|
#include "keys.h"
|
|
#include "console.h"
|
|
|
|
static void M_TextInputDel(textinput_t *input, size_t start, size_t end)
|
|
{
|
|
size_t len;
|
|
|
|
len = (end - start);
|
|
|
|
if (end != input->length)
|
|
memmove(&input->buffer[start], &input->buffer[end], input->length-end);
|
|
memset(&input->buffer[input->length - len], 0, len);
|
|
|
|
input->length -= len;
|
|
|
|
if (input->select >= end)
|
|
input->select -= len;
|
|
else if (input->select > start)
|
|
input->select = start;
|
|
|
|
if (input->cursor >= end)
|
|
input->cursor -= len;
|
|
else if (input->cursor > start)
|
|
input->cursor = start;
|
|
}
|
|
|
|
static void M_TextInputDelSelection(textinput_t *input)
|
|
{
|
|
size_t start, end;
|
|
|
|
if (input->cursor > input->select)
|
|
{
|
|
start = input->select;
|
|
end = input->cursor;
|
|
}
|
|
else
|
|
{
|
|
start = input->cursor;
|
|
end = input->select;
|
|
}
|
|
|
|
M_TextInputDel(input, start, end);
|
|
|
|
input->select = input->cursor = start;
|
|
}
|
|
|
|
static void M_TextInputAddString(textinput_t *input, const char *c)
|
|
{
|
|
size_t csize = strlen(c);
|
|
|
|
if (input->length + csize > input->buffer_size-1)
|
|
return;
|
|
|
|
if (input->cursor != input->length)
|
|
memmove(&input->buffer[input->cursor+csize], &input->buffer[input->cursor], input->length-input->cursor);
|
|
memcpy(&input->buffer[input->cursor], c, csize);
|
|
input->length += csize;
|
|
input->select = (input->cursor += csize);
|
|
input->buffer[input->length] = 0;
|
|
}
|
|
|
|
static void M_TextInputAddChar(textinput_t *input, char c)
|
|
{
|
|
if (input->length >= input->buffer_size-1)
|
|
return;
|
|
|
|
if (input->cursor != input->length)
|
|
memmove(&input->buffer[input->cursor+1], &input->buffer[input->cursor], input->length-input->cursor);
|
|
input->buffer[input->cursor++] = c;
|
|
input->buffer[++input->length] = 0;
|
|
input->select = input->cursor;
|
|
}
|
|
|
|
static void M_TextInputDelChar(textinput_t *input)
|
|
{
|
|
if (!input->cursor)
|
|
return;
|
|
|
|
if (input->cursor != input->length)
|
|
memmove(&input->buffer[input->cursor-1], &input->buffer[input->cursor], input->length-input->cursor);
|
|
input->buffer[--input->length] = 0;
|
|
input->select = --input->cursor;
|
|
}
|
|
|
|
static void M_TextInputToWordEnd(textinput_t *input, boolean move_sel)
|
|
{
|
|
// Skip spaces
|
|
while (input->cursor < input->length && isspace(input->buffer[input->cursor]))
|
|
++input->cursor;
|
|
|
|
// Skip word
|
|
while (input->cursor < input->length && !isspace(input->buffer[input->cursor]))
|
|
++input->cursor;
|
|
|
|
if (move_sel) input->select = input->cursor;
|
|
}
|
|
|
|
static void M_TextInputToWordBegin(textinput_t *input, boolean move_sel)
|
|
{
|
|
// Hack, always move back 1 character if possible so if we press ctrl-left at a word beginning
|
|
// we move to previous word
|
|
if (input->cursor) --input->cursor;
|
|
|
|
// Skip spaces
|
|
while (input->cursor && isspace(input->buffer[input->cursor]))
|
|
--input->cursor;
|
|
|
|
// Skip word
|
|
while (input->cursor && !isspace(input->buffer[input->cursor]))
|
|
--input->cursor;
|
|
|
|
// Unless we reached beginning of line, we're pointing at a space before word, so move cursor
|
|
// forward to fix that
|
|
if (input->cursor) ++input->cursor;
|
|
|
|
if (move_sel) input->select = input->cursor;
|
|
}
|
|
|
|
static void M_TextInputPaste(textinput_t *input)
|
|
{
|
|
const char *paste = I_ClipboardPaste();
|
|
if (input->select != input->cursor)
|
|
M_TextInputDelSelection(input);
|
|
if (paste != NULL)
|
|
M_TextInputAddString(input, paste);
|
|
}
|
|
|
|
static void M_TextInputLeft(textinput_t *input, boolean emotes)
|
|
{
|
|
// Just do it simple way if possible
|
|
if (!emotes || input->cursor < 2 || input->buffer[input->cursor-1] != ':')
|
|
{
|
|
if (input->cursor != 0)
|
|
--input->cursor;
|
|
return;
|
|
}
|
|
|
|
// Otherwise we need to check if we have to skip emote
|
|
size_t start = input->cursor-2;
|
|
|
|
while (input->buffer[start] != ':')
|
|
{
|
|
// We reached start of string and didn't find ':' symbol, thats definitely not an emote
|
|
// so just do it simple way
|
|
if (start == 0)
|
|
{
|
|
M_TextInputLeft(input, false);
|
|
return;
|
|
}
|
|
|
|
--start;
|
|
}
|
|
|
|
int emotelen = 0;
|
|
if (M_VerifyEmote(input->buffer+start, &emotelen))
|
|
input->cursor -= emotelen; // also skip the :
|
|
else
|
|
M_TextInputLeft(input, false); // Not a valid emote, do the simple thing
|
|
}
|
|
|
|
static void M_TextInputRight(textinput_t *input, boolean emotes)
|
|
{
|
|
// Just do it simple way if possible
|
|
if (!emotes || input->cursor == input->length || input->buffer[input->cursor] != ':')
|
|
{
|
|
if (input->cursor < input->length)
|
|
++input->cursor;
|
|
return;
|
|
}
|
|
|
|
int emotelen = 0;
|
|
if (M_VerifyEmote(input->buffer+input->cursor, &emotelen))
|
|
input->cursor += emotelen; // Also skip the :
|
|
else
|
|
M_TextInputRight(input, false); // Not a valid emote, do the simple thing
|
|
}
|
|
|
|
// Check if we're inside a valid emote
|
|
static boolean M_TextInputCheckEmote(textinput_t *input)
|
|
{
|
|
int start = input->cursor;
|
|
|
|
// Definitely not an emote
|
|
if (start == 0)
|
|
return false;
|
|
|
|
// Might be closing :, need to skip it just in case
|
|
if (input->buffer[start] == ':')
|
|
--start;
|
|
|
|
while (input->buffer[start] != ':')
|
|
{
|
|
// No ':' found, definitely not inside emote
|
|
if (start == 0)
|
|
return false;
|
|
|
|
--start;
|
|
}
|
|
|
|
// Found what may be emote start, now we can check
|
|
return M_VerifyEmote(input->buffer+start, NULL) != NULL;
|
|
}
|
|
|
|
void M_TextInputInit(textinput_t *input, char *buffer, size_t buffer_size)
|
|
{
|
|
input->buffer = buffer;
|
|
input->buffer_size = buffer_size;
|
|
|
|
input->skull = 0;
|
|
|
|
M_TextInputClear(input);
|
|
}
|
|
|
|
void M_TextInputClear(textinput_t *input)
|
|
{
|
|
input->cursor = 0;
|
|
input->select = 0;
|
|
input->length = 0;
|
|
|
|
input->buffer[0] = 0;
|
|
}
|
|
|
|
void M_TextInputSetString(textinput_t *input, const char *c)
|
|
{
|
|
memset(input->buffer, 0, input->buffer_size);
|
|
strcpy(input->buffer, c);
|
|
input->cursor = input->select = input->length = strlen(c);
|
|
}
|
|
|
|
static boolean M_TextInputHandleBase(textinput_t *input, INT32 key, boolean emotes)
|
|
{
|
|
if (key == KEY_LSHIFT || key == KEY_RSHIFT
|
|
|| key == KEY_LCTRL || key == KEY_RCTRL
|
|
|| key == KEY_LALT || key == KEY_RALT)
|
|
return false;
|
|
|
|
if (shiftdown && key == KEY_INS)
|
|
{
|
|
M_TextInputPaste(input);
|
|
return true;
|
|
}
|
|
|
|
//if ((cv_keyboardlayout.value != 3 && ctrldown) || (cv_keyboardlayout.value == 3 && ctrldown && !altdown))
|
|
if (ctrldown)
|
|
{
|
|
if (key == 'x' || key == 'X')
|
|
{
|
|
if (input->select > input->cursor)
|
|
I_ClipboardCopy(&input->buffer[input->cursor], input->select-input->cursor);
|
|
else
|
|
I_ClipboardCopy(&input->buffer[input->select], input->cursor-input->select);
|
|
M_TextInputDelSelection(input);
|
|
return true;
|
|
}
|
|
else if (key == 'c' || key == 'C')
|
|
{
|
|
if (input->select > input->cursor)
|
|
I_ClipboardCopy(&input->buffer[input->cursor], input->select-input->cursor);
|
|
else
|
|
I_ClipboardCopy(&input->buffer[input->select], input->cursor-input->select);
|
|
return true;
|
|
}
|
|
else if (key == 'v' || key == 'V')
|
|
{
|
|
M_TextInputPaste(input);
|
|
return true;
|
|
}
|
|
else if (key == 'w' || key == 'W')
|
|
{
|
|
size_t word_start, word_end, i;
|
|
word_end = i = input->cursor;
|
|
|
|
// Unless we're pointing at the beginning of line, decrement i so we only start
|
|
// removing symbols that come before the cursor
|
|
if (i) --i;
|
|
|
|
// We might be pointing to spaces, skip them first
|
|
while (i && isspace(input->buffer[i]))
|
|
--i;
|
|
|
|
// Now skip the "word"
|
|
while (i && !isspace(input->buffer[i]))
|
|
--i;
|
|
|
|
// Unless we reached beginning of line, i is pointing at first space that was found
|
|
// before word start, and we don't want to remove it
|
|
if (i) ++i;
|
|
|
|
word_start = i;
|
|
|
|
if (word_start != word_end)
|
|
M_TextInputDel(input, word_start, word_end);
|
|
|
|
return true;
|
|
}
|
|
else if (key == KEY_RIGHTARROW)
|
|
M_TextInputToWordEnd(input, !shiftdown);
|
|
else if (key == KEY_LEFTARROW)
|
|
M_TextInputToWordBegin(input, !shiftdown);
|
|
|
|
// Select all
|
|
if (key == 'a' || key == 'A')
|
|
{
|
|
input->select = 0;
|
|
input->cursor = input->length;
|
|
return true;
|
|
}
|
|
|
|
// ...why shouldn't it eat the key? if it doesn't, it just means you
|
|
// can control Sonic from the console, which is silly
|
|
return true;
|
|
}
|
|
|
|
if (key == KEY_LEFTARROW)
|
|
{
|
|
M_TextInputLeft(input, emotes);
|
|
if (!shiftdown)
|
|
input->select = input->cursor;
|
|
return true;
|
|
}
|
|
else if (key == KEY_RIGHTARROW)
|
|
{
|
|
M_TextInputRight(input, emotes);
|
|
if (!shiftdown)
|
|
input->select = input->cursor;
|
|
return true;
|
|
}
|
|
else if (key == KEY_HOME)
|
|
{
|
|
input->cursor = 0;
|
|
if (!shiftdown)
|
|
input->select = input->cursor;
|
|
return true;
|
|
}
|
|
else if (key == KEY_END)
|
|
{
|
|
input->cursor = input->length;
|
|
if (!shiftdown)
|
|
input->select = input->cursor;
|
|
return true;
|
|
}
|
|
|
|
// backspace and delete command prompt
|
|
if (input->select != input->cursor)
|
|
{
|
|
if (key == KEY_BACKSPACE || key == KEY_DEL)
|
|
{
|
|
M_TextInputDelSelection(input);
|
|
return true;
|
|
}
|
|
}
|
|
else if (key == KEY_BACKSPACE)
|
|
{
|
|
M_TextInputDelChar(input);
|
|
return true;
|
|
}
|
|
else if (key == KEY_DEL)
|
|
{
|
|
if (input->cursor == input->length)
|
|
return true;
|
|
++input->cursor;
|
|
M_TextInputDelChar(input);
|
|
return true;
|
|
}
|
|
|
|
// allow people to use keypad in console (good for typing IP addresses) - Calum
|
|
if (key >= KEY_KEYPAD7 && key <= KEY_KPADDEL)
|
|
{
|
|
char keypad_translation[] = {'7','8','9','-',
|
|
'4','5','6','+',
|
|
'1','2','3',
|
|
'0','.'};
|
|
|
|
key = keypad_translation[key - KEY_KEYPAD7];
|
|
}
|
|
else if (key == KEY_KPADSLASH)
|
|
key = '/';
|
|
|
|
// same capslock code as hu_stuff.c's HU_responder. Check there for details.
|
|
key = CON_ShiftChar(key);
|
|
|
|
// enter a char into the command prompt
|
|
if (key < 32 || key > 127)
|
|
return false;
|
|
|
|
if (input->select != input->cursor)
|
|
M_TextInputDelSelection(input);
|
|
M_TextInputAddChar(input, key);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Just an alias, more or less
|
|
boolean M_TextInputHandle(textinput_t *input, INT32 key)
|
|
{
|
|
return M_TextInputHandleBase(input, key, false);
|
|
}
|
|
|
|
boolean M_TextInputHandleEmotes(textinput_t *input, INT32 key, emote_t *suggestions[], int maxsuggestions)
|
|
{
|
|
boolean ret = M_TextInputHandleBase(input, key, true);
|
|
|
|
// After this handled key we ended up inside an emote, lets fix that
|
|
if (M_TextInputCheckEmote(input))
|
|
M_TextInputToWordEnd(input, !shiftdown);
|
|
|
|
// Always clear the first entry
|
|
suggestions[0] = NULL;
|
|
|
|
// For autocomplete
|
|
int emotestart = 0;
|
|
|
|
// Try find suggestions for emote names
|
|
for (int i = input->cursor-1; i >= 0 && input->cursor-i <= MAXEMOTENAME; --i)
|
|
{
|
|
// Space can't be part of emote name
|
|
if (isspace(input->buffer[i]))
|
|
break;
|
|
|
|
// Found a :, try suggest emotes
|
|
if (input->buffer[i] == ':')
|
|
{
|
|
emotestart = i+1;
|
|
// ...But only if we typed at least something
|
|
if ((int)input->cursor-(i-1) < 3)
|
|
break;
|
|
|
|
for (int skip = 0; skip < maxsuggestions; ++skip)
|
|
{
|
|
suggestions[skip] = M_FindEmote(input->buffer+i+1, input->cursor-i-1, skip);
|
|
|
|
// No more suggestions
|
|
if (!suggestions[skip])
|
|
break;
|
|
}
|
|
|
|
// In any case, we found what we wanted, can exit the loop now
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If we suggest emotes, try autocomplete
|
|
if (key == '\t' && suggestions[0])
|
|
{
|
|
int pos = (input->cursor-emotestart);
|
|
boolean autocomplete = true;
|
|
|
|
while (autocomplete)
|
|
{
|
|
// Check if current character matches for all suggestions
|
|
char c = suggestions[0]->name[pos];
|
|
|
|
// End of string reached
|
|
if (!c)
|
|
break;
|
|
|
|
for (int i = 1; i < maxsuggestions && suggestions[i]; ++i)
|
|
{
|
|
if (suggestions[i]->name[pos] != c)
|
|
{
|
|
autocomplete = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
++pos;
|
|
|
|
if (autocomplete)
|
|
M_TextInputAddChar(input, c);
|
|
}
|
|
|
|
// This was the only suggestion, finish autocomplete with a ':' and clear suggestions
|
|
if (maxsuggestions == 1 || !suggestions[1])
|
|
{
|
|
M_TextInputAddChar(input, ':');
|
|
suggestions[0] = NULL;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void M_DrawTextInputScroll(INT32 x, INT32 y, textinput_t *input, INT32 flags, INT32 MAXINPUTWIDTH)
|
|
{
|
|
if (renderisnewtic)
|
|
input->skull++;
|
|
input->skull %= 8;
|
|
|
|
char nametodraw[MAXSTRINGLENGTH*2+1] = {0};
|
|
|
|
size_t drawstart = 0;
|
|
size_t drawend = 0; // Only used for selection
|
|
|
|
INT32 skullx = x;
|
|
|
|
while (V_SubStringWidth(input->buffer+drawstart, input->cursor-drawstart, flags) > MAXINPUTWIDTH)
|
|
++drawstart;
|
|
|
|
size_t drawlength = V_SubStringLengthToFit(input->buffer+drawstart, MAXINPUTWIDTH+8, flags)+1;
|
|
drawend = drawstart + drawlength;
|
|
|
|
memcpy(nametodraw, input->buffer+drawstart, drawlength);
|
|
|
|
if (input->length)
|
|
skullx += V_SubStringWidth(nametodraw, input->cursor-drawstart, flags);
|
|
|
|
V_DrawString(x, y, flags, nametodraw);
|
|
|
|
// draw text cursor for name
|
|
if (input->skull < 4) // blink cursor
|
|
V_DrawCharacter(skullx, y+3, '_'|(flags & ~(V_FLIP|V_PARAMMASK)), false);
|
|
|
|
// draw selection
|
|
if (input->select != input->cursor)
|
|
{
|
|
size_t start = min(input->select, input->cursor);
|
|
size_t end = max(input->select, input->cursor);
|
|
|
|
ssize_t leftofs = start - drawstart;
|
|
INT32 startx = leftofs >= 0 ? V_SubStringWidth(nametodraw, leftofs, flags) : -2;
|
|
INT32 width = V_SubStringWidth(nametodraw + max(0, leftofs), end - start + min(0, leftofs), flags);
|
|
if (startx < 0)
|
|
width -= startx;
|
|
if (end > drawend)
|
|
width += 2;
|
|
|
|
V_DrawFill(x+startx, y, width, 8, 103|V_TRANSLUCENT|(flags & ~(V_ALPHAMASK|V_PARAMMASK)));
|
|
}
|
|
}
|