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); }