all repos — mgba @ 3c18fe162c2c76eca80692d37083f6809bae4c8d

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		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	if (oldButton >= 0) {
228		m_buttons.take(oldButton);
229	}
230	updateAxis(index, -1, GamepadAxisEvent::NEUTRAL);
231	item->setButton(button);
232	if (button >= 0) {
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	if (oldAxis >= 0) {
253		m_axes.take(qMakePair(oldAxis, oldDirection));
254	}
255	if (axis >= 0 && direction != GamepadAxisEvent::NEUTRAL) {
256		updateButton(index, -1);
257		m_axes[qMakePair(axis, direction)] = item;
258	}
259	item->setAxis(axis, direction);
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}