[Keyboard Manager] Add app-specific shortcuts to Remap shortcuts UI (#4804)

* Enable app specific shortcut remapping

* Fixed lowercase function call

* Add test file

* Moved GetForegroundProcess to II and added tests

* Fixed runtime error while testing due to heap allocation across dll boundary

* Renamed function

* Changed shortcutBuffer type

* Linked App specific UI to backend

* Added shortcut validation logic on TextBox LostFocus handler

* Moved Validate function and changed default text

* Changed to case insensitive warning check

* Changed to case insensitive warning check at OnClickAccept

* Fixed alignment and spacing issues
This commit is contained in:
Arjun Balgovind 2020-07-08 16:24:30 -07:00 committed by GitHub
parent 411140c3ea
commit 4634085402
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 217 additions and 75 deletions

View File

@ -47,7 +47,7 @@ namespace KeyboardManagerConstants
// Default window sizes
inline const int DefaultEditKeyboardWindowWidth = 800;
inline const int DefaultEditKeyboardWindowHeight = 600;
inline const int DefaultEditShortcutsWindowWidth = 1000;
inline const int DefaultEditShortcutsWindowWidth = 1050;
inline const int DefaultEditShortcutsWindowHeight = 600;
// Key Remap table constants
@ -60,12 +60,13 @@ namespace KeyboardManagerConstants
inline const DWORD64 RemapTableDropDownWidth = 110;
// Shortcut table constants
inline const long ShortcutTableColCount = 4;
inline const long ShortcutTableHeaderCount = 2;
inline const long ShortcutTableColCount = 5;
inline const long ShortcutTableHeaderCount = 3;
inline const long ShortcutTableOriginalColIndex = 0;
inline const long ShortcutTableArrowColIndex = 1;
inline const long ShortcutTableNewColIndex = 2;
inline const long ShortcutTableRemoveColIndex = 3;
inline const long ShortcutTableTargetAppColIndex = 3;
inline const long ShortcutTableRemoveColIndex = 4;
inline const DWORD64 ShortcutTableDropDownWidth = 110;
inline const DWORD64 ShortcutTableDropDownSpacing = 10;
@ -74,6 +75,7 @@ namespace KeyboardManagerConstants
inline const DWORD64 TableArrowColWidth = 20;
inline const DWORD64 TableRemoveColWidth = 20;
inline const DWORD64 TableWarningColWidth = 20;
inline const DWORD64 TableTargetAppColWidth = ShortcutTableDropDownWidth + 50;
// Shared style constants for both Remap Table and Shortcut Table
inline const DWORD64 HeaderButtonWidth = 100;
@ -85,4 +87,7 @@ namespace KeyboardManagerConstants
// Dummy key event used in between key up and down events to prevent certain global events from happening
inline const DWORD DUMMY_KEY = 0xFF;
// String constant for the default app name in Remap shortcuts
inline const std::wstring DefaultAppName = L"All Apps";
}

View File

@ -50,3 +50,14 @@ void Trace::OSLevelShortcutRemapCount(const DWORD count) noexcept
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingValue(count, "OSLevelShortcutRemapCount"));
}
// Log number of app specific shortcut remaps when the user uses Edit Shortcuts and saves settings
void Trace::AppSpecificShortcutRemapCount(const DWORD count) noexcept
{
TraceLoggingWrite(
g_hProvider,
"KeyboardManager_AppSpecificShortcutRemapCount",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingValue(count, "AppSpecificShortcutRemapCount"));
}

View File

@ -14,4 +14,7 @@ public:
// Log number of os level shortcut remaps when the user uses Edit Shortcuts and saves settings
static void OSLevelShortcutRemapCount(const DWORD count) noexcept;
// Log number of app specific shortcut remaps when the user uses Edit Shortcuts and saves settings
static void AppSpecificShortcutRemapCount(const DWORD count) noexcept;
};

View File

