[KBM]Launch apps / URI with keyboard shortcuts, support chords (#30121)

* Working UI update with just runProgram Path and isRunProgram

* First working, basic. no args or path, or setting change detections.

* Revert and fixed.

* Some clean up, working with config file monitor

* Args and Start-in should be working.

* File monitor, quotes, xaml screens one

* Fixed enable/disable toogle from XAML

* Code cleanup.

* Betting logging.

* Cleanup, start of RunProgramDescriptor and usage of run_non_elevated/run_elevated

* Code moved to KeyboardEventHandlers, but not enabled since it won't build as is, needs elevation.h. Other testing..

* Key chords working, pretty much

* Added gui for elevation level, need to refresh on change...

* f: include shellapi.h and reference wil in KBMEL

* run_elevated/run_non_elevated sorted out. Working!

* Removed lots of old temp code.

* Fix some speling errors.

* Cleanup before trying to add a UI for the chord

* Added "DifferentUser" option

* Closer on UI for chords.

* Better UI, lots working.

* Clean up

* Text for “Allow chords” – needs to look better…

* Bugs and clean-up

* Cleanup

* Refactor and clean up.

* More clean up

* Some localization.

* Don’t show “Allow chords“ to the “to” shortcut

* Maybe better foreground after opening new app

* Better chord matching.

* Runprogram fix for stealing existing shortcut.

* Better runProgram stuff

* Temp commit

* Working well

* Toast test

* More toast

* Added File and Folder picker UI

* Pre-check on run program file exists.

* Refactor to SetupRunProgramControls

* Open URI UI is going.

* Open URI working well

* Open URI stuff working well

* Allowed AppSpecific shortcut and fixed backup/restore shortcut dups

* Fixed settings screen

* Start of code to find by name...

* UI fixed

* Small fixes

* Some single edit code working.

* UI getting better.

* Fixes

* Fixed and merge from main

* UI updates

* UI updates.

* UI stuff

* Fixed crash from move ui item locations.

* Fixed crash from move ui item locations.

* Added delete confirm

* Basic sound working.

* Localized some stuff

* Added sounds

* Better experiance when shortcut is in use.

* UI tweaks

* Fixed KBM ui for unicode shortcut not having ","

* Some clean up

* Cleanup

* Cleanup

* Fixed applyXamlStyling

* Added back stuff lost in merge

* applyXamlStyling, again

* Fixed crash on change from non shortcut to shortcut

* Update src/modules/keyboardmanager/KeyboardManagerEngineTest/KeyboardManagerEngineTest.vcxproj

* Fixed some spelling type issues.

* ImplementationLibrary 231216

* Comment bump to see if the Microsoft.Windows.ImplementationLibrary version thing gets picked up

* Correct, Microsoft.Windows.ImplementationLibrary, finally?

* Fixed two test that failed because we now allow key-chords.

* Removed shortcut sounds.

* use original behavior when "allow chords" is off in shortcut window

* fix crash when editing a shortcut that has apps specified for it

* split KBM chords with comma on dashboard page

* Fix some spelling items.

* More "spelling"

* Fix XAML styling

* align TextBlock and ToggleSwitch

* fix cutoff issue at the top

* increase ComboBox width

* Added *Unsupported* for backwards compat on config of KBM

* fix spellcheck

* Fix crash on Remap key screen

* Fixed Remap Keys ComboBox width too short.

* Removed KBM Single Edit mode, fixed crash.

* Fix Xaml with xaml cops

* Fix crash on setting "target app" for some types of shortcuts.

* Space to toggle chord, combobox back

* fix spellcheck

* fix some code nits

* Code review updates.

* Add exclusions to the bug report tool

* Code review and kill CloseAndEndTask

* Fix alignment / 3 comboboxes per row

* Fix daily telemetry events to exclude start app and open URI

* Add chords and remove app start and open uri from config telemetry

* comma instead of plus in human readable shortcut telemetry data

* Code review, restore default-old state when new row added in KBM

* Code review, restore default-old state when new row added in KBM, part 2

* Still show target app on Settings

* Only allow enabling chords for origin shortcuts

---------

Co-authored-by: Andrey Nekrasov <yuyoyuppe@users.noreply.github.com>
Co-authored-by: Jaime Bernardo <jaime@janeasystems.com>
This commit is contained in:
Jeff Lord
2024-02-27 18:12:05 -05:00
committed by GitHub
parent 1a5349bf1e
commit 725c8e8c19
48 changed files with 4373 additions and 1571 deletions

View File

@@ -204,8 +204,9 @@ namespace BufferValidationHelpers
}
else
{
// warn and reset the drop down
errorType = ShortcutErrorType::ShortcutNotMoreThanOneActionKey;
// this used to "warn and reset the drop down" but for now, since we will allow Chords, we do allow this
// leaving the here and commented out for posterity, for now.
// errorType = ShortcutErrorType::ShortcutNotMoreThanOneActionKey;
}
}
else

View File

@@ -69,7 +69,7 @@ static IAsyncAction OnClickAccept(
}
// Function to create the Edit Shortcuts Window
inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardManagerState& keyboardManagerState, MappingConfiguration& mappingConfiguration)
inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardManagerState& keyboardManagerState, MappingConfiguration& mappingConfiguration, std::wstring keysForShortcutToEdit, std::wstring action)
{
Logger::trace("CreateEditShortcutsWindowImpl()");
auto locker = EventLocker::Get(KeyboardManagerConstants::EditorWindowEventName.c_str());
@@ -107,12 +107,24 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa
isEditShortcutsWindowRegistrationCompleted = true;
}
// we might be passed via cmdline some keysForShortcutToEdit, this means we're editing just one shortcut
if (!keysForShortcutToEdit.empty())
{
}
// Find coordinates of the screen where the settings window is placed.
RECT desktopRect = UIHelpers::GetForegroundWindowDesktopRect();
// Calculate DPI dependent window size
float windowWidth = EditorConstants::DefaultEditShortcutsWindowWidth;
float windowHeight = EditorConstants::DefaultEditShortcutsWindowHeight;
if (!keysForShortcutToEdit.empty())
{
windowWidth = EditorConstants::DefaultEditSingleShortcutsWindowWidth;
windowHeight = EditorConstants::DefaultEditSingleShortcutsWindowHeight;
}
DPIAware::ConvertByCursorPosition(windowWidth, windowHeight);
DPIAware::GetScreenDPIForCursor(g_currentDPI);
@@ -129,13 +141,13 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa
NULL,
hInst,
NULL);
if (_hWndEditShortcutsWindow == NULL)
{
MessageBox(NULL, GET_RESOURCE_STRING(IDS_CREATEWINDOWFAILED_ERRORMESSAGE).c_str(), GET_RESOURCE_STRING(IDS_CREATEWINDOWFAILED_ERRORTITLE).c_str(), NULL);
return;
}
// Ensures the window is in foreground on first startup. If this is not done, the window appears behind because the thread is not on the foreground.
if (_hWndEditShortcutsWindow)
{
@@ -157,7 +169,7 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa
// Create the xaml bridge object
XamlBridge2 xamlBridge(_hWndEditShortcutsWindow);
// Create the desktop window xaml source object and set its content
hWndXamlIslandEditShortcutsWindow = xamlBridge.InitBridge();
@@ -227,15 +239,15 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa
// Store handle of edit shortcuts window
ShortcutControl::editShortcutsWindowHandle = _hWndEditShortcutsWindow;
// Store keyboard manager state
ShortcutControl::keyboardManagerState = &keyboardManagerState;
KeyDropDownControl::keyboardManagerState = &keyboardManagerState;
KeyDropDownControl::mappingConfiguration = &mappingConfiguration;
// Clear the shortcut remap buffer
ShortcutControl::shortcutRemapBuffer.clear();
// Vector to store dynamically allocated control objects to avoid early destruction
std::vector<std::vector<std::unique_ptr<ShortcutControl>>> keyboardRemapControlObjects;
@@ -246,27 +258,9 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa
// Create copy of the remaps to avoid concurrent access
ShortcutRemapTable osLevelShortcutReMapCopy = mappingConfiguration.osLevelShortcutReMap;
for (const auto& it : osLevelShortcutReMapCopy)
{
ShortcutControl::AddNewShortcutControlRow(shortcutTable, keyboardRemapControlObjects, it.first, it.second.targetShortcut);
}
// Load existing app-specific shortcuts into UI
// Create copy of the remaps to avoid concurrent access
AppSpecificShortcutRemapTable appSpecificShortcutReMapCopy = mappingConfiguration.appSpecificShortcutReMap;
// Iterate through all the apps
for (const auto& itApp : appSpecificShortcutReMapCopy)
{
// Iterate through shortcuts for each app
for (const auto& itShortcut : itApp.second)
{
ShortcutControl::AddNewShortcutControlRow(shortcutTable, keyboardRemapControlObjects, itShortcut.first, itShortcut.second.targetShortcut, itApp.first);
}
}
// Apply button
Button applyButton;
applyButton.Name(L"applyButton");
applyButton.Content(winrt::box_value(GET_RESOURCE_STRING(IDS_OK_BUTTON)));
applyButton.Style(AccentButtonStyle());
applyButton.MinWidth(EditorConstants::HeaderButtonWidth);
@@ -284,7 +278,44 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa
OnClickAccept(keyboardManagerState, applyButton.XamlRoot(), ApplyRemappings);
});
auto OnClickAcceptNoCheckFn = ApplyRemappings;
for (const auto& it : osLevelShortcutReMapCopy)
{
auto isHidden = false;
// check to see if this should be hidden because it's NOT the one we are looking for.
// It will still be there for backward compatability, just not visible
if (!keysForShortcutToEdit.empty())
{
isHidden = (keysForShortcutToEdit != it.first.ToHstringVK());
}
ShortcutControl::AddNewShortcutControlRow(shortcutTable, keyboardRemapControlObjects, it.first, it.second.targetShortcut, L"");
}
// Load existing app-specific shortcuts into UI
// Create copy of the remaps to avoid concurrent access
AppSpecificShortcutRemapTable appSpecificShortcutReMapCopy = mappingConfiguration.appSpecificShortcutReMap;
// Iterate through all the apps
for (const auto& itApp : appSpecificShortcutReMapCopy)
{
// Iterate through shortcuts for each app
for (const auto& itShortcut : itApp.second)
{
auto isHidden = false;
if (!keysForShortcutToEdit.empty())
{
isHidden = (keysForShortcutToEdit != itShortcut.first.ToHstringVK());
}
ShortcutControl::AddNewShortcutControlRow(shortcutTable, keyboardRemapControlObjects, itShortcut.first, itShortcut.second.targetShortcut, itApp.first);
}
}
header.Children().Append(headerText);
header.Children().Append(applyButton);
header.Children().Append(cancelButton);
@@ -299,17 +330,41 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa
addShortcut.Margin({ 10, 10, 0, 25 });
addShortcut.Style(AccentButtonStyle());
addShortcut.Click([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) {
ShortcutControl::AddNewShortcutControlRow(shortcutTable, keyboardRemapControlObjects);
ShortcutControl& newShortcut = ShortcutControl::AddNewShortcutControlRow(shortcutTable, keyboardRemapControlObjects);
// Whenever a remap is added move to the bottom of the screen
scrollViewer.ChangeView(nullptr, scrollViewer.ScrollableHeight(), nullptr);
// Set focus to the first Type Button in the newly added row
UIHelpers::SetFocusOnTypeButtonInLastRow(shortcutTable, EditorConstants::ShortcutTableColCount);
//newShortcut.OpenNewShortcutControlRow(shortcutTable, shortcutTable.Children().GetAt(shortcutTable.Children().Size() - 1).as<StackPanel>());
});
// if this is a delete action we just want to quick load the screen to delete the shortcut and close
// this is so we can delete from the KBM settings screen
if (action == L"isDelete")
{
auto indexToDelete = -1;
for (int i = 0; i < ShortcutControl::shortcutRemapBuffer.size(); i++)
{
auto tempShortcut = std::get<Shortcut>(ShortcutControl::shortcutRemapBuffer[i].first[0]);
if (tempShortcut.ToHstringVK() == keysForShortcutToEdit)
{
indexToDelete = i;
}
}
if (indexToDelete >= 0)
{
ShortcutControl::shortcutRemapBuffer.erase(ShortcutControl::shortcutRemapBuffer.begin() + indexToDelete);
}
OnClickAcceptNoCheckFn();
return;
}
// Remap shortcut button content
StackPanel addShortcutContent;
addShortcutContent.Orientation(Orientation::Horizontal);
addShortcutContent.Spacing(10);
addShortcutContent.Children().Append(SymbolIcon(Symbol::Add));
@@ -333,6 +388,7 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa
mappingsPanel.Children().Append(addShortcut);
// Remapping table should be scrollable
scrollViewer.Content(mappingsPanel);
RelativePanel xamlContainer;
@@ -347,6 +403,7 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa
xamlContainer.Children().Append(header);
xamlContainer.Children().Append(helperText);
xamlContainer.Children().Append(scrollViewer);
try
{
// If a layout update has been triggered by other methods (e.g.: adapting to zoom level), this may throw an exception.
@@ -368,6 +425,7 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa
// Mica isn't available
xamlContainer.Background(Application::Current().Resources().Lookup(box_value(L"ApplicationPageBackgroundThemeBrush")).as<Media::SolidColorBrush>());
}
Window::Current().Content(xamlContent);
////End XAML Island section
@@ -389,10 +447,10 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa
keyboardManagerState.ClearRegisteredKeyDelays();
}
void CreateEditShortcutsWindow(HINSTANCE hInst, KBMEditor::KeyboardManagerState& keyboardManagerState, MappingConfiguration& mappingConfiguration)
void CreateEditShortcutsWindow(HINSTANCE hInst, KBMEditor::KeyboardManagerState& keyboardManagerState, MappingConfiguration& mappingConfiguration, std::wstring keysForShortcutToEdit, std::wstring action)
{
// Move implementation into the separate method so resources get destroyed correctly
CreateEditShortcutsWindowImpl(hInst, keyboardManagerState, mappingConfiguration);
CreateEditShortcutsWindowImpl(hInst, keyboardManagerState, mappingConfiguration, keysForShortcutToEdit, action);
// Calling ClearXamlIslands() outside of the message loop is not enough to prevent
// Microsoft.UI.XAML.dll from crashing during deinitialization, see https://github.com/microsoft/PowerToys/issues/10906
@@ -420,7 +478,9 @@ LRESULT CALLBACK EditShortcutsWindowProc(HWND hWnd, UINT messageCode, WPARAM wPa
LPMINMAXINFO mmi = reinterpret_cast<LPMINMAXINFO>(lParam);
float minWidth = EditorConstants::MinimumEditShortcutsWindowWidth;
float minHeight = EditorConstants::MinimumEditShortcutsWindowHeight;
DPIAware::Convert(MonitorFromWindow(hWnd, MONITOR_DEFAULTTONULL), minWidth, minHeight);
mmi->ptMinTrackSize.x = static_cast<LONG>(minWidth);
mmi->ptMinTrackSize.y = static_cast<LONG>(minHeight);
}
@@ -453,8 +513,7 @@ LRESULT CALLBACK EditShortcutsWindowProc(HWND hWnd, UINT messageCode, WPARAM wPa
rect->top,
rect->right - rect->left,
rect->bottom - rect->top,
SWP_NOZORDER | SWP_NOACTIVATE
);
SWP_NOZORDER | SWP_NOACTIVATE);
Logger::trace(L"WM_DPICHANGED: new dpi {} rect {} {} ", newDPI, rect->right - rect->left, rect->bottom - rect->top);
}

