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