all repos — mgba @ c14da05d8dca225010677643c32fea5c0ac8517a

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