MassiveKnob/Windows/MassiveKnob/ViewModel/SettingsViewModel.cs

411 lines
14 KiB
C#

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<SettingsMenuItem, Type> menuItemControls = new Dictionary<SettingsMenuItem, Type>
{
{ 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<InputOutputViewModel> analogInputs;
private IEnumerable<InputOutputViewModel> digitalInputs;
private IEnumerable<InputOutputViewModel> analogOutputs;
private IEnumerable<InputOutputViewModel> 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<DeviceViewModel> Devices { get; }
public IList<ActionViewModel> 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<InputOutputViewModel> AnalogInputs
{
get => analogInputs;
set
{
analogInputs = value;
OnPropertyChanged();
}
}
public Visibility DigitalInputVisibility => specs.HasValue && specs.Value.DigitalInputCount > 0
? Visibility.Visible
: Visibility.Collapsed;
public IEnumerable<InputOutputViewModel> DigitalInputs
{
get => digitalInputs;
set
{
digitalInputs = value;
OnPropertyChanged();
}
}
public Visibility AnalogOutputVisibility => specs.HasValue && specs.Value.AnalogOutputCount > 0
? Visibility.Visible
: Visibility.Collapsed;
public IEnumerable<InputOutputViewModel> AnalogOutputs
{
get => analogOutputs;
set
{
analogOutputs = value;
OnPropertyChanged();
}
}
public Visibility DigitalOutputVisibility => specs.HasValue && specs.Value.DigitalOutputCount > 0
? Visibility.Visible
: Visibility.Collapsed;
public IEnumerable<InputOutputViewModel> DigitalOutputs
{
get => digitalOutputs;
set
{
digitalOutputs = value;
OnPropertyChanged();
}
}
public IList<LoggingLevelViewModel> 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<ActionViewModel>
{
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<LoggingLevelViewModel>
{
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<InputOutputViewModel> 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);
}
}
}