diff --git a/src/common/keyboard_layout.cpp b/src/common/keyboard_layout.cpp index 1ca3f525a8..b117e3dcdd 100644 --- a/src/common/keyboard_layout.cpp +++ b/src/common/keyboard_layout.cpp @@ -215,19 +215,6 @@ std::vector LayoutMap::LayoutMapImpl::GetKeyCodeList(const bool isShortcu std::vector keyCodes; if (!isKeyCodeListGenerated) { - // Add modifier keys - keyCodes.push_back(CommonSharedConstants::VK_WIN_BOTH); - keyCodes.push_back(VK_LWIN); - keyCodes.push_back(VK_RWIN); - keyCodes.push_back(VK_CONTROL); - keyCodes.push_back(VK_LCONTROL); - keyCodes.push_back(VK_RCONTROL); - keyCodes.push_back(VK_MENU); - keyCodes.push_back(VK_LMENU); - keyCodes.push_back(VK_RMENU); - keyCodes.push_back(VK_SHIFT); - keyCodes.push_back(VK_LSHIFT); - keyCodes.push_back(VK_RSHIFT); // Add character keys for (auto& it : unicodeKeys) { @@ -237,7 +224,23 @@ std::vector LayoutMap::LayoutMapImpl::GetKeyCodeList(const bool isShortcu keyCodes.push_back(it.first); } } + + // Add modifier keys in alphabetical order + keyCodes.push_back(VK_MENU); + keyCodes.push_back(VK_LMENU); + keyCodes.push_back(VK_RMENU); + keyCodes.push_back(VK_CONTROL); + keyCodes.push_back(VK_LCONTROL); + keyCodes.push_back(VK_RCONTROL); + keyCodes.push_back(VK_SHIFT); + keyCodes.push_back(VK_LSHIFT); + keyCodes.push_back(VK_RSHIFT); + keyCodes.push_back(CommonSharedConstants::VK_WIN_BOTH); + keyCodes.push_back(VK_LWIN); + keyCodes.push_back(VK_RWIN); + // Add all other special keys + std::vector specialKeys; for (int i = 1; i < 256; i++) { // If it is not already been added (i.e. it was either a modifier or had a unicode representation) @@ -247,14 +250,24 @@ std::vector LayoutMap::LayoutMapImpl::GetKeyCodeList(const bool isShortcu auto it = unknownKeys.find(i); if (it == unknownKeys.end()) { - keyCodes.push_back(i); + specialKeys.push_back(i); } else if (unknownKeys[i] != keyboardLayoutMap[i]) { - keyCodes.push_back(i); + specialKeys.push_back(i); } } } + + // Sort the special keys in alphabetical order + std::sort(specialKeys.begin(), specialKeys.end(), [&](const DWORD& lhs, const DWORD& rhs) { + return keyboardLayoutMap[lhs] < keyboardLayoutMap[rhs]; + }); + for (int i = 0; i < specialKeys.size(); i++) + { + keyCodes.push_back(specialKeys[i]); + } + // Add unknown keys for (auto& it : unknownKeys) { @@ -285,20 +298,21 @@ std::vector LayoutMap::LayoutMapImpl::GetKeyNameList(const bool is { std::vector keyNames; std::vector keyCodes = GetKeyCodeList(isShortcut); + std::lock_guard lock(keyboardLayoutMap_mutex); // If it is a key list for the shortcut control then we add a "None" key at the start if (isShortcut) { keyNames.push_back(L"None"); for (int i = 1; i < keyCodes.size(); i++) { - keyNames.push_back(GetKeyName(keyCodes[i])); + keyNames.push_back(keyboardLayoutMap[keyCodes[i]]); } } else { for (int i = 0; i < keyCodes.size(); i++) { - keyNames.push_back(GetKeyName(keyCodes[i])); + keyNames.push_back(keyboardLayoutMap[keyCodes[i]]); } } diff --git a/src/modules/keyboardmanager/common/Helpers.cpp b/src/modules/keyboardmanager/common/Helpers.cpp index ec7253311a..cc9d065d4e 100644 --- a/src/modules/keyboardmanager/common/Helpers.cpp +++ b/src/modules/keyboardmanager/common/Helpers.cpp @@ -65,7 +65,7 @@ namespace KeyboardManagerHelper } // Function to return if the key is an extended key which requires the use of the extended key flag - bool isExtendedKey(DWORD key) + bool IsExtendedKey(DWORD key) { switch (key) { @@ -79,6 +79,7 @@ namespace KeyboardManagerHelper return false; } } + Collections::IVector ToBoxValue(const std::vector& list) { Collections::IVector boxList = single_threaded_vector(); @@ -89,4 +90,73 @@ namespace KeyboardManagerHelper return boxList; } + + // Function to check if two keys are equal or cover the same set of keys. Return value depends on type of overlap + ErrorType DoKeysOverlap(DWORD first, DWORD second) + { + // If the keys are same + if (first == second) + { + return ErrorType::SameKeyPreviouslyMapped; + } + else if ((GetKeyType(first) == GetKeyType(second)) && GetKeyType(first) != KeyType::Action) + { + // If the keys are of the same modifier type and overlapping, i.e. one is L/R and other is common + if ((first == VK_LWIN && second == VK_RWIN) || (first == VK_LCONTROL && second == VK_RCONTROL) || (first == VK_LMENU && second == VK_RMENU) || (first == VK_LSHIFT && second == VK_RSHIFT)) + { + return ErrorType::NoError; + } + else + { + return ErrorType::ConflictingModifierKey; + } + } + // If no overlap + else + { + return ErrorType::NoError; + } + } + + // Function to return the error message + winrt::hstring GetErrorMessage(ErrorType errorType) + { + switch (errorType) + { + case ErrorType::NoError: + return L"Remapping successful"; + case ErrorType::SameKeyPreviouslyMapped: + return L"Cannot remap a key more than once"; + case ErrorType::MapToSameKey: + return L"Cannot remap a key to itself"; + case ErrorType::ConflictingModifierKey: + return L"Cannot remap this key as it conflicts with another remapped key"; + case ErrorType::SameShortcutPreviouslyMapped: + return L"Cannot remap a shortcut more than once"; + case ErrorType::MapToSameShortcut: + return L"Cannot remap a shortcut to itself"; + case ErrorType::ConflictingModifierShortcut: + return L"Cannot remap this shortcut as it conflicts with another remapped shortcut"; + case ErrorType::WinL: + return L"Cannot remap from/to Win L"; + case ErrorType::CtrlAltDel: + return L"Cannot remap from/to Ctrl Alt Del"; + case ErrorType::RemapUnsuccessful: + return L"Some remappings were not applied"; + case ErrorType::SaveFailed: + return L"Failed to save the remappings"; + case ErrorType::MissingKey: + return L"Incomplete remapping"; + case ErrorType::ShortcutStartWithModifier: + return L"Shortcut must start with a modifier key"; + case ErrorType::ShortcutCannotHaveRepeatedModifier: + return L"Shortcut cannot contain a repeated modifier"; + case ErrorType::ShortcutAtleast2Keys: + return L"Shortcut must have atleast 2 keys"; + case ErrorType::ShortcutOneActionKey: + return L"Shortcut must contain an action key"; + case ErrorType::ShortcutNotMoreThanOneActionKey: + return L"Shortcut cannot have more than one action key"; + } + } } diff --git a/src/modules/keyboardmanager/common/Helpers.h b/src/modules/keyboardmanager/common/Helpers.h index b49d7b4187..578064fe14 100644 --- a/src/modules/keyboardmanager/common/Helpers.h +++ b/src/modules/keyboardmanager/common/Helpers.h @@ -15,6 +15,28 @@ namespace KeyboardManagerHelper Action }; + // Type to store codes for different errors + enum class ErrorType + { + NoError, + SameKeyPreviouslyMapped, + MapToSameKey, + ConflictingModifierKey, + SameShortcutPreviouslyMapped, + MapToSameShortcut, + ConflictingModifierShortcut, + WinL, + CtrlAltDel, + RemapUnsuccessful, + SaveFailed, + MissingKey, + ShortcutStartWithModifier, + ShortcutCannotHaveRepeatedModifier, + ShortcutAtleast2Keys, + ShortcutOneActionKey, + ShortcutNotMoreThanOneActionKey + }; + // Function to split a wstring based on a delimiter and return a vector of split strings std::vector splitwstring(const std::wstring& input, wchar_t delimiter); @@ -22,7 +44,7 @@ namespace KeyboardManagerHelper winrt::Windows::Foundation::IInspectable getSiblingElement(winrt::Windows::Foundation::IInspectable const& element); // Function to return if the key is an extended key which requires the use of the extended key flag - bool isExtendedKey(DWORD key); + bool IsExtendedKey(DWORD key); // Function to check if the key is a modifier key bool IsModifierKey(DWORD key); @@ -30,8 +52,11 @@ namespace KeyboardManagerHelper // Function to get the type of the key KeyType GetKeyType(DWORD key); - // Function to return if the key is an extended key which requires the use of the extended key flag - bool isExtendedKey(DWORD key); + // Function to check if two keys are equal or cover the same set of keys. Return value depends on type of overlap + ErrorType DoKeysOverlap(DWORD first, DWORD second); + + // Function to return the error message + winrt::hstring GetErrorMessage(ErrorType errorType); // Function to return the list of key name in the order for the drop down based on the key codes winrt::Windows::Foundation::Collections::IVector ToBoxValue(const std::vector& list); diff --git a/src/modules/keyboardmanager/common/KeyboardManagerConstants.h b/src/modules/keyboardmanager/common/KeyboardManagerConstants.h index da2d366f8b..38a5ddcaa7 100644 --- a/src/modules/keyboardmanager/common/KeyboardManagerConstants.h +++ b/src/modules/keyboardmanager/common/KeyboardManagerConstants.h @@ -35,4 +35,7 @@ namespace KeyboardManagerConstants // Name of the dummy update file. inline const std::wstring DummyUpdateFileName = L"settings-updated.json"; + + // Initial value for tooltip + inline const winrt::hstring ToolTipInitialContent = L"Initialised"; } \ No newline at end of file diff --git a/src/modules/keyboardmanager/common/Shortcut.cpp b/src/modules/keyboardmanager/common/Shortcut.cpp index aa457aa5e2..1b9be377b2 100644 --- a/src/modules/keyboardmanager/common/Shortcut.cpp +++ b/src/modules/keyboardmanager/common/Shortcut.cpp @@ -764,3 +764,56 @@ int Shortcut::GetCommonModifiersCount(const Shortcut& input) const return commonElements; } + +// Function to check if the two shortcuts are equal or cover the same set of keys. Return value depends on type of overlap +KeyboardManagerHelper::ErrorType Shortcut::DoKeysOverlap(const Shortcut& first, const Shortcut& second) +{ + if (first.IsValidShortcut() && second.IsValidShortcut()) + { + // If the shortcuts are equal + if (first == second) + { + return KeyboardManagerHelper::ErrorType::SameShortcutPreviouslyMapped; + } + // If both have win key modifiers and one is the both version then there will be an overlap + else if (first.winKey != ModifierKey::Disabled && second.winKey != ModifierKey::Disabled && (first.winKey == ModifierKey::Both || second.winKey == ModifierKey::Both)) + { + return KeyboardManagerHelper::ErrorType::ConflictingModifierShortcut; + } + // If both have ctrl key modifiers and one is the both version then there will be an overlap + else if (first.ctrlKey != ModifierKey::Disabled && second.ctrlKey != ModifierKey::Disabled && (first.ctrlKey == ModifierKey::Both || second.ctrlKey == ModifierKey::Both)) + { + return KeyboardManagerHelper::ErrorType::ConflictingModifierShortcut; + } + // If both have alt key modifiers and one is the both version then there will be an overlap + else if (first.altKey != ModifierKey::Disabled && second.altKey != ModifierKey::Disabled && (first.altKey == ModifierKey::Both || second.altKey == ModifierKey::Both)) + { + return KeyboardManagerHelper::ErrorType::ConflictingModifierShortcut; + } + // If both have shift key modifiers and one is the both version then there will be an overlap + else if (first.shiftKey != ModifierKey::Disabled && second.shiftKey != ModifierKey::Disabled && (first.shiftKey == ModifierKey::Both || second.shiftKey == ModifierKey::Both)) + { + return KeyboardManagerHelper::ErrorType::ConflictingModifierShortcut; + } + } + + return KeyboardManagerHelper::ErrorType::NoError; +} + +// Function to check if the shortcut is illegal (i.e. Win+L or Ctrl+Alt+Del) +KeyboardManagerHelper::ErrorType Shortcut::IsShortcutIllegal() const +{ + // Win+L + if (winKey != ModifierKey::Disabled && ctrlKey == ModifierKey::Disabled && altKey == ModifierKey::Disabled && shiftKey == ModifierKey::Disabled && actionKey == 0x4C) + { + return KeyboardManagerHelper::ErrorType::WinL; + } + + // Ctrl+Alt+Del + if (winKey == ModifierKey::Disabled && ctrlKey != ModifierKey::Disabled && altKey != ModifierKey::Disabled && shiftKey == ModifierKey::Disabled && actionKey == VK_DELETE) + { + return KeyboardManagerHelper::ErrorType::CtrlAltDel; + } + + return KeyboardManagerHelper::ErrorType::NoError; +} diff --git a/src/modules/keyboardmanager/common/Shortcut.h b/src/modules/keyboardmanager/common/Shortcut.h index c1e534cf15..7f4e617c9a 100644 --- a/src/modules/keyboardmanager/common/Shortcut.h +++ b/src/modules/keyboardmanager/common/Shortcut.h @@ -41,6 +41,12 @@ public: } } + // == operator + inline bool operator==(const Shortcut& sc) const + { + return (winKey == sc.winKey && ctrlKey == sc.ctrlKey && altKey == sc.altKey && shiftKey == sc.shiftKey && actionKey == sc.actionKey); + } + // Less than operator must be defined to use with std::map. inline bool operator<(const Shortcut& sc) const { @@ -168,4 +174,10 @@ public: // Function to get the number of modifiers that are common between the current shortcut and the shortcut in the argument int GetCommonModifiersCount(const Shortcut& input) const; + + // Function to check if the two shortcuts are equal or cover the same set of keys. Return value depends on type of overlap + static KeyboardManagerHelper::ErrorType DoKeysOverlap(const Shortcut& first, const Shortcut& second); + + // Function to check if the shortcut is illegal (i.e. Win+L or Ctrl+Alt+Del) + KeyboardManagerHelper::ErrorType IsShortcutIllegal() const; }; diff --git a/src/modules/keyboardmanager/dll/dllmain.cpp b/src/modules/keyboardmanager/dll/dllmain.cpp index a05ca1aae7..94db720c6c 100644 --- a/src/modules/keyboardmanager/dll/dllmain.cpp +++ b/src/modules/keyboardmanager/dll/dllmain.cpp @@ -396,7 +396,7 @@ public: keyEventArray[index].type = inputType; keyEventArray[index].ki.wVk = keyCode; keyEventArray[index].ki.dwFlags = flags; - if (KeyboardManagerHelper::isExtendedKey(keyCode)) + if (KeyboardManagerHelper::IsExtendedKey(keyCode)) { keyEventArray[index].ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; } diff --git a/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp b/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp index 0b88e9f916..6109f1ca76 100644 --- a/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp +++ b/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp @@ -106,12 +106,16 @@ void createEditKeyboardWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMan ColumnDefinition firstColumn; ColumnDefinition secondColumn; ColumnDefinition thirdColumn; + thirdColumn.MaxWidth(100); + ColumnDefinition fourthColumn; + fourthColumn.MaxWidth(100); keyRemapTable.Margin({ 10, 10, 10, 20 }); keyRemapTable.HorizontalAlignment(HorizontalAlignment::Stretch); keyRemapTable.ColumnSpacing(10); keyRemapTable.ColumnDefinitions().Append(firstColumn); keyRemapTable.ColumnDefinitions().Append(secondColumn); keyRemapTable.ColumnDefinitions().Append(thirdColumn); + keyRemapTable.ColumnDefinitions().Append(fourthColumn); keyRemapTable.RowDefinitions().Append(RowDefinition()); // First header textblock in the header row of the keys remap table @@ -208,7 +212,7 @@ void createEditKeyboardWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMan header.SetLeftOf(cancelButton, applyButton); applyButton.Flyout(applyFlyout); applyButton.Click([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { - bool isSuccess = true; + KeyboardManagerHelper::ErrorType isSuccess = KeyboardManagerHelper::ErrorType::NoError; // Clear existing Key Remaps keyboardManagerState.ClearSingleKeyRemaps(); @@ -250,30 +254,31 @@ void createEditKeyboardWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMan if (!result) { - isSuccess = false; + isSuccess = KeyboardManagerHelper::ErrorType::RemapUnsuccessful; + // Tooltip is already shown for this row } } else { - isSuccess = false; + isSuccess = KeyboardManagerHelper::ErrorType::RemapUnsuccessful; + // Show tooltip warning on the problematic row + uint32_t warningIndex; + // 2 at start, 4 in each row, and last element of each row + warningIndex = 1 + (i + 1) * 4; + FontIcon warning = keyRemapTable.Children().GetAt(warningIndex).as(); + ToolTip t = ToolTipService::GetToolTip(warning).as(); + t.Content(box_value(KeyboardManagerHelper::GetErrorMessage(KeyboardManagerHelper::ErrorType::MissingKey))); + warning.Visibility(Visibility::Visible); } } // Save the updated shortcuts remaps to file. - auto saveResult = keyboardManagerState.SaveConfigToFile(); - - if (isSuccess && saveResult) + bool saveResult = keyboardManagerState.SaveConfigToFile(); + if (!saveResult) { - settingsMessage.Text(L"Remapping successful!"); - } - else if (!isSuccess && saveResult) - { - settingsMessage.Text(L"All remappings were not successfully applied."); - } - else - { - settingsMessage.Text(L"Failed to save the remappings."); + isSuccess = KeyboardManagerHelper::ErrorType::SaveFailed; } + settingsMessage.Text(KeyboardManagerHelper::GetErrorMessage(isSuccess)); }); header.Children().Append(headerText); diff --git a/src/modules/keyboardmanager/ui/EditShortcutsWindow.cpp b/src/modules/keyboardmanager/ui/EditShortcutsWindow.cpp index d04b9ce7b6..dc09137832 100644 --- a/src/modules/keyboardmanager/ui/EditShortcutsWindow.cpp +++ b/src/modules/keyboardmanager/ui/EditShortcutsWindow.cpp @@ -102,12 +102,16 @@ void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMa ColumnDefinition firstColumn; ColumnDefinition secondColumn; ColumnDefinition thirdColumn; + thirdColumn.MaxWidth(100); + ColumnDefinition fourthColumn; + fourthColumn.MaxWidth(100); shortcutTable.Margin({ 10, 10, 10, 20 }); shortcutTable.HorizontalAlignment(HorizontalAlignment::Stretch); shortcutTable.ColumnSpacing(10); shortcutTable.ColumnDefinitions().Append(firstColumn); shortcutTable.ColumnDefinitions().Append(secondColumn); shortcutTable.ColumnDefinitions().Append(thirdColumn); + shortcutTable.ColumnDefinitions().Append(fourthColumn); shortcutTable.RowDefinitions().Append(RowDefinition()); // First header textblock in the header row of the shortcut table @@ -123,8 +127,8 @@ void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMa newShortcutHeader.Margin({ 0, 0, 0, 10 }); shortcutTable.SetColumn(originalShortcutHeader, 0); - shortcutTable.SetRow(newShortcutHeader, 0); - shortcutTable.SetColumn(originalShortcutHeader, 1); + shortcutTable.SetRow(originalShortcutHeader, 0); + shortcutTable.SetColumn(newShortcutHeader, 1); shortcutTable.SetRow(newShortcutHeader, 0); shortcutTable.Children().Append(originalShortcutHeader); @@ -160,7 +164,7 @@ void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMa header.SetLeftOf(cancelButton, applyButton); applyButton.Flyout(applyFlyout); applyButton.Click([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { - bool isSuccess = true; + KeyboardManagerHelper::ErrorType isSuccess = KeyboardManagerHelper::ErrorType::NoError; // Clear existing shortcuts keyboardManagerState.ClearOSLevelShortcuts(); @@ -175,30 +179,31 @@ void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMa bool result = keyboardManagerState.AddOSLevelShortcut(originalShortcut, newShortcut); if (!result) { - isSuccess = false; + isSuccess = KeyboardManagerHelper::ErrorType::RemapUnsuccessful; + // Tooltip is already shown for this row } } else { - isSuccess = false; + isSuccess = KeyboardManagerHelper::ErrorType::RemapUnsuccessful; + // Show tooltip warning on the problematic row + uint32_t warningIndex; + // 2 at start, 4 in each row, and last element of each row + warningIndex = 1 + (i + 1) * 4; + FontIcon warning = shortcutTable.Children().GetAt(warningIndex).as(); + ToolTip t = ToolTipService::GetToolTip(warning).as(); + t.Content(box_value(KeyboardManagerHelper::GetErrorMessage(KeyboardManagerHelper::ErrorType::MissingKey))); + warning.Visibility(Visibility::Visible); } } // Save the updated key remaps to file. - auto saveResult = keyboardManagerState.SaveConfigToFile(); - - if (isSuccess && saveResult) + bool saveResult = keyboardManagerState.SaveConfigToFile(); + if (!saveResult) { - settingsMessage.Text(L"Remapping successful!"); - } - else if (!isSuccess && saveResult) - { - settingsMessage.Text(L"All remappings were not successfully applied."); - } - else - { - settingsMessage.Text(L"Failed to save the remappings."); + isSuccess = KeyboardManagerHelper::ErrorType::SaveFailed; } + settingsMessage.Text(KeyboardManagerHelper::GetErrorMessage(isSuccess)); }); header.Children().Append(headerText); diff --git a/src/modules/keyboardmanager/ui/KeyDropDownControl.cpp b/src/modules/keyboardmanager/ui/KeyDropDownControl.cpp index 814b849e8a..90f09a5e41 100644 --- a/src/modules/keyboardmanager/ui/KeyDropDownControl.cpp +++ b/src/modules/keyboardmanager/ui/KeyDropDownControl.cpp @@ -37,8 +37,9 @@ void KeyDropDownControl::CheckAndUpdateKeyboardLayout(ComboBox currentDropDown, } // Function to set selection handler for single key remap drop down. Needs to be called after the constructor since the singleKeyControl StackPanel is null if called in the constructor -void KeyDropDownControl::SetSelectionHandler(Grid& table, StackPanel& singleKeyControl, size_t colIndex, std::vector>& singleKeyRemapBuffer) +void KeyDropDownControl::SetSelectionHandler(Grid& table, StackPanel& singleKeyControl, int colIndex, std::vector>& singleKeyRemapBuffer) { + // drop down selection handler dropDown.SelectionChanged([&, table, singleKeyControl, colIndex](winrt::Windows::Foundation::IInspectable const& sender, SelectionChangedEventArgs const& args) { ComboBox currentDropDown = sender.as(); int selectedKeyIndex = currentDropDown.SelectedIndex(); @@ -47,31 +48,73 @@ void KeyDropDownControl::SetSelectionHandler(Grid& table, StackPanel& singleKeyC bool indexFound = table.Children().IndexOf(singleKeyControl, controlIndex); if (indexFound) { - int rowIndex = (controlIndex - 2) / 3; + KeyboardManagerHelper::ErrorType errorType = KeyboardManagerHelper::ErrorType::NoError; + int rowIndex = (controlIndex - 2) / 4; // Check if the element was not found or the index exceeds the known keys if (selectedKeyIndex != -1 && keyCodeList.size() > selectedKeyIndex) { - singleKeyRemapBuffer[rowIndex][colIndex] = keyCodeList[selectedKeyIndex]; + // Check if the value being set is the same as the other column + if (singleKeyRemapBuffer[rowIndex][std::abs(int(colIndex) - 1)] == keyCodeList[selectedKeyIndex]) + { + errorType = KeyboardManagerHelper::ErrorType::MapToSameKey; + } + + if (errorType == KeyboardManagerHelper::ErrorType::NoError && colIndex == 0) + { + // Check if the key is already remapped to something else + for (int i = 0; i < singleKeyRemapBuffer.size(); i++) + { + if (i != rowIndex) + { + KeyboardManagerHelper::ErrorType result = KeyboardManagerHelper::DoKeysOverlap(singleKeyRemapBuffer[i][0], keyCodeList[selectedKeyIndex]); + if (result != KeyboardManagerHelper::ErrorType::NoError) + { + errorType = result; + break; + } + } + } + } + + // If there is no error, set the buffer + if (errorType == KeyboardManagerHelper::ErrorType::NoError) + { + singleKeyRemapBuffer[rowIndex][colIndex] = keyCodeList[selectedKeyIndex]; + } + else + { + singleKeyRemapBuffer[rowIndex][colIndex] = NULL; + } } else { // Reset to null if the key is not found singleKeyRemapBuffer[rowIndex][colIndex] = NULL; } + + if (errorType != KeyboardManagerHelper::ErrorType::NoError) + { + SetDropDownError(currentDropDown, KeyboardManagerHelper::GetErrorMessage(errorType)); + } + + // If either of the keys are invalid and the tooltip content has been modified after initialization, display the warning + if ((singleKeyRemapBuffer[rowIndex][0] == NULL || singleKeyRemapBuffer[rowIndex][1] == NULL) && toolTip.Content().as().GetString() != KeyboardManagerConstants::ToolTipInitialContent) + { + warning.Visibility(Visibility::Visible); + } + else + { + warning.Visibility(Visibility::Collapsed); + } } }); } // Function to set selection handler for shortcut drop down. Needs to be called after the constructor since the shortcutControl StackPanel is null if called in the constructor -void KeyDropDownControl::SetSelectionHandler(Grid& table, StackPanel& shortcutControl, StackPanel parent, size_t colIndex, std::vector>& shortcutRemapBuffer, std::vector>& keyDropDownControlObjects) +void KeyDropDownControl::SetSelectionHandler(Grid& table, StackPanel& shortcutControl, StackPanel parent, int colIndex, std::vector>& shortcutRemapBuffer, std::vector>& keyDropDownControlObjects) { - Flyout warningFlyout; - TextBlock warningMessage; - warningFlyout.Content(warningMessage); - dropDown.ContextFlyout().SetAttachedFlyout((FrameworkElement)dropDown, warningFlyout); - // drop down selection handler - dropDown.SelectionChanged([&, table, shortcutControl, colIndex, parent, warningMessage](winrt::Windows::Foundation::IInspectable const& sender, SelectionChangedEventArgs const&) { + dropDown.SelectionChanged([&, table, shortcutControl, colIndex, parent](winrt::Windows::Foundation::IInspectable const& sender, SelectionChangedEventArgs const&) { ComboBox currentDropDown = sender.as(); int selectedKeyIndex = currentDropDown.SelectedIndex(); uint32_t dropDownIndex = -1; @@ -79,47 +122,48 @@ void KeyDropDownControl::SetSelectionHandler(Grid& table, StackPanel& shortcutCo // Get row index of the single key control uint32_t controlIndex; bool controlIindexFound = table.Children().IndexOf(shortcutControl, controlIndex); + KeyboardManagerHelper::ErrorType errorType = KeyboardManagerHelper::ErrorType::NoError; if (controlIindexFound) { - int rowIndex = (controlIndex - 2) / 3; + int rowIndex = (controlIndex - 2) / 4; if (selectedKeyIndex != -1 && keyCodeList.size() > selectedKeyIndex && dropDownFound) { // If only 1 drop down and action key is chosen: Warn that a modifier must be chosen if (parent.Children().Size() == 1 && !KeyboardManagerHelper::IsModifierKey(keyCodeList[selectedKeyIndex])) { // warn and reset the drop down - SetDropDownError(currentDropDown, warningMessage, L"Shortcut must start with a modifier key"); + errorType = KeyboardManagerHelper::ErrorType::ShortcutStartWithModifier; } // If it is the last drop down else if (dropDownIndex == parent.Children().Size() - 1) { // If last drop down and a modifier is selected: add a new drop down (max of 5 drop downs should be enforced) - if (KeyboardManagerHelper::IsModifierKey(keyCodeList[selectedKeyIndex]) && parent.Children().Size() < 5) + if (KeyboardManagerHelper::IsModifierKey(keyCodeList[selectedKeyIndex]) && parent.Children().Size() < 3) { // If it matched any of the previous modifiers then reset that drop down if (CheckRepeatedModifier(parent, dropDownIndex, selectedKeyIndex, keyCodeList)) { // warn and reset the drop down - SetDropDownError(currentDropDown, warningMessage, L"Shortcut cannot contain a repeated modifier"); + errorType = KeyboardManagerHelper::ErrorType::ShortcutCannotHaveRepeatedModifier; } // If not, add a new drop down else { - AddDropDown(table, shortcutControl, parent, colIndex, shortcutRemapBuffer, keyDropDownControlObjects); + AddDropDown(table, shortcutControl, parent, colIndex, shortcutRemapBuffer, keyDropDownControlObjects, warning, toolTip); } } // If last drop down and a modifier is selected but there are already 5 drop downs: warn the user - else if (KeyboardManagerHelper::IsModifierKey(keyCodeList[selectedKeyIndex]) && parent.Children().Size() >= 5) + else if (KeyboardManagerHelper::IsModifierKey(keyCodeList[selectedKeyIndex]) && parent.Children().Size() >= 3) { // warn and reset the drop down - SetDropDownError(currentDropDown, warningMessage, L"Shortcut must contain an action key"); + errorType = KeyboardManagerHelper::ErrorType::ShortcutOneActionKey; } // If None is selected but it's the last index: warn else if (keyCodeList[selectedKeyIndex] == 0) { // warn and reset the drop down - SetDropDownError(currentDropDown, warningMessage, L"Shortcut must contain an action key"); + errorType = KeyboardManagerHelper::ErrorType::ShortcutOneActionKey; } // If none of the above, then the action key will be set } @@ -132,7 +176,7 @@ void KeyDropDownControl::SetSelectionHandler(Grid& table, StackPanel& shortcutCo if (CheckRepeatedModifier(parent, dropDownIndex, selectedKeyIndex, keyCodeList)) { // warn and reset the drop down - SetDropDownError(currentDropDown, warningMessage, L"Shortcut cannot contain a repeated modifier"); + errorType = KeyboardManagerHelper::ErrorType::ShortcutCannotHaveRepeatedModifier; } // If not, the modifier key will be set } @@ -148,7 +192,7 @@ void KeyDropDownControl::SetSelectionHandler(Grid& table, StackPanel& shortcutCo else if (keyCodeList[selectedKeyIndex] == 0 && parent.Children().Size() <= 2) { // warn and reset the drop down - SetDropDownError(currentDropDown, warningMessage, L"Shortcut must have atleast 2 keys"); + errorType = KeyboardManagerHelper::ErrorType::ShortcutAtleast2Keys; } // If the user tries to set an action key check if all drop down menus after this are empty if it is not the first key else if (dropDownIndex != 0) @@ -156,8 +200,8 @@ void KeyDropDownControl::SetSelectionHandler(Grid& table, StackPanel& shortcutCo bool isClear = true; for (int i = dropDownIndex + 1; i < (int)parent.Children().Size(); i++) { - ComboBox currentDropDown = parent.Children().GetAt(i).as(); - if (currentDropDown.SelectedIndex() != -1) + ComboBox ItDropDown = parent.Children().GetAt(i).as(); + if (ItDropDown.SelectedIndex() != -1) { isClear = false; break; @@ -178,20 +222,68 @@ void KeyDropDownControl::SetSelectionHandler(Grid& table, StackPanel& shortcutCo else { // warn and reset the drop down - SetDropDownError(currentDropDown, warningMessage, L"Shortcut cannot have more than one action key"); + errorType = KeyboardManagerHelper::ErrorType::ShortcutNotMoreThanOneActionKey; } } // If there an action key is chosen on the first drop down and there are more than one drop down menus else { // warn and reset the drop down - SetDropDownError(currentDropDown, warningMessage, L"Shortcut must start with a modifier key"); + errorType = KeyboardManagerHelper::ErrorType::ShortcutStartWithModifier; } } } + // After validating the shortcut, now for errors like remap to same shortcut, remap shortcut more than once, Win L and Ctrl Alt Del + if (errorType == KeyboardManagerHelper::ErrorType::NoError) + { + Shortcut tempShortcut; + tempShortcut.SetKeyCodes(GetKeysFromStackPanel(parent)); + // Check if the value being set is the same as the other column + if (shortcutRemapBuffer[rowIndex][std::abs(int(colIndex) - 1)] == tempShortcut) + { + errorType = KeyboardManagerHelper::ErrorType::MapToSameShortcut; + } + + if (errorType == KeyboardManagerHelper::ErrorType::NoError && colIndex == 0) + { + // Check if the key is already remapped to something else + for (int i = 0; i < shortcutRemapBuffer.size(); i++) + { + if (i != rowIndex) + { + KeyboardManagerHelper::ErrorType result = Shortcut::DoKeysOverlap(shortcutRemapBuffer[i][0], tempShortcut); + if (result != KeyboardManagerHelper::ErrorType::NoError) + { + errorType = result; + break; + } + } + } + } + + if (errorType == KeyboardManagerHelper::ErrorType::NoError) + { + errorType = tempShortcut.IsShortcutIllegal(); + } + } + + if (errorType != KeyboardManagerHelper::ErrorType::NoError) + { + SetDropDownError(currentDropDown, KeyboardManagerHelper::GetErrorMessage(errorType)); + } // Reset the buffer based on the new selected drop down items shortcutRemapBuffer[rowIndex][colIndex].SetKeyCodes(GetKeysFromStackPanel(parent)); + + // If either of the shortcuts are invalid and the tooltip content has been modified after initialization, display the warning + if ((!shortcutRemapBuffer[rowIndex][0].IsValidShortcut() || !shortcutRemapBuffer[rowIndex][1].IsValidShortcut()) && toolTip.Content().as().GetString() != KeyboardManagerConstants::ToolTipInitialContent) + { + warning.Visibility(Visibility::Visible); + } + else + { + warning.Visibility(Visibility::Collapsed); + } } // If the user searches for a key the selection handler gets invoked however if they click away it reverts back to the previous state. This can result in dangling references to added drop downs which were then reset. @@ -221,13 +313,9 @@ ComboBox KeyDropDownControl::GetComboBox() } // Function to add a drop down to the shortcut stack panel -void KeyDropDownControl::AddDropDown(Grid table, StackPanel shortcutControl, StackPanel parent, const size_t colIndex, std::vector>& shortcutRemapBuffer, std::vector>& keyDropDownControlObjects) +void KeyDropDownControl::AddDropDown(Grid table, StackPanel shortcutControl, StackPanel parent, const int colIndex, std::vector>& shortcutRemapBuffer, std::vector>& keyDropDownControlObjects, FontIcon warning, ToolTip toolTip) { - keyDropDownControlObjects.push_back(std::move(std::unique_ptr(new KeyDropDownControl(true)))); - // Flyout to display the warning on the drop down element - Flyout warningFlyout; - TextBlock warningMessage; - warningFlyout.Content(warningMessage); + keyDropDownControlObjects.push_back(std::move(std::unique_ptr(new KeyDropDownControl(true, warning, toolTip)))); parent.Children().Append(keyDropDownControlObjects[keyDropDownControlObjects.size() - 1]->GetComboBox()); keyDropDownControlObjects[keyDropDownControlObjects.size() - 1]->SetSelectionHandler(table, shortcutControl, parent, colIndex, shortcutRemapBuffer, keyDropDownControlObjects); parent.UpdateLayout(); @@ -278,10 +366,9 @@ bool KeyDropDownControl::CheckRepeatedModifier(StackPanel parent, uint32_t dropD return matchPreviousModifier; } -// Function to set the flyout warning message -void KeyDropDownControl::SetDropDownError(ComboBox dropDown, TextBlock messageBlock, hstring message) +// Function to set the warning message +void KeyDropDownControl::SetDropDownError(ComboBox currentDropDown, hstring message) { - messageBlock.Text(message); - dropDown.ContextFlyout().ShowAttachedFlyout((FrameworkElement)dropDown); - dropDown.SelectedIndex(-1); + currentDropDown.SelectedIndex(-1); + toolTip.Content(box_value(message)); } diff --git a/src/modules/keyboardmanager/ui/KeyDropDownControl.h b/src/modules/keyboardmanager/ui/KeyDropDownControl.h index 6984a82447..2d81d88421 100644 --- a/src/modules/keyboardmanager/ui/KeyDropDownControl.h +++ b/src/modules/keyboardmanager/ui/KeyDropDownControl.h @@ -11,6 +11,10 @@ private: HKL previousLayout = 0; // Stores the key code list std::vector keyCodeList; + // Stores the warning control + FontIcon warning; + // Stores the tooltip for the warning control + ToolTip toolTip; // Function to set properties apart from the SelectionChanged event handler void SetDefaultProperties(bool isShortcut); @@ -22,17 +26,18 @@ public: // Pointer to the keyboard manager state static KeyboardManagerState* keyboardManagerState; - // Constructor for single key drop down - KeyDropDownControl(bool isShortcut) + // Constructor + KeyDropDownControl(bool isShortcut, FontIcon warning, ToolTip toolTip) : + warning(warning), toolTip(toolTip) { SetDefaultProperties(isShortcut); } // Function to set selection handler for single key remap drop down. Needs to be called after the constructor since the singleKeyControl StackPanel is null if called in the constructor - void SetSelectionHandler(Grid& table, StackPanel& singleKeyControl, size_t colIndex, std::vector>& singleKeyRemapBuffer); + void SetSelectionHandler(Grid& table, StackPanel& singleKeyControl, int colIndex, std::vector>& singleKeyRemapBuffer); // Function to set selection handler for shortcut drop down. Needs to be called after the constructor since the shortcutControl StackPanel is null if called in the constructor - void SetSelectionHandler(Grid& table, StackPanel& shortcutControl, StackPanel parent, size_t colIndex, std::vector>& shortcutRemapBuffer, std::vector>& keyDropDownControlObjects); + void SetSelectionHandler(Grid& table, StackPanel& shortcutControl, StackPanel parent, int colIndex, std::vector>& shortcutRemapBuffer, std::vector>& keyDropDownControlObjects); // Function to set the selected index of the drop down void SetSelectedIndex(int32_t index); @@ -41,7 +46,7 @@ public: ComboBox GetComboBox(); // Function to add a drop down to the shortcut stack panel - static void AddDropDown(Grid table, StackPanel shortcutControl, StackPanel parent, const size_t colIndex, std::vector>& shortcutRemapBuffer, std::vector>& keyDropDownControlObjects); + static void AddDropDown(Grid table, StackPanel shortcutControl, StackPanel parent, const int colIndex, std::vector>& shortcutRemapBuffer, std::vector>& keyDropDownControlObjects, FontIcon warning, ToolTip toolTip); // Function to get the list of key codes from the shortcut combo box stack panel std::vector GetKeysFromStackPanel(StackPanel parent); @@ -49,6 +54,6 @@ public: // Function to check if a modifier has been repeated in the previous drop downs bool CheckRepeatedModifier(StackPanel parent, uint32_t dropDownIndex, int selectedKeyIndex, const std::vector& keyCodeList); - // Function to set the flyout warning message - void SetDropDownError(ComboBox dropDown, TextBlock messageBlock, hstring message); + // Function to set the warning message + void SetDropDownError(ComboBox currentDropDown, hstring message); }; diff --git a/src/modules/keyboardmanager/ui/ShortcutControl.cpp b/src/modules/keyboardmanager/ui/ShortcutControl.cpp index ec14a16281..0497725e0d 100644 --- a/src/modules/keyboardmanager/ui/ShortcutControl.cpp +++ b/src/modules/keyboardmanager/ui/ShortcutControl.cpp @@ -11,10 +11,16 @@ std::vector> ShortcutControl::shortcutRemapBuffer; // Function to add a new row to the shortcut table. If the originalKeys and newKeys args are provided, then the displayed shortcuts are set to those values. void ShortcutControl::AddNewShortcutControlRow(Grid& parent, std::vector>>& keyboardRemapControlObjects, Shortcut originalKeys, Shortcut newKeys) { + // Warning icon for the row + ToolTip warningMessage; + FontIcon warningIcon; + warningIcon.Visibility(Visibility::Collapsed); + warningMessage.Content(box_value(KeyboardManagerConstants::ToolTipInitialContent)); + // Create new ShortcutControl objects dynamically so that we does not get destructed std::vector> newrow; - newrow.push_back(std::move(std::unique_ptr(new ShortcutControl(parent, 0)))); - newrow.push_back(std::move(std::unique_ptr(new ShortcutControl(parent, 1)))); + newrow.push_back(std::move(std::unique_ptr(new ShortcutControl(parent, 0, warningIcon, warningMessage)))); + newrow.push_back(std::move(std::unique_ptr(new ShortcutControl(parent, 1, warningIcon, warningMessage)))); keyboardRemapControlObjects.push_back(std::move(newrow)); // Add to grid @@ -40,18 +46,20 @@ void ShortcutControl::AddNewShortcutControlRow(Grid& parent, std::vector()); parent.SetRow(children.GetAt(i).as(), elementRowIndex - 1); } - parent.Children().RemoveAt(index); - parent.Children().RemoveAt(index - 1); - parent.Children().RemoveAt(index - 2); + parent.Children().RemoveAt(lastIndexInRow); + parent.Children().RemoveAt(lastIndexInRow - 1); + parent.Children().RemoveAt(lastIndexInRow - 2); + parent.Children().RemoveAt(lastIndexInRow - 3); // Calculate row index in the buffer from the grid child index (first two children are header elements and then three children in each row) - int bufferIndex = (index - 2) / 3; + int bufferIndex = (lastIndexInRow - 2) / 4; // Delete the row definition parent.RowDefinitions().RemoveAt(bufferIndex + 1); // delete the row from the buffer @@ -62,14 +70,22 @@ void ShortcutControl::AddNewShortcutControlRow(Grid& parent, std::vector{ Shortcut(), Shortcut() }); - keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][0]->AddShortcutToControl(originalKeys, parent, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][0]->shortcutDropDownStackPanel, *keyboardManagerState, 0); - keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1]->AddShortcutToControl(newKeys, parent, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1]->shortcutDropDownStackPanel, *keyboardManagerState, 1); + keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][0]->AddShortcutToControl(originalKeys, parent, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][0]->shortcutDropDownStackPanel, *keyboardManagerState, 0, warningIcon, warningMessage); + keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1]->AddShortcutToControl(newKeys, parent, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1]->shortcutDropDownStackPanel, *keyboardManagerState, 1, warningIcon, warningMessage); } else { @@ -79,7 +95,7 @@ void ShortcutControl::AddNewShortcutControlRow(Grid& parent, std::vector keyCodeList = keyboardManagerState.keyboardMap.GetKeyCodeList(true); if (shortcutKeyCodes.size() != 0) { - KeyDropDownControl::AddDropDown(table, shortcutControlLayout, parent, colIndex, shortcutRemapBuffer, keyDropDownControlObjects); + KeyDropDownControl::AddDropDown(table, shortcutControlLayout, parent, colIndex, shortcutRemapBuffer, keyDropDownControlObjects, warning, toolTip); for (int i = 0; i < shortcutKeyCodes.size(); i++) { // New drop down gets added automatically when the SelectedIndex is set @@ -115,7 +131,7 @@ StackPanel ShortcutControl::getShortcutControl() } // Function to create the detect shortcut UI window -void ShortcutControl::createDetectShortcutWindow(winrt::Windows::Foundation::IInspectable const& sender, XamlRoot xamlRoot, std::vector>& shortcutRemapBuffer, KeyboardManagerState& keyboardManagerState, const size_t colIndex, Grid table) +void ShortcutControl::createDetectShortcutWindow(winrt::Windows::Foundation::IInspectable const& sender, XamlRoot xamlRoot, std::vector>& shortcutRemapBuffer, KeyboardManagerState& keyboardManagerState, const int colIndex, Grid table, FontIcon warning, ToolTip toolTip) { // ContentDialog for detecting shortcuts. This is the parent UI element. ContentDialog detectShortcutBox; @@ -148,14 +164,16 @@ void ShortcutControl::createDetectShortcutWindow(winrt::Windows::Foundation::IIn &shortcutRemapBuffer, unregisterKeys, colIndex, - table] { + table, + warning, + toolTip] { // Save the detected shortcut in the linked text block Shortcut detectedShortcutKeys = keyboardManagerState.GetDetectedShortcut(); if (!detectedShortcutKeys.IsEmpty()) { // The shortcut buffer gets set in this function - AddShortcutToControl(detectedShortcutKeys, table, linkedShortcutStackPanel, keyboardManagerState, colIndex); + AddShortcutToControl(detectedShortcutKeys, table, linkedShortcutStackPanel, keyboardManagerState, colIndex, warning, toolTip); } // Reset the keyboard manager UI state diff --git a/src/modules/keyboardmanager/ui/ShortcutControl.h b/src/modules/keyboardmanager/ui/ShortcutControl.h index 3f5f73a36d..3a4f74380c 100644 --- a/src/modules/keyboardmanager/ui/ShortcutControl.h +++ b/src/modules/keyboardmanager/ui/ShortcutControl.h @@ -29,16 +29,16 @@ public: // Vector to store dynamically allocated KeyDropDownControl objects to avoid early destruction std::vector> keyDropDownControlObjects; - ShortcutControl(Grid table, const size_t colIndex) + ShortcutControl(Grid table, const int colIndex, FontIcon warning, ToolTip toolTip) { shortcutDropDownStackPanel.Spacing(10); shortcutDropDownStackPanel.Orientation(Windows::UI::Xaml::Controls::Orientation::Horizontal); typeShortcut.Content(winrt::box_value(L"Type Shortcut")); - typeShortcut.Click([&, table, colIndex](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + typeShortcut.Click([&, table, colIndex, warning, toolTip](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { keyboardManagerState->SetUIState(KeyboardManagerUIState::DetectShortcutWindowActivated, EditShortcutsWindowHandle); // Using the XamlRoot of the typeShortcut to get the root of the XAML host - createDetectShortcutWindow(sender, sender.as