View File

@@ -8,7 +8,7 @@ namespace KBMEditor
class MappingConfiguration;
// Function to create the Edit Shortcuts Window
void CreateEditShortcutsWindow(HINSTANCE hInst, KBMEditor::KeyboardManagerState& keyboardManagerState, MappingConfiguration& mappingConfiguration);
void CreateEditShortcutsWindow(HINSTANCE hInst, KBMEditor::KeyboardManagerState& keyboardManagerState, MappingConfiguration& mappingConfiguration, std::wstring keysForShortcutToEdit, std::wstring action);
// Function to check if there is already a window active if yes bring to foreground
bool CheckEditShortcutsWindowActive();

View File

@@ -10,8 +10,13 @@ namespace EditorConstants
inline const int EditKeyboardTableMinWidth = 700;
inline const int DefaultEditShortcutsWindowWidth = 1410;
inline const int DefaultEditShortcutsWindowHeight = 600;
inline const int DefaultEditSingleShortcutsWindowWidth = 1080;
inline const int DefaultEditSingleShortcutsWindowHeight = 400;
inline const int MinimumEditShortcutsWindowWidth = 500;
inline const int MinimumEditShortcutsWindowHeight = 500;
inline const int MinimumEditSingleShortcutsWindowWidth = 500;
inline const int MinimumEditSingleShortcutsWindowHeight = 600;
inline const int EditShortcutsTableMinWidth = 1000;
// Key Remap table constants

View File

@@ -52,6 +52,16 @@ namespace EditorHelpers
// Function to return true if the shortcut is valid. A valid shortcut has atleast one modifier, as well as an action key
bool IsValidShortcut(Shortcut shortcut)
{
if (shortcut.operationType == Shortcut::OperationType::RunProgram && shortcut.runProgramFilePath.length() > 0)
{
return true;
}
if (shortcut.operationType == Shortcut::OperationType::OpenURI && shortcut.uriToOpen.length() > 0)
{
return true;
}
if (shortcut.actionKey != NULL)
{
if (shortcut.winKey != ModifierKey::Disabled || shortcut.ctrlKey != ModifierKey::Disabled || shortcut.altKey != ModifierKey::Disabled || shortcut.shiftKey != ModifierKey::Disabled)

View File

@@ -30,6 +30,11 @@ DWORD KeyDropDownControl::GetSelectedValue(ComboBox comboBox)
return stoul(std::wstring(value));
}
DWORD KeyDropDownControl::GetSelectedValue(TextBlock text)
{
return keyboardManagerState->keyboardMap.GetKeyFromName(std::wstring{ text.Text() });
}
void KeyDropDownControl::SetSelectedValue(std::wstring value)
{
this->dropDown.as<ComboBox>().SelectedValue(winrt::box_value(value));
@@ -87,7 +92,7 @@ void KeyDropDownControl::SetDefaultProperties(bool isShortcut, bool renderDisabl
auto child0 = Media::VisualTreeHelper::GetChild(combo, 0);
if (!child0)
return;
auto grid = child0.as<Grid>();
if (!grid)
return;
@@ -95,7 +100,7 @@ void KeyDropDownControl::SetDefaultProperties(bool isShortcut, bool renderDisabl
auto& gridChildren = grid.Children();
if (!gridChildren)
return;
gridChildren.Append(warningTip);
});
@@ -287,10 +292,8 @@ void KeyDropDownControl::SetSelectionHandler(StackPanel& table, StackPanel row,
}
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[validationResult.second].first[colIndex] = tempShortcut;
shortcutRemapBuffer[validationResult.second].first[colIndex] = Shortcut(selectedKeyCodes);
}
}
@@ -364,10 +367,31 @@ std::vector<int32_t> KeyDropDownControl::GetSelectedCodesFromStackPanel(Variable
std::vector<int32_t> selectedKeyCodes;
// Get selected indices for each drop down
for (int i = 0; i < (int)parent.Children().Size(); i++)
for (uint32_t i = 0; i < parent.Children().Size(); i++)
{
ComboBox ItDropDown = parent.Children().GetAt(i).as<ComboBox>();
selectedKeyCodes.push_back(GetSelectedValue(ItDropDown));
if (auto ItDropDown = parent.Children().GetAt(i).try_as<ComboBox>(); ItDropDown)
{
selectedKeyCodes.push_back(GetSelectedValue(ItDropDown));
}
// If it's a ShortcutControl -> use its layout, see KeyboardManagerState::AddKeyToLayout
else if (auto sp = parent.Children().GetAt(i).try_as<StackPanel>(); sp)
{
for (uint32_t j = 0; j < sp.Children().Size(); ++j)
{
auto border = sp.Children().GetAt(j).try_as<Border>();
// if this is null then this is a different layout
// likely because this not a shortcut to another shortcut but rather
// run app or open uri
if (border != nullptr)
{
auto textBlock = border.Child().try_as<TextBlock>();
selectedKeyCodes.push_back(GetSelectedValue(textBlock));
}
}
}
}
return selectedKeyCodes;
@@ -432,27 +456,36 @@ void KeyDropDownControl::AddShortcutToControl(Shortcut shortcut, StackPanel tabl
// Remove references to the old drop down objects to destroy them
keyDropDownControlObjects.clear();
std::vector<DWORD> shortcutKeyCodes = shortcut.GetKeyCodes();
if (shortcutKeyCodes.size() != 0)
auto secondKey = shortcut.GetSecondKey();
bool ignoreWarning = false;
// If more than one key is to be added, ignore a shortcut to key warning on partially entering the remapping
if (shortcutKeyCodes.size() > 1)
{
bool ignoreWarning = false;
ignoreWarning = true;
}
// If more than one key is to be added, ignore a shortcut to key warning on partially entering the remapping
if (shortcutKeyCodes.size() > 1)
KeyDropDownControl::AddDropDown(table, row, parent, colIndex, remapBuffer, keyDropDownControlObjects, targetApp, isHybridControl, isSingleKeyWindow, ignoreWarning);
for (int i = 0; i < shortcutKeyCodes.size(); i++)
{
// New drop down gets added automatically when the SelectedValue(key code) is set
if (i < (int)parent.Children().Size())
{
ignoreWarning = true;
ComboBox currentDropDown = parent.Children().GetAt(i).as<ComboBox>();
currentDropDown.SelectedValue(winrt::box_value(std::to_wstring(shortcutKeyCodes[i])));
}
}
if (shortcut.HasChord())
{
// if this has a chord, render it last
KeyDropDownControl::AddDropDown(table, row, parent, colIndex, remapBuffer, keyDropDownControlObjects, targetApp, isHybridControl, isSingleKeyWindow, ignoreWarning);
for (int i = 0; i < shortcutKeyCodes.size(); i++)
{
// New drop down gets added automatically when the SelectedValue(key code) is set
if (i < (int)parent.Children().Size())
{
ComboBox currentDropDown = parent.Children().GetAt(i).as<ComboBox>();
currentDropDown.SelectedValue(winrt::box_value(std::to_wstring(shortcutKeyCodes[i])));
}
}
auto nextI = static_cast<int>(shortcutKeyCodes.size());
ComboBox currentDropDown = parent.Children().GetAt(nextI).as<ComboBox>();
currentDropDown.SelectedValue(winrt::box_value(std::to_wstring(shortcut.GetSecondKey())));
}
}

View File

@@ -63,6 +63,7 @@ private:
// Get selected value of dropdown or -1 if nothing is selected
static DWORD GetSelectedValue(ComboBox comboBox);
static DWORD GetSelectedValue(TextBlock text);
// Function to set accessible name for combobox
static void SetAccessibleNameForComboBox(ComboBox dropDown, int index);
@@ -110,7 +111,7 @@ public:
static void AddShortcutToControl(Shortcut shortcut, StackPanel table, VariableSizedWrapGrid parent, KBMEditor::KeyboardManagerState& keyboardManagerState, const int colIndex, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, RemapBuffer& remapBuffer, StackPanel row, TextBox targetApp, bool isHybridControl, bool isSingleKeyWindow);
// Get keys name list depending if Disable is in dropdown
static std::vector<std::pair<DWORD,std::wstring>> GetKeyList(bool isShortcut, bool renderDisable);
static std::vector<std::pair<DWORD, std::wstring>> GetKeyList(bool isShortcut, bool renderDisable);
// Get number of selected keys. Do not count -1 and 0 values as they stand for Not selected and None
static int GetNumberOfSelectedKeys(std::vector<int32_t> keys);

View File

@@ -22,6 +22,16 @@ namespace KeyboardManagerEditorStrings
return GET_RESOURCE_STRING(IDS_MAPPING_TYPE_DROPDOWN_KEY_SHORTCUT);
}
inline std::wstring MappingTypeRunProgram()
{
return GET_RESOURCE_STRING(IDS_MAPPING_TYPE_DROPDOWN_RUN_PROGRAM);
}
inline std::wstring MappingTypeOpenUri()
{
return GET_RESOURCE_STRING(IDS_MAPPING_TYPE_DROPDOWN_OPEN_URI);
}
// Function to return the error message
winrt::hstring GetErrorMessage(ShortcutErrorType errorType);
}

