diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorUI/Helpers/ValidationErrorType.cs b/src/modules/keyboardmanager/KeyboardManagerEditorUI/Helpers/ValidationErrorType.cs new file mode 100644 index 0000000000..1d124512ff --- /dev/null +++ b/src/modules/keyboardmanager/KeyboardManagerEditorUI/Helpers/ValidationErrorType.cs @@ -0,0 +1,24 @@ +// 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.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KeyboardManagerEditorUI.Helpers +{ + public enum ValidationErrorType + { + NoError, + EmptyOriginalKeys, + EmptyRemappedKeys, + ModifierOnly, + EmptyAppName, + IllegalShortcut, + DuplicateMapping, + SelfMapping, + } +} diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorUI/Helpers/ValidationHelper.cs b/src/modules/keyboardmanager/KeyboardManagerEditorUI/Helpers/ValidationHelper.cs index d72cba64e5..afa760b412 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorUI/Helpers/ValidationHelper.cs +++ b/src/modules/keyboardmanager/KeyboardManagerEditorUI/Helpers/ValidationHelper.cs @@ -4,25 +4,16 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; +using KeyboardManagerEditorUI.Interop; namespace KeyboardManagerEditorUI.Helpers { public static class ValidationHelper { - public enum ValidationErrorType - { - EmptyOriginalKeys, - EmptyRemappedKeys, - ModifierOnly, - EmptyAppName, - IllegalShortcut, - DuplicateMapping, - SelfMapping, - } - public static readonly Dictionary ValidationMessages = new() { { ValidationErrorType.EmptyOriginalKeys, ("Missing Original Keys", "Please enter at least one original key to create a remapping.") }, @@ -33,5 +24,181 @@ namespace KeyboardManagerEditorUI.Helpers { ValidationErrorType.DuplicateMapping, ("Duplicate Remapping", "This key or shortcut is already remapped.") }, { ValidationErrorType.SelfMapping, ("Invalid Remapping", "A key or shortcut cannot be remapped to itself. Please choose a different target.") }, }; + + public static ValidationErrorType ValidateKeyMapping( + List originalKeys, + List remappedKeys, + bool isAppSpecific, + string appName, + KeyboardMappingService mappingService) + { + // Check if original keys are empty + if (originalKeys == null || originalKeys.Count == 0) + { + return ValidationErrorType.EmptyOriginalKeys; + } + + // Check if remapped keys are empty + if (remappedKeys == null || remappedKeys.Count == 0) + { + return ValidationErrorType.EmptyRemappedKeys; + } + + // Check if shortcut contains only modifier keys + if ((originalKeys.Count > 1 && ContainsOnlyModifierKeys(originalKeys)) || + (remappedKeys.Count > 1 && ContainsOnlyModifierKeys(remappedKeys))) + { + return ValidationErrorType.ModifierOnly; + } + + // Check if app specific is checked but no app name is provided + if (isAppSpecific && string.IsNullOrWhiteSpace(appName)) + { + return ValidationErrorType.EmptyAppName; + } + + // Check if this is a shortcut (multiple keys) and if it's an illegal combination + if (originalKeys.Count > 1) + { + string shortcutKeysString = string.Join(";", originalKeys.Select(k => KeyboardManagerInterop.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture))); + + if (KeyboardManagerInterop.IsShortcutIllegal(shortcutKeysString)) + { + return ValidationErrorType.IllegalShortcut; + } + } + + // Check for duplicate mappings + if (IsDuplicateMapping(originalKeys, isAppSpecific, appName, mappingService)) + { + return ValidationErrorType.DuplicateMapping; + } + + // Check for self-mapping + if (IsSelfMapping(originalKeys, remappedKeys)) + { + return ValidationErrorType.SelfMapping; + } + + return ValidationErrorType.NoError; + } + + public static bool IsDuplicateMapping(List originalKeys, bool isAppSpecific, string appName, KeyboardMappingService mappingService) + { + if (mappingService == null || originalKeys == null || originalKeys.Count == 0) + { + return false; + } + + // For single key remapping + if (originalKeys.Count == 1) + { + int originalKeyCode = KeyboardManagerInterop.GetKeyCodeFromName(originalKeys[0]); + if (originalKeyCode == 0) + { + return false; + } + + // Check if the key is already remapped + foreach (var mapping in mappingService.GetSingleKeyMappings()) + { + if (mapping.OriginalKey == originalKeyCode) + { + return true; + } + } + } + + // For shortcut remapping + else + { + string originalKeysString = string.Join(";", originalKeys.Select(k => KeyboardManagerInterop.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture))); + + // Check if the shortcut is already remapped in the same app context + foreach (var mapping in mappingService.GetShortcutMappingsByType(ShortcutOperationType.RemapShortcut)) + { + // Same shortcut in the same app context + if (KeyboardManagerInterop.AreShortcutsEqual(originalKeysString, mapping.OriginalKeys)) + { + // If both are global (all apps) + if (!isAppSpecific && string.IsNullOrEmpty(mapping.TargetApp)) + { + return true; + } + + // If both are for the same specific app + else if (isAppSpecific && !string.IsNullOrEmpty(mapping.TargetApp) && string.Equals(mapping.TargetApp, appName, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + } + } + + return false; + } + + public static bool IsSelfMapping(List originalKeys, List remappedKeys) + { + // If either list is empty, it's not a self-mapping + if (originalKeys == null || remappedKeys == null || + originalKeys.Count == 0 || remappedKeys.Count == 0) + { + return false; + } + + string originalKeysString = string.Join(";", originalKeys.Select(k => KeyboardManagerInterop.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture))); + string remappedKeysString = string.Join(";", remappedKeys.Select(k => KeyboardManagerInterop.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture))); + + return KeyboardManagerInterop.AreShortcutsEqual(originalKeysString, remappedKeysString); + } + + public static bool ContainsOnlyModifierKeys(List keys) + { + if (keys == null || keys.Count == 0) + { + return false; + } + + foreach (string key in keys) + { + int keyCode = KeyboardManagerInterop.GetKeyCodeFromName(key); + var keyType = (KeyType)KeyboardManagerInterop.GetKeyType(keyCode); + + // If any key is an action key, return false + if (keyType == KeyType.Action) + { + return false; + } + } + + // All keys are modifier keys + return true; + } + + public static bool IsKeyOrphaned(int originalKey, KeyboardMappingService mappingService) + { + // Check all single key mappings + foreach (var mapping in mappingService.GetSingleKeyMappings()) + { + if (!mapping.IsShortcut && int.TryParse(mapping.TargetKey, out int targetKey) && targetKey == originalKey) + { + return false; + } + } + + // Check all shortcut mappings + foreach (var mapping in mappingService.GetShortcutMappings()) + { + string[] targetKeys = mapping.TargetKeys.Split(';'); + if (targetKeys.Length == 1 && int.TryParse(targetKeys[0], out int shortcutTargetKey) && shortcutTargetKey == originalKey) + { + return false; + } + } + + // No mapping found for the original key + return true; + } } } diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorUI/Pages/Remappings.xaml.cs b/src/modules/keyboardmanager/KeyboardManagerEditorUI/Pages/Remappings.xaml.cs index d4185afa11..c660d5b030 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorUI/Pages/Remappings.xaml.cs +++ b/src/modules/keyboardmanager/KeyboardManagerEditorUI/Pages/Remappings.xaml.cs @@ -220,58 +220,20 @@ namespace KeyboardManagerEditorUI.Pages bool isAppSpecific = RemappingControl.GetIsAppSpecific(); string appName = RemappingControl.GetAppName(); - // Check if original keys are empty - if (originalKeys == null || originalKeys.Count == 0) + // Make sure _mappingService is not null before validating and saving + if (_mappingService == null) { - ShowValidationError(ValidationErrorType.EmptyOriginalKeys, args); + Logger.LogError("Mapping service is null, cannot validate mapping"); return; } - // Check if remapped keys are empty - if (remappedKeys == null || remappedKeys.Count == 0) - { - ShowValidationError(ValidationErrorType.EmptyRemappedKeys, args); - return; - } + // Validate the remapping + ValidationErrorType errorType = ValidationHelper.ValidateKeyMapping( + originalKeys, remappedKeys, isAppSpecific, appName, _mappingService); - // Check if shortcut contains only modifier keys - if ((originalKeys.Count > 1 && ContainsOnlyModifierKeys(originalKeys)) || - (remappedKeys.Count > 1 && ContainsOnlyModifierKeys(remappedKeys))) + if (errorType != ValidationErrorType.NoError) { - ShowValidationError(ValidationErrorType.ModifierOnly, args); - return; - } - - // Check if app specific is checked but no app name is provided - if (isAppSpecific && string.IsNullOrWhiteSpace(appName)) - { - ShowValidationError(ValidationErrorType.EmptyAppName, args); - return; - } - - // Check if this is a shortcut (multiple keys) and if it's an illegal combination - if (originalKeys.Count > 1) - { - string shortcutKeysString = string.Join(";", originalKeys.Select(k => GetKeyCode(k).ToString(CultureInfo.InvariantCulture))); - - if (KeyboardManagerInterop.IsShortcutIllegal(shortcutKeysString)) - { - ShowValidationError(ValidationErrorType.IllegalShortcut, args); - return; - } - } - - // Check for duplicate mappings - if (IsDuplicateMapping(originalKeys, isAppSpecific, appName)) - { - ShowValidationError(ValidationErrorType.DuplicateMapping, args); - return; - } - - // Check for self-mapping - if (IsSelfMapping(originalKeys, remappedKeys)) - { - ShowValidationError(ValidationErrorType.SelfMapping, args); + ShowValidationError(errorType, args); return; } @@ -294,9 +256,11 @@ namespace KeyboardManagerEditorUI.Pages } } + // If no errors, proceed to save the remapping bool saved = SaveCurrentMapping(); if (saved) { + // Display the remapping in the list after saving LoadMappings(); } } @@ -319,124 +283,6 @@ namespace KeyboardManagerEditorUI.Pages sender.IsOpen = false; } - private bool IsDuplicateMapping(List originalKeys, bool isAppSpecific, string appName) - { - if (_mappingService == null || originalKeys == null || originalKeys.Count == 0) - { - return false; - } - - // For single key remapping - if (originalKeys.Count == 1) - { - int originalKeyCode = GetKeyCode(originalKeys[0]); - if (originalKeyCode == 0) - { - return false; - } - - // Check if the key is already remapped - foreach (var mapping in _mappingService.GetSingleKeyMappings()) - { - if (mapping.OriginalKey == originalKeyCode) - { - return true; - } - } - } - - // For shortcut remapping - else - { - string originalKeysString = string.Join(";", originalKeys.Select(k => GetKeyCode(k).ToString(CultureInfo.InvariantCulture))); - - // Check if the shortcut is already remapped in the same app context - foreach (var mapping in _mappingService.GetShortcutMappingsByType(ShortcutOperationType.RemapShortcut)) - { - // Same shortcut in the same app context - if (KeyboardManagerInterop.AreShortcutsEqual(originalKeysString, mapping.OriginalKeys)) - { - // If both are global (all apps) - if (!isAppSpecific && string.IsNullOrEmpty(mapping.TargetApp)) - { - return true; - } - - // If both are for the same specific app - else if (isAppSpecific && !string.IsNullOrEmpty(mapping.TargetApp) && string.Equals(mapping.TargetApp, appName, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - } - } - - return false; - } - - private bool IsSelfMapping(List originalKeys, List remappedKeys) - { - // If either list is empty, it's not a self-mapping - if (originalKeys == null || remappedKeys == null || - originalKeys.Count == 0 || remappedKeys.Count == 0) - { - return false; - } - - string originalKeysString = string.Join(";", originalKeys.Select(k => GetKeyCode(k).ToString(CultureInfo.InvariantCulture))); - string remappedKeysString = string.Join(";", remappedKeys.Select(k => GetKeyCode(k).ToString(CultureInfo.InvariantCulture))); - - return KeyboardManagerInterop.AreShortcutsEqual(originalKeysString, remappedKeysString); - } - - private bool ContainsOnlyModifierKeys(List keys) - { - if (keys == null || keys.Count == 0) - { - return false; - } - - foreach (string key in keys) - { - int keyCode = GetKeyCode(key); - var keyType = (KeyType)KeyboardManagerInterop.GetKeyType(keyCode); - - // If any key is an action key, return false - if (keyType == KeyType.Action) - { - return false; - } - } - - // All keys are modifier keys - return true; - } - - private bool IsKeyOrphaned(int originalKey, KeyboardMappingService mappingService) - { - // Check all single key mappings - foreach (var mapping in mappingService.GetSingleKeyMappings()) - { - if (!mapping.IsShortcut && int.TryParse(mapping.TargetKey, out int targetKey) && targetKey == originalKey) - { - return false; - } - } - - // Check all shortcut mappings - foreach (var mapping in mappingService.GetShortcutMappings()) - { - string[] targetKeys = mapping.TargetKeys.Split(';'); - if (targetKeys.Length == 1 && int.TryParse(targetKeys[0], out int shortcutTargetKey) && shortcutTargetKey == originalKey) - { - return false; - } - } - - // No mapping found for the original key - return true; - } - private void OrphanedKeysTeachingTip_ActionButtonClick(TeachingTip sender, object args) { // User pressed continue anyway button