apply xaml styling

This commit is contained in:
Zach Teutsch
2026-02-10 19:45:33 -05:00
parent c2e6299622
commit 06eaeff74a
21 changed files with 414 additions and 4063 deletions

View File

@@ -17,10 +17,10 @@
<!-- Other app resources here -->
<x:Double x:Key="ContentDialogMaxWidth">960</x:Double>
<!-- Icons -->
<!-- Icons -->
<x:String x:Key="ArrowIconData">M12.001 2C17.5238 2 22.001 6.47715 22.001 12C22.001 17.5228 17.5238 22 12.001 22C6.47813 22 2.00098 17.5228 2.00098 12C2.00098 6.47715 6.47813 2 12.001 2ZM12.7813 7.46897L12.6972 7.39635C12.4362 7.2027 12.078 7.20031 11.8146 7.38918L11.7206 7.46897L11.648 7.55308C11.4544 7.81407 11.452 8.17229 11.6409 8.43568L11.7206 8.52963L14.4403 11.2493H7.75027L7.6485 11.2561C7.31571 11.3013 7.05227 11.5647 7.00712 11.8975L7.00027 11.9993L7.00712 12.1011C7.05227 12.4339 7.31571 12.6973 7.6485 12.7424L7.75027 12.7493H14.4403L11.72 15.4697L11.6474 15.5538C11.4295 15.8474 11.4536 16.264 11.7198 16.5303C11.9861 16.7967 12.4027 16.8209 12.6964 16.6032L12.7805 16.5306L16.782 12.5306L16.8547 12.4464C17.0484 12.1854 17.0508 11.8272 16.8619 11.5638L16.7821 11.4698L12.7813 7.46897L12.6972 7.39635L12.7813 7.46897Z</x:String>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@@ -1,119 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="KeyboardManagerEditorUI.Controls.AppPageInputControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helper="using:KeyboardManagerEditorUI.Helpers"
xmlns:local="using:KeyboardManagerEditorUI.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Loaded="UserControl_Loaded"
mc:Ignorable="d">
<StackPanel
Width="360"
Height="600"
Orientation="Vertical"
Spacing="8">
<!-- Shortcut section -->
<TextBlock
x:Uid="AppPageInputControlShortcutTextBlock"
Margin="0,12,0,8"
FontWeight="SemiBold" />
<ToggleButton
x:Name="ShortcutToggleBtn"
Padding="0,24,0,24"
HorizontalAlignment="Stretch"
Checked="ShortcutToggleBtn_Checked"
Style="{StaticResource CustomShortcutToggleButtonStyle}">
<ToggleButton.Content>
<ItemsControl x:Name="ShortcutKeys">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<controls:WrapPanel HorizontalSpacing="4" Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:KeyVisual Content="{Binding}" Style="{StaticResource DefaultKeyVisualStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ToggleButton.Content>
</ToggleButton>
<StackPanel
Margin="0,8,0,0"
Orientation="Horizontal"
Spacing="4">
<TextBox
x:Name="ProgramPathInput"
x:Uid="AppPageInputControlExampleTextBox"
Width="220" />
<Button
x:Name="ProgramPathSelectButton"
x:Uid="AppPageInputControlPathSelectButton"
Width="120"
VerticalAlignment="Bottom"
Click="ProgramPathSelectButton_Click" />
</StackPanel>
<TextBlock
x:Uid="AppPageInputControlExtraOptionsTextBlock"
Margin="0,12,0,8"
FontWeight="SemiBold" />
<StackPanel
Margin="0,8,0,0"
Orientation="Vertical"
Spacing="8">
<TextBox
x:Name="ProgramArgsInput"
x:Uid="AppPageInputControlArgumentsTextBox"
Width="360" />
<StackPanel Orientation="Horizontal" Spacing="4">
<TextBox
x:Name="StartInPathInput"
x:Uid="AppPageInputControlStartInTextBox"
Width="220" />
<Button
x:Name="StartInSelectButton"
x:Uid="AppPageInputControlStartInSelectButton"
Width="120"
VerticalAlignment="Bottom"
Click="StartInSelectButton_Click" />
</StackPanel>
<ComboBox
x:Name="ElevationComboBox"
x:Uid="AppPageInputControlElevationComboBox"
Width="360"
SelectedValue="Normal">
<x:String>Normal</x:String>
<x:String>Elevated</x:String>
<x:String>Different user</x:String>
</ComboBox>
<ComboBox
x:Name="IfRunningComboBox"
x:Uid="AppPageInputControlIfRunningComboBox"
Width="360"
SelectedValue="Show window">
<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="AppPageInputControlVisibilityComboBox"
Width="360"
SelectedValue="Normal">
<x:String>Normal</x:String>
<x:String>Hidden</x:String>
<x:String>Minimized</x:String>
<x:String>Maximized</x:String>
</ComboBox>
</StackPanel>
</StackPanel>
</UserControl>

View File

@@ -1,253 +0,0 @@
// 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 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;
namespace KeyboardManagerEditorUI.Controls
{
public sealed partial class AppPageInputControl : UserControl, IKeyboardHookTarget
{
private ObservableCollection<string> _shortcutKeys = new ObservableCollection<string>();
private TeachingTip? currentNotification;
private DispatcherTimer? notificationTimer;
// private bool _internalUpdate;
public AppPageInputControl()
{
this.InitializeComponent();
this.ShortcutKeys.ItemsSource = _shortcutKeys;
ShortcutToggleBtn.IsChecked = true;
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
KeyboardHookHelper.Instance.ActivateHook(this);
ProgramPathInput.GotFocus += ProgramInputBox_GotFocus;
ProgramArgsInput.GotFocus += InputArgs_GotFocus;
}
private void ShortcutToggleBtn_Checked(object sender, RoutedEventArgs e)
{
if (ShortcutToggleBtn.IsChecked == true)
{
KeyboardHookHelper.Instance.ActivateHook(this);
}
else
{
KeyboardHookHelper.Instance.CleanupHook();
}
}
public void OnKeyDown(VirtualKey key, List<string> formattedKeys)
{
_shortcutKeys.Clear();
foreach (var keyName in formattedKeys)
{
_shortcutKeys.Add(keyName);
}
}
private void ProgramInputBox_GotFocus(object sender, RoutedEventArgs e)
{
// Clean up the keyboard hook when the text box gains focus
KeyboardHookHelper.Instance.CleanupHook();
if (ShortcutToggleBtn != null && ShortcutToggleBtn.IsChecked == true)
{
ShortcutToggleBtn.IsChecked = false;
}
}
public void OnInputLimitReached()
{
ShowNotificationTip("Shortcuts can only have up to 4 modifier keys");
}
private void InputArgs_GotFocus(object sender, RoutedEventArgs e)
{
// if (_internalUpdate)
// {
// return;
// }
KeyboardHookHelper.Instance.CleanupHook();
if (ShortcutToggleBtn != null && ShortcutToggleBtn.IsChecked == true)
{
ShortcutToggleBtn.IsChecked = false;
}
}
public void ShowNotificationTip(string message)
{
CloseExistingNotification();
currentNotification = new TeachingTip
{
Title = "Input Limit",
Subtitle = message,
IsLightDismissEnabled = true,
PreferredPlacement = TeachingTipPlacementMode.Top,
XamlRoot = this.XamlRoot,
IconSource = new SymbolIconSource { Symbol = Symbol.Important },
Target = ShortcutToggleBtn,
};
if (this.Content is Panel rootPanel)
{
rootPanel.Children.Add(currentNotification);
currentNotification.IsOpen = true;
notificationTimer = new DispatcherTimer();
notificationTimer.Interval = TimeSpan.FromMilliseconds(EditorConstants.DefaultNotificationTimeout);
notificationTimer.Tick += (s, e) =>
{
CloseExistingNotification();
};
notificationTimer.Start();
}
}
private void CloseExistingNotification()
{
if (notificationTimer != null)
{
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;
}
}
public void ClearKeys()
{
_shortcutKeys.Clear();
}
public List<string> GetShortcutKeys()
{
List<string> keys = new List<string>();
foreach (var key in _shortcutKeys)
{
keys.Add(key);
}
return keys;
}
public string GetProgramPathContent()
{
return ProgramPathInput.Text;
}
public string GetProgramArgsContent()
{
return ProgramArgsInput.Text;
}
public string GetStartInDirectory()
{
return StartInPathInput.Text;
}
public ElevationLevel GetElevationLevel()
{
return (ElevationLevel)ElevationComboBox.SelectedIndex;
}
public StartWindowType GetVisibility()
{
return (StartWindowType)VisibilityComboBox.SelectedIndex;
}
public ProgramAlreadyRunningAction GetIfRunningAction()
{
return (ProgramAlreadyRunningAction)IfRunningComboBox.SelectedIndex;
}
public void SetShortcutKeys(List<string> keys)
{
if (keys != null)
{
_shortcutKeys.Clear();
foreach (var key in keys)
{
_shortcutKeys.Add(key);
}
}
}
public void SetProgramPathContent(string text)
{
ProgramPathInput.Text = text;
}
public void SetProgramArgsContent(string text)
{
ProgramArgsInput.Text = text;
}
private async void ProgramPathSelectButton_Click(object sender, RoutedEventArgs e)
{
var picker = new FileOpenPicker();
// Get the window handle (HWND) for the current window
var hwnd = WindowNative.GetWindowHandle(App.MainWindow);
InitializeWithWindow.Initialize(picker, hwnd);
// Set file type filter to .exe
picker.FileTypeFilter.Add(".exe");
// Show the picker
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();
// Get the window handle (HWND) for the current window
var hwnd = WindowNative.GetWindowHandle(App.MainWindow);
InitializeWithWindow.Initialize(picker, hwnd);
// Set file type filter (required even for folders)
picker.FileTypeFilter.Add("*");
// Show the picker
StorageFolder folder = await picker.PickSingleFolderAsync();
if (folder != null)
{
StartInPathInput.Text = folder.Path;
}
}
}
}

View File

