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 }
243 emit dataChanged(createIndex(index.row(), 0, index.internalPointer()),
244 createIndex(index.row(), 2, index.internalPointer()));
245}
246
247void ShortcutController::updateAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction direction) {
248 if (!index.isValid()) {
249 return;
250 }
251 const QModelIndex& parent = index.parent();
252 if (!parent.isValid()) {
253 return;
254 }
255 ShortcutItem* item = itemAt(index);
256 int oldAxis = item->axis();
257 GamepadAxisEvent::Direction oldDirection = item->direction();
258 if (oldAxis >= 0) {
259 m_axes.take(qMakePair(oldAxis, oldDirection));
260 }
261 if (axis >= 0 && direction != GamepadAxisEvent::NEUTRAL) {
262 updateButton(index, -1);
263 m_axes[qMakePair(axis, direction)] = item;
264 }
265 item->setAxis(axis, direction);
266 if (m_config) {
267 char d = '\0';
268 if (direction == GamepadAxisEvent::POSITIVE) {
269 d = '+';
270 }
271 if (direction == GamepadAxisEvent::NEGATIVE) {
272 d = '-';
273 }
274 m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_SECTION);
275 }
276 emit dataChanged(createIndex(index.row(), 0, index.internalPointer()),
277 createIndex(index.row(), 2, index.internalPointer()));
278}
279
280void ShortcutController::clearKey(const QModelIndex& index) {
281 updateKey(index, QKeySequence());
282}
283
284void ShortcutController::clearButton(const QModelIndex& index) {
285 updateButton(index, -1);
286}
287
288bool ShortcutController::eventFilter(QObject*, QEvent* event) {
289 if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
290 QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
291 if (keyEvent->isAutoRepeat()) {
292 return false;
293 }
294 auto item = m_heldKeys.find(keyEventToSequence(keyEvent));
295 if (item == m_heldKeys.end()) {
296 return false;
297 }
298 ShortcutItem::Functions pair = item.value()->functions();
299 if (event->type() == QEvent::KeyPress) {
300 if (pair.first) {
301 pair.first();
302 }
303 } else {
304 if (pair.second) {
305 pair.second();
306 }
307 }
308 event->accept();
309 return true;
310 }
311 if (event->type() == GamepadButtonEvent::Down()) {
312 auto item = m_buttons.find(static_cast<GamepadButtonEvent*>(event)->value());
313 if (item == m_buttons.end()) {
314 return false;
315 }
316 QAction* action = item.value()->action();
317 if (action && action->isEnabled()) {
318 action->trigger();
319 }
320 ShortcutItem::Functions pair = item.value()->functions();
321 if (pair.first) {
322 pair.first();
323 }
324 event->accept();
325 return true;
326 }
327 if (event->type() == GamepadButtonEvent::Up()) {
328 auto item = m_buttons.find(static_cast<GamepadButtonEvent*>(event)->value());
329 if (item == m_buttons.end()) {
330 return false;
331 }
332 ShortcutItem::Functions pair = item.value()->functions();
333 if (pair.second) {
334 pair.second();
335 }
336 event->accept();
337 return true;
338 }
339 if (event->type() == GamepadAxisEvent::Type()) {
340 GamepadAxisEvent* gae = static_cast<GamepadAxisEvent*>(event);
341 auto item = m_axes.find(qMakePair(gae->axis(), gae->direction()));
342 if (item == m_axes.end()) {
343 return false;
344 }
345 if (gae->isNew()) {
346 QAction* action = item.value()->action();
347 if (action && action->isEnabled()) {
348 action->trigger();
349 }
350 }
351 ShortcutItem::Functions pair = item.value()->functions();
352 if (gae->isNew()) {
353 if (pair.first) {
354 pair.first();
355 }
356 } else {
357 if (pair.second) {
358 pair.second();
359 }
360 }
361 event->accept();
362 return true;
363 }
364 return false;
365}
366
367void ShortcutController::loadShortcuts(ShortcutItem* item) {
368 QVariant shortcut = m_config->getQtOption(item->name(), KEY_SECTION);
369 if (!shortcut.isNull()) {
370 QKeySequence keySequence(shortcut.toString());
371 if (item->functions().first) {
372 QKeySequence oldShortcut = item->shortcut();
373 if (!oldShortcut.isEmpty()) {
374 m_heldKeys.take(oldShortcut);
375 }
376 m_heldKeys[keySequence] = item;
377 }
378 item->setShortcut(keySequence);
379 }
380 QVariant button = m_config->getQtOption(item->name(), BUTTON_SECTION);
381 if (!button.isNull()) {
382 int oldButton = item->button();
383 item->setButton(button.toInt());
384 if (oldButton >= 0) {
385 m_buttons.take(oldButton);
386 }
387 m_buttons[button.toInt()] = item;
388 }
389 QVariant axis = m_config->getQtOption(item->name(), AXIS_SECTION);
390 if (!axis.isNull()) {
391 int oldAxis = item->axis();
392 GamepadAxisEvent::Direction oldDirection = item->direction();
393 QString axisDesc = axis.toString();
394 if (axisDesc.size() >= 2) {
395 GamepadAxisEvent::Direction direction = GamepadAxisEvent::NEUTRAL;
396 if (axisDesc[0] == '-') {
397 direction = GamepadAxisEvent::NEGATIVE;
398 }
399 if (axisDesc[0] == '+') {
400 direction = GamepadAxisEvent::POSITIVE;
401 }
402 bool ok;
403 int axis = axisDesc.mid(1).toInt(&ok);
404 if (ok) {
405 item->setAxis(axis, direction);
406 if (oldAxis >= 0) {
407 m_axes.take(qMakePair(oldAxis, oldDirection));
408 }
409 m_axes[qMakePair(axis, direction)] = item;
410 }
411 }
412 }
413}
414
415QKeySequence ShortcutController::keyEventToSequence(const QKeyEvent* event) {
416 QString modifier = QString::null;
417
418 if (event->modifiers() & Qt::ShiftModifier) {
419 modifier += "Shift+";
420 }
421 if (event->modifiers() & Qt::ControlModifier) {
422 modifier += "Ctrl+";
423 }
424 if (event->modifiers() & Qt::AltModifier) {
425 modifier += "Alt+";
426 }
427 if (event->modifiers() & Qt::MetaModifier) {
428 modifier += "Meta+";
429 }
430
431 QString key = QKeySequence(event->key()).toString();
432 return QKeySequence(modifier + key);
433}
434
435ShortcutController::ShortcutItem::ShortcutItem(QAction* action, const QString& name, ShortcutItem* parent)
436 : m_action(action)
437 , m_shortcut(action->shortcut())
438 , m_menu(nullptr)
439 , m_name(name)
440 , m_button(-1)
441 , m_axis(-1)
442 , m_direction(GamepadAxisEvent::NEUTRAL)
443 , m_parent(parent)
444{
445 m_visibleName = action->text()
446 .remove(QRegExp("&(?!&)"))
447 .remove("...");
448}
449
450ShortcutController::ShortcutItem::ShortcutItem(ShortcutController::ShortcutItem::Functions functions, const QKeySequence& shortcut, const QString& visibleName, const QString& name, ShortcutItem* parent)
451 : m_action(nullptr)
452 , m_shortcut(shortcut)
453 , m_functions(functions)
454 , m_menu(nullptr)
455 , m_name(name)
456 , m_visibleName(visibleName)
457 , m_button(-1)
458 , m_axis(-1)
459 , m_direction(GamepadAxisEvent::NEUTRAL)
460 , m_parent(parent)
461{
462}
463
464ShortcutController::ShortcutItem::ShortcutItem(QMenu* menu, ShortcutItem* parent)
465 : m_action(nullptr)
466 , m_menu(menu)
467 , m_button(-1)
468 , m_axis(-1)
469 , m_direction(GamepadAxisEvent::NEUTRAL)
470 , m_parent(parent)
471{
472 if (menu) {
473 m_visibleName = menu->title()
474 .remove(QRegExp("&(?!&)"))
475 .remove("...");
476 }
477}
478
479void ShortcutController::ShortcutItem::addAction(QAction* action, const QString& name) {
480 m_items.append(ShortcutItem(action, name, this));
481}
482
483void ShortcutController::ShortcutItem::addFunctions(ShortcutController::ShortcutItem::Functions functions,
484 const QKeySequence& shortcut, const QString& visibleName,
485 const QString& name) {
486 m_items.append(ShortcutItem(functions, shortcut, visibleName, name, this));
487}
488
489void ShortcutController::ShortcutItem::addSubmenu(QMenu* menu) {
490 m_items.append(ShortcutItem(menu, this));
491}
492
493void ShortcutController::ShortcutItem::setShortcut(const QKeySequence& shortcut) {
494 m_shortcut = shortcut;
495 if (m_action) {
496 m_action->setShortcut(shortcut);
497 }
498}
499
500void ShortcutController::ShortcutItem::setAxis(int axis, GamepadAxisEvent::Direction direction) {
501 m_axis = axis;
502 m_direction = direction;
503}