diff --git a/OpenRA.Game/Widgets/ScrollPanelWidget.cs b/OpenRA.Game/Widgets/ScrollPanelWidget.cs index b3dbd1d74c..e4308bf4e5 100644 --- a/OpenRA.Game/Widgets/ScrollPanelWidget.cs +++ b/OpenRA.Game/Widgets/ScrollPanelWidget.cs @@ -46,7 +46,6 @@ namespace OpenRA.Widgets base.RemoveChildren(); } - public override void AddChild(Widget child) { // Initial setup of margins/height diff --git a/OpenRA.Game/Widgets/TextFieldWidget.cs b/OpenRA.Game/Widgets/TextFieldWidget.cs index a4cfe4086f..cb386340a3 100644 --- a/OpenRA.Game/Widgets/TextFieldWidget.cs +++ b/OpenRA.Game/Widgets/TextFieldWidget.cs @@ -33,7 +33,7 @@ namespace OpenRA.Widgets public Func OnTabKey = () => false; public Func OnEscKey = () => false; public Action OnLoseFocus = () => { }; - public int CursorPosition { get; protected set; } + public int CursorPosition { get; set; } public Func IsDisabled = () => false; public Color TextColor = Color.White; diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index 804b7179f2..eb31b519d4 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -380,6 +380,7 @@ + @@ -482,6 +483,10 @@ OpenRA.Game False + + {85b48234-8b31-4be6-af9c-665cc6866841} + OpenRA.Irc + {F33337BE-CB69-4B24-850F-07D23E408DDF} OpenRA.Utility diff --git a/OpenRA.Mods.RA/Widgets/Logic/IrcLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/IrcLogic.cs new file mode 100644 index 0000000000..86f7a0e0da --- /dev/null +++ b/OpenRA.Mods.RA/Widgets/Logic/IrcLogic.cs @@ -0,0 +1,252 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 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; +using OpenRA.Irc; +using OpenRA.Widgets; + +namespace OpenRA.Mods.RA.Widgets.Logic +{ + class IrcLogic + { + TextFieldWidget inputBox; + TextFieldWidget nicknameBox; + Widget connectBG; + Widget ircContainer; + + [ObjectCreator.UseCtor] + public IrcLogic(Widget widget) + { + var historyPanel = widget.Get("HISTORY_PANEL"); + var historyTemplate = widget.Get("HISTORY_TEMPLATE"); + var nicknamePanel = widget.Get("NICKNAME_PANEL"); + var nicknameTemplate = widget.Get("NICKNAME_TEMPLATE"); + + inputBox = widget.Get("INPUT_BOX"); + inputBox.OnEnterKey = EnterPressed; + inputBox.OnTabKey = TabPressed; + inputBox.IsDisabled = () => IrcClient.Instance.GetChannel(IrcClient.MainChannel) == null; + + nicknameBox = widget.Get("NICKNAME_BOX"); + nicknameBox.Text = ChooseNickname(Game.Settings.Irc.Nickname); + + connectBG = widget.Get("IRC_CONNECT_BG"); + ircContainer = widget.Get("IRC_CONTAINER"); + + widget.Get("DISCONNECT_BUTTON").OnClick = IrcClient.Instance.Disconnect; + + MaybeShowConnectPanel(); + + historyPanel.Bind(IrcClient.Instance.History, item => MakeLabelWidget(historyTemplate, item), LabelItemEquals, true); + + var mainChannel = IrcClient.Instance.GetChannel(IrcClient.MainChannel); + if (mainChannel != null) + nicknamePanel.Bind(mainChannel.Users, item => MakeLabelWidget(nicknameTemplate, item), LabelItemEquals, false); + + IrcClient.Instance.OnSync += l => + { + var channel = l.GetChannel(); + if (channel.Name.EqualsIC(IrcClient.MainChannel)) + nicknamePanel.Bind(channel.Users, item => MakeLabelWidget(nicknameTemplate, item), LabelItemEquals, false); + }; + IrcClient.Instance.OnKick += l => + { + if (l.KickeeNickname.EqualsIC(IrcClient.Instance.LocalUser.Nickname) && l.Target.EqualsIC(IrcClient.MainChannel)) + nicknamePanel.Unbind(); + }; + IrcClient.Instance.OnPart += l => + { + if (l.PrefixIsSelf() && l.Target.EqualsIC(IrcClient.MainChannel)) + nicknamePanel.Unbind(); + }; + IrcClient.Instance.OnDisconnect += () => + { + nicknamePanel.Unbind(); + MaybeShowConnectPanel(); + }; + + commands.Add("me", args => + { + IrcClient.Instance.Act(IrcClient.MainChannel, args); + IrcClient.AddAction(IrcClient.Instance.LocalUser.Nickname, args); + }); + commands.Add("slap", args => + { + IrcClient.Instance.Act(IrcClient.MainChannel, "slaps {0} around a bit with a large trout".F(args)); + IrcClient.AddAction(IrcClient.Instance.LocalUser.Nickname, "slaps {0} around a bit with a large trout".F(args)); + }); + commands.Add("notice", args => + { + var split = args.Split(new[] { ' ' }, 2); + if (split.Length < 2) + { + IrcClient.AddHistory("/notice: Not enough arguments"); + return; + } + IrcClient.Instance.Notice(split[0], split[1]); + IrcClient.AddSelfNotice(split[0], split[1]); + }); + commands.Add("disconnect", args => + { + Game.Settings.Irc.ConnectAutomatically = false; + Game.Settings.Save(); + IrcClient.Instance.Disconnect(); + }); + commands.Add("quit", args => + { + Game.Settings.Irc.ConnectAutomatically = false; + Game.Settings.Save(); + if (IrcClient.Instance.IsConnected) + IrcClient.Instance.Quit(args); + else + IrcClient.Instance.Disconnect(); + }); + commands.Add("nick", args => IrcClient.Instance.SetNickname(args)); + commands.Add("topic", args => IrcClient.Instance.GetTopic(IrcClient.MainChannel)); + } + + void MaybeShowConnectPanel() + { + if (IrcClient.Instance.IsConnected || IrcClient.Instance.IsReconnecting) + { + ircContainer.Visible = true; + connectBG.Visible = false; + return; + } + + if (Game.Settings.Irc.ConnectAutomatically) + { + ircContainer.Visible = true; + connectBG.Visible = false; + Connect(); + return; + } + + ircContainer.Visible = false; + connectBG.Visible = true; + + var connectAutomaticallyCheckBox = connectBG.Get("CONNECT_AUTOMATICALLY_CHECKBOX"); + var connectAutomaticallyChecked = false; + connectAutomaticallyCheckBox.IsChecked = () => connectAutomaticallyChecked; + connectAutomaticallyCheckBox.OnClick = () => connectAutomaticallyChecked ^= true; + + var connectButton = connectBG.Get("CONNECT_BUTTON"); + + connectButton.OnClick = () => + { + ircContainer.Visible = true; + connectBG.Visible = false; + + Game.Settings.Irc.ConnectAutomatically = connectAutomaticallyCheckBox.IsChecked(); + Game.Settings.Save(); + Connect(); + }; + } + + string ChooseNickname(string nickname) + { + if (!IrcUtils.IsNickname(nickname)) + { + nickname = Game.Settings.Player.Name; + if (!IrcUtils.IsNickname(nickname)) + nickname = Game.Settings.Irc.DefaultNickname; + } + return nickname; + } + + void Connect() + { + var nickname = ChooseNickname(nicknameBox.Text); + var s = Game.Settings.Irc; + s.Nickname = nickname; + Game.Settings.Save(); + IrcClient.Instance.Connect(s.Hostname, s.Port, s.ConnectionTimeout, nickname, s.Username ?? nickname, s.Realname ?? nickname); + } + + Widget MakeLabelWidget(LabelWidget template, object item) + { + var itemString = item.ToString(); + var widget = (LabelWidget)template.Clone(); + var font = Game.Renderer.Fonts[widget.Font]; + itemString = WidgetUtils.WrapText(itemString, widget.Bounds.Width, font); + widget.Bounds.Height = font.Measure(itemString).Y; + widget.GetText = () => itemString; + return widget; + } + + bool LabelItemEquals(Widget widget, object item) + { + return item != null && ((LabelWidget)widget).GetText() == item.ToString(); + } + + bool EnterPressed() + { + if (!inputBox.Text.Any()) + return true; + + var text = inputBox.Text; + inputBox.Text = ""; + + if (text[0] == '/') + { + var parts = text.Split(new[] { ' ' }, 2); + var name = parts[0].Substring(1); + var args = parts.Length > 1 ? parts[1] : null; + + Action command; + if (!commands.TryGetValue(name, out command)) + { + IrcClient.AddHistory("{0}: Unknown command".F(name)); + return true; + } + command(args); + } + else + { + IrcClient.Instance.Message(IrcClient.MainChannel, text); + IrcClient.AddMessage(IrcClient.Instance.LocalUser.Nickname, text); + } + return true; + } + + Dictionary> commands = new Dictionary>(StringComparer.OrdinalIgnoreCase); + + List tabMatches = new List(); + int tabMatchesIndex = -1; + + bool TabPressed() + { + if (!inputBox.Text.Any()) + return true; + + var channel = IrcClient.Instance.GetChannel(IrcClient.MainChannel); + + if (channel == null) + return true; + + var spaceIndex = inputBox.Text.TrimEnd().LastIndexOf(' '); + var tabMatchtext = inputBox.Text.Substring(spaceIndex + 1); + + if (tabMatchesIndex < 0 || !tabMatches.Any() || tabMatchtext != tabMatches[tabMatchesIndex]) + tabMatches = channel.Users.Keys.Where(u => u.StartsWith(tabMatchtext, StringComparison.OrdinalIgnoreCase)).ToList(); + + if (!tabMatches.Any()) + return true; + + tabMatchesIndex = (tabMatchesIndex + 1) % tabMatches.Count; + inputBox.Text = inputBox.Text.Remove(spaceIndex + 1) + tabMatches[tabMatchesIndex]; + inputBox.CursorPosition = inputBox.Text.Length; + + return true; + } + } +} diff --git a/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs index dfc43f105d..44d1b6118e 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs @@ -101,6 +101,8 @@ namespace OpenRA.Mods.RA.Widgets.Logic showIncompatibleCheckbox.OnClick = () => { showIncompatible ^= true; ServerList.Query(games => RefreshServerList(panel, games)); }; } + Game.LoadWidget(null, "SERVERBROWSER_IRC", panel.Get("IRC_ROOT"), new WidgetArgs()); + ServerList.Query(games => RefreshServerList(panel, games)); } diff --git a/mods/cnc/chrome/irc.yaml b/mods/cnc/chrome/irc.yaml new file mode 100644 index 0000000000..136f2ec52d --- /dev/null +++ b/mods/cnc/chrome/irc.yaml @@ -0,0 +1,68 @@ +Container@SERVERBROWSER_IRC: + Logic:IrcLogic + Width:700 + Height:250 + Children: + Container@IRC_CONTAINER: + Width:PARENT_RIGHT + Height:PARENT_BOTTOM + Children: + ScrollPanel@HISTORY_PANEL: + Width:565 + Height:PARENT_BOTTOM - 30 + ItemSpacing:5 + Label@HISTORY_TEMPLATE: + X:5 + Width:530 + Height:25 + WordWrap:True + TextField@INPUT_BOX: + Y:PARENT_BOTTOM - 25 + Width:565 + Height:25 + ScrollPanel@NICKNAME_PANEL: + X:570 + Width:130 + Height:PARENT_BOTTOM - 30 + Label@NICKNAME_TEMPLATE: + X:5 + Button@DISCONNECT_BUTTON: + X:570 + Y:PARENT_BOTTOM - 25 + Width:130 + Height:25 + Text:Disconnect + Font:Bold + Background@IRC_CONNECT_BG: + Width:PARENT_RIGHT + Height:PARENT_BOTTOM + Background:scrollpanel-bg + Children: + Label@GLOBAL_CHAT_LABEL: + Y:PARENT_BOTTOM / 4 + Width:PARENT_RIGHT + Align:Center + Text:Global Chat + Font:Bold + Label@NICKNAME_LABEL: + X:200 + Y:PARENT_BOTTOM / 4 + 35 + Text:Nickname: + TextField@NICKNAME_BOX: + X:270 + Y:PARENT_BOTTOM / 4 + 25 + Width:150 + Height:25 + Checkbox@CONNECT_AUTOMATICALLY_CHECKBOX: + X:270 + Y:PARENT_BOTTOM / 4 + 75 + Height:20 + Width:180 + Text:Connect Automatically + Button@CONNECT_BUTTON: + X:430 + Y:PARENT_BOTTOM / 4 + 25 + Width:100 + Height:25 + Text:Connect + Font:Bold \ No newline at end of file diff --git a/mods/cnc/chrome/serverbrowser.yaml b/mods/cnc/chrome/serverbrowser.yaml index 88655167e0..647212a2b2 100644 --- a/mods/cnc/chrome/serverbrowser.yaml +++ b/mods/cnc/chrome/serverbrowser.yaml @@ -1,27 +1,31 @@ Container@SERVERBROWSER_PANEL: Logic:ServerBrowserLogic X:(WINDOW_RIGHT - WIDTH)/2 - Y:(WINDOW_BOTTOM - 500)/2 - Width:540 - Height:535 + Y:(WINDOW_BOTTOM - HEIGHT)/2 + Width:730 + Height:645 Children: Label@TITLE: Text:Find Server - Width:540 - Y:0-25 + Width:740 + Y:0-10 Font:BigBold Contrast:true Align:Center Background@bg: - Width:540 - Height:500 + Width:730 + Height:600 Background:panel-black + Y:15 Children: + Container@IRC_ROOT: + X:15 + Y:15 ScrollPanel@SERVER_LIST: X:15 - Y:30 - Width:510 - Height:450 + Y:280 + Width:700 + Height:300 Children: ScrollItem@SERVER_TEMPLATE: Width:PARENT_RIGHT-27 @@ -76,7 +80,7 @@ Container@SERVERBROWSER_PANEL: Height:25 Label@PROGRESS_LABEL: X:(PARENT_RIGHT - WIDTH) / 2 - Y:PARENT_BOTTOM / 2 - HEIGHT + Y:PARENT_BOTTOM / 2 - HEIGHT + (280 / 2) Width:710 Height:25 Font:Bold @@ -85,20 +89,20 @@ Container@SERVERBROWSER_PANEL: Button@BACK_BUTTON: Key:escape X:0 - Y:499 + Y:614 Width:140 Height:35 Text:Back Button@REFRESH_BUTTON: - X:250 - Y:499 + X:PARENT_RIGHT - 140 - 10 - 140 + Y:614 Width:140 Height:35 Text:Refresh Button@JOIN_BUTTON: Key:return - X:400 - Y:499 + X:PARENT_RIGHT - 140 + Y:614 Width:140 Height:35 - Text:Join + Text:Join \ No newline at end of file diff --git a/mods/cnc/mod.yaml b/mods/cnc/mod.yaml index fff00e2b3f..a3976513fa 100644 --- a/mods/cnc/mod.yaml +++ b/mods/cnc/mod.yaml @@ -85,6 +85,7 @@ ChromeLayout: mods/cnc/chrome/dialogs.yaml mods/cnc/chrome/objectives.yaml mods/cnc/chrome/tooltips.yaml + mods/cnc/chrome/irc.yaml Weapons: mods/cnc/weapons.yaml diff --git a/mods/d2k/mod.yaml b/mods/d2k/mod.yaml index 7961437974..ac0b1ff9e7 100644 --- a/mods/d2k/mod.yaml +++ b/mods/d2k/mod.yaml @@ -72,6 +72,7 @@ ChromeLayout: mods/d2k/chrome/tooltips.yaml mods/d2k/chrome/assetbrowser.yaml mods/ra/chrome/convertassets.yaml + mods/ra/chrome/irc.yaml Weapons: mods/d2k/weapons.yaml diff --git a/mods/ra/chrome/irc.yaml b/mods/ra/chrome/irc.yaml new file mode 100644 index 0000000000..136f2ec52d --- /dev/null +++ b/mods/ra/chrome/irc.yaml @@ -0,0 +1,68 @@ +Container@SERVERBROWSER_IRC: + Logic:IrcLogic + Width:700 + Height:250 + Children: + Container@IRC_CONTAINER: + Width:PARENT_RIGHT + Height:PARENT_BOTTOM + Children: + ScrollPanel@HISTORY_PANEL: + Width:565 + Height:PARENT_BOTTOM - 30 + ItemSpacing:5 + Label@HISTORY_TEMPLATE: + X:5 + Width:530 + Height:25 + WordWrap:True + TextField@INPUT_BOX: + Y:PARENT_BOTTOM - 25 + Width:565 + Height:25 + ScrollPanel@NICKNAME_PANEL: + X:570 + Width:130 + Height:PARENT_BOTTOM - 30 + Label@NICKNAME_TEMPLATE: + X:5 + Button@DISCONNECT_BUTTON: + X:570 + Y:PARENT_BOTTOM - 25 + Width:130 + Height:25 + Text:Disconnect + Font:Bold + Background@IRC_CONNECT_BG: + Width:PARENT_RIGHT + Height:PARENT_BOTTOM + Background:scrollpanel-bg + Children: + Label@GLOBAL_CHAT_LABEL: + Y:PARENT_BOTTOM / 4 + Width:PARENT_RIGHT + Align:Center + Text:Global Chat + Font:Bold + Label@NICKNAME_LABEL: + X:200 + Y:PARENT_BOTTOM / 4 + 35 + Text:Nickname: + TextField@NICKNAME_BOX: + X:270 + Y:PARENT_BOTTOM / 4 + 25 + Width:150 + Height:25 + Checkbox@CONNECT_AUTOMATICALLY_CHECKBOX: + X:270 + Y:PARENT_BOTTOM / 4 + 75 + Height:20 + Width:180 + Text:Connect Automatically + Button@CONNECT_BUTTON: + X:430 + Y:PARENT_BOTTOM / 4 + 25 + Width:100 + Height:25 + Text:Connect + Font:Bold \ No newline at end of file diff --git a/mods/ra/chrome/serverbrowser.yaml b/mods/ra/chrome/serverbrowser.yaml index 76e114a86c..9347e24afc 100644 --- a/mods/ra/chrome/serverbrowser.yaml +++ b/mods/ra/chrome/serverbrowser.yaml @@ -2,8 +2,8 @@ Background@JOINSERVER_BG: Logic:ServerBrowserLogic X:(WINDOW_RIGHT - WIDTH)/2 Y:(WINDOW_BOTTOM - HEIGHT)/2 - Width:540 - Height:505 + Width:740 + Height:700 Children: Label@JOINSERVER_LABEL_TITLE: X:0 @@ -47,8 +47,8 @@ Background@JOINSERVER_BG: ScrollPanel@SERVER_LIST: X:20 Y:80 - Width:500 - Height:355 + Width:700 + Height:305 Children: ScrollItem@SERVER_TEMPLATE: Width:PARENT_RIGHT-27 @@ -103,34 +103,37 @@ Background@JOINSERVER_BG: Height:25 Label@PROGRESS_LABEL: X:(PARENT_RIGHT - WIDTH) / 2 - Y:PARENT_BOTTOM / 2 - HEIGHT + Y:505 / 2 - HEIGHT Width:150 Height:30 Text:Fetching games... Align:Center Button@REFRESH_BUTTON: X:20 - Y:PARENT_BOTTOM - 45 + Y:395 Width:100 Height:25 Text:Refresh Font:Bold Button@JOIN_BUTTON: - X:PARENT_RIGHT - 140 - 130 - Y:PARENT_BOTTOM - 45 + X:PARENT_RIGHT - 120 - 120 + Y:395 Width:100 Height:25 Text:Join Font:Bold Key:return Button@BACK_BUTTON: - X:PARENT_RIGHT - 140 - Y:PARENT_BOTTOM - 45 + X:PARENT_RIGHT - 120 + Y:395 Width:100 Height:25 Text:Cancel Font:Bold Key:escape + Container@IRC_ROOT: + X:20 + Y:430 Background@DIRECTCONNECT_BG: Logic:DirectConnectLogic X:(WINDOW_RIGHT - WIDTH)/2 diff --git a/mods/ra/mod.yaml b/mods/ra/mod.yaml index 7c7dd31244..00237cfaf8 100644 --- a/mods/ra/mod.yaml +++ b/mods/ra/mod.yaml @@ -85,6 +85,7 @@ ChromeLayout: mods/ra/chrome/tooltips.yaml mods/ra/chrome/assetbrowser.yaml mods/ra/chrome/convertassets.yaml + mods/ra/chrome/irc.yaml Weapons: mods/ra/weapons.yaml diff --git a/mods/ts/mod.yaml b/mods/ts/mod.yaml index 50605b71a6..b4c112630a 100644 --- a/mods/ts/mod.yaml +++ b/mods/ts/mod.yaml @@ -115,6 +115,7 @@ ChromeLayout: mods/ra/chrome/tooltips.yaml mods/ra/chrome/assetbrowser.yaml mods/ra/chrome/convertassets.yaml + mods/ra/chrome/irc.yaml Weapons: mods/ts/weapons.yaml