Browse Source

Added shortcut providers and importing/exporting

CPKreuz 3 years ago
parent
commit
1256454c12

+ 1 - 10
PixiEditor/Helpers/InputKeyHelpers.cs

@@ -14,16 +14,7 @@ namespace PixiEditor.Helpers
 
         public static string GetKeyboardKey(Key key, CultureInfo culture) => key switch
         {
-            Key.NumPad0 => "Num0",
-            Key.NumPad1 => "Num1",
-            Key.NumPad2 => "Num2",
-            Key.NumPad3 => "Num3",
-            Key.NumPad4 => "Num4",
-            Key.NumPad5 => "Num5",
-            Key.NumPad6 => "Num6",
-            Key.NumPad7 => "Num7",
-            Key.NumPad8 => "Num8",
-            Key.NumPad9 => "Num9",
+            >= Key.NumPad0 and <= Key.Divide => $"Num {GetMappedKey(key, culture)}",
             Key.Space => nameof(Key.Space),
             Key.Tab => nameof(Key.Tab),
             Key.Back => "Backspace",

+ 26 - 6
PixiEditor/Models/Commands/CommandController.cs

@@ -15,6 +15,8 @@ namespace PixiEditor.Models.Commands
         private readonly ShortcutFile shortcutFile;
 
         public static CommandController Current { get; private set; }
+        
+        public static string ShortcutsPath { get; private set; }
 
         public CommandCollection Commands { get; }
 
@@ -28,12 +30,12 @@ namespace PixiEditor.Models.Commands
         {
             Current ??= this;
 
-            shortcutFile =
-                new(Path.Join(
-                        Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
-                        "PixiEditor",
-                        "shortcuts.json"),
-                    this);
+            ShortcutsPath = Path.Join(
+                Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+                "PixiEditor",
+                "shortcuts.json");
+            
+            shortcutFile = new(ShortcutsPath, this);
 
             Commands = new();
             CommandGroups = new();
@@ -41,6 +43,22 @@ namespace PixiEditor.Models.Commands
             IconEvaluators = new();
         }
 
+        public void Import(IEnumerable<KeyValuePair<KeyCombination, IEnumerable<string>>> shortcuts, bool save = true)
+        {
+            foreach (var shortcut in shortcuts)
+            {
+                foreach (var command in shortcut.Value)
+                {
+                    Commands[command].Shortcut = shortcut.Key;
+                }
+            }
+            
+            if (save)
+            {
+                shortcutFile.SaveShortcuts();
+            }
+        }
+        
         private static List<(string internalName, string displayName)> FindCommandGroups(Type[] typesToSearchForAttributes)
         {
             List<(string internalName, string displayName)> result = new();
@@ -323,6 +341,8 @@ namespace PixiEditor.Models.Commands
 
         public void ResetShortcuts()
         {
+            File.Copy(ShortcutsPath, Path.ChangeExtension(ShortcutsPath, ".json.bak"), true);
+            
             Commands.ClearShortcuts();
 
             foreach (var command in Commands)

+ 4 - 2
PixiEditor/Models/Commands/ShortcutFile.cs

@@ -37,7 +37,9 @@ namespace PixiEditor.Models.Commands
             File.WriteAllText(Path, JsonConvert.SerializeObject(shortcuts));
         }
 
-        public IEnumerable<KeyValuePair<KeyCombination, IEnumerable<string>>> LoadShortcuts() =>
-            JsonConvert.DeserializeObject<IEnumerable<KeyValuePair<KeyCombination, IEnumerable<string>>>>(File.ReadAllText(Path));
+        public IEnumerable<KeyValuePair<KeyCombination, IEnumerable<string>>> LoadShortcuts() => LoadShortcuts(Path);
+
+        public static IEnumerable<KeyValuePair<KeyCombination, IEnumerable<string>>> LoadShortcuts(string path) =>
+            JsonConvert.DeserializeObject<IEnumerable<KeyValuePair<KeyCombination, IEnumerable<string>>>>(File.ReadAllText(path));
     }
 }

+ 38 - 0
PixiEditor/Models/Commands/Templates/DebugProvider.cs

