all repos — mgba @ fa73d25acb0b20636bd0adbe3611b528cc1f954f

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