changeset 9898:ffa87f0a2f8b draft

(svn r14047) -Codechange: move chatmessage handling to the network directory as that's the only case chat messages are used. Furthermore remove any trace of chatmessages when compiling without network support.
author rubidium <rubidium@openttd.org>
date Mon, 11 Aug 2008 22:45:11 +0000
parents 5b558ce6cb0e
children dd90dcb4082f
files projects/openttd_vs80.vcproj projects/openttd_vs90.vcproj source.list src/date.cpp src/gfx.cpp src/misc.cpp src/network/network.cpp src/network/network.h src/network/network_chat_gui.cpp src/network/network_func.h src/network/network_gui.cpp src/texteff.cpp src/texteff.hpp src/video/cocoa/event.mm src/video/sdl_v.cpp src/video/win32_v.cpp src/window.cpp
diffstat 17 files changed, 531 insertions(+), 491 deletions(-) [+]
line wrap: on
line diff
--- a/projects/openttd_vs80.vcproj
+++ b/projects/openttd_vs80.vcproj
@@ -1768,6 +1768,10 @@
 				>
 			</File>
 			<File
+				RelativePath=".\..\src\network\network_chat_gui.cpp"
+				>
+			</File>
+			<File
 				RelativePath=".\..\src\network\network_gui.cpp"
 				>
 			</File>
--- a/projects/openttd_vs90.vcproj
+++ b/projects/openttd_vs90.vcproj
@@ -1765,6 +1765,10 @@
 				>
 			</File>
 			<File
+				RelativePath=".\..\src\network\network_chat_gui.cpp"
+				>
+			</File>
+			<File
 				RelativePath=".\..\src\network\network_gui.cpp"
 				>
 			</File>
--- a/source.list
+++ b/source.list
@@ -385,6 +385,7 @@
 main_gui.cpp
 misc_gui.cpp
 music_gui.cpp
+network/network_chat_gui.cpp
 network/network_gui.cpp
 newgrf_gui.cpp
 news_gui.cpp
--- a/src/date.cpp
+++ b/src/date.cpp
@@ -33,10 +33,6 @@
 	ConvertDateToYMD(date, &ymd);
 	_cur_year = ymd.year;
 	_cur_month = ymd.month;
-#ifdef ENABLE_NETWORK
-	_network_last_advertise_frame = 0;
-	_network_need_advertise = true;
-#endif /* ENABLE_NETWORK */
 }
 
 #define M(a, b) ((a << 5) | b)
@@ -161,7 +157,6 @@
 /** Functions used by the IncreaseDate function */
 
 extern void WaypointsDailyLoop();
-extern void ChatMessageDailyLoop();
 extern void EnginesDailyLoop();
 extern void DisasterDailyLoop();
 
@@ -228,7 +223,9 @@
 	/* yeah, increase day counter and call various daily loops */
 	_date++;
 
-	ChatMessageDailyLoop();
+#ifdef ENABLE_NETWORK
+	NetworkChatMessageDailyLoop();
+#endif /* ENABLE_NETWORK */
 
 	DisasterDailyLoop();
 	WaypointsDailyLoop();
@@ -296,9 +293,11 @@
 		_date -= days_this_year;
 		FOR_ALL_VEHICLES(v) v->date_of_last_service -= days_this_year;
 
+#ifdef ENABLE_NETWORK
 		/* Because the _date wraps here, and text-messages expire by game-days, we have to clean out
 		 *  all of them if the date is set back, else those messages will hang for ever */
-		InitChatMessage();
+		NetworkInitChatMessage();
+#endif /* ENABLE_NETWORK */
 	}
 
 	if (_settings_client.gui.auto_euro) CheckSwitchToEuro();
--- a/src/gfx.cpp
+++ b/src/gfx.cpp
@@ -20,6 +20,7 @@
 #include "core/alloc_func.hpp"
 #include "core/sort_func.hpp"
 #include "landscape_type.h"
+#include "network/network_func.h"
 
 #include "table/palettes.h"
 #include "table/sprites.h"
@@ -81,7 +82,10 @@
 	if (xo == 0 && yo == 0) return;
 
 	if (_cursor.visible) UndrawMouseCursor();
-	UndrawChatMessage();
+
+#ifdef ENABLE_NETWORK
+	NetworkUndrawChatMessage();
+#endif /* ENABLE_NETWORK */
 
 	blitter->ScrollBuffer(_screen.dst_ptr, left, top, width, height, xo, yo);
 	/* This part of the screen is now dirty. */
@@ -1252,7 +1256,10 @@
 			UndrawMouseCursor();
 		}
 	}
-	UndrawChatMessage();
+
+#ifdef ENABLE_NETWORK
+	NetworkUndrawChatMessage();
+#endif /* ENABLE_NETWORK */
 
 	DrawOverlappedWindowForAll(left, top, right, bottom);
 
--- a/src/misc.cpp
+++ b/src/misc.cpp
@@ -29,6 +29,7 @@
 #include "animated_tile_func.h"
 #include "tilehighlight_func.h"
 #include "core/bitmath_func.hpp"
+#include "network/network_func.h"
 
 #include "table/strings.h"
 #include "table/sprites.h"
@@ -105,7 +106,9 @@
 	InitializeCheats();
 
 	InitTextEffects();
-	InitChatMessage();
+#ifdef ENABLE_NETWORK
+	NetworkInitChatMessage();
+#endif /* ENABLE_NETWORK */
 	InitializeAnimatedTiles();
 
 	InitializeLandscapeVariables(false);
--- a/src/network/network.cpp
+++ b/src/network/network.cpp
@@ -230,7 +230,7 @@
 
 	DebugDumpCommands("ddc:cmsg:%d;%d;%s\n", _date, _date_fract, message);
 	IConsolePrintF(color, "%s", message);
-	AddChatMessage(color, duration, "%s", message);
+	NetworkAddChatMessage(color, duration, "%s", message);
 }
 
 // Calculate the frame-lag of a client
--- a/src/network/network.h
+++ b/src/network/network.h
@@ -11,6 +11,7 @@
 
 void NetworkStartUp();
 void NetworkShutDown();