@@ -0,0 +1,38 @@
+using System.IO;
+using System.Windows.Input;
+
+namespace PixiEditor.Models.Commands.Templates;
+
+public partial class ShortcutProvider
+{
+    private static DebugProvider Debug { get; } = new DebugProvider();
+    
+    public class DebugProvider : ShortcutProvider, IShortcutDefaults, IShortcutFile, IShortcutInstallation
+    {
+        private static string InstallationPath { get; } = Path.Combine(
+            Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
+            "shortcut-provider.json");
+
+        public override string Description => "A provider for testing providers";
+
+        public DebugProvider() : base("Debug")
+        {
+        }
+
+        public ShortcutCollection DefaultShortcuts { get; } = new()
+        {
+            // Add shortcuts for undo and redo
+            { "PixiEditor.Undo.Undo", Key.Z, ModifierKeys.Control },
+            { "PixiEditor.Undo.Redo", Key.Y, ModifierKeys.Control },
+            "PixiEditor.Colors.Swap"
+        };
+        
+        public string Filter => "json (*.json)|*.json";
+
+        public ShortcutCollection GetShortcuts(string path) => new(ShortcutFile.LoadShortcuts(path));
+
+        public bool InstallationPresent => File.Exists(InstallationPath);
+        
+        public ShortcutCollection GetInstalledShortcuts() => new(ShortcutFile.LoadShortcuts(InstallationPath));
+    }
+}

+ 6 - 0
PixiEditor/Models/Commands/Templates/IShortcutDefaults.cs

@@ -0,0 +1,6 @@
+namespace PixiEditor.Models.Commands.Templates;
+
+public interface IShortcutDefaults
+{
+    ShortcutCollection DefaultShortcuts { get; }
+}

+ 8 - 0
PixiEditor/Models/Commands/Templates/IShortcutFile.cs

@@ -0,0 +1,8 @@
+namespace PixiEditor.Models.Commands.Templates;
+
+public interface IShortcutFile
+{
+    string Filter { get; }
+    
+    ShortcutCollection GetShortcuts(string path);
+}

+ 8 - 0
PixiEditor/Models/Commands/Templates/IShortcutInstallation.cs

@@ -0,0 +1,8 @@
+namespace PixiEditor.Models.Commands.Templates;
+
+public interface IShortcutInstallation
+{
+    bool InstallationPresent { get; }
+    
+    ShortcutCollection GetInstalledShortcuts();
+}

+ 25 - 0
PixiEditor/Models/Commands/Templates/ShortcutCollection.cs

@@ -0,0 +1,25 @@
+using System.Windows.Input;
+using PixiEditor.Models.DataHolders;
+
+namespace PixiEditor.Models.Commands.Templates;
+
+public class ShortcutCollection : OneToManyDictionary<KeyCombination, string>
+{
+    public ShortcutCollection() {}
+    
+    public ShortcutCollection(IEnumerable<KeyValuePair<KeyCombination, IEnumerable<string>>> enumerable) : base(enumerable) 
+    { }
+    
+    public void Add(string commandName, Key key, ModifierKeys modifiers)
+    {
+        Add(new(key, modifiers), commandName);
+    }
+    
+    /// <summary>
+    /// Unassigns a shortcut.
+    /// </summary>
+    public void Add(string commandName)
+    {
+        Add(KeyCombination.None, commandName);
+    }
+}

+ 41 - 0
PixiEditor/Models/Commands/Templates/ShortcutProvider.cs

@@ -0,0 +1,41 @@
+using System.Collections;
+using System.Windows.Input;
+using PixiEditor.Models.DataHolders;
+
+namespace PixiEditor.Models.Commands.Templates;
+
+public partial class ShortcutProvider
+{
+    public string Name { get; set; }
+
+    /// <summary>
+    /// Set this to true if this provider has default shortcuts
+    /// </summary>
+    public bool HasDefaultShortcuts => this is IShortcutDefaults;
+
+    /// <summary>
+    /// Set this to true if this provider can provide from a file
+    /// </summary>
+    public bool ProvidesImport => this is IShortcutFile;
+
+    /// <summary>
+    /// Set this to true if this provider can provide from installation
+    /// </summary>
+    public bool ProvidesFromInstallation => this is IShortcutInstallation;
+
+    public bool HasInstallationPresent => (this as IShortcutInstallation)?.InstallationPresent ?? false;
+
+    public virtual string Description { get; } = string.Empty;
+    
+    public ShortcutProvider(string name)
+    {
+        Name = name;
+    }
+
+    public static IEnumerable<ShortcutProvider> GetProviders() => new[]
+    {
+        #if DEBUG
+        Debug,
+        #endif
+    };
+}

