Enabling the edit dialog

This commit is contained in:
Niels Laute
2026-02-02 11:24:29 +01:00
parent 9c7b651160
commit 37be249362
5 changed files with 1787 additions and 28 deletions

View File

@@ -0,0 +1,314 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="KeyboardManagerEditorUI.Controls.UnifiedMappingControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:KeyboardManagerEditorUI.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
Loaded="UserControl_Loaded"
mc:Ignorable="d">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="280" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Left Column: Original Input (Trigger) -->
<StackPanel
Grid.Column="0"
Orientation="Vertical"
Spacing="8">
<TextBlock
x:Uid="UnifiedMappingControlTriggerTextBlock"
FontWeight="SemiBold"
Text="Trigger" />
<ComboBox
x:Name="TriggerTypeComboBox"
HorizontalAlignment="Stretch"
SelectionChanged="TriggerTypeComboBox_SelectionChanged">
<ComboBoxItem Content="Key or shortcut" Tag="KeyOrShortcut" IsSelected="True" />
<ComboBoxItem Content="Mouse button" Tag="Mouse" />
</ComboBox>
<tkcontrols:SwitchPresenter
x:Name="TriggerSwitchPresenter"
TargetType="x:String"
Value="{Binding SelectedItem.Tag, ElementName=TriggerTypeComboBox}">
<!-- Key or Shortcut Trigger -->
<tkcontrols:Case Value="KeyOrShortcut">
<ToggleButton
x:Name="TriggerKeyToggleBtn"
MinHeight="86"
Padding="8,24,8,24"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Checked="TriggerKeyToggleBtn_Checked"
Style="{StaticResource CustomShortcutToggleButtonStyle}"
Unchecked="TriggerKeyToggleBtn_Unchecked">
<ToggleButton.Content>
<ItemsControl x:Name="TriggerKeys">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<controls:WrapPanel
HorizontalSpacing="4"
Orientation="Horizontal"
VerticalSpacing="4" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:KeyVisual Content="{Binding}" Style="{StaticResource DefaultKeyVisualStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ToggleButton.Content>
</ToggleButton>
</tkcontrols:Case>
<!-- Mouse Button Trigger -->
<tkcontrols:Case Value="Mouse">
<ComboBox
x:Name="MouseTriggerComboBox"
MinHeight="86"
HorizontalAlignment="Stretch"
VerticalContentAlignment="Center">
<ComboBoxItem Content="Left mouse button" Tag="LeftMouse" />
<ComboBoxItem Content="Right mouse button" Tag="RightMouse" />
<ComboBoxItem Content="Scroll up" Tag="ScrollUp" />
<ComboBoxItem Content="Scroll down" Tag="ScrollDown" />
</ComboBox>
</tkcontrols:Case>
</tkcontrols:SwitchPresenter>
</StackPanel>
<!-- Arrow Separator -->
<TextBlock
Grid.Column="1"
Margin="24,0,24,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="20"
Text="&#xE0AB;" />
<!-- Right Column: Action Output -->
<StackPanel
Grid.Column="2"
MinWidth="360"
Orientation="Vertical"
Spacing="8">
<TextBlock
x:Uid="UnifiedMappingControlActionTextBlock"
FontWeight="SemiBold"
Text="Action" />
<ComboBox
x:Name="ActionTypeComboBox"
HorizontalAlignment="Stretch"
SelectionChanged="ActionTypeComboBox_SelectionChanged">
<ComboBoxItem Content="Key or shortcut" Tag="KeyOrShortcut" IsSelected="True" />
<ComboBoxItem Content="Text" Tag="Text" />
<ComboBoxItem Content="Open URL" Tag="OpenUrl" />
<ComboBoxItem Content="Open app" Tag="OpenApp" />
<ComboBoxItem Content="Mouse click" Tag="MouseClick" />
</ComboBox>
<tkcontrols:SwitchPresenter
x:Name="ActionSwitchPresenter"
TargetType="x:String"
Value="{Binding SelectedItem.Tag, ElementName=ActionTypeComboBox}">
<!-- Key or Shortcut Action -->
<tkcontrols:Case Value="KeyOrShortcut">
<StackPanel Orientation="Vertical" Spacing="8">
<ToggleButton
x:Name="ActionKeyToggleBtn"
MinHeight="86"
Padding="8,24,8,24"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Checked="ActionKeyToggleBtn_Checked"
Style="{StaticResource CustomShortcutToggleButtonStyle}"
Unchecked="ActionKeyToggleBtn_Unchecked">
<ToggleButton.Content>
<ItemsControl x:Name="ActionKeys">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<controls:WrapPanel
HorizontalSpacing="4"
Orientation="Horizontal"
VerticalSpacing="4" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:KeyVisual Content="{Binding}" Style="{StaticResource AccentKeyVisualStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ToggleButton.Content>
</ToggleButton>
<!-- App-specific remapping (only for shortcuts) -->
<CheckBox
x:Name="AppSpecificCheckBox"
x:Uid="UnifiedMappingControlAppSpecificCheckBox"
Content="Make this app-specific"
IsEnabled="False" />
<TextBox
x:Name="AppNameTextBox"
x:Uid="UnifiedMappingControlAppNameTextBox"
Background="{ThemeResource TextControlBackgroundFocused}"
BorderBrush="{ThemeResource ControlStrokeColorDefaultBrush}"
GotFocus="AppNameTextBox_GotFocus"
PlaceholderText="Enter app name (e.g., notepad.exe)"
Visibility="Collapsed" />
</StackPanel>
</tkcontrols:Case>
<!-- Text Action -->
<tkcontrols:Case Value="Text">
<StackPanel Orientation="Vertical" Spacing="8">
<TextBox
x:Name="TextContentBox"
x:Uid="UnifiedMappingControlTextContentTextBox"
Height="120"
AcceptsReturn="True"
Background="{ThemeResource TextControlBackgroundFocused}"
BorderBrush="{ThemeResource ControlStrokeColorDefaultBrush}"
FontSize="13"
GotFocus="TextContentBox_GotFocus"
Header="Text to type"
PlaceholderText="Enter the text to type when triggered"
TextWrapping="Wrap" />
<!-- App-specific for text -->
<CheckBox
x:Name="TextAppSpecificCheckBox"
x:Uid="UnifiedMappingControlTextAppSpecificCheckBox"
Content="Make this app-specific"
IsEnabled="False" />
<TextBox
x:Name="TextAppNameTextBox"
x:Uid="UnifiedMappingControlTextAppNameTextBox"
Background="{ThemeResource TextControlBackgroundFocused}"
BorderBrush="{ThemeResource ControlStrokeColorDefaultBrush}"
GotFocus="TextAppNameTextBox_GotFocus"
PlaceholderText="Enter app name (e.g., notepad.exe)"
Visibility="Collapsed" />
</StackPanel>
</tkcontrols:Case>
<!-- Open URL Action -->
<tkcontrols:Case Value="OpenUrl">
<TextBox
x:Name="UrlPathInput"
x:Uid="UnifiedMappingControlUrlPathTextBox"
Background="{ThemeResource TextControlBackgroundFocused}"
BorderBrush="{ThemeResource ControlStrokeColorDefaultBrush}"
FontSize="13"
GotFocus="UrlPathInput_GotFocus"
Header="URL to open"
PlaceholderText="https://example.com" />
</tkcontrols:Case>
<!-- Open App Action -->
<tkcontrols:Case Value="OpenApp">
<StackPanel Orientation="Vertical" Spacing="8">
<StackPanel Orientation="Horizontal" Spacing="4">
<TextBox
x:Name="ProgramPathInput"
x:Uid="UnifiedMappingControlProgramPathTextBox"
Width="240"
GotFocus="ProgramPathInput_GotFocus"
Header="Program path"
PlaceholderText="C:\Program Files\..." />
<Button
x:Name="ProgramPathSelectButton"
x:Uid="UnifiedMappingControlPathSelectButton"
VerticalAlignment="Bottom"
Click="ProgramPathSelectButton_Click"
Content="Browse..." />
</StackPanel>
<TextBox
x:Name="ProgramArgsInput"
x:Uid="UnifiedMappingControlArgumentsTextBox"
GotFocus="ProgramArgsInput_GotFocus"
Header="Arguments (optional)"
PlaceholderText="--arg1 value1" />
<StackPanel Orientation="Horizontal" Spacing="4">
<TextBox
x:Name="StartInPathInput"
x:Uid="UnifiedMappingControlStartInTextBox"
Width="240"
GotFocus="StartInPathInput_GotFocus"
Header="Start in directory (optional)"
PlaceholderText="C:\Users\..." />
<Button
x:Name="StartInSelectButton"
x:Uid="UnifiedMappingControlStartInSelectButton"
VerticalAlignment="Bottom"
Click="StartInSelectButton_Click"
Content="Browse..." />
</StackPanel>
<ComboBox
x:Name="ElevationComboBox"
x:Uid="UnifiedMappingControlElevationComboBox"
HorizontalAlignment="Stretch"
Header="Run as"
SelectedIndex="0">
<x:String>Normal</x:String>
<x:String>Elevated</x:String>
<x:String>Different user</x:String>
</ComboBox>
<ComboBox
x:Name="IfRunningComboBox"
x:Uid="UnifiedMappingControlIfRunningComboBox"
HorizontalAlignment="Stretch"
Header="If already running"
SelectedIndex="0">
<x:String>Show window</x:String>
<x:String>Start another</x:String>
<x:String>Do nothing</x:String>
<x:String>Close</x:String>
<x:String>End task</x:String>
</ComboBox>
<ComboBox
x:Name="VisibilityComboBox"
x:Uid="UnifiedMappingControlVisibilityComboBox"
HorizontalAlignment="Stretch"
Header="Window visibility"
SelectedIndex="0">
<x:String>Normal</x:String>
<x:String>Hidden</x:String>
<x:String>Minimized</x:String>
<x:String>Maximized</x:String>
</ComboBox>
</StackPanel>
</tkcontrols:Case>
<!-- Mouse Click Action (Placeholder) -->
<tkcontrols:Case Value="MouseClick">
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="Mouse click action - coming soon"
TextWrapping="Wrap" />
</tkcontrols:Case>
</tkcontrols:SwitchPresenter>
</StackPanel>
</Grid>
</UserControl>

