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