+ 6 - 0
PixiEditor/Models/DataHolders/OneToManyDictionary.cs

@@ -14,6 +14,12 @@ namespace PixiEditor.Models.DataHolders
             _dictionary = new Dictionary<TKey, List<T>>();
         }
 
+        public OneToManyDictionary(IEnumerable<KeyValuePair<TKey, IEnumerable<T>>> enumerable)
+        {
+            _dictionary = new Dictionary<TKey, List<T>>(enumerable
+                .Select(x => new KeyValuePair<TKey, List<T>>(x.Key, x.Value.ToList())));
+        }
+
         public int Count => _dictionary.Count;
 
         public bool IsReadOnly => false;

+ 49 - 1
PixiEditor/ViewModels/SettingsWindowViewModel.cs

@@ -1,8 +1,13 @@
-using PixiEditor.Helpers;
+using System.IO;
+using PixiEditor.Helpers;
 using PixiEditor.Models.Commands;
 using PixiEditor.Models.Dialogs;
 using PixiEditor.ViewModels.SubViewModels.UserPreferences;
 using System.Windows;
+using System.Windows.Input;
+using Microsoft.Win32;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Views.Dialogs;
 
 namespace PixiEditor.ViewModels
 {
@@ -64,6 +69,49 @@ namespace PixiEditor.ViewModels
             }.ShowDialog();
         }
 
