#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); strncpy(input->buffer, c, input->buffer_size); input->cursor = input->select = input->length = strlen(input->buffer); } 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 (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); } static int M_TextInputEmoteStart(textinput_t *input) { if (input->cursor == 0) return -1; int emotestart = -1; 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; break; } } return emotestart; } static boolean M_TextInputCompleteEmote(textinput_t *input, emote_autocomplete_t *autocomplete) { if (input->cursor == 0) return false; // If we're in "autocomplete" state, delete ':' for latest autocompleted emote if (autocomplete->complete[0]) { if (input->buffer[input->cursor-1] == ':') { M_TextInputDelChar(input); } autocomplete->skip++; } else { // Try to find emote to autocomplete int emotestart = autocomplete->emotestart; // No emote - no autocomplete if (emotestart == -1 || input->cursor - emotestart < 2) return false; strlcpy(autocomplete->complete, &input->buffer[emotestart], input->cursor-emotestart+1); } emote_t *completed = M_FindEmote(autocomplete->complete, strlen(autocomplete->complete), autocomplete->skip); // We hit end of list of suggested emotes if (completed == NULL) { autocomplete->skip = 0; completed = M_FindEmote(autocomplete->complete, strlen(autocomplete->complete), autocomplete->skip); // NULL again, list was empty to begin with if (completed == NULL) { // Clear autocompletion state autocomplete->complete[0] = 0; return false; } } // Erase previously typed emote. Not really efficent but too lazy to count chars and do M_TextInputDel() while (input->buffer[input->cursor-1] != ':') { M_TextInputDelChar(input); } M_TextInputAddString(input, completed->name); M_TextInputAddChar(input, ':'); return true; } boolean M_TextInputHandleEmotes(textinput_t *input, INT32 key, emote_autocomplete_t *autocomplete) { 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 update it autocomplete->emotestart = M_TextInputEmoteStart(input); if (key == '\t' || (autocomplete->complete[0] == 0 && key == KEY_ENTER)) { if (M_TextInputCompleteEmote(input, autocomplete) && key == KEY_ENTER) // Don't send message if we've autocompleted an emote ret = true; } else { // Reset state just in case autocomplete->complete[0] = 0; autocomplete->skip = 0; } 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))); } }