src/platform/qt/ShortcutController.cpp (view raw)
1/* Copyright (c) 2013-2015 Jeffrey Pfau
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6#include "ShortcutController.h"
7
8#include "ConfigController.h"
9#include "GamepadButtonEvent.h"
10#include "InputProfile.h"
11
12#include <QAction>
13#include <QKeyEvent>
14#include <QMenu>
15#include <QRegularExpression>
16
17using namespace QGBA;
18
19ShortcutController::ShortcutController(QObject* parent)
20 : QObject(parent)
21{
22}
23
24void ShortcutController::setConfigController(ConfigController* controller) {
25 m_config = controller;
26}
27
28void ShortcutController::setActionMapper(ActionMapper* actions) {
29 m_actions = actions;
30 connect(actions, &ActionMapper::actionAdded, this, &ShortcutController::generateItem);
31 connect(actions, &ActionMapper::menuCleared, this, &ShortcutController::menuCleared);
32 rebuildItems();
33}
34
35void ShortcutController::updateKey(const QString& name, int keySequence) {
36 auto item = m_items[name];
37 if (!item) {
38 return;
39 }
40 updateKey(item, keySequence);
41 if (m_config) {
42 m_config->setQtOption(item->name(), QKeySequence(keySequence).toString(), KEY_SECTION);
43 }
44}
45
46void ShortcutController::updateKey(std::shared_ptr<Shortcut> item, int keySequence) {
47 int oldShortcut = item->shortcut();
48 if (m_actions->isHeld(item->name())) {
49 if (oldShortcut > 0) {
50 m_heldKeys.take(oldShortcut);
51 }
52 if (keySequence > 0) {
53 m_heldKeys[keySequence] = item;
54 }
55 }
56
57 item->setShortcut(keySequence);
58}
59
60void ShortcutController::updateButton(const QString& name, int button) {
61 auto item = m_items[name];
62 if (!item) {
63 return;
64 }
65 int oldButton = item->button();
66 if (oldButton >= 0) {
67 m_buttons.take(oldButton);
68 }
69 item->setButton(button);
70 if (button >= 0) {
71 clearAxis(name);
72 m_buttons[button] = item;
73 }
74 if (m_config) {
75 m_config->setQtOption(name, button, BUTTON_SECTION);
76 if (!m_profileName.isNull()) {
77 m_config->setQtOption(name, button, BUTTON_PROFILE_SECTION + m_profileName);
78 }
79 }
80}
81
82void ShortcutController::updateAxis(const QString& name, int axis, GamepadAxisEvent::Direction direction) {
83 auto item = m_items[name];
84 if (!item) {
85 return;
86 }
87 int oldAxis = item->axis();
88 GamepadAxisEvent::Direction oldDirection = item->direction();
89 if (oldAxis >= 0) {
90 m_axes.take(std::make_pair(oldAxis, oldDirection));
91 }
92 if (axis >= 0 && direction != GamepadAxisEvent::NEUTRAL) {
93 clearButton(name);
94 m_axes[std::make_pair(axis, direction)] = item;
95 }
96 item->setAxis(axis, direction);
97 if (m_config) {
98 char d = '\0';
99 if (direction == GamepadAxisEvent::POSITIVE) {
100 d = '+';
101 }
102 if (direction == GamepadAxisEvent::NEGATIVE) {
103 d = '-';
104 }
105 m_config->setQtOption(name, QString("%1%2").arg(d).arg(axis), AXIS_SECTION);
106 if (!m_profileName.isNull()) {
107 m_config->setQtOption(name, QString("%1%2").arg(d).arg(axis), AXIS_PROFILE_SECTION + m_profileName);
108 }
109 }
110}
111
112void ShortcutController::clearKey(const QString& name) {
113 updateKey(name, 0);
114}
115
116void ShortcutController::clearButton(const QString& name) {
117 updateButton(name, -1);
118}
119
120void ShortcutController::clearAxis(const QString& name) {
121 updateAxis(name, -1, GamepadAxisEvent::NEUTRAL);
122}
123
124void ShortcutController::rebuildItems() {
125 m_items.clear();
126 m_buttons.clear();
127 m_axes.clear();
128 m_heldKeys.clear();
129 onSubitems({}, std::bind(&ShortcutController::generateItem, this, std::placeholders::_1));
130}
131
132bool ShortcutController::eventFilter(QObject*, QEvent* event) {
133 if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
134 QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
135 if (keyEvent->isAutoRepeat()) {
136 return false;
137 }
138 int key = keyEvent->key();
139 if (!isModifierKey(key)) {
140 key |= (keyEvent->modifiers() & ~Qt::KeypadModifier);
141 } else {
142 key = toModifierKey(key | (keyEvent->modifiers() & ~Qt::KeypadModifier));
143 }
144 auto item = m_heldKeys.find(key);
145 if (item != m_heldKeys.end()) {
146 Action::BooleanFunction fn = item.value()->action()->booleanAction();
147 fn(event->type() == QEvent::KeyPress);
148 event->accept();
149 return true;
150 }
151 }
152 if (event->type() == GamepadButtonEvent::Down()) {
153 auto item = m_buttons.find(static_cast<GamepadButtonEvent*>(event)->value());
154 if (item == m_buttons.end()) {
155 return false;
156 }
157 Action* action = item.value()->action();
158 if (action) {
159 action->trigger();
160 }
161 event->accept();
162 return true;
163 }
164 if (event->type() == GamepadButtonEvent::Up()) {
165 auto item = m_buttons.find(static_cast<GamepadButtonEvent*>(event)->value());
166 if (item == m_buttons.end()) {
167 return false;
168 }
169 Action* action = item.value()->action();
170 if (action) {
171 action->trigger(false);
172 }
173 event->accept();
174 return true;
175 }
176 if (event->type() == GamepadAxisEvent::Type()) {
177 GamepadAxisEvent* gae = static_cast<GamepadAxisEvent*>(event);
178 auto item = m_axes.find(std::make_pair(gae->axis(), gae->direction()));
179 if (item == m_axes.end()) {
180 return false;
181 }
182 Action* action = item.value()->action();
183 if (action) {
184 action->trigger(gae->isNew());
185 }
186 event->accept();
187 return true;
188 }
189 return false;
190}
191
192void ShortcutController::generateItem(const QString& itemName) {
193 if (itemName.isNull() || itemName[0] == '.') {
194 return;
195 }
196 Action* action = m_actions->getAction(itemName);
197 if (action) {
198 std::shared_ptr<Shortcut> item = std::make_shared<Shortcut>(action);
199 m_items[itemName] = item;
200 loadShortcuts(item);
201 }
202 emit shortcutAdded(itemName);
203}
204
205bool ShortcutController::loadShortcuts(std::shared_ptr<Shortcut> item) {
206 if (item->name().isNull()) {
207 return false;
208 }
209 loadGamepadShortcuts(item);
210 QVariant shortcut = m_config->getQtOption(item->name(), KEY_SECTION);
211 if (!shortcut.isNull()) {
212 if (shortcut.toString().endsWith("+")) {
213 updateKey(item, toModifierShortcut(shortcut.toString()));
214 } else {
215 updateKey(item, QKeySequence(shortcut.toString())[0]);
216 }
217 return true;
218 } else {
219 QKeySequence defaultShortcut = m_actions->defaultShortcut(item->name());
220 if (!defaultShortcut.isEmpty()) {
221 updateKey(item, defaultShortcut[0]);
222 return true;
223 }
224 }
225 return false;
226}
227
228void ShortcutController::loadGamepadShortcuts(std::shared_ptr<Shortcut> item) {
229 if (item->name().isNull()) {
230 return;
231 }
232 QVariant button = m_config->getQtOption(item->name(), !m_profileName.isNull() ? BUTTON_PROFILE_SECTION + m_profileName : BUTTON_SECTION);
233 int oldButton = item->button();
234 if (oldButton >= 0) {
235 m_buttons.take(oldButton);
236 item->setButton(-1);
237 }
238 if (button.isNull() && m_profile) {
239 int buttonInt;
240 if (m_profile->lookupShortcutButton(item->name(), &buttonInt)) {
241 button = buttonInt;
242 }
243 }
244 if (!button.isNull()) {
245 item->setButton(button.toInt());
246 m_buttons[button.toInt()] = item;
247 }
248
249 QVariant axis = m_config->getQtOption(item->name(), !m_profileName.isNull() ? AXIS_PROFILE_SECTION + m_profileName : AXIS_SECTION);
250 int oldAxis = item->axis();
251 GamepadAxisEvent::Direction oldDirection = item->direction();
252 if (oldAxis >= 0) {
253 m_axes.take(std::make_pair(oldAxis, oldDirection));
254 item->setAxis(-1, GamepadAxisEvent::NEUTRAL);
255 }
256 if (axis.isNull() && m_profile) {
257 int axisInt;
258 GamepadAxisEvent::Direction direction;
259 if (m_profile->lookupShortcutAxis(item->name(), &axisInt, &direction)) {
260 axis = QLatin1String(direction == GamepadAxisEvent::Direction::NEGATIVE ? "-" : "+") + QString::number(axisInt);
261 }
262 }
263 if (!axis.isNull()) {
264 QString axisDesc = axis.toString();
265 if (axisDesc.size() >= 2) {
266 GamepadAxisEvent::Direction direction = GamepadAxisEvent::NEUTRAL;
267 if (axisDesc[0] == '-') {
268 direction = GamepadAxisEvent::NEGATIVE;
269 }
270 if (axisDesc[0] == '+') {
271 direction = GamepadAxisEvent::POSITIVE;
272 }
273 bool ok;
274 int axis = axisDesc.mid(1).toInt(&ok);
275 if (ok) {
276 item->setAxis(axis, direction);
277 m_axes[std::make_pair(axis, direction)] = item;
278 }
279 }
280 }
281}
282
283void ShortcutController::loadProfile(const QString& profile) {
284 m_profileName = profile;
285 m_profile = InputProfile::findProfile(profile);
286 onSubitems({}, [this](std::shared_ptr<Shortcut> item) {
287 loadGamepadShortcuts(item);
288 });
289}
290
291void ShortcutController::onSubitems(const QString& menu, std::function<void(std::shared_ptr<Shortcut>)> func) {
292 for (const QString& subitem : m_actions->menuItems(menu)) {
293 auto item = m_items[subitem];
294 if (item) {
295 func(item);
296 }
297 if (subitem.size() && subitem[0] == '.') {
298 onSubitems(subitem.mid(1), func);
299 }
300 }
301}
302
303void ShortcutController::onSubitems(const QString& menu, std::function<void(const QString&)> func) {
304 for (const QString& subitem : m_actions->menuItems(menu)) {
305 func(subitem);
306 if (subitem.size() && subitem[0] == '.') {
307 onSubitems(subitem.mid(1), func);
308 }
309 }
310}
311
312int ShortcutController::toModifierShortcut(const QString& shortcut) {
313 // Qt doesn't seem to work with raw modifier shortcuts!
314 QStringList modifiers = shortcut.split('+');
315 int value = 0;
316 for (const auto& mod : modifiers) {
317 if (mod == QLatin1String("Shift")) {
318 value |= Qt::ShiftModifier;
319 continue;
320 }
321 if (mod == QLatin1String("Ctrl")) {
322 value |= Qt::ControlModifier;
323 continue;
324 }
325 if (mod == QLatin1String("Alt")) {
326 value |= Qt::AltModifier;
327 continue;
328 }
329 if (mod == QLatin1String("Meta")) {
330 value |= Qt::MetaModifier;
331 continue;
332 }
333 }
334 return value;
335}
336
337bool ShortcutController::isModifierKey(int key) {
338 switch (key) {
339 case Qt::Key_Shift:
340 case Qt::Key_Control:
341 case Qt::Key_Alt:
342 case Qt::Key_Meta:
343 return true;
344 default:
345 return false;
346 }
347}
348
349int ShortcutController::toModifierKey(int key) {
350 int modifiers = key & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier);
351 key ^= modifiers;
352 switch (key) {
353 case Qt::Key_Shift:
354 modifiers |= Qt::ShiftModifier;
355 break;
356 case Qt::Key_Control:
357 modifiers |= Qt::ControlModifier;
358 break;
359 case Qt::Key_Alt:
360 modifiers |= Qt::AltModifier;
361 break;
362 case Qt::Key_Meta:
363 modifiers |= Qt::MetaModifier;
364 break;
365 default:
366 break;
367 }
368 return modifiers;
369
370}
371
372const Shortcut* ShortcutController::shortcut(const QString& action) const {
373 return m_items[action].get();
374}
375
376QString ShortcutController::name(int index, const QString& parent) const {
377 QStringList menu = m_actions->menuItems(parent.isNull() || parent[0] != '.' ? parent : parent.mid(1));
378 menu.removeAll({});
379 if (index >= menu.size()) {
380 return {};
381 }
382
383 return menu[index];
384}
385
386QString ShortcutController::parent(const QString& action) const {
387 return QString(".%1").arg(m_actions->menuFor(action));
388}
389
390QString ShortcutController::visibleName(const QString& action) const {
391 if (action.isNull()) {
392 return {};
393 }
394 QString name;
395 if (action[0] == '.') {
396 name = m_actions->menuName(action.mid(1));
397 } else {
398 name = m_actions->getAction(action)->visibleName();
399 }
400 return name.replace(QRegularExpression("&(.)"), "\\1");
401}
402
403int ShortcutController::indexIn(const QString& action) const {
404 QString name = m_actions->menuFor(action);
405 QStringList menu = m_actions->menuItems(name);
406 menu.removeAll({});
407 return menu.indexOf(action);
408}
409
410int ShortcutController::count(const QString& name) const {
411 QStringList menu;
412 if (name.isNull()) {
413 menu = m_actions->menuItems();
414 } else if (name[0] != '.') {
415 return 0;
416 } else {
417 menu = m_actions->menuItems(name.mid(1));
418 }
419 menu.removeAll({});
420 return menu.count();
421}
422
423Shortcut::Shortcut(Action* action)
424 : m_action(action)
425{
426}
427
428void Shortcut::setShortcut(int shortcut) {
429 if (m_shortcut == shortcut) {
430 return;
431 }
432 m_shortcut = shortcut;
433 emit shortcutChanged(shortcut);
434}
435
436void Shortcut::setButton(int button) {
437 if (m_button == button) {
438 return;
439 }
440 m_button = button;
441 emit buttonChanged(button);
442}
443
444void Shortcut::setAxis(int axis, GamepadAxisEvent::Direction direction) {
445 if (m_axis == axis && m_direction == direction) {
446 return;
447 }
448 m_axis = axis;
449 m_direction = direction;
450 emit axisChanged(axis, direction);
451}