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 break;
44 }
45 return QVariant();
46}
47
48QVariant ShortcutController::headerData(int section, Qt::Orientation orientation, int role) const {
49 if (role != Qt::DisplayRole) {
50 return QAbstractItemModel::headerData(section, orientation, role);
51 }
52 if (orientation == Qt::Horizontal) {
53 switch (section) {
54 case 0:
55 return tr("Action");
56 case 1:
57 return tr("Keyboard");
58 case 2:
59 return tr("Gamepad");
60 }
61 }
62 return section;
63}
64
65QModelIndex ShortcutController::index(int row, int column, const QModelIndex& parent) const {
66 const ShortcutItem* pmenu = &m_rootMenu;
67 if (parent.isValid()) {
68 pmenu = static_cast<ShortcutItem*>(parent.internalPointer());
69 }
70 return createIndex(row, column, const_cast<ShortcutItem*>(&pmenu->items()[row]));
71}
72
73QModelIndex ShortcutController::parent(const QModelIndex& index) const {
74 if (!index.isValid() || !index.internalPointer()) {
75 return QModelIndex();
76 }
77 ShortcutItem* item = static_cast<ShortcutItem*>(index.internalPointer());
78 if (!item->parent() || !item->parent()->parent()) {
79 return QModelIndex();
80 }
81 return createIndex(item->parent()->parent()->items().indexOf(*item->parent()), 0, item->parent());
82}
83
84int ShortcutController::columnCount(const QModelIndex& index) const {
85 return 3;
86}
87
88int ShortcutController::rowCount(const QModelIndex& index) const {
89 if (!index.isValid()) {
90 return m_rootMenu.items().count();
91 }
92 const ShortcutItem* item = static_cast<const ShortcutItem*>(index.internalPointer());
93 return item->items().count();
94}
95
96void ShortcutController::addAction(QMenu* menu, QAction* action, const QString& name) {
97 ShortcutItem* smenu = m_menuMap[menu];
98 if (!smenu) {
99 return;
100 }
101 ShortcutItem* pmenu = smenu->parent();
102 int row = pmenu->items().indexOf(*smenu);
103 QModelIndex parent = createIndex(row, 0, smenu);
104 beginInsertRows(parent, smenu->items().count(), smenu->items().count());
105 smenu->addAction(action, name);
106 endInsertRows();
107 ShortcutItem* item = &smenu->items().last();
108 if (m_config) {
109 loadShortcuts(item);
110 }
111 emit dataChanged(createIndex(smenu->items().count() - 1, 0, item), createIndex(smenu->items().count() - 1, 2, item));
112}
113
114void ShortcutController::addFunctions(QMenu* menu, std::function<void ()> press, std::function<void ()> release, const QKeySequence& shortcut, const QString& visibleName, const QString& name) {
115 ShortcutItem* smenu = m_menuMap[menu];
116 if (!smenu) {
117 return;
118 }
119 ShortcutItem* pmenu = smenu->parent();
120 int row = pmenu->items().indexOf(*smenu);
121 QModelIndex parent = createIndex(row, 0, smenu);
122 beginInsertRows(parent, smenu->items().count(), smenu->items().count());
123 smenu->addFunctions(qMakePair(press, release), shortcut, visibleName, name);
124 endInsertRows();
125 ShortcutItem* item = &smenu->items().last();
126 if (m_config) {
127 loadShortcuts(item);
128 }
129 m_heldKeys[shortcut] = item;
130 emit dataChanged(createIndex(smenu->items().count() - 1, 0, item), createIndex(smenu->items().count() - 1, 2, item));
131}
132
133void ShortcutController::addMenu(QMenu* menu, QMenu* parentMenu) {
134 ShortcutItem* smenu = m_menuMap[parentMenu];
135 if (!smenu) {
136 smenu = &m_rootMenu;
137 }
138 QModelIndex parent;
139 ShortcutItem* pmenu = smenu->parent();
140 if (pmenu) {
141 int row = pmenu->items().indexOf(*smenu);
142 parent = createIndex(row, 0, smenu);
143 }
144 beginInsertRows(parent, smenu->items().count(), smenu->items().count());
145 smenu->addSubmenu(menu);
146 endInsertRows();
147 ShortcutItem* item = &smenu->items().last();
148 emit dataChanged(createIndex(smenu->items().count() - 1, 0, item), createIndex(smenu->items().count() - 1, 2, item));
149 m_menuMap[menu] = item;
150}
151
152ShortcutController::ShortcutItem* ShortcutController::itemAt(const QModelIndex& index) {
153 if (!index.isValid()) {
154 return nullptr;
155 }
156 return static_cast<ShortcutItem*>(index.internalPointer());
157}
158
159const ShortcutController::ShortcutItem* ShortcutController::itemAt(const QModelIndex& index) const {
160 if (!index.isValid()) {
161 return nullptr;
162 }
163 return static_cast<const ShortcutItem*>(index.internalPointer());
164}
165
166QKeySequence ShortcutController::shortcutAt(const QModelIndex& index) const {
167 const ShortcutItem* item = itemAt(index);
168 if (!item) {
169 return QKeySequence();
170 }
171 return item->shortcut();
172}
173
174bool ShortcutController::isMenuAt(const QModelIndex& index) const {
175 const ShortcutItem* item = itemAt(index);
176 if (!item) {
177 return false;
178 }
179 return item->menu();
180}
181
182void ShortcutController::updateKey(const QModelIndex& index, const QKeySequence& keySequence) {
183 if (!index.isValid()) {
184 return;
185 }
186 const QModelIndex& parent = index.parent();
187 if (!parent.isValid()) {
188 return;
189 }
190 ShortcutItem* item = itemAt(index);
191 if (item->functions().first) {
192 QKeySequence oldShortcut = item->shortcut();
193 if (!oldShortcut.isEmpty()) {
194 m_heldKeys.take(oldShortcut);
195 }
196 if (!keySequence.isEmpty()) {
197 m_heldKeys[keySequence] = item;
198 }
199 }
200 item->setShortcut(keySequence);
201 if (m_config) {
202 m_config->setQtOption(item->name(), keySequence.toString(), KEY_SECTION);
203 }
204 emit dataChanged(createIndex(index.row(), 0, index.internalPointer()), createIndex(index.row(), 2, index.internalPointer()));
205}
206
207void ShortcutController::updateButton(const QModelIndex& index, int button) {
208 if (!index.isValid()) {
209 return;
210 }
211 const QModelIndex& parent = index.parent();
212 if (!parent.isValid()) {
213 return;
214 }
215 ShortcutItem* item = itemAt(index);
216 int oldButton = item->button();
217 item->setButton(button);
218 if (oldButton >= 0) {
219 m_buttons.take(oldButton);
220 }
221 if (button >= 0) {
222 m_buttons[button] = item;
223 }
224 if (m_config) {
225 m_config->setQtOption(item->name(), button, BUTTON_SECTION);
226 }
227 emit dataChanged(createIndex(index.row(), 0, index.internalPointer()), createIndex(index.row(), 2, index.internalPointer()));
228}
229
230void ShortcutController::clearKey(const QModelIndex& index) {
231 updateKey(index, QKeySequence());
232}
233
234void ShortcutController::clearButton(const QModelIndex& index) {
235 updateButton(index, -1);
236}
237
238bool ShortcutController::eventFilter(QObject*, QEvent* event) {
239 if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
240 QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
241 if (keyEvent->isAutoRepeat()) {
242 return false;
243 }
244 auto item = m_heldKeys.find(keyEventToSequence(keyEvent));
245 if (item == m_heldKeys.end()) {
246 return false;
247 }
248 ShortcutItem::Functions pair = item.value()->functions();
249 if (event->type() == QEvent::KeyPress) {
250 if (pair.first) {
251 pair.first();
252 }
253 } else {
254 if (pair.second) {
255 pair.second();
256 }
257 }
258 event->accept();
259 return true;
260 }
261 if (event->type() == GamepadButtonEvent::Down()) {
262 auto item = m_buttons.find(static_cast<GamepadButtonEvent*>(event)->value());
263 if (item == m_buttons.end()) {
264 return false;
265 }
266 QAction* action = item.value()->action();
267 if (action && action->isEnabled()) {
268 action->trigger();
269 }
270 ShortcutItem::Functions pair = item.value()->functions();
271 if (pair.first) {
272 pair.first();
273 }
274 event->accept();
275 return true;
276 }
277 if (event->type() == GamepadButtonEvent::Up()) {
278 auto item = m_buttons.find(static_cast<GamepadButtonEvent*>(event)->value());
279 if (item == m_buttons.end()) {
280 return false;
281 }
282 ShortcutItem::Functions pair = item.value()->functions();
283 if (pair.second) {
284 pair.second();
285 }
286 event->accept();
287 return true;
288 }
289 return false;
290}
291
292void ShortcutController::loadShortcuts(ShortcutItem* item) {
293 QVariant shortcut = m_config->getQtOption(item->name(), KEY_SECTION);
294 if (!shortcut.isNull()) {
295 QKeySequence keySequence(shortcut.toString());
296 if (item->functions().first) {
297 QKeySequence oldShortcut = item->shortcut();
298 if (!oldShortcut.isEmpty()) {
299 m_heldKeys.take(oldShortcut);
300 }
301 m_heldKeys[keySequence] = item;
302 }
303 item->setShortcut(keySequence);
304 }
305 QVariant button = m_config->getQtOption(item->name(), BUTTON_SECTION);
306 if (!button.isNull()) {
307 int oldButton = item->button();
308 item->setButton(button.toInt());
309 if (oldButton >= 0) {
310 m_buttons.take(oldButton);
311 }
312 m_buttons[button.toInt()] = item;
313 }
314}
315
316QKeySequence ShortcutController::keyEventToSequence(const QKeyEvent* event) {
317 QString modifier = QString::null;
318
319 if (event->modifiers() & Qt::ShiftModifier) {
320 modifier += "Shift+";
321 }
322 if (event->modifiers() & Qt::ControlModifier) {
323 modifier += "Ctrl+";
324 }
325 if (event->modifiers() & Qt::AltModifier) {
326 modifier += "Alt+";
327 }
328 if (event->modifiers() & Qt::MetaModifier) {
329 modifier += "Meta+";
330 }
331
332 QString key = QKeySequence(event->key()).toString();
333 return QKeySequence(modifier + key);
334}
335
336ShortcutController::ShortcutItem::ShortcutItem(QAction* action, const QString& name, ShortcutItem* parent)
337 : m_action(action)
338 , m_shortcut(action->shortcut())
339 , m_menu(nullptr)
340 , m_name(name)
341 , m_button(-1)
342 , m_parent(parent)
343{
344 m_visibleName = action->text()
345 .remove(QRegExp("&(?!&)"))
346 .remove("...");
347}
348
349ShortcutController::ShortcutItem::ShortcutItem(ShortcutController::ShortcutItem::Functions functions, const QKeySequence& shortcut, const QString& visibleName, const QString& name, ShortcutItem* parent)
350 : m_action(nullptr)
351 , m_shortcut(shortcut)
352 , m_functions(functions)
353 , m_menu(nullptr)
354 , m_name(name)
355 , m_visibleName(visibleName)
356 , m_button(-1)
357 , m_parent(parent)
358{
359}
360
361ShortcutController::ShortcutItem::ShortcutItem(QMenu* menu, ShortcutItem* parent)
362 : m_action(nullptr)
363 , m_menu(menu)
364 , m_button(-1)
365 , m_parent(parent)
366{
367 if (menu) {
368 m_visibleName = menu->title()
369 .remove(QRegExp("&(?!&)"))
370 .remove("...");
371 }
372}
373
374void ShortcutController::ShortcutItem::addAction(QAction* action, const QString& name) {
375 m_items.append(ShortcutItem(action, name, this));
376}
377
378void ShortcutController::ShortcutItem::addFunctions(ShortcutController::ShortcutItem::Functions functions, const QKeySequence& shortcut, const QString& visibleName, const QString& name) {
379 m_items.append(ShortcutItem(functions, shortcut, visibleName, name, this));
380}
381
382void ShortcutController::ShortcutItem::addSubmenu(QMenu* menu) {
383 m_items.append(ShortcutItem(menu, this));
384}
385
386void ShortcutController::ShortcutItem::setShortcut(const QKeySequence& shortcut) {
387 m_shortcut = shortcut;
388 if (m_action) {
389 m_action->setShortcut(shortcut);
390 }
391}