From 87889a13e0f3f03916ab1062ba48b98ac5424d0f Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Mon, 6 Jan 2020 15:14:47 +1000 Subject: [PATCH] Qt: Properly handle modifier keys for input --- src/duckstation-qt/inputbindingwidgets.cpp | 62 ++++++++++----- src/duckstation-qt/inputbindingwidgets.h | 5 +- src/duckstation-qt/qtdisplaywindow.cpp | 5 +- src/duckstation-qt/qthostinterface.cpp | 2 +- src/duckstation-qt/qtutils.cpp | 93 ++++++++++++++++++++++ src/duckstation-qt/qtutils.h | 14 ++++ 6 files changed, 157 insertions(+), 24 deletions(-) diff --git a/src/duckstation-qt/inputbindingwidgets.cpp b/src/duckstation-qt/inputbindingwidgets.cpp index ea7901751..99cf3fb2d 100644 --- a/src/duckstation-qt/inputbindingwidgets.cpp +++ b/src/duckstation-qt/inputbindingwidgets.cpp @@ -15,34 +15,50 @@ InputButtonBindingWidget::InputButtonBindingWidget(QtHostInterface* host_interfa connect(this, &QPushButton::pressed, this, &InputButtonBindingWidget::onPressed); } -InputButtonBindingWidget::~InputButtonBindingWidget() = default; - -void InputButtonBindingWidget::keyPressEvent(QKeyEvent* event) +InputButtonBindingWidget::~InputButtonBindingWidget() { - // ignore the key press if we're listening for input if (isListeningForInput()) - return; - - QPushButton::keyPressEvent(event); + stopListeningForInput(); } -void InputButtonBindingWidget::keyReleaseEvent(QKeyEvent* event) +bool InputButtonBindingWidget::eventFilter(QObject* watched, QEvent* event) { - if (!isListeningForInput()) + const QEvent::Type event_type = event->type(); + + // if the key is being released, set the input + if (event_type == QEvent::KeyRelease) { - QPushButton::keyReleaseEvent(event); + setNewBinding(); + stopListeningForInput(); + return true; + } + else if (event_type == QEvent::KeyPress) + { + QString binding = QtUtils::KeyEventToString(static_cast(event)); + if (!binding.isEmpty()) + m_new_binding_value = QStringLiteral("Keyboard/%1").arg(binding); + + return true; + } + else if (event_type == QEvent::MouseButtonPress || event_type == QEvent::MouseButtonRelease || + event_type == QEvent::MouseButtonDblClick) + { + return true; + } + + return false; +} + +void InputButtonBindingWidget::setNewBinding() +{ + if (m_new_binding_value.isEmpty()) return; - } - QString key_name = QtUtils::GetKeyIdentifier(event->key()); - if (!key_name.isEmpty()) - { - // TODO: Update input map - m_current_binding_value = QStringLiteral("Keyboard/%1").arg(key_name); - m_host_interface->getQSettings().setValue(m_setting_name, m_current_binding_value); - } + m_host_interface->getQSettings().setValue(m_setting_name, m_new_binding_value); + m_host_interface->updateInputMap(); - stopListeningForInput(); + m_current_binding_value = std::move(m_new_binding_value); + m_new_binding_value.clear(); } void InputButtonBindingWidget::onPressed() @@ -75,6 +91,10 @@ void InputButtonBindingWidget::startListeningForInput() &InputButtonBindingWidget::onInputListenTimerTimeout); m_input_listen_remaining_seconds = 5; setText(tr("Push Button... [%1]").arg(m_input_listen_remaining_seconds)); + + installEventFilter(this); + grabKeyboard(); + grabMouse(); } void InputButtonBindingWidget::stopListeningForInput() @@ -82,4 +102,8 @@ void InputButtonBindingWidget::stopListeningForInput() setText(m_current_binding_value); delete m_input_listen_timer; m_input_listen_timer = nullptr; + + releaseMouse(); + releaseKeyboard(); + removeEventFilter(this); } diff --git a/src/duckstation-qt/inputbindingwidgets.h b/src/duckstation-qt/inputbindingwidgets.h index dd0722ff4..9604aed6d 100644 --- a/src/duckstation-qt/inputbindingwidgets.h +++ b/src/duckstation-qt/inputbindingwidgets.h @@ -15,8 +15,7 @@ public: ~InputButtonBindingWidget(); protected: - void keyPressEvent(QKeyEvent* event) override; - void keyReleaseEvent(QKeyEvent* event) override; + bool eventFilter(QObject* watched, QEvent* event) override; private Q_SLOTS: void onPressed(); @@ -26,10 +25,12 @@ private: bool isListeningForInput() const { return m_input_listen_timer != nullptr; } void startListeningForInput(); void stopListeningForInput(); + void setNewBinding(); QtHostInterface* m_host_interface; QString m_setting_name; QString m_current_binding_value; + QString m_new_binding_value; QTimer* m_input_listen_timer = nullptr; u32 m_input_listen_remaining_seconds = 0; }; diff --git a/src/duckstation-qt/qtdisplaywindow.cpp b/src/duckstation-qt/qtdisplaywindow.cpp index c3a96aa7b..d7b2064b2 100644 --- a/src/duckstation-qt/qtdisplaywindow.cpp +++ b/src/duckstation-qt/qtdisplaywindow.cpp @@ -1,5 +1,6 @@ #include "qtdisplaywindow.h" #include "imgui.h" +#include "qtutils.h" #include "qthostinterface.h" #include @@ -75,7 +76,7 @@ void QtDisplayWindow::keyPressEvent(QKeyEvent* event) if (event->isAutoRepeat()) return; - m_host_interface->handleKeyEvent(event->key(), true); + m_host_interface->handleKeyEvent(QtUtils::KeyEventToInt(event), true); } void QtDisplayWindow::keyReleaseEvent(QKeyEvent* event) @@ -83,7 +84,7 @@ void QtDisplayWindow::keyReleaseEvent(QKeyEvent* event) if (event->isAutoRepeat()) return; - m_host_interface->handleKeyEvent(event->key(), false); + m_host_interface->handleKeyEvent(QtUtils::KeyEventToInt(event), false); } void QtDisplayWindow::resizeEvent(QResizeEvent* event) diff --git a/src/duckstation-qt/qthostinterface.cpp b/src/duckstation-qt/qthostinterface.cpp index 2125e29fa..c5ef7ba6e 100644 --- a/src/duckstation-qt/qthostinterface.cpp +++ b/src/duckstation-qt/qthostinterface.cpp @@ -297,7 +297,7 @@ void QtHostInterface::addButtonToInputMap(const QString& binding, InputButtonHan const QString button = binding.section('/', 1, 1); if (device == QStringLiteral("Keyboard")) { - std::optional key_id = QtUtils::GetKeyIdForIdentifier(button); + std::optional key_id = QtUtils::ParseKeyString(button); if (!key_id.has_value()) { qWarning() << "Unknown keyboard key " << button; diff --git a/src/duckstation-qt/qtutils.cpp b/src/duckstation-qt/qtutils.cpp index 7f7ec12a5..cec08b512 100644 --- a/src/duckstation-qt/qtutils.cpp +++ b/src/duckstation-qt/qtutils.cpp @@ -1,10 +1,32 @@ #include "qtutils.h" +#include +#include +#include #include #include +#include #include namespace QtUtils { +QWidget* GetRootWidget(QWidget* widget, bool stop_at_window_or_dialog) +{ + QWidget* next_parent = widget->parentWidget(); + while (next_parent) + { + if (stop_at_window_or_dialog && (widget->metaObject()->inherits(&QMainWindow::staticMetaObject) || + widget->metaObject()->inherits(&QDialog::staticMetaObject))) + { + break; + } + + widget = next_parent; + next_parent = widget->parentWidget(); + } + + return widget; +} + void ResizeColumnsForTableView(QTableView* view, const std::initializer_list& widths) { const int total_width = @@ -461,6 +483,22 @@ static const std::map s_qt_key_names = { {Qt::Key_Camera, QStringLiteral("Camera")}, {Qt::Key_CameraFocus, QStringLiteral("CameraFocus")}}; +struct QtKeyModifierEntry +{ + Qt::KeyboardModifier mod; + Qt::Key key; + QString name; +}; + +static const std::array s_qt_key_modifiers = { + {{Qt::ShiftModifier, Qt::Key_Shift, QStringLiteral("Shift")}, + {Qt::ControlModifier, Qt::Key_Control, QStringLiteral("Control")}, + {Qt::AltModifier, Qt::Key_Alt, QStringLiteral("Alt")}, + {Qt::MetaModifier, Qt::Key_Meta, QStringLiteral("Meta")}, + {Qt::KeypadModifier, static_cast(0), QStringLiteral("Keypad")}}}; +static const Qt::KeyboardModifiers s_qt_modifier_mask = + Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier | Qt::KeypadModifier; + QString GetKeyIdentifier(int key) { const auto it = s_qt_key_names.find(key); @@ -478,4 +516,59 @@ std::optional GetKeyIdForIdentifier(const QString& key_identifier) return std::nullopt; } +QString KeyEventToString(const QKeyEvent* ke) +{ + const int key = ke->key(); + QString key_name = GetKeyIdentifier(key); + if (key_name.isEmpty()) + return {}; + + QString ret; + const Qt::KeyboardModifiers mods = ke->modifiers(); + for (const QtKeyModifierEntry& mod : s_qt_key_modifiers) + { + if (mods & mod.mod && key != mod.key) + { + ret.append(mod.name); + ret.append('+'); + } + } + + ret.append(key_name); + return ret; +} + +std::optional ParseKeyString(const QString& key_str) +{ + const QStringList sections = key_str.split('+'); + std::optional key_id = GetKeyIdForIdentifier(sections.last()); + if (!key_id) + return std::nullopt; + + int ret = key_id.value(); + + if (sections.size() > 1) + { + const int num_modifiers = sections.size() - 1; + for (int i = 0; i < num_modifiers; i++) + { + for (const QtKeyModifierEntry& mod : s_qt_key_modifiers) + { + if (sections[i] == mod.name) + { + ret |= static_cast(mod.mod); + break; + } + } + } + } + + return ret; +} + +int KeyEventToInt(const QKeyEvent* ke) +{ + return static_cast(ke->modifiers() & s_qt_modifier_mask) | ke->key(); +} + } // namespace QtUtils \ No newline at end of file diff --git a/src/duckstation-qt/qtutils.h b/src/duckstation-qt/qtutils.h index 7097810a2..645fde844 100644 --- a/src/duckstation-qt/qtutils.h +++ b/src/duckstation-qt/qtutils.h @@ -3,10 +3,15 @@ #include #include +class QKeyEvent; class QTableView; +class QWidget; namespace QtUtils { +/// Returns the greatest parent of a widget, i.e. its dialog/window. +QWidget* GetRootWidget(QWidget* widget, bool stop_at_window_or_dialog = true); + /// Resizes columns of the table view to at the specified widths. A width of -1 will stretch the column to use the /// remaining space. void ResizeColumnsForTableView(QTableView* view, const std::initializer_list& widths); @@ -17,4 +22,13 @@ QString GetKeyIdentifier(int key); /// Returns the integer Qt key ID for an identifier. std::optional GetKeyIdForIdentifier(const QString& key_identifier); +/// Stringizes a key event. +QString KeyEventToString(const QKeyEvent* ke); + +/// Returns an integer id for a stringized key event. Modifiers are in the upper bits. +std::optional ParseKeyString(const QString& key_str); + +/// Returns a key id for a key event, including any modifiers. +int KeyEventToInt(const QKeyEvent* ke); + } // namespace QtUtils \ No newline at end of file