diff --git a/EDSEditorGUI2/Converter/BrushConverter.cs b/EDSEditorGUI2/Converter/BrushConverter.cs
new file mode 100644
index 00000000..c9edecbf
--- /dev/null
+++ b/EDSEditorGUI2/Converter/BrushConverter.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Globalization;
+using Avalonia;
+using Avalonia.Data.Converters;
+using Avalonia.Media;
+
+namespace EDSEditorGUI2.Converter;
+
+public sealed class BrushConverter : IValueConverter
+{
+ public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ // TODO: Get theme and use the correct brush based on that
+ if (value is Boolean bValue)
+ {
+ if (bValue)
+ {
+ return new SolidColorBrush(Colors.Red);
+ }
+ else
+ {
+ var currentThemeVariant = Application.Current?.ActualThemeVariant;
+
+ // TODO: figure out how to get the default forground from theme instead of hardcoding it
+ if (currentThemeVariant == Avalonia.Styling.ThemeVariant.Dark)
+ {
+ return new SolidColorBrush(Colors.White);
+ }
+ else
+ {
+ return new SolidColorBrush(Colors.Black);
+ }
+ }
+ }
+ return new SolidColorBrush(Colors.Orange);
+ }
+
+ public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+}
diff --git a/EDSEditorGUI2/EDSEditorGUI2.csproj b/EDSEditorGUI2/EDSEditorGUI2.csproj
index ab887d37..6ad75646 100644
--- a/EDSEditorGUI2/EDSEditorGUI2.csproj
+++ b/EDSEditorGUI2/EDSEditorGUI2.csproj
@@ -14,6 +14,17 @@
+
+
+
+
+
+
+
+
diff --git a/EDSEditorGUI2/Icons.axaml b/EDSEditorGUI2/Icons.axaml
index ef341445..5583834c 100644
--- a/EDSEditorGUI2/Icons.axaml
+++ b/EDSEditorGUI2/Icons.axaml
@@ -6,6 +6,7 @@
+
@@ -16,6 +17,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
+ M3 4.5C3 3.11929 4.11929 2 5.5 2H17C18.3807 2 19.5 3.11929 19.5 4.5V11.3135C19.0218 11.159 18.5195 11.0585 18 11.0189V4.5C18 3.94772 17.5523 3.5 17 3.5H5.5C4.94772 3.5 4.5 3.94772 4.5 4.5V18H11.0189C11.0585 18.5195 11.159 19.0218 11.3135 19.5H4.5C4.5 20.0523 4.94772 20.5 5.5 20.5H11.7322C12.0194 21.051 12.3832 21.5557 12.8096 22H5.5C4.11929 22 3 20.8807 3 19.5V4.5Z M17.5 12C20.5376 12 23 14.4624 23 17.5C23 20.5376 20.5376 23 17.5 23C14.4624 23 12 20.5376 12 17.5C12 14.4624 14.4624 12 17.5 12ZM18.0011 20.5035L18.0006 18H20.503C20.7792 18 21.003 17.7762 21.003 17.5C21.003 17.2239 20.7792 17 20.503 17H18.0005L18 14.4993C18 14.2231 17.7761 13.9993 17.5 13.9993C17.2239 13.9993 17 14.2231 17 14.4993L17.0005 17H14.4961C14.22 17 13.9961 17.2239 13.9961 17.5C13.9961 17.7762 14.22 18 14.4961 18H17.0006L17.0011 20.5035C17.0011 20.7797 17.225 21.0035 17.5011 21.0035C17.7773 21.0035 18.0011 20.7797 18.0011 20.5035Z M6 6C6 5.44772 6.44772 5 7 5H15C15.5523 5 16 5.44772 16 6V8C16 8.55228 15.5523 9 15 9H7C6.44772 9 6 8.55228 6 8V6ZM7.5 7.5H14.5V6.5H7.5V7.5Z
diff --git a/EDSEditorGUI2/ViewModels/Device.cs b/EDSEditorGUI2/ViewModels/Device.cs
index c5f1ca85..568bfb04 100644
--- a/EDSEditorGUI2/ViewModels/Device.cs
+++ b/EDSEditorGUI2/ViewModels/Device.cs
@@ -8,6 +8,18 @@ public Device()
{
}
+ public override string ToString()
+ {
+ if (DeviceInfo == null)
+ {
+ return "unnamed device";
+ }
+ else
+ {
+ return DeviceInfo.ProductName;
+ }
+ }
+
[ObservableProperty]
private FileInfo _fileInfo = new();
diff --git a/EDSEditorGUI2/ViewModels/MainWindowViewModel.cs b/EDSEditorGUI2/ViewModels/MainWindowViewModel.cs
index 9d0dfdf9..5e05163e 100644
--- a/EDSEditorGUI2/ViewModels/MainWindowViewModel.cs
+++ b/EDSEditorGUI2/ViewModels/MainWindowViewModel.cs
@@ -1,5 +1,8 @@
-using CommunityToolkit.Mvvm.ComponentModel;
+using Avalonia.Media;
+using CommunityToolkit.Mvvm.ComponentModel;
using EDSEditorGUI2.Mapper;
+using System;
+using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace EDSEditorGUI2.ViewModels;
@@ -33,13 +36,115 @@ public void AddNewDevice(object sender)
var DeviceView = ProtobufferViewModelMapper.MapFromProtobuffer(device);
Network.Add(DeviceView);
}
+
+ public void InitMergeStatus(Device profile, List offsets)
+ {
+ MergeStatus.Clear();
+ if (SelectedDevice is not null)
+ {
+ foreach (var obj in profile.Objects)
+ {
+ int mergeIndex = Int32.Parse(obj.Key);
+ List objectOffset = [];
+ foreach (var offset in offsets)
+ {
+ objectOffset.Add(new(mergeIndex + offset, false));
+ }
+
+ ODIndexMergeStatus ms = new()
+ {
+ Insert = true,
+ OriginalObject = $"0x{mergeIndex:x} - {obj.Value.Name}",
+ Offsets = objectOffset,
+ OriginalIndex = mergeIndex,
+#pragma warning disable MVVMTK0034 // Direct field reference to [ObservableProperty] backing field
+ _object = obj.Value,
+#pragma warning restore MVVMTK0034 // Direct field reference to [ObservableProperty] backing field
+ TextBrush = new SolidColorBrush(Colors.Black),
+ };
+
+ MergeStatus.Add(ms);
+ }
+ UpdateMergeStatus(offsets);
+ }
+ }
+
+ ///
+ /// Update profile merge status by checking for collisions
+ ///
+ /// list of offsets in profile import
+ public void UpdateMergeStatus(List offsets)
+ {
+ if (SelectedDevice is not null && MergeStatus.Count != 0)
+ {
+ foreach (var obj in MergeStatus)
+ {
+ //first calculate all the offsets
+ //remember that the number of offsets could have changed
+ List objectOffset = [];
+ foreach (var offset in offsets)
+ {
+ int mergeIndex = obj.OriginalIndex + offset;
+ objectOffset.Add(new(mergeIndex, false));
+ }
+ obj.Offsets = objectOffset;
+ }
+
+ // check for collision with selected device objects
+ foreach (var obj in MergeStatus)
+ {
+ foreach (var offsetStatus in obj.Offsets)
+ {
+ foreach (var ob in SelectedDevice.Objects)
+ {
+ if (offsetStatus.Index == ob.Key.ToInteger())
+ {
+ offsetStatus.Collision = true;
+ offsetStatus.Index *= -1;
+ }
+ }
+ }
+ }
+
+ // check for collision with other offsets objects, collum by collum
+ var numberOfOffsets = MergeStatus[0].Offsets.Count;
+
+ // Check each collum from left to right.
+ // you only check for collision with collums to the left
+ for (int i = 0; i < numberOfOffsets; i++)
+ {
+ foreach (var leftRow in MergeStatus)
+ {
+ int rightCollumIndex = leftRow.Offsets[i].Index;
+ for (int j = i; j >= 0; j--)
+ {
+ if (j != i)
+ {
+ foreach (var rightRow in MergeStatus)
+ {
+ int leftCollumIndex = rightRow.Offsets[j].Index;
+ if (rightCollumIndex == leftCollumIndex)
+ {
+ leftRow.Offsets[i].Collision = true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
#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;
+ //Used for profile import
+ public ObservableCollection MergeStatus { get; set; } = [];
+ [ObservableProperty]
+ public int _insertObjectsOffset;
+ [ObservableProperty]
+ public Device? _selectedDevice;
}
diff --git a/EDSEditorGUI2/ViewModels/ODIndexMergeStatus.cs b/EDSEditorGUI2/ViewModels/ODIndexMergeStatus.cs
new file mode 100644
index 00000000..25ff1c30
--- /dev/null
+++ b/EDSEditorGUI2/ViewModels/ODIndexMergeStatus.cs
@@ -0,0 +1,49 @@
+using Avalonia.Media;
+using CommunityToolkit.Mvvm.ComponentModel;
+using System.Collections.Generic;
+
+namespace EDSEditorGUI2.ViewModels;
+
+///
+/// Viewmodel for merging one index
+///
+public partial class ODIndexMergeOffsetStatus : ObservableObject
+{
+ public ODIndexMergeOffsetStatus(int index, bool collision)
+ {
+ Index = index;
+ Collision = collision;
+ }
+ [ObservableProperty]
+ private int _index;
+
+ [ObservableProperty]
+ private bool _collision;
+}
+
+///
+/// Used as a view model when merging ODs or inserting profiles
+///
+public partial class ODIndexMergeStatus : ObservableObject
+{
+ [ObservableProperty]
+ private bool _insert;
+
+ [ObservableProperty]
+ private string _originalObject = string.Empty;
+
+ [ObservableProperty]
+ private List _offsets = [];
+
+ [ObservableProperty]
+ private bool _indexCollision;
+
+ [ObservableProperty]
+ private int _originalIndex;
+
+ [ObservableProperty]
+ public required OdObject _object;
+
+ [ObservableProperty]
+ public IBrush _textBrush = new SolidColorBrush(Colors.Black);
+}
diff --git a/EDSEditorGUI2/Views/MainWindow.axaml b/EDSEditorGUI2/Views/MainWindow.axaml
index d2469cd4..aadb8de2 100644
--- a/EDSEditorGUI2/Views/MainWindow.axaml
+++ b/EDSEditorGUI2/Views/MainWindow.axaml
@@ -1,7 +1,9 @@
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/EDSEditorGUI2/Views/MainWindow.axaml.cs b/EDSEditorGUI2/Views/MainWindow.axaml.cs
index 8fc2bd17..37bcb620 100644
--- a/EDSEditorGUI2/Views/MainWindow.axaml.cs
+++ b/EDSEditorGUI2/Views/MainWindow.axaml.cs
@@ -1,11 +1,264 @@
using Avalonia.Controls;
+using Avalonia.Controls.Templates;
+using Avalonia.Data;
+using Avalonia.Interactivity;
+using Avalonia.Platform.Storage;
+using DialogHostAvalonia;
+using EDSEditorGUI2.Mapper;
+using EDSEditorGUI2.ViewModels;
+using libEDSsharp;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Text.RegularExpressions;
namespace EDSEditorGUI2.Views;
public partial class MainWindow : Window
{
+ readonly FilePickerFileType xpd = new("CANopen XPD 1.1")
+ {
+ Patterns = ["*.xpd"]
+ };
+ readonly FilePickerFileType xdd = new("CANopen XDD 1.1")
+ {
+ Patterns = ["*.xdd"]
+ };
+ readonly FilePickerFileType xdc = new("CANopen XDC 1.1")
+ {
+ Patterns = ["*.xdc"]
+ };
+
public MainWindow()
{
InitializeComponent();
+ LoadProfileList();
+ }
+ private void LoadProfileList()
+ {
+ // load default profiles from the install directory
+ // load user profiles from the My Documents\.edseditor\profiles\ folder
+ // Personal is my documents in windows and ~ in mono
+
+ try
+ {
+ List profilelist = [.. Directory.GetFiles(Path.Combine(AppContext.BaseDirectory, "Profiles"))];
+ string homepath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), ".edseditor");
+ homepath = Path.Combine(homepath, "profiles");
+
+ if (Directory.Exists(homepath))
+ {
+ profilelist.AddRange(Directory.GetFiles(homepath));
+ }
+
+ List
+
+
+
+
+
+
+
+
diff --git a/GUITests/ProfileImportTests.cs b/GUITests/ProfileImportTests.cs
new file mode 100644
index 00000000..c25170ad
--- /dev/null
+++ b/GUITests/ProfileImportTests.cs
@@ -0,0 +1,240 @@
+using Avalonia.Controls;
+using Avalonia.Headless;
+using Avalonia.Headless.XUnit;
+using Avalonia.Input;
+using Avalonia.Threading;
+using Avalonia.VisualTree;
+using EDSEditorGUI2.ViewModels;
+using EDSEditorGUI2.Views;
+
+namespace GUITests;
+
+public class ImportTests : IDisposable
+{
+ readonly MainWindow window;
+ readonly MainWindowViewModel dc;
+ readonly MenuItem? profileMenu;
+ readonly TextBox? offsetsTextBox;
+
+ readonly ComboBox? target;
+ readonly Button? insert;
+ readonly Button? cancel;
+
+ public ImportTests()
+ {
+ dc = new MainWindowViewModel();
+ window = new MainWindow
+ {
+ DataContext = dc
+ };
+ window.Show();
+
+ // add device and select it
+ dc.AddNewDevice(window);
+ var deviceList = window.GetVisualDescendants().OfType().First();
+
+ Dispatcher.UIThread.RunJobs();
+ deviceList.SelectedItem = dc.Network[0];
+
+ // import profile
+ profileMenu = window.Find