all repos — mgba @ 4c38f769565e8ddd7d3a8eef1a41975206c129a0

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#include "InputProfile.h"
 11
 12#include <QAction>
 13#include <QKeyEvent>
 14#include <QMenu>
 15
 16using namespace QGBA;
 17
 18ShortcutController::ShortcutController(QObject* parent)
 19	: QAbstractItemModel(parent)
 20	, m_rootMenu(nullptr)
 21	, m_config(nullptr)
 22	, m_profile(nullptr)
 23{
 24}
 25
 26void ShortcutController::setConfigController(ConfigController* controller) {
 27	m_config = controller;
 28}
 29
 30QVariant ShortcutController::data(const QModelIndex& index, int role) const {
 31	if (role != Qt::DisplayRole || !index.isValid()) {
 32		return QVariant();
 33	}
 34	int row = index.row();
 35	const ShortcutItem* item = static_cast<const ShortcutItem*>(index.internalPointer());
 36	switch (index.column()) {
 37	case 0:
 38		return item->visibleName();
 39	case 1:
 40		return QKeySequence(item->shortcut()).toString(QKeySequence::NativeText);
 41	case 2:
 42		if (item->button() >= 0) {
 43			return item->button();
 44		}
 45		if (item->axis() >= 0) {
 46			char d = '\0';
 47			if (item->direction() == GamepadAxisEvent::POSITIVE) {
 48				d = '+';
 49			}
 50			if (item->direction() == GamepadAxisEvent::NEGATIVE) {
 51				d = '-';
 52			}
 53			return QString("%1%2").arg(d).arg(item->axis());
 54		}
 55		break;
 56	}
 57	return QVariant();
 58}
 59
 60QVariant ShortcutController::headerData(int section, Qt::Orientation orientation, int role) const {
 61	if (role != Qt::DisplayRole) {
 62		return QAbstractItemModel::headerData(section, orientation, role);
 63	}
 64	if (orientation == Qt::Horizontal) {
 65		switch (section) {
 66		case 0:
 67			return tr("Action");
 68		case 1:
 69			return tr("Keyboard");
 70		case 2:
 71			return tr("Gamepad");
 72		}
 73	}
 74	return section;
 75}
 76
 77QModelIndex ShortcutController::index(int row, int column, const QModelIndex& parent) const {
 78	const ShortcutItem* pmenu = &m_rootMenu;
 79	if (parent.isValid()) {
 80		pmenu = static_cast<ShortcutItem*>(parent.internalPointer());
 81	}
 82	return createIndex(row, column, const_cast<ShortcutItem*>(&pmenu->items()[row]));
 83}
 84
 85QModelIndex ShortcutController::parent(const QModelIndex& index) const {
 86	if (!index.isValid() || !index.internalPointer()) {
 87		return QModelIndex();
 88	}
 89	ShortcutItem* item = static_cast<ShortcutItem*>(index.internalPointer());
 90	if (!item->parent() || !item->parent()->parent()) {
 91		return QModelIndex();
 92	}
 93	return createIndex(item->parent()->parent()->items().indexOf(*item->parent()), 0, item->parent());
 94}
 95
 96int ShortcutController::columnCount(const QModelIndex& index) const {
 97	return 3;
 98}
 99