View File

@@ -0,0 +1,943 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using KeyboardManagerEditorUI.Helpers;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.System;
using WinRT.Interop;
using static KeyboardManagerEditorUI.Interop.ShortcutKeyMapping;
#pragma warning disable SA1124 // Do not use regions
namespace KeyboardManagerEditorUI.Controls
{
/// <summary>
/// Unified control that consolidates all mapping input types:
/// - Key/Shortcut remapping (InputControl)
/// - Text output (TextPageInputControl)
/// - URL opening (UrlPageInputControl)
/// - App launching (AppPageInputControl)
/// </summary>
public sealed partial class UnifiedMappingControl : UserControl, IDisposable, IKeyboardHookTarget
{
#region Fields
private readonly ObservableCollection<string> _triggerKeys = new();
private readonly ObservableCollection<string> _actionKeys = new();
private TeachingTip? _currentNotification;
private DispatcherTimer? _notificationTimer;
private bool _disposed;
private bool _internalUpdate;
private KeyInputMode _currentInputMode = KeyInputMode.OriginalKeys;
#endregion
#region Enums
/// <summary>
/// Defines the type of trigger for the mapping.
/// </summary>
public enum TriggerType
{
KeyOrShortcut,
Mouse,
}
/// <summary>
/// Defines the type of action to perform.
/// </summary>
public enum ActionType
{
KeyOrShortcut,
Text,
OpenUrl,
OpenApp,
MouseClick,
}
/// <summary>
/// Defines the mouse button options.
/// </summary>
public enum MouseButton
{
LeftMouse,
RightMouse,
ScrollUp,
ScrollDown,
}
#endregion
#region Properties
/// <summary>
/// Gets the current trigger type.
/// </summary>
public TriggerType CurrentTriggerType
{
get
{
if (TriggerTypeComboBox?.SelectedItem is ComboBoxItem item)
{
return item.Tag?.ToString() switch
{
"Mouse" => TriggerType.Mouse,
_ => TriggerType.KeyOrShortcut,
};
}
return TriggerType.KeyOrShortcut;
}
}
/// <summary>
/// Gets the current action type.
/// </summary>
public ActionType CurrentActionType
{
get
{
if (ActionTypeComboBox?.SelectedItem is ComboBoxItem item)
{
return item.Tag?.ToString() switch
{
"Text" => ActionType.Text,
"OpenUrl" => ActionType.OpenUrl,
"OpenApp" => ActionType.OpenApp,
"MouseClick" => ActionType.MouseClick,
_ => ActionType.KeyOrShortcut,
};
}
return ActionType.KeyOrShortcut;
}
}
#endregion
#region Constructor
public UnifiedMappingControl()
{
this.InitializeComponent();
TriggerKeys.ItemsSource = _triggerKeys;
ActionKeys.ItemsSource = _actionKeys;
this.Unloaded += UnifiedMappingControl_Unloaded;
}
#endregion
#region Lifecycle Events
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
// Set up event handlers for app-specific checkboxes
AppSpecificCheckBox.Checked += AppSpecificCheckBox_Changed;
AppSpecificCheckBox.Unchecked += AppSpecificCheckBox_Changed;
TextAppSpecificCheckBox.Checked += TextAppSpecificCheckBox_Changed;
TextAppSpecificCheckBox.Unchecked += TextAppSpecificCheckBox_Changed;
// Activate keyboard hook for the trigger input
if (TriggerKeyToggleBtn.IsChecked == true)
{
_currentInputMode = KeyInputMode.OriginalKeys;
KeyboardHookHelper.Instance.ActivateHook(this);
}
}
private void UnifiedMappingControl_Unloaded(object sender, RoutedEventArgs e)
{
Reset();
CleanupKeyboardHook();
}
#endregion
#region Trigger Type Handling
private void TriggerTypeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (TriggerTypeComboBox?.SelectedItem is ComboBoxItem item)
{
string? tag = item.Tag?.ToString();
// Cleanup keyboard hook when switching to mouse
if (tag == "Mouse")
{
CleanupKeyboardHook();
UncheckAllToggleButtons();
}
}
}
private void TriggerKeyToggleBtn_Checked(object sender, RoutedEventArgs e)
{
if (TriggerKeyToggleBtn.IsChecked == true)
{
_currentInputMode = KeyInputMode.OriginalKeys;
// Uncheck action toggle if checked
if (ActionKeyToggleBtn?.IsChecked == true)
{
ActionKeyToggleBtn.IsChecked = false;
}
KeyboardHookHelper.Instance.ActivateHook(this);
}
}
private void TriggerKeyToggleBtn_Unchecked(object sender, RoutedEventArgs e)
{
if (_currentInputMode == KeyInputMode.OriginalKeys)
{
CleanupKeyboardHook();
}
}
#endregion
#region Action Type Handling
private void ActionTypeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ActionTypeComboBox?.SelectedItem is ComboBoxItem item)
{
string? tag = item.Tag?.ToString();
// Cleanup keyboard hook when switching away from key/shortcut
if (tag != "KeyOrShortcut")
{
if (_currentInputMode == KeyInputMode.RemappedKeys)
{
CleanupKeyboardHook();
}
if (ActionKeyToggleBtn?.IsChecked == true)
{
ActionKeyToggleBtn.IsChecked = false;
}
}
}
}
private void ActionKeyToggleBtn_Checked(object sender, RoutedEventArgs e)
{
if (ActionKeyToggleBtn.IsChecked == true)
{
_currentInputMode = KeyInputMode.RemappedKeys;
// Uncheck trigger toggle if checked
if (TriggerKeyToggleBtn?.IsChecked == true)
{
TriggerKeyToggleBtn.IsChecked = false;
}
KeyboardHookHelper.Instance.ActivateHook(this);
}
}
private void ActionKeyToggleBtn_Unchecked(object sender, RoutedEventArgs e)
{
if (_currentInputMode == KeyInputMode.RemappedKeys)
{
CleanupKeyboardHook();
}
}
#endregion
#region App-Specific Handling
private void AppSpecificCheckBox_Changed(object sender, RoutedEventArgs e)
{
if (_internalUpdate)
{
return;
}
CleanupKeyboardHook();
UncheckAllToggleButtons();
AppNameTextBox.Visibility = AppSpecificCheckBox.IsChecked == true
? Visibility.Visible
: Visibility.Collapsed;
}
private void TextAppSpecificCheckBox_Changed(object sender, RoutedEventArgs e)
{
if (_internalUpdate)
{
return;
}
CleanupKeyboardHook();
UncheckAllToggleButtons();
TextAppNameTextBox.Visibility = TextAppSpecificCheckBox.IsChecked == true
? Visibility.Visible
: Visibility.Collapsed;
}
private void UpdateAppSpecificCheckBoxState()
{
// Only enable app-specific remapping for shortcuts (multiple keys)
bool isShortcut = _triggerKeys.Count > 1;
try
{
_internalUpdate = true;
// Update Key/Shortcut action checkbox
AppSpecificCheckBox.IsEnabled = isShortcut;
if (!isShortcut)
{
AppSpecificCheckBox.IsChecked = false;
AppNameTextBox.Visibility = Visibility.Collapsed;
}
// Update Text action checkbox
TextAppSpecificCheckBox.IsEnabled = isShortcut;
if (!isShortcut)
{
TextAppSpecificCheckBox.IsChecked = false;
TextAppNameTextBox.Visibility = Visibility.Collapsed;
}
}
finally
{
_internalUpdate = false;
}
}
#endregion
#region TextBox Focus Handlers
private void AppNameTextBox_GotFocus(object sender, RoutedEventArgs e)
{
CleanupKeyboardHook();
UncheckAllToggleButtons();
}
private void TextContentBox_GotFocus(object sender, RoutedEventArgs e)
{
CleanupKeyboardHook();
UncheckAllToggleButtons();
}
private void TextAppNameTextBox_GotFocus(object sender, RoutedEventArgs e)
{
CleanupKeyboardHook();
UncheckAllToggleButtons();
}
private void UrlPathInput_GotFocus(object sender, RoutedEventArgs e)
{
CleanupKeyboardHook();
UncheckAllToggleButtons();
}
private void ProgramPathInput_GotFocus(object sender, RoutedEventArgs e)
{
CleanupKeyboardHook();
UncheckAllToggleButtons();
}
private void ProgramArgsInput_GotFocus(object sender, RoutedEventArgs e)
{
CleanupKeyboardHook();
UncheckAllToggleButtons();
}
private void StartInPathInput_GotFocus(object sender, RoutedEventArgs e)
{
CleanupKeyboardHook();
UncheckAllToggleButtons();
}
#endregion
#region File/Folder Pickers
private async void ProgramPathSelectButton_Click(object sender, RoutedEventArgs e)
{
var picker = new FileOpenPicker();
var hwnd = WindowNative.GetWindowHandle(App.MainWindow);
InitializeWithWindow.Initialize(picker, hwnd);
picker.FileTypeFilter.Add(".exe");
StorageFile file = await picker.PickSingleFileAsync();
if (file != null)
{
ProgramPathInput.Text = file.Path;
}
}
private async void StartInSelectButton_Click(object sender, RoutedEventArgs e)
{
var picker = new FolderPicker();
var hwnd = WindowNative.GetWindowHandle(App.MainWindow);
InitializeWithWindow.Initialize(picker, hwnd);
picker.FileTypeFilter.Add("*");
StorageFolder folder = await picker.PickSingleFolderAsync();
if (folder != null)
{
StartInPathInput.Text = folder.Path;
}
}
#endregion
#region IKeyboardHookTarget Implementation
public void OnKeyDown(VirtualKey key, List<string> formattedKeys)
{
if (_currentInputMode == KeyInputMode.OriginalKeys)
{
_triggerKeys.Clear();
foreach (var keyName in formattedKeys)
{
_triggerKeys.Add(keyName);
}
UpdateAppSpecificCheckBoxState();
}
else if (_currentInputMode == KeyInputMode.RemappedKeys)
{
_actionKeys.Clear();
foreach (var keyName in formattedKeys)
{
_actionKeys.Add(keyName);
}
}
}
public void ClearKeys()
{
if (_currentInputMode == KeyInputMode.OriginalKeys)
{
_triggerKeys.Clear();
}
else
{
_actionKeys.Clear();
}
}
public void OnInputLimitReached()
{
ShowNotificationTip("Shortcuts can only have up to 4 modifier keys");
}
#endregion
#region Public API - Getters
/// <summary>
/// Gets the trigger keys.
/// </summary>
public List<string> GetTriggerKeys() => _triggerKeys.ToList();
/// <summary>
/// Gets the action keys (for Key/Shortcut action type).
/// </summary>
public List<string> GetActionKeys() => _actionKeys.ToList();
/// <summary>
/// Gets the selected mouse trigger.
/// </summary>
public MouseButton? GetMouseTrigger()
{
if (MouseTriggerComboBox?.SelectedItem is ComboBoxItem item)
{
return item.Tag?.ToString() switch
{
"LeftMouse" => MouseButton.LeftMouse,
"RightMouse" => MouseButton.RightMouse,
"ScrollUp" => MouseButton.ScrollUp,
"ScrollDown" => MouseButton.ScrollDown,
_ => null,
};
}
return null;
}
/// <summary>
/// Gets the text content (for Text action type).
/// </summary>
public string GetTextContent() => TextContentBox?.Text ?? string.Empty;
/// <summary>
/// Gets the URL (for OpenUrl action type).
/// </summary>
public string GetUrl() => UrlPathInput?.Text ?? string.Empty;
/// <summary>
/// Gets the program path (for OpenApp action type).
/// </summary>
public string GetProgramPath() => ProgramPathInput?.Text ?? string.Empty;
/// <summary>
/// Gets the program arguments (for OpenApp action type).
/// </summary>
public string GetProgramArgs() => ProgramArgsInput?.Text ?? string.Empty;
/// <summary>
/// Gets the start-in directory (for OpenApp action type).
/// </summary>
public string GetStartInDirectory() => StartInPathInput?.Text ?? string.Empty;
/// <summary>
/// Gets whether the mapping is app-specific.
/// </summary>
public bool GetIsAppSpecific()
{
return CurrentActionType switch
{
ActionType.KeyOrShortcut => AppSpecificCheckBox?.IsChecked ?? false,
ActionType.Text => TextAppSpecificCheckBox?.IsChecked ?? false,
_ => false,
};
}
/// <summary>
/// Gets the app name for app-specific mappings.
/// </summary>
public string GetAppName()
{
return CurrentActionType switch
{
ActionType.KeyOrShortcut => GetIsAppSpecific() ? (AppNameTextBox?.Text ?? string.Empty) : string.Empty,
ActionType.Text => GetIsAppSpecific() ? (TextAppNameTextBox?.Text ?? string.Empty) : string.Empty,
_ => string.Empty,
};
}
/// <summary>
/// Gets the elevation level (for OpenApp action type).
/// </summary>
public ElevationLevel GetElevationLevel() => (ElevationLevel)(ElevationComboBox?.SelectedIndex ?? 0);
/// <summary>
/// Gets the window visibility (for OpenApp action type).
/// </summary>
public StartWindowType GetVisibility() => (StartWindowType)(VisibilityComboBox?.SelectedIndex ?? 0);
/// <summary>
/// Gets the if-running action (for OpenApp action type).
/// </summary>
public ProgramAlreadyRunningAction GetIfRunningAction() => (ProgramAlreadyRunningAction)(IfRunningComboBox?.SelectedIndex ?? 0);
#endregion
#region Public API - Setters
/// <summary>
/// Sets the trigger keys.
/// </summary>
public void SetTriggerKeys(List<string> keys)
{
_triggerKeys.Clear();
if (keys != null)
{
foreach (var key in keys)
{
_triggerKeys.Add(key);
}
}
UpdateAppSpecificCheckBoxState();
}
/// <summary>
/// Sets the action keys.
/// </summary>
public void SetActionKeys(List<string> keys)
{
_actionKeys.Clear();
if (keys != null)
{
foreach (var key in keys)
{
_actionKeys.Add(key);
}
}
}
/// <summary>
/// Sets the action type.
/// </summary>
public void SetActionType(ActionType actionType)
{
int index = actionType switch
{
ActionType.Text => 1,
ActionType.OpenUrl => 2,
ActionType.OpenApp => 3,
ActionType.MouseClick => 4,
_ => 0,
};
if (ActionTypeComboBox != null)
{
ActionTypeComboBox.SelectedIndex = index;
}
}
/// <summary>
/// Sets the text content (for Text action type).
/// </summary>
public void SetTextContent(string text)
{
if (TextContentBox != null)
{
TextContentBox.Text = text;
}
}
/// <summary>
/// Sets the URL (for OpenUrl action type).
/// </summary>
public void SetUrl(string url)
{
if (UrlPathInput != null)
{
UrlPathInput.Text = url;
}
}
/// <summary>
/// Sets the program path (for OpenApp action type).
/// </summary>
public void SetProgramPath(string path)
{
if (ProgramPathInput != null)
{
ProgramPathInput.Text = path;
}
}
/// <summary>
/// Sets the program arguments (for OpenApp action type).
/// </summary>
public void SetProgramArgs(string args)
{
if (ProgramArgsInput != null)
{
ProgramArgsInput.Text = args;
}
}
/// <summary>
/// Sets the start-in directory (for OpenApp action type).
/// </summary>
public void SetStartInDirectory(string path)
{
if (StartInPathInput != null)
{
StartInPathInput.Text = path;
}
}
/// <summary>
/// Sets the elevation level (for OpenApp action type).
/// </summary>
public void SetElevationLevel(ElevationLevel elevationLevel)
{
if (ElevationComboBox != null)
{
ElevationComboBox.SelectedIndex = (int)elevationLevel;
}
}
/// <summary>
/// Sets the window visibility (for OpenApp action type).
/// </summary>
public void SetVisibility(StartWindowType visibility)
{
if (VisibilityComboBox != null)
{
VisibilityComboBox.SelectedIndex = (int)visibility;
}
}
/// <summary>
/// Sets the if-already-running action (for OpenApp action type).
/// </summary>
public void SetIfRunningAction(ProgramAlreadyRunningAction ifRunningAction)
{
if (IfRunningComboBox != null)
{
IfRunningComboBox.SelectedIndex = (int)ifRunningAction;
}
}
/// <summary>
/// Sets whether the mapping is app-specific.
/// </summary>
public void SetAppSpecific(bool isAppSpecific, string appName)
{
switch (CurrentActionType)
{
case ActionType.KeyOrShortcut:
if (AppSpecificCheckBox != null)
{
AppSpecificCheckBox.IsChecked = isAppSpecific;
if (isAppSpecific && AppNameTextBox != null)
{
AppNameTextBox.Text = appName;
AppNameTextBox.Visibility = Visibility.Visible;
}
}
break;
case ActionType.Text:
if (TextAppSpecificCheckBox != null)
{
TextAppSpecificCheckBox.IsChecked = isAppSpecific;
if (isAppSpecific && TextAppNameTextBox != null)
{
TextAppNameTextBox.Text = appName;
TextAppNameTextBox.Visibility = Visibility.Visible;
}
}
break;
}
}
#endregion
#region Helper Methods
private void UncheckAllToggleButtons()
{
if (TriggerKeyToggleBtn?.IsChecked == true)
{
TriggerKeyToggleBtn.IsChecked = false;
}
if (ActionKeyToggleBtn?.IsChecked == true)
{
ActionKeyToggleBtn.IsChecked = false;
}
}
private void CleanupKeyboardHook()
{
KeyboardHookHelper.Instance.CleanupHook();
}
/// <summary>
/// Resets all inputs to their default state.
/// </summary>
public void Reset()
{
_triggerKeys.Clear();
_actionKeys.Clear();
UncheckAllToggleButtons();
_currentInputMode = KeyInputMode.OriginalKeys;
// Reset combo boxes
if (TriggerTypeComboBox != null)
{
TriggerTypeComboBox.SelectedIndex = 0;
}
if (ActionTypeComboBox != null)
{
ActionTypeComboBox.SelectedIndex = 0;
}
if (MouseTriggerComboBox != null)
{
MouseTriggerComboBox.SelectedIndex = -1;
}
// Reset text inputs
if (TextContentBox != null)
{
TextContentBox.Text = string.Empty;
}
if (UrlPathInput != null)
{
UrlPathInput.Text = string.Empty;
}
if (ProgramPathInput != null)
{
ProgramPathInput.Text = string.Empty;
}
if (ProgramArgsInput != null)
{
ProgramArgsInput.Text = string.Empty;
}
if (StartInPathInput != null)
{
StartInPathInput.Text = string.Empty;
}
if (AppNameTextBox != null)
{
AppNameTextBox.Text = string.Empty;
AppNameTextBox.Visibility = Visibility.Collapsed;
}
if (TextAppNameTextBox != null)
{
TextAppNameTextBox.Text = string.Empty;
TextAppNameTextBox.Visibility = Visibility.Collapsed;
}
// Reset checkboxes
if (AppSpecificCheckBox != null)
{
AppSpecificCheckBox.IsChecked = false;
AppSpecificCheckBox.IsEnabled = false;
}
if (TextAppSpecificCheckBox != null)
{
TextAppSpecificCheckBox.IsChecked = false;
TextAppSpecificCheckBox.IsEnabled = false;
}
// Reset app combo boxes
if (ElevationComboBox != null)
{
ElevationComboBox.SelectedIndex = 0;
}
if (IfRunningComboBox != null)
{
IfRunningComboBox.SelectedIndex = 0;
}
if (VisibilityComboBox != null)
{
VisibilityComboBox.SelectedIndex = 0;
}
CloseExistingNotification();
}
/// <summary>
/// Resets only the toggle buttons without clearing the key displays.
/// </summary>
public void ResetToggleButtons()
{
UncheckAllToggleButtons();
}
#endregion
#region Notifications
public void ShowNotificationTip(string message)
{
CloseExistingNotification();
_currentNotification = new TeachingTip
{
Title = "Input Limit Reached",
Subtitle = message,
IsLightDismissEnabled = true,
PreferredPlacement = TeachingTipPlacementMode.Top,
XamlRoot = this.XamlRoot,
IconSource = new SymbolIconSource { Symbol = Symbol.Important },
};
// Target the appropriate toggle button
_currentNotification.Target = _currentInputMode == KeyInputMode.RemappedKeys
? ActionKeyToggleBtn
: TriggerKeyToggleBtn;
if (this.Content is Panel rootPanel)
{
rootPanel.Children.Add(_currentNotification);
_currentNotification.IsOpen = true;
_notificationTimer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(EditorConstants.DefaultNotificationTimeout),
};
_notificationTimer.Tick += (s, e) => CloseExistingNotification();
_notificationTimer.Start();
}
}
private void CloseExistingNotification()
{
_notificationTimer?.Stop();
_notificationTimer = null;
if (_currentNotification != null && _currentNotification.IsOpen)
{
_currentNotification.IsOpen = false;
if (this.Content is Panel rootPanel && rootPanel.Children.Contains(_currentNotification))
{
rootPanel.Children.Remove(_currentNotification);
}
_currentNotification = null;
}
}
#endregion
#region IDisposable
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
CleanupKeyboardHook();
CloseExistingNotification();
Reset();
}
_disposed = true;
}
}
#endregion
}
}
#pragma warning restore SA1124 // Do not use regions

