1#include "KeyMappingManager.hpp"
3#include <Core/Resources/Resources.hpp>
4#include <Core/Utils/Log.hpp>
10using namespace Core::Utils;
12KeyMappingManager::KeyMappingManager() : m_file( nullptr ) {
14 auto optionalPath { Core::Resources::getRadiumResourcesPath() };
15 auto resourcesRootDir { optionalPath.value_or(
"[[Default resrouces path not found]]" ) };
18 m_defaultConfigFile = resourcesRootDir +
std::string(
"Configs/default.xml" );
21 QString keyMappingFilename =
22 settings.value(
"keymapping/config", QString::fromStdString( m_defaultConfigFile ) )
24 if ( !keyMappingFilename.contains( QString::fromStdString( m_defaultConfigFile ) ) ) {
25 LOG( logDEBUG ) <<
"Loading key mapping " << keyMappingFilename.toStdString() <<
" (from "
26 << settings.fileName().toStdString() <<
")";
28 else { LOG( logDEBUG ) <<
"Loading default key mapping " << m_defaultConfigFile; }
29 loadConfiguration( keyMappingFilename.toStdString() );
32KeyMappingManager::KeyMappingAction
33KeyMappingManager::getAction(
const KeyMappingManager::Context& context,
38 Qt::MouseButtons buttons;
39 Qt::KeyboardModifiers modifiers;
40 const QMouseEvent* mouseEvent =
dynamic_cast<const QMouseEvent*
>( event );
42 buttons = mouseEvent->buttons();
43 modifiers = mouseEvent->modifiers();
45 return getAction( context, buttons, modifiers, key, wheel );
48KeyMappingManager::KeyMappingAction
50 const Qt::MouseButtons& buttons,
51 const Qt::KeyboardModifiers& modifiers,
55 if ( context.isInvalid() ) {
56 LOG( logDEBUG ) <<
"try to get action from an invalid context";
60 if ( ( key == Qt::Key_Shift ) || ( key == Qt::Key_Control ) || ( key == Qt::Key_Alt ) ||
61 ( key == Qt::Key_Meta ) ) {
65 return getAction( context,
EventBinding { buttons, modifiers, key, wheel } );
71 auto action = m_bindingToAction[context].find( binding );
72 if ( action != m_bindingToAction[context].end() ) {
return action->second; }
77std::optional<KeyMappingManager::EventBinding>
80 for (
const auto& [key, value] : m_bindingToAction[context] )
81 if ( value == action )
return key;
86 Ra::Core::Utils::Index contextIndex;
88 auto contextItr = m_contextNameToIndex.find( contextName );
89 if ( contextItr == m_contextNameToIndex.end() ) {
90 contextIndex = m_contextNameToIndex.size();
91 m_contextNameToIndex[contextName] = contextIndex;
92 m_actionNameToIndex.emplace_back();
93 m_bindingToAction.emplace_back();
94 CORE_ASSERT( m_actionNameToIndex.size() ==
size_t( contextIndex + 1 ),
95 "Corrupted actionName DB" );
96 CORE_ASSERT( m_bindingToAction.size() ==
size_t( contextIndex + 1 ),
97 "Corrupted mappingAction DB" );
100 contextIndex = contextItr->second;
107 auto itr = m_contextNameToIndex.find( contextName );
108 if ( itr != m_contextNameToIndex.end() )
return itr->second;
114 if (
size_t( context ) >= m_actionNameToIndex.size() || context.isInvalid() ) {
115 LOG( logWARNING ) <<
"try to get action index ( " << actionName
116 <<
" ) from an invalid context ( " << context <<
" )";
120 auto itr = m_actionNameToIndex[context].find( actionName );
121 if ( itr != m_actionNameToIndex[context].end() )
return itr->second;
122 LOG( logWARNING ) <<
"try to get action index from an invalid action name " << actionName
123 <<
" (context " << getContextName( context ) <<
" [" << context <<
"])";
124 LOG( logWARNING ) <<
"consider add to conf: "
125 <<
"<keymap context=\"" << getContextName( context )
126 <<
"\" key=\"\" modifiers=\"\" buttons=\"\" action=\"" << actionName
135 if (
size_t( context ) < m_actionNameToIndex.size() && context.isValid() ) {
139 std::end( m_actionNameToIndex[context] ),
140 [&](
const ActionNameMap::value_type& pair ) {
return pair.second == action; } );
142 if ( actionFindItr !=
std::end( m_actionNameToIndex[context] ) ) {
143 return actionFindItr->first;
153 [&](
const ContextNameMap ::value_type& pair ) {
return pair.second == context; } );
155 if ( contextFindItr !=
std::end( m_contextNameToIndex ) ) {
return contextFindItr->first; }
159int KeyMappingManager::addListener( Observer callback ) {
160 auto gid = attach( callback );
167void KeyMappingManager::removeListener(
int callbackId ) {
168 detach( callbackId );
171void KeyMappingManager::setActionBinding(
const Context& contextIndex,
175 CORE_ASSERT(
size_t( contextIndex ) < m_contextNameToIndex.size(),
176 "contextIndex is out of range" );
179 auto f = m_bindingToAction[contextIndex].find( binding );
180 if ( f != m_bindingToAction[contextIndex].end() ) {
184 std::begin( m_actionNameToIndex[contextIndex] ),
185 std::end( m_actionNameToIndex[contextIndex] ),
186 [&](
const ActionNameMap::value_type& pair ) {
return pair.second == actionIndex; } );
189 if ( findResult ==
std::end( m_actionNameToIndex[contextIndex] ) ) {
190 LOG( logERROR ) <<
"Corrupted call to bindKeyToAction, index " << actionIndex
191 <<
" must have been inserted before !\n";
197 std::begin( m_actionNameToIndex[contextIndex] ),
198 std::end( m_actionNameToIndex[contextIndex] ),
199 [&](
const ActionNameMap::value_type& pair ) {
return pair.second == f->second; } );
202 if ( findResult2 ==
std::end( m_actionNameToIndex[contextIndex] ) ) {
203 LOG( logERROR ) <<
"Corrupted call to bindKeyToAction, index " << actionIndex
204 <<
" must have been inserted before !\n";
208 LOG( logWARNING ) <<
"Binding action " << findResult->first <<
" to "
209 <<
"buttons [" << enumNamesFromMouseButtons( binding.m_buttons ) <<
"] "
210 <<
"modifiers [" << enumNamesFromKeyboardModifiers( binding.m_modifiers )
212 <<
" keycode [" << binding.m_key <<
"]"
213 <<
" wheel [" << binding.m_wheel <<
"]"
214 <<
", which is already used for action " << findResult2->first <<
".";
217 LOG( logDEBUG2 ) <<
"In context " << getContextName( contextIndex ) <<
" [" << contextIndex
219 <<
" binding action " << getActionName( contextIndex, actionIndex ) <<
" ["
220 << actionIndex <<
"]"
221 <<
" buttons [" << enumNamesFromMouseButtons( binding.m_buttons ) <<
"]"
222 <<
" modifiers [" << enumNamesFromKeyboardModifiers( binding.m_modifiers )
224 <<
" keycode [" << binding.m_key <<
"]"
225 <<
" wheel [" << binding.m_wheel <<
"]";
227 m_bindingToAction[contextIndex][binding] = actionIndex;
230void KeyMappingManager::loadConfiguration(
const std::string& inFilename ) {
233 if ( filename.
empty() ) { filename = m_defaultConfigFile.
c_str(); }
236 m_file =
new QFile( QString::fromStdString( filename ) );
238 if ( !m_file->open( QIODevice::ReadOnly ) ) {
239 if ( filename != m_defaultConfigFile ) {
240 LOG( logERROR ) <<
"Failed to open key mapping configuration file ! "
241 << m_file->fileName().toStdString();
242 LOG( logERROR ) <<
"Trying to load default configuration...";
247 LOG( logERROR ) <<
"Failed to open default key mapping configuration file !";
251 QDomDocument domDocument;
252 if ( !domDocument.setContent( m_file ) ) {
253 LOG( logERROR ) <<
"Can't associate XML file to QDomDocument !";
254 LOG( logERROR ) <<
"Trying to load default configuration...";
261 if ( filename != m_defaultConfigFile ) {
263 settings.setValue(
"keymapping/config", m_file->fileName() );
267 loadConfigurationInternal( domDocument );
273bool KeyMappingManager::saveConfiguration(
const std::string& inFilename ) {
274 QString filename { m_file->fileName() };
275 if ( !inFilename.
empty() ) { filename = QString::fromStdString( inFilename ); }
277 QFile saveTo( filename );
278 saveTo.open( QIODevice::WriteOnly );
279 QXmlStreamWriter stream( &saveTo );
280 stream.setAutoFormatting(
true );
281 stream.setAutoFormattingIndent( 4 );
282 stream.writeStartDocument();
283 stream.writeComment(
"\tRadium KeyMappingManager configuration file\t" );
285 "\n<keymap context=\"theContext\" action=\"theAction\" buttons=\"QButton\" "
286 "modifier=\"QModifier\" key=\"QKey\" wheel=\"boolean\"/>\n" );
288 saveKeymap( stream );
290 stream.writeEndDocument();
292 if ( stream.hasError() ) {
293 LOG( logERROR ) <<
"Fail to write Canonical XML.";
299void KeyMappingManager::saveKeymap( QXmlStreamWriter& stream ) {
302 auto saveAttrib = [&stream](
const QString& attribName,
303 const QString& attribValue,
304 const QString& attribDefault ) {
305 if ( attribValue != attribDefault ) stream.writeAttribute( attribName, attribValue );
308 if ( stream.hasError() ) {
return; }
310 stream.writeStartElement(
"keymaps" );
312 for (
const auto& contextPair : m_contextNameToIndex ) {
313 const auto& contextName = contextPair.first;
314 const auto& context = contextPair.second;
315 for (
const auto& actionPair : m_bindingToAction[context] ) {
316 const auto& binding = actionPair.first;
317 const auto& action = actionPair.second;
318 const auto& actionNames = m_actionNameToIndex[context];
320 std::find_if( actionNames.begin(), actionNames.end(), [&action](
const auto& a ) {
321 return a.second == action;
323 if ( actionItr != actionNames.end() ) {
324 const auto& actionName = actionItr->first;
325 stream.writeStartElement(
"keymap" );
326 stream.writeAttribute(
"context", QString::fromStdString( contextName ) );
327 stream.writeAttribute(
"action", QString::fromStdString( actionName ) );
331 QString::fromStdString( enumNamesFromMouseButtons( binding.m_buttons ) ),
332 QString::fromStdString( enumNamesFromMouseButtons( Qt::NoButton ) ) );
335 QString::fromStdString( enumNamesFromKeyboardModifiers( binding.m_modifiers ) ),
336 QString::fromStdString( enumNamesFromKeyboardModifiers( Qt::NoModifier ) ) );
337 saveAttrib(
"key",
"Key_" + QKeySequence( binding.m_key ).toString(),
"Key_" );
338 saveAttrib(
"wheel", binding.m_wheel ?
"true" :
"false",
"false" );
340 stream.writeEndElement();
345 stream.writeEndElement();
348void KeyMappingManager::loadConfigurationInternal(
const QDomDocument& domDocument ) {
351 m_contextNameToIndex.clear();
352 m_actionNameToIndex.clear();
353 m_bindingToAction.clear();
355 QDomElement domElement = domDocument.documentElement();
357 if ( domElement.tagName() !=
"keymaps" ) {
358 LOG( logWARNING ) <<
"No <keymaps> global bounding tag ! Maybe you set a different global "
359 "tag ? (Not a big deal)";
362 QDomNode node = domElement.firstChild();
363 while ( !node.isNull() ) {
364 if ( !node.isComment() ) {
365 QDomElement nodeElement = node.toElement();
366 loadConfigurationTagsInternal( nodeElement );
368 node = node.nextSibling();
372void KeyMappingManager::loadConfigurationTagsInternal( QDomElement& node ) {
373 if ( node.tagName() ==
"keymap" ) {
375 QDomElement e = node.toElement();
376 std::string keyString = e.attribute(
"key",
"-1" ).toStdString();
377 std::string modifiersString = e.attribute(
"modifiers",
"NoModifier" ).toStdString();
378 std::string buttonsString = e.attribute(
"buttons",
"NoButton" ).toStdString();
379 std::string contextName = e.attribute(
"context",
"AppContext" ).toStdString();
380 std::string wheelString = e.attribute(
"wheel",
"false" ).toStdString();
381 std::string actionName = e.attribute(
"action" ).toStdString();
383 loadConfigurationMappingInternal(
384 contextName, keyString, modifiersString, buttonsString, wheelString, actionName );
387 LOG( logERROR ) <<
"Unrecognized XML key mapping configuration file tag \""
388 << qPrintable( node.tagName() ) <<
"\" !";
389 LOG( logERROR ) <<
"Trying to load default configuration...";
398 Ra::Core::Utils::Index actionIndex;
399 auto actionItr = m_actionNameToIndex[context].find( actionName );
400 if ( actionItr == m_actionNameToIndex[context].end() ) {
401 actionIndex = m_actionNameToIndex[context].size();
402 m_actionNameToIndex[context][actionName] = actionIndex;
404 else { actionIndex = actionItr->second; }
412 auto actionIndex = addAction( context, actionName );
413 setActionBinding( context, binding, actionIndex );
417int KeyMappingManager::getKeyCode(
const std::string& keyString ) {
418 auto metaEnumKey = QMetaEnum::fromType<Qt::Key>();
419 return metaEnumKey.keyToValue( keyString.
c_str() );
423KeyMappingManager::loadConfigurationMappingInternal(
const std::string& context,
429 auto contextIndex = addContext( context );
430 auto actionIndex = addAction( contextIndex, actionName );
433 createEventBindingFromStrings( buttonsString, modifiersString, keyString, wheelString );
435 if ( binding.m_key == -1 && binding.m_buttons == Qt::NoButton && !binding.m_wheel ) {
436 LOG( logERROR ) <<
"Invalid binding for action [" << actionName <<
"] with key ["
437 << keyString <<
"], buttons [" << buttonsString <<
"], wheel ["
438 << wheelString <<
"]";
440 else { setActionBinding( contextIndex, binding, actionIndex ); }
444Qt::KeyboardModifiers KeyMappingManager::getQtModifiersValue(
const std::string& modifierString ) {
445 Qt::KeyboardModifiers modifier = Qt::NoModifier;
450 while (
getline( f, s,
',' ) ) {
451 if ( s ==
"ShiftModifier" ) { modifier |= Qt::ShiftModifier; }
452 else if ( s ==
"ControlModifier" ) { modifier |= Qt::ControlModifier; }
453 else if ( s ==
"AltModifier" ) { modifier |= Qt::AltModifier; }
454 else if ( s ==
"MetaModifier" ) { modifier |= Qt::MetaModifier; }
455 else if ( s ==
"KeypadModifier" ) { modifier |= Qt::KeypadModifier; }
456 else if ( s ==
"GroupSwitchModifier" ) { modifier |= Qt::GroupSwitchModifier; }
462Qt::MouseButtons KeyMappingManager::getQtMouseButtonsValue(
const std::string& keyString ) {
463 Qt::MouseButtons key = Qt::NoButton;
465 if ( keyString ==
"LeftButton" ) { key = Qt::LeftButton; }
466 else if ( keyString ==
"RightButton" ) { key = Qt::RightButton; }
467 else if ( keyString ==
"MidButton" || keyString ==
"MiddleButton" ) { key = Qt::MiddleButton; }
468 else if ( keyString ==
"XButton1" ) { key = Qt::XButton1; }
469 else if ( keyString ==
"XButton2" ) { key = Qt::XButton2; }
474void KeyMappingManager::reloadConfiguration() {
475 QString filename = m_file->fileName();
476 loadConfiguration( filename.toStdString().c_str() );
479KeyMappingManager::~KeyMappingManager() {
480 if ( m_file->isOpen() ) { m_file->close(); }
485 for (
const auto& context : m_contextNameToIndex ) {
487 auto end = contextName.
find(
"Context" );
488 contextName = contextName.
substr( 0, end );
489 text <<
"<h2>" << contextName <<
"</h2>\n";
491 for (
const auto& action : m_bindingToAction[context.second] ) {
492 const auto& binding = action.first;
493 const auto& actionIndex = action.second;
494 const auto& actionNames = m_actionNameToIndex[context.second];
495 const auto& actionName =
498 [&actionIndex](
const auto& a ) { return a.second == actionIndex; } );
499 if ( actionName != actionNames.
end() ) {
501 text <<
"<b>" << actionName->first <<
"</b>";
503 if ( binding.m_buttons != Qt::NoButton )
504 text <<
"[" << enumNamesFromMouseButtons( binding.m_buttons ) <<
"] ";
505 if ( binding.m_wheel ) text <<
" [Wheel] ";
506 if ( binding.m_modifiers != Qt::NoModifier ) {
509 auto modifiers = enumNamesFromKeyboardModifiers( binding.m_modifiers );
510 auto found = modifiers.find( modifierString );
511 while ( found != std::string::npos ) {
512 modifiers.erase( found, modifierString.
length() );
513 found = modifiers.find( modifierString, found );
515 text <<
"[" << modifiers <<
"] ";
517 if ( binding.m_key != -1 )
518 text <<
"[key: " << QKeySequence( binding.m_key ).toString().toStdString()
520 text <<
"</td></tr>\n";
523 text <<
"</table>\n";
528#define TEST_BUTTON_STRING( BUTTON ) \
529 if ( buttons & Qt::BUTTON ) { \
530 returnText += sep + #BUTTON; \
534std::string KeyMappingManager::enumNamesFromMouseButtons(
const Qt::MouseButtons& buttons ) {
538 if ( buttons == Qt::NoButton )
return "NoButton";
540 TEST_BUTTON_STRING( LeftButton );
541 TEST_BUTTON_STRING( RightButton );
542 TEST_BUTTON_STRING( MiddleButton );
543 TEST_BUTTON_STRING( BackButton );
544 TEST_BUTTON_STRING( ForwardButton );
545 TEST_BUTTON_STRING( TaskButton );
546 TEST_BUTTON_STRING( ExtraButton4 );
547 TEST_BUTTON_STRING( ExtraButton5 );
548 TEST_BUTTON_STRING( ExtraButton6 );
549 TEST_BUTTON_STRING( ExtraButton7 );
550 TEST_BUTTON_STRING( ExtraButton8 );
551 TEST_BUTTON_STRING( ExtraButton9 );
552 TEST_BUTTON_STRING( ExtraButton10 );
553 TEST_BUTTON_STRING( ExtraButton11 );
554 TEST_BUTTON_STRING( ExtraButton12 );
555 TEST_BUTTON_STRING( ExtraButton13 );
556 TEST_BUTTON_STRING( ExtraButton14 );
557 TEST_BUTTON_STRING( ExtraButton15 );
558 TEST_BUTTON_STRING( ExtraButton16 );
559 TEST_BUTTON_STRING( ExtraButton17 );
560 TEST_BUTTON_STRING( ExtraButton18 );
561 TEST_BUTTON_STRING( ExtraButton19 );
562 TEST_BUTTON_STRING( ExtraButton20 );
563 TEST_BUTTON_STRING( ExtraButton21 );
564 TEST_BUTTON_STRING( ExtraButton22 );
565 TEST_BUTTON_STRING( ExtraButton23 );
566 TEST_BUTTON_STRING( ExtraButton24 );
572KeyMappingManager::enumNamesFromKeyboardModifiers(
const Qt::KeyboardModifiers& buttons ) {
576 if ( buttons == Qt::NoModifier )
return "NoModifier";
578 TEST_BUTTON_STRING( ShiftModifier );
579 TEST_BUTTON_STRING( ControlModifier );
580 TEST_BUTTON_STRING( AltModifier );
581 TEST_BUTTON_STRING( MetaModifier );
582 TEST_BUTTON_STRING( KeypadModifier );
583 TEST_BUTTON_STRING( GroupSwitchModifier );
588#undef TEST_BUTTON_STRING
591KeyMappingManager::createEventBindingFromStrings(
const std::string& buttonsString,
595 return { getQtMouseButtonsValue( buttonsString ),
596 getQtModifiersValue( modifiersString ),
597 getKeyCode( keyString ),
598 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