@ -30,11 +30,37 @@ static IAsyncAction OnClickAccept(
XamlRoot root,
std::function<void()> ApplyRemappings)
{
KeyboardManagerHelper::ErrorType isSuccess = Dialog::CheckIfRemappingsAreValid<Shortcut>(
ShortcutControl::shortcutRemapBuffer,
[](Shortcut shortcut) {
return shortcut.IsValidShortcut();
});
KeyboardManagerHelper::ErrorType isSuccess = KeyboardManagerHelper::ErrorType::NoError;
std::map<std::wstring, std::vector<std::vector<Shortcut>>> appSpecificBuffer;
// Create per app shortcut buffer
for (int i = 0; i < ShortcutControl::shortcutRemapBuffer.size(); i++)
{
std::wstring currAppName = ShortcutControl::shortcutRemapBuffer[i].second;
std::transform(currAppName.begin(), currAppName.end(), currAppName.begin(), towlower);
if (appSpecificBuffer.find(currAppName) == appSpecificBuffer.end())
{
appSpecificBuffer[currAppName] = std::vector<std::vector<Shortcut>>();
}
appSpecificBuffer[currAppName].push_back(ShortcutControl::shortcutRemapBuffer[i].first);
}
for (auto it : appSpecificBuffer)
{
KeyboardManagerHelper::ErrorType currentSuccess = Dialog::CheckIfRemappingsAreValid<Shortcut>(
it.second,
[](Shortcut shortcut) {
return shortcut.IsValidShortcut();
});
if (currentSuccess != KeyboardManagerHelper::ErrorType::NoError)
{
isSuccess = currentSuccess;
break;
}
}
if (isSuccess != KeyboardManagerHelper::ErrorType::NoError)
{
if (!co_await Dialog::PartialRemappingConfirmationDialog(root, L"Some of the shortcuts could not be remapped. Do you want to continue anyway?"))
@ -166,6 +192,8 @@ void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMa
ColumnDefinition newColumn;
newColumn.MinWidth(3 * KeyboardManagerConstants::ShortcutTableDropDownWidth + 2 * KeyboardManagerConstants::ShortcutTableDropDownSpacing);
newColumn.MaxWidth(3 * KeyboardManagerConstants::ShortcutTableDropDownWidth + 2 * KeyboardManagerConstants::ShortcutTableDropDownSpacing);
ColumnDefinition targetAppColumn;
targetAppColumn.MinWidth(KeyboardManagerConstants::TableTargetAppColWidth);
ColumnDefinition removeColumn;
removeColumn.MinWidth(KeyboardManagerConstants::TableRemoveColWidth);
shortcutTable.Margin({ 10, 10, 10, 20 });
@ -173,6 +201,7 @@ void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMa
shortcutTable.ColumnDefinitions().Append(originalColumn);
shortcutTable.ColumnDefinitions().Append(arrowColumn);
shortcutTable.ColumnDefinitions().Append(newColumn);
shortcutTable.ColumnDefinitions().Append(targetAppColumn);
shortcutTable.ColumnDefinitions().Append(removeColumn);
shortcutTable.RowDefinitions().Append(RowDefinition());
@ -188,13 +217,24 @@ void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMa
newShortcutHeader.FontWeight(Text::FontWeights::Bold());
newShortcutHeader.Margin({ 0, 0, 0, 10 });
// Third header textblock in the header row of the shortcut table
TextBlock targetAppHeader;
targetAppHeader.Text(L"Target App:");
targetAppHeader.Width(KeyboardManagerConstants::ShortcutTableDropDownWidth);
targetAppHeader.FontWeight(Text::FontWeights::Bold());
targetAppHeader.Margin({ 0, 0, 0, 10 });
targetAppHeader.HorizontalAlignment(HorizontalAlignment::Center);
shortcutTable.SetColumn(originalShortcutHeader, KeyboardManagerConstants::ShortcutTableOriginalColIndex);
shortcutTable.SetRow(originalShortcutHeader, 0);
shortcutTable.SetColumn(newShortcutHeader, KeyboardManagerConstants::ShortcutTableNewColIndex);
shortcutTable.SetRow(newShortcutHeader, 0);
shortcutTable.SetColumn(targetAppHeader, KeyboardManagerConstants::ShortcutTableTargetAppColIndex);
shortcutTable.SetRow(targetAppHeader, 0);
shortcutTable.Children().Append(originalShortcutHeader);
shortcutTable.Children().Append(newShortcutHeader);
shortcutTable.Children().Append(targetAppHeader);
// Store handle of edit shortcuts window
ShortcutControl::EditShortcutsWindowHandle = _hWndEditShortcutsWindow;
@ -209,13 +249,26 @@ void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMa
// Set keyboard manager UI state so that shortcut remaps are not applied while on this window
keyboardManagerState.SetUIState(KeyboardManagerUIState::EditShortcutsWindowActivated, _hWndEditShortcutsWindow);
// Load existing shortcuts into UI
std::unique_lock<std::mutex> lock(keyboardManagerState.osLevelShortcutReMap_mutex);
// Load existing os level shortcuts into UI
std::unique_lock<std::mutex> lockOSLevel(keyboardManagerState.osLevelShortcutReMap_mutex);
for (const auto& it : keyboardManagerState.osLevelShortcutReMap)
{
ShortcutControl::AddNewShortcutControlRow(shortcutTable, keyboardRemapControlObjects, it.first, it.second.targetShortcut);
}
lock.unlock();
lockOSLevel.unlock();
// Load existing app-specific shortcuts into UI
std::unique_lock<std::mutex> lockAppSpecific(keyboardManagerState.appSpecificShortcutReMap_mutex);
// Iterate through all the apps
for (const auto& itApp : keyboardManagerState.appSpecificShortcutReMap)
{
// Iterate through shortcuts for each app
for (const auto& itShortcut : itApp.second)
{
ShortcutControl::AddNewShortcutControlRow(shortcutTable, keyboardRemapControlObjects, itShortcut.first, itShortcut.second.targetShortcut, itApp.first);
}
}
lockAppSpecific.unlock();
// Apply button
Button applyButton;
@ -230,24 +283,40 @@ void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMa
KeyboardManagerHelper::ErrorType isSuccess = KeyboardManagerHelper::ErrorType::NoError;
// Clear existing shortcuts
keyboardManagerState.ClearOSLevelShortcuts();
DWORD successfulRemapCount = 0;
keyboardManagerState.ClearAppSpecificShortcuts();
DWORD successfulOSLevelRemapCount = 0;
DWORD successfulAppSpecificRemapCount = 0;
// Save the shortcuts that are valid and report if any of them were invalid
for (int i = 0; i < ShortcutControl::shortcutRemapBuffer.size(); i++)
{
Shortcut originalShortcut = ShortcutControl::shortcutRemapBuffer[i][0];
Shortcut newShortcut = ShortcutControl::shortcutRemapBuffer[i][1];
Shortcut originalShortcut = ShortcutControl::shortcutRemapBuffer[i].first[0];
Shortcut newShortcut = ShortcutControl::shortcutRemapBuffer[i].first[1];
if (originalShortcut.IsValidShortcut() && newShortcut.IsValidShortcut())
{
bool result = keyboardManagerState.AddOSLevelShortcut(originalShortcut, newShortcut);
if (!result)
if (ShortcutControl::shortcutRemapBuffer[i].second == L"")
{
isSuccess = KeyboardManagerHelper::ErrorType::RemapUnsuccessful;
// Tooltip is already shown for this row
bool result = keyboardManagerState.AddOSLevelShortcut(originalShortcut, newShortcut);
if (!result)
{
isSuccess = KeyboardManagerHelper::ErrorType::RemapUnsuccessful;
}
else
{
successfulOSLevelRemapCount += 1;
}
}
else
{
successfulRemapCount += 1;
bool result = keyboardManagerState.AddAppSpecificShortcut(ShortcutControl::shortcutRemapBuffer[i].second, originalShortcut, newShortcut);
if (!result)
{
isSuccess = KeyboardManagerHelper::ErrorType::RemapUnsuccessful;
}
else
{
successfulAppSpecificRemapCount += 1;
}
}
}
else
@ -256,7 +325,10 @@ void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMa
}
}
Trace::OSLevelShortcutRemapCount(successfulRemapCount);
// Telemetry events
Trace::OSLevelShortcutRemapCount(successfulOSLevelRemapCount);
Trace::AppSpecificShortcutRemapCount(successfulAppSpecificRemapCount);
// Save the updated key remaps to file.
bool saveResult = keyboardManagerState.SaveConfigToFile();
if (!saveResult)

View File

@ -125,7 +125,7 @@ void KeyDropDownControl::SetSelectionHandler(Grid& table, StackPanel& singleKeyC
});
}
std::pair<KeyboardManagerHelper::ErrorType, int> KeyDropDownControl::ValidateShortcutSelection(Grid table, StackPanel shortcutControl, StackPanel parent, int colIndex, std::vector<std::vector<Shortcut>>& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects)
std::pair<KeyboardManagerHelper::ErrorType, int> KeyDropDownControl::ValidateShortcutSelection(Grid table, StackPanel shortcutControl, StackPanel parent, int colIndex, std::vector<std::pair<std::vector<Shortcut>, std::wstring>>& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, TextBox targetApp)
{
ComboBox currentDropDown = dropDown;
int selectedKeyIndex = currentDropDown.SelectedIndex();
@ -164,7 +164,7 @@ std::pair<KeyboardManagerHelper::ErrorType, int> KeyDropDownControl::ValidateSho
// If not, add a new drop down
else
{
AddDropDown(table, shortcutControl, parent, colIndex, shortcutRemapBuffer, keyDropDownControlObjects);
AddDropDown(table, shortcutControl, parent, colIndex, shortcutRemapBuffer, keyDropDownControlObjects, targetApp);
}
}
// If last drop down and a modifier is selected but there are already max drop downs: warn the user
@ -251,20 +251,27 @@ std::pair<KeyboardManagerHelper::ErrorType, int> KeyDropDownControl::ValidateSho
{
Shortcut tempShortcut;
tempShortcut.SetKeyCodes(GetKeysFromStackPanel(parent));
std::wstring appName = targetApp.Text().c_str();
// Convert app name to lower case
std::transform(appName.begin(), appName.end(), appName.begin(), towlower);
// Check if the value being set is the same as the other column
if (shortcutRemapBuffer[rowIndex][std::abs(int(colIndex) - 1)] == tempShortcut && shortcutRemapBuffer[rowIndex][std::abs(int(colIndex) - 1)].IsValidShortcut() && tempShortcut.IsValidShortcut())
if (shortcutRemapBuffer[rowIndex].first[std::abs(int(colIndex) - 1)] == tempShortcut && shortcutRemapBuffer[rowIndex].first[std::abs(int(colIndex) - 1)].IsValidShortcut() && tempShortcut.IsValidShortcut())
{
errorType = KeyboardManagerHelper::ErrorType::MapToSameShortcut;
}
if (errorType == KeyboardManagerHelper::ErrorType::NoError && colIndex == 0)
{
// Check if the key is already remapped to something else
// Check if the key is already remapped to something else for the same target app
for (int i = 0; i < shortcutRemapBuffer.size(); i++)
{
if (i != rowIndex)
std::wstring currAppName = shortcutRemapBuffer[i].second;
std::transform(currAppName.begin(), currAppName.end(), currAppName.begin(), towlower);
if (i != rowIndex && currAppName == appName)
{
KeyboardManagerHelper::ErrorType result = Shortcut::DoKeysOverlap(shortcutRemapBuffer[i][colIndex], tempShortcut);
KeyboardManagerHelper::ErrorType result = Shortcut::DoKeysOverlap(shortcutRemapBuffer[i].first[colIndex], tempShortcut);
if (result != KeyboardManagerHelper::ErrorType::NoError)
{
errorType = result;
@ -299,10 +306,10 @@ std::pair<KeyboardManagerHelper::ErrorType, int> KeyDropDownControl::ValidateSho
}
// 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, int colIndex, std::vector<std::vector<Shortcut>>& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects)
void KeyDropDownControl::SetSelectionHandler(Grid& table, StackPanel& shortcutControl, StackPanel parent, int colIndex, std::vector<std::pair<std::vector<Shortcut>, std::wstring>>& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, TextBox& targetApp)
{
auto onSelectionChange = [&, table, shortcutControl, colIndex, parent](winrt::Windows::Foundation::IInspectable const& sender) {
std::pair<KeyboardManagerHelper::ErrorType, int> validationResult = ValidateShortcutSelection(table, shortcutControl, parent, colIndex, shortcutRemapBuffer, keyDropDownControlObjects);
auto onSelectionChange = [&, table, shortcutControl, colIndex, parent, targetApp](winrt::Windows::Foundation::IInspectable const& sender) {
std::pair<KeyboardManagerHelper::ErrorType, int> validationResult = ValidateShortcutSelection(table, shortcutControl, parent, colIndex, shortcutRemapBuffer, keyDropDownControlObjects, targetApp);
// Check if the drop down row index was identified from the return value of validateSelection
if (validationResult.second != -1)
@ -310,23 +317,13 @@ void KeyDropDownControl::SetSelectionHandler(Grid& table, StackPanel& shortcutCo
// If an error occurred
if (validationResult.first != KeyboardManagerHelper::ErrorType::NoError)
{
// Iterate over all drop downs from left to right in that row/col and validate if there is an error in any of the drop downs. After this the state should be error-free (if it is a valid shortcut)
for (int i = 0; i < keyDropDownControlObjects.size(); i++)
{
// Check for errors only if the current selection is a valid shortcut
Shortcut tempComputedShortcut;
tempComputedShortcut.SetKeyCodes(keyDropDownControlObjects[i]->GetKeysFromStackPanel(parent));
// If the shortcut is valid and that drop down is not empty
if (tempComputedShortcut.IsValidShortcut() && keyDropDownControlObjects[i]->GetComboBox().SelectedIndex() != -1)
{
keyDropDownControlObjects[i]->ValidateShortcutSelection(table, shortcutControl, parent, colIndex, shortcutRemapBuffer, keyDropDownControlObjects);
}
}
// Validate all the drop downs
ValidateShortcutFromDropDownList(table, shortcutControl, parent, colIndex, shortcutRemapBuffer, keyDropDownControlObjects, targetApp);
}
// Reset the buffer based on the new selected drop down items
shortcutRemapBuffer[validationResult.second][colIndex].SetKeyCodes(GetKeysFromStackPanel(parent));
shortcutRemapBuffer[validationResult.second].first[colIndex].SetKeyCodes(GetKeysFromStackPanel(parent));
shortcutRemapBuffer[validationResult.second].second = targetApp.Text().c_str();
}
// 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.
@ -370,11 +367,11 @@ ComboBox KeyDropDownControl::GetComboBox()
}
// Function to add a drop down to the shortcut stack panel
void KeyDropDownControl::AddDropDown(Grid table, StackPanel shortcutControl, StackPanel parent, const int colIndex, std::vector<std::vector<Shortcut>>& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects)
void KeyDropDownControl::AddDropDown(Grid table, StackPanel shortcutControl, StackPanel parent, const int colIndex, std::vector<std::pair<std::vector<Shortcut>, std::wstring>>& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, TextBox& targetApp)
{
keyDropDownControlObjects.push_back(std::move(std::unique_ptr<KeyDropDownControl>(new KeyDropDownControl(true))));
parent.Children().Append(keyDropDownControlObjects[keyDropDownControlObjects.size() - 1]->GetComboBox());
keyDropDownControlObjects[keyDropDownControlObjects.size() - 1]->SetSelectionHandler(table, shortcutControl, parent, colIndex, shortcutRemapBuffer, keyDropDownControlObjects);
keyDropDownControlObjects[keyDropDownControlObjects.size() - 1]->SetSelectionHandler(table, shortcutControl, parent, colIndex, shortcutRemapBuffer, keyDropDownControlObjects, targetApp);
parent.UpdateLayout();
}
@ -435,6 +432,24 @@ bool KeyDropDownControl::CheckRepeatedModifier(StackPanel parent, int selectedKe
return matchPreviousModifier;
}
// Function for validating the selection of shortcuts for all the associated drop downs
void KeyDropDownControl::ValidateShortcutFromDropDownList(Grid table, StackPanel shortcutControl, StackPanel parent, int colIndex, std::vector<std::pair<std::vector<Shortcut>, std::wstring>>& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, TextBox targetApp)
{
// Iterate over all drop downs from left to right in that row/col and validate if there is an error in any of the drop downs. After this the state should be error-free (if it is a valid shortcut)
for (int i = 0; i < keyDropDownControlObjects.size(); i++)
{
// Check for errors only if the current selection is a valid shortcut
Shortcut tempComputedShortcut;
tempComputedShortcut.SetKeyCodes(keyDropDownControlObjects[i]->GetKeysFromStackPanel(parent));
// If the shortcut is valid and that drop down is not empty
if (tempComputedShortcut.IsValidShortcut() && keyDropDownControlObjects[i]->GetComboBox().SelectedIndex() != -1)
{
keyDropDownControlObjects[i]->ValidateShortcutSelection(table, shortcutControl, parent, colIndex, shortcutRemapBuffer, keyDropDownControlObjects, targetApp);
}
}
}
// Function to set the warning message
void KeyDropDownControl::SetDropDownError(ComboBox currentDropDown, hstring message)
{

View File

@ -36,10 +36,10 @@ public:
void SetSelectionHandler(Grid& table, StackPanel& singleKeyControl, int colIndex, std::vector<std::vector<DWORD>>& singleKeyRemapBuffer);
// Function for validating the selection of shortcuts for the drop down
std::pair<KeyboardManagerHelper::ErrorType, int> ValidateShortcutSelection(Grid table, StackPanel shortcutControl, StackPanel parent, int colIndex, std::vector<std::vector<Shortcut>>& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects);
std::pair<KeyboardManagerHelper::ErrorType, int> ValidateShortcutSelection(Grid table, StackPanel shortcutControl, StackPanel parent, int colIndex, std::vector<std::pair<std::vector<Shortcut>, std::wstring>>& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, TextBox targetApp);
// 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, int colIndex, std::vector<std::vector<Shortcut>>& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects);
void SetSelectionHandler(Grid& table, StackPanel& shortcutControl, StackPanel parent, int colIndex, std::vector<std::pair<std::vector<Shortcut>, std::wstring>>& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, TextBox& targetApp);
// Function to set the selected index of the drop down
void SetSelectedIndex(int32_t index);
@ -48,7 +48,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 int colIndex, std::vector<std::vector<Shortcut>>& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects);
static void AddDropDown(Grid table, StackPanel shortcutControl, StackPanel parent, const int colIndex, std::vector<std::pair<std::vector<Shortcut>, std::wstring>>& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, TextBox& targetApp);
// Function to get the list of key codes from the shortcut combo box stack panel
static std::vector<DWORD> GetKeysFromStackPanel(StackPanel parent);
@ -56,6 +56,9 @@ public:
// Function to check if a modifier has been repeated in the previous drop downs
static bool CheckRepeatedModifier(StackPanel parent, int selectedKeyIndex, const std::vector<DWORD>& keyCodeList);
// Function for validating the selection of shortcuts for all the associated drop downs
static void ValidateShortcutFromDropDownList(Grid table, StackPanel shortcutControl, StackPanel parent, int colIndex, std::vector<std::pair<std::vector<Shortcut>, std::wstring>>& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, TextBox targetApp);
// Function to set the warning message
void SetDropDownError(ComboBox currentDropDown, hstring message);
};

View File

@ -6,15 +6,18 @@
HWND ShortcutControl::EditShortcutsWindowHandle = nullptr;
KeyboardManagerState* ShortcutControl::keyboardManagerState = nullptr;
// Initialized as new vector
std::vector<std::vector<Shortcut>> ShortcutControl::shortcutRemapBuffer;
std::vector<std::pair<std::vector<Shortcut>, std::wstring>> 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<std::vector<std::unique_ptr<ShortcutControl>>>& keyboardRemapControlObjects, Shortcut originalKeys, Shortcut newKeys)
void ShortcutControl::AddNewShortcutControlRow(Grid& parent, std::vector<std::vector<std::unique_ptr<ShortcutControl>>>& keyboardRemapControlObjects, Shortcut originalKeys, Shortcut newKeys, std::wstring targetAppName)
{
// Textbox for target application
TextBox targetAppTextBox;
// Create new ShortcutControl objects dynamically so that we does not get destructed
std::vector<std::unique_ptr<ShortcutControl>> newrow;
newrow.push_back(std::move(std::unique_ptr<ShortcutControl>(new ShortcutControl(parent, 0))));
newrow.push_back(std::move(std::unique_ptr<ShortcutControl>(new ShortcutControl(parent, 1))));
newrow.push_back(std::move(std::unique_ptr<ShortcutControl>(new ShortcutControl(parent, 0, targetAppTextBox))));
newrow.push_back(std::move(std::unique_ptr<ShortcutControl>(new ShortcutControl(parent, 1, targetAppTextBox))));
keyboardRemapControlObjects.push_back(std::move(newrow));
// Add to grid
@ -39,6 +42,37 @@ void ShortcutControl::AddNewShortcutControlRow(Grid& parent, std::vector<std::ve
// ShortcutControl for the new shortcut
parent.Children().Append(keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1]->getShortcutControl());
targetAppTextBox.Width(KeyboardManagerConstants::ShortcutTableDropDownWidth);
targetAppTextBox.Margin({ 0, 0, 0, KeyboardManagerConstants::ShortcutTableDropDownSpacing });
targetAppTextBox.VerticalAlignment(VerticalAlignment::Bottom);
targetAppTextBox.HorizontalAlignment(HorizontalAlignment::Center);
targetAppTextBox.PlaceholderText(KeyboardManagerConstants::DefaultAppName);
targetAppTextBox.Text(targetAppName);
// 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, targetAppTextBox](auto const& sender, auto const& e) {
// Get index of targetAppTextBox button
UIElementCollection children = parent.Children();
uint32_t index;
children.IndexOf(targetAppTextBox, index);
uint32_t lastIndexInRow = index + ((KeyboardManagerConstants::ShortcutTableColCount - 1) - KeyboardManagerConstants::ShortcutTableTargetAppColIndex);
// Calculate row index in the buffer from the grid child index (first set of children are header elements and then three children in each row)
int rowIndex = (lastIndexInRow - KeyboardManagerConstants::ShortcutTableHeaderCount) / KeyboardManagerConstants::ShortcutTableColCount;
// Validate both set of drop downs
KeyDropDownControl::ValidateShortcutFromDropDownList(parent, keyboardRemapControlObjects[rowIndex][0]->getShortcutControl(), keyboardRemapControlObjects[rowIndex][0]->shortcutDropDownStackPanel, 0, ShortcutControl::shortcutRemapBuffer, keyboardRemapControlObjects[rowIndex][0]->keyDropDownControlObjects, targetAppTextBox);
KeyDropDownControl::ValidateShortcutFromDropDownList(parent, keyboardRemapControlObjects[rowIndex][1]->getShortcutControl(), keyboardRemapControlObjects[rowIndex][1]->shortcutDropDownStackPanel, 1, ShortcutControl::shortcutRemapBuffer, keyboardRemapControlObjects[rowIndex][1]->keyDropDownControlObjects, targetAppTextBox);
// Reset the buffer based on the selected drop down items
shortcutRemapBuffer[rowIndex].first[0].SetKeyCodes(KeyDropDownControl::GetKeysFromStackPanel(keyboardRemapControlObjects[rowIndex][0]->shortcutDropDownStackPanel));
shortcutRemapBuffer[rowIndex].first[1].SetKeyCodes(KeyDropDownControl::GetKeysFromStackPanel(keyboardRemapControlObjects[rowIndex][1]->shortcutDropDownStackPanel));
shortcutRemapBuffer[rowIndex].second = targetAppTextBox.Text().c_str();
});
parent.SetColumn(targetAppTextBox, KeyboardManagerConstants::ShortcutTableTargetAppColIndex);
parent.SetRow(targetAppTextBox, parent.RowDefinitions().Size() - 1);
parent.Children().Append(targetAppTextBox);
// Delete row button
Windows::UI::Xaml::Controls::Button deleteShortcut;
FontIcon deleteSymbol;
@ -66,7 +100,7 @@ void ShortcutControl::AddNewShortcutControlRow(Grid& parent, std::vector<std::ve
parent.Children().RemoveAt(lastIndexInRow - i);
}
// Calculate row index in the buffer from the grid child index (first two children are header elements and then three children in each row)
// Calculate row index in the buffer from the grid child index (first set of children are header elements and then three children in each row)
int bufferIndex = (lastIndexInRow - KeyboardManagerConstants::ShortcutTableHeaderCount) / KeyboardManagerConstants::ShortcutTableColCount;
// Delete the row definition
parent.RowDefinitions().RemoveAt(bufferIndex + 1);
@ -83,19 +117,20 @@ void ShortcutControl::AddNewShortcutControlRow(Grid& parent, std::vector<std::ve
// Set the shortcut text if the two vectors are not empty (i.e. default args)
if (originalKeys.IsValidShortcut() && newKeys.IsValidShortcut())
{
shortcutRemapBuffer.push_back(std::vector<Shortcut>{ 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);
// change to load app name
shortcutRemapBuffer.push_back(std::make_pair<std::vector<Shortcut>, std::wstring>(std::vector<Shortcut>{ Shortcut(), Shortcut() }, std::wstring(targetAppName)));
keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][0]->AddShortcutToControl(originalKeys, parent, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][0]->shortcutDropDownStackPanel, *keyboardManagerState, 0, targetAppTextBox);
keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1]->AddShortcutToControl(newKeys, parent, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1]->shortcutDropDownStackPanel, *keyboardManagerState, 1, targetAppTextBox);
}
else
{
// Initialize both shortcuts as empty shortcuts
shortcutRemapBuffer.push_back(std::vector<Shortcut>{ Shortcut(), Shortcut() });
shortcutRemapBuffer.push_back(std::make_pair<std::vector<Shortcut>, std::wstring>(std::vector<Shortcut>{ Shortcut(), Shortcut() }, std::wstring(targetAppName)));
}
}
// Function to add a shortcut to the shortcut control as combo boxes
void ShortcutControl::AddShortcutToControl(Shortcut& shortcut, Grid table, StackPanel parent, KeyboardManagerState& keyboardManagerState, const int colIndex)
void ShortcutControl::AddShortcutToControl(Shortcut& shortcut, Grid table, StackPanel parent, KeyboardManagerState& keyboardManagerState, const int colIndex, TextBox targetApp)
{
// Delete the existing drop down menus
parent.Children().Clear();
@ -106,7 +141,7 @@ void ShortcutControl::AddShortcutToControl(Shortcut& shortcut, Grid table, Stack
std::vector<DWORD> 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, targetApp);
for (int i = 0; i < shortcutKeyCodes.size(); i++)
{
// New drop down gets added automatically when the SelectedIndex is set
@ -131,7 +166,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<std::vector<Shortcut>>& shortcutRemapBuffer, KeyboardManagerState& keyboardManagerState, const int colIndex, Grid table)
void ShortcutControl::createDetectShortcutWindow(winrt::Windows::Foundation::IInspectable const& sender, XamlRoot xamlRoot, std::vector<std::pair<std::vector<Shortcut>, std::wstring>>& shortcutRemapBuffer, KeyboardManagerState& keyboardManagerState, const int colIndex, Grid table, TextBox targetApp)
{
// ContentDialog for detecting shortcuts. This is the parent UI element.
ContentDialog detectShortcutBox;
@ -164,14 +199,15 @@ void ShortcutControl::createDetectShortcutWindow(winrt::Windows::Foundation::IIn
&shortcutRemapBuffer,
unregisterKeys,
colIndex,
table] {
table,
targetApp] {
// 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, targetApp);
}
// Hide the type shortcut UI
detectShortcutBox.Hide();

View File

@ -7,9 +7,6 @@
class ShortcutControl
{
private:
// Textblock to display the selected shortcut
TextBlock shortcutText;
// Stack panel for the drop downs to display the selected shortcut
StackPanel shortcutDropDownStackPanel;
@ -25,21 +22,21 @@ public:
// Pointer to the keyboard manager state
static KeyboardManagerState* keyboardManagerState;
// Stores the current list of remappings
static std::vector<std::vector<Shortcut>> shortcutRemapBuffer;
static std::vector<std::pair<std::vector<Shortcut>, std::wstring>> shortcutRemapBuffer;
// Vector to store dynamically allocated KeyDropDownControl objects to avoid early destruction
std::vector<std::unique_ptr<KeyDropDownControl>> keyDropDownControlObjects;
ShortcutControl(Grid table, const int colIndex)
ShortcutControl(Grid table, const int colIndex, TextBox targetApp)
{
shortcutDropDownStackPanel.Spacing(10);
shortcutDropDownStackPanel.Spacing(KeyboardManagerConstants::ShortcutTableDropDownSpacing);
shortcutDropDownStackPanel.Orientation(Windows::UI::Xaml::Controls::Orientation::Horizontal);
typeShortcut.Content(winrt::box_value(L"Type Shortcut"));
typeShortcut.Width(KeyboardManagerConstants::ShortcutTableDropDownWidth);
typeShortcut.Click([&, table, colIndex](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) {
typeShortcut.Click([&, table, colIndex, targetApp](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<Button>().XamlRoot(), shortcutRemapBuffer, *keyboardManagerState, colIndex, table);
createDetectShortcutWindow(sender, sender.as<Button>().XamlRoot(), shortcutRemapBuffer, *keyboardManagerState, colIndex, table, targetApp);
});
shortcutControlLayout.Margin({ 0, 0, 0, 10 });
@ -47,19 +44,19 @@ public:
shortcutControlLayout.Children().Append(typeShortcut);
shortcutControlLayout.Children().Append(shortcutDropDownStackPanel);
KeyDropDownControl::AddDropDown(table, shortcutControlLayout, shortcutDropDownStackPanel, colIndex, shortcutRemapBuffer, keyDropDownControlObjects);
KeyDropDownControl::AddDropDown(table, shortcutControlLayout, shortcutDropDownStackPanel, colIndex, shortcutRemapBuffer, keyDropDownControlObjects, targetApp);
shortcutControlLayout.UpdateLayout();
}
// 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(Grid& parent, std::vector<std::vector<std::unique_ptr<ShortcutControl>>>& keyboardRemapControlObjects, Shortcut originalKeys = Shortcut(), Shortcut newKeys = Shortcut());
static void AddNewShortcutControlRow(Grid& parent, std::vector<std::vector<std::unique_ptr<ShortcutControl>>>& keyboardRemapControlObjects, Shortcut originalKeys = Shortcut(), Shortcut newKeys = Shortcut(), std::wstring targetAppName = L"");
// Function to add a shortcut to the shortcut control as combo boxes
void AddShortcutToControl(Shortcut& shortcut, Grid table, StackPanel parent, KeyboardManagerState& keyboardManagerState, const int colIndex);
void AddShortcutToControl(Shortcut& shortcut, Grid table, StackPanel parent, KeyboardManagerState& keyboardManagerState, const int colIndex, TextBox targetApp);
// 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();
// Function to create the detect shortcut UI window
void createDetectShortcutWindow(winrt::Windows::Foundation::IInspectable const& sender, XamlRoot xamlRoot, std::vector<std::vector<Shortcut>>& shortcutRemapBuffer, KeyboardManagerState& keyboardManagerState, const int colIndex, Grid table);
void createDetectShortcutWindow(winrt::Windows::Foundation::IInspectable const& sender, XamlRoot xamlRoot, std::vector<std::pair<std::vector<Shortcut>, std::wstring>>& shortcutRemapBuffer, KeyboardManagerState& keyboardManagerState, const int colIndex, Grid table, TextBox targetApp);
};