@@ -1,112 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="KeyboardManagerEditorUI.Controls.InputControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helper="using:KeyboardManagerEditorUI.Helpers"
xmlns:local="using:KeyboardManagerEditorUI.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Loaded="UserControl_Loaded"
mc:Ignorable="d">
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="240" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="240" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock x:Uid="InputControlOriginalKeysTextBlock" Margin="0,12,0,0" />
<Grid Grid.Column="2">
<TextBlock x:Uid="InputControlNewKeysTextBlock" Margin="0,12,0,0" />
</Grid>
<Grid Grid.Row="1" Margin="0,8,0,0">
<ToggleButton
x:Name="OriginalToggleBtn"
MinHeight="86"
Padding="8,24,8,24"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Checked="OriginalToggleBtn_Checked"
Style="{StaticResource CustomShortcutToggleButtonStyle}">
<ToggleButton.Content>
<ItemsControl x:Name="OriginalKeys">
<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>
</Grid>
<TextBlock
Grid.Row="1"
Grid.Column="1"
Margin="24,0,24,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
Text="&#xE0AB;" />
<Grid
Grid.Row="1"
Grid.Column="2"
Margin="0,8,0,0">
<ToggleButton
x:Name="RemappedToggleBtn"
MinHeight="86"
Padding="8,24,8,24"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Checked="RemappedToggleBtn_Checked"
Style="{StaticResource CustomShortcutToggleButtonStyle}">
<ToggleButton.Content>
<ItemsControl x:Name="RemappedKeys">
<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>
</Grid>
</Grid>
<CheckBox
x:Name="AllAppsCheckBox"
x:Uid="InputControlAllAppsCheckBox"
Margin="0,24,0,12" />
<TextBox
x:Name="AppNameTextBox"
x:Uid="InputControlAppNameTextBox"
IsEnabled="{Binding ElementName=AllAppsCheckBox, Path=IsChecked}" />
</StackPanel>
</UserControl>

View File

@@ -1,419 +0,0 @@
// 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 KeyboardManagerEditorUI.Interop;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Windows.System;
namespace KeyboardManagerEditorUI.Controls
{
public sealed partial class InputControl : UserControl, IDisposable, IKeyboardHookTarget
{
// Collection to store original and remapped keys
private ObservableCollection<string> _originalKeys = new ObservableCollection<string>();
private ObservableCollection<string> _remappedKeys = new ObservableCollection<string>();
// TeachingTip for notifications
private TeachingTip? currentNotification;
private DispatcherTimer? notificationTimer;
private bool _disposed;
public static readonly DependencyProperty InputModeProperty =
DependencyProperty.Register(
"InputMode",
typeof(KeyInputMode),
typeof(InputControl),
new PropertyMetadata(KeyInputMode.OriginalKeys));
public KeyInputMode InputMode
{
get { return (KeyInputMode)GetValue(InputModeProperty); }
set { SetValue(InputModeProperty, value); }
}
public InputControl()
{
this.InitializeComponent();
this.OriginalKeys.ItemsSource = _originalKeys;
this.RemappedKeys.ItemsSource = _remappedKeys;
this.Unloaded += InputControl_Unloaded;
// Set the default focus state
OriginalToggleBtn.IsChecked = true;
// Ensure AllAppsCheckBox is in the correct state initially
UpdateAllAppsCheckBoxState();
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
AllAppsCheckBox.Checked += AllAppsCheckBox_Checked;
AllAppsCheckBox.Unchecked += AllAppsCheckBox_Unchecked;
AppNameTextBox.GotFocus += AppNameTextBox_GotFocus;
}
private void InputControl_Unloaded(object sender, RoutedEventArgs e)
{
// Reset the control when it is unloaded
Reset();
}
public void OnKeyDown(VirtualKey key, List<string> formattedKeys)
{
if (InputMode == KeyInputMode.RemappedKeys)
{
_remappedKeys.Clear();
foreach (var keyName in formattedKeys)
{
_remappedKeys.Add(keyName);
}
}
else
{
_originalKeys.Clear();
foreach (var keyName in formattedKeys)
{
_originalKeys.Add(keyName);
}
}
UpdateAllAppsCheckBoxState();
}
public void ClearKeys()
{
if (InputMode == KeyInputMode.RemappedKeys)
{
_remappedKeys.Clear();
}
else
{
_originalKeys.Clear();
}
}
public void OnInputLimitReached()
{
ShowNotificationTip("Shortcuts can only have up to 4 modifier keys");
}
public void CleanupKeyboardHook()
{
KeyboardHookHelper.Instance.CleanupHook();
}
private void RemappedToggleBtn_Checked(object sender, RoutedEventArgs e)
{
// Only set NewMode to true if RemappedToggleBtn is checked
if (RemappedToggleBtn.IsChecked == true)
{
InputMode = KeyInputMode.RemappedKeys;
// Make sure OriginalToggleBtn is unchecked
if (OriginalToggleBtn.IsChecked == true)
{
OriginalToggleBtn.IsChecked = false;
}
KeyboardHookHelper.Instance.ActivateHook(this);
}
else
{
CleanupKeyboardHook();
}
}
private void OriginalToggleBtn_Checked(object sender, RoutedEventArgs e)
{
// Only set NewMode to false if OriginalToggleBtn is checked
if (OriginalToggleBtn.IsChecked == true)
{
InputMode = KeyInputMode.OriginalKeys;
// Make sure RemappedToggleBtn is unchecked
if (RemappedToggleBtn.IsChecked == true)
{
RemappedToggleBtn.IsChecked = false;
}
KeyboardHookHelper.Instance.ActivateHook(this);
}
}
private void AllAppsCheckBox_Checked(object sender, RoutedEventArgs e)
{
if (RemappedToggleBtn != null && RemappedToggleBtn.IsChecked == true)
{
RemappedToggleBtn.IsChecked = false;
}
if (OriginalToggleBtn != null && OriginalToggleBtn.IsChecked == true)
{
OriginalToggleBtn.IsChecked = false;
}
CleanupKeyboardHook();
AppNameTextBox.Visibility = Visibility.Visible;
}
private void AllAppsCheckBox_Unchecked(object sender, RoutedEventArgs e)
{
AppNameTextBox.Visibility = Visibility.Collapsed;
}
private void AppNameTextBox_GotFocus(object sender, RoutedEventArgs e)
{
// Reset the focus state when the AppNameTextBox is focused
if (RemappedToggleBtn != null && RemappedToggleBtn.IsChecked == true)
{
RemappedToggleBtn.IsChecked = false;
}
if (OriginalToggleBtn != null && OriginalToggleBtn.IsChecked == true)
{
OriginalToggleBtn.IsChecked = false;
}
CleanupKeyboardHook();
}
public void SetRemappedKeys(List<string> keys)
{
_remappedKeys.Clear();
if (keys != null)
{
foreach (var key in keys)
{
_remappedKeys.Add(key);
}
}
UpdateAllAppsCheckBoxState();
}
public void SetOriginalKeys(List<string> keys)
{
_originalKeys.Clear();
if (keys != null)
{
foreach (var key in keys)
{
_originalKeys.Add(key);
}
}
}
public void SetApp(bool isSpecificApp, string appName)
{
if (isSpecificApp)
{
AllAppsCheckBox.IsChecked = true;
AppNameTextBox.Text = appName;
AppNameTextBox.Visibility = Visibility.Visible;
}
else
{
AllAppsCheckBox.IsChecked = false;
AppNameTextBox.Visibility = Visibility.Collapsed;
}
}
public List<string> GetOriginalKeys()
{
return _originalKeys.ToList();
}
public List<string> GetRemappedKeys()
{
return _remappedKeys.ToList();
}
public bool GetIsAppSpecific()
{
return AllAppsCheckBox.IsChecked ?? false;
}
public string GetAppName()
{
return AppNameTextBox.Text ?? string.Empty;
}
public void SetUpToggleButtonInitialStatus()
{
// Ensure OriginalToggleBtn is checked
if (OriginalToggleBtn != null && OriginalToggleBtn.IsChecked != true)
{
OriginalToggleBtn.IsChecked = true;
}
// Make sure RemappedToggleBtn is not checked
if (RemappedToggleBtn != null && RemappedToggleBtn.IsChecked == true)
{
RemappedToggleBtn.IsChecked = false;
}
}
public void UpdateAllAppsCheckBoxState()
{
// Only enable app-specific remapping for shortcuts (multiple keys)
bool isShortcut = _originalKeys.Count > 1;
AllAppsCheckBox.IsEnabled = isShortcut;
// If it's not a shortcut, ensure the checkbox is unchecked and app textbox is hidden
if (!isShortcut)
{
AllAppsCheckBox.IsChecked = false;
AppNameTextBox.Visibility = Visibility.Collapsed;
}
}
public void ShowNotificationTip(string message)
{
// If there's already an active notification, close and remove it first
CloseExistingNotification();
// Create a new notification
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 toggle button that triggered the notification
currentNotification.Target = InputMode == KeyInputMode.RemappedKeys ? RemappedToggleBtn : OriginalToggleBtn;
// Add the notification to the root panel and show it
if (this.Content is Panel rootPanel)
{
rootPanel.Children.Add(currentNotification);
currentNotification.IsOpen = true;
// Create a timer to auto-dismiss the notification
notificationTimer = new DispatcherTimer();
notificationTimer.Interval = TimeSpan.FromMilliseconds(EditorConstants.DefaultNotificationTimeout);
notificationTimer.Tick += (s, e) =>
{
CloseExistingNotification();
notificationTimer = null;
};
notificationTimer.Start();
}
}
// Helper method to close existing notifications
private void CloseExistingNotification()
{
// Stop any running timer
if (notificationTimer != null)
{
notificationTimer.Stop();
notificationTimer = null;
}
// Close and remove any existing notification
if (currentNotification != null && currentNotification.IsOpen)
{
currentNotification.IsOpen = false;
if (this.Content is Panel rootPanel)
{
rootPanel.Children.Remove(currentNotification);
}
currentNotification = null;
}
}
public void ResetToggleButtons()
{
// Reset toggle button status without clearing the key displays
if (RemappedToggleBtn != null)
{
RemappedToggleBtn.IsChecked = false;
}
if (OriginalToggleBtn != null)
{
OriginalToggleBtn.IsChecked = false;
}
}
public void Reset()
{
// Reset displayed keys
_originalKeys.Clear();
_remappedKeys.Clear();
// Reset toggle button status
if (RemappedToggleBtn != null)
{
RemappedToggleBtn.IsChecked = false;
}
if (OriginalToggleBtn != null)
{
OriginalToggleBtn.IsChecked = false;
}
InputMode = KeyInputMode.OriginalKeys;
// Reset app name text box
if (AppNameTextBox != null)
{
AppNameTextBox.Text = string.Empty;
}
UpdateAllAppsCheckBoxState();
// Close any existing notifications
CloseExistingNotification();
// Reset the focus status
if (this.FocusState != FocusState.Unfocused)
{
this.IsTabStop = false;
this.IsTabStop = true;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
CleanupKeyboardHook();
CloseExistingNotification();
Reset();
}
_disposed = true;
}
}
}
}

View File

