changeset 4315:796e6f02d96f draft

(svn r5968) -Feature: add auto-completion in chat-window. It completes Player-Names and Town-Names (that order) using <tab>. Based on FS#28 by egladil.
author truelight <truelight@openttd.org>
date Sun, 20 Aug 2006 10:51:27 +0000
parents 7eb9bc1b9f3f
children 597a8a364ec5
files network_gui.c
diffstat 1 files changed, 156 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/network_gui.c
+++ b/network_gui.c
@@ -26,6 +26,7 @@
 #include "network_udp.h"
 #include "settings.h"
 #include "string.h"
+#include "town.h"
 
 #define BGC 5
 #define BTC 15
@@ -55,6 +56,7 @@
 static NetworkGameSorting _ng_sorting;
 
 static char _edit_str_buf[64];
+static bool _chat_tab_completion_active;
 
 static void ShowNetworkStartServerWindow(void);
 static void ShowNetworkLobbyWindow(NetworkGameList *ngl);
@@ -1488,6 +1490,151 @@
 	}
 }
 
+/**
+ * Find the next item of the list of things that can be auto-completed.
+ */
+static 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 */
+	if (*item < (uint)MAX_CLIENT_INFO + GetTownPoolSize()) {
+		Town *t;
+
+		FOR_ALL_TOWNS_FROM(t, *item - MAX_CLIENT_INFO) {
+			int32 temp[1];
+
+			/* Skip empty towns */
+			if (t->xy == 0) {
+				(*item)++;
+				continue;
+			}
+
+			/* Get the town-name via the string-system */
+			temp[0] = t->townnameparts;
+			GetStringWithArgs(chat_tab_temp_buffer, t->townnametype, temp);
+			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;
+
+	/* Scan from the right to the left */
+	p = &buf[strlen(buf)];
+	while (p != buf) {
+		/* If we find a space, we try to complete the thing right of it */
+		if (*p == ' ') {
+			*p = '\0';
+			return p + 1;
+		}
+		p--;
+	}
+
+	/* Not found, so complete the whole text */
+	return p;
+}
+
+/**
+ * See if we can auto-complete the current text of the user.
+ */
+static void ChatTabCompletion(Window *w)
+{
+	static char _chat_tab_completion_buf[lengthof(_edit_str_buf)];
+	Textbuf *tb = &WP(w, querystr_d).text;
+	uint 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) {
+				uint offset;
+				uint 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, lengthof(_edit_str_buf), "%s: ", cur_name);
+			} else {
+				snprintf(tb->buf, lengthof(_edit_str_buf), "%s %s", pre_buf, cur_name);
+			}
+
+			/* Update the textbuffer */
+			UpdateTextBufferSize(&WP(w, querystr_d).text);
+
+			SetWindowDirty(w);
+			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(&WP(w, querystr_d).text);
+
+		SetWindowDirty(w);
+	}
+	free(pre_buf);
+}
 
 /* uses querystr_d WP macro */
 static void ChatWindowWndProc(Window *w, WindowEvent *e)
@@ -1515,9 +1662,14 @@
 		break;
 
 	case WE_KEYPRESS:
-		switch (HandleEditBoxKey(w, &WP(w, querystr_d), 1, e, CS_ALPHANUMERAL)) {
-			case 1: /* Return */ SendChat(WP(w, querystr_d).text.buf); /* FALLTHROUGH */
-			case 2: /* Escape */ DeleteWindow(w); break;
+		if (e->keypress.keycode == WKC_TAB) {
+			ChatTabCompletion(w);
+		} else {
+			_chat_tab_completion_active = false;
+			switch (HandleEditBoxKey(w, &WP(w, querystr_d), 1, e, CS_ALPHANUMERAL)) {
+				case 1: /* Return */ SendChat(WP(w, querystr_d).text.buf); /* FALLTHROUGH */
+				case 2: /* Escape */ DeleteWindow(w); break;
+			}
 		}
 		break;
 
@@ -1555,6 +1707,7 @@
 	DeleteWindowById(WC_SEND_NETWORK_MSG, 0);
 
 	_edit_str_buf[0] = '\0';
+	_chat_tab_completion_active = false;
 
 	w = AllocateWindowDesc(&_chat_window_desc);