From ca0b54a3c1f2bc7478325063af35394297eed992 Mon Sep 17 00:00:00 2001
From: Izy <izy@>
Date: Mon, 17 Nov 2014 14:21:17 +0000
Subject: [PATCH] chat patch by Izy

Provided by mynick1 on IRC,
whitespace- and indent-cleaned,
applied against xonotic-v0.7.0 and rebased

Patch description for chat:
- Added (in the code for the chat) the support for modifiers
- Removed the "signed int chat_mode" thing and replaced it with an enum (generally speaking enums allow fast jump tables in switches and the code is easier to update)
- TAB completes nicks even if you're completing them from within a substring
- Ins mode (overwriting mode) by pressing INS on the keyboard (using modifier ins)
- now CTRL+{left|right}ARROW moves the cursor between words (using modifier ctrl)
- removed the exception for the commandmode, now all works as in say/say_team
- cursor changed to match those used in the console (square = ins, arrow = std)
- Support for the right/left arrow buttons and the home/end buttons.
- Support for del and backspace.
- Edit the text in-place.
---
 console.c |  60 +++++++++----
 keys.c    | 265 ++++++++++++++++++++++++++++++++++++++++++++++++------
 keys.h    |  16 +++-
 3 files changed, 297 insertions(+), 44 deletions(-)

diff --git a/console.c b/console.c
index 8dde83d9..622b9fc6 100644
--- a/console.c
+++ b/console.c
@@ -431,7 +431,7 @@ static void Log_DestBuffer_Flush_NoLock(void)
 		log_dest_buffer[log_dest_buffer_pos++] = 0;
 
 		if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
