2020-03-23 10:44:02 -07:00
# include "pch.h"
# include "ShortcutControl.h"
2020-04-18 16:12:26 -07:00
# include "KeyDropDownControl.h"
2020-07-13 11:49:09 -07:00
# include "keyboardmanager/common/KeyboardManagerState.h"
2020-07-23 16:43:49 -07:00
# include "keyboardmanager/common/Helpers.h"
2020-08-17 13:46:50 -07:00
# include "common/common.h"
2020-08-24 15:10:50 -07:00
# include "keyboardmanager/dll/Generated Files/resource.h"
2020-10-02 15:36:36 +03:00
# include <common\shared_constants.h>
2020-08-17 13:46:50 -07:00
extern " C " IMAGE_DOS_HEADER __ImageBase ;
2020-03-23 10:44:02 -07:00
//Both static members are initialized to null
HWND ShortcutControl : : EditShortcutsWindowHandle = nullptr ;
KeyboardManagerState * ShortcutControl : : keyboardManagerState = nullptr ;
2020-04-09 09:20:19 -07:00
// Initialized as new vector
2020-08-13 16:32:15 -07:00
RemapBuffer ShortcutControl : : shortcutRemapBuffer ;
2020-03-23 10:44:02 -07:00
2020-07-13 11:49:09 -07:00
ShortcutControl : : ShortcutControl ( Grid table , const int colIndex , TextBox targetApp )
{
shortcutDropDownStackPanel = StackPanel ( ) ;
typeShortcut = Button ( ) ;
shortcutControlLayout = StackPanel ( ) ;
2020-07-23 16:43:49 -07:00
bool isHybridControl = colIndex = = 1 ? true : false ;
2020-07-13 11:49:09 -07:00
shortcutDropDownStackPanel . as < StackPanel > ( ) . Spacing ( KeyboardManagerConstants : : ShortcutTableDropDownSpacing ) ;
shortcutDropDownStackPanel . as < StackPanel > ( ) . Orientation ( Windows : : UI : : Xaml : : Controls : : Orientation : : Horizontal ) ;
2020-08-17 13:46:50 -07:00
typeShortcut . as < Button > ( ) . Content ( winrt : : box_value ( GET_RESOURCE_STRING ( IDS_TYPE_BUTTON ) ) ) ;
2020-07-13 11:49:09 -07:00
typeShortcut . as < Button > ( ) . Width ( KeyboardManagerConstants : : ShortcutTableDropDownWidth ) ;
2020-07-23 16:43:49 -07:00
typeShortcut . as < Button > ( ) . Click ( [ & , table , colIndex , isHybridControl , targetApp ] ( winrt : : Windows : : Foundation : : IInspectable const & sender , RoutedEventArgs const & ) {
2020-07-13 11:49:09 -07:00
keyboardManagerState - > SetUIState ( KeyboardManagerUIState : : DetectShortcutWindowActivated , EditShortcutsWindowHandle ) ;
// Using the XamlRoot of the typeShortcut to get the root of the XAML host
2020-07-23 16:43:49 -07:00
createDetectShortcutWindow ( sender , sender . as < Button > ( ) . XamlRoot ( ) , * keyboardManagerState , colIndex , table , keyDropDownControlObjects , shortcutControlLayout . as < StackPanel > ( ) , targetApp , isHybridControl , false , EditShortcutsWindowHandle , shortcutRemapBuffer ) ;
2020-07-13 11:49:09 -07:00
} ) ;
2020-09-08 12:24:59 -07:00
// Set an accessible name for the type shortcut button
typeShortcut . as < Button > ( ) . SetValue ( Automation : : AutomationProperties : : NameProperty ( ) , box_value ( GET_RESOURCE_STRING ( IDS_TYPE_BUTTON ) ) ) ;
2020-07-13 11:49:09 -07:00
shortcutControlLayout . as < StackPanel > ( ) . Margin ( { 0 , 0 , 0 , 10 } ) ;
shortcutControlLayout . as < StackPanel > ( ) . Spacing ( KeyboardManagerConstants : : ShortcutTableDropDownSpacing ) ;
shortcutControlLayout . as < StackPanel > ( ) . Children ( ) . Append ( typeShortcut . as < Button > ( ) ) ;
shortcutControlLayout . as < StackPanel > ( ) . Children ( ) . Append ( shortcutDropDownStackPanel . as < StackPanel > ( ) ) ;
2020-07-23 16:43:49 -07:00
KeyDropDownControl : : AddDropDown ( table , shortcutControlLayout . as < StackPanel > ( ) , shortcutDropDownStackPanel . as < StackPanel > ( ) , colIndex , shortcutRemapBuffer , keyDropDownControlObjects , targetApp , isHybridControl , false ) ;
2020-07-13 11:49:09 -07:00
shortcutControlLayout . as < StackPanel > ( ) . UpdateLayout ( ) ;
}
2020-09-08 12:24:59 -07:00
// Function to set the accessible name of the target App text box
2020-09-18 17:12:37 -07:00
void ShortcutControl : : SetAccessibleNameForTextBox ( TextBox targetAppTextBox , int rowIndex )
2020-09-08 12:24:59 -07:00
{
// To set the accessible name of the target App text box by adding the string `All Apps` if the text box is empty, if not the application name is read by narrator.
2020-09-18 17:12:37 -07:00
std : : wstring targetAppTextBoxAccessibleName = GET_RESOURCE_STRING ( IDS_AUTOMATIONPROPERTIES_ROW ) + std : : to_wstring ( rowIndex ) + L " , " + GET_RESOURCE_STRING ( IDS_EDITSHORTCUTS_TARGETAPPHEADER ) ;
2020-09-08 12:24:59 -07:00
if ( targetAppTextBox . Text ( ) = = L " " )
{
targetAppTextBoxAccessibleName + = GET_RESOURCE_STRING ( IDS_EDITSHORTCUTS_ALLAPPS ) ;
}
targetAppTextBox . SetValue ( Automation : : AutomationProperties : : NameProperty ( ) , box_value ( targetAppTextBoxAccessibleName ) ) ;
}
2020-09-18 17:12:37 -07:00
// Function to set the accessible names for all the controls in a row
void ShortcutControl : : UpdateAccessibleNames ( StackPanel sourceColumn , StackPanel mappedToColumn , TextBox targetAppTextBox , Button deleteButton , int rowIndex )
{
sourceColumn . SetValue ( Automation : : AutomationProperties : : NameProperty ( ) , box_value ( GET_RESOURCE_STRING ( IDS_AUTOMATIONPROPERTIES_ROW ) + std : : to_wstring ( rowIndex ) + L " , " + GET_RESOURCE_STRING ( IDS_EDITSHORTCUTS_SOURCEHEADER ) ) ) ;
mappedToColumn . SetValue ( Automation : : AutomationProperties : : NameProperty ( ) , box_value ( GET_RESOURCE_STRING ( IDS_AUTOMATIONPROPERTIES_ROW ) + std : : to_wstring ( rowIndex ) + L " , " + GET_RESOURCE_STRING ( IDS_EDITSHORTCUTS_TARGETHEADER ) ) ) ;
ShortcutControl : : SetAccessibleNameForTextBox ( targetAppTextBox , rowIndex ) ;
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 ) ) ) ;
}
2020-03-23 10:44:02 -07:00
// 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.
2020-07-23 16:43:49 -07:00
void ShortcutControl : : AddNewShortcutControlRow ( Grid & parent , std : : vector < std : : vector < std : : unique_ptr < ShortcutControl > > > & keyboardRemapControlObjects , const Shortcut & originalKeys , const std : : variant < DWORD , Shortcut > & newKeys , const std : : wstring & targetAppName )
2020-03-23 10:44:02 -07:00
{
2020-07-08 16:24:30 -07:00
// Textbox for target application
TextBox targetAppTextBox ;
2020-04-18 16:12:26 -07:00
// Create new ShortcutControl objects dynamically so that we does not get destructed
std : : vector < std : : unique_ptr < ShortcutControl > > newrow ;
2020-07-08 16:24:30 -07:00
newrow . push_back ( std : : move ( std : : unique_ptr < ShortcutControl > ( new ShortcutControl ( parent , 0 , targetAppTextBox ) ) ) ) ;
newrow . push_back ( std : : move ( std : : unique_ptr < ShortcutControl > ( new ShortcutControl ( parent , 1 , targetAppTextBox ) ) ) ) ;
2020-04-18 16:12:26 -07:00
keyboardRemapControlObjects . push_back ( std : : move ( newrow ) ) ;
2020-04-23 09:14:16 -07:00
// Add to grid
parent . RowDefinitions ( ) . Append ( RowDefinition ( ) ) ;
2020-05-08 17:34:24 -07:00
parent . SetColumn ( keyboardRemapControlObjects [ keyboardRemapControlObjects . size ( ) - 1 ] [ 0 ] - > getShortcutControl ( ) , KeyboardManagerConstants : : ShortcutTableOriginalColIndex ) ;
2020-04-23 09:14:16 -07:00
parent . SetRow ( keyboardRemapControlObjects [ keyboardRemapControlObjects . size ( ) - 1 ] [ 0 ] - > getShortcutControl ( ) , parent . RowDefinitions ( ) . Size ( ) - 1 ) ;
2020-05-08 17:34:24 -07:00
parent . SetColumn ( keyboardRemapControlObjects [ keyboardRemapControlObjects . size ( ) - 1 ] [ 1 ] - > getShortcutControl ( ) , KeyboardManagerConstants : : ShortcutTableNewColIndex ) ;
2020-04-23 09:14:16 -07:00
parent . SetRow ( keyboardRemapControlObjects [ keyboardRemapControlObjects . size ( ) - 1 ] [ 1 ] - > getShortcutControl ( ) , parent . RowDefinitions ( ) . Size ( ) - 1 ) ;
2020-03-23 10:44:02 -07:00
// ShortcutControl for the original shortcut
2020-04-23 09:14:16 -07:00
parent . Children ( ) . Append ( keyboardRemapControlObjects [ keyboardRemapControlObjects . size ( ) - 1 ] [ 0 ] - > getShortcutControl ( ) ) ;
2020-05-08 17:34:24 -07:00
// Arrow icon
FontIcon arrowIcon ;
arrowIcon . FontFamily ( Xaml : : Media : : FontFamily ( L " Segoe MDL2 Assets " ) ) ;
arrowIcon . Glyph ( L " \xE72A " ) ;
arrowIcon . VerticalAlignment ( VerticalAlignment : : Center ) ;
arrowIcon . HorizontalAlignment ( HorizontalAlignment : : Center ) ;
parent . SetColumn ( arrowIcon , KeyboardManagerConstants : : ShortcutTableArrowColIndex ) ;
parent . SetRow ( arrowIcon , parent . RowDefinitions ( ) . Size ( ) - 1 ) ;
parent . Children ( ) . Append ( arrowIcon ) ;
2020-03-23 10:44:02 -07:00
// ShortcutControl for the new shortcut
2020-04-23 09:14:16 -07:00
parent . Children ( ) . Append ( keyboardRemapControlObjects [ keyboardRemapControlObjects . size ( ) - 1 ] [ 1 ] - > getShortcutControl ( ) ) ;
2020-03-23 10:44:02 -07:00
2020-07-08 16:24:30 -07:00
targetAppTextBox . Width ( KeyboardManagerConstants : : ShortcutTableDropDownWidth ) ;
targetAppTextBox . Margin ( { 0 , 0 , 0 , KeyboardManagerConstants : : ShortcutTableDropDownSpacing } ) ;
targetAppTextBox . VerticalAlignment ( VerticalAlignment : : Bottom ) ;
targetAppTextBox . HorizontalAlignment ( HorizontalAlignment : : Center ) ;
targetAppTextBox . PlaceholderText ( KeyboardManagerConstants : : DefaultAppName ) ;
targetAppTextBox . Text ( targetAppName ) ;
// LostFocus handler will be called whenever text is updated by a user and then they click something else or tab to another control. Does not get called if Text is updated while the TextBox isn't in focus (i.e. from code)
targetAppTextBox . LostFocus ( [ & keyboardRemapControlObjects , parent , targetAppTextBox ] ( auto const & sender , auto const & e ) {
// Get index of targetAppTextBox button
UIElementCollection children = parent . Children ( ) ;
uint32_t index ;
2020-09-10 11:31:49 -07:00
bool indexFound = children . IndexOf ( targetAppTextBox , index ) ;
// IndexOf could fail if the the row got deleted after LostFocus handler was invoked. In this case it should return
if ( ! indexFound )
{
return ;
}
2020-07-08 16:24:30 -07:00
uint32_t lastIndexInRow = index + ( ( KeyboardManagerConstants : : ShortcutTableColCount - 1 ) - KeyboardManagerConstants : : ShortcutTableTargetAppColIndex ) ;
// Calculate row index in the buffer from the grid child index (first set of children are header elements and then three children in each row)
int rowIndex = ( lastIndexInRow - KeyboardManagerConstants : : ShortcutTableHeaderCount ) / KeyboardManagerConstants : : ShortcutTableColCount ;
2020-09-10 11:31:49 -07:00
// rowIndex could be out of bounds if the the row got deleted after LostFocus handler was invoked. In this case it should return
if ( rowIndex > = keyboardRemapControlObjects . size ( ) )
{
return ;
}
2020-07-08 16:24:30 -07:00
// Validate both set of drop downs
2020-07-23 16:43:49 -07:00
KeyDropDownControl : : ValidateShortcutFromDropDownList ( parent , keyboardRemapControlObjects [ rowIndex ] [ 0 ] - > getShortcutControl ( ) , keyboardRemapControlObjects [ rowIndex ] [ 0 ] - > shortcutDropDownStackPanel . as < StackPanel > ( ) , 0 , ShortcutControl : : shortcutRemapBuffer , keyboardRemapControlObjects [ rowIndex ] [ 0 ] - > keyDropDownControlObjects , targetAppTextBox , false , false ) ;
KeyDropDownControl : : ValidateShortcutFromDropDownList ( parent , keyboardRemapControlObjects [ rowIndex ] [ 1 ] - > getShortcutControl ( ) , keyboardRemapControlObjects [ rowIndex ] [ 1 ] - > shortcutDropDownStackPanel . as < StackPanel > ( ) , 1 , ShortcutControl : : shortcutRemapBuffer , keyboardRemapControlObjects [ rowIndex ] [ 1 ] - > keyDropDownControlObjects , targetAppTextBox , true , false ) ;
2020-07-08 16:24:30 -07:00
// Reset the buffer based on the selected drop down items
2020-10-02 15:36:36 +03:00
std : : get < Shortcut > ( shortcutRemapBuffer [ rowIndex ] . first [ 0 ] ) . SetKeyCodes ( KeyboardManagerHelper : : GetKeyCodesFromSelectedIndices ( KeyDropDownControl : : GetSelectedIndicesFromStackPanel ( keyboardRemapControlObjects [ rowIndex ] [ 0 ] - > shortcutDropDownStackPanel . as < StackPanel > ( ) ) , KeyDropDownControl : : GetKeyCodeList ( true , false ) ) ) ;
2020-07-23 16:43:49 -07:00
// second column is a hybrid column
2020-10-02 15:36:36 +03:00
std : : vector < DWORD > selectedKeyCodes = KeyboardManagerHelper : : GetKeyCodesFromSelectedIndices ( KeyDropDownControl : : GetSelectedIndicesFromStackPanel ( keyboardRemapControlObjects [ rowIndex ] [ 1 ] - > shortcutDropDownStackPanel . as < StackPanel > ( ) ) , KeyDropDownControl : : GetKeyCodeList ( true , true ) ) ;
2020-07-23 16:43:49 -07:00
// If exactly one key is selected consider it to be a key remap
if ( selectedKeyCodes . size ( ) = = 1 )
{
shortcutRemapBuffer [ rowIndex ] . first [ 1 ] = 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 ;
}
2020-07-10 17:07:28 -07:00
std : : wstring newText = targetAppTextBox . Text ( ) . c_str ( ) ;
std : : wstring lowercaseDefAppName = KeyboardManagerConstants : : DefaultAppName ;
std : : transform ( newText . begin ( ) , newText . end ( ) , newText . begin ( ) , towlower ) ;
std : : transform ( lowercaseDefAppName . begin ( ) , lowercaseDefAppName . end ( ) , lowercaseDefAppName . begin ( ) , towlower ) ;
if ( newText = = lowercaseDefAppName )
{
shortcutRemapBuffer [ rowIndex ] . second = L " " ;
}
else
{
shortcutRemapBuffer [ rowIndex ] . second = targetAppTextBox . Text ( ) . c_str ( ) ;
}
2020-09-08 12:24:59 -07:00
// To set the accessibile name of the target app text box when focus is lost
2020-09-18 17:12:37 -07:00
ShortcutControl : : SetAccessibleNameForTextBox ( targetAppTextBox , rowIndex + 1 ) ;
2020-07-08 16:24:30 -07:00
} ) ;
parent . SetColumn ( targetAppTextBox , KeyboardManagerConstants : : ShortcutTableTargetAppColIndex ) ;
parent . SetRow ( targetAppTextBox , parent . RowDefinitions ( ) . Size ( ) - 1 ) ;
parent . Children ( ) . Append ( targetAppTextBox ) ;
2020-03-23 10:44:02 -07:00
// Delete row button
Windows : : UI : : Xaml : : Controls : : Button deleteShortcut ;
FontIcon deleteSymbol ;
deleteSymbol . FontFamily ( Xaml : : Media : : FontFamily ( L " Segoe MDL2 Assets " ) ) ;
deleteSymbol . Glyph ( L " \xE74D " ) ;
deleteShortcut . Content ( deleteSymbol ) ;
2020-05-06 08:34:26 -07:00
deleteShortcut . Background ( Media : : SolidColorBrush ( Colors : : Transparent ( ) ) ) ;
2020-05-08 17:34:24 -07:00
deleteShortcut . HorizontalAlignment ( HorizontalAlignment : : Center ) ;
2020-04-20 08:22:36 -07:00
deleteShortcut . Click ( [ & ] ( winrt : : Windows : : Foundation : : IInspectable const & sender , RoutedEventArgs const & ) {
2020-04-23 09:14:16 -07:00
Button currentButton = sender . as < Button > ( ) ;
2020-03-23 10:44:02 -07:00
uint32_t index ;
2020-04-23 09:14:16 -07:00
// Get index of delete button
UIElementCollection children = parent . Children ( ) ;
2020-09-10 11:31:49 -07:00
bool indexFound = children . IndexOf ( currentButton , index ) ;
// IndexOf could fail if the the row got deleted and the button handler was invoked twice. In this case it should return
if ( ! indexFound )
{
return ;
}
2020-05-08 17:34:24 -07:00
uint32_t lastIndexInRow = index + ( ( KeyboardManagerConstants : : ShortcutTableColCount - 1 ) - KeyboardManagerConstants : : ShortcutTableRemoveColIndex ) ;
2020-04-23 09:14:16 -07:00
// Change the row index of elements appearing after the current row, as we will delete the row definition
2020-04-26 15:09:40 -07:00
for ( uint32_t i = lastIndexInRow + 1 ; i < children . Size ( ) ; i + + )
2020-04-23 09:14:16 -07:00
{
int32_t elementRowIndex = parent . GetRow ( children . GetAt ( i ) . as < FrameworkElement > ( ) ) ;
parent . SetRow ( children . GetAt ( i ) . as < FrameworkElement > ( ) , elementRowIndex - 1 ) ;
}
2020-05-08 17:34:24 -07:00
2020-09-18 17:12:37 -07:00
// Update accessible names for each row after the deleted row
for ( uint32_t i = lastIndexInRow + 1 ; i < children . Size ( ) ; i + = KeyboardManagerConstants : : ShortcutTableColCount )
{
// Get row index from grid
int32_t elementRowIndex = parent . GetRow ( children . GetAt ( i ) . as < FrameworkElement > ( ) ) ;
StackPanel sourceCol = children . GetAt ( i + KeyboardManagerConstants : : ShortcutTableOriginalColIndex ) . as < StackPanel > ( ) ;
StackPanel targetCol = children . GetAt ( i + KeyboardManagerConstants : : ShortcutTableNewColIndex ) . as < StackPanel > ( ) ;
TextBox targetApp = children . GetAt ( i + KeyboardManagerConstants : : ShortcutTableTargetAppColIndex ) . as < TextBox > ( ) ;
Button delButton = children . GetAt ( i + KeyboardManagerConstants : : ShortcutTableRemoveColIndex ) . as < Button > ( ) ;
UpdateAccessibleNames ( sourceCol , targetCol , targetApp , delButton , elementRowIndex ) ;
}
2020-05-08 17:34:24 -07:00
for ( int i = 0 ; i < KeyboardManagerConstants : : ShortcutTableColCount ; i + + )
{
parent . Children ( ) . RemoveAt ( lastIndexInRow - i ) ;
}
2020-04-23 09:14:16 -07:00
2020-07-08 16:24:30 -07:00
// Calculate row index in the buffer from the grid child index (first set of children are header elements and then three children in each row)
2020-05-08 17:34:24 -07:00
int bufferIndex = ( lastIndexInRow - KeyboardManagerConstants : : ShortcutTableHeaderCount ) / KeyboardManagerConstants : : ShortcutTableColCount ;
2020-04-23 09:14:16 -07:00
// Delete the row definition
parent . RowDefinitions ( ) . RemoveAt ( bufferIndex + 1 ) ;
// delete the row from the buffer
shortcutRemapBuffer . erase ( shortcutRemapBuffer . begin ( ) + bufferIndex ) ;
2020-04-18 16:12:26 -07:00
// delete the ShortcutControl objects so that they get destructed
2020-04-23 09:14:16 -07:00
keyboardRemapControlObjects . erase ( keyboardRemapControlObjects . begin ( ) + bufferIndex ) ;
2020-03-23 10:44:02 -07:00
} ) ;
2020-09-08 12:24:59 -07:00
// To set the accessible name of the delete button
deleteShortcut . SetValue ( Automation : : AutomationProperties : : NameProperty ( ) , box_value ( GET_RESOURCE_STRING ( IDS_DELETE_REMAPPING_BUTTON ) ) ) ;
2020-05-08 17:34:24 -07:00
parent . SetColumn ( deleteShortcut , KeyboardManagerConstants : : ShortcutTableRemoveColIndex ) ;
2020-04-23 09:14:16 -07:00
parent . SetRow ( deleteShortcut , parent . RowDefinitions ( ) . Size ( ) - 1 ) ;
parent . Children ( ) . Append ( deleteShortcut ) ;
2020-04-18 16:12:26 -07:00
parent . UpdateLayout ( ) ;
2020-09-18 17:12:37 -07:00
// Set accessible names
UpdateAccessibleNames ( keyboardRemapControlObjects [ keyboardRemapControlObjects . size ( ) - 1 ] [ 0 ] - > getShortcutControl ( ) , keyboardRemapControlObjects [ keyboardRemapControlObjects . size ( ) - 1 ] [ 1 ] - > getShortcutControl ( ) , targetAppTextBox , deleteShortcut , parent . RowDefinitions ( ) . Size ( ) - 1 ) ;
2020-04-18 16:12:26 -07:00
// Set the shortcut text if the two vectors are not empty (i.e. default args)
2020-07-23 16:43:49 -07:00
if ( originalKeys . IsValidShortcut ( ) & & ! ( newKeys . index ( ) = = 0 & & std : : get < DWORD > ( newKeys ) = = NULL ) & & ! ( newKeys . index ( ) = = 1 & & ! std : : get < Shortcut > ( newKeys ) . IsValidShortcut ( ) ) )
2020-04-18 16:12:26 -07:00
{
2020-07-08 16:24:30 -07:00
// change to load app name
2020-08-13 16:32:15 -07:00
shortcutRemapBuffer . push_back ( std : : make_pair < RemapBufferItem , std : : wstring > ( RemapBufferItem { Shortcut ( ) , Shortcut ( ) } , std : : wstring ( targetAppName ) ) ) ;
2020-07-23 16:43:49 -07:00
KeyDropDownControl : : AddShortcutToControl ( originalKeys , parent , keyboardRemapControlObjects [ keyboardRemapControlObjects . size ( ) - 1 ] [ 0 ] - > shortcutDropDownStackPanel . as < StackPanel > ( ) , * keyboardManagerState , 0 , keyboardRemapControlObjects [ keyboardRemapControlObjects . size ( ) - 1 ] [ 0 ] - > keyDropDownControlObjects , shortcutRemapBuffer , keyboardRemapControlObjects [ keyboardRemapControlObjects . size ( ) - 1 ] [ 0 ] - > shortcutControlLayout . as < StackPanel > ( ) , targetAppTextBox , false , false ) ;
2020-04-18 16:12:26 -07:00
2020-07-23 16:43:49 -07:00
if ( newKeys . index ( ) = = 0 )
2020-04-18 16:12:26 -07:00
{
2020-10-02 15:36:36 +03:00
std : : vector < DWORD > shortcutListKeyCodes = KeyDropDownControl : : GetKeyCodeList ( true , true ) ;
2020-07-23 16:43:49 -07:00
auto it = std : : find ( shortcutListKeyCodes . begin ( ) , shortcutListKeyCodes . end ( ) , std : : get < DWORD > ( newKeys ) ) ;
if ( it ! = shortcutListKeyCodes . end ( ) )
2020-04-18 16:12:26 -07:00
{
2020-07-23 16:43:49 -07:00
keyboardRemapControlObjects [ keyboardRemapControlObjects . size ( ) - 1 ] [ 1 ] - > keyDropDownControlObjects [ 0 ] - > SetSelectedIndex ( ( int32_t ) std : : distance ( shortcutListKeyCodes . begin ( ) , it ) ) ;
2020-04-18 16:12:26 -07:00
}
}
2020-07-23 16:43:49 -07:00
else
{
KeyDropDownControl : : AddShortcutToControl ( std : : get < Shortcut > ( newKeys ) , parent , keyboardRemapControlObjects [ keyboardRemapControlObjects . size ( ) - 1 ] [ 1 ] - > shortcutDropDownStackPanel . as < StackPanel > ( ) , * keyboardManagerState , 1 , keyboardRemapControlObjects [ keyboardRemapControlObjects . size ( ) - 1 ] [ 1 ] - > keyDropDownControlObjects , shortcutRemapBuffer , keyboardRemapControlObjects [ keyboardRemapControlObjects . size ( ) - 1 ] [ 1 ] - > shortcutControlLayout . as < StackPanel > ( ) , targetAppTextBox , true , false ) ;
}
}
else
{
// Initialize both shortcuts as empty shortcuts
2020-08-13 16:32:15 -07:00
shortcutRemapBuffer . push_back ( std : : make_pair < RemapBufferItem , std : : wstring > ( RemapBufferItem { Shortcut ( ) , Shortcut ( ) } , std : : wstring ( targetAppName ) ) ) ;
2020-04-18 16:12:26 -07:00
}
2020-03-23 10:44:02 -07:00
}
// 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 ShortcutControl : : getShortcutControl ( )
{
2020-07-13 11:49:09 -07:00
return shortcutControlLayout . as < StackPanel > ( ) ;
2020-03-23 10:44:02 -07:00
}
// Function to create the detect shortcut UI window
2020-08-13 16:32:15 -07:00
void ShortcutControl : : createDetectShortcutWindow ( winrt : : Windows : : Foundation : : IInspectable const & sender , XamlRoot xamlRoot , KeyboardManagerState & keyboardManagerState , const int colIndex , Grid table , std : : vector < std : : unique_ptr < KeyDropDownControl > > & keyDropDownControlObjects , StackPanel controlLayout , TextBox targetApp , bool isHybridControl , bool isSingleKeyWindow , HWND parentWindow , RemapBuffer & remapBuffer )
2020-03-23 10:44:02 -07:00
{
// ContentDialog for detecting shortcuts. This is the parent UI element.
ContentDialog detectShortcutBox ;
// ContentDialog requires manually setting the XamlRoot (https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.contentdialog#contentdialog-in-appwindow-or-xaml-islands)
detectShortcutBox . XamlRoot ( xamlRoot ) ;
2020-08-17 13:46:50 -07:00
detectShortcutBox . Title ( box_value ( GET_RESOURCE_STRING ( IDS_TYPESHORTCUT_TITLE ) ) ) ;
2020-04-16 09:16:48 -07:00
detectShortcutBox . IsPrimaryButtonEnabled ( false ) ;
2020-03-23 10:44:02 -07:00
detectShortcutBox . IsSecondaryButtonEnabled ( false ) ;
2020-07-23 16:43:49 -07:00
// Get the linked stack panel for the "Type shortcut" button that was clicked
2020-04-20 08:22:36 -07:00
StackPanel linkedShortcutStackPanel = KeyboardManagerHelper : : getSiblingElement ( sender ) . as < StackPanel > ( ) ;
2020-03-23 10:44:02 -07:00
2020-04-16 09:16:48 -07:00
auto unregisterKeys = [ & keyboardManagerState ] ( ) {
2020-09-11 15:16:34 -07:00
keyboardManagerState . ClearRegisteredKeyDelays ( ) ;
2020-04-16 09:16:48 -07:00
} ;
auto selectDetectedShortcutAndResetKeys = [ & keyboardManagerState ] ( DWORD key ) {
keyboardManagerState . SelectDetectedShortcut ( key ) ;
keyboardManagerState . ResetDetectedShortcutKey ( key ) ;
} ;
2020-07-23 16:43:49 -07:00
auto onPressEnter = [ linkedShortcutStackPanel ,
2020-05-13 10:13:44 -07:00
detectShortcutBox ,
& keyboardManagerState ,
unregisterKeys ,
colIndex ,
2020-07-08 16:24:30 -07:00
table ,
2020-07-23 16:43:49 -07:00
targetApp ,
& keyDropDownControlObjects ,
controlLayout ,
isHybridControl ,
isSingleKeyWindow ,
& remapBuffer ] {
2020-03-23 10:44:02 -07:00
// Save the detected shortcut in the linked text block
2020-04-08 09:11:58 -07:00
Shortcut detectedShortcutKeys = keyboardManagerState . GetDetectedShortcut ( ) ;
2020-04-09 09:20:19 -07:00
if ( ! detectedShortcutKeys . IsEmpty ( ) )
{
2020-04-18 16:12:26 -07:00
// The shortcut buffer gets set in this function
2020-07-23 16:43:49 -07:00
KeyDropDownControl : : AddShortcutToControl ( detectedShortcutKeys , table , linkedShortcutStackPanel , keyboardManagerState , colIndex , keyDropDownControlObjects , remapBuffer , controlLayout , targetApp , isHybridControl , isSingleKeyWindow ) ;
2020-04-09 09:20:19 -07:00
}
2020-05-13 10:13:44 -07:00
// Hide the type shortcut UI
detectShortcutBox . Hide ( ) ;
} ;
2020-03-23 10:44:02 -07:00
2020-05-13 10:13:44 -07:00
auto onReleaseEnter = [ & keyboardManagerState ,
2020-07-23 16:43:49 -07:00
unregisterKeys ,
isSingleKeyWindow ,
parentWindow ] {
2020-03-23 10:44:02 -07:00
// Reset the keyboard manager UI state
keyboardManagerState . ResetUIState ( ) ;
2020-07-23 16:43:49 -07:00
if ( isSingleKeyWindow )
{
// Revert UI state back to Edit Keyboard window
keyboardManagerState . SetUIState ( KeyboardManagerUIState : : EditKeyboardWindowActivated , parentWindow ) ;
}
else
{
// Revert UI state back to Edit Shortcut window
keyboardManagerState . SetUIState ( KeyboardManagerUIState : : EditShortcutsWindowActivated , parentWindow ) ;
}
2020-04-16 09:16:48 -07:00
unregisterKeys ( ) ;
2020-05-13 10:13:44 -07:00
} ;
auto onAccept = [ onPressEnter ,
onReleaseEnter ] {
onPressEnter ( ) ;
onReleaseEnter ( ) ;
2020-04-16 09:16:48 -07:00
} ;
TextBlock primaryButtonText ;
2020-08-17 13:46:50 -07:00
primaryButtonText . Text ( GET_RESOURCE_STRING ( IDS_OK_BUTTON ) ) ;
2020-04-16 09:16:48 -07:00
Button primaryButton ;
primaryButton . HorizontalAlignment ( HorizontalAlignment : : Stretch ) ;
primaryButton . Margin ( { 2 , 2 , 2 , 2 } ) ;
primaryButton . Content ( primaryButtonText ) ;
// OK button
2020-04-20 08:22:36 -07:00
primaryButton . Click ( [ onAccept ] ( winrt : : Windows : : Foundation : : IInspectable const & sender , RoutedEventArgs const & ) {
2020-04-16 09:16:48 -07:00
onAccept ( ) ;
2020-03-23 10:44:02 -07:00
} ) ;
2020-10-01 18:19:11 -07:00
// NOTE: UnregisterKeys should never be called on the DelayThread, as it will re-enter the mutex. To avoid this it is run on the dispatcher thread
2020-04-16 09:16:48 -07:00
keyboardManagerState . RegisterKeyDelay (
VK_RETURN ,
selectDetectedShortcutAndResetKeys ,
2020-05-13 10:13:44 -07:00
[ primaryButton , onPressEnter , detectShortcutBox ] ( DWORD ) {
2020-04-16 09:16:48 -07:00
detectShortcutBox . Dispatcher ( ) . RunAsync (
Windows : : UI : : Core : : CoreDispatcherPriority : : Normal ,
2020-05-13 10:13:44 -07:00
[ primaryButton , onPressEnter ] {
2020-04-21 13:42:06 -07:00
// Use the base medium low brush to be consistent with the theme
primaryButton . Background ( Windows : : UI : : Xaml : : Application : : Current ( ) . Resources ( ) . Lookup ( box_value ( L " SystemControlBackgroundBaseMediumLowBrush " ) ) . as < Windows : : UI : : Xaml : : Media : : SolidColorBrush > ( ) ) ;
2020-05-13 10:13:44 -07:00
onPressEnter ( ) ;
2020-04-16 09:16:48 -07:00
} ) ;
} ,
2020-10-01 18:19:11 -07:00
[ onReleaseEnter , detectShortcutBox ] ( DWORD ) {
detectShortcutBox . Dispatcher ( ) . RunAsync (
Windows : : UI : : Core : : CoreDispatcherPriority : : Normal ,
[ onReleaseEnter ] ( ) {
onReleaseEnter ( ) ;
} ) ;
2020-04-16 09:16:48 -07:00
} ) ;
TextBlock cancelButtonText ;
2020-08-17 13:46:50 -07:00
cancelButtonText . Text ( GET_RESOURCE_STRING ( IDS_CANCEL_BUTTON ) ) ;
2020-04-16 09:16:48 -07:00
2020-10-01 18:19:11 -07:00
auto onCancel = [ & keyboardManagerState ,
detectShortcutBox ,
unregisterKeys ,
isSingleKeyWindow ,
parentWindow ] {
detectShortcutBox . Hide ( ) ;
2020-03-23 10:44:02 -07:00
// Reset the keyboard manager UI state
keyboardManagerState . ResetUIState ( ) ;
2020-07-23 16:43:49 -07:00
if ( isSingleKeyWindow )
{
// Revert UI state back to Edit Keyboard window
keyboardManagerState . SetUIState ( KeyboardManagerUIState : : EditKeyboardWindowActivated , parentWindow ) ;
}
else
{
// Revert UI state back to Edit Shortcut window
keyboardManagerState . SetUIState ( KeyboardManagerUIState : : EditShortcutsWindowActivated , parentWindow ) ;
}
2020-04-16 09:16:48 -07:00
unregisterKeys ( ) ;
2020-10-01 18:19:11 -07:00
} ;
Button cancelButton ;
cancelButton . HorizontalAlignment ( HorizontalAlignment : : Stretch ) ;
cancelButton . Margin ( { 2 , 2 , 2 , 2 } ) ;
cancelButton . Content ( cancelButtonText ) ;
// Cancel button
cancelButton . Click ( [ onCancel ] ( winrt : : Windows : : Foundation : : IInspectable const & sender , RoutedEventArgs const & ) {
onCancel ( ) ;
2020-03-23 10:44:02 -07:00
} ) ;
2020-10-01 18:19:11 -07:00
// NOTE: UnregisterKeys should never be called on the DelayThread, as it will re-enter the mutex. To avoid this it is run on the dispatcher thread
2020-04-16 09:16:48 -07:00
keyboardManagerState . RegisterKeyDelay (
VK_ESCAPE ,
selectDetectedShortcutAndResetKeys ,
2020-10-01 18:19:11 -07:00
[ onCancel , detectShortcutBox ] ( DWORD ) {
2020-04-16 09:16:48 -07:00
detectShortcutBox . Dispatcher ( ) . RunAsync (
Windows : : UI : : Core : : CoreDispatcherPriority : : Normal ,
2020-10-01 18:19:11 -07:00
[ onCancel ] {
onCancel ( ) ;
2020-04-16 09:16:48 -07:00
} ) ;
} ,
nullptr ) ;
2020-03-23 10:44:02 -07:00
// StackPanel parent for the displayed text in the dialog
Windows : : UI : : Xaml : : Controls : : StackPanel stackPanel ;
2020-04-08 14:31:31 -07:00
detectShortcutBox . Content ( stackPanel ) ;
2020-03-23 10:44:02 -07:00
// Header textblock
TextBlock text ;
2020-08-17 13:46:50 -07:00
text . Text ( GET_RESOURCE_STRING ( IDS_TYPESHORTCUT_HEADER ) ) ;
2020-03-23 10:44:02 -07:00
text . Margin ( { 0 , 0 , 0 , 10 } ) ;
2020-04-08 14:31:31 -07:00
stackPanel . Children ( ) . Append ( text ) ;
2020-03-23 10:44:02 -07:00
2020-05-14 09:23:51 -07:00
// Target StackPanel to place the selected key - first row (for 1-3 keys)
Windows : : UI : : Xaml : : Controls : : StackPanel keyStackPanel1 ;
keyStackPanel1 . Orientation ( Orientation : : Horizontal ) ;
stackPanel . Children ( ) . Append ( keyStackPanel1 ) ;
// Target StackPanel to place the selected key - second row (for 4-5 keys)
Windows : : UI : : Xaml : : Controls : : StackPanel keyStackPanel2 ;
keyStackPanel2 . Orientation ( Orientation : : Horizontal ) ;
keyStackPanel2 . Margin ( { 0 , 20 , 0 , 0 } ) ;
keyStackPanel2 . Visibility ( Visibility : : Collapsed ) ;
stackPanel . Children ( ) . Append ( keyStackPanel2 ) ;
2020-03-23 10:44:02 -07:00
2020-04-16 09:16:48 -07:00
TextBlock holdEscInfo ;
2020-08-17 13:46:50 -07:00
holdEscInfo . Text ( GET_RESOURCE_STRING ( IDS_TYPE_HOLDESC ) ) ;
2020-04-16 09:16:48 -07:00
holdEscInfo . FontSize ( 12 ) ;
holdEscInfo . Margin ( { 0 , 20 , 0 , 0 } ) ;
stackPanel . Children ( ) . Append ( holdEscInfo ) ;
TextBlock holdEnterInfo ;
2020-08-17 13:46:50 -07:00
holdEnterInfo . Text ( GET_RESOURCE_STRING ( IDS_TYPE_HOLDENTER ) ) ;
2020-04-16 09:16:48 -07:00
holdEnterInfo . FontSize ( 12 ) ;
holdEnterInfo . Margin ( { 0 , 0 , 0 , 0 } ) ;
stackPanel . Children ( ) . Append ( holdEnterInfo ) ;
ColumnDefinition primaryButtonColumn ;
ColumnDefinition cancelButtonColumn ;
Grid buttonPanel ;
buttonPanel . Margin ( { 0 , 20 , 0 , 0 } ) ;
buttonPanel . HorizontalAlignment ( HorizontalAlignment : : Stretch ) ;
buttonPanel . ColumnDefinitions ( ) . Append ( primaryButtonColumn ) ;
buttonPanel . ColumnDefinitions ( ) . Append ( cancelButtonColumn ) ;
buttonPanel . SetColumn ( primaryButton , 0 ) ;
buttonPanel . SetColumn ( cancelButton , 1 ) ;
buttonPanel . Children ( ) . Append ( primaryButton ) ;
buttonPanel . Children ( ) . Append ( cancelButton ) ;
stackPanel . Children ( ) . Append ( buttonPanel ) ;
2020-03-23 10:44:02 -07:00
stackPanel . UpdateLayout ( ) ;
// Configure the keyboardManagerState to store the UI information.
2020-05-14 09:23:51 -07:00
keyboardManagerState . ConfigureDetectShortcutUI ( keyStackPanel1 , keyStackPanel2 ) ;
2020-03-23 10:44:02 -07:00
// Show the dialog
detectShortcutBox . ShowAsync ( ) ;
2020-05-11 10:10:36 -07:00
}