all repos — mgba @ f6755a6e1b7b0cf2b944cd8ca842746f11d6bf82

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#include <QRegularExpression>
 16
 17using namespace QGBA;
 18
 19ShortcutController::ShortcutController(QObject* parent)
 20	: QObject(parent)
 21{
 22}
 23
 24void ShortcutController::setConfigController(ConfigController* controller) {
 25	m_config = controller;
 26}
 27
 28void ShortcutController::setActionMapper(ActionMapper* actions) {
 29	m_actions = actions;
 30	connect(actions, &ActionMapper::actionAdded, this, &ShortcutController::generateItem);
 31	connect(actions, &ActionMapper::menuCleared, this, &ShortcutController::menuCleared);
 32	rebuildItems();
 33}
 34
 35void ShortcutController::updateKey(const QString& name, int keySequence) {
 36	auto item = m_items[name];
 37	if (!item) {
 38		return;
 39	}
 40	updateKey(item, keySequence);
 41	if (m_config) {
 42		m_config->setQtOption(item->name(), QKeySequence(keySequence).toString(), KEY_SECTION);
 43	}
 44}
 45
 46void ShortcutController::updateKey(std::shared_ptr<Shortcut> item, int keySequence) {
 47	int oldShortcut = item->shortcut();
 48	if (m_actions->isHeld(item->name())) {
 49		if (oldShortcut > 0) {
 50			m_heldKeys.take(oldShortcut);
 51		}
 52		if (keySequence > 0) {
 53			m_heldKeys[keySequence] = item;
 54		}
 55	}
 56
 57	item->setShortcut(keySequence);
 58}
 59
 60void ShortcutController::updateButton(const QString& name, int button) {
 61	auto item = m_items[name];
 62	if (!item) {
 63		return;
 64	}
 65	int oldButton = item->button();
 66	if (oldButton >= 0) {
 67		m_buttons.take(oldButton);
 68	}
 69	item->setButton(button);
 70	if (button >= 0) {
 71		clearAxis(name);
 72		m_buttons[button] = item;
 73	}
 74	if (m_config) {
 75		m_config->setQtOption(name, button, BUTTON_SECTION);
 76		if (!m_profileName.isNull()) {
 77			m_config->setQtOption(name, button, BUTTON_PROFILE_SECTION + m_profileName);
 78		}
 79	}
 80}
 81
 82void ShortcutController::updateAxis(const QString& name, int axis, GamepadAxisEvent::Direction direction) {
 83	auto item = m_items[name];
 84	if (!item) {
 85		return;
 86	}
 87	int oldAxis = item->axis();
 88	GamepadAxisEvent::Direction oldDirection = item->direction();
 89	if (oldAxis >= 0) {
 90		m_axes.take(std::make_pair(oldAxis, oldDirection));
 91	}
 92	if (axis >= 0 && direction != GamepadAxisEvent::NEUTRAL) {
 93		clearButton(name);
 94		m_axes[std::make_pair(axis, direction)] = item;
 95	}
 96	item->setAxis(axis, direction);
 97	if (m_config) {
 98		char d = '\0';
 99		if (direction == GamepadAxisEvent::POSITIVE) {
100			d = '+';
101		}
102		if (direction == GamepadAxisEvent::NEGATIVE) {
103			d = '-';
104		}
105		m_config->setQtOption(name, QString("%1%2").arg(d).arg(axis), AXIS_SECTION);
106		if (!m_profileName.isNull()) {
107			m_config->setQtOption(name, QString("%1%2").arg(d).arg(axis), AXIS_PROFILE_SECTION + m_profileName);
108		}
109	}
110}
111
112void ShortcutController::clearKey(const QString& name) {
113	updateKey(name, 0);
114}
115
116void ShortcutController::clearButton(const QString& name) {
117	updateButton(name, -1);
118}
119
120void ShortcutController::clearAxis(const QString& name) {
121	updateAxis(name, -1, GamepadAxisEvent::NEUTRAL);
122}
123
124void ShortcutController::rebuildItems() {
125	m_items.clear();
126	m_buttons.clear();
127	m_axes.clear();
128	m_heldKeys.clear();
129	onSubitems({}, std::bind(&ShortcutController::generateItem, this, std::placeholders::_1));
130}
131
132bool ShortcutController::eventFilter(QObject*, QEvent* event) {
133	if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
134		QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
135		if (keyEvent->isAutoRepeat()) {
136			return false;
137		}
138		int key = keyEvent->key();
139		if (!isModifierKey(key)) {
140			key |= (keyEvent->modifiers() & ~Qt::KeypadModifier);
141		} else {
142			key = toModifierKey(key | (keyEvent->modifiers() & ~Qt::KeypadModifier));
143		}
144		auto item = m_heldKeys.find(key);
145		if (item != m_heldKeys.end()) {
146			Action::BooleanFunction fn = item.value()->action()->booleanAction();
147			fn(event->type() == QEvent::KeyPress);
148			event->accept();
149			return true;
150		}
151	}
152	if (event->type() == GamepadButtonEvent::Down()) {
153		auto item = m_buttons.find(static_cast<GamepadButtonEvent*>(event)->value());
154		if (item == m_buttons.end()) {
155			return false;
156		}
157		Action* action = item.value()->action();
158		if (action) {
159			action->trigger();
160		}
161		event->accept();
162		return true;
163	}
164	if (event->type() == GamepadButtonEvent::Up()) {
165		auto item = m_buttons.find(static_cast<GamepadButtonEvent*>(event)->value());
166		if (item == m_buttons.end()) {
167			return false;
168		}
169		Action* action = item.value()->action();
170		if (action) {
171			action->trigger(false);
172		}
173		event->accept();
174		return true;
175	}
176	if (event->type() == GamepadAxisEvent::Type()) {
177		GamepadAxisEvent* gae = static_cast<GamepadAxisEvent*>(event);
178		auto item = m_axes.find(std::make_pair(gae->axis(), gae->direction()));
179		if (item == m_axes.end()) {
180			return false;
181		}
182		Action* action = item.value()->action();
183		if (action) {
184			action->trigger(gae->isNew());
185		}
186		event->accept();
187		return true;
188	}
189	return false;
190}
191
192void ShortcutController::generateItem(const QString& itemName) {
193	if (itemName.isNull() || itemName[0] == '.') {
194		return;
195	}
196	Action* action = m_actions->getAction(itemName);
197	if (action) {
198		std::shared_ptr<Shortcut> item = std::make_shared<Shortcut>(action);
199		m_items[itemName] = item;
200		loadShortcuts(item);
201	}
202	emit shortcutAdded(itemName);
203}
204
205bool ShortcutController::loadShortcuts(std::shared_ptr<Shortcut> item) {
206	if (item->name().isNull()) {
207		return false;
208	}
209	loadGamepadShortcuts(item);
210	QVariant shortcut = m_config->getQtOption(item->name(), KEY_SECTION);
211	if (!shortcut.isNull()) {
212		if (shortcut.toString().endsWith("+")) {
213			updateKey(item, toModifierShortcut(shortcut.toString()));
214		} else {
215			updateKey(item, QKeySequence(shortcut.toString())[0]);
216		}
217		return true;
218	} else {
219		QKeySequence defaultShortcut = m_actions->defaultShortcut(item->name());
220		if (!defaultShortcut.isEmpty()) {
221			updateKey(item, defaultShortcut[0]);
222			return true;
223		}
224	}
225	return false;
226}
227
228void ShortcutController::loadGamepadShortcuts(std::shared_ptr<Shortcut> item) {
229	if (item->name().isNull()) {
230		return;
231	}
232	QVariant button = m_config->getQtOption(item->name(), !m_profileName.isNull() ? BUTTON_PROFILE_SECTION + m_profileName : BUTTON_SECTION);
233	int oldButton = item->button();
234	if (oldButton >= 0) {
235		m_buttons.take(oldButton);
236		item->setButton(-1);
237	}
238	if (button.isNull() && m_profile) {
239		int buttonInt;
240		if (m_profile->lookupShortcutButton(item->name(), &buttonInt)) {
241			button = buttonInt;
242		}
243	}
244	if (!button.isNull()) {
245		item->setButton(button.toInt());
246		m_buttons[button.toInt()] = item;
247	}
248
249	QVariant axis = m_config->getQtOption(item->name(), !m_profileName.isNull() ? AXIS_PROFILE_SECTION + m_profileName : AXIS_SECTION);
250	int oldAxis = item->axis();
251	GamepadAxisEvent::Direction oldDirection = item->direction();
252	if (oldAxis >= 0) {
253		m_axes.take(std::make_pair(oldAxis, oldDirection));
254		item->setAxis(-1, GamepadAxisEvent::NEUTRAL);
255	}
256	if (axis.isNull() && m_profile) {
257		int axisInt;
258		GamepadAxisEvent::Direction direction;
259		if (m_profile->lookupShortcutAxis(item->name(), &axisInt, &direction)) {
260			axis = QLatin1String(direction == GamepadAxisEvent::Direction::NEGATIVE ? "-" : "+") + QString::number(axisInt);
261		}
262	}
263	if (!axis.isNull()) {
264		QString axisDesc = axis.toString();
265		if (axisDesc.size() >= 2) {
266			GamepadAxisEvent::Direction direction = GamepadAxisEvent::NEUTRAL;
267			if (axisDesc[0] == '-') {
268				direction = GamepadAxisEvent::NEGATIVE;
269			}
270			if (axisDesc[0] == '+') {
271				direction = GamepadAxisEvent::POSITIVE;
272			}
273			bool ok;
274			int axis = axisDesc.mid(1).toInt(&ok);
275			if (ok) {
276				item->setAxis(axis, direction);
277				m_axes[std::make_pair(axis, direction)] = item;
278			}
279		}
280	}
281}
282
283void ShortcutController::loadProfile(const QString& profile) {
284	m_profileName = profile;
285	m_profile = InputProfile::findProfile(profile);
286	onSubitems({}, [this](std::shared_ptr<Shortcut> item) {
287		loadGamepadShortcuts(item);
288	});
289}
290
291void ShortcutController::onSubitems(const QString& menu, std::function<void(std::shared_ptr<Shortcut>)> func) {
292	for (const QString& subitem : m_actions->menuItems(menu)) {
293		auto item = m_items[subitem];
294		if (item) {
295			func(item);
296		}
297		if (subitem.size() && subitem[0] == '.') {
298			onSubitems(subitem.mid(1), func);
299		}
300	}
301}
302
303void ShortcutController::onSubitems(const QString& menu, std::function<void(const QString&)> func) {
304	for (const QString& subitem : m_actions->menuItems(menu)) {
305		func(subitem);
306		if (subitem.size() && subitem[0] == '.') {
307			onSubitems(subitem.mid(1), func);
308		}
309	}
310}
311
312int ShortcutController::toModifierShortcut(const QString& shortcut) {
313	// Qt doesn't seem to work with raw modifier shortcuts!
314	QStringList modifiers = shortcut.split('+');
315	int value = 0;
316	for (const auto& mod : modifiers) {
317		if (mod == QLatin1String("Shift")) {
318			value |= Qt::ShiftModifier;
319			continue;
320		}
321		if (mod == QLatin1String("Ctrl")) {
322			value |= Qt::ControlModifier;
323			continue;
324		}
325		if (mod == QLatin1String("Alt")) {
326			value |= Qt::AltModifier;
327			continue;
328		}
329		if (mod == QLatin1String("Meta")) {
330			value |= Qt::MetaModifier;
331			continue;
332		}
333	}
334	return value;
335}
336
337bool ShortcutController::isModifierKey(int key) {
338	switch (key) {
339	case Qt::Key_Shift:
340	case Qt::Key_Control:
341	case Qt::Key_Alt:
342	case Qt::Key_Meta:
343		return true;
344	default:
345		return false;
346	}
347}
348
349int ShortcutController::toModifierKey(int key) {
350	int modifiers = key & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier);
351	key ^= modifiers;
352	switch (key) {
353	case Qt::Key_Shift:
354		modifiers |= Qt::ShiftModifier;
355		break;
356	case Qt::Key_Control:
357		modifiers |= Qt::ControlModifier;
358		break;
359	case Qt::Key_Alt:
360		modifiers |= Qt::AltModifier;
361		break;
362	case Qt::Key_Meta:
363		modifiers |= Qt::MetaModifier;
364		break;
365	default:
366		break;
367	}
368	return modifiers;
369
370}
371
372const Shortcut* ShortcutController::shortcut(const QString& action) const {
373	return m_items[action].get();
374}
375
376QString ShortcutController::name(int index, const QString& parent) const {
377	QStringList menu = m_actions->menuItems(parent.isNull() || parent[0] != '.' ? parent : parent.mid(1));
378	menu.removeAll({});
379	if (index >= menu.size()) {
380		return {};
381	}
382
383	return menu[index];
384}
385
386QString ShortcutController::parent(const QString& action) const {
387	return QString(".%1").arg(m_actions->menuFor(action));
388}
389
390QString ShortcutController::visibleName(const QString& action) const {
391	if (action.isNull()) {
392		return {};
393	}
394	QString name;
395	if (action[0] == '.') {
396		name = m_actions->menuName(action.mid(1));
397	} else {
398		name = m_actions->getAction(action)->visibleName();
399	}
400	return name.replace(QRegularExpression("&(.)"), "\\1");
401}
402
403int ShortcutController::indexIn(const QString& action) const {
404	QString name = m_actions->menuFor(action);
405	QStringList menu = m_actions->menuItems(name);
406	menu.removeAll({});
407	return menu.indexOf(action);
408}
409
410int ShortcutController::count(const QString& name) const {
411	QStringList menu;
412	if (name.isNull()) {
413		menu = m_actions->menuItems();
414	} else if (name[0] != '.') {
415		return 0;
416	} else {
417		menu = m_actions->menuItems(name.mid(1));
418	}
419	menu.removeAll({});
420	return menu.count();
421}
422
423Shortcut::Shortcut(Action* action)
424	: m_action(action)
425{
426}
427
428void Shortcut::setShortcut(int shortcut) {
429	if (m_shortcut == shortcut) {
430		return;
431	}
432	m_shortcut = shortcut;
433	emit shortcutChanged(shortcut);
434}
435
436void Shortcut::setButton(int button) {
437	if (m_button == button) {
438		return;
439	}
440	m_button = button;
441	emit buttonChanged(button);
442}
443
444void Shortcut::setAxis(int axis, GamepadAxisEvent::Direction direction) {
445	if (m_axis == axis && m_direction == direction) {
446		return;
447	}
448	m_axis = axis;
449	m_direction = direction;
450	emit axisChanged(axis, direction);
451}