@@ -1,72 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="KeyboardManagerEditorUI.Controls.TextPageInputControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helper="using:KeyboardManagerEditorUI.Helpers"
xmlns:local="using:KeyboardManagerEditorUI.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Loaded="UserControl_Loaded"
mc:Ignorable="d">
<StackPanel
Width="360"
Height="360"
Orientation="Vertical">
<!-- Shortcut section -->
<TextBlock
x:Uid="TextPageInputControlShortcutKeysTextBlock"
Margin="0,12,0,8"
FontWeight="SemiBold" />
<ToggleButton
x:Name="ShortcutToggleBtn"
Padding="0,24,0,24"
HorizontalAlignment="Stretch"
Checked="ShortcutToggleBtn_Checked"
Style="{StaticResource CustomShortcutToggleButtonStyle}">
<ToggleButton.Content>
<ItemsControl x:Name="ShortcutKeys">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<controls:WrapPanel HorizontalSpacing="4" Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:KeyVisual Content="{Binding}" Style="{StaticResource DefaultKeyVisualStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ToggleButton.Content>
</ToggleButton>
<!-- Text section -->
<TextBox
x:Name="TextContentBox"
x:Uid="TextPageInputControlTextContentTextBox"
Height="120"
Margin="0,8,0,0"
AcceptsReturn="True"
Background="{ThemeResource TextControlBackgroundFocused}"
BorderBrush="{ThemeResource ControlStrokeColorDefaultBrush}"
FontSize="13"
TextWrapping="Wrap" />
<!-- App specific section -->
<CheckBox
x:Name="AllAppsCheckBox"
x:Uid="TextPageInputControlAllAppsCheckBox"
Margin="0,8,0,0"
Foreground="{ThemeResource TextFillColorPrimaryBrush}" />
<TextBox
x:Name="AppNameTextBox"
x:Uid="TextPageInputControlAllAppsTextBox"
Background="{ThemeResource TextControlBackgroundFocused}"
BorderBrush="{ThemeResource ControlStrokeColorDefaultBrush}"
IsEnabled="{Binding ElementName=AllAppsCheckBox, Path=IsChecked}" />
</StackPanel>
</UserControl>

View File

@@ -1,252 +0,0 @@
// 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 KeyboardManagerEditorUI.Helpers;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.System;
namespace KeyboardManagerEditorUI.Controls
{
public sealed partial class TextPageInputControl : UserControl, IKeyboardHookTarget
{
private ObservableCollection<string> _shortcutKeys = new ObservableCollection<string>();
private TeachingTip? currentNotification;
private DispatcherTimer? notificationTimer;
private bool _internalUpdate;
public TextPageInputControl()
{
this.InitializeComponent();
this.ShortcutKeys.ItemsSource = _shortcutKeys;
ShortcutToggleBtn.IsChecked = true;
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
KeyboardHookHelper.Instance.ActivateHook(this);
TextContentBox.GotFocus += TextContentBox_GotFocus;
AllAppsCheckBox.Checked += AllAppsCheckBox_Changed;
AllAppsCheckBox.Unchecked += AllAppsCheckBox_Changed;
AppNameTextBox.GotFocus += AppNameTextBox_GotFocus;
AppNameTextBox.Visibility = AllAppsCheckBox.IsChecked == true ? Visibility.Visible : Visibility.Collapsed;
}
private void ShortcutToggleBtn_Checked(object sender, RoutedEventArgs e)
{
if (ShortcutToggleBtn.IsChecked == true)
{
KeyboardHookHelper.Instance.ActivateHook(this);
}
else
{
KeyboardHookHelper.Instance.CleanupHook();
}
}
public void OnKeyDown(VirtualKey key, List<string> formattedKeys)
{
_shortcutKeys.Clear();
foreach (var keyName in formattedKeys)
{
_shortcutKeys.Add(keyName);
}
UpdateAllAppsCheckBoxState();
}
private void TextContentBox_GotFocus(object sender, RoutedEventArgs e)
{
// Clean up the keyboard hook when the text box gains focus
KeyboardHookHelper.Instance.CleanupHook();
if (ShortcutToggleBtn != null && ShortcutToggleBtn.IsChecked == true)
{
ShortcutToggleBtn.IsChecked = false;
}
}
public void OnInputLimitReached()
{
ShowNotificationTip("Shortcuts can only have up to 4 modifier keys");
}
public void UpdateAllAppsCheckBoxState()
{
// Only enable app-specific remapping for shortcuts (multiple keys)
bool isShortcut = _shortcutKeys.Count > 1;
AllAppsCheckBox.IsEnabled = isShortcut;
// If it's not a shortcut, ensure the checkbox is unchecked and app textbox is hidden
try
{
if (!isShortcut)
{
_internalUpdate = true;
AllAppsCheckBox.IsChecked = false;
AppNameTextBox.Visibility = Visibility.Collapsed;
}
else if (AllAppsCheckBox.IsChecked == true)
{
AppNameTextBox.Visibility = Visibility.Visible;
}
}
finally
{
_internalUpdate = false;
}
}
private void AllAppsCheckBox_Changed(object sender, RoutedEventArgs e)
{
if (_internalUpdate)
{
return;
}
KeyboardHookHelper.Instance.CleanupHook();
if (ShortcutToggleBtn != null && ShortcutToggleBtn.IsChecked == true)
{
ShortcutToggleBtn.IsChecked = false;
}
AppNameTextBox.Visibility = AllAppsCheckBox.IsChecked == true ? Visibility.Visible : Visibility.Collapsed;
}
private void AppNameTextBox_GotFocus(object sender, RoutedEventArgs e)
{
if (_internalUpdate)
{
return;
}
KeyboardHookHelper.Instance.CleanupHook();
if (ShortcutToggleBtn != null && ShortcutToggleBtn.IsChecked == true)
{
ShortcutToggleBtn.IsChecked = false;
}
}
public void ShowNotificationTip(string message)
{
CloseExistingNotification();
currentNotification = new TeachingTip
{
Title = "Input Limit",
Subtitle = message,
IsLightDismissEnabled = true,
PreferredPlacement = TeachingTipPlacementMode.Top,
XamlRoot = this.XamlRoot,
IconSource = new SymbolIconSource { Symbol = Symbol.Important },
Target = ShortcutToggleBtn,
};
if (this.Content is Panel rootPanel)
{
rootPanel.Children.Add(currentNotification);
currentNotification.IsOpen = true;
notificationTimer = new DispatcherTimer();
notificationTimer.Interval = TimeSpan.FromMilliseconds(EditorConstants.DefaultNotificationTimeout);
notificationTimer.Tick += (s, e) =>
{
CloseExistingNotification();
};
notificationTimer.Start();
}
}
private void CloseExistingNotification()
{
if (notificationTimer != null)
{
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;
}
}
public void ClearKeys()
{
_shortcutKeys.Clear();
UpdateAllAppsCheckBoxState();
}
public List<string> GetShortcutKeys()
{
List<string> keys = new List<string>();
foreach (var key in _shortcutKeys)
{
keys.Add(key);
}
return keys;
}
public string GetTextContent()
{
return TextContentBox.Text;
}
public bool GetIsAppSpecific()
{
return AllAppsCheckBox.IsChecked ?? false;
}
public string GetAppName()
{
return AllAppsCheckBox.IsChecked == true ? AppNameTextBox.Text : string.Empty;
}
public void SetShortcutKeys(List<string> keys)
{
if (keys != null)
{
_shortcutKeys.Clear();
foreach (var key in keys)
{
_shortcutKeys.Add(key);
}
}
UpdateAllAppsCheckBoxState();
}
public void SetTextContent(string text)
{
TextContentBox.Text = text;
}
public void SetAppSpecific(bool isAppSpecific, string appName)
{
AllAppsCheckBox.IsChecked = isAppSpecific;
if (isAppSpecific)
{
AppNameTextBox.Text = appName;
}
}
}
}

View File

@@ -1,58 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="KeyboardManagerEditorUI.Controls.UrlPageInputControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helper="using:KeyboardManagerEditorUI.Helpers"
xmlns:local="using:KeyboardManagerEditorUI.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Loaded="UserControl_Loaded"
mc:Ignorable="d">
<StackPanel
Width="360"
Height="360"
Orientation="Vertical"
Spacing="8">
<!-- Shortcut section -->
<TextBlock
x:Uid="UrlPageInputControlShortcutTextBlock"
Margin="0,12,0,8"
FontWeight="SemiBold" />
<ToggleButton
x:Name="ShortcutToggleBtn"
Padding="0,24,0,24"
HorizontalAlignment="Stretch"
Checked="ShortcutToggleBtn_Checked"
Style="{StaticResource CustomShortcutToggleButtonStyle}">
<ToggleButton.Content>
<ItemsControl x:Name="ShortcutKeys">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<controls:WrapPanel HorizontalSpacing="4" Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:KeyVisual Content="{Binding}" Style="{StaticResource DefaultKeyVisualStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ToggleButton.Content>
</ToggleButton>
<TextBox
x:Name="UrlPathInput"
x:Uid="UrlPageInputControlToOpenTextBox"
Margin="0,8,0,0"
AcceptsReturn="True"
Background="{ThemeResource TextControlBackgroundFocused}"
BorderBrush="{ThemeResource ControlStrokeColorDefaultBrush}"
FontSize="13"
TextWrapping="Wrap"
Width="360" />
</StackPanel>
</UserControl>

View File

@@ -1,167 +0,0 @@
// 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 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;
namespace KeyboardManagerEditorUI.Controls
{
public sealed partial class UrlPageInputControl : UserControl, IKeyboardHookTarget
{
private ObservableCollection<string> _shortcutKeys = new ObservableCollection<string>();
private TeachingTip? currentNotification;
private DispatcherTimer? notificationTimer;
// private bool _internalUpdate;
public UrlPageInputControl()
{
this.InitializeComponent();
this.ShortcutKeys.ItemsSource = _shortcutKeys;
ShortcutToggleBtn.IsChecked = true;
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
KeyboardHookHelper.Instance.ActivateHook(this);
UrlPathInput.GotFocus += UrlInputBox_GotFocus;
}
private void ShortcutToggleBtn_Checked(object sender, RoutedEventArgs e)
{
if (ShortcutToggleBtn.IsChecked == true)
{
KeyboardHookHelper.Instance.ActivateHook(this);
}
else
{
KeyboardHookHelper.Instance.CleanupHook();
}
}
public void OnKeyDown(VirtualKey key, List<string> formattedKeys)
{
_shortcutKeys.Clear();
foreach (var keyName in formattedKeys)
{
_shortcutKeys.Add(keyName);
}
}
private void UrlInputBox_GotFocus(object sender, RoutedEventArgs e)
{
// Clean up the keyboard hook when the text box gains focus
KeyboardHookHelper.Instance.CleanupHook();
if (ShortcutToggleBtn != null && ShortcutToggleBtn.IsChecked == true)
{
ShortcutToggleBtn.IsChecked = false;
}
}
public void OnInputLimitReached()
{
ShowNotificationTip("Shortcuts can only have up to 4 modifier keys");
}
public void ShowNotificationTip(string message)
{
CloseExistingNotification();
currentNotification = new TeachingTip
{
Title = "Input Limit",
Subtitle = message,
IsLightDismissEnabled = true,
PreferredPlacement = TeachingTipPlacementMode.Top,
XamlRoot = this.XamlRoot,
IconSource = new SymbolIconSource { Symbol = Symbol.Important },
Target = ShortcutToggleBtn,
};
if (this.Content is Panel rootPanel)
{
rootPanel.Children.Add(currentNotification);
currentNotification.IsOpen = true;
notificationTimer = new DispatcherTimer();
notificationTimer.Interval = TimeSpan.FromMilliseconds(EditorConstants.DefaultNotificationTimeout);
notificationTimer.Tick += (s, e) =>
{
CloseExistingNotification();
};
notificationTimer.Start();
}
}
private void CloseExistingNotification()
{
if (notificationTimer != null)
{
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;
}
}
public void ClearKeys()
{
_shortcutKeys.Clear();
}
public List<string> GetShortcutKeys()
{
List<string> keys = new List<string>();
foreach (var key in _shortcutKeys)
{
keys.Add(key);
}
return keys;
}
public string GetUrlPathContent()
{
return UrlPathInput.Text;
}
public void SetShortcutKeys(List<string> keys)
{
if (keys != null)
{
_shortcutKeys.Clear();
foreach (var key in keys)
{
_shortcutKeys.Add(key);
}
}
}
public void SetUrlPathContent(string text)
{
UrlPathInput.Text = text;
}
}
}

