all repos — mgba @ b69cbd433d8a93d0679d9623118dfc4adff37aad

mGBA Game Boy Advance Emulator

src/platform/qt/GBAKeyEditor.cpp (view raw)

  1/* Copyright (c) 2013-2014 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 "GBAKeyEditor.h"
  7
  8#include <QApplication>
  9#include <QComboBox>
 10#include <QHBoxLayout>
 11#include <QPaintEvent>
 12#include <QPainter>
 13#include <QPushButton>
 14#include <QVBoxLayout>
 15
 16#include "InputController.h"
 17#include "KeyEditor.h"
 18
 19#ifdef BUILD_SDL
 20#include "platform/sdl/sdl-events.h"
 21#endif
 22
 23using namespace QGBA;
 24
 25const qreal GBAKeyEditor::DPAD_CENTER_X = 0.247;
 26const qreal GBAKeyEditor::DPAD_CENTER_Y = 0.432;
 27const qreal GBAKeyEditor::DPAD_WIDTH = 0.12;
 28const qreal GBAKeyEditor::DPAD_HEIGHT = 0.12;
 29
 30GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString& profile, QWidget* parent)
 31	: QWidget(parent)
 32	, m_type(type)
 33	, m_profile(profile)
 34	, m_controller(controller)
 35{
 36	setWindowFlags(windowFlags() & ~Qt::WindowFullscreenButtonHint);
 37	setMinimumSize(300, 300);
 38
 39	const mInputMap* map = controller->map();
 40	controller->stealFocus(this);
 41
 42	m_keyDU = new KeyEditor(this);
 43	m_keyDD = new KeyEditor(this);
 44	m_keyDL = new KeyEditor(this);
 45	m_keyDR = new KeyEditor(this);
 46	m_keySelect = new KeyEditor(this);
 47	m_keyStart = new KeyEditor(this);
 48	m_keyA = new KeyEditor(this);
 49	m_keyB = new KeyEditor(this);
 50	m_keyL = new KeyEditor(this);
 51	m_keyR = new KeyEditor(this);
 52
 53	refresh();
 54
 55#ifdef BUILD_SDL
 56	if (type == SDL_BINDING_BUTTON) {
 57		m_profileSelect = new QComboBox(this);
 58		connect(m_profileSelect, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
 59		        this, &GBAKeyEditor::selectGamepad);
 60
 61		updateJoysticks();
 62
 63		m_clear = new QWidget(this);
 64		QHBoxLayout* layout = new QHBoxLayout;
 65		m_clear->setLayout(layout);
 66		layout->setSpacing(6);
 67
 68		QPushButton* clearButton = new QPushButton(tr("Clear Button"));
 69		layout->addWidget(clearButton);
 70		connect(clearButton, &QAbstractButton::pressed, [this]() {
 71			if (!findFocus()) {
 72				return;
 73			}
 74			bool signalsBlocked = (*m_currentKey)->blockSignals(true);
 75			(*m_currentKey)->clearButton();
 76			(*m_currentKey)->clearHat();
 77			(*m_currentKey)->blockSignals(signalsBlocked);
 78		});
 79
 80		QPushButton* clearAxis = new QPushButton(tr("Clear Analog"));
 81		layout->addWidget(clearAxis);
 82		connect(clearAxis, &QAbstractButton::pressed, [this]() {
 83			if (!findFocus()) {
 84				return;
 85			}
 86			bool signalsBlocked = (*m_currentKey)->blockSignals(true);
 87			(*m_currentKey)->clearAxis();
 88			(*m_currentKey)->blockSignals(signalsBlocked);
 89		});
 90
 91		QPushButton* updateJoysticksButton = new QPushButton(tr("Refresh"));
 92		layout->addWidget(updateJoysticksButton);
 93		connect(updateJoysticksButton, &QAbstractButton::pressed, this, &GBAKeyEditor::updateJoysticks);
 94	}
 95#endif
 96
 97	m_buttons = new QWidget(this);
 98	QVBoxLayout* layout = new QVBoxLayout;
 99	m_buttons->setLayout(layout);
100
101	QPushButton* setAll = new QPushButton(tr("Set all"));
102	connect(setAll, &QAbstractButton::pressed, this, &GBAKeyEditor::setAll);
103	layout->addWidget(setAll);
104
105	layout->setSpacing(6);
106
107	m_keyOrder = QList<KeyEditor*>{
108		m_keyDU,
109		m_keyDR,
110		m_keyDD,
111		m_keyDL,
112		m_keyA,
113		m_keyB,
114		m_keySelect,
115		m_keyStart,
116		m_keyL,
117		m_keyR
118	};
119
120	for (auto& key : m_keyOrder) {
121		connect(key, &KeyEditor::valueChanged, this, &GBAKeyEditor::setNext);
122		connect(key, &KeyEditor::axisChanged, this, &GBAKeyEditor::setNext);
123		connect(key, &KeyEditor::hatChanged, this, &GBAKeyEditor::setNext);
124		key->installEventFilter(this);
125	}
126
127	m_currentKey = m_keyOrder.end();
128
129	m_background.load(":/res/keymap.qpic");
130
131	setAll->setFocus();
132}
133
134GBAKeyEditor::~GBAKeyEditor() {
135	m_controller->releaseFocus(this);
136}
137
138void GBAKeyEditor::setAll() {
139	m_currentKey = m_keyOrder.begin();
140	(*m_currentKey)->setFocus();
141}
142
143void GBAKeyEditor::resizeEvent(QResizeEvent* event) {
144	setLocation(m_buttons, 0.5, 0.2);
145	setLocation(m_keyDU, DPAD_CENTER_X, DPAD_CENTER_Y - DPAD_HEIGHT);
146	setLocation(m_keyDD, DPAD_CENTER_X, DPAD_CENTER_Y + DPAD_HEIGHT);
147	setLocation(m_keyDL, DPAD_CENTER_X - DPAD_WIDTH, DPAD_CENTER_Y);
148	setLocation(m_keyDR, DPAD_CENTER_X + DPAD_WIDTH, DPAD_CENTER_Y);
149	setLocation(m_keySelect, 0.415, 0.93);
150	setLocation(m_keyStart, 0.585, 0.93);
151	setLocation(m_keyA, 0.826, 0.475);
152	setLocation(m_keyB, 0.667, 0.514);
153	setLocation(m_keyL, 0.1, 0.1);
154	setLocation(m_keyR, 0.9, 0.1);
155
156	if (m_profileSelect) {
157		setLocation(m_profileSelect, 0.5, 0.67);
158	}
159
160	if (m_clear) {
161		setLocation(m_clear, 0.5, 0.77);
162	}
163}
164
165void GBAKeyEditor::paintEvent(QPaintEvent* event) {
166	QPainter painter(this);
167	painter.scale(width() / 480.0, height() / 480.0);
168	painter.drawPicture(0, 0, m_background);
169}
170
171void GBAKeyEditor::closeEvent(QCloseEvent*) {
172	m_controller->releaseFocus(this);
173}
174
175bool GBAKeyEditor::event(QEvent* event) {
176	QEvent::Type type = event->type();
177	if (type == QEvent::WindowActivate || type == QEvent::Show) {
178		m_controller->stealFocus(this);
179	} else if (type == QEvent::WindowDeactivate || type == QEvent::Hide) {
180		m_controller->releaseFocus(this);
181	}
182	return QWidget::event(event);
183}
184
185bool GBAKeyEditor::eventFilter(QObject* obj, QEvent* event) {
186	KeyEditor* keyEditor = static_cast<KeyEditor*>(obj);
187	if (event->type() == QEvent::FocusOut) {
188		keyEditor->setPalette(QApplication::palette(keyEditor));
189	}
190	if (event->type() != QEvent::FocusIn) {
191		return false;
192	}
193
194	QPalette palette = keyEditor->palette();
195	palette.setBrush(keyEditor->backgroundRole(), palette.highlight());
196	palette.setBrush(keyEditor->foregroundRole(), palette.highlightedText());
197	keyEditor->setPalette(palette);
198
199	findFocus(keyEditor);
200	return true;
201}
202
203void GBAKeyEditor::setNext() {
204	if (m_currentKey == m_keyOrder.end()) {
205		return;
206	}
207
208	++m_currentKey;
209	if (m_currentKey != m_keyOrder.end()) {
210		(*m_currentKey)->setFocus();
211	} else {
212		(*(m_currentKey - 1))->clearFocus();
213	}
214}
215
216void GBAKeyEditor::save() {
217#ifdef BUILD_SDL
218	m_controller->unbindAllAxes(m_type);
219#endif
220
221	bindKey(m_keyDU, GBA_KEY_UP);
222	bindKey(m_keyDD, GBA_KEY_DOWN);
223	bindKey(m_keyDL, GBA_KEY_LEFT);
224	bindKey(m_keyDR, GBA_KEY_RIGHT);
225	bindKey(m_keySelect, GBA_KEY_SELECT);
226	bindKey(m_keyStart, GBA_KEY_START);
227	bindKey(m_keyA, GBA_KEY_A);
228	bindKey(m_keyB, GBA_KEY_B);
229	bindKey(m_keyL, GBA_KEY_L);
230	bindKey(m_keyR, GBA_KEY_R);
231	m_controller->saveConfiguration(m_type);
232
233#ifdef BUILD_SDL
234	if (m_profileSelect) {
235		m_controller->setPreferredGamepad(m_type, m_profileSelect->currentText());
236	}
237#endif
238
239	if (!m_profile.isNull()) {
240		m_controller->saveProfile(m_type, m_profile);
241	}
242}
243
244void GBAKeyEditor::refresh() {
245	const mInputMap* map = m_controller->map();
246	lookupBinding(map, m_keyDU, GBA_KEY_UP);
247	lookupBinding(map, m_keyDD, GBA_KEY_DOWN);
248	lookupBinding(map, m_keyDL, GBA_KEY_LEFT);
249	lookupBinding(map, m_keyDR, GBA_KEY_RIGHT);
250	lookupBinding(map, m_keySelect, GBA_KEY_SELECT);
251	lookupBinding(map, m_keyStart, GBA_KEY_START);
252	lookupBinding(map, m_keyA, GBA_KEY_A);
253	lookupBinding(map, m_keyB, GBA_KEY_B);
254	lookupBinding(map, m_keyL, GBA_KEY_L);
255	lookupBinding(map, m_keyR, GBA_KEY_R);
256
257#ifdef BUILD_SDL
258	lookupAxes(map);
259	lookupHats(map);
260#endif
261}
262
263void GBAKeyEditor::lookupBinding(const mInputMap* map, KeyEditor* keyEditor, GBAKey key) {
264#ifdef BUILD_SDL
265	if (m_type == SDL_BINDING_BUTTON) {
266		int value = mInputQueryBinding(map, m_type, key);
267		keyEditor->setValueButton(value);
268		return;
269	}
270#endif
271	keyEditor->setValueKey(mInputQueryBinding(map, m_type, key));
272}
273
274#ifdef BUILD_SDL
275void GBAKeyEditor::lookupAxes(const mInputMap* map) {
276	mInputEnumerateAxes(map, m_type, [](int axis, const mInputAxis* description, void* user) {
277		GBAKeyEditor* self = static_cast<GBAKeyEditor*>(user);
278		if (description->highDirection != GBA_KEY_NONE) {
279			KeyEditor* key = self->keyById(static_cast<enum GBAKey>(description->highDirection));
280			if (key) {
281				key->setValueAxis(axis, description->deadHigh);
282			}
283		}
284		if (description->lowDirection != GBA_KEY_NONE) {
285			KeyEditor* key = self->keyById(static_cast<enum GBAKey>(description->lowDirection));
286			if (key) {
287				key->setValueAxis(axis, description->deadLow);
288			}
289		}
290	}, this);
291}
292
293void GBAKeyEditor::lookupHats(const mInputMap* map) {
294	struct mInputHatBindings bindings;
295	int i = 0;
296	while (mInputQueryHat(map, m_type, i, &bindings)) {
297		if (bindings.up >= 0) {
298			KeyEditor* key = keyById(static_cast<enum GBAKey>(bindings.up));
299			if (key) {
300				key->setValueHat(i, GamepadHatEvent::UP);
301			}
302		}
303		if (bindings.right >= 0) {
304			KeyEditor* key = keyById(static_cast<enum GBAKey>(bindings.right));
305			if (key) {
306				key->setValueHat(i, GamepadHatEvent::RIGHT);
307			}
308		}
309		if (bindings.down >= 0) {
310			KeyEditor* key = keyById(static_cast<enum GBAKey>(bindings.down));
311			if (key) {
312				key->setValueHat(i, GamepadHatEvent::DOWN);
313			}
314		}
315		if (bindings.left >= 0) {
316			KeyEditor* key = keyById(static_cast<enum GBAKey>(bindings.left));
317			if (key) {
318				key->setValueHat(i, GamepadHatEvent::LEFT);
319			}
320		}
321		++i;
322	}
323}
324#endif
325
326void GBAKeyEditor::bindKey(const KeyEditor* keyEditor, GBAKey key) {
327#ifdef BUILD_SDL
328	if (m_type == SDL_BINDING_BUTTON && keyEditor->axis() >= 0) {
329		m_controller->bindAxis(m_type, keyEditor->axis(), keyEditor->direction(), key);
330	}
331	if (m_type == SDL_BINDING_BUTTON && keyEditor->hat() >= 0) {
332		m_controller->bindHat(m_type, keyEditor->hat(), keyEditor->hatDirection(), key);
333	}
334#endif
335	m_controller->bindKey(m_type, keyEditor->value(), key);
336}
337
338bool GBAKeyEditor::findFocus(KeyEditor* needle) {
339	if (m_currentKey != m_keyOrder.end() && (*m_currentKey)->hasFocus()) {
340		return true;
341	}
342
343	for (auto key = m_keyOrder.begin(); key != m_keyOrder.end(); ++key) {
344		if ((*key)->hasFocus() || needle == *key) {
345			m_currentKey = key;
346			return true;
347		}
348	}
349	return m_currentKey != m_keyOrder.end();
350}
351
352#ifdef BUILD_SDL
353void GBAKeyEditor::setAxisValue(int axis, int32_t value) {
354	if (!findFocus()) {
355		return;
356	}
357	KeyEditor* focused = *m_currentKey;
358	focused->setValueAxis(axis, value);
359}
360
361void GBAKeyEditor::selectGamepad(int index) {
362	m_controller->setGamepad(m_type, index);
363	m_profile = m_profileSelect->currentText();
364	m_controller->loadProfile(m_type, m_profile);
365	refresh();
366}
367#endif
368
369KeyEditor* GBAKeyEditor::keyById(GBAKey key) {
370	switch (key) {
371	case GBA_KEY_UP:
372		return m_keyDU;
373	case GBA_KEY_DOWN:
374		return m_keyDD;
375	case GBA_KEY_LEFT:
376		return m_keyDL;
377	case GBA_KEY_RIGHT:
378		return m_keyDR;
379	case GBA_KEY_A:
380		return m_keyA;
381	case GBA_KEY_B:
382		return m_keyB;
383	case GBA_KEY_L:
384		return m_keyL;
385	case GBA_KEY_R:
386		return m_keyR;
387	case GBA_KEY_SELECT:
388		return m_keySelect;
389	case GBA_KEY_START:
390		return m_keyStart;
391	default:
392		break;
393	}
394	return nullptr;
395}
396
397void GBAKeyEditor::setLocation(QWidget* widget, qreal x, qreal y) {
398	QSize s = size();
399	QSize hint = widget->sizeHint();
400	widget->setGeometry(s.width() * x - hint.width() / 2.0, s.height() * y - hint.height() / 2.0, hint.width(),
401	                    hint.height());
402}
403
404#ifdef BUILD_SDL
405void GBAKeyEditor::updateJoysticks() {
406	m_controller->updateJoysticks();
407	m_controller->recalibrateAxes();
408
409	// Block the currentIndexChanged signal while rearranging the combo box
410	auto wasBlocked = m_profileSelect->blockSignals(true);
411	m_profileSelect->clear();
412	m_profileSelect->addItems(m_controller->connectedGamepads(m_type));
413	int activeGamepad = m_controller->gamepad(m_type);
414	m_profileSelect->setCurrentIndex(activeGamepad);
415	m_profileSelect->blockSignals(wasBlocked);
416
417	selectGamepad(activeGamepad);
418}
419#endif