all repos — mgba @ 61ddffbcae2b1b213c13811754121e831608dc78

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),
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}