Loading [MathJax]/extensions/TeX/AMSmath.js
Radium Engine  1.5.28
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
keymapping.cpp
1#include <Core/Resources/Resources.hpp>
2#include <Core/Utils/StdFilesystem.hpp>
3#include <Gui/Utils/KeyMappingManager.hpp>
4#include <catch2/catch_test_macros.hpp>
5
6#include <QtGlobal>
7
8using namespace Ra;
9using namespace Ra::Gui;
12
14class Dummy : public KeyMappingManageable<Dummy>
15{
16 friend class KeyMappingManageable<Dummy>;
17 static void configureKeyMapping_impl();
19
21
22 public:
24
25 private:
26 bool checkIntegrity( const std::string& mess ) const;
27 // here in public for testing purpose
28 public:
29#define KeyMappingDummy \
30 KMA_VALUE( TEST1 ) \
31 KMA_VALUE( TEST2 )
32
33#define KMA_VALUE( XX ) static KeyMappingManager::KeyMappingAction XX;
34 KeyMappingDummy
35#undef KMA_VALUE
36};
37
38#define KMA_VALUE( XX ) Gui::KeyMappingManager::KeyMappingAction Dummy::XX;
39KeyMappingDummy
40#undef KMA_VALUE
41
45
47void Dummy::configureKeyMapping_impl() {
49 Gui::KeyMappingManager::getInstance()->getContext( "DummyContext" ) );
50 if ( getContext().isInvalid() ) {
51 LOG( Ra::Core::Utils::logINFO )
52 << "DummyContext not defined (maybe the configuration file do not contains it)";
53 return;
54 }
55
56 m_myAction =
57 Ra::Gui::KeyMappingManager::getInstance()->getAction( getContext(), "MyActionName" );
59
60#define KMA_VALUE( XX ) \
61 XX = Gui::KeyMappingManager::getInstance()->getAction( \
62 KeyMappingManageable<Dummy>::getContext(), #XX );
63 KeyMappingDummy
64#undef KMA_VALUE
65}
66
67TEST_CASE( "Gui/Utils/KeyMappingManager", "[unittests][Gui][Gui/Utils][KeyMappingManager]" ) {
68 QCoreApplication::setOrganizationName( "RadiumUnitTests" );
69 QCoreApplication::setApplicationName( "KeyMappingManager" );
70 QSettings settings;
71 settings.clear();
72 KeyMappingManager::createInstance();
73 auto mgr = Gui::KeyMappingManager::getInstance();
74 auto optionalPath { Core::Resources::getRadiumResourcesPath() };
75 auto resourcesRootDir { optionalPath.value_or( "[[Default resrouces path not found]]" ) };
77 auto defaultConfigFile = resourcesRootDir + std::string( "Configs/default.xml" );
78
79 SECTION( "key mapping file load" ) {
80 REQUIRE( std::filesystem::path { defaultConfigFile } ==
81 std::filesystem::path { mgr->getLoadedFilename() } );
82 mgr->loadConfiguration( "dummy" );
83 REQUIRE( std::filesystem::path { defaultConfigFile } ==
84 std::filesystem::path { mgr->getLoadedFilename() } );
85 std::cout << std::filesystem::current_path() << "\n";
86 mgr->loadConfiguration( "data/keymapping-valid.xml" );
87 REQUIRE( "data/keymapping-valid.xml" == mgr->getLoadedFilename() );
88 // invalid xml should not be loaded, and switch to default
89 mgr->loadConfiguration( "data/keymapping-invalid.xml" );
90 REQUIRE( std::filesystem::path { defaultConfigFile } ==
91 std::filesystem::path { mgr->getLoadedFilename() } );
92 // while config error are loaded, with error message
93 mgr->loadConfiguration( "data/keymapping-double-actions.xml" );
94 REQUIRE( "data/keymapping-double-actions.xml" == mgr->getLoadedFilename() );
95 // bad tag load defaults
96 mgr->loadConfiguration( "data/keymapping-bad-tag.xml" );
97 REQUIRE( std::filesystem::path { defaultConfigFile } ==
98 std::filesystem::path { mgr->getLoadedFilename() } );
99 // bad main tag loads with a warning
100 mgr->loadConfiguration( "data/keymapping-bad-main.xml" );
101 REQUIRE( "data/keymapping-bad-main.xml" == mgr->getLoadedFilename() );
102 }
103
104 SECTION( "getAction" ) {
105 mgr->loadConfiguration( "data/keymapping-valid.xml" );
106 // check context
107 auto cameraContext { mgr->getContext( "CameraContext" ) };
108 REQUIRE( cameraContext.isValid() );
109 REQUIRE( mgr->getContextName( cameraContext ) == "CameraContext" );
110
111 auto viewerContext { mgr->getContext( "ViewerContext" ) };
112 REQUIRE( viewerContext.isValid() );
113 REQUIRE( viewerContext != cameraContext );
114
115 auto invalidContext { mgr->getContext( "InvalidContext" ) };
116 REQUIRE( invalidContext.isInvalid() );
117 // invalidContext index returns "Invalid" context name
118 REQUIRE( mgr->getContextName( Ctx {} ) == "Invalid" );
119 REQUIRE( mgr->getContextName( Ctx { 42 } ) == "Invalid" );
120
121 // test on action
122 auto validAction { mgr->getAction( cameraContext, Qt::LeftButton, Qt::NoModifier, -1 ) };
123 REQUIRE( validAction.isValid() );
124 REQUIRE( validAction == mgr->getAction( cameraContext, "TRACKBALLCAMERA_ROTATE" ) );
125 REQUIRE( mgr->getAction( Ctx {}, "TRACKBALLCAMERA_ROTATE" ).isInvalid() );
126 REQUIRE( mgr->getAction( Ctx { 42 }, "TRACKBALLCAMERA_ROTATE" ).isInvalid() );
127 REQUIRE( mgr->getActionName( cameraContext, validAction ) == "TRACKBALLCAMERA_ROTATE" );
128
129 // invalid action index returns "Invalid" action name
130 REQUIRE( mgr->getActionName( cameraContext, Idx {} ) == "Invalid" );
131
132 // modifiers as key are ignored
133 validAction = mgr->getAction( cameraContext, Qt::LeftButton, Qt::ShiftModifier, -1 );
134 auto action2 {
135 mgr->getAction( cameraContext, Qt::LeftButton, Qt::ShiftModifier, Qt::Key_Shift ) };
136 REQUIRE( validAction == action2 );
137
138 // tests some invalid actions
139 auto invalidAction { mgr->getAction( cameraContext, Qt::LeftButton, Qt::AltModifier, -1 ) };
140 REQUIRE( invalidAction.isInvalid() );
141 REQUIRE( invalidAction != mgr->getAction( cameraContext, "TRACKBALLCAMERA_ROTATE" ) );
142 REQUIRE( mgr->getAction( Idx {}, Qt::LeftButton, Qt::AltModifier, -1 ).isInvalid() );
143 REQUIRE( mgr->getAction( Idx {}, Qt::LeftButton, Qt::AltModifier, 1 ).isInvalid() );
144 REQUIRE( mgr->getAction( Idx {}, Qt::LeftButton, Qt::NoModifier, -1 ).isInvalid() );
145 REQUIRE( mgr->getAction( Idx {}, Qt::RightButton, Qt::AltModifier, -1 ).isInvalid() );
146
147 // with key and modifiers
148 validAction = mgr->getAction( viewerContext, Qt::RightButton, Qt::NoModifier, Qt::Key_V );
149 REQUIRE( validAction.isValid() );
150 REQUIRE( validAction == mgr->getAction( viewerContext, "VIEWER_PICKING_VERTEX" ) );
151
152 // action index
153 REQUIRE( mgr->getAction( viewerContext, "UnkownAction" ).isInvalid() );
154 validAction = mgr->getAction( viewerContext, Qt::NoButton, Qt::ControlModifier, Qt::Key_W );
155 REQUIRE( validAction.isValid() );
156 REQUIRE( validAction == mgr->getAction( viewerContext, "VIEWER_TOGGLE_WIREFRAME" ) );
157
158 // action are context dependent
159 invalidAction = mgr->getAction( viewerContext, Qt::LeftButton, Qt::NoModifier, -1 );
160 REQUIRE( invalidAction.isInvalid() );
161 REQUIRE( mgr->getAction( cameraContext, "VIEWER_TOGGLE_WIREFRAME" ).isInvalid() );
162 }
163
164 SECTION( "listener" ) {
165 // dummy1.xml
166 // LeftButton triggers TEST1, RightButton triggers TEST2
167 // (with index TEST1 : 0, TEST2: 1)
168 // dummy2.xml is the other way round dummy2.xml
169 // LeftButton triggers TEST2, RightButton triggers TEST1
170 // (with index TEST2: 0, TEST1: 1)
171
172 auto didx =
174 Gui::KeyMappingManager::getInstance()->addListener( Dummy::configureKeyMapping );
176
177 mgr->loadConfiguration( "data/dummy1.xml" );
178
179 // action index correspond to configuration
180 auto test1Idx = mgr->getAction( Dummy::getContext(), "TEST1" );
181 auto test2Idx = mgr->getAction( Dummy::getContext(), "TEST2" );
182
183 REQUIRE( test1Idx ==
184 mgr->getAction( Dummy::getContext(), Qt::LeftButton, Qt::NoModifier, -1 ) );
185 REQUIRE( test2Idx ==
186 mgr->getAction( Dummy::getContext(), Qt::RightButton, Qt::NoModifier, -1 ) );
187 // and set in Dummy class
188 REQUIRE( Dummy::TEST1 == test1Idx );
189 REQUIRE( Dummy::TEST2 == test2Idx );
190
191 // reload trigger configureKeyMapping and update binding/action index
192 mgr->loadConfiguration( "data/dummy2.xml" );
193 auto test1Idx2 = mgr->getAction( Dummy::getContext(), "TEST1" );
194 auto test2Idx2 = mgr->getAction( Dummy::getContext(), "TEST2" );
195
196 // action index have been updated in Dummy class (by observation)
197 REQUIRE( Dummy::TEST1 ==
198 mgr->getAction( Dummy::getContext(), Qt::RightButton, Qt::NoModifier, -1 ) );
199 REQUIRE( Dummy::TEST2 ==
200 mgr->getAction( Dummy::getContext(), Qt::LeftButton, Qt::NoModifier, -1 ) );
201 // and do not corresponds to the old one
202 REQUIRE( test1Idx != Dummy::TEST1 );
203 REQUIRE( test2Idx != Dummy::TEST2 );
204
205 // remove listener and relaod do not update action index
206 mgr->removeListener( didx );
207 mgr->loadConfiguration( "data/dummy1.xml" );
208 // action index have been reverted
209 REQUIRE( test1Idx ==
210 mgr->getAction( Dummy::getContext(), Qt::LeftButton, Qt::NoModifier, -1 ) );
211 REQUIRE( test2Idx ==
212 mgr->getAction( Dummy::getContext(), Qt::RightButton, Qt::NoModifier, -1 ) );
213 // but not updated in Dummy class
214 REQUIRE( Dummy::TEST1 != test1Idx );
215 REQUIRE( Dummy::TEST1 == test1Idx2 );
216 REQUIRE( Dummy::TEST2 != test2Idx );
217 REQUIRE( Dummy::TEST2 == test2Idx2 );
218 }
219
220 SECTION( "Custom context and actions" ) {
221 auto nonExistingcontext = mgr->getContext( "Keymapping::CustomContext" );
222 REQUIRE( nonExistingcontext.isInvalid() );
223 auto customContext = mgr->addContext( "Keymapping::CustomContext" );
224 REQUIRE( customContext.isValid() );
225 auto replicateContext = mgr->addContext( "Keymapping::CustomContext" );
226 REQUIRE( replicateContext == customContext );
227 auto customAction = mgr->addAction(
228 customContext, mgr->createEventBindingFromStrings( "", "", "Key_F1" ), "CustomAction" );
229 REQUIRE( customAction.isValid() );
230 auto customActionDuplicate = mgr->addAction(
231 customContext, mgr->createEventBindingFromStrings( "", "", "Key_F1" ), "CustomAction" );
232 REQUIRE( customActionDuplicate.isValid() );
233 REQUIRE( customActionDuplicate == customAction );
234 auto customActionOtherBinding = mgr->addAction(
235 customContext, mgr->createEventBindingFromStrings( "", "", "Key_F2" ), "CustomAction" );
236 REQUIRE( customActionOtherBinding.isValid() );
237 REQUIRE( customActionOtherBinding == customAction );
238 }
239
240 SECTION( "Get Binding" ) {
241 mgr->loadConfiguration( "data/keymapping-valid.xml" );
242 // check context
243 auto cameraContext { mgr->getContext( "CameraContext" ) };
244 REQUIRE( cameraContext.isValid() );
245 REQUIRE( mgr->getContextName( cameraContext ) == "CameraContext" );
246
247 // test on action
248 auto validAction { mgr->getAction( cameraContext, Qt::LeftButton, Qt::NoModifier, -1 ) };
249 auto validBinding { mgr->getBinding( cameraContext, validAction ) };
250 REQUIRE( validAction.isValid() );
251 REQUIRE( validBinding );
252 REQUIRE( validAction == mgr->getAction( cameraContext,
253 validBinding->m_buttons,
254 validBinding->m_modifiers,
255 validBinding->m_key ) );
256 REQUIRE( validBinding->m_buttons == Qt::LeftButton );
257 REQUIRE( validBinding->m_modifiers == Qt::NoModifier );
258 REQUIRE( validBinding->m_key == -1 );
259 REQUIRE( validBinding->m_wheel == false );
260
261 auto invalidBinding { mgr->getBinding( cameraContext, {} ) };
262 REQUIRE( !invalidBinding );
263
264 KeyMappingManager::EventBinding mouseEvent { Qt::MouseButtons { Qt::LeftButton },
265 Qt::ShiftModifier };
266 REQUIRE( mouseEvent.isMouseEvent() );
267
268 KeyMappingManager::EventBinding wheelEvent { true, Qt::ControlModifier };
269 REQUIRE( wheelEvent.isWheelEvent() );
270
271 KeyMappingManager::EventBinding keyEvent { Qt::Key_Bar, Qt::MetaModifier };
272 REQUIRE( keyEvent.isKeyEvent() );
273
274 KeyMappingManager::EventBinding combinedEvent {
275 Qt::LeftButton, Qt::MetaModifier, Qt::Key_Bar };
276 REQUIRE( ( !combinedEvent.isKeyEvent() && combinedEvent.isMouseEvent() ) );
277 }
278}
[Declare KeyMappingManageable]
Dummy()
[Declare KeyMappingManageable]
KeyMappingManageable decorator to use as CRTP.
Inner class to store event binding.
Ra::Core::Utils::Index KeyMappingAction
handle to an action
Ra::Core::Utils::Index Context
handle to a Context
optional< std::string > getRadiumResourcesPath()
Get the path of Radium internal resources.
Definition Resources.cpp:33
hepler function to manage enum as underlying types in VariableSet
Definition Cage.cpp:4