View File

@@ -46,6 +46,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Common" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
<PackageReference Include="Microsoft.WindowsAppSDK" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls.Markdown" />

View File

@@ -12,14 +12,33 @@
mc:Ignorable="d">
<Grid Padding="16">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<Grid.RowDefinitions>
<RowDefinition Height="64" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Add New Remapping Button -->
<Button
x:Name="NewRemappingBtn"
Grid.Row="0"
Height="36"
Margin="0,0,0,16"
Click="NewRemappingBtn_Click">
<StackPanel Orientation="Horizontal" Spacing="8">
<FontIcon
FontSize="14"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
Glyph="&#xE109;" />
<TextBlock VerticalAlignment="Center" Text="Add new remapping" />
</StackPanel>
</Button>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
<StackPanel Orientation="Vertical" Spacing="24">
<!-- Remappings Section -->
<!-- Remappings Section -->
<StackPanel Orientation="Vertical" Spacing="12">
<TextBlock
Style="{StaticResource SubtitleTextBlockStyle}"
Text="Remappings" />
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="Remappings" />
<Grid
HorizontalAlignment="Stretch"
Background="{ThemeResource LayerFillColorDefaultBrush}"
@@ -73,7 +92,8 @@
<ListView
Grid.Row="1"
Grid.ColumnSpan="4"
IsItemClickEnabled="False"
IsItemClickEnabled="True"
ItemClick="RemappingsList_ItemClick"
ItemsSource="{x:Bind RemappingList}"
SelectionMode="None">
<ListView.ItemTemplate>
@@ -152,11 +172,9 @@
</Grid>
</StackPanel>
<!-- Text Section -->
<!-- Text Section -->
<StackPanel Orientation="Vertical" Spacing="12">
<TextBlock
Style="{StaticResource SubtitleTextBlockStyle}"
Text="Text" />
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="Text" />
<Grid
HorizontalAlignment="Stretch"
Background="{ThemeResource LayerFillColorDefaultBrush}"
@@ -211,7 +229,8 @@
<ListView
Grid.Row="1"
Grid.ColumnSpan="4"
IsItemClickEnabled="False"
IsItemClickEnabled="True"
ItemClick="TextMappingsList_ItemClick"
ItemsSource="{x:Bind TextMappings}"
SelectionMode="None">
<ListView.ItemTemplate>
@@ -273,11 +292,9 @@
</Grid>
</StackPanel>
<!-- Programs Section -->
<!-- Programs Section -->
<StackPanel Orientation="Vertical" Spacing="12">
<TextBlock
Style="{StaticResource SubtitleTextBlockStyle}"
Text="Programs" />
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="Programs" />
<Grid
HorizontalAlignment="Stretch"
Background="{ThemeResource LayerFillColorDefaultBrush}"
@@ -320,7 +337,8 @@
<ListView
Grid.Row="1"
Grid.ColumnSpan="4"
IsItemClickEnabled="False"
IsItemClickEnabled="True"
ItemClick="ProgramShortcutsList_ItemClick"
ItemsSource="{x:Bind ProgramShortcuts}"
SelectionMode="None">
<ListView.ItemTemplate>
@@ -362,9 +380,7 @@
Grid.Column="1"
Orientation="Horizontal"
Spacing="8">
<TextBlock
VerticalAlignment="Center"
Text="{x:Bind AppToRun}" />
<TextBlock VerticalAlignment="Center" Text="{x:Bind AppToRun}" />
<FontIcon
VerticalAlignment="Center"
FontSize="14"
@@ -372,9 +388,7 @@
Glyph="&#xE946;">
<ToolTipService.ToolTip>
<TextBlock>
<Run
x:Uid="ProgramsPageArgumentsRun"
FontWeight="SemiBold" />
<Run x:Uid="ProgramsPageArgumentsRun" FontWeight="SemiBold" />
<Run Text="{x:Bind Args}" />
</TextBlock>
</ToolTipService.ToolTip>
@@ -387,11 +401,9 @@
</Grid>
</StackPanel>
<!-- URLs Section -->
<!-- URLs Section -->
<StackPanel Orientation="Vertical" Spacing="12">
<TextBlock
Style="{StaticResource SubtitleTextBlockStyle}"
Text="URLs" />
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="URLs" />
<Grid
HorizontalAlignment="Stretch"
Background="{ThemeResource LayerFillColorDefaultBrush}"
@@ -434,7 +446,8 @@
<ListView
Grid.Row="1"
Grid.ColumnSpan="4"
IsItemClickEnabled="False"
IsItemClickEnabled="True"
ItemClick="UrlShortcutsList_ItemClick"
ItemsSource="{x:Bind UrlShortcuts}"
SelectionMode="None">
<ListView.ItemTemplate>
@@ -485,5 +498,19 @@
</StackPanel>
</ScrollViewer>
<!-- Content Dialog for new remapping -->
<ContentDialog
x:Name="RemappingDialog"
Title="Add new remapping"
MinWidth="800"
MinHeight="500"
MaxWidth="900"
CloseButtonText="Cancel"
DefaultButton="Primary"
PrimaryButtonStyle="{StaticResource AccentButtonStyle}"
PrimaryButtonText="Save">
<controls:UnifiedMappingControl x:Name="UnifiedMappingControl" Margin="0,16,0,0" />
</ContentDialog>
</Grid>
</Page>

