[KBM]Allow remapping keys and shortcuts to arbitrary unicode sequences (#29399)

* [KBM] Allow remapping keys and shortcuts to arbitrary unicode sequences

* f: spelling

* f: tests

* f: split shortcut configuration

* f: address ui layout comments

* [BugReport]Don't report personal info

* f: fix crash in KBME

* f: add missed type button

* f: fix shortcut line UI elements alignment

* f: align elements size

* f: add warning about non-mapped keys
This commit is contained in:
Andrey Nekrasov
2023-11-23 11:46:07 +01:00
committed by GitHub
parent 2543dee1f1
commit f742d3c1c3
32 changed files with 698 additions and 173 deletions

View File

@@ -220,7 +220,7 @@ namespace BufferValidationHelpers
// 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 == ShortcutErrorType::NoError)
{
KeyShortcutUnion tempShortcut;
KeyShortcutTextUnion tempShortcut;
if (isHybridControl && KeyDropDownControl::GetNumberOfSelectedKeys(selectedCodes) == 1)
{
tempShortcut = (DWORD)*std::find_if(selectedCodes.begin(), selectedCodes.end(), [](int32_t a) { return a != -1 && a != 0; });

View File

@@ -285,14 +285,21 @@ inline void CreateEditKeyboardWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMan
// Load existing remaps into UI
SingleKeyRemapTable singleKeyRemapCopy = mappingConfiguration.singleKeyReMap;
SingleKeyToTextRemapTable singleKeyToTextRemapCopy = mappingConfiguration.singleKeyToTextReMap;
LoadingAndSavingRemappingHelper::PreProcessRemapTable(singleKeyRemapCopy);
LoadingAndSavingRemappingHelper::PreProcessRemapTable(singleKeyToTextRemapCopy);
for (const auto& it : singleKeyRemapCopy)
{
SingleKeyRemapControl::AddNewControlKeyRemapRow(keyRemapTable, keyboardRemapControlObjects, it.first, it.second);
}
for (const auto& it : singleKeyToTextRemapCopy)
{
SingleKeyRemapControl::AddNewControlKeyRemapRow(keyRemapTable, keyboardRemapControlObjects, it.first, it.second);
}
// Main Header Apply button
Button applyButton;
applyButton.Content(winrt::box_value(GET_RESOURCE_STRING(IDS_OK_BUTTON)));

View File

@@ -35,7 +35,7 @@ namespace EditorConstants
inline const long ShortcutTableRemoveColIndex = 4;
inline const long ShortcutArrowColumnWidth = 90;
inline const DWORD64 ShortcutTableDropDownWidth = 160;
inline const DWORD64 ShortcutTableDropDownSpacing = 10;
inline const long ShortcutTableDropDownSpacing = 10;
inline const long ShortcutOriginColumnWidth = 3 * ShortcutTableDropDownWidth + 3 * ShortcutTableDropDownSpacing;
inline const long ShortcutTargetColumnWidth = 3 * ShortcutTableDropDownWidth + 3 * ShortcutTableDropDownSpacing + 15;

View File

@@ -66,7 +66,7 @@ void KeyDropDownControl::SetDefaultProperties(bool isShortcut, bool renderDisabl
}
dropDown.as<ComboBox>().MaxDropDownHeight(EditorConstants::TableDropDownHeight);
// Initialise layout attribute
previousLayout = GetKeyboardLayout(0);
dropDown.as<ComboBox>().SelectedValuePath(L"DataContext");
@@ -83,7 +83,20 @@ void KeyDropDownControl::SetDefaultProperties(bool isShortcut, bool renderDisabl
// Attach the tip to the drop down
warningTip.Target(dropDown.as<ComboBox>());
dropDown.as<ComboBox>().Loaded([&](winrt::Windows::Foundation::IInspectable const& sender, auto args) {
Media::VisualTreeHelper::GetChild(dropDown.as<ComboBox>(), 0).as<Grid>().Children().Append(warningTip);
auto combo = dropDown.as<ComboBox>();
auto child0 = Media::VisualTreeHelper::GetChild(combo, 0);
if (!child0)
return;
auto grid = child0.as<Grid>();
if (!grid)
return;
auto& gridChildren = grid.Children();
if (!gridChildren)
return;
gridChildren.Append(warningTip);
});
// Tip properties
@@ -102,7 +115,7 @@ void KeyDropDownControl::SetDefaultProperties(bool isShortcut, bool renderDisabl
warningFlyout.as<Flyout>().FlyoutPresenterStyle(style);
dropDown.as<ComboBox>().ContextFlyout().SetAttachedFlyout((FrameworkElement)dropDown.as<ComboBox>(), warningFlyout.as<Flyout>());
#endif
// To set the accessible name of the combo-box (by default index 1)
SetAccessibleNameForComboBox(dropDown.as<ComboBox>(), 1);
}
@@ -141,7 +154,7 @@ void KeyDropDownControl::SetSelectionHandler(StackPanel& table, StackPanel row,
ComboBox currentDropDown = sender.as<ComboBox>();
int selectedKeyCode = GetSelectedValue(currentDropDown);
// Validate current remap selection
ShortcutErrorType errorType = BufferValidationHelpers::ValidateAndUpdateKeyBufferElement(rowIndex, colIndex, selectedKeyCode, singleKeyRemapBuffer);
@@ -228,7 +241,7 @@ std::pair<ShortcutErrorType, int> KeyDropDownControl::ValidateShortcutSelection(
}
parent.Children().RemoveAt(dropDownIndex);
// delete drop down control object from the vector so that it can be destructed
keyDropDownControlObjects.erase(keyDropDownControlObjects.begin() + dropDownIndex);
}
@@ -368,7 +381,7 @@ void KeyDropDownControl::ValidateShortcutFromDropDownList(StackPanel table, Stac
{
// Check for errors only if the current selection is a valid shortcut
std::vector<int32_t> selectedKeyCodes = GetSelectedCodesFromStackPanel(parent);
KeyShortcutUnion currentShortcut;
KeyShortcutTextUnion currentShortcut;
if (GetNumberOfSelectedKeys(selectedKeyCodes) == 1 && isHybridControl)
{
currentShortcut = (DWORD)selectedKeyCodes[0];
@@ -415,7 +428,7 @@ void KeyDropDownControl::AddShortcutToControl(Shortcut shortcut, StackPanel tabl
{
// Delete the existing drop down menus
parent.Children().Clear();
// Remove references to the old drop down objects to destroy them
keyDropDownControlObjects.clear();
std::vector<DWORD> shortcutKeyCodes = shortcut.GetKeyCodes();

View File

@@ -11,7 +11,22 @@ namespace KeyboardManagerEditorStrings
{
return GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_ALLAPPS);
}
inline std::wstring MappingTypeText()
{
return GET_RESOURCE_STRING(IDS_MAPPING_TYPE_DROPDOWN_TEXT);
}
inline std::wstring MappingTypeShortcut()
{
return GET_RESOURCE_STRING(IDS_MAPPING_TYPE_DROPDOWN_SHORTCUT);
}
inline std::wstring MappingTypeKey()
{
return GET_RESOURCE_STRING(IDS_MAPPING_TYPE_DROPDOWN_KEY);
}
// Function to return the error message
winrt::hstring GetErrorMessage(ShortcutErrorType errorType);
}

View File

@@ -17,20 +17,20 @@ namespace LoadingAndSavingRemappingHelper
ShortcutErrorType CheckIfRemappingsAreValid(const RemapBuffer& remappings)
{
ShortcutErrorType isSuccess = ShortcutErrorType::NoError;
std::map<std::wstring, std::set<KeyShortcutUnion>> ogKeys;
std::map<std::wstring, std::set<KeyShortcutTextUnion>> ogKeys;
for (int i = 0; i < remappings.size(); i++)
{
KeyShortcutUnion ogKey = remappings[i].first[0];
KeyShortcutUnion newKey = remappings[i].first[1];
KeyShortcutTextUnion ogKey = remappings[i].first[0];
KeyShortcutTextUnion newKey = remappings[i].first[1];
std::wstring appName = remappings[i].second;
bool ogKeyValidity = (ogKey.index() == 0 && std::get<DWORD>(ogKey) != NULL) || (ogKey.index() == 1 && EditorHelpers::IsValidShortcut(std::get<Shortcut>(ogKey)));
bool newKeyValidity = (newKey.index() == 0 && std::get<DWORD>(newKey) != NULL) || (newKey.index() == 1 && EditorHelpers::IsValidShortcut(std::get<Shortcut>(newKey)));
const bool ogKeyValidity = (ogKey.index() == 0 && std::get<DWORD>(ogKey) != NULL) || (ogKey.index() == 1 && EditorHelpers::IsValidShortcut(std::get<Shortcut>(ogKey)));
const bool newKeyValidity = (newKey.index() == 0 && std::get<DWORD>(newKey) != NULL) || (newKey.index() == 1 && EditorHelpers::IsValidShortcut(std::get<Shortcut>(newKey))) || (newKey.index() == 2 && !std::get<std::wstring>(newKey).empty());
// Add new set for a new target app name
if (ogKeys.find(appName) == ogKeys.end())
{
ogKeys[appName] = std::set<KeyShortcutUnion>();
ogKeys[appName] = std::set<KeyShortcutTextUnion>();
}
if (ogKeyValidity && newKeyValidity && ogKeys[appName].find(ogKey) == ogKeys[appName].end())
@@ -59,9 +59,12 @@ namespace LoadingAndSavingRemappingHelper
for (int i = 0; i < remappings.size(); i++)
{
DWORD ogKey = std::get<DWORD>(remappings[i].first[0]);
KeyShortcutUnion newKey = remappings[i].first[1];
KeyShortcutTextUnion newKey = remappings[i].first[1];
if (ogKey != NULL && ((newKey.index() == 0 && std::get<DWORD>(newKey) != 0) || (newKey.index() == 1 && EditorHelpers::IsValidShortcut(std::get<Shortcut>(newKey)))))
const bool hasValidKeyRemapping = newKey.index() == 0 && std::get<DWORD>(newKey) != 0;
const bool hasValidShortcutRemapping = newKey.index() == 1 && EditorHelpers::IsValidShortcut(std::get<Shortcut>(newKey));
const bool hasValidTextRemapping = newKey.index() == 2 && !std::get<std::wstring>(newKey).empty();
if (ogKey != NULL && (hasValidKeyRemapping || hasValidShortcutRemapping || hasValidTextRemapping))
{
ogKeys.insert(ogKey);
@@ -116,53 +119,64 @@ namespace LoadingAndSavingRemappingHelper
{
// Clear existing Key Remaps
mappingConfiguration.ClearSingleKeyRemaps();
mappingConfiguration.ClearSingleKeyToTextRemaps();
DWORD successfulKeyToKeyRemapCount = 0;
DWORD successfulKeyToShortcutRemapCount = 0;
DWORD successfulKeyToTextRemapCount = 0;
for (int i = 0; i < remappings.size(); i++)
{
DWORD originalKey = std::get<DWORD>(remappings[i].first[0]);
KeyShortcutUnion newKey = remappings[i].first[1];
const DWORD originalKey = std::get<DWORD>(remappings[i].first[0]);
KeyShortcutTextUnion newKey = remappings[i].first[1];
if (originalKey != NULL && !(newKey.index() == 0 && std::get<DWORD>(newKey) == NULL) && !(newKey.index() == 1 && !EditorHelpers::IsValidShortcut(std::get<Shortcut>(newKey))))
if (originalKey != NULL && !(newKey.index() == 0 && std::get<DWORD>(newKey) == NULL) && !(newKey.index() == 1 && !EditorHelpers::IsValidShortcut(std::get<Shortcut>(newKey))) && !(newKey.index() == 2 && std::get<std::wstring>(newKey).empty()))
{
// If Ctrl/Alt/Shift are added, add their L and R versions instead to the same key
bool result = false;
bool res1, res2;
switch (originalKey)
std::vector<DWORD> originalKeysWithModifiers;
if (originalKey == VK_CONTROL)
{
case VK_CONTROL:
res1 = mappingConfiguration.AddSingleKeyRemap(VK_LCONTROL, newKey);
res2 = mappingConfiguration.AddSingleKeyRemap(VK_RCONTROL, newKey);
result = res1 && res2;
break;
case VK_MENU:
res1 = mappingConfiguration.AddSingleKeyRemap(VK_LMENU, newKey);
res2 = mappingConfiguration.AddSingleKeyRemap(VK_RMENU, newKey);
result = res1 && res2;
break;
case VK_SHIFT:
res1 = mappingConfiguration.AddSingleKeyRemap(VK_LSHIFT, newKey);
res2 = mappingConfiguration.AddSingleKeyRemap(VK_RSHIFT, newKey);
result = res1 && res2;
break;
case CommonSharedConstants::VK_WIN_BOTH:
res1 = mappingConfiguration.AddSingleKeyRemap(VK_LWIN, newKey);
res2 = mappingConfiguration.AddSingleKeyRemap(VK_RWIN, newKey);
result = res1 && res2;
break;
default:
result = mappingConfiguration.AddSingleKeyRemap(originalKey, newKey);
originalKeysWithModifiers.push_back(VK_LCONTROL);
originalKeysWithModifiers.push_back(VK_RCONTROL);
}
else if (originalKey == VK_MENU)
{
originalKeysWithModifiers.push_back(VK_LMENU);
originalKeysWithModifiers.push_back(VK_RMENU);
}
else if (originalKey == VK_SHIFT)
{
originalKeysWithModifiers.push_back(VK_LSHIFT);
originalKeysWithModifiers.push_back(VK_RSHIFT);
}
else if (originalKey == CommonSharedConstants::VK_WIN_BOTH)
{
originalKeysWithModifiers.push_back(VK_LWIN);
originalKeysWithModifiers.push_back(VK_RWIN);
}
else
{
originalKeysWithModifiers.push_back(originalKey);
}
for (const DWORD key : originalKeysWithModifiers)
{
const bool mappedToText = newKey.index() == 2;
result = mappedToText ? mappingConfiguration.AddSingleKeyToTextRemap(key, std::get<std::wstring>(newKey)) : mappingConfiguration.AddSingleKeyRemap(key, newKey) && result;
}
if (result)
{
if (newKey.index() == 0)
{
successfulKeyToKeyRemapCount += 1;
++successfulKeyToKeyRemapCount;
}
else
else if (newKey.index() == 1)
{
successfulKeyToShortcutRemapCount += 1;
++successfulKeyToShortcutRemapCount;
}
else if (newKey.index() == 2)
{
++successfulKeyToTextRemapCount;
}
}
}
@@ -171,7 +185,7 @@ namespace LoadingAndSavingRemappingHelper
// If telemetry is to be logged, log the key remap counts
if (isTelemetryRequired)
{
Trace::KeyRemapCount(successfulKeyToKeyRemapCount, successfulKeyToShortcutRemapCount);
Trace::KeyRemapCount(successfulKeyToKeyRemapCount, successfulKeyToShortcutRemapCount, successfulKeyToTextRemapCount);
}
}
@@ -185,14 +199,14 @@ namespace LoadingAndSavingRemappingHelper
DWORD successfulOSLevelShortcutToKeyRemapCount = 0;
DWORD successfulAppSpecificShortcutToShortcutRemapCount = 0;
DWORD successfulAppSpecificShortcutToKeyRemapCount = 0;
// Save the shortcuts that are valid and report if any of them were invalid
for (int i = 0; i < remappings.size(); i++)
{
Shortcut originalShortcut = std::get<Shortcut>(remappings[i].first[0]);
KeyShortcutUnion newShortcut = remappings[i].first[1];
KeyShortcutTextUnion newShortcut = remappings[i].first[1];
if (EditorHelpers::IsValidShortcut(originalShortcut) && ((newShortcut.index() == 0 && std::get<DWORD>(newShortcut) != NULL) || (newShortcut.index() == 1 && EditorHelpers::IsValidShortcut(std::get<Shortcut>(newShortcut)))))
if (EditorHelpers::IsValidShortcut(originalShortcut) && ((newShortcut.index() == 0 && std::get<DWORD>(newShortcut) != NULL) || (newShortcut.index() == 1 && EditorHelpers::IsValidShortcut(std::get<Shortcut>(newShortcut))) || (newShortcut.index() == 2 && !std::get<std::wstring>(newShortcut).empty())))
{
if (remappings[i].second == L"")
{

View File

@@ -15,10 +15,10 @@ namespace LoadingAndSavingRemappingHelper
std::vector<DWORD> GetOrphanedKeys(const RemapBuffer& remappings);
// Function to combine remappings if the L and R version of the modifier is mapped to the same key
void CombineRemappings(std::unordered_map<DWORD, KeyShortcutUnion>& table, DWORD leftKey, DWORD rightKey, DWORD combinedKey);
void CombineRemappings(std::unordered_map<DWORD, KeyShortcutTextUnion>& table, DWORD leftKey, DWORD rightKey, DWORD combinedKey);
// Function to pre process the remap table before loading it into the UI
void PreProcessRemapTable(std::unordered_map<DWORD, KeyShortcutUnion>& table);
void PreProcessRemapTable(std::unordered_map<DWORD, KeyShortcutTextUnion>& table);
// Function to apply the single key remappings from the buffer to the KeyboardManagerState variable
void ApplySingleKeyRemappings(MappingConfiguration& mappingConfiguration, const RemapBuffer& remappings, bool isTelemetryRequired);

View File

@@ -21,7 +21,7 @@ ShortcutControl::ShortcutControl(StackPanel table, StackPanel row, const int col
shortcutDropDownVariableSizedWrapGrid = VariableSizedWrapGrid();
typeShortcut = Button();
shortcutControlLayout = StackPanel();
bool isHybridControl = colIndex == 1 ? true : false;
const bool isHybridControl = colIndex == 1;
// TODO: Check if there is a VariableSizedWrapGrid equivalent.
// shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>().Spacing(EditorConstants::ShortcutTableDropDownSpacing);
@@ -41,7 +41,13 @@ ShortcutControl::ShortcutControl(StackPanel table, StackPanel row, const int col
shortcutControlLayout.as<StackPanel>().Spacing(EditorConstants::ShortcutTableDropDownSpacing);
shortcutControlLayout.as<StackPanel>().Children().Append(typeShortcut.as<Button>());
keyComboAndSelectStackPanel = StackPanel();
keyComboAndSelectStackPanel.as<StackPanel>().Orientation(Windows::UI::Xaml::Controls::Orientation::Horizontal);
keyComboAndSelectStackPanel.as<StackPanel>().Spacing(EditorConstants::ShortcutTableDropDownSpacing);
keyComboAndSelectStackPanel.as<StackPanel>().Children().Append(typeShortcut.as<Button>());
shortcutControlLayout.as<StackPanel>().Children().InsertAt(0, keyComboAndSelectStackPanel.as<StackPanel>());
shortcutControlLayout.as<StackPanel>().Children().Append(shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>());
KeyDropDownControl::AddDropDown(table, row, shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>(), colIndex, shortcutRemapBuffer, keyDropDownControlObjects, targetApp, isHybridControl, false);
try
@@ -63,7 +69,7 @@ void ShortcutControl::SetAccessibleNameForTextBox(TextBox targetAppTextBox, int
{
targetAppTextBoxAccessibleName += GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_ALLAPPS);
}
targetAppTextBox.SetValue(Automation::AutomationProperties::NameProperty(), box_value(targetAppTextBoxAccessibleName));
}
@@ -77,7 +83,7 @@ void ShortcutControl::UpdateAccessibleNames(StackPanel sourceColumn, StackPanel
}
// 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(StackPanel& parent, std::vector<std::vector<std::unique_ptr<ShortcutControl>>>& keyboardRemapControlObjects, const Shortcut& originalKeys, const KeyShortcutUnion& newKeys, const std::wstring& targetAppName)
void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector<std::vector<std::unique_ptr<ShortcutControl>>>& keyboardRemapControlObjects, const Shortcut& originalKeys, const KeyShortcutTextUnion& newKeys, const std::wstring& targetAppName)
{
// Textbox for target application
TextBox targetAppTextBox;
@@ -115,6 +121,60 @@ void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector<s
// ShortcutControl for the new shortcut
auto target = keyboardRemapControlObjects.back()[1]->GetShortcutControl();
target.Width(EditorConstants::ShortcutTargetColumnWidth);
auto typeCombo = ComboBox();
typeCombo.Width(EditorConstants::RemapTableDropDownWidth);
typeCombo.Items().Append(winrt::box_value(KeyboardManagerEditorStrings::MappingTypeShortcut()));
typeCombo.Items().Append(winrt::box_value(KeyboardManagerEditorStrings::MappingTypeText()));
auto controlStackPanel = keyboardRemapControlObjects.back()[1]->shortcutControlLayout.as<StackPanel>();
auto firstLineStackPanel = keyboardRemapControlObjects.back()[1]->keyComboAndSelectStackPanel.as<StackPanel>();
firstLineStackPanel.Children().InsertAt(0, typeCombo);
auto textInput = TextBox();
auto textInputMargin = Windows::UI::Xaml::Thickness();
textInputMargin.Top = -EditorConstants::ShortcutTableDropDownSpacing;
textInputMargin.Bottom = EditorConstants::ShortcutTableDropDownSpacing; // compensate for a collapsed UIElement
textInput.Margin(textInputMargin);
textInput.AcceptsReturn(false);
textInput.Visibility(Visibility::Collapsed);
textInput.Width(EditorConstants::TableDropDownHeight);
controlStackPanel.Children().Append(textInput);
textInput.HorizontalAlignment(HorizontalAlignment::Left);
textInput.TextChanged([parent, row](winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Controls::TextChangedEventArgs const& e) mutable {
auto textbox = sender.as<TextBox>();
auto text = textbox.Text();
uint32_t rowIndex = -1;
if (!parent.Children().IndexOf(row, rowIndex))
{
return;
}
shortcutRemapBuffer[rowIndex].first[1] = text.c_str();
});
auto grid = keyboardRemapControlObjects.back()[1]->shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>();
auto gridMargin = Windows::UI::Xaml::Thickness();
gridMargin.Bottom = -EditorConstants::ShortcutTableDropDownSpacing; // compensate for a collapsed textInput
grid.Margin(gridMargin);
auto button = keyboardRemapControlObjects.back()[1]->typeShortcut.as<Button>();
typeCombo.SelectionChanged([typeCombo, grid, button, textInput](winrt::Windows::Foundation::IInspectable const&, SelectionChangedEventArgs const&) {
const bool textSelected = typeCombo.SelectedIndex() == 1;
const auto shortcutInputVisibility = textSelected ? Visibility::Collapsed : Visibility::Visible;
grid.Visibility(shortcutInputVisibility);
button.Visibility(shortcutInputVisibility);
const auto textInputVisibility = textSelected ? Visibility::Visible : Visibility::Collapsed;
textInput.Visibility(textInputVisibility);
});
const bool textSelected = newKeys.index() == 2;
typeCombo.SelectedIndex(textSelected);
row.Children().Append(target);
targetAppTextBox.Width(EditorConstants::ShortcutTableDropDownWidth);
@@ -129,7 +189,7 @@ void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector<s
});
// LostFocus handler will be called whenever text is updated by a user and then they click something else or tab to another control. Does not get called if Text is updated while the TextBox isn't in focus (i.e. from code)
targetAppTextBox.LostFocus([&keyboardRemapControlObjects, parent, row, targetAppTextBox](auto const& sender, auto const& e) {
targetAppTextBox.LostFocus([&keyboardRemapControlObjects, parent, row, targetAppTextBox, typeCombo, textInput](auto const& sender, auto const& e) {
// Get index of targetAppTextBox button
uint32_t rowIndex;
if (!parent.Children().IndexOf(row, rowIndex))
@@ -151,19 +211,27 @@ void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector<s
std::get<Shortcut>(shortcutRemapBuffer[rowIndex].first[0]).SetKeyCodes(KeyDropDownControl::GetSelectedCodesFromStackPanel(keyboardRemapControlObjects[rowIndex][0]->shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>()));
// second column is a hybrid column
std::vector<int32_t> selectedKeyCodes = KeyDropDownControl::GetSelectedCodesFromStackPanel(keyboardRemapControlObjects[rowIndex][1]->shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>());
// If exactly one key is selected consider it to be a key remap
if (selectedKeyCodes.size() == 1)
const bool textSelected = typeCombo.SelectedIndex() == 1;
if (textSelected)
{
shortcutRemapBuffer[rowIndex].first[1] = (DWORD)selectedKeyCodes[0];
shortcutRemapBuffer[rowIndex].first[1] = textInput.Text().c_str();
}
else
{
Shortcut tempShortcut;
tempShortcut.SetKeyCodes(selectedKeyCodes);
// Assign instead of setting the value in the buffer since the previous value may not be a Shortcut
shortcutRemapBuffer[rowIndex].first[1] = tempShortcut;
std::vector<int32_t> selectedKeyCodes = KeyDropDownControl::GetSelectedCodesFromStackPanel(keyboardRemapControlObjects[rowIndex][1]->shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>());
// If exactly one key is selected consider it to be a key remap
if (selectedKeyCodes.size() == 1)
{
shortcutRemapBuffer[rowIndex].first[1] = (DWORD)selectedKeyCodes[0];
}
else
{
Shortcut tempShortcut;
tempShortcut.SetKeyCodes(selectedKeyCodes);
// Assign instead of setting the value in the buffer since the previous value may not be a Shortcut
shortcutRemapBuffer[rowIndex].first[1] = tempShortcut;
}
}
std::wstring newText = targetAppTextBox.Text().c_str();
std::wstring lowercaseDefAppName = KeyboardManagerEditorStrings::DefaultAppName();
@@ -263,10 +331,19 @@ void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector<s
{
keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1]->keyDropDownControlObjects[0]->SetSelectedValue(std::to_wstring(std::get<DWORD>(newKeys)));
}
else
else if (newKeys.index() == 1)
{
KeyDropDownControl::AddShortcutToControl(std::get<Shortcut>(newKeys), parent, keyboardRemapControlObjects.back()[1]->shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>(), *keyboardManagerState, 1, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1]->keyDropDownControlObjects, shortcutRemapBuffer, row, targetAppTextBox, true, false);
}
else if (newKeys.index() == 2)
{
shortcutRemapBuffer.back().first[1] = std::get<std::wstring>(newKeys);
const auto& remapControl = keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1];
const auto& controlChildren = remapControl->GetShortcutControl().Children();
const auto& topLineChildren = controlChildren.GetAt(0).as<StackPanel>();
topLineChildren.Children().GetAt(0).as<ComboBox>().SelectedIndex(1);
controlChildren.GetAt(2).as<TextBox>().Text(std::get<std::wstring>(newKeys));
}
}
else
{
@@ -291,8 +368,8 @@ void ShortcutControl::CreateDetectShortcutWindow(winrt::Windows::Foundation::IIn
detectShortcutBox.XamlRoot(xamlRoot);
detectShortcutBox.Title(box_value(GET_RESOURCE_STRING(IDS_TYPESHORTCUT_TITLE)));
// Get the linked stack panel for the "Type shortcut" button that was clicked
VariableSizedWrapGrid linkedShortcutVariableSizedWrapGrid = UIHelpers::GetSiblingElement(sender).as<VariableSizedWrapGrid>();
// Get the parent linked stack panel for the "Type shortcut" button that was clicked
VariableSizedWrapGrid linkedShortcutVariableSizedWrapGrid = UIHelpers::GetSiblingElement(sender.as<FrameworkElement>().Parent()).as<VariableSizedWrapGrid>();
auto unregisterKeys = [&keyboardManagerState]() {
keyboardManagerState.ClearRegisteredKeyDelays();

View File

@@ -31,6 +31,9 @@ private:
// StackPanel to parent the above controls
winrt::Windows::Foundation::IInspectable shortcutControlLayout;
// StackPanel to parent the first line of "To" Column
winrt::Windows::Foundation::IInspectable keyComboAndSelectStackPanel;
// Function to set the accessible name of the target app text box
static void SetAccessibleNameForTextBox(TextBox targetAppTextBox, int rowIndex);
@@ -54,7 +57,7 @@ public:
ShortcutControl(StackPanel table, StackPanel row, const int colIndex, TextBox targetApp);
// 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.
static void AddNewShortcutControlRow(StackPanel& parent, std::vector<std::vector<std::unique_ptr<ShortcutControl>>>& keyboardRemapControlObjects, const Shortcut& originalKeys = Shortcut(), const KeyShortcutUnion& newKeys = Shortcut(), const std::wstring& targetAppName = L"");
static void AddNewShortcutControlRow(StackPanel& parent, std::vector<std::vector<std::unique_ptr<ShortcutControl>>>& keyboardRemapControlObjects, const Shortcut& originalKeys = Shortcut(), const KeyShortcutTextUnion& newKeys = Shortcut(), const std::wstring& targetAppName = L"");
// Function to return the stack panel element of the ShortcutControl. This is the externally visible UI element which can be used to add it to other layouts
StackPanel GetShortcutControl();

View File

@@ -2,6 +2,7 @@
#include "SingleKeyRemapControl.h"
#include "KeyboardManagerState.h"
#include "KeyboardManagerEditorStrings.h"
#include "ShortcutControl.h"
#include "UIHelpers.h"
#include "EditorHelpers.h"
@@ -20,25 +21,80 @@ SingleKeyRemapControl::SingleKeyRemapControl(StackPanel table, StackPanel row, c
typeKey.as<Button>().Content(winrt::box_value(GET_RESOURCE_STRING(IDS_TYPE_BUTTON)));
singleKeyRemapControlLayout = StackPanel();
singleKeyRemapControlLayout.as<StackPanel>().Spacing(10);
singleKeyRemapControlLayout.as<StackPanel>().Children().Append(typeKey.as<Button>());
singleKeyRemapControlLayout.as<StackPanel>().Spacing(EditorConstants::ShortcutTableDropDownSpacing);
// Key column
// Key column (From key)
if (colIndex == 0)
{
singleKeyRemapControlLayout.as<StackPanel>().Children().Append(typeKey.as<Button>());
keyDropDownControlObjects.emplace_back(std::make_unique<KeyDropDownControl>(false));
singleKeyRemapControlLayout.as<StackPanel>().Children().Append(keyDropDownControlObjects[0]->GetComboBox());
// Set selection handler for the drop down
keyDropDownControlObjects[0]->SetSelectionHandler(table, row, colIndex, singleKeyRemapBuffer);
}
// Hybrid column
// Hybrid column (To Key/Shortcut/Text)
else
{
StackPanel keyComboAndSelectStackPanel;
keyComboAndSelectStackPanel.Orientation(Windows::UI::Xaml::Controls::Orientation::Horizontal);
keyComboAndSelectStackPanel.Spacing(EditorConstants::ShortcutTableDropDownSpacing);
hybridDropDownVariableSizedWrapGrid = VariableSizedWrapGrid();
hybridDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>().Orientation(Windows::UI::Xaml::Controls::Orientation::Horizontal);
KeyDropDownControl::AddDropDown(table, row, hybridDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>(), colIndex, singleKeyRemapBuffer, keyDropDownControlObjects, nullptr, true, true);
singleKeyRemapControlLayout.as<StackPanel>().Children().Append(hybridDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>());
auto grid = hybridDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>();
grid.Orientation(Windows::UI::Xaml::Controls::Orientation::Horizontal);
auto gridMargin = Windows::UI::Xaml::Thickness();
gridMargin.Bottom = -EditorConstants::ShortcutTableDropDownSpacing; // compensate for a collapsed textInput
grid.Margin(gridMargin);
KeyDropDownControl::AddDropDown(table, row, grid, colIndex, singleKeyRemapBuffer, keyDropDownControlObjects, nullptr, true, true);
singleKeyRemapControlLayout.as<StackPanel>().Children().Append(grid);
auto textInput = TextBox();
auto textBoxMargin = Windows::UI::Xaml::Thickness();
textBoxMargin.Top = -EditorConstants::ShortcutTableDropDownSpacing; // compensate for a collapsed grid
textBoxMargin.Bottom = EditorConstants::ShortcutTableDropDownSpacing;
textInput.Margin(textBoxMargin);
textInput.AcceptsReturn(false);
textInput.Visibility(Visibility::Collapsed);
textInput.Width(EditorConstants::TableDropDownHeight);
singleKeyRemapControlLayout.as<StackPanel>().Children().Append(textInput);
textInput.HorizontalAlignment(HorizontalAlignment::Left);
textInput.TextChanged([this, row, table](winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Controls::TextChangedEventArgs const& e) mutable {
auto textbox = sender.as<TextBox>();
auto text = textbox.Text();
uint32_t rowIndex = -1;
if (!table.Children().IndexOf(row, rowIndex))
{
return;
}
singleKeyRemapBuffer[rowIndex].first[1] = text.c_str();
});
auto typeCombo = ComboBox();
typeCombo.Width(EditorConstants::RemapTableDropDownWidth);
typeCombo.Items().Append(winrt::box_value(KeyboardManagerEditorStrings::MappingTypeKey()));
typeCombo.Items().Append(winrt::box_value(KeyboardManagerEditorStrings::MappingTypeText()));
keyComboAndSelectStackPanel.Children().Append(typeCombo);
keyComboAndSelectStackPanel.Children().Append(typeKey.as<Button>());
singleKeyRemapControlLayout.as<StackPanel>().Children().InsertAt(0, keyComboAndSelectStackPanel);
typeCombo.SelectedIndex(0);
typeCombo.SelectionChanged([this, typeCombo, grid, textInput](winrt::Windows::Foundation::IInspectable const&, SelectionChangedEventArgs const&) {
const bool textSelected = typeCombo.SelectedIndex() == 1;
const auto keyInputVisibility = textSelected ? Visibility::Collapsed : Visibility::Visible;
grid.Visibility(keyInputVisibility);
typeKey.as<Button>().Visibility(keyInputVisibility);
const auto textInputVisibility = textSelected ? Visibility::Visible : Visibility::Collapsed;
textInput.Visibility(textInputVisibility);
});
}
typeKey.as<Button>().Click([&, table, colIndex, row](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) {
@@ -73,8 +129,15 @@ void SingleKeyRemapControl::UpdateAccessibleNames(StackPanel sourceColumn, Stack
deleteButton.SetValue(Automation::AutomationProperties::NameProperty(), box_value(GET_RESOURCE_STRING(IDS_AUTOMATIONPROPERTIES_ROW) + std::to_wstring(rowIndex) + L", " + GET_RESOURCE_STRING(IDS_DELETE_REMAPPING_BUTTON)));
}
void SingleKeyRemapControl::TextToMapChangedHandler(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Controls::TextChangedEventArgs const& e) // TODO: remove
{
auto textbox = sender.as<TextBox>();
auto text = textbox.Text();
(void)text;
}
// Function to add a new row to the remap keys table. If the originalKey and newKey args are provided, then the displayed remap keys are set to those values.
void SingleKeyRemapControl::AddNewControlKeyRemapRow(StackPanel& parent, std::vector<std::vector<std::unique_ptr<SingleKeyRemapControl>>>& keyboardRemapControlObjects, const DWORD originalKey, const KeyShortcutUnion newKey)
void SingleKeyRemapControl::AddNewControlKeyRemapRow(StackPanel& parent, std::vector<std::vector<std::unique_ptr<SingleKeyRemapControl>>>& keyboardRemapControlObjects, const DWORD originalKey, const KeyShortcutTextUnion newKey)
{
// Create new SingleKeyRemapControl objects dynamically so that we does not get destructed
std::vector<std::unique_ptr<SingleKeyRemapControl>> newrow;
@@ -112,7 +175,7 @@ void SingleKeyRemapControl::AddNewControlKeyRemapRow(StackPanel& parent, std::ve
row.Children().Append(targetElement);
// Set the key text if the two keys are not null (i.e. default args)
if (originalKey != NULL && !(newKey.index() == 0 && std::get<DWORD>(newKey) == NULL) && !(newKey.index() == 1 && !EditorHelpers::IsValidShortcut(std::get<Shortcut>(newKey))))
if (originalKey != NULL && !(newKey.index() == 0 && std::get<DWORD>(newKey) == NULL) && !(newKey.index() == 1 && !EditorHelpers::IsValidShortcut(std::get<Shortcut>(newKey))) && !(newKey.index() == 2 && std::get<std::wstring>(newKey).empty()))
{
singleKeyRemapBuffer.push_back(std::make_pair<RemapBufferItem, std::wstring>(RemapBufferItem{ originalKey, newKey }, L""));
keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][0]->keyDropDownControlObjects[0]->SetSelectedValue(std::to_wstring(originalKey));
@@ -120,10 +183,20 @@ void SingleKeyRemapControl::AddNewControlKeyRemapRow(StackPanel& parent, std::ve
{
keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1]->keyDropDownControlObjects[0]->SetSelectedValue(std::to_wstring(std::get<DWORD>(newKey)));
}
else
else if (newKey.index() == 1)
{
KeyDropDownControl::AddShortcutToControl(std::get<Shortcut>(newKey), parent, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1]->hybridDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>(), *keyboardManagerState, 1, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1]->keyDropDownControlObjects, singleKeyRemapBuffer, row, nullptr, true, true);
}
else if (newKey.index() == 2)
{
auto& singleKeyRemapControl = keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1];
const auto& firstLineStackPanel = singleKeyRemapControl->singleKeyRemapControlLayout.as<StackPanel>().Children().GetAt(0).as<StackPanel>();
firstLineStackPanel.Children().GetAt(0).as<ComboBox>().SelectedIndex(1);
singleKeyRemapControl->singleKeyRemapControlLayout.as<StackPanel>().Children().GetAt(2).as<TextBox>().Text(std::get<std::wstring>(newKey));
}
}
else
{
@@ -178,7 +251,7 @@ void SingleKeyRemapControl::AddNewControlKeyRemapRow(StackPanel& parent, std::ve
{
}
singleKeyRemapBuffer.erase(singleKeyRemapBuffer.begin() + rowIndex);
// delete the SingleKeyRemapControl objects so that they get destructed
keyboardRemapControlObjects.erase(keyboardRemapControlObjects.begin() + rowIndex);
});

View File

@@ -35,6 +35,8 @@ private:
// Function to set the accessible names for all the controls in a row
static void UpdateAccessibleNames(StackPanel sourceColumn, StackPanel mappedToColumn, Button deleteButton, int rowIndex);
void TextToMapChangedHandler(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Controls::TextChangedEventArgs const& e);
public:
// Vector to store dynamically allocated KeyDropDownControl objects to avoid early destruction
std::vector<std::unique_ptr<KeyDropDownControl>> keyDropDownControlObjects;
@@ -52,7 +54,7 @@ public:
SingleKeyRemapControl(StackPanel table, StackPanel row, const int colIndex);
// Function to add a new row to the remap keys table. If the originalKey and newKey args are provided, then the displayed remap keys are set to those values.
static void AddNewControlKeyRemapRow(StackPanel& parent, std::vector<std::vector<std::unique_ptr<SingleKeyRemapControl>>>& keyboardRemapControlObjects, const DWORD originalKey = 0, const KeyShortcutUnion newKey = (DWORD)0);
static void AddNewControlKeyRemapRow(StackPanel& parent, std::vector<std::vector<std::unique_ptr<SingleKeyRemapControl>>>& keyboardRemapControlObjects, const DWORD originalKey = 0, const KeyShortcutTextUnion newKey = (DWORD)0);
// Function to return the stack panel element of the SingleKeyRemapControl. This is the externally visible UI element which can be used to add it to other layouts
winrt::Windows::UI::Xaml::Controls::StackPanel getSingleKeyRemapControl();

View File

@@ -9,13 +9,19 @@ namespace UIHelpers
void SetFocusOnTypeButtonInLastRow(StackPanel& parent, long colCount)
{
// First element in the last row (StackPanel)
StackPanel firstElementInLastRow = parent.Children().GetAt(parent.Children().Size() - 1).as<StackPanel>().Children().GetAt(0).as<StackPanel>();
auto lastHotKeyLine = parent.Children().GetAt(parent.Children().Size() - 1).as<StackPanel>();
// Type button is the first child in the StackPanel
Button firstTypeButtonInLastRow = firstElementInLastRow.Children().GetAt(0).as<Button>();
// Get "To" Column
auto toColumn = lastHotKeyLine.Children().GetAt(2).as<StackPanel>();
// Get first line in "To" Column
auto firstLineIntoColumn = toColumn.Children().GetAt(0).as<StackPanel>();
// Get Type Button from the first line
Button typeButton = firstLineIntoColumn.Children().GetAt(1).as<Button>();
// Set programmatic focus on the button
firstTypeButtonInLastRow.Focus(FocusState::Programmatic);
typeButton.Focus(FocusState::Programmatic);
}
RECT GetForegroundWindowDesktopRect()
@@ -68,4 +74,22 @@ namespace UIHelpers
return boxList;
}
#ifndef NDEBUG
std::vector<std::wstring> GetChildrenNames(StackPanel& s)
{
std::vector<std::wstring> result;
for (auto child : s.Children())
{
std::wstring nameAndClass =
child.as<IFrameworkElement>().Name().c_str();
nameAndClass += L" ";
nameAndClass += winrt::get_class_name(child.try_as<winrt::Windows::Foundation::IInspectable>()).c_str();
result.push_back(nameAndClass);
}
return result;
}
#endif
}

View File

@@ -29,4 +29,9 @@ namespace UIHelpers
// 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<winrt::Windows::Foundation::IInspectable> ToBoxValue(const std::vector<std::pair<DWORD, std::wstring>>& list);
#ifndef NDEBUG
// Useful For debugging issues
std::vector<std::wstring> GetChildrenNames(StackPanel& s);
#endif
}

View File

@@ -19,7 +19,7 @@ void Trace::UnregisterProvider() noexcept
}
// Log number of key remaps when the user uses Edit Keyboard and saves settings
void Trace::KeyRemapCount(const DWORD keyToKeyCount, const DWORD keyToShortcutCount) noexcept
void Trace::KeyRemapCount(const DWORD keyToKeyCount, const DWORD keyToShortcutCount, const DWORD keyToTextCount) noexcept
{
TraceLoggingWrite(
g_hProvider,
@@ -28,7 +28,8 @@ void Trace::KeyRemapCount(const DWORD keyToKeyCount, const DWORD keyToShortcutCo
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingValue(keyToKeyCount + keyToShortcutCount, "KeyRemapCount"),
TraceLoggingValue(keyToKeyCount, "KeyToKeyRemapCount"),
TraceLoggingValue(keyToShortcutCount, "KeyToShortcutRemapCount"));
TraceLoggingValue(keyToShortcutCount, "KeyToShortcutRemapCount"),
TraceLoggingValue(keyToTextCount, "KeyToTextRemapCount"));
}
// Log number of os level shortcut remaps when the user uses Edit Shortcuts and saves settings

View File

@@ -7,14 +7,14 @@ public:
static void UnregisterProvider() noexcept;
// Log number of key remaps when the user uses Edit Keyboard and saves settings
static void KeyRemapCount(const DWORD keyToKeyCount, const DWORD keyToShortcutCount) noexcept;
static void KeyRemapCount(const DWORD keyToKeyCount, const DWORD keyToShortcutCount, const DWORD keyToTextCount) noexcept;
// Log number of os level shortcut remaps when the user uses Edit Shortcuts and saves settings
static void OSLevelShortcutRemapCount(const DWORD shortcutToShortcutCount, const DWORD shortcutToKeyCount) noexcept;
// Log number of app specific shortcut remaps when the user uses Edit Shortcuts and saves settings
static void AppSpecificShortcutRemapCount(const DWORD shortcutToShortcutCount, const DWORD shortcutToKeyCount) noexcept;
// Log if an error occurs in KBM
static void Error(const DWORD errorCode, std::wstring errorMessage, std::wstring methodName) noexcept;
};