+void NetworkDrawChatMessage();
 
 extern bool _networking;         ///< are we in networking mode?
 extern bool _network_server;     ///< network-server is active
@@ -23,6 +24,7 @@
 
 static inline void NetworkStartUp() {}
 static inline void NetworkShutDown() {}
+static inline void NetworkDrawChatMessage() {}
 
 #define _networking 0
 #define _network_server 0
new file mode 100644
--- /dev/null
+++ b/src/network/network_chat_gui.cpp
@@ -0,0 +1,487 @@
+/* $Id$ */
+
+/** @file network_chat_gui.cpp GUI for handling chat messages. */
+
+#include <stdarg.h> /* va_list */
+
+#ifdef ENABLE_NETWORK
+
+#include "../stdafx.h"
+#include "network.h"
+#include "network_type.h"
+#include "../date_func.h"
+#include "../gfx_func.h"
+#include "../strings_func.h"
+#include "../blitter/factory.hpp"
+#include "../console_func.h"
+#include "../video/video_driver.hpp"
+#include "../table/sprites.h"
+#include "../window_gui.h"
+#include "../textbuf_gui.h"
+#include "../querystring_gui.h"
+#include "../town.h"
+#include "../window_func.h"
+#include "network_internal.h"
+#include "network_client.h"
+
+#include "table/strings.h"
+
+enum {
+	MAX_CHAT_MESSAGES      =  10,
+};
+
+struct ChatMessage {
+	char message[NETWORK_CHAT_LENGTH];
+	uint16 color;
+	Date end_date;
+};
+
+/* used for chat window */
+static ChatMessage _chatmsg_list[MAX_CHAT_MESSAGES];
+static bool _chatmessage_dirty = false;
+static bool _chatmessage_visible = false;
+static bool _chat_tab_completion_active;
+
+/* The chatbox grows from the bottom so the coordinates are pixels from
+ * the left and pixels from the bottom. The height is the maximum height */
+static const PointDimension _chatmsg_box = {10, 30, 500, 150};
+static uint8 _chatmessage_backup[150 * 500 * 6]; // (height * width)
+
+static inline uint GetChatMessageCount()
+{
+	uint i = 0;
+	for (; i < MAX_CHAT_MESSAGES; i++) {
+		if (_chatmsg_list[i].message[0] == '\0') break;
+	}
+
+	return i;
+}
+
+/**
+ * Add a text message to the 'chat window' to be shown
+ * @param color The colour this message is to be shown in
+ * @param duration The duration of the chat message in game-days
+ * @param message message itself in printf() style
+ */
+void CDECL NetworkAddChatMessage(uint16 color, uint8 duration, const char *message, ...)
+{
+	char buf[NETWORK_CHAT_LENGTH];
+	const char *bufp;
+	va_list va;
+	uint msg_count;
+	uint16 lines;
+
+	va_start(va, message);
+	vsnprintf(buf, lengthof(buf), message, va);
+	va_end(va);
+
+
+	Utf8TrimString(buf, NETWORK_CHAT_LENGTH);
+
+	/* Force linebreaks for strings that are too long */
+	lines = GB(FormatStringLinebreaks(buf, _chatmsg_box.width - 8), 0, 16) + 1;
+	if (lines >= MAX_CHAT_MESSAGES) return;
+
+	msg_count = GetChatMessageCount();
+	/* We want to add more chat messages than there is free space for, remove 'old' */
+	if (lines > MAX_CHAT_MESSAGES - msg_count) {
+		int i = lines - (MAX_CHAT_MESSAGES - msg_count);
+		memmove(&_chatmsg_list[0], &_chatmsg_list[i], sizeof(_chatmsg_list[0]) * (msg_count - i));
+		msg_count = MAX_CHAT_MESSAGES - lines;
+	}
+
+	for (bufp = buf; lines != 0; lines--) {
+		ChatMessage *cmsg = &_chatmsg_list[msg_count++];
+		ttd_strlcpy(cmsg->message, bufp, sizeof(cmsg->message));
+
+		/* The default colour for a message is player colour. Replace this with
+		 * white for any additional lines */
+		cmsg->color = (bufp == buf && color & IS_PALETTE_COLOR) ? color : (0x1D - 15) | IS_PALETTE_COLOR;
+		cmsg->end_date = _date + duration;
+
+		bufp += strlen(bufp) + 1; // jump to 'next line' in the formatted string
+	}
+
+	_chatmessage_dirty = true;
+}
+
+void NetworkInitChatMessage()
+{
+	for (uint i = 0; i < MAX_CHAT_MESSAGES; i++) {
+		_chatmsg_list[i].message[0] = '\0';
+	}
+}
+
+/** Hide the chatbox */
+void NetworkUndrawChatMessage()
+{
+	if (_chatmessage_visible) {
+		Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
+		/* Sometimes we also need to hide the cursor
+		 *   This is because both textmessage and the cursor take a shot of the
+		 *   screen before drawing.
+		 *   Now the textmessage takes his shot and paints his data before the cursor
+		 *   does, so in the shot of the cursor is the screen-data of the textmessage
+		 *   included when the cursor hangs somewhere over the textmessage. To
+		 *   avoid wrong repaints, we undraw the cursor in that case, and everything
+		 *   looks nicely ;)
+		 * (and now hope this story above makes sense to you ;))
+		 */
+
+		if (_cursor.visible) {
+			if (_cursor.draw_pos.x + _cursor.draw_size.x >= _chatmsg_box.x &&
+				_cursor.draw_pos.x <= _chatmsg_box.x + _chatmsg_box.width &&
+				_cursor.draw_pos.y + _cursor.draw_size.y >= _screen.height - _chatmsg_box.y - _chatmsg_box.height &&
+				_cursor.draw_pos.y <= _screen.height - _chatmsg_box.y) {
+				UndrawMouseCursor();
+			}
+		}
+
+		int x      = _chatmsg_box.x;
+		int y      = _screen.height - _chatmsg_box.y - _chatmsg_box.height;
+		int width  = _chatmsg_box.width;
+		int height = _chatmsg_box.height;
+		if (y < 0) {
+			height = max(height + y, min(_chatmsg_box.height, _screen.height));
+			y = 0;
+		}
+		if (x + width >= _screen.width) {
+			width = _screen.width - x;
+		}
+		if (width <= 0 || height <= 0) return;
+
+		_chatmessage_visible = false;
+		/* Put our 'shot' back to the screen */
+		blitter->CopyFromBuffer(blitter->MoveTo(_screen.dst_ptr, x, y), _chatmessage_backup, width, height);
+		/* And make sure it is updated next time */
+		_video_driver->MakeDirty(x, y, width, height);
+
+		_chatmessage_dirty = true;
+	}
+}
+
+/** Check if a message is expired every day */
+void NetworkChatMessageDailyLoop()
+{
+	for (uint i = 0; i < MAX_CHAT_MESSAGES; i++) {
+		ChatMessage *cmsg = &_chatmsg_list[i];
+		if (cmsg->message[0] == '\0') continue;
+
+		/* Message has expired, remove from the list */
+		if (cmsg->end_date < _date) {
+			/* Move the remaining messages over the current message */
+			if (i != MAX_CHAT_MESSAGES - 1) memmove(cmsg, cmsg + 1, sizeof(*cmsg) * (MAX_CHAT_MESSAGES - i - 1));
+
+			/* Mark the last item as empty */
+			_chatmsg_list[MAX_CHAT_MESSAGES - 1].message[0] = '\0';
+			_chatmessage_dirty = true;
+
+			/* Go one item back, because we moved the array 1 to the left */
+			i--;
+		}
+	}
+}
+
+/** Draw the chat message-box */
+void NetworkDrawChatMessage()
+{
+	Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
+	if (!_chatmessage_dirty) return;
+
+	/* First undraw if needed */
+	NetworkUndrawChatMessage();
+
+	if (_iconsole_mode == ICONSOLE_FULL) return;
+
+	/* Check if we have anything to draw at all */
+	uint count = GetChatMessageCount();
+	if (count == 0) return;
+
+	int x      = _chatmsg_box.x;
+	int y      = _screen.height - _chatmsg_box.y - _chatmsg_box.height;
+	int width  = _chatmsg_box.width;
+	int height = _chatmsg_box.height;
+	if (y < 0) {
+		height = max(height + y, min(_chatmsg_box.height, _screen.height));
+		y = 0;
+	}
+	if (x + width >= _screen.width) {
+		width = _screen.width - x;
+	}
+	if (width <= 0 || height <= 0) return;
+
+	assert(blitter->BufferSize(width, height) < (int)sizeof(_chatmessage_backup));
+
+	/* Make a copy of the screen as it is before painting (for undraw) */
+	blitter->CopyToBuffer(blitter->MoveTo(_screen.dst_ptr, x, y), _chatmessage_backup, width, height);
+
+	_cur_dpi = &_screen; // switch to _screen painting
+
+	/* Paint a half-transparent box behind the chat messages */
+	GfxFillRect(
+			_chatmsg_box.x,
+			_screen.height - _chatmsg_box.y - count * 13 - 2,
+			_chatmsg_box.x + _chatmsg_box.width - 1,
+			_screen.height - _chatmsg_box.y - 2,
+			PALETTE_TO_TRANSPARENT, FILLRECT_RECOLOR // black, but with some alpha for background
+		);
+
+	/* Paint the chat messages starting with the lowest at the bottom */
+	for (uint y = 13; count-- != 0; y += 13) {
+		DoDrawString(_chatmsg_list[count].message, _chatmsg_box.x + 3, _screen.height - _chatmsg_box.y - y + 1, _chatmsg_list[count].color);
+	}
+
+	/* Make sure the data is updated next flush */
+	_video_driver->MakeDirty(x, y, width, height);
+
+	_chatmessage_visible = true;
+	_chatmessage_dirty = false;
+}
+
+
+static void SendChat(const char *buf, DestType type, int dest)
+{
+	if (StrEmpty(buf)) return;
+	if (!_network_server) {
+		SEND_COMMAND(PACKET_CLIENT_CHAT)((NetworkAction)(NETWORK_ACTION_CHAT + type), type, dest, buf);
+	} else {
+		NetworkServerSendChat((NetworkAction)(NETWORK_ACTION_CHAT + type), type, dest, buf, NETWORK_SERVER_INDEX);
+	}
+}
+
+
+struct NetworkChatWindow : public QueryStringBaseWindow {
+	DestType dtype;
+	int dest;
+
+	NetworkChatWindow (const WindowDesc *desc, DestType type, int dest) : QueryStringBaseWindow(NETWORK_CHAT_LENGTH, desc)
+	{
+		this->LowerWidget(2);
+		this->dtype   = type;
+		this->dest    = dest;
+		this->afilter = CS_ALPHANUMERAL;
+		InitializeTextBuffer(&this->text, this->edit_str_buf, this->edit_str_size, 0);
+
+		InvalidateWindowData(WC_NEWS_WINDOW, 0, this->height);
+		SetBit(_no_scroll, SCROLL_CHAT); // do not scroll the game with the arrow-keys
+
+		_chat_tab_completion_active = false;
+
+		this->FindWindowPlacementAndResize(desc);
+	}
+
+	~NetworkChatWindow ()
+	{
+		InvalidateWindowData(WC_NEWS_WINDOW, 0, 0);
+		ClrBit(_no_scroll, SCROLL_CHAT);
+	}
+
+	/**
+	 * Find the next item of the list of things that can be auto-completed.
+	 * @param item The current indexed item to return. This function can, and most
+	 *     likely will, alter item, to skip empty items in the arrays.
+	 * @return Returns the char that matched to the index.
+	 */
+	const char *ChatTabCompletionNextItem(uint *item)
+	{
+		static char chat_tab_temp_buffer[64];
+
+		/* First, try clients */
+		if (*item < MAX_CLIENT_INFO) {
+			/* Skip inactive clients */
+			while (_network_client_info[*item].client_index == NETWORK_EMPTY_INDEX && *item < MAX_CLIENT_INFO) (*item)++;
+			if (*item < MAX_CLIENT_INFO) return _network_client_info[*item].client_name;
+		}
+
+		/* Then, try townnames */
+		/* Not that the following assumes all town indices are adjacent, ie no
+		* towns have been deleted. */
+		if (*item <= (uint)MAX_CLIENT_INFO + GetMaxTownIndex()) {
+			const Town *t;
+
+			FOR_ALL_TOWNS_FROM(t, *item - MAX_CLIENT_INFO) {
+				/* Get the town-name via the string-system */
+				SetDParam(0, t->index);
+				GetString(chat_tab_temp_buffer, STR_TOWN, lastof(chat_tab_temp_buffer));
+				return &chat_tab_temp_buffer[0];
+			}
+		}
+
+		return NULL;
+	}
+
+	/**
+	 * Find what text to complete. It scans for a space from the left and marks
+	 *  the word right from that as to complete. It also writes a \0 at the
+	 *  position of the space (if any). If nothing found, buf is returned.
+	 */
+	static char *ChatTabCompletionFindText(char *buf)
+	{
+		char *p = strrchr(buf, ' ');
+		if (p == NULL) return buf;
+
+		*p = '\0';
+		return p + 1;
+	}
+
+	/**
+	 * See if we can auto-complete the current text of the user.
+	 */
+	void ChatTabCompletion()
+	{
+		static char _chat_tab_completion_buf[NETWORK_CHAT_LENGTH];
+		assert(this->edit_str_size == lengthof(_chat_tab_completion_buf));
+
+		Textbuf *tb = &this->text;
+		size_t len, tb_len;
+		uint item;
+		char *tb_buf, *pre_buf;
+		const char *cur_name;
+		bool second_scan = false;
+
+		item = 0;
+
+		/* Copy the buffer so we can modify it without damaging the real data */
+		pre_buf = (_chat_tab_completion_active) ? strdup(_chat_tab_completion_buf) : strdup(tb->buf);
+
+		tb_buf  = ChatTabCompletionFindText(pre_buf);
+		tb_len  = strlen(tb_buf);
+
+		while ((cur_name = ChatTabCompletionNextItem(&item)) != NULL) {
+			item++;
+
+			if (_chat_tab_completion_active) {
+				/* We are pressing TAB again on the same name, is there an other name
+				*  that starts with this? */
+				if (!second_scan) {
+					size_t offset;
+					size_t length;
+
+					/* If we are completing at the begin of the line, skip the ': ' we added */
+					if (tb_buf == pre_buf) {
+						offset = 0;
+						length = tb->length - 2;
+					} else {
+						/* Else, find the place we are completing at */
+						offset = strlen(pre_buf) + 1;
+						length = tb->length - offset;
+					}
+
+					/* Compare if we have a match */
+					if (strlen(cur_name) == length && strncmp(cur_name, tb->buf + offset, length) == 0) second_scan = true;
+
+					continue;
+				}
+
+				/* Now any match we make on _chat_tab_completion_buf after this, is perfect */
+			}
+
+			len = strlen(cur_name);
+			if (tb_len < len && strncasecmp(cur_name, tb_buf, tb_len) == 0) {
+				/* Save the data it was before completion */
+				if (!second_scan) snprintf(_chat_tab_completion_buf, lengthof(_chat_tab_completion_buf), "%s", tb->buf);
+				_chat_tab_completion_active = true;
+
+				/* Change to the found name. Add ': ' if we are at the start of the line (pretty) */
+				if (pre_buf == tb_buf) {
+					snprintf(tb->buf, this->edit_str_size, "%s: ", cur_name);
+				} else {
+					snprintf(tb->buf, this->edit_str_size, "%s %s", pre_buf, cur_name);
+				}
+
+				/* Update the textbuffer */
+				UpdateTextBufferSize(&this->text);
+
+				this->SetDirty();
+				free(pre_buf);
+				return;
+			}
+		}
+
+		if (second_scan) {
+			/* We walked all posibilities, and the user presses tab again.. revert to original text */
+			strcpy(tb->buf, _chat_tab_completion_buf);
+			_chat_tab_completion_active = false;
+
+			/* Update the textbuffer */
+			UpdateTextBufferSize(&this->text);
+
+			this->SetDirty();
+		}
+		free(pre_buf);
+	}
+
+	virtual void OnPaint()
+	{
+		static const StringID chat_captions[] = {
+			STR_NETWORK_CHAT_ALL_CAPTION,
+			STR_NETWORK_CHAT_COMPANY_CAPTION,
+			STR_NETWORK_CHAT_CLIENT_CAPTION
+		};
+
+		this->DrawWidgets();
+
+		assert((uint)this->dtype < lengthof(chat_captions));
+		DrawStringRightAligned(this->widget[2].left - 2, this->widget[2].top + 1, chat_captions[this->dtype], TC_BLACK);
+		this->DrawEditBox(2);
+	}
+
+	virtual void OnClick(Point pt, int widget)
+	{
+		switch (widget) {
+			case 2:
+				ShowOnScreenKeyboard(this, 2, 0, 3);
+				break;
+
+			case 3: /* Send */
+				SendChat(this->text.buf, this->dtype, this->dest);
+			/* FALLTHROUGH */
+			case 0: /* Cancel */ delete this; break;
+		}
+	}
+
+	virtual void OnMouseLoop()
+	{
+		this->HandleEditBox(2);
+	}
+
+	virtual EventState OnKeyPress(uint16 key, uint16 keycode)
+	{
+		EventState state = ES_NOT_HANDLED;
+		if (keycode == WKC_TAB) {
+			ChatTabCompletion();
+		} else {
+			_chat_tab_completion_active = false;
+			switch (this->HandleEditBoxKey(2, key, keycode, state)) {
+				case 1: /* Return */
+					SendChat(this->text.buf, this->dtype, this->dest);
+				/* FALLTHROUGH */
+				case 2: /* Escape */ delete this; break;
+			}
+		}
+		return state;
+	}
+};
+
+static const Widget _chat_window_widgets[] = {
+{   WWT_CLOSEBOX, RESIZE_NONE,   COLOUR_GREY,   0,  10,  0, 13, STR_00C5,                  STR_018B_CLOSE_WINDOW},
+{      WWT_PANEL, RESIZE_RIGHT,  COLOUR_GREY,  11, 319,  0, 13, 0x0,                       STR_NULL}, // background
+{    WWT_EDITBOX, RESIZE_RIGHT,  COLOUR_GREY,  75, 257,  1, 12, STR_NETWORK_CHAT_OSKTITLE, STR_NULL}, // text box
+{ WWT_PUSHTXTBTN, RESIZE_LR,     COLOUR_GREY, 258, 319,  1, 12, STR_NETWORK_SEND,          STR_NULL}, // send button
+{   WIDGETS_END},
+};
+
+static const WindowDesc _chat_window_desc = {
+	WDP_CENTER, -26, 320, 14, 640, 14, // x, y, width, height
+	WC_SEND_NETWORK_MSG, WC_NONE,
+	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET,
+	_chat_window_widgets,
+};
+
+void ShowNetworkChatQueryWindow(DestType type, int dest)
+{
+	DeleteWindowById(WC_SEND_NETWORK_MSG, 0);
+	new NetworkChatWindow (&_chat_window_desc, type, dest);
+}
+
+#endif /* ENABLE_NETWORK */
--- a/src/network/network_func.h
+++ b/src/network/network_func.h
@@ -57,6 +57,11 @@
 void NetworkServerSendError(uint16 client_index, NetworkErrorCode error);
 void NetworkServerSendChat(NetworkAction action, DestType type, int dest, const char *msg, uint16 from_index);
 
