all repos — mgba @ 8ef50827fdc358fbdd7af8ac8e3ca548e305294b

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		if (!m_profile.isNull()) {
243			m_config->setQtOption(item->name(), button, BUTTON_PROFILE_SECTION + m_profile);
244		}
245	}
246	emit dataChanged(createIndex(index.row(), 0, index.internalPointer()),
247	                 createIndex(index.row(), 2, index.internalPointer()));
248}
249
250void ShortcutController::updateAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction direction) {
251	if (!index.isValid()) {
252		return;
253	}
254	const QModelIndex& parent = index.parent();
255	if (!parent.isValid()) {
256		return;
257	}
258	ShortcutItem* item = itemAt(index);
259	int oldAxis = item->axis();
260	GamepadAxisEvent::Direction oldDirection = item->direction();
261	if (oldAxis >= 0) {
262		m_axes.take(qMakePair(oldAxis, oldDirection));
263	}
264	if (axis >= 0 && direction != GamepadAxisEvent::NEUTRAL) {
265		updateButton(index, -1);
266		m_axes[qMakePair(axis, direction)] = item;
267	}
268	item->setAxis(axis, direction);
269	if (m_config) {
270		char d = '\0';
271		if (direction == GamepadAxisEvent::POSITIVE) {
272			d = '+';
273		}
274		if (direction == GamepadAxisEvent::NEGATIVE) {
275			d = '-';
276		}
277		m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_SECTION);
278		if (!m_profile.isNull()) {
279			m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_PROFILE_SECTION + m_profile);
280		}
281	}
282	emit dataChanged(createIndex(index.row(), 0, index.internalPointer()),
283	                 createIndex(index.row(), 2, index.internalPointer()));
284}
285
286void ShortcutController::clearKey(const QModelIndex& index) {
287	updateKey(index, QKeySequence());
288}
289
290void ShortcutController::clearButton(const QModelIndex& index) {
291	updateButton(index, -1);
292}
293
294bool ShortcutController::eventFilter(QObject*, QEvent* event) {
295	if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
296		QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
297		if (keyEvent->isAutoRepeat()) {
298			return false;
299		}
300		auto item = m_heldKeys.find(keyEventToSequence(keyEvent));
301		if (item == m_heldKeys.end()) {
302			return false;
303		}
304		ShortcutItem::Functions pair = item.value()->functions();
305		if (event->type() == QEvent::KeyPress) {
306			if (pair.first) {
307				pair.first();
308			}
309		} else {
310			if (pair.second) {
311				pair.second();
312			}
313		}
314		event->accept();
315		return true;
316	}
317	if (event->type() == GamepadButtonEvent::Down()) {
318		auto item = m_buttons.find(static_cast<GamepadButtonEvent*>(event)->value());
319		if (item == m_buttons.end()) {
320			return false;
321		}
322		QAction* action = item.value()->action();
323		if (action && action->isEnabled()) {
324			action->trigger();
325		}
326		ShortcutItem::Functions pair = item.value()->functions();
327		if (pair.first) {
328			pair.first();
329		}
330		event->accept();
331		return true;
332	}
333	if (event->type() == GamepadButtonEvent::Up()) {
334		auto item = m_buttons.find(static_cast<GamepadButtonEvent*>(event)->value());
335		if (item == m_buttons.end()) {
336			return false;
337		}
338		ShortcutItem::Functions pair = item.value()->functions();
339		if (pair.second) {
340			pair.second();
341		}
342		event->accept();
343		return true;
344	}
345	if (event->type() == GamepadAxisEvent::Type()) {
346		GamepadAxisEvent* gae = static_cast<GamepadAxisEvent*>(event);
347		auto item = m_axes.find(qMakePair(gae->axis(), gae->direction()));
348		if (item == m_axes.end()) {
349			return false;
350		}
351		if (gae->isNew()) {
352			QAction* action = item.value()->action();
353			if (action && action->isEnabled()) {
354				action->trigger();
355			}
356		}
357		ShortcutItem::Functions pair = item.value()->functions();
358		if (gae->isNew()) {
359			if (pair.first) {
360				pair.first();
361			}
362		} else {
363			if (pair.second) {
364				pair.second();
365			}
366		}
367		event->accept();
368		return true;
369	}
370	return false;
371}
372
373void ShortcutController::loadShortcuts(ShortcutItem* item) {
374	if (item->name().isNull()) {
375		return;
376	}
377	QVariant shortcut = m_config->getQtOption(item->name(), KEY_SECTION);
378	if (!shortcut.isNull()) {
379		QKeySequence keySequence(shortcut.toString());
380		if (item->functions().first) {
381			QKeySequence oldShortcut = item->shortcut();
382			if (!oldShortcut.isEmpty()) {
383				m_heldKeys.take(oldShortcut);
384			}
385			m_heldKeys[keySequence] = item;
386		}
387		item->setShortcut(keySequence);
388	}
389	loadGamepadShortcuts(item);
390}
391
392void ShortcutController::loadGamepadShortcuts(ShortcutItem* item) {
393	if (item->name().isNull()) {
394		return;
395	}
396	QVariant button = m_config->getQtOption(item->name(), !m_profile.isNull() ? BUTTON_PROFILE_SECTION + m_profile : BUTTON_SECTION);
397	int oldButton = item->button();
398	if (oldButton >= 0) {
399		m_buttons.take(oldButton);
400		item->setButton(-1);
401	}
402	if (!button.isNull()) {
403		item->setButton(button.toInt());
404		m_buttons[button.toInt()] = item;
405	}
406
407	QVariant axis = m_config->getQtOption(item->name(), !m_profile.isNull() ? AXIS_PROFILE_SECTION + m_profile : AXIS_SECTION);
408	int oldAxis = item->axis();
409	GamepadAxisEvent::Direction oldDirection = item->direction();
410	if (oldAxis >= 0) {
411		m_axes.take(qMakePair(oldAxis, oldDirection));
412		item->setAxis(-1, GamepadAxisEvent::NEUTRAL);
413	}
414	if (!axis.isNull()) {
415		QString axisDesc = axis.toString();
416		if (axisDesc.size() >= 2) {
417			GamepadAxisEvent::Direction direction = GamepadAxisEvent::NEUTRAL;
418			if (axisDesc[0] == '-') {
419				direction = GamepadAxisEvent::NEGATIVE;
420			}
421			if (axisDesc[0] == '+') {
422				direction = GamepadAxisEvent::POSITIVE;
423			}
424			bool ok;
425			int axis = axisDesc.mid(1).toInt(&ok);
426			if (ok) {
427				item->setAxis(axis, direction);
428				m_axes[qMakePair(axis, direction)] = item;
429			}
430		}
431	}
432}
433
434QKeySequence ShortcutController::keyEventToSequence(const QKeyEvent* event) {
435	QString modifier = QString::null;
436
437	if (event->modifiers() & Qt::ShiftModifier) {
438		modifier += "Shift+";
439	}
440	if (event->modifiers() & Qt::ControlModifier) {
441		modifier += "Ctrl+";
442	}
443	if (event->modifiers() & Qt::AltModifier) {
444		modifier += "Alt+";
445	}
446	if (event->modifiers() & Qt::MetaModifier) {
447		modifier += "Meta+";
448	}
449
450	QString key = QKeySequence(event->key()).toString();
451	return QKeySequence(modifier + key);
452}
453
454void ShortcutController::loadProfile(const QString& profile) {
455	m_profile = profile;
456	onSubitems(&m_rootMenu, [this](ShortcutItem* item) {
457		loadGamepadShortcuts(item);
458	});
459}
460
461void ShortcutController::onSubitems(ShortcutItem* item, std::function<void(ShortcutItem*)> func) {
462	for (ShortcutItem& subitem : item->items()) {
463		func(&subitem);
464		onSubitems(&subitem, func);
465	}
466}
467
468ShortcutController::ShortcutItem::ShortcutItem(QAction* action, const QString& name, ShortcutItem* parent)
469	: m_action(action)
470	, m_shortcut(action->shortcut())
471	, m_menu(nullptr)
472	, m_name(name)
473	, m_button(-1)
474	, m_axis(-1)
475	, m_direction(GamepadAxisEvent::NEUTRAL)
476	, m_parent(parent)
477{
478	m_visibleName = action->text()
479		.remove(QRegExp("&(?!&)"))
480		.remove("...");
481}
482
483ShortcutController::ShortcutItem::ShortcutItem(ShortcutController::ShortcutItem::Functions functions, const QKeySequence& shortcut, const QString& visibleName, const QString& name, ShortcutItem* parent)
484	: m_action(nullptr)
485	, m_shortcut(shortcut)
486	, m_functions(functions)
487	, m_menu(nullptr)
488	, m_name(name)
489	, m_visibleName(visibleName)
490	, m_button(-1)
491	, m_axis(-1)
492	, m_direction(GamepadAxisEvent::NEUTRAL)
493	, m_parent(parent)
494{
495}
496
497ShortcutController::ShortcutItem::ShortcutItem(QMenu* menu, ShortcutItem* parent)
498	: m_action(nullptr)
499	, m_menu(menu)
500	, m_button(-1)
501	, m_axis(-1)
502	, m_direction(GamepadAxisEvent::NEUTRAL)
503	, m_parent(parent)
504{
505	if (menu) {
506		m_visibleName = menu->title()
507			.remove(QRegExp("&(?!&)"))
508			.remove("...");
509	}
510}
511
512void ShortcutController::ShortcutItem::addAction(QAction* action, const QString& name) {
513	m_items.append(ShortcutItem(action, name, this));
514}
515
516void ShortcutController::ShortcutItem::addFunctions(ShortcutController::ShortcutItem::Functions functions,
517                                                    const QKeySequence& shortcut, const QString& visibleName,
518                                                    const QString& name) {
519	m_items.append(ShortcutItem(functions, shortcut, visibleName, name, this));
520}
521
522void ShortcutController::ShortcutItem::addSubmenu(QMenu* menu) {
523	m_items.append(ShortcutItem(menu, this));
524}
525
526void ShortcutController::ShortcutItem::setShortcut(const QKeySequence& shortcut) {
527	m_shortcut = shortcut;
528	if (m_action) {
529		m_action->setShortcut(shortcut);
530	}
531}
532
533void ShortcutController::ShortcutItem::setAxis(int axis, GamepadAxisEvent::Direction direction) {
534	m_axis = axis;
535	m_direction = direction;
536}