From f564c4ef51f414509fdd9b61a67d0ea26b9e58a3 Mon Sep 17 00:00:00 2001 From: Lars Elgtvedt Susaas Date: Fri, 15 Nov 2024 19:43:05 +0100 Subject: [PATCH 01/19] initial --- EDSEditor.sln | 6 + EDSEditorGUI2/.gitignore | 2 + EDSEditorGUI2/App.axaml | 15 +++ EDSEditorGUI2/App.axaml.cs | 33 +++++ EDSEditorGUI2/Assets/avalonia-logo.ico | Bin 0 -> 176111 bytes EDSEditorGUI2/EDSEditorGUI2.csproj | 27 ++++ EDSEditorGUI2/Program.cs | 21 ++++ EDSEditorGUI2/ViewLocator.cs | 33 +++++ EDSEditorGUI2/ViewModels/Device.cs | 47 +++++++ EDSEditorGUI2/ViewModels/DeviceInfo.cs | 118 ++++++++++++++++++ EDSEditorGUI2/ViewModels/DeviceOD.cs | 25 ++++ .../ViewModels/MainWindowViewModel.cs | 47 +++++++ EDSEditorGUI2/ViewModels/ViewModelBase.cs | 7 ++ EDSEditorGUI2/Views/DeviceInfoView.axaml | 40 ++++++ EDSEditorGUI2/Views/DeviceInfoView.axaml.cs | 13 ++ EDSEditorGUI2/Views/DeviceODView.axaml | 10 ++ EDSEditorGUI2/Views/DeviceODView.axaml.cs | 13 ++ EDSEditorGUI2/Views/DeviceView.axaml | 29 +++++ EDSEditorGUI2/Views/DeviceView.axaml.cs | 11 ++ EDSEditorGUI2/Views/MainWindow.axaml | 62 +++++++++ EDSEditorGUI2/Views/MainWindow.axaml.cs | 11 ++ EDSEditorGUI2/app.manifest | 18 +++ 22 files changed, 588 insertions(+) create mode 100644 EDSEditorGUI2/.gitignore create mode 100644 EDSEditorGUI2/App.axaml create mode 100644 EDSEditorGUI2/App.axaml.cs create mode 100644 EDSEditorGUI2/Assets/avalonia-logo.ico create mode 100644 EDSEditorGUI2/EDSEditorGUI2.csproj create mode 100644 EDSEditorGUI2/Program.cs create mode 100644 EDSEditorGUI2/ViewLocator.cs create mode 100644 EDSEditorGUI2/ViewModels/Device.cs create mode 100644 EDSEditorGUI2/ViewModels/DeviceInfo.cs create mode 100644 EDSEditorGUI2/ViewModels/DeviceOD.cs create mode 100644 EDSEditorGUI2/ViewModels/MainWindowViewModel.cs create mode 100644 EDSEditorGUI2/ViewModels/ViewModelBase.cs create mode 100644 EDSEditorGUI2/Views/DeviceInfoView.axaml create mode 100644 EDSEditorGUI2/Views/DeviceInfoView.axaml.cs create mode 100644 EDSEditorGUI2/Views/DeviceODView.axaml create mode 100644 EDSEditorGUI2/Views/DeviceODView.axaml.cs create mode 100644 EDSEditorGUI2/Views/DeviceView.axaml create mode 100644 EDSEditorGUI2/Views/DeviceView.axaml.cs create mode 100644 EDSEditorGUI2/Views/MainWindow.axaml create mode 100644 EDSEditorGUI2/Views/MainWindow.axaml.cs create mode 100644 EDSEditorGUI2/app.manifest diff --git a/EDSEditor.sln b/EDSEditor.sln index 5b28768c..5d1ea4ae 100644 --- a/EDSEditor.sln +++ b/EDSEditor.sln @@ -14,6 +14,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EDSSharp", "EDSSharp\EDSSharp.csproj", "{8B7A7545-6257-44BF-8868-F429E1B72C77}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EDSEditorGUI2", "EDSEditorGUI2\EDSEditorGUI2.csproj", "{F175A47B-8BB8-480F-8D31-AF802086B8B4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -36,6 +38,10 @@ Global {8B7A7545-6257-44BF-8868-F429E1B72C77}.Debug|Any CPU.Build.0 = Debug|Any CPU {8B7A7545-6257-44BF-8868-F429E1B72C77}.Release|Any CPU.ActiveCfg = Release|Any CPU {8B7A7545-6257-44BF-8868-F429E1B72C77}.Release|Any CPU.Build.0 = Release|Any CPU + {F175A47B-8BB8-480F-8D31-AF802086B8B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F175A47B-8BB8-480F-8D31-AF802086B8B4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F175A47B-8BB8-480F-8D31-AF802086B8B4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F175A47B-8BB8-480F-8D31-AF802086B8B4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/EDSEditorGUI2/.gitignore b/EDSEditorGUI2/.gitignore new file mode 100644 index 00000000..fe7ab09c --- /dev/null +++ b/EDSEditorGUI2/.gitignore @@ -0,0 +1,2 @@ +/obj/* +/bin/* diff --git a/EDSEditorGUI2/App.axaml b/EDSEditorGUI2/App.axaml new file mode 100644 index 00000000..cb04bad3 --- /dev/null +++ b/EDSEditorGUI2/App.axaml @@ -0,0 +1,15 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/EDSEditorGUI2/App.axaml.cs b/EDSEditorGUI2/App.axaml.cs new file mode 100644 index 00000000..84f86990 --- /dev/null +++ b/EDSEditorGUI2/App.axaml.cs @@ -0,0 +1,33 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Data.Core; +using Avalonia.Data.Core.Plugins; +using Avalonia.Markup.Xaml; +using EDSEditorGUI2.ViewModels; +using EDSEditorGUI2.Views; + +namespace EDSEditorGUI2; + +public partial class App : Application +{ + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + // Line below is needed to remove Avalonia data validation. + // Without this line you will get duplicate validations from both Avalonia and CT + BindingPlugins.DataValidators.RemoveAt(0); + desktop.MainWindow = new MainWindow + { + DataContext = new MainWindowViewModel(), + }; + } + + base.OnFrameworkInitializationCompleted(); + } +} \ No newline at end of file diff --git a/EDSEditorGUI2/Assets/avalonia-logo.ico b/EDSEditorGUI2/Assets/avalonia-logo.ico new file mode 100644 index 0000000000000000000000000000000000000000..da8d49ff9b94e52778f5324a1b87dd443a698b57 GIT binary patch literal 176111 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 zds⪻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

r@R8DskCg$QY%d=x6zD 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

!LxQh!NmF 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 + + WinExe + net8.0 + enable + true + app.manifest + true + + + + + + + + + + + + + + + + + + + diff --git a/EDSEditorGUI2/Program.cs b/EDSEditorGUI2/Program.cs new file mode 100644 index 00000000..c22186a5 --- /dev/null +++ b/EDSEditorGUI2/Program.cs @@ -0,0 +1,21 @@ +using Avalonia; +using System; + +namespace EDSEditorGUI2; + +sealed class Program +{ + // Initialization code. Don't use any Avalonia, third-party APIs or any + // SynchronizationContext-reliant code before AppMain is called: things aren't initialized + // yet and stuff might break. + [STAThread] + public static void Main(string[] args) => BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + + // Avalonia configuration, don't remove; also used by visual designer. + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() + .WithInterFont() + .LogToTrace(); +} diff --git a/EDSEditorGUI2/ViewLocator.cs b/EDSEditorGUI2/ViewLocator.cs new file mode 100644 index 00000000..cb84293d --- /dev/null +++ b/EDSEditorGUI2/ViewLocator.cs @@ -0,0 +1,33 @@ +using System; +using Avalonia.Controls; +using Avalonia.Controls.Templates; +using EDSEditorGUI2.ViewModels; + +namespace EDSEditorGUI2; + +public class ViewLocator : IDataTemplate +{ + + public Control? Build(object? data) + { + if (data is null) + return null; + + var name = data.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal); + var type = Type.GetType(name); + + if (type != null) + { + var control = (Control)Activator.CreateInstance(type)!; + control.DataContext = data; + return control; + } + + return new TextBlock { Text = "Not Found: " + name }; + } + + public bool Match(object? data) + { + return data is ViewModelBase; + } +} diff --git a/EDSEditorGUI2/ViewModels/Device.cs b/EDSEditorGUI2/ViewModels/Device.cs new file mode 100644 index 00000000..67d28c50 --- /dev/null +++ b/EDSEditorGUI2/ViewModels/Device.cs @@ -0,0 +1,47 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using LibCanOpen; +using System.ComponentModel; +using System.Xml.Linq; + +namespace EDSEditorGUI2.ViewModels +{ + public partial class Device : ObservableObject + { + [ObservableProperty] + CanOpenDevice _model; + public Device(CanOpenDevice model) { Model = model; + + _DeviceInfo = new(Model.DeviceInfo); + _DeviceInfo.PropertyChanged += (s, e) => { OnPropertyChanged(nameof(DeviceInfo)); }; + //Model.Objects + } + + private DeviceInfo _DeviceInfo; + public DeviceInfo DeviceInfo + { + get => _DeviceInfo; + set + { + Model.DeviceInfo = value.Model; + OnPropertyChanged(nameof(DeviceInfo)); + } + } + + private DeviceOD _objects; + public DeviceOD Objects + { + get => _objects; + set + { + //Model.Objects = value.Model; + OnPropertyChanged(nameof(Objects)); + } + } + + public void OnClickCommand() + { + // do something + } + } +} + diff --git a/EDSEditorGUI2/ViewModels/DeviceInfo.cs b/EDSEditorGUI2/ViewModels/DeviceInfo.cs new file mode 100644 index 00000000..4c571959 --- /dev/null +++ b/EDSEditorGUI2/ViewModels/DeviceInfo.cs @@ -0,0 +1,118 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using LibCanOpen; + +namespace EDSEditorGUI2.ViewModels +{ + public partial class DeviceInfo : ObservableObject + { + [ObservableProperty] + private CanOpen_DeviceInfo _model; + public DeviceInfo(CanOpen_DeviceInfo model) { Model = model; } + + public string VendorName + { + get => Model.VendorName; + set + { + Model.VendorName = value; + OnPropertyChanged(nameof(VendorName)); + } + } + public string ProductName + { + get => Model.ProductName; + set + { + SetProperty(Model.ProductName, value, Model, (u, n) => u.ProductName = n); + } + } + public bool BaudRate10 + { + get => Model.BaudRate10; + set + { + SetProperty(Model.BaudRate10, value, Model, (u, n) => u.BaudRate10 = n); + } + } + public bool BaudRate20 + { + get => Model.BaudRate20; + set + { + SetProperty(Model.BaudRate20, value, Model, (u, n) => u.BaudRate20 = n); + } + } + public bool BaudRate50 + { + get => Model.BaudRate50; + set + { + SetProperty(Model.BaudRate50, value, Model, (u, n) => u.BaudRate50 = n); + } + } + public bool BaudRate125 + { + get => Model.BaudRate125; + set + { + SetProperty(Model.BaudRate125, value, Model, (u, n) => u.BaudRate125 = n); + } + } + public bool BaudRate250 + { + get => Model.BaudRate250; + set + { + SetProperty(Model.BaudRate250, value, Model, (u, n) => u.BaudRate250 = n); + } + } + public bool BaudRate500 + { + get => Model.BaudRate500; + set + { + SetProperty(Model.BaudRate500, value, Model, (u, n) => u.BaudRate500 = n); + } + } + public bool BaudRate800 + { + get => Model.BaudRate800; + set + { + SetProperty(Model.BaudRate800, value, Model, (u, n) => u.BaudRate800 = n); + } + } + public bool BaudRate1000 + { + get => Model.BaudRate1000; + set + { + SetProperty(Model.BaudRate1000, value, Model, (u, n) => u.BaudRate1000 = n); + } + } + public bool BaudRateAuto + { + get => Model.BaudRateAuto; + set + { + SetProperty(Model.BaudRateAuto, value, Model, (u, n) => u.BaudRateAuto = n); + } + } + public bool LssSlave + { + get => Model.LssSlave; + set + { + SetProperty(Model.LssSlave, value, Model, (u, n) => u.LssSlave = n); + } + } + public bool LssMaster + { + get => Model.LssMaster; + set + { + SetProperty(Model.LssMaster, value, Model, (u, n) => u.LssMaster = n); + } + } + } +} diff --git a/EDSEditorGUI2/ViewModels/DeviceOD.cs b/EDSEditorGUI2/ViewModels/DeviceOD.cs new file mode 100644 index 00000000..8f62051f --- /dev/null +++ b/EDSEditorGUI2/ViewModels/DeviceOD.cs @@ -0,0 +1,25 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using Google.Protobuf.Collections; +using LibCanOpen; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace EDSEditorGUI2.ViewModels; +public partial class DeviceOD : ObservableObject +{ + [ObservableProperty] + private MapField _model; + public DeviceOD(MapField model) + { + Model = model; + _viewModel = new(Model); + _viewModel.CollectionChanged += ViewModel_CollectionChanged; + } + + private void ViewModel_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + OnPropertyChanged(nameof(ViewModel)); + } + [ObservableProperty] + ObservableCollection> _viewModel; +} diff --git a/EDSEditorGUI2/ViewModels/MainWindowViewModel.cs b/EDSEditorGUI2/ViewModels/MainWindowViewModel.cs new file mode 100644 index 00000000..0a36bdaa --- /dev/null +++ b/EDSEditorGUI2/ViewModels/MainWindowViewModel.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics.Metrics; +using Avalonia.Interactivity; +using CommunityToolkit.Mvvm.ComponentModel; +using LibCanOpen; + +namespace EDSEditorGUI2.ViewModels; + +public partial class MainWindowViewModel : ViewModelBase +{ + int Counter = 0; + public void AddNewDevice(object sender) + { + var device = new LibCanOpen.CanOpenDevice + { + DeviceInfo = new() + { + ProductName = "New Product" + Counter.ToString() + }, + }; + + Counter++; + + //string dir = Environment.OSVersion.Platform == PlatformID.Win32NT ? "\\" : "/"; + //eds.projectFilename = Environment.GetFolderPath(Environment.SpecialFolder.Personal) + dir + "project"; + + //DeviceView device = new DeviceView(eds, network); + //device.UpdateODViewForEDS += Device_UpdateODViewForEDS; + + //eds.OnDataDirty += Eds_onDataDirty; + + //device.Dock = DockStyle.Fill; + //device.dispatch_updateOD(); + + Network.Add(new Device(device)); + } +#pragma warning disable CA1822 // Mark members as static + public string Greeting => "Welcome to Avalonia!"; +#pragma warning restore CA1822 // Mark members as static + public ObservableCollection Network { get; set; } = []; + + [ObservableProperty] + public Device? selectedDevice; + + +} diff --git a/EDSEditorGUI2/ViewModels/ViewModelBase.cs b/EDSEditorGUI2/ViewModels/ViewModelBase.cs new file mode 100644 index 00000000..a4565d46 --- /dev/null +++ b/EDSEditorGUI2/ViewModels/ViewModelBase.cs @@ -0,0 +1,7 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace EDSEditorGUI2.ViewModels; + +public class ViewModelBase : ObservableObject +{ +} diff --git a/EDSEditorGUI2/Views/DeviceInfoView.axaml b/EDSEditorGUI2/Views/DeviceInfoView.axaml new file mode 100644 index 00000000..a624698e --- /dev/null +++ b/EDSEditorGUI2/Views/DeviceInfoView.axaml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + LSS Slave + Master + + + Baudrates + 10 kbit/s. + 20 kbit/s. + 50 kbit/s. + 125 kbit/s. + 250 kbit/s. + 500 kbit/s. + 800 kbit/s. + 1000 kbit/s. + auto + + + + + diff --git a/EDSEditorGUI2/Views/DeviceInfoView.axaml.cs b/EDSEditorGUI2/Views/DeviceInfoView.axaml.cs new file mode 100644 index 00000000..dfed0f20 --- /dev/null +++ b/EDSEditorGUI2/Views/DeviceInfoView.axaml.cs @@ -0,0 +1,13 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace EDSEditorGUI2.Views; + +public partial class DeviceInfoView : UserControl +{ + public DeviceInfoView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/EDSEditorGUI2/Views/DeviceODView.axaml b/EDSEditorGUI2/Views/DeviceODView.axaml new file mode 100644 index 00000000..a4d65a94 --- /dev/null +++ b/EDSEditorGUI2/Views/DeviceODView.axaml @@ -0,0 +1,10 @@ + + Welcome to Avalonia! + diff --git a/EDSEditorGUI2/Views/DeviceODView.axaml.cs b/EDSEditorGUI2/Views/DeviceODView.axaml.cs new file mode 100644 index 00000000..b66651fc --- /dev/null +++ b/EDSEditorGUI2/Views/DeviceODView.axaml.cs @@ -0,0 +1,13 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace EDSEditorGUI2; + +public partial class DeviceODView : UserControl +{ + public DeviceODView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/EDSEditorGUI2/Views/DeviceView.axaml b/EDSEditorGUI2/Views/DeviceView.axaml new file mode 100644 index 00000000..785dccd2 --- /dev/null +++ b/EDSEditorGUI2/Views/DeviceView.axaml @@ -0,0 +1,29 @@ + + + + + + + + + + + This is tab 3 content + + + This is tab 3 content + + + This is tab 3 content + + + + diff --git a/EDSEditorGUI2/Views/DeviceView.axaml.cs b/EDSEditorGUI2/Views/DeviceView.axaml.cs new file mode 100644 index 00000000..13758624 --- /dev/null +++ b/EDSEditorGUI2/Views/DeviceView.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace EDSEditorGUI2.Views; + +public partial class DeviceView : UserControl +{ + public DeviceView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/EDSEditorGUI2/Views/MainWindow.axaml b/EDSEditorGUI2/Views/MainWindow.axaml new file mode 100644 index 00000000..d3c688bd --- /dev/null +++ b/EDSEditorGUI2/Views/MainWindow.axaml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/EDSEditorGUI2/Views/MainWindow.axaml.cs b/EDSEditorGUI2/Views/MainWindow.axaml.cs new file mode 100644 index 00000000..8fc2bd17 --- /dev/null +++ b/EDSEditorGUI2/Views/MainWindow.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace EDSEditorGUI2.Views; + +public partial class MainWindow : Window +{ + public MainWindow() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/EDSEditorGUI2/app.manifest b/EDSEditorGUI2/app.manifest new file mode 100644 index 00000000..d88e268e --- /dev/null +++ b/EDSEditorGUI2/app.manifest @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + From c36848cd2764e514f92b89941c28b6be2b4586ef Mon Sep 17 00:00:00 2001 From: Lars Elgtvedt Susaas Date: Thu, 26 Dec 2024 10:52:36 +0100 Subject: [PATCH 02/19] removed avalonia greeting --- EDSEditorGUI2/Views/MainWindow.axaml | 1 - 1 file changed, 1 deletion(-) diff --git a/EDSEditorGUI2/Views/MainWindow.axaml b/EDSEditorGUI2/Views/MainWindow.axaml index d3c688bd..3a0e4a89 100644 --- a/EDSEditorGUI2/Views/MainWindow.axaml +++ b/EDSEditorGUI2/Views/MainWindow.axaml @@ -55,7 +55,6 @@ - From 087ed1663f586317f8fe1bd7a1a5e4c4350ed451 Mon Sep 17 00:00:00 2001 From: Lars Elgtvedt Susaas Date: Thu, 26 Dec 2024 10:55:00 +0100 Subject: [PATCH 03/19] changed avalonia version to get datagrid package setup --- EDSEditorGUI2/App.axaml | 1 + EDSEditorGUI2/EDSEditorGUI2.csproj | 13 +++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/EDSEditorGUI2/App.axaml b/EDSEditorGUI2/App.axaml index cb04bad3..5300b3a2 100644 --- a/EDSEditorGUI2/App.axaml +++ b/EDSEditorGUI2/App.axaml @@ -11,5 +11,6 @@ + \ No newline at end of file diff --git a/EDSEditorGUI2/EDSEditorGUI2.csproj b/EDSEditorGUI2/EDSEditorGUI2.csproj index c6e525d7..a2f86ee3 100644 --- a/EDSEditorGUI2/EDSEditorGUI2.csproj +++ b/EDSEditorGUI2/EDSEditorGUI2.csproj @@ -15,13 +15,14 @@ - - - - - + + + + + + - + From b6a1467851d86e5a2c4dd833d473cd15240c0704 Mon Sep 17 00:00:00 2001 From: Lars Elgtvedt Susaas Date: Thu, 26 Dec 2024 10:56:40 +0100 Subject: [PATCH 04/19] initial od view code --- EDSEditorGUI2/Views/DeviceODView.axaml | 4 ++-- EDSEditorGUI2/Views/DeviceODView.axaml.cs | 4 +--- EDSEditorGUI2/Views/DeviceView.axaml | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/EDSEditorGUI2/Views/DeviceODView.axaml b/EDSEditorGUI2/Views/DeviceODView.axaml index a4d65a94..7a17b15a 100644 --- a/EDSEditorGUI2/Views/DeviceODView.axaml +++ b/EDSEditorGUI2/Views/DeviceODView.axaml @@ -5,6 +5,6 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="EDSEditorGUI2.DeviceODView"> - Welcome to Avalonia! + x:Class="EDSEditorGUI2.Views.DeviceODView" + x:DataType="vm:DeviceOD"> diff --git a/EDSEditorGUI2/Views/DeviceODView.axaml.cs b/EDSEditorGUI2/Views/DeviceODView.axaml.cs index b66651fc..ea487fa7 100644 --- a/EDSEditorGUI2/Views/DeviceODView.axaml.cs +++ b/EDSEditorGUI2/Views/DeviceODView.axaml.cs @@ -1,8 +1,6 @@ -using Avalonia; using Avalonia.Controls; -using Avalonia.Markup.Xaml; -namespace EDSEditorGUI2; +namespace EDSEditorGUI2.Views; public partial class DeviceODView : UserControl { diff --git a/EDSEditorGUI2/Views/DeviceView.axaml b/EDSEditorGUI2/Views/DeviceView.axaml index 785dccd2..b7032913 100644 --- a/EDSEditorGUI2/Views/DeviceView.axaml +++ b/EDSEditorGUI2/Views/DeviceView.axaml @@ -13,7 +13,7 @@ - + This is tab 3 content From aa1e8613096543966cbb70713888315580d718e9 Mon Sep 17 00:00:00 2001 From: Lars Elgtvedt Susaas Date: Sat, 4 Jan 2025 13:44:53 +0100 Subject: [PATCH 05/19] Added OD view(s) --- EDSEditorGUI2/App.axaml | 3 + .../Converter/NewIndexMultiConvert.cs | 46 ++++++++++++ EDSEditorGUI2/EDSEditorGUI2.csproj | 1 + EDSEditorGUI2/Icons.axaml | 16 ++++ EDSEditorGUI2/ViewModels/Device.cs | 3 +- EDSEditorGUI2/ViewModels/DeviceOD.cs | 67 ++++++++++++++++- EDSEditorGUI2/Views/DeviceODView.axaml | 56 ++++++++++++++ EDSEditorGUI2/Views/DeviceODView.axaml.cs | 26 +++++++ EDSEditorGUI2/Views/ODIndexRangeView.axaml | 42 +++++++++++ EDSEditorGUI2/Views/ODIndexRangeView.axaml.cs | 74 +++++++++++++++++++ 10 files changed, 331 insertions(+), 3 deletions(-) create mode 100644 EDSEditorGUI2/Converter/NewIndexMultiConvert.cs create mode 100644 EDSEditorGUI2/Icons.axaml create mode 100644 EDSEditorGUI2/Views/ODIndexRangeView.axaml create mode 100644 EDSEditorGUI2/Views/ODIndexRangeView.axaml.cs diff --git a/EDSEditorGUI2/App.axaml b/EDSEditorGUI2/App.axaml index 5300b3a2..c1793b31 100644 --- a/EDSEditorGUI2/App.axaml +++ b/EDSEditorGUI2/App.axaml @@ -1,5 +1,6 @@ @@ -12,5 +13,7 @@ + + \ No newline at end of file diff --git a/EDSEditorGUI2/Converter/NewIndexMultiConvert.cs b/EDSEditorGUI2/Converter/NewIndexMultiConvert.cs new file mode 100644 index 00000000..9e537fb5 --- /dev/null +++ b/EDSEditorGUI2/Converter/NewIndexMultiConvert.cs @@ -0,0 +1,46 @@ +using Avalonia.Data.Converters; +using Avalonia.Data; +using Avalonia.Media.Immutable; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace EDSEditorGUI2.Converter; + +public class NewIndexRequest(int index, string name, LibCanOpen.OdObject.Types.ObjectType type) +{ + public int Index { get; } = index; + public string Name { get; } = name; + public LibCanOpen.OdObject.Types.ObjectType Type { get; } = type; +} + +public sealed class NewIndexMultiConvert : IMultiValueConverter +{ + public object? Convert(IList values, Type targetType, object? parameter, CultureInfo culture) + { + // Ensure all bindings are provided and attached to correct target type + if (values?.Count != 3 || !targetType.IsAssignableFrom(typeof(ImmutableSolidColorBrush))) + throw new NotSupportedException(); + + if (values[0] is not string rawindex || + values[1] is not string name || + values[2] is not int typeIndex) + return BindingOperations.DoNothing; + + int index = int.Parse(rawindex, NumberStyles.HexNumber); + + var typeValues = Enum.GetNames(typeof(LibCanOpen.OdObject.Types.ObjectType)).Skip(1).ToArray(); + bool parseOk = Enum.TryParse(typeValues[typeIndex], out LibCanOpen.OdObject.Types.ObjectType type); + + if(parseOk) + { + var indexRequest = new NewIndexRequest(index, name, type); + return indexRequest; + } + else + { + return BindingOperations.DoNothing; + } + } +} diff --git a/EDSEditorGUI2/EDSEditorGUI2.csproj b/EDSEditorGUI2/EDSEditorGUI2.csproj index a2f86ee3..aa430e63 100644 --- a/EDSEditorGUI2/EDSEditorGUI2.csproj +++ b/EDSEditorGUI2/EDSEditorGUI2.csproj @@ -24,5 +24,6 @@ + diff --git a/EDSEditorGUI2/Icons.axaml b/EDSEditorGUI2/Icons.axaml new file mode 100644 index 00000000..7e7cf674 --- /dev/null +++ b/EDSEditorGUI2/Icons.axaml @@ -0,0 +1,16 @@ + + + + + + + + + + + M3.7547787,12.4995322 L20.2466903,12.4995322 C20.6609039,12.4995322 20.9966903,12.1637458 20.9966903,11.7495322 C20.9966903,11.3353187 20.6609039,10.9995322 20.2466903,10.9995322 L3.7547787,10.9995322 C3.34056514,10.9995322 3.0047787,11.3353187 3.0047787,11.7495322 C3.0047787,12.1637458 3.34056514,12.4995322 3.7547787,12.4995322 Z + M14.5,13 L14.5,3.75378577 C14.5,3.33978577 14.164,3.00378577 13.75,3.00378577 C13.336,3.00378577 13,3.33978577 13,3.75378577 L13,13 L3.75387573,13 C3.33987573,13 3.00387573,13.336 3.00387573,13.75 C3.00387573,14.164 3.33987573,14.5 3.75387573,14.5 L13,14.5 L13,23.7523651 C13,24.1663651 13.336,24.5023651 13.75,24.5023651 C14.164,24.5023651 14.5,24.1663651 14.5,23.7523651 L14.5,14.5 L23.7498262,14.5030754 C24.1638262,14.5030754 24.4998262,14.1670754 24.4998262,13.7530754 C24.4998262,13.3390754 24.1638262,13.0030754 23.7498262,13.0030754 L14.5,13 Z + + + diff --git a/EDSEditorGUI2/ViewModels/Device.cs b/EDSEditorGUI2/ViewModels/Device.cs index 67d28c50..dd7033eb 100644 --- a/EDSEditorGUI2/ViewModels/Device.cs +++ b/EDSEditorGUI2/ViewModels/Device.cs @@ -13,7 +13,7 @@ public partial class Device : ObservableObject _DeviceInfo = new(Model.DeviceInfo); _DeviceInfo.PropertyChanged += (s, e) => { OnPropertyChanged(nameof(DeviceInfo)); }; - //Model.Objects + _objects = new(_model.Objects); } private DeviceInfo _DeviceInfo; @@ -33,7 +33,6 @@ public DeviceOD Objects get => _objects; set { - //Model.Objects = value.Model; OnPropertyChanged(nameof(Objects)); } } diff --git a/EDSEditorGUI2/ViewModels/DeviceOD.cs b/EDSEditorGUI2/ViewModels/DeviceOD.cs index 8f62051f..4fbeff74 100644 --- a/EDSEditorGUI2/ViewModels/DeviceOD.cs +++ b/EDSEditorGUI2/ViewModels/DeviceOD.cs @@ -1,24 +1,89 @@ -using CommunityToolkit.Mvvm.ComponentModel; +using Avalonia.Interactivity; +using CommunityToolkit.Mvvm.ComponentModel; +using DialogHostAvalonia; using Google.Protobuf.Collections; using LibCanOpen; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Linq; +using System.Reflection; +using System.Xml.Linq; namespace EDSEditorGUI2.ViewModels; public partial class DeviceOD : ObservableObject { [ObservableProperty] private MapField _model; + [ObservableProperty] + ReadOnlyObservableCollection> _DataTypes; + + [ObservableProperty] + OdObject _SelectedObject; + + [ObservableProperty] + OdSubObject _SelectedSubObject; + public DeviceOD(MapField model) { Model = model; _viewModel = new(Model); _viewModel.CollectionChanged += ViewModel_CollectionChanged; + HackyUpdate(); } private void ViewModel_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { OnPropertyChanged(nameof(ViewModel)); +/* + var newObj = new OdObject(); + newObj.Name = "name1"; + newObj.Type = OdObject.Types.ObjectType.Var; + Model.Add("1000", newObj); + + var newObj2 = new OdObject(); + newObj.Name = "name2"; + newObj.Type = OdObject.Types.ObjectType.Array; + Model.Add("1001", newObj2); +*/ + HackyUpdate(); + } + + private void HackyUpdate() + { + //Hack should be rewritten + var temp = Model.Where(key => 0x0001 <= IndexStringToInt(key.Key) && IndexStringToInt(key.Key) <= 0x025F); + DataTypes = new(new ObservableCollection>(temp)); + } + + private static int IndexStringToInt(string str) + { + if (str.StartsWith("0x")) + { + var hex = str[2..]; + return Convert.ToUInt16(hex, 16); + } + else + { + return Convert.ToUInt16(str); + } + } + + public void AddIndex(int index, string name, OdObject.Types.ObjectType type) + { + var strIndex = index.ToString("X4"); + var newObj = new OdObject + { + Name = name, + Type = type + }; + ViewModel.Add(new KeyValuePair(strIndex,newObj)); + Model.Add(strIndex, newObj); + } + + public void RemoveIndex(object sender) + { + } [ObservableProperty] ObservableCollection> _viewModel; diff --git a/EDSEditorGUI2/Views/DeviceODView.axaml b/EDSEditorGUI2/Views/DeviceODView.axaml index 7a17b15a..3a199502 100644 --- a/EDSEditorGUI2/Views/DeviceODView.axaml +++ b/EDSEditorGUI2/Views/DeviceODView.axaml @@ -1,10 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/EDSEditorGUI2/Views/DeviceODView.axaml.cs b/EDSEditorGUI2/Views/DeviceODView.axaml.cs index ea487fa7..06b5f841 100644 --- a/EDSEditorGUI2/Views/DeviceODView.axaml.cs +++ b/EDSEditorGUI2/Views/DeviceODView.axaml.cs @@ -1,4 +1,9 @@ using Avalonia.Controls; +using Avalonia.Interactivity; +using DialogHostAvalonia; +using EDSEditorGUI2.Converter; +using System; +using System.Linq; namespace EDSEditorGUI2.Views; @@ -7,5 +12,26 @@ public partial class DeviceODView : UserControl public DeviceODView() { InitializeComponent(); + + var values = Enum.GetNames(typeof(LibCanOpen.OdObject.Types.ObjectType)).Skip(1).ToArray(); + type.ItemsSource = values; + } + private void OnDialogClosing(object? sender, DialogClosingEventArgs e) + { + if(e.Parameter != null) + { + if(DataContext is ViewModels.DeviceOD dc && e.Parameter is NewIndexRequest param) + { + dc.AddIndex(param.Index, param.Name, param.Type); + } + } + } + private async void AddIndex(object? sender, RoutedEventArgs e) + { + await DialogHost.Show(Resources["NewIndexDialog"]!, "NoAnimationDialogHost"); + } + + private void DataGrid_SelectionChanged(object? sender, Avalonia.Controls.SelectionChangedEventArgs e) + { } } \ No newline at end of file diff --git a/EDSEditorGUI2/Views/ODIndexRangeView.axaml b/EDSEditorGUI2/Views/ODIndexRangeView.axaml new file mode 100644 index 00000000..c3ed7a1a --- /dev/null +++ b/EDSEditorGUI2/Views/ODIndexRangeView.axaml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/EDSEditorGUI2/Views/ODIndexRangeView.axaml.cs b/EDSEditorGUI2/Views/ODIndexRangeView.axaml.cs new file mode 100644 index 00000000..f290648b --- /dev/null +++ b/EDSEditorGUI2/Views/ODIndexRangeView.axaml.cs @@ -0,0 +1,74 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Markup.Xaml; +using Avalonia.Media; +using DialogHostAvalonia; +using System; + +namespace EDSEditorGUI2.Views; + +public partial class ODIndexRangeView : UserControl +{ + public ODIndexRangeView() + { + InitializeComponent(); + grid.LoadingRow += GridLoadingRow; + } + + /// + /// Hides rows with indexes that is not in min&max range + /// + /// sender object + /// event param + private void GridLoadingRow(object sender, DataGridRowEventArgs e) + { + var dc = (System.Collections.Generic.KeyValuePair)e.Row.DataContext; + int index = int.Parse(dc.Key, System.Globalization.NumberStyles.HexNumber); + int min = Convert.ToInt32(MinIndex, 16); + int max = Convert.ToInt32(MaxIndex, 16); + e.Row.IsVisible = (min <= index && index <= max); + } + + public static readonly StyledProperty HeadingProperty = + AvaloniaProperty.Register(nameof(HeadingProperty)); + public string Heading + { + get { return GetValue(HeadingProperty); } + set { SetValue(HeadingProperty, value); HeadingText.Text = value; } + } + + public static readonly StyledProperty MinIndexProperty = + AvaloniaProperty.Register(nameof(MinIndexProperty)); + public string MinIndex + { + get { return GetValue(MinIndexProperty); } + set { SetValue(MinIndexProperty, value); } + } + + public static readonly StyledProperty MaxIndexProperty = + AvaloniaProperty.Register(nameof(MaxIndexProperty)); + public string MaxIndex + { + get { return GetValue(MaxIndexProperty); } + set { SetValue(MaxIndexProperty, value); } + } + + private async void AddIndex(object? sender, RoutedEventArgs e) + { + //TODO: find a way to make it more rubust and remove the parent parent thingy + var DialogHostRes = Parent.Parent.Parent.Parent.Resources; + await DialogHost.Show(DialogHostRes["NewIndexDialog"]!, "NoAnimationDialogHost"); + } + + private async void RemoveIndex(object? sender, RoutedEventArgs e) + { + //TODO: find a way to make it more rubust and remove the parent parent thingy + var DialogHostRes = Parent.Parent.Parent.Parent.Resources; + await DialogHost.Show(DialogHostRes["NewIndexDialog"]!, "NoAnimationDialogHost"); + } + + private void DataGrid_SelectionChanged(object? sender, Avalonia.Controls.SelectionChangedEventArgs e) + { + } +} \ No newline at end of file From 88a18240bf94cb5eedc54f5dc8e4fbaa917b98d1 Mon Sep 17 00:00:00 2001 From: Lars Elgtvedt Susaas Date: Sat, 4 Jan 2025 15:22:33 +0100 Subject: [PATCH 06/19] adding default subvalues when creating new objects --- EDSEditorGUI2/ViewModels/DeviceOD.cs | 41 +++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/EDSEditorGUI2/ViewModels/DeviceOD.cs b/EDSEditorGUI2/ViewModels/DeviceOD.cs index 4fbeff74..23ce88d7 100644 --- a/EDSEditorGUI2/ViewModels/DeviceOD.cs +++ b/EDSEditorGUI2/ViewModels/DeviceOD.cs @@ -77,7 +77,46 @@ public void AddIndex(int index, string name, OdObject.Types.ObjectType type) Name = name, Type = type }; - ViewModel.Add(new KeyValuePair(strIndex,newObj)); + + // create OD entry + if (type == OdObject.Types.ObjectType.Var) + { + var newSub = new OdSubObject() + { + Name = name, + Type = OdSubObject.Types.DataType.Unsigned32, + Sdo = OdSubObject.Types.AccessSDO.Rw, + Pdo = OdSubObject.Types.AccessPDO.No, + Srdo = OdSubObject.Types.AccessSRDO.No, + DefaultValue = "0" + }; + newObj.SubObjects.Add("0", newSub); + } + else + { + var CountSub = new OdSubObject() + { + Name = "Highest sub-index supported", + Type = OdSubObject.Types.DataType.Unsigned8, + Sdo = OdSubObject.Types.AccessSDO.Ro, + Pdo = OdSubObject.Types.AccessPDO.No, + Srdo = OdSubObject.Types.AccessSRDO.No, + DefaultValue = "0x01" + }; + var Sub1 = new OdSubObject() + { + Name = "Sub Object 1", + Type = OdSubObject.Types.DataType.Unsigned32, + Sdo = OdSubObject.Types.AccessSDO.Rw, + Pdo = OdSubObject.Types.AccessPDO.No, + Srdo = OdSubObject.Types.AccessSRDO.No, + DefaultValue = "0" + }; + newObj.SubObjects.Add("0", CountSub); + newObj.SubObjects.Add("1", Sub1); + } + + ViewModel.Add(new KeyValuePair(strIndex, newObj)); Model.Add(strIndex, newObj); } From 6c62316aafdade5738e8945fe3baed58f8b9b972 Mon Sep 17 00:00:00 2001 From: Lars Elgtvedt Susaas Date: Sat, 4 Jan 2025 15:28:06 +0100 Subject: [PATCH 07/19] adding subobject view --- EDSEditorGUI2/Views/DeviceODView.axaml | 18 +++++++++++- EDSEditorGUI2/Views/DeviceODView.axaml.cs | 35 +++++++++++++++++++---- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/EDSEditorGUI2/Views/DeviceODView.axaml b/EDSEditorGUI2/Views/DeviceODView.axaml index 3a199502..daade369 100644 --- a/EDSEditorGUI2/Views/DeviceODView.axaml +++ b/EDSEditorGUI2/Views/DeviceODView.axaml @@ -14,16 +14,19 @@ @@ -31,7 +34,20 @@ - + + + + + + + + + + + diff --git a/EDSEditorGUI2/Views/DeviceODView.axaml.cs b/EDSEditorGUI2/Views/DeviceODView.axaml.cs index 06b5f841..479a46fe 100644 --- a/EDSEditorGUI2/Views/DeviceODView.axaml.cs +++ b/EDSEditorGUI2/Views/DeviceODView.axaml.cs @@ -2,19 +2,29 @@ using Avalonia.Interactivity; using DialogHostAvalonia; using EDSEditorGUI2.Converter; +using LibCanOpen; using System; +using System.Collections.Generic; using System.Linq; namespace EDSEditorGUI2.Views; public partial class DeviceODView : UserControl { + private List _odViews = []; public DeviceODView() { InitializeComponent(); - var values = Enum.GetNames(typeof(LibCanOpen.OdObject.Types.ObjectType)).Skip(1).ToArray(); + var values = Enum.GetNames(typeof(OdObject.Types.ObjectType)).Skip(1).ToArray(); type.ItemsSource = values; + ODView_Com.grid.SelectionChanged += GridSelectionChanged; + ODView_Manufacture.grid.SelectionChanged += GridSelectionChanged; + ODView_Device.grid.SelectionChanged += GridSelectionChanged; + + _odViews.Add(ODView_Com.grid); + _odViews.Add(ODView_Manufacture.grid); + _odViews.Add(ODView_Device.grid); } private void OnDialogClosing(object? sender, DialogClosingEventArgs e) { @@ -26,12 +36,27 @@ private void OnDialogClosing(object? sender, DialogClosingEventArgs e) } } } - private async void AddIndex(object? sender, RoutedEventArgs e) + + private void GridSelectionChanged(object? sender, SelectionChangedEventArgs e) { - await DialogHost.Show(Resources["NewIndexDialog"]!, "NoAnimationDialogHost"); + if(sender is DataGrid s && DataContext is ViewModels.DeviceOD dc) + { + if(s.SelectedItem is KeyValuePair selected) + { + dc.SelectedObject = selected.Value; + foreach (var dg in _odViews) + { + if (dg != s) + { + dg.SelectedItem = null; + } + } + } + } } - private void DataGrid_SelectionChanged(object? sender, Avalonia.Controls.SelectionChangedEventArgs e) + private async void AddIndex(object? sender, RoutedEventArgs e) { + await DialogHost.Show(Resources["NewIndexDialog"]!, "NoAnimationDialogHost"); } -} \ No newline at end of file +} From 85c3b1e9b983457bd1bc483ea1b4fefd3dff402c Mon Sep 17 00:00:00 2001 From: Lars Elgtvedt Susaas Date: Sat, 4 Jan 2025 15:28:45 +0100 Subject: [PATCH 08/19] cleanup --- EDSEditorGUI2/Views/DeviceODView.axaml.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/EDSEditorGUI2/Views/DeviceODView.axaml.cs b/EDSEditorGUI2/Views/DeviceODView.axaml.cs index 479a46fe..3b30acfd 100644 --- a/EDSEditorGUI2/Views/DeviceODView.axaml.cs +++ b/EDSEditorGUI2/Views/DeviceODView.axaml.cs @@ -54,9 +54,4 @@ private void GridSelectionChanged(object? sender, SelectionChangedEventArgs e) } } } - - private async void AddIndex(object? sender, RoutedEventArgs e) - { - await DialogHost.Show(Resources["NewIndexDialog"]!, "NoAnimationDialogHost"); - } } From 0a57ebdfb67d455f12161d3beffd77278c2d5a9c Mon Sep 17 00:00:00 2001 From: Lars Elgtvedt Susaas Date: Sat, 4 Jan 2025 16:22:47 +0100 Subject: [PATCH 09/19] Copied the GUI from v1, not mapped or checked --- EDSEditorGUI2/Views/DeviceODView.axaml | 61 +++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/EDSEditorGUI2/Views/DeviceODView.axaml b/EDSEditorGUI2/Views/DeviceODView.axaml index daade369..8d60dc75 100644 --- a/EDSEditorGUI2/Views/DeviceODView.axaml +++ b/EDSEditorGUI2/Views/DeviceODView.axaml @@ -6,7 +6,7 @@ xmlns:converter="using:EDSEditorGUI2.Converter" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" + mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="800" x:Class="EDSEditorGUI2.Views.DeviceODView" x:DataType="vm:DeviceOD"> @@ -49,7 +49,64 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Autosave + + From 0f4f7ed4285bffcf49ad6668bcf3ec4a5651c5a0 Mon Sep 17 00:00:00 2001 From: Lars Elgtvedt Susaas Date: Sat, 4 Jan 2025 18:59:34 +0100 Subject: [PATCH 10/19] mapping objects subobjects to gui --- EDSEditorGUI2/ViewModels/DeviceOD.cs | 25 +++-------- EDSEditorGUI2/Views/DeviceODView.axaml | 50 +++++++++++----------- EDSEditorGUI2/Views/DeviceODView.axaml.cs | 52 ++++++++++++++++++----- 3 files changed, 72 insertions(+), 55 deletions(-) diff --git a/EDSEditorGUI2/ViewModels/DeviceOD.cs b/EDSEditorGUI2/ViewModels/DeviceOD.cs index 23ce88d7..ad710719 100644 --- a/EDSEditorGUI2/ViewModels/DeviceOD.cs +++ b/EDSEditorGUI2/ViewModels/DeviceOD.cs @@ -1,14 +1,10 @@ -using Avalonia.Interactivity; -using CommunityToolkit.Mvvm.ComponentModel; -using DialogHostAvalonia; +using CommunityToolkit.Mvvm.ComponentModel; using Google.Protobuf.Collections; using LibCanOpen; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using System.Reflection; -using System.Xml.Linq; namespace EDSEditorGUI2.ViewModels; public partial class DeviceOD : ObservableObject @@ -19,10 +15,10 @@ public partial class DeviceOD : ObservableObject ReadOnlyObservableCollection> _DataTypes; [ObservableProperty] - OdObject _SelectedObject; + KeyValuePair _SelectedObject; [ObservableProperty] - OdSubObject _SelectedSubObject; + KeyValuePair _SelectedSubObject; public DeviceOD(MapField model) { @@ -35,17 +31,6 @@ public DeviceOD(MapField model) private void ViewModel_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { OnPropertyChanged(nameof(ViewModel)); -/* - var newObj = new OdObject(); - newObj.Name = "name1"; - newObj.Type = OdObject.Types.ObjectType.Var; - Model.Add("1000", newObj); - - var newObj2 = new OdObject(); - newObj.Name = "name2"; - newObj.Type = OdObject.Types.ObjectType.Array; - Model.Add("1001", newObj2); -*/ HackyUpdate(); } @@ -69,7 +54,7 @@ private static int IndexStringToInt(string str) } } - public void AddIndex(int index, string name, OdObject.Types.ObjectType type) + public void AddIndex(int index, string name, OdObject.Types.ObjectType type) { var strIndex = index.ToString("X4"); var newObj = new OdObject @@ -122,7 +107,7 @@ public void AddIndex(int index, string name, OdObject.Types.ObjectType type) public void RemoveIndex(object sender) { - + } [ObservableProperty] ObservableCollection> _viewModel; diff --git a/EDSEditorGUI2/Views/DeviceODView.axaml b/EDSEditorGUI2/Views/DeviceODView.axaml index 8d60dc75..67a886d7 100644 --- a/EDSEditorGUI2/Views/DeviceODView.axaml +++ b/EDSEditorGUI2/Views/DeviceODView.axaml @@ -34,8 +34,8 @@ - @@ -53,58 +53,58 @@ - + - + - - + + - + - + - + - + - + - + - + - + - + - - - + + + - + - + - + - - + + - + - Autosave + Autosave diff --git a/EDSEditorGUI2/Views/DeviceODView.axaml.cs b/EDSEditorGUI2/Views/DeviceODView.axaml.cs index 3b30acfd..be5398b3 100644 --- a/EDSEditorGUI2/Views/DeviceODView.axaml.cs +++ b/EDSEditorGUI2/Views/DeviceODView.axaml.cs @@ -1,5 +1,4 @@ using Avalonia.Controls; -using Avalonia.Interactivity; using DialogHostAvalonia; using EDSEditorGUI2.Converter; using LibCanOpen; @@ -18,40 +17,73 @@ public DeviceODView() var values = Enum.GetNames(typeof(OdObject.Types.ObjectType)).Skip(1).ToArray(); type.ItemsSource = values; - ODView_Com.grid.SelectionChanged += GridSelectionChanged; - ODView_Manufacture.grid.SelectionChanged += GridSelectionChanged; - ODView_Device.grid.SelectionChanged += GridSelectionChanged; + ODView_Com.grid.SelectionChanged += IndexGridSelectionChanged; + ODView_Manufacture.grid.SelectionChanged += IndexGridSelectionChanged; + ODView_Device.grid.SelectionChanged += IndexGridSelectionChanged; + + subindexGrid.SelectionChanged += subindexGridSelectionChanged; _odViews.Add(ODView_Com.grid); _odViews.Add(ODView_Manufacture.grid); _odViews.Add(ODView_Device.grid); + + foreach (var v in Enum.GetNames(typeof(OdSubObject.Types.DataType))) + { + combo_datatype.Items.Add(v); + } + + foreach (var v in Enum.GetNames(typeof(OdSubObject.Types.AccessSDO))) + { + combo_sdo.Items.Add(v); + } + + foreach (var v in Enum.GetNames(typeof(OdSubObject.Types.AccessPDO))) + { + combo_pdo.Items.Add(v); + } + + foreach (var v in Enum.GetNames(typeof(OdSubObject.Types.AccessSRDO))) + { + combo_srdo.Items.Add(v); + } } private void OnDialogClosing(object? sender, DialogClosingEventArgs e) { - if(e.Parameter != null) + if (e.Parameter != null) { - if(DataContext is ViewModels.DeviceOD dc && e.Parameter is NewIndexRequest param) + if (DataContext is ViewModels.DeviceOD dc && e.Parameter is NewIndexRequest param) { dc.AddIndex(param.Index, param.Name, param.Type); } } } - private void GridSelectionChanged(object? sender, SelectionChangedEventArgs e) + private void IndexGridSelectionChanged(object? sender, SelectionChangedEventArgs e) { - if(sender is DataGrid s && DataContext is ViewModels.DeviceOD dc) + if (sender is DataGrid s && DataContext is ViewModels.DeviceOD dc) { - if(s.SelectedItem is KeyValuePair selected) + if (s.SelectedItem is KeyValuePair selected) { - dc.SelectedObject = selected.Value; + dc.SelectedObject = selected; foreach (var dg in _odViews) { if (dg != s) { dg.SelectedItem = null; + subindexGrid.SelectedItem = null; } } } } } + private void subindexGridSelectionChanged(object? sender, SelectionChangedEventArgs e) + { + if (sender is DataGrid s && DataContext is ViewModels.DeviceOD dc) + { + if (s.SelectedItem is KeyValuePair selected) + { + dc.SelectedSubObject = selected; + } + } + } } From 7551c7bc4b2dd0ae46fa8c0cb93a535c7e188b42 Mon Sep 17 00:00:00 2001 From: Lars Elgtvedt Susaas Date: Sat, 4 Jan 2025 19:00:13 +0100 Subject: [PATCH 11/19] cleanup --- EDSEditorGUI2/App.axaml.cs | 1 - EDSEditorGUI2/Converter/NewIndexMultiConvert.cs | 8 ++++---- EDSEditorGUI2/ViewLocator.cs | 6 +++--- EDSEditorGUI2/ViewModels/Device.cs | 6 +++--- EDSEditorGUI2/ViewModels/MainWindowViewModel.cs | 6 +----- EDSEditorGUI2/Views/DeviceInfoView.axaml.cs | 2 -- EDSEditorGUI2/Views/ODIndexRangeView.axaml.cs | 4 +--- 7 files changed, 12 insertions(+), 21 deletions(-) diff --git a/EDSEditorGUI2/App.axaml.cs b/EDSEditorGUI2/App.axaml.cs index 84f86990..07140117 100644 --- a/EDSEditorGUI2/App.axaml.cs +++ b/EDSEditorGUI2/App.axaml.cs @@ -1,6 +1,5 @@ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Data.Core; using Avalonia.Data.Core.Plugins; using Avalonia.Markup.Xaml; using EDSEditorGUI2.ViewModels; diff --git a/EDSEditorGUI2/Converter/NewIndexMultiConvert.cs b/EDSEditorGUI2/Converter/NewIndexMultiConvert.cs index 9e537fb5..5a5b8d47 100644 --- a/EDSEditorGUI2/Converter/NewIndexMultiConvert.cs +++ b/EDSEditorGUI2/Converter/NewIndexMultiConvert.cs @@ -1,5 +1,5 @@ -using Avalonia.Data.Converters; -using Avalonia.Data; +using Avalonia.Data; +using Avalonia.Data.Converters; using Avalonia.Media.Immutable; using System; using System.Collections.Generic; @@ -12,7 +12,7 @@ public class NewIndexRequest(int index, string name, LibCanOpen.OdObject.Types.O { public int Index { get; } = index; public string Name { get; } = name; - public LibCanOpen.OdObject.Types.ObjectType Type { get; } = type; + public LibCanOpen.OdObject.Types.ObjectType Type { get; } = type; } public sealed class NewIndexMultiConvert : IMultiValueConverter @@ -33,7 +33,7 @@ values[1] is not string name || var typeValues = Enum.GetNames(typeof(LibCanOpen.OdObject.Types.ObjectType)).Skip(1).ToArray(); bool parseOk = Enum.TryParse(typeValues[typeIndex], out LibCanOpen.OdObject.Types.ObjectType type); - if(parseOk) + if (parseOk) { var indexRequest = new NewIndexRequest(index, name, type); return indexRequest; diff --git a/EDSEditorGUI2/ViewLocator.cs b/EDSEditorGUI2/ViewLocator.cs index cb84293d..c52fcee4 100644 --- a/EDSEditorGUI2/ViewLocator.cs +++ b/EDSEditorGUI2/ViewLocator.cs @@ -1,7 +1,7 @@ -using System; using Avalonia.Controls; using Avalonia.Controls.Templates; using EDSEditorGUI2.ViewModels; +using System; namespace EDSEditorGUI2; @@ -12,7 +12,7 @@ public class ViewLocator : IDataTemplate { if (data is null) return null; - + var name = data.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal); var type = Type.GetType(name); @@ -22,7 +22,7 @@ public class ViewLocator : IDataTemplate control.DataContext = data; return control; } - + return new TextBlock { Text = "Not Found: " + name }; } diff --git a/EDSEditorGUI2/ViewModels/Device.cs b/EDSEditorGUI2/ViewModels/Device.cs index dd7033eb..c8180b10 100644 --- a/EDSEditorGUI2/ViewModels/Device.cs +++ b/EDSEditorGUI2/ViewModels/Device.cs @@ -1,7 +1,5 @@ using CommunityToolkit.Mvvm.ComponentModel; using LibCanOpen; -using System.ComponentModel; -using System.Xml.Linq; namespace EDSEditorGUI2.ViewModels { @@ -9,7 +7,9 @@ public partial class Device : ObservableObject { [ObservableProperty] CanOpenDevice _model; - public Device(CanOpenDevice model) { Model = model; + public Device(CanOpenDevice model) + { + Model = model; _DeviceInfo = new(Model.DeviceInfo); _DeviceInfo.PropertyChanged += (s, e) => { OnPropertyChanged(nameof(DeviceInfo)); }; diff --git a/EDSEditorGUI2/ViewModels/MainWindowViewModel.cs b/EDSEditorGUI2/ViewModels/MainWindowViewModel.cs index 0a36bdaa..d1630760 100644 --- a/EDSEditorGUI2/ViewModels/MainWindowViewModel.cs +++ b/EDSEditorGUI2/ViewModels/MainWindowViewModel.cs @@ -1,9 +1,5 @@ -using System.Collections.Generic; +using CommunityToolkit.Mvvm.ComponentModel; using System.Collections.ObjectModel; -using System.Diagnostics.Metrics; -using Avalonia.Interactivity; -using CommunityToolkit.Mvvm.ComponentModel; -using LibCanOpen; namespace EDSEditorGUI2.ViewModels; diff --git a/EDSEditorGUI2/Views/DeviceInfoView.axaml.cs b/EDSEditorGUI2/Views/DeviceInfoView.axaml.cs index dfed0f20..fbf6126a 100644 --- a/EDSEditorGUI2/Views/DeviceInfoView.axaml.cs +++ b/EDSEditorGUI2/Views/DeviceInfoView.axaml.cs @@ -1,6 +1,4 @@ -using Avalonia; using Avalonia.Controls; -using Avalonia.Markup.Xaml; namespace EDSEditorGUI2.Views; diff --git a/EDSEditorGUI2/Views/ODIndexRangeView.axaml.cs b/EDSEditorGUI2/Views/ODIndexRangeView.axaml.cs index f290648b..6e4d5948 100644 --- a/EDSEditorGUI2/Views/ODIndexRangeView.axaml.cs +++ b/EDSEditorGUI2/Views/ODIndexRangeView.axaml.cs @@ -1,8 +1,6 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Interactivity; -using Avalonia.Markup.Xaml; -using Avalonia.Media; using DialogHostAvalonia; using System; @@ -31,7 +29,7 @@ private void GridLoadingRow(object sender, DataGridRowEventArgs e) } public static readonly StyledProperty HeadingProperty = - AvaloniaProperty.Register(nameof(HeadingProperty)); + AvaloniaProperty.Register(nameof(HeadingProperty)); public string Heading { get { return GetValue(HeadingProperty); } From 6dfae2242bc39a128932d1a8a3f8af421bec99f2 Mon Sep 17 00:00:00 2001 From: Lars Elgtvedt Susaas Date: Sat, 4 Jan 2025 23:12:09 +0100 Subject: [PATCH 12/19] Moved dialog definition to rangeview --- EDSEditorGUI2/Icons.axaml | 2 +- EDSEditorGUI2/Views/DeviceODView.axaml | 26 ----------------- EDSEditorGUI2/Views/DeviceODView.axaml.cs | 4 --- EDSEditorGUI2/Views/ODIndexRangeView.axaml | 29 ++++++++++++++++++- EDSEditorGUI2/Views/ODIndexRangeView.axaml.cs | 13 +++++---- 5 files changed, 36 insertions(+), 38 deletions(-) diff --git a/EDSEditorGUI2/Icons.axaml b/EDSEditorGUI2/Icons.axaml index 7e7cf674..228087ff 100644 --- a/EDSEditorGUI2/Icons.axaml +++ b/EDSEditorGUI2/Icons.axaml @@ -6,7 +6,7 @@ - + M3.7547787,12.4995322 L20.2466903,12.4995322 C20.6609039,12.4995322 20.9966903,12.1637458 20.9966903,11.7495322 C20.9966903,11.3353187 20.6609039,10.9995322 20.2466903,10.9995322 L3.7547787,10.9995322 C3.34056514,10.9995322 3.0047787,11.3353187 3.0047787,11.7495322 C3.0047787,12.1637458 3.34056514,12.4995322 3.7547787,12.4995322 Z diff --git a/EDSEditorGUI2/Views/DeviceODView.axaml b/EDSEditorGUI2/Views/DeviceODView.axaml index 67a886d7..96ea4961 100644 --- a/EDSEditorGUI2/Views/DeviceODView.axaml +++ b/EDSEditorGUI2/Views/DeviceODView.axaml @@ -110,30 +110,4 @@ - - - - - - - - - - - - - - - - - diff --git a/EDSEditorGUI2/Views/DeviceODView.axaml.cs b/EDSEditorGUI2/Views/DeviceODView.axaml.cs index be5398b3..5f8a656e 100644 --- a/EDSEditorGUI2/Views/DeviceODView.axaml.cs +++ b/EDSEditorGUI2/Views/DeviceODView.axaml.cs @@ -4,7 +4,6 @@ using LibCanOpen; using System; using System.Collections.Generic; -using System.Linq; namespace EDSEditorGUI2.Views; @@ -14,9 +13,6 @@ public partial class DeviceODView : UserControl public DeviceODView() { InitializeComponent(); - - var values = Enum.GetNames(typeof(OdObject.Types.ObjectType)).Skip(1).ToArray(); - type.ItemsSource = values; ODView_Com.grid.SelectionChanged += IndexGridSelectionChanged; ODView_Manufacture.grid.SelectionChanged += IndexGridSelectionChanged; ODView_Device.grid.SelectionChanged += IndexGridSelectionChanged; diff --git a/EDSEditorGUI2/Views/ODIndexRangeView.axaml b/EDSEditorGUI2/Views/ODIndexRangeView.axaml index c3ed7a1a..2740ab44 100644 --- a/EDSEditorGUI2/Views/ODIndexRangeView.axaml +++ b/EDSEditorGUI2/Views/ODIndexRangeView.axaml @@ -29,7 +29,7 @@ - + @@ -39,4 +39,31 @@ + + + + + + + + + + + + + + + + + + diff --git a/EDSEditorGUI2/Views/ODIndexRangeView.axaml.cs b/EDSEditorGUI2/Views/ODIndexRangeView.axaml.cs index 6e4d5948..acf95c4a 100644 --- a/EDSEditorGUI2/Views/ODIndexRangeView.axaml.cs +++ b/EDSEditorGUI2/Views/ODIndexRangeView.axaml.cs @@ -2,7 +2,9 @@ using Avalonia.Controls; using Avalonia.Interactivity; using DialogHostAvalonia; +using LibCanOpen; using System; +using System.Linq; namespace EDSEditorGUI2.Views; @@ -11,6 +13,9 @@ public partial class ODIndexRangeView : UserControl public ODIndexRangeView() { InitializeComponent(); + var values = Enum.GetNames(typeof(OdObject.Types.ObjectType)).Skip(1).ToArray(); + type.ItemsSource = values; + grid.LoadingRow += GridLoadingRow; } @@ -54,16 +59,12 @@ public string MaxIndex private async void AddIndex(object? sender, RoutedEventArgs e) { - //TODO: find a way to make it more rubust and remove the parent parent thingy - var DialogHostRes = Parent.Parent.Parent.Parent.Resources; - await DialogHost.Show(DialogHostRes["NewIndexDialog"]!, "NoAnimationDialogHost"); + await DialogHost.Show(Resources["NewIndexDialog"]!, "NoAnimationDialogHost"); } private async void RemoveIndex(object? sender, RoutedEventArgs e) { - //TODO: find a way to make it more rubust and remove the parent parent thingy - var DialogHostRes = Parent.Parent.Parent.Parent.Resources; - await DialogHost.Show(DialogHostRes["NewIndexDialog"]!, "NoAnimationDialogHost"); + await DialogHost.Show(Resources["NewIndexDialog"]!, "NoAnimationDialogHost"); } private void DataGrid_SelectionChanged(object? sender, Avalonia.Controls.SelectionChangedEventArgs e) From 35ddf933d85cc0ac1e14d2cf2a546ead9e912b27 Mon Sep 17 00:00:00 2001 From: Lars Elgtvedt Susaas Date: Sun, 5 Jan 2025 14:16:24 +0100 Subject: [PATCH 13/19] Moved newIndex dialog callback to OdRangeView --- EDSEditorGUI2/Views/DeviceODView.axaml | 2 +- EDSEditorGUI2/Views/DeviceODView.axaml.cs | 12 ------------ EDSEditorGUI2/Views/ODIndexRangeView.axaml.cs | 14 +++++++++++++- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/EDSEditorGUI2/Views/DeviceODView.axaml b/EDSEditorGUI2/Views/DeviceODView.axaml index 96ea4961..d1cdd082 100644 --- a/EDSEditorGUI2/Views/DeviceODView.axaml +++ b/EDSEditorGUI2/Views/DeviceODView.axaml @@ -10,7 +10,7 @@ x:Class="EDSEditorGUI2.Views.DeviceODView" x:DataType="vm:DeviceOD"> - + Date: Sun, 12 Jan 2025 13:18:59 +0100 Subject: [PATCH 14/19] Rewrote the viewmodels to only bind the model to root viewmodel --- EDSEditorGUI2/EDSEditorGUI2.csproj | 1 + .../Mapper/ProtobufferViewModelMapper.cs | 51 ++++++ EDSEditorGUI2/ViewModels/Device.cs | 39 ++--- .../ViewModels/DeviceCommissioning.cs | 16 ++ EDSEditorGUI2/ViewModels/DeviceInfo.cs | 153 +++++------------- EDSEditorGUI2/ViewModels/DeviceOD.cs | 68 +++----- EDSEditorGUI2/ViewModels/FileInfo.cs | 25 +++ .../ViewModels/MainWindowViewModel.cs | 4 +- EDSEditorGUI2/ViewModels/OdObject.cs | 37 +++++ EDSEditorGUI2/ViewModels/OdSubObject.cs | 40 +++++ EDSEditorGUI2/Views/DeviceODView.axaml.cs | 5 +- EDSEditorGUI2/Views/ODIndexRangeView.axaml | 2 +- EDSEditorGUI2/Views/ODIndexRangeView.axaml.cs | 4 +- 13 files changed, 251 insertions(+), 194 deletions(-) create mode 100644 EDSEditorGUI2/Mapper/ProtobufferViewModelMapper.cs create mode 100644 EDSEditorGUI2/ViewModels/DeviceCommissioning.cs create mode 100644 EDSEditorGUI2/ViewModels/FileInfo.cs create mode 100644 EDSEditorGUI2/ViewModels/OdObject.cs create mode 100644 EDSEditorGUI2/ViewModels/OdSubObject.cs diff --git a/EDSEditorGUI2/EDSEditorGUI2.csproj b/EDSEditorGUI2/EDSEditorGUI2.csproj index aa430e63..ab887d37 100644 --- a/EDSEditorGUI2/EDSEditorGUI2.csproj +++ b/EDSEditorGUI2/EDSEditorGUI2.csproj @@ -25,5 +25,6 @@ + diff --git a/EDSEditorGUI2/Mapper/ProtobufferViewModelMapper.cs b/EDSEditorGUI2/Mapper/ProtobufferViewModelMapper.cs new file mode 100644 index 00000000..9fff16ce --- /dev/null +++ b/EDSEditorGUI2/Mapper/ProtobufferViewModelMapper.cs @@ -0,0 +1,51 @@ +using AutoMapper; +using Google.Protobuf.WellKnownTypes; +using LibCanOpen; +using System; + +namespace EDSEditorGUI2.Mapper +{ + public partial class ProtobufferViewModelMapper + { + public static ViewModels.Device MapFromProtobuffer(CanOpenDevice source) + { + var config = new MapperConfiguration(cfg => + { + cfg.CreateMap().ConvertUsing(ts => ts.ToDateTime()); + cfg.CreateMap() + .ForMember(dest => dest.FileVersion, opt => opt.MapFrom(src => src.FileVersion)) + .ForMember(dest => dest.Description, opt => opt.MapFrom(src => src.Description)) + .ForMember(dest => dest.CreationTime, opt => opt.MapFrom(src => src.CreationTime)) + .ForMember(dest => dest.CreatedBy, opt => opt.MapFrom(src => src.CreatedBy)) + .ForMember(dest => dest.ModificationTime, opt => opt.MapFrom(src => src.ModificationTime)) + .ForMember(dest => dest.ModifiedBy, opt => opt.MapFrom(src => src.ModifiedBy)); + cfg.CreateMap() + .ForMember(dest => dest.FileInfo, opt => opt.MapFrom(src => src.FileInfo)) + .ForMember(dest => dest.DeviceInfo, opt => opt.MapFrom(src => src.DeviceInfo)) + .ForMember(dest => dest.DeviceCommissioning, opt => opt.MapFrom(src => src.DeviceCommissioning)) + .ForPath(dest => dest.Objects.Data, opt => opt.MapFrom(src => src.Objects)); + + cfg.CreateMap(); + cfg.CreateMap(); + cfg.CreateMap(); + + }); + config.AssertConfigurationIsValid(); + var mapper = config.CreateMapper(); + var result = mapper.Map(source); + return result; + } + + public static CanOpenDevice MapToProtobuffer(ViewModels.Device source) + { + var config = new MapperConfiguration(cfg => + { + //TODO + }); + config.AssertConfigurationIsValid(); + var mapper = config.CreateMapper(); + var result = mapper.Map(source); + return result; + } + } +} diff --git a/EDSEditorGUI2/ViewModels/Device.cs b/EDSEditorGUI2/ViewModels/Device.cs index c8180b10..82868a44 100644 --- a/EDSEditorGUI2/ViewModels/Device.cs +++ b/EDSEditorGUI2/ViewModels/Device.cs @@ -1,41 +1,24 @@ using CommunityToolkit.Mvvm.ComponentModel; -using LibCanOpen; namespace EDSEditorGUI2.ViewModels { public partial class Device : ObservableObject { - [ObservableProperty] - CanOpenDevice _model; - public Device(CanOpenDevice model) + public Device() { - Model = model; - - _DeviceInfo = new(Model.DeviceInfo); - _DeviceInfo.PropertyChanged += (s, e) => { OnPropertyChanged(nameof(DeviceInfo)); }; - _objects = new(_model.Objects); } - private DeviceInfo _DeviceInfo; - public DeviceInfo DeviceInfo - { - get => _DeviceInfo; - set - { - Model.DeviceInfo = value.Model; - OnPropertyChanged(nameof(DeviceInfo)); - } - } + [ObservableProperty] + private FileInfo _fileInfo = new(); - private DeviceOD _objects; - public DeviceOD Objects - { - get => _objects; - set - { - OnPropertyChanged(nameof(Objects)); - } - } + [ObservableProperty] + private DeviceInfo _deviceInfo = new(); + + [ObservableProperty] + private DeviceCommissioning _deviceCommissioning = new(); + + [ObservableProperty] + private DeviceOD _objects = new(); public void OnClickCommand() { diff --git a/EDSEditorGUI2/ViewModels/DeviceCommissioning.cs b/EDSEditorGUI2/ViewModels/DeviceCommissioning.cs new file mode 100644 index 00000000..aee74b5d --- /dev/null +++ b/EDSEditorGUI2/ViewModels/DeviceCommissioning.cs @@ -0,0 +1,16 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using System; + +namespace EDSEditorGUI2.ViewModels; + +public partial class DeviceCommissioning : ObservableObject +{ + [ObservableProperty] + private UInt32 _nodeId; + + [ObservableProperty] + private string _nodeName = string.Empty; + + [ObservableProperty] + private UInt32 _baudrate; +} diff --git a/EDSEditorGUI2/ViewModels/DeviceInfo.cs b/EDSEditorGUI2/ViewModels/DeviceInfo.cs index 4c571959..3f8497a9 100644 --- a/EDSEditorGUI2/ViewModels/DeviceInfo.cs +++ b/EDSEditorGUI2/ViewModels/DeviceInfo.cs @@ -1,118 +1,45 @@ using CommunityToolkit.Mvvm.ComponentModel; -using LibCanOpen; -namespace EDSEditorGUI2.ViewModels +namespace EDSEditorGUI2.ViewModels; + +public partial class DeviceInfo : ObservableObject { - public partial class DeviceInfo : ObservableObject - { - [ObservableProperty] - private CanOpen_DeviceInfo _model; - public DeviceInfo(CanOpen_DeviceInfo model) { Model = model; } + [ObservableProperty] + string _vendorName; + + [ObservableProperty] + string _productName; + + [ObservableProperty] + bool _baudRate10; + + [ObservableProperty] + bool _baudRate20; + + [ObservableProperty] + bool _baudRate50; + + [ObservableProperty] + bool _baudRate125; + + [ObservableProperty] + bool _baudRate250; + + [ObservableProperty] + bool _baudRate500; + + [ObservableProperty] + bool _baudRate800; + + [ObservableProperty] + bool _baudRate1000; + + [ObservableProperty] + bool _baudRateAuto; + + [ObservableProperty] + bool _lssSlave; - public string VendorName - { - get => Model.VendorName; - set - { - Model.VendorName = value; - OnPropertyChanged(nameof(VendorName)); - } - } - public string ProductName - { - get => Model.ProductName; - set - { - SetProperty(Model.ProductName, value, Model, (u, n) => u.ProductName = n); - } - } - public bool BaudRate10 - { - get => Model.BaudRate10; - set - { - SetProperty(Model.BaudRate10, value, Model, (u, n) => u.BaudRate10 = n); - } - } - public bool BaudRate20 - { - get => Model.BaudRate20; - set - { - SetProperty(Model.BaudRate20, value, Model, (u, n) => u.BaudRate20 = n); - } - } - public bool BaudRate50 - { - get => Model.BaudRate50; - set - { - SetProperty(Model.BaudRate50, value, Model, (u, n) => u.BaudRate50 = n); - } - } - public bool BaudRate125 - { - get => Model.BaudRate125; - set - { - SetProperty(Model.BaudRate125, value, Model, (u, n) => u.BaudRate125 = n); - } - } - public bool BaudRate250 - { - get => Model.BaudRate250; - set - { - SetProperty(Model.BaudRate250, value, Model, (u, n) => u.BaudRate250 = n); - } - } - public bool BaudRate500 - { - get => Model.BaudRate500; - set - { - SetProperty(Model.BaudRate500, value, Model, (u, n) => u.BaudRate500 = n); - } - } - public bool BaudRate800 - { - get => Model.BaudRate800; - set - { - SetProperty(Model.BaudRate800, value, Model, (u, n) => u.BaudRate800 = n); - } - } - public bool BaudRate1000 - { - get => Model.BaudRate1000; - set - { - SetProperty(Model.BaudRate1000, value, Model, (u, n) => u.BaudRate1000 = n); - } - } - public bool BaudRateAuto - { - get => Model.BaudRateAuto; - set - { - SetProperty(Model.BaudRateAuto, value, Model, (u, n) => u.BaudRateAuto = n); - } - } - public bool LssSlave - { - get => Model.LssSlave; - set - { - SetProperty(Model.LssSlave, value, Model, (u, n) => u.LssSlave = n); - } - } - public bool LssMaster - { - get => Model.LssMaster; - set - { - SetProperty(Model.LssMaster, value, Model, (u, n) => u.LssMaster = n); - } - } - } + [ObservableProperty] + bool _lssMaster; } diff --git a/EDSEditorGUI2/ViewModels/DeviceOD.cs b/EDSEditorGUI2/ViewModels/DeviceOD.cs index ad710719..6cedf848 100644 --- a/EDSEditorGUI2/ViewModels/DeviceOD.cs +++ b/EDSEditorGUI2/ViewModels/DeviceOD.cs @@ -1,18 +1,12 @@ using CommunityToolkit.Mvvm.ComponentModel; -using Google.Protobuf.Collections; -using LibCanOpen; using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Linq; namespace EDSEditorGUI2.ViewModels; public partial class DeviceOD : ObservableObject { - [ObservableProperty] - private MapField _model; - [ObservableProperty] - ReadOnlyObservableCollection> _DataTypes; + public ObservableCollection> Data { get; } = []; [ObservableProperty] KeyValuePair _SelectedObject; @@ -20,25 +14,8 @@ public partial class DeviceOD : ObservableObject [ObservableProperty] KeyValuePair _SelectedSubObject; - public DeviceOD(MapField model) - { - Model = model; - _viewModel = new(Model); - _viewModel.CollectionChanged += ViewModel_CollectionChanged; - HackyUpdate(); - } - - private void ViewModel_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + public DeviceOD() { - OnPropertyChanged(nameof(ViewModel)); - HackyUpdate(); - } - - private void HackyUpdate() - { - //Hack should be rewritten - var temp = Model.Where(key => 0x0001 <= IndexStringToInt(key.Key) && IndexStringToInt(key.Key) <= 0x025F); - DataTypes = new(new ObservableCollection>(temp)); } private static int IndexStringToInt(string str) @@ -54,7 +31,7 @@ private static int IndexStringToInt(string str) } } - public void AddIndex(int index, string name, OdObject.Types.ObjectType type) + public void AddIndex(int index, string name, LibCanOpen.OdObject.Types.ObjectType type) { var strIndex = index.ToString("X4"); var newObj = new OdObject @@ -64,51 +41,48 @@ public void AddIndex(int index, string name, OdObject.Types.ObjectType type) }; // create OD entry - if (type == OdObject.Types.ObjectType.Var) + if (type == LibCanOpen.OdObject.Types.ObjectType.Var) { var newSub = new OdSubObject() { Name = name, - Type = OdSubObject.Types.DataType.Unsigned32, - Sdo = OdSubObject.Types.AccessSDO.Rw, - Pdo = OdSubObject.Types.AccessPDO.No, - Srdo = OdSubObject.Types.AccessSRDO.No, + Type = LibCanOpen.OdSubObject.Types.DataType.Unsigned32, + Sdo = LibCanOpen.OdSubObject.Types.AccessSDO.Rw, + Pdo = LibCanOpen.OdSubObject.Types.AccessPDO.No, + Srdo = LibCanOpen.OdSubObject.Types.AccessSRDO.No, DefaultValue = "0" }; - newObj.SubObjects.Add("0", newSub); + newObj.SubObjects.Add(new KeyValuePair("0", newSub)); } else { var CountSub = new OdSubObject() { Name = "Highest sub-index supported", - Type = OdSubObject.Types.DataType.Unsigned8, - Sdo = OdSubObject.Types.AccessSDO.Ro, - Pdo = OdSubObject.Types.AccessPDO.No, - Srdo = OdSubObject.Types.AccessSRDO.No, + Type = LibCanOpen.OdSubObject.Types.DataType.Unsigned8, + Sdo = LibCanOpen.OdSubObject.Types.AccessSDO.Ro, + Pdo = LibCanOpen.OdSubObject.Types.AccessPDO.No, + Srdo = LibCanOpen.OdSubObject.Types.AccessSRDO.No, DefaultValue = "0x01" }; var Sub1 = new OdSubObject() { Name = "Sub Object 1", - Type = OdSubObject.Types.DataType.Unsigned32, - Sdo = OdSubObject.Types.AccessSDO.Rw, - Pdo = OdSubObject.Types.AccessPDO.No, - Srdo = OdSubObject.Types.AccessSRDO.No, + Type = LibCanOpen.OdSubObject.Types.DataType.Unsigned32, + Sdo = LibCanOpen.OdSubObject.Types.AccessSDO.Rw, + Pdo = LibCanOpen.OdSubObject.Types.AccessPDO.No, + Srdo = LibCanOpen.OdSubObject.Types.AccessSRDO.No, DefaultValue = "0" }; - newObj.SubObjects.Add("0", CountSub); - newObj.SubObjects.Add("1", Sub1); - } - ViewModel.Add(new KeyValuePair(strIndex, newObj)); - Model.Add(strIndex, newObj); + newObj.SubObjects.Add(new KeyValuePair("0", CountSub)); + newObj.SubObjects.Add(new KeyValuePair("1", Sub1)); + } + Data.Add(new KeyValuePair(strIndex, newObj)); } public void RemoveIndex(object sender) { } - [ObservableProperty] - ObservableCollection> _viewModel; } diff --git a/EDSEditorGUI2/ViewModels/FileInfo.cs b/EDSEditorGUI2/ViewModels/FileInfo.cs new file mode 100644 index 00000000..7d2f5596 --- /dev/null +++ b/EDSEditorGUI2/ViewModels/FileInfo.cs @@ -0,0 +1,25 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using System; + +namespace EDSEditorGUI2.ViewModels; + +public partial class FileInfo : ObservableObject +{ + [ObservableProperty] + private string _fileVersion = string.Empty; + + [ObservableProperty] + private string _description = string.Empty; + + [ObservableProperty] + private DateTime _creationTime; + + [ObservableProperty] + private string _createdBy = string.Empty; + + [ObservableProperty] + private DateTime _modificationTime; + + [ObservableProperty] + private string _modifiedBy = string.Empty; +} diff --git a/EDSEditorGUI2/ViewModels/MainWindowViewModel.cs b/EDSEditorGUI2/ViewModels/MainWindowViewModel.cs index d1630760..9d0dfdf9 100644 --- a/EDSEditorGUI2/ViewModels/MainWindowViewModel.cs +++ b/EDSEditorGUI2/ViewModels/MainWindowViewModel.cs @@ -1,4 +1,5 @@ using CommunityToolkit.Mvvm.ComponentModel; +using EDSEditorGUI2.Mapper; using System.Collections.ObjectModel; namespace EDSEditorGUI2.ViewModels; @@ -29,7 +30,8 @@ public void AddNewDevice(object sender) //device.Dock = DockStyle.Fill; //device.dispatch_updateOD(); - Network.Add(new Device(device)); + var DeviceView = ProtobufferViewModelMapper.MapFromProtobuffer(device); + Network.Add(DeviceView); } #pragma warning disable CA1822 // Mark members as static public string Greeting => "Welcome to Avalonia!"; diff --git a/EDSEditorGUI2/ViewModels/OdObject.cs b/EDSEditorGUI2/ViewModels/OdObject.cs new file mode 100644 index 00000000..515ee555 --- /dev/null +++ b/EDSEditorGUI2/ViewModels/OdObject.cs @@ -0,0 +1,37 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace EDSEditorGUI2.ViewModels +{ + public partial class OdObject : ObservableObject + { + [ObservableProperty] + private bool _disabled; + + [ObservableProperty] + string _name = string.Empty; + + [ObservableProperty] + string _alias = string.Empty; + + [ObservableProperty] + string _description = string.Empty; + + [ObservableProperty] + LibCanOpen.OdObject.Types.ObjectType _type; + + [ObservableProperty] + string _countLabel = string.Empty; + + [ObservableProperty] + string _storageGroup = string.Empty; + + [ObservableProperty] + bool flagsPDO; + + [ObservableProperty] + ObservableCollection> _subObjects = []; + + } +} diff --git a/EDSEditorGUI2/ViewModels/OdSubObject.cs b/EDSEditorGUI2/ViewModels/OdSubObject.cs new file mode 100644 index 00000000..99d0b946 --- /dev/null +++ b/EDSEditorGUI2/ViewModels/OdSubObject.cs @@ -0,0 +1,40 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using static LibCanOpen.OdSubObject.Types; + +namespace EDSEditorGUI2.ViewModels; + +public partial class OdSubObject : ObservableObject +{ + [ObservableProperty] + private string _name = string.Empty; + + [ObservableProperty] + private string _alias = string.Empty; + + [ObservableProperty] + private DataType _type; + + [ObservableProperty] + private AccessSDO _sdo; + + [ObservableProperty] + private AccessPDO _pdo; + + [ObservableProperty] + private AccessSRDO _srdo; + + [ObservableProperty] + private string _defaultValue = string.Empty; + + [ObservableProperty] + private string _actualValue = string.Empty; + + [ObservableProperty] + private string _lowLimit = string.Empty; + + [ObservableProperty] + string _highLimit = string.Empty; + + [ObservableProperty] + private uint _stringLengthMin; +} diff --git a/EDSEditorGUI2/Views/DeviceODView.axaml.cs b/EDSEditorGUI2/Views/DeviceODView.axaml.cs index fea3b88c..872be7ad 100644 --- a/EDSEditorGUI2/Views/DeviceODView.axaml.cs +++ b/EDSEditorGUI2/Views/DeviceODView.axaml.cs @@ -1,4 +1,5 @@ using Avalonia.Controls; +using Avalonia.Interactivity; using LibCanOpen; using System; using System.Collections.Generic; @@ -46,7 +47,7 @@ private void IndexGridSelectionChanged(object? sender, SelectionChangedEventArgs { if (sender is DataGrid s && DataContext is ViewModels.DeviceOD dc) { - if (s.SelectedItem is KeyValuePair selected) + if (s.SelectedItem is KeyValuePair selected) { dc.SelectedObject = selected; foreach (var dg in _odViews) @@ -64,7 +65,7 @@ private void subindexGridSelectionChanged(object? sender, SelectionChangedEventA { if (sender is DataGrid s && DataContext is ViewModels.DeviceOD dc) { - if (s.SelectedItem is KeyValuePair selected) + if (s.SelectedItem is KeyValuePair selected) { dc.SelectedSubObject = selected; } diff --git a/EDSEditorGUI2/Views/ODIndexRangeView.axaml b/EDSEditorGUI2/Views/ODIndexRangeView.axaml index 2740ab44..08b0d548 100644 --- a/EDSEditorGUI2/Views/ODIndexRangeView.axaml +++ b/EDSEditorGUI2/Views/ODIndexRangeView.axaml @@ -12,7 +12,7 @@ - /// sender object /// event param - private void GridLoadingRow(object sender, DataGridRowEventArgs e) + private void GridLoadingRow(object? sender, DataGridRowEventArgs e) { - var dc = (System.Collections.Generic.KeyValuePair)e.Row.DataContext; + var dc = (System.Collections.Generic.KeyValuePair)e.Row.DataContext; int index = int.Parse(dc.Key, System.Globalization.NumberStyles.HexNumber); int min = Convert.ToInt32(MinIndex, 16); int max = Convert.ToInt32(MaxIndex, 16); From f59ac0349c685a0adedec4dc268c7f3af08cd527 Mon Sep 17 00:00:00 2001 From: Lars Elgtvedt Susaas Date: Mon, 13 Jan 2025 21:20:39 +0100 Subject: [PATCH 15/19] add missing string init --- EDSEditorGUI2/ViewModels/DeviceInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EDSEditorGUI2/ViewModels/DeviceInfo.cs b/EDSEditorGUI2/ViewModels/DeviceInfo.cs index 3f8497a9..c9111e83 100644 --- a/EDSEditorGUI2/ViewModels/DeviceInfo.cs +++ b/EDSEditorGUI2/ViewModels/DeviceInfo.cs @@ -5,10 +5,10 @@ namespace EDSEditorGUI2.ViewModels; public partial class DeviceInfo : ObservableObject { [ObservableProperty] - string _vendorName; + string _vendorName = string.Empty; [ObservableProperty] - string _productName; + string _productName = string.Empty; [ObservableProperty] bool _baudRate10; From a3ed98198f468df6aef57fe104cdd014d3145e7a Mon Sep 17 00:00:00 2001 From: Lars Elgtvedt Susaas Date: Mon, 13 Jan 2025 21:54:10 +0100 Subject: [PATCH 16/19] added subindex context menu (not enable yet) and gui tests --- EDSEditor.sln | 6 ++ EDSEditorGUI2/Extensions.cs | 15 +++ EDSEditorGUI2/ViewModels/DeviceOD.cs | 13 ++- EDSEditorGUI2/ViewModels/OdObject.cs | 121 ++++++++++++++++++++- EDSEditorGUI2/Views/DeviceODView.axaml | 19 ++++ EDSEditorGUI2/Views/DeviceODView.axaml.cs | 39 +++++++ GUITests/.gitignore | 3 + GUITests/GUITests.csproj | 25 +++++ GUITests/ViewModels_OdObjects.cs | 124 ++++++++++++++++++++++ 9 files changed, 359 insertions(+), 6 deletions(-) create mode 100644 EDSEditorGUI2/Extensions.cs create mode 100644 GUITests/.gitignore create mode 100644 GUITests/GUITests.csproj create mode 100644 GUITests/ViewModels_OdObjects.cs diff --git a/EDSEditor.sln b/EDSEditor.sln index 5d1ea4ae..00abe8c3 100644 --- a/EDSEditor.sln +++ b/EDSEditor.sln @@ -16,6 +16,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EDSSharp", "EDSSharp\EDSSha EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EDSEditorGUI2", "EDSEditorGUI2\EDSEditorGUI2.csproj", "{F175A47B-8BB8-480F-8D31-AF802086B8B4}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GUITests", "GUITests\GUITests.csproj", "{9B9B5461-1864-484D-A49D-D39422DA16E0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -42,6 +44,10 @@ Global {F175A47B-8BB8-480F-8D31-AF802086B8B4}.Debug|Any CPU.Build.0 = Debug|Any CPU {F175A47B-8BB8-480F-8D31-AF802086B8B4}.Release|Any CPU.ActiveCfg = Release|Any CPU {F175A47B-8BB8-480F-8D31-AF802086B8B4}.Release|Any CPU.Build.0 = Release|Any CPU + {9B9B5461-1864-484D-A49D-D39422DA16E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9B9B5461-1864-484D-A49D-D39422DA16E0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9B9B5461-1864-484D-A49D-D39422DA16E0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9B9B5461-1864-484D-A49D-D39422DA16E0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/EDSEditorGUI2/Extensions.cs b/EDSEditorGUI2/Extensions.cs new file mode 100644 index 00000000..2ccc548a --- /dev/null +++ b/EDSEditorGUI2/Extensions.cs @@ -0,0 +1,15 @@ +using System; + +namespace EDSEditorGUI2 +{ + public static class StringExtensions + { + /// + /// Convert different types of hex/dec string to integer + /// + public static UInt16 ToInteger(this String val) + { + return (UInt16)Convert.ToInt32(val, 16); + } + } +} diff --git a/EDSEditorGUI2/ViewModels/DeviceOD.cs b/EDSEditorGUI2/ViewModels/DeviceOD.cs index 6cedf848..03b2128b 100644 --- a/EDSEditorGUI2/ViewModels/DeviceOD.cs +++ b/EDSEditorGUI2/ViewModels/DeviceOD.cs @@ -9,10 +9,13 @@ public partial class DeviceOD : ObservableObject public ObservableCollection> Data { get; } = []; [ObservableProperty] - KeyValuePair _SelectedObject; + KeyValuePair _selectedObject; [ObservableProperty] - KeyValuePair _SelectedSubObject; + KeyValuePair _selectedSubObject; + + [ObservableProperty] + ObservableCollection> _selectedSubObjects = []; public DeviceOD() { @@ -52,7 +55,7 @@ public void AddIndex(int index, string name, LibCanOpen.OdObject.Types.ObjectTyp Srdo = LibCanOpen.OdSubObject.Types.AccessSRDO.No, DefaultValue = "0" }; - newObj.SubObjects.Add(new KeyValuePair("0", newSub)); + newObj.SubObjects.Add(new KeyValuePair("0x0", newSub)); } else { @@ -75,8 +78,8 @@ public void AddIndex(int index, string name, LibCanOpen.OdObject.Types.ObjectTyp DefaultValue = "0" }; - newObj.SubObjects.Add(new KeyValuePair("0", CountSub)); - newObj.SubObjects.Add(new KeyValuePair("1", Sub1)); + newObj.SubObjects.Add(new KeyValuePair("0x0", CountSub)); + newObj.SubObjects.Add(new KeyValuePair("0x1", Sub1)); } Data.Add(new KeyValuePair(strIndex, newObj)); } diff --git a/EDSEditorGUI2/ViewModels/OdObject.cs b/EDSEditorGUI2/ViewModels/OdObject.cs index 515ee555..fc07b952 100644 --- a/EDSEditorGUI2/ViewModels/OdObject.cs +++ b/EDSEditorGUI2/ViewModels/OdObject.cs @@ -1,6 +1,9 @@ -using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.ComponentModel; +using libEDSsharp; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Linq; namespace EDSEditorGUI2.ViewModels { @@ -33,5 +36,121 @@ public partial class OdObject : ObservableObject [ObservableProperty] ObservableCollection> _subObjects = []; + /// + /// very based on ODentry.AddSubEntry + /// + public OdSubObject? AddSubEntry(KeyValuePair selected) + { + if (Type == LibCanOpen.OdObject.Types.ObjectType.Var) + return null; + + OdSubObject newOd; + + //Do we need the type check?? + if ((SubObjects.Count == 0) && ((Type == LibCanOpen.OdObject.Types.ObjectType.Array) || (Type == LibCanOpen.OdObject.Types.ObjectType.Record))) + { + SubObjects.Add(new KeyValuePair("0", new OdSubObject + { + Name = "Highest sub-index supported", + Type = LibCanOpen.OdSubObject.Types.DataType.Unsigned8, + Sdo = LibCanOpen.OdSubObject.Types.AccessSDO.Ro, + Pdo = LibCanOpen.OdSubObject.Types.AccessPDO.No, + Srdo = LibCanOpen.OdSubObject.Types.AccessSRDO.No, + DefaultValue = "0x01" + })); + } + + var lastSubOd = SubObjects.Last(); + OdObject? originalOd = null; + UInt16 maxSubIndex = 1; + UInt16 lastSubIndex = 1; + + // create new or clone existing sub od + if (lastSubOd.Value == null || lastSubOd.Key.ToInteger() < 1) + { + newOd = new OdSubObject + { + Name = "item", + Type = LibCanOpen.OdSubObject.Types.DataType.Unsigned32 + }; + } + else + { + newOd = new OdSubObject + { + //TODO: make a clone function with reflection to keep it up-to-date + Name = selected.Value.Name, + Alias = selected.Value.Alias, + Type = selected.Value.Type, + Sdo = selected.Value.Sdo, + Pdo = selected.Value.Pdo, + Srdo = selected.Value.Srdo, + DefaultValue = selected.Value.DefaultValue, + ActualValue = selected.Value.ActualValue, + LowLimit = selected.Value.LowLimit, + HighLimit = selected.Value.HighLimit, + StringLengthMin = selected.Value.StringLengthMin, + }; + } + + // insert new sub od + ObservableCollection> newSubObjects = []; + UInt16 newSubIndex = 0; + foreach (var sub in SubObjects) + { + var subOd = sub.Value; + if (sub.Key.ToInteger() > newSubIndex) + newSubIndex = sub.Key.ToInteger(); + + newSubObjects.Add(new KeyValuePair((newSubIndex++).ToHexString(), subOd)); + + if (selected.Value == subOd) + newSubObjects.Add(new KeyValuePair((newSubIndex++).ToHexString(), newOd)); + } + + SubObjects = newSubObjects; + + // Write maxSubIndex to first sub index + if (maxSubIndex > 0 && maxSubIndex == lastSubIndex && SubObjects.Count > 0) + { + SubObjects[0].Value.DefaultValue = string.Format("0x{0:X2}", newSubIndex - 1); + } + + return newOd; + } + /// + /// Remove current sub entry + /// + /// Keyvalue pair of the subindex to be removed + /// Renumber subentries + /// true on successfull removal + public bool RemoveSubEntry(KeyValuePair subObjectToRemove, bool renumber) + { + if (Type == LibCanOpen.OdObject.Types.ObjectType.Array || Type == LibCanOpen.OdObject.Types.ObjectType.Record) + { + UInt16 maxSubIndex = SubObjects[0].Value.DefaultValue.ToInteger(); + UInt16 lastSubIndex = SubObjects.Last().Key.ToInteger(); + + SubObjects.Remove(subObjectToRemove); + + if (renumber) + { + ObservableCollection> newSubObjects = []; + UInt16 subIndex = 0; + foreach (var subOd in SubObjects) + newSubObjects.Add(new KeyValuePair((subIndex++).ToHexString(), subOd.Value)); + + SubObjects = newSubObjects; + } + + // Write maxSubIndex to first sub index + if (maxSubIndex > 0 && maxSubIndex == lastSubIndex && SubObjects.Count > 0) + { + SubObjects[0].Value.DefaultValue = string.Format("0x{0:X2}", SubObjects.Last().Key.ToInteger()); + } + return true; + } + return false; + } } } diff --git a/EDSEditorGUI2/Views/DeviceODView.axaml b/EDSEditorGUI2/Views/DeviceODView.axaml index d1cdd082..0b5453f1 100644 --- a/EDSEditorGUI2/Views/DeviceODView.axaml +++ b/EDSEditorGUI2/Views/DeviceODView.axaml @@ -47,6 +47,25 @@ + + + + + + + + + + + + + + + + + + + diff --git a/EDSEditorGUI2/Views/DeviceODView.axaml.cs b/EDSEditorGUI2/Views/DeviceODView.axaml.cs index 872be7ad..74cc6e93 100644 --- a/EDSEditorGUI2/Views/DeviceODView.axaml.cs +++ b/EDSEditorGUI2/Views/DeviceODView.axaml.cs @@ -3,6 +3,8 @@ using LibCanOpen; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; namespace EDSEditorGUI2.Views; @@ -68,6 +70,43 @@ private void subindexGridSelectionChanged(object? sender, SelectionChangedEventA if (s.SelectedItem is KeyValuePair selected) { dc.SelectedSubObject = selected; + dc.SelectedSubObjects.Clear(); + foreach (var row in s.SelectedItems) + { + if (row is KeyValuePair subObj) + { + dc.SelectedSubObjects.Add(subObj); + } + } + } + } + } + private void ContextMenuSubObjectAddClick(object? sender, RoutedEventArgs e) + { + if (DataContext is ViewModels.DeviceOD dc) + { + var selectedObj = dc.SelectedObject.Value; + ObservableCollection> selection = []; + + foreach (var row in dc.SelectedSubObjects) + { + selectedObj.AddSubEntry(row); + } + } + } + private void ContextMenuSubObjectRemoveClick(object? sender, RoutedEventArgs e) + { + bool renumber = sender == contextMenu_subObject_removeSubItemToolStripMenuItem; + + if (DataContext is ViewModels.DeviceOD dc) + { + var selectedObject = dc.SelectedObject.Value; + + //Clone the list because we cant modify the list we iterate on + var selectedObj = dc.SelectedSubObjects.ToList(); + foreach (var item in selectedObj) + { + selectedObject.RemoveSubEntry(item, renumber); } } } diff --git a/GUITests/.gitignore b/GUITests/.gitignore new file mode 100644 index 00000000..aaddf9f7 --- /dev/null +++ b/GUITests/.gitignore @@ -0,0 +1,3 @@ +/obj +/bin + diff --git a/GUITests/GUITests.csproj b/GUITests/GUITests.csproj new file mode 100644 index 00000000..499faeae --- /dev/null +++ b/GUITests/GUITests.csproj @@ -0,0 +1,25 @@ + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + diff --git a/GUITests/ViewModels_OdObjects.cs b/GUITests/ViewModels_OdObjects.cs new file mode 100644 index 00000000..25f779ff --- /dev/null +++ b/GUITests/ViewModels_OdObjects.cs @@ -0,0 +1,124 @@ +using EDSEditorGUI2.ViewModels; + +namespace GUITests +{ + public class ViewModels_OdObjects + { + private OdObject sut; + public ViewModels_OdObjects() + { + sut = new OdObject(); + sut.SubObjects.Add(new KeyValuePair("0", new OdSubObject + { + Name = "Highest sub-index supported", + Type = LibCanOpen.OdSubObject.Types.DataType.Unsigned8, + Sdo = LibCanOpen.OdSubObject.Types.AccessSDO.Ro, + Pdo = LibCanOpen.OdSubObject.Types.AccessPDO.No, + Srdo = LibCanOpen.OdSubObject.Types.AccessSRDO.No, + DefaultValue = "0x01" + })); + sut.SubObjects.Add(new KeyValuePair("1", new OdSubObject() + { + Name = "Sub Object 1", + Type = LibCanOpen.OdSubObject.Types.DataType.Unsigned32, + Sdo = LibCanOpen.OdSubObject.Types.AccessSDO.Rw, + Pdo = LibCanOpen.OdSubObject.Types.AccessPDO.No, + Srdo = LibCanOpen.OdSubObject.Types.AccessSRDO.No, + DefaultValue = "0" + })); + sut.SubObjects.Add(new KeyValuePair("2", new OdSubObject() + { + Name = "Sub Object 2", + Type = LibCanOpen.OdSubObject.Types.DataType.Unsigned32, + Sdo = LibCanOpen.OdSubObject.Types.AccessSDO.Rw, + Pdo = LibCanOpen.OdSubObject.Types.AccessPDO.No, + Srdo = LibCanOpen.OdSubObject.Types.AccessSRDO.No, + DefaultValue = "0" + })); + } + + [Fact] + public void AddSubEntry_VarType() + { + sut = new OdObject(); + sut.Type = LibCanOpen.OdObject.Types.ObjectType.Var; + sut.SubObjects.Add(new KeyValuePair("0", new OdSubObject + { + Name = "variableTest", + Type = LibCanOpen.OdSubObject.Types.DataType.Unsigned32, + Sdo = LibCanOpen.OdSubObject.Types.AccessSDO.Rw, + Pdo = LibCanOpen.OdSubObject.Types.AccessPDO.No, + Srdo = LibCanOpen.OdSubObject.Types.AccessSRDO.No, + DefaultValue = "0" + })); + sut.AddSubEntry(sut.SubObjects[0]); + Assert.Single(sut.SubObjects); + } + + [Fact] + public void AddSubEntry_RecordType() + { + sut.Type = LibCanOpen.OdObject.Types.ObjectType.Record; + sut.AddSubEntry(sut.SubObjects[1]); + Assert.Equal(4, sut.SubObjects.Count); + Assert.Equal("0x03", sut.SubObjects[0].Value.DefaultValue); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void RemoveSubEntry_VarType(bool renumber) + { + sut = new OdObject(); + sut.Type = LibCanOpen.OdObject.Types.ObjectType.Var; + sut.SubObjects.Add(new KeyValuePair("0x01", new OdSubObject + { + Name = "variableTest", + Type = LibCanOpen.OdSubObject.Types.DataType.Unsigned32, + Sdo = LibCanOpen.OdSubObject.Types.AccessSDO.Rw, + Pdo = LibCanOpen.OdSubObject.Types.AccessPDO.No, + Srdo = LibCanOpen.OdSubObject.Types.AccessSRDO.No, + DefaultValue = "0" + })); + + var result = sut.RemoveSubEntry(sut.SubObjects[0], renumber); + Assert.False(result); + } + [Fact] + public void RemoveSubEntry_RecordType() + { + sut = new OdObject(); + sut.Type = LibCanOpen.OdObject.Types.ObjectType.Record; + sut.SubObjects.Add(new KeyValuePair("0x00", new OdSubObject + { + Name = "variableTest0", + Type = LibCanOpen.OdSubObject.Types.DataType.Unsigned32, + Sdo = LibCanOpen.OdSubObject.Types.AccessSDO.Rw, + Pdo = LibCanOpen.OdSubObject.Types.AccessPDO.No, + Srdo = LibCanOpen.OdSubObject.Types.AccessSRDO.No, + DefaultValue = "0x01" + })); + sut.SubObjects.Add(new KeyValuePair("0x01", new OdSubObject + { + Name = "variableTest1", + Type = LibCanOpen.OdSubObject.Types.DataType.Unsigned32, + Sdo = LibCanOpen.OdSubObject.Types.AccessSDO.Rw, + Pdo = LibCanOpen.OdSubObject.Types.AccessPDO.No, + Srdo = LibCanOpen.OdSubObject.Types.AccessSRDO.No, + DefaultValue = "0" + })); + sut.SubObjects.Add(new KeyValuePair("0x02", new OdSubObject + { + Name = "variableTest2", + Type = LibCanOpen.OdSubObject.Types.DataType.Unsigned32, + Sdo = LibCanOpen.OdSubObject.Types.AccessSDO.Rw, + Pdo = LibCanOpen.OdSubObject.Types.AccessPDO.No, + Srdo = LibCanOpen.OdSubObject.Types.AccessSRDO.No, + DefaultValue = "0" + })); + + var result = sut.RemoveSubEntry(sut.SubObjects[1], false); + Assert.False(result); + } + } +} \ No newline at end of file From 312c2c6a16d1562a95d7f62ea2f27fb637d4757b Mon Sep 17 00:00:00 2001 From: Lars Elgtvedt Susaas Date: Sat, 1 Feb 2025 10:55:48 +0100 Subject: [PATCH 17/19] very basic pdo mapping gui with no data integration --- EDSEditorGUI2/Icons.axaml | 9 ++- EDSEditorGUI2/Views/DevicePDOView.axaml | 78 +++++++++++++++++++++ EDSEditorGUI2/Views/DevicePDOView.axaml.cs | 81 ++++++++++++++++++++++ EDSEditorGUI2/Views/DeviceView.axaml | 4 +- 4 files changed, 168 insertions(+), 4 deletions(-) create mode 100644 EDSEditorGUI2/Views/DevicePDOView.axaml create mode 100644 EDSEditorGUI2/Views/DevicePDOView.axaml.cs diff --git a/EDSEditorGUI2/Icons.axaml b/EDSEditorGUI2/Icons.axaml index 228087ff..ef341445 100644 --- a/EDSEditorGUI2/Icons.axaml +++ b/EDSEditorGUI2/Icons.axaml @@ -1,8 +1,12 @@ - - + + + + + + @@ -11,6 +15,7 @@ M3.7547787,12.4995322 L20.2466903,12.4995322 C20.6609039,12.4995322 20.9966903,12.1637458 20.9966903,11.7495322 C20.9966903,11.3353187 20.6609039,10.9995322 20.2466903,10.9995322 L3.7547787,10.9995322 C3.34056514,10.9995322 3.0047787,11.3353187 3.0047787,11.7495322 C3.0047787,12.1637458 3.34056514,12.4995322 3.7547787,12.4995322 Z M14.5,13 L14.5,3.75378577 C14.5,3.33978577 14.164,3.00378577 13.75,3.00378577 C13.336,3.00378577 13,3.33978577 13,3.75378577 L13,13 L3.75387573,13 C3.33987573,13 3.00387573,13.336 3.00387573,13.75 C3.00387573,14.164 3.33987573,14.5 3.75387573,14.5 L13,14.5 L13,23.7523651 C13,24.1663651 13.336,24.5023651 13.75,24.5023651 C14.164,24.5023651 14.5,24.1663651 14.5,23.7523651 L14.5,14.5 L23.7498262,14.5030754 C24.1638262,14.5030754 24.4998262,14.1670754 24.4998262,13.7530754 C24.4998262,13.3390754 24.1638262,13.0030754 23.7498262,13.0030754 L14.5,13 Z + M3 5.75C3 4.23122 4.23122 3 5.75 3H15.7145C16.5764 3 17.4031 3.34241 18.0126 3.9519L20.0481 5.98744C20.6576 6.59693 21 7.42358 21 8.28553V18.25C21 19.7688 19.7688 21 18.25 21H5.75C4.23122 21 3 19.7688 3 18.25V5.75ZM5.75 4.5C5.05964 4.5 4.5 5.05964 4.5 5.75V18.25C4.5 18.9404 5.05964 19.5 5.75 19.5H6V14.25C6 13.0074 7.00736 12 8.25 12H15.75C16.9926 12 18 13.0074 18 14.25V19.5H18.25C18.9404 19.5 19.5 18.9404 19.5 18.25V8.28553C19.5 7.8214 19.3156 7.37629 18.9874 7.0481L16.9519 5.01256C16.6918 4.75246 16.3582 4.58269 16 4.52344V7.25C16 8.49264 14.9926 9.5 13.75 9.5H9.25C8.00736 9.5 7 8.49264 7 7.25V4.5H5.75ZM16.5 19.5V14.25C16.5 13.8358 16.1642 13.5 15.75 13.5H8.25C7.83579 13.5 7.5 13.8358 7.5 14.25V19.5H16.5ZM8.5 4.5V7.25C8.5 7.66421 8.83579 8 9.25 8H13.75C14.1642 8 14.5 7.66421 14.5 7.25V4.5H8.5Z diff --git a/EDSEditorGUI2/Views/DevicePDOView.axaml b/EDSEditorGUI2/Views/DevicePDOView.axaml new file mode 100644 index 00000000..0fe71fc2 --- /dev/null +++ b/EDSEditorGUI2/Views/DevicePDOView.axaml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Invalid + + + + + + + + Zoom: + + + diff --git a/EDSEditorGUI2/Views/DevicePDOView.axaml.cs b/EDSEditorGUI2/Views/DevicePDOView.axaml.cs new file mode 100644 index 00000000..b7917baf --- /dev/null +++ b/EDSEditorGUI2/Views/DevicePDOView.axaml.cs @@ -0,0 +1,81 @@ +using Avalonia.Controls; +using Avalonia.Media; +using System.Collections.Generic; + +namespace EDSEditorGUI2.Views; + +public partial class DevicePDOView : UserControl +{ + private List _bitColumns = []; + public DevicePDOView() + { + InitializeComponent(); + + CreateMappingBitsAndBytesIndication(); + Zoom.Value = 100; + } + + void CreateMappingBitsAndBytesIndication() + { + //Bits + for (int i = 0; i < 64; i++) + { + var indication = new TextBlock + { + Text = i.ToString(), + VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center, + TextWrapping = TextWrapping.Wrap, + }; + if ((i % 8) == 0) + { + indication.Foreground = Brushes.Red; + } + AddToMappingGrid(indication, 0, 3 + i); + + var newColumn = new ColumnDefinition(new GridLength(10 * 1.0)); + _bitColumns.Add(newColumn); + + MappingGrid.ColumnDefinitions.Add(newColumn); + } + //Bytes + for (int i = 0; i < 8; i++) + { + var indication = new TextBlock + { + Text = $"Byte {i}", + HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center, + + TextWrapping = TextWrapping.Wrap, + }; + AddToMappingGrid(indication, 1, 3 + (i*8), 8); + } + } + void AddToMappingGrid(Control element, int row,int column, int columnspam = 1) + { + Grid.SetRow(element, row); + Grid.SetColumn(element, column); + Grid.SetColumnSpan(element, columnspam); + MappingGrid.Children.Add(element); + } + + private void Zoom_PropertyChanged(object? sender, Avalonia.AvaloniaPropertyChangedEventArgs e) + { + if (e.Property.Name == "Value") + { + decimal newValue = Zoom.Value ?? 0; + ChangeMappingZoom((double)newValue); + } + } + /// + /// Changes the zoom level on pdo mapping + /// + /// zoom level in percent + private void ChangeMappingZoom(double zoomPercent) + { + var zoom = zoomPercent / 100; + foreach (var column in _bitColumns) + { + column.Width = new GridLength(10 * zoom); + } + } +} diff --git a/EDSEditorGUI2/Views/DeviceView.axaml b/EDSEditorGUI2/Views/DeviceView.axaml index b7032913..707f1da9 100644 --- a/EDSEditorGUI2/Views/DeviceView.axaml +++ b/EDSEditorGUI2/Views/DeviceView.axaml @@ -16,10 +16,10 @@ - This is tab 3 content + - This is tab 3 content + This is tab 3 content From 0716cb02b0b37672e54374a2063b919529c3ad2a Mon Sep 17 00:00:00 2001 From: Lars Elgtvedt Susaas Date: Sat, 1 Feb 2025 11:19:32 +0100 Subject: [PATCH 18/19] disable entries in the file menu when no device is selected --- EDSEditorGUI2/Views/MainWindow.axaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/EDSEditorGUI2/Views/MainWindow.axaml b/EDSEditorGUI2/Views/MainWindow.axaml index 3a0e4a89..d2469cd4 100644 --- a/EDSEditorGUI2/Views/MainWindow.axaml +++ b/EDSEditorGUI2/Views/MainWindow.axaml @@ -21,17 +21,17 @@ - - + + - + - - - + + + From 354b8d74a5613733dc8822e96f4a0c28ee0d51a9 Mon Sep 17 00:00:00 2001 From: Lars Elgtvedt Susaas Date: Sat, 1 Feb 2025 15:15:23 +0100 Subject: [PATCH 19/19] cleanup & fixing warnings --- EDSEditorGUI2/ViewModels/OdObject.cs | 1 - EDSEditorGUI2/Views/ODIndexRangeView.axaml.cs | 13 ++++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/EDSEditorGUI2/ViewModels/OdObject.cs b/EDSEditorGUI2/ViewModels/OdObject.cs index fc07b952..02e90e64 100644 --- a/EDSEditorGUI2/ViewModels/OdObject.cs +++ b/EDSEditorGUI2/ViewModels/OdObject.cs @@ -61,7 +61,6 @@ public partial class OdObject : ObservableObject } var lastSubOd = SubObjects.Last(); - OdObject? originalOd = null; UInt16 maxSubIndex = 1; UInt16 lastSubIndex = 1; diff --git a/EDSEditorGUI2/Views/ODIndexRangeView.axaml.cs b/EDSEditorGUI2/Views/ODIndexRangeView.axaml.cs index 69df9a00..f395a39a 100644 --- a/EDSEditorGUI2/Views/ODIndexRangeView.axaml.cs +++ b/EDSEditorGUI2/Views/ODIndexRangeView.axaml.cs @@ -27,11 +27,14 @@ public ODIndexRangeView() /// event param private void GridLoadingRow(object? sender, DataGridRowEventArgs e) { - var dc = (System.Collections.Generic.KeyValuePair)e.Row.DataContext; - int index = int.Parse(dc.Key, System.Globalization.NumberStyles.HexNumber); - int min = Convert.ToInt32(MinIndex, 16); - int max = Convert.ToInt32(MaxIndex, 16); - e.Row.IsVisible = (min <= index && index <= max); + if(e.Row.DataContext != null) + { + var dc = (System.Collections.Generic.KeyValuePair)e.Row.DataContext; + int index = int.Parse(dc.Key, System.Globalization.NumberStyles.HexNumber); + int min = Convert.ToInt32(MinIndex, 16); + int max = Convert.ToInt32(MaxIndex, 16); + e.Row.IsVisible = (min <= index && index <= max); + } } public static readonly StyledProperty HeadingProperty =