all repos — mgba @ 851d942cdd8ceebfe08f9a1be8940d4c3a62e689

mGBA Game Boy Advance Emulator

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}