Qt: Allow shortcuts to be controlled with a gamepad
jump to
@@ -44,7 +44,6 @@ GBAKeyEditor.cpp
GIFView.cpp GameController.cpp GamePakView.cpp - GamepadMonitor.cpp InputController.cpp KeyEditor.cpp LoadSaveState.cpp
@@ -11,7 +11,6 @@ #include <QPushButton>
#include <QVBoxLayout> #include "InputController.h" -#include "GamepadMonitor.h" #include "KeyEditor.h" using namespace QGBA;@@ -25,7 +24,6 @@ GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, QWidget* parent)
: QWidget(parent) , m_type(type) , m_controller(controller) - , m_gamepadMonitor(nullptr) { setWindowFlags(windowFlags() & ~Qt::WindowFullscreenButtonHint); setMinimumSize(300, 300);@@ -114,9 +112,8 @@ setAll->setFocus();
#ifdef BUILD_SDL if (type == SDL_BINDING_BUTTON) { - m_gamepadMonitor = new GamepadMonitor(m_controller, this); - connect(m_gamepadMonitor, SIGNAL(buttonPressed(int)), this, SLOT(setButton(int))); - connect(m_gamepadMonitor, SIGNAL(axisChanged(int, int32_t)), this, SLOT(setAxisValue(int, int32_t))); + connect(m_controller, SIGNAL(buttonPressed(int)), this, SLOT(setButton(int))); + connect(m_controller, SIGNAL(axisChanged(int, int32_t)), this, SLOT(setAxisValue(int, int32_t))); } #endif }
@@ -20,7 +20,6 @@
namespace QGBA { class InputController; -class GamepadMonitor; class KeyEditor; class GBAKeyEditor : public QWidget {@@ -76,8 +75,6 @@ KeyEditor* m_keyL;
KeyEditor* m_keyR; QList<KeyEditor*> m_keyOrder; QList<KeyEditor*>::iterator m_currentKey; - - GamepadMonitor* m_gamepadMonitor; uint32_t m_type; InputController* m_controller;
@@ -1,46 +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 "GamepadMonitor.h" - -#include "InputController.h" - -#include <QTimer> - -using namespace QGBA; - -GamepadMonitor::GamepadMonitor(InputController* controller, QObject* parent) - : QObject(parent) - , m_controller(controller) -{ -#ifdef BUILD_SDL - m_gamepadTimer = new QTimer(this); - connect(m_gamepadTimer, SIGNAL(timeout()), this, SLOT(testGamepad())); - m_gamepadTimer->setInterval(50); - m_gamepadTimer->start(); -#endif -} - -void GamepadMonitor::testGamepad() { -#ifdef BUILD_SDL - m_gamepadTimer->setInterval(50); - - auto activeAxes = m_controller->activeGamepadAxes(); - auto oldAxes = m_activeAxes; - m_activeAxes = activeAxes; - activeAxes.subtract(oldAxes); - if (!activeAxes.empty()) { - emit axisChanged(activeAxes.begin()->first, activeAxes.begin()->second); - } - - auto activeButtons = m_controller->activeGamepadButtons(); - auto oldButtons = m_activeButtons; - m_activeButtons = activeButtons; - activeButtons.subtract(oldButtons); - if (!activeButtons.empty()) { - emit buttonPressed(*activeButtons.begin()); - } -#endif -}
@@ -1,40 +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_GAMEPAD_MONITOR -#define QGBA_GAMEPAD_MONITOR - -#include <QObject> -#include <QSet> - -class QTimer; - -namespace QGBA { - -class InputController; - -class GamepadMonitor : public QObject { -Q_OBJECT - -public: - GamepadMonitor(InputController* controller, QObject* parent = nullptr); - -signals: - void axisChanged(int axis, int32_t value); - void buttonPressed(int button); - -public slots: - void testGamepad(); - -private: - InputController* m_controller; - QSet<int> m_activeButtons; - QSet<QPair<int, int32_t>> m_activeAxes; - QTimer* m_gamepadTimer; -}; - -} - -#endif
@@ -7,13 +7,19 @@ #include "InputController.h"
#include "ConfigController.h" +#include <QTimer> + extern "C" { #include "util/configuration.h" } using namespace QGBA; -InputController::InputController() { +InputController::InputController(QObject* parent) + : QObject(parent) + , m_config(nullptr) + , m_gamepadTimer(nullptr) +{ GBAInputMapInit(&m_inputMap); #ifdef BUILD_SDL@@ -21,6 +27,11 @@ m_sdlEvents.bindings = &m_inputMap;
GBASDLInitEvents(&m_sdlEvents); GBASDLInitBindings(&m_inputMap); SDL_JoystickEventState(SDL_QUERY); + + m_gamepadTimer = new QTimer(this); + connect(m_gamepadTimer, SIGNAL(timeout()), this, SLOT(testGamepad())); + m_gamepadTimer->setInterval(50); + m_gamepadTimer->start(); #endif GBAInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_X, GBA_KEY_A);@@ -163,3 +174,27 @@ }
GBAInputBindAxis(&m_inputMap, SDL_BINDING_BUTTON, axis, &description); } #endif + +void InputController::testGamepad() { +#ifdef BUILD_SDL + auto activeAxes = activeGamepadAxes(); + auto oldAxes = m_activeAxes; + m_activeAxes = activeAxes; + activeAxes.subtract(oldAxes); + if (!activeAxes.empty()) { + emit axisChanged(activeAxes.begin()->first, activeAxes.begin()->second); + } + + auto activeButtons = activeGamepadButtons(); + auto oldButtons = m_activeButtons; + m_activeButtons = activeButtons; + activeButtons.subtract(oldButtons); + oldButtons.subtract(m_activeButtons); + for (int button : activeButtons) { + emit buttonPressed(button); + } + for (int button : oldButtons) { + emit buttonReleased(button); + } +#endif +}
@@ -6,6 +6,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef QGBA_INPUT_CONTROLLER_H #define QGBA_INPUT_CONTROLLER_H +#include <QObject> +#include <QSet> + +class QTimer; + extern "C" { #include "gba-input.h"@@ -13,18 +18,18 @@ #ifdef BUILD_SDL
#include "platform/sdl/sdl-events.h" #endif } - -#include <QSet> namespace QGBA { class ConfigController; -class InputController { +class InputController : public QObject { +Q_OBJECT + public: static const uint32_t KEYBOARD = 0x51545F4B; - InputController(); + InputController(QObject* parent = nullptr); ~InputController(); void setConfiguration(ConfigController* config);@@ -52,6 +57,14 @@
void bindAxis(uint32_t type, int axis, Direction, GBAKey); #endif +signals: + void axisChanged(int axis, int32_t value); + void buttonPressed(int button); + void buttonReleased(int button); + +public slots: + void testGamepad(); + private: GBAInputMap m_inputMap; ConfigController* m_config;@@ -59,6 +72,10 @@
#ifdef BUILD_SDL GBASDLEvents m_sdlEvents; #endif + + QSet<int> m_activeButtons; + QSet<QPair<int, int32_t>> m_activeAxes; + QTimer* m_gamepadTimer; }; }
@@ -17,15 +17,17 @@
public: KeyEditor(QWidget* parent = nullptr); - void setValue(int key); int value() const { return m_key; } - void setValueKey(int key); - void setValueButton(int button); - void setValueAxis(int axis, int32_t value); InputController::Direction direction() const { return m_direction; } virtual QSize sizeHint() const override; + +public slots: + void setValue(int key); + void setValueKey(int key); + void setValueButton(int button); + void setValueAxis(int axis, int32_t value); signals: void valueChanged(int key);
@@ -28,6 +28,11 @@ case 0:
return item.visibleName(); case 1: return item.action()->shortcut().toString(QKeySequence::NativeText); + case 2: + if (item.button() >= 0) { + return item.button(); + } + return QVariant(); } } else if (index.column() == 0) { return m_menus[index.row()].visibleName();@@ -44,7 +49,9 @@ switch (section) {
case 0: return tr("Action"); case 1: - return tr("Shortcut"); + return tr("Keyboard"); + case 2: + return tr("Gamepad"); } } return section;@@ -68,7 +75,7 @@ return createIndex(index.internalId(), 0, -1);
} int ShortcutController::columnCount(const QModelIndex& index) const { - return 2; + return 3; } int ShortcutController::rowCount(const QModelIndex& index) const {@@ -97,7 +104,7 @@ QModelIndex parent = createIndex(row, 0, -1);
beginInsertRows(parent, smenu->shortcuts().count(), smenu->shortcuts().count()); smenu->addAction(action, name); endInsertRows(); - emit dataChanged(createIndex(smenu->shortcuts().count() - 1, 0, row), createIndex(smenu->shortcuts().count() - 1, 1, row)); + emit dataChanged(createIndex(smenu->shortcuts().count() - 1, 0, row), createIndex(smenu->shortcuts().count() - 1, 2, row)); } void ShortcutController::addMenu(QMenu* menu) {@@ -137,12 +144,44 @@ }
ShortcutMenu& menu = m_menus[parent.row()]; ShortcutItem& item = menu.shortcuts()[index.row()]; item.action()->setShortcut(keySequence); - emit dataChanged(createIndex(index.row(), 0, index.internalId()), createIndex(index.row(), 1, index.internalId())); + emit dataChanged(createIndex(index.row(), 0, index.internalId()), createIndex(index.row(), 2, index.internalId())); +} + +void ShortcutController::updateButton(const QModelIndex& index, int button) { + if (!index.isValid()) { + return; + } + const QModelIndex& parent = index.parent(); + if (!parent.isValid()) { + return; + } + ShortcutMenu& menu = m_menus[parent.row()]; + ShortcutItem& item = menu.shortcuts()[index.row()]; + int oldButton = item.button(); + item.setButton(button); + if (oldButton >= 0) { + m_buttons.take(oldButton); + } + m_buttons[button] = &item; + emit dataChanged(createIndex(index.row(), 0, index.internalId()), createIndex(index.row(), 2, index.internalId())); +} + +void ShortcutController::pressButton(int button) { + auto item = m_buttons.find(button); + if (item == m_buttons.end()) { + return; + } + QAction* action = item.value()->action(); + if (!action->isEnabled()) { + return; + } + action->trigger(); } ShortcutController::ShortcutItem::ShortcutItem(QAction* action, const QString& name) : m_action(action) , m_name(name) + , m_button(-1) { m_visibleName = action->text() .remove(QRegExp("&(?!&)"))
@@ -15,6 +15,8 @@
namespace QGBA { class ShortcutController : public QAbstractItemModel { +Q_OBJECT + public: ShortcutController(QObject* parent = nullptr);@@ -32,6 +34,10 @@ void addMenu(QMenu* menu);
const QAction* actionAt(const QModelIndex& index) const; void updateKey(const QModelIndex& index, const QKeySequence& keySequence); + void updateButton(const QModelIndex& index, int button); + +private slots: + void pressButton(int button); private: class ShortcutItem {@@ -42,11 +48,14 @@ QAction* action() { return m_action; }
const QAction* action() const { return m_action; } const QString& visibleName() const { return m_visibleName; } const QString& name() const { return m_name; } + int button() const { return m_button; } + void setButton(int button) { m_button = button; } private: QAction* m_action; QString m_name; QString m_visibleName; + int m_button; }; class ShortcutMenu {@@ -67,6 +76,7 @@ QList<ShortcutItem> m_shortcuts;
}; QList<ShortcutMenu> m_menus; + QMap<int, ShortcutItem*> m_buttons; }; }
@@ -11,16 +11,25 @@ using namespace QGBA;
ShortcutView::ShortcutView(QWidget* parent) : QWidget(parent) + , m_controller(nullptr) + , m_inputController(nullptr) { m_ui.setupUi(this); connect(m_ui.keySequenceEdit, SIGNAL(editingFinished()), this, SLOT(updateKey())); + connect(m_ui.keyEdit, SIGNAL(valueChanged(int)), this, SLOT(updateButton(int))); connect(m_ui.shortcutTable, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(loadKey(const QModelIndex&))); } void ShortcutView::setController(ShortcutController* controller) { m_controller = controller; m_ui.shortcutTable->setModel(controller); +} + +void ShortcutView::setInputController(InputController* controller) { + m_inputController = controller; + connect(controller, SIGNAL(buttonPressed(int)), m_ui.keyEdit, SLOT(setValueButton(int))); + connect(controller, SIGNAL(axisChanged(int, int32_t)), m_ui.keyEdit, SLOT(setValueAxis(int, int32_t))); } void ShortcutView::loadKey(const QModelIndex& index) {@@ -42,3 +51,11 @@ }
m_ui.keySequenceEdit->clearFocus(); m_controller->updateKey(m_ui.shortcutTable->selectionModel()->currentIndex(), m_ui.keySequenceEdit->keySequence()); } + +void ShortcutView::updateButton(int button) { + if (!m_controller) { + return; + } + m_controller->updateButton(m_ui.shortcutTable->selectionModel()->currentIndex(), button); + +}
@@ -12,8 +12,10 @@ #include "ui_ShortcutView.h"
namespace QGBA { +class InputController; class ShortcutController; +// TODO: suspend shortcuts (keyboard and gamepad) while window is open class ShortcutView : public QWidget { Q_OBJECT@@ -21,15 +23,18 @@ public:
ShortcutView(QWidget* parent = nullptr); void setController(ShortcutController* controller); + void setInputController(InputController* controller); private slots: void loadKey(const QModelIndex&); void updateKey(); + void updateButton(int button); private: Ui::ShortcutView m_ui; ShortcutController* m_controller; + InputController* m_inputController; }; }
@@ -6,8 +6,8 @@ <property name="geometry">
<rect> <x>0</x> <y>0</y> - <width>300</width> - <height>400</height> + <width>425</width> + <height>443</height> </rect> </property> <property name="windowTitle">@@ -22,25 +22,26 @@ </attribute>
</widget> </item> <item> - <layout class="QHBoxLayout" name="horizontalLayout_2"> + <layout class="QHBoxLayout" name="horizontalLayout"> <item> - <spacer name="horizontalSpacer_4"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> + <widget class="QRadioButton" name="keyboardButton"> + <property name="text"> + <string>Keyboard</string> </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> + <property name="checked"> + <bool>true</bool> </property> - </spacer> + </widget> </item> <item> - <widget class="QKeySequenceEdit" name="keySequenceEdit"/> + <widget class="QRadioButton" name="gamepadButton"> + <property name="text"> + <string>Gamepad</string> + </property> + </widget> </item> <item> - <spacer name="horizontalSpacer_5"> + <spacer name="horizontalSpacer_2"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property>@@ -52,73 +53,103 @@ </size>
</property> </spacer> </item> - </layout> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"> <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> + <widget class="QKeySequenceEdit" name="keySequenceEdit"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> </property> - <property name="sizeHint" stdset="0"> + <property name="minimumSize"> <size> <width>40</width> - <height>20</height> + <height>0</height> </size> </property> - </spacer> - </item> - <item> - <widget class="QRadioButton" name="radioButton"> - <property name="text"> - <string>Keyboard</string> - </property> - <property name="checked"> - <bool>true</bool> + <property name="maximumSize"> + <size> + <width>100</width> + <height>16777215</height> + </size> </property> </widget> </item> <item> - <spacer name="horizontalSpacer_3"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> + <widget class="QGBA::KeyEditor" name="keyEdit"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> </property> - <property name="sizeHint" stdset="0"> + <property name="minimumSize"> <size> <width>40</width> - <height>20</height> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>100</width> + <height>16777215</height> </size> </property> - </spacer> - </item> - <item> - <widget class="QRadioButton" name="radioButton_2"> - <property name="enabled"> + <property name="visible"> <bool>false</bool> </property> - <property name="text"> - <string>Gamepad</string> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="placeholderText"> + <string>Press button</string> </property> </widget> </item> - <item> - <spacer name="horizontalSpacer_2"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> </layout> </item> </layout> </widget> + <customwidgets> + <customwidget> + <class>QGBA::KeyEditor</class> + <extends>QLineEdit</extends> + <header>KeyEditor.h</header> + </customwidget> + </customwidgets> <resources/> - <connections/> + <connections> + <connection> + <sender>keyboardButton</sender> + <signal>toggled(bool)</signal> + <receiver>keySequenceEdit</receiver> + <slot>setVisible(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>86</x> + <y>374</y> + </hint> + <hint type="destinationlabel"> + <x>66</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>gamepadButton</sender> + <signal>toggled(bool)</signal> + <receiver>keyEdit</receiver> + <slot>setVisible(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>213</x> + <y>374</y> + </hint> + <hint type="destinationlabel"> + <x>206</x> + <y>340</y> + </hint> + </hints> + </connection> + </connections> </ui>
@@ -93,6 +93,8 @@ connect(this, SIGNAL(audioBufferSamplesChanged(int)), m_controller, SLOT(setAudioBufferSamples(int)));
connect(this, SIGNAL(fpsTargetChanged(float)), m_controller, SLOT(setFPSTarget(float))); connect(&m_fpsTimer, SIGNAL(timeout()), this, SLOT(showFPS())); + connect(&m_inputController, SIGNAL(buttonPressed(int)), m_shortcutController, SLOT(pressButton(int))); + m_logView->setLevels(GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL); m_fpsTimer.setInterval(FPS_TIMER_INTERVAL);@@ -213,6 +215,7 @@
void Window::openShortcutWindow() { ShortcutView* shortcutView = new ShortcutView(); shortcutView->setController(m_shortcutController); + shortcutView->setInputController(&m_inputController); connect(this, SIGNAL(shutdown()), shortcutView, SLOT(close())); shortcutView->setAttribute(Qt::WA_DeleteOnClose); shortcutView->show();