From 034625c2eadaa1cc2c75322202a60865a529747f Mon Sep 17 00:00:00 2001 From: Alexander Fast Date: Tue, 19 Aug 2014 17:10:04 +0200 Subject: [PATCH 1/9] Added tab completion to in-game chat. Both player names and chat commands can be completed. Names of local players and bots are not candidates for completion. If a completed name is the first word ": " is appended to the end. The hotkey for toggling team/all chat has been moved to left Alt. --- OpenRA.Game/Widgets/TextFieldWidget.cs | 4 ++ .../Widgets/Logic/IngameChatLogic.cs | 55 ++++++++++++++++++- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/OpenRA.Game/Widgets/TextFieldWidget.cs b/OpenRA.Game/Widgets/TextFieldWidget.cs index 2f955a2ef9..90ad72861d 100644 --- a/OpenRA.Game/Widgets/TextFieldWidget.cs +++ b/OpenRA.Game/Widgets/TextFieldWidget.cs @@ -30,6 +30,7 @@ namespace OpenRA.Widgets public Func OnEnterKey = () => false; public Func OnTabKey = () => false; public Func OnEscKey = () => false; + public Func OnAltKey = () => false; public Action OnLoseFocus = () => { }; public Action OnTextEdited = () => { }; public int CursorPosition { get; set; } @@ -121,6 +122,9 @@ namespace OpenRA.Widgets if (e.Key == Keycode.ESCAPE && OnEscKey()) return true; + if (e.Key == Keycode.LALT && OnAltKey()) + return true; + if (e.Key == Keycode.LEFT) { if (CursorPosition > 0) diff --git a/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs index 76159a33ca..8f9c289b6c 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs @@ -8,6 +8,7 @@ */ #endregion +using System; using System.Collections.Generic; using System.Drawing; using System.Linq; @@ -30,6 +31,9 @@ namespace OpenRA.Mods.RA.Widgets.Logic readonly List chatTraits; + readonly List commandNames; + readonly List playerNames; + bool teamChat; [ObjectCreator.UseCtor] @@ -43,6 +47,9 @@ 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 = players.Select(x => x.PlayerName).ToList(); + var chatPanel = (ContainerWidget)widget; chatOverlay = chatPanel.Get("CHAT_OVERLAY"); chatOverlayDisplay = chatOverlay.Get("CHAT_DISPLAY"); @@ -57,7 +64,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic chatMode.IsDisabled = () => disableTeamChat; chatText = chatChrome.Get("CHAT_TEXTFIELD"); - chatText.OnTabKey = () => + chatText.OnAltKey = () => { if (!disableTeamChat) teamChat ^= true; @@ -76,6 +83,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic CloseChat(); return true; }; + chatText.OnTabKey = () => AutoCompleteText(); chatText.OnEscKey = () => { CloseChat(); return true; }; @@ -166,5 +174,48 @@ namespace OpenRA.Mods.RA.Widgets.Logic Sound.PlayNotification(modRules, null, "Sounds", "ChatLine", null); } + + bool AutoCompleteText() + { + if (string.IsNullOrEmpty(chatText.Text)) + return false; + + string suggestion; + + if (chatText.Text.StartsWith("/")) + { + suggestion = commandNames.FirstOrDefault(x => x.StartsWith(chatText.Text)); + if (suggestion == null) + return false; + } + else + { + string toComplete; + bool oneWord; + if (chatText.Text.Contains(' ')) + { + toComplete = chatText.Text.Substring(chatText.Text.LastIndexOf(' ') + 1); + oneWord = false; + } + else + { + toComplete = chatText.Text; + oneWord = true; + } + + 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; + } } -} \ No newline at end of file +} From ab61830d0f3525d37a402398b661369d3a6a2775 Mon Sep 17 00:00:00 2001 From: Alexander Fast Date: Tue, 19 Aug 2014 21:27:39 +0200 Subject: [PATCH 2/9] Code cleanup based on code review. Fixes multiple enumeration of IEnumerable. Fixes return value of pure method not used. Converted OnTabKey and OnClick handlers to method groups. Removed one set of redundant parenthesis. Replaced explicit type with var. --- .../Widgets/Logic/IngameChatLogic.cs | 28 +++++++------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs index 8f9c289b6c..334b3af385 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs @@ -43,7 +43,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic chatTraits = world.WorldActor.TraitsImplementing().ToList(); - var players = world.Players.Where(p => p != world.LocalPlayer && !p.NonCombatant && !p.IsBot); + var players = world.Players.Where(p => p != world.LocalPlayer && !p.NonCombatant && !p.IsBot).ToList(); var disableTeamChat = world.LocalPlayer == null || world.LobbyInfo.IsSinglePlayer || !players.Any(p => p.IsAlliedWith(world.LocalPlayer)); teamChat = !disableTeamChat; @@ -78,19 +78,19 @@ namespace OpenRA.Mods.RA.Widgets.Logic orderManager.IssueOrder(Order.Chat(team, chatText.Text.Trim())); else if (chatTraits != null) - chatTraits.All(x => x.OnChat(orderManager.LocalClient.Name, chatText.Text.Trim())); + chatTraits.ForEach(x => x.OnChat(orderManager.LocalClient.Name, chatText.Text.Trim())); CloseChat(); return true; }; - chatText.OnTabKey = () => AutoCompleteText(); + chatText.OnTabKey = AutoCompleteText; chatText.OnEscKey = () => { CloseChat(); return true; }; var chatClose = chatChrome.Get("CHAT_CLOSE"); - chatClose.OnClick += () => CloseChat(); + chatClose.OnClick += CloseChat; - chatPanel.OnKeyPress = (e) => + chatPanel.OnKeyPress = e => { if (e.Event == KeyInputEvent.Up) return false; @@ -180,7 +180,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic if (string.IsNullOrEmpty(chatText.Text)) return false; - string suggestion; + var suggestion = ""; if (chatText.Text.StartsWith("/")) { @@ -190,18 +190,10 @@ namespace OpenRA.Mods.RA.Widgets.Logic } else { - string toComplete; - bool oneWord; - if (chatText.Text.Contains(' ')) - { - toComplete = chatText.Text.Substring(chatText.Text.LastIndexOf(' ') + 1); - oneWord = false; - } - else - { - toComplete = chatText.Text; - oneWord = true; - } + var oneWord = chatText.Text.Contains(' '); + var toComplete = oneWord + ? chatText.Text.Substring(chatText.Text.LastIndexOf(' ') + 1) + : chatText.Text; suggestion = playerNames.FirstOrDefault(x => x.StartsWith(toComplete, StringComparison.InvariantCultureIgnoreCase)); if (suggestion == null) From 3160fa40f6478de57d0701738a6a9105c1a66425 Mon Sep 17 00:00:00 2001 From: Alexander Fast Date: Tue, 19 Aug 2014 22:48:51 +0200 Subject: [PATCH 3/9] Added tab completion to lobby chat. Also fixes accidental reversion of one word logic during cleanup. Fields that could be readonly are now readonly. --- .../Widgets/Logic/IngameChatLogic.cs | 6 +- OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs | 74 ++++++++++++++++--- 2 files changed, 67 insertions(+), 13 deletions(-) diff --git a/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs index 334b3af385..a2b195e780 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs @@ -190,10 +190,10 @@ namespace OpenRA.Mods.RA.Widgets.Logic } else { - var oneWord = chatText.Text.Contains(' '); + var oneWord = !chatText.Text.Contains(' '); var toComplete = oneWord - ? chatText.Text.Substring(chatText.Text.LastIndexOf(' ') + 1) - : chatText.Text; + ? chatText.Text + : chatText.Text.Substring(chatText.Text.LastIndexOf(' ') + 1); suggestion = playerNames.FirstOrDefault(x => x.StartsWith(toComplete, StringComparison.InvariantCultureIgnoreCase)); if (suggestion == null) diff --git a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs index 900c0a7049..4aa5882d42 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs @@ -35,18 +35,23 @@ namespace OpenRA.Mods.RA.Widgets.Logic enum PanelType { Players, Options, Kick, ForceStart } PanelType panel = PanelType.Players; - Widget lobby; + readonly Widget lobby; + readonly Widget editablePlayerTemplate; + readonly Widget nonEditablePlayerTemplate; + readonly Widget emptySlotTemplate; + readonly Widget editableSpectatorTemplate; + readonly Widget nonEditableSpectatorTemplate; + readonly Widget newSpectatorTemplate; - Widget editablePlayerTemplate, nonEditablePlayerTemplate, emptySlotTemplate, - editableSpectatorTemplate, nonEditableSpectatorTemplate, newSpectatorTemplate; + readonly ScrollPanelWidget chatPanel; + readonly Widget chatTemplate; - ScrollPanelWidget chatPanel; - Widget chatTemplate; + readonly ScrollPanelWidget players; + readonly Dictionary countryNames; - ScrollPanelWidget players; - Dictionary countryNames; + readonly ColorPreviewManagerWidget colorPreview; - ColorPreviewManagerWidget colorPreview; + List playerNames; // Listen for connection failures void ConnectionStateChanged(OrderManager om) @@ -498,13 +503,13 @@ namespace OpenRA.Mods.RA.Widgets.Logic chatTextField.Text = ""; return true; }; - - chatTextField.OnTabKey = () => + chatTextField.OnAltKey = () => { teamChat ^= true; chatLabel.Text = teamChat ? "Team:" : "Chat:"; return true; }; + chatTextField.OnTabKey = AutoCompleteText; chatPanel = lobby.Get("CHAT_DISPLAY"); chatTemplate = chatPanel.Get("CHAT_TEMPLATE"); @@ -764,6 +769,8 @@ namespace OpenRA.Mods.RA.Widgets.Logic while (players.Children.Count > idx) players.RemoveChild(players.Children[idx]); + + playerNames = GetPlayerNames().ToList(); } void OnGameStart() @@ -772,6 +779,53 @@ namespace OpenRA.Mods.RA.Widgets.Logic onStart(); } + IEnumerable GetPlayerNames() + { + foreach (var container in players.Children) + { + if (container.Id == "TEMPLATE_EDITABLE_PLAYER") + { + var textWidget = container.Children.FirstOrDefault(x => x.Id == "NAME") as TextFieldWidget; + if (textWidget == null) + continue; + yield return textWidget.Text; + } + else if (container.Id == "TEMPLATE_NONEDITABLE_PLAYER") + { + var labelWidget = container.Children.FirstOrDefault(x => x.Id == "NAME") as LabelWidget; + if (labelWidget == null) + continue; + yield return labelWidget.GetText(); + } + } + } + + bool AutoCompleteText() + { + var chatText = lobby.Get("CHAT_TEXTFIELD"); + if (chatText == null || string.IsNullOrEmpty(chatText.Text)) + 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; From e9d488fd9c6b12d6e8384d9e8dcba27aff553fb0 Mon Sep 17 00:00:00 2001 From: Alexander Fast Date: Wed, 20 Aug 2014 12:45:49 +0200 Subject: [PATCH 4/9] Update AUTHORS Added myself as requested by Mailaender. --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 7c6aa72cf8..01787fff5a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -19,6 +19,7 @@ Previous developers included: Also thanks to: * Adam Valy (Tschokky) * Akseli Virtanen (RAGEQUIT) + * Alexander Fast (mizipzor) * Allen262 * Andrew Aldridge (i80and) * Andrew Perkins From 0c6f180f56850b428cc5900b4b80cb03acdf0d53 Mon Sep 17 00:00:00 2001 From: Alexander Fast Date: Thu, 21 Aug 2014 12:39:53 +0200 Subject: [PATCH 5/9] Replaced ForEach with loop, moved Trim outside. --- OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs index a2b195e780..c7cddb380d 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs @@ -78,7 +78,11 @@ namespace OpenRA.Mods.RA.Widgets.Logic orderManager.IssueOrder(Order.Chat(team, chatText.Text.Trim())); else if (chatTraits != null) - chatTraits.ForEach(x => x.OnChat(orderManager.LocalClient.Name, chatText.Text.Trim())); + { + var text = chatText.Text.Trim(); + foreach (var trait in chatTraits) + trait.OnChat(orderManager.LocalClient.Name, text); + } CloseChat(); return true; From bab7f765dd6801cf5a9789919b0d67d60255aedc Mon Sep 17 00:00:00 2001 From: Alexander Fast Date: Thu, 21 Aug 2014 13:02:17 +0200 Subject: [PATCH 6/9] Replaced GetPlayerNames method with simpler linq. Bonus feature: it now auto complete bot names as well. --- OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs | 23 +--------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs index 4aa5882d42..25ee218c67 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs @@ -770,7 +770,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic while (players.Children.Count > idx) players.RemoveChild(players.Children[idx]); - playerNames = GetPlayerNames().ToList(); + playerNames = orderManager.LobbyInfo.Clients.Select(c => c.Name).ToList(); } void OnGameStart() @@ -779,27 +779,6 @@ namespace OpenRA.Mods.RA.Widgets.Logic onStart(); } - IEnumerable GetPlayerNames() - { - foreach (var container in players.Children) - { - if (container.Id == "TEMPLATE_EDITABLE_PLAYER") - { - var textWidget = container.Children.FirstOrDefault(x => x.Id == "NAME") as TextFieldWidget; - if (textWidget == null) - continue; - yield return textWidget.Text; - } - else if (container.Id == "TEMPLATE_NONEDITABLE_PLAYER") - { - var labelWidget = container.Children.FirstOrDefault(x => x.Id == "NAME") as LabelWidget; - if (labelWidget == null) - continue; - yield return labelWidget.GetText(); - } - } - } - bool AutoCompleteText() { var chatText = lobby.Get("CHAT_TEXTFIELD"); From a73898d44163c2c678a4a8ca9a04fcf28ac46ed1 Mon Sep 17 00:00:00 2001 From: Alexander Fast Date: Thu, 21 Aug 2014 13:05:04 +0200 Subject: [PATCH 7/9] Fixes double auto completion. Pressing tab twice would auto complete (the same name) twice. Auto completion is now disabled when last character in sentence is a space. --- OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs index 25ee218c67..4d444d5d49 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs @@ -785,6 +785,9 @@ namespace OpenRA.Mods.RA.Widgets.Logic 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 From e934df374d04c886cc1fc67e294003e9488e3a5a Mon Sep 17 00:00:00 2001 From: Alexander Fast Date: Thu, 21 Aug 2014 13:12:29 +0200 Subject: [PATCH 8/9] Fixes double auto completion in in-game chat. --- OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs index c7cddb380d..018b4ca69a 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs @@ -184,6 +184,9 @@ namespace OpenRA.Mods.RA.Widgets.Logic if (string.IsNullOrEmpty(chatText.Text)) return false; + if (chatText.Text.LastOrDefault() == ' ') + return false; + var suggestion = ""; if (chatText.Text.StartsWith("/")) From af0efda6f5061f2f954c13e68de850f841d8b444 Mon Sep 17 00:00:00 2001 From: Alexander Fast Date: Sat, 23 Aug 2014 21:18:54 +0200 Subject: [PATCH 9/9] Changes how the playerNames collection is created. It's now extracted from the orderManager, like it's done in LobbyLogic. This is to increase consistency. --- OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs index 018b4ca69a..f9ba129837 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs @@ -43,12 +43,12 @@ namespace OpenRA.Mods.RA.Widgets.Logic chatTraits = world.WorldActor.TraitsImplementing().ToList(); - var players = world.Players.Where(p => p != world.LocalPlayer && !p.NonCombatant && !p.IsBot).ToList(); + var players = world.Players.Where(p => p != world.LocalPlayer && !p.NonCombatant && !p.IsBot); 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 = players.Select(x => x.PlayerName).ToList(); + playerNames = orderManager.LobbyInfo.Clients.Select(c => c.Name).ToList(); var chatPanel = (ContainerWidget)widget; chatOverlay = chatPanel.Get("CHAT_OVERLAY");