View File

@@ -27,9 +27,6 @@
<ItemGroup>
<None Remove="Assets\Keyboard.ico" />
<None Remove="Pages\Programs.xaml" />
<None Remove="Pages\Text.xaml" />
<None Remove="Pages\URLs.xaml" />
<None Remove="Styles\Colors.xaml" />
</ItemGroup>
@@ -74,33 +71,10 @@
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\UrlPageInputControl.xaml">
<SubType>Designer</SubType>
</Page>
<Page Update="Styles\Button.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Pages\URLs.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Pages\Text.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Pages\Programs.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Pages\Remappings.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<!--
Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution

View File

@@ -39,45 +39,5 @@
Grid.Row="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
<!--<NavigationView
x:Name="RootView"
Grid.Row="1"
IsBackButtonVisible="Collapsed"
IsBackEnabled="False"
IsPaneToggleButtonVisible="False"
IsSettingsVisible="False"
PaneDisplayMode="Top"
SelectionChanged="RootView_SelectionChanged">
<NavigationView.MenuItems>
<NavigationViewItem Content="All" Tag="All">
<NavigationViewItem.Icon>
<FontIcon Glyph="&#xE8FD;" />
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Content="Remappings" Tag="Remappings">
<NavigationViewItem.Icon>
<FontIcon Glyph="&#xEDA7;" />
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Content="Text" Tag="Text">
<NavigationViewItem.Icon>
<FontIcon Glyph="&#xE8D2;" />
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Content="Programs" Tag="Programs">
<NavigationViewItem.Icon>
<FontIcon Glyph="&#xECAA;" />
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Content="URLs" Tag="URLs">
<NavigationViewItem.Icon>
<FontIcon Glyph="&#xE8A7;" />
</NavigationViewItem.Icon>
</NavigationViewItem>
</NavigationView.MenuItems>
<NavigationView.Content>
<Frame x:Name="NavigationFrame" Margin="0,0,0,0" />
</NavigationView.Content>
</NavigationView>-->
</Grid>
</winuiex:WindowEx>

View File

@@ -61,23 +61,5 @@ namespace KeyboardManagerEditorUI
this.Activated -= MainWindow_Activated;
this.Closed -= MainWindow_Closed;
}
private void RootView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
{
// Cleanup the keyboard hook when the selected page changes
/* KeyboardHookHelper.Instance.CleanupHook();
if (args.SelectedItem is NavigationViewItem selectedItem)
{
switch ((string)selectedItem.Tag)
{
case "All": NavigationFrame.Navigate(typeof(Pages.All)); break;
case "Remappings": NavigationFrame.Navigate(typeof(Pages.Remappings)); break;
case "Programs": NavigationFrame.Navigate(typeof(Pages.Programs)); break;
case "Text": NavigationFrame.Navigate(typeof(Pages.Text)); break;
case "URLs": NavigationFrame.Navigate(typeof(Pages.URLs)); break;
}
} */
}
}
}

View File

@@ -1,185 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<Page
x:Class="KeyboardManagerEditorUI.Pages.Programs"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:KeyboardManagerEditorUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helper="using:KeyboardManagerEditorUI.Helpers"
xmlns:local="using:KeyboardManagerEditorUI.Pages"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Padding="16">
<StackPanel Orientation="Vertical" Spacing="12">
<TextBlock
x:Uid="ProgramsPageInstructionTextBlock"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap" />
<Button
x:Name="NewShortcutBtn"
Height="36"
Margin="0,12,0,0"
Click="NewShortcutBtn_Click">
<StackPanel Orientation="Horizontal" Spacing="8">
<FontIcon
FontSize="14"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
Glyph="&#xE109;" />
<TextBlock
x:Uid="ProgramsPageNewTextBlock"
VerticalAlignment="Center" />
</StackPanel>
</Button>
<Grid
HorizontalAlignment="Stretch"
Background="{ThemeResource LayerFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="{StaticResource OverlayCornerRadius}">
<Grid.RowDefinitions>
<RowDefinition Height="48" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="348" />
<ColumnDefinition Width="236" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="84" />
</Grid.ColumnDefinitions>
<TextBlock
x:Uid="ProgramsPageShortcutTextBlock"
Grid.Column="0"
Margin="16,-2,0,0"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<AppBarSeparator
Grid.Column="1"
Margin="-6,4,0,4"
HorizontalAlignment="Left" />
<TextBlock
x:Uid="ProgramsPageProgramsTextBlock"
Grid.Column="1"
Margin="12,-2,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<Rectangle
Grid.ColumnSpan="4"
Height="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
Fill="{ThemeResource CardStrokeColorDefaultBrush}" />
<ListView
Grid.Row="1"
Grid.ColumnSpan="4"
IsItemClickEnabled="True"
ItemClick="ListView_ItemClick"
ItemsSource="{x:Bind Shortcuts}"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate x:DataType="helper:ProgramShortcut">
<Grid Height="48">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="348" />
<ColumnDefinition Width="476" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="84" />
</Grid.ColumnDefinitions>
<Rectangle
Grid.ColumnSpan="5"
Height="1"
Margin="-16,0,-16,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
Fill="{ThemeResource CardStrokeColorDefaultBrush}"
Opacity="0.8" />
<ItemsControl
Grid.Column="0"
VerticalAlignment="Center"
ItemsSource="{x:Bind Shortcut}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="4" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:KeyVisual
HorizontalAlignment="Left"
Content="{Binding}"
Style="{StaticResource DefaultKeyVisualStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<StackPanel
Grid.Column="1"
Orientation="Horizontal"
Spacing="8">
<TextBlock
VerticalAlignment="Center"
Text="{x:Bind AppToRun}" />
<FontIcon
VerticalAlignment="Center"
FontSize="14"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
Glyph="&#xE946;">
<ToolTipService.ToolTip>
<TextBlock>
<Run
x:Uid="ProgramsPageArgumentsRun"
FontWeight="SemiBold" />
<Run Text="{x:Bind Args}" />
</TextBlock>
</ToolTipService.ToolTip>
</FontIcon>
</StackPanel>
<StackPanel
Orientation="Horizontal"
Spacing="8"
Grid.ColumnSpan="4"
Margin="0,0,4,0"
Padding="8,4"
HorizontalAlignment="Right"
VerticalAlignment="Center">
<ToggleSwitch
IsOn="{x:Bind IsActive}"
Toggled="ToggleSwitch_Toggled"/>
<Button
x:Uid="ProgramsPageDeleteButton"
AutomationProperties.Name="Delete program shortcut"
Background="Transparent"
BorderThickness="0"
Click="DeleteButton_Click">
<FontIcon
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="16"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Glyph="&#xE74D;" />
</Button>
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</StackPanel>
<ContentDialog
x:Name="KeyDialog"
x:Uid="ProgramsPageKeyDialog"
Width="480"
MinWidth="600"
MaxWidth="600"
MinHeight="600"
MaxHeight="600"
Height="600"
PrimaryButtonClick="KeyDialog_PrimaryButtonClick"
PrimaryButtonStyle="{StaticResource AccentButtonStyle}">
<Grid>
<controls:AppPageInputControl x:Name="AppShortcutControl" />
</Grid>
</ContentDialog>
</Grid>
</Page>

View File

