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