From 8c044213873b87114de33d6f231f25eaa301bce7 Mon Sep 17 00:00:00 2001 From: Tomas Agustin Raies Date: Mon, 11 May 2020 12:45:55 -0700 Subject: [PATCH] [Keyboard Manager] Confirmation Dialog for orphaned keys and partial remappings. (#2811) * WIP Confirmation dialog for orphaned keys * Confirmation Dialog for orphaned keys * White OK button, Anyways capitalizef * Change Apply to Ok for shortcuts * Validate that mappings can be made before changing keyboardManagerState * Set fixed MinWidth for OK button * Fix typo * Partial remappings confirmation dialog Both for Shortcuts and SingleKey * Remove warning icon callback in OnClickAccept * Add text wrapping for OrphanKeys dialog --- .../common/KeyboardManagerConstants.h | 3 + src/modules/keyboardmanager/ui/Dialog.cpp | 17 +++ src/modules/keyboardmanager/ui/Dialog.h | 41 ++++++ .../keyboardmanager/ui/EditKeyboardWindow.cpp | 118 ++++++++++++++++-- .../ui/EditShortcutsWindow.cpp | 55 ++++++-- .../ui/KeyboardManagerUI.vcxproj | 4 + .../ui/KeyboardManagerUI.vcxproj.filters | 4 + src/modules/keyboardmanager/ui/Styles.cpp | 17 +++ src/modules/keyboardmanager/ui/Styles.h | 6 + 9 files changed, 239 insertions(+), 26 deletions(-) create mode 100644 src/modules/keyboardmanager/ui/Dialog.cpp create mode 100644 src/modules/keyboardmanager/ui/Dialog.h create mode 100644 src/modules/keyboardmanager/ui/Styles.cpp create mode 100644 src/modules/keyboardmanager/ui/Styles.h diff --git a/src/modules/keyboardmanager/common/KeyboardManagerConstants.h b/src/modules/keyboardmanager/common/KeyboardManagerConstants.h index e7888d821c..5b623ab0b3 100644 --- a/src/modules/keyboardmanager/common/KeyboardManagerConstants.h +++ b/src/modules/keyboardmanager/common/KeyboardManagerConstants.h @@ -73,4 +73,7 @@ namespace KeyboardManagerConstants inline const long TableArrowColWidth = 20; inline const long TableRemoveColWidth = 20; inline const long TableWarningColWidth = 20; + + // Shared style constants for both Remap Table and Shortcut Table + inline const double HeaderButtonWidth = 100; } \ No newline at end of file diff --git a/src/modules/keyboardmanager/ui/Dialog.cpp b/src/modules/keyboardmanager/ui/Dialog.cpp new file mode 100644 index 0000000000..ceb9412b10 --- /dev/null +++ b/src/modules/keyboardmanager/ui/Dialog.cpp @@ -0,0 +1,17 @@ +#include "pch.h" +#include "Dialog.h" + +IAsyncOperation Dialog::PartialRemappingConfirmationDialog(XamlRoot root) +{ + ContentDialog confirmationDialog; + confirmationDialog.XamlRoot(root); + confirmationDialog.Title(box_value(L"Some of the keys could not be remapped. Do you want to continue anyway?")); + confirmationDialog.IsPrimaryButtonEnabled(true); + confirmationDialog.DefaultButton(ContentDialogButton::Primary); + confirmationDialog.PrimaryButtonText(winrt::hstring(L"Continue Anyway")); + confirmationDialog.IsSecondaryButtonEnabled(true); + confirmationDialog.SecondaryButtonText(winrt::hstring(L"Cancel")); + + ContentDialogResult res = co_await confirmationDialog.ShowAsync(); + co_return res == ContentDialogResult::Primary; +} diff --git a/src/modules/keyboardmanager/ui/Dialog.h b/src/modules/keyboardmanager/ui/Dialog.h new file mode 100644 index 0000000000..ec4a6a560a --- /dev/null +++ b/src/modules/keyboardmanager/ui/Dialog.h @@ -0,0 +1,41 @@ +#pragma once +#include +#include +#include +#include +#include + +using namespace winrt::Windows::Foundation; + +namespace Dialog +{ + template + KeyboardManagerHelper::ErrorType CheckIfRemappingsAreValid( + const std::vector>& remappings, + std::function isValid) + { + KeyboardManagerHelper::ErrorType isSuccess = KeyboardManagerHelper::ErrorType::NoError; + std::set ogKeys; + for (int i = 0; i < remappings.size(); i++) + { + T ogKey = remappings[i][0]; + T newKey = remappings[i][1]; + + if (isValid(ogKey) && isValid(newKey) && ogKeys.find(ogKey) == ogKeys.end()) + { + ogKeys.insert(ogKey); + } + else if (isValid(ogKey) && isValid(newKey) && ogKeys.find(ogKey) != ogKeys.end()) + { + isSuccess = KeyboardManagerHelper::ErrorType::RemapUnsuccessful; + } + else + { + isSuccess = KeyboardManagerHelper::ErrorType::RemapUnsuccessful; + } + } + return isSuccess; + } + + IAsyncOperation PartialRemappingConfirmationDialog(winrt::Windows::UI::Xaml::XamlRoot root); +}; diff --git a/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp b/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp index b2d251d05f..8f6b5d7148 100644 --- a/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp +++ b/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp @@ -4,6 +4,13 @@ #include "KeyDropDownControl.h" #include "XamlBridge.h" #include +#include +#include +#include +#include "Styles.h" +#include "Dialog.h" + +using namespace winrt::Windows::Foundation; LRESULT CALLBACK EditKeyboardWindowProc(HWND, UINT, WPARAM, LPARAM); @@ -17,6 +24,88 @@ std::mutex editKeyboardWindowMutex; // Stores a pointer to the Xaml Bridge object so that it can be accessed from the window procedure static XamlBridge* xamlBridgePtr = nullptr; +static std::vector GetOrphanedKeys() +{ + std::set ogKeys; + std::set newKeys; + + for (int i = 0; i < SingleKeyRemapControl::singleKeyRemapBuffer.size(); i++) + { + DWORD ogKey = SingleKeyRemapControl::singleKeyRemapBuffer[i][0]; + DWORD newKey = SingleKeyRemapControl::singleKeyRemapBuffer[i][1]; + if (ogKey != 0 && newKey != 0) + { + ogKeys.insert(ogKey); + newKeys.insert(newKey); + } + } + + for (auto& k : newKeys) + { + ogKeys.erase(k); + } + + return std::vector(ogKeys.begin(), ogKeys.end()); +} + +static IAsyncOperation OrphanKeysConfirmationDialog( + KeyboardManagerState& state, + const std::vector& keys, + XamlRoot root) +{ + ContentDialog confirmationDialog; + confirmationDialog.XamlRoot(root); + confirmationDialog.Title(box_value(L"The following keys are unassigned and you won't be able to use them:")); + confirmationDialog.Content(nullptr); + confirmationDialog.IsPrimaryButtonEnabled(true); + confirmationDialog.DefaultButton(ContentDialogButton::Primary); + confirmationDialog.PrimaryButtonText(winrt::hstring(L"Continue Anyway")); + confirmationDialog.IsSecondaryButtonEnabled(true); + confirmationDialog.SecondaryButtonText(winrt::hstring(L"Cancel")); + + TextBlock orphanKeysBlock; + std::wstring orphanKeyString; + for (auto k : keys) + { + orphanKeyString.append(state.keyboardMap.GetKeyName(k)); + orphanKeyString.append(L", "); + } + orphanKeyString = orphanKeyString.substr(0, max(0, orphanKeyString.length() - 2)); + orphanKeysBlock.Text(winrt::hstring(orphanKeyString)); + orphanKeysBlock.TextWrapping(TextWrapping::Wrap); + confirmationDialog.Content(orphanKeysBlock); + + ContentDialogResult res = co_await confirmationDialog.ShowAsync(); + + co_return res == ContentDialogResult::Primary; +} + +static IAsyncAction OnClickAccept(KeyboardManagerState& keyboardManagerState, XamlRoot root, std::function ApplyRemappings) +{ + KeyboardManagerHelper::ErrorType isSuccess = Dialog::CheckIfRemappingsAreValid( + SingleKeyRemapControl::singleKeyRemapBuffer, + [](DWORD key) { + return key != 0; + }); + if (isSuccess != KeyboardManagerHelper::ErrorType::NoError) + { + if (!co_await Dialog::PartialRemappingConfirmationDialog(root)) + { + co_return; + } + } + // Check for orphaned keys + // Draw content Dialog + std::vector orphanedKeys = GetOrphanedKeys(); + if (orphanedKeys.size() > 0) + { + if (!co_await OrphanKeysConfirmationDialog(keyboardManagerState, orphanedKeys, root)) + { + co_return; + } + } + ApplyRemappings(); +} // Function to create the Edit Keyboard Window void createEditKeyboardWindow(HINSTANCE hInst, KeyboardManagerState& keyboardManagerState) { @@ -100,7 +189,7 @@ void createEditKeyboardWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMan // Header Cancel button Button cancelButton; cancelButton.Content(winrt::box_value(L"Cancel")); - cancelButton.Margin({ 0, 0, 10, 0 }); + cancelButton.Margin({ 10, 0, 0, 0 }); cancelButton.Click([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { // Close the window since settings do not need to be saved PostMessage(_hWndEditKeyboardWindow, WM_CLOSE, 0, 0); @@ -159,11 +248,6 @@ void createEditKeyboardWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMan keyRemapTable.Children().Append(originalKeyRemapHeader); keyRemapTable.Children().Append(newKeyRemapHeader); - // Message to display success/failure of saving settings. - Flyout applyFlyout; - TextBlock settingsMessage; - applyFlyout.Content(settingsMessage); - // Store handle of edit keyboard window SingleKeyRemapControl::EditKeyboardWindowHandle = _hWndEditKeyboardWindow; // Store keyboard manager state @@ -231,11 +315,14 @@ void createEditKeyboardWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMan // Main Header Apply button Button applyButton; - applyButton.Content(winrt::box_value(L"Apply")); - header.SetAlignRightWithPanel(applyButton, true); - header.SetLeftOf(cancelButton, applyButton); - applyButton.Flyout(applyFlyout); - applyButton.Click([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + applyButton.Content(winrt::box_value(L"OK")); + applyButton.Style(AccentButtonStyle()); + applyButton.MinWidth(KeyboardManagerConstants::HeaderButtonWidth); + cancelButton.MinWidth(KeyboardManagerConstants::HeaderButtonWidth); + header.SetAlignRightWithPanel(cancelButton, true); + header.SetLeftOf(applyButton, cancelButton); + + auto ApplyRemappings = [&keyboardManagerState, _hWndEditKeyboardWindow]() { KeyboardManagerHelper::ErrorType isSuccess = KeyboardManagerHelper::ErrorType::NoError; // Clear existing Key Remaps keyboardManagerState.ClearSingleKeyRemaps(); @@ -292,14 +379,19 @@ void createEditKeyboardWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMan } } + Trace::KeyRemapCount(successfulRemapCount); // Save the updated shortcuts remaps to file. bool saveResult = keyboardManagerState.SaveConfigToFile(); if (!saveResult) { isSuccess = KeyboardManagerHelper::ErrorType::SaveFailed; } - Trace::KeyRemapCount(successfulRemapCount); - settingsMessage.Text(KeyboardManagerHelper::GetErrorMessage(isSuccess)); + + PostMessage(_hWndEditKeyboardWindow, WM_CLOSE, 0, 0); + }; + + applyButton.Click([&keyboardManagerState, ApplyRemappings, applyButton](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + OnClickAccept(keyboardManagerState, applyButton.XamlRoot(), ApplyRemappings); }); header.Children().Append(headerText); diff --git a/src/modules/keyboardmanager/ui/EditShortcutsWindow.cpp b/src/modules/keyboardmanager/ui/EditShortcutsWindow.cpp index 1f20f12580..921c588a7e 100644 --- a/src/modules/keyboardmanager/ui/EditShortcutsWindow.cpp +++ b/src/modules/keyboardmanager/ui/EditShortcutsWindow.cpp @@ -4,6 +4,12 @@ #include "KeyDropDownControl.h" #include "XamlBridge.h" #include +#include +#include +#include "Styles.h" +#include "Dialog.h" + +using namespace winrt::Windows::Foundation; LRESULT CALLBACK EditShortcutsWindowProc(HWND, UINT, WPARAM, LPARAM); @@ -17,6 +23,26 @@ std::mutex editShortcutsWindowMutex; // Stores a pointer to the Xaml Bridge object so that it can be accessed from the window procedure static XamlBridge* xamlBridgePtr = nullptr; +static IAsyncAction OnClickAccept( + KeyboardManagerState& keyboardManagerState, + XamlRoot root, + std::function ApplyRemappings) +{ + KeyboardManagerHelper::ErrorType isSuccess = Dialog::CheckIfRemappingsAreValid( + ShortcutControl::shortcutRemapBuffer, + [](Shortcut shortcut) { + return shortcut.IsValidShortcut(); + }); + if (isSuccess != KeyboardManagerHelper::ErrorType::NoError) + { + if (!co_await Dialog::PartialRemappingConfirmationDialog(root)) + { + co_return; + } + } + ApplyRemappings(); +} + // Function to create the Edit Shortcuts Window void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardManagerState) { @@ -101,7 +127,7 @@ void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMa // Cancel button Button cancelButton; cancelButton.Content(winrt::box_value(L"Cancel")); - cancelButton.Margin({ 0, 0, 10, 0 }); + cancelButton.Margin({ 10, 0, 0, 0 }); cancelButton.Click([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { // Close the window since settings do not need to be saved PostMessage(_hWndEditShortcutsWindow, WM_CLOSE, 0, 0); @@ -161,11 +187,6 @@ void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMa shortcutTable.Children().Append(originalShortcutHeader); shortcutTable.Children().Append(newShortcutHeader); - // Message to display success/failure of saving settings. - Flyout applyFlyout; - TextBlock settingsMessage; - applyFlyout.Content(settingsMessage); - // Store handle of edit shortcuts window ShortcutControl::EditShortcutsWindowHandle = _hWndEditShortcutsWindow; // Store keyboard manager state @@ -189,11 +210,14 @@ void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMa // Apply button Button applyButton; - applyButton.Content(winrt::box_value(L"Apply")); - header.SetAlignRightWithPanel(applyButton, true); - header.SetLeftOf(cancelButton, applyButton); - applyButton.Flyout(applyFlyout); - applyButton.Click([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + applyButton.Content(winrt::box_value(L"OK")); + applyButton.Style(AccentButtonStyle()); + applyButton.MinWidth(KeyboardManagerConstants::HeaderButtonWidth); + cancelButton.MinWidth(KeyboardManagerConstants::HeaderButtonWidth); + header.SetAlignRightWithPanel(cancelButton, true); + header.SetLeftOf(applyButton, cancelButton); + + auto ApplyRemappings = [&keyboardManagerState, _hWndEditShortcutsWindow]() { KeyboardManagerHelper::ErrorType isSuccess = KeyboardManagerHelper::ErrorType::NoError; // Clear existing shortcuts keyboardManagerState.ClearOSLevelShortcuts(); @@ -223,14 +247,19 @@ void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMa } } + Trace::OSLevelShortcutRemapCount(successfulRemapCount); // Save the updated key remaps to file. bool saveResult = keyboardManagerState.SaveConfigToFile(); if (!saveResult) { isSuccess = KeyboardManagerHelper::ErrorType::SaveFailed; } - Trace::OSLevelShortcutRemapCount(successfulRemapCount); - settingsMessage.Text(KeyboardManagerHelper::GetErrorMessage(isSuccess)); + + PostMessage(_hWndEditShortcutsWindow, WM_CLOSE, 0, 0); + }; + + applyButton.Click([&keyboardManagerState, applyButton, ApplyRemappings](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + OnClickAccept(keyboardManagerState, applyButton.XamlRoot(), ApplyRemappings); }); header.Children().Append(headerText); diff --git a/src/modules/keyboardmanager/ui/KeyboardManagerUI.vcxproj b/src/modules/keyboardmanager/ui/KeyboardManagerUI.vcxproj index 91debc97e8..83cbacfb26 100644 --- a/src/modules/keyboardmanager/ui/KeyboardManagerUI.vcxproj +++ b/src/modules/keyboardmanager/ui/KeyboardManagerUI.vcxproj @@ -98,6 +98,7 @@ + @@ -107,11 +108,14 @@ + + + diff --git a/src/modules/keyboardmanager/ui/KeyboardManagerUI.vcxproj.filters b/src/modules/keyboardmanager/ui/KeyboardManagerUI.vcxproj.filters index b42dad8681..ffb4e69407 100644 --- a/src/modules/keyboardmanager/ui/KeyboardManagerUI.vcxproj.filters +++ b/src/modules/keyboardmanager/ui/KeyboardManagerUI.vcxproj.filters @@ -8,6 +8,8 @@ + + @@ -17,6 +19,8 @@ + + diff --git a/src/modules/keyboardmanager/ui/Styles.cpp b/src/modules/keyboardmanager/ui/Styles.cpp new file mode 100644 index 0000000000..9219eada8a --- /dev/null +++ b/src/modules/keyboardmanager/ui/Styles.cpp @@ -0,0 +1,17 @@ +#include "pch.h" +#include "Styles.h" +#include +#include +#include + +Style AccentButtonStyle() +{ + Style style{ winrt::xaml_typename() }; + style.Setters().Append(Setter{ + Controls::Control::BackgroundProperty(), + winrt::Windows::UI::Xaml::Media::SolidColorBrush{ WindowsColors::get_accent_color() } }); + style.Setters().Append(Setter{ + Controls::Control::ForegroundProperty(), + winrt::Windows::UI::Xaml::Media::SolidColorBrush{ winrt::Windows::UI::Colors::White() } }); + return style; +} \ No newline at end of file diff --git a/src/modules/keyboardmanager/ui/Styles.h b/src/modules/keyboardmanager/ui/Styles.h new file mode 100644 index 0000000000..db1d5c1ec5 --- /dev/null +++ b/src/modules/keyboardmanager/ui/Styles.h @@ -0,0 +1,6 @@ +#pragma once +#include + +using namespace winrt::Windows::UI::Xaml; + +Style AccentButtonStyle();