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
16using namespace QGBA;
17
18ShortcutController::ShortcutController(QObject* parent)
19 : QAbstractItemModel(parent)
20 , m_rootMenu(nullptr)
21 , m_config(nullptr)
22 , m_profile(nullptr)
23{
24}
25
26void ShortcutController::setConfigController(ConfigController* controller) {
27 m_config = controller;
28}
29
30QVariant ShortcutController::data(const QModelIndex& index, int role) const {
31 if (role != Qt::DisplayRole || !index.isValid()) {
32 return QVariant();
33 }
34 int row = index.row();
35 const ShortcutItem* item = static_cast<const ShortcutItem*>(index.internalPointer());
36 switch (index.column()) {
37 case 0:
38 return item->visibleName();
39 case 1:
40 return QKeySequence(item->shortcut()).toString(QKeySequence::NativeText);
41 case 2:
42 if (item->button() >= 0) {
43 return item->button();
44 }
45 if (item->axis() >= 0) {
46 char d = '\0';
47 if (item->direction() == GamepadAxisEvent::POSITIVE) {
48 d = '+';
49 }
50 if (item->direction() == GamepadAxisEvent::NEGATIVE) {
51 d = '-';
52 }
53 return QString("%1%2").arg(d).arg(item->axis());
54 }
55 break;
56 }
57 return QVariant();
58}
59
60QVariant ShortcutController::headerData(int section, Qt::Orientation orientation, int role) const {
61 if (role != Qt::DisplayRole) {
62 return QAbstractItemModel::headerData(section, orientation, role);
63 }
64 if (orientation == Qt::Horizontal) {
65 switch (section) {
66 case 0:
67 return tr("Action");
68 case 1:
69 return tr("Keyboard");
70 case 2:
71 return tr("Gamepad");
72 }
73 }
74 return section;
75}
76
77QModelIndex ShortcutController::index(int row, int column, const QModelIndex& parent) const {
78 const ShortcutItem* pmenu = &m_rootMenu;
79 if (parent.isValid()) {
80 pmenu = static_cast<ShortcutItem*>(parent.internalPointer());
81 }
82 return createIndex(row, column, const_cast<ShortcutItem*>(&pmenu->items()[row]));
83}
84
85QModelIndex ShortcutController::parent(const QModelIndex& index) const {
86 if (!index.isValid() || !index.internalPointer()) {
87 return QModelIndex();
88 }
89 ShortcutItem* item = static_cast<ShortcutItem*>(index.internalPointer());
90 if (!item->parent() || !item->parent()->parent()) {
91 return QModelIndex();
92 }
93 return createIndex(item->parent()->parent()->items().indexOf(*item->parent()), 0, item->parent());
94}
95
96int ShortcutController::columnCount(const QModelIndex& index) const {
97 return 3;
98}
99
100int ShortcutController::rowCount(const QModelIndex& index) const {
101 if (!index.isValid()) {
102 return m_rootMenu.items().count();
103 }
104 const ShortcutItem* item = static_cast<const ShortcutItem*>(index.internalPointer());
105 return item->items().count();
106}
107
108void ShortcutController::addAction(QMenu* menu, QAction* action, const QString& name) {
109 ShortcutItem* smenu = m_menuMap[menu];
110 if (!smenu) {
111 return;
112 }
113 ShortcutItem* pmenu = smenu->parent();
114 int row = pmenu->items().indexOf(*smenu);
115 QModelIndex parent = createIndex(row, 0, smenu);
116 beginInsertRows(parent, smenu->items().count(), smenu->items().count());
117 smenu->addAction(action, name);
118 endInsertRows();
119 ShortcutItem* item = &smenu->items().last();
120 if (m_config) {
121 loadShortcuts(item);
122 }
123 emit dataChanged(createIndex(smenu->items().count() - 1, 0, item),
124 createIndex(smenu->items().count() - 1, 2, item));
125}
126
127void ShortcutController::addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release,
128 int shortcut, const QString& visibleName, const QString& name) {
129 ShortcutItem* smenu = m_menuMap[menu];
130 if (!smenu) {
131 return;
132 }
133 ShortcutItem* pmenu = smenu->parent();
134 int row = pmenu->items().indexOf(*smenu);
135 QModelIndex parent = createIndex(row, 0, smenu);
136 beginInsertRows(parent, smenu->items().count(), smenu->items().count());
137 smenu->addFunctions(qMakePair(press, release), shortcut, visibleName, name);
138 endInsertRows();
139 ShortcutItem* item = &smenu->items().last();
140 bool loadedShortcut = false;
141 if (m_config) {
142 loadedShortcut = loadShortcuts(item);
143 }
144 if (!loadedShortcut && !m_heldKeys.contains(shortcut)) {
145 m_heldKeys[shortcut] = item;
146 }
147 emit dataChanged(createIndex(smenu->items().count() - 1, 0, item),
148 createIndex(smenu->items().count() - 1, 2, item));
149}
150
151void ShortcutController::addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release,
152 const QKeySequence& shortcut, const QString& visibleName, const QString& name) {
153 addFunctions(menu, press, release, shortcut[0], visibleName, name);
154}
155
156void ShortcutController::addMenu(QMenu* menu, QMenu* parentMenu) {
157 ShortcutItem* smenu = m_menuMap[parentMenu];
158 if (!smenu) {
159 smenu = &m_rootMenu;
160 }
161 QModelIndex parent;
162 ShortcutItem* pmenu = smenu->parent();
163 if (pmenu) {
164 int row = pmenu->items().indexOf(*smenu);
165 parent = createIndex(row, 0, smenu);
166 }
167 beginInsertRows(parent, smenu->items().count(), smenu->items().count());
168 smenu->addSubmenu(menu);
169 endInsertRows();
170 ShortcutItem* item = &smenu->items().last();
171 emit dataChanged(createIndex(smenu->items().count() - 1, 0, item),
172 createIndex(smenu->items().count() - 1, 2, item));
173 m_menuMap[menu] = item;
174}
175
176ShortcutController::ShortcutItem* ShortcutController::itemAt(const QModelIndex& index) {
177 if (!index.isValid()) {
178 return nullptr;
179 }
180 return static_cast<ShortcutItem*>(index.internalPointer());
181}
182
183const ShortcutController::ShortcutItem* ShortcutController::itemAt(const QModelIndex& index) const {
184 if (!index.isValid()) {
185 return nullptr;
186 }
187 return static_cast<const ShortcutItem*>(index.internalPointer());
188}
189
190int ShortcutController::shortcutAt(const QModelIndex& index) const {
191 const ShortcutItem* item = itemAt(index);
192 if (!item) {
193 return 0;
194 }
195 return item->shortcut();
196}
197
198bool ShortcutController::isMenuAt(const QModelIndex& index) const {
199 const ShortcutItem* item = itemAt(index);
200 if (!item) {
201 return false;
202 }
203 return item->menu();
204}
205
206void ShortcutController::updateKey(const QModelIndex& index, int keySequence) {
207 if (!index.isValid()) {
208 return;
209 }
210 const QModelIndex& parent = index.parent();
211 if (!parent.isValid()) {
212 return;
213 }
214 ShortcutItem* item = itemAt(index);
215 updateKey(item, keySequence);
216 if (m_config) {
217 m_config->setQtOption(item->name(), QKeySequence(keySequence).toString(), KEY_SECTION);
218 }
219 emit dataChanged(createIndex(index.row(), 0, index.internalPointer()),
220 createIndex(index.row(), 2, index.internalPointer()));
221}
222
223void ShortcutController::updateKey(ShortcutItem* item, int keySequence) {
224 int oldShortcut = item->shortcut();
225 if (item->functions().first) {
226 if (oldShortcut > 0) {
227 m_heldKeys.take(oldShortcut);
228 }
229 if (keySequence > 0) {
230 m_heldKeys[keySequence] = item;
231 }
232 }
233
234 item->setShortcut(keySequence);
235}
236
237void ShortcutController::updateButton(const QModelIndex& index, int button) {
238 if (!index.isValid()) {
239 return;
240 }
241 const QModelIndex& parent = index.parent();
242 if (!parent.isValid()) {
243 return;
244 }
245 ShortcutItem* item = itemAt(index);
246 int oldButton = item->button();
247 if (oldButton >= 0) {
248 m_buttons.take(oldButton);
249 }
250 updateAxis(index, -1, GamepadAxisEvent::NEUTRAL);
251 item->setButton(button);
252 if (button >= 0) {
253 m_buttons[button] = item;
254 }
255 if (m_config) {
256 m_config->setQtOption(item->name(), button, BUTTON_SECTION);
257 if (!m_profileName.isNull()) {
258 m_config->setQtOption(item->name(), button, BUTTON_PROFILE_SECTION + m_profileName);
259 }
260 }
261 emit dataChanged(createIndex(index.row(), 0, index.internalPointer()),
262 createIndex(index.row(), 2, index.internalPointer()));
263}
264
265void ShortcutController::updateAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction direction) {
266 if (!index.isValid()) {
267 return;
268 }
269 const QModelIndex& parent = index.parent();
270 if (!parent.isValid()) {
271 return;
272 }
273 ShortcutItem* item = itemAt(index);
274 int oldAxis = item->axis();
275 GamepadAxisEvent::Direction oldDirection = item->direction();
276 if (oldAxis >= 0) {
277 m_axes.take(qMakePair(oldAxis, oldDirection));
278 }
279 if (axis >= 0 && direction != GamepadAxisEvent::NEUTRAL) {
280 updateButton(index, -1);
281 m_axes[qMakePair(axis, direction)] = item;
282 }
283 item->setAxis(axis, direction);
284 if (m_config) {
285 char d = '\0';
286 if (direction == GamepadAxisEvent::POSITIVE) {
287 d = '+';
288 }
289 if (direction == GamepadAxisEvent::NEGATIVE) {
290 d = '-';
291 }
292 m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_SECTION);
293 if (!m_profileName.isNull()) {
294 m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_PROFILE_SECTION + m_profileName);
295 }
296 }
297 emit dataChanged(createIndex(index.row(), 0, index.internalPointer()),
298 createIndex(index.row(), 2, index.internalPointer()));
299}
300
301void ShortcutController::clearKey(const QModelIndex& index) {
302 updateKey(index, 0);
303}
304
305void ShortcutController::clearButton(const QModelIndex& index) {
306 updateButton(index, -1);
307}
308
309bool ShortcutController::eventFilter(QObject*, QEvent* event) {
310 if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
311 QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
312 if (keyEvent->isAutoRepeat()) {
313 return false;
314 }
315 int key = keyEvent->key();
316 if (!isModifierKey(key)) {
317 key |= (keyEvent->modifiers() & ~Qt::KeypadModifier);
318 } else {
319 key = toModifierKey(key | (keyEvent->modifiers() & ~Qt::KeypadModifier));
320 }
321 auto item = m_heldKeys.find(key);
322 if (item != m_heldKeys.end()) {
323 ShortcutItem::Functions pair = item.value()->functions();
324 if (event->type() == QEvent::KeyPress) {
325 if (pair.first) {
326 pair.first();
327 }
328 } else {
329 if (pair.second) {
330 pair.second();
331 }
332 }
333 event->accept();
334 return true;
335 }
336 }
337 if (event->type() == GamepadButtonEvent::Down()) {
338 auto item = m_buttons.find(static_cast<GamepadButtonEvent*>(event)->value());
339 if (item == m_buttons.end()) {
340 return false;
341 }
342 QAction* action = item.value()->action();
343 if (action && action->isEnabled()) {
344 action->trigger();
345 }
346 ShortcutItem::Functions pair = item.value()->functions();
347 if (pair.first) {
348 pair.first();
349 }
350 event->accept();
351 return true;
352 }
353 if (event->type() == GamepadButtonEvent::Up()) {
354 auto item = m_buttons.find(static_cast<GamepadButtonEvent*>(event)->value());
355 if (item == m_buttons.end()) {
356 return false;
357 }
358 ShortcutItem::Functions pair = item.value()->functions();
359 if (pair.second) {
360 pair.second();
361 }
362 event->accept();
363 return true;
364 }
365 if (event->type() == GamepadAxisEvent::Type()) {
366 GamepadAxisEvent* gae = static_cast<GamepadAxisEvent*>(event);
367 auto item = m_axes.find(qMakePair(gae->axis(), gae->direction()));
368 if (item == m_axes.end()) {
369 return false;
370 }
371 if (gae->isNew()) {
372 QAction* action = item.value()->action();
373 if (action && action->isEnabled()) {
374 action->trigger();
375 }
376 }
377 ShortcutItem::Functions pair = item.value()->functions();
378 if (gae->isNew()) {
379 if (pair.first) {
380 pair.first();
381 }
382 } else {
383 if (pair.second) {
384 pair.second();
385 }
386 }
387 event->accept();
388 return true;
389 }
390 return false;
391}
392
393bool ShortcutController::loadShortcuts(ShortcutItem* item) {
394 if (item->name().isNull()) {
395 return false;
396 }
397 loadGamepadShortcuts(item);
398 QVariant shortcut = m_config->getQtOption(item->name(), KEY_SECTION);
399 if (!shortcut.isNull()) {
400 if (shortcut.toString().endsWith("+")) {
401 updateKey(item, toModifierShortcut(shortcut.toString()));
402 } else {
403 updateKey(item, QKeySequence(shortcut.toString())[0]);
404 }
405 return true;
406 }
407 return false;
408}
409
410void ShortcutController::loadGamepadShortcuts(ShortcutItem* item) {
411 if (item->name().isNull()) {
412 return;
413 }
414 QVariant button = m_config->getQtOption(item->name(), !m_profileName.isNull() ? BUTTON_PROFILE_SECTION + m_profileName : BUTTON_SECTION);
415 int oldButton = item->button();
416 if (oldButton >= 0) {
417 m_buttons.take(oldButton);
418 item->setButton(-1);
419 }
420 if (button.isNull() && m_profile) {
421 int buttonInt;
422 if (m_profile->lookupShortcutButton(item->name(), &buttonInt)) {
423 button = buttonInt;
424 }
425 }
426 if (!button.isNull()) {
427 item->setButton(button.toInt());
428 m_buttons[button.toInt()] = item;
429 }
430
431 QVariant axis = m_config->getQtOption(item->name(), !m_profileName.isNull() ? AXIS_PROFILE_SECTION + m_profileName : AXIS_SECTION);
432 int oldAxis = item->axis();
433 GamepadAxisEvent::Direction oldDirection = item->direction();
434 if (oldAxis >= 0) {
435 m_axes.take(qMakePair(oldAxis, oldDirection));
436 item->setAxis(-1, GamepadAxisEvent::NEUTRAL);
437 }
438 if (axis.isNull() && m_profile) {
439 int axisInt;
440 GamepadAxisEvent::Direction direction;
441 if (m_profile->lookupShortcutAxis(item->name(), &axisInt, &direction)) {
442 axis = QLatin1String(direction == GamepadAxisEvent::Direction::NEGATIVE ? "-" : "+") + QString::number(axisInt);
443 }
444 }
445 if (!axis.isNull()) {
446 QString axisDesc = axis.toString();
447 if (axisDesc.size() >= 2) {
448 GamepadAxisEvent::Direction direction = GamepadAxisEvent::NEUTRAL;
449 if (axisDesc[0] == '-') {
450 direction = GamepadAxisEvent::NEGATIVE;
451 }
452 if (axisDesc[0] == '+') {
453 direction = GamepadAxisEvent::POSITIVE;
454 }
455 bool ok;
456 int axis = axisDesc.mid(1).toInt(&ok);
457 if (ok) {
458 item->setAxis(axis, direction);
459 m_axes[qMakePair(axis, direction)] = item;
460 }
461 }
462 }
463}
464
465void ShortcutController::loadProfile(const QString& profile) {
466 m_profileName = profile;
467 m_profile = InputProfile::findProfile(profile);
468 onSubitems(&m_rootMenu, [this](ShortcutItem* item) {
469 loadGamepadShortcuts(item);
470 });
471}
472
473void ShortcutController::onSubitems(ShortcutItem* item, std::function<void(ShortcutItem*)> func) {
474 for (ShortcutItem& subitem : item->items()) {
475 func(&subitem);
476 onSubitems(&subitem, func);
477 }
478}
479
480int ShortcutController::toModifierShortcut(const QString& shortcut) {
481 // Qt doesn't seem to work with raw modifier shortcuts!
482 QStringList modifiers = shortcut.split('+');
483 int value = 0;
484 for (const auto& mod : modifiers) {
485 if (mod == QLatin1String("Shift")) {
486 value |= Qt::ShiftModifier;
487 continue;
488 }
489 if (mod == QLatin1String("Ctrl")) {
490 value |= Qt::ControlModifier;
491 continue;
492 }
493 if (mod == QLatin1String("Alt")) {
494 value |= Qt::AltModifier;
495 continue;
496 }
497 if (mod == QLatin1String("Meta")) {
498 value |= Qt::MetaModifier;
499 continue;
500 }
501 }
502 return value;
503}
504
505bool ShortcutController::isModifierKey(int key) {
506 switch (key) {
507 case Qt::Key_Shift:
508 case Qt::Key_Control:
509 case Qt::Key_Alt:
510 case Qt::Key_Meta:
511 return true;
512 default:
513 return false;
514 }
515}
516
517int ShortcutController::toModifierKey(int key) {
518 int modifiers = key & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier);
519 key ^= modifiers;
520 switch (key) {
521 case Qt::Key_Shift:
522 modifiers |= Qt::ShiftModifier;
523 break;
524 case Qt::Key_Control:
525 modifiers |= Qt::ControlModifier;
526 break;
527 case Qt::Key_Alt:
528 modifiers |= Qt::AltModifier;
529 break;
530 case Qt::Key_Meta:
531 modifiers |= Qt::MetaModifier;
532 break;
533 default:
534 break;
535 }
536 return modifiers;
537
538}
539
540ShortcutController::ShortcutItem::ShortcutItem(QAction* action, const QString& name, ShortcutItem* parent)
541 : m_action(action)
542 , m_shortcut(action->shortcut().isEmpty() ? 0 : action->shortcut()[0])
543 , m_menu(nullptr)
544 , m_name(name)
545 , m_button(-1)
546 , m_axis(-1)
547 , m_direction(GamepadAxisEvent::NEUTRAL)
548 , m_parent(parent)
549{
550 m_visibleName = action->text()
551 .remove(QRegExp("&(?!&)"))
552 .remove("...");
553}
554
555ShortcutController::ShortcutItem::ShortcutItem(ShortcutController::ShortcutItem::Functions functions, int shortcut, const QString& visibleName, const QString& name, ShortcutItem* parent)
556 : m_action(nullptr)
557 , m_shortcut(shortcut)
558 , m_functions(functions)
559 , m_menu(nullptr)
560 , m_name(name)
561 , m_visibleName(visibleName)
562 , m_button(-1)
563 , m_axis(-1)
564 , m_direction(GamepadAxisEvent::NEUTRAL)
565 , m_parent(parent)
566{
567}
568
569ShortcutController::ShortcutItem::ShortcutItem(QMenu* menu, ShortcutItem* parent)
570 : m_action(nullptr)
571 , m_shortcut(0)
572 , m_menu(menu)
573 , m_button(-1)
574 , m_axis(-1)
575 , m_direction(GamepadAxisEvent::NEUTRAL)
576 , m_parent(parent)
577{
578 if (menu) {
579 m_visibleName = menu->title()
580 .remove(QRegExp("&(?!&)"))
581 .remove("...");
582 }
583}
584
585void ShortcutController::ShortcutItem::addAction(QAction* action, const QString& name) {
586 m_items.append(ShortcutItem(action, name, this));
587}
588
589void ShortcutController::ShortcutItem::addFunctions(ShortcutController::ShortcutItem::Functions functions,
590 int shortcut, const QString& visibleName,
591 const QString& name) {
592 m_items.append(ShortcutItem(functions, shortcut, visibleName, name, this));
593}
594
595void ShortcutController::ShortcutItem::addSubmenu(QMenu* menu) {
596 m_items.append(ShortcutItem(menu, this));
597}
598
599void ShortcutController::ShortcutItem::setShortcut(int shortcut) {
600 m_shortcut = shortcut;
601 if (m_action) {
602 m_action->setShortcut(QKeySequence(shortcut));
603 }
604}
605
606void ShortcutController::ShortcutItem::setAxis(int axis, GamepadAxisEvent::Direction direction) {
607 m_axis = axis;
608 m_direction = direction;
609}