diff --git a/src/console.c b/src/console.c index 025bc1c1..f4a1e4f8 100644 --- a/src/console.c +++ b/src/console.c @@ -91,11 +91,13 @@ static char inputlines[32][CON_MAXPROMPTCHARS]; // hold last 32 prompt lines static INT32 inputline; // current input line number static INT32 inputhist; // line number of history input line to restore static size_t input_cx; // position in current input line +static INT32 input_selection; // selection border in current input line, -1 if no selection // protos. static void CON_InputInit(void); static void CON_RecalcSize(void); +static void CON_DeleteSelectedText(void); static void CONS_hudlines_Change(void); static void CON_DrawBackpic(patch_t *pic, INT32 startx, INT32 destwidth); //static void CON_DrawBackpic2(pic_t *pic, INT32 startx, INT32 destwidth); @@ -394,6 +396,7 @@ static void CON_InputInit(void) inputlines[i][0] = CON_PROMPTCHAR; inputline = 0; input_cx = 1; + input_selection = -1; } //====================================================================== @@ -618,6 +621,25 @@ void CON_Ticker(void) } } +// Deletes selected text, assuming there is, and resets selection. +// Also sets the cursor to the correct position. +// +static void CON_DeleteSelectedText(void) +{ + UINT32 i, j; + char *line = inputlines[inputline]; + size_t selstart = min(input_cx, (size_t)input_selection); + size_t selend = max(input_cx, (size_t)input_selection); + + for (i = selstart, j = selend; line[j]; ++i, ++j) + line[i] = line[j]; + while (line[i]) + line[i++] = 0; + + input_cx = selstart; + input_selection = -1; +} + // Handles console key input // boolean CON_Responder(event_t *ev) @@ -704,6 +726,9 @@ boolean CON_Responder(event_t *ev) // command completion forward (tab) and backward (shift-tab) if (key == KEY_TAB) { + input_cx = strlen(inputlines[inputline]); // make sure the cursor is at the end of the string, in case we were inserting + input_selection = -1; // make sure there is no text selected, it would look odd + // show all cvars/commands that match what we have inputted if (ctrldown) { @@ -839,21 +864,51 @@ boolean CON_Responder(event_t *ev) return true; } - if (key == KEY_HOME) // oldest text in buffer + if (key == KEY_HOME) { - con_scrollup = (con_totallines-((con_curlines-16)>>3)); + if (shiftdown) + { + if (input_selection == -1) + input_selection = input_cx; + } + else + input_selection = -1; + + if (ctrldown) + con_scrollup = (con_totallines-((con_curlines-16)>>3)); // oldest text in buffer + else + input_cx = 1; + + if ((INT32)input_cx == input_selection) + input_selection = -1; + return true; } - else if (key == KEY_END) // most recent text in buffer + else if (key == KEY_END) { - con_scrollup = 0; + if (shiftdown) + { + if (input_selection == -1) + input_selection = input_cx; + } + else + input_selection = -1; + + if (ctrldown) + con_scrollup = 0; // most recent text in buffer + else + input_cx = strlen(inputlines[inputline]); + + if ((INT32)input_cx == input_selection) + input_selection = -1; + return true; } // command enter if (key == KEY_ENTER) { - if (input_cx < 2) + if (strlen(inputlines[inputline]) < 2) return true; // push the command @@ -868,18 +923,107 @@ boolean CON_Responder(event_t *ev) memset(inputlines[inputline], 0, CON_MAXPROMPTCHARS); inputlines[inputline][0] = CON_PROMPTCHAR; input_cx = 1; + input_selection = -1; return true; } - // backspace command prompt + // backspace command prompt or delete selected text if (key == KEY_BACKSPACE) { - if (input_cx > 1) + if (input_selection == -1) { - input_cx--; - inputlines[inputline][input_cx] = 0; + if (input_cx > 1) + { + UINT32 i, j; + char *line = inputlines[inputline]; + + for (i = input_cx - 1, j = input_cx; line[j]; ++i, ++j) + line[i] = line[j]; + line[i] = 0; + input_cx--; + } } + else + CON_DeleteSelectedText(); + return true; + } + + // delete character under cursor or selected text + if (key == KEY_DEL) + { + if (input_selection == -1) + { + UINT32 i, j; + char *line = inputlines[inputline]; + + for (i = input_cx, j = input_cx + 1; line[j]; ++i, ++j) + line[i] = line[j]; + line[i] = 0; + } + else + CON_DeleteSelectedText(); + + return true; + } + + if (key == KEY_LEFTARROW) + { + if (shiftdown) + { + if (input_selection == -1) + input_selection = input_cx; + } + else + input_selection = -1; + + // move cursor to previous word + if (ctrldown) + { + char *line = inputlines[inputline]; + + while (input_cx > 1 && line[input_cx - 1] == ' ') + input_cx--; + while (input_cx > 1 && line[input_cx - 1] != ' ') + input_cx--; + } + // move cursor left + else if (input_cx > 1) + input_cx--; + + if ((INT32)input_cx == input_selection) + input_selection = -1; + + return true; + } + + if (key == KEY_RIGHTARROW) + { + if (shiftdown) + { + if (input_selection == -1) + input_selection = input_cx; + } + else + input_selection = -1; + + // move cursor to next word + if (ctrldown) + { + char *line = inputlines[inputline]; + + while (line[input_cx] && line[input_cx] != ' ') + input_cx++; + while (line[input_cx] && line[input_cx] == ' ') + input_cx++; + } + // move cursor right + else if (inputlines[inputline][input_cx]) + input_cx++; + + if ((INT32)input_cx == input_selection) + input_selection = -1; + return true; } @@ -950,13 +1094,21 @@ boolean CON_Responder(event_t *ev) return false; // add key to cmd line here - if (input_cx < CON_MAXPROMPTCHARS) + if (strlen(inputlines[inputline]) < CON_MAXPROMPTCHARS - 1) { + INT32 i, j; + char *line = inputlines[inputline]; + if (key >= 'A' && key <= 'Z' && !shiftdown) //this is only really necessary for dedicated servers key = key + 'a' - 'A'; - inputlines[inputline][input_cx] = (char)key; - inputlines[inputline][input_cx + 1] = 0; + if (input_selection != -1) + CON_DeleteSelectedText(); + + for (i = strlen(line), j = i + 1; j > (INT32)input_cx; --i, --j) + line[j] = line[i]; + + line[input_cx] = (char)key; input_cx++; } @@ -1246,6 +1398,7 @@ static void CON_DrawInput(void) size_t c; INT32 x, y; INT32 charwidth = (INT32)con_scalefactor << 3; + INT32 f = cv_constextsize.value | V_NOSCALESTART; // input line scrolls left if it gets too long p = inputlines[inputline]; @@ -1254,14 +1407,44 @@ static void CON_DrawInput(void) y = con_curlines - 12 * con_scalefactor; - for (c = 0, x = charwidth; c < con_width-11; c++, x += charwidth) - V_DrawCharacter(x, y, p[c] | cv_constextsize.value | V_NOSCALESTART, !cv_allcaps.value); + if (input_selection == -1) + { + for (c = 0, x = charwidth; c < con_width-11; c++, x += charwidth) + V_DrawCharacter(x, y, p[c] | f, !cv_allcaps.value); + } + else + { + size_t selstart = min(input_cx, (size_t)input_selection); + size_t selend = max(input_cx, (size_t)input_selection); + + for (c = 0, x = charwidth; c < selstart && c < con_width-11; c++, x += charwidth) + V_DrawCharacter(x, y, p[c] | f, !cv_allcaps.value); + + f |= V_YELLOWMAP; + for (; c < selend && c < con_width-11; c++, x += charwidth) + V_DrawCharacter(x, y, p[c] | f, !cv_allcaps.value); + f &= ~V_YELLOWMAP; + + for (; c < con_width-11; c++, x += charwidth) + V_DrawCharacter(x, y, p[c] | f, !cv_allcaps.value); + } + //for (c = 0, x = charwidth; c < con_width-11; c++, x += charwidth) + //V_DrawCharacter(x, y, p[c] | cv_constextsize.value | V_NOSCALESTART, !cv_allcaps.value); // draw the blinking cursor // x = ((input_cx >= con_width-11) ? (INT32)(con_width-11) : (INT32)((input_cx + 1)) * charwidth); if (con_tick < 4) - V_DrawCharacter(x, y, '_' | cv_constextsize.value | V_NOSCALESTART, !cv_allcaps.value); + { + if (inputlines[inputline][input_cx]) + //V_DrawCharacter(x - 2 * con_scalefactor, y, '|' | f, !cv_allcaps.value); + V_DrawCharacter(x, y + 2 * con_scalefactor, '_' | f, !cv_allcaps.value); + else + V_DrawCharacter(x, y, '_' | f, !cv_allcaps.value); + } + /*x = ((input_cx >= con_width-11) ? (INT32)(con_width-11) : (INT32)((input_cx + 1)) * charwidth); + if (con_tick < 4) + V_DrawCharacter(x, y, '_' | cv_constextsize.value | V_NOSCALESTART, !cv_allcaps.value);*/ } // draw the last lines of console text to the top of the screen diff --git a/src/hu_stuff.c b/src/hu_stuff.c index ec747305..80e7cf71 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -73,6 +73,9 @@ patch_t *cred_font[CRED_FONTSIZE]; static player_t *plr; boolean chat_on; // entering a chat message? static char w_chat[HU_MAXMSGLEN]; +static size_t chat_pos; // position of the cursor in the chat +static INT32 chat_selection; // selection border in current input line, -1 if no selection +static boolean teamtalk = false; static boolean headsupactive = false; boolean hu_showscores; // draw rankings static char hu_tick; @@ -106,6 +109,7 @@ static patch_t *crosshair[HU_CROSSHAIRS]; // 3 precached crosshair graphics static void HU_DrawRankings(void); static void HU_DrawCoopOverlay(void); static void HU_DrawNetplayCoopOverlay(void); +static void HU_DeleteSelectedText(void); //====================================================================== // KEYBOARD LAYOUTS FOR ENTERING TEXT @@ -621,36 +625,183 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum) } #endif +// Deletes selected text, assuming there is, and resets selection. +// Also sets the cursor to the correct position. +// +static void HU_DeleteSelectedText(void) +{ + UINT32 i, j; + size_t selstart = min(chat_pos, (size_t)chat_selection); + size_t selend = max(chat_pos, (size_t)chat_selection); + + for (i = selstart, j = selend; w_chat[j]; ++i, ++j) + w_chat[i] = w_chat[j]; + while (w_chat[i]) + w_chat[i++] = 0; + + chat_pos = selstart; + chat_selection = -1; +} + // Handles key input and string input // -static inline boolean HU_keyInChatString(char *s, char ch) +static void HU_keyInChatString(UINT32 key, boolean shiftdown, boolean ctrldown) { - size_t l; - - if ((ch >= HU_FONTSTART && ch <= HU_FONTEND && hu_font[ch-HU_FONTSTART]) - || ch == ' ') // Allow spaces, of course + switch (key) { - l = strlen(s); - if (l < HU_MAXMSGLEN - 1) + case KEY_ESCAPE: + chat_on = false; + break; + case KEY_ENTER: + { + // send automatically the message (no more chat char) + char buf[2+256]; + size_t ci = 2; + char *cp = w_chat; + + while (*cp) { - s[l++] = ch; - s[l]=0; - return true; + if (*cp >= ' ' && !(*cp & 0x80)) + buf[ci++] = *cp; + cp++; } - return false; - } - else if (ch == KEY_BACKSPACE) - { - l = strlen(s); - if (l) - s[--l] = 0; - else - return false; - } - else if (ch != KEY_ENTER) - return false; // did not eat key + buf[ci] = 0; - return true; // ate the key + // last minute mute check + if (cv_mute.value && !(server || adminplayer == consoleplayer)) + { + CONS_Alert(CONS_NOTICE, M_GetText("The chat is muted. You can't say anything at the moment.\n")); + return; + } + + if (ci > 2) // don't send target+flags+empty message. + { + if (teamtalk) + buf[0] = -1; // target + else + buf[0] = 0; // target + buf[1] = 0; // flags + SendNetXCmd(XD_SAY, buf, 2 + strlen(&buf[2]) + 1); + } + + chat_on = false; + break; + } + // cursor moving + case KEY_LEFTARROW: + case KEY_RIGHTARROW: + case KEY_HOME: + case KEY_END: + if (shiftdown) + { + if (chat_selection == -1) + chat_selection = chat_pos; + } + else + chat_selection = -1; + + switch (key) + { + case KEY_LEFTARROW: + // move cursor to previous word + if (ctrldown) + { + while (chat_pos > 0 && w_chat[chat_pos - 1] == ' ') + chat_pos--; + while (chat_pos > 0 && w_chat[chat_pos - 1] != ' ') + chat_pos--; + } + // move cursor left + else if (chat_pos > 0) + chat_pos--; + break; + case KEY_RIGHTARROW: + // move cursor to next word + if (ctrldown) + { + while (w_chat[chat_pos] && w_chat[chat_pos] != ' ') + chat_pos++; + while (w_chat[chat_pos] && w_chat[chat_pos] == ' ') + chat_pos++; + } + // move cursor right + else if (w_chat[chat_pos]) + chat_pos++; + break; + case KEY_HOME: + chat_pos = 0; + break; + case KEY_END: + chat_pos = strlen(w_chat); + } + + if ((INT32)chat_pos == chat_selection) + chat_selection = -1; + break; + // backspace or delete selected text + case KEY_BACKSPACE: + if (chat_selection == -1) + { + if (chat_pos > 0) + { + UINT32 i, j; + for (i = chat_pos - 1, j = chat_pos; w_chat[j]; ++i, ++j) + w_chat[i] = w_chat[j]; + w_chat[i] = 0; + chat_pos--; + } + } + else + HU_DeleteSelectedText(); + break; + // delete character under cursor + case KEY_DEL: + if (chat_selection == -1) + { + UINT32 i, j; + + for (i = chat_pos, j = chat_pos + 1; w_chat[j]; ++i, ++j) + w_chat[i] = w_chat[j]; + w_chat[i] = 0; + } + else + HU_DeleteSelectedText(); + break; + default: + // allow people to use keypad in chat + if (key >= KEY_KEYPAD7 && key <= KEY_KPADDEL) + { + XBOXSTATIC 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 = '/'; + + // use console translations + if (shiftdown) + key = shiftxform[key]; + + if ((key >= HU_FONTSTART && key <= HU_FONTEND && hu_font[key-HU_FONTSTART]) + || key == ' ') // Allow spaces, of course + { + if (strlen(w_chat) < HU_MAXMSGLEN - 1) + { + UINT32 i, j; + + if (chat_selection != -1) + HU_DeleteSelectedText(); + + for (i = strlen(w_chat), j = i + 1; j > chat_pos; --i, --j) + w_chat[j] = w_chat[i]; + w_chat[chat_pos] = (char)key; + chat_pos++; + } + } + } } // @@ -669,86 +820,9 @@ void HU_Ticker(void) hu_showscores = false; } -#define QUEUESIZE 256 - -static boolean teamtalk = false; -static char chatchars[QUEUESIZE]; -static INT32 head = 0, tail = 0; - -// -// HU_dequeueChatChar -// -char HU_dequeueChatChar(void) -{ - char c; - - if (head != tail) - { - c = chatchars[tail]; - tail = (tail + 1) & (QUEUESIZE-1); - } - else - c = 0; - - return c; -} - -// -// -static void HU_queueChatChar(char c) -{ - // send automaticly the message (no more chat char) - if (c == KEY_ENTER) - { - char buf[2+256]; - size_t ci = 2; - - do { - c = HU_dequeueChatChar(); - if (!c || (c >= ' ' && !(c & 0x80))) // copy printable characters and terminating '\0' only. - buf[ci++]=c; - } while (c); - - // last minute mute check - if (cv_mute.value && !(server || adminplayer == consoleplayer)) - { - CONS_Alert(CONS_NOTICE, M_GetText("The chat is muted. You can't say anything at the moment.\n")); - return; - } - - if (ci > 3) // don't send target+flags+empty message. - { - if (teamtalk) - buf[0] = -1; // target - else - buf[0] = 0; // target - buf[1] = 0; // flags - SendNetXCmd(XD_SAY, buf, 2 + strlen(&buf[2]) + 1); - } - return; - } - - if (((head + 1) & (QUEUESIZE-1)) == tail) - CONS_Printf(M_GetText("[Message unsent]\n")); // message not sent - else - { - if (c == KEY_BACKSPACE) - { - if (tail != head) - head = (head - 1) & (QUEUESIZE-1); - } - else - { - chatchars[head] = c; - head = (head + 1) & (QUEUESIZE-1); - } - } -} - +// REMOVE? Now this has become pretty useless IMO void HU_clearChatChars(void) { - while (tail != head) - HU_queueChatChar(KEY_BACKSPACE); chat_on = false; } @@ -758,13 +832,19 @@ void HU_clearChatChars(void) boolean HU_Responder(event_t *ev) { static boolean shiftdown = false; - UINT8 c; + static boolean ctrldown = false; + INT32 key = ev->data1; // only valid if ev->type is a key event - if (ev->data1 == KEY_LSHIFT || ev->data1 == KEY_RSHIFT) + if (key == KEY_LSHIFT || key == KEY_RSHIFT) { shiftdown = (ev->type == ev_keydown); return chat_on; } + else if (key == KEY_LCTRL || key == KEY_RCTRL) + { + ctrldown = (ev->type == ev_keydown); + return chat_on; + } if (ev->type != ev_keydown) return false; @@ -774,42 +854,36 @@ boolean HU_Responder(event_t *ev) if (!chat_on) { // enter chat mode - if ((ev->data1 == gamecontrol[gc_talkkey][0] || ev->data1 == gamecontrol[gc_talkkey][1]) + if ((key == gamecontrol[gc_talkkey][0] || key == gamecontrol[gc_talkkey][1]) && netgame && (!cv_mute.value || server || (adminplayer == consoleplayer))) { - if (cv_mute.value && !(server || adminplayer == consoleplayer)) - return false; + // we already checked for this two lines before... + //if (cv_mute.value && !(server || adminplayer == consoleplayer)) + //return false; chat_on = true; w_chat[0] = 0; + chat_pos = 0; + chat_selection = -1; teamtalk = false; return true; } - if ((ev->data1 == gamecontrol[gc_teamkey][0] || ev->data1 == gamecontrol[gc_teamkey][1]) + if ((key == gamecontrol[gc_teamkey][0] || key == gamecontrol[gc_teamkey][1]) && netgame && (!cv_mute.value || server || (adminplayer == consoleplayer))) { - if (cv_mute.value && !(server || adminplayer == consoleplayer)) - return false; + // we already checked for this two lines before... + //if (cv_mute.value && !(server || adminplayer == consoleplayer)) + //return false; chat_on = true; w_chat[0] = 0; + chat_pos = 0; + chat_selection = -1; teamtalk = true; return true; } } else // if chat_on { - c = (UINT8)ev->data1; - - // use console translations - if (shiftdown) - c = shiftxform[c]; - - if (HU_keyInChatString(w_chat,c)) - HU_queueChatChar(c); - if (c == KEY_ENTER) - chat_on = false; - else if (c == KEY_ESCAPE) - chat_on = false; - + HU_keyInChatString(key, shiftdown, ctrldown); return true; } return false; @@ -826,7 +900,7 @@ boolean HU_Responder(event_t *ev) // static void HU_DrawChat(void) { - INT32 t = 0, c = 0, y = HU_INPUTY; + INT32 t = 0, f = 0, c = 0, y = HU_INPUTY; size_t i = 0; const char *ntalk = "Say: ", *ttalk = "Say-Team: "; const char *talk = ntalk; @@ -844,6 +918,8 @@ static void HU_DrawChat(void) #endif } + f = cv_constextsize.value | V_NOSCALESTART; + while (talk[i]) { if (talk[i] < HU_FONTSTART) @@ -854,36 +930,123 @@ static void HU_DrawChat(void) else { //charwidth = SHORT(hu_font[talk[i]-HU_FONTSTART]->width) * con_scalefactor; - V_DrawCharacter(HU_INPUTX + c, y, talk[i++] | cv_constextsize.value | V_NOSCALESTART, !cv_allcaps.value); + V_DrawCharacter(HU_INPUTX + c, y, talk[i++] | f, !cv_allcaps.value); } c += charwidth; } + f |= t; i = 0; - while (w_chat[i]) + if (chat_selection == -1) { - //Hurdler: isn't it better like that? - if (w_chat[i] < HU_FONTSTART) + while (w_chat[i]) { - ++i; - //charwidth = 4 * con_scalefactor; + //Hurdler: isn't it better like that? + if (w_chat[i] < HU_FONTSTART) + { + ++i; + //charwidth = 4 * con_scalefactor; + } + else + { + //charwidth = SHORT(hu_font[w_chat[i]-HU_FONTSTART]->width) * con_scalefactor; + V_DrawCharacter(HU_INPUTX + c, y, w_chat[i++] | f, !cv_allcaps.value); + } + + c += charwidth; + if (c >= vid.width) + { + c = 0; + y += charheight; + } } - else + } + else + { + size_t selstart = min(chat_pos, (size_t)chat_selection); + size_t selend = max(chat_pos, (size_t)chat_selection); + + while (i < selstart) { - //charwidth = SHORT(hu_font[w_chat[i]-HU_FONTSTART]->width) * con_scalefactor; - V_DrawCharacter(HU_INPUTX + c, y, w_chat[i++] | cv_constextsize.value | V_NOSCALESTART | t, !cv_allcaps.value); + //Hurdler: isn't it better like that? + if (w_chat[i] < HU_FONTSTART) + { + ++i; + //charwidth = 4 * con_scalefactor; + } + else + { + //charwidth = SHORT(hu_font[w_chat[i]-HU_FONTSTART]->width) * con_scalefactor; + V_DrawCharacter(HU_INPUTX + c, y, w_chat[i++] | f, !cv_allcaps.value); + } + + c += charwidth; + if (c >= vid.width) + { + c = 0; + y += charheight; + } } - c += charwidth; - if (c >= vid.width) + f &= ~t; + f |= V_YELLOWMAP; + while (i < selend) { - c = 0; - y += charheight; + //Hurdler: isn't it better like that? + if (w_chat[i] < HU_FONTSTART) + { + ++i; + //charwidth = 4 * con_scalefactor; + } + else + { + //charwidth = SHORT(hu_font[w_chat[i]-HU_FONTSTART]->width) * con_scalefactor; + V_DrawCharacter(HU_INPUTX + c, y, w_chat[i++] | f, !cv_allcaps.value); + } + + c += charwidth; + if (c >= vid.width) + { + c = 0; + y += charheight; + } + } + f &= ~V_YELLOWMAP; + f |= t; + + while (w_chat[i]) + { + //Hurdler: isn't it better like that? + if (w_chat[i] < HU_FONTSTART) + { + ++i; + //charwidth = 4 * con_scalefactor; + } + else + { + //charwidth = SHORT(hu_font[w_chat[i]-HU_FONTSTART]->width) * con_scalefactor; + V_DrawCharacter(HU_INPUTX + c, y, w_chat[i++] | f, !cv_allcaps.value); + } + + c += charwidth; + if (c >= vid.width) + { + c = 0; + y += charheight; + } } } if (hu_tick < 4) - V_DrawCharacter(HU_INPUTX + c, y, '_' | cv_constextsize.value |V_NOSCALESTART|t, !cv_allcaps.value); + { + if (w_chat[chat_pos]) + { + i = (strlen(talk) + chat_pos) * charwidth; + c = i % vid.width; + y = HU_INPUTY + i / vid.width * charheight + 2 * con_scalefactor; + } + V_DrawCharacter(HU_INPUTX + c, y, '_' | f, !cv_allcaps.value); + } } diff --git a/src/hu_stuff.h b/src/hu_stuff.h index 7b22f33f..5dca1098 100644 --- a/src/hu_stuff.h +++ b/src/hu_stuff.h @@ -93,7 +93,6 @@ boolean HU_Responder(event_t *ev); void HU_Ticker(void); void HU_Drawer(void); -char HU_dequeueChatChar(void); void HU_Erase(void); void HU_clearChatChars(void); void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, INT32 whiteplayer);