diff --git a/OctoPatch.sln b/OctoPatch.sln index 97ec5b0..fc91a46 100644 --- a/OctoPatch.sln +++ b/OctoPatch.sln @@ -31,6 +31,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OctoPatch.Plugin.Keyboard", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OctoPatch.Plugin.Rest", "src\Plugins\OctoPatch.Plugin.Rest\OctoPatch.Plugin.Rest.csproj", "{35E27930-07AA-4AE2-999E-E6BBE4B71DFB}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OctoPatch.Server.Test", "test\OctoPatch.Server.Test\OctoPatch.Server.Test.csproj", "{91546803-665C-4CC1-B849-8498947F5186}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -81,6 +83,10 @@ Global {35E27930-07AA-4AE2-999E-E6BBE4B71DFB}.Debug|Any CPU.Build.0 = Debug|Any CPU {35E27930-07AA-4AE2-999E-E6BBE4B71DFB}.Release|Any CPU.ActiveCfg = Release|Any CPU {35E27930-07AA-4AE2-999E-E6BBE4B71DFB}.Release|Any CPU.Build.0 = Release|Any CPU + {91546803-665C-4CC1-B849-8498947F5186}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {91546803-665C-4CC1-B849-8498947F5186}.Debug|Any CPU.Build.0 = Debug|Any CPU + {91546803-665C-4CC1-B849-8498947F5186}.Release|Any CPU.ActiveCfg = Release|Any CPU + {91546803-665C-4CC1-B849-8498947F5186}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -93,6 +99,7 @@ Global {DBA37B79-AF44-4C21-8BD6-B7704BD339F5} = {FA7B21A1-3ADA-4C35-BF59-100BFD2A689D} {DD956A0D-1556-4CCA-B1F3-4010D0F0F8F3} = {826BC5FD-CEF2-466A-88E5-55489BDAD44B} {35E27930-07AA-4AE2-999E-E6BBE4B71DFB} = {826BC5FD-CEF2-466A-88E5-55489BDAD44B} + {91546803-665C-4CC1-B849-8498947F5186} = {FA7B21A1-3ADA-4C35-BF59-100BFD2A689D} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8F9B99B3-F5BE-4428-9751-1CC99FA5761B} diff --git a/src/Applications/OctoPatch.DesktopClient/ConfigurationMap.cs b/src/Applications/OctoPatch.DesktopClient/ConfigurationMap.cs new file mode 100644 index 0000000..6323640 --- /dev/null +++ b/src/Applications/OctoPatch.DesktopClient/ConfigurationMap.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using OctoPatch.DesktopClient.Models; +using System.Reflection; +using System.Windows; +using System.Windows.Controls; + +namespace OctoPatch.DesktopClient +{ + /// + /// Central place to map configuration setup (node/adapter key to configuration model and configuration view) + /// + public sealed class ConfigurationMap + { + /// + /// Holds the current map + /// + private static readonly List Map = new List(); + + /// + /// Constructor scans for existing configuration map attributes within the current app domain + /// + static ConfigurationMap() + { + // Scan all referenced assemblies + foreach (var assemblies in AppDomain.CurrentDomain.GetAssemblies()) + { + try + { + // Scan all types + foreach (var type in assemblies.GetTypes()) + { + // Scan all existing attributes + foreach (var attribute in type.GetCustomAttributes()) + { + Map.Add(new MapEntry(attribute.Key, attribute.ModelType, type)); + } + } + } + catch (Exception) + { + // Some assemblies are not allowed to be reflected...? + } + } + } + + /// + /// Returns a new instance of the configuration model fitting to the given key or null, when no key is registered + /// + /// node key + /// new model instance + public static ConfigurationModel GetConfigurationModel(string key) + { + var entry = Map.FirstOrDefault(e => string.Equals(e.Key, key, StringComparison.InvariantCultureIgnoreCase)); + return entry == null ? null : (ConfigurationModel)Activator.CreateInstance(entry.ModelType); + } + + public static UserControl GetConfigurationView(ConfigurationModel model) + { + if (model == null) + { + return null; + } + + var entry = Map.FirstOrDefault(e => e.ModelType == model.GetType()); + if (entry == null) + { + return null; + } + + var control = (UserControl) Activator.CreateInstance(entry.ViewType); + + // Set data context if possible + if (control != null) + { + control.DataContext = model; + } + + return control; + } + + #region nested types + + /// + /// Local container to store a map entry + /// + private class MapEntry + { + /// + /// Gets the type of the data model + /// + public Type ModelType { get; } + + /// + /// Gets the type of the view + /// + public Type ViewType { get; } + + /// + /// Gets the key of the node or adapter + /// + public string Key { get; } + + public MapEntry(string key, Type modelType, Type viewType) + { + Key = key; + ModelType = modelType; + ViewType = viewType; + } + } + + #endregion + } +} diff --git a/src/Applications/OctoPatch.DesktopClient/ConfigurationMapAttribute.cs b/src/Applications/OctoPatch.DesktopClient/ConfigurationMapAttribute.cs new file mode 100644 index 0000000..7b70fcc --- /dev/null +++ b/src/Applications/OctoPatch.DesktopClient/ConfigurationMapAttribute.cs @@ -0,0 +1,27 @@ +using System; + +namespace OctoPatch.DesktopClient +{ + /// + /// Attribute to decorate the configuration view with + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + public sealed class ConfigurationMapAttribute : Attribute + { + /// + /// Gets the key string for the node or adapter + /// + public string Key { get; } + + /// + /// Gets the model type which handles the node or adapter settings + /// + public Type ModelType { get; } + + public ConfigurationMapAttribute(string key, Type modelType) + { + Key = key; + ModelType = modelType; + } + } +} diff --git a/src/Applications/OctoPatch.DesktopClient/Converters/ConnectorToVisibilityConverter.cs b/src/Applications/OctoPatch.DesktopClient/Converters/ConnectorToVisibilityConverter.cs deleted file mode 100644 index a42dd2f..0000000 --- a/src/Applications/OctoPatch.DesktopClient/Converters/ConnectorToVisibilityConverter.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Globalization; -using System.Windows; -using System.Windows.Data; -using OctoPatch.DesktopClient.Models; - -namespace OctoPatch.DesktopClient.Converters -{ - [ValueConversion(typeof(NodeModel), typeof(Visibility))] - public sealed class ConnectorToVisibilityConverter : IValueConverter - { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - return value is InputNodeModel || value is OutputNodeModel ? Visibility.Visible : Visibility.Collapsed; - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/Applications/OctoPatch.DesktopClient/Converters/InRangeToVisibilityConverter.cs b/src/Applications/OctoPatch.DesktopClient/Converters/InRangeToVisibilityConverter.cs new file mode 100644 index 0000000..891cb5a --- /dev/null +++ b/src/Applications/OctoPatch.DesktopClient/Converters/InRangeToVisibilityConverter.cs @@ -0,0 +1,46 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace OctoPatch.DesktopClient.Converters +{ + /// + /// Checks if the given value is within the range + /// + [ValueConversion(typeof(int), typeof(Visibility))] + public sealed class InRangeToVisibilityConverter : IValueConverter + { + /// + /// Minimum value + /// + public int MinValue { get; set; } + + /// + /// Maximum value + /// + public int MaxValue { get; set; } + + public InRangeToVisibilityConverter() + { + MinValue = int.MinValue; + MaxValue = int.MaxValue; + } + + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (!(value is int input)) + { + return Visibility.Collapsed; + } + + return input >= MinValue && input <= MaxValue ? Visibility.Visible : Visibility.Collapsed; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Applications/OctoPatch.DesktopClient/Converters/NodeToVisibilityConverter.cs b/src/Applications/OctoPatch.DesktopClient/Converters/NodeToVisibilityConverter.cs index ac1ac6e..2159a0c 100644 --- a/src/Applications/OctoPatch.DesktopClient/Converters/NodeToVisibilityConverter.cs +++ b/src/Applications/OctoPatch.DesktopClient/Converters/NodeToVisibilityConverter.cs @@ -6,12 +6,87 @@ namespace OctoPatch.DesktopClient.Converters { + /// + /// Converts selected tree node type into visibility + /// [ValueConversion(typeof(NodeModel), typeof(Visibility))] class NodeToVisibilityConverter : IValueConverter { + /// + /// show up when common node is selected + /// + public bool CommonNode { get; set; } + + /// + /// show up when attached node is selected + /// + public bool AttachedNode { get; set; } + + /// + /// show up when splitter node is selected + /// + public bool SplitterNode { get; set; } + + /// + /// show up when collector node is selected + /// + public bool CollectorNode { get; set; } + + /// + /// show up when input connector is selected + /// + public bool InputConnector { get; set; } + + /// + /// show up when output connector is selected + /// + public bool OutputConnector { get; set; } + + /// + /// show up when wire is selected + /// + public bool Wire { get; set; } + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - return value is CommonNodeModel || value is AttachedNodeModel || value is SplitterNodeModel || value is CollectorNodeModel ? Visibility.Visible : Visibility.Collapsed; + var visible = false; + + if (CommonNode && value is CommonNodeModel) + { + visible = true; + } + + if (AttachedNode && value is AttachedNodeModel) + { + visible = true; + } + + if (SplitterNode && value is SplitterNodeModel) + { + visible = true; + } + + if (CollectorNode && value is CollectorNodeModel) + { + visible = true; + } + + if (InputConnector && value is InputNodeModel) + { + visible = true; + } + + if (OutputConnector && value is OutputNodeModel) + { + visible = true; + } + + if (Wire && value is WireNodeModel) + { + visible = true; + } + + return visible ? Visibility.Visible : Visibility.Collapsed; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) diff --git a/src/Applications/OctoPatch.DesktopClient/MainWindow.xaml b/src/Applications/OctoPatch.DesktopClient/MainWindow.xaml index 6cb46a1..7b7cd36 100644 --- a/src/Applications/OctoPatch.DesktopClient/MainWindow.xaml +++ b/src/Applications/OctoPatch.DesktopClient/MainWindow.xaml @@ -6,7 +6,5 @@ xmlns:views="clr-namespace:OctoPatch.DesktopClient.Views" mc:Ignorable="d" Title="OctoPatch Desktop" Height="700" Width="800"> - - - + diff --git a/src/Applications/OctoPatch.DesktopClient/Models/AdapterConfigurationModel.cs b/src/Applications/OctoPatch.DesktopClient/Models/AdapterConfigurationModel.cs new file mode 100644 index 0000000..aa21899 --- /dev/null +++ b/src/Applications/OctoPatch.DesktopClient/Models/AdapterConfigurationModel.cs @@ -0,0 +1,38 @@ +using Newtonsoft.Json; + +namespace OctoPatch.DesktopClient.Models +{ + /// + /// Basic configuration type for node configuration + /// + /// model type for configuration + /// model type for environment + public abstract class AdapterConfigurationModel : ConfigurationModel + where TConfiguration : IConfiguration + where TEnvironment : IEnvironment + { + public override void Setup(string environment) + { + // Do nothing since adapter do not have any environment yet + } + + public override void SetConfiguration(string configuration) + { + if (configuration == null) + { + return; + } + + OnSetConfiguration(JsonConvert.DeserializeObject(configuration)); + } + + protected abstract void OnSetConfiguration(TConfiguration configuration); + + public override string GetConfiguration() + { + return JsonConvert.SerializeObject(OnGetConfiguration()); + } + + protected abstract TConfiguration OnGetConfiguration(); + } +} diff --git a/src/Applications/OctoPatch.DesktopClient/Models/ConfigurationModel.cs b/src/Applications/OctoPatch.DesktopClient/Models/ConfigurationModel.cs new file mode 100644 index 0000000..098db3d --- /dev/null +++ b/src/Applications/OctoPatch.DesktopClient/Models/ConfigurationModel.cs @@ -0,0 +1,27 @@ +namespace OctoPatch.DesktopClient.Models +{ + /// + /// Basic type for all kind of configuration models + /// + public abstract class ConfigurationModel : Model + { + /// + /// Method to apply environment to the configuration model + /// + /// serialized environment + public abstract void Setup(string environment); + + /// + /// Method to apply configuration to the configuration model + /// + /// serialized configuration + public abstract void SetConfiguration(string configuration); + + /// + /// Method to grab the configuration back in serialized format + /// + /// serialized configuration + public abstract string GetConfiguration(); + + } +} diff --git a/src/Applications/OctoPatch.DesktopClient/Models/KeyboardStringModel.cs b/src/Applications/OctoPatch.DesktopClient/Models/KeyboardStringModel.cs new file mode 100644 index 0000000..bda6ba7 --- /dev/null +++ b/src/Applications/OctoPatch.DesktopClient/Models/KeyboardStringModel.cs @@ -0,0 +1,40 @@ +using OctoPatch.Plugin.Keyboard; + +namespace OctoPatch.DesktopClient.Models +{ + public sealed class KeyboardStringModel : NodeConfigurationModel + { + private bool _ignoreNotPrintable; + + /// + /// Ignores not printable character like whitespace and newline + /// + public bool IgnoreNotPrintable + { + get => _ignoreNotPrintable; + set + { + _ignoreNotPrintable = value; + OnPropertyChanged(); + } + } + + protected override void OnSetup(EmptyEnvironment environment) + { + + } + + protected override KeyboardStringConfiguration OnGetConfiguration() + { + return new KeyboardStringConfiguration + { + IgnoreNotPrintable = IgnoreNotPrintable + }; + } + + protected override void OnSetConfiguration(KeyboardStringConfiguration configuration) + { + IgnoreNotPrintable = configuration.IgnoreNotPrintable; + } + } +} \ No newline at end of file diff --git a/src/Applications/OctoPatch.DesktopClient/Models/LinearAdapterModel.cs b/src/Applications/OctoPatch.DesktopClient/Models/LinearAdapterModel.cs new file mode 100644 index 0000000..4710d67 --- /dev/null +++ b/src/Applications/OctoPatch.DesktopClient/Models/LinearAdapterModel.cs @@ -0,0 +1,34 @@ +using System.Windows.Markup; +using OctoPatch.Core.Adapters; + +namespace OctoPatch.DesktopClient.Models +{ + public sealed class LinearAdapterModel : AdapterConfigurationModel + { + private bool _inverted; + + public bool Inverted + { + get => _inverted; + set + { + _inverted = value; + OnPropertyChanged(); + } + } + + protected override void OnSetConfiguration(LinearTransformationAdapter.Config configuration) + { + Inverted = configuration.Inverted; + + } + + protected override LinearTransformationAdapter.Config OnGetConfiguration() + { + return new LinearTransformationAdapter.Config + { + Inverted = Inverted + }; + } + } +} diff --git a/src/Applications/OctoPatch.DesktopClient/Models/MidiAttachedNodeModel.cs b/src/Applications/OctoPatch.DesktopClient/Models/MidiAttachedNodeModel.cs new file mode 100644 index 0000000..f8ced20 --- /dev/null +++ b/src/Applications/OctoPatch.DesktopClient/Models/MidiAttachedNodeModel.cs @@ -0,0 +1,59 @@ +using OctoPatch.Plugin.Midi; + +namespace OctoPatch.DesktopClient.Models +{ + /// + /// Configuration model for all kind of attached nodes of the MIDI Device + /// + public sealed class MidiAttachedNodeModel : NodeConfigurationModel + { + private int _channel; + + /// + /// Gets or sets the channel of the message filter + /// + public int Channel + { + get => _channel; + set + { + _channel = value; + OnPropertyChanged(); + } + } + + private int _key; + + /// + /// Gets or sets the key of the message filter + /// + public int Key + { + get => _key; + set + { + _key = value; + OnPropertyChanged(); + } + } + + protected override void OnSetup(EmptyEnvironment environment) + { + } + + protected override void OnSetConfiguration(AttachedNodeConfiguration configuration) + { + Channel = configuration?.Channel ?? 0; + Key = configuration?.Key ?? 0; + } + + protected override AttachedNodeConfiguration OnGetConfiguration() + { + return new AttachedNodeConfiguration + { + Channel = Channel, + Key = Key + }; + } + } +} diff --git a/src/Applications/OctoPatch.DesktopClient/Models/NodeConfigurationModel.cs b/src/Applications/OctoPatch.DesktopClient/Models/NodeConfigurationModel.cs index 8f239e2..e99efef 100644 --- a/src/Applications/OctoPatch.DesktopClient/Models/NodeConfigurationModel.cs +++ b/src/Applications/OctoPatch.DesktopClient/Models/NodeConfigurationModel.cs @@ -2,16 +2,12 @@ namespace OctoPatch.DesktopClient.Models { - public abstract class NodeConfigurationModel : Model - { - public abstract void Setup(string environment); - - public abstract void SetConfiguration(string configuration); - - public abstract string GetConfiguration(); - } - - public abstract class NodeConfigurationModel : NodeConfigurationModel + /// + /// Basic configuration type for node configuration + /// + /// model type for configuration + /// model type for environment + public abstract class NodeConfigurationModel : ConfigurationModel where TConfiguration : IConfiguration where TEnvironment : IEnvironment { diff --git a/src/Applications/OctoPatch.DesktopClient/Models/NodeModel.cs b/src/Applications/OctoPatch.DesktopClient/Models/NodeModel.cs index bd80124..6afbc29 100644 --- a/src/Applications/OctoPatch.DesktopClient/Models/NodeModel.cs +++ b/src/Applications/OctoPatch.DesktopClient/Models/NodeModel.cs @@ -59,5 +59,14 @@ protected NodeModel(ConnectorDescription description) : this(description.Key) { Name = description.DisplayName; } + + protected NodeModel(Guid wireId) + { + this.Name = "Wire"; + Items = new ObservableCollection(); + Key = wireId.ToString(); + State = NodeState.Uninitialized; + + } } } diff --git a/src/Applications/OctoPatch.DesktopClient/Models/WireNodeModel.cs b/src/Applications/OctoPatch.DesktopClient/Models/WireNodeModel.cs new file mode 100644 index 0000000..56bb5cb --- /dev/null +++ b/src/Applications/OctoPatch.DesktopClient/Models/WireNodeModel.cs @@ -0,0 +1,12 @@ +using System; + +namespace OctoPatch.DesktopClient.Models +{ + public sealed class WireNodeModel : NodeModel + { + public WireNodeModel(Guid wireId, string name) : base(wireId) + { + Name = name; + } + } +} diff --git a/src/Applications/OctoPatch.DesktopClient/ModelsX/NodeModel.cs b/src/Applications/OctoPatch.DesktopClient/ModelsX/NodeModel.cs new file mode 100644 index 0000000..69ee9b4 --- /dev/null +++ b/src/Applications/OctoPatch.DesktopClient/ModelsX/NodeModel.cs @@ -0,0 +1,12 @@ +using OctoPatch.Descriptions; +using OctoPatch.Setup; + +namespace OctoPatch.DesktopClient.ModelsX +{ + public sealed class NodeModel + { + public NodeSetup Setup { get; set; } + + public NodeDescription Description { get; set; } + } +} diff --git a/src/Applications/OctoPatch.DesktopClient/NodeTemplateSelector.cs b/src/Applications/OctoPatch.DesktopClient/NodeTemplateSelector.cs deleted file mode 100644 index 9620db3..0000000 --- a/src/Applications/OctoPatch.DesktopClient/NodeTemplateSelector.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Windows; -using System.Windows.Controls; -using OctoPatch.DesktopClient.Models; - -namespace OctoPatch.DesktopClient -{ - public sealed class NodeTemplateSelector : DataTemplateSelector - { - public DataTemplate MidiDeviceTemplate { get; set; } - - public DataTemplate RestGetTemplate { get; set; } - - public override DataTemplate SelectTemplate(object item, DependencyObject container) - { - if (item is MidiDeviceModel) - { - return MidiDeviceTemplate; - } - else if(item is RestGetModel) - { - return RestGetTemplate; - } - - return base.SelectTemplate(item, container); - } - } -} diff --git a/src/Applications/OctoPatch.DesktopClient/TreeTemplateSelector.cs b/src/Applications/OctoPatch.DesktopClient/TreeTemplateSelector.cs index ca004d4..d732ede 100644 --- a/src/Applications/OctoPatch.DesktopClient/TreeTemplateSelector.cs +++ b/src/Applications/OctoPatch.DesktopClient/TreeTemplateSelector.cs @@ -18,6 +18,8 @@ public sealed class TreeTemplateSelector : DataTemplateSelector public DataTemplate InputTemplate { get; set; } + public DataTemplate WireTemplate { get; set; } + public override DataTemplate SelectTemplate(object item, DependencyObject container) { switch (item) @@ -34,6 +36,8 @@ public override DataTemplate SelectTemplate(object item, DependencyObject contai return OutputTemplate; case InputNodeModel _: return InputTemplate; + case WireNodeModel _: + return WireTemplate; } return base.SelectTemplate(item, container); diff --git a/src/Applications/OctoPatch.DesktopClient/ViewModels/IRuntimeViewModel.cs b/src/Applications/OctoPatch.DesktopClient/ViewModels/IRuntimeViewModel.cs index 3f7439b..86aa50b 100644 --- a/src/Applications/OctoPatch.DesktopClient/ViewModels/IRuntimeViewModel.cs +++ b/src/Applications/OctoPatch.DesktopClient/ViewModels/IRuntimeViewModel.cs @@ -3,46 +3,187 @@ using System.Windows.Input; using OctoPatch.Descriptions; using OctoPatch.DesktopClient.Models; -using OctoPatch.Setup; +using OctoPatch.Server; namespace OctoPatch.DesktopClient.ViewModels { public interface IRuntimeViewModel : INotifyPropertyChanged { + IRuntime Runtime { get; } + + #region Application + + /// + /// Command to clean grid + /// + ICommand NewCommand { get; } + + /// + /// Loads a file to the grid + /// + ICommand LoadCommand { get; } + + /// + /// Saves the current grid to a file + /// + ICommand SaveCommand { get; } + + #endregion + + #region Toolbox + + /// + /// List of available nodes to drag into tree + /// ObservableCollection NodeDescriptions { get; } + /// + /// Gets or sets the current selected node description in toolbox + /// NodeDescription SelectedNodeDescription { get; set; } + /// + /// Command to add the selected node description to the tree + /// ICommand AddSelectedNodeDescription { get; } + #endregion + + #region Patch + + /// + /// Hierarchical tree structure + /// + ObservableCollection NodeTree { get; } + + /// + /// Gets or sets the current selected node within the tree + /// + NodeModel SelectedNode { get; set; } + + /// + /// Command to delete the current selected tree node + /// + ICommand RemoveSelectedNode { get; } + + /// + /// Command to delete the current selected wire + /// + ICommand RemoveSelectedWire { get; } + + #endregion + + #region Context Toolbox + + /// + /// List of available nodes for the current selected tree node + /// ObservableCollection ContextNodeDescriptions { get; } + /// + /// Gets or sets the current selected node description of context toolbox + /// NodeDescription SelectedContextNodeDescription { get; set; } + /// + /// Command to add the current selected context node description + /// to the current selected tree node + /// ICommand AddSelectedContextNodeDescription { get; } - ObservableCollection NodeTree { get; } + #endregion - ICommand RemoveSelectedNode { get; } + #region Property bar + + #region Node lifecycle management + /// + /// Command to start the current selected node + /// ICommand StartSelectedNode { get; } + /// + /// Command to stop the current selected node + /// ICommand StopSelectedNode { get; } - NodeModel SelectedNode { get; set; } + #endregion + #region Common node configuration + + /// + /// Gets the common configuration (name and description) of the current selected tree node + /// NodeDescriptionModel NodeDescription { get; } + /// + /// Command to store all the changed made in the NodeDescription property + /// ICommand SaveNodeDescription { get; } - NodeConfigurationModel NodeConfiguration { get; } + #endregion + + #region Node specific configuration + /// + /// Holds the specific configuration model for the current selected tree node + /// + ConfigurationModel NodeConfiguration { get; } + + /// + /// Command to store all the changes made in the node configuration + /// ICommand SaveNodeConfiguration { get; } + #endregion + + #region Wire wizard + + /// + /// gets the current selected ouptut connector (if available) + /// OutputNodeModel SelectedWireConnector { get; } + /// + /// Command to take the selected output / input connector to wire them up + /// ICommand TakeConnector { get; } - ObservableCollection Wires { get; } + #endregion + + #region Wire configuration + + /// + /// List of all available adapters for the selected wire context + /// + ObservableCollection AdapterDescriptions { get; } + + /// + /// Gets or sets the current selected adapter + /// + AdapterDescription SelectedAdapterDescription { get; set; } + + /// + /// Command to apply current adapter selection to the selected wire + /// + ICommand SaveAdapter { get; } + + #endregion + + #region Adapter configuration + + /// + /// Gets the configuration for the adapter of the current selected wire + /// + ConfigurationModel AdapterConfiguration { get; } + + /// + /// Command to store all the changes in the adapter configuration + /// + ICommand SaveAdapterConfiguration { get; } + + #endregion + + #endregion } } diff --git a/src/Applications/OctoPatch.DesktopClient/ViewModels/RuntimeViewModel.cs b/src/Applications/OctoPatch.DesktopClient/ViewModels/RuntimeViewModel.cs index ec63c74..6d3af4e 100644 --- a/src/Applications/OctoPatch.DesktopClient/ViewModels/RuntimeViewModel.cs +++ b/src/Applications/OctoPatch.DesktopClient/ViewModels/RuntimeViewModel.cs @@ -2,14 +2,16 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; +using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using System.Windows.Input; +using Microsoft.Win32; +using Newtonsoft.Json; using OctoPatch.Descriptions; using OctoPatch.DesktopClient.Models; -using OctoPatch.Plugin.Rest; using OctoPatch.Server; using OctoPatch.Setup; @@ -19,14 +21,43 @@ public sealed class RuntimeViewModel : IRuntimeViewModel { private readonly IRuntime _runtime; - private List _nodes; + private readonly List _nodes; - private List _descriptions; + private readonly List _wires; + private readonly List _nodeDescriptions; + + private readonly List _adapterDescriptions; + + public IRuntime Runtime => _runtime; + + #region Application + + private readonly ActionCommand _newCommand; + + /// + public ICommand NewCommand => _newCommand; + + private readonly ActionCommand _loadCommand; + + /// + public ICommand LoadCommand => _loadCommand; + + private readonly ActionCommand _saveCommand; + + /// + public ICommand SaveCommand => _saveCommand; + + #endregion + + #region Toolbox + + /// public ObservableCollection NodeDescriptions { get; } private NodeDescription _selectedNodeDescription; + /// public NodeDescription SelectedNodeDescription { get => _selectedNodeDescription; @@ -41,43 +72,15 @@ public NodeDescription SelectedNodeDescription private readonly ActionCommand _addSelectedNodeDescription; + /// public ICommand AddSelectedNodeDescription => _addSelectedNodeDescription; - public ObservableCollection ContextNodeDescriptions { get; } - - private NodeDescription _selectedContextNodeDescription; - - public NodeDescription SelectedContextNodeDescription - { - get => _selectedContextNodeDescription; - set - { - _selectedContextNodeDescription = value; - OnPropertyChanged(); - - _addSelectedContextNodeDescription.Enabled = value != null; - } - } - - private readonly ActionCommand _addSelectedContextNodeDescription; - - public ICommand AddSelectedContextNodeDescription => _addSelectedContextNodeDescription; + #endregion + #region Patch public ObservableCollection NodeTree { get; } - private readonly ActionCommand _removeSelectedNode; - - public ICommand RemoveSelectedNode => _removeSelectedNode; - - private readonly ActionCommand _startSelectedNode; - - public ICommand StartSelectedNode => _startSelectedNode; - - private readonly ActionCommand _stopSelectedNode; - - public ICommand StopSelectedNode => _stopSelectedNode; - private NodeModel _selectedNode; public NodeModel SelectedNode @@ -94,7 +97,7 @@ public NodeModel SelectedNode { case InputNodeModel input: - foreach (var description in _descriptions.OfType().Where(d => d.TypeKey == input.TypeKey)) + foreach (var description in _nodeDescriptions.OfType().Where(d => d.TypeKey == input.TypeKey)) { ContextNodeDescriptions.Add(description); } @@ -102,7 +105,7 @@ public NodeModel SelectedNode break; case OutputNodeModel output: - foreach (var description in _descriptions.OfType().Where(d => d.TypeKey == output.TypeKey)) + foreach (var description in _nodeDescriptions.OfType().Where(d => d.TypeKey == output.TypeKey)) { ContextNodeDescriptions.Add(description); } @@ -111,7 +114,7 @@ public NodeModel SelectedNode case CommonNodeModel common: - foreach (var description in _descriptions.OfType().Where(d => d.ParentKey == common.Key)) + foreach (var description in _nodeDescriptions.OfType().Where(d => d.ParentKey == common.Key)) { ContextNodeDescriptions.Add(description); } @@ -119,7 +122,7 @@ public NodeModel SelectedNode break; case AttachedNodeModel attached: - foreach (var description in _descriptions.OfType().Where(d => d.ParentKey == attached.Key)) + foreach (var description in _nodeDescriptions.OfType().Where(d => d.ParentKey == attached.Key)) { ContextNodeDescriptions.Add(description); } @@ -128,7 +131,7 @@ public NodeModel SelectedNode case SplitterNodeModel splitter: - foreach (var description in _descriptions.OfType().Where(d => d.ParentKey == splitter.Key)) + foreach (var description in _nodeDescriptions.OfType().Where(d => d.ParentKey == splitter.Key)) { ContextNodeDescriptions.Add(description); } @@ -137,20 +140,21 @@ public NodeModel SelectedNode case CollectorNodeModel collector: - foreach (var description in _descriptions.OfType().Where(d => d.ParentKey == collector.Key)) + foreach (var description in _nodeDescriptions.OfType().Where(d => d.ParentKey == collector.Key)) { ContextNodeDescriptions.Add(description); } break; - } - var item = _nodes.FirstOrDefault(n => n.Model == value); + #region node management + var item = _nodes.FirstOrDefault(n => n.Model == value); if (item == null) { NodeDescription = null; + NodeConfiguration = null; } else { @@ -159,44 +163,111 @@ public NodeModel SelectedNode Name = item.Setup.Name, Description = item.Setup.Description }; + + NodeConfiguration = ConfigurationMap.GetConfigurationModel(item.Setup.Key); + if (NodeConfiguration != null) + { + NodeConfiguration.Setup(item.Environment); + NodeConfiguration.SetConfiguration(item.Setup.Configuration); + } } _removeSelectedNode.Enabled = item != null; _startSelectedNode.Enabled = item != null; _stopSelectedNode.Enabled = item != null; _saveNodeDescription.Enabled = item != null; - _takeConnector.Enabled = (SelectedWireConnector != null && value is InputNodeModel) || value is OutputNodeModel; + _takeConnector.Enabled = SelectedWireConnector != null && value is InputNodeModel || value is OutputNodeModel; + _saveNodeConfiguration.Enabled = NodeConfiguration != null; - //// TODO: Lookup model by Attribute - if (item?.Setup.Key == "12ea0035-45af-4da8-8b5d-e1b9d9484ba4:MidiDeviceNode") - { - var model = new MidiDeviceModel(); - model.Setup(item.Environment); - model.SetConfiguration(item.Setup.Configuration); - NodeConfiguration = model; - _saveNodeConfiguration.Enabled = true; - } - else if (item?.Setup.Key == "a6fe76d7-5f0e-4763-a3a5-fcaf43c71464:KeyboardNode") - { - NodeConfiguration = null; - _saveNodeConfiguration.Enabled = true; - } - else if(item?.Setup.Key == $"{RestPlugin.PluginId[1..^1].ToLower()}:{nameof(RestGetNode)}") + #endregion + + #region wire management + + var wire = _wires.FirstOrDefault(w => w.InputWire == value || w.OutputWire == value); + if (wire == null) { - var model = new RestGetModel(); - model.Setup(item.Environment); - model.SetConfiguration(item.Setup.Configuration); - NodeConfiguration = model; - _saveNodeConfiguration.Enabled = true; + AdapterDescriptions.Clear(); + SelectedAdapterDescription = null; + AdapterConfiguration = null; } else { - NodeConfiguration = null; - _saveNodeConfiguration.Enabled = false; + // Fill list of supported adapter + foreach (var adapter in wire.SupportedAdapter) + { + AdapterDescriptions.Add(adapter); + } + + SelectedAdapterDescription = + AdapterDescriptions.FirstOrDefault(d => d.Key == wire.Setup.AdapterKey); + + AdapterConfiguration = ConfigurationMap.GetConfigurationModel(wire.Setup.AdapterKey); + if (AdapterConfiguration != null) + { + AdapterConfiguration.Setup(wire.Environment); + AdapterConfiguration.SetConfiguration(wire.Setup.AdapterConfiguration); + } + } + + _removeSelectedWire.Enabled = wire != null; + _saveAdapter.Enabled = wire != null; + _saveAdapterConfiguration.Enabled = AdapterConfiguration != null; + + #endregion } } + private readonly ActionCommand _removeSelectedNode; + + public ICommand RemoveSelectedNode => _removeSelectedNode; + + private readonly ActionCommand _removeSelectedWire; + + public ICommand RemoveSelectedWire => _removeSelectedWire; + + #endregion + + #region Context Toolbox + + public ObservableCollection ContextNodeDescriptions { get; } + + private NodeDescription _selectedContextNodeDescription; + + public NodeDescription SelectedContextNodeDescription + { + get => _selectedContextNodeDescription; + set + { + _selectedContextNodeDescription = value; + OnPropertyChanged(); + + _addSelectedContextNodeDescription.Enabled = value != null; + } + } + + private readonly ActionCommand _addSelectedContextNodeDescription; + + public ICommand AddSelectedContextNodeDescription => _addSelectedContextNodeDescription; + + #endregion + + #region Property bar + + #region Node lifecycle management + + private readonly ActionCommand _startSelectedNode; + + public ICommand StartSelectedNode => _startSelectedNode; + + private readonly ActionCommand _stopSelectedNode; + + public ICommand StopSelectedNode => _stopSelectedNode; + + #endregion + + #region Common node configuration + private NodeDescriptionModel _nodeDescription; public NodeDescriptionModel NodeDescription @@ -213,9 +284,13 @@ private set public ICommand SaveNodeDescription => _saveNodeDescription; - private NodeConfigurationModel _nodeConfiguration; + #endregion + + #region Node specific configuration + + private ConfigurationModel _nodeConfiguration; - public NodeConfigurationModel NodeConfiguration + public ConfigurationModel NodeConfiguration { get => _nodeConfiguration; private set @@ -229,6 +304,10 @@ private set public ICommand SaveNodeConfiguration => _saveNodeConfiguration; + #endregion + + #region Wire wizard + private OutputNodeModel _selectedWireConnector; public OutputNodeModel SelectedWireConnector @@ -245,26 +324,79 @@ private set public ICommand TakeConnector => _takeConnector; - public ObservableCollection Wires { get; } + #endregion + + #region Wire configuration + + public ObservableCollection AdapterDescriptions { get; } + + private AdapterDescription _selectedAdapterDescription; + + public AdapterDescription SelectedAdapterDescription + { + get => _selectedAdapterDescription; + set + { + _selectedAdapterDescription = value; + OnPropertyChanged(); + } + } + + private readonly ActionCommand _saveAdapter; + + public ICommand SaveAdapter => _saveAdapter; + + #endregion + + #region Adapter configuration + + private ConfigurationModel _adapterConfiguration; + + public ConfigurationModel AdapterConfiguration + { + get => _adapterConfiguration; + private set + { + _adapterConfiguration = value; + OnPropertyChanged(); + } + } + + private readonly ActionCommand _saveAdapterConfiguration; + + public ICommand SaveAdapterConfiguration => _saveAdapterConfiguration; + + #endregion + + #endregion public RuntimeViewModel() { _nodes = new List(); - _descriptions = new List(); + _wires = new List(); + _nodeDescriptions = new List(); + _adapterDescriptions = new List(); NodeDescriptions = new ObservableCollection(); ContextNodeDescriptions = new ObservableCollection(); NodeTree = new ObservableCollection(); - Wires = new ObservableCollection(); + AdapterDescriptions = new ObservableCollection(); _addSelectedNodeDescription = new ActionCommand(AddNodeDescriptionCallback, false); _addSelectedContextNodeDescription = new ActionCommand(AddContextNodeDescriptionCallback, false); _removeSelectedNode = new ActionCommand(RemoveSelectedNodeCallback, false); + _removeSelectedWire = new ActionCommand(RemoveSelectedWireCallback, false); _startSelectedNode = new ActionCommand(StartSelectedNodeCallback, false); _stopSelectedNode = new ActionCommand(StopSelectedNodeCallback, false); _saveNodeDescription = new ActionCommand(SaveNodeDescriptionCallback, false); _saveNodeConfiguration = new ActionCommand(SaveNodeConfigurationCallback, false); _takeConnector = new ActionCommand(TakeConnectorCallback, false); + _saveAdapter = new ActionCommand(SaveAdapterCallback, false); + _saveAdapterConfiguration = new ActionCommand(SaveAdapterConfigurationCallback, false); + + _newCommand = new ActionCommand(NewCommandCallback); + _loadCommand = new ActionCommand(LoadCommandCallback); + _saveCommand = new ActionCommand(SaveCommandCallback); var repository = new Repository(); _runtime = new Runtime(repository); @@ -276,10 +408,103 @@ public RuntimeViewModel() _runtime.NodeEnvironmentChanged += RuntimeOnOnNodeEnvironmentChanged; _runtime.WireAdded += RuntimeOnOnWireAdded; _runtime.WireRemoved += RuntimeOnOnWireRemoved; + _runtime.WireUpdated += RuntimeOnWireUpdated; + _runtime.AdapterEnvironmentChanged += RuntimeOnAdapterEnvironmentChanged; Task.Run(() => Setup(CancellationToken.None)); } + private async void SaveCommandCallback(object obj) + { + var dialog = new SaveFileDialog + { + Filter = "OctoPatch Grid|*.grid" + }; + + if (dialog.ShowDialog() == true) + { + var grid = await _runtime.GetConfiguration(CancellationToken.None); + var output = JsonConvert.SerializeObject(grid); + await File.WriteAllTextAsync(dialog.FileName, output); + } + } + + private async void LoadCommandCallback(object obj) + { + var dialog = new OpenFileDialog + { + Filter = "OctoPatch Grid|*.grid" + }; + + if (dialog.ShowDialog() == true) + { + var input = await File.ReadAllTextAsync(dialog.FileName, CancellationToken.None); + var grid = JsonConvert.DeserializeObject(input); + await _runtime.SetConfiguration(grid, CancellationToken.None); + } + } + + private async void NewCommandCallback(object obj) + { + await _runtime.SetConfiguration(null, CancellationToken.None); + } + + private void RuntimeOnWireUpdated(WireSetup wireSetup) + { + var wire = _wires.FirstOrDefault(n => n.Setup.WireId == wireSetup.WireId); + if (wire == null) + { + return; + } + + wire.Setup = wireSetup; + } + + private void RuntimeOnAdapterEnvironmentChanged(Guid wireId, string environment) + { + var wire = _wires.FirstOrDefault(n => n.Setup.WireId == wireId); + if (wire == null) + { + return; + } + + wire.Environment = environment; + } + + + private void SaveAdapterConfigurationCallback(object obj) + { + throw new NotImplementedException(); + } + + private async void SaveAdapterCallback(object obj) + { + var node = SelectedNode; + var item = _wires.FirstOrDefault(n => n.InputWire == node || n.OutputWire == node); + if (item == null) + { + return; + } + + var adapterDescription = SelectedAdapterDescription; + if (adapterDescription != null) + { + await _runtime.SetAdapter(item.Setup.WireId, adapterDescription.Key, CancellationToken.None); + } + } + + private async void RemoveSelectedWireCallback(object obj) + { + var node = SelectedNode; + var item = _wires.FirstOrDefault(n => n.InputWire == node || n.OutputWire == node); + if (item == null) + { + return; + } + + await _runtime.RemoveWire(item.Setup.WireId, CancellationToken.None); + } + private async void TakeConnectorCallback(object obj) { var node = SelectedNode; @@ -295,8 +520,9 @@ private async void TakeConnectorCallback(object obj) if (SelectedWireConnector != null && node is InputNodeModel inputNode) { await _runtime.AddWire( - SelectedWireConnector.ParentId, SelectedWireConnector.Key, - inputNode.ParentId, inputNode.Key, CancellationToken.None); + inputNode.ParentId, inputNode.Key, + SelectedWireConnector.ParentId, SelectedWireConnector.Key, + CancellationToken.None); SelectedWireConnector = null; _takeConnector.Enabled = false; } @@ -352,15 +578,15 @@ private async void AddContextNodeDescriptionCallback(object obj) if (contextNode is SplitterNodeDescription) { - await _runtime.AddNode(contextNode.Key, parentId, connectorKey, CancellationToken.None); + await _runtime.AddNode(contextNode.Key, parentId, connectorKey, 0, 0, CancellationToken.None); } else if (contextNode is CollectorNodeDescription) { - await _runtime.AddNode(contextNode.Key, parentId, connectorKey, CancellationToken.None); + await _runtime.AddNode(contextNode.Key, parentId, connectorKey, 0, 0, CancellationToken.None); } else if (contextNode is AttachedNodeDescription) { - await _runtime.AddNode(contextNode.Key, parentId, null, CancellationToken.None); + await _runtime.AddNode(contextNode.Key, parentId, null, 0, 0, CancellationToken.None); } } @@ -406,6 +632,7 @@ private void RuntimeOnOnNodeUpdated(NodeSetup setup) } node.Setup = setup; + node.Model.Name = setup.Name; } private void RuntimeOnOnNodeStateChanged(Guid nodeId, NodeState state) @@ -471,16 +698,52 @@ private async void AddNodeDescriptionCallback(object obj) var description = SelectedNodeDescription; if (description != null) { - await _runtime.AddNode(description.Key, null, null, CancellationToken.None); + await _runtime.AddNode(description.Key, null, null, 0, 0, CancellationToken.None); } } - private void RuntimeOnOnWireRemoved(Guid obj) + private void RuntimeOnOnWireRemoved(Guid wireId) { + var wire = _wires.FirstOrDefault(n => n.Setup.WireId == wireId); + if (wire == null) + { + return; + } + + // Remove node in tree + RemoveRecursive(wire.InputWire, NodeTree); + RemoveRecursive(wire.OutputWire, NodeTree); + + _wires.Remove(wire); } - private void RuntimeOnOnWireAdded(WireSetup obj) + private async void RuntimeOnOnWireAdded(WireSetup wire) { + var inputNode = _nodes.First(n => n.Setup.NodeId == wire.InputNodeId); + var inputConnector = inputNode.Model.Items.OfType() + .First(m => m.Key == wire.InputConnectorKey); + + var outputNode = _nodes.First(n => n.Setup.NodeId == wire.OutputNodeId); + var outputConnector = outputNode.Model.Items.OfType() + .First(m => m.Key == wire.OutputConnectorKey); + + var inputWire = new WireNodeModel(wire.WireId, $"Wire to {outputNode.Model.Name} ({outputConnector.Name})"); + inputConnector.Items.Add(inputWire); + + var outputWire = new WireNodeModel(wire.WireId, $"Wire to {inputNode.Model.Name} ({inputConnector.Name})"); + outputConnector.Items.Add(outputWire); + + var supportedAdapters = await _runtime.GetSupportedAdapters(wire.WireId, CancellationToken.None); + + _wires.Add(new WireItem + { + InputConnector = inputConnector, + InputWire = inputWire, + OutputConnector = outputConnector, + OutputWire = outputWire, + Setup = wire, + SupportedAdapter = _adapterDescriptions.Where(a => supportedAdapters.Contains(a.Key)).ToArray() + }); } private void RuntimeOnOnNodeRemoved(Guid obj) @@ -520,7 +783,7 @@ private void RemoveRecursive(NodeModel model, ObservableCollection li private void RuntimeOnOnNodeAdded(NodeSetup setup, NodeState state, string environment) { // identify description - var description = _descriptions.FirstOrDefault(d => d.Key == setup.Key); + var description = _nodeDescriptions.FirstOrDefault(d => d.Key == setup.Key); NodeModel nodeModel = null; switch (description) @@ -534,7 +797,7 @@ private void RuntimeOnOnNodeAdded(NodeSetup setup, NodeState state, string envir return; } - attachedParent.Model.Items.Add(new AttachedNodeModel(setup.NodeId, attached)); + attachedParent.Model.Items.Add(nodeModel); break; case CollectorNodeDescription collector: nodeModel = new CollectorNodeModel(setup.NodeId, collector); @@ -552,7 +815,7 @@ private void RuntimeOnOnNodeAdded(NodeSetup setup, NodeState state, string envir return; } - input.Items.Add(new CollectorNodeModel(setup.NodeId, collector)); + input.Items.Add(nodeModel); break; case SplitterNodeDescription splitter: nodeModel = new SplitterNodeModel(setup.NodeId, splitter); @@ -570,7 +833,7 @@ private void RuntimeOnOnNodeAdded(NodeSetup setup, NodeState state, string envir return; } - output.Items.Add(new SplitterNodeModel(setup.NodeId, splitter)); + output.Items.Add(nodeModel); break; case CommonNodeDescription common: @@ -591,15 +854,19 @@ private void RuntimeOnOnNodeAdded(NodeSetup setup, NodeState state, string envir public async Task Setup(CancellationToken cancellationToken) { - var descriptions = await _runtime.GetNodeDescriptions(cancellationToken); + var nodeDescriptions = await _runtime.GetNodeDescriptions(cancellationToken); + var adapterDescriptions = await _runtime.GetAdapterDescriptions(cancellationToken); - _descriptions.Clear(); - _descriptions.AddRange(descriptions); + _nodeDescriptions.Clear(); + _nodeDescriptions.AddRange(nodeDescriptions); - foreach (var description in _descriptions.OfType()) + foreach (var description in _nodeDescriptions.OfType()) { NodeDescriptions.Add(description); } + + _adapterDescriptions.Clear(); + _adapterDescriptions.AddRange(adapterDescriptions); } public event PropertyChangedEventHandler PropertyChanged; @@ -617,5 +884,22 @@ private sealed class NodeItem public NodeModel Model { get; set; } } + + private sealed class WireItem + { + public OutputNodeModel InputConnector { get; set; } + + public InputNodeModel OutputConnector { get; set; } + + public WireNodeModel InputWire { get; set; } + + public WireNodeModel OutputWire { get; set; } + + public WireSetup Setup { get; set; } + + public string Environment { get; set; } + + public AdapterDescription[] SupportedAdapter { get; set; } + } } } diff --git a/src/Applications/OctoPatch.DesktopClient/Views/CommonNodeSetupView.xaml b/src/Applications/OctoPatch.DesktopClient/Views/CommonNodeSetupView.xaml deleted file mode 100644 index b6dcc42..0000000 --- a/src/Applications/OctoPatch.DesktopClient/Views/CommonNodeSetupView.xaml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - -