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 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 (must have) show device status 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) } }; 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; // ReSharper disable UnusedMember.Global - used by WPF Binding public SettingsMenuItem SelectedMenuItem { get => selectedMenuItem; set { if (value == selectedMenuItem) return; selectedMenuItem = value; OnPropertyChanged(); if (menuItemControls.TryGetValue(selectedMenuItem, out var viewType)) SelectedView = (UserControl) Activator.CreateInstance(viewType); orchestrator?.UpdateSettings(settings => { settings.UI.ActiveMenuItem = selectedMenuItem; }); } } public UserControl SelectedView { get => selectedView; set { if (value == selectedView) return; selectedView = value; OnPropertyChanged(); } } 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 DeviceSpecs? Specs { get => specs; set { specs = value; OnPropertyChanged(); OnDependantPropertyChanged("AnalogInputVisibility"); OnDependantPropertyChanged("DigitalInputVisibility"); OnDependantPropertyChanged("AnalogOutputVisibility"); OnDependantPropertyChanged("DigitalOutputVisibility"); DisposeInputOutputViewModels(AnalogInputs); DisposeInputOutputViewModels(DigitalInputs); DisposeInputOutputViewModels(AnalogOutputs); DisposeInputOutputViewModels(DigitalOutputs); AnalogInputs = Enumerable .Range(0, specs?.AnalogInputCount ?? 0) .Select(i => new InputOutputViewModel(this, orchestrator, MassiveKnobActionType.InputAnalog, i)); DigitalInputs = Enumerable .Range(0, specs?.DigitalInputCount ?? 0) .Select(i => new InputOutputViewModel(this, orchestrator, MassiveKnobActionType.InputDigital, i)); AnalogOutputs = Enumerable .Range(0, specs?.AnalogOutputCount ?? 0) .Select(i => new InputOutputViewModel(this, orchestrator, MassiveKnobActionType.OutputAnalog, i)); DigitalOutputs = Enumerable .Range(0, specs?.DigitalOutputCount ?? 0) .Select(i => new InputOutputViewModel(this, orchestrator, MassiveKnobActionType.OutputDigital, i)); } } public Visibility AnalogInputVisibility => specs.HasValue && specs.Value.AnalogInputCount > 0 ? Visibility.Visible : Visibility.Collapsed; public IEnumerable AnalogInputs { get => analogInputs; set { analogInputs = value; OnPropertyChanged(); } } public Visibility DigitalInputVisibility => specs.HasValue && specs.Value.DigitalInputCount > 0 ? Visibility.Visible : Visibility.Collapsed; public IEnumerable DigitalInputs { get => digitalInputs; set { digitalInputs = value; OnPropertyChanged(); } } public Visibility AnalogOutputVisibility => specs.HasValue && specs.Value.AnalogOutputCount > 0 ? Visibility.Visible : Visibility.Collapsed; public IEnumerable AnalogOutputs { get => analogOutputs; set { analogOutputs = value; OnPropertyChanged(); } } public Visibility DigitalOutputVisibility => specs.HasValue && specs.Value.DigitalOutputCount > 0 ? Visibility.Visible : Visibility.Collapsed; public IEnumerable DigitalOutputs { get => digitalOutputs; set { digitalOutputs = value; OnPropertyChanged(); } } 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")); private bool runAtStartup; public bool RunAtStartup { get => runAtStartup; set { if (value == runAtStartup) return; runAtStartup = value; OnPropertyChanged(); ApplyRunAtStartup(); } } // ReSharper restore UnusedMember.Global public SettingsViewModel(IPluginManager pluginManager, IMassiveKnobOrchestrator orchestrator, ILoggingSwitch loggingSwitch) { this.orchestrator = orchestrator; this.loggingSwitch = loggingSwitch; // For design-time support if (orchestrator == null) return; var activeMenuItem = orchestrator.GetSettings().UI.ActiveMenuItem; if (activeMenuItem == SettingsMenuItem.None) activeMenuItem = SettingsMenuItem.Device; SelectedMenuItem = activeMenuItem; orchestrator.ActiveDeviceSubject.Subscribe(info => { Specs = info.Specs; }); Devices = pluginManager.GetDevicePlugins() .SelectMany(dp => dp.Devices.Select(d => new DeviceViewModel(dp, d))) .ToList(); var allActions = new List { new ActionViewModel(null, null) }; allActions.AddRange( pluginManager.GetActionPlugins() .SelectMany(ap => ap.Actions.Select(a => new ActionViewModel(ap, a)))); 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; 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; 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); } private void ApplyLoggingSettings() { orchestrator?.UpdateSettings(settings => { settings.Log.Enabled = LoggingEnabled; settings.Log.Level = SelectedLoggingLevel.Level; }); loggingSwitch?.SetLogging(LoggingEnabled, selectedLoggingLevel.Level); } private void ApplyRunAtStartup() { var runKey = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true); Debug.Assert(runKey != null, nameof(runKey) + " != null"); if (RunAtStartup) { var entryAssembly = Assembly.GetEntryAssembly(); Debug.Assert(entryAssembly != null, nameof(entryAssembly) + " != null"); runKey.SetValue("MassiveKnob", new Uri(entryAssembly.CodeBase).LocalPath); } else { runKey.DeleteValue("MassiveKnob", false); } } private static void DisposeInputOutputViewModels(IEnumerable viewModels) { if (viewModels == null) return; foreach (var viewModel in viewModels) viewModel.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 SettingsViewModelDesignTime : SettingsViewModel { public SettingsViewModelDesignTime() : base(null, null, null) { Specs = new DeviceSpecs(2, 2, 2, 2); } } }