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