Improved stability of analog signals in the Arduino sketch
Reimplemented automatic refresh of the serial port list Improved serial device connection retry Moved plugins to their own subfolder for better separation, added metadata on the entry assembly to load Added logging settings Added Run at startup setting Many visual enhancements
This commit is contained in:
parent
cd1ab91f23
commit
197aef531a
@ -5,31 +5,31 @@
|
||||
*
|
||||
*/
|
||||
// Set this to the number of potentiometers you have connected
|
||||
const byte AnalogInputCount = 1;
|
||||
const byte AnalogInputCount = 3;
|
||||
|
||||
// Set this to the number of buttons you have connected
|
||||
const byte DigitalInputCount = 1;
|
||||
const byte DigitalInputCount = 0;
|
||||
|
||||
// Not supported yet - maybe PWM and/or other means of analog output?
|
||||
const byte AnalogOutputCount = 0;
|
||||
|
||||
// Set this to the number of digital outputs you have connected
|
||||
const byte DigitalOutputCount = 1;
|
||||
const byte DigitalOutputCount = 0;
|
||||
|
||||
|
||||
// For each potentiometer, specify the pin
|
||||
const byte AnalogInputPin[AnalogInputCount] = {
|
||||
A0
|
||||
A0,
|
||||
A1,
|
||||
A2
|
||||
};
|
||||
|
||||
// For each button, specify the pin. Assumes pull-up.
|
||||
const byte DigitalInputPin[DigitalInputCount] = {
|
||||
3
|
||||
};
|
||||
|
||||
// For each digital output, specify the pin
|
||||
const byte DigitalOutputPin[DigitalOutputCount] = {
|
||||
2
|
||||
};
|
||||
|
||||
|
||||
@ -42,7 +42,12 @@ const float EMAAlpha = 0.6;
|
||||
// How many measurements to take at boot time for analog inputs to seed the EMA
|
||||
const byte EMASeedCount = 5;
|
||||
|
||||
// Minimum treshold for reporting changes in analog values, reduces noise left over from the EMA. Note that once an analog value
|
||||
// changes beyond the treshold, that input will report all changes until the FocusTimeout has expired to avoid losing accuracy.
|
||||
const byte AnalogTreshold = 2;
|
||||
|
||||
// How long to ignore other inputs after an input changes. Reduces noise due voltage drops.
|
||||
const unsigned long FocusTimeout = 100;
|
||||
|
||||
|
||||
/*
|
||||
@ -51,6 +56,12 @@ const byte EMASeedCount = 5;
|
||||
* Here be dragons.
|
||||
*
|
||||
*/
|
||||
|
||||
// If defined, only outputs will be sent to the serial port as Arduino Plotter compatible data
|
||||
//#define DebugOutputPlotter
|
||||
|
||||
|
||||
#ifndef DebugOutputPlotter
|
||||
#include "./min.h"
|
||||
#include "./min.c"
|
||||
|
||||
@ -77,6 +88,7 @@ const uint8_t FrameIDAnalogOutput = 3;
|
||||
const uint8_t FrameIDDigitalOutput = 4;
|
||||
const uint8_t FrameIDQuit = 62;
|
||||
const uint8_t FrameIDError = 63;
|
||||
#endif
|
||||
|
||||
|
||||
struct AnalogInputStatus
|
||||
@ -94,7 +106,6 @@ struct DigitalInputStatus
|
||||
};
|
||||
|
||||
|
||||
bool active = false;
|
||||
struct AnalogInputStatus analogInputStatus[AnalogInputCount];
|
||||
struct DigitalInputStatus digitalInputStatus[AnalogInputCount];
|
||||
|
||||
@ -107,8 +118,10 @@ void setup()
|
||||
while (!Serial) {}
|
||||
|
||||
|
||||
#ifndef DebugOutputPlotter
|
||||
// Set up the MIN protocol (http://github.com/min-protocol/min)
|
||||
min_init_context(&minContext, 0);
|
||||
#endif
|
||||
|
||||
|
||||
// Seed the moving average for analog inputs
|
||||
@ -147,12 +160,39 @@ void setup()
|
||||
}
|
||||
|
||||
|
||||
#ifdef DebugOutputPlotter
|
||||
unsigned long lastOutput = 0;
|
||||
#endif
|
||||
|
||||
enum FocusType
|
||||
{
|
||||
FocusTypeNone = 0,
|
||||
FocusTypeAnalogInput = 1,
|
||||
FocusTypeDigitalInput = 2,
|
||||
FocusTypeOutput = 3
|
||||
};
|
||||
|
||||
bool active = false;
|
||||
FocusType focusType = FocusTypeNone;
|
||||
byte focusInputIndex;
|
||||
unsigned long focusOutputTime;
|
||||
|
||||
#define IsAnalogInputFocus(i) ((focusType == FocusInputType.AnalogInput) && (focusInputIndex == i))
|
||||
#define IsDigitalInputFocus(i) ((focusType == FocusInputType.DigitalInput) && (focusInputIndex == i))
|
||||
|
||||
|
||||
void loop()
|
||||
{
|
||||
#ifndef DebugOutputPlotter
|
||||
char readBuffer[32];
|
||||
size_t readBufferSize = Serial.available() > 0 ? Serial.readBytes(readBuffer, 32U) : 0;
|
||||
|
||||
min_poll(&minContext, (uint8_t*)readBuffer, (uint8_t)readBufferSize);
|
||||
#endif
|
||||
|
||||
|
||||
if (focusType == FocusTypeOutput && millis() - focusOutputTime >= FocusTimeout)
|
||||
focusType = FocusTypeNone;
|
||||
|
||||
|
||||
// Check analog inputs
|
||||
@ -160,8 +200,32 @@ void loop()
|
||||
for (byte i = 0; i < AnalogInputCount; i++)
|
||||
{
|
||||
newAnalogValue = getAnalogValue(i);
|
||||
bool changed;
|
||||
|
||||
if (newAnalogValue != analogInputStatus[i].Value && (millis() - analogInputStatus[i].LastChange >= MinimumInterval))
|
||||
switch (focusType)
|
||||
{
|
||||
case FocusTypeAnalogInput:
|
||||
if (focusInputIndex != i)
|
||||
continue;
|
||||
|
||||
if (millis() - analogInputStatus[i].LastChange < FocusTimeout)
|
||||
{
|
||||
changed = newAnalogValue != analogInputStatus[i].Value;
|
||||
break;
|
||||
}
|
||||
else
|
||||
focusType = FocusTypeNone;
|
||||
// fall-through
|
||||
|
||||
case FocusTypeNone:
|
||||
changed = abs(analogInputStatus[i].Value - newAnalogValue) >= AnalogTreshold;
|
||||
break;
|
||||
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
if (changed && (millis() - analogInputStatus[i].LastChange >= MinimumInterval))
|
||||
{
|
||||
if (active)
|
||||
// Send out new value
|
||||
@ -179,6 +243,23 @@ void loop()
|
||||
{
|
||||
newDigitalValue = getDigitalValue(i);
|
||||
|
||||
switch (focusType)
|
||||
{
|
||||
case FocusTypeAnalogInput:
|
||||
case FocusTypeOutput:
|
||||
continue;
|
||||
|
||||
case FocusTypeDigitalInput:
|
||||
if (focusInputIndex != i)
|
||||
continue;
|
||||
|
||||
if (millis() - digitalInputStatus[i].LastChange >= FocusTimeout)
|
||||
focusType = FocusTypeNone;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (newDigitalValue != digitalInputStatus[i].Value && (millis() - digitalInputStatus[i].LastChange >= MinimumInterval))
|
||||
{
|
||||
if (active)
|
||||
@ -189,9 +270,33 @@ void loop()
|
||||
digitalInputStatus[i].LastChange = millis();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DebugOutputPlotter
|
||||
if (millis() - lastOutput >= 100)
|
||||
{
|
||||
for (byte i = 0; i < AnalogInputCount; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
Serial.print("\t");
|
||||
|
||||
Serial.print(analogInputStatus[i].Value);
|
||||
}
|
||||
|
||||
for (byte i = 0; i < DigitalInputCount; i++)
|
||||
{
|
||||
if (i > 0 || AnalogInputCount > 0)
|
||||
Serial.print("\t");
|
||||
|
||||
Serial.print(digitalInputStatus[i].Value ? 100 : 0);
|
||||
}
|
||||
|
||||
Serial.println();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#ifndef DebugOutputPlotter
|
||||
void min_application_handler(uint8_t min_id, uint8_t *min_payload, uint8_t len_payload, uint8_t port)
|
||||
{
|
||||
switch (min_id)
|
||||
@ -248,7 +353,12 @@ void processDigitalOutputMessage(uint8_t *min_payload, uint8_t len_payload)
|
||||
|
||||
byte outputIndex = min_payload[0];
|
||||
if (outputIndex < DigitalOutputCount)
|
||||
{
|
||||
digitalWrite(DigitalOutputPin[min_payload[0]], min_payload[1] == 0 ? LOW : HIGH);
|
||||
|
||||
focusType = FocusTypeOutput;
|
||||
focusOutputTime = millis();
|
||||
}
|
||||
else
|
||||
outputError("Invalid digital output index: " + String(outputIndex));
|
||||
}
|
||||
@ -258,6 +368,7 @@ void processQuitMessage()
|
||||
{
|
||||
active = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
byte getAnalogValue(byte analogInputIndex)
|
||||
@ -285,19 +396,25 @@ bool getDigitalValue(byte digitalInputIndex)
|
||||
|
||||
void outputAnalogValue(byte analogInputIndex, byte newValue)
|
||||
{
|
||||
#ifndef DebugOutputPlotter
|
||||
byte payload[2] = { analogInputIndex, newValue };
|
||||
min_send_frame(&minContext, FrameIDAnalogInput, (uint8_t *)payload, 2);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void outputDigitalValue(byte digitalInputIndex, bool newValue)
|
||||
{
|
||||
#ifndef DebugOutputPlotter
|
||||
byte payload[2] = { digitalInputIndex, newValue ? 1 : 0 };
|
||||
min_send_frame(&minContext, FrameIDDigitalInput, (uint8_t *)payload, 2);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void outputError(String message)
|
||||
{
|
||||
#ifndef DebugOutputPlotter
|
||||
min_send_frame(&minContext, FrameIDError, (uint8_t *)message.c_str(), message.length());
|
||||
#endif
|
||||
}
|
||||
|
@ -23,3 +23,6 @@ Because of the second requirement, a simple media keys HID device does not suffi
|
||||
The hardware side uses an Arduino sketch to communicate the hardware state over the serial port.
|
||||
|
||||
The Windows software is written in C# using .NET Framework 4.7.2 and Visual Studio 2019.
|
||||
|
||||
|
||||
Some icons courtesy of https://feathericons.com/
|
@ -17,7 +17,7 @@
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>$(localappdata)\MassiveKnob\Plugins\</OutputPath>
|
||||
<OutputPath>$(localappdata)\MassiveKnob\Plugins\CoreAudio\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
@ -145,6 +145,10 @@
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<ItemGroup>
|
||||
<None Include="MassiveKnobPlugin.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"EntryAssembly": "MassiveKnob.Plugin.CoreAudio.dll"
|
||||
}
|
@ -8,8 +8,8 @@ namespace MassiveKnob.Plugin.EmulatorDevice.Devices
|
||||
public class EmulatorDevice : IMassiveKnobDevice
|
||||
{
|
||||
public Guid DeviceId { get; } = new Guid("e1a4977a-abf4-4c75-a17d-fd8d3a8451ff");
|
||||
public string Name { get; } = "Mock device";
|
||||
public string Description { get; } = "Emulates the actual device but does not communicate with anything.";
|
||||
public string Name { get; } = "Emulator";
|
||||
public string Description { get; } = "Emulates an actual device but does not communicate with anything.";
|
||||
|
||||
public IMassiveKnobDeviceInstance Create(ILogger logger)
|
||||
{
|
||||
|
@ -17,7 +17,7 @@
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>$(localappdata)\MassiveKnob\Plugins\</OutputPath>
|
||||
<OutputPath>$(localappdata)\MassiveKnob\Plugins\EmulatorDevice\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
@ -79,5 +79,10 @@
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="MassiveKnobPlugin.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"EntryAssembly": "MassiveKnob.Plugin.EmulatorDevice.dll"
|
||||
}
|
@ -17,7 +17,7 @@
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>$(localappdata)\MassiveKnob\Plugins\</OutputPath>
|
||||
<OutputPath>$(localappdata)\MassiveKnob\Plugins\SerialDevice\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
@ -79,9 +79,17 @@
|
||||
<PackageReference Include="Crc32.NET">
|
||||
<Version>1.2.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Dapplo.Windows.Devices">
|
||||
<Version>0.11.24</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions">
|
||||
<Version>5.0.0</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="MassiveKnobPlugin.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"EntryAssembly": "MassiveKnob.Plugin.SerialDevice.dll"
|
||||
}
|
@ -1,14 +1,22 @@
|
||||
namespace MassiveKnob.Plugin.SerialDevice.Settings
|
||||
using System;
|
||||
|
||||
namespace MassiveKnob.Plugin.SerialDevice.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for SerialDeviceSettingsView.xaml
|
||||
/// </summary>
|
||||
public partial class SerialDeviceSettingsView
|
||||
public partial class SerialDeviceSettingsView : IDisposable
|
||||
{
|
||||
public SerialDeviceSettingsView(SerialDeviceSettingsViewModel viewModel)
|
||||
{
|
||||
DataContext = viewModel;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
(DataContext as SerialDeviceSettingsViewModel)?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,23 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO.Ports;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Dapplo.Windows.Devices;
|
||||
using Dapplo.Windows.Devices.Enums;
|
||||
|
||||
namespace MassiveKnob.Plugin.SerialDevice.Settings
|
||||
{
|
||||
public class SerialDeviceSettingsViewModel : INotifyPropertyChanged
|
||||
public class SerialDeviceSettingsViewModel : IDisposable, INotifyPropertyChanged, IObserver<DeviceNotificationEvent>
|
||||
{
|
||||
private readonly SerialDeviceSettings settings;
|
||||
private IEnumerable<string> serialPorts;
|
||||
private IList<string> serialPorts;
|
||||
private readonly IDisposable deviceSubscription;
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
|
||||
// ReSharper disable UnusedMember.Global - used by WPF Binding
|
||||
public IEnumerable<string> SerialPorts
|
||||
public IList<string> SerialPorts
|
||||
{
|
||||
get => serialPorts;
|
||||
set
|
||||
@ -29,7 +33,7 @@ namespace MassiveKnob.Plugin.SerialDevice.Settings
|
||||
get => settings.PortName;
|
||||
set
|
||||
{
|
||||
if (value == settings.PortName)
|
||||
if (value == settings.PortName || value == null)
|
||||
return;
|
||||
|
||||
settings.PortName = value;
|
||||
@ -72,8 +76,13 @@ namespace MassiveKnob.Plugin.SerialDevice.Settings
|
||||
this.settings = settings;
|
||||
|
||||
serialPorts = SerialPort.GetPortNames();
|
||||
deviceSubscription = DeviceNotification.OnNotification.Subscribe(this);
|
||||
}
|
||||
|
||||
// TODO (must have - port from old source) subscribe to device notification to refresh list
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
deviceSubscription.Dispose();
|
||||
}
|
||||
|
||||
|
||||
@ -87,5 +96,31 @@ namespace MassiveKnob.Plugin.SerialDevice.Settings
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
|
||||
protected virtual void OnOtherPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void OnNext(DeviceNotificationEvent value)
|
||||
{
|
||||
if ((value.EventType == DeviceChangeEvent.DeviceArrival ||
|
||||
value.EventType == DeviceChangeEvent.DeviceRemoveComplete) &&
|
||||
value.Is(DeviceBroadcastDeviceType.DeviceInterface))
|
||||
{
|
||||
SerialPorts = SerialPort.GetPortNames();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnError(Exception error)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnCompleted()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,6 +49,8 @@ namespace MassiveKnob.Plugin.SerialDevice.Worker
|
||||
|
||||
public void Connect(string portName, int baudRate, bool dtrEnable)
|
||||
{
|
||||
context.Connecting();
|
||||
|
||||
lock (minProtocolLock)
|
||||
{
|
||||
if (portName == lastPortName && baudRate == lastBaudRate && dtrEnable == lastDtrEnable)
|
||||
@ -75,14 +77,28 @@ namespace MassiveKnob.Plugin.SerialDevice.Worker
|
||||
|
||||
public void SetAnalogOutput(int analogOutputIndex, byte value)
|
||||
{
|
||||
minProtocol?.QueueFrame(
|
||||
IMINProtocol instance;
|
||||
|
||||
lock (minProtocolLock)
|
||||
{
|
||||
instance = minProtocol;
|
||||
}
|
||||
|
||||
instance?.QueueFrame(
|
||||
(byte)MassiveKnobFrameID.AnalogOutput,
|
||||
new [] { (byte)analogOutputIndex, value });
|
||||
}
|
||||
|
||||
public void SetDigitalOutput(int digitalOutputIndex, bool on)
|
||||
{
|
||||
minProtocol?.QueueFrame(
|
||||
IMINProtocol instance;
|
||||
|
||||
lock (minProtocolLock)
|
||||
{
|
||||
instance = minProtocol;
|
||||
}
|
||||
|
||||
instance?.QueueFrame(
|
||||
(byte)MassiveKnobFrameID.DigitalOutput,
|
||||
new [] { (byte)digitalOutputIndex, on ? (byte)1 : (byte)0 });
|
||||
}
|
||||
@ -90,16 +106,42 @@ namespace MassiveKnob.Plugin.SerialDevice.Worker
|
||||
|
||||
private void MinProtocolOnOnConnected(object sender, EventArgs e)
|
||||
{
|
||||
IMINProtocol instance;
|
||||
|
||||
lock (minProtocolLock)
|
||||
{
|
||||
if (minProtocol != sender as IMINProtocol)
|
||||
return;
|
||||
|
||||
instance = minProtocol;
|
||||
}
|
||||
|
||||
if (instance == null)
|
||||
return;
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await minProtocol.Reset();
|
||||
await minProtocol.QueueFrame((byte)MassiveKnobFrameID.Handshake, new[] { (byte)'M', (byte)'K' });
|
||||
await instance.Reset();
|
||||
await instance.QueueFrame((byte)MassiveKnobFrameID.Handshake, new[] { (byte)'M', (byte)'K' });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void MinProtocolOnOnFrame(object sender, MINFrameEventArgs e)
|
||||
{
|
||||
IMINProtocol instance;
|
||||
|
||||
lock (minProtocolLock)
|
||||
{
|
||||
if (minProtocol != sender as IMINProtocol)
|
||||
return;
|
||||
|
||||
instance = minProtocol;
|
||||
}
|
||||
|
||||
if (instance == null)
|
||||
return;
|
||||
|
||||
// ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault - by design
|
||||
switch ((MassiveKnobFrameID)e.Id)
|
||||
{
|
||||
@ -108,6 +150,8 @@ namespace MassiveKnob.Plugin.SerialDevice.Worker
|
||||
{
|
||||
logger.LogError("Invalid handshake response length, expected 4, got {length}: {payload}",
|
||||
e.Payload.Length, BitConverter.ToString(e.Payload));
|
||||
|
||||
Disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -153,7 +197,10 @@ namespace MassiveKnob.Plugin.SerialDevice.Worker
|
||||
lock (minProtocolLock)
|
||||
{
|
||||
minProtocol?.Dispose();
|
||||
minProtocol = null;
|
||||
}
|
||||
|
||||
context.Disconnected();
|
||||
}
|
||||
|
||||
|
||||
|
@ -51,8 +51,11 @@ namespace MassiveKnob
|
||||
settingsWindow.Show();
|
||||
}
|
||||
else
|
||||
{
|
||||
settingsWindow.WindowState = WindowState.Normal;
|
||||
settingsWindow.Activate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void NotifyIconMenuSettingsClick(object sender, RoutedEventArgs e)
|
||||
|
@ -1,7 +1,8 @@
|
||||
using System;
|
||||
using MassiveKnob.Plugin;
|
||||
using MassiveKnob.Settings;
|
||||
|
||||
namespace MassiveKnob.Model
|
||||
namespace MassiveKnob.Core
|
||||
{
|
||||
public interface IMassiveKnobOrchestrator : IDisposable
|
||||
{
|
||||
@ -12,6 +13,9 @@ namespace MassiveKnob.Model
|
||||
|
||||
MassiveKnobActionInfo GetAction(MassiveKnobActionType actionType, int index);
|
||||
MassiveKnobActionInfo SetAction(MassiveKnobActionType actionType, int index, IMassiveKnobAction action);
|
||||
|
||||
MassiveKnobSettings GetSettings();
|
||||
void UpdateSettings(Action<MassiveKnobSettings> applyChanges);
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using MassiveKnob.Plugin;
|
||||
|
||||
namespace MassiveKnob.Model
|
||||
namespace MassiveKnob.Core
|
||||
{
|
||||
public interface IPluginManager
|
||||
{
|
@ -9,7 +9,7 @@ using Newtonsoft.Json.Linq;
|
||||
using Serilog.Extensions.Logging;
|
||||
using ILogger = Serilog.ILogger;
|
||||
|
||||
namespace MassiveKnob.Model
|
||||
namespace MassiveKnob.Core
|
||||
{
|
||||
public class MassiveKnobOrchestrator : IMassiveKnobOrchestrator
|
||||
{
|
||||
@ -17,7 +17,7 @@ namespace MassiveKnob.Model
|
||||
private readonly ILogger logger;
|
||||
|
||||
private readonly object settingsLock = new object();
|
||||
private Settings.Settings settings;
|
||||
private MassiveKnobSettings massiveKnobSettings;
|
||||
private readonly SerialQueue flushSettingsQueue = new SerialQueue();
|
||||
|
||||
private MassiveKnobDeviceInfo activeDevice;
|
||||
@ -49,15 +49,17 @@ namespace MassiveKnob.Model
|
||||
public IObservable<MassiveKnobDeviceInfo> ActiveDeviceSubject => activeDeviceInfoSubject;
|
||||
|
||||
|
||||
public MassiveKnobOrchestrator(IPluginManager pluginManager, ILogger logger)
|
||||
public MassiveKnobOrchestrator(IPluginManager pluginManager, ILogger logger, MassiveKnobSettings massiveKnobSettings)
|
||||
{
|
||||
this.pluginManager = pluginManager;
|
||||
this.logger = logger;
|
||||
this.massiveKnobSettings = massiveKnobSettings;
|
||||
}
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
activeDeviceContext = null;
|
||||
activeDevice?.Instance?.Dispose();
|
||||
|
||||
void DisposeMappings(ICollection<ActionMapping> mappings)
|
||||
@ -85,13 +87,11 @@ namespace MassiveKnob.Model
|
||||
{
|
||||
lock (settingsLock)
|
||||
{
|
||||
settings = SettingsJsonSerializer.Deserialize();
|
||||
|
||||
if (settings.Device == null)
|
||||
if (massiveKnobSettings.Device == null)
|
||||
return;
|
||||
|
||||
var allDevices = pluginManager.GetDevicePlugins().SelectMany(dp => dp.Devices);
|
||||
var device = allDevices.FirstOrDefault(d => d.DeviceId == settings.Device.DeviceId);
|
||||
var device = allDevices.FirstOrDefault(d => d.DeviceId == massiveKnobSettings.Device.DeviceId);
|
||||
|
||||
InternalSetActiveDevice(device, false);
|
||||
}
|
||||
@ -135,7 +135,7 @@ namespace MassiveKnob.Model
|
||||
while (index >= settingsList.Count)
|
||||
settingsList.Add(null);
|
||||
|
||||
settingsList[index] = action == null ? null : new Settings.Settings.ActionSettings
|
||||
settingsList[index] = action == null ? null : new MassiveKnobSettings.ActionSettings
|
||||
{
|
||||
ActionId = action.ActionId,
|
||||
Settings = null
|
||||
@ -162,6 +162,26 @@ namespace MassiveKnob.Model
|
||||
}
|
||||
|
||||
|
||||
public MassiveKnobSettings GetSettings()
|
||||
{
|
||||
lock (settingsLock)
|
||||
{
|
||||
return massiveKnobSettings.Clone();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void UpdateSettings(Action<MassiveKnobSettings> applyChanges)
|
||||
{
|
||||
lock (settingsLock)
|
||||
{
|
||||
applyChanges(massiveKnobSettings);
|
||||
}
|
||||
|
||||
FlushSettings();
|
||||
}
|
||||
|
||||
|
||||
private MassiveKnobDeviceInfo InternalSetActiveDevice(IMassiveKnobDevice device, bool resetSettings)
|
||||
{
|
||||
if (device == ActiveDevice?.Info)
|
||||
@ -173,10 +193,10 @@ namespace MassiveKnob.Model
|
||||
lock (settingsLock)
|
||||
{
|
||||
if (device == null)
|
||||
settings.Device = null;
|
||||
massiveKnobSettings.Device = null;
|
||||
else
|
||||
{
|
||||
settings.Device = new Settings.Settings.DeviceSettings
|
||||
massiveKnobSettings.Device = new MassiveKnobSettings.DeviceSettings
|
||||
{
|
||||
DeviceId = device.DeviceId,
|
||||
Settings = null
|
||||
@ -191,7 +211,7 @@ namespace MassiveKnob.Model
|
||||
|
||||
if (device != null)
|
||||
{
|
||||
var instance = device.Create(new SerilogLoggerProvider(logger.ForContext("Device", device.DeviceId)).CreateLogger(null));
|
||||
var instance = device.Create(new SerilogLoggerProvider(logger.ForContext("Context", new { Device = device.DeviceId })).CreateLogger(null));
|
||||
ActiveDevice = new MassiveKnobDeviceInfo(device, instance, null);
|
||||
|
||||
activeDeviceContext = new DeviceContext(this, device);
|
||||
@ -210,11 +230,11 @@ namespace MassiveKnob.Model
|
||||
protected T GetDeviceSettings<T>(IMassiveKnobDeviceContext context) where T : class, new()
|
||||
{
|
||||
if (context != activeDeviceContext)
|
||||
throw new InvalidOperationException("Caller must be the active device to retrieve the settings");
|
||||
throw new InvalidOperationException("Caller must be the active device to retrieve the massiveKnobSettings");
|
||||
|
||||
lock (settingsLock)
|
||||
{
|
||||
return settings.Device.Settings?.ToObject<T>() ?? new T();
|
||||
return massiveKnobSettings.Device.Settings?.ToObject<T>() ?? new T();
|
||||
}
|
||||
}
|
||||
|
||||
@ -222,17 +242,17 @@ namespace MassiveKnob.Model
|
||||
protected void SetDeviceSettings<T>(IMassiveKnobDeviceContext context, IMassiveKnobDevice device, T deviceSettings) where T : class, new()
|
||||
{
|
||||
if (context != activeDeviceContext)
|
||||
throw new InvalidOperationException("Caller must be the active device to update the settings");
|
||||
throw new InvalidOperationException("Caller must be the active device to update the massiveKnobSettings");
|
||||
|
||||
lock (settingsLock)
|
||||
{
|
||||
if (settings.Device == null)
|
||||
settings.Device = new Settings.Settings.DeviceSettings
|
||||
if (massiveKnobSettings.Device == null)
|
||||
massiveKnobSettings.Device = new MassiveKnobSettings.DeviceSettings
|
||||
{
|
||||
DeviceId = device.DeviceId
|
||||
};
|
||||
|
||||
settings.Device.Settings = JObject.FromObject(deviceSettings);
|
||||
massiveKnobSettings.Device.Settings = JObject.FromObject(deviceSettings);
|
||||
}
|
||||
|
||||
FlushSettings();
|
||||
@ -248,7 +268,7 @@ namespace MassiveKnob.Model
|
||||
return new T();
|
||||
|
||||
if (list[index]?.Context != context)
|
||||
throw new InvalidOperationException("Caller must be the active action to retrieve the settings");
|
||||
throw new InvalidOperationException("Caller must be the active action to retrieve the massiveKnobSettings");
|
||||
|
||||
var settingsList = GetActionSettingsList(action.ActionType);
|
||||
if (index >= settingsList.Count)
|
||||
@ -268,7 +288,7 @@ namespace MassiveKnob.Model
|
||||
return;
|
||||
|
||||
if (list[index]?.Context != context)
|
||||
throw new InvalidOperationException("Caller must be the active action to retrieve the settings");
|
||||
throw new InvalidOperationException("Caller must be the active action to retrieve the massiveKnobSettings");
|
||||
|
||||
var settingsList = GetActionSettingsList(action.ActionType);
|
||||
|
||||
@ -276,7 +296,7 @@ namespace MassiveKnob.Model
|
||||
settingsList.Add(null);
|
||||
|
||||
if (settingsList[index] == null)
|
||||
settingsList[index] = new Settings.Settings.ActionSettings
|
||||
settingsList[index] = new MassiveKnobSettings.ActionSettings
|
||||
{
|
||||
ActionId = action.ActionId
|
||||
};
|
||||
@ -408,21 +428,21 @@ namespace MassiveKnob.Model
|
||||
}
|
||||
|
||||
|
||||
private List<Settings.Settings.ActionSettings> GetActionSettingsList(MassiveKnobActionType actionType)
|
||||
private List<MassiveKnobSettings.ActionSettings> GetActionSettingsList(MassiveKnobActionType actionType)
|
||||
{
|
||||
switch (actionType)
|
||||
{
|
||||
case MassiveKnobActionType.InputAnalog:
|
||||
return settings.AnalogInput;
|
||||
return massiveKnobSettings.AnalogInput;
|
||||
|
||||
case MassiveKnobActionType.InputDigital:
|
||||
return settings.DigitalInput;
|
||||
return massiveKnobSettings.DigitalInput;
|
||||
|
||||
case MassiveKnobActionType.OutputAnalog:
|
||||
return settings.AnalogOutput;
|
||||
return massiveKnobSettings.AnalogOutput;
|
||||
|
||||
case MassiveKnobActionType.OutputDigital:
|
||||
return settings.DigitalOutput;
|
||||
return massiveKnobSettings.DigitalOutput;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(actionType), actionType, null);
|
||||
@ -431,16 +451,16 @@ namespace MassiveKnob.Model
|
||||
|
||||
private void FlushSettings()
|
||||
{
|
||||
Settings.Settings settingsSnapshot;
|
||||
MassiveKnobSettings massiveKnobSettingsSnapshot;
|
||||
|
||||
lock (settingsLock)
|
||||
{
|
||||
settingsSnapshot = settings.Clone();
|
||||
massiveKnobSettingsSnapshot = massiveKnobSettings.Clone();
|
||||
}
|
||||
|
||||
flushSettingsQueue.Enqueue(async () =>
|
||||
{
|
||||
await SettingsJsonSerializer.Serialize(settingsSnapshot);
|
||||
await MassiveKnobSettingsJsonSerializer.Serialize(massiveKnobSettingsSnapshot);
|
||||
});
|
||||
}
|
||||
|
||||
@ -462,10 +482,10 @@ namespace MassiveKnob.Model
|
||||
|
||||
lock (settingsLock)
|
||||
{
|
||||
UpdateMapping(analogInputs, specs.AnalogInputCount, settings.AnalogInput, DelayedInitialize);
|
||||
UpdateMapping(digitalInputs, specs.DigitalInputCount, settings.DigitalInput, DelayedInitialize);
|
||||
UpdateMapping(analogOutputs, specs.AnalogOutputCount, settings.AnalogOutput, DelayedInitialize);
|
||||
UpdateMapping(digitalOutputs, specs.DigitalOutputCount, settings.DigitalOutput, DelayedInitialize);
|
||||
UpdateMapping(analogInputs, specs.AnalogInputCount, massiveKnobSettings.AnalogInput, DelayedInitialize);
|
||||
UpdateMapping(digitalInputs, specs.DigitalInputCount, massiveKnobSettings.DigitalInput, DelayedInitialize);
|
||||
UpdateMapping(analogOutputs, specs.AnalogOutputCount, massiveKnobSettings.AnalogOutput, DelayedInitialize);
|
||||
UpdateMapping(digitalOutputs, specs.DigitalOutputCount, massiveKnobSettings.DigitalOutput, DelayedInitialize);
|
||||
}
|
||||
|
||||
foreach (var delayedInitializeAction in delayedInitializeActions)
|
||||
@ -487,7 +507,7 @@ namespace MassiveKnob.Model
|
||||
}
|
||||
|
||||
|
||||
private void UpdateMapping(List<ActionMapping> mapping, int newCount, List<Settings.Settings.ActionSettings> actionSettings, Action<IMassiveKnobActionInstance, IMassiveKnobActionContext> initializeOutsideLock)
|
||||
private void UpdateMapping(List<ActionMapping> mapping, int newCount, List<MassiveKnobSettings.ActionSettings> actionSettings, Action<IMassiveKnobActionInstance, IMassiveKnobActionContext> initializeOutsideLock)
|
||||
{
|
||||
if (mapping.Count > newCount)
|
||||
{
|
||||
@ -524,10 +544,13 @@ namespace MassiveKnob.Model
|
||||
if (action == null)
|
||||
return null;
|
||||
|
||||
var actionLogger = logger
|
||||
.ForContext("Action", action.ActionId)
|
||||
.ForContext("ActionType", action.ActionType)
|
||||
.ForContext("Index", index);
|
||||
var actionLogger = logger.ForContext("Context",
|
||||
new
|
||||
{
|
||||
Action = action.ActionId,
|
||||
action.ActionType,
|
||||
Index = index
|
||||
});
|
||||
|
||||
var instance = action.Create(new SerilogLoggerProvider(actionLogger).CreateLogger(null));
|
||||
var context = new ActionContext(this, action, index);
|
@ -3,11 +3,13 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using MassiveKnob.Plugin;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
using Serilog.Extensions.Logging;
|
||||
|
||||
namespace MassiveKnob.Model
|
||||
namespace MassiveKnob.Core
|
||||
{
|
||||
public class MassiveKnobPluginIdConflictException : Exception
|
||||
{
|
||||
@ -75,26 +77,70 @@ namespace MassiveKnob.Model
|
||||
|
||||
private void LoadPlugins(string path, RegisteredIds registeredIds, Action<Exception, string> onException)
|
||||
{
|
||||
logger.Information("Checking {path} for plugins...", path);
|
||||
if (!Directory.Exists(path))
|
||||
return;
|
||||
|
||||
var filenames = Directory.GetFiles(path, "*.dll");
|
||||
|
||||
foreach (var filename in filenames)
|
||||
var metadataFilenames = Directory.GetFiles(path, "MassiveKnobPlugin.json", SearchOption.AllDirectories);
|
||||
|
||||
foreach (var metadataFilename in metadataFilenames)
|
||||
{
|
||||
var pluginPath = Path.GetDirectoryName(metadataFilename);
|
||||
if (string.IsNullOrEmpty(pluginPath))
|
||||
continue;
|
||||
|
||||
PluginMetadata pluginMetadata;
|
||||
try
|
||||
{
|
||||
var pluginAssembly = Assembly.LoadFrom(filename);
|
||||
RegisterPlugins(filename, pluginAssembly, registeredIds);
|
||||
pluginMetadata = LoadMetadata(metadataFilename);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
onException(e, filename);
|
||||
logger.Warning("Could not load plugin metadata from {metadataFilename}: {message}", metadataFilename, e.Message);
|
||||
continue;
|
||||
}
|
||||
|
||||
var entryAssemblyFilename = Path.Combine(pluginPath, pluginMetadata.EntryAssembly);
|
||||
if (!File.Exists(entryAssemblyFilename))
|
||||
{
|
||||
logger.Warning("Entry assembly specified in {metadataFilename} does not exist: {entryAssemblyFilename}", entryAssemblyFilename);
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
logger.Information("Plugin found in {pluginPath}", pluginPath);
|
||||
|
||||
var pluginAssembly = Assembly.LoadFrom(entryAssemblyFilename);
|
||||
RegisterPlugins(entryAssemblyFilename, pluginAssembly, registeredIds);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Warning("Error while loading plugin {entryAssemblyFilename}: {message}", entryAssemblyFilename, e.Message);
|
||||
onException(e, entryAssemblyFilename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static PluginMetadata LoadMetadata(string filename)
|
||||
{
|
||||
string json;
|
||||
|
||||
using (var stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, true))
|
||||
using (var streamReader = new StreamReader(stream, Encoding.UTF8))
|
||||
{
|
||||
json = streamReader.ReadToEnd();
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(json))
|
||||
throw new IOException("Metadata file is empty");
|
||||
|
||||
return JsonConvert.DeserializeObject<PluginMetadata>(json);
|
||||
}
|
||||
|
||||
|
||||
private void RegisterPlugins(string filename, Assembly assembly, RegisteredIds registeredIds)
|
||||
{
|
||||
var pluginTypes = assembly.GetTypes().Where(t => t.GetCustomAttribute<MassiveKnobPluginAttribute>() != null);
|
||||
@ -104,7 +150,10 @@ namespace MassiveKnob.Model
|
||||
if (!(pluginInstance is IMassiveKnobPlugin))
|
||||
throw new InvalidCastException($"Type {pluginType.FullName} claims to be a MassiveKnobPlugin but does not implement IMassiveKnobPlugin");
|
||||
|
||||
ValidateRegistration(filename, (IMassiveKnobPlugin)pluginInstance, registeredIds);
|
||||
var plugin = (IMassiveKnobPlugin) pluginInstance;
|
||||
logger.Information("Found plugin with Id {pluginId}: {name}", plugin.PluginId, plugin.Name);
|
||||
|
||||
ValidateRegistration(filename, plugin, registeredIds);
|
||||
plugins.Add((IMassiveKnobPlugin)pluginInstance);
|
||||
}
|
||||
}
|
||||
@ -125,6 +174,8 @@ namespace MassiveKnob.Model
|
||||
{
|
||||
foreach (var device in devicePlugin.Devices)
|
||||
{
|
||||
logger.Information("- Device {deviceId}: {name}", device.DeviceId, device.Name);
|
||||
|
||||
if (registeredIds.DeviceById.TryGetValue(device.DeviceId, out var conflictingDeviceFilename))
|
||||
throw new MassiveKnobPluginIdConflictException(device.DeviceId, conflictingDeviceFilename, filename);
|
||||
|
||||
@ -138,6 +189,8 @@ namespace MassiveKnob.Model
|
||||
{
|
||||
foreach (var action in actionPlugin.Actions)
|
||||
{
|
||||
logger.Information("- Action {actionId}: {name}", action.ActionId, action.Name);
|
||||
|
||||
if (registeredIds.ActionById.TryGetValue(action.ActionId, out var conflictingActionFilename))
|
||||
throw new MassiveKnobPluginIdConflictException(action.ActionId, conflictingActionFilename, filename);
|
||||
|
||||
@ -184,5 +237,11 @@ namespace MassiveKnob.Model
|
||||
public readonly Dictionary<Guid, string> DeviceById = new Dictionary<Guid, string>();
|
||||
public readonly Dictionary<Guid, string> ActionById = new Dictionary<Guid, string>();
|
||||
}
|
||||
|
||||
|
||||
private class PluginMetadata
|
||||
{
|
||||
public string EntryAssembly { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
18
Windows/MassiveKnob/Helpers/ComparisonConverter.cs
Normal file
18
Windows/MassiveKnob/Helpers/ComparisonConverter.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace MassiveKnob.Helpers
|
||||
{
|
||||
public class ComparisonConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
return value?.Equals(parameter);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
return value?.Equals(true) == true ? parameter : Binding.DoNothing;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
/*
|
||||
using System;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace MassiveKnob.Helpers
|
||||
{
|
||||
public class DelegateCommand : ICommand
|
||||
{
|
||||
private readonly Action execute;
|
||||
private readonly Func<bool> canExecute;
|
||||
|
||||
|
||||
public DelegateCommand(Action execute) : this(execute, null)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public DelegateCommand(Action execute, Func<bool> canExecute)
|
||||
{
|
||||
this.execute = execute ?? throw new ArgumentNullException(nameof(execute));
|
||||
this.canExecute = canExecute;
|
||||
}
|
||||
|
||||
|
||||
public bool CanExecute(object parameter)
|
||||
{
|
||||
return canExecute?.Invoke() ?? true;
|
||||
}
|
||||
|
||||
|
||||
public void Execute(object parameter)
|
||||
{
|
||||
execute();
|
||||
}
|
||||
|
||||
|
||||
public event EventHandler CanExecuteChanged
|
||||
{
|
||||
add => CommandManager.RequerySuggested += value;
|
||||
remove => CommandManager.RequerySuggested -= value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class DelegateCommand<T> : ICommand
|
||||
{
|
||||
private readonly Action<T> execute;
|
||||
private readonly Predicate<T> canExecute;
|
||||
|
||||
|
||||
public DelegateCommand(Action<T> execute) : this(execute, null)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public DelegateCommand(Action<T> execute, Predicate<T> canExecute)
|
||||
{
|
||||
this.execute = execute ?? throw new ArgumentNullException(nameof(execute));
|
||||
this.canExecute = canExecute;
|
||||
}
|
||||
|
||||
|
||||
public bool CanExecute(object parameter)
|
||||
{
|
||||
return canExecute?.Invoke((T)parameter) ?? true;
|
||||
}
|
||||
|
||||
|
||||
public void Execute(object parameter)
|
||||
{
|
||||
execute((T)parameter);
|
||||
}
|
||||
|
||||
|
||||
public event EventHandler CanExecuteChanged
|
||||
{
|
||||
add => CommandManager.RequerySuggested += value;
|
||||
remove => CommandManager.RequerySuggested -= value;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
Binary file not shown.
Before Width: | Height: | Size: 169 KiB |
@ -36,7 +36,7 @@
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>MainIcon.ico</ApplicationIcon>
|
||||
<ApplicationIcon>Resources\MainIcon.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<StartupObject>MassiveKnob.Program</StartupObject>
|
||||
@ -59,15 +59,19 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Helpers\ComboBoxTemplateSelector.cs" />
|
||||
<Compile Include="Helpers\DelegateCommand.cs" />
|
||||
<Compile Include="Helpers\ComparisonConverter.cs" />
|
||||
<Compile Include="Helpers\SerialQueue.cs" />
|
||||
<Compile Include="Model\IMassiveKnobOrchestrator.cs" />
|
||||
<Compile Include="Model\IPluginManager.cs" />
|
||||
<Compile Include="Model\MassiveKnobOrchestrator.cs" />
|
||||
<Compile Include="Model\PluginManager.cs" />
|
||||
<Compile Include="Core\IMassiveKnobOrchestrator.cs" />
|
||||
<Compile Include="Core\IPluginManager.cs" />
|
||||
<Compile Include="Core\MassiveKnobOrchestrator.cs" />
|
||||
<Compile Include="Core\PluginManager.cs" />
|
||||
<Compile Include="Settings\ILoggingSwitch.cs" />
|
||||
<Compile Include="Settings\LoggingSwitch.cs" />
|
||||
<Compile Include="ViewModel\LoggingLevelViewModel.cs" />
|
||||
<Compile Include="ViewModel\ActionViewModel.cs" />
|
||||
<Compile Include="ViewModel\DeviceViewModel.cs" />
|
||||
<Compile Include="ViewModel\InputOutputViewModel.cs" />
|
||||
<Compile Include="ViewModel\MenuItemProperties.cs" />
|
||||
<Compile Include="ViewModel\SettingsViewModel.cs" />
|
||||
<Compile Include="View\InputOutputView.xaml.cs">
|
||||
<DependentUpon>InputOutputView.xaml</DependentUpon>
|
||||
@ -77,16 +81,38 @@
|
||||
</Compile>
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Settings\Settings.cs" />
|
||||
<Compile Include="Settings\SettingsJsonSerializer.cs" />
|
||||
<Compile Include="Settings\MassiveKnobSettings.cs" />
|
||||
<Compile Include="Settings\MassiveKnobSettingsJsonSerializer.cs" />
|
||||
<Compile Include="Strings.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>Strings.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="View\Settings\AnalogInputsView.xaml.cs">
|
||||
<DependentUpon>AnalogInputsView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="View\Settings\AnalogOutputsView.xaml.cs">
|
||||
<DependentUpon>AnalogOutputsView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="View\Settings\StartupView.xaml.cs">
|
||||
<DependentUpon>StartupView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="View\Settings\LoggingView.xaml.cs">
|
||||
<DependentUpon>LoggingView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="View\Settings\DigitalInputsView.xaml.cs">
|
||||
<DependentUpon>DigitalInputsView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="View\Settings\DigitalOutputsView.xaml.cs">
|
||||
<DependentUpon>DigitalOutputsView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="View\Settings\DeviceView.xaml.cs">
|
||||
<DependentUpon>DeviceView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<EmbeddedResource Include="Strings.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Strings.Designer.cs</LastGenOutput>
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@ -134,7 +160,7 @@
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="MainIcon.ico" />
|
||||
<Resource Include="Resources\MainIcon.ico" />
|
||||
<Resource Include="Resources\NotifyIcon.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@ -146,6 +172,58 @@
|
||||
<DependentUpon>App.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Page Include="Resources\Analog.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Resources\Device.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Resources\Digital.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Resources\IconStyle.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Resources\Logging.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Resources\Startup.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="View\Settings\AnalogInputsView.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="View\Settings\AnalogOutputsView.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="View\Settings\StartupView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="View\Settings\LoggingView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="View\Settings\DigitalInputsView.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="View\Settings\DigitalOutputsView.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="View\Settings\DeviceView.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Include="Style.xaml">
|
||||
|
@ -2,39 +2,36 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using MassiveKnob.Model;
|
||||
using MassiveKnob.Core;
|
||||
using MassiveKnob.Settings;
|
||||
using MassiveKnob.View;
|
||||
using MassiveKnob.ViewModel;
|
||||
using Serilog;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
using SimpleInjector;
|
||||
|
||||
|
||||
namespace MassiveKnob
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// The main entry point for the application.
|
||||
/// </summary>
|
||||
[STAThread]
|
||||
public static int Main()
|
||||
{
|
||||
// TODO (should have) make configurable
|
||||
var loggingLevelSwitch = new LoggingLevelSwitch();
|
||||
//var loggingLevelSwitch = new LoggingLevelSwitch(LogEventLevel.Verbose);
|
||||
var settings = MassiveKnobSettingsJsonSerializer.Deserialize();
|
||||
|
||||
var loggingSwitch = new LoggingSwitch();
|
||||
loggingSwitch.SetLogging(settings.Log.Enabled, settings.Log.Level);
|
||||
|
||||
var logger = new LoggerConfiguration()
|
||||
//.MinimumLevel.Verbose()
|
||||
.MinimumLevel.ControlledBy(loggingLevelSwitch)
|
||||
.Filter.ByIncludingOnly(loggingSwitch.IsIncluded)
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.File(
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"MassiveKnob", @"Logs", @".log"),
|
||||
LogEventLevel.Verbose, rollingInterval: RollingInterval.Day)
|
||||
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);
|
||||
|
||||
var messages = new StringBuilder();
|
||||
@ -49,7 +46,7 @@ namespace MassiveKnob
|
||||
return 1;
|
||||
}
|
||||
|
||||
var orchestrator = new MassiveKnobOrchestrator(pluginManager, logger);
|
||||
var orchestrator = new MassiveKnobOrchestrator(pluginManager, logger, settings);
|
||||
orchestrator.Load();
|
||||
|
||||
|
||||
@ -57,7 +54,7 @@ namespace MassiveKnob
|
||||
container.Options.EnableAutoVerification = false;
|
||||
|
||||
container.RegisterInstance(logger);
|
||||
container.RegisterInstance(loggingLevelSwitch);
|
||||
container.RegisterInstance<ILoggingSwitch>(loggingSwitch);
|
||||
container.RegisterInstance<IPluginManager>(pluginManager);
|
||||
container.RegisterInstance<IMassiveKnobOrchestrator>(orchestrator);
|
||||
|
||||
@ -69,6 +66,7 @@ namespace MassiveKnob
|
||||
var app = container.GetInstance<App>();
|
||||
app.Run();
|
||||
|
||||
logger.Information("MassiveKnob shutting down");
|
||||
orchestrator.Dispose();
|
||||
return 0;
|
||||
}
|
||||
|
15
Windows/MassiveKnob/Resources/Analog.xaml
Normal file
15
Windows/MassiveKnob/Resources/Analog.xaml
Normal file
@ -0,0 +1,15 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
<Viewbox Stretch="Uniform" x:Key="Analog">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Canvas.Resources>
|
||||
<ResourceDictionary Source="IconStyle.xaml" />
|
||||
</Canvas.Resources>
|
||||
<Polyline Points="22 12 18 12 15 21 9 3 6 12 2 12" FillRule="NonZero" Style="{StaticResource IconStroke}"/>
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</ResourceDictionary>
|
24
Windows/MassiveKnob/Resources/Device.xaml
Normal file
24
Windows/MassiveKnob/Resources/Device.xaml
Normal file
@ -0,0 +1,24 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
<Viewbox Stretch="Uniform" x:Key="Device">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Canvas.Resources>
|
||||
<ResourceDictionary Source="IconStyle.xaml" />
|
||||
</Canvas.Resources>
|
||||
<Rectangle Canvas.Left="4" Canvas.Top="4" Width="16" Height="16" RadiusX="2" RadiusY="2" Style="{StaticResource IconStroke}" />
|
||||
<Rectangle Canvas.Left="9" Canvas.Top="9" Width="6" Height="6" Style="{StaticResource IconStroke}" />
|
||||
<Line X1="9" Y1="1" X2="9" Y2="4" Style="{StaticResource IconStroke}" />
|
||||
<Line X1="15" Y1="1" X2="15" Y2="4" Style="{StaticResource IconStroke}" />
|
||||
<Line X1="9" Y1="20" X2="9" Y2="23" Style="{StaticResource IconStroke}" />
|
||||
<Line X1="15" Y1="20" X2="15" Y2="23" Style="{StaticResource IconStroke}" />
|
||||
<Line X1="20" Y1="9" X2="23" Y2="9" Style="{StaticResource IconStroke}" />
|
||||
<Line X1="20" Y1="14" X2="23" Y2="14" Style="{StaticResource IconStroke}" />
|
||||
<Line X1="1" Y1="9" X2="4" Y2="9" Style="{StaticResource IconStroke}" />
|
||||
<Line X1="1" Y1="14" X2="4" Y2="14" Style="{StaticResource IconStroke}" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</ResourceDictionary>
|
16
Windows/MassiveKnob/Resources/Digital.xaml
Normal file
16
Windows/MassiveKnob/Resources/Digital.xaml
Normal file
@ -0,0 +1,16 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
<Viewbox Stretch="Uniform" x:Key="Digital">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Canvas.Resources>
|
||||
<ResourceDictionary Source="IconStyle.xaml" />
|
||||
</Canvas.Resources>
|
||||
<Rectangle Canvas.Left="1" Canvas.Top="5" Width="22" Height="14" RadiusX="7" RadiusY="7" Style="{StaticResource IconStroke}" />
|
||||
<Ellipse Canvas.Left="13" Canvas.Top="9" Width="6" Height="6" Style="{StaticResource IconStroke}" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</ResourceDictionary>
|
File diff suppressed because one or more lines are too long
10
Windows/MassiveKnob/Resources/IconStyle.xaml
Normal file
10
Windows/MassiveKnob/Resources/IconStyle.xaml
Normal file
@ -0,0 +1,10 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Style x:Key="IconStroke">
|
||||
<Setter Property="Shape.StrokeThickness" Value="2" />
|
||||
<Setter Property="Shape.Stroke" Value="Black" />
|
||||
<Setter Property="Line.StrokeLineJoin" Value="Round" />
|
||||
<Setter Property="Line.StrokeStartLineCap" Value="Round" />
|
||||
<Setter Property="Line.StrokeEndLineCap" Value="Round" />
|
||||
</Style>
|
||||
</ResourceDictionary>
|
20
Windows/MassiveKnob/Resources/Logging.xaml
Normal file
20
Windows/MassiveKnob/Resources/Logging.xaml
Normal file
@ -0,0 +1,20 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
<Viewbox Stretch="Uniform" x:Key="Logging">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Name="path2" StrokeThickness="2" Stroke="Black" StrokeLineJoin="Round" StrokeStartLineCap="Round" StrokeEndLineCap="Round">
|
||||
<Path.Data>
|
||||
<PathGeometry Figures="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" FillRule="NonZero"/>
|
||||
</Path.Data>
|
||||
</Path>
|
||||
<Polyline Points="14 2 14 8 20 8" Name="polyline4" FillRule="NonZero" StrokeThickness="2" Stroke="Black" StrokeLineJoin="Round" StrokeStartLineCap="Round" StrokeEndLineCap="Round"/>
|
||||
<Line X1="16" Y1="13" X2="8" Y2="13" Name="line6" StrokeThickness="2" Stroke="Black" StrokeLineJoin="Round" StrokeStartLineCap="Round" StrokeEndLineCap="Round"/>
|
||||
<Line X1="16" Y1="17" X2="8" Y2="17" Name="line8" StrokeThickness="2" Stroke="Black" StrokeLineJoin="Round" StrokeStartLineCap="Round" StrokeEndLineCap="Round"/>
|
||||
<Polyline Points="10 9 9 9 8 9" Name="polyline10" FillRule="NonZero" StrokeThickness="2" Stroke="Black" StrokeLineJoin="Round" StrokeStartLineCap="Round" StrokeEndLineCap="Round"/>
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</ResourceDictionary>
|
Binary file not shown.
Before Width: | Height: | Size: 169 KiB After Width: | Height: | Size: 164 KiB |
Binary file not shown.
Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 165 KiB |
15
Windows/MassiveKnob/Resources/Startup.xaml
Normal file
15
Windows/MassiveKnob/Resources/Startup.xaml
Normal file
@ -0,0 +1,15 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
<Viewbox Stretch="Uniform" x:Key="Startup">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Canvas.Resources>
|
||||
<ResourceDictionary Source="IconStyle.xaml" />
|
||||
</Canvas.Resources>
|
||||
<Polygon Points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" FillRule="NonZero" Style="{StaticResource IconStroke}"/>
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</ResourceDictionary>
|
9
Windows/MassiveKnob/Settings/ILoggingSwitch.cs
Normal file
9
Windows/MassiveKnob/Settings/ILoggingSwitch.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using Serilog.Events;
|
||||
|
||||
namespace MassiveKnob.Settings
|
||||
{
|
||||
public interface ILoggingSwitch
|
||||
{
|
||||
void SetLogging(bool enabled, LogEventLevel minimumLevel);
|
||||
}
|
||||
}
|
26
Windows/MassiveKnob/Settings/LoggingSwitch.cs
Normal file
26
Windows/MassiveKnob/Settings/LoggingSwitch.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace MassiveKnob.Settings
|
||||
{
|
||||
public class LoggingSwitch : LoggingLevelSwitch, ILoggingSwitch
|
||||
{
|
||||
private bool enabled;
|
||||
private LogEventLevel minimumLevel;
|
||||
|
||||
|
||||
public bool IsIncluded(LogEvent logEvent)
|
||||
{
|
||||
return enabled && logEvent.Level >= minimumLevel;
|
||||
}
|
||||
|
||||
|
||||
// ReSharper disable ParameterHidesMember
|
||||
public void SetLogging(bool enabled, LogEventLevel minimumLevel)
|
||||
{
|
||||
this.enabled = enabled;
|
||||
this.minimumLevel = minimumLevel;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,10 +2,25 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace MassiveKnob.Settings
|
||||
{
|
||||
public class Settings
|
||||
public enum SettingsMenuItem
|
||||
{
|
||||
None,
|
||||
Device,
|
||||
AnalogInputs,
|
||||
DigitalInputs,
|
||||
AnalogOutputs,
|
||||
DigitalOutputs,
|
||||
Logging,
|
||||
Startup
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class MassiveKnobSettings
|
||||
{
|
||||
public DeviceSettings Device { get; set; }
|
||||
public List<ActionSettings> AnalogInput { get; set; }
|
||||
@ -13,6 +28,20 @@ namespace MassiveKnob.Settings
|
||||
public List<ActionSettings> AnalogOutput { get; set; }
|
||||
public List<ActionSettings> DigitalOutput { get; set; }
|
||||
|
||||
private UISettings ui;
|
||||
public UISettings UI
|
||||
{
|
||||
get => ui ?? (ui = new UISettings());
|
||||
set => ui = value ?? new UISettings();
|
||||
}
|
||||
|
||||
private LogSettings log;
|
||||
public LogSettings Log
|
||||
{
|
||||
get => log ?? (log = new LogSettings());
|
||||
set => log = value ?? new LogSettings();
|
||||
}
|
||||
|
||||
|
||||
public void Verify()
|
||||
{
|
||||
@ -23,15 +52,17 @@ namespace MassiveKnob.Settings
|
||||
}
|
||||
|
||||
|
||||
public Settings Clone()
|
||||
public MassiveKnobSettings Clone()
|
||||
{
|
||||
return new Settings
|
||||
return new MassiveKnobSettings
|
||||
{
|
||||
Device = Device?.Clone(),
|
||||
AnalogInput = AnalogInput.Select(a => a?.Clone()).ToList(),
|
||||
DigitalInput = DigitalInput.Select(a => a?.Clone()).ToList(),
|
||||
AnalogOutput = AnalogOutput.Select(a => a?.Clone()).ToList(),
|
||||
DigitalOutput = DigitalOutput.Select(a => a?.Clone()).ToList()
|
||||
DigitalOutput = DigitalOutput.Select(a => a?.Clone()).ToList(),
|
||||
UI = UI.Clone(),
|
||||
Log = Log.Clone()
|
||||
};
|
||||
}
|
||||
|
||||
@ -70,5 +101,35 @@ namespace MassiveKnob.Settings
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class UISettings
|
||||
{
|
||||
public SettingsMenuItem ActiveMenuItem { get; set; } = SettingsMenuItem.None;
|
||||
|
||||
public UISettings Clone()
|
||||
{
|
||||
return new UISettings
|
||||
{
|
||||
ActiveMenuItem = ActiveMenuItem
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class LogSettings
|
||||
{
|
||||
public bool Enabled { get; set; } = true;
|
||||
public LogEventLevel Level { get; set; } = LogEventLevel.Information;
|
||||
|
||||
public LogSettings Clone()
|
||||
{
|
||||
return new LogSettings
|
||||
{
|
||||
Enabled = Enabled,
|
||||
Level = Level
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@ using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace MassiveKnob.Settings
|
||||
{
|
||||
public static class SettingsJsonSerializer
|
||||
public static class MassiveKnobSettingsJsonSerializer
|
||||
{
|
||||
private static readonly JsonSerializerSettings DefaultSettings = new JsonSerializerSettings
|
||||
{
|
||||
@ -29,12 +29,12 @@ namespace MassiveKnob.Settings
|
||||
}
|
||||
|
||||
|
||||
public static Task Serialize(Settings settings)
|
||||
public static Task Serialize(MassiveKnobSettings settings)
|
||||
{
|
||||
return Serialize(settings, GetDefaultFilename());
|
||||
}
|
||||
|
||||
public static async Task Serialize(Settings settings, string filename)
|
||||
public static async Task Serialize(MassiveKnobSettings settings, string filename)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(settings, DefaultSettings);
|
||||
|
||||
@ -47,14 +47,14 @@ namespace MassiveKnob.Settings
|
||||
}
|
||||
|
||||
|
||||
public static Settings Deserialize()
|
||||
public static MassiveKnobSettings Deserialize()
|
||||
{
|
||||
return Deserialize(GetDefaultFilename());
|
||||
}
|
||||
|
||||
public static Settings Deserialize(string filename)
|
||||
public static MassiveKnobSettings Deserialize(string filename)
|
||||
{
|
||||
Settings settings = null;
|
||||
MassiveKnobSettings settings = null;
|
||||
|
||||
if (File.Exists(filename))
|
||||
{
|
||||
@ -67,11 +67,11 @@ namespace MassiveKnob.Settings
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(json))
|
||||
settings = JsonConvert.DeserializeObject<Settings>(json, DefaultSettings);
|
||||
settings = JsonConvert.DeserializeObject<MassiveKnobSettings>(json, DefaultSettings);
|
||||
}
|
||||
|
||||
if (settings == null)
|
||||
settings = new Settings();
|
||||
settings = new MassiveKnobSettings();
|
||||
|
||||
settings.Verify();
|
||||
return settings;
|
224
Windows/MassiveKnob/Strings.Designer.cs
generated
224
Windows/MassiveKnob/Strings.Designer.cs
generated
@ -22,7 +22,7 @@ namespace MassiveKnob {
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Strings {
|
||||
public class Strings {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
@ -36,7 +36,7 @@ namespace MassiveKnob {
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
public static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MassiveKnob.Strings", typeof(Strings).Assembly);
|
||||
@ -51,7 +51,7 @@ namespace MassiveKnob {
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
public static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
@ -63,10 +63,226 @@ namespace MassiveKnob {
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Not configured.
|
||||
/// </summary>
|
||||
internal static string ActionNotConfigured {
|
||||
public static string ActionNotConfigured {
|
||||
get {
|
||||
return ResourceManager.GetString("ActionNotConfigured", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Input #{0}.
|
||||
/// </summary>
|
||||
public static string InputHeader {
|
||||
get {
|
||||
return ResourceManager.GetString("InputHeader", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Enabled.
|
||||
/// </summary>
|
||||
public static string LoggingEnabled {
|
||||
get {
|
||||
return ResourceManager.GetString("LoggingEnabled", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Logging level.
|
||||
/// </summary>
|
||||
public static string LoggingLevel {
|
||||
get {
|
||||
return ResourceManager.GetString("LoggingLevel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Error.
|
||||
/// </summary>
|
||||
public static string LoggingLevelError {
|
||||
get {
|
||||
return ResourceManager.GetString("LoggingLevelError", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Only serious errors are logged..
|
||||
/// </summary>
|
||||
public static string LoggingLevelErrorDescription {
|
||||
get {
|
||||
return ResourceManager.GetString("LoggingLevelErrorDescription", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Information.
|
||||
/// </summary>
|
||||
public static string LoggingLevelInformation {
|
||||
get {
|
||||
return ResourceManager.GetString("LoggingLevelInformation", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Information, warnings and errors are logged. This is the recommended level..
|
||||
/// </summary>
|
||||
public static string LoggingLevelInformationDescription {
|
||||
get {
|
||||
return ResourceManager.GetString("LoggingLevelInformationDescription", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Verbose.
|
||||
/// </summary>
|
||||
public static string LoggingLevelVerbose {
|
||||
get {
|
||||
return ResourceManager.GetString("LoggingLevelVerbose", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to I like big logs and I can not lie!.
|
||||
/// </summary>
|
||||
public static string LoggingLevelVerboseDescription {
|
||||
get {
|
||||
return ResourceManager.GetString("LoggingLevelVerboseDescription", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Warning.
|
||||
/// </summary>
|
||||
public static string LoggingLevelWarning {
|
||||
get {
|
||||
return ResourceManager.GetString("LoggingLevelWarning", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Warnings and errors are logged. This includes issues with loading plugins..
|
||||
/// </summary>
|
||||
public static string LoggingLevelWarningDescription {
|
||||
get {
|
||||
return ResourceManager.GetString("LoggingLevelWarningDescription", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Logs are saved to {0}.
|
||||
/// </summary>
|
||||
public static string LoggingOutputPath {
|
||||
get {
|
||||
return ResourceManager.GetString("LoggingOutputPath", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Device.
|
||||
/// </summary>
|
||||
public static string MenuGroupDevice {
|
||||
get {
|
||||
return ResourceManager.GetString("MenuGroupDevice", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Settings.
|
||||
/// </summary>
|
||||
public static string MenuGroupSettings {
|
||||
get {
|
||||
return ResourceManager.GetString("MenuGroupSettings", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Analog inputs.
|
||||
/// </summary>
|
||||
public static string MenuItemAnalogInputs {
|
||||
get {
|
||||
return ResourceManager.GetString("MenuItemAnalogInputs", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Analog outputs.
|
||||
/// </summary>
|
||||
public static string MenuItemAnalogOutputs {
|
||||
get {
|
||||
return ResourceManager.GetString("MenuItemAnalogOutputs", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Device.
|
||||
/// </summary>
|
||||
public static string MenuItemDevice {
|
||||
get {
|
||||
return ResourceManager.GetString("MenuItemDevice", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Digital inputs.
|
||||
/// </summary>
|
||||
public static string MenuItemDigitalInputs {
|
||||
get {
|
||||
return ResourceManager.GetString("MenuItemDigitalInputs", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Digital outputs.
|
||||
/// </summary>
|
||||
public static string MenuItemDigitalOutputs {
|
||||
get {
|
||||
return ResourceManager.GetString("MenuItemDigitalOutputs", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Logging.
|
||||
/// </summary>
|
||||
public static string MenuItemLogging {
|
||||
get {
|
||||
return ResourceManager.GetString("MenuItemLogging", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Startup.
|
||||
/// </summary>
|
||||
public static string MenuItemStartup {
|
||||
get {
|
||||
return ResourceManager.GetString("MenuItemStartup", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Output #{0}.
|
||||
/// </summary>
|
||||
public static string OutputHeader {
|
||||
get {
|
||||
return ResourceManager.GetString("OutputHeader", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Run MassiveKnob at startup.
|
||||
/// </summary>
|
||||
public static string RunAtStartup {
|
||||
get {
|
||||
return ResourceManager.GetString("RunAtStartup", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Massive Knob - Settings.
|
||||
/// </summary>
|
||||
public static string SettingsWindowTitle {
|
||||
get {
|
||||
return ResourceManager.GetString("SettingsWindowTitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -120,4 +120,76 @@
|
||||
<data name="ActionNotConfigured" xml:space="preserve">
|
||||
<value>Not configured</value>
|
||||
</data>
|
||||
<data name="InputHeader" xml:space="preserve">
|
||||
<value>Input #{0}</value>
|
||||
</data>
|
||||
<data name="LoggingEnabled" xml:space="preserve">
|
||||
<value>Enabled</value>
|
||||
</data>
|
||||
<data name="LoggingLevel" xml:space="preserve">
|
||||
<value>Logging level</value>
|
||||
</data>
|
||||
<data name="LoggingLevelError" xml:space="preserve">
|
||||
<value>Error</value>
|
||||
</data>
|
||||
<data name="LoggingLevelErrorDescription" xml:space="preserve">
|
||||
<value>Only serious errors are logged.</value>
|
||||
</data>
|
||||
<data name="LoggingLevelInformation" xml:space="preserve">
|
||||
<value>Information</value>
|
||||
</data>
|
||||
<data name="LoggingLevelInformationDescription" xml:space="preserve">
|
||||
<value>Information, warnings and errors are logged. This is the recommended level.</value>
|
||||
</data>
|
||||
<data name="LoggingLevelVerbose" xml:space="preserve">
|
||||
<value>Verbose</value>
|
||||
</data>
|
||||
<data name="LoggingLevelVerboseDescription" xml:space="preserve">
|
||||
<value>I like big logs and I can not lie!</value>
|
||||
</data>
|
||||
<data name="LoggingLevelWarning" xml:space="preserve">
|
||||
<value>Warning</value>
|
||||
</data>
|
||||
<data name="LoggingLevelWarningDescription" xml:space="preserve">
|
||||
<value>Warnings and errors are logged. This includes issues with loading plugins.</value>
|
||||
</data>
|
||||
<data name="LoggingOutputPath" xml:space="preserve">
|
||||
<value>Logs are saved to {0}</value>
|
||||
</data>
|
||||
<data name="MenuGroupDevice" xml:space="preserve">
|
||||
<value>Device</value>
|
||||
</data>
|
||||
<data name="MenuGroupSettings" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
<data name="MenuItemAnalogInputs" xml:space="preserve">
|
||||
<value>Analog inputs</value>
|
||||
</data>
|
||||
<data name="MenuItemAnalogOutputs" xml:space="preserve">
|
||||
<value>Analog outputs</value>
|
||||
</data>
|
||||
<data name="MenuItemDevice" xml:space="preserve">
|
||||
<value>Device</value>
|
||||
</data>
|
||||
<data name="MenuItemDigitalInputs" xml:space="preserve">
|
||||
<value>Digital inputs</value>
|
||||
</data>
|
||||
<data name="MenuItemDigitalOutputs" xml:space="preserve">
|
||||
<value>Digital outputs</value>
|
||||
</data>
|
||||
<data name="MenuItemLogging" xml:space="preserve">
|
||||
<value>Logging</value>
|
||||
</data>
|
||||
<data name="MenuItemStartup" xml:space="preserve">
|
||||
<value>Startup</value>
|
||||
</data>
|
||||
<data name="OutputHeader" xml:space="preserve">
|
||||
<value>Output #{0}</value>
|
||||
</data>
|
||||
<data name="RunAtStartup" xml:space="preserve">
|
||||
<value>Run MassiveKnob at startup</value>
|
||||
</data>
|
||||
<data name="SettingsWindowTitle" xml:space="preserve">
|
||||
<value>Massive Knob - Settings</value>
|
||||
</data>
|
||||
</root>
|
@ -1,7 +1,7 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Style TargetType="Window" x:Key="DefaultWindow">
|
||||
<Setter Property="Background" Value="#f0f0f0" />
|
||||
<Setter Property="Background" Value="White" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="MenuItem" x:Key="DefaultMenuItem">
|
||||
@ -31,6 +31,14 @@
|
||||
</Style>
|
||||
|
||||
<Style TargetType="TextBlock" x:Key="ComboBoxDescription">
|
||||
<Setter Property="Foreground" Value="{x:Static SystemColors.GrayTextBrush}" />
|
||||
<Setter Property="Foreground" Value="#808080" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="TextBlock" x:Key="Label">
|
||||
<Setter Property="Margin" Value="0,0,0,4" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="TextBlock" x:Key="SubLabel">
|
||||
<Setter Property="Foreground" Value="#808080" />
|
||||
</Style>
|
||||
</ResourceDictionary>
|
29
Windows/MassiveKnob/View/Settings/AnalogInputsView.xaml
Normal file
29
Windows/MassiveKnob/View/Settings/AnalogInputsView.xaml
Normal file
@ -0,0 +1,29 @@
|
||||
<UserControl x:Class="MassiveKnob.View.Settings.AnalogInputsView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:viewModel="clr-namespace:MassiveKnob.ViewModel"
|
||||
xmlns:view="clr-namespace:MassiveKnob.View"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="300" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance Type=viewModel:SettingsViewModelDesignTime, IsDesignTimeCreatable=True}">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Style.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
||||
<StackPanel Orientation="Vertical" SnapsToDevicePixels="True" UseLayoutRounding="True" TextOptions.TextFormattingMode="Display">
|
||||
<ItemsControl ItemsSource="{Binding AnalogInputs}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<view:InputOutputView />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
</StackPanel>
|
||||
</UserControl>
|
13
Windows/MassiveKnob/View/Settings/AnalogInputsView.xaml.cs
Normal file
13
Windows/MassiveKnob/View/Settings/AnalogInputsView.xaml.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace MassiveKnob.View.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for AnalogInputsView.xaml
|
||||
/// </summary>
|
||||
public partial class AnalogInputsView
|
||||
{
|
||||
public AnalogInputsView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
28
Windows/MassiveKnob/View/Settings/AnalogOutputsView.xaml
Normal file
28
Windows/MassiveKnob/View/Settings/AnalogOutputsView.xaml
Normal file
@ -0,0 +1,28 @@
|
||||
<UserControl x:Class="MassiveKnob.View.Settings.AnalogOutputsView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:viewModel="clr-namespace:MassiveKnob.ViewModel"
|
||||
xmlns:view="clr-namespace:MassiveKnob.View"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="300" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance Type=viewModel:SettingsViewModelDesignTime, IsDesignTimeCreatable=True}">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Style.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
||||
<StackPanel Orientation="Vertical" SnapsToDevicePixels="True" UseLayoutRounding="True" TextOptions.TextFormattingMode="Display">
|
||||
<ItemsControl ItemsSource="{Binding AnalogOutputs}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<view:InputOutputView />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</UserControl>
|
13
Windows/MassiveKnob/View/Settings/AnalogOutputsView.xaml.cs
Normal file
13
Windows/MassiveKnob/View/Settings/AnalogOutputsView.xaml.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace MassiveKnob.View.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for AnalogOutputsView.xaml
|
||||
/// </summary>
|
||||
public partial class AnalogOutputsView
|
||||
{
|
||||
public AnalogOutputsView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
44
Windows/MassiveKnob/View/Settings/DeviceView.xaml
Normal file
44
Windows/MassiveKnob/View/Settings/DeviceView.xaml
Normal file
@ -0,0 +1,44 @@
|
||||
<UserControl x:Class="MassiveKnob.View.Settings.DeviceView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:viewModel="clr-namespace:MassiveKnob.ViewModel"
|
||||
xmlns:helpers="clr-namespace:MassiveKnob.Helpers"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="200" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance Type=viewModel:SettingsViewModelDesignTime, IsDesignTimeCreatable=True}">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Style.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<DataTemplate x:Key="DeviceDropdownItem">
|
||||
<StackPanel Orientation="Vertical" d:DataContext="{d:DesignInstance Type=viewModel:DeviceViewModel}">
|
||||
<TextBlock Text="{Binding Name}" />
|
||||
<TextBlock Text="{Binding Description}" Style="{StaticResource ComboBoxDescription}" Visibility="{Binding DescriptionVisibility}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="DeviceSelectedItem">
|
||||
<TextBlock Text="{Binding Name}" d:DataContext="{d:DesignInstance Type=viewModel:DeviceViewModel}" />
|
||||
</DataTemplate>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
||||
<StackPanel Orientation="Vertical" SnapsToDevicePixels="True" UseLayoutRounding="True" TextOptions.TextFormattingMode="Display">
|
||||
<StackPanel Orientation="Vertical" Style="{StaticResource Content}">
|
||||
<ComboBox
|
||||
ItemsSource="{Binding Devices}"
|
||||
SelectedItem="{Binding SelectedDevice}"
|
||||
IsSynchronizedWithCurrentItem="False"
|
||||
ItemTemplateSelector="{helpers:ComboBoxTemplateSelector
|
||||
SelectedItemTemplate={StaticResource DeviceSelectedItem},
|
||||
DropdownItemsTemplate={StaticResource DeviceDropdownItem}}" />
|
||||
|
||||
|
||||
<ContentControl Focusable="False" Content="{Binding SettingsControl}" Style="{StaticResource SettingsControl}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</UserControl>
|
13
Windows/MassiveKnob/View/Settings/DeviceView.xaml.cs
Normal file
13
Windows/MassiveKnob/View/Settings/DeviceView.xaml.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace MassiveKnob.View.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for DeviceView.xaml
|
||||
/// </summary>
|
||||
public partial class DeviceView
|
||||
{
|
||||
public DeviceView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
28
Windows/MassiveKnob/View/Settings/DigitalInputsView.xaml
Normal file
28
Windows/MassiveKnob/View/Settings/DigitalInputsView.xaml
Normal file
@ -0,0 +1,28 @@
|
||||
<UserControl x:Class="MassiveKnob.View.Settings.DigitalInputsView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:viewModel="clr-namespace:MassiveKnob.ViewModel"
|
||||
xmlns:view="clr-namespace:MassiveKnob.View"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="300" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance Type=viewModel:SettingsViewModelDesignTime, IsDesignTimeCreatable=True}">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Style.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
||||
<StackPanel Orientation="Vertical" SnapsToDevicePixels="True" UseLayoutRounding="True" TextOptions.TextFormattingMode="Display">
|
||||
<ItemsControl ItemsSource="{Binding DigitalInputs}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<view:InputOutputView />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</UserControl>
|
13
Windows/MassiveKnob/View/Settings/DigitalInputsView.xaml.cs
Normal file
13
Windows/MassiveKnob/View/Settings/DigitalInputsView.xaml.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace MassiveKnob.View.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for DigitalInputsView.xaml
|
||||
/// </summary>
|
||||
public partial class DigitalInputsView
|
||||
{
|
||||
public DigitalInputsView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
28
Windows/MassiveKnob/View/Settings/DigitalOutputsView.xaml
Normal file
28
Windows/MassiveKnob/View/Settings/DigitalOutputsView.xaml
Normal file
@ -0,0 +1,28 @@
|
||||
<UserControl x:Class="MassiveKnob.View.Settings.DigitalOutputsView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:viewModel="clr-namespace:MassiveKnob.ViewModel"
|
||||
xmlns:view="clr-namespace:MassiveKnob.View"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="300" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance Type=viewModel:SettingsViewModelDesignTime, IsDesignTimeCreatable=True}">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Style.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
||||
<StackPanel Orientation="Vertical" SnapsToDevicePixels="True" UseLayoutRounding="True" TextOptions.TextFormattingMode="Display">
|
||||
<ItemsControl ItemsSource="{Binding DigitalOutputs}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<view:InputOutputView />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</UserControl>
|
13
Windows/MassiveKnob/View/Settings/DigitalOutputsView.xaml.cs
Normal file
13
Windows/MassiveKnob/View/Settings/DigitalOutputsView.xaml.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace MassiveKnob.View.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for DigitalOutputsView.xaml
|
||||
/// </summary>
|
||||
public partial class DigitalOutputsView
|
||||
{
|
||||
public DigitalOutputsView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
50
Windows/MassiveKnob/View/Settings/LoggingView.xaml
Normal file
50
Windows/MassiveKnob/View/Settings/LoggingView.xaml
Normal file
@ -0,0 +1,50 @@
|
||||
<UserControl x:Class="MassiveKnob.View.Settings.LoggingView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:viewModel="clr-namespace:MassiveKnob.ViewModel"
|
||||
xmlns:helpers="clr-namespace:MassiveKnob.Helpers"
|
||||
xmlns:massiveKnob="clr-namespace:MassiveKnob"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="200" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance Type=viewModel:SettingsViewModelDesignTime, IsDesignTimeCreatable=True}">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Style.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<DataTemplate x:Key="LoggingLevelDropdownItem">
|
||||
<StackPanel Orientation="Vertical" d:DataContext="{d:DesignInstance Type=viewModel:LoggingLevelViewModel}">
|
||||
<TextBlock Text="{Binding Name}" />
|
||||
<TextBlock Text="{Binding Description}" Style="{StaticResource ComboBoxDescription}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="LoggingLevelSelectedItem">
|
||||
<TextBlock Text="{Binding Name}" d:DataContext="{d:DesignInstance Type=viewModel:LoggingLevelViewModel}" />
|
||||
</DataTemplate>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
||||
<StackPanel Orientation="Vertical" SnapsToDevicePixels="True" UseLayoutRounding="True" TextOptions.TextFormattingMode="Display">
|
||||
<StackPanel Orientation="Vertical" Style="{StaticResource Content}">
|
||||
<CheckBox Margin="0,0,0,8" IsChecked="{Binding LoggingEnabled}">
|
||||
<TextBlock Text="{x:Static massiveKnob:Strings.LoggingEnabled}" />
|
||||
</CheckBox>
|
||||
|
||||
<TextBlock Text="{x:Static massiveKnob:Strings.LoggingLevel}" Style="{StaticResource Label}" />
|
||||
<ComboBox
|
||||
Margin="0,0,0,8"
|
||||
ItemsSource="{Binding LoggingLevels}"
|
||||
SelectedItem="{Binding SelectedLoggingLevel}"
|
||||
IsSynchronizedWithCurrentItem="False"
|
||||
ItemTemplateSelector="{helpers:ComboBoxTemplateSelector
|
||||
SelectedItemTemplate={StaticResource LoggingLevelSelectedItem},
|
||||
DropdownItemsTemplate={StaticResource LoggingLevelDropdownItem}}" />
|
||||
|
||||
<TextBlock Text="{Binding LoggingOutputPath}" Style="{StaticResource SubLabel}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</UserControl>
|
13
Windows/MassiveKnob/View/Settings/LoggingView.xaml.cs
Normal file
13
Windows/MassiveKnob/View/Settings/LoggingView.xaml.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace MassiveKnob.View.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for LoggingView.xaml
|
||||
/// </summary>
|
||||
public partial class LoggingView
|
||||
{
|
||||
public LoggingView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
26
Windows/MassiveKnob/View/Settings/StartupView.xaml
Normal file
26
Windows/MassiveKnob/View/Settings/StartupView.xaml
Normal file
@ -0,0 +1,26 @@
|
||||
<UserControl x:Class="MassiveKnob.View.Settings.StartupView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:viewModel="clr-namespace:MassiveKnob.ViewModel"
|
||||
xmlns:massiveKnob="clr-namespace:MassiveKnob"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="200" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance Type=viewModel:SettingsViewModelDesignTime, IsDesignTimeCreatable=True}">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Style.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
||||
<StackPanel Orientation="Vertical" SnapsToDevicePixels="True" UseLayoutRounding="True" TextOptions.TextFormattingMode="Display">
|
||||
<StackPanel Orientation="Vertical" Style="{StaticResource Content}">
|
||||
<CheckBox Margin="0,0,0,8" IsChecked="{Binding RunAtStartup}">
|
||||
<TextBlock Text="{x:Static massiveKnob:Strings.RunAtStartup}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</UserControl>
|
13
Windows/MassiveKnob/View/Settings/StartupView.xaml.cs
Normal file
13
Windows/MassiveKnob/View/Settings/StartupView.xaml.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace MassiveKnob.View.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for StartupView.xaml
|
||||
/// </summary>
|
||||
public partial class StartupView
|
||||
{
|
||||
public StartupView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
@ -3,118 +3,89 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:helpers="clr-namespace:MassiveKnob.Helpers"
|
||||
xmlns:viewModel="clr-namespace:MassiveKnob.ViewModel"
|
||||
xmlns:view="clr-namespace:MassiveKnob.View"
|
||||
xmlns:massiveKnob="clr-namespace:MassiveKnob"
|
||||
xmlns:helpers="clr-namespace:MassiveKnob.Helpers"
|
||||
xmlns:settings="clr-namespace:MassiveKnob.Settings"
|
||||
mc:Ignorable="d"
|
||||
Title="Massive Knob - Settings" Height="555" Width="704.231"
|
||||
Title="{x:Static massiveKnob:Strings.SettingsWindowTitle}" Height="555" Width="800"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
ResizeMode="CanMinimize"
|
||||
Style="{StaticResource DefaultWindow}"
|
||||
d:DataContext="{d:DesignInstance Type=viewModel:SettingsViewModel}">
|
||||
d:DataContext="{d:DesignInstance Type=viewModel:SettingsViewModelDesignTime, IsDesignTimeCreatable=True}">
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../Style.xaml" />
|
||||
<ResourceDictionary Source="../Resources/Analog.xaml" />
|
||||
<ResourceDictionary Source="../Resources/Device.xaml" />
|
||||
<ResourceDictionary Source="../Resources/Digital.xaml" />
|
||||
<ResourceDictionary Source="../Resources/Logging.xaml" />
|
||||
<ResourceDictionary Source="../Resources/Device.xaml" />
|
||||
<ResourceDictionary Source="../Resources/Startup.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<DataTemplate x:Key="DeviceDropdownItem">
|
||||
<StackPanel Orientation="Vertical" d:DataContext="{d:DesignInstance Type=viewModel:DeviceViewModel}">
|
||||
<TextBlock Text="{Binding Name}" />
|
||||
<TextBlock Text="{Binding Description}" Style="{StaticResource ComboBoxDescription}" Visibility="{Binding DescriptionVisibility}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
<helpers:ComparisonConverter x:Key="ComparisonConverter" />
|
||||
|
||||
<DataTemplate x:Key="DeviceSelectedItem">
|
||||
<TextBlock Text="{Binding Name}" d:DataContext="{d:DesignInstance Type=viewModel:DeviceViewModel}" />
|
||||
</DataTemplate>
|
||||
<Style TargetType="StackPanel" x:Key="Menu">
|
||||
<Setter Property="Background" Value="#f0f0f0" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="TextBlock" x:Key="MenuGroup">
|
||||
<Setter Property="Background" Value="#e0e0e0" />
|
||||
<Setter Property="Padding" Value="8,4,8,4" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="RadioButton" x:Key="MenuItem">
|
||||
<Setter Property="OverridesDefaultStyle" Value="True" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="RadioButton">
|
||||
<DockPanel x:Name="Root">
|
||||
<ContentControl DockPanel.Dock="Left" Content="{TemplateBinding viewModel:MenuItemProperties.Icon}" Height="16" Margin="8,0,0,0" />
|
||||
<TextBlock Padding="8,4,8,4" FontSize="14" Text="{TemplateBinding viewModel:MenuItemProperties.Text}" />
|
||||
</DockPanel>
|
||||
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#4081a9f1" TargetName="Root" />
|
||||
</Trigger>
|
||||
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter Property="Background" Value="#8081a9f1" TargetName="Root" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<!--
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="250" />
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
Device
|
||||
<ScrollViewer Grid.Row="0" Grid.Column="0" VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Style="{StaticResource Menu}" Orientation="Vertical" SnapsToDevicePixels="True" UseLayoutRounding="True" TextOptions.TextFormattingMode="Display">
|
||||
<TextBlock Style="{StaticResource MenuGroup}" Text="{x:Static massiveKnob:Strings.MenuGroupDevice}" />
|
||||
<RadioButton Style="{StaticResource MenuItem}" viewModel:MenuItemProperties.Text="{x:Static massiveKnob:Strings.MenuItemDevice}" viewModel:MenuItemProperties.Icon="{StaticResource Device}" IsChecked="{Binding Path=SelectedMenuItem, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static settings:SettingsMenuItem.Device}}"/>
|
||||
<RadioButton Style="{StaticResource MenuItem}" viewModel:MenuItemProperties.Text="{x:Static massiveKnob:Strings.MenuItemAnalogInputs}" viewModel:MenuItemProperties.Icon="{StaticResource Analog}" Visibility="{Binding AnalogInputVisibility}" IsChecked="{Binding Path=SelectedMenuItem, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static settings:SettingsMenuItem.AnalogInputs}}"/>
|
||||
<RadioButton Style="{StaticResource MenuItem}" viewModel:MenuItemProperties.Text="{x:Static massiveKnob:Strings.MenuItemDigitalInputs}" viewModel:MenuItemProperties.Icon="{StaticResource Digital}" Visibility="{Binding DigitalInputVisibility}" IsChecked="{Binding Path=SelectedMenuItem, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static settings:SettingsMenuItem.DigitalInputs}}"/>
|
||||
<RadioButton Style="{StaticResource MenuItem}" viewModel:MenuItemProperties.Text="{x:Static massiveKnob:Strings.MenuItemAnalogOutputs}" viewModel:MenuItemProperties.Icon="{StaticResource Analog}" Visibility="{Binding AnalogOutputVisibility}" IsChecked="{Binding Path=SelectedMenuItem, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static settings:SettingsMenuItem.AnalogOutputs}}"/>
|
||||
<RadioButton Style="{StaticResource MenuItem}" viewModel:MenuItemProperties.Text="{x:Static massiveKnob:Strings.MenuItemDigitalOutputs}" viewModel:MenuItemProperties.Icon="{StaticResource Digital}" Visibility="{Binding DigitalOutputVisibility}" IsChecked="{Binding Path=SelectedMenuItem, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static settings:SettingsMenuItem.DigitalOutputs}}"/>
|
||||
|
||||
-->
|
||||
<TextBlock Style="{StaticResource Header}">Device</TextBlock>
|
||||
|
||||
<StackPanel Orientation="Vertical" Style="{StaticResource Content}">
|
||||
<ComboBox
|
||||
ItemsSource="{Binding Devices}"
|
||||
SelectedItem="{Binding SelectedDevice}"
|
||||
IsSynchronizedWithCurrentItem="False"
|
||||
ItemTemplateSelector="{helpers:ComboBoxTemplateSelector
|
||||
SelectedItemTemplate={StaticResource DeviceSelectedItem},
|
||||
DropdownItemsTemplate={StaticResource DeviceDropdownItem}}" />
|
||||
|
||||
|
||||
<ContentControl Focusable="False" Content="{Binding SettingsControl}" Style="{StaticResource SettingsControl}" />
|
||||
</StackPanel>
|
||||
|
||||
|
||||
<!--
|
||||
|
||||
Analog inputs
|
||||
|
||||
-->
|
||||
<TextBlock Style="{StaticResource Header}" Visibility="{Binding AnalogInputVisibility}">Analog inputs</TextBlock>
|
||||
|
||||
<ItemsControl ItemsSource="{Binding AnalogInputs}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<view:InputOutputView />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
|
||||
<!--
|
||||
|
||||
Digital inputs
|
||||
|
||||
-->
|
||||
<TextBlock Style="{StaticResource Header}" Visibility="{Binding DigitalInputVisibility}">Digital inputs</TextBlock>
|
||||
|
||||
<ItemsControl ItemsSource="{Binding DigitalInputs}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<view:InputOutputView />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
|
||||
<!--
|
||||
|
||||
Analog outputs
|
||||
|
||||
-->
|
||||
<TextBlock Style="{StaticResource Header}" Visibility="{Binding AnalogOutputVisibility}">Analog outputs</TextBlock>
|
||||
|
||||
<ItemsControl ItemsSource="{Binding AnalogOutputs}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<view:InputOutputView />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
|
||||
<!--
|
||||
|
||||
Digital outputs
|
||||
|
||||
-->
|
||||
<TextBlock Style="{StaticResource Header}" Visibility="{Binding DigitalOutputVisibility}">Digital outputs</TextBlock>
|
||||
|
||||
<ItemsControl ItemsSource="{Binding DigitalOutputs}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<view:InputOutputView />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<TextBlock Style="{StaticResource MenuGroup}" Text="{x:Static massiveKnob:Strings.MenuGroupSettings}" />
|
||||
<RadioButton Style="{StaticResource MenuItem}" viewModel:MenuItemProperties.Text="{x:Static massiveKnob:Strings.MenuItemLogging}" viewModel:MenuItemProperties.Icon="{StaticResource Logging}" IsChecked="{Binding Path=SelectedMenuItem, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static settings:SettingsMenuItem.Logging}}"/>
|
||||
<RadioButton Style="{StaticResource MenuItem}" viewModel:MenuItemProperties.Text="{x:Static massiveKnob:Strings.MenuItemStartup}" viewModel:MenuItemProperties.Icon="{StaticResource Startup}" IsChecked="{Binding Path=SelectedMenuItem, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static settings:SettingsMenuItem.Startup}}"/>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<ScrollViewer Grid.Row="0" Grid.Column="1" VerticalScrollBarVisibility="Auto">
|
||||
<ContentControl Content="{Binding SelectedView}" />
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
@ -7,10 +7,16 @@ namespace MassiveKnob.View
|
||||
/// </summary>
|
||||
public partial class SettingsWindow
|
||||
{
|
||||
// ReSharper disable once SuggestBaseTypeForParameter - for clarity
|
||||
public SettingsWindow(SettingsViewModel settingsViewModel)
|
||||
{
|
||||
DataContext = settingsViewModel;
|
||||
InitializeComponent();
|
||||
|
||||
Closed += (sender, args) =>
|
||||
{
|
||||
settingsViewModel.Dispose();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows.Controls;
|
||||
using MassiveKnob.Model;
|
||||
using MassiveKnob.Core;
|
||||
using MassiveKnob.Plugin;
|
||||
|
||||
namespace MassiveKnob.ViewModel
|
||||
{
|
||||
public class InputOutputViewModel : INotifyPropertyChanged
|
||||
public class InputOutputViewModel : IDisposable, INotifyPropertyChanged
|
||||
{
|
||||
private readonly IMassiveKnobOrchestrator orchestrator;
|
||||
private readonly MassiveKnobActionType actionType;
|
||||
@ -19,9 +20,11 @@ namespace MassiveKnob.ViewModel
|
||||
|
||||
|
||||
// ReSharper disable UnusedMember.Global - used by WPF Binding
|
||||
public string DisplayName => actionType == MassiveKnobActionType.OutputAnalog || actionType == MassiveKnobActionType.OutputDigital
|
||||
? $"Output #{index + 1}"
|
||||
: $"Input #{index + 1}";
|
||||
public string DisplayName => string.Format(
|
||||
actionType == MassiveKnobActionType.OutputAnalog || actionType == MassiveKnobActionType.OutputDigital
|
||||
? Strings.OutputHeader
|
||||
: Strings.InputHeader,
|
||||
index + 1);
|
||||
|
||||
public IList<ActionViewModel> Actions { get; }
|
||||
|
||||
@ -51,6 +54,9 @@ namespace MassiveKnob.ViewModel
|
||||
if (value == actionSettingsControl)
|
||||
return;
|
||||
|
||||
if (actionSettingsControl is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
|
||||
actionSettingsControl = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
@ -65,6 +71,11 @@ namespace MassiveKnob.ViewModel
|
||||
this.index = index;
|
||||
|
||||
|
||||
// For design-time support
|
||||
if (orchestrator == null)
|
||||
return;
|
||||
|
||||
|
||||
Actions = settingsViewModel.Actions.Where(a => a.RepresentsNull || a.Action.ActionType == actionType).ToList();
|
||||
|
||||
var actionInfo = orchestrator.GetAction(actionType, index);
|
||||
@ -77,6 +88,13 @@ namespace MassiveKnob.ViewModel
|
||||
}
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (ActionSettingsControl is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
}
|
||||
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
|
20
Windows/MassiveKnob/ViewModel/LoggingLevelViewModel.cs
Normal file
20
Windows/MassiveKnob/ViewModel/LoggingLevelViewModel.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using Serilog.Events;
|
||||
|
||||
namespace MassiveKnob.ViewModel
|
||||
{
|
||||
public class LoggingLevelViewModel
|
||||
{
|
||||
public LogEventLevel Level { get; }
|
||||
public string Name { get; }
|
||||
public string Description { get; }
|
||||
|
||||
|
||||
public LoggingLevelViewModel(LogEventLevel level, string name, string description)
|
||||
{
|
||||
Level = level;
|
||||
Name = name;
|
||||
Description = description;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
21
Windows/MassiveKnob/ViewModel/MenuItemProperties.cs
Normal file
21
Windows/MassiveKnob/ViewModel/MenuItemProperties.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace MassiveKnob.ViewModel
|
||||
{
|
||||
public static class MenuItemProperties
|
||||
{
|
||||
public static string GetText(DependencyObject obj) { return (string) obj.GetValue(TextProperty); }
|
||||
public static void SetText(DependencyObject obj, string value) { obj.SetValue(TextProperty, value); }
|
||||
|
||||
public static readonly DependencyProperty TextProperty =
|
||||
DependencyProperty.RegisterAttached("Text", typeof(string), typeof(MenuItemProperties), new FrameworkPropertyMetadata("Menu item"));
|
||||
|
||||
|
||||
public static Viewbox GetIcon(DependencyObject obj) { return (Viewbox)obj.GetValue(IconProperty); }
|
||||
public static void SetIcon(DependencyObject obj, Viewbox value) { obj.SetValue(IconProperty, value); }
|
||||
|
||||
public static readonly DependencyProperty IconProperty =
|
||||
DependencyProperty.RegisterAttached("Icon", typeof(Viewbox), typeof(MenuItemProperties), new FrameworkPropertyMetadata(null));
|
||||
}
|
||||
}
|
@ -1,20 +1,42 @@
|
||||
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.Model;
|
||||
using MassiveKnob.Core;
|
||||
using MassiveKnob.Plugin;
|
||||
using MassiveKnob.Settings;
|
||||
using MassiveKnob.View.Settings;
|
||||
using Microsoft.Win32;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace MassiveKnob.ViewModel
|
||||
{
|
||||
// TODO (nice to have) better design-time version
|
||||
public class SettingsViewModel : INotifyPropertyChanged
|
||||
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;
|
||||
@ -25,6 +47,42 @@ namespace MassiveKnob.ViewModel
|
||||
|
||||
|
||||
// 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; }
|
||||
|
||||
@ -38,7 +96,7 @@ namespace MassiveKnob.ViewModel
|
||||
return;
|
||||
|
||||
selectedDevice = value;
|
||||
var deviceInfo = orchestrator.SetActiveDevice(value?.Device);
|
||||
var deviceInfo = orchestrator?.SetActiveDevice(value?.Device);
|
||||
|
||||
OnPropertyChanged();
|
||||
|
||||
@ -54,6 +112,9 @@ namespace MassiveKnob.ViewModel
|
||||
if (value == settingsControl)
|
||||
return;
|
||||
|
||||
if (settingsControl is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
|
||||
settingsControl = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
@ -71,6 +132,11 @@ namespace MassiveKnob.ViewModel
|
||||
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));
|
||||
@ -89,6 +155,7 @@ namespace MassiveKnob.ViewModel
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Visibility AnalogInputVisibility => specs.HasValue && specs.Value.AnalogInputCount > 0
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
@ -144,13 +211,80 @@ namespace MassiveKnob.ViewModel
|
||||
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)
|
||||
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; });
|
||||
|
||||
@ -170,15 +304,86 @@ namespace MassiveKnob.ViewModel
|
||||
|
||||
Actions = allActions;
|
||||
|
||||
if (orchestrator.ActiveDevice == null)
|
||||
return;
|
||||
|
||||
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)
|
||||
@ -191,4 +396,13 @@ namespace MassiveKnob.ViewModel
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class SettingsViewModelDesignTime : SettingsViewModel
|
||||
{
|
||||
public SettingsViewModelDesignTime() : base(null, null, null)
|
||||
{
|
||||
Specs = new DeviceSpecs(2, 2, 2, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 6db7da6234713a50a2c278c00bcd710249738e5e
|
||||
Subproject commit 65c76b3f214522dd5f1da3704b83375bf238daba
|
Loading…
Reference in New Issue
Block a user