- 		{
+		{
 			have_opened_temp_sockets = true;
 			NetConn_OpenServerPorts(true);
 		}
@@ -710,11 +710,11 @@ Con_MessageMode_f
 static void Con_MessageMode_f (void)
 {
 	key_dest = key_message;
-	chat_mode = 0; // "say"
+	chat_mode = DP_CHAT_MODE_SAY; // "say"
 	if(Cmd_Argc() > 1)
 	{
 		dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
-		chat_bufferlen = (unsigned int)strlen(chat_buffer);
+		chat_bufferlen = chat_cursor = (unsigned int)strlen(chat_buffer); // Izy's patch
 	}
 }
 
@@ -727,11 +727,11 @@ Con_MessageMode2_f
 static void Con_MessageMode2_f (void)
 {
 	key_dest = key_message;
-	chat_mode = 1; // "say_team"
+	chat_mode = DP_CHAT_MODE_SAYTEAM; // "say_team"
 	if(Cmd_Argc() > 1)
 	{
 		dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
-		chat_bufferlen = (unsigned int)strlen(chat_buffer);
+		chat_bufferlen = chat_cursor = (unsigned int)strlen(chat_buffer); // Izy's patch
 	}
 }
 
@@ -746,9 +746,9 @@ static void Con_CommandMode_f (void)
 	if(Cmd_Argc() > 1)
 	{
 		dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
-		chat_bufferlen = (unsigned int)strlen(chat_buffer);
+		chat_bufferlen = chat_cursor = (unsigned int)strlen(chat_buffer); // Izy's patch
 	}
-	chat_mode = -1; // command
+	chat_mode = DP_CHAT_MODE_COMMAND; // command
 }
 
 /*
@@ -1830,17 +1830,47 @@ void Con_DrawNotify (void)
 	{
 		//static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
 		int colorindex = -1;
+		// Added by Izy (izy from izysoftware.com)
+		size_t  skiptext;
+		const char *prefix = "";
 		const char *cursor;
 		char charbuf16[16];
-		cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL, charbuf16);
-
-		// LordHavoc: speedup, and other improvements
-		if (chat_mode < 0)
-			dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor);
-		else if(chat_mode)
-			dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor);
+		unsigned onoff;
+		size_t cursor_size;
+		size_t sizeofchar_to_replace;
+		const unsigned magic_numbers[2][2] = { {0xE00A, 0xE00B}, {0xE00A, 0xE08D} };
+		onoff = (unsigned)(realtime * con_cursorspeed) & 1;
+		cursor_size = u8_fromchar(magic_numbers[chat_modifiers.ins ? 0 : 1][onoff], charbuf16, sizeof(charbuf16));
+		cursor = charbuf16;
+
+		// Added by Izy (izy from izysoftware.com)
+		switch(chat_mode)
+		{
+			case DP_CHAT_MODE_COMMAND:
+				prefix = "]";
+				break;
+			case DP_CHAT_MODE_SAY:
+				prefix = "say:";
+				break;
+			case DP_CHAT_MODE_SAYTEAM:
+				prefix = "say_team:";
+				break;
+		}
+		// Added by Izy (izy from izysoftware.com)
+		if(chat_bufferlen == chat_cursor)
+			dpsnprintf(temptext, sizeof(temptext), "%s%s%s", prefix, chat_buffer, cursor);
 		else
-			dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor);
+		{
+			dpsnprintf(temptext, sizeof(temptext), "%s%s", prefix, chat_buffer);
+			if(onoff)
+			{
+				skiptext = strlen(prefix);
+				sizeofchar_to_replace = u8_bytelen(&temptext[chat_cursor+skiptext], 1);
+				if(sizeofchar_to_replace != cursor_size)
+					memmove(&temptext[chat_cursor+skiptext+cursor_size], &temptext[chat_cursor+skiptext+sizeofchar_to_replace], chat_bufferlen - (chat_cursor + sizeofchar_to_replace) + 1);
+				memcpy(&temptext[chat_cursor+skiptext], cursor, cursor_size);
+			}
+		}
 
 		// FIXME word wrap
 		inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
diff --git a/keys.c b/keys.c
index 32648560..8a57c4b0 100644
--- a/keys.c
+++ b/keys.c
@@ -1105,7 +1105,7 @@ Key_Console (int key, int unicode)
 			con_backscroll -= ((vid_conheight.integer >> 1) / con_textsize.integer)-3;
 		return;
 	}
- 
+
 	if (key == K_MWHEELUP)
 	{
 		if(keydown[K_CTRL])
@@ -1201,63 +1201,274 @@ Key_Console (int key, int unicode)
 
 //============================================================================
 
-int chat_mode;
+dp_chat_mode_t chat_mode;
 char		chat_buffer[MAX_INPUTLINE];
 unsigned int	chat_bufferlen = 0;
+unsigned int	chat_cursor = 0;
+dp_chat_modifiers_t chat_modifiers = DP_CHAT_MODIFIERS_OFF;
+
+static void
+dp_chat_modifiers_cloning_tool(dp_chat_modifiers_t *dest, const dp_chat_modifiers_t *src)
+{
+	*dest = *src;
+}
+
+static void
+Key_Message_Reset_All_Modifiers (void) // Added by Izy (izy from izysoftware.com)
+{
+	const dp_chat_modifiers_t off = DP_CHAT_MODIFIERS_OFF;
+	chat_modifiers = off;
+}
+
+static qboolean
+Key_Message_Modifiers_Enabled (void) // Added by Izy (izy from izysoftware.com)
+{
+	const dp_chat_modifiers_t off = DP_CHAT_MODIFIERS_OFF;
+	return memcmp(&chat_modifiers, &off, sizeof(off)) != 0;
+}
+
+typedef struct
+{
+	int key;
+	int ascii;
+	qboolean down;
+} dp_chat_fakekey_t;
+
+static void Key_Message_SendFakeKeys(const dp_chat_fakekey_t *fakekeys, const size_t count);
 
 static void
-Key_Message (int key, int ascii)
+Key_Message (int key, int ascii, qboolean down)
 {
 	char vabuf[1024];
+	unsigned int inc; /* Added by Izy (izy from izysoftware.com) - uint */
+	size_t dec; /* Added by Izy (izy from izysoftware.com) - size_t */
+	unsigned charscounter;
+	size_t sizeofchar_to_replace;
+	const dp_chat_fakekey_t fake_backspace[] = {{K_LEFTARROW, 0, true}, {K_DEL, 0, true}};
+	const dp_chat_fakekey_t fake_del[] = {{K_DEL, 0, true}};
+
+	if(key == 133 && ascii == 0) // Added by Izy (izy from izysoftware.com)
+		chat_modifiers.ctrl = down;
+	if(key == 147 && ascii == 0 && down) // Added by Izy (izy from izysoftware.com)
+		chat_modifiers.ins = chat_modifiers.ins == false;
+
+	if(down == false) // Izy's Patch
+		return;
+
 	if (key == K_ENTER || ascii == 10 || ascii == 13)
 	{
-		if(chat_mode < 0)
-			Cmd_ExecuteString(chat_buffer, src_command, true); // not Cbuf_AddText to allow semiclons in args; however, this allows no variables then. Use aliases!
-		else
-			Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "%s %s", chat_mode ? "say_team" : "say ", chat_buffer));
+
+		switch(chat_mode)
+		{
+			case DP_CHAT_MODE_COMMAND:
+				// not Cbuf_AddText to allow semiclons in args; however, this allows no variables then. Use aliases!
+				Cmd_ExecuteString(chat_buffer, src_command, true);
+				break;
+			case DP_CHAT_MODE_SAY:
+				Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "%s %s", "say", chat_buffer));
+				break;
+			case DP_CHAT_MODE_SAYTEAM:
+				Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "%s %s", "say_team", chat_buffer));
+				break;
+		}
 
 		key_dest = key_game;
