all repos — mgba @ cb0f95b07053e63e817bd05df0a36bf917667d09

mGBA Game Boy Advance Emulator

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