View File

@@ -5,24 +5,34 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using KeyboardManagerEditorUI.Controls;
using KeyboardManagerEditorUI.Helpers;
using KeyboardManagerEditorUI.Interop;
using KeyboardManagerEditorUI.Settings;
using ManagedCommon;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using static KeyboardManagerEditorUI.Interop.ShortcutKeyMapping;
namespace KeyboardManagerEditorUI.Pages
{
/// <summary>
/// A consolidated page that displays all mappings from Remappings, Text, Programs, and URLs pages.
/// </summary>
#pragma warning disable SA1124 // Do not use regions
public sealed partial class All : Page, IDisposable
{
private KeyboardMappingService? _mappingService;
private bool _disposed;
// Edit mode tracking
private bool _isEditMode;
private EditingItem? _editingItem;
public ObservableCollection<Remapping> RemappingList { get; } = new ObservableCollection<Remapping>();
public ObservableCollection<TextMapping> TextMappings { get; } = new ObservableCollection<TextMapping>();
@@ -34,6 +44,30 @@ namespace KeyboardManagerEditorUI.Pages
[DllImport("PowerToys.KeyboardManagerEditorLibraryWrapper.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private static extern void GetKeyDisplayName(int keyCode, [Out] StringBuilder keyName, int maxLength);
/// <summary>
/// Tracks what item is being edited and its type.
/// </summary>
private sealed class EditingItem
{
public enum ItemType
{
Remapping,
TextMapping,
ProgramShortcut,
UrlShortcut,
}
public ItemType Type { get; set; }
public object Item { get; set; } = null!;
public List<string> OriginalTriggerKeys { get; set; } = new();
public string? AppName { get; set; }
public bool IsAllApps { get; set; } = true;
}
public All()
{
this.InitializeComponent();
@@ -51,11 +85,444 @@ namespace KeyboardManagerEditorUI.Pages
this.Unloaded += All_Unloaded;
}
private void All_Unloaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
private void All_Unloaded(object sender, RoutedEventArgs e)
{
Dispose();
}
#region Dialog Show Methods
private async void NewRemappingBtn_Click(object sender, RoutedEventArgs e)
{
_isEditMode = false;
_editingItem = null;
// Reset the control before showing
UnifiedMappingControl.Reset();
RemappingDialog.Title = "Add new remapping";
await ShowRemappingDialog();
}
private async void RemappingsList_ItemClick(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is Remapping remapping)
{
_isEditMode = true;
_editingItem = new EditingItem
{
Type = EditingItem.ItemType.Remapping,
Item = remapping,
OriginalTriggerKeys = remapping.OriginalKeys.ToList(),
AppName = remapping.AppName,
IsAllApps = remapping.IsAllApps,
};
UnifiedMappingControl.Reset();
UnifiedMappingControl.SetTriggerKeys(remapping.OriginalKeys.ToList());
UnifiedMappingControl.SetActionType(UnifiedMappingControl.ActionType.KeyOrShortcut);
UnifiedMappingControl.SetActionKeys(remapping.RemappedKeys.ToList());
UnifiedMappingControl.SetAppSpecific(!remapping.IsAllApps, remapping.AppName);
RemappingDialog.Title = "Edit remapping";
await ShowRemappingDialog();
}
}
private async void TextMappingsList_ItemClick(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is TextMapping textMapping)
{
_isEditMode = true;
_editingItem = new EditingItem
{
Type = EditingItem.ItemType.TextMapping,
Item = textMapping,
OriginalTriggerKeys = textMapping.Keys.ToList(),
AppName = textMapping.AppName,
IsAllApps = textMapping.IsAllApps,
};
UnifiedMappingControl.Reset();
UnifiedMappingControl.SetTriggerKeys(textMapping.Keys.ToList());
UnifiedMappingControl.SetActionType(UnifiedMappingControl.ActionType.Text);
UnifiedMappingControl.SetTextContent(textMapping.Text);
UnifiedMappingControl.SetAppSpecific(!textMapping.IsAllApps, textMapping.AppName);
RemappingDialog.Title = "Edit text mapping";
await ShowRemappingDialog();
}
}
private async void ProgramShortcutsList_ItemClick(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is ProgramShortcut programShortcut)
{
_isEditMode = true;
_editingItem = new EditingItem
{
Type = EditingItem.ItemType.ProgramShortcut,
Item = programShortcut,
OriginalTriggerKeys = programShortcut.Shortcut.ToList(),
};
UnifiedMappingControl.Reset();
UnifiedMappingControl.SetTriggerKeys(programShortcut.Shortcut.ToList());
UnifiedMappingControl.SetActionType(UnifiedMappingControl.ActionType.OpenApp);
UnifiedMappingControl.SetProgramPath(programShortcut.AppToRun);
UnifiedMappingControl.SetProgramArgs(programShortcut.Args);
// Load additional settings from SettingsManager if available
if (!string.IsNullOrEmpty(programShortcut.Id) &&
SettingsManager.EditorSettings.ShortcutSettingsDictionary.TryGetValue(programShortcut.Id, out var settings))
{
var mapping = settings.Shortcut;
UnifiedMappingControl.SetStartInDirectory(mapping.StartInDirectory);
UnifiedMappingControl.SetElevationLevel(mapping.Elevation);
UnifiedMappingControl.SetVisibility(mapping.Visibility);
UnifiedMappingControl.SetIfRunningAction(mapping.IfRunningAction);
}
RemappingDialog.Title = "Edit program shortcut";
await ShowRemappingDialog();
}
}
private async void UrlShortcutsList_ItemClick(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is URLShortcut urlShortcut)
{
_isEditMode = true;
_editingItem = new EditingItem
{
Type = EditingItem.ItemType.UrlShortcut,
Item = urlShortcut,
OriginalTriggerKeys = urlShortcut.Shortcut.ToList(),
};
UnifiedMappingControl.Reset();
UnifiedMappingControl.SetTriggerKeys(urlShortcut.Shortcut.ToList());
UnifiedMappingControl.SetActionType(UnifiedMappingControl.ActionType.OpenUrl);
UnifiedMappingControl.SetUrl(urlShortcut.URL);
RemappingDialog.Title = "Edit URL shortcut";
await ShowRemappingDialog();
}
}
private async System.Threading.Tasks.Task ShowRemappingDialog()
{
// Hook up the primary button click handler
RemappingDialog.PrimaryButtonClick += RemappingDialog_PrimaryButtonClick;
// Show the dialog
await RemappingDialog.ShowAsync();
// Unhook the handler
RemappingDialog.PrimaryButtonClick -= RemappingDialog_PrimaryButtonClick;
// Reset edit mode
_isEditMode = false;
_editingItem = null;
// Cleanup keyboard hook after dialog closes
KeyboardHookHelper.Instance.CleanupHook();
}
#endregion
#region Save Logic
private void RemappingDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
if (_mappingService == null)
{
Logger.LogError("Mapping service is null, cannot save mapping");
args.Cancel = true;
return;
}
try
{
bool saved = false;
var actionType = UnifiedMappingControl.CurrentActionType;
List<string> triggerKeys = UnifiedMappingControl.GetTriggerKeys();
if (triggerKeys == null || triggerKeys.Count == 0)
{
// No trigger keys specified
args.Cancel = true;
return;
}
// If in edit mode, delete the existing mapping first
if (_isEditMode && _editingItem != null)
{
DeleteExistingMapping();
}
switch (actionType)
{
case UnifiedMappingControl.ActionType.KeyOrShortcut:
saved = SaveKeyOrShortcutMapping(triggerKeys);
break;
case UnifiedMappingControl.ActionType.Text:
saved = SaveTextMapping(triggerKeys);
break;
case UnifiedMappingControl.ActionType.OpenUrl:
saved = SaveUrlMapping(triggerKeys);
break;
case UnifiedMappingControl.ActionType.OpenApp:
saved = SaveProgramMapping(triggerKeys);
break;
case UnifiedMappingControl.ActionType.MouseClick:
// Not implemented yet
args.Cancel = true;
return;
}
if (saved)
{
LoadAllMappings();
}
else
{
args.Cancel = true;
}
}
catch (Exception ex)
{
Logger.LogError("Error saving mapping: " + ex.Message);
args.Cancel = true;
}
}
private void DeleteExistingMapping()
{
if (_editingItem == null || _mappingService == null)
{
return;
}
try
{
var originalKeys = _editingItem.OriginalTriggerKeys;
switch (_editingItem.Type)
{
case EditingItem.ItemType.Remapping:
if (_editingItem.Item is Remapping remapping)
{
RemappingHelper.DeleteRemapping(_mappingService, remapping);
}
break;
case EditingItem.ItemType.TextMapping:
if (originalKeys.Count == 1)
{
int originalKey = _mappingService.GetKeyCodeFromName(originalKeys[0]);
if (originalKey != 0)
{
_mappingService.DeleteSingleKeyToTextMapping(originalKey);
}
}
else
{
string originalKeysString = string.Join(";", originalKeys.Select(k => _mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
_mappingService.DeleteShortcutMapping(originalKeysString, _editingItem.IsAllApps ? string.Empty : _editingItem.AppName ?? string.Empty);
}
break;
case EditingItem.ItemType.ProgramShortcut:
if (_editingItem.Item is ProgramShortcut programShortcut)
{
if (originalKeys.Count == 1)
{
int originalKey = _mappingService.GetKeyCodeFromName(originalKeys[0]);
if (originalKey != 0)
{
_mappingService.DeleteSingleKeyMapping(originalKey);
}
}
else
{
string originalKeysString = string.Join(";", originalKeys.Select(k => _mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
_mappingService.DeleteShortcutMapping(originalKeysString);
}
if (!string.IsNullOrEmpty(programShortcut.Id))
{
SettingsManager.RemoveShortcutKeyMappingFromSettings(programShortcut.Id);
}
}
break;
case EditingItem.ItemType.UrlShortcut:
if (originalKeys.Count == 1)
{
int originalKey = _mappingService.GetKeyCodeFromName(originalKeys[0]);
if (originalKey != 0)
{
_mappingService.DeleteSingleKeyMapping(originalKey);
}
}
else
{
string originalKeysString = string.Join(";", originalKeys.Select(k => _mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
_mappingService.DeleteShortcutMapping(originalKeysString);
}
break;
}
}
catch (Exception ex)
{
Logger.LogError("Error deleting existing mapping: " + ex.Message);
}
}
private bool SaveKeyOrShortcutMapping(List<string> triggerKeys)
{
List<string> actionKeys = UnifiedMappingControl.GetActionKeys();
bool isAppSpecific = UnifiedMappingControl.GetIsAppSpecific();
string appName = UnifiedMappingControl.GetAppName();
if (actionKeys == null || actionKeys.Count == 0)
{
return false;
}
return RemappingHelper.SaveMapping(_mappingService!, triggerKeys, actionKeys, isAppSpecific, appName);
}
private bool SaveTextMapping(List<string> triggerKeys)
{
string textContent = UnifiedMappingControl.GetTextContent();
bool isAppSpecific = UnifiedMappingControl.GetIsAppSpecific();
string appName = UnifiedMappingControl.GetAppName();
if (string.IsNullOrEmpty(textContent))
{
return false;
}
if (triggerKeys.Count == 1)
{
// Single key to text mapping
int originalKey = _mappingService!.GetKeyCodeFromName(triggerKeys[0]);
if (originalKey != 0)
{
bool saved = _mappingService.AddSingleKeyToTextMapping(originalKey, textContent);
if (saved)
{
return _mappingService.SaveSettings();
}
}
}
else
{
// Shortcut to text mapping
string originalKeysString = string.Join(";", triggerKeys.Select(k => _mappingService!.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
bool saved;
if (isAppSpecific && !string.IsNullOrEmpty(appName))
{
saved = _mappingService!.AddShortcutMapping(originalKeysString, textContent, appName, ShortcutOperationType.RemapText);
}
else
{
saved = _mappingService!.AddShortcutMapping(originalKeysString, textContent, operationType: ShortcutOperationType.RemapText);
}
if (saved)
{
return _mappingService.SaveSettings();
}
}
return false;
}
private bool SaveUrlMapping(List<string> triggerKeys)
{
string url = UnifiedMappingControl.GetUrl();
if (string.IsNullOrEmpty(url))
{
return false;
}
string originalKeysString = string.Join(";", triggerKeys.Select(k => _mappingService!.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
ShortcutKeyMapping shortcutKeyMapping = new ShortcutKeyMapping()
{
OperationType = ShortcutOperationType.OpenUri,
OriginalKeys = originalKeysString,
TargetKeys = originalKeysString,
UriToOpen = url,
};
bool saved = _mappingService!.AddShorcutMapping(shortcutKeyMapping);
if (saved)
{
return _mappingService.SaveSettings();
}
return false;
}
private bool SaveProgramMapping(List<string> triggerKeys)
{
string programPath = UnifiedMappingControl.GetProgramPath();
string programArgs = UnifiedMappingControl.GetProgramArgs();
string startInDir = UnifiedMappingControl.GetStartInDirectory();
ElevationLevel elevationLevel = UnifiedMappingControl.GetElevationLevel();
StartWindowType visibility = UnifiedMappingControl.GetVisibility();
ProgramAlreadyRunningAction ifRunningAction = UnifiedMappingControl.GetIfRunningAction();
if (string.IsNullOrEmpty(programPath))
{
return false;
}
string originalKeysString = string.Join(";", triggerKeys.Select(k => _mappingService!.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
ShortcutKeyMapping shortcutKeyMapping = new ShortcutKeyMapping()
{
OperationType = ShortcutOperationType.RunProgram,
OriginalKeys = originalKeysString,
TargetKeys = originalKeysString,
ProgramPath = programPath,
ProgramArgs = programArgs,
StartInDirectory = startInDir,
IfRunningAction = ifRunningAction,
Visibility = visibility,
Elevation = elevationLevel,
};
bool saved = _mappingService!.AddShorcutMapping(shortcutKeyMapping);
if (saved)
{
_mappingService.SaveSettings();
SettingsManager.AddShortcutKeyMappingToSettings(shortcutKeyMapping);
return true;
}
return false;
}
#endregion
#region Load Methods
private void LoadAllMappings()
{
LoadRemappings();
@@ -244,6 +711,10 @@ namespace KeyboardManagerEditorUI.Pages
return keyName.ToString();
}
#endregion
#region IDisposable
public void Dispose()
{
Dispose(true);
@@ -263,5 +734,8 @@ namespace KeyboardManagerEditorUI.Pages
_disposed = true;
}
}
#endregion
}
}
#pragma warning restore SA1124 // Do not use regions