@@ -1,336 +0,0 @@
// 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.Globalization;
using System.Linq;
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
{
public sealed partial class Programs : Page, IDisposable
{
private KeyboardMappingService? _mappingService;
// Flag to indicate if the user is editing an existing mapping
private bool _isEditMode;
private ProgramShortcut? _editingMapping;
private bool _disposed;
// The list of text mappings
public ObservableCollection<ProgramShortcut> Shortcuts { get; set; } = new ObservableCollection<ProgramShortcut> { };
public Programs()
{
this.InitializeComponent();
try
{
_mappingService = new KeyboardMappingService();
LoadProgramShortcuts();
}
catch (Exception ex)
{
Logger.LogError("Failed to initialize KeyboardMappingService: " + ex.Message);
}
this.Unloaded += Text_Unloaded;
}
private void Text_Unloaded(object sender, RoutedEventArgs e)
{
Dispose();
}
private void LoadProgramShortcuts()
{
if (_mappingService == null)
{
return;
}
Shortcuts.Clear();
foreach (var shortcutSettings in SettingsManager.GetShortcutSettingsByOperationType(ShortcutOperationType.RunProgram))
{
ShortcutKeyMapping mapping = shortcutSettings.Shortcut;
string[] originalKeyCodes = mapping.OriginalKeys.Split(';');
var originalKeyNames = new List<string>();
foreach (var keyCode in originalKeyCodes)
{
if (int.TryParse(keyCode, out int code))
{
originalKeyNames.Add(_mappingService.GetKeyDisplayName(code));
}
}
Shortcuts.Add(new ProgramShortcut
{
Shortcut = originalKeyNames,
AppToRun = mapping.ProgramPath,
Args = mapping.ProgramArgs,
IsActive = shortcutSettings.IsActive,
Id = shortcutSettings.Id,
});
}
}
private async void NewShortcutBtn_Click(object sender, RoutedEventArgs e)
{
_isEditMode = false;
_editingMapping = null;
AppShortcutControl.ClearKeys();
AppShortcutControl.SetProgramPathContent(string.Empty);
AppShortcutControl.SetProgramArgsContent(string.Empty);
await KeyDialog.ShowAsync();
}
private async void ListView_ItemClick(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is ProgramShortcut selectedMapping)
{
_isEditMode = true;
_editingMapping = selectedMapping;
AppShortcutControl.SetShortcutKeys(selectedMapping.Shortcut);
AppShortcutControl.SetProgramPathContent(selectedMapping.AppToRun);
AppShortcutControl.SetProgramArgsContent(selectedMapping.Args);
await KeyDialog.ShowAsync();
}
}
private void KeyDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
if (_mappingService == null)
{
return;
}
List<string> keys = AppShortcutControl.GetShortcutKeys();
string programPath = AppShortcutControl.GetProgramPathContent();
string programArgs = AppShortcutControl.GetProgramArgsContent();
ElevationLevel elevationLevel = AppShortcutControl.GetElevationLevel();
StartWindowType startWindowType = AppShortcutControl.GetVisibility();
ProgramAlreadyRunningAction programAlreadyRunningAction = AppShortcutControl.GetIfRunningAction();
// Validate inputs
ValidationErrorType errorType = ValidationHelper.ValidateProgramOrUrlMapping(keys, false, string.Empty, _mappingService);
if (errorType != ValidationErrorType.NoError)
{
ShowValidationError(errorType, args);
return;
}
bool saved = false;
try
{
// Delete existing mapping if in edit mode
if (_isEditMode && _editingMapping != null)
{
if (_editingMapping.Shortcut.Count == 1)
{
int originalKey = _mappingService.GetKeyCodeFromName(_editingMapping.Shortcut[0]);
if (originalKey != 0)
{
_mappingService.DeleteSingleKeyMapping(originalKey);
}
}
else
{
string originalKeys = string.Join(";", _editingMapping.Shortcut.Select(k => _mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
_mappingService.DeleteShortcutMapping(originalKeys);
}
SettingsManager.RemoveShortcutKeyMappingFromSettings(_editingMapping.Id);
}
// Shortcut to text mapping
string originalKeysString = string.Join(";", keys.Select(k => _mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
// if (isAppSpecific && !string.IsNullOrEmpty(appName))
// {
// saved = _mappingService.AddShortcutMapping(originalKeysString, programPath, appName, ShortcutOperationType.RemapText);
// }
// else
// {
ShortcutKeyMapping shortcutKeyMapping = new ShortcutKeyMapping()
{
OperationType = ShortcutOperationType.RunProgram,
OriginalKeys = originalKeysString,
TargetKeys = originalKeysString,
ProgramPath = programPath,
ProgramArgs = programArgs,
IfRunningAction = programAlreadyRunningAction,
Visibility = startWindowType,
Elevation = elevationLevel,
};
saved = _mappingService.AddShorcutMapping(shortcutKeyMapping);
if (saved)
{
_mappingService.SaveSettings();
SettingsManager.AddShortcutKeyMappingToSettings(shortcutKeyMapping);
LoadProgramShortcuts(); // Refresh the list
}
}
catch (Exception ex)
{
Logger.LogError("Error saving text mapping: " + ex.Message);
args.Cancel = true;
}
}
private void DeleteButton_Click(object sender, RoutedEventArgs e)
{
if (_mappingService == null || !(sender is Button button) || !(button.DataContext is ProgramShortcut shortcut))
{
return;
}
try
{
bool deleted = false;
if (shortcut.Shortcut.Count == 1)
{
// Single key mapping
int originalKey = _mappingService.GetKeyCodeFromName(shortcut.Shortcut[0]);
if (originalKey != 0)
{
deleted = _mappingService.DeleteSingleKeyToTextMapping(originalKey);
}
}
else
{
// Shortcut mapping
string originalKeys = string.Join(";", shortcut.Shortcut.Select(k => _mappingService.GetKeyCodeFromName(k)));
deleted = _mappingService.DeleteShortcutMapping(originalKeys);
}
Shortcuts.Remove(shortcut);
SettingsManager.RemoveShortcutKeyMappingFromSettings(shortcut.Id);
if (deleted)
{
_mappingService.SaveSettings();
}
LoadProgramShortcuts();
}
catch (Exception ex)
{
Logger.LogError("Error deleting text mapping: " + ex.Message);
}
}
private void ShowValidationError(ValidationErrorType errorType, ContentDialogButtonClickEventArgs args)
{
// if (ValidationHelper.ValidationMessages.TryGetValue(errorType, out (string Title, string Message) error))
// {
// ValidationTip.Title = error.Title;
// ValidationTip.Subtitle = error.Message;
// ValidationTip.IsOpen = true;
// args.Cancel = true;
// }
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_mappingService?.Dispose();
_mappingService = null;
}
_disposed = true;
}
}
private void ToggleSwitch_Toggled(object sender, RoutedEventArgs e)
{
if (sender is ToggleSwitch toggleSwitch && toggleSwitch.DataContext is ProgramShortcut shortcut && _mappingService != null)
{
try
{
if (toggleSwitch.IsOn)
{
bool saved = false;
ShortcutKeyMapping shortcutKeyMapping = SettingsManager.EditorSettings.ShortcutSettingsDictionary[shortcut.Id].Shortcut;
saved = _mappingService.AddShorcutMapping(shortcutKeyMapping);
if (saved)
{
shortcut.IsActive = true;
_mappingService.SaveSettings();
SettingsManager.ToggleShortcutKeyMappingActiveState(shortcut.Id);
}
else
{
toggleSwitch.IsOn = false;
}
}
else
{
bool deleted = false;
if (shortcut.Shortcut.Count == 1)
{
// Single key mapping
int originalKey = _mappingService.GetKeyCodeFromName(shortcut.Shortcut[0]);
if (originalKey != 0)
{
deleted = _mappingService.DeleteSingleKeyToTextMapping(originalKey);
}
}
else
{
// Shortcut mapping
string originalKeys = string.Join(";", shortcut.Shortcut.Select(k => _mappingService.GetKeyCodeFromName(k)));
deleted = _mappingService.DeleteShortcutMapping(originalKeys);
}
if (deleted)
{
shortcut.IsActive = false;
SettingsManager.ToggleShortcutKeyMappingActiveState(shortcut.Id);
_mappingService.SaveSettings();
}
else
{
toggleSwitch.IsOn = true;
}
LoadProgramShortcuts();
}
}
catch (Exception ex)
{
Logger.LogError("Error toggling shortcut active state: " + ex.Message);
}
}
}
}
}

View File

@@ -1,218 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<Page
x:Class="KeyboardManagerEditorUI.Pages.Remappings"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:KeyboardManagerEditorUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helper="using:KeyboardManagerEditorUI.Helpers"
xmlns:local="using:KeyboardManagerEditorUI.Pages"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
mc:Ignorable="d">
<Grid Padding="16">
<StackPanel Orientation="Vertical" Spacing="12">
<TextBlock
x:Name="RemapInstruction"
x:Uid="RemappingsPageInstructionTextBlock"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap" />
<Button
x:Name="NewRemappingBtn"
Height="36"
Margin="0,12,0,0"
Click="NewRemappingBtn_Click">
<StackPanel Orientation="Horizontal" Spacing="8">
<FontIcon
FontSize="14"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
Glyph="&#xE109;" />
<TextBlock x:Uid="RemappingsPageNewTextBlock" VerticalAlignment="Center" />
</StackPanel>
</Button>
<Grid
HorizontalAlignment="Stretch"
Background="{ThemeResource LayerFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="{StaticResource OverlayCornerRadius}">
<Grid.RowDefinitions>
<RowDefinition Height="48" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="236" />
<ColumnDefinition Width="236" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="84" />
</Grid.ColumnDefinitions>
<TextBlock
x:Uid="RemappingsPageOriginalKeysTextBlock"
Grid.Column="0"
Margin="16,-2,0,0"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<AppBarSeparator
Grid.Column="1"
Margin="-6,4,0,4"
HorizontalAlignment="Left" />
<TextBlock
x:Uid="RemappingsPageNewKeysTextBlock"
Grid.Column="1"
Margin="12,-2,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<AppBarSeparator
Grid.Column="2"
Margin="-6,4,0,4"
HorizontalAlignment="Left" />
<TextBlock
x:Uid="RemmapingsPageApplicableAppsTextBlock"
Grid.Column="2"
Margin="12,-2,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<Rectangle
Grid.ColumnSpan="4"
Height="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
Fill="{ThemeResource CardStrokeColorDefaultBrush}" />
<ListView
Grid.Row="1"
Grid.ColumnSpan="4"
IsItemClickEnabled="True"
ItemClick="ListView_ItemClick"
ItemsSource="{x:Bind RemappingList}"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate x:DataType="helper:Remapping">
<Grid Height="Auto" MinHeight="48">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="236" />
<ColumnDefinition Width="Auto" MinWidth="236" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="84" />
</Grid.ColumnDefinitions>
<Rectangle
Grid.ColumnSpan="5"
Height="1"
Margin="-16,0,-16,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
Fill="{ThemeResource CardStrokeColorDefaultBrush}"
Opacity="0.8" />
<ItemsControl
Grid.Column="0"
VerticalAlignment="Center"
IsEnabled="{x:Bind IsEnabled, Mode=OneWay}"
ItemsSource="{x:Bind Shortcut}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<tkcontrols:WrapPanel
MaxWidth="230"
HorizontalSpacing="4"
Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:KeyVisual
HorizontalAlignment="Left"
Content="{Binding}"
Style="{StaticResource DefaultKeyVisualStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl
Grid.Column="1"
Margin="0,6,0,6"
VerticalAlignment="Center"
IsEnabled="{x:Bind IsEnabled, Mode=OneWay}"
ItemsSource="{x:Bind RemappedKeys}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<tkcontrols:WrapPanel
MaxWidth="230"
HorizontalSpacing="4"
Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:KeyVisual
HorizontalAlignment="Left"
Content="{Binding}"
Style="{StaticResource AccentKeyVisualStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock
Grid.Column="2"
Margin="-4,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="12"
Text="{x:Bind AppName}" />
<Button
x:Uid="RemappingsPageDeleteButton"
Grid.Column="3"
Margin="0,0,4,0"
Padding="8,4"
HorizontalAlignment="Right"
VerticalAlignment="Center"
AutomationProperties.Name="Delete remapping"
Background="Transparent"
BorderThickness="0"
Click="DeleteButton_Click">
<FontIcon
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="16"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Glyph="&#xE74D;" />
</Button>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</StackPanel>
<ContentDialog
x:Name="KeyDialog"
x:Uid="RemappingsPageKeyDialog"
MinWidth="600"
MaxWidth="600"
PrimaryButtonStyle="{StaticResource AccentButtonStyle}">
<Grid>
<controls:InputControl x:Name="RemappingControl" />
</Grid>
</ContentDialog>
<TeachingTip
x:Name="OrphanedKeysTeachingTip"
x:Uid="RemmapingsPageOrphanedKeysTeachingTip"
ActionButtonClick="OrphanedKeysTeachingTip_ActionButtonClick"
ActionButtonStyle="{StaticResource AccentButtonStyle}"
CloseButtonClick="OrphanedKeysTeachingTip_CloseButtonClick"
IsLightDismissEnabled="False"
PreferredPlacement="Center">
<TeachingTip.IconSource>
<SymbolIconSource Symbol="Important" />
</TeachingTip.IconSource>
</TeachingTip>
<TeachingTip
x:Name="ValidationTeachingTip"
x:Uid="RemappingsPageValidationTeachingTip"
CloseButtonClick="ValidationTeachingTip_CloseButtonClick"
IsLightDismissEnabled="True"
PreferredPlacement="Center">
<TeachingTip.IconSource>
<SymbolIconSource Symbol="Important" />
</TeachingTip.IconSource>
</TeachingTip>
</Grid>
</Page>

