all repos — mgba @ e217a1fefb8b87de47a62422f42dfc6009591d2c

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