100int ShortcutController::rowCount(const QModelIndex& index) const {
101	if (!index.isValid()) {
102		return m_rootMenu.items().count();
103	}
104	const ShortcutItem* item = static_cast<const ShortcutItem*>(index.internalPointer());
105	return item->items().count();
106}
107
108void ShortcutController::addAction(QMenu* menu, QAction* action, const QString& name) {
109	ShortcutItem* smenu = m_menuMap[menu];
110	if (!smenu) {
111		return;
112	}
113	ShortcutItem* pmenu = smenu->parent();
114	int row = pmenu->items().indexOf(*smenu);
115	QModelIndex parent = createIndex(row, 0, smenu);
116	beginInsertRows(parent, smenu->items().count(), smenu->items().count());
117	smenu->addAction(action, name);
118	endInsertRows();
119	ShortcutItem* item = &smenu->items().last();
120	if (m_config) {
121		loadShortcuts(item);
122	}
123	emit dataChanged(createIndex(smenu->items().count() - 1, 0, item),
124	                 createIndex(smenu->items().count() - 1, 2, item));
125}
126
127void ShortcutController::addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release,
128                                      int shortcut, const QString& visibleName, const QString& name) {
129	ShortcutItem* smenu = m_menuMap[menu];
130	if (!smenu) {
131		return;
132	}
133	ShortcutItem* pmenu = smenu->parent();
134	int row = pmenu->items().indexOf(*smenu);
135	QModelIndex parent = createIndex(row, 0, smenu);
136	beginInsertRows(parent, smenu->items().count(), smenu->items().count());
137	smenu->addFunctions(qMakePair(press, release), shortcut, visibleName, name);
138	endInsertRows();
139	ShortcutItem* item = &smenu->items().last();
140	bool loadedShortcut = false;
141	if (m_config) {
142		loadedShortcut = loadShortcuts(item);
143	}
144	if (!loadedShortcut && !m_heldKeys.contains(shortcut)) {
145		m_heldKeys[shortcut] = item;
146	}
147	emit dataChanged(createIndex(smenu->items().count() - 1, 0, item),
148	                 createIndex(smenu->items().count() - 1, 2, item));
149}
150
151void ShortcutController::addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release,
152                                      const QKeySequence& shortcut, const QString& visibleName, const QString& name) {
153	addFunctions(menu, press, release, shortcut[0], visibleName, name);
154}
155
156void ShortcutController::addMenu(QMenu* menu, QMenu* parentMenu) {
157	ShortcutItem* smenu = m_menuMap[parentMenu];
158	if (!smenu) {
159		smenu = &m_rootMenu;
160	}
161	QModelIndex parent;
162	ShortcutItem* pmenu = smenu->parent();
163	if (pmenu) {
164		int row = pmenu->items().indexOf(*smenu);
165		parent = createIndex(row, 0, smenu);
166	}
167	beginInsertRows(parent, smenu->items().count(), smenu->items().count());
168	smenu->addSubmenu(menu);
169	endInsertRows();
170	ShortcutItem* item = &smenu->items().last();
171	emit dataChanged(createIndex(smenu->items().count() - 1, 0, item),
172	                 createIndex(smenu->items().count() - 1, 2, item));
173	m_menuMap[menu] = item;
174}
175
176ShortcutController::ShortcutItem* ShortcutController::itemAt(const QModelIndex& index) {
177	if (!index.isValid()) {
178		return nullptr;
179	}
180	return static_cast<ShortcutItem*>(index.internalPointer());
181}
182
183const ShortcutController::ShortcutItem* ShortcutController::itemAt(const QModelIndex& index) const {
184	if (!index.isValid()) {
185		return nullptr;
186	}
187	return static_cast<const ShortcutItem*>(index.internalPointer());
188}
189
190int ShortcutController::shortcutAt(const QModelIndex& index) const {
191	const ShortcutItem* item = itemAt(index);
192	if (!item) {
193		return 0;
194	}
195	return item->shortcut();
196}
197
198bool ShortcutController::isMenuAt(const QModelIndex& index) const {
199	const ShortcutItem* item = itemAt(index);
200	if (!item) {
201		return false;
202	}
203	return item->menu();
204}
205
206void ShortcutController::updateKey(const QModelIndex& index, int keySequence) {
207	if (!index.isValid()) {
208		return;
209	}
210	const QModelIndex& parent = index.parent();
211	if (!parent.isValid()) {
212		return;
213	}
214	ShortcutItem* item = itemAt(index);
215	updateKey(item, keySequence);
216	if (m_config) {
217		m_config->setQtOption(item->name(), QKeySequence(keySequence).toString(), KEY_SECTION);
218	}
219	emit dataChanged(createIndex(index.row(), 0, index.internalPointer()),
220	                 createIndex(index.row(), 2, index.internalPointer()));
221}
222
223void ShortcutController::updateKey(ShortcutItem* item, int keySequence) {
224	int oldShortcut = item->shortcut();
225	if (item->functions().first) {
226		if (oldShortcut > 0) {
227			m_heldKeys.take(oldShortcut);
228		}
229		if (keySequence > 0) {
230			m_heldKeys[keySequence] = item;
231		}
232	}
233
234	item->setShortcut(keySequence);
235}
236
237void ShortcutController::updateButton(const QModelIndex& index, int button) {
238	if (!index.isValid()) {
239		return;
240	}
241	const QModelIndex& parent = index.parent();
242	if (!parent.isValid()) {
243		return;
244	}
245	ShortcutItem* item = itemAt(index);
246	int oldButton = item->button();
247	if (oldButton >= 0) {
248		m_buttons.take(oldButton);
249	}
250	updateAxis(index, -1, GamepadAxisEvent::NEUTRAL);
251	item->setButton(button);
252	if (button >= 0) {
253		m_buttons[button] = item;
254	}
255	if (m_config) {
256		m_config->setQtOption(item->name(), button, BUTTON_SECTION);
257		if (!m_profileName.isNull()) {
258			m_config->setQtOption(item->name(), button, BUTTON_PROFILE_SECTION + m_profileName);
259		}
260	}
261	emit dataChanged(createIndex(index.row(), 0, index.internalPointer()),
262	                 createIndex(index.row(), 2, index.internalPointer()));
263}
264
265void ShortcutController::updateAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction direction) {
266	if (!index.isValid()) {
267		return;
268	}
269	const QModelIndex& parent = index.parent();
270	if (!parent.isValid()) {
271		return;
272	}
273	ShortcutItem* item = itemAt(index);
274	int oldAxis = item->axis();
275	GamepadAxisEvent::Direction oldDirection = item->direction();
276	if (oldAxis >= 0) {
277		m_axes.take(qMakePair(oldAxis, oldDirection));
278	}
279	if (axis >= 0 && direction != GamepadAxisEvent::NEUTRAL) {
280		updateButton(index, -1);
281		m_axes[qMakePair(axis, direction)] = item;
282	}
283	item->setAxis(axis, direction);
284	if (m_config) {
285		char d = '\0';
286		if (direction == GamepadAxisEvent::POSITIVE) {
287			d = '+';
288		}
289		if (direction == GamepadAxisEvent::NEGATIVE) {
290			d = '-';
291		}
292		m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_SECTION);
293		if (!m_profileName.isNull()) {
294			m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_PROFILE_SECTION + m_profileName);
295		}
296	}
297	emit dataChanged(createIndex(index.row(), 0, index.internalPointer()),
298	                 createIndex(index.row(), 2, index.internalPointer()));
299}
300
301void ShortcutController::clearKey(const QModelIndex& index) {
302	updateKey(index, 0);
303}
304
305void ShortcutController::clearButton(const QModelIndex& index) {
306	updateButton(index, -1);
307}
308
309bool ShortcutController::eventFilter(QObject*, QEvent* event) {
310	if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
311		QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
312		if (keyEvent->isAutoRepeat()) {
313			return false;
314		}
315		int key = keyEvent->key();
316		if (!isModifierKey(key)) {
317			key |= (keyEvent->modifiers() & ~Qt::KeypadModifier);
318		} else {
319			key = toModifierKey(key | (keyEvent->modifiers() & ~Qt::KeypadModifier));
320		}
321		auto item = m_heldKeys.find(key);
322		if (item != m_heldKeys.end()) {
323			ShortcutItem::Functions pair = item.value()->functions();
324			if (event->type() == QEvent::KeyPress) {
325				if (pair.first) {
326					pair.first();
327				}
328			} else {
329				if (pair.second) {
330					pair.second();
331				}
332			}
333			event->accept();
334			return true;
335		}
336	}
337	if (event->type() == GamepadButtonEvent::Down()) {
338		auto item = m_buttons.find(static_cast<GamepadButtonEvent*>(event)->value());
339		if (item == m_buttons.end()) {
340			return false;
341		}
342		QAction* action = item.value()->action();
343		if (action && action->isEnabled()) {
344			action->trigger();
345		}
346		ShortcutItem::Functions pair = item.value()->functions();
347		if (pair.first) {
348			pair.first();
349		}
350		event->accept();
351		return true;
352	}
353	if (event->type() == GamepadButtonEvent::Up()) {
354		auto item = m_buttons.find(static_cast<GamepadButtonEvent*>(event)->value());
355		if (item == m_buttons.end()) {
356			return false;
357		}
358		ShortcutItem::Functions pair = item.value()->functions();
359		if (pair.second) {
360			pair.second();
361		}
362		event->accept();
363		return true;
364	}
365	if (event->type() == GamepadAxisEvent::Type()) {
366		GamepadAxisEvent* gae = static_cast<GamepadAxisEvent*>(event);
367		auto item = m_axes.find(qMakePair(gae->axis(), gae->direction()));
368		if (item == m_axes.end()) {
369			return false;
370		}
371		if (gae->isNew()) {
372			QAction* action = item.value()->action();
373			if (action && action->isEnabled()) {
374				action->trigger();
375			}
376		}
377		ShortcutItem::Functions pair = item.value()->functions();
378		if (gae->isNew()) {
379			if (pair.first) {
380				pair.first();
381			}
382		} else {
383			if (pair.second) {
384				pair.second();
385			}
386		}
387		event->accept();
388		return true;
389	}
390	return false;
391}
392
393bool ShortcutController::loadShortcuts(ShortcutItem* item) {
394	if (item->name().isNull()) {
395		return false;
396	}
397	loadGamepadShortcuts(item);
398	QVariant shortcut = m_config->getQtOption(item->name(), KEY_SECTION);
399	if (!shortcut.isNull()) {
400		if (shortcut.toString().endsWith("+")) {
401			updateKey(item, toModifierShortcut(shortcut.toString()));
402		} else {
403			updateKey(item, QKeySequence(shortcut.toString())[0]);
404		}
405		return true;
406	}
407	return false;
408}
409
410void ShortcutController::loadGamepadShortcuts(ShortcutItem* item) {
411	if (item->name().isNull()) {
412		return;
413	}
414	QVariant button = m_config->getQtOption(item->name(), !m_profileName.isNull() ? BUTTON_PROFILE_SECTION + m_profileName : BUTTON_SECTION);
415	int oldButton = item->button();
416	if (oldButton >= 0) {
417		m_buttons.take(oldButton);
418		item->setButton(-1);
419	}
420	if (button.isNull() && m_profile) {
421		int buttonInt;
422		if (m_profile->lookupShortcutButton(item->name(), &buttonInt)) {
423			button = buttonInt;
424		}
425	}
426	if (!button.isNull()) {
427		item->setButton(button.toInt());
428		m_buttons[button.toInt()] = item;
429	}
430
431	QVariant axis = m_config->getQtOption(item->name(), !m_profileName.isNull() ? AXIS_PROFILE_SECTION + m_profileName : AXIS_SECTION);
432	int oldAxis = item->axis();
433	GamepadAxisEvent::Direction oldDirection = item->direction();
434	if (oldAxis >= 0) {
435		m_axes.take(qMakePair(oldAxis, oldDirection));
436		item->setAxis(-1, GamepadAxisEvent::NEUTRAL);
437	}
438	if (axis.isNull() && m_profile) {
439		int axisInt;
440		GamepadAxisEvent::Direction direction;
441		if (m_profile->lookupShortcutAxis(item->name(), &axisInt, &direction)) {
442			axis = QLatin1String(direction == GamepadAxisEvent::Direction::NEGATIVE ? "-" : "+") + QString::number(axisInt);
443		}
444	}
445	if (!axis.isNull()) {
446		QString axisDesc = axis.toString();
447		if (axisDesc.size() >= 2) {
448			GamepadAxisEvent::Direction direction = GamepadAxisEvent::NEUTRAL;
449			if (axisDesc[0] == '-') {
450				direction = GamepadAxisEvent::NEGATIVE;
451			}
452			if (axisDesc[0] == '+') {
453				direction = GamepadAxisEvent::POSITIVE;
454			}
455			bool ok;
456			int axis = axisDesc.mid(1).toInt(&ok);
457			if (ok) {
458				item->setAxis(axis, direction);
459				m_axes[qMakePair(axis, direction)] = item;
460			}
461		}
462	}
463}
464
465void ShortcutController::loadProfile(const QString& profile) {
466	m_profileName = profile;
467	m_profile = InputProfile::findProfile(profile);
468	onSubitems(&m_rootMenu, [this](ShortcutItem* item) {
469		loadGamepadShortcuts(item);
470	});
471}
472
473void ShortcutController::onSubitems(ShortcutItem* item, std::function<void(ShortcutItem*)> func) {
474	for (ShortcutItem& subitem : item->items()) {
475		func(&subitem);
476		onSubitems(&subitem, func);
477	}
478}
479
480int ShortcutController::toModifierShortcut(const QString& shortcut) {
481	// Qt doesn't seem to work with raw modifier shortcuts!
482	QStringList modifiers = shortcut.split('+');
483	int value = 0;
484	for (const auto& mod : modifiers) {
485		if (mod == QLatin1String("Shift")) {
486			value |= Qt::ShiftModifier;
487			continue;
488		}
489		if (mod == QLatin1String("Ctrl")) {
490			value |= Qt::ControlModifier;
491			continue;
492		}
493		if (mod == QLatin1String("Alt")) {
494			value |= Qt::AltModifier;
495			continue;
496		}
497		if (mod == QLatin1String("Meta")) {
498			value |= Qt::MetaModifier;
499			continue;
500		}
501	}
502	return value;
503}
504
505bool ShortcutController::isModifierKey(int key) {
506	switch (key) {
507	case Qt::Key_Shift:
508	case Qt::Key_Control:
509	case Qt::Key_Alt:
510	case Qt::Key_Meta:
511		return true;
512	default:
513		return false;
514	}
515}
516
517int ShortcutController::toModifierKey(int key) {
518	int modifiers = key & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier);
519	key ^= modifiers;
520	switch (key) {
521	case Qt::Key_Shift:
522		modifiers |= Qt::ShiftModifier;
523		break;
524	case Qt::Key_Control:
525		modifiers |= Qt::ControlModifier;
526		break;
527	case Qt::Key_Alt:
528		modifiers |= Qt::AltModifier;
529		break;
530	case Qt::Key_Meta:
531		modifiers |= Qt::MetaModifier;
532		break;
533	default:
534		break;
535	}
536	return modifiers;
537
538}
539
540ShortcutController::ShortcutItem::ShortcutItem(QAction* action, const QString& name, ShortcutItem* parent)
541	: m_action(action)
542	, m_shortcut(action->shortcut().isEmpty() ? 0 : action->shortcut()[0])
543	, m_menu(nullptr)
544	, m_name(name)
545	, m_button(-1)
546	, m_axis(-1)
547	, m_direction(GamepadAxisEvent::NEUTRAL)
548	, m_parent(parent)
549{
550	m_visibleName = action->text()
551		.remove(QRegExp("&(?!&)"))
552		.remove("...");
553}
554
555ShortcutController::ShortcutItem::ShortcutItem(ShortcutController::ShortcutItem::Functions functions, int shortcut, const QString& visibleName, const QString& name, ShortcutItem* parent)
556	: m_action(nullptr)
557	, m_shortcut(shortcut)
558	, m_functions(functions)
559	, m_menu(nullptr)
560	, m_name(name)
561	, m_visibleName(visibleName)
562	, m_button(-1)
563	, m_axis(-1)
564	, m_direction(GamepadAxisEvent::NEUTRAL)
565	, m_parent(parent)
566{
567}
568
569ShortcutController::ShortcutItem::ShortcutItem(QMenu* menu, ShortcutItem* parent)
570	: m_action(nullptr)
571	, m_shortcut(0)
572	, m_menu(menu)
573	, m_button(-1)
574	, m_axis(-1)
575	, m_direction(GamepadAxisEvent::NEUTRAL)
576	, m_parent(parent)
577{
578	if (menu) {
579		m_visibleName = menu->title()
580			.remove(QRegExp("&(?!&)"))
581			.remove("...");
582	}
583}
584
585void ShortcutController::ShortcutItem::addAction(QAction* action, const QString& name) {
586	m_items.append(ShortcutItem(action, name, this));
587}
588
589void ShortcutController::ShortcutItem::addFunctions(ShortcutController::ShortcutItem::Functions functions,
590                                                    int shortcut, const QString& visibleName,
591                                                    const QString& name) {
592	m_items.append(ShortcutItem(functions, shortcut, visibleName, name, this));
593}
594
595void ShortcutController::ShortcutItem::addSubmenu(QMenu* menu) {
596	m_items.append(ShortcutItem(menu, this));
597}
598
599void ShortcutController::ShortcutItem::setShortcut(int shortcut) {
600	m_shortcut = shortcut;
601	if (m_action) {
602		m_action->setShortcut(QKeySequence(shortcut));
603	}
604}
605
606void ShortcutController::ShortcutItem::setAxis(int axis, GamepadAxisEvent::Direction direction) {
607	m_axis = axis;
608	m_direction = direction;
609}