View File

@@ -1,370 +0,0 @@
// 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.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text;
using System.Threading.Tasks;
using KeyboardManagerEditorUI.Helpers;
using KeyboardManagerEditorUI.Interop;
using ManagedCommon;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using Microsoft.Windows.ApplicationModel.Resources;
using Windows.ApplicationModel;
using Windows.Foundation;
using Windows.Foundation.Collections;
using static KeyboardManagerEditorUI.Helpers.ValidationHelper;
namespace KeyboardManagerEditorUI.Pages
{
/// <summary>
/// The Remapping page that allow users to configure a single key or shortcut to a new key or shortcut
/// </summary>
public sealed partial class Remappings : Page, IDisposable
{
private KeyboardMappingService? _mappingService;
// Flag to indicate if the user is editing an existing remapping
private bool _isEditMode;
private Remapping? _editingRemapping;
private bool _disposed;
// The list of single key mappings
public ObservableCollection<KeyMapping> SingleKeyMappings { get; } = new ObservableCollection<KeyMapping>();
// The list of shortcut key mappings
public ObservableCollection<ShortcutKeyMapping> ShortcutKeyMappings { get; } = new ObservableCollection<ShortcutKeyMapping>();
// The full list of remappings
public ObservableCollection<Remapping> RemappingList { get; set; }
public Remappings()
{
this.InitializeComponent();
RemappingList = new ObservableCollection<Remapping>();
_mappingService = new KeyboardMappingService();
// Load all existing remappings
LoadMappings();
this.Unloaded += Remappings_Unloaded;
}
private void Remappings_Unloaded(object sender, RoutedEventArgs e)
{
// Make sure we unregister the handler when the page is unloaded
UnregisterWindowActivationHandler();
KeyboardHookHelper.Instance.CleanupHook();
}
private void RegisterWindowActivationHandler()
{
// Get the current window that contains this page
if (App.MainWindow is Window window)
{
// Register for window activation events
window.Activated += Dialog_WindowActivated;
}
}
private void UnregisterWindowActivationHandler()
{
// Unregister to prevent memory leaks
if (App.MainWindow is Window window)
{
window.Activated -= Dialog_WindowActivated;
}
}
private void Dialog_WindowActivated(object sender, WindowActivatedEventArgs args)
{
// When window is deactivated (user switched to another app)
if (args.WindowActivationState == WindowActivationState.Deactivated)
{
// Make sure to cleanup the keyboard hook when the window loses focus
KeyboardHookHelper.Instance.CleanupHook();
RemappingControl.ResetToggleButtons();
RemappingControl.UpdateAllAppsCheckBoxState();
}
}
private async void NewRemappingBtn_Click(object sender, RoutedEventArgs e)
{
_isEditMode = false;
_editingRemapping = null;
RemappingControl.SetOriginalKeys(new List<string>());
RemappingControl.SetRemappedKeys(new List<string>());
RemappingControl.SetApp(false, string.Empty);
RemappingControl.SetUpToggleButtonInitialStatus();
RegisterWindowActivationHandler();
// Show the dialog to add a new remapping
KeyDialog.PrimaryButtonClick += KeyDialog_PrimaryButtonClick;
await KeyDialog.ShowAsync();
KeyDialog.PrimaryButtonClick -= KeyDialog_PrimaryButtonClick;
UnregisterWindowActivationHandler();
KeyboardHookHelper.Instance.CleanupHook();
}
private async void ListView_ItemClick(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is Remapping selectedRemapping && selectedRemapping.IsEnabled)
{
// Set to edit mode
_isEditMode = true;
_editingRemapping = selectedRemapping;
RemappingControl.SetOriginalKeys(selectedRemapping.Shortcut);
RemappingControl.SetRemappedKeys(selectedRemapping.RemappedKeys);
RemappingControl.SetApp(!selectedRemapping.IsAllApps, selectedRemapping.AppName);
RemappingControl.SetUpToggleButtonInitialStatus();
RegisterWindowActivationHandler();
KeyDialog.PrimaryButtonClick += KeyDialog_PrimaryButtonClick;
await KeyDialog.ShowAsync();
KeyDialog.PrimaryButtonClick -= KeyDialog_PrimaryButtonClick;
UnregisterWindowActivationHandler();
KeyboardHookHelper.Instance.CleanupHook();
// Reset the edit status
_isEditMode = false;
_editingRemapping = null;
}
}
private void KeyDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
List<string> originalKeys = RemappingControl.GetOriginalKeys();
List<string> remappedKeys = RemappingControl.GetRemappedKeys();
bool isAppSpecific = RemappingControl.GetIsAppSpecific();
string appName = RemappingControl.GetAppName();
// Make sure _mappingService is not null before validating and saving
if (_mappingService == null)
{
Logger.LogError("Mapping service is null, cannot validate mapping");
return;
}
// Validate the remapping
ValidationErrorType errorType = ValidationHelper.ValidateKeyMapping(
originalKeys, remappedKeys, isAppSpecific, appName, _mappingService, _isEditMode, _editingRemapping);
if (errorType != ValidationErrorType.NoError)
{
ShowValidationError(errorType, args);
return;
}
// Check for orphaned keys
if (originalKeys.Count == 1 && _mappingService != null)
{
int originalKeyCode = _mappingService.GetKeyCodeFromName(originalKeys[0]);
if (IsKeyOrphaned(originalKeyCode, _mappingService))
{
string keyName = _mappingService.GetKeyDisplayName(originalKeyCode);
OrphanedKeysTeachingTip.Target = RemappingControl;
OrphanedKeysTeachingTip.Subtitle = $"The key {keyName} will become orphaned (inaccessible) after remapping. Please confirm if you want to proceed.";
OrphanedKeysTeachingTip.Tag = args;
OrphanedKeysTeachingTip.IsOpen = true;
args.Cancel = true;
return;
}
}
// If in edit mode, delete the existing remapping before saving the new one
if (_isEditMode && _editingRemapping != null)
{
if (!RemappingHelper.DeleteRemapping(_mappingService!, _editingRemapping))
{
return;
}
}
// If no errors, proceed to save the remapping
bool saved = RemappingHelper.SaveMapping(_mappingService!, originalKeys, remappedKeys, isAppSpecific, appName);
if (saved)
{
// Display the remapping in the list after saving
LoadMappings();
}
}
private void DeleteButton_Click(object sender, RoutedEventArgs e)
{
if (sender is Button button && button.DataContext is Remapping remapping)
{
if (RemappingHelper.DeleteRemapping(_mappingService!, remapping))
{
LoadMappings();
}
}
}
private void ValidationTeachingTip_CloseButtonClick(TeachingTip sender, object args)
{
sender.IsOpen = false;
}
private void OrphanedKeysTeachingTip_ActionButtonClick(TeachingTip sender, object args)
{
// User pressed continue anyway button
sender.IsOpen = false;
if (_isEditMode && _editingRemapping != null)
{
if (!RemappingHelper.DeleteRemapping(_mappingService!, _editingRemapping))
{
return;
}
}
bool saved = RemappingHelper.SaveMapping(
_mappingService!, RemappingControl.GetOriginalKeys(), RemappingControl.GetRemappedKeys(), RemappingControl.GetIsAppSpecific(), RemappingControl.GetAppName());
if (saved)
{
KeyDialog.Hide();
LoadMappings();
}
}
private void OrphanedKeysTeachingTip_CloseButtonClick(TeachingTip sender, object args)
{
// Just close the teaching tip if the user canceled
sender.IsOpen = false;
}
private void LoadMappings()
{
if (_mappingService == null)
{
return;
}
SingleKeyMappings.Clear();
ShortcutKeyMappings.Clear();
RemappingList.Clear();
// Load all single key mappings
foreach (var mapping in _mappingService.GetSingleKeyMappings())
{
SingleKeyMappings.Add(mapping);
string[] targetKeyCodes = mapping.TargetKey.Split(';');
var targetKeyNames = new List<string>();
foreach (var keyCode in targetKeyCodes)
{
if (int.TryParse(keyCode, out int code))
{
targetKeyNames.Add(_mappingService.GetKeyDisplayName(code));
}
}
RemappingList.Add(new Remapping
{
Shortcut = new List<string> { _mappingService.GetKeyDisplayName(mapping.OriginalKey) },
RemappedKeys = targetKeyNames,
IsAllApps = true,
});
}
// Load all shortcut key mappings
foreach (var mapping in _mappingService.GetShortcutMappingsByType(ShortcutOperationType.RemapShortcut))
{
ShortcutKeyMappings.Add(mapping);
string[] originalKeyCodes = mapping.OriginalKeys.Split(';');
string[] targetKeyCodes = mapping.TargetKeys.Split(';');
var originalKeyNames = new List<string>();
var targetKeyNames = new List<string>();
foreach (var keyCode in originalKeyCodes)
{
if (int.TryParse(keyCode, out int code))
{
originalKeyNames.Add(_mappingService.GetKeyDisplayName(code));
}
}
foreach (var keyCode in targetKeyCodes)
{
if (int.TryParse(keyCode, out int code))
{
targetKeyNames.Add(_mappingService.GetKeyDisplayName(code));
}
}
RemappingList.Add(new Remapping
{
Shortcut = originalKeyNames,
RemappedKeys = targetKeyNames,
IsAllApps = string.IsNullOrEmpty(mapping.TargetApp),
AppName = string.IsNullOrEmpty(mapping.TargetApp) ? string.Empty : mapping.TargetApp,
});
}
}
private bool ShowValidationError(ValidationErrorType errorType, ContentDialogButtonClickEventArgs args)
{
var (title, message) = ValidationMessages[errorType];
ValidationTeachingTip.Title = title;
ValidationTeachingTip.Subtitle = message;
ValidationTeachingTip.Target = RemappingControl;
ValidationTeachingTip.Tag = args;
ValidationTeachingTip.IsOpen = true;
args.Cancel = true;
return false;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// Dispose managed resources
_mappingService?.Dispose();
_mappingService = null;
}
_disposed = true;
}
}
}
}

View File