+        [Models.Commands.Attributes.Command.Internal("PixiEditor.Shortcuts.Export")]
+        public static void ExportShortcuts()
+        {
+            var dialog = new SaveFileDialog();
+            dialog.Filter = "PixiShorts (*.pixisc)|*.pixisc|json (*.json)|*.json|All files (*.*)|*.*";
+            dialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
+            if (dialog.ShowDialog().GetValueOrDefault())
+            {
+                File.Copy(CommandController.ShortcutsPath, dialog.FileName, true);
+            }
+            // Sometimes, focus was brought back to the last edited shortcut
+            Keyboard.ClearFocus();
+        }
+        
+        [Models.Commands.Attributes.Command.Internal("PixiEditor.Shortcuts.Import")]
+        public static void ImportShortcuts()
+        {
+            var dialog = new OpenFileDialog();
+            dialog.Filter = "PixiShorts (*.pixisc)|*.pixisc|json (*.json)|*.json|All files (*.*)|*.*";
+            dialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
+            if (dialog.ShowDialog().GetValueOrDefault())
+            {
+                var shortcuts = ShortcutFile.LoadShortcuts(dialog.FileName)?.ToArray();
+                if (shortcuts is null)
+                {
+                    NoticeDialog.Show("Shortcuts file was not in a valid format", "Invalid file");
+                    return;
+                }
+                CommandController.Current.ResetShortcuts();
+                CommandController.Current.Import(shortcuts, false);
+                File.Copy(dialog.FileName, CommandController.ShortcutsPath, true);
+                NoticeDialog.Show("Shortcuts were imported successfully", "Success");
+            }
+            // Sometimes, focus was brought back to the last edited shortcut
+            Keyboard.ClearFocus();
+        }
+
+        [Models.Commands.Attributes.Command.Internal("PixiEditor.Shortcuts.OpenTemplatePopup")]
+        public static void OpenTemplatePopup()
+        {
+            new ImportShortcutTemplatePopup().ShowDialog();
+        }
+
         public SettingsWindowViewModel()
         {
             Commands = new(CommandController.Current.CommandGroups.Select(x => new GroupSearchResult(x)));

+ 69 - 0
PixiEditor/Views/Dialogs/ImportShortcutTemplatePopup.xaml

@@ -0,0 +1,69 @@
+<Window x:Class="PixiEditor.Views.Dialogs.ImportShortcutTemplatePopup"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:diag="clr-namespace:PixiEditor.Views.Dialogs"
+        xmlns:xaml="clr-namespace:PixiEditor.Models.Commands.XAML"
+        mc:Ignorable="d"
+        Title="Import from template" Foreground="White"
+        MinWidth="580"
+        SizeToContent="WidthAndHeight" WindowStyle="None"
+        Background="{StaticResource AccentColor}"
+        x:Name="window">
+    
+    <Window.CommandBindings>
+        <CommandBinding Command="{x:Static SystemCommands.CloseWindowCommand}" CanExecute="CommandBinding_CanExecute"
+                        Executed="CommandBinding_Executed_Close" />
+    </Window.CommandBindings>
+    
+    <WindowChrome.WindowChrome>
+        <WindowChrome CaptionHeight="32" GlassFrameThickness="0.1"
+                      ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
+    </WindowChrome.WindowChrome>
+
+    <Grid>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="32"/>
+            <RowDefinition Height="*"/>
+        </Grid.RowDefinitions>
+        <diag:DialogTitleBar DockPanel.Dock="Top"
+                             TitleText="Import from template" CloseCommand="{x:Static SystemCommands.CloseWindowCommand}"/>
+        <ItemsControl Grid.Row="1" ItemsSource="{Binding Templates, ElementName=window}"
+                      Margin="10">
+            <ItemsControl.ItemTemplate>
+                <DataTemplate>
+                    <Grid>
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="*"/>
+                            <ColumnDefinition Width="Auto"/>
+                        </Grid.ColumnDefinitions>
+                        <TextBlock Text="{Binding Name}" VerticalAlignment="Center"
+                                   ToolTip="{Binding Description}"/>
+                        <StackPanel Grid.Column="1" Orientation="Horizontal">
+                            <StackPanel.Resources>
+                                <Style TargetType="Button" BasedOn="{StaticResource DarkRoundButton}">
+                                    <Setter Property="Width" Value="120"/>
+                                    <Setter Property="Height" Value="Auto"/>
+                                    <Setter Property="Padding" Value="5"/>
+                                    <Setter Property="FontSize" Value="12"/>
+                                    <Setter Property="Margin" Value="5,0, 0, 0"/>
+                                </Style>
+                            </StackPanel.Resources>
+                            <Button Command="{xaml:Command PixiEditor.Shortcuts.Provider.ImportInstallation, UseProvided=True}"
+                                    CommandParameter="{Binding}" Content="From installed"
+                                    Visibility="{Binding ProvidesFromInstallation, Converter={BoolToVisibilityConverter}}"
+                                    IsEnabled="{Binding HasInstallationPresent}"/>
+                            <Button Command="{xaml:Command PixiEditor.Shortcuts.Provider.ImportFile, UseProvided=True}"
+                                    CommandParameter="{Binding}" Content="Import"
+                                    Visibility="{Binding ProvidesImport, Converter={BoolToVisibilityConverter}}"/>
+                            <Button Command="{xaml:Command PixiEditor.Shortcuts.Provider.ImportDefault, UseProvided=True}"
+                                    CommandParameter="{Binding}" Content="Use default"
+                                    Visibility="{Binding HasDefaultShortcuts, Converter={BoolToVisibilityConverter}}"/>
+                        </StackPanel>
+                    </Grid>
+                </DataTemplate>
+            </ItemsControl.ItemTemplate>
+        </ItemsControl>
+    </Grid>
+</Window>

+ 104 - 0
PixiEditor/Views/Dialogs/ImportShortcutTemplatePopup.xaml.cs

@@ -0,0 +1,104 @@
+using System.IO;
+using System.Windows;
+using System.Windows.Input;
+using Microsoft.Win32;
+using PixiEditor.Models.Commands;
+using PixiEditor.Models.Commands.Templates;
+using PixiEditor.Models.Dialogs;
+
+namespace PixiEditor.Views.Dialogs;
+
+public partial class ImportShortcutTemplatePopup : Window
+{
+    public IEnumerable<ShortcutProvider> Templates { get; set; }
+
+    public ImportShortcutTemplatePopup()
+    {
+        Templates = ShortcutProvider.GetProviders();
+        InitializeComponent();
+        SourceInitialized += (_, _) =>
+        {
+            MinHeight = ActualHeight;
+        };
+    }
+    
+    [Models.Commands.Attributes.Command.Internal("PixiEditor.Shortcuts.Provider.ImportDefault")]
+    public static void ImportDefaults(ShortcutProvider provider)
+    {
+        if (provider is not IShortcutDefaults defaults)
+        {
+            throw new ArgumentException("provider must implement IShortcutDefaults", nameof(provider));
+        }
+        
+        CommandController.Current.ResetShortcuts();
+        CommandController.Current.Import(defaults.DefaultShortcuts);
+        
+        Success(provider);
+    }
+    
+    [Models.Commands.Attributes.Command.Internal("PixiEditor.Shortcuts.Provider.ImportFile")]
+    public static void ImportFile(ShortcutProvider provider)
+    {
+        if (provider is not IShortcutFile defaults)
+        {
+            throw new ArgumentException("provider must implement IShortcutFile", nameof(provider));
+        }
+
+        var picker = new OpenFileDialog();
+
+        if (!picker.ShowDialog().GetValueOrDefault())
+        {
+            return;
+        }
+        
+        try
+        {
+            var shortcuts = defaults.GetShortcuts(picker.FileName);
+            
+            CommandController.Current.ResetShortcuts();
+            CommandController.Current.Import(shortcuts);
+        }
+        catch (FileFormatException)
+        {
+            NoticeDialog.Show($"The file was not in a correct format", "Error");
+            return;
+        }
+
+        Success(provider);
+    }
+
+    [Models.Commands.Attributes.Command.Internal("PixiEditor.Shortcuts.Provider.ImportInstallation")]
+    public static void ImportInstallation(ShortcutProvider provider)
+    {
+        if (provider is not IShortcutInstallation defaults)
+        {
+            throw new ArgumentException("provider must implement IShortcutInstallation", nameof(provider));
+        }
+        
+        CommandController.Current.ResetShortcuts();
+
+        try
+        {
+            CommandController.Current.Import(defaults.GetInstalledShortcuts());
+        }
+        catch
+        {
+            NoticeDialog.Show($"The file was not in a correct format", "Error");
+            return;
+        }
+
+        Success(provider);
+    }
+
+    private static void Success(ShortcutProvider provider) => NoticeDialog.Show($"Shortcuts from {provider.Name} were imported successfully", "Success");
+
+    private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
+    {
+        e.CanExecute = true;
+    }
+
+    private void CommandBinding_Executed_Close(object sender, ExecutedRoutedEventArgs e)
+    {
+        SystemCommands.CloseWindow(this);
+    }
+}

+ 27 - 16
PixiEditor/Views/Dialogs/SettingsWindow.xaml

@@ -16,8 +16,8 @@
         xmlns:cmds="clr-namespace:PixiEditor.Models.Commands.XAML"
         mc:Ignorable="d"
         Title="Settings" Name="window" 
-        Height="688" Width="766"
-        MinHeight="500" MinWidth="640"
+        Height="688" Width="780"
+        MinHeight="500" MinWidth="680"
         WindowStyle="None" DataContext="{DynamicResource SettingsWindowViewModel}"
         WindowStartupLocation="CenterScreen"
         BorderBrush="Black" BorderThickness="1"
@@ -183,23 +183,34 @@
             <Grid Visibility="{Binding CurrentPage, Converter={converters:EqualityBoolToVisibilityConverter}, ConverterParameter='Keybinds'}"
                         Margin="10,10,10,0">
                 <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
                     <RowDefinition Height="Auto"/>
                     <RowDefinition/>
                 </Grid.RowDefinitions>
-                <Grid Margin="0,0,0,10">
-                    <Grid.ColumnDefinitions>
-                        <ColumnDefinition/>
-                        <ColumnDefinition Width="Auto"/>
-                    </Grid.ColumnDefinitions>
-                    <TextBox Style="{StaticResource DarkTextBoxStyle}"
-                             Text="{Binding SearchTerm, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
-                    <Button Grid.Column="1" Style="{StaticResource DarkRoundButton}"
-                            Content="Reset all" Height="Auto" Margin="5,0"
-                            FontSize="12" Padding="1"
-                            Command="{cmds:Command PixiEditor.Shortcuts.Reset}"/>
-                </Grid>
+                <StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
+                    <StackPanel.Resources>
+                        <Style TargetType="Button" BasedOn="{StaticResource DarkRoundButton}">
+                            <Setter Property="HorizontalAlignment" Value="Stretch"/>
+                            <Setter Property="Width" Value="115"/>
+                            <Setter Property="Height" Value="Auto"/>
+                            <Setter Property="Margin" Value="5,0"/>
+                            <Setter Property="FontSize" Value="12"/>
+                            <Setter Property="Padding" Value="5"/>
+                        </Style>
+                    </StackPanel.Resources>
+                    <Button Command="{cmds:Command PixiEditor.Shortcuts.Export}"
+                            Content="Export"/>
+                    <Button Command="{cmds:Command PixiEditor.Shortcuts.Import}"
+                            Content="Import"/>
+                    <Button Command="{cmds:Command PixiEditor.Shortcuts.OpenTemplatePopup}"
+                            Content="Import other"/>
+                    <Button Command="{cmds:Command PixiEditor.Shortcuts.Reset}"
+                            Content="Reset all"/>
+                </StackPanel>
+                <TextBox Grid.Row="1" Style="{StaticResource DarkTextBoxStyle}" Margin="0,10"
+                         Text="{Binding SearchTerm, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
 
-                <ScrollViewer Grid.Row="1" x:Name="commandScroll">
+                <ScrollViewer Grid.Row="2" x:Name="commandScroll">
                     <ScrollViewer.Template>
                         <ControlTemplate TargetType="{x:Type ScrollViewer}">
                             <Grid x:Name="Grid" Background="{TemplateBinding Background}">
@@ -244,7 +255,7 @@
                     </Grid>
                 </ScrollViewer>
 
-                <Grid Grid.Row="1" Height="10" VerticalAlignment="Top"
+                <Grid Grid.Row="2" Height="10" VerticalAlignment="Top"
                       Visibility="{Binding VerticalOffset, ElementName=commandScroll, Mode=OneWay, Converter={converters:EqualityBoolToVisibilityConverter Invert=True}, ConverterParameter=0}"
                       Margin="-10,0">
                     <Grid.Background>

+ 3 - 4
PixiEditor/Views/UserControls/ShortcutBox.cs

@@ -47,14 +47,14 @@ namespace PixiEditor.Views.UserControls
 
             if (e != KeyCombination.None)
             {
-                if (controller.Commands[e].Any())
+                if (controller.Commands[e].SkipWhile(x => x == Command).FirstOrDefault() is { } oldCommand)
                 {
                     var oldShortcut = Command.Shortcut;
                     bool enableSwap = oldShortcut is not { Key: Key.None, Modifiers: ModifierKeys.None };
                     
                     string text = enableSwap ?
-                        $"This shortcut is already assigned to '{controller.Commands[e].First().DisplayName}'\nDo you want to replace the existing shortcut or swap the two?" :
-                        $"This shortcut is already assigned to '{controller.Commands[e].First().DisplayName}'\nDo you want to replace the existing shortcut?";
+                        $"This shortcut is already assigned to '{oldCommand.DisplayName}'\nDo you want to replace the existing shortcut or swap the two?" :
+                        $"This shortcut is already assigned to '{oldCommand.DisplayName}'\nDo you want to replace the existing shortcut?";
                     OptionsDialog<string> dialog = new("Already assigned", text);
                     
                     dialog.Add("Replace", x => controller.ReplaceShortcut(Command, e));
@@ -62,7 +62,6 @@ namespace PixiEditor.Views.UserControls
                     {
                         dialog.Add("Swap", x =>
                         {
-                            var oldCommand = controller.Commands[e].First();
                             controller.ReplaceShortcut(Command, e);
                             controller.ReplaceShortcut(oldCommand, oldShortcut);
                         });