Qt: Properly handle modifier keys for input

This commit is contained in:
Connor McLaughlin 2020-01-06 15:14:47 +10:00
parent 6d5eca13a6
commit 87889a13e0
6 changed files with 157 additions and 24 deletions

View file

@ -15,34 +15,50 @@ InputButtonBindingWidget::InputButtonBindingWidget(QtHostInterface* host_interfa
connect(this, &QPushButton::pressed, this, &InputButtonBindingWidget::onPressed); connect(this, &QPushButton::pressed, this, &InputButtonBindingWidget::onPressed);
} }
InputButtonBindingWidget::~InputButtonBindingWidget() = default; InputButtonBindingWidget::~InputButtonBindingWidget()
void InputButtonBindingWidget::keyPressEvent(QKeyEvent* event)
{ {
// ignore the key press if we're listening for input
if (isListeningForInput()) if (isListeningForInput())
return; stopListeningForInput();
QPushButton::keyPressEvent(event);
} }
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<QKeyEvent*>(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; return;
}
QString key_name = QtUtils::GetKeyIdentifier(event->key()); m_host_interface->getQSettings().setValue(m_setting_name, m_new_binding_value);
if (!key_name.isEmpty()) m_host_interface->updateInputMap();
{
// 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);
}
stopListeningForInput(); m_current_binding_value = std::move(m_new_binding_value);
m_new_binding_value.clear();
} }
void InputButtonBindingWidget::onPressed() void InputButtonBindingWidget::onPressed()
@ -75,6 +91,10 @@ void InputButtonBindingWidget::startListeningForInput()
&InputButtonBindingWidget::onInputListenTimerTimeout); &InputButtonBindingWidget::onInputListenTimerTimeout);
m_input_listen_remaining_seconds = 5; m_input_listen_remaining_seconds = 5;
setText(tr("Push Button... [%1]").arg(m_input_listen_remaining_seconds)); setText(tr("Push Button... [%1]").arg(m_input_listen_remaining_seconds));
installEventFilter(this);
grabKeyboard();
grabMouse();
} }
void InputButtonBindingWidget::stopListeningForInput() void InputButtonBindingWidget::stopListeningForInput()
@ -82,4 +102,8 @@ void InputButtonBindingWidget::stopListeningForInput()
setText(m_current_binding_value); setText(m_current_binding_value);
delete m_input_listen_timer; delete m_input_listen_timer;
m_input_listen_timer = nullptr; m_input_listen_timer = nullptr;
releaseMouse();
releaseKeyboard();
removeEventFilter(this);
} }

View file

@ -15,8 +15,7 @@ public:
~InputButtonBindingWidget(); ~InputButtonBindingWidget();
protected: protected:
void keyPressEvent(QKeyEvent* event) override; bool eventFilter(QObject* watched, QEvent* event) override;
void keyReleaseEvent(QKeyEvent* event) override;
private Q_SLOTS: private Q_SLOTS:
void onPressed(); void onPressed();
@ -26,10 +25,12 @@ private:
bool isListeningForInput() const { return m_input_listen_timer != nullptr; } bool isListeningForInput() const { return m_input_listen_timer != nullptr; }
void startListeningForInput(); void startListeningForInput();
void stopListeningForInput(); void stopListeningForInput();
void setNewBinding();
QtHostInterface* m_host_interface; QtHostInterface* m_host_interface;
QString m_setting_name; QString m_setting_name;
QString m_current_binding_value; QString m_current_binding_value;
QString m_new_binding_value;
QTimer* m_input_listen_timer = nullptr; QTimer* m_input_listen_timer = nullptr;
u32 m_input_listen_remaining_seconds = 0; u32 m_input_listen_remaining_seconds = 0;
}; };

View file