@@ -1,200 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<Page
x:Class="KeyboardManagerEditorUI.Pages.Text"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:KeyboardManagerEditorUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helper="using:KeyboardManagerEditorUI.Helpers"
xmlns:local="using:KeyboardManagerEditorUI.Pages"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tkcontrols="using:CommunityToolkit.WinUI.UI.Controls"
mc:Ignorable="d">
<Grid Padding="16">
<StackPanel Orientation="Vertical" Spacing="12">
<TextBlock
x:Uid="TextPageInstructionTextBlock"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap" />
<Button
x:Name="NewShortcutBtn"
Height="36"
Margin="0,12,0,0"
Click="NewShortcutBtn_Click">
<StackPanel Orientation="Horizontal" Spacing="8">
<FontIcon
FontSize="14"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
Glyph="&#xE109;" />
<TextBlock x:Uid="TextPageNewTextBlock" VerticalAlignment="Center" />
</StackPanel>
</Button>
<Grid
HorizontalAlignment="Stretch"
Background="{ThemeResource LayerFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="{StaticResource OverlayCornerRadius}">
<Grid.RowDefinitions>
<RowDefinition Height="48" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="236" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="120" />
<ColumnDefinition Width="84" />
</Grid.ColumnDefinitions>
<TextBlock
x:Uid="TextPageOriginalKeysTextBlock"
Grid.Column="0"
Margin="16,-2,0,0"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<AppBarSeparator
Grid.Column="1"
Margin="-6,4,0,4"
HorizontalAlignment="Left" />
<TextBlock
x:Uid="TextPageTextTextBlock"
Grid.Column="1"
Margin="12,-2,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<AppBarSeparator
Grid.Column="2"
Margin="-6,4,0,4"
HorizontalAlignment="Left" />
<TextBlock
x:Uid="TextPageApplicableAppsTextBlock"
Grid.Column="2"
Margin="12,-2,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<Rectangle
Grid.Row="0"
Grid.ColumnSpan="4"
Height="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
Fill="{ThemeResource CardStrokeColorDefaultBrush}" />
<ListView
x:Name="MappingsList"
Grid.Row="1"
Grid.ColumnSpan="4"
IsItemClickEnabled="True"
ItemClick="ListView_ItemClick"
ItemsSource="{x:Bind TextMappings}"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate x:DataType="helper:TextMapping">
<Grid MinHeight="48">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="236" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="120" />
<ColumnDefinition Width="48" />
</Grid.ColumnDefinitions>
<Rectangle
Grid.ColumnSpan="4"
Height="1"
Margin="-16,0,-16,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
Fill="{ThemeResource CardStrokeColorDefaultBrush}"
Opacity="0.8" />
<!-- Shortcut keys -->
<ItemsControl
Grid.Column="0"
VerticalAlignment="Center"
ItemsSource="{x:Bind Shortcut}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="4" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:KeyVisual
HorizontalAlignment="Left"
Content="{Binding}"
Style="{StaticResource DefaultKeyVisualStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- Text content -->
<TextBlock
Grid.Column="1"
Margin="-4,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="12"
Text="{x:Bind Text}"
TextTrimming="CharacterEllipsis" />
<!-- App name -->
<TextBlock
Grid.Column="2"
Margin="-4,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{x:Bind AppName}"
TextTrimming="CharacterEllipsis" />
<!-- Delete button -->
<Button
x:Uid="TextPageDeleteButton"
Grid.Column="3"
Margin="0,0,4,0"
Padding="8,4"
HorizontalAlignment="Right"
VerticalAlignment="Center"
AutomationProperties.Name="Delete remapping"
Background="Transparent"
BorderThickness="0"
Click="DeleteButton_Click">
<FontIcon
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="16"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Glyph="&#xE74D;" />
</Button>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</StackPanel>
<ContentDialog
x:Name="KeyDialog"
x:Uid="TextPageKeyDialog"
Width="480"
Height="360"
MinWidth="600"
MaxWidth="600"
PrimaryButtonStyle="{StaticResource AccentButtonStyle}">
<Grid>
<controls:TextPageInputControl x:Name="TextInputControl" />
</Grid>
</ContentDialog>
<TeachingTip
x:Name="ValidationTip"
x:Uid="TextPageValidationTip"
IsLightDismissEnabled="True"
PreferredPlacement="Center">
<TeachingTip.IconSource>
<SymbolIconSource Symbol="Important" />
</TeachingTip.IconSource>
</TeachingTip>
</Grid>
</Page>

View File

