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{
21}
22
23void ShortcutController::setConfigController(ConfigController* controller) {
24 m_config = controller;
25}
26
27QVariant ShortcutController::data(const QModelIndex& index, int role) const {
28 if (role != Qt::DisplayRole || !index.isValid()) {
29 return QVariant();
30 }
31 int row = index.row();
32 const ShortcutItem* item = static_cast<const ShortcutItem*>(index.internalPointer());
33 switch (index.column()) {
34 case 0:
35 return item->visibleName();
36 case 1:
37 return QKeySequence(item->shortcut()).toString(QKeySequence::NativeText);
38 case 2:
39 if (item->button() >= 0) {
40 return item->button();
41 }
42 if (item->axis() >= 0) {
43 char d = '\0';
44 if (item->direction() == GamepadAxisEvent::POSITIVE) {
45 d = '+';
46 }
47 if (item->direction() == GamepadAxisEvent::NEGATIVE) {
48 d = '-';
49 }
50 return QString("%1%2").arg(d).arg(item->axis());
51 }
52 break;
53 }
54 return QVariant();
55}
56
57QVariant ShortcutController::headerData(int section, Qt::Orientation orientation, int role) const {
58 if (role != Qt::DisplayRole) {
59 return QAbstractItemModel::headerData(section, orientation, role);
60 }
61 if (orientation == Qt::Horizontal) {
62 switch (section) {
63 case 0:
64 return tr("Action");
65 case 1:
66 return tr("Keyboard");
67 case 2:
68 return tr("Gamepad");
69 }
70 }
71 return section;
72}
73
74QModelIndex ShortcutController::index(int row, int column, const QModelIndex& parent) const {
75 const ShortcutItem* pmenu = &m_rootMenu;
76 if (parent.isValid()) {
77 pmenu = static_cast<ShortcutItem*>(parent.internalPointer());
78 }
79 return createIndex(row, column, const_cast<ShortcutItem*>(&pmenu->items()[row]));
80}
81
82QModelIndex ShortcutController::parent(const QModelIndex& index) const {
83 if (!index.isValid() || !index.internalPointer()) {
84 return QModelIndex();
85 }
86 ShortcutItem* item = static_cast<ShortcutItem*>(index.internalPointer());
87 if (!item->parent() || !item->parent()->parent()) {
88 return QModelIndex();
89 }
90 return createIndex(item->parent()->parent()->items().indexOf(*item->parent()), 0, item->parent());
91}
92
93int ShortcutController::columnCount(const QModelIndex& index) const {
94 return 3;
95}
96
97int ShortcutController::rowCount(const QModelIndex& index) const {
98 if (!index.isValid()) {
99 return m_rootMenu.items().count();
100 }
101 const ShortcutItem* item = static_cast<const ShortcutItem*>(index.internalPointer());
102 return item->items().count();
103}
104
105void ShortcutController::addAction(QMenu* menu, QAction* action, const QString& name) {
106 ShortcutItem* smenu = m_menuMap[menu];
107 if (!smenu) {
108 return;
109 }
110 ShortcutItem* pmenu = smenu->parent();
111 int row = pmenu->items().indexOf(*smenu);
112 QModelIndex parent = createIndex(row, 0, smenu);
113 beginInsertRows(parent, smenu->items().count(), smenu->items().count());
114 smenu->addAction(action, name);
115 endInsertRows();
116 ShortcutItem* item = &smenu->items().last();
117 if (m_config) {
118 loadShortcuts(item);
119 }
120 emit dataChanged(createIndex(smenu->items().count() - 1, 0, item),
121 createIndex(smenu->items().count() - 1, 2, item));
122}
123
124void ShortcutController::addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release,
125 int shortcut, const QString& visibleName, const QString& name) {
126 ShortcutItem* smenu = m_menuMap[menu];
127 if (!smenu) {
128 return;
129 }
130 ShortcutItem* pmenu = smenu->parent();
131 int row = pmenu->items().indexOf(*smenu);
132 QModelIndex parent = createIndex(row, 0, smenu);
133 beginInsertRows(parent, smenu->items().count(), smenu->items().count());
134 smenu->addFunctions(qMakePair(press, release), shortcut, visibleName, name);
135 endInsertRows();
136 ShortcutItem* item = &smenu->items().last();
137 bool loadedShortcut = false;
138 if (m_config) {
139 loadedShortcut = loadShortcuts(item);
140 }
141 if (!loadedShortcut && !m_heldKeys.contains(shortcut)) {
142 m_heldKeys[shortcut] = item;
143 }
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
390bool ShortcutController::loadShortcuts(ShortcutItem* item) {
391 if (item->name().isNull()) {
392 return false;
393 }
394 loadGamepadShortcuts(item);
395 QVariant shortcut = m_config->getQtOption(item->name(), KEY_SECTION);
396 if (!shortcut.isNull()) {
397 if (shortcut.toString().endsWith("+")) {
398 updateKey(item, toModifierShortcut(shortcut.toString()));
399 } else {
400 updateKey(item, QKeySequence(shortcut.toString())[0]);
401 }
402 return true;
403 }
404 return false;
405}
406
407void ShortcutController::loadGamepadShortcuts(ShortcutItem* item) {
408 if (item->name().isNull()) {
409 return;
410 }
411 QVariant button = m_config->getQtOption(item->name(), !m_profileName.isNull() ? BUTTON_PROFILE_SECTION + m_profileName : BUTTON_SECTION);
412 int oldButton = item->button();
413 if (oldButton >= 0) {
414 m_buttons.take(oldButton);
415 item->setButton(-1);
416 }
417 if (button.isNull() && m_profile) {
418 int buttonInt;
419 if (m_profile->lookupShortcutButton(item->name(), &buttonInt)) {
420 button = buttonInt;
421 }
422 }
423 if (!button.isNull()) {
424 item->setButton(button.toInt());
425 m_buttons[button.toInt()] = item;
426 }
427
428 QVariant axis = m_config->getQtOption(item->name(), !m_profileName.isNull() ? AXIS_PROFILE_SECTION + m_profileName : AXIS_SECTION);
429 int oldAxis = item->axis();
430 GamepadAxisEvent::Direction oldDirection = item->direction();
431 if (oldAxis >= 0) {
432 m_axes.take(qMakePair(oldAxis, oldDirection));
433 item->setAxis(-1, GamepadAxisEvent::NEUTRAL);
434 }
435 if (axis.isNull() && m_profile) {
436 int axisInt;
437 GamepadAxisEvent::Direction direction;
438 if (m_profile->lookupShortcutAxis(item->name(), &axisInt, &direction)) {
439 axis = QLatin1String(direction == GamepadAxisEvent::Direction::NEGATIVE ? "-" : "+") + QString::number(axisInt);
440 }
441 }
442 if (!axis.isNull()) {
443 QString axisDesc = axis.toString();
444 if (axisDesc.size() >= 2) {
445 GamepadAxisEvent::Direction direction = GamepadAxisEvent::NEUTRAL;
446 if (axisDesc[0] == '-') {
447 direction = GamepadAxisEvent::NEGATIVE;
448 }
449 if (axisDesc[0] == '+') {
450 direction = GamepadAxisEvent::POSITIVE;
451 }
452 bool ok;
453 int axis = axisDesc.mid(1).toInt(&ok);
454 if (ok) {
455 item->setAxis(axis, direction);
456 m_axes[qMakePair(axis, direction)] = item;
457 }
458 }
459 }
460}
461
462void ShortcutController::loadProfile(const QString& profile) {
463 m_profileName = profile;
464 m_profile = InputProfile::findProfile(profile);
465 onSubitems(&m_rootMenu, [this](ShortcutItem* item) {
466 loadGamepadShortcuts(item);
467 });
468}
469
470void ShortcutController::onSubitems(ShortcutItem* item, std::function<void(ShortcutItem*)> func) {
471 for (ShortcutItem& subitem : item->items()) {
472 func(&subitem);
473 onSubitems(&subitem, func);
474 }
475}
476
477int ShortcutController::toModifierShortcut(const QString& shortcut) {
478 // Qt doesn't seem to work with raw modifier shortcuts!
479 QStringList modifiers = shortcut.split('+');
480 int value = 0;
481 for (const auto& mod : modifiers) {
482 if (mod == QLatin1String("Shift")) {
483 value |= Qt::ShiftModifier;
484 continue;
485 }
486 if (mod == QLatin1String("Ctrl")) {
487 value |= Qt::ControlModifier;
488 continue;
489 }
490 if (mod == QLatin1String("Alt")) {
491 value |= Qt::AltModifier;
492 continue;
493 }
494 if (mod == QLatin1String("Meta")) {
495 value |= Qt::MetaModifier;
496 continue;
497 }
498 }
499 return value;
500}
501
502bool ShortcutController::isModifierKey(int key) {
503 switch (key) {
504 case Qt::Key_Shift:
505 case Qt::Key_Control:
506 case Qt::Key_Alt:
507 case Qt::Key_Meta:
508 return true;
509 default:
510 return false;
511 }
512}
513
514int ShortcutController::toModifierKey(int key) {
515 int modifiers = key & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier);
516 key ^= modifiers;
517 switch (key) {
518 case Qt::Key_Shift:
519 modifiers |= Qt::ShiftModifier;
520 break;
521 case Qt::Key_Control:
522 modifiers |= Qt::ControlModifier;
523 break;
524 case Qt::Key_Alt:
525 modifiers |= Qt::AltModifier;
526 break;
527 case Qt::Key_Meta:
528 modifiers |= Qt::MetaModifier;
529 break;
530 default:
531 break;
532 }
533 return modifiers;
534
535}
536
537ShortcutController::ShortcutItem::ShortcutItem(QAction* action, const QString& name, ShortcutItem* parent)
538 : m_action(action)
539 , m_shortcut(action->shortcut().isEmpty() ? 0 : action->shortcut()[0])
540 , m_name(name)
541 , m_direction(GamepadAxisEvent::NEUTRAL)
542 , m_parent(parent)
543{
544 m_visibleName = action->text()
545 .remove(QRegExp("&(?!&)"))
546 .remove("...");
547}
548
549ShortcutController::ShortcutItem::ShortcutItem(ShortcutController::ShortcutItem::Functions functions, int shortcut, const QString& visibleName, const QString& name, ShortcutItem* parent)
550 : m_shortcut(shortcut)
551 , m_functions(functions)
552 , m_name(name)
553 , m_visibleName(visibleName)
554 , m_direction(GamepadAxisEvent::NEUTRAL)
555 , m_parent(parent)
556{
557}
558
559ShortcutController::ShortcutItem::ShortcutItem(QMenu* menu, ShortcutItem* parent)
560 : m_menu(menu)
561 , m_direction(GamepadAxisEvent::NEUTRAL)
562 , m_parent(parent)
563{
564 if (menu) {
565 m_visibleName = menu->title()
566 .remove(QRegExp("&(?!&)"))
567 .remove("...");
568 }
569}
570
571void ShortcutController::ShortcutItem::addAction(QAction* action, const QString& name) {
572 m_items.append(ShortcutItem(action, name, this));
573}
574
575void ShortcutController::ShortcutItem::addFunctions(ShortcutController::ShortcutItem::Functions functions,
576 int shortcut, const QString& visibleName,
577 const QString& name) {
578 m_items.append(ShortcutItem(functions, shortcut, visibleName, name, this));
579}
580
581void ShortcutController::ShortcutItem::addSubmenu(QMenu* menu) {
582 m_items.append(ShortcutItem(menu, this));
583}
584
585void ShortcutController::ShortcutItem::setShortcut(int shortcut) {
586 m_shortcut = shortcut;
587 if (m_action) {
588 m_action->setShortcut(QKeySequence(shortcut));
589 }
590}
591
592void ShortcutController::ShortcutItem::setAxis(int axis, GamepadAxisEvent::Direction direction) {
593 m_axis = axis;
594 m_direction = direction;
595}