View File

@@ -110,13 +110,13 @@ void KeyboardManagerState::ConfigureDetectSingleKeyRemapUI(const StackPanel& tex
currentSingleKeyUI = textBlock.as<winrt::Windows::Foundation::IInspectable>();
}
void KeyboardManagerState::AddKeyToLayout(const StackPanel& panel, const hstring& key)
TextBlock KeyboardManagerState::AddKeyToLayout(const StackPanel& panel, const hstring& key)
{
// Textblock to display the detected key
TextBlock remapKey;
Border border;
border.Padding({ 20, 10, 20, 10 });
border.Padding({ 10, 5, 10, 5 });
border.Margin({ 0, 0, 10, 0 });
// Based on settings-ui\Settings.UI\SettingsXAML\Controls\KeyVisual\KeyVisual.xaml
@@ -127,12 +127,15 @@ void KeyboardManagerState::AddKeyToLayout(const StackPanel& panel, const hstring
remapKey.Foreground(Application::Current().Resources().Lookup(box_value(L"ButtonForeground")).as<Media::Brush>());
remapKey.FontWeight(Text::FontWeights::SemiBold());
remapKey.FontSize(20);
remapKey.FontSize(14);
border.HorizontalAlignment(HorizontalAlignment::Left);
border.Child(remapKey);
remapKey.Text(key);
panel.Children().Append(border);
return remapKey;
}
// Function to update the detect shortcut UI based on the entered keys
@@ -167,17 +170,39 @@ void KeyboardManagerState::UpdateDetectShortcutUI()
currentShortcutUI2.as<StackPanel>().Visibility(Visibility::Collapsed);
}
auto lastStackPanel = currentShortcutUI2.as<StackPanel>();
for (int i = 0; i < shortcut.size(); i++)
{
if (i < 3)
{
AddKeyToLayout(currentShortcutUI1.as<StackPanel>(), shortcut[i]);
lastStackPanel = currentShortcutUI1.as<StackPanel>();
}
else
{
AddKeyToLayout(currentShortcutUI2.as<StackPanel>(), shortcut[i]);
lastStackPanel = currentShortcutUI2.as<StackPanel>();
}
}
if (!AllowChord)
{
detectedShortcut.secondKey = NULL;
}
// add a TextBlock, to show what shortcut in text, e.g.: "CTRL+j, k" OR "CTRL+j, CTRL+k".
if (detectedShortcut.HasChord())
{
TextBlock txtComma;
txtComma.Text(L",");
txtComma.FontSize(20);
txtComma.Padding({ 0, 0, 10, 0 });
txtComma.VerticalAlignment(VerticalAlignment::Bottom);
txtComma.TextAlignment(TextAlignment::Left);
lastStackPanel.Children().Append(txtComma);
AddKeyToLayout(lastStackPanel, EditorHelpers::GetKeyVector(Shortcut(detectedShortcutCopy.secondKey), keyboardMap)[0]);
}
try
{
// If a layout update has been triggered by other methods (e.g.: adapting to zoom level), this may throw an exception.
@@ -228,6 +253,12 @@ Shortcut KeyboardManagerState::GetDetectedShortcut()
return currentShortcut;
}
void KeyboardManagerState::SetDetectedShortcut(Shortcut shortcut)
{
detectedShortcut = shortcut;
UpdateDetectShortcutUI();
}
// Function to return the currently detected remap key which is displayed on the UI
DWORD KeyboardManagerState::GetDetectedSingleRemapKey()
{
@@ -246,9 +277,57 @@ void KeyboardManagerState::SelectDetectedRemapKey(DWORD key)
void KeyboardManagerState::SelectDetectedShortcut(DWORD key)
{
// Set the new key and store if a change occurred
std::unique_lock<std::mutex> lock(detectedShortcut_mutex);
bool updateUI = detectedShortcut.SetKey(key);
lock.unlock();
bool updateUI = false;
if (AllowChord)
{
// Code to determine if we're building/updating a chord.
auto currentFirstKey = detectedShortcut.GetActionKey();
auto currentSecondKey = detectedShortcut.GetSecondKey();
Shortcut tempShortcut = Shortcut(key);
bool isKeyActionTypeKey = (tempShortcut.actionKey != NULL);
if (isKeyActionTypeKey)
{
// we want a chord and already have the first key set
std::unique_lock<std::mutex> lock(detectedShortcut_mutex);
if (currentFirstKey == NULL)
{
Logger::trace(L"AllowChord AND no first");
updateUI = detectedShortcut.SetKey(key);
}
else if (currentSecondKey == NULL)
{
// we don't have the second key, set it now
Logger::trace(L"AllowChord AND we have first key of {}, will use {}", currentFirstKey, key);
updateUI = detectedShortcut.SetSecondKey(key);
}
else
{
// we already have the second key, swap it to first, and use new as second
Logger::trace(L"DO have secondKey, will make first {} and second {}", currentSecondKey, key);
detectedShortcut.actionKey = currentSecondKey;
detectedShortcut.secondKey = key;
updateUI = true;
}
updateUI = true;
lock.unlock();
}
else
{
std::unique_lock<std::mutex> lock(detectedShortcut_mutex);
updateUI = detectedShortcut.SetKey(key);
lock.unlock();
}
}
else
{
std::unique_lock<std::mutex> lock(detectedShortcut_mutex);
updateUI = detectedShortcut.SetKey(key);
lock.unlock();
}
if (updateUI)
{
@@ -261,9 +340,12 @@ void KeyboardManagerState::SelectDetectedShortcut(DWORD key)
void KeyboardManagerState::ResetDetectedShortcutKey(DWORD key)
{
std::lock_guard<std::mutex> lock(detectedShortcut_mutex);
detectedShortcut.ResetKey(key);
// only clear if mod, not if action, since we need to keek actionKey and secondKey for chord
if (Shortcut::IsModifier(key))
{
detectedShortcut.ResetKey(key);
}
}
// Function which can be used in HandleKeyboardHookEvent before the single key remap event to use the UI and suppress events while the remap window is active.
Helpers::KeyboardHookDecision KeyboardManagerState::DetectSingleRemapKeyUIBackend(LowlevelKeyboardEvent* data)
{
@@ -372,6 +454,12 @@ void KeyboardManagerState::ClearRegisteredKeyDelays()
keyDelays.clear();
}
void KBMEditor::KeyboardManagerState::ClearStoredShortcut()
{
std::scoped_lock<std::mutex> detectedShortcut_lock(detectedShortcut_mutex);
detectedShortcut.Reset();
}
bool KeyboardManagerState::HandleKeyDelayEvent(LowlevelKeyboardEvent* ev)
{
if (currentUIWindow != GetForegroundWindow())

View File

@@ -22,6 +22,7 @@ namespace Helpers
namespace winrt::Windows::UI::Xaml::Controls
{
struct StackPanel;
struct TextBlock;
}
namespace KBMEditor
@@ -80,10 +81,15 @@ namespace KBMEditor
std::map<DWORD, std::unique_ptr<KeyDelay>> keyDelays;
std::mutex keyDelays_mutex;
// Display a key by appending a border Control as a child of the panel.
void AddKeyToLayout(const winrt::Windows::UI::Xaml::Controls::StackPanel& panel, const winrt::hstring& key);
public:
// Display a key by appending a border Control as a child of the panel.
winrt::Windows::UI::Xaml::Controls::TextBlock AddKeyToLayout(const winrt::Windows::UI::Xaml::Controls::StackPanel& panel, const winrt::hstring& key);
// flag to set if we want to allow building a chord
bool AllowChord = false;
// Stores the keyboard layout
LayoutMap keyboardMap;
@@ -120,6 +126,9 @@ namespace KBMEditor
// Function to return the currently detected shortcut which is displayed on the UI
Shortcut GetDetectedShortcut();
// Function to SetDetectedShortcut and also UpdateDetectShortcutUI
void KeyboardManagerState::SetDetectedShortcut(Shortcut shortcut);
// Function to return the currently detected remap key which is displayed on the UI
DWORD GetDetectedSingleRemapKey();
@@ -146,6 +155,8 @@ namespace KBMEditor
// Function to clear all the registered key delays
void ClearRegisteredKeyDelays();
void ClearStoredShortcut();
// Handle a key event, for a delayed key.
bool HandleKeyDelayEvent(LowlevelKeyboardEvent* ev);

View File

@@ -1,6 +1,8 @@
#include "pch.h"
#include "ShortcutControl.h"
#include <Windows.h>
#include <commdlg.h>
#include <ShlObj.h>
#include <common/interop/shared_constants.h>
#include "KeyboardManagerState.h"
@@ -19,8 +21,9 @@ RemapBuffer ShortcutControl::shortcutRemapBuffer;
ShortcutControl::ShortcutControl(StackPanel table, StackPanel row, const int colIndex, TextBox targetApp)
{
shortcutDropDownVariableSizedWrapGrid = VariableSizedWrapGrid();
typeShortcut = Button();
btnPickShortcut = Button();
shortcutControlLayout = StackPanel();
const bool isHybridControl = colIndex == 1;
// TODO: Check if there is a VariableSizedWrapGrid equivalent.
@@ -28,28 +31,44 @@ ShortcutControl::ShortcutControl(StackPanel table, StackPanel row, const int col
shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>().Orientation(Windows::UI::Xaml::Controls::Orientation::Horizontal);
shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>().MaximumRowsOrColumns(3);
typeShortcut.as<Button>().Content(winrt::box_value(GET_RESOURCE_STRING(IDS_TYPE_BUTTON)));
typeShortcut.as<Button>().Width(EditorConstants::ShortcutTableDropDownWidth);
typeShortcut.as<Button>().Click([&, table, row, colIndex, isHybridControl, targetApp](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) {
btnPickShortcut.as<Button>().Content(winrt::box_value(GET_RESOURCE_STRING(IDS_TYPE_BUTTON)));
btnPickShortcut.as<Button>().Width(EditorConstants::ShortcutTableDropDownWidth / 2);
btnPickShortcut.as<Button>().Click([&, table, row, colIndex, isHybridControl, targetApp](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) {
keyboardManagerState->SetUIState(KBMEditor::KeyboardManagerUIState::DetectShortcutWindowActivated, editShortcutsWindowHandle);
// Using the XamlRoot of the typeShortcut to get the root of the XAML host
CreateDetectShortcutWindow(sender, sender.as<Button>().XamlRoot(), *keyboardManagerState, colIndex, table, keyDropDownControlObjects, row, targetApp, isHybridControl, false, editShortcutsWindowHandle, shortcutRemapBuffer);
});
FontIcon fontIcon;
fontIcon.Glyph(L"\uE70F"); // Unicode for the accept icon
fontIcon.FontFamily(Media::FontFamily(L"Segoe MDL2 Assets")); // Set the font family to Segoe MDL2 Assets
// Set the FontIcon as the content of the button
btnPickShortcut.as<Button>().Content(fontIcon);
uint32_t rowIndex;
UIElementCollection children = table.Children();
bool indexFound = children.IndexOf(row, rowIndex);
auto nameX = L"btnPickShortcut_" + std::to_wstring(colIndex);
btnPickShortcut.as<Button>().Name(nameX);
// Set an accessible name for the type shortcut button
typeShortcut.as<Button>().SetValue(Automation::AutomationProperties::NameProperty(), box_value(GET_RESOURCE_STRING(IDS_TYPE_BUTTON)));
btnPickShortcut.as<Button>().SetValue(Automation::AutomationProperties::NameProperty(), box_value(GET_RESOURCE_STRING(IDS_TYPE_BUTTON)));
shortcutControlLayout.as<StackPanel>().Spacing(EditorConstants::ShortcutTableDropDownSpacing);
keyComboAndSelectStackPanel = StackPanel();
keyComboAndSelectStackPanel.as<StackPanel>().Orientation(Windows::UI::Xaml::Controls::Orientation::Horizontal);
keyComboAndSelectStackPanel.as<StackPanel>().Spacing(EditorConstants::ShortcutTableDropDownSpacing);
keyComboStackPanel = StackPanel();
keyComboStackPanel.as<StackPanel>().Orientation(Windows::UI::Xaml::Controls::Orientation::Horizontal);
keyComboStackPanel.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(keyComboStackPanel.as<StackPanel>());
spBtnPickShortcut = UIHelpers::GetLabelWrapped(btnPickShortcut.as<Button>(), GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_LABEL_SHORTCUT), 80).as<StackPanel>();
shortcutControlLayout.as<StackPanel>().Children().Append(spBtnPickShortcut);
shortcutControlLayout.as<StackPanel>().Children().Append(shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>());
KeyDropDownControl::AddDropDown(table, row, shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>(), colIndex, shortcutRemapBuffer, keyDropDownControlObjects, targetApp, isHybridControl, false);
try
{
// If a layout update has been triggered by other methods (e.g.: adapting to zoom level), this may throw an exception.
@@ -60,6 +79,13 @@ ShortcutControl::ShortcutControl(StackPanel table, StackPanel row, const int col
}
}
void ShortcutControl::OpenNewShortcutControlRow(StackPanel table, StackPanel row)
{
keyboardManagerState->SetUIState(KBMEditor::KeyboardManagerUIState::DetectShortcutWindowActivated, editShortcutsWindowHandle);
// Using the XamlRoot of the typeShortcut to get the root of the XAML host
CreateDetectShortcutWindow(btnPickShortcut, btnPickShortcut.XamlRoot(), *keyboardManagerState, 0, table, keyDropDownControlObjects, row, nullptr, false, false, editShortcutsWindowHandle, shortcutRemapBuffer);
}
// Function to set the accessible name of the target App text box
void ShortcutControl::SetAccessibleNameForTextBox(TextBox targetAppTextBox, int rowIndex)
{
@@ -82,18 +108,36 @@ void ShortcutControl::UpdateAccessibleNames(StackPanel sourceColumn, StackPanel
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 ShortcutControl::DeleteShortcutControl(StackPanel& parent, std::vector<std::vector<std::unique_ptr<ShortcutControl>>>& keyboardRemapControlObjects, int rowIndex)
{
UIElementCollection children = parent.Children();
children.RemoveAt(rowIndex);
shortcutRemapBuffer.erase(shortcutRemapBuffer.begin() + rowIndex);
// delete the SingleKeyRemapControl objects so that they get destructed
keyboardRemapControlObjects.erase(keyboardRemapControlObjects.begin() + rowIndex);
}
// 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 KeyShortcutTextUnion& newKeys, const std::wstring& targetAppName)
ShortcutControl& 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;
int runProgramLabelWidth = 80;
// Create new ShortcutControl objects dynamically so that we does not get destructed
std::vector<std::unique_ptr<ShortcutControl>> newrow;
StackPanel row = StackPanel();
row.Name(L"row");
parent.Children().Append(row);
newrow.emplace_back(std::make_unique<ShortcutControl>(parent, row, 0, targetAppTextBox));
ShortcutControl& newShortcutToRemap = *(newrow.back());
newrow.emplace_back(std::make_unique<ShortcutControl>(parent, row, 1, targetAppTextBox));
keyboardRemapControlObjects.push_back(std::move(newrow));
row.Padding({ 10, 15, 10, 5 });
@@ -105,7 +149,9 @@ void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector<s
// ShortcutControl for the original shortcut
auto origin = keyboardRemapControlObjects.back()[0]->GetShortcutControl();
origin.Width(EditorConstants::ShortcutOriginColumnWidth);
row.Children().Append(origin);
// Arrow icon
@@ -122,26 +168,46 @@ void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector<s
auto target = keyboardRemapControlObjects.back()[1]->GetShortcutControl();
target.Width(EditorConstants::ShortcutTargetColumnWidth);
auto typeCombo = ComboBox();
typeCombo.Width(EditorConstants::RemapTableDropDownWidth);
typeCombo.Items().Append(winrt::box_value(KeyboardManagerEditorStrings::MappingTypeKeyShortcut()));
typeCombo.Items().Append(winrt::box_value(KeyboardManagerEditorStrings::MappingTypeText()));
uint32_t rowIndex = -1;
if (!parent.Children().IndexOf(row, rowIndex))
{
return newShortcutToRemap;
}
// add shortcut type choice
auto actionTypeCombo = ComboBox();
actionTypeCombo.Name(L"actionTypeCombo_" + std::to_wstring(rowIndex));
actionTypeCombo.Width(EditorConstants::RemapTableDropDownWidth);
actionTypeCombo.Items().Append(winrt::box_value(KeyboardManagerEditorStrings::MappingTypeKeyShortcut()));
actionTypeCombo.Items().Append(winrt::box_value(KeyboardManagerEditorStrings::MappingTypeText()));
actionTypeCombo.Items().Append(winrt::box_value(KeyboardManagerEditorStrings::MappingTypeRunProgram()));
actionTypeCombo.Items().Append(winrt::box_value(KeyboardManagerEditorStrings::MappingTypeOpenUri()));
auto controlStackPanel = keyboardRemapControlObjects.back()[1]->shortcutControlLayout.as<StackPanel>();
auto firstLineStackPanel = keyboardRemapControlObjects.back()[1]->keyComboAndSelectStackPanel.as<StackPanel>();
firstLineStackPanel.Children().InsertAt(0, typeCombo);
auto firstLineStackPanel = keyboardRemapControlObjects.back()[1]->keyComboStackPanel.as<StackPanel>();
firstLineStackPanel.Children().InsertAt(0, UIHelpers::GetLabelWrapped(actionTypeCombo, GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_LABEL_ACTION), runProgramLabelWidth).as<StackPanel>());
// add textbox for when it's a text input
auto unicodeTextKeysInput = TextBox();
unicodeTextKeysInput.Name(L"unicodeTextKeysInput_" + std::to_wstring(rowIndex));
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);
unicodeTextKeysInput.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 {
unicodeTextKeysInput.AcceptsReturn(false);
//unicodeTextKeysInput.Visibility(Visibility::Collapsed);
unicodeTextKeysInput.Width(EditorConstants::TableDropDownHeight);
StackPanel spUnicodeTextKeysInput = UIHelpers::GetLabelWrapped(unicodeTextKeysInput, GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_LABEL_KEYS), runProgramLabelWidth).as<StackPanel>();
controlStackPanel.Children().Append(spUnicodeTextKeysInput);
unicodeTextKeysInput.HorizontalAlignment(HorizontalAlignment::Left);
unicodeTextKeysInput.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;
@@ -154,29 +220,93 @@ void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector<s
shortcutRemapBuffer[rowIndex].first[1] = text.c_str();
});
auto grid = keyboardRemapControlObjects.back()[1]->shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>();
const bool textSelected = newKeys.index() == 2;
bool isRunProgram = false;
bool isOpenUri = false;
Shortcut shortCut;
if (!textSelected && newKeys.index() == 1)
{
shortCut = std::get<Shortcut>(newKeys);
isRunProgram = (shortCut.operationType == Shortcut::OperationType::RunProgram);
isOpenUri = (shortCut.operationType == Shortcut::OperationType::OpenURI);
}
// add TextBoxes for when it's a runProgram fields
auto runProgramStackPanel = SetupRunProgramControls(parent, row, shortCut, textInputMargin, controlStackPanel);
runProgramStackPanel.Margin({ 0, -30, 0, 0 });
auto openURIStackPanel = SetupOpenURIControls(parent, row, shortCut, textInputMargin, controlStackPanel);
// add grid for when it's a key/shortcut
auto shortcutItemsGrid = 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>();
shortcutItemsGrid.Margin(gridMargin);
auto shortcutButton = keyboardRemapControlObjects.back()[1]->btnPickShortcut.as<Button>();
auto spBtnPickShortcut = keyboardRemapControlObjects.back()[1]->spBtnPickShortcut.as<StackPanel>();
typeCombo.SelectionChanged([typeCombo, grid, button, textInput](winrt::Windows::Foundation::IInspectable const&, SelectionChangedEventArgs const&) {
const bool textSelected = typeCombo.SelectedIndex() == 1;
// event code for when type changes
actionTypeCombo.SelectionChanged([parent, row, controlStackPanel, actionTypeCombo, shortcutItemsGrid, spBtnPickShortcut, spUnicodeTextKeysInput, runProgramStackPanel, openURIStackPanel](winrt::Windows::Foundation::IInspectable const&, SelectionChangedEventArgs const&) {
const auto shortcutType = ShortcutControl::GetShortcutType(actionTypeCombo);
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);
if (shortcutType == ShortcutControl::ShortcutType::Shortcut)
{
spBtnPickShortcut.Visibility(Visibility::Visible);
shortcutItemsGrid.Visibility(Visibility::Visible);
spUnicodeTextKeysInput.Visibility(Visibility::Collapsed);
runProgramStackPanel.Visibility(Visibility::Collapsed);
openURIStackPanel.Visibility(Visibility::Collapsed);
}
else if (shortcutType == ShortcutControl::ShortcutType::Text)
{
spBtnPickShortcut.Visibility(Visibility::Collapsed);
shortcutItemsGrid.Visibility(Visibility::Collapsed);
spUnicodeTextKeysInput.Visibility(Visibility::Visible);
runProgramStackPanel.Visibility(Visibility::Collapsed);
openURIStackPanel.Visibility(Visibility::Collapsed);
}
else if (shortcutType == ShortcutControl::ShortcutType::RunProgram)
{
spBtnPickShortcut.Visibility(Visibility::Collapsed);
shortcutItemsGrid.Visibility(Visibility::Collapsed);
spUnicodeTextKeysInput.Visibility(Visibility::Collapsed);
runProgramStackPanel.Visibility(Visibility::Visible);
openURIStackPanel.Visibility(Visibility::Collapsed);
}
else
{
spBtnPickShortcut.Visibility(Visibility::Collapsed);
shortcutItemsGrid.Visibility(Visibility::Collapsed);
spUnicodeTextKeysInput.Visibility(Visibility::Collapsed);
runProgramStackPanel.Visibility(Visibility::Collapsed);
openURIStackPanel.Visibility(Visibility::Visible);
}
});
const bool textSelected = newKeys.index() == 2;
typeCombo.SelectedIndex(textSelected);
row.Children().Append(target);
if (textSelected)
{
actionTypeCombo.SelectedIndex(1);
}
else
{
if (shortCut.operationType == Shortcut::OperationType::RunProgram)
{
actionTypeCombo.SelectedIndex(2);
}
else if (shortCut.operationType == Shortcut::OperationType::OpenURI)
{
actionTypeCombo.SelectedIndex(3);
}
else
{
actionTypeCombo.SelectedIndex(0);
}
}
targetAppTextBox.Width(EditorConstants::ShortcutTableDropDownWidth);
targetAppTextBox.PlaceholderText(KeyboardManagerEditorStrings::DefaultAppName());
targetAppTextBox.Text(targetAppName);
@@ -189,7 +319,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, typeCombo, textInput](auto const& sender, auto const& e) {
targetAppTextBox.LostFocus([&keyboardRemapControlObjects, parent, row, targetAppTextBox, actionTypeCombo, unicodeTextKeysInput](auto const& sender, auto const& e) {
// Get index of targetAppTextBox button
uint32_t rowIndex;
if (!parent.Children().IndexOf(row, rowIndex))
@@ -211,28 +341,60 @@ 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
const bool textSelected = typeCombo.SelectedIndex() == 1;
const bool regularShortcut = actionTypeCombo.SelectedIndex() == 0;
const bool textSelected = actionTypeCombo.SelectedIndex() == 1;
const bool runProgram = actionTypeCombo.SelectedIndex() == 2;
const bool openUri = actionTypeCombo.SelectedIndex() == 3;
if (textSelected)
{
shortcutRemapBuffer[rowIndex].first[1] = textInput.Text().c_str();
shortcutRemapBuffer[rowIndex].first[1] = unicodeTextKeysInput.Text().c_str();
}
else
{
std::vector<int32_t> selectedKeyCodes = KeyDropDownControl::GetSelectedCodesFromStackPanel(keyboardRemapControlObjects[rowIndex][1]->shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>());
if (regularShortcut)
{
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];
// 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;
}
}
else
else if (runProgram)
{
auto runProgramPathInput = row.FindName(L"runProgramPathInput_" + std::to_wstring(rowIndex)).as<TextBox>();
auto runProgramArgsForProgramInput = row.FindName(L"runProgramArgsForProgramInput_" + std::to_wstring(rowIndex)).as<TextBox>();
auto runProgramStartInDirInput = row.FindName(L"runProgramStartInDirInput_" + std::to_wstring(rowIndex)).as<TextBox>();
auto runProgramElevationTypeCombo = row.FindName(L"runProgramElevationTypeCombo_" + std::to_wstring(rowIndex)).as<ComboBox>();
auto runProgramAlreadyRunningAction = row.FindName(L"runProgramAlreadyRunningAction_" + std::to_wstring(rowIndex)).as<ComboBox>();
Shortcut tempShortcut;
tempShortcut.SetKeyCodes(selectedKeyCodes);
tempShortcut.operationType = Shortcut::OperationType::RunProgram;
tempShortcut.runProgramFilePath = ShortcutControl::RemoveExtraQuotes(runProgramPathInput.Text().c_str());
tempShortcut.runProgramArgs = (runProgramArgsForProgramInput.Text().c_str());
tempShortcut.runProgramStartInDir = (runProgramStartInDirInput.Text().c_str());
tempShortcut.elevationLevel = static_cast<Shortcut::ElevationLevel>(runProgramElevationTypeCombo.SelectedIndex());
tempShortcut.alreadyRunningAction = static_cast<Shortcut::ProgramAlreadyRunningAction>(runProgramAlreadyRunningAction.SelectedIndex());
// Assign instead of setting the value in the buffer since the previous value may not be a Shortcut
shortcutRemapBuffer[rowIndex].first[1] = tempShortcut;
}
else if (openUri)
{
}
}
std::wstring newText = targetAppTextBox.Text().c_str();
std::wstring lowercaseDefAppName = KeyboardManagerEditorStrings::DefaultAppName();
std::transform(newText.begin(), newText.end(), newText.begin(), towlower);
@@ -251,16 +413,19 @@ void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector<s
});
// We need two containers in order to align it horizontally and vertically
StackPanel targetAppHorizontal = UIHelpers::GetWrapped(targetAppTextBox, EditorConstants::TableTargetAppColWidth).as<StackPanel>();
targetAppHorizontal.Orientation(Orientation::Horizontal);
targetAppHorizontal.HorizontalAlignment(HorizontalAlignment::Left);
StackPanel targetAppContainer = UIHelpers::GetWrapped(targetAppHorizontal, EditorConstants::TableTargetAppColWidth).as<StackPanel>();
targetAppContainer.Orientation(Orientation::Vertical);
targetAppContainer.VerticalAlignment(VerticalAlignment::Center);
row.Children().Append(targetAppContainer);
// Delete row button
Windows::UI::Xaml::Controls::Button deleteShortcut;
deleteShortcut.Content(SymbolIcon(Symbol::Delete));
deleteShortcut.Background(Media::SolidColorBrush(Colors::Transparent()));
deleteShortcut.HorizontalAlignment(HorizontalAlignment::Center);
@@ -312,9 +477,11 @@ void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector<s
ToolTipService::SetToolTip(deleteShortcut, deleteShortcuttoolTip);
StackPanel deleteShortcutContainer = StackPanel();
deleteShortcutContainer.Name(L"deleteShortcutContainer");
deleteShortcutContainer.Children().Append(deleteShortcut);
deleteShortcutContainer.Orientation(Orientation::Vertical);
deleteShortcutContainer.VerticalAlignment(VerticalAlignment::Center);
row.Children().Append(deleteShortcutContainer);
// Set accessible names
@@ -324,12 +491,25 @@ void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector<s
if (EditorHelpers::IsValidShortcut(originalKeys) && !(newKeys.index() == 0 && std::get<DWORD>(newKeys) == NULL) && !(newKeys.index() == 1 && !EditorHelpers::IsValidShortcut(std::get<Shortcut>(newKeys))))
{
// change to load app name
shortcutRemapBuffer.push_back(std::make_pair<RemapBufferItem, std::wstring>(RemapBufferItem{ Shortcut(), Shortcut() }, std::wstring(targetAppName)));
if (isRunProgram || isOpenUri)
{
// not sure why by we need to add the current item in here, so we have it even if does not change.
auto newShortcut = std::get<Shortcut>(newKeys);
shortcutRemapBuffer.push_back(std::make_pair<RemapBufferItem, std::wstring>(RemapBufferItem{ Shortcut(), newShortcut }, std::wstring(targetAppName)));
}
else
{
shortcutRemapBuffer.push_back(std::make_pair<RemapBufferItem, std::wstring>(RemapBufferItem{ Shortcut(), Shortcut() }, std::wstring(targetAppName)));
}
KeyDropDownControl::AddShortcutToControl(originalKeys, parent, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][0]->shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>(), *keyboardManagerState, 0, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][0]->keyDropDownControlObjects, shortcutRemapBuffer, row, targetAppTextBox, false, false);
if (newKeys.index() == 0)
{
keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1]->keyDropDownControlObjects[0]->SetSelectedValue(std::to_wstring(std::get<DWORD>(newKeys)));
auto shortcut = new Shortcut;
shortcut->SetKey(std::get<DWORD>(newKeys));
KeyDropDownControl::AddShortcutToControl(*shortcut, parent, keyboardRemapControlObjects.back()[1]->shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>(), *keyboardManagerState, 1, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1]->keyDropDownControlObjects, shortcutRemapBuffer, row, targetAppTextBox, true, false);
}
else if (newKeys.index() == 1)
{
@@ -339,16 +519,404 @@ void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector<s
{
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));
actionTypeCombo.SelectedIndex(1);
unicodeTextKeysInput.Text(std::get<std::wstring>(newKeys));
}
}
else
{
// Initialize both shortcuts as empty shortcuts
shortcutRemapBuffer.push_back(std::make_pair<RemapBufferItem, std::wstring>(RemapBufferItem{ Shortcut(), Shortcut() }, std::wstring(targetAppName)));
KeyDropDownControl::AddShortcutToControl(originalKeys, parent, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][0]->shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>(), *keyboardManagerState, 0, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][0]->keyDropDownControlObjects, shortcutRemapBuffer, row, targetAppTextBox, false, false);
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);
}
return newShortcutToRemap;
}
StackPanel SetupOpenURIControls(StackPanel& parent, StackPanel& row, Shortcut& shortCut, winrt::Windows::UI::Xaml::Thickness& textInputMargin, ::StackPanel& _controlStackPanel)
{
StackPanel openUriStackPanel;
auto uriTextBox = TextBox();
int runProgramLabelWidth = 80;
uriTextBox.Text(shortCut.uriToOpen);
uriTextBox.PlaceholderText(GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_URI_EXAMPLE));
uriTextBox.Margin(textInputMargin);
uriTextBox.Width(EditorConstants::TableDropDownHeight);
uriTextBox.HorizontalAlignment(HorizontalAlignment::Left);
winrt::Windows::UI::Xaml::Controls::HyperlinkButton hyperlinkButton;
hyperlinkButton.NavigateUri(Windows::Foundation::Uri(L"https://learn.microsoft.com/windows/uwp/launch-resume/launch-app-with-uri"));
hyperlinkButton.Content(winrt::box_value(GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_WHAT_CAN_I_USE_LINK)));
hyperlinkButton.Margin(textInputMargin);
StackPanel boxAndLink;
boxAndLink.Orientation(Orientation::Horizontal);
boxAndLink.Children().Append(uriTextBox);
boxAndLink.Children().Append(hyperlinkButton);
openUriStackPanel.Children().Append(UIHelpers::GetLabelWrapped(boxAndLink, GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_LABEL_PATH_URI), runProgramLabelWidth).as<StackPanel>());
uriTextBox.TextChanged([parent, row, uriTextBox](winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Controls::TextChangedEventArgs const& e) mutable {
uint32_t rowIndex = -1;
if (!parent.Children().IndexOf(row, rowIndex))
{
return;
}
Shortcut tempShortcut;
tempShortcut.operationType = Shortcut::OperationType::OpenURI;
tempShortcut.uriToOpen = ShortcutControl::RemoveExtraQuotes(uriTextBox.Text().c_str());
ShortcutControl::shortcutRemapBuffer[rowIndex].first[1] = tempShortcut;
});
_controlStackPanel.Children().Append(openUriStackPanel);
return openUriStackPanel;
}
StackPanel SetupRunProgramControls(StackPanel& parent, StackPanel& row, Shortcut& shortCut, winrt::Windows::UI::Xaml::Thickness& textInputMargin, ::StackPanel& _controlStackPanel)
{
uint32_t rowIndex;
// Get index of delete button
UIElementCollection children = parent.Children();
children.IndexOf(row, rowIndex);
StackPanel controlStackPanel;
controlStackPanel.Name(L"RunProgramControls_" + std::to_wstring(rowIndex));
auto runProgramPathInput = TextBox();
runProgramPathInput.Name(L"runProgramPathInput_" + std::to_wstring(rowIndex));
auto runProgramArgsForProgramInput = TextBox();
runProgramArgsForProgramInput.Name(L"runProgramArgsForProgramInput_" + std::to_wstring(rowIndex));
auto runProgramStartInDirInput = TextBox();
runProgramStartInDirInput.Name(L"runProgramStartInDirInput_" + std::to_wstring(rowIndex));
Button pickFileBtn;
Button pickPathBtn;
auto runProgramElevationTypeCombo = ComboBox();
runProgramElevationTypeCombo.Name(L"runProgramElevationTypeCombo_" + std::to_wstring(rowIndex));
auto runProgramAlreadyRunningAction = ComboBox();
runProgramAlreadyRunningAction.Name(L"runProgramAlreadyRunningAction_" + std::to_wstring(rowIndex));
_controlStackPanel.Children().Append(controlStackPanel);
StackPanel stackPanelForRunProgramPath;
StackPanel stackPanelRunProgramStartInDir;
runProgramPathInput.PlaceholderText(GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_PATH_TO_PROGRAM));
runProgramPathInput.Margin(textInputMargin);
runProgramPathInput.AcceptsReturn(false);
runProgramPathInput.IsSpellCheckEnabled(false);
runProgramPathInput.Width(EditorConstants::TableDropDownHeight);
runProgramPathInput.HorizontalAlignment(HorizontalAlignment::Left);
runProgramArgsForProgramInput.PlaceholderText(GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_ARGS_FOR_PROGRAM));
runProgramArgsForProgramInput.Margin(textInputMargin);
runProgramArgsForProgramInput.AcceptsReturn(false);
runProgramArgsForProgramInput.IsSpellCheckEnabled(false);
runProgramArgsForProgramInput.Width(EditorConstants::TableDropDownHeight);
runProgramArgsForProgramInput.HorizontalAlignment(HorizontalAlignment::Left);
runProgramStartInDirInput.IsSpellCheckEnabled(false);
runProgramStartInDirInput.PlaceholderText(GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_START_IN_DIR_FOR_PROGRAM));
runProgramStartInDirInput.Margin(textInputMargin);
runProgramStartInDirInput.AcceptsReturn(false);
runProgramStartInDirInput.Width(EditorConstants::TableDropDownHeight);
runProgramStartInDirInput.HorizontalAlignment(HorizontalAlignment::Left);
stackPanelForRunProgramPath.Orientation(Orientation::Horizontal);
stackPanelRunProgramStartInDir.Orientation(Orientation::Horizontal);
pickFileBtn.Content(winrt::box_value(GET_RESOURCE_STRING(IDS_BROWSE_FOR_PROGRAM_BUTTON)));
pickPathBtn.Content(winrt::box_value(GET_RESOURCE_STRING(IDS_BROWSE_FOR_PATH_BUTTON)));
pickFileBtn.Margin(textInputMargin);
pickPathBtn.Margin(textInputMargin);
stackPanelForRunProgramPath.Children().Append(runProgramPathInput);
stackPanelForRunProgramPath.Children().Append(pickFileBtn);
stackPanelRunProgramStartInDir.Children().Append(runProgramStartInDirInput);
stackPanelRunProgramStartInDir.Children().Append(pickPathBtn);
int runProgramLabelWidth = 90;
controlStackPanel.Children().Append(UIHelpers::GetLabelWrapped(stackPanelForRunProgramPath, GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_LABEL_PROGRAM), runProgramLabelWidth).as<StackPanel>());
controlStackPanel.Children().Append(UIHelpers::GetLabelWrapped(runProgramArgsForProgramInput, GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_LABEL_ARGS), runProgramLabelWidth).as<StackPanel>());
controlStackPanel.Children().Append(UIHelpers::GetLabelWrapped(stackPanelRunProgramStartInDir, GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_LABEL_START_IN), runProgramLabelWidth).as<StackPanel>());
// add shortcut type choice
runProgramElevationTypeCombo.Width(EditorConstants::TableDropDownHeight);
runProgramElevationTypeCombo.Items().Append(winrt::box_value(GET_RESOURCE_STRING(IDS_ELEVATION_TYPE_NORMAL)));
runProgramElevationTypeCombo.Items().Append(winrt::box_value(GET_RESOURCE_STRING(IDS_ELEVATION_TYPE_ELEVATED)));
runProgramElevationTypeCombo.Items().Append(winrt::box_value(GET_RESOURCE_STRING(IDS_ELEVATION_TYPE_DIFFERENT_USER)));
runProgramElevationTypeCombo.SelectedIndex(0);
// runProgramAlreadyRunningAction
runProgramAlreadyRunningAction.Width(EditorConstants::TableDropDownHeight);
runProgramAlreadyRunningAction.Items().Append(winrt::box_value(GET_RESOURCE_STRING(IDS_ALREADY_RUNNING_SHOW_WINDOW)));
runProgramAlreadyRunningAction.Items().Append(winrt::box_value(GET_RESOURCE_STRING(IDS_ALREADY_RUNNING_START_ANOTHER)));
runProgramAlreadyRunningAction.Items().Append(winrt::box_value(GET_RESOURCE_STRING(IDS_ALREADY_RUNNING_DO_NOTHING)));
runProgramAlreadyRunningAction.Items().Append(winrt::box_value(GET_RESOURCE_STRING(IDS_ALREADY_RUNNING_CLOSE)));
runProgramAlreadyRunningAction.Items().Append(winrt::box_value(GET_RESOURCE_STRING(IDS_ALREADY_RUNNING_TERMINATE)));
runProgramAlreadyRunningAction.SelectedIndex(0);
controlStackPanel.Children().Append(UIHelpers::GetLabelWrapped(runProgramElevationTypeCombo, GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_LABEL_ELEVATION), runProgramLabelWidth).as<StackPanel>());
controlStackPanel.Children().Append(UIHelpers::GetLabelWrapped(runProgramAlreadyRunningAction, GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_LABEL_IF_RUNNING), runProgramLabelWidth).as<StackPanel>());
auto runProgramStartWindow = ComboBox();
runProgramStartWindow.Name(L"runProgramStartWindow_" + std::to_wstring(rowIndex));
runProgramStartWindow.Width(EditorConstants::TableDropDownHeight);
runProgramStartWindow.Items().Append(winrt::box_value(GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_VISIBILITY_NORMAL)));
runProgramStartWindow.Items().Append(winrt::box_value(GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_VISIBILITY_HIDDEN)));
runProgramStartWindow.SelectedIndex(0);
controlStackPanel.Children().Append(UIHelpers::GetLabelWrapped(runProgramStartWindow, GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_LABEL_START_AS), runProgramLabelWidth).as<StackPanel>());
// add events to TextBoxes for runProgram fields.
runProgramPathInput.TextChanged([parent, row](winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Controls::TextChangedEventArgs const& e) mutable {
uint32_t rowIndex = -1;
if (!parent.Children().IndexOf(row, rowIndex))
{
return;
}
Shortcut tempShortcut;
CreateNewTempShortcut(row, tempShortcut, rowIndex);
ShortcutControl::shortcutRemapBuffer[rowIndex].first[1] = tempShortcut;
});
runProgramArgsForProgramInput.TextChanged([parent, row](winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Controls::TextChangedEventArgs const& e) mutable {
uint32_t rowIndex = -1;
if (!parent.Children().IndexOf(row, rowIndex))
{
return;
}
Shortcut tempShortcut;
CreateNewTempShortcut(row, tempShortcut, rowIndex);
ShortcutControl::shortcutRemapBuffer[rowIndex].first[1] = tempShortcut;
});
runProgramStartInDirInput.TextChanged([parent, row](winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Controls::TextChangedEventArgs const& e) mutable {
uint32_t rowIndex = -1;
if (!parent.Children().IndexOf(row, rowIndex))
{
return;
}
if (ShortcutControl::shortcutRemapBuffer.size() <= rowIndex)
{
return;
}
Shortcut tempShortcut;
CreateNewTempShortcut(row, tempShortcut, rowIndex);
ShortcutControl::shortcutRemapBuffer[rowIndex].first[1] = tempShortcut;
});
runProgramAlreadyRunningAction.SelectionChanged([parent, row](winrt::Windows::Foundation::IInspectable const&, SelectionChangedEventArgs const&) {
uint32_t rowIndex;
if (!parent.Children().IndexOf(row, rowIndex))
{
return;
}
if (ShortcutControl::shortcutRemapBuffer.size() <= rowIndex)
{
return;
}
Shortcut tempShortcut;
CreateNewTempShortcut(static_cast<StackPanel>(row), tempShortcut, rowIndex);
ShortcutControl::shortcutRemapBuffer[rowIndex].first[1] = tempShortcut;
});
runProgramElevationTypeCombo.SelectionChanged([parent, row](winrt::Windows::Foundation::IInspectable const&, SelectionChangedEventArgs const&) {
uint32_t rowIndex;
if (!parent.Children().IndexOf(row, rowIndex))
{
return;
}
if (ShortcutControl::shortcutRemapBuffer.size() <= rowIndex)
{
return;
}
Shortcut tempShortcut;
CreateNewTempShortcut(static_cast<StackPanel>(row), tempShortcut, rowIndex);
ShortcutControl::shortcutRemapBuffer[rowIndex].first[1] = tempShortcut;
});
runProgramStartWindow.SelectionChanged([parent, row](winrt::Windows::Foundation::IInspectable const&, SelectionChangedEventArgs const&) {
uint32_t rowIndex;
if (!parent.Children().IndexOf(row, rowIndex))
{
return;
}
if (ShortcutControl::shortcutRemapBuffer.size() <= rowIndex)
{
return;
}
Shortcut tempShortcut;
CreateNewTempShortcut(static_cast<StackPanel>(row), tempShortcut, rowIndex);
ShortcutControl::shortcutRemapBuffer[rowIndex].first[1] = tempShortcut;
});
pickFileBtn.Click([&, parent, row](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) {
Button currentButton = sender.as<Button>();
uint32_t rowIndex;
UIElementCollection children = parent.Children();
bool indexFound = children.IndexOf(row, rowIndex);
if (!indexFound)
{
return;
}
OPENFILENAME openFileName;
TCHAR szFile[260] = { 0 };
ZeroMemory(&openFileName, sizeof(openFileName));
openFileName.lStructSize = sizeof(openFileName);
openFileName.hwndOwner = NULL;
openFileName.lpstrFile = szFile;
openFileName.nMaxFile = sizeof(szFile);
openFileName.lpstrFilter = TEXT("All Files (*.*)\0*.*\0");
openFileName.nFilterIndex = 1;
openFileName.lpstrFileTitle = NULL;
openFileName.nMaxFileTitle = 0;
openFileName.lpstrInitialDir = NULL;
openFileName.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
auto runProgramPathInput = row.FindName(L"runProgramPathInput_" + std::to_wstring(rowIndex)).as<TextBox>();
if (GetOpenFileName(&openFileName) == TRUE)
{
runProgramPathInput.Text(szFile);
}
});
pickPathBtn.Click([&, parent, row](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) {
Button currentButton = sender.as<Button>();
uint32_t rowIndex;
UIElementCollection children = parent.Children();
bool indexFound = children.IndexOf(row, rowIndex);
if (!indexFound)
{
return;
}
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (!FAILED(hr))
{
// Create a buffer to store the selected folder path
wchar_t path[MAX_PATH];
ZeroMemory(path, sizeof(path));
// Initialize the BROWSEINFO structure
BROWSEINFO browseInfo = { 0 };
browseInfo.hwndOwner = NULL; // Use NULL if there's no owner window
browseInfo.pidlRoot = NULL; // Use NULL to start from the desktop
browseInfo.pszDisplayName = path; // Buffer to store the display name
browseInfo.lpszTitle = L"Select a folder"; // Title of the dialog
browseInfo.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE; // Show only file system directories
// Show the dialog
LPITEMIDLIST pidl = SHBrowseForFolder(&browseInfo);
if (pidl != NULL)
{
// Get the selected folder's path
if (SHGetPathFromIDList(pidl, path))
{
auto runProgramStartInDirInput = row.FindName(L"runProgramStartInDirInput_" + std::to_wstring(rowIndex)).as<TextBox>();
runProgramStartInDirInput.Text(path);
}
// Free the PIDL
CoTaskMemFree(pidl);
}
// Release COM
CoUninitialize();
// Uninitialize COM
CoUninitialize();
}
});
// this really should not be here, it just works because SelectionChanged is always changed?
runProgramPathInput.Text(shortCut.runProgramFilePath);
runProgramArgsForProgramInput.Text(shortCut.runProgramArgs);
runProgramStartInDirInput.Text(shortCut.runProgramStartInDir);
runProgramElevationTypeCombo.SelectedIndex(shortCut.elevationLevel);
runProgramAlreadyRunningAction.SelectedIndex(shortCut.alreadyRunningAction);
runProgramStartWindow.SelectedIndex(shortCut.startWindowType);
return controlStackPanel;
}
void CreateNewTempShortcut(StackPanel& row, Shortcut& tempShortcut, const uint32_t& rowIndex)
{
tempShortcut.operationType = Shortcut::OperationType::RunProgram;
//tempShortcut.isRunProgram = true;
auto runProgramPathInput = row.FindName(L"runProgramPathInput_" + std::to_wstring(rowIndex)).as<TextBox>();
auto runProgramArgsForProgramInput = row.FindName(L"runProgramArgsForProgramInput_" + std::to_wstring(rowIndex)).as<TextBox>();
auto runProgramStartInDirInput = row.FindName(L"runProgramStartInDirInput_" + std::to_wstring(rowIndex)).as<TextBox>();
auto runProgramElevationTypeCombo = row.FindName(L"runProgramElevationTypeCombo_" + std::to_wstring(rowIndex)).as<ComboBox>();
auto runProgramAlreadyRunningAction = row.FindName(L"runProgramAlreadyRunningAction_" + std::to_wstring(rowIndex)).as<ComboBox>();
auto runProgramStartWindow = row.FindName(L"runProgramStartWindow_" + std::to_wstring(rowIndex)).as<ComboBox>();
tempShortcut.runProgramFilePath = ShortcutControl::RemoveExtraQuotes(runProgramPathInput.Text().c_str());
tempShortcut.runProgramArgs = (runProgramArgsForProgramInput.Text().c_str());
tempShortcut.runProgramStartInDir = (runProgramStartInDirInput.Text().c_str());
// Assign instead of setting the value in the buffer since the previous value may not be a Shortcut
tempShortcut.elevationLevel = static_cast<Shortcut::ElevationLevel>(runProgramElevationTypeCombo.SelectedIndex());
tempShortcut.alreadyRunningAction = static_cast<Shortcut::ProgramAlreadyRunningAction>(runProgramAlreadyRunningAction.SelectedIndex());
tempShortcut.startWindowType = static_cast<Shortcut::StartWindowType>(runProgramStartWindow.SelectedIndex());
}
std::wstring ShortcutControl::RemoveExtraQuotes(const std::wstring& str)
{
if (!str.empty() && str.front() == L'"' && str.back() == L'"')
{
return str.substr(1, str.size() - 2);
}
return str;
}
ShortcutControl::ShortcutType ShortcutControl::GetShortcutType(const winrt::Windows::UI::Xaml::Controls::ComboBox& typeCombo)
{
if (typeCombo.SelectedIndex() == 0)
{
return ShortcutControl::ShortcutType::Shortcut;
}
else if (typeCombo.SelectedIndex() == 1)
{
return ShortcutControl::ShortcutType::Text;
}
else if (typeCombo.SelectedIndex() == 2)
{
return ShortcutControl::ShortcutType::RunProgram;
}
else
{
return ShortcutControl::ShortcutType::OpenURI;
}
}
@@ -361,14 +929,53 @@ StackPanel ShortcutControl::GetShortcutControl()
// Function to create the detect shortcut UI window
void ShortcutControl::CreateDetectShortcutWindow(winrt::Windows::Foundation::IInspectable const& sender, XamlRoot xamlRoot, KBMEditor::KeyboardManagerState& keyboardManagerState, const int colIndex, StackPanel table, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, StackPanel row, TextBox targetApp, bool isHybridControl, bool isSingleKeyWindow, HWND parentWindow, RemapBuffer& remapBuffer)
{
// check to see if this orig or map-to shortcut;
bool isOrigShortcut = (colIndex == 0);
uint32_t rowIndex;
UIElementCollection children = table.Children();
bool indexFound = children.IndexOf(row, rowIndex);
Shortcut shortcut;
if (shortcutRemapBuffer.size() > 0)
{
if (colIndex == 0)
{
shortcut = std::get<Shortcut>(shortcutRemapBuffer[rowIndex].first[0]);
}
else
{
if (shortcutRemapBuffer[rowIndex].first[1].index() != 1)
{
// not a shortcut, let's fix that.
Shortcut newShortcut;
shortcutRemapBuffer[rowIndex].first[1] = newShortcut;
}
shortcut = std::get<Shortcut>(shortcutRemapBuffer[rowIndex].first[1]);
}
if (!shortcut.IsEmpty() && shortcut.HasChord())
{
keyboardManagerState.AllowChord = true;
} else {
keyboardManagerState.AllowChord = false;
}
}
//remapBuffer[rowIndex].first.
// ContentDialog for detecting shortcuts. This is the parent UI element.
ContentDialog detectShortcutBox;
ToggleSwitch allowChordSwitch;
// ContentDialog requires manually setting the XamlRoot (https://learn.microsoft.com/uwp/api/windows.ui.xaml.controls.contentdialog#contentdialog-in-appwindow-or-xaml-islands)
detectShortcutBox.XamlRoot(xamlRoot);
detectShortcutBox.Title(box_value(GET_RESOURCE_STRING(IDS_TYPESHORTCUT_TITLE)));
// 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]() {
@@ -424,6 +1031,13 @@ void ShortcutControl::CreateDetectShortcutWindow(winrt::Windows::Foundation::IIn
unregisterKeys();
};
auto onReleaseSpace = [&keyboardManagerState,
allowChordSwitch] {
keyboardManagerState.AllowChord = !keyboardManagerState.AllowChord;
allowChordSwitch.IsOn(keyboardManagerState.AllowChord);
};
auto onAccept = [onPressEnter,
onReleaseEnter] {
onPressEnter();
@@ -481,6 +1095,22 @@ void ShortcutControl::CreateDetectShortcutWindow(winrt::Windows::Foundation::IIn
unregisterKeys();
};
if (isOrigShortcut)
{
// Hold space to allow chords. Chords are only available for origin shortcuts.
keyboardManagerState.RegisterKeyDelay(
VK_SPACE,
selectDetectedShortcutAndResetKeys,
[onReleaseSpace, detectShortcutBox](DWORD) {
detectShortcutBox.Dispatcher().RunAsync(
Windows::UI::Core::CoreDispatcherPriority::Normal,
[onReleaseSpace] {
onReleaseSpace();
});
},
nullptr);
}
// Cancel button
detectShortcutBox.CloseButtonText(GET_RESOURCE_STRING(IDS_CANCEL_BUTTON));
detectShortcutBox.CloseButtonClick([onCancel](winrt::Windows::Foundation::IInspectable const& sender, ContentDialogButtonClickEventArgs const& args) {
@@ -525,6 +1155,42 @@ void ShortcutControl::CreateDetectShortcutWindow(winrt::Windows::Foundation::IIn
keyStackPanel2.Visibility(Visibility::Collapsed);
stackPanel.Children().Append(keyStackPanel2);
// Detect Chord
Windows::UI::Xaml::Controls::StackPanel chordStackPanel;
if (isOrigShortcut)
{
constexpr double verticalMargin = 20.f;
TextBlock allowChordText;
allowChordText.Text(GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_ALLOW_CHORDS));
allowChordText.FontSize(12);
allowChordText.Margin({ 0, 12 + verticalMargin, 0, 0 });
chordStackPanel.VerticalAlignment(VerticalAlignment::Center);
allowChordText.TextAlignment(TextAlignment::Center);
chordStackPanel.Orientation(Orientation::Horizontal);
allowChordSwitch.OnContent(nullptr);
allowChordSwitch.OffContent(nullptr);
allowChordSwitch.Margin({ 12, verticalMargin, 0, 0 });
chordStackPanel.Children().Append(allowChordText);
chordStackPanel.Children().Append(allowChordSwitch);
stackPanel.Children().Append(chordStackPanel);
allowChordSwitch.IsOn(keyboardManagerState.AllowChord);
auto toggleHandler = [allowChordSwitch, &keyboardManagerState](auto const& sender, auto const& e) {
keyboardManagerState.AllowChord = allowChordSwitch.IsOn();
if (!allowChordSwitch.IsOn())
{
keyboardManagerState.ClearStoredShortcut();
}
};
allowChordSwitch.Toggled(toggleHandler);
}
TextBlock holdEscInfo;
holdEscInfo.Text(GET_RESOURCE_STRING(IDS_TYPE_HOLDESC));
holdEscInfo.FontSize(12);
@@ -537,6 +1203,16 @@ void ShortcutControl::CreateDetectShortcutWindow(winrt::Windows::Foundation::IIn
holdEnterInfo.Margin({ 0, 0, 0, 0 });
stackPanel.Children().Append(holdEnterInfo);
if (isOrigShortcut)
{
// Hold space to allow chords. Chords are only available for origin shortcuts.
TextBlock holdSpaceInfo;
holdSpaceInfo.Text(GET_RESOURCE_STRING(IDS_TYPE_HOLDSPACE));
holdSpaceInfo.FontSize(12);
holdSpaceInfo.Margin({ 0, 0, 0, 0 });
stackPanel.Children().Append(holdSpaceInfo);
}
try
{
// If a layout update has been triggered by other methods (e.g.: adapting to zoom level), this may throw an exception.
@@ -551,4 +1227,9 @@ void ShortcutControl::CreateDetectShortcutWindow(winrt::Windows::Foundation::IIn
// Show the dialog
detectShortcutBox.ShowAsync();
if (!shortcut.IsEmpty() && keyboardManagerState.AllowChord)
{
keyboardManagerState.SetDetectedShortcut(shortcut);
}
}

View File

@@ -26,13 +26,16 @@ private:
winrt::Windows::Foundation::IInspectable shortcutDropDownVariableSizedWrapGrid;
// Button to type the shortcut
winrt::Windows::Foundation::IInspectable typeShortcut;
Button btnPickShortcut;
// StackPanel to hold the shortcut
StackPanel spBtnPickShortcut;
// 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;
winrt::Windows::Foundation::IInspectable keyComboStackPanel;
// Function to set the accessible name of the target app text box
static void SetAccessibleNameForTextBox(TextBox targetAppTextBox, int rowIndex);
@@ -40,6 +43,15 @@ private:
// Function to set the accessible names for all the controls in a row
static void UpdateAccessibleNames(StackPanel sourceColumn, StackPanel mappedToColumn, TextBox targetAppTextBox, Button deleteButton, int rowIndex);
// enum for the type of shortcut, to make it easier to switch on and read
enum class ShortcutType
{
Shortcut,
Text,
RunProgram,
OpenURI
};
public:
// Handle to the current Edit Shortcuts Window
static HWND editShortcutsWindowHandle;
@@ -56,8 +68,20 @@ public:
// constructor
ShortcutControl(StackPanel table, StackPanel row, const int colIndex, TextBox targetApp);
// Function to that will CreateDetectShortcutWindow, created here to it can be done automatically when "new shortcut" is clicked.
void OpenNewShortcutControlRow(StackPanel table, StackPanel row);
// 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 KeyShortcutTextUnion& newKeys = Shortcut(), const std::wstring& targetAppName = L"");
static ShortcutControl& 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 delete the shortcut control
static void ShortcutControl::DeleteShortcutControl(StackPanel& parent, std::vector<std::vector<std::unique_ptr<ShortcutControl>>>& keyboardRemapControlObjects, int index);
// Function to get the shortcut type
static ShortcutType GetShortcutType(const Controls::ComboBox& typeCombo);
// Function to remove extra quotes from the start and end of the string (used where we will add them as needed later)
static std::wstring ShortcutControl::RemoveExtraQuotes(const std::wstring& str);
// 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();
@@ -65,3 +89,9 @@ public:
// Function to create the detect shortcut UI window
static void CreateDetectShortcutWindow(winrt::Windows::Foundation::IInspectable const& sender, XamlRoot xamlRoot, KBMEditor::KeyboardManagerState& keyboardManagerState, const int colIndex, StackPanel table, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, StackPanel controlLayout, TextBox targetApp, bool isHybridControl, bool isSingleKeyWindow, HWND parentWindow, RemapBuffer& remapBuffer);
};
StackPanel SetupRunProgramControls(StackPanel& parent, StackPanel& row, Shortcut& shortCut, winrt::Windows::UI::Xaml::Thickness& textInputMargin, ::StackPanel& _controlStackPanel);
void CreateNewTempShortcut(StackPanel& row, Shortcut& tempShortcut, const uint32_t& rowIndex);
StackPanel SetupOpenURIControls(StackPanel& parent, StackPanel& row, Shortcut& shortCut, winrt::Windows::UI::Xaml::Thickness& textInputMargin, ::StackPanel& _controlStackPanel);

View File

@@ -3,6 +3,9 @@
#include <common/monitor_utils.h>
using namespace Windows::UI::Xaml::Media;
using namespace Windows::UI::Xaml::Automation::Peers;
namespace UIHelpers
{
// This method sets focus to the first Type button on the last row of the Grid
@@ -19,9 +22,11 @@ namespace UIHelpers
// Get Type Button from the first line
Button typeButton = firstLineIntoColumn.Children().GetAt(1).as<Button>();
// Set programmatic focus on the button
typeButton.Focus(FocusState::Programmatic);
if (typeButton != nullptr)
{
// Set programmatic focus on the button
typeButton.Focus(FocusState::Programmatic);
}
}
RECT GetForegroundWindowDesktopRect()
@@ -53,6 +58,34 @@ namespace UIHelpers
return parentElement.Children().GetAt(index + 1);
}
winrt::Windows::Foundation::IInspectable GetLabelWrapped(const winrt::Windows::Foundation::IInspectable& element, std::wstring label, double textWidth, HorizontalAlignment horizontalAlignment)
{
StackPanel sp = StackPanel();
try
{
sp.Name(L"Wrapped_" + element.as<FrameworkElement>().Name());
}
catch (...)
{
}
sp.Orientation(Orientation::Horizontal);
sp.HorizontalAlignment(horizontalAlignment);
TextBlock text;
text.FontWeight(Text::FontWeights::Bold());
text.Text(label);
if (textWidth >= 0)
{
text.Width(textWidth);
}
sp.Children().Append(text);
sp.Children().Append(element.as<FrameworkElement>());
return sp;
}
winrt::Windows::Foundation::IInspectable GetWrapped(const winrt::Windows::Foundation::IInspectable& element, double width)
{
StackPanel sp = StackPanel();

View File

@@ -27,6 +27,9 @@ namespace UIHelpers
winrt::Windows::Foundation::IInspectable GetWrapped(const winrt::Windows::Foundation::IInspectable& element, double width);
// Function to return a StackPanel with an element and a TextBlock label.
winrt::Windows::Foundation::IInspectable GetLabelWrapped(const winrt::Windows::Foundation::IInspectable& element, std::wstring label, double width, HorizontalAlignment horizontalAlignment = HorizontalAlignment::Left);
// 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);