+void NetworkInitChatMessage();
+void CDECL NetworkAddChatMessage(uint16 color, uint8 duration, const char *message, ...);
+void NetworkUndrawChatMessage();
+void NetworkChatMessageDailyLoop();
+
 #define FOR_ALL_ACTIVE_CLIENT_INFOS(ci) for (ci = _network_client_info; ci != endof(_network_client_info); ci++) if (ci->client_index != NETWORK_EMPTY_INDEX)
 
 #endif /* ENABLE_NETWORK */
--- a/src/network/network_gui.cpp
+++ b/src/network/network_gui.cpp
@@ -15,11 +15,9 @@
 #include "network_gamelist.h"
 #include "../gui.h"
 #include "../window_gui.h"
-#include "../textbuf_gui.h"
 #include "../variables.h"
 #include "network_server.h"
 #include "network_udp.h"
-#include "../town.h"
 #include "../newgrf.h"
 #include "../functions.h"
 #include "../window_func.h"
@@ -37,8 +35,6 @@
 #include "../table/sprites.h"
 
 
-static bool _chat_tab_completion_active;
-
 static void ShowNetworkStartServerWindow();
 static void ShowNetworkLobbyWindow(NetworkGameList *ngl);
 extern void SwitchMode(int new_mode);
@@ -1732,250 +1728,6 @@
 	new NetworkJoinStatusWindow(&_network_join_status_window_desc);
 }
 