@@ -1,332 +0,0 @@
// 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.Globalization;
using System.Linq;
using System.Threading.Tasks;
using KeyboardManagerEditorUI.Helpers;
using KeyboardManagerEditorUI.Interop;
using ManagedCommon;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
namespace KeyboardManagerEditorUI.Pages
{
public sealed partial class Text : Page, IDisposable
{
private KeyboardMappingService? _mappingService;
// Flag to indicate if the user is editing an existing mapping
private bool _isEditMode;
private TextMapping? _editingMapping;
private bool _disposed;
// The list of text mappings
public ObservableCollection<TextMapping> TextMappings { get; } = new ObservableCollection<TextMapping>();
public Text()
{
this.InitializeComponent();
try
{
_mappingService = new KeyboardMappingService();
LoadTextMappings();
}
catch (Exception ex)
{
Logger.LogError("Failed to initialize KeyboardMappingService: " + ex.Message);
System.Diagnostics.Debug.WriteLine(ex.Message);
}
this.Unloaded += Text_Unloaded;
}
private void Text_Unloaded(object sender, RoutedEventArgs e)
{
// Make sure we unregister the handler when the page is unloaded
UnregisterWindowActivationHandler();
KeyboardHookHelper.Instance.CleanupHook();
}
private void RegisterWindowActivationHandler()
{
// Get the current window that contains this page
if (App.MainWindow is Window window)
{
// Register for window activation events
window.Activated += Dialog_WindowActivated;
}
}
private void UnregisterWindowActivationHandler()
{
// Unregister to prevent memory leaks
if (App.MainWindow is Window window)
{
window.Activated -= Dialog_WindowActivated;
}
}
private void Dialog_WindowActivated(object sender, WindowActivatedEventArgs args)
{
// When window is deactivated (user switched to another app)
if (args.WindowActivationState == WindowActivationState.Deactivated)
{
// Make sure to cleanup the keyboard hook when the window loses focus
KeyboardHookHelper.Instance.CleanupHook();
TextInputControl.ClearKeys();
TextInputControl.UpdateAllAppsCheckBoxState();
}
}
private void LoadTextMappings()
{
if (_mappingService == null)
{
return;
}
TextMappings.Clear();
// Load key-to-text mappings
var keyToTextMappings = _mappingService.GetKeyToTextMappings();
foreach (var mapping in keyToTextMappings)
{
TextMappings.Add(new TextMapping
{
Shortcut = new List<string> { _mappingService.GetKeyDisplayName(mapping.OriginalKey) },
Text = mapping.TargetText,
IsAllApps = true,
AppName = string.Empty,
});
}
// Load shortcut-to-text mappings
foreach (var mapping in _mappingService.GetShortcutMappingsByType(ShortcutOperationType.RemapText))
{
string[] originalKeyCodes = mapping.OriginalKeys.Split(';');
var originalKeyNames = new List<string>();
foreach (var keyCode in originalKeyCodes)
{
if (int.TryParse(keyCode, out int code))
{
originalKeyNames.Add(_mappingService.GetKeyDisplayName(code));
}
}
TextMappings.Add(new TextMapping
{
Shortcut = originalKeyNames,
Text = mapping.TargetText,
IsAllApps = string.IsNullOrEmpty(mapping.TargetApp),
AppName = string.IsNullOrEmpty(mapping.TargetApp) ? string.Empty : mapping.TargetApp,
});
}
}
private async void NewShortcutBtn_Click(object sender, RoutedEventArgs e)
{
_isEditMode = false;
_editingMapping = null;
TextInputControl.ClearKeys();
TextInputControl.SetTextContent(string.Empty);
TextInputControl.SetAppSpecific(false, string.Empty);
RegisterWindowActivationHandler();
KeyDialog.PrimaryButtonClick += KeyDialog_PrimaryButtonClick;
await KeyDialog.ShowAsync();
KeyDialog.PrimaryButtonClick -= KeyDialog_PrimaryButtonClick;
UnregisterWindowActivationHandler();
KeyboardHookHelper.Instance.CleanupHook();
}
private async void ListView_ItemClick(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is TextMapping selectedMapping)
{
_isEditMode = true;
_editingMapping = selectedMapping;
TextInputControl.SetShortcutKeys(selectedMapping.Shortcut);
TextInputControl.SetTextContent(selectedMapping.Text);
TextInputControl.SetAppSpecific(!selectedMapping.IsAllApps, selectedMapping.AppName);
RegisterWindowActivationHandler();
KeyDialog.PrimaryButtonClick += KeyDialog_PrimaryButtonClick;
await KeyDialog.ShowAsync();
KeyDialog.PrimaryButtonClick -= KeyDialog_PrimaryButtonClick;
UnregisterWindowActivationHandler();
KeyboardHookHelper.Instance.CleanupHook();
// Reset the edit status
_isEditMode = false;
_editingMapping = null;
}
}
private void KeyDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
if (_mappingService == null)
{
return;
}
List<string> keys = TextInputControl.GetShortcutKeys();
string textContent = TextInputControl.GetTextContent();
bool isAppSpecific = TextInputControl.GetIsAppSpecific();
string appName = TextInputControl.GetAppName();
// Validate inputs
ValidationErrorType errorType = ValidationHelper.ValidateTextMapping(
keys, textContent, isAppSpecific, appName, _mappingService);
if (errorType != ValidationErrorType.NoError)
{
ShowValidationError(errorType, args);
return;
}
bool saved = false;
try
{
// Delete existing mapping if in edit mode
if (_isEditMode && _editingMapping != null)
{
if (_editingMapping.Shortcut.Count == 1)
{
int originalKey = _mappingService.GetKeyCodeFromName(_editingMapping.Shortcut[0]);
if (originalKey != 0)
{
_mappingService.DeleteSingleKeyToTextMapping(originalKey);
}
}
else
{
string originalKeys = string.Join(";", _editingMapping.Shortcut.Select(k => _mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
_mappingService.DeleteShortcutMapping(originalKeys, _editingMapping.IsAllApps ? string.Empty : _editingMapping.AppName);
}
}
// Add new mapping
if (keys.Count == 1)
{
// Single key to text mapping
int originalKey = _mappingService.GetKeyCodeFromName(keys[0]);
if (originalKey != 0)
{
saved = _mappingService.AddSingleKeyToTextMapping(originalKey, textContent);
}
}
else
{
// Shortcut to text mapping
string originalKeysString = string.Join(";", keys.Select(k => _mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
if (isAppSpecific && !string.IsNullOrEmpty(appName))
{
saved = _mappingService.AddShortcutMapping(originalKeysString, textContent, appName, ShortcutOperationType.RemapText);
}
else
{
saved = _mappingService.AddShortcutMapping(originalKeysString, textContent, operationType: ShortcutOperationType.RemapText);
}
}
if (saved)
{
_mappingService.SaveSettings();
LoadTextMappings(); // Refresh the list
}
}
catch (Exception ex)
{
Logger.LogError("Error saving text mapping: " + ex.Message);
args.Cancel = true;
}
}
private void DeleteButton_Click(object sender, RoutedEventArgs e)
{
if (_mappingService == null || !(sender is Button button) || !(button.DataContext is TextMapping mapping))
{
return;
}
try
{
bool deleted = false;
if (mapping.Shortcut.Count == 1)
{
// Single key mapping
int originalKey = _mappingService.GetKeyCodeFromName(mapping.Shortcut[0]);
if (originalKey != 0)
{
deleted = _mappingService.DeleteSingleKeyToTextMapping(originalKey);
}
}
else
{
// Shortcut mapping
string originalKeys = string.Join(";", mapping.Shortcut.Select(k => _mappingService.GetKeyCodeFromName(k)));
deleted = _mappingService.DeleteShortcutMapping(originalKeys, mapping.IsAllApps ? string.Empty : mapping.AppName);
}
if (deleted)
{
_mappingService.SaveSettings();
TextMappings.Remove(mapping);
}
}
catch (Exception ex)
{
Logger.LogError("Error deleting text mapping: " + ex.Message);
}
}
private void ShowValidationError(ValidationErrorType errorType, ContentDialogButtonClickEventArgs args)
{
if (ValidationHelper.ValidationMessages.TryGetValue(errorType, out (string Title, string Message) error))
{
ValidationTip.Title = error.Title;
ValidationTip.Subtitle = error.Message;
ValidationTip.IsOpen = true;
args.Cancel = true;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_mappingService?.Dispose();
_mappingService = null;
}
_disposed = true;
}
}
}
}

View File

@@ -1,159 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<Page
x:Class="KeyboardManagerEditorUI.Pages.URLs"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:KeyboardManagerEditorUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helper="using:KeyboardManagerEditorUI.Helpers"
xmlns:local="using:KeyboardManagerEditorUI.Pages"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Padding="16">
<StackPanel
Orientation="Vertical"
Spacing="12">
<TextBlock
x:Uid="UrlPageInstructionTextBlock"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap" />
<Button
x:Name="NewShortcutBtn"
Height="36"
Margin="0,12,0,0"
Click="NewShortcutBtn_Click">
<StackPanel Orientation="Horizontal" Spacing="8">
<FontIcon
FontSize="14"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
Glyph="&#xE109;" />
<TextBlock
x:Uid="UrlPageNewTextBlock"
VerticalAlignment="Center" />
</StackPanel>
</Button>
<Grid
HorizontalAlignment="Stretch"
Background="{ThemeResource LayerFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="{ThemeResource OverlayCornerRadius}">
<Grid.RowDefinitions>
<RowDefinition Height="48" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="348" />
<ColumnDefinition Width="236" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="84" />
</Grid.ColumnDefinitions>
<TextBlock
x:Uid="UrlPageShortcutTextBlock"
Grid.Column="0"
Margin="16,-2,0,0"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<AppBarSeparator
Grid.Column="1"
Margin="-6,4,0,4"
HorizontalAlignment="Left" />
<TextBlock
x:Uid="UrlPageUrlTextBlock"
Grid.Column="1"
Margin="12,-2,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<Rectangle
Grid.ColumnSpan="4"
Height="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
Fill="{ThemeResource CardStrokeColorDefaultBrush}" />
<ListView
Grid.Row="1"
Grid.ColumnSpan="4"
IsItemClickEnabled="True"
ItemClick="ListView_ItemClick"
ItemsSource="{x:Bind Shortcuts}"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate x:DataType="helper:URLShortcut">
<Grid Height="48">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="348" />
<ColumnDefinition Width="476" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="84" />
</Grid.ColumnDefinitions>
<Rectangle
Grid.ColumnSpan="5"
Height="1"
Margin="-16,0,-16,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
Fill="{ThemeResource CardStrokeColorDefaultBrush}"
Opacity="0.8" />
<ItemsControl
Grid.Column="0"
VerticalAlignment="Center"
ItemsSource="{x:Bind Shortcut}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="4" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:KeyVisual
HorizontalAlignment="Left"
Content="{Binding}"
Style="{StaticResource DefaultKeyVisualStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<HyperlinkButton
Grid.Column="1"
Margin="-12,0,0,0"
Content="{x:Bind URL}" />
<Button
x:Uid="UrlPageDeleteButton"
Grid.ColumnSpan="4"
Margin="0,0,4,0"
Padding="8,4"
HorizontalAlignment="Right"
VerticalAlignment="Center"
AutomationProperties.Name="Delete URL shortcut"
Background="Transparent"
BorderThickness="0"
Click="DeleteButton_Click">
<FontIcon
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="16"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Glyph="&#xE74D;" />
</Button>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</StackPanel>
<ContentDialog
x:Name="KeyDialog"
x:Uid="UrlPageKeyDialog"
Width="480"
Height="360"
MinWidth="600"
MaxWidth="600"
PrimaryButtonClick="KeyDialog_PrimaryButtonClick"
PrimaryButtonStyle="{StaticResource AccentButtonStyle}">
<Grid>
<controls:UrlPageInputControl x:Name="UrlShortcutControl" />
</Grid>
</ContentDialog>
</Grid>
</Page>

View File

@@ -1,261 +0,0 @@
// 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.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text;
using KeyboardManagerEditorUI.Helpers;
using KeyboardManagerEditorUI.Interop;
using ManagedCommon;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using Windows.Foundation;
using Windows.Foundation.Collections;
using static KeyboardManagerEditorUI.Interop.ShortcutKeyMapping;
namespace KeyboardManagerEditorUI.Pages
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class URLs : Page, IDisposable
{
private KeyboardMappingService? _mappingService;
private bool _disposed;
private bool _isEditMode;
private URLShortcut? _editingMapping;
public ObservableCollection<URLShortcut> Shortcuts { get; set; }
[DllImport("PowerToys.KeyboardManagerEditorLibraryWrapper.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private static extern void GetKeyDisplayName(int keyCode, [Out] StringBuilder keyName, int maxLength);
public URLs()
{
this.InitializeComponent();
Shortcuts = new ObservableCollection<URLShortcut>();
_mappingService = new KeyboardMappingService();
LoadUrlShortcuts();
}
public void LoadUrlShortcuts()
{
if (_mappingService == null)
{
return;
}
foreach (var mapping in _mappingService.GetShortcutMappingsByType(ShortcutOperationType.OpenUri))
{
string[] originalKeyCodes = mapping.OriginalKeys.Split(';');
var originalKeyNames = new List<string>();
foreach (var keyCode in originalKeyCodes)
{
if (int.TryParse(keyCode, out int code))
{
originalKeyNames.Add(GetKeyDisplayName(code));
}
}
var shortcut = new URLShortcut
{
Shortcut = originalKeyNames,
URL = mapping.UriToOpen,
};
Shortcuts.Add(shortcut);
}
}
public static string GetKeyDisplayName(int keyCode)
{
var keyName = new StringBuilder(64);
GetKeyDisplayName(keyCode, keyName, keyName.Capacity);
return keyName.ToString();
}
private async void NewShortcutBtn_Click(object sender, RoutedEventArgs e)
{
_isEditMode = false;
_editingMapping = null;
UrlShortcutControl.ClearKeys();
UrlShortcutControl.SetUrlPathContent(string.Empty);
await KeyDialog.ShowAsync();
}
private async void ListView_ItemClick(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is URLShortcut urlShortcut)
{
_isEditMode = true;
_editingMapping = urlShortcut;
UrlShortcutControl.SetShortcutKeys(urlShortcut.Shortcut);
UrlShortcutControl.SetUrlPathContent(urlShortcut.URL);
await KeyDialog.ShowAsync();
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// Dispose managed resources
_mappingService?.Dispose();
_mappingService = null;
}
_disposed = true;
}
}
private void KeyDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
if (_mappingService == null)
{
return;
}
List<string> keys = UrlShortcutControl.GetShortcutKeys();
string urlPath = UrlShortcutControl.GetUrlPathContent();
// Validate inputs
ValidationErrorType errorType = ValidationHelper.ValidateProgramOrUrlMapping(keys, false, string.Empty, _mappingService);
if (errorType != ValidationErrorType.NoError)
{
ShowValidationError(errorType, args);
return;
}
bool saved = false;
try
{
// Delete existing mapping if in edit mode
if (_isEditMode && _editingMapping != null)
{
if (_editingMapping.Shortcut.Count == 1)
{
int originalKey = _mappingService.GetKeyCodeFromName(_editingMapping.Shortcut[0]);
if (originalKey != 0)
{
_mappingService.DeleteSingleKeyMapping(originalKey);
}
}
else
{
string originalKeys = string.Join(";", _editingMapping.Shortcut.Select(k => _mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
_mappingService.DeleteShortcutMapping(originalKeys);
}
}
// Shortcut to text mapping
string originalKeysString = string.Join(";", keys.Select(k => _mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
// if (isAppSpecific && !string.IsNullOrEmpty(appName))
// {
// saved = _mappingService.AddShortcutMapping(originalKeysString, programPath, appName, ShortcutOperationType.RemapText);
// }
// else
// {
ShortcutKeyMapping shortcutKeyMapping = new ShortcutKeyMapping()
{
OperationType = ShortcutOperationType.OpenUri,
OriginalKeys = originalKeysString,
TargetKeys = originalKeysString,
UriToOpen = urlPath,
};
saved = _mappingService.AddShorcutMapping(shortcutKeyMapping);
if (saved)
{
_mappingService.SaveSettings();
LoadUrlShortcuts();
}
}
catch (Exception ex)
{
Logger.LogError("Error saving text mapping: " + ex.Message);
args.Cancel = true;
}
}
private void ShowValidationError(ValidationErrorType errorType, ContentDialogButtonClickEventArgs args)
{
// if (ValidationHelper.ValidationMessages.TryGetValue(errorType, out (string Title, string Message) error))
// {
// ValidationTip.Title = error.Title;
// ValidationTip.Subtitle = error.Message;
// ValidationTip.IsOpen = true;
// args.Cancel = true;
// }
}
private void DeleteButton_Click(object sender, RoutedEventArgs e)
{
if (_mappingService == null || !(sender is Button button) || !(button.DataContext is URLShortcut shortcut))
{
return;
}
try
{
bool deleted = false;
if (shortcut.Shortcut.Count == 1)
{
// Single key mapping
int originalKey = _mappingService.GetKeyCodeFromName(shortcut.Shortcut[0]);
if (originalKey != 0)
{
deleted = _mappingService.DeleteSingleKeyToTextMapping(originalKey);
}
}
else
{
// Shortcut mapping
string originalKeys = string.Join(";", shortcut.Shortcut.Select(k => _mappingService.GetKeyCodeFromName(k)));
deleted = _mappingService.DeleteShortcutMapping(originalKeys);
}
if (deleted)
{
_mappingService.SaveSettings();
Shortcuts.Remove(shortcut);
LoadUrlShortcuts();
}
}
catch (Exception ex)
{
Logger.LogError("Error deleting text mapping: " + ex.Message);
}
}
}
}