all repos — mgba @ 0cd28060e07c587ab10901ce815563a9e998f297

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