-static void SendChat(const char *buf, DestType type, int dest)
-{
-	if (StrEmpty(buf)) return;
-	if (!_network_server) {
-		SEND_COMMAND(PACKET_CLIENT_CHAT)((NetworkAction)(NETWORK_ACTION_CHAT + type), type, dest, buf);
-	} else {
-		NetworkServerSendChat((NetworkAction)(NETWORK_ACTION_CHAT + type), type, dest, buf, NETWORK_SERVER_INDEX);
-	}
-}
-
-
-struct NetworkChatWindow : public QueryStringBaseWindow {
-	DestType dtype;
-	int dest;
-
-	NetworkChatWindow (const WindowDesc *desc, DestType type, int dest) : QueryStringBaseWindow(NETWORK_CHAT_LENGTH, desc)
-	{
-		this->LowerWidget(2);
-		this->dtype   = type;
-		this->dest    = dest;
-		this->afilter = CS_ALPHANUMERAL;
-		InitializeTextBuffer(&this->text, this->edit_str_buf, this->edit_str_size, 0);
-
-		InvalidateWindowData(WC_NEWS_WINDOW, 0, this->height);
-		SetBit(_no_scroll, SCROLL_CHAT); // do not scroll the game with the arrow-keys
-
-		_chat_tab_completion_active = false;
-
-		this->FindWindowPlacementAndResize(desc);
-	}
-
-	~NetworkChatWindow ()
-	{
-		InvalidateWindowData(WC_NEWS_WINDOW, 0, 0);
-		ClrBit(_no_scroll, SCROLL_CHAT);
-	}
-
-	/**
-	 * Find the next item of the list of things that can be auto-completed.
-	 * @param item The current indexed item to return. This function can, and most
-	 *     likely will, alter item, to skip empty items in the arrays.
-	 * @return Returns the char that matched to the index.
-	 */
-	const char *ChatTabCompletionNextItem(uint *item)
-	{
-		static char chat_tab_temp_buffer[64];
-
-		/* First, try clients */
-		if (*item < MAX_CLIENT_INFO) {
-			/* Skip inactive clients */
-			while (_network_client_info[*item].client_index == NETWORK_EMPTY_INDEX && *item < MAX_CLIENT_INFO) (*item)++;
-			if (*item < MAX_CLIENT_INFO) return _network_client_info[*item].client_name;
-		}
-
-		/* Then, try townnames */
-		/* Not that the following assumes all town indices are adjacent, ie no
-		* towns have been deleted. */
-		if (*item <= (uint)MAX_CLIENT_INFO + GetMaxTownIndex()) {
-			const Town *t;
-
-			FOR_ALL_TOWNS_FROM(t, *item - MAX_CLIENT_INFO) {
-				/* Get the town-name via the string-system */
-				SetDParam(0, t->index);
-				GetString(chat_tab_temp_buffer, STR_TOWN, lastof(chat_tab_temp_buffer));
-				return &chat_tab_temp_buffer[0];
-			}
-		}
-
-		return NULL;
-	}
-
-	/**
-	 * Find what text to complete. It scans for a space from the left and marks
-	 *  the word right from that as to complete. It also writes a \0 at the
-	 *  position of the space (if any). If nothing found, buf is returned.
-	 */
-	static char *ChatTabCompletionFindText(char *buf)
-	{
-		char *p = strrchr(buf, ' ');
-		if (p == NULL) return buf;
-
-		*p = '\0';
-		return p + 1;
-	}
-
-	/**
-	 * See if we can auto-complete the current text of the user.
-	 */
-	void ChatTabCompletion()
-	{
-		static char _chat_tab_completion_buf[NETWORK_CHAT_LENGTH];
-		assert(this->edit_str_size == lengthof(_chat_tab_completion_buf));
-
-		Textbuf *tb = &this->text;
-		size_t len, tb_len;
-		uint item;
-		char *tb_buf, *pre_buf;
-		const char *cur_name;
-		bool second_scan = false;
-
-		item = 0;
-
-		/* Copy the buffer so we can modify it without damaging the real data */
-		pre_buf = (_chat_tab_completion_active) ? strdup(_chat_tab_completion_buf) : strdup(tb->buf);
-
-		tb_buf  = ChatTabCompletionFindText(pre_buf);
-		tb_len  = strlen(tb_buf);
-
-		while ((cur_name = ChatTabCompletionNextItem(&item)) != NULL) {
-			item++;
-
-			if (_chat_tab_completion_active) {
-				/* We are pressing TAB again on the same name, is there an other name
-				*  that starts with this? */
-				if (!second_scan) {
-					size_t offset;
-					size_t length;
-
-					/* If we are completing at the begin of the line, skip the ': ' we added */
-					if (tb_buf == pre_buf) {
-						offset = 0;
-						length = tb->length - 2;
-					} else {
-						/* Else, find the place we are completing at */
-						offset = strlen(pre_buf) + 1;
-						length = tb->length - offset;
-					}
-
-					/* Compare if we have a match */
-					if (strlen(cur_name) == length && strncmp(cur_name, tb->buf + offset, length) == 0) second_scan = true;
-
-					continue;
-				}
-
-				/* Now any match we make on _chat_tab_completion_buf after this, is perfect */
-			}
-
-			len = strlen(cur_name);
-			if (tb_len < len && strncasecmp(cur_name, tb_buf, tb_len) == 0) {
-				/* Save the data it was before completion */
-				if (!second_scan) snprintf(_chat_tab_completion_buf, lengthof(_chat_tab_completion_buf), "%s", tb->buf);
-				_chat_tab_completion_active = true;
-
-				/* Change to the found name. Add ': ' if we are at the start of the line (pretty) */
-				if (pre_buf == tb_buf) {
-					snprintf(tb->buf, this->edit_str_size, "%s: ", cur_name);
-				} else {
-					snprintf(tb->buf, this->edit_str_size, "%s %s", pre_buf, cur_name);
-				}
-
-				/* Update the textbuffer */
-				UpdateTextBufferSize(&this->text);
-
-				this->SetDirty();
-				free(pre_buf);
-				return;
-			}
-		}
-
-		if (second_scan) {
-			/* We walked all posibilities, and the user presses tab again.. revert to original text */
-			strcpy(tb->buf, _chat_tab_completion_buf);
-			_chat_tab_completion_active = false;
-
-			/* Update the textbuffer */
-			UpdateTextBufferSize(&this->text);
-
-			this->SetDirty();
-		}
-		free(pre_buf);
-	}
-
-	virtual void OnPaint()
-	{
-		static const StringID chat_captions[] = {
-			STR_NETWORK_CHAT_ALL_CAPTION,
-			STR_NETWORK_CHAT_COMPANY_CAPTION,
-			STR_NETWORK_CHAT_CLIENT_CAPTION
-		};
-
-		this->DrawWidgets();
-
-		assert((uint)this->dtype < lengthof(chat_captions));
-		DrawStringRightAligned(this->widget[2].left - 2, this->widget[2].top + 1, chat_captions[this->dtype], TC_BLACK);
-		this->DrawEditBox(2);
-	}
-
-	virtual void OnClick(Point pt, int widget)
-	{
-		switch (widget) {
-			case 2:
-				ShowOnScreenKeyboard(this, 2, 0, 3);
-				break;
-
-			case 3: /* Send */
-				SendChat(this->text.buf, this->dtype, this->dest);
-			/* FALLTHROUGH */
-			case 0: /* Cancel */ delete this; break;
-		}
-	}
-
-	virtual void OnMouseLoop()
-	{
-		this->HandleEditBox(2);
-	}
-
-	virtual EventState OnKeyPress(uint16 key, uint16 keycode)
-	{
-		EventState state = ES_NOT_HANDLED;
-		if (keycode == WKC_TAB) {
-			ChatTabCompletion();
-		} else {
-			_chat_tab_completion_active = false;
-			switch (this->HandleEditBoxKey(2, key, keycode, state)) {
-				case 1: /* Return */
-					SendChat(this->text.buf, this->dtype, this->dest);
-				/* FALLTHROUGH */
-				case 2: /* Escape */ delete this; break;
-			}
-		}
-		return state;
-	}
-};
-
-static const Widget _chat_window_widgets[] = {
-{   WWT_CLOSEBOX, RESIZE_NONE,   COLOUR_GREY,   0,  10,  0, 13, STR_00C5,                  STR_018B_CLOSE_WINDOW},
-{      WWT_PANEL, RESIZE_RIGHT,  COLOUR_GREY,  11, 319,  0, 13, 0x0,                       STR_NULL}, // background
-{    WWT_EDITBOX, RESIZE_RIGHT,  COLOUR_GREY,  75, 257,  1, 12, STR_NETWORK_CHAT_OSKTITLE, STR_NULL}, // text box
-{ WWT_PUSHTXTBTN, RESIZE_LR,     COLOUR_GREY, 258, 319,  1, 12, STR_NETWORK_SEND,          STR_NULL}, // send button
-{   WIDGETS_END},
-};
-
-static const WindowDesc _chat_window_desc = {
-	WDP_CENTER, -26, 320, 14, 640, 14, // x, y, width, height
-	WC_SEND_NETWORK_MSG, WC_NONE,
-	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET,
-	_chat_window_widgets,
-};
-
-void ShowNetworkChatQueryWindow(DestType type, int dest)
-{
-	DeleteWindowById(WC_SEND_NETWORK_MSG, 0);
-	new NetworkChatWindow (&_chat_window_desc, type, dest);
-}
 
 /** Enum for NetworkGameWindow, referring to _network_game_window_widgets */
 enum NetworkCompanyPasswordWindowWidgets {
--- a/src/texteff.cpp
+++ b/src/texteff.cpp
@@ -6,27 +6,18 @@
 #include "openttd.h"
 #include "landscape.h"
 #include "gfx_func.h"
-#include "console_func.h"
 #include "variables.h"
-#include "blitter/factory.hpp"
 #include "texteff.hpp"
-#include "video/video_driver.hpp"
+#include "core/bitmath_func.hpp"
 #include "transparency.h"
 #include "strings_func.h"
 #include "core/alloc_func.hpp"
-#include "date_func.h"
 #include "functions.h"
 #include "viewport_func.h"
 #include "settings_type.h"
 
-#include "table/sprites.h"
-
-#include <stdarg.h> /* va_list */
-
 enum {
-	MAX_TEXTMESSAGE_LENGTH = 200,
-	INIT_NUM_TEXT_MESSAGES =  20,
-	MAX_CHAT_MESSAGES      =  10,
+	INIT_NUM_TEXT_EFFECTS  =  20,
 };
 
 struct TextEffect {
@@ -41,220 +32,9 @@
 	TextEffectMode mode;
 };
 
-
-struct ChatMessage {
-	char message[MAX_TEXTMESSAGE_LENGTH];
-	uint16 color;
-	Date end_date;
-};
-
 /* used for text effects */
 static TextEffect *_text_effect_list = NULL;
-static uint16 _num_text_effects = INIT_NUM_TEXT_MESSAGES;
-
-/* used for chat window */
-static ChatMessage _chatmsg_list[MAX_CHAT_MESSAGES];
-static bool _chatmessage_dirty = false;
-static bool _chatmessage_visible = false;
-
-/* The chatbox grows from the bottom so the coordinates are pixels from
- * the left and pixels from the bottom. The height is the maximum height */
-static const PointDimension _chatmsg_box = {10, 30, 500, 150};
-static uint8 _chatmessage_backup[150 * 500 * 6]; // (height * width)
-
-static inline uint GetChatMessageCount()
-{
-	uint i;
-
-	for (i = 0; i < MAX_CHAT_MESSAGES; i++) {
-		if (_chatmsg_list[i].message[0] == '\0') break;
-	}
-
-	return i;
-}
-
-/* Add a text message to the 'chat window' to be shown
- * @param color The colour this message is to be shown in
- * @param duration The duration of the chat message in game-days
- * @param message message itself in printf() style */
-void CDECL AddChatMessage(uint16 color, uint8 duration, const char *message, ...)
-{
-	char buf[MAX_TEXTMESSAGE_LENGTH];
-	const char *bufp;
-	va_list va;
-	uint msg_count;
-	uint16 lines;
-
-	va_start(va, message);
-	vsnprintf(buf, lengthof(buf), message, va);
-	va_end(va);
-
-
-	Utf8TrimString(buf, MAX_TEXTMESSAGE_LENGTH);
-
-	/* Force linebreaks for strings that are too long */
-	lines = GB(FormatStringLinebreaks(buf, _chatmsg_box.width - 8), 0, 16) + 1;
-	if (lines >= MAX_CHAT_MESSAGES) return;
-
-	msg_count = GetChatMessageCount();
-	/* We want to add more chat messages than there is free space for, remove 'old' */
-	if (lines > MAX_CHAT_MESSAGES - msg_count) {
-		int i = lines - (MAX_CHAT_MESSAGES - msg_count);
-		memmove(&_chatmsg_list[0], &_chatmsg_list[i], sizeof(_chatmsg_list[0]) * (msg_count - i));
-		msg_count = MAX_CHAT_MESSAGES - lines;
-	}
-
-	for (bufp = buf; lines != 0; lines--) {
-		ChatMessage *cmsg = &_chatmsg_list[msg_count++];
-		ttd_strlcpy(cmsg->message, bufp, sizeof(cmsg->message));
-
-		/* The default colour for a message is player colour. Replace this with
-		 * white for any additional lines */
-		cmsg->color = (bufp == buf && color & IS_PALETTE_COLOR) ? color : (0x1D - 15) | IS_PALETTE_COLOR;
-		cmsg->end_date = _date + duration;
-
-		bufp += strlen(bufp) + 1; // jump to 'next line' in the formatted string
-	}
-
-	_chatmessage_dirty = true;
-}
-
-void InitChatMessage()
-{
-	uint i;
-
-	for (i = 0; i < MAX_CHAT_MESSAGES; i++) {
-		_chatmsg_list[i].message[0] = '\0';
-	}
-}
-
-/** Hide the chatbox */
-void UndrawChatMessage()
-{
-	if (_chatmessage_visible) {
-		Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
-		/* Sometimes we also need to hide the cursor
-		 *   This is because both textmessage and the cursor take a shot of the
-		 *   screen before drawing.
-		 *   Now the textmessage takes his shot and paints his data before the cursor
-		 *   does, so in the shot of the cursor is the screen-data of the textmessage
-		 *   included when the cursor hangs somewhere over the textmessage. To
-		 *   avoid wrong repaints, we undraw the cursor in that case, and everything
-		 *   looks nicely ;)
-		 * (and now hope this story above makes sense to you ;))
-		 */
-
-		if (_cursor.visible) {
-			if (_cursor.draw_pos.x + _cursor.draw_size.x >= _chatmsg_box.x &&
-				_cursor.draw_pos.x <= _chatmsg_box.x + _chatmsg_box.width &&
-				_cursor.draw_pos.y + _cursor.draw_size.y >= _screen.height - _chatmsg_box.y - _chatmsg_box.height &&
-				_cursor.draw_pos.y <= _screen.height - _chatmsg_box.y) {
-				UndrawMouseCursor();
-			}
-		}
-
-		int x      = _chatmsg_box.x;
-		int y      = _screen.height - _chatmsg_box.y - _chatmsg_box.height;
-		int width  = _chatmsg_box.width;
-		int height = _chatmsg_box.height;
-		if (y < 0) {
-			height = max(height + y, min(_chatmsg_box.height, _screen.height));
-			y = 0;
-		}
-		if (x + width >= _screen.width) {
-			width = _screen.width - x;
-		}
-		if (width <= 0 || height <= 0) return;
-
-		_chatmessage_visible = false;
-		/* Put our 'shot' back to the screen */
-		blitter->CopyFromBuffer(blitter->MoveTo(_screen.dst_ptr, x, y), _chatmessage_backup, width, height);
-		/* And make sure it is updated next time */
-		_video_driver->MakeDirty(x, y, width, height);
-
-		_chatmessage_dirty = true;
-	}
-}
-
-/** Check if a message is expired every day */
-void ChatMessageDailyLoop()
-{
-	uint i;
-
-	for (i = 0; i < MAX_CHAT_MESSAGES; i++) {
-		ChatMessage *cmsg = &_chatmsg_list[i];
-		if (cmsg->message[0] == '\0') continue;
-
-		/* Message has expired, remove from the list */
-		if (cmsg->end_date < _date) {
-			/* Move the remaining messages over the current message */
-			if (i != MAX_CHAT_MESSAGES - 1) memmove(cmsg, cmsg + 1, sizeof(*cmsg) * (MAX_CHAT_MESSAGES - i - 1));
-
-			/* Mark the last item as empty */
-			_chatmsg_list[MAX_CHAT_MESSAGES - 1].message[0] = '\0';
-			_chatmessage_dirty = true;
-
-			/* Go one item back, because we moved the array 1 to the left */
-			i--;
-		}
-	}
-}
-
-/** Draw the chat message-box */
-void DrawChatMessage()
-{
-	Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
-	if (!_chatmessage_dirty) return;
-
-	/* First undraw if needed */
-	UndrawChatMessage();
-
-	if (_iconsole_mode == ICONSOLE_FULL) return;
-
-	/* Check if we have anything to draw at all */
-	uint count = GetChatMessageCount();
-	if (count == 0) return;
-
-	int x      = _chatmsg_box.x;
-	int y      = _screen.height - _chatmsg_box.y - _chatmsg_box.height;
-	int width  = _chatmsg_box.width;
-	int height = _chatmsg_box.height;
-	if (y < 0) {
-		height = max(height + y, min(_chatmsg_box.height, _screen.height));
-		y = 0;
-	}
-	if (x + width >= _screen.width) {
-		width = _screen.width - x;
-	}
-	if (width <= 0 || height <= 0) return;
-
-	assert(blitter->BufferSize(width, height) < (int)sizeof(_chatmessage_backup));
-
-	/* Make a copy of the screen as it is before painting (for undraw) */
-	blitter->CopyToBuffer(blitter->MoveTo(_screen.dst_ptr, x, y), _chatmessage_backup, width, height);
-
-	_cur_dpi = &_screen; // switch to _screen painting
-
-	/* Paint a half-transparent box behind the chat messages */
-	GfxFillRect(
-			_chatmsg_box.x,
-			_screen.height - _chatmsg_box.y - count * 13 - 2,
-			_chatmsg_box.x + _chatmsg_box.width - 1,
-			_screen.height - _chatmsg_box.y - 2,
-			PALETTE_TO_TRANSPARENT, FILLRECT_RECOLOR // black, but with some alpha for background
-		);
-
-	/* Paint the chat messages starting with the lowest at the bottom */
-	for (uint y = 13; count-- != 0; y += 13) {
-		DoDrawString(_chatmsg_list[count].message, _chatmsg_box.x + 3, _screen.height - _chatmsg_box.y - y + 1, _chatmsg_list[count].color);
-	}
-
-	/* Make sure the data is updated next flush */
-	_video_driver->MakeDirty(x, y, width, height);
-
-	_chatmessage_visible = true;
-	_chatmessage_dirty = false;
-}
+static uint16 _num_text_effects = INIT_NUM_TEXT_EFFECTS;
 
 /* Text Effects */
 /**
--- a/src/texteff.hpp
+++ b/src/texteff.hpp
@@ -26,11 +26,6 @@
 void UpdateTextEffect(TextEffectID effect_id, StringID msg);
 void RemoveTextEffect(TextEffectID effect_id);
 
-void InitChatMessage();
-void DrawChatMessage();
-void CDECL AddChatMessage(uint16 color, uint8 duration, const char *message, ...);
-void UndrawChatMessage();
-
 /* misc_gui.cpp */
 TextEffectID ShowFillingPercent(int x, int y, int z, uint8 percent, StringID color);
 void UpdateFillingPercent(TextEffectID te_id, uint8 percent, StringID color);
--- a/src/video/cocoa/event.mm
+++ b/src/video/cocoa/event.mm
@@ -694,7 +694,7 @@
 			st += GetTick() - st0;
 #endif
 			_screen.dst_ptr = _cocoa_subdriver->GetPixelBuffer();
-			DrawChatMessage();
+			NetworkDrawChatMessage();
 			DrawMouseCursor();
 			_cocoa_subdriver->Draw();
 		}
