From 16965d57975a40b0138ae68e287e39104fae48df Mon Sep 17 00:00:00 2001 From: Alexander Fast Date: Tue, 26 Aug 2014 15:47:51 +0200 Subject: [PATCH] Improves tab completion in chat. Pressing tab after completion now cycles to the next candidate. --- OpenRA.Mods.RA/OpenRA.Mods.RA.csproj | 1 + .../Widgets/Logic/IngameChatLogic.cs | 53 +++---------- OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs | 40 ++-------- .../Widgets/Logic/TabCompletionLogic.cs | 77 +++++++++++++++++++ 4 files changed, 95 insertions(+), 76 deletions(-) create mode 100644 OpenRA.Mods.RA/Widgets/Logic/TabCompletionLogic.cs diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index aaf689a4dc..838e64024c 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -298,6 +298,7 @@ + diff --git a/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs index f9ba129837..2e26f35b41 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs @@ -8,7 +8,6 @@ */ #endregion -using System; using System.Collections.Generic; using System.Drawing; using System.Linq; @@ -31,8 +30,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic readonly List chatTraits; - readonly List commandNames; - readonly List playerNames; + readonly TabCompletionLogic tabCompletion = new TabCompletionLogic(); bool teamChat; @@ -47,8 +45,8 @@ namespace OpenRA.Mods.RA.Widgets.Logic var disableTeamChat = world.LocalPlayer == null || world.LobbyInfo.IsSinglePlayer || !players.Any(p => p.IsAlliedWith(world.LocalPlayer)); teamChat = !disableTeamChat; - commandNames = chatTraits.OfType().SelectMany(x => x.Commands.Keys).Select(x => "/" + x).ToList(); - playerNames = orderManager.LobbyInfo.Clients.Select(c => c.Name).ToList(); + tabCompletion.Commands = chatTraits.OfType().SelectMany(x => x.Commands.Keys).ToList(); + tabCompletion.Names = orderManager.LobbyInfo.Clients.Select(c => c.Name).Distinct().ToList(); var chatPanel = (ContainerWidget)widget; chatOverlay = chatPanel.Get("CHAT_OVERLAY"); @@ -87,7 +85,12 @@ namespace OpenRA.Mods.RA.Widgets.Logic CloseChat(); return true; }; - chatText.OnTabKey = AutoCompleteText; + chatText.OnTabKey = () => + { + chatText.Text = tabCompletion.Complete(chatText.Text); + chatText.CursorPosition = chatText.Text.Length; + return true; + }; chatText.OnEscKey = () => { CloseChat(); return true; }; @@ -178,43 +181,5 @@ namespace OpenRA.Mods.RA.Widgets.Logic Sound.PlayNotification(modRules, null, "Sounds", "ChatLine", null); } - - bool AutoCompleteText() - { - if (string.IsNullOrEmpty(chatText.Text)) - return false; - - if (chatText.Text.LastOrDefault() == ' ') - return false; - - var suggestion = ""; - - if (chatText.Text.StartsWith("/")) - { - suggestion = commandNames.FirstOrDefault(x => x.StartsWith(chatText.Text)); - if (suggestion == null) - return false; - } - else - { - var oneWord = !chatText.Text.Contains(' '); - var toComplete = oneWord - ? chatText.Text - : chatText.Text.Substring(chatText.Text.LastIndexOf(' ') + 1); - - suggestion = playerNames.FirstOrDefault(x => x.StartsWith(toComplete, StringComparison.InvariantCultureIgnoreCase)); - if (suggestion == null) - return false; - - if (oneWord) - suggestion += ": "; - else - suggestion = chatText.Text.Substring(0, chatText.Text.Length - toComplete.Length) + suggestion; - } - - chatText.Text = suggestion; - chatText.CursorPosition = chatText.Text.Length; - return true; - } } } diff --git a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs index 4d444d5d49..0f54f8c71a 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs @@ -51,7 +51,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic readonly ColorPreviewManagerWidget colorPreview; - List playerNames; + readonly TabCompletionLogic tabCompletion = new TabCompletionLogic(); // Listen for connection failures void ConnectionStateChanged(OrderManager om) @@ -509,7 +509,12 @@ namespace OpenRA.Mods.RA.Widgets.Logic chatLabel.Text = teamChat ? "Team:" : "Chat:"; return true; }; - chatTextField.OnTabKey = AutoCompleteText; + chatTextField.OnTabKey = () => + { + chatTextField.Text = tabCompletion.Complete(chatTextField.Text); + chatTextField.CursorPosition = chatTextField.Text.Length; + return true; + }; chatPanel = lobby.Get("CHAT_DISPLAY"); chatTemplate = chatPanel.Get("CHAT_TEMPLATE"); @@ -770,7 +775,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic while (players.Children.Count > idx) players.RemoveChild(players.Children[idx]); - playerNames = orderManager.LobbyInfo.Clients.Select(c => c.Name).ToList(); + tabCompletion.Names = orderManager.LobbyInfo.Clients.Select(c => c.Name).Distinct().ToList(); } void OnGameStart() @@ -779,35 +784,6 @@ namespace OpenRA.Mods.RA.Widgets.Logic onStart(); } - bool AutoCompleteText() - { - var chatText = lobby.Get("CHAT_TEXTFIELD"); - if (chatText == null || string.IsNullOrEmpty(chatText.Text)) - return false; - - if (chatText.Text.LastOrDefault() == ' ') - return false; - - var suggestion = ""; - var oneWord = !chatText.Text.Contains(' '); - var toComplete = oneWord - ? chatText.Text - : chatText.Text.Substring(chatText.Text.LastIndexOf(' ') + 1); - - suggestion = playerNames.FirstOrDefault(x => x.StartsWith(toComplete, StringComparison.InvariantCultureIgnoreCase)); - if (suggestion == null) - return false; - - if (oneWord) - suggestion += ": "; - else - suggestion = chatText.Text.Substring(0, chatText.Text.Length - toComplete.Length) + suggestion; - - chatText.Text = suggestion; - chatText.CursorPosition = chatText.Text.Length; - return true; - } - class DropDownOption { public string Title; diff --git a/OpenRA.Mods.RA/Widgets/Logic/TabCompletionLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/TabCompletionLogic.cs new file mode 100644 index 0000000000..8dfbaf2218 --- /dev/null +++ b/OpenRA.Mods.RA/Widgets/Logic/TabCompletionLogic.cs @@ -0,0 +1,77 @@ +#region Copyright & License Information +/* + * Copyright 2007-2014 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace OpenRA.Mods.RA.Widgets.Logic +{ + class TabCompletionLogic + { + IList candidates = new List(); + int currentCandidateIndex = 0; + string lastCompleted; + string prefix; + string suffix; + + public IList Commands { get; set; } + + public IList Names { get; set; } + + public string Complete(string text) + { + if (string.IsNullOrWhiteSpace(text)) + return text; + + if (lastCompleted == text) + { + lastCompleted = prefix + candidates[++currentCandidateIndex % candidates.Count] + suffix; + return lastCompleted; + } + + var toComplete = ""; + if (text.StartsWith("/") && Commands != null) + { + prefix = "/"; + suffix = ""; + toComplete = text.Substring(1); + candidates = Commands.Where(x => x.StartsWith(toComplete, StringComparison.InvariantCultureIgnoreCase)).ToList(); + } + else if (Names != null) + { + var oneWord = text.Contains(' '); + if (oneWord) + { + prefix = text.Substring(0, text.LastIndexOf(' ') + 1); + suffix = ""; + toComplete = text.Substring(prefix.Length); + } + else + { + prefix = ""; + suffix = ": "; + toComplete = text; + } + candidates = Names.Where(x => x.StartsWith(toComplete, StringComparison.InvariantCultureIgnoreCase)).ToList(); + } + else + return text; + + currentCandidateIndex = 0; + + if (candidates.Count == 0) + return text; + + lastCompleted = prefix + candidates[currentCandidateIndex] + suffix; + return lastCompleted; + } + } +}