-		chat_bufferlen = 0;
+		chat_bufferlen = chat_cursor = 0;
 		chat_buffer[0] = 0;
+		Key_Message_Reset_All_Modifiers(); // Izy
 		return;
 	}
 
-	// TODO add support for arrow keys and simple editing
-
 	if (key == K_ESCAPE) {
 		key_dest = key_game;
-		chat_bufferlen = 0;
+		chat_bufferlen = chat_cursor = 0;
 		chat_buffer[0] = 0;
+		Key_Message_Reset_All_Modifiers(); // Izy
 		return;
 	}
 
-	if (key == K_BACKSPACE) {
-		if (chat_bufferlen) {
-			chat_bufferlen = (unsigned int)u8_prevbyte(chat_buffer, chat_bufferlen);
-			chat_buffer[chat_bufferlen] = 0;
+	// delete char before cursor
+	if (key == K_BACKSPACE) // Added by Izy (izy from izysoftware.com)
+	{
+		if(chat_cursor > 0)
+		{
+			// Backspace is always equivalent to K_LEFTARROW + K_DEL
+			#if 1 // ok, allow the quick fast case
+			if(chat_cursor == chat_bufferlen)
+			{
+				chat_bufferlen = chat_cursor = (unsigned int)u8_prevbyte(chat_buffer, chat_bufferlen);
+				chat_buffer[chat_bufferlen] = 0;
+				return;
+			}
+			#endif
+			Key_Message_SendFakeKeys(fake_backspace, sizeof(fake_backspace)/sizeof(fake_backspace[0]));
 		}
 		return;
 	}
 
-	if(key == K_TAB) {
-		chat_bufferlen = Nicks_CompleteChatLine(chat_buffer, sizeof(chat_buffer), chat_bufferlen);
+	// delete char on cursor
+	if (key == K_DEL) // Added by Izy (izy from izysoftware.com)
+	{
+		if(chat_cursor < chat_bufferlen)
+		{
+			dec = u8_bytelen(&chat_buffer[chat_cursor], 1);
+			memmove(&chat_buffer[chat_cursor], &chat_buffer[chat_cursor+dec], chat_bufferlen - (chat_cursor + dec) + 1);
+			chat_bufferlen -= dec;
+		}
 		return;
 	}
 
-	// ctrl+key generates an ascii value < 32 and shows a char from the charmap
-	if (ascii > 0 && ascii < 32 && utf8_enable.integer)
-		ascii = 0xE000 + ascii;
+	// move cursor to the previous character
+	if (key == K_LEFTARROW) // Added by Izy (izy from izysoftware.com)
+	{
+		if (chat_cursor > 0)
+		{
+			if(chat_modifiers.ctrl) // move cursor to the previous word
+			{
+				chat_cursor = u8_prevbyte(chat_buffer, chat_cursor);
+				while(chat_cursor > 0 && isspace(chat_buffer[chat_cursor]))
+					chat_cursor = u8_prevbyte(chat_buffer, chat_cursor);
+				charscounter = 0;
+				if(chat_cursor > 0)
+					do
+					{
+						chat_cursor = u8_prevbyte(chat_buffer, chat_cursor);
+						charscounter++;
+					} while(chat_cursor > 0 && !isspace(chat_buffer[chat_cursor]));
+				if(charscounter && chat_cursor)
+					chat_cursor += u8_bytelen(&chat_buffer[chat_cursor], 1);
+				return;
+			}
+			chat_cursor = u8_prevbyte(chat_buffer, chat_cursor);
+		}
+		return;
+	}
+
+	// move cursor to the next character
+	if (key == K_RIGHTARROW) // Added by Izy (izy from izysoftware.com)
+	{
+		if (chat_cursor < chat_bufferlen)
+		{
+			if(chat_modifiers.ctrl) // move cursor to the next word
+			{
+				chat_cursor += u8_bytelen(&chat_buffer[chat_cursor], 1);
+				while(chat_cursor < chat_bufferlen && isspace(chat_buffer[chat_cursor]))
+					chat_cursor += u8_bytelen(&chat_buffer[chat_cursor], 1);
+				if(chat_cursor < chat_bufferlen)
+					do
+					{
+						chat_cursor += u8_bytelen(&chat_buffer[chat_cursor], 1);
+					} while(chat_cursor < chat_bufferlen && !isspace(chat_buffer[chat_cursor]));
+				return;
+			}
+			chat_cursor += u8_bytelen(&chat_buffer[chat_cursor], 1);
+		}
+		return;
+	}
+
+	// move cursor to the first character
+	if (key == 151 /* HOME */) // Added by Izy (izy from izysoftware.com)
+	{
+		chat_cursor = 0;
+		return;
+	}
+
+	// move cursor to the last character
+	if (key == 152 /* END */) // Added by Izy (izy from izysoftware.com)
+	{
+		chat_cursor = chat_bufferlen;
+		return;
+	}
+
+	if(key == K_TAB)
+	{
+		if(chat_cursor == chat_bufferlen) // Added by Izy (izy from izysoftware.com)
+			chat_cursor = chat_bufferlen = Nicks_CompleteChatLine(chat_buffer, sizeof(chat_buffer), chat_bufferlen);
+		else
+			if(chat_cursor > 0) // Added by Izy (izy from izysoftware.com)
+			{
+				int nicks;
+				char nick[sizeof(chat_buffer)];
+				memcpy(nick, chat_buffer, chat_cursor);
+				nick[chat_cursor] = 0;
+				nicks = Nicks_CompleteChatLine(nick, sizeof(nick), chat_cursor);
+				if(nicks > 0 && (unsigned)nicks > chat_cursor)
+				{
+					inc = nicks - chat_cursor;
+					if(sizeof(chat_buffer) > chat_bufferlen + inc)
+					{
+						memmove(&chat_buffer[chat_cursor+inc], &chat_buffer[chat_cursor], chat_bufferlen - chat_cursor + 1);
+						memcpy(chat_buffer, nick, nicks);
+						chat_bufferlen += inc;
+						chat_cursor += inc;
+						if(isspace(chat_buffer[chat_cursor])) /* remove the double space */
+							Key_Message_SendFakeKeys(fake_del, sizeof(fake_del)/sizeof(fake_del[0]));
+					}
+				}
+			}
+		return;
+	}
 
 	if (chat_bufferlen == sizeof (chat_buffer) - 1)
 		return;							// all full
 
-	if (!ascii)
+	if (ascii <= 0)
 		return;							// non printable
 
-	chat_bufferlen += u8_fromchar(ascii, chat_buffer+chat_bufferlen, sizeof(chat_buffer) - chat_bufferlen - 1);
+	// ctrl+key generates an ascii value < 32 and shows a char from the charmap
+	if (ascii < 32 && utf8_enable.integer)
+		ascii = 0xE000 + ascii;
 
-	//chat_buffer[chat_bufferlen++] = ascii;
-	//chat_buffer[chat_bufferlen] = 0;
+	// Added by Izy (izy from izysoftware.com)
+	if(chat_cursor == chat_bufferlen)
+	{
+		inc = u8_fromchar(ascii, chat_buffer+chat_bufferlen, sizeof(chat_buffer) - chat_bufferlen - 1);
+		chat_bufferlen += inc;
+		chat_cursor += inc;
+	}
+	else
+	{
+		char utf8buffer[16];
+		inc = u8_fromchar(ascii, utf8buffer, sizeof(utf8buffer));
+		if(chat_modifiers.ins)
+		{
+			sizeofchar_to_replace = u8_bytelen(&chat_buffer[chat_cursor], 1);
+			if(sizeofchar_to_replace != inc)
+				memmove(&chat_buffer[chat_cursor+inc], &chat_buffer[chat_cursor+sizeofchar_to_replace], chat_bufferlen - (chat_cursor + sizeofchar_to_replace) + 1);
+			memcpy(&chat_buffer[chat_cursor], utf8buffer, inc);
+			chat_bufferlen = chat_bufferlen - sizeofchar_to_replace + inc;
+			chat_cursor += inc;
+		}
+		else
+			if(sizeof(chat_buffer) > chat_bufferlen + inc)
+			{
+				memmove(&chat_buffer[chat_cursor+inc], &chat_buffer[chat_cursor], chat_bufferlen - chat_cursor + 1);
+				memcpy(&chat_buffer[chat_cursor], utf8buffer, inc);
+				chat_bufferlen += inc;
+				chat_cursor += inc;
+			}
+	}
+}
+
+
+static void
+Key_Message_SendFakeKeys(const dp_chat_fakekey_t *fakekeys, const size_t count) // Added by Izy (izy from izysoftware.com)
+{
+	dp_chat_modifiers_t modbackup;
+	unsigned modifiers_enabled;
+	size_t i;
+	modifiers_enabled = Key_Message_Modifiers_Enabled();
+	if(modifiers_enabled)
+	{
+		dp_chat_modifiers_cloning_tool(&modbackup, &chat_modifiers);
+		Key_Message_Reset_All_Modifiers();
+	}
+	for(i = 0; i < count; i++)
+		Key_Message(fakekeys[i].key, fakekeys[i].ascii, fakekeys[i].down);
+	if(modifiers_enabled)
+	{
+		dp_chat_modifiers_cloning_tool(&chat_modifiers, &modbackup);
+	}
 }
 
 //============================================================================
@@ -1853,8 +2064,7 @@ Key_Event (int key, int ascii, qboolean down)
 				break;
 
 			case key_message:
-				if (down)
-					Key_Message (key, ascii); // that'll close the message input
+				Key_Message (key, ascii, down); // that'll close the message input
 				break;
 
 			case key_menu:
@@ -1953,8 +2163,7 @@ Key_Event (int key, int ascii, qboolean down)
 	switch (keydest)
 	{
 		case key_message:
-			if (down)
-				Key_Message (key, ascii);
+			Key_Message (key, ascii, down);
 			break;
 		case key_menu:
 		case key_menu_grabbed:
diff --git a/keys.h b/keys.h
index a84eb4c6..d455de41 100644
--- a/keys.h
+++ b/keys.h
@@ -367,9 +367,23 @@ extern	keydest_t	key_dest;
 extern	int			key_consoleactive;
 extern	char		*keybindings[MAX_BINDMAPS][MAX_KEYS];
 
-extern int chat_mode; // 0 for say, 1 for say_team, -1 for command
+typedef enum
+{
+	DP_CHAT_MODE_COMMAND,
+	DP_CHAT_MODE_SAY,
+	DP_CHAT_MODE_SAYTEAM
+} dp_chat_mode_t; /* Added by Izy (izy from izysoftware.com) */
+extern dp_chat_mode_t chat_mode;
 extern char chat_buffer[MAX_INPUTLINE];
 extern unsigned int chat_bufferlen;
+extern unsigned int chat_cursor; // Added by Izy (izy from izysoftware.com)
+typedef struct
+{
+#define DP_CHAT_MODIFIERS_OFF { false, false }
+	qboolean  ctrl;
+	qboolean  ins;
+} dp_chat_modifiers_t; /* Added by Izy (izy from izysoftware.com) */
+extern dp_chat_modifiers_t chat_modifiers;
 
 void Key_ClearEditLine(int edit_line);
 void Key_WriteBindings(qfile_t *f);
-- 
2.39.5