From 81e65c08d80e39d6ffa199c345f891832ebbae04 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 7 Nov 2014 03:11:44 -0800 Subject: [PATCH] Qt: Add keyboard remapper --- res/keymap.png | Bin 0 -> 32882 bytes res/keymap.svg | 136 ++++++++++++++++++++++ src/gba/gba-input.c | 21 ++++ src/gba/gba-input.h | 1 + src/platform/qt/CMakeLists.txt | 2 + src/platform/qt/GBAKeyEditor.cpp | 171 ++++++++++++++++++++++++++++ src/platform/qt/GBAKeyEditor.h | 59 ++++++++++ src/platform/qt/InputController.cpp | 4 + src/platform/qt/InputController.h | 4 + src/platform/qt/KeyEditor.cpp | 28 +++++ src/platform/qt/KeyEditor.h | 31 +++++ src/platform/qt/Window.cpp | 13 +++ src/platform/qt/Window.h | 2 + src/platform/qt/resources.qrc | 1 + 14 files changed, 473 insertions(+) create mode 100644 res/keymap.png create mode 100644 res/keymap.svg create mode 100644 src/platform/qt/GBAKeyEditor.cpp create mode 100644 src/platform/qt/GBAKeyEditor.h create mode 100644 src/platform/qt/KeyEditor.cpp create mode 100644 src/platform/qt/KeyEditor.h diff --git a/res/keymap.png b/res/keymap.png new file mode 100644 index 0000000000000000000000000000000000000000..0c94d2c8d93f051a6137be020fc1e8524ef4a71d GIT binary patch literal 32882 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 literal 0 HcmV?d00001 diff --git a/res/keymap.svg b/res/keymap.svg new file mode 100644 index 000000000..5ec9d5099 --- /dev/null +++ b/res/keymap.svg @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/gba/gba-input.c b/src/gba/gba-input.c index 0a41bd9ad..ad70d6168 100644 --- a/src/gba/gba-input.c +++ b/src/gba/gba-input.c @@ -109,6 +109,27 @@ void GBAInputBindKey(struct GBAInputMap* map, uint32_t type, int key, enum GBAKe impl->map[input] = key; } +int GBAInputQueryBinding(const struct GBAInputMap* map, uint32_t type, enum GBAKey input) { + if (input >= GBA_KEY_MAX) { + return 0; + } + + size_t m; + const struct GBAInputMapImpl* impl = 0; + for (m = 0; m < map->numMaps; ++m) { + if (map->maps[m].type == type) { + impl = &map->maps[m]; + break; + } + } + if (!impl || !impl->map) { + return 0; + } + + return impl->map[input]; +} + + void GBAInputMapLoad(struct GBAInputMap* map, uint32_t type, const struct Configuration* config) { _loadKey(map, type, config, GBA_KEY_A, "A"); _loadKey(map, type, config, GBA_KEY_B, "B"); diff --git a/src/gba/gba-input.h b/src/gba/gba-input.h index 2f785540b..f7e7cc51a 100644 --- a/src/gba/gba-input.h +++ b/src/gba/gba-input.h @@ -15,6 +15,7 @@ void GBAInputMapDeinit(struct GBAInputMap*); enum GBAKey GBAInputMapKey(const struct GBAInputMap*, uint32_t type, int key); void GBAInputBindKey(struct GBAInputMap*, uint32_t type, int key, enum GBAKey input); +int GBAInputQueryBinding(const struct GBAInputMap*, uint32_t type, enum GBAKey input); void GBAInputMapLoad(struct GBAInputMap*, uint32_t type, const struct Configuration*); diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index c044c4591..0a0456ffc 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -34,8 +34,10 @@ set(SOURCE_FILES ConfigController.cpp Display.cpp GBAApp.cpp + GBAKeyEditor.cpp GameController.cpp InputController.cpp + KeyEditor.cpp LoadSaveState.cpp LogView.cpp SavestateButton.cpp diff --git a/src/platform/qt/GBAKeyEditor.cpp b/src/platform/qt/GBAKeyEditor.cpp new file mode 100644 index 000000000..b69f5c8fd --- /dev/null +++ b/src/platform/qt/GBAKeyEditor.cpp @@ -0,0 +1,171 @@ +#include "GBAKeyEditor.h" + +#include +#include +#include +#include + +#include "InputController.h" +#include "KeyEditor.h" + +extern "C" { +#include "gba-input.h" +} + +using namespace QGBA; + +const qreal GBAKeyEditor::DPAD_CENTER_X = 0.247; +const qreal GBAKeyEditor::DPAD_CENTER_Y = 0.431; +const qreal GBAKeyEditor::DPAD_WIDTH = 0.1; +const qreal GBAKeyEditor::DPAD_HEIGHT = 0.1; + +GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, QWidget* parent) + : QWidget(parent) + , m_background(QString(":/res/keymap.png")) +{ + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + setWindowFlags(windowFlags() & ~Qt::WindowFullscreenButtonHint); + setMinimumSize(300, 300); + + const GBAInputMap* map = controller->map(); + + m_keyDU = new KeyEditor(this); + m_keyDD = new KeyEditor(this); + m_keyDL = new KeyEditor(this); + m_keyDR = new KeyEditor(this); + m_keySelect = new KeyEditor(this); + m_keyStart = new KeyEditor(this); + m_keyA = new KeyEditor(this); + m_keyB = new KeyEditor(this); + m_keyL = new KeyEditor(this); + m_keyR = new KeyEditor(this); + m_keyDU->setValue(GBAInputQueryBinding(map, type, GBA_KEY_UP)); + m_keyDD->setValue(GBAInputQueryBinding(map, type, GBA_KEY_DOWN)); + m_keyDL->setValue(GBAInputQueryBinding(map, type, GBA_KEY_LEFT)); + m_keyDR->setValue(GBAInputQueryBinding(map, type, GBA_KEY_RIGHT)); + m_keySelect->setValue(GBAInputQueryBinding(map, type, GBA_KEY_SELECT)); + m_keyStart->setValue(GBAInputQueryBinding(map, type, GBA_KEY_START)); + m_keyA->setValue(GBAInputQueryBinding(map, type, GBA_KEY_A)); + m_keyB->setValue(GBAInputQueryBinding(map, type, GBA_KEY_B)); + m_keyL->setValue(GBAInputQueryBinding(map, type, GBA_KEY_L)); + m_keyR->setValue(GBAInputQueryBinding(map, type, GBA_KEY_R)); + + connect(m_keyDU, &KeyEditor::valueChanged, [this, type, controller](int key) { + controller->bindKey(type, key, GBA_KEY_UP); + setNext(); + }); + + connect(m_keyDD, &KeyEditor::valueChanged, [this, type, controller](int key) { + controller->bindKey(type, key, GBA_KEY_DOWN); + setNext(); + }); + + connect(m_keyDL, &KeyEditor::valueChanged, [this, type, controller](int key) { + controller->bindKey(type, key, GBA_KEY_LEFT); + setNext(); + }); + + connect(m_keyDR, &KeyEditor::valueChanged, [this, type, controller](int key) { + controller->bindKey(type, key, GBA_KEY_RIGHT); + setNext(); + }); + + connect(m_keySelect, &KeyEditor::valueChanged, [this, type, controller](int key) { + controller->bindKey(type, key, GBA_KEY_SELECT); + setNext(); + }); + + connect(m_keyStart, &KeyEditor::valueChanged, [this, type, controller](int key) { + controller->bindKey(type, key, GBA_KEY_START); + setNext(); + }); + + connect(m_keyA, &KeyEditor::valueChanged, [this, type, controller](int key) { + controller->bindKey(type, key, GBA_KEY_A); + setNext(); + }); + + connect(m_keyB, &KeyEditor::valueChanged, [this, type, controller](int key) { + controller->bindKey(type, key, GBA_KEY_B); + setNext(); + }); + + connect(m_keyL, &KeyEditor::valueChanged, [this, type, controller](int key) { + controller->bindKey(type, key, GBA_KEY_L); + setNext(); + }); + + connect(m_keyR, &KeyEditor::valueChanged, [this, type, controller](int key) { + controller->bindKey(type, key, GBA_KEY_R); + setNext(); + }); + + m_setAll = new QPushButton(tr("Set all"), this); + connect(m_setAll, SIGNAL(pressed()), this, SLOT(setAll())); + + m_keyOrder = QList{ + m_keyDU, + m_keyDR, + m_keyDD, + m_keyDL, + m_keyA, + m_keyB, + m_keySelect, + m_keyStart, + m_keyL, + m_keyR + }; + + m_currentKey = m_keyOrder.end(); + + QPixmap background(":/res/keymap.png"); + m_background = background.scaled(QSize(300, 300) * devicePixelRatio(), Qt::KeepAspectRatio, Qt::SmoothTransformation); + m_background.setDevicePixelRatio(devicePixelRatio()); +} + +void GBAKeyEditor::setAll() { + m_currentKey = m_keyOrder.begin(); + (*m_currentKey)->setFocus(); +} + +void GBAKeyEditor::resizeEvent(QResizeEvent* event) { + setLocation(m_setAll, 0.5, 0.2); + setLocation(m_keyDU, DPAD_CENTER_X, DPAD_CENTER_Y - DPAD_HEIGHT); + setLocation(m_keyDD, DPAD_CENTER_X, DPAD_CENTER_Y + DPAD_HEIGHT); + setLocation(m_keyDL, DPAD_CENTER_X - DPAD_WIDTH, DPAD_CENTER_Y); + setLocation(m_keyDR, DPAD_CENTER_X + DPAD_WIDTH, DPAD_CENTER_Y); + setLocation(m_keySelect, 0.415, 0.93); + setLocation(m_keyStart, 0.585, 0.93); + setLocation(m_keyA, 0.826, 0.451); + setLocation(m_keyB, 0.667, 0.490); + setLocation(m_keyL, 0.1, 0.1); + setLocation(m_keyR, 0.9, 0.1); +} + +void GBAKeyEditor::paintEvent(QPaintEvent* event) { + QPainter painter(this); + painter.drawPixmap(0, 0, m_background); +} + +void GBAKeyEditor::setNext() { + if (m_currentKey == m_keyOrder.end()) { + return; + } + + if (!(*m_currentKey)->hasFocus()) { + m_currentKey = m_keyOrder.end(); + } + + ++m_currentKey; + if (m_currentKey != m_keyOrder.end()) { + (*m_currentKey)->setFocus(); + } else { + (*(m_currentKey - 1))->clearFocus(); + } +} + +void GBAKeyEditor::setLocation(QWidget* widget, qreal x, qreal y) { + QSize s = size(); + QSize hint = widget->sizeHint(); + widget->setGeometry(s.width() * x - hint.width() / 2.0, s.height() * y - hint.height() / 2.0, hint.width(), hint.height()); +} diff --git a/src/platform/qt/GBAKeyEditor.h b/src/platform/qt/GBAKeyEditor.h new file mode 100644 index 000000000..1f03a9674 --- /dev/null +++ b/src/platform/qt/GBAKeyEditor.h @@ -0,0 +1,59 @@ +#ifndef QGBA_GBA_KEY_EDITOR +#define QGBA_GBA_KEY_EDITOR + +#include +#include +#include + +class QPushButton; + +namespace QGBA { + +class InputController; +class KeyEditor; + +class GBAKeyEditor : public QWidget { +Q_OBJECT + +public: + GBAKeyEditor(InputController* controller, int type, QWidget* parent = nullptr); + +public slots: + void setAll(); + +protected: + virtual void resizeEvent(QResizeEvent*) override; + virtual void paintEvent(QPaintEvent*) override; + +private: + static const qreal DPAD_CENTER_X; + static const qreal DPAD_CENTER_Y; + static const qreal DPAD_WIDTH; + static const qreal DPAD_HEIGHT; + + void setNext(); + + void setLocation(QWidget* widget, qreal x, qreal y); + + QPushButton* m_setAll; + KeyEditor* m_keyDU; + KeyEditor* m_keyDD; + KeyEditor* m_keyDL; + KeyEditor* m_keyDR; + KeyEditor* m_keySelect; + KeyEditor* m_keyStart; + KeyEditor* m_keyA; + KeyEditor* m_keyB; + KeyEditor* m_keyL; + KeyEditor* m_keyR; + QList m_keyOrder; + QList::iterator m_currentKey; + + InputController* m_controller; + + QPixmap m_background; +}; + +} + +#endif diff --git a/src/platform/qt/InputController.cpp b/src/platform/qt/InputController.cpp index 4470e16e9..da5a1b696 100644 --- a/src/platform/qt/InputController.cpp +++ b/src/platform/qt/InputController.cpp @@ -52,6 +52,10 @@ GBAKey InputController::mapKeyboard(int key) const { return GBAInputMapKey(&m_inputMap, KEYBOARD, key); } +void InputController::bindKey(uint32_t type, int key, GBAKey gbaKey) { + return GBAInputBindKey(&m_inputMap, type, key, gbaKey); +} + #ifdef BUILD_SDL int InputController::testSDLEvents() { SDL_Joystick* joystick = m_sdlEvents.joystick; diff --git a/src/platform/qt/InputController.h b/src/platform/qt/InputController.h index fb4593341..ea54b88cc 100644 --- a/src/platform/qt/InputController.h +++ b/src/platform/qt/InputController.h @@ -25,6 +25,10 @@ public: GBAKey mapKeyboard(int key) const; + void bindKey(uint32_t type, int key, GBAKey); + + const GBAInputMap* map() const { return &m_inputMap; } + #ifdef BUILD_SDL int testSDLEvents(); #endif diff --git a/src/platform/qt/KeyEditor.cpp b/src/platform/qt/KeyEditor.cpp new file mode 100644 index 000000000..958ce69e1 --- /dev/null +++ b/src/platform/qt/KeyEditor.cpp @@ -0,0 +1,28 @@ +#include "KeyEditor.h" + +#include + +using namespace QGBA; + +KeyEditor::KeyEditor(QWidget* parent) + : QLineEdit(parent) +{ + setAlignment(Qt::AlignCenter); +} + +void KeyEditor::setValue(int key) { + setText(QKeySequence(key).toString(QKeySequence::NativeText)); + m_key = key; + emit valueChanged(key); +} + +QSize KeyEditor::sizeHint() const { + QSize hint = QLineEdit::sizeHint(); + hint.setWidth(40); + return hint; +} + +void KeyEditor::keyPressEvent(QKeyEvent* event) { + setValue(event->key()); + event->accept(); +} diff --git a/src/platform/qt/KeyEditor.h b/src/platform/qt/KeyEditor.h new file mode 100644 index 000000000..362bcd6ab --- /dev/null +++ b/src/platform/qt/KeyEditor.h @@ -0,0 +1,31 @@ +#ifndef QGBA_KEY_EDITOR +#define QGBA_KEY_EDITOR + +#include + +namespace QGBA { + +class KeyEditor : public QLineEdit { +Q_OBJECT + +public: + KeyEditor(QWidget* parent = nullptr); + + void setValue(int key); + int value() const { return m_key; } + + virtual QSize sizeHint() const override; + +signals: + void valueChanged(int key); + +protected: + virtual void keyPressEvent(QKeyEvent* event) override; + +private: + int m_key; +}; + +} + +#endif diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index e59624d90..e64fb7ec0 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -8,6 +8,7 @@ #include "ConfigController.h" #include "GameController.h" +#include "GBAKeyEditor.h" #include "GDBController.h" #include "GDBWindow.h" #include "LoadSaveState.h" @@ -136,6 +137,13 @@ void Window::selectPatch() { } } +void Window::openKeymapWindow() { + GBAKeyEditor* keyEditor = new GBAKeyEditor(&m_inputController, InputController::KEYBOARD); + connect(this, SIGNAL(shutdown()), keyEditor, SLOT(close())); + keyEditor->setAttribute(Qt::WA_DeleteOnClose); + keyEditor->show(); +} + #ifdef USE_FFMPEG void Window::openVideoWindow() { if (!m_videoView) { @@ -395,6 +403,11 @@ void Window::setupMenu(QMenuBar* menubar) { audioSync->connect([this](const QVariant& value) { m_controller->setAudioSync(value.toBool()); }); m_config->updateOption("audioSync"); + emulationMenu->addSeparator(); + QAction* keymap = new QAction(tr("Remap keyboard..."), emulationMenu); + connect(keymap, SIGNAL(triggered()), this, SLOT(openKeymapWindow())); + emulationMenu->addAction(keymap); + QMenu* videoMenu = menubar->addMenu(tr("&Video")); QMenu* frameMenu = videoMenu->addMenu(tr("Frame size")); QAction* setSize = new QAction(tr("1x"), videoMenu); diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index a6e7fb350..312ee6f23 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -50,6 +50,8 @@ public slots: void loadConfig(); void saveConfig(); + void openKeymapWindow(); + #ifdef USE_FFMPEG void openVideoWindow(); #endif diff --git a/src/platform/qt/resources.qrc b/src/platform/qt/resources.qrc index d90ba8844..492193011 100644 --- a/src/platform/qt/resources.qrc +++ b/src/platform/qt/resources.qrc @@ -1,5 +1,6 @@ ../../../res/mgba-1024.png + ../../../res/keymap.png