2025-07-18 22:31:16 +02:00
/*
2025-07-19 16:46:22 +02:00
* KeyBindingsWindow . cpp , part of VCMI engine
2025-07-18 22:31:16 +02:00
*
* Authors : listed in file AUTHORS in main folder
*
* License : GNU General Public License v2 .0 or later
* Full text of license available in license . txt file , in main folder
*
*/
# include "StdInc.h"
2025-07-19 16:46:22 +02:00
# include "KeyBindingsWindow.h"
2025-07-18 22:31:16 +02:00
2025-07-19 13:59:55 +02:00
# include "../../CPlayerInterface.h"
2025-07-19 13:09:15 +02:00
# include "../../GameEngine.h"
2025-07-19 13:59:55 +02:00
# include "../../GameInstance.h"
2025-07-18 22:31:16 +02:00
# include "../../gui/Shortcut.h"
# include "../../gui/WindowHandler.h"
# include "../../widgets/Buttons.h"
# include "../../widgets/GraphicalPrimitiveCanvas.h"
# include "../../widgets/Images.h"
# include "../../widgets/TextControls.h"
# include "../../widgets/Slider.h"
# include "../../windows/InfoWindows.h"
2025-07-19 15:19:09 +02:00
# include "../../../lib/CConfigHandler.h"
2025-07-18 22:31:16 +02:00
# include "../../../lib/texts/MetaString.h"
# include "../../../lib/json/JsonNode.h"
# include "../../../lib/json/JsonUtils.h"
2025-07-19 16:46:22 +02:00
KeyBindingsWindow : : KeyBindingsWindow ( )
2025-07-18 22:31:16 +02:00
: CWindowObject ( BORDERED )
{
OBJECT_CONSTRUCTION ;
pos . w = 500 ;
pos . h = 450 ;
updateShadow ( ) ;
center ( ) ;
backgroundTexture = std : : make_shared < CFilledTexture > ( ImagePath : : builtin ( " DiBoxBck " ) , Rect ( 0 , 0 , pos . w , pos . h ) ) ;
buttonOk = std : : make_shared < CButton > ( Point ( 218 , 404 ) , AnimationPath : : builtin ( " IOKAY " ) , CButton : : tooltip ( ) , [ this ] ( ) { close ( ) ; } , EShortcut : : GLOBAL_ACCEPT ) ;
labelTitle = std : : make_shared < CLabel > (
2025-07-19 16:46:22 +02:00
pos . w / 2 , 20 , FONT_BIG , ETextAlignment : : CENTER , Colors : : YELLOW , MetaString : : createFromTextID ( " vcmi.keyBindings.button.hover " ) . toString ( )
2025-07-18 22:31:16 +02:00
) ;
backgroundRect = std : : make_shared < TransparentFilledRectangle > ( Rect ( 8 , 48 , pos . w - 16 , 348 ) , ColorRGBA ( 0 , 0 , 0 , 64 ) , ColorRGBA ( 128 , 100 , 75 ) , 1 ) ;
int count = 0 ;
2025-07-19 16:46:22 +02:00
for ( auto & group : keyBindingsConfig . toJsonNode ( ) . Struct ( ) )
2025-07-18 22:31:16 +02:00
{
count + + ;
count + = group . second . Struct ( ) . size ( ) ;
}
slider = std : : make_shared < CSlider > ( Point ( backgroundRect - > pos . x - pos . x + backgroundRect - > pos . w - 18 , backgroundRect - > pos . y - pos . y + 1 ) , backgroundRect - > pos . h - 3 , [ this ] ( int pos ) { fillList ( pos ) ; redraw ( ) ; } , MAX_LINES , count , 0 , Orientation : : VERTICAL , CSlider : : BROWN ) ;
slider - > setPanningStep ( LINE_HEIGHT ) ;
slider - > setScrollBounds ( Rect ( - backgroundRect - > pos . w + slider - > pos . w , 0 , slider - > pos . x - pos . x + slider - > pos . w , slider - > pos . h ) ) ;
2025-07-19 16:46:22 +02:00
buttonReset = std : : make_shared < CButton > ( Point ( 411 , 403 ) , AnimationPath : : builtin ( " settingsWindow/button80 " ) , std : : make_pair ( " " , MetaString : : createFromTextID ( " vcmi.keyBindings.reset.help " ) . toString ( ) ) ) ;
buttonReset - > setOverlay ( std : : make_shared < CLabel > ( 0 , 0 , FONT_MEDIUM , ETextAlignment : : CENTER , Colors : : YELLOW , MetaString : : createFromTextID ( " vcmi.keyBindings.reset " ) . toString ( ) ) ) ;
2025-07-19 14:28:48 +02:00
buttonReset - > addCallback ( [ this ] ( ) {
2025-07-19 16:46:22 +02:00
GAME - > interface ( ) - > showYesNoDialog ( MetaString : : createFromTextID ( " vcmi.keyBindings.resetConfirm " ) . toString ( ) , [ this ] ( ) {
2025-07-19 14:28:48 +02:00
resetKeyBinding ( ) ;
} , nullptr ) ;
} ) ;
2025-07-18 22:31:16 +02:00
fillList ( 0 ) ;
}
2025-07-19 16:46:22 +02:00
void KeyBindingsWindow : : fillList ( int start )
2025-07-18 22:31:16 +02:00
{
OBJECT_CONSTRUCTION ;
listElements . clear ( ) ;
int i = 0 ;
[ & ] {
2025-07-19 16:46:22 +02:00
for ( auto group = keyBindingsConfig . toJsonNode ( ) . Struct ( ) . rbegin ( ) ; group ! = keyBindingsConfig . toJsonNode ( ) . Struct ( ) . rend ( ) ; + + group )
2025-07-18 22:31:16 +02:00
{
if ( i > = start )
2025-07-19 16:46:22 +02:00
listElements . push_back ( std : : make_shared < KeyBindingElement > ( group - > first , listElements . size ( ) ) ) ;
2025-07-18 22:31:16 +02:00
i + + ;
if ( listElements . size ( ) = = MAX_LINES )
return ;
2025-07-18 23:24:57 +02:00
for ( auto & elem : group - > second . Struct ( ) )
2025-07-18 22:31:16 +02:00
{
if ( i > = start )
2025-07-19 16:46:22 +02:00
listElements . push_back ( std : : make_shared < KeyBindingElement > ( elem . first , elem . second , listElements . size ( ) , [ this , group ] ( const std : : string & id , const std : : string & keyName ) {
auto str = MetaString : : createFromTextID ( " vcmi.keyBindings.inputSet " ) ;
str . replaceTextID ( " vcmi.keyBindings.keyBinding. " + id ) ;
2025-07-19 13:59:55 +02:00
str . replaceRawString ( keyName ) ;
2025-07-19 15:19:09 +02:00
GAME - > interface ( ) - > showYesNoDialog ( str . toString ( ) , [ this , group , id , keyName ] ( ) {
setKeyBinding ( id , group - > first , keyName , true ) ;
} , [ this , group , id , keyName ] ( ) {
setKeyBinding ( id , group - > first , keyName , false ) ;
2025-07-19 13:59:55 +02:00
} ) ;
} ) ) ;
2025-07-18 22:31:16 +02:00
i + + ;
if ( listElements . size ( ) = = MAX_LINES )
return ;
}
}
} ( ) ;
}
2025-07-19 16:46:22 +02:00
void KeyBindingsWindow : : setKeyBinding ( const std : : string & id , const std : : string & group , const std : : string & keyName , bool append )
2025-07-19 13:59:55 +02:00
{
2025-07-19 16:46:22 +02:00
auto existing = keyBindingsConfig [ group ] [ id ] ;
Settings existingWrite = keyBindingsConfig . write [ group ] [ id ] ;
2025-07-19 15:19:09 +02:00
if ( ( existing . isVector ( ) | | ( existing . isString ( ) & & ! existing . String ( ) . empty ( ) ) ) & & append )
{
JsonVector tmp ;
if ( existing . isVector ( ) )
tmp = existing . Vector ( ) ;
if ( existing . isString ( ) )
tmp . push_back ( existing ) ;
2025-07-19 15:45:34 +02:00
if ( ! vstd : : contains ( tmp , JsonNode ( keyName ) ) )
tmp . push_back ( JsonNode ( keyName ) ) ;
2025-07-19 15:19:09 +02:00
existingWrite - > Vector ( ) = tmp ;
}
else
existingWrite - > String ( ) = keyName ;
2025-07-19 14:28:48 +02:00
fillList ( slider - > getValue ( ) ) ;
}
2025-07-19 16:46:22 +02:00
void KeyBindingsWindow : : resetKeyBinding ( )
2025-07-19 14:28:48 +02:00
{
2025-07-19 15:45:34 +02:00
{
2025-07-19 16:46:22 +02:00
Settings write = keyBindingsConfig . write ;
2025-07-19 15:45:34 +02:00
write - > clear ( ) ;
}
{
2025-07-19 16:46:22 +02:00
Settings write = keyBindingsConfig . write ;
write - > Struct ( ) = JsonUtils : : assembleFromFiles ( " config/keyBindingsConfig.json " ) . Struct ( ) ;
2025-07-19 15:45:34 +02:00
}
2025-07-19 14:28:48 +02:00
fillList ( slider - > getValue ( ) ) ;
2025-07-19 13:59:55 +02:00
}
2025-07-19 16:46:22 +02:00
KeyBindingElement : : KeyBindingElement ( std : : string id , JsonNode keys , int elem , std : : function < void ( const std : : string & id , const std : : string & keyName ) > func )
2025-07-19 16:05:04 +02:00
: func ( func )
2025-07-18 22:31:16 +02:00
{
OBJECT_CONSTRUCTION ;
pos . x + = 14 ;
pos . y + = 56 ;
pos . y + = elem * LINE_HEIGHT ;
2025-07-19 16:05:04 +02:00
pos . w = 455 ;
pos . h = LINE_HEIGHT ;
addUsedEvents ( SHOW_POPUP ) ;
2025-07-19 16:46:22 +02:00
popupText = MetaString : : createFromTextID ( " vcmi.keyBindings.popup " ) ;
popupText . replaceTextID ( " vcmi.keyBindings.keyBinding. " + id ) ;
2025-07-19 15:45:34 +02:00
2025-07-18 22:31:16 +02:00
std : : string keyBinding = " " ;
if ( keys . isString ( ) )
2025-07-19 15:45:34 +02:00
{
2025-07-18 22:31:16 +02:00
keyBinding = keys . String ( ) ;
2025-07-19 15:45:34 +02:00
popupText . appendRawString ( keyBinding ) ;
}
2025-07-18 22:31:16 +02:00
else if ( keys . isVector ( ) )
{
std : : vector < std : : string > strings ;
std : : transform ( keys . Vector ( ) . begin ( ) , keys . Vector ( ) . end ( ) , std : : back_inserter ( strings ) , [ ] ( const auto & k ) { return k . String ( ) ; } ) ;
2025-07-19 15:45:34 +02:00
keyBinding = boost : : join ( strings , " | " ) ;
popupText . appendRawString ( boost : : join ( strings , " \n " ) ) ;
2025-07-18 22:31:16 +02:00
}
labelName = std : : make_shared < CLabel > (
2025-07-19 16:46:22 +02:00
0 , LINE_HEIGHT / 2 , FONT_SMALL , ETextAlignment : : CENTERLEFT , Colors : : WHITE , MetaString : : createFromTextID ( " vcmi.keyBindings.keyBinding. " + id ) . toString ( ) , 245
2025-07-18 22:31:16 +02:00
) ;
labelKeys = std : : make_shared < CLabel > (
2025-07-18 23:24:57 +02:00
250 , LINE_HEIGHT / 2 , FONT_SMALL , ETextAlignment : : CENTERLEFT , Colors : : WHITE , keyBinding , 170
2025-07-18 22:31:16 +02:00
) ;
2025-07-19 16:46:22 +02:00
buttonEdit = std : : make_shared < CButton > ( Point ( 422 , 3 ) , AnimationPath : : builtin ( " settingsWindow/button32 " ) , std : : make_pair ( " " , MetaString : : createFromTextID ( " vcmi.keyBindings.editButton.help " ) . toString ( ) ) ) ;
2025-07-18 22:31:16 +02:00
buttonEdit - > setOverlay ( std : : make_shared < CPicture > ( ImagePath : : builtin ( " settingsWindow/gear " ) ) ) ;
2025-07-19 13:59:55 +02:00
buttonEdit - > addCallback ( [ id , func ] ( ) {
2025-07-19 16:46:22 +02:00
ENGINE - > windows ( ) . createAndPushWindow < KeyBindingsEditWindow > ( id , [ func ] ( const std : : string & id , const std : : string & keyName ) {
2025-07-19 13:59:55 +02:00
if ( func )
func ( id , keyName ) ;
} ) ;
2025-07-18 22:31:16 +02:00
} ) ;
if ( elem < MAX_LINES - 1 )
seperationLine = std : : make_shared < TransparentFilledRectangle > ( Rect ( 0 , LINE_HEIGHT , 456 , 1 ) , ColorRGBA ( 0 , 0 , 0 , 64 ) , ColorRGBA ( 128 , 100 , 75 ) , 1 ) ;
}
2025-07-19 16:46:22 +02:00
KeyBindingElement : : KeyBindingElement ( std : : string group , int elem )
2025-07-19 13:59:55 +02:00
: func ( nullptr )
2025-07-18 22:31:16 +02:00
{
OBJECT_CONSTRUCTION ;
pos . x + = 14 ;
pos . y + = 56 ;
pos . y + = elem * LINE_HEIGHT ;
labelName = std : : make_shared < CLabel > (
2025-07-19 16:46:22 +02:00
0 , LINE_HEIGHT / 2 , FONT_SMALL , ETextAlignment : : CENTERLEFT , Colors : : YELLOW , MetaString : : createFromTextID ( " vcmi.keyBindings.group. " + group ) . toString ( ) , 300
2025-07-18 22:31:16 +02:00
) ;
if ( elem < MAX_LINES - 1 )
seperationLine = std : : make_shared < TransparentFilledRectangle > ( Rect ( 0 , LINE_HEIGHT , 456 , 1 ) , ColorRGBA ( 0 , 0 , 0 , 64 ) , ColorRGBA ( 128 , 100 , 75 ) , 1 ) ;
}
2025-07-19 13:09:15 +02:00
2025-07-19 16:46:22 +02:00
void KeyBindingElement : : showPopupWindow ( const Point & cursorPosition )
2025-07-19 15:45:34 +02:00
{
CRClickPopup : : createAndPush ( popupText . toString ( ) ) ;
}
2025-07-19 16:46:22 +02:00
KeyBindingsEditWindow : : KeyBindingsEditWindow ( const std : : string & id , std : : function < void ( const std : : string & id , const std : : string & keyName ) > func )
2025-07-19 13:09:15 +02:00
: CWindowObject ( BORDERED )
2025-07-19 13:59:55 +02:00
, id ( id )
, func ( func )
2025-07-19 13:09:15 +02:00
{
OBJECT_CONSTRUCTION ;
2025-07-19 13:59:55 +02:00
pos . w = 250 ;
pos . h = 150 ;
2025-07-19 16:46:22 +02:00
auto str = MetaString : : createFromTextID ( " vcmi.keyBindings.input " ) ;
str . replaceTextID ( " vcmi.keyBindings.keyBinding. " + id ) ;
2025-07-19 13:59:55 +02:00
backgroundTexture = std : : make_shared < CFilledTexture > ( ImagePath : : builtin ( " DiBoxBck " ) , Rect ( 0 , 0 , pos . w , pos . h ) ) ;
text = std : : make_shared < CTextBox > ( str . toString ( ) , Rect ( 0 , 0 , 250 , 150 ) , 0 , FONT_MEDIUM , ETextAlignment : : CENTER , Colors : : WHITE ) ;
2025-07-19 13:09:15 +02:00
updateShadow ( ) ;
center ( ) ;
2025-07-19 16:32:19 +02:00
addUsedEvents ( LCLICK | KEY_NAME ) ;
2025-07-19 13:09:15 +02:00
}
2025-07-19 16:46:22 +02:00
void KeyBindingsEditWindow : : keyReleased ( const std : : string & keyName )
2025-07-19 13:09:15 +02:00
{
2025-07-19 13:59:55 +02:00
if ( boost : : algorithm : : ends_with ( keyName , " Ctrl " ) | | boost : : algorithm : : ends_with ( keyName , " Shift " ) | | boost : : algorithm : : ends_with ( keyName , " Alt " ) ) // skip if only control key pressed
return ;
close ( ) ;
func ( id , keyName ) ;
2025-07-19 13:09:15 +02:00
}
2025-07-19 16:32:19 +02:00
2025-07-19 16:46:22 +02:00
void KeyBindingsEditWindow : : notFocusedClick ( )
2025-07-19 16:32:19 +02:00
{
close ( ) ; // possibility to close without setting key (e.g. on touch screens)
}