summaryrefslogtreecommitdiff
path: root/src/network
diff options
context:
space:
mode:
authorrubidium <rubidium@openttd.org>2008-08-11 22:45:11 +0000
committerrubidium <rubidium@openttd.org>2008-08-11 22:45:11 +0000
commitd0c1a989a4226cc06a50b1a8c95b9ca8f0b9599e (patch)
tree96b3b7c215db821df9b9268eb9af5a55ee1200ce /src/network
parent6995365535370da08116d49a30ebd84d56e7d8ff (diff)
downloadopenttd-d0c1a989a4226cc06a50b1a8c95b9ca8f0b9599e.tar.xz
(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.
Diffstat (limited to 'src/network')
-rw-r--r--src/network/network.cpp2
-rw-r--r--src/network/network.h2
-rw-r--r--src/network/network_chat_gui.cpp487
-rw-r--r--src/network/network_func.h5
-rw-r--r--src/network/network_gui.cpp248
5 files changed, 495 insertions, 249 deletions
diff --git a/src/network/network.cpp b/src/network/network.cpp
index 437023636..54f6dd1b4 100644
--- a/src/network/network.cpp
+++ b/src/network/network.cpp
@@ -230,7 +230,7 @@ void CDECL NetworkTextMessage(NetworkAction action, ConsoleColour color, bool se
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
diff --git a/src/network/network.h b/src/network/network.h
index 8de34f112..9c913ceb6 100644
--- 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 @@ extern bool _is_network_server; ///< Does this client wants to be a network-ser
static inline void NetworkStartUp() {}
static inline void NetworkShutDown() {}
+static inline void NetworkDrawChatMessage() {}
#define _networking 0
#define _network_server 0
diff --git a/src/network/network_chat_gui.cpp b/src/network/network_chat_gui.cpp
new file mode 100644
index 000000000..2d4289ffb
--- /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 */
diff --git a/src/network/network_func.h b/src/network/network_func.h
index 3f7612751..cd1a5aa5d 100644
--- a/src/network/network_func.h
+++ b/src/network/network_func.h
@@ -57,6 +57,11 @@ void NetworkServerSendRcon(uint16 client_index, ConsoleColour colour_code, const
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 */
diff --git a/src/network/network_gui.cpp b/src/network/network_gui.cpp
index 70ddccd92..d8ebfffea 100644
--- 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 @@ void ShowJoinStatusWindow()
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 {