@ -1,5 +1,6 @@
#include "qtdisplaywindow.h" #include "qtdisplaywindow.h"
#include "imgui.h" #include "imgui.h"
#include "qtutils.h"
#include "qthostinterface.h" #include "qthostinterface.h"
#include <QtGui/QKeyEvent> #include <QtGui/QKeyEvent>
@ -75,7 +76,7 @@ void QtDisplayWindow::keyPressEvent(QKeyEvent* event)
if (event->isAutoRepeat()) if (event->isAutoRepeat())
return; return;
m_host_interface->handleKeyEvent(event->key(), true); m_host_interface->handleKeyEvent(QtUtils::KeyEventToInt(event), true);
} }
void QtDisplayWindow::keyReleaseEvent(QKeyEvent* event) void QtDisplayWindow::keyReleaseEvent(QKeyEvent* event)
@ -83,7 +84,7 @@ void QtDisplayWindow::keyReleaseEvent(QKeyEvent* event)
if (event->isAutoRepeat()) if (event->isAutoRepeat())
return; return;
m_host_interface->handleKeyEvent(event->key(), false); m_host_interface->handleKeyEvent(QtUtils::KeyEventToInt(event), false);
} }
void QtDisplayWindow::resizeEvent(QResizeEvent* event) void QtDisplayWindow::resizeEvent(QResizeEvent* event)

View file

@ -297,7 +297,7 @@ void QtHostInterface::addButtonToInputMap(const QString& binding, InputButtonHan
const QString button = binding.section('/', 1, 1); const QString button = binding.section('/', 1, 1);
if (device == QStringLiteral("Keyboard")) if (device == QStringLiteral("Keyboard"))
{ {
std::optional<int> key_id = QtUtils::GetKeyIdForIdentifier(button); std::optional<int> key_id = QtUtils::ParseKeyString(button);
if (!key_id.has_value()) if (!key_id.has_value())
{ {
qWarning() << "Unknown keyboard key " << button; qWarning() << "Unknown keyboard key " << button;

View file

@ -1,10 +1,32 @@
#include "qtutils.h" #include "qtutils.h"
#include <QtGui/QKeyEvent>
#include <QtWidgets/QDialog>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QTableView> #include <QtWidgets/QTableView>
#include <algorithm> #include <algorithm>
#include <array>
#include <map> #include <map>
namespace QtUtils { 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<int>& widths) void ResizeColumnsForTableView(QTableView* view, const std::initializer_list<int>& widths)
{ {
const int total_width = const int total_width =
@ -461,6 +483,22 @@ static const std::map<int, QString> s_qt_key_names = {
{Qt::Key_Camera, QStringLiteral("Camera")}, {Qt::Key_Camera, QStringLiteral("Camera")},
{Qt::Key_CameraFocus, QStringLiteral("CameraFocus")}}; {Qt::Key_CameraFocus, QStringLiteral("CameraFocus")}};
struct QtKeyModifierEntry
{
Qt::KeyboardModifier mod;
Qt::Key key;
QString name;
};
static const std::array<QtKeyModifierEntry, 5> 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<Qt::Key>(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) QString GetKeyIdentifier(int key)
{ {
const auto it = s_qt_key_names.find(key); const auto it = s_qt_key_names.find(key);
@ -478,4 +516,59 @@ std::optional<int> GetKeyIdForIdentifier(const QString& key_identifier)
return std::nullopt; 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<int> ParseKeyString(const QString& key_str)
{
const QStringList sections = key_str.split('+');
std::optional<int> 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<int>(mod.mod);
break;
}
}
}
}
return ret;
}
int KeyEventToInt(const QKeyEvent* ke)
{
return static_cast<int>(ke->modifiers() & s_qt_modifier_mask) | ke->key();
}
} // namespace QtUtils } // namespace QtUtils

View file

@ -3,10 +3,15 @@
#include <initializer_list> #include <initializer_list>
#include <optional> #include <optional>
class QKeyEvent;
class QTableView; class QTableView;
class QWidget;
namespace QtUtils { 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 /// Resizes columns of the table view to at the specified widths. A width of -1 will stretch the column to use the
/// remaining space. /// remaining space.
void ResizeColumnsForTableView(QTableView* view, const std::initializer_list<int>& widths); void ResizeColumnsForTableView(QTableView* view, const std::initializer_list<int>& widths);
@ -17,4 +22,13 @@ QString GetKeyIdentifier(int key);
/// Returns the integer Qt key ID for an identifier. /// Returns the integer Qt key ID for an identifier.
std::optional<int> GetKeyIdForIdentifier(const QString& key_identifier); std::optional<int> 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<int> ParseKeyString(const QString& key_str);
/// Returns a key id for a key event, including any modifiers.
int KeyEventToInt(const QKeyEvent* ke);
} // namespace QtUtils } // namespace QtUtils