all repos — mgba @ a5bcfc7c80db49fadd652677ed4a3b87c6f235e5

mGBA Game Boy Advance Emulator

Merge branch 'feature/input-revamp' into medusa
Vicki Pfau vi@endrift.com
Sun, 26 Mar 2017 23:50:02 -0700
commit

a5bcfc7c80db49fadd652677ed4a3b87c6f235e5

parent

938c2f84b814337997363a9be6b171d1f38d23e0

M CHANGESCHANGES

@@ -33,6 +33,7 @@ - GBA I/O: Handle audio registers specially when deserializing

- Util: Fix highest-fd socket not being returned by SocketAccept - Qt: Fix linking after some windows have been closed - GBA Video: Fix wrong palette on 256-color sprites in OBJWIN + - Windows: Fix VDir.rewind Misc: - SDL: Remove scancode key input - GBA Video: Clean up unused timers

@@ -76,6 +77,9 @@ - Qt: Rename "Resample video" option to "Bilinear filtering"

- GBA Video: Optimize when BLD* registers are written frequently - Core: Cores can now have multiple sets of callbacks - GBA: Ignore invalid opcodes used by the Wii U VC emulator + - Qt: Remove audio thread + - Qt: Remove audio buffer sizing in AudioProcessorQt + - Qt: Re-enable QtMultimedia on Windows 0.5.2: (2016-12-31) Bugfixes:
M CMakeLists.txtCMakeLists.txt

@@ -294,6 +294,7 @@ check_function_exists(setlocale HAVE_SETLOCALE)

else() if(DEFINED 3DS) check_function_exists(snprintf_l HAVE_SNPRINTF_L) + check_function_exists(strtof_l HAVE_STRTOF_L) check_function_exists(newlocale HAVE_NEWLOCALE) check_function_exists(freelocale HAVE_FREELOCALE) check_function_exists(uselocale HAVE_USELOCALE)
M include/mgba/internal/ds/input.hinclude/mgba/internal/ds/input.h

@@ -14,7 +14,7 @@ #include <mgba/core/input.h>