--- a/src/video/sdl_v.cpp
+++ b/src/video/sdl_v.cpp
@@ -518,7 +518,7 @@
 		} else {
 			SDL_CALL SDL_Delay(1);
 			_screen.dst_ptr = _sdl_screen->pixels;
-			DrawChatMessage();
+			NetworkDrawChatMessage();
 			DrawMouseCursor();
 			DrawSurfaceToScreen();
 		}
--- a/src/video/win32_v.cpp
+++ b/src/video/win32_v.cpp
@@ -890,7 +890,7 @@
 			GdiFlush();
 #endif
 			_screen.dst_ptr = _wnd.buffer_bits;
-			DrawChatMessage();
+			NetworkDrawChatMessage();
 			DrawMouseCursor();
 		}
 	}
--- a/src/window.cpp
+++ b/src/window.cpp
@@ -23,6 +23,7 @@
 #include "cheat_func.h"
 #include "window_func.h"
 #include "tilehighlight_func.h"
+#include "network/network.h"
 
 #include "table/sprites.h"
 
@@ -1984,7 +1985,7 @@
 	FOR_ALL_WINDOWS(wz) {
 		if ((*wz)->viewport != NULL) UpdateViewportPosition(*wz);
 	}
-	DrawChatMessage();
+	NetworkDrawChatMessage();
 	/* Redraw mouse cursor in case it was hidden */
 	DrawMouseCursor();
 }