From 359cb2695a94f7b7847fde00e83b67b07e576900 Mon Sep 17 00:00:00 2001 From: Si Carter Date: Sat, 13 Jan 2024 07:33:30 +0100 Subject: [PATCH] Feature/custom tab controls (#58) --- src/.editorconfig | 3 + src/GCAAnalyser/Analyzers/AnalyzeM600Pause.cs | 2 +- .../Analyzers/AnalyzeM601Timeout.cs | 2 +- .../Analyzers/AnalyzeM605PlaySounds.cs | 2 +- .../Analyzers/AnalyzeM630RunProgram.cs | 2 +- .../Analyzers/AnalyzeM630_1RunParameters.cs | 2 +- .../Analyzers/AnalyzeM631_1RunParameters.cs | 2 +- .../Analyzers/AnalyzeM631_2ReturnCode.cs | 2 +- src/GCATests/Mocks/MockEditorPluginHost.cs | 5 + src/GCATests/Mocks/MockGSendContext.cs | 36 +++ src/GCATests/Mocks/MockSenderPluginHost.cs | 22 +- src/GCATests/Mocks/MockServiceProvider.cs | 6 + .../GSendProTuningWizardPluginTests.cs | 2 +- .../HeartbeatControlItemTests.cs | 48 +++ .../HeartbeatTests/HeartbeatsPluginTests.cs | 82 +++++ .../HelpMenuTests/BugsAndIdeasMenuTests.cs | 75 +++++ .../HelpMenuTests/HelpMenuItemTests.cs | 79 +++++ .../HelpMenuTests/HelpMenuPluginTests.cs} | 13 +- .../HelpMenuTests/HomePageMenuTests.cs | 76 +++++ .../Shared/Plugins/ServerMenuPluginTests.cs | 2 +- src/GCSDesktop/FormMain.cs | 24 +- src/GCSDesktop/Forms/FrmMachine.Designer.cs | 190 ++--------- src/GCSDesktop/Forms/FrmMachine.cs | 79 ++--- src/GCSDesktop/Forms/FrmMachine.resx | 298 +++++++++--------- src/GCSShared/Plugins/PluginEnums.cs | 11 +- .../Abstractions/IGSendPluginModule.cs | 10 +- .../Abstractions/IPluginControl.cs | 15 + src/GSendControls/Abstractions/IPluginHost.cs | 2 + .../Abstractions/IPluginItemBase.cs | 20 +- .../Abstractions/IPluginItemInteractive.cs | 10 + src/GSendControls/Abstractions/IPluginMenu.cs | 2 +- .../Abstractions/IPluginMessages.cs | 18 ++ .../Abstractions/IPluginToolbarButton.cs | 2 +- src/GSendControls/Controls/PluginControl.cs | 39 +++ src/GSendControls/Controls/PluginControl.resx | 120 +++++++ src/GSendControls/GlobalSuppressions.cs | 7 +- .../Hearbeats/HearbeatsPlugin.cs | 50 +++ .../Hearbeats/HeartbeatControlItem.cs | 61 ++++ .../Hearbeats/HeartbeatControls.Designer.cs | 171 ++++++++++ .../Hearbeats/HeartbeatControls.cs | 59 ++++ .../Hearbeats/HeartbeatControls.resx | 120 +++++++ .../HelpMenu/BugsAndIdeasMenu.cs | 14 +- .../InternalPlugins/HelpMenu/HelpMenuItem.cs | 15 +- .../HelpMenu/HelpMenuPlugin.cs | 22 +- .../InternalPlugins/HelpMenu/HomePageMenu.cs | 17 +- .../SearchMenu/SearchMenuPlugin.cs | 6 +- .../ServerMenu/ConfigureServerMenuItem.cs | 2 +- .../ServerMenu/ServerMenuPlugin.cs | 6 +- .../ServerMenu/ServerRootMenuItem.cs | 2 +- .../ServerMenu/ServerSelectMenuItem.cs | 2 +- src/GSendControls/Plugins/PluginHelper.cs | 25 +- src/GSendEditor/FrmMain.cs | 14 +- .../GrblTuning/GSendProTuningWizardPlugin.cs | 11 +- src/Plugins/GrblTuning/GlobalSuppressions.cs | 9 + wwwroot/Controllers/MCodesController.cs | 6 +- 55 files changed, 1461 insertions(+), 461 deletions(-) create mode 100644 src/GCATests/Mocks/MockGSendContext.cs create mode 100644 src/GCATests/Plugins/HeartbeatTests/HeartbeatControlItemTests.cs create mode 100644 src/GCATests/Plugins/HeartbeatTests/HeartbeatsPluginTests.cs create mode 100644 src/GCATests/Plugins/HelpMenuTests/BugsAndIdeasMenuTests.cs create mode 100644 src/GCATests/Plugins/HelpMenuTests/HelpMenuItemTests.cs rename src/GCATests/{Shared/Plugins/HelpMenuTests.cs => Plugins/HelpMenuTests/HelpMenuPluginTests.cs} (91%) create mode 100644 src/GCATests/Plugins/HelpMenuTests/HomePageMenuTests.cs create mode 100644 src/GSendControls/Abstractions/IPluginControl.cs create mode 100644 src/GSendControls/Abstractions/IPluginItemInteractive.cs create mode 100644 src/GSendControls/Abstractions/IPluginMessages.cs create mode 100644 src/GSendControls/Controls/PluginControl.cs create mode 100644 src/GSendControls/Controls/PluginControl.resx create mode 100644 src/GSendControls/Plugins/InternalPlugins/Hearbeats/HearbeatsPlugin.cs create mode 100644 src/GSendControls/Plugins/InternalPlugins/Hearbeats/HeartbeatControlItem.cs create mode 100644 src/GSendControls/Plugins/InternalPlugins/Hearbeats/HeartbeatControls.Designer.cs create mode 100644 src/GSendControls/Plugins/InternalPlugins/Hearbeats/HeartbeatControls.cs create mode 100644 src/GSendControls/Plugins/InternalPlugins/Hearbeats/HeartbeatControls.resx create mode 100644 src/Plugins/GrblTuning/GlobalSuppressions.cs diff --git a/src/.editorconfig b/src/.editorconfig index 11354e0f..3fafbafd 100644 --- a/src/.editorconfig +++ b/src/.editorconfig @@ -31,6 +31,9 @@ dotnet_diagnostic.IDE0305.severity = none dotnet_diagnostic.IDE0300.severity = none dotnet_diagnostic.IDE0290.severity = none +# S1168: Empty arrays and collections should be returned instead of null +dotnet_diagnostic.S1168.severity = none + [*.{cs,vb}] #### Naming styles #### diff --git a/src/GCAAnalyser/Analyzers/AnalyzeM600Pause.cs b/src/GCAAnalyser/Analyzers/AnalyzeM600Pause.cs index 0af95c4f..16d2b9cf 100644 --- a/src/GCAAnalyser/Analyzers/AnalyzeM600Pause.cs +++ b/src/GCAAnalyser/Analyzers/AnalyzeM600Pause.cs @@ -14,7 +14,7 @@ public void Analyze(string fileName, IGCodeAnalyses gCodeAnalyses) if (m600Commands.Count == 0) return; - if (m600Commands.Count > 0 && gCodeAnalyses is GCodeAnalyses codeAnalyses) + if (gCodeAnalyses is GCodeAnalyses codeAnalyses) { foreach (IGCodeCommand command in m600Commands) { diff --git a/src/GCAAnalyser/Analyzers/AnalyzeM601Timeout.cs b/src/GCAAnalyser/Analyzers/AnalyzeM601Timeout.cs index c6058c65..10e533b5 100644 --- a/src/GCAAnalyser/Analyzers/AnalyzeM601Timeout.cs +++ b/src/GCAAnalyser/Analyzers/AnalyzeM601Timeout.cs @@ -14,7 +14,7 @@ public void Analyze(string fileName, IGCodeAnalyses gCodeAnalyses) if (m601Commands.Count == 0) return; - if (m601Commands.Count > 0 && gCodeAnalyses is GCodeAnalyses codeAnalyses) + if (gCodeAnalyses is GCodeAnalyses codeAnalyses) { foreach (IGCodeCommand command in m601Commands) { diff --git a/src/GCAAnalyser/Analyzers/AnalyzeM605PlaySounds.cs b/src/GCAAnalyser/Analyzers/AnalyzeM605PlaySounds.cs index 9485fe46..7519fe6d 100644 --- a/src/GCAAnalyser/Analyzers/AnalyzeM605PlaySounds.cs +++ b/src/GCAAnalyser/Analyzers/AnalyzeM605PlaySounds.cs @@ -14,7 +14,7 @@ public void Analyze(string fileName, IGCodeAnalyses gCodeAnalyses) if (m605Commands.Count == 0) return; - if (m605Commands.Count > 0 && gCodeAnalyses is GCodeAnalyses codeAnalyses) + if (gCodeAnalyses is GCodeAnalyses codeAnalyses) { if (!gCodeAnalyses.AnalysesOptions.HasFlag(AnalysesOptions.PlaySound)) codeAnalyses.AddOptions(AnalysesOptions.PlaySound); diff --git a/src/GCAAnalyser/Analyzers/AnalyzeM630RunProgram.cs b/src/GCAAnalyser/Analyzers/AnalyzeM630RunProgram.cs index 189c61f4..17d0c5d9 100644 --- a/src/GCAAnalyser/Analyzers/AnalyzeM630RunProgram.cs +++ b/src/GCAAnalyser/Analyzers/AnalyzeM630RunProgram.cs @@ -14,7 +14,7 @@ public void Analyze(string fileName, IGCodeAnalyses gCodeAnalyses) if (m630Commands.Count == 0) return; - if (m630Commands.Count > 0 && gCodeAnalyses is GCodeAnalyses codeAnalyses) + if (gCodeAnalyses is GCodeAnalyses codeAnalyses) { if (!gCodeAnalyses.AnalysesOptions.HasFlag(AnalysesOptions.RunProgram)) codeAnalyses.AddOptions(AnalysesOptions.RunProgram); diff --git a/src/GCAAnalyser/Analyzers/AnalyzeM630_1RunParameters.cs b/src/GCAAnalyser/Analyzers/AnalyzeM630_1RunParameters.cs index db2eacd3..e9c83a37 100644 --- a/src/GCAAnalyser/Analyzers/AnalyzeM630_1RunParameters.cs +++ b/src/GCAAnalyser/Analyzers/AnalyzeM630_1RunParameters.cs @@ -14,7 +14,7 @@ public void Analyze(string fileName, IGCodeAnalyses gCodeAnalyses) if (mCommands.Count == 0) return; - if (mCommands.Count > 0 && gCodeAnalyses is GCodeAnalyses codeAnalyses) + if (gCodeAnalyses is GCodeAnalyses codeAnalyses) { List lineNumbers = []; diff --git a/src/GCAAnalyser/Analyzers/AnalyzeM631_1RunParameters.cs b/src/GCAAnalyser/Analyzers/AnalyzeM631_1RunParameters.cs index 33661a0f..1a8efb23 100644 --- a/src/GCAAnalyser/Analyzers/AnalyzeM631_1RunParameters.cs +++ b/src/GCAAnalyser/Analyzers/AnalyzeM631_1RunParameters.cs @@ -14,7 +14,7 @@ public void Analyze(string fileName, IGCodeAnalyses gCodeAnalyses) if (mCommands.Count == 0) return; - if (mCommands.Count > 0 && gCodeAnalyses is GCodeAnalyses codeAnalyses) + if (gCodeAnalyses is GCodeAnalyses codeAnalyses) { List lineNumbers = []; diff --git a/src/GCAAnalyser/Analyzers/AnalyzeM631_2ReturnCode.cs b/src/GCAAnalyser/Analyzers/AnalyzeM631_2ReturnCode.cs index 78f93452..d2b6a7c5 100644 --- a/src/GCAAnalyser/Analyzers/AnalyzeM631_2ReturnCode.cs +++ b/src/GCAAnalyser/Analyzers/AnalyzeM631_2ReturnCode.cs @@ -14,7 +14,7 @@ public void Analyze(string fileName, IGCodeAnalyses gCodeAnalyses) if (mCommands.Count == 0) return; - if (mCommands.Count > 0 && gCodeAnalyses is GCodeAnalyses codeAnalyses) + if (gCodeAnalyses is GCodeAnalyses codeAnalyses) { foreach (IGCodeCommand command in mCommands) { diff --git a/src/GCATests/Mocks/MockEditorPluginHost.cs b/src/GCATests/Mocks/MockEditorPluginHost.cs index 32cf9b3e..c04ea18c 100644 --- a/src/GCATests/Mocks/MockEditorPluginHost.cs +++ b/src/GCATests/Mocks/MockEditorPluginHost.cs @@ -32,6 +32,11 @@ public MockEditorPluginHost() public IGSendContext GSendContext => throw new NotImplementedException(); + public void AddControl(IPluginControl pluginControl) + { + throw new NotImplementedException(); + } + public void AddMenu(IPluginMenu pluginMenu) { throw new NotImplementedException(); diff --git a/src/GCATests/Mocks/MockGSendContext.cs b/src/GCATests/Mocks/MockGSendContext.cs new file mode 100644 index 00000000..6bcc8871 --- /dev/null +++ b/src/GCATests/Mocks/MockGSendContext.cs @@ -0,0 +1,36 @@ +using System; + +using GSendShared; +using GSendShared.Abstractions; + +namespace GSendTests.Mocks +{ + internal class MockGSendContext : IGSendContext + { + public MockGSendContext() + { + } + + public MockGSendContext(IServiceProvider serviceProvider) + : this() + { + ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + } + + public IServiceProvider ServiceProvider { get; set; } + + public bool IsClosing => throw new NotImplementedException(); + + public IGSendSettings Settings => throw new NotImplementedException(); + + public void CloseContext() + { + throw new NotImplementedException(); + } + + public void ShowMachine(IMachine machine) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/GCATests/Mocks/MockSenderPluginHost.cs b/src/GCATests/Mocks/MockSenderPluginHost.cs index 99bec1c8..d563d8f5 100644 --- a/src/GCATests/Mocks/MockSenderPluginHost.cs +++ b/src/GCATests/Mocks/MockSenderPluginHost.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; + using GSendControls.Abstractions; + using GSendShared; using GSendShared.Models; using GSendShared.Plugins; @@ -16,11 +18,22 @@ public MockSenderPluginHost() } + public MockSenderPluginHost(IGSendContext gSendContext) + { + GSendContext = gSendContext ?? throw new ArgumentNullException(nameof(gSendContext)); + } + public MockSenderPluginHost(IPluginMenu parentMenu) { _parent = parentMenu; } + public MockSenderPluginHost(IPluginMenu parentMenu, IGSendContext gSendContext) + : this(gSendContext) + { + _parent = parentMenu; + } + public IMachine Machine { get; set; } public List Messages { get; } = new(); @@ -39,7 +52,7 @@ IPluginMenu IPluginHost.GetMenu(MenuParent menuParent) public void AddMenu(IPluginMenu pluginMenu) { - + } public void AddMessage(InformationType informationType, string message) @@ -84,6 +97,11 @@ public void SendMessage(string message) Messages.Add(message); } - public IGSendContext GSendContext => throw new NotImplementedException(); + public void AddControl(IPluginControl pluginControl) + { + throw new NotImplementedException(); + } + + public IGSendContext GSendContext { get; set; } } } diff --git a/src/GCATests/Mocks/MockServiceProvider.cs b/src/GCATests/Mocks/MockServiceProvider.cs index 88675303..0206bfc5 100644 --- a/src/GCATests/Mocks/MockServiceProvider.cs +++ b/src/GCATests/Mocks/MockServiceProvider.cs @@ -7,6 +7,8 @@ using PluginManager.Abstractions; +using Shared.Classes; + namespace GSendTests.Mocks { internal class MockServiceProvider : IServiceProvider @@ -27,6 +29,10 @@ public object GetService(Type serviceType) { return new MockPluginClassesService(); } + else if (serviceType.Equals(typeof(IRunProgram))) + { + return new MockRunProgram(); + } } return null; diff --git a/src/GCATests/Plugins/GrblTuningWizardTests/GSendProTuningWizardPluginTests.cs b/src/GCATests/Plugins/GrblTuningWizardTests/GSendProTuningWizardPluginTests.cs index 72d8593d..c8d25503 100644 --- a/src/GCATests/Plugins/GrblTuningWizardTests/GSendProTuningWizardPluginTests.cs +++ b/src/GCATests/Plugins/GrblTuningWizardTests/GSendProTuningWizardPluginTests.cs @@ -28,7 +28,7 @@ public void Create_ValidInstance_Success() Assert.AreEqual("GRBL Tuning Wizard", sut.Name); Assert.AreEqual(1u, sut.Version); Assert.AreEqual(PluginHosts.Sender, sut.Host); - Assert.AreEqual(PluginOptions.HasMenuItems | PluginOptions.MessageReceived, sut.Options); + Assert.AreEqual(PluginOptions.HasMenuItems, sut.Options); } [TestMethod] diff --git a/src/GCATests/Plugins/HeartbeatTests/HeartbeatControlItemTests.cs b/src/GCATests/Plugins/HeartbeatTests/HeartbeatControlItemTests.cs new file mode 100644 index 00000000..f1795dd7 --- /dev/null +++ b/src/GCATests/Plugins/HeartbeatTests/HeartbeatControlItemTests.cs @@ -0,0 +1,48 @@ +using System.Diagnostics.CodeAnalysis; + +using GSendControls.Abstractions; +using GSendControls.Plugins.InternalPlugins.Hearbeats; + +using GSendShared.Plugins; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace GSendTests.Plugins.HeartbeatTests +{ + [TestClass] + [ExcludeFromCodeCoverage] + public sealed class HeartbeatControlItemTests + { + [TestMethod] + public void Construct_ValidInstance_Success() + { + HeartbeatControlItem sut = new(); + Assert.IsNotNull(sut); + Assert.IsInstanceOfType(sut); + Assert.AreEqual("Graphs", sut.Name); + Assert.IsNotNull(sut.Control); + Assert.AreEqual(ControlLocation.Secondary, sut.Location); + Assert.AreEqual("Heartbeat Graphs", sut.Text); + Assert.AreEqual(20, sut.Index); + Assert.IsTrue(sut.ReceiveClientMessages); + Assert.IsTrue(sut.IsEnabled()); + } + + [TestMethod] + public void ClientMessageReceived_DoesNotThrowException() + { + HeartbeatControlItem sut = new(); + bool exceptionRaised = false; + try + { + sut.UpdateHost(null); + } + catch + { + exceptionRaised = true; + } + + Assert.IsFalse(exceptionRaised); + } + } +} diff --git a/src/GCATests/Plugins/HeartbeatTests/HeartbeatsPluginTests.cs b/src/GCATests/Plugins/HeartbeatTests/HeartbeatsPluginTests.cs new file mode 100644 index 00000000..19983bb9 --- /dev/null +++ b/src/GCATests/Plugins/HeartbeatTests/HeartbeatsPluginTests.cs @@ -0,0 +1,82 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +using GSendControls.Abstractions; +using GSendControls.Plugins.InternalPlugins.Hearbeats; + +using GSendShared.Plugins; + +using GSendTests.Mocks; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace GSendTests.Plugins.HeartbeatTests +{ + [TestClass] + [ExcludeFromCodeCoverage] + public sealed class HeartbeatsPluginTests + { + [TestMethod] + public void Construct_ValidInstance_Success() + { + HearbeatsPlugin sut = new(); + Assert.IsNotNull(sut); + Assert.IsInstanceOfType(sut); + Assert.AreEqual("Heartbeats", sut.Name); + Assert.AreEqual(1u, sut.Version); + Assert.AreEqual(PluginHosts.Sender, sut.Host); + Assert.AreEqual(PluginOptions.HasControls, sut.Options); + Assert.IsNull(sut.MenuItems); + Assert.IsNull(sut.ToolbarItems); + Assert.IsFalse(sut.ReceiveClientMessages); + } + + [TestMethod] + public void Validate_ControlItems_Success() + { + IPluginMenu parentMenu = new MockPluginMenu("test", MenuType.MenuItem, null); + MockSenderPluginHost pluginHost = new(parentMenu); + + HearbeatsPlugin sut = new(); + sut.Initialize(pluginHost); + IReadOnlyList controlItems = sut.ControlItems; + + Assert.AreEqual(1, controlItems.Count); + Assert.AreEqual("Heartbeat Graphs", controlItems[0].Text); + } + + [TestMethod] + public void ClientMessageReceived_DoesNotThrowException() + { + HearbeatsPlugin sut = new(); + bool exceptionRaised = false; + try + { + sut.ClientMessageReceived(null); + } + catch + { + exceptionRaised = true; + } + + Assert.IsFalse(exceptionRaised); + } + + [TestMethod] + public void Initialize_DoesNotThrowException() + { + HearbeatsPlugin sut = new(); + bool exceptionRaised = false; + try + { + sut.Initialize(null); + } + catch + { + exceptionRaised = true; + } + + Assert.IsFalse(exceptionRaised); + } + } +} diff --git a/src/GCATests/Plugins/HelpMenuTests/BugsAndIdeasMenuTests.cs b/src/GCATests/Plugins/HelpMenuTests/BugsAndIdeasMenuTests.cs new file mode 100644 index 00000000..9afe8a21 --- /dev/null +++ b/src/GCATests/Plugins/HelpMenuTests/BugsAndIdeasMenuTests.cs @@ -0,0 +1,75 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +using GSendControls.Plugins.InternalPlugins.HelpMenu; + +using GSendShared.Plugins; + +using GSendTests.Mocks; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace GSendTests.Plugins.HelpMenuTests +{ + [TestClass] + [ExcludeFromCodeCoverage] + public sealed class BugsAndIdeasMenuTests + { + [TestMethod] + public void Construct_BugsAndIdeasMenuItem_Success() + { + MockPluginMenu parentMenu = new MockPluginMenu("test", MenuType.MenuItem, null); + BugsAndIdeasMenu sut = new(parentMenu, new MockRunProgram()); + Assert.IsNotNull(sut); + Assert.IsNull(sut.MenuImage); + Assert.AreEqual(MenuType.MenuItem, sut.MenuType); + Assert.IsNotNull(sut.ParentMenu); + Assert.AreSame(parentMenu, sut.ParentMenu); + Assert.AreEqual("Bugs and Ideas", sut.Text); + Assert.AreEqual(2, sut.Index); + Assert.IsFalse(sut.ReceiveClientMessages); + Assert.IsFalse(sut.GetShortcut([], out string grpName, out string shrtCutName)); + Assert.IsNull(grpName); + Assert.IsNull(shrtCutName); + Assert.IsFalse(sut.IsChecked()); + Assert.IsTrue(sut.IsEnabled()); + Assert.IsTrue(sut.IsVisible()); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void Construct_InvalidParam_ParentNull_Throws_ArgumentNullException() + { + new BugsAndIdeasMenu(null, new MockRunProgram()); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void Construct_InvalidParam_RunProgramNull_Throws_ArgumentNullException() + { + new BugsAndIdeasMenu(new MockPluginMenu("test", MenuType.MenuItem, null), null); + } + + [TestMethod] + public void UpdateHost_DoesNotCrash_Success() + { + HelpMenuItem sut = new(new MockPluginMenu("test", MenuType.MenuItem, null), new MockRunProgram()); + Assert.IsNotNull(sut); + sut.UpdateHost(null); + } + + [TestMethod] + public void Clicked_RunsHomePageUsingShellExecute_Success() + { + MockRunProgram mockRunProgram = new(); + MockPluginMenu parentMenu = new MockPluginMenu("test", MenuType.MenuItem, null); + BugsAndIdeasMenu sut = new(parentMenu, mockRunProgram); + sut.Clicked(); + Assert.AreEqual("https://github.com/k3ldar/GSendPro/issues", mockRunProgram.ProgramName); + Assert.IsNull(mockRunProgram.Parameters); + Assert.IsTrue(mockRunProgram.UseShellExecute); + Assert.IsFalse(mockRunProgram.WaitForFinish); + Assert.AreEqual(500, mockRunProgram.TimeoutMilliseconds); + } + } +} diff --git a/src/GCATests/Plugins/HelpMenuTests/HelpMenuItemTests.cs b/src/GCATests/Plugins/HelpMenuTests/HelpMenuItemTests.cs new file mode 100644 index 00000000..7eba91bd --- /dev/null +++ b/src/GCATests/Plugins/HelpMenuTests/HelpMenuItemTests.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using GSendControls.Plugins.InternalPlugins.HelpMenu; + +using GSendShared.Plugins; + +using GSendTests.Mocks; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace GSendTests.Plugins.HelpMenuTests +{ + [TestClass] + [ExcludeFromCodeCoverage] + public sealed class HelpMenuItemTests + { + [TestMethod] + public void Construct_HelpMenuItem_Success() + { + MockPluginMenu parentMenu = new MockPluginMenu("test", MenuType.MenuItem, null); + HelpMenuItem sut = new(parentMenu, new MockRunProgram()); + Assert.IsNotNull(sut); + Assert.IsNull(sut.MenuImage); + Assert.AreEqual(MenuType.MenuItem, sut.MenuType); + Assert.IsNotNull(sut.ParentMenu); + Assert.AreSame(parentMenu, sut.ParentMenu); + Assert.AreEqual("Help", sut.Text); + Assert.AreEqual(0, sut.Index); + Assert.IsFalse(sut.ReceiveClientMessages); + Assert.IsFalse(sut.GetShortcut([], out string grpName, out string shrtCutName)); + Assert.IsNull(grpName); + Assert.IsNull(shrtCutName); + Assert.IsFalse(sut.IsChecked()); + Assert.IsTrue(sut.IsEnabled()); + Assert.IsTrue(sut.IsVisible()); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void Construct_InvalidParam_ParentNull_Throws_ArgumentNullException() + { + new HelpMenuItem(null, new MockRunProgram()); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void Construct_InvalidParam_RunProgramNull_Throws_ArgumentNullException() + { + new HelpMenuItem(new MockPluginMenu("test", MenuType.MenuItem, null), null); + } + + [TestMethod] + public void UpdateHost_DoesNotCrash_Success() + { + HelpMenuItem sut = new(new MockPluginMenu("test", MenuType.MenuItem, null), new MockRunProgram()); + Assert.IsNotNull(sut); + sut.UpdateHost(null); + } + + [TestMethod] + public void Clicked_RunsHomePageUsingShellExecute_Success() + { + MockRunProgram mockRunProgram = new(); + MockPluginMenu parentMenu = new MockPluginMenu("test", MenuType.MenuItem, null); + HelpMenuItem sut = new(parentMenu, mockRunProgram); + sut.Clicked(); + Assert.AreEqual("https://www.gsend.pro/help", mockRunProgram.ProgramName); + Assert.IsNull(mockRunProgram.Parameters); + Assert.IsTrue(mockRunProgram.UseShellExecute); + Assert.IsFalse(mockRunProgram.WaitForFinish); + Assert.AreEqual(500, mockRunProgram.TimeoutMilliseconds); + } + } +} diff --git a/src/GCATests/Shared/Plugins/HelpMenuTests.cs b/src/GCATests/Plugins/HelpMenuTests/HelpMenuPluginTests.cs similarity index 91% rename from src/GCATests/Shared/Plugins/HelpMenuTests.cs rename to src/GCATests/Plugins/HelpMenuTests/HelpMenuPluginTests.cs index d91c582a..037c76ff 100644 --- a/src/GCATests/Shared/Plugins/HelpMenuTests.cs +++ b/src/GCATests/Plugins/HelpMenuTests/HelpMenuPluginTests.cs @@ -6,12 +6,10 @@ using GSendControls.Plugins.InternalPlugins.HelpMenu; using GSendShared.Plugins; - using GSendTests.Mocks; - using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace GSendTests.Shared.Plugins +namespace GSendTests.Plugins.HelpMenuTests { [TestClass] [ExcludeFromCodeCoverage] @@ -21,7 +19,8 @@ public class HelpMenuPluginTests public void HelpMenuPlugin_ConstructValidInstance_Success() { IPluginMenu parentMenu = new MockPluginMenu("test", MenuType.MenuItem, null); - MockSenderPluginHost pluginHost = new(parentMenu); + MockGSendContext gSendContext = new(new MockServiceProvider()); + MockSenderPluginHost pluginHost = new(parentMenu, gSendContext); HelpMenuPlugin sut = new(); sut.Initialize(pluginHost); @@ -43,7 +42,7 @@ public void HelpMenuPlugin_ConstructValidInstance_Success() public void HelpMenuItem_ConstructValidInstance_Success() { IPluginMenu parent = new MockPluginMenu("help", MenuType.MenuItem, null); - HelpMenuItem sut = new(parent); + HelpMenuItem sut = new(parent, new MockRunProgram()); Assert.IsNotNull(sut); Assert.AreEqual("Help", sut.Text); @@ -82,7 +81,7 @@ public void SeperatorMenuItem_ConstructValidInstance_Success() public void BugsAndIdeasMenuItem_ConstructValidInstance_Success() { IPluginMenu parent = new MockPluginMenu("help", MenuType.MenuItem, null); - BugsAndIdeasMenu sut = new(parent); + BugsAndIdeasMenu sut = new(parent, new MockRunProgram()); Assert.IsNotNull(sut); Assert.AreEqual("Bugs and Ideas", sut.Text); Assert.AreEqual(2, sut.Index); @@ -101,7 +100,7 @@ public void BugsAndIdeasMenuItem_ConstructValidInstance_Success() public void HomePageMenuItem_ConstructValidInstance_Success() { IPluginMenu parent = new MockPluginMenu("help", MenuType.MenuItem, null); - HomePageMenu sut = new(parent); + HomePageMenu sut = new(parent, new MockRunProgram()); Assert.IsNotNull(sut); Assert.AreEqual("Home Page", sut.Text); Assert.AreEqual(4, sut.Index); diff --git a/src/GCATests/Plugins/HelpMenuTests/HomePageMenuTests.cs b/src/GCATests/Plugins/HelpMenuTests/HomePageMenuTests.cs new file mode 100644 index 00000000..c6f6d815 --- /dev/null +++ b/src/GCATests/Plugins/HelpMenuTests/HomePageMenuTests.cs @@ -0,0 +1,76 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +using GSendControls.Plugins.InternalPlugins.HelpMenu; +using GSendControls.Plugins.InternalPlugins.SearchMenu; + +using GSendShared.Plugins; + +using GSendTests.Mocks; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace GSendTests.Plugins.HelpMenuTests +{ + [TestClass] + [ExcludeFromCodeCoverage] + public sealed class HomePageMenuTests + { + [TestMethod] + public void Construct_HomePageMenuItem_Success() + { + MockPluginMenu parentMenu = new MockPluginMenu("test", MenuType.MenuItem, null); + HomePageMenu sut = new(parentMenu, new MockRunProgram()); + Assert.IsNotNull(sut); + Assert.IsNull(sut.MenuImage); + Assert.AreEqual(MenuType.MenuItem, sut.MenuType); + Assert.IsNotNull(sut.ParentMenu); + Assert.AreSame(parentMenu, sut.ParentMenu); + Assert.AreEqual("Home Page", sut.Text); + Assert.AreEqual(4, sut.Index); + Assert.IsFalse(sut.ReceiveClientMessages); + Assert.IsFalse(sut.GetShortcut([], out string grpName, out string shrtCutName)); + Assert.IsNull(grpName); + Assert.IsNull(shrtCutName); + Assert.IsFalse(sut.IsChecked()); + Assert.IsTrue(sut.IsEnabled()); + Assert.IsTrue(sut.IsVisible()); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void Construct_InvalidParam_ParentNull_Throws_ArgumentNullException() + { + new HomePageMenu(null, new MockRunProgram()); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void Construct_InvalidParam_RunProgramNull_Throws_ArgumentNullException() + { + new HomePageMenu(new MockPluginMenu("test", MenuType.MenuItem, null), null); + } + + [TestMethod] + public void UpdateHost_DoesNotCrash_Success() + { + HomePageMenu sut = new(new MockPluginMenu("test", MenuType.MenuItem, null), new MockRunProgram()); + Assert.IsNotNull(sut); + sut.UpdateHost(null); + } + + [TestMethod] + public void Clicked_RunsHomePageUsingShellExecute_Success() + { + MockRunProgram mockRunProgram = new(); + MockPluginMenu parentMenu = new MockPluginMenu("test", MenuType.MenuItem, null); + HomePageMenu sut = new(parentMenu, mockRunProgram); + sut.Clicked(); + Assert.AreEqual("https://www.gsend.pro/", mockRunProgram.ProgramName); + Assert.IsNull(mockRunProgram.Parameters); + Assert.IsTrue(mockRunProgram.UseShellExecute); + Assert.IsFalse(mockRunProgram.WaitForFinish); + Assert.AreEqual(500, mockRunProgram.TimeoutMilliseconds); + } + } +} diff --git a/src/GCATests/Shared/Plugins/ServerMenuPluginTests.cs b/src/GCATests/Shared/Plugins/ServerMenuPluginTests.cs index c2d9cebc..05066c85 100644 --- a/src/GCATests/Shared/Plugins/ServerMenuPluginTests.cs +++ b/src/GCATests/Shared/Plugins/ServerMenuPluginTests.cs @@ -65,7 +65,7 @@ public void SeperatorMenuItem_ConstructValidInstance_Success() public void HomePageMenuItem_ConstructValidInstance_Success() { IPluginMenu parent = new MockPluginMenu("help", MenuType.MenuItem, null); - HomePageMenu sut = new(parent); + HomePageMenu sut = new(parent, new MockRunProgram()); Assert.IsNotNull(sut); Assert.AreEqual("Home Page", sut.Text); Assert.AreEqual(4, sut.Index); diff --git a/src/GCSDesktop/FormMain.cs b/src/GCSDesktop/FormMain.cs index 3a9e9eaa..605a8b80 100644 --- a/src/GCSDesktop/FormMain.cs +++ b/src/GCSDesktop/FormMain.cs @@ -43,6 +43,7 @@ public partial class FormMain : BaseForm, IPluginHost, IOnlineStatusUpdate private IMachine _selectedMachine = null; private readonly IPluginHelper _pluginHelper; private long _machineHashCombined = 0; + private readonly List _pluginItemsWithClientMessages = []; public FormMain(IGSendContext context, IGSendApiWrapper machineApiWrapper, ICommandProcessor processCommand, GSendSettings settings) @@ -131,6 +132,9 @@ private void ClientWebSocket_ProcessMessage(string message) } UpdateEnabledState(); + + // notify plugin items interested in messages + _pluginItemsWithClientMessages.ForEach(pcm => pcm.ClientMessageReceived(clientMessage)); } [System.Diagnostics.CodeAnalysis.SuppressMessage("Major Code Smell", "S125:Sections of code should not be commented out", Justification = "Left in for now")] @@ -566,7 +570,7 @@ public void UpdateOnlineStatus(bool isOnline, string server) toolStripStatusConnected.Text = statusText; } - #region ISenderPluginHost + #region IPluginHost public PluginHosts Host => PluginHosts.SenderHost; @@ -576,7 +580,10 @@ public void UpdateOnlineStatus(bool isOnline, string server) public void AddPlugin(IGSendPluginModule pluginModule) { - // nothing special to do for this host + ArgumentNullException.ThrowIfNull(pluginModule); + + if (pluginModule.ReceiveClientMessages) + _pluginItemsWithClientMessages.Add(pluginModule); } public IPluginMenu GetMenu(MenuParent menuParent) @@ -600,12 +607,23 @@ public void AddMenu(IPluginMenu pluginMenu) { pluginMenu.UpdateHost(this as IPluginHost); _pluginHelper.AddMenu(this, menuStripMain, pluginMenu, null); + + if (pluginMenu.ReceiveClientMessages) + _pluginItemsWithClientMessages.Add(pluginMenu); } public void AddToolbar(IPluginToolbarButton toolbarButton) { toolbarButton.UpdateHost(this as IEditorPluginHost); _pluginHelper.AddToolbarButton(this, toolStripMain, toolbarButton); + + if (toolbarButton.ReceiveClientMessages) + _pluginItemsWithClientMessages.Add(toolbarButton); + } + + public void AddControl(IPluginControl pluginControl) + { + // nothing to do for this host! } public void AddMessage(InformationType informationType, string message) @@ -613,6 +631,6 @@ public void AddMessage(InformationType informationType, string message) throw new InvalidOperationException(); } - #endregion ISenderPluginHost + #endregion IPluginHost } } \ No newline at end of file diff --git a/src/GCSDesktop/Forms/FrmMachine.Designer.cs b/src/GCSDesktop/Forms/FrmMachine.Designer.cs index d6640a5c..65de7007 100644 --- a/src/GCSDesktop/Forms/FrmMachine.Designer.cs +++ b/src/GCSDesktop/Forms/FrmMachine.Designer.cs @@ -172,6 +172,7 @@ private void InitializeComponent() mnuActionStop = new System.Windows.Forms.ToolStripMenuItem(); mnuOptions = new System.Windows.Forms.ToolStripMenuItem(); mnuOptionsShortcutKeys = new System.Windows.Forms.ToolStripMenuItem(); + mnuTools = new System.Windows.Forms.ToolStripMenuItem(); mnuHelp = new System.Windows.Forms.ToolStripMenuItem(); mnuHelpAbout = new System.Windows.Forms.ToolStripMenuItem(); warningsAndErrors = new GSendControls.WarningContainer(); @@ -193,17 +194,7 @@ private void InitializeComponent() tabPage2DView = new System.Windows.Forms.TabPage(); panelZoom = new System.Windows.Forms.Panel(); machine2dView1 = new GSendControls.Machine2DView(); - tabPageHeartbeat = new System.Windows.Forms.TabPage(); - flowLayoutPanelHeartbeat = new System.Windows.Forms.FlowLayoutPanel(); - heartbeatPanelCommandQueue = new GSendControls.HeartbeatPanel(); - heartbeatPanelBufferSize = new GSendControls.HeartbeatPanel(); - heartbeatPanelQueueSize = new GSendControls.HeartbeatPanel(); - heartbeatPanelFeed = new GSendControls.HeartbeatPanel(); - heartbeatPanelSpindle = new GSendControls.HeartbeatPanel(); - heartbeatPanelAvailableBlocks = new GSendControls.HeartbeatPanel(); - heartbeatPanelAvailableRXBytes = new GSendControls.HeartbeatPanel(); openFileDialog1 = new System.Windows.Forms.OpenFileDialog(); - mnuTools = new System.Windows.Forms.ToolStripMenuItem(); toolStripMain.SuspendLayout(); statusStrip.SuspendLayout(); tabControlMain.SuspendLayout(); @@ -228,8 +219,6 @@ private void InitializeComponent() tabPageConsole.SuspendLayout(); tabPageGCode.SuspendLayout(); tabPage2DView.SuspendLayout(); - tabPageHeartbeat.SuspendLayout(); - flowLayoutPanelHeartbeat.SuspendLayout(); SuspendLayout(); // // selectionOverrideSpindle @@ -458,7 +447,7 @@ private void InitializeComponent() // toolStripDropDownButtonCoordinateSystem.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; toolStripDropDownButtonCoordinateSystem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { g54ToolStripMenuItem, g55ToolStripMenuItem, g56ToolStripMenuItem, g57ToolStripMenuItem, g58ToolStripMenuItem, g59ToolStripMenuItem }); - toolStripDropDownButtonCoordinateSystem.Font = new System.Drawing.Font("Segoe UI", 15F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point); + toolStripDropDownButtonCoordinateSystem.Font = new System.Drawing.Font("Segoe UI", 15F, System.Drawing.FontStyle.Bold); toolStripDropDownButtonCoordinateSystem.Image = (System.Drawing.Image)resources.GetObject("toolStripDropDownButtonCoordinateSystem.Image"); toolStripDropDownButtonCoordinateSystem.ImageTransparentColor = System.Drawing.Color.Magenta; toolStripDropDownButtonCoordinateSystem.Name = "toolStripDropDownButtonCoordinateSystem"; @@ -470,7 +459,7 @@ private void InitializeComponent() g54ToolStripMenuItem.Checked = true; g54ToolStripMenuItem.CheckState = System.Windows.Forms.CheckState.Checked; g54ToolStripMenuItem.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; - g54ToolStripMenuItem.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + g54ToolStripMenuItem.Font = new System.Drawing.Font("Segoe UI", 9F); g54ToolStripMenuItem.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; g54ToolStripMenuItem.Name = "g54ToolStripMenuItem"; g54ToolStripMenuItem.Size = new System.Drawing.Size(99, 22); @@ -479,7 +468,7 @@ private void InitializeComponent() // // g55ToolStripMenuItem // - g55ToolStripMenuItem.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + g55ToolStripMenuItem.Font = new System.Drawing.Font("Segoe UI", 9F); g55ToolStripMenuItem.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; g55ToolStripMenuItem.Name = "g55ToolStripMenuItem"; g55ToolStripMenuItem.Size = new System.Drawing.Size(99, 22); @@ -489,7 +478,7 @@ private void InitializeComponent() // g56ToolStripMenuItem // g56ToolStripMenuItem.CheckOnClick = true; - g56ToolStripMenuItem.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + g56ToolStripMenuItem.Font = new System.Drawing.Font("Segoe UI", 9F); g56ToolStripMenuItem.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; g56ToolStripMenuItem.Name = "g56ToolStripMenuItem"; g56ToolStripMenuItem.Size = new System.Drawing.Size(99, 22); @@ -498,7 +487,7 @@ private void InitializeComponent() // // g57ToolStripMenuItem // - g57ToolStripMenuItem.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + g57ToolStripMenuItem.Font = new System.Drawing.Font("Segoe UI", 9F); g57ToolStripMenuItem.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; g57ToolStripMenuItem.Name = "g57ToolStripMenuItem"; g57ToolStripMenuItem.Size = new System.Drawing.Size(99, 22); @@ -507,7 +496,7 @@ private void InitializeComponent() // // g58ToolStripMenuItem // - g58ToolStripMenuItem.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + g58ToolStripMenuItem.Font = new System.Drawing.Font("Segoe UI", 9F); g58ToolStripMenuItem.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; g58ToolStripMenuItem.Name = "g58ToolStripMenuItem"; g58ToolStripMenuItem.Size = new System.Drawing.Size(99, 22); @@ -516,7 +505,7 @@ private void InitializeComponent() // // g59ToolStripMenuItem // - g59ToolStripMenuItem.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + g59ToolStripMenuItem.Font = new System.Drawing.Font("Segoe UI", 9F); g59ToolStripMenuItem.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; g59ToolStripMenuItem.Name = "g59ToolStripMenuItem"; g59ToolStripMenuItem.Size = new System.Drawing.Size(99, 22); @@ -1159,7 +1148,7 @@ private void InitializeComponent() // lblPropertyHeader.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; lblPropertyHeader.AutoSize = true; - lblPropertyHeader.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point); + lblPropertyHeader.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold); lblPropertyHeader.Location = new System.Drawing.Point(380, 139); lblPropertyHeader.Name = "lblPropertyHeader"; lblPropertyHeader.Size = new System.Drawing.Size(40, 15); @@ -1406,38 +1395,38 @@ private void InitializeComponent() // mnuMachineLoadGCode // mnuMachineLoadGCode.Name = "mnuMachineLoadGCode"; - mnuMachineLoadGCode.Size = new System.Drawing.Size(180, 22); + mnuMachineLoadGCode.Size = new System.Drawing.Size(145, 22); mnuMachineLoadGCode.Text = "Load G-Code"; mnuMachineLoadGCode.Click += mnuMachineLoadGCode_Click; // // mnuMachineClearGCode // mnuMachineClearGCode.Name = "mnuMachineClearGCode"; - mnuMachineClearGCode.Size = new System.Drawing.Size(180, 22); + mnuMachineClearGCode.Size = new System.Drawing.Size(145, 22); mnuMachineClearGCode.Text = "Clear G-Code"; mnuMachineClearGCode.Click += mnuMachineClearGCode_Click; // // toolStripMenuItem2 // toolStripMenuItem2.Name = "toolStripMenuItem2"; - toolStripMenuItem2.Size = new System.Drawing.Size(177, 6); + toolStripMenuItem2.Size = new System.Drawing.Size(142, 6); // // mnuMachineRename // mnuMachineRename.Name = "mnuMachineRename"; - mnuMachineRename.Size = new System.Drawing.Size(180, 22); + mnuMachineRename.Size = new System.Drawing.Size(145, 22); mnuMachineRename.Text = "Rename"; mnuMachineRename.Click += mnuMachineRename_Click; // // toolStripMenuItem7 // toolStripMenuItem7.Name = "toolStripMenuItem7"; - toolStripMenuItem7.Size = new System.Drawing.Size(177, 6); + toolStripMenuItem7.Size = new System.Drawing.Size(142, 6); // // mnuMachineClose // mnuMachineClose.Name = "mnuMachineClose"; - mnuMachineClose.Size = new System.Drawing.Size(180, 22); + mnuMachineClose.Size = new System.Drawing.Size(145, 22); mnuMachineClose.Text = "Close"; mnuMachineClose.Click += closeToolStripMenuItem_Click; // @@ -1526,7 +1515,7 @@ private void InitializeComponent() // mnuActionConnect.Name = "mnuActionConnect"; mnuActionConnect.Size = new System.Drawing.Size(175, 22); - mnuActionConnect.Text = GSend.Language.Resources.Connect; + mnuActionConnect.Text = "Connect"; mnuActionConnect.Click += toolStripButtonConnect_Click; // // mnuActionDisconnect @@ -1590,7 +1579,7 @@ private void InitializeComponent() // mnuActionStop.Name = "mnuActionStop"; mnuActionStop.Size = new System.Drawing.Size(175, 22); - mnuActionStop.Text = GSend.Language.Resources.Stop; + mnuActionStop.Text = "Stop"; mnuActionStop.Click += toolStripButtonStop_Click; // // mnuOptions @@ -1607,6 +1596,12 @@ private void InitializeComponent() mnuOptionsShortcutKeys.Text = "Shortcut Keys"; mnuOptionsShortcutKeys.Click += mnuOptionsShortcutKeys_Click; // + // mnuTools + // + mnuTools.Name = "mnuTools"; + mnuTools.Size = new System.Drawing.Size(46, 20); + mnuTools.Text = "Tools"; + // // mnuHelp // mnuHelp.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { mnuHelpAbout }); @@ -1617,7 +1612,7 @@ private void InitializeComponent() // mnuHelpAbout // mnuHelpAbout.Name = "mnuHelpAbout"; - mnuHelpAbout.Size = new System.Drawing.Size(180, 22); + mnuHelpAbout.Size = new System.Drawing.Size(107, 22); mnuHelpAbout.Text = "About"; mnuHelpAbout.Click += mnuHelpAbout_Click; // @@ -1642,7 +1637,6 @@ private void InitializeComponent() tabControlSecondary.Controls.Add(tabPageConsole); tabControlSecondary.Controls.Add(tabPageGCode); tabControlSecondary.Controls.Add(tabPage2DView); - tabControlSecondary.Controls.Add(tabPageHeartbeat); tabControlSecondary.Location = new System.Drawing.Point(12, 411); tabControlSecondary.Name = "tabControlSecondary"; tabControlSecondary.SelectedIndex = 0; @@ -1800,6 +1794,7 @@ private void InitializeComponent() machine2dView1.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None; machine2dView1.Configuration = GSendShared.AxisConfiguration.None; machine2dView1.Location = new System.Drawing.Point(6, 6); + machine2dView1.LockUpdate = true; machine2dView1.MachineSize = new System.Drawing.Rectangle(0, 0, 0, 0); machine2dView1.Name = "machine2dView1"; machine2dView1.Size = new System.Drawing.Size(485, 131); @@ -1808,134 +1803,10 @@ private void InitializeComponent() machine2dView1.YPosition = 0F; machine2dView1.ZoomPanel = panelZoom; // - // tabPageHeartbeat - // - tabPageHeartbeat.Controls.Add(flowLayoutPanelHeartbeat); - tabPageHeartbeat.Location = new System.Drawing.Point(4, 4); - tabPageHeartbeat.Name = "tabPageHeartbeat"; - tabPageHeartbeat.Padding = new System.Windows.Forms.Padding(3); - tabPageHeartbeat.Size = new System.Drawing.Size(779, 143); - tabPageHeartbeat.TabIndex = 3; - tabPageHeartbeat.Text = "graphs"; - tabPageHeartbeat.UseVisualStyleBackColor = true; - // - // flowLayoutPanelHeartbeat - // - flowLayoutPanelHeartbeat.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; - flowLayoutPanelHeartbeat.AutoScroll = true; - flowLayoutPanelHeartbeat.Controls.Add(heartbeatPanelCommandQueue); - flowLayoutPanelHeartbeat.Controls.Add(heartbeatPanelBufferSize); - flowLayoutPanelHeartbeat.Controls.Add(heartbeatPanelQueueSize); - flowLayoutPanelHeartbeat.Controls.Add(heartbeatPanelFeed); - flowLayoutPanelHeartbeat.Controls.Add(heartbeatPanelSpindle); - flowLayoutPanelHeartbeat.Controls.Add(heartbeatPanelAvailableBlocks); - flowLayoutPanelHeartbeat.Controls.Add(heartbeatPanelAvailableRXBytes); - flowLayoutPanelHeartbeat.Location = new System.Drawing.Point(6, 6); - flowLayoutPanelHeartbeat.Name = "flowLayoutPanelHeartbeat"; - flowLayoutPanelHeartbeat.Size = new System.Drawing.Size(753, 131); - flowLayoutPanelHeartbeat.TabIndex = 0; - // - // heartbeatPanelCommandQueue - // - heartbeatPanelCommandQueue.AutoPoints = true; - heartbeatPanelCommandQueue.BackGround = System.Drawing.Color.LightCyan; - heartbeatPanelCommandQueue.GraphName = null; - heartbeatPanelCommandQueue.Location = new System.Drawing.Point(3, 3); - heartbeatPanelCommandQueue.MaximumPoints = 60; - heartbeatPanelCommandQueue.Name = "heartbeatPanelCommandQueue"; - heartbeatPanelCommandQueue.PrimaryColor = System.Drawing.Color.SlateGray; - heartbeatPanelCommandQueue.SecondaryColor = System.Drawing.Color.BlueViolet; - heartbeatPanelCommandQueue.Size = new System.Drawing.Size(200, 100); - heartbeatPanelCommandQueue.TabIndex = 2; - // - // heartbeatPanelBufferSize - // - heartbeatPanelBufferSize.AutoPoints = true; - heartbeatPanelBufferSize.BackGround = System.Drawing.Color.LightCyan; - heartbeatPanelBufferSize.GraphName = null; - heartbeatPanelBufferSize.Location = new System.Drawing.Point(209, 3); - heartbeatPanelBufferSize.MaximumPoints = 60; - heartbeatPanelBufferSize.Name = "heartbeatPanelBufferSize"; - heartbeatPanelBufferSize.PrimaryColor = System.Drawing.Color.SlateGray; - heartbeatPanelBufferSize.SecondaryColor = System.Drawing.Color.BlueViolet; - heartbeatPanelBufferSize.Size = new System.Drawing.Size(200, 100); - heartbeatPanelBufferSize.TabIndex = 0; - // - // heartbeatPanelQueueSize - // - heartbeatPanelQueueSize.AutoPoints = true; - heartbeatPanelQueueSize.BackGround = System.Drawing.Color.LightCyan; - heartbeatPanelQueueSize.GraphName = null; - heartbeatPanelQueueSize.Location = new System.Drawing.Point(415, 3); - heartbeatPanelQueueSize.MaximumPoints = 60; - heartbeatPanelQueueSize.Name = "heartbeatPanelQueueSize"; - heartbeatPanelQueueSize.PrimaryColor = System.Drawing.Color.SlateGray; - heartbeatPanelQueueSize.SecondaryColor = System.Drawing.Color.BlueViolet; - heartbeatPanelQueueSize.Size = new System.Drawing.Size(200, 100); - heartbeatPanelQueueSize.TabIndex = 1; - // - // heartbeatPanelFeed - // - heartbeatPanelFeed.AutoPoints = true; - heartbeatPanelFeed.BackGround = System.Drawing.Color.LightCyan; - heartbeatPanelFeed.GraphName = null; - heartbeatPanelFeed.Location = new System.Drawing.Point(3, 109); - heartbeatPanelFeed.MaximumPoints = 60; - heartbeatPanelFeed.Name = "heartbeatPanelFeed"; - heartbeatPanelFeed.PrimaryColor = System.Drawing.Color.SlateGray; - heartbeatPanelFeed.SecondaryColor = System.Drawing.Color.BlueViolet; - heartbeatPanelFeed.Size = new System.Drawing.Size(200, 100); - heartbeatPanelFeed.TabIndex = 3; - // - // heartbeatPanelSpindle - // - heartbeatPanelSpindle.AutoPoints = true; - heartbeatPanelSpindle.BackGround = System.Drawing.Color.LightCyan; - heartbeatPanelSpindle.GraphName = null; - heartbeatPanelSpindle.Location = new System.Drawing.Point(209, 109); - heartbeatPanelSpindle.MaximumPoints = 60; - heartbeatPanelSpindle.Name = "heartbeatPanelSpindle"; - heartbeatPanelSpindle.PrimaryColor = System.Drawing.Color.SlateGray; - heartbeatPanelSpindle.SecondaryColor = System.Drawing.Color.BlueViolet; - heartbeatPanelSpindle.Size = new System.Drawing.Size(200, 100); - heartbeatPanelSpindle.TabIndex = 4; - // - // heartbeatPanelAvailableBlocks - // - heartbeatPanelAvailableBlocks.AutoPoints = true; - heartbeatPanelAvailableBlocks.BackGround = System.Drawing.Color.LightCyan; - heartbeatPanelAvailableBlocks.GraphName = null; - heartbeatPanelAvailableBlocks.Location = new System.Drawing.Point(415, 109); - heartbeatPanelAvailableBlocks.MaximumPoints = 34; - heartbeatPanelAvailableBlocks.Name = "heartbeatPanelAvailableBlocks"; - heartbeatPanelAvailableBlocks.PrimaryColor = System.Drawing.Color.SlateGray; - heartbeatPanelAvailableBlocks.SecondaryColor = System.Drawing.Color.DarkSeaGreen; - heartbeatPanelAvailableBlocks.Size = new System.Drawing.Size(200, 100); - heartbeatPanelAvailableBlocks.TabIndex = 5; - // - // heartbeatPanelAvailableRXBytes - // - heartbeatPanelAvailableRXBytes.AutoPoints = true; - heartbeatPanelAvailableRXBytes.BackGround = System.Drawing.Color.LightCyan; - heartbeatPanelAvailableRXBytes.GraphName = null; - heartbeatPanelAvailableRXBytes.Location = new System.Drawing.Point(3, 215); - heartbeatPanelAvailableRXBytes.MaximumPoints = 255; - heartbeatPanelAvailableRXBytes.Name = "heartbeatPanelAvailableRXBytes"; - heartbeatPanelAvailableRXBytes.PrimaryColor = System.Drawing.Color.SlateGray; - heartbeatPanelAvailableRXBytes.SecondaryColor = System.Drawing.Color.DarkSeaGreen; - heartbeatPanelAvailableRXBytes.Size = new System.Drawing.Size(200, 100); - heartbeatPanelAvailableRXBytes.TabIndex = 6; - // // openFileDialog1 // openFileDialog1.Filter = "G-Code Files|*.gcode;*.nc;*.ncc;*.ngc;*.tap;*.txt|All Files|*.*"; // - // mnuTools - // - mnuTools.Name = "mnuTools"; - mnuTools.Size = new System.Drawing.Size(46, 20); - mnuTools.Text = "Tools"; - // // FrmMachine // AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); @@ -1996,8 +1867,6 @@ private void InitializeComponent() tabPageConsole.PerformLayout(); tabPageGCode.ResumeLayout(false); tabPage2DView.ResumeLayout(false); - tabPageHeartbeat.ResumeLayout(false); - flowLayoutPanelHeartbeat.ResumeLayout(false); ResumeLayout(false); PerformLayout(); } @@ -2161,15 +2030,6 @@ private void InitializeComponent() private System.Windows.Forms.ColumnHeader columnHeaderSpindleSpeed; private System.Windows.Forms.ColumnHeader columnHeaderAttributes; private System.Windows.Forms.ColumnHeader columnHeaderLine; - private System.Windows.Forms.TabPage tabPageHeartbeat; - private System.Windows.Forms.FlowLayoutPanel flowLayoutPanelHeartbeat; - private GSendControls.HeartbeatPanel heartbeatPanelBufferSize; - private GSendControls.HeartbeatPanel heartbeatPanelQueueSize; - private GSendControls.HeartbeatPanel heartbeatPanelCommandQueue; - private GSendControls.HeartbeatPanel heartbeatPanelFeed; - private GSendControls.HeartbeatPanel heartbeatPanelSpindle; - private GSendControls.HeartbeatPanel heartbeatPanelAvailableBlocks; - private GSendControls.HeartbeatPanel heartbeatPanelAvailableRXBytes; private System.Windows.Forms.ToolStripMenuItem mnuOptions; private System.Windows.Forms.ToolStripMenuItem mnuOptionsShortcutKeys; private System.Windows.Forms.ToolStripMenuItem mnuHelp; diff --git a/src/GCSDesktop/Forms/FrmMachine.cs b/src/GCSDesktop/Forms/FrmMachine.cs index 1a6ac36e..9e4da76d 100644 --- a/src/GCSDesktop/Forms/FrmMachine.cs +++ b/src/GCSDesktop/Forms/FrmMachine.cs @@ -64,8 +64,7 @@ public partial class FrmMachine : BaseForm, IUiUpdate, IShortcutImplementation, private List _shortcuts; private readonly ShortcutHandler _shortcutHandler; private readonly IPluginHelper _pluginHelper; - private readonly List _pluginsWithClientMessage = []; - private readonly List _pluginsWithClientMessages = []; + private readonly List _pluginItemsWithClientMessages = []; #endregion Private Fields @@ -336,12 +335,6 @@ private void ClientWebSocket_ProcessMessage(string message) AddMessageToConsole(clientMessage.message.ToString()); } - // notify plugins interested in messages - Parallel.ForEach(_pluginsWithClientMessage, sp => - { - sp.ClientMessageReceived(clientMessage); - }); - break; case Constants.GrblError: @@ -436,12 +429,8 @@ private void ClientWebSocket_ProcessMessage(string message) break; } - - // notify plugins interested in messages - Parallel.ForEach(_pluginsWithClientMessages, sp => - { - sp.ClientMessageReceived(clientMessage); - }); + // notify plugin items interested in messages + _pluginItemsWithClientMessages.ForEach(pcm => pcm.ClientMessageReceived(clientMessage)); } protected override void UpdateEnabledState() @@ -545,25 +534,6 @@ private void UpdateMachineStatus(MachineStateModel status) UpdateLabelText(lblQueueSize, String.Format(GSend.Language.Resources.QueueSize, status.QueueSize)); UpdateLabelText(lblCommandQueueSize, String.Format(GSend.Language.Resources.CommandQueueSize, status.CommandQueueSize)); - heartbeatPanelBufferSize.AddPoint(status.BufferSize); - heartbeatPanelCommandQueue.AddPoint(status.CommandQueueSize); - heartbeatPanelFeed.AddPoint((int)status.FeedRate); - heartbeatPanelQueueSize.AddPoint(status.QueueSize); - heartbeatPanelSpindle.AddPoint((int)status.SpindleSpeed); - - if (heartbeatPanelAvailableRXBytes.MaximumPoints == 0 && status.AvailableRXbytes > 0) - { - heartbeatPanelAvailableRXBytes.MaximumPoints = status.AvailableRXbytes; - } - - if (heartbeatPanelAvailableBlocks.MaximumPoints == 0 && status.BufferAvailableBlocks > 0) - { - heartbeatPanelAvailableBlocks.MaximumPoints = status.BufferAvailableBlocks; - } - - heartbeatPanelAvailableRXBytes.AddPoint(status.AvailableRXbytes); - heartbeatPanelAvailableBlocks.AddPoint(status.BufferAvailableBlocks); - if (!_appliedSettingsChanged) { LoadAllStatusChangeWarnings(status); @@ -1816,19 +1786,6 @@ protected override void LoadResources() // 2d view tabPage2DView.Text = GSend.Language.Resources.View2D; - // heartbeat tab - tabPageHeartbeat.Text = GSend.Language.Resources.Graphs; - heartbeatPanelBufferSize.GraphName = GSend.Language.Resources.GraphBufferSize; - heartbeatPanelCommandQueue.GraphName = GSend.Language.Resources.GraphCommandQueue; - heartbeatPanelFeed.GraphName = GSend.Language.Resources.GraphFeedRate; - heartbeatPanelQueueSize.GraphName = GSend.Language.Resources.GraphQueueSize; - heartbeatPanelSpindle.GraphName = GSend.Language.Resources.GraphSpindleSpeed; - heartbeatPanelAvailableBlocks.GraphName = GSend.Language.Resources.GraphAvailableBlocks; - heartbeatPanelAvailableRXBytes.GraphName = GSend.Language.Resources.GraphAvailableRXBytes; - - - - // menu items openFileDialog1.Filter = _gSendContext.Settings.FileFilter; @@ -2654,7 +2611,7 @@ public List GetShortcuts() #endregion Shortcuts - #region ISenderPluginHost + #region IPluginHost public PluginHosts Host => PluginHosts.Sender; @@ -2666,8 +2623,8 @@ public void AddPlugin(IGSendPluginModule pluginModule) { ArgumentNullException.ThrowIfNull(pluginModule); - if (pluginModule.Options.HasFlag(PluginOptions.MessageReceived)) - _pluginsWithClientMessage.Add(pluginModule); + if (pluginModule.ReceiveClientMessages) + _pluginItemsWithClientMessages.Add(pluginModule); } public IPluginMenu GetMenu(MenuParent menuParent) @@ -2702,7 +2659,7 @@ public void AddMenu(IPluginMenu pluginMenu) _pluginHelper.AddMenu(this, menuStripMain, pluginMenu, _shortcuts); if (pluginMenu.ReceiveClientMessages) - _pluginsWithClientMessages.Add(pluginMenu); + _pluginItemsWithClientMessages.Add(pluginMenu); } public void AddToolbar(IPluginToolbarButton toolbarButton) @@ -2711,7 +2668,25 @@ public void AddToolbar(IPluginToolbarButton toolbarButton) _pluginHelper.AddToolbarButton(this, toolStripMain, toolbarButton); if (toolbarButton.ReceiveClientMessages) - _pluginsWithClientMessages.Add(toolbarButton); + _pluginItemsWithClientMessages.Add(toolbarButton); + } + + public void AddControl(IPluginControl pluginControl) + { + TabPage controlTabPage = new(); + + if (pluginControl.Location == ControlLocation.Primary) + tabControlMain.TabPages.Add(controlTabPage); + else + tabControlSecondary.TabPages.Add(controlTabPage); + + controlTabPage.Controls.Add(pluginControl.Control); + pluginControl.Control.UpdatePosition(controlTabPage); + + controlTabPage.Text = pluginControl.Name; + + if (pluginControl.ReceiveClientMessages) + _pluginItemsWithClientMessages.Add(pluginControl); } public void SendMessage(string message) @@ -2735,6 +2710,6 @@ public void AddMessage(InformationType informationType, string message) public IMachine GetMachine() => _machine; - #endregion ISenderPluginHost + #endregion IPluginHost } } diff --git a/src/GCSDesktop/Forms/FrmMachine.resx b/src/GCSDesktop/Forms/FrmMachine.resx index a297af61..dd403b42 100644 --- a/src/GCSDesktop/Forms/FrmMachine.resx +++ b/src/GCSDesktop/Forms/FrmMachine.resx @@ -500,155 +500,155 @@ - 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 - BkGPA4TTu/4fntsFqt5lD+QAAAAASUVORK5CYII= + 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 + 3mUP5AAAAABJRU5ErkJggg== diff --git a/src/GCSShared/Plugins/PluginEnums.cs b/src/GCSShared/Plugins/PluginEnums.cs index 8be5e997..70f38ea7 100644 --- a/src/GCSShared/Plugins/PluginEnums.cs +++ b/src/GCSShared/Plugins/PluginEnums.cs @@ -33,7 +33,9 @@ public enum PluginOptions HasToolbarButtons = 4, - MessageReceived = 8, + HasControls = 8, + + MessageReceived = 8192, } /// @@ -83,4 +85,11 @@ public enum ButtonType Button } + + public enum ControlLocation + { + Primary, + + Secondary, + } } diff --git a/src/GSendControls/Abstractions/IGSendPluginModule.cs b/src/GSendControls/Abstractions/IGSendPluginModule.cs index 88ea5fe9..74bae966 100644 --- a/src/GSendControls/Abstractions/IGSendPluginModule.cs +++ b/src/GSendControls/Abstractions/IGSendPluginModule.cs @@ -5,7 +5,7 @@ namespace GSendControls.Abstractions { - public interface IGSendPluginModule + public interface IGSendPluginModule : IPluginMessages { /// /// Name of the plugin @@ -38,8 +38,14 @@ public interface IGSendPluginModule /// IReadOnlyList MenuItems { get; } + /// + /// Toolbar items to be displayed within the host + /// IReadOnlyList ToolbarItems { get; } - void ClientMessageReceived(IClientBaseMessage clientBaseMessage); + /// + /// Controls to be displayed within host + /// + IReadOnlyList ControlItems { get; } } } diff --git a/src/GSendControls/Abstractions/IPluginControl.cs b/src/GSendControls/Abstractions/IPluginControl.cs new file mode 100644 index 00000000..7a367088 --- /dev/null +++ b/src/GSendControls/Abstractions/IPluginControl.cs @@ -0,0 +1,15 @@ +using GSendControls.Controls; + +using GSendShared.Plugins; + +namespace GSendControls.Abstractions +{ + public interface IPluginControl : IPluginItemBase + { + string Name { get; } + + PluginControl Control { get; } + + ControlLocation Location { get; } + } +} diff --git a/src/GSendControls/Abstractions/IPluginHost.cs b/src/GSendControls/Abstractions/IPluginHost.cs index f8d64532..7f2d779b 100644 --- a/src/GSendControls/Abstractions/IPluginHost.cs +++ b/src/GSendControls/Abstractions/IPluginHost.cs @@ -17,6 +17,8 @@ public interface IPluginHost void AddToolbar(IPluginToolbarButton toolbarButton); + void AddControl(IPluginControl pluginControl); + void AddMessage(InformationType informationType, string message); void AddPlugin(IGSendPluginModule pluginModule); diff --git a/src/GSendControls/Abstractions/IPluginItemBase.cs b/src/GSendControls/Abstractions/IPluginItemBase.cs index c49c3702..1e1f1b99 100644 --- a/src/GSendControls/Abstractions/IPluginItemBase.cs +++ b/src/GSendControls/Abstractions/IPluginItemBase.cs @@ -5,7 +5,7 @@ namespace GSendControls.Abstractions /// /// Base plugin item interface for common methods/properties /// - public interface IPluginItemBase + public interface IPluginItemBase : IPluginMessages { /// /// Menu name/text @@ -17,30 +17,12 @@ public interface IPluginItemBase /// int Index { get; } - /// - /// Click event when menu clicked - /// - /// - /// - void Clicked(); - /// /// Determines whether the menu is enabled or not /// /// bool IsEnabled(); - /// - /// Indicates the plugin item wishes to receive Status change notifications - /// - bool ReceiveClientMessages { get; } - - /// - /// Machine Status Change, if required, requires plugin options to be set correctly for entire plugin - /// - /// - void ClientMessageReceived(IClientBaseMessage clientMessage); - /// /// Updates the /// diff --git a/src/GSendControls/Abstractions/IPluginItemInteractive.cs b/src/GSendControls/Abstractions/IPluginItemInteractive.cs new file mode 100644 index 00000000..8745fcf9 --- /dev/null +++ b/src/GSendControls/Abstractions/IPluginItemInteractive.cs @@ -0,0 +1,10 @@ +namespace GSendControls.Abstractions +{ + public interface IPluginItemInteractive : IPluginItemBase + { + /// + /// Click event when menu clicked + /// + void Clicked(); + } +} diff --git a/src/GSendControls/Abstractions/IPluginMenu.cs b/src/GSendControls/Abstractions/IPluginMenu.cs index 3b3bc97f..b15f1eb1 100644 --- a/src/GSendControls/Abstractions/IPluginMenu.cs +++ b/src/GSendControls/Abstractions/IPluginMenu.cs @@ -8,7 +8,7 @@ namespace GSendControls.Abstractions /// /// Interface created by a plugin to add menu items to the host /// - public interface IPluginMenu : IPluginItemBase + public interface IPluginMenu : IPluginItemInteractive { /// /// Image to be displayed with menu diff --git a/src/GSendControls/Abstractions/IPluginMessages.cs b/src/GSendControls/Abstractions/IPluginMessages.cs new file mode 100644 index 00000000..2a935296 --- /dev/null +++ b/src/GSendControls/Abstractions/IPluginMessages.cs @@ -0,0 +1,18 @@ +using GSendShared; + +namespace GSendControls.Abstractions +{ + public interface IPluginMessages + { + /// + /// Indicates the plugin item wishes to receive messages including Status change notifications + /// + bool ReceiveClientMessages { get; } + + /// + /// Machine Status Change, if required, requires plugin options to be set correctly for entire plugin + /// + /// + void ClientMessageReceived(IClientBaseMessage clientMessage); + } +} diff --git a/src/GSendControls/Abstractions/IPluginToolbarButton.cs b/src/GSendControls/Abstractions/IPluginToolbarButton.cs index 59e68334..3fe15c72 100644 --- a/src/GSendControls/Abstractions/IPluginToolbarButton.cs +++ b/src/GSendControls/Abstractions/IPluginToolbarButton.cs @@ -3,7 +3,7 @@ namespace GSendControls.Abstractions { - public interface IPluginToolbarButton : IPluginItemBase + public interface IPluginToolbarButton : IPluginItemInteractive { /// /// Type of button to be created diff --git a/src/GSendControls/Controls/PluginControl.cs b/src/GSendControls/Controls/PluginControl.cs new file mode 100644 index 00000000..716723c9 --- /dev/null +++ b/src/GSendControls/Controls/PluginControl.cs @@ -0,0 +1,39 @@ +using System.Windows.Forms; + +namespace GSendControls.Controls +{ + public class PluginControl : BaseControl + { + new public Control Parent + { + get + { + return null; + } + + set + { + base.Parent = value; + } + } + + new public AnchorStyles Anchor + { + get => base.Anchor; + + set + { + base.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Right; + } + } + + public void UpdatePosition(Control parentControl) + { + Left = 2; + Top = 2; + Height = parentControl.Height - 4; + Width = parentControl.Width - 4; + Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Right; + } + } +} diff --git a/src/GSendControls/Controls/PluginControl.resx b/src/GSendControls/Controls/PluginControl.resx new file mode 100644 index 00000000..1af7de15 --- /dev/null +++ b/src/GSendControls/Controls/PluginControl.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/GSendControls/GlobalSuppressions.cs b/src/GSendControls/GlobalSuppressions.cs index 1ced59dd..801305df 100644 --- a/src/GSendControls/GlobalSuppressions.cs +++ b/src/GSendControls/GlobalSuppressions.cs @@ -19,4 +19,9 @@ [assembly: SuppressMessage("Major Code Smell", "S1168:Empty arrays and collections should be returned instead of null", Justification = "expects null", Scope = "member", Target = "~P:GSendControls.Plugins.InternalPlugins.ServerMenu.ServerMenuPlugin.ToolbarItems")] [assembly: SuppressMessage("Major Code Smell", "S1168:Empty arrays and collections should be returned instead of null", Justification = "expects null", Scope = "member", Target = "~P:GSendControls.Plugins.InternalPlugins.HelpMenu.HelpMenuPlugin.ToolbarItems")] [assembly: SuppressMessage("Major Bug", "S2259:Null pointers should not be dereferenced", Justification = "", Scope = "member", Target = "~M:GSendControls.Plugins.PluginHelper.InitializeAllPlugins(GSendControls.Abstractions.IPluginHost)")] -[assembly: SuppressMessage("Major Code Smell", "S1168:Empty arrays and collections should be returned instead of null", Justification = "Null is fine", Scope = "member", Target = "~P:GSendControls.Plugins.InternalPlugins.SearchMenu.SearchMenuPlugin.ToolbarItems")] +[assembly: SuppressMessage("Major Code Smell", "S1168:Empty arrays and collections should be returned instead of null", Justification = "Null is fine and by design", Scope = "member", Target = "~P:GSendControls.Plugins.InternalPlugins.SearchMenu.SearchMenuPlugin.ToolbarItems")] +[assembly: SuppressMessage("Major Code Smell", "S1168:Empty arrays and collections should be returned instead of null", Justification = "Null is fine and by design", Scope = "member", Target = "~P:GSendControls.Plugins.InternalPlugins.HelpMenu.HelpMenuPlugin.ControlItems")] +[assembly: SuppressMessage("Major Code Smell", "S1168:Empty arrays and collections should be returned instead of null", Justification = "Null is fine and by design", Scope = "member", Target = "~P:GSendControls.Plugins.InternalPlugins.SearchMenu.SearchMenuPlugin.ControlItems")] +[assembly: SuppressMessage("Major Code Smell", "S1168:Empty arrays and collections should be returned instead of null", Justification = "Null is fine and by design", Scope = "member", Target = "~P:GSendControls.Plugins.InternalPlugins.ServerMenu.ServerMenuPlugin.ControlItems")] +[assembly: SuppressMessage("Major Code Smell", "S1168:Empty arrays and collections should be returned instead of null", Justification = "Null is fine and by design", Scope = "member", Target = "~P:GSendControls.Plugins.InternalPlugins.Hearbeats.HearbeatsPlugin.MenuItems")] +[assembly: SuppressMessage("Blocker Code Smell", "S3237:\"value\" contextual keyword should be used", Justification = "Purposfully ignoring value as ensuring it is anchored to all", Scope = "member", Target = "~P:GSendControls.Controls.PluginControl.Anchor")] diff --git a/src/GSendControls/Plugins/InternalPlugins/Hearbeats/HearbeatsPlugin.cs b/src/GSendControls/Plugins/InternalPlugins/Hearbeats/HearbeatsPlugin.cs new file mode 100644 index 00000000..d0f16df7 --- /dev/null +++ b/src/GSendControls/Plugins/InternalPlugins/Hearbeats/HearbeatsPlugin.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; + +using GSendControls.Abstractions; + +using GSendShared; +using GSendShared.Plugins; + +namespace GSendControls.Plugins.InternalPlugins.Hearbeats +{ + internal class HearbeatsPlugin : IGSendPluginModule + { + private List _pluginControls; + + public string Name => "Heartbeats"; + + public ushort Version => 1; + + public PluginHosts Host => PluginHosts.Sender; + + public PluginOptions Options => PluginOptions.HasControls; + + public IReadOnlyList MenuItems => null; + + public IReadOnlyList ToolbarItems => null; + + public IReadOnlyList ControlItems + { + get + { + _pluginControls ??= [ + new HeartbeatControlItem(), + ]; + + return _pluginControls; + } + } + + public bool ReceiveClientMessages => false; + + public void ClientMessageReceived(IClientBaseMessage clientMessage) + { + // from interface, not used in this context + } + + public void Initialize(IPluginHost pluginHost) + { + // from interface, not used in this context + } + } +} diff --git a/src/GSendControls/Plugins/InternalPlugins/Hearbeats/HeartbeatControlItem.cs b/src/GSendControls/Plugins/InternalPlugins/Hearbeats/HeartbeatControlItem.cs new file mode 100644 index 00000000..b03aae0f --- /dev/null +++ b/src/GSendControls/Plugins/InternalPlugins/Hearbeats/HeartbeatControlItem.cs @@ -0,0 +1,61 @@ +using System.Text.Json; + +using GSendControls.Abstractions; +using GSendControls.Controls; + +using GSendShared; +using GSendShared.Models; +using GSendShared.Plugins; + +namespace GSendControls.Plugins.InternalPlugins.Hearbeats +{ + internal class HeartbeatControlItem : IPluginControl + { + private readonly HeartbeatControls _heartbeates; + + public HeartbeatControlItem() + { + _heartbeates = new(); + } + + public string Name => GSend.Language.Resources.Graphs; + + public PluginControl Control => _heartbeates; + + public ControlLocation Location => ControlLocation.Secondary; + + public string Text => "Heartbeat Graphs"; + + public int Index => 20; + + public bool ReceiveClientMessages => true; + + public void ClientMessageReceived(IClientBaseMessage clientMessage) + { + switch (clientMessage.request) + { + case Constants.MessageMachineStatusServer: + case Constants.StateChanged: + JsonElement element = (JsonElement)clientMessage.message; + MachineStateModel machineStatusModel = element.Deserialize(); + + if (machineStatusModel != null) + { + _heartbeates.UpdateMachineStatus(machineStatusModel); + } + + break; + } + } + + public bool IsEnabled() + { + return true; + } + + public void UpdateHost(T senderPluginHost) + { + //not used in this context + } + } +} diff --git a/src/GSendControls/Plugins/InternalPlugins/Hearbeats/HeartbeatControls.Designer.cs b/src/GSendControls/Plugins/InternalPlugins/Hearbeats/HeartbeatControls.Designer.cs new file mode 100644 index 00000000..71a66119 --- /dev/null +++ b/src/GSendControls/Plugins/InternalPlugins/Hearbeats/HeartbeatControls.Designer.cs @@ -0,0 +1,171 @@ +namespace GSendControls.Plugins.InternalPlugins.Hearbeats +{ + partial class HeartbeatControls + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + flowLayoutPanelHeartbeat = new System.Windows.Forms.FlowLayoutPanel(); + heartbeatPanelCommandQueue = new HeartbeatPanel(); + heartbeatPanelBufferSize = new HeartbeatPanel(); + heartbeatPanelQueueSize = new HeartbeatPanel(); + heartbeatPanelFeed = new HeartbeatPanel(); + heartbeatPanelSpindle = new HeartbeatPanel(); + heartbeatPanelAvailableBlocks = new HeartbeatPanel(); + heartbeatPanelAvailableRXBytes = new HeartbeatPanel(); + flowLayoutPanelHeartbeat.SuspendLayout(); + SuspendLayout(); + // + // flowLayoutPanelHeartbeat + // + flowLayoutPanelHeartbeat.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + flowLayoutPanelHeartbeat.AutoScroll = true; + flowLayoutPanelHeartbeat.Controls.Add(heartbeatPanelCommandQueue); + flowLayoutPanelHeartbeat.Controls.Add(heartbeatPanelBufferSize); + flowLayoutPanelHeartbeat.Controls.Add(heartbeatPanelQueueSize); + flowLayoutPanelHeartbeat.Controls.Add(heartbeatPanelFeed); + flowLayoutPanelHeartbeat.Controls.Add(heartbeatPanelSpindle); + flowLayoutPanelHeartbeat.Controls.Add(heartbeatPanelAvailableBlocks); + flowLayoutPanelHeartbeat.Controls.Add(heartbeatPanelAvailableRXBytes); + flowLayoutPanelHeartbeat.Location = new System.Drawing.Point(3, 3); + flowLayoutPanelHeartbeat.Name = "flowLayoutPanelHeartbeat"; + flowLayoutPanelHeartbeat.Size = new System.Drawing.Size(848, 293); + flowLayoutPanelHeartbeat.TabIndex = 1; + // + // heartbeatPanelCommandQueue + // + heartbeatPanelCommandQueue.AutoPoints = true; + heartbeatPanelCommandQueue.BackGround = System.Drawing.Color.LightCyan; + heartbeatPanelCommandQueue.GraphName = null; + heartbeatPanelCommandQueue.Location = new System.Drawing.Point(3, 3); + heartbeatPanelCommandQueue.MaximumPoints = 60; + heartbeatPanelCommandQueue.Name = "heartbeatPanelCommandQueue"; + heartbeatPanelCommandQueue.PrimaryColor = System.Drawing.Color.SlateGray; + heartbeatPanelCommandQueue.SecondaryColor = System.Drawing.Color.BlueViolet; + heartbeatPanelCommandQueue.Size = new System.Drawing.Size(200, 100); + heartbeatPanelCommandQueue.TabIndex = 2; + // + // heartbeatPanelBufferSize + // + heartbeatPanelBufferSize.AutoPoints = true; + heartbeatPanelBufferSize.BackGround = System.Drawing.Color.LightCyan; + heartbeatPanelBufferSize.GraphName = null; + heartbeatPanelBufferSize.Location = new System.Drawing.Point(209, 3); + heartbeatPanelBufferSize.MaximumPoints = 60; + heartbeatPanelBufferSize.Name = "heartbeatPanelBufferSize"; + heartbeatPanelBufferSize.PrimaryColor = System.Drawing.Color.SlateGray; + heartbeatPanelBufferSize.SecondaryColor = System.Drawing.Color.BlueViolet; + heartbeatPanelBufferSize.Size = new System.Drawing.Size(200, 100); + heartbeatPanelBufferSize.TabIndex = 0; + // + // heartbeatPanelQueueSize + // + heartbeatPanelQueueSize.AutoPoints = true; + heartbeatPanelQueueSize.BackGround = System.Drawing.Color.LightCyan; + heartbeatPanelQueueSize.GraphName = null; + heartbeatPanelQueueSize.Location = new System.Drawing.Point(415, 3); + heartbeatPanelQueueSize.MaximumPoints = 60; + heartbeatPanelQueueSize.Name = "heartbeatPanelQueueSize"; + heartbeatPanelQueueSize.PrimaryColor = System.Drawing.Color.SlateGray; + heartbeatPanelQueueSize.SecondaryColor = System.Drawing.Color.BlueViolet; + heartbeatPanelQueueSize.Size = new System.Drawing.Size(200, 100); + heartbeatPanelQueueSize.TabIndex = 1; + // + // heartbeatPanelFeed + // + heartbeatPanelFeed.AutoPoints = true; + heartbeatPanelFeed.BackGround = System.Drawing.Color.LightCyan; + heartbeatPanelFeed.GraphName = null; + heartbeatPanelFeed.Location = new System.Drawing.Point(621, 3); + heartbeatPanelFeed.MaximumPoints = 60; + heartbeatPanelFeed.Name = "heartbeatPanelFeed"; + heartbeatPanelFeed.PrimaryColor = System.Drawing.Color.SlateGray; + heartbeatPanelFeed.SecondaryColor = System.Drawing.Color.BlueViolet; + heartbeatPanelFeed.Size = new System.Drawing.Size(200, 100); + heartbeatPanelFeed.TabIndex = 3; + // + // heartbeatPanelSpindle + // + heartbeatPanelSpindle.AutoPoints = true; + heartbeatPanelSpindle.BackGround = System.Drawing.Color.LightCyan; + heartbeatPanelSpindle.GraphName = null; + heartbeatPanelSpindle.Location = new System.Drawing.Point(3, 109); + heartbeatPanelSpindle.MaximumPoints = 60; + heartbeatPanelSpindle.Name = "heartbeatPanelSpindle"; + heartbeatPanelSpindle.PrimaryColor = System.Drawing.Color.SlateGray; + heartbeatPanelSpindle.SecondaryColor = System.Drawing.Color.BlueViolet; + heartbeatPanelSpindle.Size = new System.Drawing.Size(200, 100); + heartbeatPanelSpindle.TabIndex = 4; + // + // heartbeatPanelAvailableBlocks + // + heartbeatPanelAvailableBlocks.AutoPoints = true; + heartbeatPanelAvailableBlocks.BackGround = System.Drawing.Color.LightCyan; + heartbeatPanelAvailableBlocks.GraphName = null; + heartbeatPanelAvailableBlocks.Location = new System.Drawing.Point(209, 109); + heartbeatPanelAvailableBlocks.MaximumPoints = 34; + heartbeatPanelAvailableBlocks.Name = "heartbeatPanelAvailableBlocks"; + heartbeatPanelAvailableBlocks.PrimaryColor = System.Drawing.Color.SlateGray; + heartbeatPanelAvailableBlocks.SecondaryColor = System.Drawing.Color.DarkSeaGreen; + heartbeatPanelAvailableBlocks.Size = new System.Drawing.Size(200, 100); + heartbeatPanelAvailableBlocks.TabIndex = 5; + // + // heartbeatPanelAvailableRXBytes + // + heartbeatPanelAvailableRXBytes.AutoPoints = true; + heartbeatPanelAvailableRXBytes.BackGround = System.Drawing.Color.LightCyan; + heartbeatPanelAvailableRXBytes.GraphName = null; + heartbeatPanelAvailableRXBytes.Location = new System.Drawing.Point(415, 109); + heartbeatPanelAvailableRXBytes.MaximumPoints = 255; + heartbeatPanelAvailableRXBytes.Name = "heartbeatPanelAvailableRXBytes"; + heartbeatPanelAvailableRXBytes.PrimaryColor = System.Drawing.Color.SlateGray; + heartbeatPanelAvailableRXBytes.SecondaryColor = System.Drawing.Color.DarkSeaGreen; + heartbeatPanelAvailableRXBytes.Size = new System.Drawing.Size(200, 100); + heartbeatPanelAvailableRXBytes.TabIndex = 6; + // + // HeartbeatControls + // + AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + Controls.Add(flowLayoutPanelHeartbeat); + Name = "HeartbeatControls"; + Size = new System.Drawing.Size(854, 299); + flowLayoutPanelHeartbeat.ResumeLayout(false); + ResumeLayout(false); + } + + #endregion + + private System.Windows.Forms.FlowLayoutPanel flowLayoutPanelHeartbeat; + private HeartbeatPanel heartbeatPanelCommandQueue; + private HeartbeatPanel heartbeatPanelBufferSize; + private HeartbeatPanel heartbeatPanelQueueSize; + private HeartbeatPanel heartbeatPanelFeed; + private HeartbeatPanel heartbeatPanelSpindle; + private HeartbeatPanel heartbeatPanelAvailableBlocks; + private HeartbeatPanel heartbeatPanelAvailableRXBytes; + } +} diff --git a/src/GSendControls/Plugins/InternalPlugins/Hearbeats/HeartbeatControls.cs b/src/GSendControls/Plugins/InternalPlugins/Hearbeats/HeartbeatControls.cs new file mode 100644 index 00000000..533fa07c --- /dev/null +++ b/src/GSendControls/Plugins/InternalPlugins/Hearbeats/HeartbeatControls.cs @@ -0,0 +1,59 @@ +using System.Globalization; + +using GSendControls.Controls; + +using GSendShared.Models; + +namespace GSendControls.Plugins.InternalPlugins.Hearbeats +{ + public partial class HeartbeatControls : PluginControl + { + public HeartbeatControls() + { + InitializeComponent(); + } + + public override void LanguageChanged(CultureInfo culture) + { + base.LanguageChanged(culture); + heartbeatPanelBufferSize.GraphName = GSend.Language.Resources.GraphBufferSize; + heartbeatPanelCommandQueue.GraphName = GSend.Language.Resources.GraphCommandQueue; + heartbeatPanelFeed.GraphName = GSend.Language.Resources.GraphFeedRate; + heartbeatPanelQueueSize.GraphName = GSend.Language.Resources.GraphQueueSize; + heartbeatPanelSpindle.GraphName = GSend.Language.Resources.GraphSpindleSpeed; + heartbeatPanelAvailableBlocks.GraphName = GSend.Language.Resources.GraphAvailableBlocks; + heartbeatPanelAvailableRXBytes.GraphName = GSend.Language.Resources.GraphAvailableRXBytes; + } + + public void UpdateMachineStatus(MachineStateModel status) + { + if (InvokeRequired) + { + Invoke(UpdateMachineStatus, status); + return; + } + + if (status.IsConnected) + { + heartbeatPanelBufferSize.AddPoint(status.BufferSize); + heartbeatPanelCommandQueue.AddPoint(status.CommandQueueSize); + heartbeatPanelFeed.AddPoint((int)status.FeedRate); + heartbeatPanelQueueSize.AddPoint(status.QueueSize); + heartbeatPanelSpindle.AddPoint((int)status.SpindleSpeed); + + if (heartbeatPanelAvailableRXBytes.MaximumPoints == 0 && status.AvailableRXbytes > 0) + { + heartbeatPanelAvailableRXBytes.MaximumPoints = status.AvailableRXbytes; + } + + if (heartbeatPanelAvailableBlocks.MaximumPoints == 0 && status.BufferAvailableBlocks > 0) + { + heartbeatPanelAvailableBlocks.MaximumPoints = status.BufferAvailableBlocks; + } + + heartbeatPanelAvailableRXBytes.AddPoint(status.AvailableRXbytes); + heartbeatPanelAvailableBlocks.AddPoint(status.BufferAvailableBlocks); + } + } + } +} diff --git a/src/GSendControls/Plugins/InternalPlugins/Hearbeats/HeartbeatControls.resx b/src/GSendControls/Plugins/InternalPlugins/Hearbeats/HeartbeatControls.resx new file mode 100644 index 00000000..af32865e --- /dev/null +++ b/src/GSendControls/Plugins/InternalPlugins/Hearbeats/HeartbeatControls.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/GSendControls/Plugins/InternalPlugins/HelpMenu/BugsAndIdeasMenu.cs b/src/GSendControls/Plugins/InternalPlugins/HelpMenu/BugsAndIdeasMenu.cs index 8f1e503c..67bce29d 100644 --- a/src/GSendControls/Plugins/InternalPlugins/HelpMenu/BugsAndIdeasMenu.cs +++ b/src/GSendControls/Plugins/InternalPlugins/HelpMenu/BugsAndIdeasMenu.cs @@ -8,15 +8,19 @@ using GSendShared; using GSendShared.Plugins; +using Shared.Classes; + namespace GSendControls.Plugins.InternalPlugins.HelpMenu { internal sealed class BugsAndIdeasMenu : IPluginMenu { private readonly IPluginMenu _parentMenu; + private readonly IRunProgram _runProgram; - public BugsAndIdeasMenu(IPluginMenu parentMenu) + public BugsAndIdeasMenu(IPluginMenu parentMenu, IRunProgram runProgram) { _parentMenu = parentMenu ?? throw new ArgumentNullException(nameof(parentMenu)); + _runProgram = runProgram ?? throw new ArgumentNullException(nameof(runProgram)); } public string Text => "Bugs and Ideas"; @@ -33,13 +37,7 @@ public BugsAndIdeasMenu(IPluginMenu parentMenu) public void Clicked() { - ProcessStartInfo psi = new() - { - FileName = "https://github.com/k3ldar/GSendPro/issues", - UseShellExecute = true - }; - - Process.Start(psi); + _runProgram.Run("https://github.com/k3ldar/GSendPro/issues", null, true, false, 500); } public bool GetShortcut(in List defaultKeys, out string groupName, out string shortcutName) diff --git a/src/GSendControls/Plugins/InternalPlugins/HelpMenu/HelpMenuItem.cs b/src/GSendControls/Plugins/InternalPlugins/HelpMenu/HelpMenuItem.cs index 635ca20c..a2808d20 100644 --- a/src/GSendControls/Plugins/InternalPlugins/HelpMenu/HelpMenuItem.cs +++ b/src/GSendControls/Plugins/InternalPlugins/HelpMenu/HelpMenuItem.cs @@ -8,13 +8,18 @@ using GSendShared; using GSendShared.Plugins; +using Shared.Classes; + namespace GSendControls.Plugins.InternalPlugins.HelpMenu { internal sealed class HelpMenuItem : IPluginMenu { - public HelpMenuItem(IPluginMenu parentMenu) + private readonly IRunProgram _runProgram; + + public HelpMenuItem(IPluginMenu parentMenu, IRunProgram runProgram) { ParentMenu = parentMenu ?? throw new ArgumentNullException(nameof(parentMenu)); + _runProgram = runProgram ?? throw new ArgumentNullException(nameof(runProgram)); } public string Text => "Help"; @@ -31,13 +36,7 @@ public HelpMenuItem(IPluginMenu parentMenu) public void Clicked() { - ProcessStartInfo psi = new() - { - FileName = Constants.HelpWebsite, - UseShellExecute = true - }; - - Process.Start(psi); + _runProgram.Run(Constants.HelpWebsite, null, true, false, 500); } public bool GetShortcut(in List defaultKeys, out string groupName, out string shortcutName) diff --git a/src/GSendControls/Plugins/InternalPlugins/HelpMenu/HelpMenuPlugin.cs b/src/GSendControls/Plugins/InternalPlugins/HelpMenu/HelpMenuPlugin.cs index 5b3a272f..28d14e3b 100644 --- a/src/GSendControls/Plugins/InternalPlugins/HelpMenu/HelpMenuPlugin.cs +++ b/src/GSendControls/Plugins/InternalPlugins/HelpMenu/HelpMenuPlugin.cs @@ -1,15 +1,22 @@ using System; using System.Collections.Generic; + using GSendControls.Abstractions; + using GSendShared; using GSendShared.Plugins; +using Microsoft.Extensions.DependencyInjection; + +using Shared.Classes; + namespace GSendControls.Plugins.InternalPlugins.HelpMenu { public sealed class HelpMenuPlugin : IGSendPluginModule { private List _pluginMenus; private IPluginHost _pluginHost; + private IRunProgram _runProgram; public string Name => "Help Menu"; @@ -32,9 +39,9 @@ public IReadOnlyList MenuItems new SeperatorMenu(parentHelpMenu, 0), new SeperatorMenu(parentHelpMenu, 0), new SeperatorMenu(parentHelpMenu, 0), - new HelpMenuItem(parentHelpMenu), - new BugsAndIdeasMenu(parentHelpMenu), - new HomePageMenu(parentHelpMenu), + new HelpMenuItem(parentHelpMenu, _runProgram), + new BugsAndIdeasMenu(parentHelpMenu, _runProgram), + new HomePageMenu(parentHelpMenu, _runProgram), ]; } @@ -44,14 +51,19 @@ public IReadOnlyList MenuItems public IReadOnlyList ToolbarItems => null; - public void ClientMessageReceived(IClientBaseMessage clientBaseMessage) + public IReadOnlyList ControlItems => null; + + public bool ReceiveClientMessages => false; + + public void ClientMessageReceived(IClientBaseMessage clientMessage) { - // from interface, not used in any context + // from interface, not used in this context } public void Initialize(IPluginHost pluginHost) { _pluginHost = pluginHost ?? throw new ArgumentNullException(nameof(pluginHost)); + _runProgram = pluginHost.GSendContext.ServiceProvider.GetService() ?? throw new InvalidOperationException("IRunProgram not registered"); } } } diff --git a/src/GSendControls/Plugins/InternalPlugins/HelpMenu/HomePageMenu.cs b/src/GSendControls/Plugins/InternalPlugins/HelpMenu/HomePageMenu.cs index 94316bb8..81f44f06 100644 --- a/src/GSendControls/Plugins/InternalPlugins/HelpMenu/HomePageMenu.cs +++ b/src/GSendControls/Plugins/InternalPlugins/HelpMenu/HomePageMenu.cs @@ -1,20 +1,25 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Drawing; + using GSendControls.Abstractions; + using GSendShared; using GSendShared.Plugins; +using Shared.Classes; + namespace GSendControls.Plugins.InternalPlugins.HelpMenu { internal sealed class HomePageMenu : IPluginMenu { private readonly IPluginMenu _parentMenu; + private readonly IRunProgram _runProgram; - public HomePageMenu(IPluginMenu parentMenu) + public HomePageMenu(IPluginMenu parentMenu, IRunProgram runProgram) { _parentMenu = parentMenu ?? throw new ArgumentNullException(nameof(parentMenu)); + _runProgram = runProgram ?? throw new ArgumentNullException(nameof(runProgram)); } public string Text => "Home Page"; @@ -31,13 +36,7 @@ public HomePageMenu(IPluginMenu parentMenu) public void Clicked() { - ProcessStartInfo psi = new() - { - FileName = "https://www.gsend.pro/", - UseShellExecute = true - }; - - Process.Start(psi); + _runProgram.Run("https://www.gsend.pro/", null, true, false, 500); } public bool GetShortcut(in List defaultKeys, out string groupName, out string shortcutName) diff --git a/src/GSendControls/Plugins/InternalPlugins/SearchMenu/SearchMenuPlugin.cs b/src/GSendControls/Plugins/InternalPlugins/SearchMenu/SearchMenuPlugin.cs index 49c80e9c..c5fbf832 100644 --- a/src/GSendControls/Plugins/InternalPlugins/SearchMenu/SearchMenuPlugin.cs +++ b/src/GSendControls/Plugins/InternalPlugins/SearchMenu/SearchMenuPlugin.cs @@ -44,7 +44,11 @@ public IReadOnlyList MenuItems public IReadOnlyList ToolbarItems => null; - public void ClientMessageReceived(IClientBaseMessage clientBaseMessage) + public IReadOnlyList ControlItems => null; + + public bool ReceiveClientMessages => false; + + public void ClientMessageReceived(IClientBaseMessage clientMessage) { // from interface, not used in this context } diff --git a/src/GSendControls/Plugins/InternalPlugins/ServerMenu/ConfigureServerMenuItem.cs b/src/GSendControls/Plugins/InternalPlugins/ServerMenu/ConfigureServerMenuItem.cs index 61f131c1..f81f50c4 100644 --- a/src/GSendControls/Plugins/InternalPlugins/ServerMenu/ConfigureServerMenuItem.cs +++ b/src/GSendControls/Plugins/InternalPlugins/ServerMenu/ConfigureServerMenuItem.cs @@ -40,7 +40,7 @@ public void Clicked() public void ClientMessageReceived(IClientBaseMessage clientMessage) { - + // from interface, not used in this context } public bool GetShortcut(in List defaultKeys, out string groupName, out string shortcutName) diff --git a/src/GSendControls/Plugins/InternalPlugins/ServerMenu/ServerMenuPlugin.cs b/src/GSendControls/Plugins/InternalPlugins/ServerMenu/ServerMenuPlugin.cs index cac33c2c..f3173a6a 100644 --- a/src/GSendControls/Plugins/InternalPlugins/ServerMenu/ServerMenuPlugin.cs +++ b/src/GSendControls/Plugins/InternalPlugins/ServerMenu/ServerMenuPlugin.cs @@ -76,7 +76,11 @@ public IReadOnlyList MenuItems public IReadOnlyList ToolbarItems => null; - public void ClientMessageReceived(IClientBaseMessage clientBaseMessage) + public IReadOnlyList ControlItems => null; + + public bool ReceiveClientMessages => false; + + public void ClientMessageReceived(IClientBaseMessage clientMessage) { // from interface, not used in this context } diff --git a/src/GSendControls/Plugins/InternalPlugins/ServerMenu/ServerRootMenuItem.cs b/src/GSendControls/Plugins/InternalPlugins/ServerMenu/ServerRootMenuItem.cs index 5d80d21f..0c477641 100644 --- a/src/GSendControls/Plugins/InternalPlugins/ServerMenu/ServerRootMenuItem.cs +++ b/src/GSendControls/Plugins/InternalPlugins/ServerMenu/ServerRootMenuItem.cs @@ -30,7 +30,7 @@ public void Clicked() public void ClientMessageReceived(IClientBaseMessage clientMessage) { - + // from interface, not used in this context } public bool GetShortcut(in List defaultKeys, out string groupName, out string shortcutName) diff --git a/src/GSendControls/Plugins/InternalPlugins/ServerMenu/ServerSelectMenuItem.cs b/src/GSendControls/Plugins/InternalPlugins/ServerMenu/ServerSelectMenuItem.cs index a0d1f373..617de6e4 100644 --- a/src/GSendControls/Plugins/InternalPlugins/ServerMenu/ServerSelectMenuItem.cs +++ b/src/GSendControls/Plugins/InternalPlugins/ServerMenu/ServerSelectMenuItem.cs @@ -42,7 +42,7 @@ public void Clicked() public void ClientMessageReceived(IClientBaseMessage clientMessage) { - + // from interface, not used in this context } public bool GetShortcut(in List defaultKeys, out string groupName, out string shortcutName) diff --git a/src/GSendControls/Plugins/PluginHelper.cs b/src/GSendControls/Plugins/PluginHelper.cs index e998ba1e..63fad87f 100644 --- a/src/GSendControls/Plugins/PluginHelper.cs +++ b/src/GSendControls/Plugins/PluginHelper.cs @@ -94,14 +94,15 @@ public void InitializeAllPlugins(IPluginHost pluginHost) if (plugin == null) continue; + if (!plugin.Host.HasFlag(pluginHost.Host)) + { + _logger.AddToLog(PluginManager.LogLevel.PluginLoadError, $"Plugin {plugin.Name} is not valid for host {pluginHost.Host}"); + continue; + } + plugin.Initialize(pluginHost); try { - if (!plugin.Host.HasFlag(pluginHost.Host)) - { - _logger.AddToLog(PluginManager.LogLevel.PluginLoadError, $"Plugin {plugin.Name} is not valid for host {pluginHost.Host}"); - continue; - } if (plugin.Options.HasFlag(PluginOptions.HasMenuItems)) { @@ -131,6 +132,20 @@ public void InitializeAllPlugins(IPluginHost pluginHost) } } + if (plugin.Options.HasFlag(PluginOptions.HasControls)) + { + if (plugin.ControlItems == null) + throw new InvalidOperationException("Controls can not be null if HasControls option is used"); + + foreach (IPluginControl pluginControl in plugin.ControlItems) + { + if (pluginControl == null) + continue; + + pluginHost.AddControl(pluginControl); + } + } + pluginHost.AddPlugin(plugin); _logger.AddToLog(PluginManager.LogLevel.PluginLoadSuccess, $"{plugin.Name} was loaded for host {pluginHost}"); } diff --git a/src/GSendEditor/FrmMain.cs b/src/GSendEditor/FrmMain.cs index 6c9a2366..d3361fdd 100644 --- a/src/GSendEditor/FrmMain.cs +++ b/src/GSendEditor/FrmMain.cs @@ -1189,7 +1189,7 @@ public List GetShortcuts() #endregion Shortcuts - #region ISenderPluginHost + #region IPluginHost public PluginHosts Host => PluginHosts.Editor; @@ -1235,12 +1235,22 @@ public void AddToolbar(IPluginToolbarButton toolbarButton) _pluginHelper.AddToolbarButton(this, toolbarMain, toolbarButton); } + public void AddControl(IPluginControl pluginControl) + { + TabPage controlTabPage = new(); + tabControlMain.TabPages.Add(controlTabPage); + controlTabPage.Controls.Add(pluginControl.Control); + pluginControl.Control.UpdatePosition(controlTabPage); + + controlTabPage.Text = pluginControl.Name; + } + public void AddMessage(InformationType informationType, string message) { throw new InvalidOperationException(); } - #endregion ISenderPluginHost + #endregion IPluginHost #region IEditorPluginHost diff --git a/src/Plugins/GrblTuning/GSendProTuningWizardPlugin.cs b/src/Plugins/GrblTuning/GSendProTuningWizardPlugin.cs index a041755a..174d6db6 100644 --- a/src/Plugins/GrblTuning/GSendProTuningWizardPlugin.cs +++ b/src/Plugins/GrblTuning/GSendProTuningWizardPlugin.cs @@ -15,7 +15,7 @@ public sealed class GSendProTuningWizardPlugin : IGSendPluginModule public PluginHosts Host => PluginHosts.Sender; - public PluginOptions Options => PluginOptions.HasMenuItems | PluginOptions.MessageReceived; + public PluginOptions Options => PluginOptions.HasMenuItems; public IReadOnlyList MenuItems { @@ -30,12 +30,15 @@ public IReadOnlyList MenuItems } } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Major Code Smell", "S1168:Empty arrays and collections should be returned instead of null", Justification = "Expecting null if non available")] public IReadOnlyList ToolbarItems => null; - public void ClientMessageReceived(IClientBaseMessage clientBaseMessage) - { + public IReadOnlyList ControlItems => null; + + public bool ReceiveClientMessages => false; + public void ClientMessageReceived(IClientBaseMessage clientMessage) + { + // from interface, not used in this context } public void Initialize(IPluginHost pluginHost) diff --git a/src/Plugins/GrblTuning/GlobalSuppressions.cs b/src/Plugins/GrblTuning/GlobalSuppressions.cs new file mode 100644 index 00000000..4b865444 --- /dev/null +++ b/src/Plugins/GrblTuning/GlobalSuppressions.cs @@ -0,0 +1,9 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Major Code Smell", "S1168:Empty arrays and collections should be returned instead of null", Justification = "Null is fine and by design", Scope = "member", Target = "~P:GrblTuningWizard.GSendProTuningWizardPlugin.ControlItems")] +[assembly: SuppressMessage("Major Code Smell", "S1168:Empty arrays and collections should be returned instead of null", Justification = "Null is fine and by design", Scope = "member", Target = "~P:GrblTuningWizard.GSendProTuningWizardPlugin.ToolbarItems")] diff --git a/wwwroot/Controllers/MCodesController.cs b/wwwroot/Controllers/MCodesController.cs index 342fd152..431c5c64 100644 --- a/wwwroot/Controllers/MCodesController.cs +++ b/wwwroot/Controllers/MCodesController.cs @@ -14,10 +14,10 @@ public class MCodesController : BaseController { public const string MCodes = "MCodes"; - private static readonly string[] _validMCodes = { + private static readonly string[] _validMCodes = [ "M600", "M601", "M602", "M605", "M620", "M621", "M622", "M623", "M630", "M630.1", "M631", - "M631.1", "M631.2" }; + "M631.1", "M631.2" ]; private static readonly Dictionary _seeAlso = new() { @@ -52,7 +52,7 @@ public IActionResult MCode(string mCode) bool seeAlsoExists = _seeAlso.TryGetValue(mCode, out _); MCodeModel mCodeModel = new(GetModelData(), mCode, menuData, - seeAlsoExists ? _seeAlso[mCode] : Array.Empty()); + seeAlsoExists ? _seeAlso[mCode] : []); mCodeModel.Breadcrumbs.Add(new BreadcrumbItem(GSend.Language.Resources.BreadcrumbMCodes, "/MCodes/Index", false)); mCodeModel.Breadcrumbs.Add(new BreadcrumbItem(mCode, $"/MCodes/{mCode}/", false));