extern const struct mInputPlatformInfo DSInputInfo; -enum GBAKey { +enum DSKey { DS_KEY_A = 0, DS_KEY_B = 1, DS_KEY_SELECT = 2,
A include/mgba/internal/gb/input.h

@@ -0,0 +1,32 @@

+/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef GB_INPUT_H +#define GB_INPUT_H + +#include <mgba-util/common.h> + +CXX_GUARD_START + +#include <mgba/core/input.h> + +extern const struct mInputPlatformInfo GBInputInfo; + +enum GBKey { + GB_KEY_A = 0, + GB_KEY_B = 1, + GB_KEY_SELECT = 2, + GB_KEY_START = 3, + GB_KEY_RIGHT = 4, + GB_KEY_LEFT = 5, + GB_KEY_UP = 6, + GB_KEY_DOWN = 7, + GB_KEY_MAX, + GB_KEY_NONE = -1 +}; + +CXX_GUARD_END + +#endif
M src/gb/core.csrc/gb/core.c

@@ -9,6 +9,7 @@ #include <mgba/core/core.h>

#include <mgba/internal/gb/cheats.h> #include <mgba/internal/gb/extra/cli.h> #include <mgba/internal/gb/gb.h> +#include <mgba/internal/gb/input.h> #include <mgba/internal/gb/mbc.h> #include <mgba/internal/gb/overrides.h> #include <mgba/internal/gb/renderers/software.h>

@@ -68,7 +69,7 @@ mDirectorySetInit(&core->dirs);

#endif #ifndef MINIMAL_CORE - core->inputInfo = &GBAInputInfo; // TODO: GBInputInfo + core->inputInfo = &GBInputInfo; #endif return true;

@@ -269,7 +270,7 @@ }

} if (!found) { GBDetectModel(gb); - const char* configPath; + const char* configPath = NULL; switch (gb->model) { case GB_MODEL_DMG:
A src/gb/input.c

@@ -0,0 +1,29 @@

+/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include <mgba/internal/gb/input.h> + +#include <mgba/gb/interface.h> + +const struct mInputPlatformInfo GBInputInfo = { + .platformName = "gb", + .keyId = (const char*[]) { + "A", + "B", + "Select", + "Start", + "Right", + "Left", + "Up", + "Down" + }, + .nKeys = GB_KEY_MAX, + .hat = { + .up = GB_KEY_UP, + .left = GB_KEY_LEFT, + .down = GB_KEY_DOWN, + .right = GB_KEY_RIGHT + } +};
M src/platform/qt/AudioProcessorQt.cppsrc/platform/qt/AudioProcessorQt.cpp

@@ -58,7 +58,6 @@ }

m_device->setInput(input()); m_device->setFormat(m_audioOutput->format()); - m_audioOutput->setBufferSize(input()->core->getAudioBufferSize(input()->core) * 4); m_audioOutput->start(m_device); return m_audioOutput->state() == QAudio::ActiveState;

@@ -71,12 +70,6 @@ }

} void AudioProcessorQt::setBufferSamples(int samples) { - AudioProcessor::setBufferSamples(samples); - if (m_audioOutput) { - m_audioOutput->stop(); - m_audioOutput->setBufferSize(samples * 4); - m_audioOutput->start(m_device); - } } void AudioProcessorQt::inputParametersChanged() {
M src/platform/qt/CMakeLists.txtsrc/platform/qt/CMakeLists.txt

@@ -30,11 +30,8 @@

set(CMAKE_AUTOMOC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) -if(NOT WIN32 OR NOT BUILD_SDL) - find_package(Qt5Multimedia) -endif() +find_package(Qt5Multimedia) find_package(Qt5OpenGL) -find_package(Qt5Network) find_package(Qt5Widgets) if(NOT BUILD_GL AND NOT BUILD_GLES2)

@@ -43,7 +40,7 @@ set(BUILD_QT OFF PARENT_SCOPE)

return() endif() -if(NOT Qt5OpenGL_FOUND OR NOT Qt5Widgets_FOUND OR NOT Qt5Network_FOUND) +if(NOT Qt5OpenGL_FOUND OR NOT Qt5Widgets_FOUND) message(WARNING "Cannot find Qt modules") set(BUILD_QT OFF PARENT_SCOPE) return()

@@ -78,7 +75,6 @@ Display.cpp

DisplayGL.cpp DisplayQt.cpp GBAApp.cpp - GBAKeyEditor.cpp GIFView.cpp GameController.cpp GamepadAxisEvent.cpp

@@ -86,6 +82,8 @@ GamepadButtonEvent.cpp

GamepadHatEvent.cpp IOViewer.cpp InputController.cpp + InputItem.cpp + InputModel.cpp InputProfile.cpp KeyEditor.cpp LoadSaveState.cpp

@@ -103,7 +101,6 @@ SavestateButton.cpp

SensorView.cpp SettingsView.cpp ShaderSelector.cpp - ShortcutController.cpp ShortcutView.cpp Swatch.cpp TilePainter.cpp

@@ -265,16 +262,31 @@ if(APPLE OR WIN32)

set_target_properties(${BINARY_NAME}-qt PROPERTIES OUTPUT_NAME ${PROJECT_NAME}) endif() if(APPLE) - get_target_property(QTCOCOA Qt5::QCocoaIntegrationPlugin LOCATION) - get_target_property(COREAUDIO Qt5::CoreAudioPlugin LOCATION) - get_target_property(BUNDLE_PATH ${BINARY_NAME}-qt LOCATION) - target_sources(${BINARY_NAME}-qt PRIVATE "${PLUGINS}") - set_source_files_properties("${QTCOCOA}" PROPERTIES MACOSX_PACKAGE_LOCATION Contents/PlugIns) - set_source_files_properties("${COREAUDIO}" PROPERTIES MACOSX_PACKAGE_LOCATION Contents/PlugIns) - install(CODE " - include(BundleUtilities) - set(BU_CHMOD_BUNDLE_ITEMS ON) - file(GLOB_RECURSE PLUGINS \"${BUNDLE_PATH}/Contents/PlugIns/*${CMAKE_SHARED_LIBRARY_SUFFIX}\") - fixup_bundle(\"${BUNDLE_PATH}\" \"${PLUGINS}\" \"\") - " COMPONENT ${BINARY_NAME}-qt) + message(STATUS ${CMAKE_SYSTEM_NAME}) + if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") + get_target_property(QTCOCOA Qt5::QCocoaIntegrationPlugin LOCATION) + get_target_property(COREAUDIO Qt5::CoreAudioPlugin LOCATION) + get_target_property(BUNDLE_PATH ${BINARY_NAME}-qt LOCATION) + target_sources(${BINARY_NAME}-qt PRIVATE "${PLUGINS}") + set_source_files_properties("${QTCOCOA}" PROPERTIES MACOSX_PACKAGE_LOCATION Contents/PlugIns) + set_source_files_properties("${COREAUDIO}" PROPERTIES MACOSX_PACKAGE_LOCATION Contents/PlugIns) + install(CODE " + include(BundleUtilities) + set(BU_CHMOD_BUNDLE_ITEMS ON) + file(GLOB_RECURSE PLUGINS \"${BUNDLE_PATH}/Contents/PlugIns/*${CMAKE_SHARED_LIBRARY_SUFFIX}\") + fixup_bundle(\"${BUNDLE_PATH}\" \"${PLUGINS}\" \"\") + " COMPONENT ${BINARY_NAME}-qt) + else() + set(DEPLOY_OPTIONS -p platforms/libqcocoa.dylib,audio/libqtaudio_coreaudio.dylib) + if(NOT CMAKE_INSTALL_NAME_TOOL EQUAL "install_name_tool") + set(DEPLOY_OPTIONS ${DEPLOY_OPTIONS} -I "${CMAKE_INSTALL_NAME_TOOL}") + endif() + if(DEFINED CMAKE_OTOOL AND NOT CMAKE_OTOOL EQUAL "otool") + set(DEPLOY_OPTIONS ${DEPLOY_OPTIONS} -O "${CMAKE_OTOOL}") + endif() + if(DEFINED CROSS_ROOT) + set(DEPLOY_OPTIONS ${DEPLOY_OPTIONS} -R "${CROSS_ROOT}") + endif() + install(CODE "execute_process(COMMAND \"${CMAKE_SOURCE_DIR}/tools/deploy-mac.py\" -v ${DEPLOY_OPTIONS} \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/Applications/${PROJECT_NAME}.app\")") + endif() endif()
D src/platform/qt/GBAKeyEditor.cpp

@@ -1,409 +0,0 @@

-/* Copyright (c) 2013-2014 Jeffrey Pfau - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include "GBAKeyEditor.h" - -#include <QComboBox> -#include <QHBoxLayout> -#include <QPaintEvent> -#include <QPainter> -#include <QPushButton> -#include <QVBoxLayout> - -#include "InputController.h" -#include "KeyEditor.h" - -#ifdef BUILD_SDL -#include "platform/sdl/sdl-events.h" -#endif - -using namespace QGBA; - -const qreal GBAKeyEditor::DPAD_CENTER_X = 0.247; -const qreal GBAKeyEditor::DPAD_CENTER_Y = 0.432; -const qreal GBAKeyEditor::DPAD_WIDTH = 0.12; -const qreal GBAKeyEditor::DPAD_HEIGHT = 0.12; - -GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString& profile, QWidget* parent) - : QWidget(parent) - , m_profileSelect(nullptr) - , m_clear(nullptr) - , m_type(type) - , m_profile(profile) - , m_controller(controller) -{ - setWindowFlags(windowFlags() & ~Qt::WindowFullscreenButtonHint); - setMinimumSize(300, 300); - - const mInputMap* map = controller->map(); - controller->stealFocus(this); - - 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); - - refresh(); - -#ifdef BUILD_SDL - if (type == SDL_BINDING_BUTTON) { - m_profileSelect = new QComboBox(this); - connect(m_profileSelect, SIGNAL(currentIndexChanged(int)), this, SLOT(selectGamepad(int))); - - updateJoysticks(); - - m_clear = new QWidget(this); - QHBoxLayout* layout = new QHBoxLayout; - m_clear->setLayout(layout); - layout->setSpacing(6); - - QPushButton* clearButton = new QPushButton(tr("Clear Button")); - layout->addWidget(clearButton); - connect(clearButton, &QAbstractButton::pressed, [this]() { - if (!findFocus()) { - return; - } - bool signalsBlocked = (*m_currentKey)->blockSignals(true); - (*m_currentKey)->clearButton(); - (*m_currentKey)->clearHat(); - (*m_currentKey)->blockSignals(signalsBlocked); - }); - - QPushButton* clearAxis = new QPushButton(tr("Clear Analog")); - layout->addWidget(clearAxis); - connect(clearAxis, &QAbstractButton::pressed, [this]() { - if (!findFocus()) { - return; - } - bool signalsBlocked = (*m_currentKey)->blockSignals(true); - (*m_currentKey)->clearAxis(); - (*m_currentKey)->blockSignals(signalsBlocked); - }); - - QPushButton* updateJoysticksButton = new QPushButton(tr("Refresh")); - layout->addWidget(updateJoysticksButton); - connect(updateJoysticksButton, SIGNAL(pressed()), this, SLOT(updateJoysticks())); - } -#endif - - m_buttons = new QWidget(this); - QVBoxLayout* layout = new QVBoxLayout; - m_buttons->setLayout(layout); - - QPushButton* setAll = new QPushButton(tr("Set all")); - connect(setAll, SIGNAL(pressed()), this, SLOT(setAll())); - layout->addWidget(setAll); - - layout->setSpacing(6); - - m_keyOrder = QList<KeyEditor*>{ - m_keyDU, - m_keyDR, - m_keyDD, - m_keyDL, - m_keyA, - m_keyB, - m_keySelect, - m_keyStart, - m_keyL, - m_keyR - }; - - for (auto& key : m_keyOrder) { - connect(key, SIGNAL(valueChanged(int)), this, SLOT(setNext())); - connect(key, SIGNAL(axisChanged(int, int)), this, SLOT(setNext())); - connect(key, SIGNAL(hatChanged(int, int)), this, SLOT(setNext())); - key->installEventFilter(this); - } - - m_currentKey = m_keyOrder.end(); - - m_background.load(":/res/keymap.qpic"); - - setAll->setFocus(); -} - -GBAKeyEditor::~GBAKeyEditor() { - m_controller->releaseFocus(this); -} - -void GBAKeyEditor::setAll() { - m_currentKey = m_keyOrder.begin(); - (*m_currentKey)->setFocus(); -} - -void GBAKeyEditor::resizeEvent(QResizeEvent* event) { - setLocation(m_buttons, 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.475); - setLocation(m_keyB, 0.667, 0.514); - setLocation(m_keyL, 0.1, 0.1); - setLocation(m_keyR, 0.9, 0.1); - - if (m_profileSelect) { - setLocation(m_profileSelect, 0.5, 0.67); - } - - if (m_clear) { - setLocation(m_clear, 0.5, 0.77); - } -} - -void GBAKeyEditor::paintEvent(QPaintEvent* event) { - QPainter painter(this); - painter.scale(width() / 480.0, height() / 480.0); - painter.drawPicture(0, 0, m_background); -} - -void GBAKeyEditor::closeEvent(QCloseEvent*) { - m_controller->releaseFocus(this); -} - -bool GBAKeyEditor::event(QEvent* event) { - QEvent::Type type = event->type(); - if (type == QEvent::WindowActivate || type == QEvent::Show) { - m_controller->stealFocus(this); - } else if (type == QEvent::WindowDeactivate || type == QEvent::Hide) { - m_controller->releaseFocus(this); - } - return QWidget::event(event); -} - -bool GBAKeyEditor::eventFilter(QObject* obj, QEvent* event) { - if (event->type() != QEvent::FocusIn) { - return false; - } - findFocus(static_cast<KeyEditor*>(obj)); - return true; -} - -void GBAKeyEditor::setNext() { - if (m_currentKey == m_keyOrder.end()) { - return; - } - - ++m_currentKey; - if (m_currentKey != m_keyOrder.end()) { - (*m_currentKey)->setFocus(); - } else { - (*(m_currentKey - 1))->clearFocus(); - } -} - -void GBAKeyEditor::save() { -#ifdef BUILD_SDL - m_controller->unbindAllAxes(m_type); -#endif - - bindKey(m_keyDU, GBA_KEY_UP); - bindKey(m_keyDD, GBA_KEY_DOWN); - bindKey(m_keyDL, GBA_KEY_LEFT); - bindKey(m_keyDR, GBA_KEY_RIGHT); - bindKey(m_keySelect, GBA_KEY_SELECT); - bindKey(m_keyStart, GBA_KEY_START); - bindKey(m_keyA, GBA_KEY_A); - bindKey(m_keyB, GBA_KEY_B); - bindKey(m_keyL, GBA_KEY_L); - bindKey(m_keyR, GBA_KEY_R); - m_controller->saveConfiguration(m_type); - -#ifdef BUILD_SDL - if (m_profileSelect) { - m_controller->setPreferredGamepad(m_type, m_profileSelect->currentText()); - } -#endif - - if (!m_profile.isNull()) { - m_controller->saveProfile(m_type, m_profile); - } -} - -void GBAKeyEditor::refresh() { - const mInputMap* map = m_controller->map(); - lookupBinding(map, m_keyDU, GBA_KEY_UP); - lookupBinding(map, m_keyDD, GBA_KEY_DOWN); - lookupBinding(map, m_keyDL, GBA_KEY_LEFT); - lookupBinding(map, m_keyDR, GBA_KEY_RIGHT); - lookupBinding(map, m_keySelect, GBA_KEY_SELECT); - lookupBinding(map, m_keyStart, GBA_KEY_START); - lookupBinding(map, m_keyA, GBA_KEY_A); - lookupBinding(map, m_keyB, GBA_KEY_B); - lookupBinding(map, m_keyL, GBA_KEY_L); - lookupBinding(map, m_keyR, GBA_KEY_R); - -#ifdef BUILD_SDL - lookupAxes(map); - lookupHats(map); -#endif -} - -void GBAKeyEditor::lookupBinding(const mInputMap* map, KeyEditor* keyEditor, GBAKey key) { -#ifdef BUILD_SDL - if (m_type == SDL_BINDING_BUTTON) { - int value = mInputQueryBinding(map, m_type, key); - keyEditor->setValueButton(value); - return; - } -#endif - keyEditor->setValueKey(mInputQueryBinding(map, m_type, key)); -} - -#ifdef BUILD_SDL -void GBAKeyEditor::lookupAxes(const mInputMap* map) { - mInputEnumerateAxes(map, m_type, [](int axis, const mInputAxis* description, void* user) { - GBAKeyEditor* self = static_cast<GBAKeyEditor*>(user); - if (description->highDirection != GBA_KEY_NONE) { - KeyEditor* key = self->keyById(static_cast<enum GBAKey>(description->highDirection)); - if (key) { - key->setValueAxis(axis, description->deadHigh); - } - } - if (description->lowDirection != GBA_KEY_NONE) { - KeyEditor* key = self->keyById(static_cast<enum GBAKey>(description->lowDirection)); - if (key) { - key->setValueAxis(axis, description->deadLow); - } - } - }, this); -} - -void GBAKeyEditor::lookupHats(const mInputMap* map) { - struct mInputHatBindings bindings; - int i = 0; - while (mInputQueryHat(map, m_type, i, &bindings)) { - if (bindings.up >= 0) { - KeyEditor* key = keyById(static_cast<enum GBAKey>(bindings.up)); - if (key) { - key->setValueHat(i, GamepadHatEvent::UP); - } - } - if (bindings.right >= 0) { - KeyEditor* key = keyById(static_cast<enum GBAKey>(bindings.right)); - if (key) { - key->setValueHat(i, GamepadHatEvent::RIGHT); - } - } - if (bindings.down >= 0) { - KeyEditor* key = keyById(static_cast<enum GBAKey>(bindings.down)); - if (key) { - key->setValueHat(i, GamepadHatEvent::DOWN); - } - } - if (bindings.left >= 0) { - KeyEditor* key = keyById(static_cast<enum GBAKey>(bindings.left)); - if (key) { - key->setValueHat(i, GamepadHatEvent::LEFT); - } - } - ++i; - } -} -#endif - -void GBAKeyEditor::bindKey(const KeyEditor* keyEditor, GBAKey key) { -#ifdef BUILD_SDL - if (m_type == SDL_BINDING_BUTTON && keyEditor->axis() >= 0) { - m_controller->bindAxis(m_type, keyEditor->axis(), keyEditor->direction(), key); - } - if (m_type == SDL_BINDING_BUTTON && keyEditor->hat() >= 0) { - m_controller->bindHat(m_type, keyEditor->hat(), keyEditor->hatDirection(), key); - } -#endif - m_controller->bindKey(m_type, keyEditor->value(), key); -} - -bool GBAKeyEditor::findFocus(KeyEditor* needle) { - if (m_currentKey != m_keyOrder.end() && (*m_currentKey)->hasFocus()) { - return true; - } - - for (auto key = m_keyOrder.begin(); key != m_keyOrder.end(); ++key) { - if ((*key)->hasFocus() || needle == *key) { - m_currentKey = key; - return true; - } - } - return m_currentKey != m_keyOrder.end(); -} - -#ifdef BUILD_SDL -void GBAKeyEditor::setAxisValue(int axis, int32_t value) { - if (!findFocus()) { - return; - } - KeyEditor* focused = *m_currentKey; - focused->setValueAxis(axis, value); -} - -void GBAKeyEditor::selectGamepad(int index) { - m_controller->setGamepad(m_type, index); - m_profile = m_profileSelect->currentText(); - m_controller->loadProfile(m_type, m_profile); - refresh(); -} -#endif - -KeyEditor* GBAKeyEditor::keyById(GBAKey key) { - switch (key) { - case GBA_KEY_UP: - return m_keyDU; - case GBA_KEY_DOWN: - return m_keyDD; - case GBA_KEY_LEFT: - return m_keyDL; - case GBA_KEY_RIGHT: - return m_keyDR; - case GBA_KEY_A: - return m_keyA; - case GBA_KEY_B: - return m_keyB; - case GBA_KEY_L: - return m_keyL; - case GBA_KEY_R: - return m_keyR; - case GBA_KEY_SELECT: - return m_keySelect; - case GBA_KEY_START: - return m_keyStart; - default: - break; - } - return nullptr; -} - -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()); -} - -#ifdef BUILD_SDL -void GBAKeyEditor::updateJoysticks() { - m_controller->updateJoysticks(); - m_controller->recalibrateAxes(); - - m_profileSelect->clear(); - m_profileSelect->addItems(m_controller->connectedGamepads(m_type)); - int activeGamepad = m_controller->gamepad(m_type); - selectGamepad(activeGamepad); - if (activeGamepad > 0) { - m_profileSelect->setCurrentIndex(activeGamepad); - } - lookupAxes(m_controller->map()); - lookupHats(m_controller->map()); -} -#endif
D src/platform/qt/GBAKeyEditor.h

@@ -1,96 +0,0 @@

-/* Copyright (c) 2013-2015 Jeffrey Pfau - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#ifndef QGBA_GBA_KEY_EDITOR -#define QGBA_GBA_KEY_EDITOR - -#include <QList> -#include <QPicture> -#include <QSet> -#include <QWidget> - -#include <mgba/internal/gba/input.h> - -class QComboBox; -class QTimer; - -namespace QGBA { - -class InputController; -class KeyEditor; - -class GBAKeyEditor : public QWidget { -Q_OBJECT - -public: - GBAKeyEditor(InputController* controller, int type, const QString& profile = QString(), QWidget* parent = nullptr); - virtual ~GBAKeyEditor(); - -public slots: - void setAll(); - void save(); - -protected: - virtual void resizeEvent(QResizeEvent*) override; - virtual void paintEvent(QPaintEvent*) override; - virtual bool event(QEvent*) override; - virtual void closeEvent(QCloseEvent*) override; - virtual bool eventFilter(QObject* obj, QEvent* event) override; - -private slots: - void setNext(); - void refresh(); -#ifdef BUILD_SDL - void setAxisValue(int axis, int32_t value); - void selectGamepad(int index); - void updateJoysticks(); -#endif - -private: - static const qreal DPAD_CENTER_X; - static const qreal DPAD_CENTER_Y; - static const qreal DPAD_WIDTH; - static const qreal DPAD_HEIGHT; - - void setLocation(QWidget* widget, qreal x, qreal y); - - void lookupBinding(const mInputMap*, KeyEditor*, GBAKey); - void bindKey(const KeyEditor*, GBAKey); - - bool findFocus(KeyEditor* needle = nullptr); - -#ifdef BUILD_SDL - void lookupAxes(const mInputMap*); - void lookupHats(const mInputMap*); -#endif - - KeyEditor* keyById(GBAKey); - - QComboBox* m_profileSelect; - QWidget* m_clear; - QWidget* m_buttons; - 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<KeyEditor*> m_keyOrder; - QList<KeyEditor*>::iterator m_currentKey; - - uint32_t m_type; - QString m_profile; - InputController* m_controller; - - QPicture m_background; -}; - -} - -#endif
M src/platform/qt/GameController.cppsrc/platform/qt/GameController.cpp

@@ -13,7 +13,6 @@ #include "VFileDevice.h"

#include <QCoreApplication> #include <QDateTime> -#include <QThread> #include <ctime>

@@ -48,7 +47,6 @@ , m_logLevels(0)

, m_gameOpen(false) , m_vf(nullptr) , m_useBios(false) - , m_audioThread(new QThread(this)) , m_audioProcessor(AudioProcessor::create()) , m_pauseAfterFrame(false) , m_sync(true)

@@ -176,7 +174,7 @@ }

controller->m_patch = QString(); controller->clearOverride(); - QMetaObject::invokeMethod(controller->m_audioProcessor, "pause", Qt::BlockingQueuedConnection); + controller->m_audioProcessor->pause(); QMetaObject::invokeMethod(controller, "gameStopped", Q_ARG(mCoreThread*, context)); QMetaObject::invokeMethod(controller, "cleanGame");

@@ -294,9 +292,6 @@ };

m_threadContext.userData = this; - m_audioThread->setObjectName("Audio Thread"); - m_audioThread->start(QThread::TimeCriticalPriority); - m_audioProcessor->moveToThread(m_audioThread); connect(this, SIGNAL(gamePaused(mCoreThread*)), m_audioProcessor, SLOT(pause())); connect(this, SIGNAL(gameStarted(mCoreThread*, const QString&)), m_audioProcessor, SLOT(setInput(mCoreThread*))); connect(this, SIGNAL(frameAvailable(const uint32_t*)), this, SLOT(pollEvents()));

@@ -306,8 +301,6 @@

GameController::~GameController() { disconnect(); closeGame(); - m_audioThread->quit(); - m_audioThread->wait(); clearMultiplayerController(); delete m_backupLoadState; }

@@ -864,7 +857,7 @@ if (m_audioProcessor) {

threadInterrupt(); redoSamples(samples); threadContinue(); - QMetaObject::invokeMethod(m_audioProcessor, "setBufferSamples", Qt::BlockingQueuedConnection, Q_ARG(int, samples)); + m_audioProcessor->setBufferSamples(samples); } }

@@ -876,7 +869,7 @@ if (m_audioProcessor) {

threadInterrupt(); redoSamples(m_audioProcessor->getBufferSamples()); threadContinue(); - QMetaObject::invokeMethod(m_audioProcessor, "requestSampleRate", Q_ARG(unsigned, rate)); + m_audioProcessor->requestSampleRate(rate); } }

@@ -929,9 +922,7 @@ }

} void GameController::startAudio() { - bool started = false; - QMetaObject::invokeMethod(m_audioProcessor, "start", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, started)); - if (!started) { + if (!m_audioProcessor->start()) { LOG(QT, ERROR) << tr("Failed to start audio processor"); // Don't freeze! m_audioSync = false;

@@ -1149,7 +1140,7 @@ void GameController::reloadAudioDriver() {

int samples = 0; unsigned sampleRate = 0; if (m_audioProcessor) { - QMetaObject::invokeMethod(m_audioProcessor, "pause", Qt::BlockingQueuedConnection); + m_audioProcessor->pause(); samples = m_audioProcessor->getBufferSamples(); sampleRate = m_audioProcessor->sampleRate(); delete m_audioProcessor;

@@ -1161,7 +1152,6 @@ }

if (sampleRate) { m_audioProcessor->requestSampleRate(sampleRate); } - m_audioProcessor->moveToThread(m_audioThread); connect(this, SIGNAL(gamePaused(mCoreThread*)), m_audioProcessor, SLOT(pause())); connect(this, SIGNAL(gameStarted(mCoreThread*, const QString&)), m_audioProcessor, SLOT(setInput(mCoreThread*))); if (isLoaded()) {

@@ -1236,7 +1226,7 @@ void GameController::redoSamples(int samples) {

if (m_threadContext.core) { m_threadContext.core->setAudioBufferSize(m_threadContext.core, samples); } - QMetaObject::invokeMethod(m_audioProcessor, "inputParametersChanged"); + m_audioProcessor->inputParametersChanged(); } void GameController::setLogLevel(int levels) {
M src/platform/qt/GameController.hsrc/platform/qt/GameController.h

@@ -30,8 +30,6 @@ struct mCoreConfig;

struct mDebugger; struct mTileCache; -class QThread; - namespace QGBA { class AudioProcessor;

@@ -208,7 +206,6 @@ bool m_useBios;

QString m_patch; Override* m_override; - QThread* m_audioThread; AudioProcessor* m_audioProcessor; QAtomicInt m_pauseAfterFrame;
M src/platform/qt/GamepadAxisEvent.cppsrc/platform/qt/GamepadAxisEvent.cpp

@@ -27,6 +27,7 @@ }

QEvent::Type GamepadAxisEvent::Type() { if (s_type == None) { + qRegisterMetaType<Direction>("GamepadAxisEvent::Direction"); s_type = static_cast<enum Type>(registerEventType()); } return s_type;
M src/platform/qt/GamepadHatEvent.cppsrc/platform/qt/GamepadHatEvent.cpp

@@ -27,6 +27,7 @@ }

QEvent::Type GamepadHatEvent::Down() { if (s_downType == None) { + qRegisterMetaType<Direction>("GamepadHatEvent::Direction"); s_downType = static_cast<Type>(registerEventType()); } return s_downType;
M src/platform/qt/InputController.cppsrc/platform/qt/InputController.cpp

@@ -6,11 +6,15 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "InputController.h" #include "ConfigController.h" +#include "GameController.h" #include "GamepadAxisEvent.h" #include "GamepadButtonEvent.h" +#include "InputModel.h" #include "InputProfile.h" #include <QApplication> +#include <QKeyEvent> +#include <QMenu> #include <QTimer> #include <QWidget>

@@ -24,8 +28,10 @@ int InputController::s_sdlInited = 0;

mSDLEvents InputController::s_sdlEvents; #endif -InputController::InputController(int playerId, QWidget* topLevel, QObject* parent) +InputController::InputController(InputModel* model, int playerId, QWidget* topLevel, QObject* parent) : QObject(parent) + , m_inputModel(model) + , m_platform(PLATFORM_NONE) , m_playerId(playerId) , m_config(nullptr) , m_gamepadTimer(nullptr)

@@ -37,15 +43,11 @@ , m_allowOpposing(false)

, m_topLevel(topLevel) , m_focusParent(topLevel) { - mInputMapInit(&m_inputMap, &GBAInputInfo); - #ifdef BUILD_SDL if (s_sdlInited == 0) { mSDLInitEvents(&s_sdlEvents); } ++s_sdlInited; - m_sdlPlayer.bindings = &m_inputMap; - mSDLInitBindingsGBA(&m_inputMap); updateJoysticks(); #endif

@@ -60,20 +62,21 @@ #endif

m_gamepadTimer.setInterval(50); m_gamepadTimer.start(); - mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_X, GBA_KEY_A); - mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Z, GBA_KEY_B); - mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_A, GBA_KEY_L); - mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_S, GBA_KEY_R); - mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Return, GBA_KEY_START); - mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Backspace, GBA_KEY_SELECT); - mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Up, GBA_KEY_UP); - mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Down, GBA_KEY_DOWN); - mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Left, GBA_KEY_LEFT); - mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Right, GBA_KEY_RIGHT); + m_autofireMenu = std::unique_ptr<QMenu>(new QMenu(tr("Autofire"))); + m_inputModel->addMenu(m_autofireMenu.get()); + + m_inputMenu = std::unique_ptr<QMenu>(new QMenu(tr("Bindings"))); + m_inputModel->addMenu(m_inputMenu.get()); + + connect(m_inputModel, SIGNAL(keyRebound(const QModelIndex&, int)), this, SLOT(bindKey(const QModelIndex&, int))); + connect(m_inputModel, SIGNAL(buttonRebound(const QModelIndex&, int)), this, SLOT(bindButton(const QModelIndex&, int))); + connect(m_inputModel, SIGNAL(axisRebound(const QModelIndex&, int, GamepadAxisEvent::Direction)), this, SLOT(bindAxis(const QModelIndex&, int, GamepadAxisEvent::Direction))); } InputController::~InputController() { - mInputMapDeinit(&m_inputMap); + for (auto& inputMap : m_inputMaps) { + mInputMapDeinit(&inputMap); + } #ifdef BUILD_SDL if (m_playerAttached) {

@@ -87,6 +90,42 @@ }

#endif } +void InputController::addPlatform(mPlatform platform, const QString& visibleName, const mInputPlatformInfo* info) { + mInputMap* inputMap = &m_inputMaps[platform]; + mInputMapInit(inputMap, info); + + QMenu* input = m_inputMenu->addMenu(visibleName); + QMenu* autofire = m_autofireMenu->addMenu(visibleName); + m_inputMenuIndices[platform] = m_inputModel->addMenu(input, m_inputMenu.get()); + m_inputModel->addMenu(autofire, m_autofireMenu.get()); + + for (size_t i = 0; i < info->nKeys; ++i) { + m_inputModel->addKey(input, platform, i, 0, info->keyId[i], info->keyId[i]); + m_inputModel->addKey(autofire, platform, i, 0, info->keyId[i], info->keyId[i]); + } + +#ifdef BUILD_SDL + mSDLInitBindingsGBA(inputMap); +#endif + mInputBindKey(inputMap, KEYBOARD, Qt::Key_X, GBA_KEY_A); + mInputBindKey(inputMap, KEYBOARD, Qt::Key_Z, GBA_KEY_B); + mInputBindKey(inputMap, KEYBOARD, Qt::Key_A, GBA_KEY_L); + mInputBindKey(inputMap, KEYBOARD, Qt::Key_S, GBA_KEY_R); + mInputBindKey(inputMap, KEYBOARD, Qt::Key_Return, GBA_KEY_START); + mInputBindKey(inputMap, KEYBOARD, Qt::Key_Backspace, GBA_KEY_SELECT); + mInputBindKey(inputMap, KEYBOARD, Qt::Key_Up, GBA_KEY_UP); + mInputBindKey(inputMap, KEYBOARD, Qt::Key_Down, GBA_KEY_DOWN); + mInputBindKey(inputMap, KEYBOARD, Qt::Key_Left, GBA_KEY_LEFT); + mInputBindKey(inputMap, KEYBOARD, Qt::Key_Right, GBA_KEY_RIGHT); +} + +void InputController::setPlatform(mPlatform platform) { +#ifdef BUILD_SDL + m_sdlPlayer.bindings = &m_inputMaps[platform]; +#endif + m_platform = platform; +} + void InputController::setConfiguration(ConfigController* config) { m_config = config; setAllowOpposing(config->getOption("allowOpposingDirections").toInt());

@@ -99,26 +138,35 @@ }

loadConfiguration(SDL_BINDING_BUTTON); loadProfile(SDL_BINDING_BUTTON, profileForType(SDL_BINDING_BUTTON)); #endif + restoreModel(); } void InputController::loadConfiguration(uint32_t type) { - mInputMapLoad(&m_inputMap, type, m_config->input()); + for (auto& inputMap : m_inputMaps) { + mInputMapLoad(&inputMap, type, m_config->input()); #ifdef BUILD_SDL - if (m_playerAttached) { - mSDLPlayerLoadConfig(&m_sdlPlayer, m_config->input()); - } + if (m_playerAttached) { + mInputMap* bindings = m_sdlPlayer.bindings; + m_sdlPlayer.bindings = &inputMap; + mSDLPlayerLoadConfig(&m_sdlPlayer, m_config->input()); + m_sdlPlayer.bindings = bindings; + } #endif + } } void InputController::loadProfile(uint32_t type, const QString& profile) { - bool loaded = mInputProfileLoad(&m_inputMap, type, m_config->input(), profile.toUtf8().constData()); - recalibrateAxes(); - if (!loaded) { - const InputProfile* ip = InputProfile::findProfile(profile); - if (ip) { - ip->apply(this); + for (auto iter = m_inputMaps.begin(); iter != m_inputMaps.end(); ++iter) { + bool loaded = mInputProfileLoad(&iter.value(), type, m_config->input(), profile.toUtf8().constData()); + if (!loaded) { + const InputProfile* ip = InputProfile::findProfile(iter.key(), profile); + if (ip) { + ip->apply(iter.key(), this); + } } } + recalibrateAxes(); + m_inputModel->loadProfile(PLATFORM_NONE, profile); // TODO emit profileLoaded(profile); }

@@ -135,12 +183,16 @@ #endif

} void InputController::saveConfiguration(uint32_t type) { - mInputMapSave(&m_inputMap, type, m_config->input()); + for (auto& inputMap : m_inputMaps) { + mInputMapSave(&inputMap, type, m_config->input()); + } m_config->write(); } void InputController::saveProfile(uint32_t type, const QString& profile) { - mInputProfileSave(&m_inputMap, type, m_config->input(), profile.toUtf8().constData()); + for (auto& inputMap : m_inputMaps) { + mInputProfileSave(&inputMap, type, m_config->input(), profile.toUtf8().constData()); + } m_config->write(); }

@@ -277,14 +329,6 @@ }

#endif } -GBAKey InputController::mapKeyboard(int key) const { - return static_cast<GBAKey>(mInputMapKey(&m_inputMap, KEYBOARD, key)); -} - -void InputController::bindKey(uint32_t type, int key, GBAKey gbaKey) { - return mInputBindKey(&m_inputMap, type, key, gbaKey); -} - void InputController::updateJoysticks() { #ifdef BUILD_SDL QString profile = profileForType(SDL_BINDING_BUTTON);

@@ -296,6 +340,10 @@ }

#endif } +const mInputMap* InputController::map() { + return &m_inputMaps[m_platform]; +} + int InputController::pollEvents() { int activeButtons = 0; #ifdef BUILD_SDL

@@ -305,7 +353,7 @@ SDL_JoystickUpdate();

int numButtons = SDL_JoystickNumButtons(joystick); int i; for (i = 0; i < numButtons; ++i) { - GBAKey key = static_cast<GBAKey>(mInputMapKey(&m_inputMap, SDL_BINDING_BUTTON, i)); + GBAKey key = static_cast<GBAKey>(mInputMapKey(&m_inputMaps[m_platform], SDL_BINDING_BUTTON, i)); if (key == GBA_KEY_NONE) { continue; }

@@ -319,14 +367,14 @@ }

int numHats = SDL_JoystickNumHats(joystick); for (i = 0; i < numHats; ++i) { int hat = SDL_JoystickGetHat(joystick, i); - activeButtons |= mInputMapHat(&m_inputMap, SDL_BINDING_BUTTON, i, hat); + activeButtons |= mInputMapHat(&m_inputMaps[m_platform], SDL_BINDING_BUTTON, i, hat); } int numAxes = SDL_JoystickNumAxes(joystick); for (i = 0; i < numAxes; ++i) { int value = SDL_JoystickGetAxis(joystick, i); - enum GBAKey key = static_cast<GBAKey>(mInputMapAxis(&m_inputMap, SDL_BINDING_BUTTON, i, value)); + enum GBAKey key = static_cast<GBAKey>(mInputMapAxis(&m_inputMaps[m_platform], SDL_BINDING_BUTTON, i, value)); if (key != GBA_KEY_NONE) { activeButtons |= 1 << key; }

@@ -396,8 +444,25 @@ #endif

return activeAxes; } -void InputController::bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direction direction, GBAKey key) { - const mInputAxis* old = mInputQueryAxis(&m_inputMap, type, axis); +void InputController::bindKey(mPlatform platform, uint32_t type, int key, int coreKey) { + QModelIndex index = m_inputModel->index(coreKey, 0, m_inputMenuIndices[platform]); + bool signalsBlocked = m_inputModel->blockSignals(true); + if (type != KEYBOARD) { + m_inputModel->updateButton(index, key); + } else { + m_inputModel->updateKey(index, key); + } + m_inputModel->blockSignals(signalsBlocked); + mInputBindKey(&m_inputMaps[platform], type, key, coreKey); +} + +void InputController::bindAxis(mPlatform platform, uint32_t type, int axis, GamepadAxisEvent::Direction direction, int key) { + QModelIndex index = m_inputModel->index(key, 0, m_inputMenuIndices[platform]); + bool signalsBlocked = m_inputModel->blockSignals(true); + m_inputModel->updateAxis(index, axis, direction); + m_inputModel->blockSignals(signalsBlocked); + + const mInputAxis* old = mInputQueryAxis(&m_inputMaps[platform], type, axis); mInputAxis description = { GBA_KEY_NONE, GBA_KEY_NONE, -AXIS_THRESHOLD, AXIS_THRESHOLD }; if (old) { description = *old;

@@ -419,11 +484,7 @@ break;

default: return; } - mInputBindAxis(&m_inputMap, type, axis, &description); -} - -void InputController::unbindAllAxes(uint32_t type) { - mInputUnbindAllAxes(&m_inputMap, type); + mInputBindAxis(&m_inputMaps[platform], type, axis, &description); } QSet<QPair<int, GamepadHatEvent::Direction>> InputController::activeGamepadHats(int type) {

@@ -458,26 +519,29 @@ #endif

return activeHats; } -void InputController::bindHat(uint32_t type, int hat, GamepadHatEvent::Direction direction, GBAKey gbaKey) { +void InputController::bindHat(mPlatform platform, uint32_t type, int hat, GamepadHatEvent::Direction direction, int coreKey) { + QModelIndex index = m_inputModel->index(coreKey, 0, m_inputMenuIndices[platform]); + //m_inputModel->updateHat(index, hat, direction); + mInputHatBindings bindings{ -1, -1, -1, -1 }; - mInputQueryHat(&m_inputMap, type, hat, &bindings); + mInputQueryHat(&m_inputMaps[platform], type, hat, &bindings); switch (direction) { case GamepadHatEvent::UP: - bindings.up = gbaKey; + bindings.up = coreKey; break; case GamepadHatEvent::RIGHT: - bindings.right = gbaKey; + bindings.right = coreKey; break; case GamepadHatEvent::DOWN: - bindings.down = gbaKey; + bindings.down = coreKey; break; case GamepadHatEvent::LEFT: - bindings.left = gbaKey; + bindings.left = coreKey; break; default: return; } - mInputBindHat(&m_inputMap, type, hat, &bindings); + mInputBindHat(&m_inputMaps[platform], type, hat, &bindings); } void InputController::testGamepad(int type) {

@@ -569,15 +633,15 @@ }

QApplication::sendEvent(focusWidget, event); } -void InputController::postPendingEvent(GBAKey key) { +void InputController::postPendingEvent(int key) { m_pendingEvents.insert(key); } -void InputController::clearPendingEvent(GBAKey key) { +void InputController::clearPendingEvent(int key) { m_pendingEvents.remove(key); } -bool InputController::hasPendingEvent(GBAKey key) const { +bool InputController::hasPendingEvent(int key) const { return m_pendingEvents.contains(key); }

@@ -614,3 +678,139 @@ if (focus == m_focusParent) {

m_focusParent = m_topLevel; } } + +void InputController::setupCallback(GameController* controller) { + m_inputModel->setKeyCallback([this, controller](QMenu* menu, int key, bool down) { + if (menu == m_autofireMenu.get()) { + controller->setAutofire(key, down); + } else { + if (down) { + controller->keyPressed(key); + } else { + controller->keyReleased(key); + } + } + }); +} + +void InputController::bindKey(const QModelIndex& index, int key) { + int coreKey = m_inputModel->keyAt(index); + if (coreKey < 0) { + return; + } + mPlatform platform = m_inputMenuIndices.key(index.parent(), PLATFORM_NONE); + bindKey(platform, KEYBOARD, key, coreKey); +} + +#ifdef BUILD_SDL +void InputController::bindButton(const QModelIndex& index, int key) { + int coreKey = m_inputModel->keyAt(index); + if (coreKey < 0) { + return; + } + mPlatform platform = m_inputMenuIndices.key(index.parent(), PLATFORM_NONE); + bindKey(platform, SDL_BINDING_BUTTON, key, coreKey); +} + +void InputController::bindAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction direction) { + int coreKey = m_inputModel->keyAt(index); + if (coreKey < 0) { + return; + } + mPlatform platform = m_inputMenuIndices.key(index.parent(), PLATFORM_NONE); + bindAxis(platform, SDL_BINDING_BUTTON, axis, direction, coreKey); +} + +void InputController::bindHat(const QModelIndex& index, int hat, GamepadHatEvent::Direction direction) { + int coreKey = m_inputModel->keyAt(index); + if (coreKey < 0) { + return; + } + mPlatform platform = m_inputMenuIndices.key(index.parent(), PLATFORM_NONE); + bindHat(platform, SDL_BINDING_BUTTON, hat, direction, coreKey); +} +#else +void InputController::bindButton(const QModelIndex& index, int key, int) {} +void InputController::bindAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction, int) {} +void InputController::bindHat(const QModelIndex& index, int hat, GamepadHatEvent::Direction, int) {} +#endif + +bool InputController::eventFilter(QObject*, QEvent* event) { + if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { + QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); + int key = keyEvent->key(); + if (!InputModel::isModifierKey(key)) { + key |= (keyEvent->modifiers() & ~Qt::KeypadModifier); + } else { + key = InputModel::toModifierKey(key | (keyEvent->modifiers() & ~Qt::KeypadModifier)); + } + + if (keyEvent->isAutoRepeat()) { + event->accept(); + return true; + } + + if (m_inputModel->triggerKey(key, event->type() == QEvent::KeyPress, m_platform)) { + event->accept(); + return true; + } + } + + + if (event->type() == GamepadButtonEvent::Down() || event->type() == GamepadButtonEvent::Up()) { + GamepadButtonEvent* gbe = static_cast<GamepadButtonEvent*>(event); + if (m_inputModel->triggerButton(gbe->value(), event->type() == GamepadButtonEvent::Down())) { + event->accept(); + return true; + } + } + if (event->type() == GamepadAxisEvent::Type()) { + GamepadAxisEvent* gae = static_cast<GamepadAxisEvent*>(event); + if (m_inputModel->triggerAxis(gae->axis(), gae->direction(), gae->isNew())) { + event->accept(); + return true; + } + } + return false; +} + +void InputController::restoreModel() { + bool signalsBlocked = m_inputModel->blockSignals(true); + for (auto iter = m_inputMaps.begin(); iter != m_inputMaps.end(); ++iter) { + mPlatform platform = iter.key(); + QModelIndex parent = m_inputMenuIndices[platform]; + int nKeys = iter->info->nKeys; + for (int i = 0; i < nKeys; ++i) { + int key = mInputQueryBinding(&iter.value(), KEYBOARD, i); + if (key >= 0) { + m_inputModel->updateKey(m_inputModel->index(i, 0, parent), key); + } else { + m_inputModel->clearKey(m_inputModel->index(i, 0, parent)); + } +#ifdef BUILD_SDL + key = mInputQueryBinding(&iter.value(), SDL_BINDING_BUTTON, i); + if (key >= 0) { + m_inputModel->updateButton(m_inputModel->index(i, 0, parent), key); + } else { + m_inputModel->clearButton(m_inputModel->index(i, 0, parent)); + } +#endif + } +#ifdef BUILD_SDL + struct Context { + InputModel* model; + QModelIndex parent; + } context{ m_inputModel, parent }; + mInputEnumerateAxes(&iter.value(), SDL_BINDING_BUTTON, [](int axis, const struct mInputAxis* description, void* user) { + Context* context = static_cast<Context*>(user); + if (description->highDirection >= 0) { + context->model->updateAxis(context->model->index(description->highDirection, 0, context->parent), axis, GamepadAxisEvent::POSITIVE); + } + if (description->lowDirection >= 0) { + context->model->updateAxis(context->model->index(description->lowDirection, 0, context->parent), axis, GamepadAxisEvent::NEGATIVE); + } + }, &context); +#endif + } + m_inputModel->blockSignals(signalsBlocked); +}
M src/platform/qt/InputController.hsrc/platform/qt/InputController.h

@@ -9,23 +9,29 @@

#include "GamepadAxisEvent.h" #include "GamepadHatEvent.h" +#include <QMap> #include <QObject> #include <QSet> #include <QTimer> #include <QVector> -#include <mgba/internal/gba/input.h> +#include <mgba/core/core.h> +#include <mgba/core/input.h> #ifdef BUILD_SDL #include "platform/sdl/sdl-events.h" #endif +class QMenu; + struct mRotationSource; struct mRumble; namespace QGBA { class ConfigController; +class GameController; +class InputModel; class InputController : public QObject { Q_OBJECT

@@ -33,9 +39,12 @@

public: static const uint32_t KEYBOARD = 0x51545F4B; - InputController(int playerId = 0, QWidget* topLevel = nullptr, QObject* parent = nullptr); + InputController(InputModel* model, int playerId = 0, QWidget* topLevel = nullptr, QObject* parent = nullptr); ~InputController(); + void addPlatform(mPlatform, const QString& visibleName, const mInputPlatformInfo*); + void setPlatform(mPlatform); + void setConfiguration(ConfigController* config); void saveConfiguration(); void loadConfiguration(uint32_t type);

@@ -47,11 +56,7 @@

bool allowOpposing() const { return m_allowOpposing; } void setAllowOpposing(bool allowOpposing) { m_allowOpposing = allowOpposing; } - GBAKey mapKeyboard(int key) const; - - void bindKey(uint32_t type, int key, GBAKey); - - const mInputMap* map() const { return &m_inputMap; } + const mInputMap* map(); int pollEvents();

@@ -61,10 +66,9 @@ QSet<QPair<int, GamepadAxisEvent::Direction>> activeGamepadAxes(int type);

QSet<QPair<int, GamepadHatEvent::Direction>> activeGamepadHats(int type); void recalibrateAxes(); - void bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direction, GBAKey); - void unbindAllAxes(uint32_t type); - - void bindHat(uint32_t type, int hat, GamepadHatEvent::Direction, GBAKey); + void bindKey(mPlatform platform, uint32_t type, int key, int); + void bindAxis(mPlatform platform, uint32_t type, int axis, GamepadAxisEvent::Direction, int); + void bindHat(mPlatform platform, uint32_t type, int hat, GamepadHatEvent::Direction, int); QStringList connectedGamepads(uint32_t type) const; int gamepad(uint32_t type) const;

@@ -85,6 +89,8 @@

mRumble* rumble(); mRotationSource* rotationSource(); + void setupCallback(GameController* controller); + signals: void profileLoaded(const QString& profile);

@@ -97,13 +103,25 @@ void suspendScreensaver();

void resumeScreensaver(); void setScreensaverSuspendable(bool); +private slots: + void bindKey(const QModelIndex&, int key); + void bindButton(const QModelIndex&, int key); + void bindAxis(const QModelIndex&, int axis, GamepadAxisEvent::Direction); + void bindHat(const QModelIndex&, int hat, GamepadHatEvent::Direction); + +protected: + bool eventFilter(QObject*, QEvent*) override; + private: - void postPendingEvent(GBAKey); - void clearPendingEvent(GBAKey); - bool hasPendingEvent(GBAKey) const; + void postPendingEvent(int key); + void clearPendingEvent(int key); + bool hasPendingEvent(int key) const; void sendGamepadEvent(QEvent*); + void restoreModel(); - mInputMap m_inputMap; + InputModel* m_inputModel; + mPlatform m_platform; + QMap<mPlatform, mInputMap> m_inputMaps; ConfigController* m_config; int m_playerId; bool m_allowOpposing;

@@ -119,12 +137,16 @@ #endif

QVector<int> m_deadzones; + std::unique_ptr<QMenu> m_inputMenu; + std::unique_ptr<QMenu> m_autofireMenu; + QMap<mPlatform, QModelIndex> m_inputMenuIndices; + QSet<int> m_activeButtons; QSet<QPair<int, GamepadAxisEvent::Direction>> m_activeAxes; QSet<QPair<int, GamepadHatEvent::Direction>> m_activeHats; QTimer m_gamepadTimer; - QSet<GBAKey> m_pendingEvents; + QSet<int> m_pendingEvents; }; }
A src/platform/qt/InputItem.cpp

@@ -0,0 +1,104 @@

+/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "InputItem.h" + +#include <QMenu> + +using namespace QGBA; + +InputItem::InputItem(QAction* action, const QString& name, InputItem* parent) + : m_action(action) + , m_shortcut(action->shortcut().isEmpty() ? 0 : action->shortcut()[0]) + , m_keys(-1) + , m_menu(nullptr) + , m_name(name) + , m_button(-1) + , m_axis(-1) + , m_direction(GamepadAxisEvent::NEUTRAL) + , m_platform(PLATFORM_NONE) + , m_parent(parent) +{ + m_visibleName = action->text() + .remove(QRegExp("&(?!&)")) + .remove("..."); +} + +InputItem::InputItem(InputItem::Functions functions, int shortcut, const QString& visibleName, const QString& name, InputItem* parent) + : m_action(nullptr) + , m_shortcut(shortcut) + , m_functions(functions) + , m_keys(-1) + , m_menu(nullptr) + , m_name(name) + , m_visibleName(visibleName) + , m_button(-1) + , m_axis(-1) + , m_direction(GamepadAxisEvent::NEUTRAL) + , m_platform(PLATFORM_NONE) + , m_parent(parent) +{ +} + +InputItem::InputItem(mPlatform platform, int key, int shortcut, const QString& visibleName, const QString& name, InputItem* parent) + : m_action(nullptr) + , m_shortcut(shortcut) + , m_keys(key) + , m_menu(nullptr) + , m_name(name) + , m_visibleName(visibleName) + , m_button(-1) + , m_axis(-1) + , m_direction(GamepadAxisEvent::NEUTRAL) + , m_platform(platform) + , m_parent(parent) +{ +} + +InputItem::InputItem(QMenu* menu, InputItem* parent) + : m_action(nullptr) + , m_shortcut(0) + , m_menu(menu) + , m_button(-1) + , m_axis(-1) + , m_direction(GamepadAxisEvent::NEUTRAL) + , m_parent(parent) +{ + if (menu) { + m_visibleName = menu->title() + .remove(QRegExp("&(?!&)")) + .remove("..."); + } +} + +void InputItem::addAction(QAction* action, const QString& name) { + m_items.append(InputItem(action, name, this)); +} + +void InputItem::addFunctions(InputItem::Functions functions, + int shortcut, const QString& visibleName, + const QString& name) { + m_items.append(InputItem(functions, shortcut, visibleName, name, this)); +} + +void InputItem::addKey(mPlatform platform, int key, int shortcut, const QString& visibleName, const QString& name) { + m_items.append(InputItem(platform, key, shortcut, visibleName, name, this)); +} + +void InputItem::addSubmenu(QMenu* menu) { + m_items.append(InputItem(menu, this)); +} + +void InputItem::setShortcut(int shortcut) { + m_shortcut = shortcut; + if (m_action) { + m_action->setShortcut(QKeySequence(shortcut)); + } +} + +void InputItem::setAxis(int axis, GamepadAxisEvent::Direction direction) { + m_axis = axis; + m_direction = direction; +}
A src/platform/qt/InputItem.h

@@ -0,0 +1,76 @@

+/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef QGBA_INPUT_ITEM +#define QGBA_INPUT_ITEM + +#include "GamepadAxisEvent.h" +#include "GamepadHatEvent.h" + +#include <QAction> + +#include <mgba/core/core.h> + +namespace QGBA { + +class InputItem { +public: + typedef QPair<std::function<void ()>, std::function<void ()>> Functions; + + InputItem(QAction* action, const QString& name, InputItem* parent = nullptr); + InputItem(Functions functions, int shortcut, const QString& visibleName, + const QString& name, InputItem* parent = nullptr); + InputItem(mPlatform platform, int key, int shortcut, const QString& name, const QString& visibleName, InputItem* parent = nullptr); + InputItem(QMenu* action, InputItem* parent = nullptr); + + QAction* action() { return m_action; } + const QAction* action() const { return m_action; } + int shortcut() const { return m_shortcut; } + mPlatform platform() const { return m_platform; } + Functions functions() const { return m_functions; } + int key() const { return m_keys; } + QMenu* menu() { return m_menu; } + const QMenu* menu() const { return m_menu; } + const QString& visibleName() const { return m_visibleName; } + const QString& name() const { return m_name; } + QList<InputItem>& items() { return m_items; } + const QList<InputItem>& items() const { return m_items; } + InputItem* parent() { return m_parent; } + const InputItem* parent() const { return m_parent; } + void addAction(QAction* action, const QString& name); + void addFunctions(Functions functions, int shortcut, const QString& visibleName, + const QString& name); + void addKey(mPlatform platform, int key, int shortcut, const QString& visibleName, const QString& name); + void addSubmenu(QMenu* menu); + int button() const { return m_button; } + void setShortcut(int sequence); + void setButton(int button) { m_button = button; } + int axis() const { return m_axis; } + GamepadAxisEvent::Direction direction() const { return m_direction; } + void setAxis(int axis, GamepadAxisEvent::Direction direction); + + bool operator==(const InputItem& other) const { + return m_menu == other.m_menu && m_action == other.m_action; + } + +private: + QAction* m_action; + int m_shortcut; + QMenu* m_menu; + Functions m_functions; + QString m_name; + QString m_visibleName; + int m_button; + int m_axis; + int m_keys; + GamepadAxisEvent::Direction m_direction; + mPlatform m_platform; + QList<InputItem> m_items; + InputItem* m_parent; +}; + +} + +#endif
A src/platform/qt/InputModel.cpp

@@ -0,0 +1,564 @@

+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "InputModel.h" + +#include "ConfigController.h" +#include "GamepadButtonEvent.h" +#include "InputProfile.h" + +#include <QAction> +#include <QKeyEvent> +#include <QMenu> + +using namespace QGBA; + +InputModel::InputModel(QObject* parent) + : QAbstractItemModel(parent) + , m_rootMenu(nullptr) + , m_config(nullptr) + , m_profile(nullptr) +{ +} + +void InputModel::setConfigController(ConfigController* controller) { + m_config = controller; +} + +QVariant InputModel::data(const QModelIndex& index, int role) const { + if (role != Qt::DisplayRole || !index.isValid()) { + return QVariant(); + } + int row = index.row(); + const InputItem* item = static_cast<const InputItem*>(index.internalPointer()); + switch (index.column()) { + case 0: + return item->visibleName(); + case 1: + return QKeySequence(item->shortcut()).toString(QKeySequence::NativeText); + case 2: + if (item->button() >= 0) { + return item->button(); + } + if (item->axis() >= 0) { + char d = '\0'; + if (item->direction() == GamepadAxisEvent::POSITIVE) { + d = '+'; + } + if (item->direction() == GamepadAxisEvent::NEGATIVE) { + d = '-'; + } + return QString("%1%2").arg(d).arg(item->axis()); + } + break; + } + return QVariant(); +} + +QVariant InputModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (role != Qt::DisplayRole) { + return QAbstractItemModel::headerData(section, orientation, role); + } + if (orientation == Qt::Horizontal) { + switch (section) { + case 0: + return tr("Action"); + case 1: + return tr("Keyboard"); + case 2: + return tr("Gamepad"); + } + } + return section; +} + +QModelIndex InputModel::index(int row, int column, const QModelIndex& parent) const { + const InputItem* pmenu = &m_rootMenu; + if (parent.isValid()) { + pmenu = static_cast<InputItem*>(parent.internalPointer()); + } + return createIndex(row, column, const_cast<InputItem*>(&pmenu->items()[row])); +} + +QModelIndex InputModel::parent(const QModelIndex& index) const { + if (!index.isValid() || !index.internalPointer()) { + return QModelIndex(); + } + InputItem* item = static_cast<InputItem*>(index.internalPointer()); + return this->index(item->parent()); +} + +QModelIndex InputModel::index(InputItem* item) const { + if (!item || !item->parent()) { + return QModelIndex(); + } + return createIndex(item->parent()->items().indexOf(*item), 0, item); +} + +int InputModel::columnCount(const QModelIndex& index) const { + return 3; +} + +int InputModel::rowCount(const QModelIndex& index) const { + if (!index.isValid()) { + return m_rootMenu.items().count(); + } + const InputItem* item = static_cast<const InputItem*>(index.internalPointer()); + return item->items().count(); +} + +InputItem* InputModel::add(QMenu* menu, std::function<void (InputItem*)> callback) { + InputItem* smenu = m_menuMap[menu]; + if (!smenu) { + return nullptr; + } + QModelIndex parent = index(smenu); + beginInsertRows(parent, smenu->items().count(), smenu->items().count()); + callback(smenu); + endInsertRows(); + InputItem* item = &smenu->items().last(); + emit dataChanged(createIndex(smenu->items().count() - 1, 0, item), + createIndex(smenu->items().count() - 1, 2, item)); + return item; +} + +void InputModel::addAction(QMenu* menu, QAction* action, const QString& name) { + InputItem* item = add(menu, [&](InputItem* smenu) { + smenu->addAction(action, name); + }); + if (!item) { + return; + } + if (m_config) { + loadShortcuts(item); + } +} + +void InputModel::addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release, + int shortcut, const QString& visibleName, const QString& name) { + InputItem* item = add(menu, [&](InputItem* smenu) { + smenu->addFunctions(qMakePair(press, release), shortcut, visibleName, name); + }); + if (!item) { + return; + } + + bool loadedShortcut = false; + if (m_config) { + loadedShortcut = loadShortcuts(item); + } + if (!loadedShortcut && !m_heldKeys.contains(shortcut)) { + m_heldKeys[shortcut] = item; + } +} + +void InputModel::addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release, + const QKeySequence& shortcut, const QString& visibleName, const QString& name) { + addFunctions(menu, press, release, shortcut[0], visibleName, name); +} + +void InputModel::addKey(QMenu* menu, mPlatform platform, int key, int shortcut, const QString& visibleName, const QString& name) { + InputItem* item = add(menu, [&](InputItem* smenu) { + smenu->addKey(platform, key, shortcut, visibleName, name); + }); + if (!item) { + return; + } + m_keys[qMakePair(platform, key)] = item; +} + +QModelIndex InputModel::addMenu(QMenu* menu, QMenu* parentMenu) { + InputItem* smenu = m_menuMap[parentMenu]; + if (!smenu) { + smenu = &m_rootMenu; + } + QModelIndex parent = index(smenu); + beginInsertRows(parent, smenu->items().count(), smenu->items().count()); + smenu->addSubmenu(menu); + endInsertRows(); + InputItem* item = &smenu->items().last(); + emit dataChanged(createIndex(smenu->items().count() - 1, 0, item), + createIndex(smenu->items().count() - 1, 2, item)); + m_menuMap[menu] = item; + return index(item); +} + +InputItem* InputModel::itemAt(const QModelIndex& index) { + if (!index.isValid()) { + return nullptr; + } + if (index.internalPointer()) { + return static_cast<InputItem*>(index.internalPointer()); + } + if (!index.parent().isValid()) { + return nullptr; + } + InputItem* pmenu = static_cast<InputItem*>(index.parent().internalPointer()); + return &pmenu->items()[index.row()]; +} + +const InputItem* InputModel::itemAt(const QModelIndex& index) const { + if (!index.isValid()) { + return nullptr; + } + if (index.internalPointer()) { + return static_cast<InputItem*>(index.internalPointer()); + } + if (!index.parent().isValid()) { + return nullptr; + } + InputItem* pmenu = static_cast<InputItem*>(index.parent().internalPointer()); + return &pmenu->items()[index.row()]; +} + +int InputModel::shortcutAt(const QModelIndex& index) const { + const InputItem* item = itemAt(index); + if (!item) { + return 0; + } + return item->shortcut(); +} + +int InputModel::keyAt(const QModelIndex& index) const { + const InputItem* item = itemAt(index); + if (!item) { + return -1; + } + return item->key(); +} + +bool InputModel::isMenuAt(const QModelIndex& index) const { + const InputItem* item = itemAt(index); + if (!item) { + return false; + } + return item->menu(); +} + +void InputModel::updateKey(const QModelIndex& index, int keySequence) { + if (!index.isValid()) { + return; + } + const QModelIndex& parent = index.parent(); + if (!parent.isValid()) { + return; + } + InputItem* item = itemAt(index); + updateKey(item, keySequence); + if (m_config) { + m_config->setQtOption(item->name(), QKeySequence(keySequence).toString(), KEY_SECTION); + } +} + +void InputModel::updateKey(InputItem* item, int keySequence) { + int oldShortcut = item->shortcut(); + if (item->functions().first || item->key() >= 0) { + if (oldShortcut > 0) { + m_heldKeys.take(oldShortcut); + } + if (keySequence >= 0) { + m_keys[qMakePair(item->platform(), keySequence)] = item; + } + } + + item->setShortcut(keySequence); + + emit dataChanged(createIndex(index(item).row(), 0, item), + createIndex(index(item).row(), 2, item)); + + emit keyRebound(index(item), keySequence); +} + +void InputModel::updateButton(const QModelIndex& index, int button) { + if (!index.isValid()) { + return; + } + const QModelIndex& parent = index.parent(); + if (!parent.isValid()) { + return; + } + InputItem* item = itemAt(index); + int oldButton = item->button(); + if (oldButton >= 0) { + m_buttons.take(oldButton); + } + updateAxis(index, -1, GamepadAxisEvent::NEUTRAL); + item->setButton(button); + if (button >= 0) { + m_buttons[button] = item; + } + if (m_config) { + m_config->setQtOption(item->name(), button, BUTTON_SECTION); + if (!m_profileName.isNull()) { + m_config->setQtOption(item->name(), button, BUTTON_PROFILE_SECTION + m_profileName); + } + } + emit dataChanged(createIndex(index.row(), 0, index.internalPointer()), + createIndex(index.row(), 2, index.internalPointer())); + + emit buttonRebound(index, button); +} + +void InputModel::updateAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction direction) { + if (!index.isValid()) { + return; + } + const QModelIndex& parent = index.parent(); + if (!parent.isValid()) { + return; + } + InputItem* item = itemAt(index); + int oldAxis = item->axis(); + GamepadAxisEvent::Direction oldDirection = item->direction(); + if (oldAxis >= 0) { + m_axes.take(qMakePair(oldAxis, oldDirection)); + } + if (axis >= 0 && direction != GamepadAxisEvent::NEUTRAL) { + updateButton(index, -1); + m_axes[qMakePair(axis, direction)] = item; + } + item->setAxis(axis, direction); + if (m_config) { + char d = '\0'; + if (direction == GamepadAxisEvent::POSITIVE) { + d = '+'; + } + if (direction == GamepadAxisEvent::NEGATIVE) { + d = '-'; + } + m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_SECTION); + if (!m_profileName.isNull()) { + m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_PROFILE_SECTION + m_profileName); + } + } + emit dataChanged(createIndex(index.row(), 0, index.internalPointer()), + createIndex(index.row(), 2, index.internalPointer())); + + emit axisRebound(index, axis, direction); +} + +void InputModel::clearKey(const QModelIndex& index) { + updateKey(index, 0); +} + +void InputModel::clearButton(const QModelIndex& index) { + updateButton(index, -1); +} + +bool InputModel::triggerKey(int keySequence, bool down, mPlatform platform) { + auto key = m_keys.find(qMakePair(platform, keySequence)); + if (key != m_keys.end()) { + m_keyCallback(key.value()->parent()->menu(), key.value()->key(), down); + return true; + } + auto heldKey = m_heldKeys.find(keySequence); + if (heldKey != m_heldKeys.end()) { + auto pair = heldKey.value()->functions(); + if (down) { + if (pair.first) { + pair.first(); + } + } else { + if (pair.second) { + pair.second(); + } + } + return true; + } + return false; +} + +bool InputModel::triggerButton(int button, bool down) { + auto item = m_buttons.find(button); + if (item == m_buttons.end()) { + return false; + } + if (down) { + QAction* action = item.value()->action(); + if (action && action->isEnabled()) { + action->trigger(); + } + auto pair = item.value()->functions(); + if (pair.first) { + pair.first(); + } + } else { + auto pair = item.value()->functions(); + if (pair.second) { + pair.second(); + } + } + return true; +} + +bool InputModel::triggerAxis(int axis, GamepadAxisEvent::Direction direction, bool isNew) { + auto item = m_axes.find(qMakePair(axis, direction)); + if (item == m_axes.end()) { + return false; + } + if (isNew) { + QAction* action = item.value()->action(); + if (action && action->isEnabled()) { + action->trigger(); + } + } + auto pair = item.value()->functions(); + if (isNew) { + if (pair.first) { + pair.first(); + } + } else { + if (pair.second) { + pair.second(); + } + } + return true; +} + +bool InputModel::loadShortcuts(InputItem* item) { + if (item->name().isNull()) { + return false; + } + loadGamepadShortcuts(item); + QVariant shortcut = m_config->getQtOption(item->name(), KEY_SECTION); + if (!shortcut.isNull()) { + if (shortcut.toString().endsWith("+")) { + updateKey(item, toModifierShortcut(shortcut.toString())); + } else { + updateKey(item, QKeySequence(shortcut.toString())[0]); + } + return true; + } + return false; +} + +void InputModel::loadGamepadShortcuts(InputItem* item) { + if (item->name().isNull()) { + return; + } + QVariant button = m_config->getQtOption(item->name(), !m_profileName.isNull() ? BUTTON_PROFILE_SECTION + m_profileName : BUTTON_SECTION); + int oldButton = item->button(); + if (oldButton >= 0) { + m_buttons.take(oldButton); + item->setButton(-1); + } + if (button.isNull() && m_profile) { + int buttonInt; + if (m_profile->lookupShortcutButton(item->name(), &buttonInt)) { + button = buttonInt; + } + } + if (!button.isNull()) { + item->setButton(button.toInt()); + m_buttons[button.toInt()] = item; + } + + QVariant axis = m_config->getQtOption(item->name(), !m_profileName.isNull() ? AXIS_PROFILE_SECTION + m_profileName : AXIS_SECTION); + int oldAxis = item->axis(); + GamepadAxisEvent::Direction oldDirection = item->direction(); + if (oldAxis >= 0) { + m_axes.take(qMakePair(oldAxis, oldDirection)); + item->setAxis(-1, GamepadAxisEvent::NEUTRAL); + } + if (axis.isNull() && m_profile) { + int axisInt; + GamepadAxisEvent::Direction direction; + if (m_profile->lookupShortcutAxis(item->name(), &axisInt, &direction)) { + axis = QLatin1String(direction == GamepadAxisEvent::Direction::NEGATIVE ? "-" : "+") + QString::number(axisInt); + } + } + if (!axis.isNull()) { + QString axisDesc = axis.toString(); + if (axisDesc.size() >= 2) { + GamepadAxisEvent::Direction direction = GamepadAxisEvent::NEUTRAL; + if (axisDesc[0] == '-') { + direction = GamepadAxisEvent::NEGATIVE; + } + if (axisDesc[0] == '+') { + direction = GamepadAxisEvent::POSITIVE; + } + bool ok; + int axis = axisDesc.mid(1).toInt(&ok); + if (ok) { + item->setAxis(axis, direction); + m_axes[qMakePair(axis, direction)] = item; + } + } + } +} + +void InputModel::loadProfile(mPlatform platform, const QString& profile) { + m_profileName = profile; + m_profile = InputProfile::findProfile(platform, profile); + onSubitems(&m_rootMenu, [this](InputItem* item) { + loadGamepadShortcuts(item); + }); +} + +void InputModel::onSubitems(InputItem* item, std::function<void(InputItem*)> func) { + for (InputItem& subitem : item->items()) { + func(&subitem); + onSubitems(&subitem, func); + } +} + +int InputModel::toModifierShortcut(const QString& shortcut) { + // Qt doesn't seem to work with raw modifier shortcuts! + QStringList modifiers = shortcut.split('+'); + int value = 0; + for (const auto& mod : modifiers) { + if (mod == QLatin1String("Shift")) { + value |= Qt::ShiftModifier; + continue; + } + if (mod == QLatin1String("Ctrl")) { + value |= Qt::ControlModifier; + continue; + } + if (mod == QLatin1String("Alt")) { + value |= Qt::AltModifier; + continue; + } + if (mod == QLatin1String("Meta")) { + value |= Qt::MetaModifier; + continue; + } + } + return value; +} + +bool InputModel::isModifierKey(int key) { + switch (key) { + case Qt::Key_Shift: + case Qt::Key_Control: + case Qt::Key_Alt: + case Qt::Key_Meta: + return true; + default: + return false; + } +} + +int InputModel::toModifierKey(int key) { + int modifiers = key & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier); + key ^= modifiers; + switch (key) { + case Qt::Key_Shift: + modifiers |= Qt::ShiftModifier; + break; + case Qt::Key_Control: + modifiers |= Qt::ControlModifier; + break; + case Qt::Key_Alt: + modifiers |= Qt::AltModifier; + break; + case Qt::Key_Meta: + modifiers |= Qt::MetaModifier; + break; + default: + break; + } + return modifiers; +}
A src/platform/qt/InputModel.h

@@ -0,0 +1,117 @@

+/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef QGBA_INPUT_MODEL +#define QGBA_INPUT_MODEL + +#include <mgba/core/core.h> + +#include "GamepadAxisEvent.h" +#include "InputItem.h" + +#include <QAbstractItemModel> + +#include <functional> + +class QAction; +class QKeyEvent; +class QMenu; +class QString; + +namespace QGBA { + +class ConfigController; +class InputProfile; + +class InputModel : public QAbstractItemModel { +Q_OBJECT + +private: + constexpr static const char* const KEY_SECTION = "shortcutKey"; + constexpr static const char* const BUTTON_SECTION = "shortcutButton"; + constexpr static const char* const AXIS_SECTION = "shortcutAxis"; + constexpr static const char* const BUTTON_PROFILE_SECTION = "shortcutProfileButton."; + constexpr static const char* const AXIS_PROFILE_SECTION = "shortcutProfileAxis."; + +public: + InputModel(QObject* parent = nullptr); + + void setConfigController(ConfigController* controller); + void setProfile(const QString& profile); + void setKeyCallback(std::function<void (QMenu*, int, bool)> callback) { m_keyCallback = callback; } + + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + virtual QModelIndex index(int row, int column, const QModelIndex& parent) const override; + virtual QModelIndex parent(const QModelIndex& index) const override; + + virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override; + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; + + void addAction(QMenu* menu, QAction* action, const QString& name); + void addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release, + int shortcut, const QString& visibleName, const QString& name); + void addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release, + const QKeySequence& shortcut, const QString& visibleName, const QString& name); + void addKey(QMenu* menu, mPlatform platform, int key, int shortcut, const QString& visibleName, const QString& name); + QModelIndex addMenu(QMenu* menu, QMenu* parent = nullptr); + + QAction* getAction(const QString& name); + int shortcutAt(const QModelIndex& index) const; + int keyAt(const QModelIndex& index) const; + bool isMenuAt(const QModelIndex& index) const; + + void updateKey(const QModelIndex& index, int keySequence); + void updateButton(const QModelIndex& index, int button); + void updateAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction); + void updateHat(const QModelIndex& index, int hat, GamepadHatEvent::Direction); + + void clearKey(const QModelIndex& index); + void clearButton(const QModelIndex& index); + + static int toModifierShortcut(const QString& shortcut); + static bool isModifierKey(int key); + static int toModifierKey(int key); + + void loadProfile(mPlatform platform, const QString& profile); + + bool triggerKey(int keySequence, bool down, mPlatform platform = PLATFORM_NONE); + bool triggerButton(int button, bool down); + bool triggerAxis(int axis, GamepadAxisEvent::Direction, bool isNew); + bool triggerHat(int hat, GamepadHatEvent::Direction); + +signals: + void keyRebound(const QModelIndex&, int keySequence); + void buttonRebound(const QModelIndex&, int button); + void axisRebound(const QModelIndex& index, int axis, GamepadAxisEvent::Direction); + void hatRebound(const QModelIndex& index, int hat, GamepadHatEvent::Direction); + +private: + InputItem* add(QMenu* menu, std::function<void (InputItem*)>); + InputItem* itemAt(const QModelIndex& index); + const InputItem* itemAt(const QModelIndex& index) const; + bool loadShortcuts(InputItem*); + void loadGamepadShortcuts(InputItem*); + void onSubitems(InputItem*, std::function<void(InputItem*)> func); + void updateKey(InputItem* item, int keySequence); + + QModelIndex index(InputItem* item) const; + + InputItem m_rootMenu; + QMap<QMenu*, InputItem*> m_menuMap; + QMap<int, InputItem*> m_buttons; + QMap<QPair<int, GamepadAxisEvent::Direction>, InputItem*> m_axes; + QMap<int, InputItem*> m_heldKeys; + QMap<QPair<mPlatform, int>, InputItem*> m_keys; + ConfigController* m_config; + std::function<void (QMenu*, int key, bool down)> m_keyCallback; + QString m_profileName; + const InputProfile* m_profile; +}; + +} + +#endif
M src/platform/qt/InputProfile.cppsrc/platform/qt/InputProfile.cpp

@@ -199,7 +199,8 @@ , m_gyroSensitivity(gyroSensitivity)

{ } -const InputProfile* InputProfile::findProfile(const QString& name) { +const InputProfile* InputProfile::findProfile(mPlatform platform, const QString& name) { + // TODO: Use platform for (size_t i = 0; i < sizeof(s_defaultMaps) / sizeof(*s_defaultMaps); ++i) { QRegExp re(s_defaultMaps[i].m_profileName); if (re.exactMatch(name)) {

@@ -209,11 +210,11 @@ }

return nullptr; } -void InputProfile::apply(InputController* controller) const { +void InputProfile::apply(mPlatform platform, InputController* controller) const { for (size_t i = 0; i < GBA_KEY_MAX; ++i) { #ifdef BUILD_SDL - controller->bindKey(SDL_BINDING_BUTTON, m_keys[i], static_cast<GBAKey>(i)); - controller->bindAxis(SDL_BINDING_BUTTON, m_axes[i].axis, m_axes[i].direction, static_cast<GBAKey>(i)); + controller->bindKey(platform, SDL_BINDING_BUTTON, m_keys[i], static_cast<GBAKey>(i)); + controller->bindAxis(platform, SDL_BINDING_BUTTON, m_axes[i].axis, m_axes[i].direction, static_cast<GBAKey>(i)); #endif } controller->registerTiltAxisX(m_tiltAxis.x);
M src/platform/qt/InputProfile.hsrc/platform/qt/InputProfile.h

@@ -8,6 +8,7 @@ #define QGBA_INPUT_PROFILE

#include "GamepadAxisEvent.h" +#include <mgba/core/core.h> #include <mgba/gba/interface.h> namespace QGBA {

@@ -16,9 +17,9 @@ class InputController;

class InputProfile { public: - static const InputProfile* findProfile(const QString& name); + static const InputProfile* findProfile(mPlatform platform, const QString& name); - void apply(InputController*) const; + void apply(mPlatform platform, InputController*) const; bool lookupShortcutButton(const QString& shortcut, int* button) const; bool lookupShortcutAxis(const QString& shortcut, int* axis, GamepadAxisEvent::Direction* direction) const;
M src/platform/qt/KeyEditor.cppsrc/platform/qt/KeyEditor.cpp

@@ -7,7 +7,7 @@ #include "KeyEditor.h"

#include "GamepadAxisEvent.h" #include "GamepadButtonEvent.h" -#include "ShortcutController.h" +#include "InputModel.h" #include <QFontMetrics> #include <QKeyEvent>

@@ -103,7 +103,7 @@ m_key = 0;

} m_lastKey.start(KEY_TIME); if (m_key) { - if (ShortcutController::isModifierKey(m_key)) { + if (InputModel::isModifierKey(m_key)) { switch (event->key()) { case Qt::Key_Shift: setValue(Qt::ShiftModifier);

@@ -119,7 +119,7 @@ setValue(Qt::MetaModifier);

break; } } - if (ShortcutController::isModifierKey(event->key())) { + if (InputModel::isModifierKey(event->key())) { switch (event->key()) { case Qt::Key_Shift: setValue(m_key | Qt::ShiftModifier);
M src/platform/qt/SettingsView.cppsrc/platform/qt/SettingsView.cpp

@@ -9,7 +9,6 @@ #include "AudioProcessor.h"

#include "ConfigController.h" #include "Display.h" #include "GBAApp.h" -#include "GBAKeyEditor.h" #include "InputController.h" #include "ShortcutView.h"

@@ -18,7 +17,7 @@ #include <mgba/internal/gba/gba.h>

using namespace QGBA; -SettingsView::SettingsView(ConfigController* controller, InputController* inputController, ShortcutController* shortcutController, QWidget* parent) +SettingsView::SettingsView(ConfigController* controller, InputController* inputController, InputModel* inputModel, QWidget* parent) : QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint) , m_controller(controller) {

@@ -146,37 +145,13 @@ connect(m_ui.gbcBiosBrowse, &QPushButton::clicked, [this]() {

selectBios(m_ui.gbcBios); }); - GBAKeyEditor* editor = new GBAKeyEditor(inputController, InputController::KEYBOARD, QString(), this); - m_ui.stackedWidget->addWidget(editor); - m_ui.tabs->addItem(tr("Keyboard")); - connect(m_ui.buttonBox, SIGNAL(accepted()), editor, SLOT(save())); - - GBAKeyEditor* buttonEditor = nullptr; -#ifdef BUILD_SDL - inputController->recalibrateAxes(); - const char* profile = inputController->profileForType(SDL_BINDING_BUTTON); - buttonEditor = new GBAKeyEditor(inputController, SDL_BINDING_BUTTON, profile); - m_ui.stackedWidget->addWidget(buttonEditor); - m_ui.tabs->addItem(tr("Controllers")); - connect(m_ui.buttonBox, SIGNAL(accepted()), buttonEditor, SLOT(save())); -#endif - connect(m_ui.buttonBox, SIGNAL(accepted()), this, SLOT(updateConfig())); - connect(m_ui.buttonBox, &QDialogButtonBox::clicked, [this, editor, buttonEditor](QAbstractButton* button) { - if (m_ui.buttonBox->buttonRole(button) == QDialogButtonBox::ApplyRole) { - updateConfig(); - editor->save(); - if (buttonEditor) { - buttonEditor->save(); - } - } - }); ShortcutView* shortcutView = new ShortcutView(); - shortcutView->setController(shortcutController); + shortcutView->setModel(inputModel); shortcutView->setInputController(inputController); m_ui.stackedWidget->addWidget(shortcutView); - m_ui.tabs->addItem(tr("Shortcuts")); + m_ui.tabs->addItem(tr("Bindings")); } void SettingsView::selectBios(QLineEdit* bios) {
M src/platform/qt/SettingsView.hsrc/platform/qt/SettingsView.h

@@ -16,13 +16,13 @@ namespace QGBA {

class ConfigController; class InputController; -class ShortcutController; +class InputModel; class SettingsView : public QDialog { Q_OBJECT public: - SettingsView(ConfigController* controller, InputController* inputController, ShortcutController* shortcutController, QWidget* parent = nullptr); + SettingsView(ConfigController* controller, InputController* inputController, InputModel* inputModel, QWidget* parent = nullptr); signals: void biosLoaded(int platform, const QString&);
D src/platform/qt/ShortcutController.cpp

@@ -1,609 +0,0 @@

-/* Copyright (c) 2013-2015 Jeffrey Pfau - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include "ShortcutController.h" - -#include "ConfigController.h" -#include "GamepadButtonEvent.h" -#include "InputProfile.h" - -#include <QAction> -#include <QKeyEvent> -#include <QMenu> - -using namespace QGBA; - -ShortcutController::ShortcutController(QObject* parent) - : QAbstractItemModel(parent) - , m_rootMenu(nullptr) - , m_config(nullptr) - , m_profile(nullptr) -{ -} - -void ShortcutController::setConfigController(ConfigController* controller) { - m_config = controller; -} - -QVariant ShortcutController::data(const QModelIndex& index, int role) const { - if (role != Qt::DisplayRole || !index.isValid()) { - return QVariant(); - } - int row = index.row(); - const ShortcutItem* item = static_cast<const ShortcutItem*>(index.internalPointer()); - switch (index.column()) { - case 0: - return item->visibleName(); - case 1: - return QKeySequence(item->shortcut()).toString(QKeySequence::NativeText); - case 2: - if (item->button() >= 0) { - return item->button(); - } - if (item->axis() >= 0) { - char d = '\0'; - if (item->direction() == GamepadAxisEvent::POSITIVE) { - d = '+'; - } - if (item->direction() == GamepadAxisEvent::NEGATIVE) { - d = '-'; - } - return QString("%1%2").arg(d).arg(item->axis()); - } - break; - } - return QVariant(); -} - -QVariant ShortcutController::headerData(int section, Qt::Orientation orientation, int role) const { - if (role != Qt::DisplayRole) { - return QAbstractItemModel::headerData(section, orientation, role); - } - if (orientation == Qt::Horizontal) { - switch (section) { - case 0: - return tr("Action"); - case 1: - return tr("Keyboard"); - case 2: - return tr("Gamepad"); - } - } - return section; -} - -QModelIndex ShortcutController::index(int row, int column, const QModelIndex& parent) const { - const ShortcutItem* pmenu = &m_rootMenu; - if (parent.isValid()) { - pmenu = static_cast<ShortcutItem*>(parent.internalPointer()); - } - return createIndex(row, column, const_cast<ShortcutItem*>(&pmenu->items()[row])); -} - -QModelIndex ShortcutController::parent(const QModelIndex& index) const { - if (!index.isValid() || !index.internalPointer()) { - return QModelIndex(); - } - ShortcutItem* item = static_cast<ShortcutItem*>(index.internalPointer()); - if (!item->parent() || !item->parent()->parent()) { - return QModelIndex(); - } - return createIndex(item->parent()->parent()->items().indexOf(*item->parent()), 0, item->parent()); -} - -int ShortcutController::columnCount(const QModelIndex& index) const { - return 3; -} - -int ShortcutController::rowCount(const QModelIndex& index) const { - if (!index.isValid()) { - return m_rootMenu.items().count(); - } - const ShortcutItem* item = static_cast<const ShortcutItem*>(index.internalPointer()); - return item->items().count(); -} - -void ShortcutController::addAction(QMenu* menu, QAction* action, const QString& name) { - ShortcutItem* smenu = m_menuMap[menu]; - if (!smenu) { - return; - } - ShortcutItem* pmenu = smenu->parent(); - int row = pmenu->items().indexOf(*smenu); - QModelIndex parent = createIndex(row, 0, smenu); - beginInsertRows(parent, smenu->items().count(), smenu->items().count()); - smenu->addAction(action, name); - endInsertRows(); - ShortcutItem* item = &smenu->items().last(); - if (m_config) { - loadShortcuts(item); - } - emit dataChanged(createIndex(smenu->items().count() - 1, 0, item), - createIndex(smenu->items().count() - 1, 2, item)); -} - -void ShortcutController::addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release, - int shortcut, const QString& visibleName, const QString& name) { - ShortcutItem* smenu = m_menuMap[menu]; - if (!smenu) { - return; - } - ShortcutItem* pmenu = smenu->parent(); - int row = pmenu->items().indexOf(*smenu); - QModelIndex parent = createIndex(row, 0, smenu); - beginInsertRows(parent, smenu->items().count(), smenu->items().count()); - smenu->addFunctions(qMakePair(press, release), shortcut, visibleName, name); - endInsertRows(); - ShortcutItem* item = &smenu->items().last(); - bool loadedShortcut = false; - if (m_config) { - loadedShortcut = loadShortcuts(item); - } - if (!loadedShortcut && !m_heldKeys.contains(shortcut)) { - m_heldKeys[shortcut] = item; - } - emit dataChanged(createIndex(smenu->items().count() - 1, 0, item), - createIndex(smenu->items().count() - 1, 2, item)); -} - -void ShortcutController::addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release, - const QKeySequence& shortcut, const QString& visibleName, const QString& name) { - addFunctions(menu, press, release, shortcut[0], visibleName, name); -} - -void ShortcutController::addMenu(QMenu* menu, QMenu* parentMenu) { - ShortcutItem* smenu = m_menuMap[parentMenu]; - if (!smenu) { - smenu = &m_rootMenu; - } - QModelIndex parent; - ShortcutItem* pmenu = smenu->parent(); - if (pmenu) { - int row = pmenu->items().indexOf(*smenu); - parent = createIndex(row, 0, smenu); - } - beginInsertRows(parent, smenu->items().count(), smenu->items().count()); - smenu->addSubmenu(menu); - endInsertRows(); - ShortcutItem* item = &smenu->items().last(); - emit dataChanged(createIndex(smenu->items().count() - 1, 0, item), - createIndex(smenu->items().count() - 1, 2, item)); - m_menuMap[menu] = item; -} - -ShortcutController::ShortcutItem* ShortcutController::itemAt(const QModelIndex& index) { - if (!index.isValid()) { - return nullptr; - } - return static_cast<ShortcutItem*>(index.internalPointer()); -} - -const ShortcutController::ShortcutItem* ShortcutController::itemAt(const QModelIndex& index) const { - if (!index.isValid()) { - return nullptr; - } - return static_cast<const ShortcutItem*>(index.internalPointer()); -} - -int ShortcutController::shortcutAt(const QModelIndex& index) const { - const ShortcutItem* item = itemAt(index); - if (!item) { - return 0; - } - return item->shortcut(); -} - -bool ShortcutController::isMenuAt(const QModelIndex& index) const { - const ShortcutItem* item = itemAt(index); - if (!item) { - return false; - } - return item->menu(); -} - -void ShortcutController::updateKey(const QModelIndex& index, int keySequence) { - if (!index.isValid()) { - return; - } - const QModelIndex& parent = index.parent(); - if (!parent.isValid()) { - return; - } - ShortcutItem* item = itemAt(index); - updateKey(item, keySequence); - if (m_config) { - m_config->setQtOption(item->name(), QKeySequence(keySequence).toString(), KEY_SECTION); - } - emit dataChanged(createIndex(index.row(), 0, index.internalPointer()), - createIndex(index.row(), 2, index.internalPointer())); -} - -void ShortcutController::updateKey(ShortcutItem* item, int keySequence) { - int oldShortcut = item->shortcut(); - if (item->functions().first) { - if (oldShortcut > 0) { - m_heldKeys.take(oldShortcut); - } - if (keySequence > 0) { - m_heldKeys[keySequence] = item; - } - } - - item->setShortcut(keySequence); -} - -void ShortcutController::updateButton(const QModelIndex& index, int button) { - if (!index.isValid()) { - return; - } - const QModelIndex& parent = index.parent(); - if (!parent.isValid()) { - return; - } - ShortcutItem* item = itemAt(index); - int oldButton = item->button(); - if (oldButton >= 0) { - m_buttons.take(oldButton); - } - updateAxis(index, -1, GamepadAxisEvent::NEUTRAL); - item->setButton(button); - if (button >= 0) { - m_buttons[button] = item; - } - if (m_config) { - m_config->setQtOption(item->name(), button, BUTTON_SECTION); - if (!m_profileName.isNull()) { - m_config->setQtOption(item->name(), button, BUTTON_PROFILE_SECTION + m_profileName); - } - } - emit dataChanged(createIndex(index.row(), 0, index.internalPointer()), - createIndex(index.row(), 2, index.internalPointer())); -} - -void ShortcutController::updateAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction direction) { - if (!index.isValid()) { - return; - } - const QModelIndex& parent = index.parent(); - if (!parent.isValid()) { - return; - } - ShortcutItem* item = itemAt(index); - int oldAxis = item->axis(); - GamepadAxisEvent::Direction oldDirection = item->direction(); - if (oldAxis >= 0) { - m_axes.take(qMakePair(oldAxis, oldDirection)); - } - if (axis >= 0 && direction != GamepadAxisEvent::NEUTRAL) { - updateButton(index, -1); - m_axes[qMakePair(axis, direction)] = item; - } - item->setAxis(axis, direction); - if (m_config) { - char d = '\0'; - if (direction == GamepadAxisEvent::POSITIVE) { - d = '+'; - } - if (direction == GamepadAxisEvent::NEGATIVE) { - d = '-'; - } - m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_SECTION); - if (!m_profileName.isNull()) { - m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_PROFILE_SECTION + m_profileName); - } - } - emit dataChanged(createIndex(index.row(), 0, index.internalPointer()), - createIndex(index.row(), 2, index.internalPointer())); -} - -void ShortcutController::clearKey(const QModelIndex& index) { - updateKey(index, 0); -} - -void ShortcutController::clearButton(const QModelIndex& index) { - updateButton(index, -1); -} - -bool ShortcutController::eventFilter(QObject*, QEvent* event) { - if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { - QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); - if (keyEvent->isAutoRepeat()) { - return false; - } - int key = keyEvent->key(); - if (!isModifierKey(key)) { - key |= (keyEvent->modifiers() & ~Qt::KeypadModifier); - } else { - key = toModifierKey(key | (keyEvent->modifiers() & ~Qt::KeypadModifier)); - } - auto item = m_heldKeys.find(key); - if (item != m_heldKeys.end()) { - ShortcutItem::Functions pair = item.value()->functions(); - if (event->type() == QEvent::KeyPress) { - if (pair.first) { - pair.first(); - } - } else { - if (pair.second) { - pair.second(); - } - } - event->accept(); - return true; - } - } - if (event->type() == GamepadButtonEvent::Down()) { - auto item = m_buttons.find(static_cast<GamepadButtonEvent*>(event)->value()); - if (item == m_buttons.end()) { - return false; - } - QAction* action = item.value()->action(); - if (action && action->isEnabled()) { - action->trigger(); - } - ShortcutItem::Functions pair = item.value()->functions(); - if (pair.first) { - pair.first(); - } - event->accept(); - return true; - } - if (event->type() == GamepadButtonEvent::Up()) { - auto item = m_buttons.find(static_cast<GamepadButtonEvent*>(event)->value()); - if (item == m_buttons.end()) { - return false; - } - ShortcutItem::Functions pair = item.value()->functions(); - if (pair.second) { - pair.second(); - } - event->accept(); - return true; - } - if (event->type() == GamepadAxisEvent::Type()) { - GamepadAxisEvent* gae = static_cast<GamepadAxisEvent*>(event); - auto item = m_axes.find(qMakePair(gae->axis(), gae->direction())); - if (item == m_axes.end()) { - return false; - } - if (gae->isNew()) { - QAction* action = item.value()->action(); - if (action && action->isEnabled()) { - action->trigger(); - } - } - ShortcutItem::Functions pair = item.value()->functions(); - if (gae->isNew()) { - if (pair.first) { - pair.first(); - } - } else { - if (pair.second) { - pair.second(); - } - } - event->accept(); - return true; - } - return false; -} - -bool ShortcutController::loadShortcuts(ShortcutItem* item) { - if (item->name().isNull()) { - return false; - } - loadGamepadShortcuts(item); - QVariant shortcut = m_config->getQtOption(item->name(), KEY_SECTION); - if (!shortcut.isNull()) { - if (shortcut.toString().endsWith("+")) { - updateKey(item, toModifierShortcut(shortcut.toString())); - } else { - updateKey(item, QKeySequence(shortcut.toString())[0]); - } - return true; - } - return false; -} - -void ShortcutController::loadGamepadShortcuts(ShortcutItem* item) { - if (item->name().isNull()) { - return; - } - QVariant button = m_config->getQtOption(item->name(), !m_profileName.isNull() ? BUTTON_PROFILE_SECTION + m_profileName : BUTTON_SECTION); - int oldButton = item->button(); - if (oldButton >= 0) { - m_buttons.take(oldButton); - item->setButton(-1); - } - if (button.isNull() && m_profile) { - int buttonInt; - if (m_profile->lookupShortcutButton(item->name(), &buttonInt)) { - button = buttonInt; - } - } - if (!button.isNull()) { - item->setButton(button.toInt()); - m_buttons[button.toInt()] = item; - } - - QVariant axis = m_config->getQtOption(item->name(), !m_profileName.isNull() ? AXIS_PROFILE_SECTION + m_profileName : AXIS_SECTION); - int oldAxis = item->axis(); - GamepadAxisEvent::Direction oldDirection = item->direction(); - if (oldAxis >= 0) { - m_axes.take(qMakePair(oldAxis, oldDirection)); - item->setAxis(-1, GamepadAxisEvent::NEUTRAL); - } - if (axis.isNull() && m_profile) { - int axisInt; - GamepadAxisEvent::Direction direction; - if (m_profile->lookupShortcutAxis(item->name(), &axisInt, &direction)) { - axis = QLatin1String(direction == GamepadAxisEvent::Direction::NEGATIVE ? "-" : "+") + QString::number(axisInt); - } - } - if (!axis.isNull()) { - QString axisDesc = axis.toString(); - if (axisDesc.size() >= 2) { - GamepadAxisEvent::Direction direction = GamepadAxisEvent::NEUTRAL; - if (axisDesc[0] == '-') { - direction = GamepadAxisEvent::NEGATIVE; - } - if (axisDesc[0] == '+') { - direction = GamepadAxisEvent::POSITIVE; - } - bool ok; - int axis = axisDesc.mid(1).toInt(&ok); - if (ok) { - item->setAxis(axis, direction); - m_axes[qMakePair(axis, direction)] = item; - } - } - } -} - -void ShortcutController::loadProfile(const QString& profile) { - m_profileName = profile; - m_profile = InputProfile::findProfile(profile); - onSubitems(&m_rootMenu, [this](ShortcutItem* item) { - loadGamepadShortcuts(item); - }); -} - -void ShortcutController::onSubitems(ShortcutItem* item, std::function<void(ShortcutItem*)> func) { - for (ShortcutItem& subitem : item->items()) { - func(&subitem); - onSubitems(&subitem, func); - } -} - -int ShortcutController::toModifierShortcut(const QString& shortcut) { - // Qt doesn't seem to work with raw modifier shortcuts! - QStringList modifiers = shortcut.split('+'); - int value = 0; - for (const auto& mod : modifiers) { - if (mod == QLatin1String("Shift")) { - value |= Qt::ShiftModifier; - continue; - } - if (mod == QLatin1String("Ctrl")) { - value |= Qt::ControlModifier; - continue; - } - if (mod == QLatin1String("Alt")) { - value |= Qt::AltModifier; - continue; - } - if (mod == QLatin1String("Meta")) { - value |= Qt::MetaModifier; - continue; - } - } - return value; -} - -bool ShortcutController::isModifierKey(int key) { - switch (key) { - case Qt::Key_Shift: - case Qt::Key_Control: - case Qt::Key_Alt: - case Qt::Key_Meta: - return true; - default: - return false; - } -} - -int ShortcutController::toModifierKey(int key) { - int modifiers = key & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier); - key ^= modifiers; - switch (key) { - case Qt::Key_Shift: - modifiers |= Qt::ShiftModifier; - break; - case Qt::Key_Control: - modifiers |= Qt::ControlModifier; - break; - case Qt::Key_Alt: - modifiers |= Qt::AltModifier; - break; - case Qt::Key_Meta: - modifiers |= Qt::MetaModifier; - break; - default: - break; - } - return modifiers; - -} - -ShortcutController::ShortcutItem::ShortcutItem(QAction* action, const QString& name, ShortcutItem* parent) - : m_action(action) - , m_shortcut(action->shortcut().isEmpty() ? 0 : action->shortcut()[0]) - , m_menu(nullptr) - , m_name(name) - , m_button(-1) - , m_axis(-1) - , m_direction(GamepadAxisEvent::NEUTRAL) - , m_parent(parent) -{ - m_visibleName = action->text() - .remove(QRegExp("&(?!&)")) - .remove("..."); -} - -ShortcutController::ShortcutItem::ShortcutItem(ShortcutController::ShortcutItem::Functions functions, int shortcut, const QString& visibleName, const QString& name, ShortcutItem* parent) - : m_action(nullptr) - , m_shortcut(shortcut) - , m_functions(functions) - , m_menu(nullptr) - , m_name(name) - , m_visibleName(visibleName) - , m_button(-1) - , m_axis(-1) - , m_direction(GamepadAxisEvent::NEUTRAL) - , m_parent(parent) -{ -} - -ShortcutController::ShortcutItem::ShortcutItem(QMenu* menu, ShortcutItem* parent) - : m_action(nullptr) - , m_shortcut(0) - , m_menu(menu) - , m_button(-1) - , m_axis(-1) - , m_direction(GamepadAxisEvent::NEUTRAL) - , m_parent(parent) -{ - if (menu) { - m_visibleName = menu->title() - .remove(QRegExp("&(?!&)")) - .remove("..."); - } -} - -void ShortcutController::ShortcutItem::addAction(QAction* action, const QString& name) { - m_items.append(ShortcutItem(action, name, this)); -} - -void ShortcutController::ShortcutItem::addFunctions(ShortcutController::ShortcutItem::Functions functions, - int shortcut, const QString& visibleName, - const QString& name) { - m_items.append(ShortcutItem(functions, shortcut, visibleName, name, this)); -} - -void ShortcutController::ShortcutItem::addSubmenu(QMenu* menu) { - m_items.append(ShortcutItem(menu, this)); -} - -void ShortcutController::ShortcutItem::setShortcut(int shortcut) { - m_shortcut = shortcut; - if (m_action) { - m_action->setShortcut(QKeySequence(shortcut)); - } -} - -void ShortcutController::ShortcutItem::setAxis(int axis, GamepadAxisEvent::Direction direction) { - m_axis = axis; - m_direction = direction; -}
D src/platform/qt/ShortcutController.h

@@ -1,148 +0,0 @@

-/* Copyright (c) 2013-2015 Jeffrey Pfau - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#ifndef QGBA_SHORTCUT_MODEL -#define QGBA_SHORTCUT_MODEL - -#include "GamepadAxisEvent.h" - -#include <QAbstractItemModel> - -#include <functional> - -class QAction; -class QKeyEvent; -class QMenu; -class QString; - -namespace QGBA { - -class ConfigController; -class InputProfile; - -class ShortcutController : public QAbstractItemModel { -Q_OBJECT - -private: - constexpr static const char* const KEY_SECTION = "shortcutKey"; - constexpr static const char* const BUTTON_SECTION = "shortcutButton"; - constexpr static const char* const AXIS_SECTION = "shortcutAxis"; - constexpr static const char* const BUTTON_PROFILE_SECTION = "shortcutProfileButton."; - constexpr static const char* const AXIS_PROFILE_SECTION = "shortcutProfileAxis."; - - class ShortcutItem { - public: - typedef QPair<std::function<void ()>, std::function<void ()>> Functions; - - ShortcutItem(QAction* action, const QString& name, ShortcutItem* parent = nullptr); - ShortcutItem(Functions functions, int shortcut, const QString& visibleName, const QString& name, - ShortcutItem* parent = nullptr); - ShortcutItem(QMenu* action, ShortcutItem* parent = nullptr); - - QAction* action() { return m_action; } - const QAction* action() const { return m_action; } - const int shortcut() const { return m_shortcut; } - Functions functions() const { return m_functions; } - QMenu* menu() { return m_menu; } - const QMenu* menu() const { return m_menu; } - const QString& visibleName() const { return m_visibleName; } - const QString& name() const { return m_name; } - QList<ShortcutItem>& items() { return m_items; } - const QList<ShortcutItem>& items() const { return m_items; } - ShortcutItem* parent() { return m_parent; } - const ShortcutItem* parent() const { return m_parent; } - void addAction(QAction* action, const QString& name); - void addFunctions(Functions functions, int shortcut, const QString& visibleName, - const QString& name); - void addSubmenu(QMenu* menu); - int button() const { return m_button; } - void setShortcut(int sequence); - void setButton(int button) { m_button = button; } - int axis() const { return m_axis; } - GamepadAxisEvent::Direction direction() const { return m_direction; } - void setAxis(int axis, GamepadAxisEvent::Direction direction); - - bool operator==(const ShortcutItem& other) const { - return m_menu == other.m_menu && m_action == other.m_action; - } - - private: - QAction* m_action; - int m_shortcut; - QMenu* m_menu; - Functions m_functions; - QString m_name; - QString m_visibleName; - int m_button; - int m_axis; - GamepadAxisEvent::Direction m_direction; - QList<ShortcutItem> m_items; - ShortcutItem* m_parent; - }; - -public: - ShortcutController(QObject* parent = nullptr); - - void setConfigController(ConfigController* controller); - void setProfile(const QString& profile); - - virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - - virtual QModelIndex index(int row, int column, const QModelIndex& parent) const override; - virtual QModelIndex parent(const QModelIndex& index) const override; - - virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override; - virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; - - void addAction(QMenu* menu, QAction* action, const QString& name); - void addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release, - int shortcut, const QString& visibleName, const QString& name); - void addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release, - const QKeySequence& shortcut, const QString& visibleName, const QString& name); - void addMenu(QMenu* menu, QMenu* parent = nullptr); - - QAction* getAction(const QString& name); - int shortcutAt(const QModelIndex& index) const; - bool isMenuAt(const QModelIndex& index) const; - - void updateKey(const QModelIndex& index, int keySequence); - void updateButton(const QModelIndex& index, int button); - void updateAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction direction); - - void clearKey(const QModelIndex& index); - void clearButton(const QModelIndex& index); - - static int toModifierShortcut(const QString& shortcut); - static bool isModifierKey(int key); - static int toModifierKey(int key); - -public slots: - void loadProfile(const QString& profile); - -protected: - bool eventFilter(QObject*, QEvent*) override; - -private: - ShortcutItem* itemAt(const QModelIndex& index); - const ShortcutItem* itemAt(const QModelIndex& index) const; - bool loadShortcuts(ShortcutItem*); - void loadGamepadShortcuts(ShortcutItem*); - void onSubitems(ShortcutItem*, std::function<void(ShortcutItem*)> func); - void updateKey(ShortcutItem* item, int keySequence); - - ShortcutItem m_rootMenu; - QMap<QMenu*, ShortcutItem*> m_menuMap; - QMap<int, ShortcutItem*> m_buttons; - QMap<QPair<int, GamepadAxisEvent::Direction>, ShortcutItem*> m_axes; - QMap<int, ShortcutItem*> m_heldKeys; - ConfigController* m_config; - QString m_profileName; - const InputProfile* m_profile; -}; - -} - -#endif
M src/platform/qt/ShortcutView.cppsrc/platform/qt/ShortcutView.cpp

@@ -7,7 +7,7 @@ #include "ShortcutView.h"

#include "GamepadButtonEvent.h" #include "InputController.h" -#include "ShortcutController.h" +#include "InputModel.h" #include <QKeyEvent>

@@ -41,9 +41,9 @@ ShortcutView::~ShortcutView() {

m_input->releaseFocus(this); } -void ShortcutView::setController(ShortcutController* controller) { - m_controller = controller; - m_ui.shortcutTable->setModel(controller); +void ShortcutView::setModel(InputModel* model) { + m_controller = model; + m_ui.shortcutTable->setModel(model); } void ShortcutView::setInputController(InputController* controller) {
M src/platform/qt/ShortcutView.hsrc/platform/qt/ShortcutView.h

@@ -15,7 +15,7 @@

namespace QGBA { class InputController; -class ShortcutController; +class InputModel; class ShortcutView : public QWidget { Q_OBJECT

@@ -24,7 +24,7 @@ public:

ShortcutView(QWidget* parent = nullptr); ~ShortcutView(); - void setController(ShortcutController* controller); + void setModel(InputModel* controller); void setInputController(InputController* input); protected:

@@ -40,7 +40,7 @@

private: Ui::ShortcutView m_ui; - ShortcutController* m_controller; + InputModel* m_controller; InputController* m_input; };
M src/platform/qt/Window.cppsrc/platform/qt/Window.cpp

@@ -28,6 +28,7 @@ #include "GBAApp.h"

#include "GDBController.h" #include "GDBWindow.h" #include "GIFView.h" +#include "InputModel.h" #include "IOViewer.h" #include "LoadSaveState.h" #include "LogView.h"

@@ -40,19 +41,23 @@ #include "ROMInfo.h"

#include "SensorView.h" #include "SettingsView.h" #include "ShaderSelector.h" -#include "ShortcutController.h" #include "TileView.h" #include "VideoView.h" #include <mgba/core/version.h> #ifdef M_CORE_GB #include <mgba/internal/gb/gb.h> +#include <mgba/internal/gb/input.h> #include <mgba/internal/gb/video.h> #endif #ifdef M_CORE_GBA #include <mgba/internal/gba/gba.h> +#include <mgba/internal/gba/input.h> #include <mgba/internal/gba/video.h> #endif +#ifdef M_CORE_DS +#include <mgba/internal/ds/input.h> +#endif #include "feature/commandline.h" #include "feature/sqlite3/no-intro.h" #include <mgba-util/vfs.h>

@@ -67,7 +72,8 @@ , m_stateWindow(nullptr)

, m_screenWidget(new WindowBackground()) , m_logo(":/res/mgba-1024.png") , m_config(config) - , m_inputController(playerId, this) + , m_inputModel(new InputModel(this)) + , m_inputController(m_inputModel, playerId, this) #ifdef USE_FFMPEG , m_videoView(nullptr) #endif

@@ -81,7 +87,6 @@ #ifdef USE_DEBUGGERS

, m_console(nullptr) #endif , m_mruMenu(nullptr) - , m_shortcutController(new ShortcutController(this)) , m_fullscreenOnStart(false) , m_autoresume(false) , m_wasOpened(false)

@@ -194,14 +199,24 @@ });

connect(m_display, &Display::showCursor, [this]() { m_screenWidget->unsetCursor(); }); - connect(&m_inputController, SIGNAL(profileLoaded(const QString&)), m_shortcutController, SLOT(loadProfile(const QString&))); m_log.setLevels(mLOG_WARN | mLOG_ERROR | mLOG_FATAL); m_fpsTimer.setInterval(FPS_TIMER_INTERVAL); m_focusCheck.setInterval(200); - m_shortcutController->setConfigController(m_config); + m_inputModel->setConfigController(m_config); setupMenu(menuBar()); + +#ifdef M_CORE_GBA + m_inputController.addPlatform(PLATFORM_GBA, tr("Game Boy Advance"), &GBAInputInfo); +#endif +#ifdef M_CORE_GB + m_inputController.addPlatform(PLATFORM_GB, tr("Game Boy"), &GBInputInfo); +#endif +#ifdef M_CORE_DS + m_inputController.addPlatform(PLATFORM_DS, tr("DS"), &DSInputInfo); +#endif + m_inputController.setupCallback(m_controller); } Window::~Window() {

@@ -491,7 +506,7 @@ }

} void Window::openSettingsWindow() { - SettingsView* settingsWindow = new SettingsView(m_config, &m_inputController, m_shortcutController); + SettingsView* settingsWindow = new SettingsView(m_config, &m_inputController, m_inputModel); connect(settingsWindow, SIGNAL(biosLoaded(int, const QString&)), m_controller, SLOT(loadBIOS(int, const QString&))); connect(settingsWindow, SIGNAL(audioDriverChanged()), m_controller, SLOT(reloadAudioDriver())); connect(settingsWindow, SIGNAL(displayDriverChanged()), this, SLOT(mustRestart()));

@@ -574,34 +589,6 @@ openView(window);

} #endif -void Window::keyPressEvent(QKeyEvent* event) { - if (event->isAutoRepeat()) { - QWidget::keyPressEvent(event); - return; - } - GBAKey key = m_inputController.mapKeyboard(event->key()); - if (key == GBA_KEY_NONE) { - QWidget::keyPressEvent(event); - return; - } - m_controller->keyPressed(key); - event->accept(); -} - -void Window::keyReleaseEvent(QKeyEvent* event) { - if (event->isAutoRepeat()) { - QWidget::keyReleaseEvent(event); - return; - } - GBAKey key = m_inputController.mapKeyboard(event->key()); - if (key == GBA_KEY_NONE) { - QWidget::keyPressEvent(event); - return; - } - m_controller->keyReleased(key); - event->accept(); -} - void Window::resizeEvent(QResizeEvent* event) { if (!isFullScreen()) { m_config->setOption("height", m_screenWidget->height());

@@ -800,6 +787,8 @@ menuBar()->hide();

} #endif + m_inputController.setPlatform(m_controller->platform()); + m_hitUnimplementedBiosCall = false; m_fpsTimer.start(); m_focusCheck.start();

@@ -968,8 +957,8 @@

void Window::setupMenu(QMenuBar* menubar) { menubar->clear(); QMenu* fileMenu = menubar->addMenu(tr("&File")); - m_shortcutController->addMenu(fileMenu); - installEventFilter(m_shortcutController); + m_inputModel->addMenu(fileMenu); + installEventFilter(&m_inputController); addControlledAction(fileMenu, fileMenu->addAction(tr("Load &ROM..."), this, SLOT(selectROM()), QKeySequence::Open), "loadROM"); #ifdef USE_SQLITE3

@@ -1024,8 +1013,8 @@ addControlledAction(fileMenu, saveState, "saveState");

QMenu* quickLoadMenu = fileMenu->addMenu(tr("Quick load")); QMenu* quickSaveMenu = fileMenu->addMenu(tr("Quick save")); - m_shortcutController->addMenu(quickLoadMenu); - m_shortcutController->addMenu(quickSaveMenu); + m_inputModel->addMenu(quickLoadMenu); + m_inputModel->addMenu(quickSaveMenu); QAction* quickLoad = new QAction(tr("Load recent"), quickLoadMenu); connect(quickLoad, SIGNAL(triggered()), m_controller, SLOT(loadState()));

@@ -1111,7 +1100,7 @@ addControlledAction(fileMenu, fileMenu->addAction(tr("E&xit"), this, SLOT(close()), QKeySequence::Quit), "quit");

#endif QMenu* emulationMenu = menubar->addMenu(tr("&Emulation")); - m_shortcutController->addMenu(emulationMenu); + m_inputModel->addMenu(emulationMenu); QAction* reset = new QAction(tr("&Reset"), emulationMenu); reset->setShortcut(tr("Ctrl+R")); connect(reset, SIGNAL(triggered()), m_controller, SLOT(reset()));

@@ -1152,7 +1141,7 @@ addControlledAction(emulationMenu, frameAdvance, "frameAdvance");

emulationMenu->addSeparator(); - m_shortcutController->addFunctions(emulationMenu, [this]() { + m_inputModel->addFunctions(emulationMenu, [this]() { m_controller->setTurbo(true, false); }, [this]() { m_controller->setTurbo(false, false);

@@ -1178,7 +1167,7 @@ ffspeed->addValue(tr("%0x").arg(i), i, ffspeedMenu);

} m_config->updateOption("fastForwardRatio"); - m_shortcutController->addFunctions(emulationMenu, [this]() { + m_inputModel->addFunctions(emulationMenu, [this]() { m_controller->startRewinding(); }, [this]() { m_controller->stopRewinding();

@@ -1217,7 +1206,7 @@

emulationMenu->addSeparator(); QMenu* solarMenu = emulationMenu->addMenu(tr("Solar sensor")); - m_shortcutController->addMenu(solarMenu); + m_inputModel->addMenu(solarMenu); QAction* solarIncrease = new QAction(tr("Increase solar level"), solarMenu); connect(solarIncrease, SIGNAL(triggered()), m_controller, SLOT(increaseLuminanceLevel())); addControlledAction(solarMenu, solarIncrease, "increaseLuminanceLevel");

@@ -1244,9 +1233,9 @@ addControlledAction(solarMenu, setSolar, QString("luminanceLevel.%1").arg(QString::number(i)));

} QMenu* avMenu = menubar->addMenu(tr("Audio/&Video")); - m_shortcutController->addMenu(avMenu); + m_inputModel->addMenu(avMenu); QMenu* frameMenu = avMenu->addMenu(tr("Frame size")); - m_shortcutController->addMenu(frameMenu, avMenu); + m_inputModel->addMenu(frameMenu, avMenu); for (int i = 1; i <= 6; ++i) { QAction* setSize = new QAction(tr("%1x").arg(QString::number(i)), avMenu); setSize->setCheckable(true);

@@ -1360,7 +1349,7 @@ #endif

avMenu->addSeparator(); QMenu* videoLayers = avMenu->addMenu(tr("Video layers")); - m_shortcutController->addMenu(videoLayers, avMenu); + m_inputModel->addMenu(videoLayers, avMenu); for (int i = 0; i < 4; ++i) { QAction* enableBg = new QAction(tr("Background %0").arg(i), videoLayers);

@@ -1377,7 +1366,7 @@ connect(enableObj, &QAction::triggered, [this](bool enable) { m_controller->setVideoLayerEnabled(4, enable); });

addControlledAction(videoLayers, enableObj, "enableOBJ"); QMenu* audioChannels = avMenu->addMenu(tr("Audio channels")); - m_shortcutController->addMenu(audioChannels, avMenu); + m_inputModel->addMenu(audioChannels, avMenu); for (int i = 0; i < 4; ++i) { QAction* enableCh = new QAction(tr("Channel %0").arg(i + 1), audioChannels);

@@ -1400,7 +1389,7 @@ connect(enableChB, &QAction::triggered, [this, i](bool enable) { m_controller->setAudioChannelEnabled(5, enable); });

addControlledAction(audioChannels, enableChB, QString("enableChB")); QMenu* toolsMenu = menubar->addMenu(tr("&Tools")); - m_shortcutController->addMenu(toolsMenu); + m_inputModel->addMenu(toolsMenu); QAction* viewLogs = new QAction(tr("View &logs..."), toolsMenu); connect(viewLogs, SIGNAL(triggered()), m_logView, SLOT(show())); addControlledAction(toolsMenu, viewLogs, "viewLogs");

@@ -1526,69 +1515,6 @@ connect(exitFullScreen, SIGNAL(triggered()), this, SLOT(exitFullScreen()));

exitFullScreen->setShortcut(QKeySequence("Esc")); addHiddenAction(frameMenu, exitFullScreen, "exitFullScreen"); - QMenu* autofireMenu = new QMenu(tr("Autofire"), this); - m_shortcutController->addMenu(autofireMenu); - - m_shortcutController->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_A, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_A, false); - }, QKeySequence(), tr("Autofire A"), "autofireA"); - - m_shortcutController->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_B, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_B, false); - }, QKeySequence(), tr("Autofire B"), "autofireB"); - - m_shortcutController->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_L, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_L, false); - }, QKeySequence(), tr("Autofire L"), "autofireL"); - - m_shortcutController->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_R, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_R, false); - }, QKeySequence(), tr("Autofire R"), "autofireR"); - - m_shortcutController->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_START, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_START, false); - }, QKeySequence(), tr("Autofire Start"), "autofireStart"); - - m_shortcutController->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_SELECT, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_SELECT, false); - }, QKeySequence(), tr("Autofire Select"), "autofireSelect"); - - m_shortcutController->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_UP, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_UP, false); - }, QKeySequence(), tr("Autofire Up"), "autofireUp"); - - m_shortcutController->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_RIGHT, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_RIGHT, false); - }, QKeySequence(), tr("Autofire Right"), "autofireRight"); - - m_shortcutController->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_DOWN, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_DOWN, false); - }, QKeySequence(), tr("Autofire Down"), "autofireDown"); - - m_shortcutController->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_LEFT, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_LEFT, false); - }, QKeySequence(), tr("Autofire Left"), "autofireLeft"); - foreach (QAction* action, m_gameActions) { action->setDisabled(true); }

@@ -1644,7 +1570,7 @@ return action;

} QAction* Window::addHiddenAction(QMenu* menu, QAction* action, const QString& name) { - m_shortcutController->addAction(menu, action, name); + m_inputModel->addAction(menu, action, name); action->setShortcutContext(Qt::WidgetShortcut); addAction(action); return action;
M src/platform/qt/Window.hsrc/platform/qt/Window.h

@@ -28,10 +28,10 @@ class Display;

class GameController; class GDBController; class GIFView; +class InputModel; class LibraryView; class LogView; class ShaderSelector; -class ShortcutController; class VideoView; class WindowBackground;

@@ -98,8 +98,6 @@ void gdbOpen();

#endif protected: - virtual void keyPressEvent(QKeyEvent* event) override; - virtual void keyReleaseEvent(QKeyEvent* event) override; virtual void resizeEvent(QResizeEvent*) override; virtual void showEvent(QShowEvent*) override; virtual void closeEvent(QCloseEvent*) override;

@@ -170,12 +168,12 @@ LoadSaveState* m_stateWindow;

WindowBackground* m_screenWidget; QPixmap m_logo; ConfigController* m_config; + InputModel* m_inputModel; InputController m_inputController; QList<QDateTime> m_frameList; QTimer m_fpsTimer; QList<QString> m_mruFiles; QMenu* m_mruMenu; - ShortcutController* m_shortcutController; ShaderSelector* m_shaderView; bool m_fullscreenOnStart; QTimer m_focusCheck;
M src/platform/windows/vfs-w32.csrc/platform/windows/vfs-w32.c

@@ -84,7 +84,7 @@ void _vdwRewind(struct VDir* vd) {

struct VDirW32* vdw = (struct VDirW32*) vd; FindClose(vdw->handle); wchar_t name[MAX_PATH + 1]; - MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, vdw->path, -1, name, MAX_PATH); + MultiByteToWideChar(CP_UTF8, 0, vdw->path, -1, name, MAX_PATH); StringCchCatNW(name, MAX_PATH, L"\\*", 2); if (vdw->vde.utf8Name) { free(vdw->vde.utf8Name);
A tools/deploy-mac.py

@@ -0,0 +1,170 @@

+#!/usr/bin/env python +from __future__ import print_function +import argparse +import errno +import os +import re +import shutil +import subprocess + +qtPath = None +verbose = False + +def splitPath(path): + folders = [] + while True: + path, folder = os.path.split(path) + if folder != '': + folders.append(folder) + else: + if path != '': + folders.append(path) + break + folders.reverse() + return folders + +def joinPath(path): + return reduce(os.path.join, path, '') + +def findFramework(path): + child = [] + while path and not path[-1].endswith('.framework'): + child.append(path.pop()) + child.reverse() + return path, child + +def findQtPath(path): + parent, child = findFramework(splitPath(path)) + return joinPath(parent[:-2]) + +def makedirs(path): + split = splitPath(path) + accum = [] + split.reverse() + while split: + accum.append(split.pop()) + newPath = joinPath(accum) + if newPath == '/': + continue + try: + os.mkdir(newPath) + except OSError as e: + if e.errno != errno.EEXIST: + raise + + +def parseOtoolLine(line, execPath, root): + if not line.startswith('\t'): + return None, None, None, None + line = line[1:] + match = re.match(r'(\S.*) \(compatibility version.*\)', line) + path = match.group(1) + split = splitPath(path) + newExecPath = ['@executable_path', '..', 'Frameworks'] + newPath = execPath[:-1] + newPath.append('Frameworks') + if split[:3] == ['/', 'usr', 'lib'] or split[:2] == ['/', 'System']: + return None, None, None, None + if split[0] == '@executable_path': + split[:1] = execPath + if split[0] == '/' and not os.access(joinPath(split), os.F_OK): + split[:1] = root + oldPath = os.path.realpath(joinPath(split)) + split = splitPath(oldPath) + isFramework = False + if not split[-1].endswith('.dylib'): + isFramework = True + split, framework = findFramework(split) + newPath.append(split[-1]) + newExecPath.append(split[-1]) + if isFramework: + newPath.extend(framework) + newExecPath.extend(framework) + split.extend(framework) + newPath = joinPath(newPath) + newExecPath = joinPath(newExecPath) + return joinPath(split), newPath, path, newExecPath + +def updateMachO(bin, execPath, root): + global qtPath + otoolOutput = subprocess.check_output([otool, '-L', bin]) + toUpdate = [] + for line in otoolOutput.split('\n'): + oldPath, newPath, oldExecPath, newExecPath = parseOtoolLine(line, execPath, root) + if not newPath: + continue + if os.access(newPath, os.F_OK): + if verbose: + print('Skipping copying {}, already done.'.format(oldPath)) + newPath = None + elif os.path.abspath(oldPath) != os.path.abspath(newPath): + if verbose: + print('Copying {} to {}...'.format(oldPath, newPath)) + parent, child = os.path.split(newPath) + makedirs(parent) + shutil.copy2(oldPath, newPath) + os.chmod(newPath, 0o644) + toUpdate.append((newPath, oldExecPath, newExecPath)) + if not qtPath and 'Qt' in oldPath: + qtPath = findQtPath(oldPath) + if verbose: + print('Found Qt path at {}.'.format(qtPath)) + args = [installNameTool] + for path, oldExecPath, newExecPath in toUpdate: + if path != bin: + if path: + updateMachO(path, execPath, root) + if verbose: + print('Updating Mach-O load from {} to {}...'.format(oldExecPath, newExecPath)) + args.extend(['-change', oldExecPath, newExecPath]) + else: + if verbose: + print('Updating Mach-O id from {} to {}...'.format(oldExecPath, newExecPath)) + args.extend(['-id', newExecPath]) + args.append(bin) + subprocess.check_call(args) + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('-R', '--root', metavar='ROOT', default='/', help='root directory to search') + parser.add_argument('-I', '--install-name-tool', metavar='INSTALL_NAME_TOOL', default='install_name_tool', help='path to install_name_tool') + parser.add_argument('-O', '--otool', metavar='OTOOL', default='otool', help='path to otool') + parser.add_argument('-p', '--qt-plugins', metavar='PLUGINS', default='', help='Qt plugins to include (comma-separated)') + parser.add_argument('-v', '--verbose', action='store_true', default=False, help='output more information') + parser.add_argument('bundle', help='application bundle to deploy') + args = parser.parse_args() + + otool = args.otool + installNameTool = args.install_name_tool + verbose = args.verbose + + try: + shutil.rmtree(os.path.join(args.bundle, 'Contents/Frameworks/')) + except OSError as e: + if e.errno != errno.ENOENT: + raise + + for executable in os.listdir(os.path.join(args.bundle, 'Contents/MacOS')): + if executable.endswith('.dSYM'): + continue + fullPath = os.path.join(args.bundle, 'Contents/MacOS/', executable) + updateMachO(fullPath, splitPath(os.path.join(args.bundle, 'Contents/MacOS')), splitPath(args.root)) + if args.qt_plugins: + try: + shutil.rmtree(os.path.join(args.bundle, 'Contents/PlugIns/')) + except OSError as e: + if e.errno != errno.ENOENT: + raise + makedirs(os.path.join(args.bundle, 'Contents/PlugIns')) + makedirs(os.path.join(args.bundle, 'Contents/Resources')) + with open(os.path.join(args.bundle, 'Contents/Resources/qt.conf'), 'w') as conf: + conf.write('[Paths]\nPlugins = PlugIns\n') + plugins = args.qt_plugins.split(',') + for plugin in plugins: + plugin = plugin.strip() + kind, plug = os.path.split(plugin) + newDir = os.path.join(args.bundle, 'Contents/PlugIns/', kind) + makedirs(newDir) + newPath = os.path.join(newDir, plug) + shutil.copy2(os.path.join(qtPath, 'plugins', plugin), newPath) + updateMachO(newPath, splitPath(os.path.join(args.bundle, 'Contents/MacOS')), splitPath(args.root))