From 4974e57221bc6b09c6f0c1461afcaa57b6b7ebf8 Mon Sep 17 00:00:00 2001 From: Mark van Renswoude Date: Tue, 9 Mar 2021 18:42:49 +0100 Subject: [PATCH 1/3] Refactored settings views Improved global exception handling Added plugins page --- Windows/MassiveKnob/App.xaml.cs | 8 +- Windows/MassiveKnob/ContainerBuilder.cs | 48 +++++ Windows/MassiveKnob/Core/IPluginManager.cs | 9 + Windows/MassiveKnob/Core/PluginManager.cs | 29 ++- Windows/MassiveKnob/MassiveKnob.csproj | 73 +++---- Windows/MassiveKnob/Program.cs | 38 ++-- Windows/MassiveKnob/Resources/Plugins.xaml | 17 ++ .../Settings/MassiveKnobSettings.cs | 3 +- Windows/MassiveKnob/Strings.Designer.cs | 9 + Windows/MassiveKnob/Strings.resx | 3 + Windows/MassiveKnob/Style.xaml | 19 +- Windows/MassiveKnob/View/InputOutputView.xaml | 65 ------- .../MassiveKnob/View/InputOutputView.xaml.cs | 13 -- .../View/Settings/AnalogInputsView.xaml | 29 --- .../View/Settings/AnalogInputsView.xaml.cs | 13 -- .../View/Settings/AnalogOutputsView.xaml | 28 --- .../View/Settings/AnalogOutputsView.xaml.cs | 13 -- .../Settings/BaseSettingsInputOutputView.xaml | 75 ++++++++ .../BaseSettingsInputOutputView.xaml.cs | 16 ++ .../View/Settings/DeviceView.xaml.cs | 13 -- .../View/Settings/DigitalInputsView.xaml | 28 --- .../View/Settings/DigitalInputsView.xaml.cs | 13 -- .../View/Settings/DigitalOutputsView.xaml | 28 --- .../View/Settings/DigitalOutputsView.xaml.cs | 13 -- .../View/Settings/LoggingView.xaml.cs | 13 -- .../View/Settings/SettingsAnalogInputsView.cs | 12 ++ .../Settings/SettingsAnalogOutputsView.cs | 12 ++ ...eviceView.xaml => SettingsDeviceView.xaml} | 5 +- .../View/Settings/SettingsDeviceView.xaml.cs | 16 ++ .../Settings/SettingsDigitalInputsView.cs | 12 ++ .../Settings/SettingsDigitalOutputsView.cs | 12 ++ ...gingView.xaml => SettingsLoggingView.xaml} | 2 +- .../View/Settings/SettingsLoggingView.xaml.cs | 16 ++ .../View/Settings/SettingsPluginsView.xaml | 29 +++ .../View/Settings/SettingsPluginsView.xaml.cs | 16 ++ ...rtupView.xaml => SettingsStartupView.xaml} | 2 +- .../View/Settings/SettingsStartupView.xaml.cs | 16 ++ .../View/Settings/StartupView.xaml.cs | 13 -- Windows/MassiveKnob/View/SettingsWindow.xaml | 2 + .../ViewModel/InputOutputViewModel.cs | 4 +- .../MassiveKnob/ViewModel/PluginViewModel.cs | 23 +++ .../BaseSettingsInputOutputViewModel.cs | 148 +++++++++++++++ .../Settings/SettingsAnalogInputsViewModel.cs | 13 ++ .../SettingsAnalogOutputsViewModel.cs | 13 ++ .../Settings/SettingsDeviceViewModel.cs | 165 ++++++++++++++++ .../SettingsDigitalInputsViewModel.cs | 13 ++ .../SettingsDigitalOutputsViewModel.cs | 13 ++ .../Settings/SettingsLoggingViewModel.cs | 106 +++++++++++ .../Settings/SettingsPluginsViewModel.cs | 41 ++++ .../Settings/SettingsStartupViewModel.cs | 67 +++++++ .../ViewModel/SettingsViewModel.cs | 178 ++++-------------- 51 files changed, 1077 insertions(+), 488 deletions(-) create mode 100644 Windows/MassiveKnob/ContainerBuilder.cs create mode 100644 Windows/MassiveKnob/Resources/Plugins.xaml delete mode 100644 Windows/MassiveKnob/View/InputOutputView.xaml delete mode 100644 Windows/MassiveKnob/View/InputOutputView.xaml.cs delete mode 100644 Windows/MassiveKnob/View/Settings/AnalogInputsView.xaml delete mode 100644 Windows/MassiveKnob/View/Settings/AnalogInputsView.xaml.cs delete mode 100644 Windows/MassiveKnob/View/Settings/AnalogOutputsView.xaml delete mode 100644 Windows/MassiveKnob/View/Settings/AnalogOutputsView.xaml.cs create mode 100644 Windows/MassiveKnob/View/Settings/BaseSettingsInputOutputView.xaml create mode 100644 Windows/MassiveKnob/View/Settings/BaseSettingsInputOutputView.xaml.cs delete mode 100644 Windows/MassiveKnob/View/Settings/DeviceView.xaml.cs delete mode 100644 Windows/MassiveKnob/View/Settings/DigitalInputsView.xaml delete mode 100644 Windows/MassiveKnob/View/Settings/DigitalInputsView.xaml.cs delete mode 100644 Windows/MassiveKnob/View/Settings/DigitalOutputsView.xaml delete mode 100644 Windows/MassiveKnob/View/Settings/DigitalOutputsView.xaml.cs delete mode 100644 Windows/MassiveKnob/View/Settings/LoggingView.xaml.cs create mode 100644 Windows/MassiveKnob/View/Settings/SettingsAnalogInputsView.cs create mode 100644 Windows/MassiveKnob/View/Settings/SettingsAnalogOutputsView.cs rename Windows/MassiveKnob/View/Settings/{DeviceView.xaml => SettingsDeviceView.xaml} (90%) create mode 100644 Windows/MassiveKnob/View/Settings/SettingsDeviceView.xaml.cs create mode 100644 Windows/MassiveKnob/View/Settings/SettingsDigitalInputsView.cs create mode 100644 Windows/MassiveKnob/View/Settings/SettingsDigitalOutputsView.cs rename Windows/MassiveKnob/View/Settings/{LoggingView.xaml => SettingsLoggingView.xaml} (97%) create mode 100644 Windows/MassiveKnob/View/Settings/SettingsLoggingView.xaml.cs create mode 100644 Windows/MassiveKnob/View/Settings/SettingsPluginsView.xaml create mode 100644 Windows/MassiveKnob/View/Settings/SettingsPluginsView.xaml.cs rename Windows/MassiveKnob/View/Settings/{StartupView.xaml => SettingsStartupView.xaml} (95%) create mode 100644 Windows/MassiveKnob/View/Settings/SettingsStartupView.xaml.cs delete mode 100644 Windows/MassiveKnob/View/Settings/StartupView.xaml.cs create mode 100644 Windows/MassiveKnob/ViewModel/PluginViewModel.cs create mode 100644 Windows/MassiveKnob/ViewModel/Settings/BaseSettingsInputOutputViewModel.cs create mode 100644 Windows/MassiveKnob/ViewModel/Settings/SettingsAnalogInputsViewModel.cs create mode 100644 Windows/MassiveKnob/ViewModel/Settings/SettingsAnalogOutputsViewModel.cs create mode 100644 Windows/MassiveKnob/ViewModel/Settings/SettingsDeviceViewModel.cs create mode 100644 Windows/MassiveKnob/ViewModel/Settings/SettingsDigitalInputsViewModel.cs create mode 100644 Windows/MassiveKnob/ViewModel/Settings/SettingsDigitalOutputsViewModel.cs create mode 100644 Windows/MassiveKnob/ViewModel/Settings/SettingsLoggingViewModel.cs create mode 100644 Windows/MassiveKnob/ViewModel/Settings/SettingsPluginsViewModel.cs create mode 100644 Windows/MassiveKnob/ViewModel/Settings/SettingsStartupViewModel.cs diff --git a/Windows/MassiveKnob/App.xaml.cs b/Windows/MassiveKnob/App.xaml.cs index b0b699b..22f38de 100644 --- a/Windows/MassiveKnob/App.xaml.cs +++ b/Windows/MassiveKnob/App.xaml.cs @@ -1,8 +1,11 @@ -using System.Diagnostics; +using System; +using System.Diagnostics; using System.Windows; using Hardcodet.Wpf.TaskbarNotification; using MassiveKnob.View; +using Serilog; using SimpleInjector; +using WpfBindingErrors; namespace MassiveKnob { @@ -28,6 +31,9 @@ namespace MassiveKnob { base.OnStartup(e); + // Do not let WPF swallow exceptions in bindings + BindingExceptionThrower.Attach(); + notifyIcon = (TaskbarIcon)FindResource("NotifyIcon"); Debug.Assert(notifyIcon != null, nameof(notifyIcon) + " != null"); } diff --git a/Windows/MassiveKnob/ContainerBuilder.cs b/Windows/MassiveKnob/ContainerBuilder.cs new file mode 100644 index 0000000..65818fc --- /dev/null +++ b/Windows/MassiveKnob/ContainerBuilder.cs @@ -0,0 +1,48 @@ +using MassiveKnob.View; +using MassiveKnob.View.Settings; +using MassiveKnob.ViewModel; +using MassiveKnob.ViewModel.Settings; +using SimpleInjector; + +namespace MassiveKnob +{ + public static class ContainerBuilder + { + public static Container Create() + { + var container = new Container(); + container.Options.EnableAutoVerification = false; + + container.Register(); + + container.Register(); + container.Register(); + + container.Register(); + container.Register(); + + container.Register(); + container.Register(); + + container.Register(); + container.Register(); + + container.Register(); + container.Register(); + + container.Register(); + container.Register(); + + container.Register(); + container.Register(); + + container.Register(); + container.Register(); + + container.Register(); + container.Register(); + + return container; + } + } +} diff --git a/Windows/MassiveKnob/Core/IPluginManager.cs b/Windows/MassiveKnob/Core/IPluginManager.cs index bbc8710..35b27ce 100644 --- a/Windows/MassiveKnob/Core/IPluginManager.cs +++ b/Windows/MassiveKnob/Core/IPluginManager.cs @@ -5,7 +5,16 @@ namespace MassiveKnob.Core { public interface IPluginManager { + IEnumerable GetPlugins(); + IEnumerable GetDevicePlugins(); IEnumerable GetActionPlugins(); } + + + public interface IMassiveKnobPluginInfo + { + string Filename { get; } + IMassiveKnobPlugin Plugin { get; } + } } diff --git a/Windows/MassiveKnob/Core/PluginManager.cs b/Windows/MassiveKnob/Core/PluginManager.cs index e5434b3..e45c8bb 100644 --- a/Windows/MassiveKnob/Core/PluginManager.cs +++ b/Windows/MassiveKnob/Core/PluginManager.cs @@ -34,23 +34,28 @@ namespace MassiveKnob.Core public class PluginManager : IPluginManager { private readonly ILogger logger; - private readonly List plugins = new List(); + private readonly List plugins = new List(); public PluginManager(ILogger logger) { this.logger = logger; } - + + + public IEnumerable GetPlugins() + { + return plugins; + } public IEnumerable GetDevicePlugins() { - return plugins.Where(p => p is IMassiveKnobDevicePlugin).Cast(); + return plugins.Where(p => p.Plugin is IMassiveKnobDevicePlugin).Select(p => (IMassiveKnobDevicePlugin)p.Plugin); } public IEnumerable GetActionPlugins() { - return plugins.Where(p => p is IMassiveKnobActionPlugin).Cast(); + return plugins.Where(p => p.Plugin is IMassiveKnobActionPlugin).Select(p => (IMassiveKnobActionPlugin)p.Plugin); } @@ -190,7 +195,7 @@ namespace MassiveKnob.Core logger.Information("Found plugin with Id {pluginId}: {name}", plugin.PluginId, plugin.Name); ValidateRegistration(filename, plugin, registeredIds); - plugins.Add((IMassiveKnobPlugin)pluginInstance); + plugins.Add(new PluginInfo(filename, (IMassiveKnobPlugin)pluginInstance)); } } @@ -280,5 +285,19 @@ namespace MassiveKnob.Core // ReSharper disable once UnusedAutoPropertyAccessor.Local - for JSON deserialization public string EntryAssembly { get; set; } } + + + private class PluginInfo : IMassiveKnobPluginInfo + { + public string Filename { get; } + public IMassiveKnobPlugin Plugin { get; } + + + public PluginInfo(string filename, IMassiveKnobPlugin plugin) + { + Filename = filename; + Plugin = plugin; + } + } } } diff --git a/Windows/MassiveKnob/MassiveKnob.csproj b/Windows/MassiveKnob/MassiveKnob.csproj index 5756b27..0e94192 100644 --- a/Windows/MassiveKnob/MassiveKnob.csproj +++ b/Windows/MassiveKnob/MassiveKnob.csproj @@ -58,6 +58,7 @@ + @@ -72,10 +73,17 @@ + - - InputOutputView.xaml - + + + + + + + + + SettingsWindow.xaml @@ -88,26 +96,24 @@ True Strings.resx - - AnalogInputsView.xaml + + BaseSettingsInputOutputView.xaml - - AnalogOutputsView.xaml + + + + + + SettingsPluginsView.xaml - - StartupView.xaml + + SettingsStartupView.xaml - - LoggingView.xaml + + SettingsLoggingView.xaml - - DigitalInputsView.xaml - - - DigitalOutputsView.xaml - - - DeviceView.xaml + + SettingsDeviceView.xaml PublicResXFileCodeGenerator @@ -158,6 +164,9 @@ 5.0.0 + + 1.1.0 + @@ -192,35 +201,31 @@ MSBuild:Compile Designer + + MSBuild:Compile + Designer + MSBuild:Compile Designer - + Designer MSBuild:Compile - - Designer - MSBuild:Compile - - + MSBuild:Compile Designer - + MSBuild:Compile Designer - - Designer + MSBuild:Compile - - Designer - MSBuild:Compile - + Designer MSBuild:Compile @@ -230,10 +235,6 @@ Designer MSBuild:Compile - - Designer - MSBuild:Compile - Designer MSBuild:Compile diff --git a/Windows/MassiveKnob/Program.cs b/Windows/MassiveKnob/Program.cs index 0e64f6f..ad4f8a9 100644 --- a/Windows/MassiveKnob/Program.cs +++ b/Windows/MassiveKnob/Program.cs @@ -4,10 +4,7 @@ using System.Text; using System.Windows; using MassiveKnob.Core; using MassiveKnob.Settings; -using MassiveKnob.View; -using MassiveKnob.ViewModel; using Serilog; -using SimpleInjector; namespace MassiveKnob { @@ -19,23 +16,40 @@ namespace MassiveKnob { var settings = MassiveKnobSettingsJsonSerializer.Deserialize(); + var loggingSwitch = new LoggingSwitch(); loggingSwitch.SetLogging(settings.Log.Enabled, settings.Log.Level); + var logFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"MassiveKnob", @"Logs"); + var logger = new LoggerConfiguration() .MinimumLevel.Verbose() .Filter.ByIncludingOnly(loggingSwitch.IsIncluded) .Enrich.FromLogContext() .WriteTo.File( - Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"MassiveKnob", @"Logs", @".log"), + Path.Combine(logFilePath, @".log"), rollingInterval: RollingInterval.Day, outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{@Context}{NewLine}{Exception}") .CreateLogger(); logger.Information("MassiveKnob starting"); - var pluginManager = new PluginManager(logger); + + AppDomain.CurrentDomain.UnhandledException += (sender, args) => + { + var e = (Exception)args.ExceptionObject; + logger.Error(e, "Unhandled exception: {message}", e.Message); + + MessageBox.Show( + "Oops, something went very wrong. Please notify the developer and include this message, you can copy it using Ctrl-C. " + + "Preferably also include the log file which can be found at:" + Environment.NewLine + logFilePath + + Environment.NewLine + Environment.NewLine + + e.Message, "Massive Knob - Fatal error", MessageBoxButton.OK, MessageBoxImage.Error); + }; + + + var pluginManager = new PluginManager(logger); var messages = new StringBuilder(); pluginManager.Load((exception, filename) => { @@ -48,23 +62,17 @@ namespace MassiveKnob return 1; } + var orchestrator = new MassiveKnobOrchestrator(pluginManager, logger, settings); orchestrator.Load(); - - var container = new Container(); - container.Options.EnableAutoVerification = false; - - container.RegisterInstance(logger); + + var container = ContainerBuilder.Create(); + container.RegisterInstance(logger); container.RegisterInstance(loggingSwitch); container.RegisterInstance(pluginManager); container.RegisterInstance(orchestrator); - container.Register(); - container.Register(); - container.Register(); - - var app = container.GetInstance(); app.Run(); diff --git a/Windows/MassiveKnob/Resources/Plugins.xaml b/Windows/MassiveKnob/Resources/Plugins.xaml new file mode 100644 index 0000000..a258dc4 --- /dev/null +++ b/Windows/MassiveKnob/Resources/Plugins.xaml @@ -0,0 +1,17 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/Windows/MassiveKnob/Settings/MassiveKnobSettings.cs b/Windows/MassiveKnob/Settings/MassiveKnobSettings.cs index b56c4f1..3f0d88f 100644 --- a/Windows/MassiveKnob/Settings/MassiveKnobSettings.cs +++ b/Windows/MassiveKnob/Settings/MassiveKnobSettings.cs @@ -15,7 +15,8 @@ namespace MassiveKnob.Settings AnalogOutputs, DigitalOutputs, Logging, - Startup + Startup, + Plugins } diff --git a/Windows/MassiveKnob/Strings.Designer.cs b/Windows/MassiveKnob/Strings.Designer.cs index b34191f..10def61 100644 --- a/Windows/MassiveKnob/Strings.Designer.cs +++ b/Windows/MassiveKnob/Strings.Designer.cs @@ -303,6 +303,15 @@ namespace MassiveKnob { } } + /// + /// Looks up a localized string similar to Plugins. + /// + public static string MenuItemPlugins { + get { + return ResourceManager.GetString("MenuItemPlugins", resourceCulture); + } + } + /// /// Looks up a localized string similar to Startup. /// diff --git a/Windows/MassiveKnob/Strings.resx b/Windows/MassiveKnob/Strings.resx index 58fdfc1..021591c 100644 --- a/Windows/MassiveKnob/Strings.resx +++ b/Windows/MassiveKnob/Strings.resx @@ -198,6 +198,9 @@ Logging + + Plugins + Startup diff --git a/Windows/MassiveKnob/Style.xaml b/Windows/MassiveKnob/Style.xaml index 37a8685..8193282 100644 --- a/Windows/MassiveKnob/Style.xaml +++ b/Windows/MassiveKnob/Style.xaml @@ -22,8 +22,8 @@ - + + + + + + \ No newline at end of file diff --git a/Windows/MassiveKnob/View/InputOutputView.xaml b/Windows/MassiveKnob/View/InputOutputView.xaml deleted file mode 100644 index 99fc97f..0000000 --- a/Windows/MassiveKnob/View/InputOutputView.xaml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Windows/MassiveKnob/View/InputOutputView.xaml.cs b/Windows/MassiveKnob/View/InputOutputView.xaml.cs deleted file mode 100644 index 5d4d6a5..0000000 --- a/Windows/MassiveKnob/View/InputOutputView.xaml.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace MassiveKnob.View -{ - /// - /// Interaction logic for InputOutputView.xaml - /// - public partial class InputOutputView - { - public InputOutputView() - { - InitializeComponent(); - } - } -} diff --git a/Windows/MassiveKnob/View/Settings/AnalogInputsView.xaml b/Windows/MassiveKnob/View/Settings/AnalogInputsView.xaml deleted file mode 100644 index c2a890e..0000000 --- a/Windows/MassiveKnob/View/Settings/AnalogInputsView.xaml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/Windows/MassiveKnob/View/Settings/AnalogInputsView.xaml.cs b/Windows/MassiveKnob/View/Settings/AnalogInputsView.xaml.cs deleted file mode 100644 index 4b0a7e4..0000000 --- a/Windows/MassiveKnob/View/Settings/AnalogInputsView.xaml.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace MassiveKnob.View.Settings -{ - /// - /// Interaction logic for AnalogInputsView.xaml - /// - public partial class AnalogInputsView - { - public AnalogInputsView() - { - InitializeComponent(); - } - } -} diff --git a/Windows/MassiveKnob/View/Settings/AnalogOutputsView.xaml b/Windows/MassiveKnob/View/Settings/AnalogOutputsView.xaml deleted file mode 100644 index 19e8585..0000000 --- a/Windows/MassiveKnob/View/Settings/AnalogOutputsView.xaml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Windows/MassiveKnob/View/Settings/AnalogOutputsView.xaml.cs b/Windows/MassiveKnob/View/Settings/AnalogOutputsView.xaml.cs deleted file mode 100644 index b0b0d57..0000000 --- a/Windows/MassiveKnob/View/Settings/AnalogOutputsView.xaml.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace MassiveKnob.View.Settings -{ - /// - /// Interaction logic for AnalogOutputsView.xaml - /// - public partial class AnalogOutputsView - { - public AnalogOutputsView() - { - InitializeComponent(); - } - } -} diff --git a/Windows/MassiveKnob/View/Settings/BaseSettingsInputOutputView.xaml b/Windows/MassiveKnob/View/Settings/BaseSettingsInputOutputView.xaml new file mode 100644 index 0000000..869b80f --- /dev/null +++ b/Windows/MassiveKnob/View/Settings/BaseSettingsInputOutputView.xaml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Windows/MassiveKnob/View/Settings/BaseSettingsInputOutputView.xaml.cs b/Windows/MassiveKnob/View/Settings/BaseSettingsInputOutputView.xaml.cs new file mode 100644 index 0000000..1933399 --- /dev/null +++ b/Windows/MassiveKnob/View/Settings/BaseSettingsInputOutputView.xaml.cs @@ -0,0 +1,16 @@ +using MassiveKnob.ViewModel.Settings; + +namespace MassiveKnob.View.Settings +{ + /// + /// Interaction logic for BaseSettingsInputOutputView.xaml + /// + public partial class BaseSettingsInputOutputView + { + public BaseSettingsInputOutputView(BaseSettingsInputOutputViewModel viewModel) + { + DataContext = viewModel; + InitializeComponent(); + } + } +} diff --git a/Windows/MassiveKnob/View/Settings/DeviceView.xaml.cs b/Windows/MassiveKnob/View/Settings/DeviceView.xaml.cs deleted file mode 100644 index 5cfec2b..0000000 --- a/Windows/MassiveKnob/View/Settings/DeviceView.xaml.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace MassiveKnob.View.Settings -{ - /// - /// Interaction logic for DeviceView.xaml - /// - public partial class DeviceView - { - public DeviceView() - { - InitializeComponent(); - } - } -} diff --git a/Windows/MassiveKnob/View/Settings/DigitalInputsView.xaml b/Windows/MassiveKnob/View/Settings/DigitalInputsView.xaml deleted file mode 100644 index 652fc93..0000000 --- a/Windows/MassiveKnob/View/Settings/DigitalInputsView.xaml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Windows/MassiveKnob/View/Settings/DigitalInputsView.xaml.cs b/Windows/MassiveKnob/View/Settings/DigitalInputsView.xaml.cs deleted file mode 100644 index 189a997..0000000 --- a/Windows/MassiveKnob/View/Settings/DigitalInputsView.xaml.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace MassiveKnob.View.Settings -{ - /// - /// Interaction logic for DigitalInputsView.xaml - /// - public partial class DigitalInputsView - { - public DigitalInputsView() - { - InitializeComponent(); - } - } -} diff --git a/Windows/MassiveKnob/View/Settings/DigitalOutputsView.xaml b/Windows/MassiveKnob/View/Settings/DigitalOutputsView.xaml deleted file mode 100644 index 87d67d9..0000000 --- a/Windows/MassiveKnob/View/Settings/DigitalOutputsView.xaml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Windows/MassiveKnob/View/Settings/DigitalOutputsView.xaml.cs b/Windows/MassiveKnob/View/Settings/DigitalOutputsView.xaml.cs deleted file mode 100644 index 61f2a1c..0000000 --- a/Windows/MassiveKnob/View/Settings/DigitalOutputsView.xaml.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace MassiveKnob.View.Settings -{ - /// - /// Interaction logic for DigitalOutputsView.xaml - /// - public partial class DigitalOutputsView - { - public DigitalOutputsView() - { - InitializeComponent(); - } - } -} diff --git a/Windows/MassiveKnob/View/Settings/LoggingView.xaml.cs b/Windows/MassiveKnob/View/Settings/LoggingView.xaml.cs deleted file mode 100644 index 7fe2838..0000000 --- a/Windows/MassiveKnob/View/Settings/LoggingView.xaml.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace MassiveKnob.View.Settings -{ - /// - /// Interaction logic for LoggingView.xaml - /// - public partial class LoggingView - { - public LoggingView() - { - InitializeComponent(); - } - } -} diff --git a/Windows/MassiveKnob/View/Settings/SettingsAnalogInputsView.cs b/Windows/MassiveKnob/View/Settings/SettingsAnalogInputsView.cs new file mode 100644 index 0000000..7438f59 --- /dev/null +++ b/Windows/MassiveKnob/View/Settings/SettingsAnalogInputsView.cs @@ -0,0 +1,12 @@ +using MassiveKnob.ViewModel.Settings; + +namespace MassiveKnob.View.Settings +{ + public class SettingsAnalogInputsView : BaseSettingsInputOutputView + { + // ReSharper disable once SuggestBaseTypeForParameter - required for injection + public SettingsAnalogInputsView(SettingsAnalogInputsViewModel viewModel) : base(viewModel) + { + } + } +} diff --git a/Windows/MassiveKnob/View/Settings/SettingsAnalogOutputsView.cs b/Windows/MassiveKnob/View/Settings/SettingsAnalogOutputsView.cs new file mode 100644 index 0000000..238b49f --- /dev/null +++ b/Windows/MassiveKnob/View/Settings/SettingsAnalogOutputsView.cs @@ -0,0 +1,12 @@ +using MassiveKnob.ViewModel.Settings; + +namespace MassiveKnob.View.Settings +{ + public class SettingsAnalogOutputsView : BaseSettingsInputOutputView + { + // ReSharper disable once SuggestBaseTypeForParameter - required for injection + public SettingsAnalogOutputsView(SettingsAnalogOutputsViewModel viewModel) : base(viewModel) + { + } + } +} diff --git a/Windows/MassiveKnob/View/Settings/DeviceView.xaml b/Windows/MassiveKnob/View/Settings/SettingsDeviceView.xaml similarity index 90% rename from Windows/MassiveKnob/View/Settings/DeviceView.xaml rename to Windows/MassiveKnob/View/Settings/SettingsDeviceView.xaml index 334a619..f5fb62a 100644 --- a/Windows/MassiveKnob/View/Settings/DeviceView.xaml +++ b/Windows/MassiveKnob/View/Settings/SettingsDeviceView.xaml @@ -1,13 +1,14 @@ - + d:DataContext="{d:DesignInstance Type=settings:SettingsDeviceViewModelDesignTime, IsDesignTimeCreatable=True}"> diff --git a/Windows/MassiveKnob/View/Settings/SettingsDeviceView.xaml.cs b/Windows/MassiveKnob/View/Settings/SettingsDeviceView.xaml.cs new file mode 100644 index 0000000..e655ca8 --- /dev/null +++ b/Windows/MassiveKnob/View/Settings/SettingsDeviceView.xaml.cs @@ -0,0 +1,16 @@ +using MassiveKnob.ViewModel.Settings; + +namespace MassiveKnob.View.Settings +{ + /// + /// Interaction logic for SettingsDeviceView.xaml + /// + public partial class SettingsDeviceView + { + public SettingsDeviceView(SettingsDeviceViewModel viewModel) + { + DataContext = viewModel; + InitializeComponent(); + } + } +} diff --git a/Windows/MassiveKnob/View/Settings/SettingsDigitalInputsView.cs b/Windows/MassiveKnob/View/Settings/SettingsDigitalInputsView.cs new file mode 100644 index 0000000..dfe6d3a --- /dev/null +++ b/Windows/MassiveKnob/View/Settings/SettingsDigitalInputsView.cs @@ -0,0 +1,12 @@ +using MassiveKnob.ViewModel.Settings; + +namespace MassiveKnob.View.Settings +{ + public class SettingsDigitalInputsView : BaseSettingsInputOutputView + { + // ReSharper disable once SuggestBaseTypeForParameter - required for injection + public SettingsDigitalInputsView(SettingsDigitalInputsViewModel viewModel) : base(viewModel) + { + } + } +} diff --git a/Windows/MassiveKnob/View/Settings/SettingsDigitalOutputsView.cs b/Windows/MassiveKnob/View/Settings/SettingsDigitalOutputsView.cs new file mode 100644 index 0000000..fc1239e --- /dev/null +++ b/Windows/MassiveKnob/View/Settings/SettingsDigitalOutputsView.cs @@ -0,0 +1,12 @@ +using MassiveKnob.ViewModel.Settings; + +namespace MassiveKnob.View.Settings +{ + public class SettingsDigitalOutputsView : BaseSettingsInputOutputView + { + // ReSharper disable once SuggestBaseTypeForParameter - required for injection + public SettingsDigitalOutputsView(SettingsDigitalOutputsViewModel viewModel) : base(viewModel) + { + } + } +} diff --git a/Windows/MassiveKnob/View/Settings/LoggingView.xaml b/Windows/MassiveKnob/View/Settings/SettingsLoggingView.xaml similarity index 97% rename from Windows/MassiveKnob/View/Settings/LoggingView.xaml rename to Windows/MassiveKnob/View/Settings/SettingsLoggingView.xaml index 5a53d07..0aee137 100644 --- a/Windows/MassiveKnob/View/Settings/LoggingView.xaml +++ b/Windows/MassiveKnob/View/Settings/SettingsLoggingView.xaml @@ -1,4 +1,4 @@ - + /// Interaction logic for SettingsLoggingView.xaml + /// + public partial class SettingsLoggingView + { + public SettingsLoggingView(SettingsLoggingViewModel viewModel) + { + DataContext = viewModel; + InitializeComponent(); + } + } +} diff --git a/Windows/MassiveKnob/View/Settings/SettingsPluginsView.xaml b/Windows/MassiveKnob/View/Settings/SettingsPluginsView.xaml new file mode 100644 index 0000000..3006f35 --- /dev/null +++ b/Windows/MassiveKnob/View/Settings/SettingsPluginsView.xaml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Windows/MassiveKnob/View/Settings/SettingsPluginsView.xaml.cs b/Windows/MassiveKnob/View/Settings/SettingsPluginsView.xaml.cs new file mode 100644 index 0000000..62c5ca9 --- /dev/null +++ b/Windows/MassiveKnob/View/Settings/SettingsPluginsView.xaml.cs @@ -0,0 +1,16 @@ +using MassiveKnob.ViewModel.Settings; + +namespace MassiveKnob.View.Settings +{ + /// + /// Interaction logic for SettingsPluginsView.xaml + /// + public partial class SettingsPluginsView + { + public SettingsPluginsView(SettingsPluginsViewModel viewModel) + { + DataContext = viewModel; + InitializeComponent(); + } + } +} diff --git a/Windows/MassiveKnob/View/Settings/StartupView.xaml b/Windows/MassiveKnob/View/Settings/SettingsStartupView.xaml similarity index 95% rename from Windows/MassiveKnob/View/Settings/StartupView.xaml rename to Windows/MassiveKnob/View/Settings/SettingsStartupView.xaml index f9ce4c2..f3caa84 100644 --- a/Windows/MassiveKnob/View/Settings/StartupView.xaml +++ b/Windows/MassiveKnob/View/Settings/SettingsStartupView.xaml @@ -1,4 +1,4 @@ - + /// Interaction logic for SettingsStartupView.xaml + /// + public partial class SettingsStartupView + { + public SettingsStartupView(SettingsStartupViewModel viewModel) + { + DataContext = viewModel; + InitializeComponent(); + } + } +} diff --git a/Windows/MassiveKnob/View/Settings/StartupView.xaml.cs b/Windows/MassiveKnob/View/Settings/StartupView.xaml.cs deleted file mode 100644 index b8ee75e..0000000 --- a/Windows/MassiveKnob/View/Settings/StartupView.xaml.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace MassiveKnob.View.Settings -{ - /// - /// Interaction logic for StartupView.xaml - /// - public partial class StartupView - { - public StartupView() - { - InitializeComponent(); - } - } -} diff --git a/Windows/MassiveKnob/View/SettingsWindow.xaml b/Windows/MassiveKnob/View/SettingsWindow.xaml index ddc16e1..84c9b00 100644 --- a/Windows/MassiveKnob/View/SettingsWindow.xaml +++ b/Windows/MassiveKnob/View/SettingsWindow.xaml @@ -23,6 +23,7 @@ + @@ -81,6 +82,7 @@ + diff --git a/Windows/MassiveKnob/ViewModel/InputOutputViewModel.cs b/Windows/MassiveKnob/ViewModel/InputOutputViewModel.cs index 77c3d11..8e4a544 100644 --- a/Windows/MassiveKnob/ViewModel/InputOutputViewModel.cs +++ b/Windows/MassiveKnob/ViewModel/InputOutputViewModel.cs @@ -125,7 +125,7 @@ namespace MassiveKnob.ViewModel // ReSharper restore UnusedMember.Global - public InputOutputViewModel(SettingsViewModel settingsViewModel, IMassiveKnobOrchestrator orchestrator, + public InputOutputViewModel(IEnumerable allActions, IMassiveKnobOrchestrator orchestrator, MassiveKnobActionType actionType, int index) { this.orchestrator = orchestrator; @@ -155,7 +155,7 @@ namespace MassiveKnob.ViewModel } - Actions = settingsViewModel.Actions.Where(AllowAction).ToList(); + Actions = allActions.Where(AllowAction).ToList(); var actionInfo = orchestrator.GetAction(actionType, index); diff --git a/Windows/MassiveKnob/ViewModel/PluginViewModel.cs b/Windows/MassiveKnob/ViewModel/PluginViewModel.cs new file mode 100644 index 0000000..5d292e0 --- /dev/null +++ b/Windows/MassiveKnob/ViewModel/PluginViewModel.cs @@ -0,0 +1,23 @@ +using System.Windows; + +namespace MassiveKnob.ViewModel +{ + public class PluginViewModel + { + // ReSharper disable UnusedMember.Global - used by WPF Binding + public string Name { get; } + public string Description { get; } + public string Filename { get; } + + public Visibility DescriptionVisibility => string.IsNullOrEmpty(Description) ? Visibility.Collapsed : Visibility.Visible; + // ReSharper restore UnusedMember.Global + + + public PluginViewModel(string name, string description, string filename) + { + Name = name; + Description = description; + Filename = filename; + } + } +} diff --git a/Windows/MassiveKnob/ViewModel/Settings/BaseSettingsInputOutputViewModel.cs b/Windows/MassiveKnob/ViewModel/Settings/BaseSettingsInputOutputViewModel.cs new file mode 100644 index 0000000..b3eb84d --- /dev/null +++ b/Windows/MassiveKnob/ViewModel/Settings/BaseSettingsInputOutputViewModel.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Windows; +using MassiveKnob.Core; +using MassiveKnob.Plugin; + +namespace MassiveKnob.ViewModel.Settings +{ + public class BaseSettingsInputOutputViewModel : IDisposable, INotifyPropertyChanged + { + private readonly IMassiveKnobOrchestrator orchestrator; + + private readonly MassiveKnobActionType inputOutputType; + private DeviceSpecs? specs; + private readonly IDisposable activeDeviceSubscription; + private IEnumerable inputOutputs; + + + // ReSharper disable UnusedMember.Global - used by WPF Binding + public IList Actions { get; } + + public DeviceSpecs? Specs + { + get => specs; + set + { + specs = value; + OnPropertyChanged(); + + DisposeInputOutputViewModels(); + int inputOutputCount; + + switch (inputOutputType) + { + case MassiveKnobActionType.InputAnalog: + inputOutputCount = specs?.AnalogInputCount ?? 0; + break; + + case MassiveKnobActionType.InputDigital: + inputOutputCount = specs?.DigitalInputCount ?? 0; + break; + + case MassiveKnobActionType.OutputAnalog: + inputOutputCount = specs?.AnalogOutputCount ?? 0; + break; + + case MassiveKnobActionType.OutputDigital: + inputOutputCount = specs?.DigitalOutputCount ?? 0; + break; + + default: + throw new ArgumentOutOfRangeException(); + } + + InputOutputs = Enumerable + .Range(0, inputOutputCount) + .Select(i => new InputOutputViewModel(Actions, orchestrator, inputOutputType, i)) + .ToList(); + } + } + + + public IEnumerable InputOutputs + { + get => inputOutputs; + set + { + inputOutputs = value; + OnPropertyChanged(); + } + } + // ReSharper restore UnusedMember.Global + + + public BaseSettingsInputOutputViewModel(IPluginManager pluginManager, IMassiveKnobOrchestrator orchestrator, MassiveKnobActionType inputOutputType) + { + this.orchestrator = orchestrator; + this.inputOutputType = inputOutputType; + + // For design-time support + if (orchestrator == null) + return; + + + var allActions = new List + { + new ActionViewModel(null, null) + }; + + allActions.AddRange( + pluginManager.GetActionPlugins() + .SelectMany(ap => ap.Actions.Select(a => new ActionViewModel(ap, a))) + .OrderBy(a => a.Name.ToLower())); + + Actions = allActions; + + + activeDeviceSubscription = orchestrator.ActiveDeviceSubject.Subscribe(info => + { + Application.Current?.Dispatcher.Invoke(() => + { + Specs = info.Specs; + }); + }); + + if (orchestrator.ActiveDevice != null) + Specs = orchestrator.ActiveDevice.Specs; + } + + + public void Dispose() + { + DisposeInputOutputViewModels(); + activeDeviceSubscription?.Dispose(); + } + + + private void DisposeInputOutputViewModels() + { + if (inputOutputs == null) + return; + + foreach (var viewModel in inputOutputs) + viewModel.Dispose(); + } + + + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } + + + public class BaseSettingsInputOutputViewModelDesignTime : BaseSettingsInputOutputViewModel + { + public BaseSettingsInputOutputViewModelDesignTime() + : base(null, null, MassiveKnobActionType.InputAnalog) + { + Specs = new DeviceSpecs(2, 2, 2, 2); + } + } +} diff --git a/Windows/MassiveKnob/ViewModel/Settings/SettingsAnalogInputsViewModel.cs b/Windows/MassiveKnob/ViewModel/Settings/SettingsAnalogInputsViewModel.cs new file mode 100644 index 0000000..8060fea --- /dev/null +++ b/Windows/MassiveKnob/ViewModel/Settings/SettingsAnalogInputsViewModel.cs @@ -0,0 +1,13 @@ +using MassiveKnob.Core; +using MassiveKnob.Plugin; + +namespace MassiveKnob.ViewModel.Settings +{ + public class SettingsAnalogInputsViewModel : BaseSettingsInputOutputViewModel + { + public SettingsAnalogInputsViewModel(IPluginManager pluginManager, IMassiveKnobOrchestrator orchestrator) + : base(pluginManager, orchestrator, MassiveKnobActionType.InputAnalog) + { + } + } +} diff --git a/Windows/MassiveKnob/ViewModel/Settings/SettingsAnalogOutputsViewModel.cs b/Windows/MassiveKnob/ViewModel/Settings/SettingsAnalogOutputsViewModel.cs new file mode 100644 index 0000000..4440fe0 --- /dev/null +++ b/Windows/MassiveKnob/ViewModel/Settings/SettingsAnalogOutputsViewModel.cs @@ -0,0 +1,13 @@ +using MassiveKnob.Core; +using MassiveKnob.Plugin; + +namespace MassiveKnob.ViewModel.Settings +{ + public class SettingsAnalogOutputsViewModel : BaseSettingsInputOutputViewModel + { + public SettingsAnalogOutputsViewModel(IPluginManager pluginManager, IMassiveKnobOrchestrator orchestrator) + : base(pluginManager, orchestrator, MassiveKnobActionType.OutputAnalog) + { + } + } +} diff --git a/Windows/MassiveKnob/ViewModel/Settings/SettingsDeviceViewModel.cs b/Windows/MassiveKnob/ViewModel/Settings/SettingsDeviceViewModel.cs new file mode 100644 index 0000000..a7e1a00 --- /dev/null +++ b/Windows/MassiveKnob/ViewModel/Settings/SettingsDeviceViewModel.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Windows.Controls; +using System.Windows.Media; +using MassiveKnob.Core; + +namespace MassiveKnob.ViewModel.Settings +{ + public class SettingsDeviceViewModel : IDisposable, INotifyPropertyChanged + { + private readonly IMassiveKnobOrchestrator orchestrator; + private DeviceViewModel selectedDevice; + private UserControl settingsControl; + + private readonly IDisposable deviceStatusSubscription; + + // ReSharper disable UnusedMember.Global - used by WPF Binding + public IList Devices { get; } + + + public DeviceViewModel SelectedDevice + { + get => selectedDevice; + set + { + if (value == selectedDevice) + return; + + selectedDevice = value; + var deviceInfo = orchestrator?.SetActiveDevice(value?.Device); + + OnPropertyChanged(); + + SettingsControl = deviceInfo?.Instance.CreateSettingsControl(); + } + } + + public UserControl SettingsControl + { + get => settingsControl; + set + { + if (value == settingsControl) + return; + + if (settingsControl is IDisposable disposable) + disposable.Dispose(); + + settingsControl = value; + OnPropertyChanged(); + } + } + + + public string ConnectionStatusText + { + get + { + if (orchestrator == null) + return "Design-time"; + + switch (orchestrator.DeviceStatus) + { + case MassiveKnobDeviceStatus.Disconnected: + return Strings.DeviceStatusDisconnected; + + case MassiveKnobDeviceStatus.Connecting: + return Strings.DeviceStatusConnecting; + + case MassiveKnobDeviceStatus.Connected: + return Strings.DeviceStatusConnected; + + default: + return null; + } + } + } + + public Brush ConnectionStatusColor + { + get + { + if (orchestrator == null) + return Brushes.Fuchsia; + + switch (orchestrator.DeviceStatus) + { + case MassiveKnobDeviceStatus.Disconnected: + return Brushes.DarkRed; + + case MassiveKnobDeviceStatus.Connecting: + return Brushes.Orange; + + case MassiveKnobDeviceStatus.Connected: + return Brushes.ForestGreen; + + default: + return null; + } + } + } + // ReSharper restore UnusedMember.Global + + + public SettingsDeviceViewModel(IPluginManager pluginManager, IMassiveKnobOrchestrator orchestrator) + { + this.orchestrator = orchestrator; + + // For design-time support + if (orchestrator == null) + return; + + deviceStatusSubscription = orchestrator.DeviceStatusSubject.Subscribe(status => + { + OnDependantPropertyChanged(nameof(ConnectionStatusColor)); + OnDependantPropertyChanged(nameof(ConnectionStatusText)); + }); + + + Devices = pluginManager.GetDevicePlugins() + .SelectMany(dp => dp.Devices.Select(d => new DeviceViewModel(dp, d))) + .OrderBy(d => d.Name.ToLower()) + .ToList(); + + if (orchestrator.ActiveDevice == null) + return; + + selectedDevice = Devices.Single(d => d.Device.DeviceId == orchestrator.ActiveDevice.Info.DeviceId); + SettingsControl = orchestrator.ActiveDevice.Instance.CreateSettingsControl(); + } + + + public void Dispose() + { + if (SettingsControl is IDisposable disposable) + disposable.Dispose(); + + deviceStatusSubscription?.Dispose(); + } + + + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + protected virtual void OnDependantPropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } + + + public class SettingsDeviceViewModelDesignTime : SettingsDeviceViewModel + { + public SettingsDeviceViewModelDesignTime() : base(null, null) + { + } + } +} diff --git a/Windows/MassiveKnob/ViewModel/Settings/SettingsDigitalInputsViewModel.cs b/Windows/MassiveKnob/ViewModel/Settings/SettingsDigitalInputsViewModel.cs new file mode 100644 index 0000000..4458ba6 --- /dev/null +++ b/Windows/MassiveKnob/ViewModel/Settings/SettingsDigitalInputsViewModel.cs @@ -0,0 +1,13 @@ +using MassiveKnob.Core; +using MassiveKnob.Plugin; + +namespace MassiveKnob.ViewModel.Settings +{ + public class SettingsDigitalInputsViewModel : BaseSettingsInputOutputViewModel + { + public SettingsDigitalInputsViewModel(IPluginManager pluginManager, IMassiveKnobOrchestrator orchestrator) + : base(pluginManager, orchestrator, MassiveKnobActionType.InputDigital) + { + } + } +} diff --git a/Windows/MassiveKnob/ViewModel/Settings/SettingsDigitalOutputsViewModel.cs b/Windows/MassiveKnob/ViewModel/Settings/SettingsDigitalOutputsViewModel.cs new file mode 100644 index 0000000..dec55c6 --- /dev/null +++ b/Windows/MassiveKnob/ViewModel/Settings/SettingsDigitalOutputsViewModel.cs @@ -0,0 +1,13 @@ +using MassiveKnob.Core; +using MassiveKnob.Plugin; + +namespace MassiveKnob.ViewModel.Settings +{ + public class SettingsDigitalOutputsViewModel : BaseSettingsInputOutputViewModel + { + public SettingsDigitalOutputsViewModel(IPluginManager pluginManager, IMassiveKnobOrchestrator orchestrator) + : base(pluginManager, orchestrator, MassiveKnobActionType.OutputDigital) + { + } + } +} diff --git a/Windows/MassiveKnob/ViewModel/Settings/SettingsLoggingViewModel.cs b/Windows/MassiveKnob/ViewModel/Settings/SettingsLoggingViewModel.cs new file mode 100644 index 0000000..bfb1bbb --- /dev/null +++ b/Windows/MassiveKnob/ViewModel/Settings/SettingsLoggingViewModel.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using MassiveKnob.Core; +using MassiveKnob.Settings; +using Serilog.Events; + +namespace MassiveKnob.ViewModel.Settings +{ + public class SettingsLoggingViewModel : INotifyPropertyChanged + { + private readonly IMassiveKnobOrchestrator orchestrator; + private readonly ILoggingSwitch loggingSwitch; + + // ReSharper disable UnusedMember.Global - used by WPF Binding + public IList LoggingLevels { get; } + + private LoggingLevelViewModel selectedLoggingLevel; + public LoggingLevelViewModel SelectedLoggingLevel + { + get => selectedLoggingLevel; + set + { + if (value == selectedLoggingLevel) + return; + + selectedLoggingLevel = value; + OnPropertyChanged(); + + ApplyLoggingSettings(); + } + } + + + private bool loggingEnabled; + public bool LoggingEnabled + { + get => loggingEnabled; + set + { + if (value == loggingEnabled) + return; + + loggingEnabled = value; + OnPropertyChanged(); + + ApplyLoggingSettings(); + } + } + + + // TODO (code quality) do not hardcode path here + public string LoggingOutputPath { get; } = string.Format(Strings.LoggingOutputPath, + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"MassiveKnob", + @"Logs")); + // ReSharper restore UnusedMember.Global + + + public SettingsLoggingViewModel(IMassiveKnobOrchestrator orchestrator, ILoggingSwitch loggingSwitch) + { + this.orchestrator = orchestrator; + this.loggingSwitch = loggingSwitch; + + // For design-time support + if (orchestrator == null) + return; + + + var logSettings = orchestrator.GetSettings().Log; + LoggingLevels = new List + { + new LoggingLevelViewModel(LogEventLevel.Error, Strings.LoggingLevelError, Strings.LoggingLevelErrorDescription), + new LoggingLevelViewModel(LogEventLevel.Warning, Strings.LoggingLevelWarning, Strings.LoggingLevelWarningDescription), + new LoggingLevelViewModel(LogEventLevel.Information, Strings.LoggingLevelInformation, Strings.LoggingLevelInformationDescription), + new LoggingLevelViewModel(LogEventLevel.Verbose, Strings.LoggingLevelVerbose, Strings.LoggingLevelVerboseDescription) + }; + + selectedLoggingLevel = LoggingLevels.SingleOrDefault(l => l.Level == logSettings.Level) + ?? LoggingLevels.Single(l => l.Level == LogEventLevel.Information); + loggingEnabled = logSettings.Enabled; + } + + + private void ApplyLoggingSettings() + { + orchestrator?.UpdateSettings(settings => + { + settings.Log.Enabled = LoggingEnabled; + settings.Log.Level = SelectedLoggingLevel.Level; + }); + + loggingSwitch?.SetLogging(LoggingEnabled, selectedLoggingLevel.Level); + } + + + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/Windows/MassiveKnob/ViewModel/Settings/SettingsPluginsViewModel.cs b/Windows/MassiveKnob/ViewModel/Settings/SettingsPluginsViewModel.cs new file mode 100644 index 0000000..d6a6211 --- /dev/null +++ b/Windows/MassiveKnob/ViewModel/Settings/SettingsPluginsViewModel.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using MassiveKnob.Core; + +namespace MassiveKnob.ViewModel.Settings +{ + public class SettingsPluginsViewModel + { + // ReSharper disable UnusedMember.Global - used by WPF Binding + public IEnumerable Plugins { get; protected set; } + // ReSharper restore UnusedMember.Global + + + public SettingsPluginsViewModel(IPluginManager pluginManager) + { + // Design-time support + if (pluginManager == null) + return; + + Plugins = pluginManager.GetPlugins() + .Select(p => new PluginViewModel(p.Plugin.Name, p.Plugin.Description, p.Filename)) + .OrderBy(p => p.Name, StringComparer.CurrentCultureIgnoreCase) + .ToList(); + } + } + + + public class SettingsPluginsViewModelDesignTime : SettingsPluginsViewModel + { + public SettingsPluginsViewModelDesignTime() + : base(null) + { + Plugins = new[] + { + new PluginViewModel("Plugin without description", null, "D:\\Does\\Not\\Exist.dll"), + new PluginViewModel("Design-time plugin", "Fake plugin only visible at design-time.", "C:\\Does\\Not\\Exist.dll") + }; + } + } +} diff --git a/Windows/MassiveKnob/ViewModel/Settings/SettingsStartupViewModel.cs b/Windows/MassiveKnob/ViewModel/Settings/SettingsStartupViewModel.cs new file mode 100644 index 0000000..fee4592 --- /dev/null +++ b/Windows/MassiveKnob/ViewModel/Settings/SettingsStartupViewModel.cs @@ -0,0 +1,67 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.CompilerServices; +using Microsoft.Win32; + +namespace MassiveKnob.ViewModel.Settings +{ + public class SettingsStartupViewModel : INotifyPropertyChanged + { + public const string RunKey = @"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"; + public const string RunValue = @"MassiveKnob"; + + // ReSharper disable UnusedMember.Global - used by WPF Binding + private bool runAtStartup; + public bool RunAtStartup + { + get => runAtStartup; + set + { + if (value == runAtStartup) + return; + + runAtStartup = value; + OnPropertyChanged(); + + ApplyRunAtStartup(); + } + } + // ReSharper restore UnusedMember.Global + + + public SettingsStartupViewModel() + { + var runKey = Registry.CurrentUser.OpenSubKey(RunKey, false); + runAtStartup = runKey?.GetValue(RunValue) != null; + } + + + private void ApplyRunAtStartup() + { + var runKey = Registry.CurrentUser.OpenSubKey(RunKey, true); + Debug.Assert(runKey != null, nameof(runKey) + " != null"); + + if (RunAtStartup) + { + var entryAssembly = Assembly.GetEntryAssembly(); + Debug.Assert(entryAssembly != null, nameof(entryAssembly) + " != null"); + + runKey.SetValue(RunValue, new Uri(entryAssembly.CodeBase).LocalPath); + } + else + { + runKey.DeleteValue(RunValue, false); + } + } + + + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/Windows/MassiveKnob/ViewModel/SettingsViewModel.cs b/Windows/MassiveKnob/ViewModel/SettingsViewModel.cs index 11e1928..cd8a771 100644 --- a/Windows/MassiveKnob/ViewModel/SettingsViewModel.cs +++ b/Windows/MassiveKnob/ViewModel/SettingsViewModel.cs @@ -1,55 +1,39 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; using System.Runtime.CompilerServices; using System.Windows; using System.Windows.Controls; -using System.Windows.Media; using MassiveKnob.Core; using MassiveKnob.Plugin; using MassiveKnob.Settings; using MassiveKnob.View.Settings; -using Microsoft.Win32; -using Serilog.Events; namespace MassiveKnob.ViewModel { - // TODO (code quality) split ViewModel for individual views, create viewmodel using container - // TODO (nice to have) installed plugins list public class SettingsViewModel : IDisposable, INotifyPropertyChanged { private readonly Dictionary menuItemControls = new Dictionary { - { SettingsMenuItem.Device, typeof(DeviceView) }, - { SettingsMenuItem.AnalogInputs, typeof(AnalogInputsView) }, - { SettingsMenuItem.DigitalInputs, typeof(DigitalInputsView) }, - { SettingsMenuItem.AnalogOutputs, typeof(AnalogOutputsView) }, - { SettingsMenuItem.DigitalOutputs, typeof(DigitalOutputsView) }, - { SettingsMenuItem.Logging, typeof(LoggingView) }, - { SettingsMenuItem.Startup, typeof(StartupView) } + { SettingsMenuItem.Device, typeof(SettingsDeviceView) }, + { SettingsMenuItem.AnalogInputs, typeof(SettingsAnalogInputsView) }, + { SettingsMenuItem.DigitalInputs, typeof(SettingsDigitalInputsView) }, + { SettingsMenuItem.AnalogOutputs, typeof(SettingsAnalogOutputsView) }, + { SettingsMenuItem.DigitalOutputs, typeof(SettingsDigitalOutputsView) }, + { SettingsMenuItem.Logging, typeof(SettingsLoggingView) }, + { SettingsMenuItem.Startup, typeof(SettingsStartupView) }, + { SettingsMenuItem.Plugins, typeof(SettingsPluginsView) } }; - - + + private readonly SimpleInjector.Container container; private readonly IMassiveKnobOrchestrator orchestrator; - private readonly ILoggingSwitch loggingSwitch; - private DeviceViewModel selectedDevice; private UserControl selectedView; private SettingsMenuItem selectedMenuItem; - private UserControl settingsControl; private DeviceSpecs? specs; - private IEnumerable analogInputs; - private IEnumerable digitalInputs; - private IEnumerable analogOutputs; - private IEnumerable digitalOutputs; - private readonly IDisposable activeDeviceSubscription; - private readonly IDisposable deviceStatusSubscription; + // ReSharper disable UnusedMember.Global - used by WPF Binding public SettingsMenuItem SelectedMenuItem @@ -64,8 +48,8 @@ namespace MassiveKnob.ViewModel OnPropertyChanged(); if (menuItemControls.TryGetValue(selectedMenuItem, out var viewType)) - SelectedView = (UserControl) Activator.CreateInstance(viewType); - + SelectedView = (UserControl)container?.GetInstance(viewType); + orchestrator?.UpdateSettings(settings => { settings.UI.ActiveMenuItem = selectedMenuItem; @@ -88,42 +72,7 @@ namespace MassiveKnob.ViewModel - public IList Devices { get; } - public IList Actions { get; } - - - public DeviceViewModel SelectedDevice - { - get => selectedDevice; - set - { - if (value == selectedDevice) - return; - - selectedDevice = value; - var deviceInfo = orchestrator?.SetActiveDevice(value?.Device); - - OnPropertyChanged(); - - SettingsControl = deviceInfo?.Instance.CreateSettingsControl(); - } - } - - public UserControl SettingsControl - { - get => settingsControl; - set - { - if (value == settingsControl) - return; - - if (settingsControl is IDisposable disposable) - disposable.Dispose(); - - settingsControl = value; - OnPropertyChanged(); - } - } + //public IList Actions { get; } public DeviceSpecs? Specs { @@ -136,7 +85,7 @@ namespace MassiveKnob.ViewModel OnDependantPropertyChanged("DigitalInputVisibility"); OnDependantPropertyChanged("AnalogOutputVisibility"); OnDependantPropertyChanged("DigitalOutputVisibility"); - + /* DisposeInputOutputViewModels(AnalogInputs); DisposeInputOutputViewModels(DigitalInputs); DisposeInputOutputViewModels(AnalogOutputs); @@ -161,6 +110,7 @@ namespace MassiveKnob.ViewModel .Range(0, specs?.DigitalOutputCount ?? 0) .Select(i => new InputOutputViewModel(this, orchestrator, MassiveKnobActionType.OutputDigital, i)) .ToList(); + */ } } @@ -169,6 +119,7 @@ namespace MassiveKnob.ViewModel ? Visibility.Visible : Visibility.Collapsed; + /* public IEnumerable AnalogInputs { get => analogInputs; @@ -178,11 +129,13 @@ namespace MassiveKnob.ViewModel OnPropertyChanged(); } } + */ public Visibility DigitalInputVisibility => specs.HasValue && specs.Value.DigitalInputCount > 0 ? Visibility.Visible : Visibility.Collapsed; + /* public IEnumerable DigitalInputs { get => digitalInputs; @@ -192,11 +145,13 @@ namespace MassiveKnob.ViewModel OnPropertyChanged(); } } + */ public Visibility AnalogOutputVisibility => specs.HasValue && specs.Value.AnalogOutputCount > 0 ? Visibility.Visible : Visibility.Collapsed; + /* public IEnumerable AnalogOutputs { get => analogOutputs; @@ -206,11 +161,13 @@ namespace MassiveKnob.ViewModel OnPropertyChanged(); } } + */ public Visibility DigitalOutputVisibility => specs.HasValue && specs.Value.DigitalOutputCount > 0 ? Visibility.Visible : Visibility.Collapsed; + /* public IEnumerable DigitalOutputs { get => digitalOutputs; @@ -221,7 +178,6 @@ namespace MassiveKnob.ViewModel } } - public IList LoggingLevels { get; } private LoggingLevelViewModel selectedLoggingLevel; @@ -281,62 +237,15 @@ namespace MassiveKnob.ViewModel ApplyRunAtStartup(); } } - - - public string ConnectionStatusText - { - get - { - if (orchestrator == null) - return "Design-time"; - - switch (orchestrator.DeviceStatus) - { - case MassiveKnobDeviceStatus.Disconnected: - return Strings.DeviceStatusDisconnected; - - case MassiveKnobDeviceStatus.Connecting: - return Strings.DeviceStatusConnecting; - - case MassiveKnobDeviceStatus.Connected: - return Strings.DeviceStatusConnected; - - default: - return null; - } - } - } - - public Brush ConnectionStatusColor - { - get - { - if (orchestrator == null) - return Brushes.Fuchsia; - - switch (orchestrator.DeviceStatus) - { - case MassiveKnobDeviceStatus.Disconnected: - return Brushes.DarkRed; - - case MassiveKnobDeviceStatus.Connecting: - return Brushes.Orange; - - case MassiveKnobDeviceStatus.Connected: - return Brushes.ForestGreen; - - default: - return null; - } - } - } + */ // ReSharper restore UnusedMember.Global - public SettingsViewModel(IPluginManager pluginManager, IMassiveKnobOrchestrator orchestrator, ILoggingSwitch loggingSwitch) + public SettingsViewModel(SimpleInjector.Container container, /*IPluginManager pluginManager, */IMassiveKnobOrchestrator orchestrator/*, ILoggingSwitch loggingSwitch*/) { + this.container = container; this.orchestrator = orchestrator; - this.loggingSwitch = loggingSwitch; + //this.loggingSwitch = loggingSwitch; // For design-time support if (orchestrator == null) @@ -348,6 +257,7 @@ namespace MassiveKnob.ViewModel SelectedMenuItem = activeMenuItem; + activeDeviceSubscription = orchestrator.ActiveDeviceSubject.Subscribe(info => { Application.Current?.Dispatcher.Invoke(() => @@ -355,18 +265,12 @@ namespace MassiveKnob.ViewModel Specs = info.Specs; }); }); - deviceStatusSubscription = orchestrator.DeviceStatusSubject.Subscribe(status => - { - OnDependantPropertyChanged(nameof(ConnectionStatusColor)); - OnDependantPropertyChanged(nameof(ConnectionStatusText)); - }); + + if (orchestrator.ActiveDevice != null) + Specs = orchestrator.ActiveDevice.Specs; - Devices = pluginManager.GetDevicePlugins() - .SelectMany(dp => dp.Devices.Select(d => new DeviceViewModel(dp, d))) - .OrderBy(d => d.Name.ToLower()) - .ToList(); - + /* var allActions = new List { new ActionViewModel(null, null) @@ -379,12 +283,6 @@ namespace MassiveKnob.ViewModel Actions = allActions; - if (orchestrator.ActiveDevice != null) - { - selectedDevice = Devices.Single(d => d.Device.DeviceId == orchestrator.ActiveDevice.Info.DeviceId); - SettingsControl = orchestrator.ActiveDevice.Instance.CreateSettingsControl(); - Specs = orchestrator.ActiveDevice.Specs; - } var logSettings = orchestrator.GetSettings().Log; @@ -403,24 +301,23 @@ namespace MassiveKnob.ViewModel var runKey = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", false); runAtStartup = runKey?.GetValue("MassiveKnob") != null; + */ } public void Dispose() { - if (SettingsControl is IDisposable disposable) - disposable.Dispose(); - + /* DisposeInputOutputViewModels(AnalogInputs); DisposeInputOutputViewModels(DigitalInputs); DisposeInputOutputViewModels(AnalogOutputs); DisposeInputOutputViewModels(DigitalOutputs); + */ activeDeviceSubscription?.Dispose(); - deviceStatusSubscription?.Dispose(); } - + /* private void ApplyLoggingSettings() { orchestrator?.UpdateSettings(settings => @@ -460,6 +357,7 @@ namespace MassiveKnob.ViewModel foreach (var viewModel in viewModels) viewModel.Dispose(); } + */ public event PropertyChangedEventHandler PropertyChanged; @@ -478,7 +376,7 @@ namespace MassiveKnob.ViewModel public class SettingsViewModelDesignTime : SettingsViewModel { - public SettingsViewModelDesignTime() : base(null, null, null) + public SettingsViewModelDesignTime() : base(null, null) { Specs = new DeviceSpecs(2, 2, 2, 2); } From bc8396795c81246337c0681a0812fc7c13f8f2c4 Mon Sep 17 00:00:00 2001 From: Mark van Renswoude Date: Wed, 10 Mar 2021 12:39:41 +0100 Subject: [PATCH 2/3] Added Run Program plugin Added Author and URL display in Plugins list --- .../MassiveKnob.Plugin.RunProgram.csproj | 91 +++++++++++ .../MassiveKnobPlugin.json | 3 + .../MassiveKnobRunProgramPlugin.cs | 21 +++ .../Properties/AssemblyInfo.cs | 36 +++++ .../RunProgram/RunProgramAction.cs | 81 ++++++++++ .../RunProgram/RunProgramSettings.cs | 8 + .../RunProgram/RunProgramSettingsView.xaml | 21 +++ .../RunProgram/RunProgramSettingsView.xaml.cs | 27 ++++ .../RunProgram/RunProgramSettingsViewModel.cs | 53 +++++++ .../Strings.Designer.cs | 126 ++++++++++++++++ .../Strings.resx | 141 ++++++++++++++++++ .../MassiveKnob.Plugin/IMassiveKnobPlugin.cs | 2 - Windows/MassiveKnob.sln | 6 + Windows/MassiveKnob/Style.xaml | 11 ++ .../View/Settings/SettingsPluginsView.xaml | 2 + .../View/Settings/SettingsPluginsView.xaml.cs | 17 ++- .../MassiveKnob/ViewModel/PluginViewModel.cs | 8 +- .../Settings/SettingsPluginsViewModel.cs | 6 +- 18 files changed, 653 insertions(+), 7 deletions(-) create mode 100644 Windows/MassiveKnob.Plugin.RunProgram/MassiveKnob.Plugin.RunProgram.csproj create mode 100644 Windows/MassiveKnob.Plugin.RunProgram/MassiveKnobPlugin.json create mode 100644 Windows/MassiveKnob.Plugin.RunProgram/MassiveKnobRunProgramPlugin.cs create mode 100644 Windows/MassiveKnob.Plugin.RunProgram/Properties/AssemblyInfo.cs create mode 100644 Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramAction.cs create mode 100644 Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramSettings.cs create mode 100644 Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramSettingsView.xaml create mode 100644 Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramSettingsView.xaml.cs create mode 100644 Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramSettingsViewModel.cs create mode 100644 Windows/MassiveKnob.Plugin.RunProgram/Strings.Designer.cs create mode 100644 Windows/MassiveKnob.Plugin.RunProgram/Strings.resx diff --git a/Windows/MassiveKnob.Plugin.RunProgram/MassiveKnob.Plugin.RunProgram.csproj b/Windows/MassiveKnob.Plugin.RunProgram/MassiveKnob.Plugin.RunProgram.csproj new file mode 100644 index 0000000..a83be72 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.RunProgram/MassiveKnob.Plugin.RunProgram.csproj @@ -0,0 +1,91 @@ + + + + + Debug + AnyCPU + {BB1E8BA4-7965-4E46-B1BE-D2A7C491A204} + Library + Properties + MassiveKnob.Plugin.RunProgram + MassiveKnob.Plugin.RunProgram + v4.7.2 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + RunProgramSettingsView.xaml + + + + + + Strings.resx + True + True + + + + + Always + + + + + {A1298BE4-1D23-416C-8C56-FC9264487A95} + MassiveKnob.Plugin + + + + + PublicResXFileCodeGenerator + Strings.Designer.cs + + + + + 4.5.4 + + + + + MSBuild:Compile + Designer + + + + \ No newline at end of file diff --git a/Windows/MassiveKnob.Plugin.RunProgram/MassiveKnobPlugin.json b/Windows/MassiveKnob.Plugin.RunProgram/MassiveKnobPlugin.json new file mode 100644 index 0000000..d892ba4 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.RunProgram/MassiveKnobPlugin.json @@ -0,0 +1,3 @@ +{ + "EntryAssembly": "MassiveKnob.Plugin.RunProgram.dll" +} diff --git a/Windows/MassiveKnob.Plugin.RunProgram/MassiveKnobRunProgramPlugin.cs b/Windows/MassiveKnob.Plugin.RunProgram/MassiveKnobRunProgramPlugin.cs new file mode 100644 index 0000000..6e9f0ff --- /dev/null +++ b/Windows/MassiveKnob.Plugin.RunProgram/MassiveKnobRunProgramPlugin.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using MassiveKnob.Plugin.RunProgram.RunProgram; + +namespace MassiveKnob.Plugin.RunProgram +{ + [MassiveKnobPlugin] + public class MassiveKnobRunProgramPlugin : IMassiveKnobActionPlugin + { + public Guid PluginId { get; } = new Guid("10537f2a-6876-48b8-8ef9-8d05f185fa62"); + public string Name { get; } = Strings.PluginName; + public string Description { get; } = Strings.PluginDescription; + public string Author { get; } = "Mark van Renswoude "; + public string Url { get; } = "https://www.github.com/MvRens/MassiveKnob/"; + + public IEnumerable Actions { get; } = new IMassiveKnobAction[] + { + new RunProgramAction() + }; + } +} diff --git a/Windows/MassiveKnob.Plugin.RunProgram/Properties/AssemblyInfo.cs b/Windows/MassiveKnob.Plugin.RunProgram/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..754d30c --- /dev/null +++ b/Windows/MassiveKnob.Plugin.RunProgram/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MassiveKnob.Plugin.RunProgram")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MassiveKnob.Plugin.RunProgram")] +[assembly: AssemblyCopyright("Copyright © 2021")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("bb1e8ba4-7965-4e46-b1be-d2a7c491a204")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramAction.cs b/Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramAction.cs new file mode 100644 index 0000000..8a1342a --- /dev/null +++ b/Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramAction.cs @@ -0,0 +1,81 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using System.Windows.Controls; +using Microsoft.Extensions.Logging; + +namespace MassiveKnob.Plugin.RunProgram.RunProgram +{ + public class RunProgramAction : IMassiveKnobAction + { + public Guid ActionId { get; } = new Guid("c3a79015-4b8f-414d-9682-02307de8639c"); + public MassiveKnobActionType ActionType { get; } = MassiveKnobActionType.InputDigital; + public string Name { get; } = Strings.RunProgramName; + public string Description { get; } = Strings.RunProgramDescription; + + + public IMassiveKnobActionInstance Create(ILogger logger) + { + return new Instance(logger); + } + + + private class Instance : IMassiveKnobDigitalAction + { + private readonly ILogger logger; + private IMassiveKnobActionContext actionContext; + private RunProgramSettings settings; + + + public Instance(ILogger logger) + { + this.logger = logger; + } + + + public void Initialize(IMassiveKnobActionContext context) + { + actionContext = context; + settings = context.GetSettings(); + } + + + public void Dispose() + { + } + + + public UserControl CreateSettingsControl() + { + var viewModel = new RunProgramSettingsViewModel(settings); + viewModel.PropertyChanged += (sender, args) => + { + actionContext.SetSettings(settings); + }; + + return new RunProgramSettingsView(viewModel); + } + + + public ValueTask DigitalChanged(bool on) + { + if (!on) + return default; + + if (string.IsNullOrEmpty(settings.Filename)) + return default; + + logger.LogInformation("Run program: filename = {filename}, arguments = {arguments}", settings.Filename, settings.Arguments); + Process.Start(new ProcessStartInfo + { + FileName = settings.Filename, + Arguments = settings.Arguments, + UseShellExecute = true, + Verb = "open" + }); + + return default; + } + } + } +} \ No newline at end of file diff --git a/Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramSettings.cs b/Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramSettings.cs new file mode 100644 index 0000000..f0b00d6 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramSettings.cs @@ -0,0 +1,8 @@ +namespace MassiveKnob.Plugin.RunProgram.RunProgram +{ + public class RunProgramSettings + { + public string Filename { get; set; } + public string Arguments { get; set; } + } +} diff --git a/Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramSettingsView.xaml b/Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramSettingsView.xaml new file mode 100644 index 0000000..6ddd583 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramSettingsView.xaml @@ -0,0 +1,21 @@ + + + + + + + + + + + + diff --git a/Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramSettingsView.xaml.cs b/Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramSettingsView.xaml.cs new file mode 100644 index 0000000..bad83f5 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramSettingsView.xaml.cs @@ -0,0 +1,27 @@ +using System.Windows; + +namespace MassiveKnob.Plugin.RunProgram.RunProgram +{ + /// + /// Interaction logic for RunProgramSettingsView.xaml + /// + public partial class RunProgramSettingsView + { + public RunProgramSettingsView(RunProgramSettingsViewModel viewModel) + { + DataContext = viewModel; + InitializeComponent(); + } + + private void ButtonBrowseClick(object sender, RoutedEventArgs e) + { + var dialog = new Microsoft.Win32.OpenFileDialog + { + Filter = Strings.FilenameDialogFilter + }; + + if (dialog.ShowDialog().GetValueOrDefault()) + ((RunProgramSettingsViewModel) DataContext).Filename = dialog.FileName; + } + } +} diff --git a/Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramSettingsViewModel.cs b/Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramSettingsViewModel.cs new file mode 100644 index 0000000..8d4a63e --- /dev/null +++ b/Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramSettingsViewModel.cs @@ -0,0 +1,53 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace MassiveKnob.Plugin.RunProgram.RunProgram +{ + public class RunProgramSettingsViewModel : INotifyPropertyChanged + { + private readonly RunProgramSettings settings; + + + // ReSharper disable UnusedMember.Global - used by WPF Binding + public string Filename + { + get => settings.Filename; + set + { + if (value == settings.Filename) + return; + + settings.Filename = value; + OnPropertyChanged(); + } + } + + public string Arguments + { + get => settings.Arguments; + set + { + if (value == settings.Arguments) + return; + + settings.Arguments = value; + OnPropertyChanged(); + } + } + // ReSharper restore UnusedMember.Global + + + public RunProgramSettingsViewModel(RunProgramSettings settings) + { + this.settings = settings; + } + + + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/Windows/MassiveKnob.Plugin.RunProgram/Strings.Designer.cs b/Windows/MassiveKnob.Plugin.RunProgram/Strings.Designer.cs new file mode 100644 index 0000000..f2c7400 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.RunProgram/Strings.Designer.cs @@ -0,0 +1,126 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace MassiveKnob.Plugin.RunProgram { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Strings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Strings() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MassiveKnob.Plugin.RunProgram.Strings", typeof(Strings).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Executable files (*.exe, *.bat, *.cmd)|*.exe,*.bat,*.cmd|All files (*.*)|*.*. + /// + public static string FilenameDialogFilter { + get { + return ResourceManager.GetString("FilenameDialogFilter", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Provides an action to run an application when a button is pressed.. + /// + public static string PluginDescription { + get { + return ResourceManager.GetString("PluginDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Run Program. + /// + public static string PluginName { + get { + return ResourceManager.GetString("PluginName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Runs an application when a button is pressed.. + /// + public static string RunProgramDescription { + get { + return ResourceManager.GetString("RunProgramDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Run program. + /// + public static string RunProgramName { + get { + return ResourceManager.GetString("RunProgramName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Optional arguments passed to the executable. + /// + public static string SettingArguments { + get { + return ResourceManager.GetString("SettingArguments", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Executable, file or URL to open. + /// + public static string SettingFilename { + get { + return ResourceManager.GetString("SettingFilename", resourceCulture); + } + } + } +} diff --git a/Windows/MassiveKnob.Plugin.RunProgram/Strings.resx b/Windows/MassiveKnob.Plugin.RunProgram/Strings.resx new file mode 100644 index 0000000..82a4572 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.RunProgram/Strings.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Executable files (*.exe, *.bat, *.cmd)|*.exe,*.bat,*.cmd|All files (*.*)|*.* + + + Provides an action to run an application when a button is pressed. + + + Run Program + + + Runs an application when a button is pressed. + + + Run program + + + Optional arguments passed to the executable + + + Executable, file or URL to open + + \ No newline at end of file diff --git a/Windows/MassiveKnob.Plugin/IMassiveKnobPlugin.cs b/Windows/MassiveKnob.Plugin/IMassiveKnobPlugin.cs index e4a138d..d7b15a0 100644 --- a/Windows/MassiveKnob.Plugin/IMassiveKnobPlugin.cs +++ b/Windows/MassiveKnob.Plugin/IMassiveKnobPlugin.cs @@ -1,7 +1,5 @@ using System; -// ReSharper disable UnusedMember.Global - public API - namespace MassiveKnob.Plugin { /// diff --git a/Windows/MassiveKnob.sln b/Windows/MassiveKnob.sln index c19297a..6268963 100644 --- a/Windows/MassiveKnob.sln +++ b/Windows/MassiveKnob.sln @@ -21,6 +21,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MassiveKnob.Plugin.VoiceMee EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Voicemeeter", "VoicemeeterRemote\Voicemeeter\Voicemeeter.csproj", "{F35DD8E5-91FA-403E-B6F6-8D2B4AE84198}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MassiveKnob.Plugin.RunProgram", "MassiveKnob.Plugin.RunProgram\MassiveKnob.Plugin.RunProgram.csproj", "{BB1E8BA4-7965-4E46-B1BE-D2A7C491A204}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -63,6 +65,10 @@ Global {F35DD8E5-91FA-403E-B6F6-8D2B4AE84198}.Debug|Any CPU.Build.0 = Debug|Any CPU {F35DD8E5-91FA-403E-B6F6-8D2B4AE84198}.Release|Any CPU.ActiveCfg = Release|Any CPU {F35DD8E5-91FA-403E-B6F6-8D2B4AE84198}.Release|Any CPU.Build.0 = Release|Any CPU + {BB1E8BA4-7965-4E46-B1BE-D2A7C491A204}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BB1E8BA4-7965-4E46-B1BE-D2A7C491A204}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BB1E8BA4-7965-4E46-B1BE-D2A7C491A204}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BB1E8BA4-7965-4E46-B1BE-D2A7C491A204}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Windows/MassiveKnob/Style.xaml b/Windows/MassiveKnob/Style.xaml index 8193282..e0a5785 100644 --- a/Windows/MassiveKnob/Style.xaml +++ b/Windows/MassiveKnob/Style.xaml @@ -56,4 +56,15 @@ + + + + \ No newline at end of file diff --git a/Windows/MassiveKnob/View/Settings/SettingsPluginsView.xaml b/Windows/MassiveKnob/View/Settings/SettingsPluginsView.xaml index 3006f35..3b5c24a 100644 --- a/Windows/MassiveKnob/View/Settings/SettingsPluginsView.xaml +++ b/Windows/MassiveKnob/View/Settings/SettingsPluginsView.xaml @@ -21,6 +21,8 @@ + + diff --git a/Windows/MassiveKnob/View/Settings/SettingsPluginsView.xaml.cs b/Windows/MassiveKnob/View/Settings/SettingsPluginsView.xaml.cs index 62c5ca9..8c0d43b 100644 --- a/Windows/MassiveKnob/View/Settings/SettingsPluginsView.xaml.cs +++ b/Windows/MassiveKnob/View/Settings/SettingsPluginsView.xaml.cs @@ -1,4 +1,8 @@ -using MassiveKnob.ViewModel.Settings; +using System.Diagnostics; +using System.Windows; +using System.Windows.Input; +using MassiveKnob.ViewModel; +using MassiveKnob.ViewModel.Settings; namespace MassiveKnob.View.Settings { @@ -12,5 +16,16 @@ namespace MassiveKnob.View.Settings DataContext = viewModel; InitializeComponent(); } + + private void UrlMouseDown(object sender, MouseButtonEventArgs e) + { + var dataContext = ((FrameworkElement) e.Source).DataContext; + + Process.Start(new ProcessStartInfo + { + FileName = ((PluginViewModel) dataContext).Url, + Verb = "open" + }); + } } } diff --git a/Windows/MassiveKnob/ViewModel/PluginViewModel.cs b/Windows/MassiveKnob/ViewModel/PluginViewModel.cs index 5d292e0..86cc751 100644 --- a/Windows/MassiveKnob/ViewModel/PluginViewModel.cs +++ b/Windows/MassiveKnob/ViewModel/PluginViewModel.cs @@ -8,16 +8,22 @@ namespace MassiveKnob.ViewModel public string Name { get; } public string Description { get; } public string Filename { get; } + public string Author { get; } + public string Url { get; } public Visibility DescriptionVisibility => string.IsNullOrEmpty(Description) ? Visibility.Collapsed : Visibility.Visible; + public Visibility AuthorVisibility => string.IsNullOrEmpty(Author) ? Visibility.Collapsed : Visibility.Visible; + public Visibility UrlVisibility => string.IsNullOrEmpty(Url) ? Visibility.Collapsed : Visibility.Visible; // ReSharper restore UnusedMember.Global - public PluginViewModel(string name, string description, string filename) + public PluginViewModel(string name, string description, string filename, string author, string url) { Name = name; Description = description; Filename = filename; + Author = author; + Url = url; } } } diff --git a/Windows/MassiveKnob/ViewModel/Settings/SettingsPluginsViewModel.cs b/Windows/MassiveKnob/ViewModel/Settings/SettingsPluginsViewModel.cs index d6a6211..dfe6c80 100644 --- a/Windows/MassiveKnob/ViewModel/Settings/SettingsPluginsViewModel.cs +++ b/Windows/MassiveKnob/ViewModel/Settings/SettingsPluginsViewModel.cs @@ -19,7 +19,7 @@ namespace MassiveKnob.ViewModel.Settings return; Plugins = pluginManager.GetPlugins() - .Select(p => new PluginViewModel(p.Plugin.Name, p.Plugin.Description, p.Filename)) + .Select(p => new PluginViewModel(p.Plugin.Name, p.Plugin.Description, p.Filename, p.Plugin.Author, p.Plugin.Url)) .OrderBy(p => p.Name, StringComparer.CurrentCultureIgnoreCase) .ToList(); } @@ -33,8 +33,8 @@ namespace MassiveKnob.ViewModel.Settings { Plugins = new[] { - new PluginViewModel("Plugin without description", null, "D:\\Does\\Not\\Exist.dll"), - new PluginViewModel("Design-time plugin", "Fake plugin only visible at design-time.", "C:\\Does\\Not\\Exist.dll") + new PluginViewModel("Plugin without description", null, "D:\\Does\\Not\\Exist.dll", "Some Massive Knob ", "https://lmgtfy.app/?q=Massive+Knob"), + new PluginViewModel("Design-time plugin", "Fake plugin only visible at design-time.", "C:\\Does\\Not\\Exist.dll", null, null) }; } } From 80621a3df294333f128296c5e91ac65fb00ee1fd Mon Sep 17 00:00:00 2001 From: Mark van Renswoude Date: Wed, 10 Mar 2021 12:47:18 +0100 Subject: [PATCH 3/3] Forgot to include the new plugin in the setup --- Windows/Setup/MassiveKnobSetup.iss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Windows/Setup/MassiveKnobSetup.iss b/Windows/Setup/MassiveKnobSetup.iss index ded0de0..c345a3f 100644 --- a/Windows/Setup/MassiveKnobSetup.iss +++ b/Windows/Setup/MassiveKnobSetup.iss @@ -36,6 +36,7 @@ Name: essentialplugins; Description: "Essential plugins"; Types: full custom Name: essentialplugins\serialdevice; Description: "Serial device"; Types: full custom Name: essentialplugins\coreaudio; Description: "Windows Core Audio actions"; Types: full custom Name: optionalplugins; Description: "Optional plugins"; Types: full custom +Name: optionalplugins\runprogram; Description: "Run program"; Types: full custom Name: optionalplugins\emulatordevice; Description: "Emulator device"; Types: full custom Name: optionalplugins\voicemeeter; Description: "VoiceMeeter actions"; Types: full custom @@ -65,6 +66,10 @@ Source: {#BasePath}\MassiveKnob.Plugin.EmulatorDevice\bin\Release\*.dll; DestDir Source: {#BasePath}\MassiveKnob.Plugin.VoiceMeeter\bin\Release\MassiveKnobPlugin.json; DestDir: "{app}\Plugins\VoiceMeeter"; Flags: ignoreversion; Components: optionalplugins\voicemeeter Source: {#BasePath}\MassiveKnob.Plugin.VoiceMeeter\bin\Release\*.dll; DestDir: "{app}\Plugins\VoiceMeeter"; Flags: ignoreversion; Components: optionalplugins\voicemeeter +; Run Program plugin +Source: {#BasePath}\MassiveKnob.Plugin.RunProgram\bin\Release\MassiveKnobPlugin.json; DestDir: "{app}\Plugins\RunProgram"; Flags: ignoreversion; Components: optionalplugins\runprogram +Source: {#BasePath}\MassiveKnob.Plugin.RunProgram\bin\Release\*.dll; DestDir: "{app}\Plugins\RunProgram"; Flags: ignoreversion; Components: optionalplugins\runprogram + [Dirs] Name: "{localappdata}\MassiveKnob" Name: "{localappdata}\MassiveKnob\Logs"