1 #include "KeyMappingManager.hpp"
3 #include <Core/Resources/Resources.hpp>
4 #include <Core/Utils/Log.hpp>
11 using namespace Core::Utils;
13 KeyMappingManager::KeyMappingManager() : m_file( nullptr ) {
15 auto optionalPath { Core::Resources::getRadiumResourcesPath() };
16 auto resourcesRootDir { optionalPath.value_or(
"[[Default resrouces path not found]]" ) };
19 m_defaultConfigFile = resourcesRootDir + std::string(
"Configs/default.xml" );
22 QString keyMappingFilename =
23 settings.value(
"keymapping/config", QString::fromStdString( m_defaultConfigFile ) )
25 if ( !keyMappingFilename.contains( QString::fromStdString( m_defaultConfigFile ) ) ) {
26 LOG( logDEBUG ) <<
"Loading key mapping " << keyMappingFilename.toStdString() <<
" (from "
27 << settings.fileName().toStdString() <<
")";
29 else { LOG( logDEBUG ) <<
"Loading default key mapping " << m_defaultConfigFile; }
30 loadConfiguration( keyMappingFilename.toStdString() );
33 KeyMappingManager::KeyMappingAction
34 KeyMappingManager::getAction(
const KeyMappingManager::Context& context,
39 Qt::MouseButtons buttons;
40 Qt::KeyboardModifiers modifiers;
41 const QMouseEvent* mouseEvent =
dynamic_cast<const QMouseEvent*
>( event );
43 buttons = mouseEvent->buttons();
44 modifiers = mouseEvent->modifiers();
46 return getAction( context, buttons, modifiers, key, wheel );
49 KeyMappingManager::KeyMappingAction
51 const Qt::MouseButtons& buttons,
52 const Qt::KeyboardModifiers& modifiers,
56 if ( context.isInvalid() ) {
57 LOG( logDEBUG ) <<
"try to get action from an invalid context";
61 if ( ( key == Qt::Key_Shift ) || ( key == Qt::Key_Control ) || ( key == Qt::Key_Alt ) ||
62 ( key == Qt::Key_Meta ) ) {
66 return getAction( context,
EventBinding { buttons, modifiers, key, wheel } );
72 auto action = m_bindingToAction[context].find( binding );
73 if ( action != m_bindingToAction[context].end() ) {
return action->second; }
78 std::optional<KeyMappingManager::EventBinding>
81 for (
const auto& [key, value] : m_bindingToAction[context] )
82 if ( value == action )
return key;
87 Ra::Core::Utils::Index contextIndex;
89 auto contextItr = m_contextNameToIndex.find( contextName );
90 if ( contextItr == m_contextNameToIndex.end() ) {
91 contextIndex = m_contextNameToIndex.size();
92 m_contextNameToIndex[contextName] = contextIndex;
93 m_actionNameToIndex.emplace_back();
94 m_bindingToAction.emplace_back();
95 CORE_ASSERT( m_actionNameToIndex.size() ==
size_t( contextIndex + 1 ),
96 "Corrupted actionName DB" );
97 CORE_ASSERT( m_bindingToAction.size() ==
size_t( contextIndex + 1 ),
98 "Corrupted mappingAction DB" );
101 contextIndex = contextItr->second;
108 auto itr = m_contextNameToIndex.find( contextName );
109 if ( itr != m_contextNameToIndex.end() )
return itr->second;
114 const std::string& actionName ) {
115 if (
size_t( context ) >= m_actionNameToIndex.size() || context.isInvalid() ) {
116 LOG( logWARNING ) <<
"try to get action index ( " << actionName
117 <<
" ) from an invalid context ( " << context <<
" )";
121 auto itr = m_actionNameToIndex[context].find( actionName );
122 if ( itr != m_actionNameToIndex[context].end() )
return itr->second;
123 LOG( logWARNING ) <<
"try to get action index from an invalid action name " << actionName
124 <<
" (context " << getContextName( context ) <<
" [" << context <<
"])";
125 LOG( logWARNING ) <<
"consider add to conf: "
126 <<
"<keymap context=\"" << getContextName( context )
127 <<
"\" key=\"\" modifiers=\"\" buttons=\"\" action=\"" << actionName
133 std::string KeyMappingManager::getActionName(
const Context& context,
136 if (
size_t( context ) < m_actionNameToIndex.size() && context.isValid() ) {
138 auto actionFindItr = std::find_if(
139 std::begin( m_actionNameToIndex[context] ),
140 std::end( m_actionNameToIndex[context] ),
141 [&](
const ActionNameMap::value_type& pair ) {
return pair.second == action; } );
143 if ( actionFindItr != std::end( m_actionNameToIndex[context] ) ) {
144 return actionFindItr->first;
150 std::string KeyMappingManager::getContextName(
const Context& context ) {
151 auto contextFindItr = std::find_if(
152 std::begin( m_contextNameToIndex ),
153 std::end( m_contextNameToIndex ),
154 [&](
const ContextNameMap ::value_type& pair ) {
return pair.second == context; } );
156 if ( contextFindItr != std::end( m_contextNameToIndex ) ) {
return contextFindItr->first; }
160 int KeyMappingManager::addListener( Observer callback ) {
161 auto gid = attach( callback );
168 void KeyMappingManager::removeListener(
int callbackId ) {
169 detach( callbackId );
172 void KeyMappingManager::setActionBinding(
const Context& contextIndex,
176 CORE_ASSERT(
size_t( contextIndex ) < m_contextNameToIndex.size(),
177 "contextIndex is out of range" );
180 auto f = m_bindingToAction[contextIndex].find( binding );
181 if ( f != m_bindingToAction[contextIndex].end() ) {
184 auto findResult = std::find_if(
185 std::begin( m_actionNameToIndex[contextIndex] ),
186 std::end( m_actionNameToIndex[contextIndex] ),
187 [&](
const ActionNameMap::value_type& pair ) {
return pair.second == actionIndex; } );
190 if ( findResult == std::end( m_actionNameToIndex[contextIndex] ) ) {
191 LOG( logERROR ) <<
"Corrupted call to bindKeyToAction, index " << actionIndex
192 <<
" must have been inserted before !\n";
197 auto findResult2 = std::find_if(
198 std::begin( m_actionNameToIndex[contextIndex] ),
199 std::end( m_actionNameToIndex[contextIndex] ),
200 [&](
const ActionNameMap::value_type& pair ) {
return pair.second == f->second; } );
203 if ( findResult2 == std::end( m_actionNameToIndex[contextIndex] ) ) {
204 LOG( logERROR ) <<
"Corrupted call to bindKeyToAction, index " << actionIndex
205 <<
" must have been inserted before !\n";
209 LOG( logWARNING ) <<
"Binding action " << findResult->first <<
" to "
210 <<
"buttons [" << enumNamesFromMouseButtons( binding.m_buttons ) <<
"] "
211 <<
"modifiers [" << enumNamesFromKeyboardModifiers( binding.m_modifiers )
213 <<
" keycode [" << binding.m_key <<
"]"
214 <<
" wheel [" << binding.m_wheel <<
"]"
215 <<
", which is already used for action " << findResult2->first <<
".";
218 LOG( logDEBUG2 ) <<
"In context " << getContextName( contextIndex ) <<
" [" << contextIndex
220 <<
" binding action " << getActionName( contextIndex, actionIndex ) <<
" ["
221 << actionIndex <<
"]"
222 <<
" buttons [" << enumNamesFromMouseButtons( binding.m_buttons ) <<
"]"
223 <<
" modifiers [" << enumNamesFromKeyboardModifiers( binding.m_modifiers )
225 <<
" keycode [" << binding.m_key <<
"]"
226 <<
" wheel [" << binding.m_wheel <<
"]";
228 m_bindingToAction[contextIndex][binding] = actionIndex;
231 void KeyMappingManager::loadConfiguration(
const std::string& inFilename ) {
233 std::string filename = inFilename;
234 if ( filename.empty() ) { filename = m_defaultConfigFile.c_str(); }
237 m_file =
new QFile( QString::fromStdString( filename ) );
239 if ( !m_file->open( QIODevice::ReadOnly ) ) {
240 if ( filename != m_defaultConfigFile ) {
241 LOG( logERROR ) <<
"Failed to open key mapping configuration file ! "
242 << m_file->fileName().toStdString();
243 LOG( logERROR ) <<
"Trying to load default configuration...";
248 LOG( logERROR ) <<
"Failed to open default key mapping configuration file !";
252 QDomDocument domDocument;
253 if ( !domDocument.setContent( m_file ) ) {
254 LOG( logERROR ) <<
"Can't associate XML file to QDomDocument !";
255 LOG( logERROR ) <<
"Trying to load default configuration...";
262 if ( filename != m_defaultConfigFile ) {
264 settings.setValue(
"keymapping/config", m_file->fileName() );
268 loadConfigurationInternal( domDocument );
274 bool KeyMappingManager::saveConfiguration(
const std::string& inFilename ) {
275 QString filename { m_file->fileName() };
276 if ( !inFilename.empty() ) { filename = QString::fromStdString( inFilename ); }
278 QFile saveTo( filename );
279 saveTo.open( QIODevice::WriteOnly );
280 QXmlStreamWriter stream( &saveTo );
281 stream.setAutoFormatting(
true );
282 stream.setAutoFormattingIndent( 4 );
283 stream.writeStartDocument();
284 stream.writeComment(
"\tRadium KeyMappingManager configuration file\t" );
286 "\n<keymap context=\"theContext\" action=\"theAction\" buttons=\"QButton\" "
287 "modifier=\"QModifier\" key=\"QKey\" wheel=\"boolean\"/>\n" );
289 saveKeymap( stream );
291 stream.writeEndDocument();
293 if ( stream.hasError() ) {
294 LOG( logERROR ) <<
"Fail to write Canonical XML.";
300 void KeyMappingManager::saveKeymap( QXmlStreamWriter& stream ) {
303 auto saveAttrib = [&stream](
const QString& attribName,
304 const QString& attribValue,
305 const QString& attribDefault ) {
306 if ( attribValue != attribDefault ) stream.writeAttribute( attribName, attribValue );
309 if ( stream.hasError() ) {
return; }
311 stream.writeStartElement(
"keymaps" );
313 for (
const auto& contextPair : m_contextNameToIndex ) {
314 const auto& contextName = contextPair.first;
315 const auto& context = contextPair.second;
316 for (
const auto& actionPair : m_bindingToAction[context] ) {
317 const auto& binding = actionPair.first;
318 const auto& action = actionPair.second;
319 const auto& actionNames = m_actionNameToIndex[context];
321 std::find_if( actionNames.begin(), actionNames.end(), [&action](
const auto& a ) {
322 return a.second == action;
324 if ( actionItr != actionNames.end() ) {
325 const auto& actionName = actionItr->first;
326 stream.writeStartElement(
"keymap" );
327 stream.writeAttribute(
"context", QString::fromStdString( contextName ) );
328 stream.writeAttribute(
"action", QString::fromStdString( actionName ) );
332 QString::fromStdString( enumNamesFromMouseButtons( binding.m_buttons ) ),
333 QString::fromStdString( enumNamesFromMouseButtons( Qt::NoButton ) ) );
336 QString::fromStdString( enumNamesFromKeyboardModifiers( binding.m_modifiers ) ),
337 QString::fromStdString( enumNamesFromKeyboardModifiers( Qt::NoModifier ) ) );
338 saveAttrib(
"key",
"Key_" + QKeySequence( binding.m_key ).toString(),
"Key_" );
339 saveAttrib(
"wheel", binding.m_wheel ?
"true" :
"false",
"false" );
341 stream.writeEndElement();
346 stream.writeEndElement();
349 void KeyMappingManager::loadConfigurationInternal(
const QDomDocument& domDocument ) {
352 m_contextNameToIndex.clear();
353 m_actionNameToIndex.clear();
354 m_bindingToAction.clear();
356 QDomElement domElement = domDocument.documentElement();
358 if ( domElement.tagName() !=
"keymaps" ) {
359 LOG( logWARNING ) <<
"No <keymaps> global bounding tag ! Maybe you set a different global "
360 "tag ? (Not a big deal)";
363 QDomNode node = domElement.firstChild();
364 while ( !node.isNull() ) {
365 if ( !node.isComment() ) {
366 QDomElement nodeElement = node.toElement();
367 loadConfigurationTagsInternal( nodeElement );
369 node = node.nextSibling();
373 void KeyMappingManager::loadConfigurationTagsInternal( QDomElement& node ) {
374 if ( node.tagName() ==
"keymap" ) {
376 QDomElement e = node.toElement();
377 std::string keyString = e.attribute(
"key",
"-1" ).toStdString();
378 std::string modifiersString = e.attribute(
"modifiers",
"NoModifier" ).toStdString();
379 std::string buttonsString = e.attribute(
"buttons",
"NoButton" ).toStdString();
380 std::string contextName = e.attribute(
"context",
"AppContext" ).toStdString();
381 std::string wheelString = e.attribute(
"wheel",
"false" ).toStdString();
382 std::string actionName = e.attribute(
"action" ).toStdString();
384 loadConfigurationMappingInternal(
385 contextName, keyString, modifiersString, buttonsString, wheelString, actionName );
388 LOG( logERROR ) <<
"Unrecognized XML key mapping configuration file tag \""
389 << qPrintable( node.tagName() ) <<
"\" !";
390 LOG( logERROR ) <<
"Trying to load default configuration...";
398 const std::string& actionName ) {
399 Ra::Core::Utils::Index actionIndex;
400 auto actionItr = m_actionNameToIndex[context].find( actionName );
401 if ( actionItr == m_actionNameToIndex[context].end() ) {
402 actionIndex = m_actionNameToIndex[context].size();
403 m_actionNameToIndex[context][actionName] = actionIndex;
405 else { actionIndex = actionItr->second; }
412 const std::string& actionName ) {
413 auto actionIndex = addAction( context, actionName );
414 setActionBinding( context, binding, actionIndex );
418 int KeyMappingManager::getKeyCode(
const std::string& keyString ) {
419 auto metaEnumKey = QMetaEnum::fromType<Qt::Key>();
420 return metaEnumKey.keyToValue( keyString.c_str() );
424 KeyMappingManager::loadConfigurationMappingInternal(
const std::string& context,
425 const std::string& keyString,
426 const std::string& modifiersString,
427 const std::string& buttonsString,
428 const std::string& wheelString,
429 const std::string& actionName ) {
430 auto contextIndex = addContext( context );
431 auto actionIndex = addAction( contextIndex, actionName );
434 createEventBindingFromStrings( buttonsString, modifiersString, keyString, wheelString );
436 if ( binding.m_key == -1 && binding.m_buttons == Qt::NoButton && !binding.m_wheel ) {
437 LOG( logERROR ) <<
"Invalid binding for action [" << actionName <<
"] with key ["
438 << keyString <<
"], buttons [" << buttonsString <<
"], wheel ["
439 << wheelString <<
"]";
441 else { setActionBinding( contextIndex, binding, actionIndex ); }
445 Qt::KeyboardModifiers KeyMappingManager::getQtModifiersValue(
const std::string& modifierString ) {
446 Qt::KeyboardModifiers modifier = Qt::NoModifier;
448 std::istringstream f( modifierString );
451 while ( getline( f, s,
',' ) ) {
452 if ( s ==
"ShiftModifier" ) { modifier |= Qt::ShiftModifier; }
453 else if ( s ==
"ControlModifier" ) { modifier |= Qt::ControlModifier; }
454 else if ( s ==
"AltModifier" ) { modifier |= Qt::AltModifier; }
455 else if ( s ==
"MetaModifier" ) { modifier |= Qt::MetaModifier; }
456 else if ( s ==
"KeypadModifier" ) { modifier |= Qt::KeypadModifier; }
457 else if ( s ==
"GroupSwitchModifier" ) { modifier |= Qt::GroupSwitchModifier; }
463 Qt::MouseButtons KeyMappingManager::getQtMouseButtonsValue(
const std::string& keyString ) {
464 Qt::MouseButtons key = Qt::NoButton;
466 if ( keyString ==
"LeftButton" ) { key = Qt::LeftButton; }
467 else if ( keyString ==
"RightButton" ) { key = Qt::RightButton; }
468 else if ( keyString ==
"MidButton" || keyString ==
"MiddleButton" ) { key = Qt::MiddleButton; }
469 else if ( keyString ==
"XButton1" ) { key = Qt::XButton1; }
470 else if ( keyString ==
"XButton2" ) { key = Qt::XButton2; }
475 void KeyMappingManager::reloadConfiguration() {
476 QString filename = m_file->fileName();
477 loadConfiguration( filename.toStdString().c_str() );
480 KeyMappingManager::~KeyMappingManager() {
481 if ( m_file->isOpen() ) { m_file->close(); }
484 std::string KeyMappingManager::getHelpText() {
485 std::ostringstream text;
486 for (
const auto& context : m_contextNameToIndex ) {
487 std::string contextName { context.first };
488 auto end = contextName.find(
"Context" );
489 contextName = contextName.substr( 0, end );
490 text <<
"<h2>" << contextName <<
"</h2>\n";
492 for (
const auto& action : m_bindingToAction[context.second] ) {
493 const auto& binding = action.first;
494 const auto& actionIndex = action.second;
495 const auto& actionNames = m_actionNameToIndex[context.second];
496 const auto& actionName =
497 std::find_if( actionNames.begin(),
499 [&actionIndex](
const auto& a ) { return a.second == actionIndex; } );
500 if ( actionName != actionNames.end() ) {
502 text <<
"<b>" << actionName->first <<
"</b>";
504 if ( binding.m_buttons != Qt::NoButton )
505 text <<
"[" << enumNamesFromMouseButtons( binding.m_buttons ) <<
"] ";
506 if ( binding.m_wheel ) text <<
" [Wheel] ";
507 if ( binding.m_modifiers != Qt::NoModifier ) {
509 std::string modifierString {
"Modifier" };
510 auto modifiers = enumNamesFromKeyboardModifiers( binding.m_modifiers );
511 auto found = modifiers.find( modifierString );
512 while ( found != std::string::npos ) {
513 modifiers.erase( found, modifierString.length() );
514 found = modifiers.find( modifierString, found );
516 text <<
"[" << modifiers <<
"] ";
518 if ( binding.m_key != -1 )
519 text <<
"[key: " << QKeySequence( binding.m_key ).toString().toStdString()
521 text <<
"</td></tr>\n";
524 text <<
"</table>\n";
529 #define TEST_BUTTON_STRING( BUTTON ) \
530 if ( buttons & Qt::BUTTON ) { \
531 returnText += sep + #BUTTON; \
535 std::string KeyMappingManager::enumNamesFromMouseButtons(
const Qt::MouseButtons& buttons ) {
536 std::string returnText;
539 if ( buttons == Qt::NoButton )
return "NoButton";
541 TEST_BUTTON_STRING( LeftButton );
542 TEST_BUTTON_STRING( RightButton );
543 TEST_BUTTON_STRING( MiddleButton );
544 TEST_BUTTON_STRING( BackButton );
545 TEST_BUTTON_STRING( ForwardButton );
546 TEST_BUTTON_STRING( TaskButton );
547 TEST_BUTTON_STRING( ExtraButton4 );
548 TEST_BUTTON_STRING( ExtraButton5 );
549 TEST_BUTTON_STRING( ExtraButton6 );
550 TEST_BUTTON_STRING( ExtraButton7 );
551 TEST_BUTTON_STRING( ExtraButton8 );
552 TEST_BUTTON_STRING( ExtraButton9 );
553 TEST_BUTTON_STRING( ExtraButton10 );
554 TEST_BUTTON_STRING( ExtraButton11 );
555 TEST_BUTTON_STRING( ExtraButton12 );
556 TEST_BUTTON_STRING( ExtraButton13 );
557 TEST_BUTTON_STRING( ExtraButton14 );
558 TEST_BUTTON_STRING( ExtraButton15 );
559 TEST_BUTTON_STRING( ExtraButton16 );
560 TEST_BUTTON_STRING( ExtraButton17 );
561 TEST_BUTTON_STRING( ExtraButton18 );
562 TEST_BUTTON_STRING( ExtraButton19 );
563 TEST_BUTTON_STRING( ExtraButton20 );
564 TEST_BUTTON_STRING( ExtraButton21 );
565 TEST_BUTTON_STRING( ExtraButton22 );
566 TEST_BUTTON_STRING( ExtraButton23 );
567 TEST_BUTTON_STRING( ExtraButton24 );
573 KeyMappingManager::enumNamesFromKeyboardModifiers(
const Qt::KeyboardModifiers& buttons ) {
574 std::string returnText;
577 if ( buttons == Qt::NoModifier )
return "NoModifier";
579 TEST_BUTTON_STRING( ShiftModifier );
580 TEST_BUTTON_STRING( ControlModifier );
581 TEST_BUTTON_STRING( AltModifier );
582 TEST_BUTTON_STRING( MetaModifier );
583 TEST_BUTTON_STRING( KeypadModifier );
584 TEST_BUTTON_STRING( GroupSwitchModifier );
589 #undef TEST_BUTTON_STRING
592 KeyMappingManager::createEventBindingFromStrings(
const std::string& buttonsString,
593 const std::string& modifiersString,
594 const std::string& keyString,
595 const std::string& wheelString ) {
596 return { getQtMouseButtonsValue( buttonsString ),
597 getQtModifiersValue( modifiersString ),
598 getKeyCode( keyString ),
599 wheelString.compare(
"true" ) == 0 };
Inner class to store event binding.
An utility class used to map a (combination of) key / modifiers / mouse buttons to a specific action....
Ra::Core::Utils::Index KeyMappingAction
handle to an action
Ra::Core::Utils::Index Context
handle to a Context