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