all repos — mgba @ b04bf153048f3452da372e22b6abb8f875d0c915

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