From fdb4a26ba7f24368804bf11518f39ae938a49bf2 Mon Sep 17 00:00:00 2001 From: Hartmnt Date: Thu, 4 Jan 2024 13:32:35 +0000 Subject: [PATCH] Experiemental slider improvement --- src/mumble/InlineWidgets.cpp | 26 ---------- src/mumble/InlineWidgets.h | 10 ---- src/mumble/ListenerVolumeSlider.cpp | 1 + src/mumble/MenuLabel.cpp | 23 ++++++++- src/mumble/UserLocalVolumeSlider.cpp | 2 + src/mumble/UserLocalVolumeSlider.h | 4 +- src/mumble/VolumeSliderWidgetAction.cpp | 67 +++++++++++++++++++----- src/mumble/VolumeSliderWidgetAction.h | 6 ++- src/mumble/widgets/EventFilters.cpp | 69 +++++++++++++++++++++++++ src/mumble/widgets/EventFilters.h | 36 +++++++++++++ 10 files changed, 191 insertions(+), 53 deletions(-) diff --git a/src/mumble/InlineWidgets.cpp b/src/mumble/InlineWidgets.cpp index 4105d5d8819..dd0cc336de6 100644 --- a/src/mumble/InlineWidgets.cpp +++ b/src/mumble/InlineWidgets.cpp @@ -9,32 +9,6 @@ #include #include -UpDownKeyEventFilter::UpDownKeyEventFilter(QObject *parent) : QObject(parent) { -} - -bool UpDownKeyEventFilter::eventFilter(QObject *obj, QEvent *event) { - // Converts up/down to tab/backtab - // Useful when overriding interactive QWidgetActions such as sliders - - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast< QKeyEvent * >(event); - - if (keyEvent->key() == Qt::Key_Up) { - QKeyEvent *keyPress = new QKeyEvent(QEvent::KeyPress, Qt::Key_Backtab, Qt::NoModifier); - QApplication::sendEvent(QApplication::focusWidget(), keyPress); - return true; - } - - if (keyEvent->key() == Qt::Key_Down) { - QKeyEvent *keyPress = new QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier); - QApplication::sendEvent(QApplication::focusWidget(), keyPress); - return true; - } - } - - return QObject::eventFilter(obj, event); -} - OverrideTabOrderFilter::OverrideTabOrderFilter(QObject *parent, QWidget *target) : QObject(parent), focusTarget(target) { } diff --git a/src/mumble/InlineWidgets.h b/src/mumble/InlineWidgets.h index 9819945bb0d..89c4e4eccb3 100644 --- a/src/mumble/InlineWidgets.h +++ b/src/mumble/InlineWidgets.h @@ -10,16 +10,6 @@ #include #include -class UpDownKeyEventFilter : public QObject { - Q_OBJECT - -public: - UpDownKeyEventFilter(QObject *parent); - -protected: - bool eventFilter(QObject *obj, QEvent *event) override; -}; - class OverrideTabOrderFilter : public QObject { Q_OBJECT diff --git a/src/mumble/ListenerVolumeSlider.cpp b/src/mumble/ListenerVolumeSlider.cpp index 6db5a8fdd3e..ede6c59aba5 100644 --- a/src/mumble/ListenerVolumeSlider.cpp +++ b/src/mumble/ListenerVolumeSlider.cpp @@ -40,6 +40,7 @@ void ListenerVolumeSlider::on_VolumeSlider_changeCompleted() { } VolumeAdjustment adjustment = VolumeAdjustment::fromDBAdjustment(m_volumeSlider->value()); + updateLabelValue(); if (handler->m_version >= Mumble::Protocol::PROTOBUF_INTRODUCTION_VERSION) { // With the new audio protocol, volume adjustments for listeners are handled on the server and thus we want diff --git a/src/mumble/MenuLabel.cpp b/src/mumble/MenuLabel.cpp index 1655a5f5156..271dba194b1 100644 --- a/src/mumble/MenuLabel.cpp +++ b/src/mumble/MenuLabel.cpp @@ -4,7 +4,9 @@ // Mumble source tree or at . #include "MenuLabel.h" +#include "widgets/EventFilters.h" +#include #include MenuLabel::MenuLabel(const QString &text, QObject *parent) : QWidgetAction(parent), m_text(text) { @@ -12,8 +14,25 @@ MenuLabel::MenuLabel(const QString &text, QObject *parent) : QWidgetAction(paren } QWidget *MenuLabel::createWidget(QWidget *parent) { + QWidget *widget = new QWidget(parent); + + // Not setting any focus is not an alternative here, as the + // QWidgetAction WILL get and automatically forward focus to the child. + // The widget needs tab focus policy, so it can process the event filters + // and also forward the focus. + widget->setFocusPolicy(Qt::TabFocus); + widget->installEventFilter(new SkipFocusEventFilter(widget)); + + // Using a widget and layout here instead of a plain label + // because otherwise screen readers might partially read the + // label text. + QGridLayout *layout = new QGridLayout(); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + QLabel *label = new QLabel(m_text, parent); - label->setFocusPolicy(Qt::TabFocus); + layout->addWidget(label, 0, 0); + widget->setLayout(layout); - return label; + return widget; } diff --git a/src/mumble/UserLocalVolumeSlider.cpp b/src/mumble/UserLocalVolumeSlider.cpp index ea3f23fa4a3..dcb4f880b12 100644 --- a/src/mumble/UserLocalVolumeSlider.cpp +++ b/src/mumble/UserLocalVolumeSlider.cpp @@ -43,5 +43,7 @@ void UserLocalVolumeSlider::on_VolumeSlider_changeCompleted() { } else { Global::get().mw->logChangeNotPermanent(QObject::tr("Local Volume Adjustment..."), user); } + + updateLabelValue(); } } diff --git a/src/mumble/UserLocalVolumeSlider.h b/src/mumble/UserLocalVolumeSlider.h index b79f574710b..707e75cf0c5 100644 --- a/src/mumble/UserLocalVolumeSlider.h +++ b/src/mumble/UserLocalVolumeSlider.h @@ -23,8 +23,8 @@ class UserLocalVolumeSlider : public VolumeSliderWidgetAction { void setUser(unsigned int sessionId); private slots: - void on_VolumeSlider_valueChanged(int value); - void on_VolumeSlider_changeCompleted(); + void on_VolumeSlider_valueChanged(int value) override; + void on_VolumeSlider_changeCompleted() override; }; #endif diff --git a/src/mumble/VolumeSliderWidgetAction.cpp b/src/mumble/VolumeSliderWidgetAction.cpp index 7553ae68b9f..6a7d7af4fc0 100644 --- a/src/mumble/VolumeSliderWidgetAction.cpp +++ b/src/mumble/VolumeSliderWidgetAction.cpp @@ -4,22 +4,27 @@ // Mumble source tree or at . #include "VolumeSliderWidgetAction.h" -#include "InlineWidgets.h" +#include "MumbleApplication.h" #include "VolumeAdjustment.h" #include "widgets/EventFilters.h" +#include +#include #include #include VolumeSliderWidgetAction::VolumeSliderWidgetAction(QWidget *parent) - : QWidgetAction(parent), m_volumeSlider(make_qt_unique< QSlider >(Qt::Horizontal, parent)) { + : QWidgetAction(parent), m_widget(make_qt_unique< QWidget >(parent)), + m_volumeSlider(new QSlider(Qt::Horizontal, parent)), m_label(new QLabel("0 db", parent)) { m_volumeSlider->setMinimum(-30); m_volumeSlider->setMaximum(30); - m_volumeSlider->setAccessibleName(tr("Slider for volume adjustment")); - m_volumeSlider->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + m_volumeSlider->setAccessibleName(tr("Local volume adjustment")); - KeyEventObserver *keyEventFilter = new KeyEventObserver(this, QEvent::KeyRelease, false, - { Qt::Key_Left, Qt::Key_Right, Qt::Key_Up, Qt::Key_Down }); + m_label->setStyleSheet("QLabel { margin-left: 0px; padding: 0px; }"); + m_volumeSlider->setStyleSheet("QSlider { margin-right: 0px; }"); + + KeyEventObserver *keyEventFilter = + new KeyEventObserver(this, QEvent::KeyRelease, false, { Qt::Key_Left, Qt::Key_Right }); m_volumeSlider->installEventFilter(keyEventFilter); // The list of wheel events observed seems odd at first. We have to check for multiple @@ -33,27 +38,65 @@ VolumeSliderWidgetAction::VolumeSliderWidgetAction(QWidget *parent) new MouseWheelEventObserver(this, { Qt::ScrollMomentum, Qt::ScrollUpdate, Qt::ScrollEnd }, false); m_volumeSlider->installEventFilter(wheelEventFilter); - connect(m_volumeSlider.get(), &QSlider::valueChanged, this, - &VolumeSliderWidgetAction::on_VolumeSlider_valueChanged); - connect(m_volumeSlider.get(), &QSlider::sliderReleased, this, - &VolumeSliderWidgetAction::on_VolumeSlider_changeCompleted); + + // Since we do not want the inline label to update when we drag the mouse, we + // must install a click event observer to update the label when the user just randomly + // clicks on the slider bar. + MouseClickEventObserver *mouseEventFilter = new MouseClickEventObserver(this, false); + m_volumeSlider->installEventFilter(mouseEventFilter); + connect(mouseEventFilter, &MouseClickEventObserver::clickEventObserved, this, [=]() { + m_volumeSlider->setFocus(Qt::TabFocusReason); + updateLabelValue(false); + }); + + // Also update the label explicitly when the slider body is released. + connect(m_volumeSlider, &QSlider::sliderReleased, this, [=]() { + m_volumeSlider->setFocus(Qt::TabFocusReason); + updateLabelValue(false); + }); + + connect(m_volumeSlider, &QSlider::valueChanged, this, &VolumeSliderWidgetAction::on_VolumeSlider_valueChanged); + + connect(m_volumeSlider, &QSlider::sliderReleased, this, &VolumeSliderWidgetAction::on_VolumeSlider_changeCompleted); connect(keyEventFilter, &KeyEventObserver::keyEventObserved, this, &VolumeSliderWidgetAction::on_VolumeSlider_changeCompleted); connect(wheelEventFilter, &MouseWheelEventObserver::wheelEventObserved, this, &VolumeSliderWidgetAction::on_VolumeSlider_changeCompleted); - setDefaultWidget(m_volumeSlider.get()); - UpDownKeyEventFilter *eventFilter = new UpDownKeyEventFilter(this); m_volumeSlider->installEventFilter(eventFilter); + // Used to display the drag tooltip at the mouse position m_volumeSlider->setProperty("mouseTracking", true); + + QHBoxLayout *layout = new QHBoxLayout(); + layout->addWidget(m_volumeSlider); + layout->addWidget(m_label); + layout->setContentsMargins(0, 0, -1, 0); + layout->setSpacing(3); + m_widget->setLayout(layout); + + m_widget->setFocusProxy(m_volumeSlider); + m_widget->setFocusPolicy(Qt::TabFocus); + + setDefaultWidget(m_widget.get()); +} + +void VolumeSliderWidgetAction::updateLabelValue(bool checkMouseButtons) { + if (checkMouseButtons && MumbleApplication::instance()->mouseButtons() != Qt::NoButton) { + // Do not update the label while the user is dragging the slider. + // This will otherwise cause a glitchy experience. + return; + } + + m_label->setText(QString("%01dB").arg(m_volumeSlider->value())); } void VolumeSliderWidgetAction::updateSliderValue(float value) { int dbShift = VolumeAdjustment::toIntegerDBAdjustment(value); m_volumeSlider->setValue(dbShift); updateTooltip(dbShift); + updateLabelValue(false); } void VolumeSliderWidgetAction::updateTooltip(int value) { diff --git a/src/mumble/VolumeSliderWidgetAction.h b/src/mumble/VolumeSliderWidgetAction.h index 25bb408670f..a008eb1da8f 100644 --- a/src/mumble/VolumeSliderWidgetAction.h +++ b/src/mumble/VolumeSliderWidgetAction.h @@ -11,6 +11,7 @@ #include "QtUtils.h" class QSlider; +class QLabel; class VolumeSliderWidgetAction : public QWidgetAction { Q_OBJECT @@ -19,8 +20,11 @@ class VolumeSliderWidgetAction : public QWidgetAction { VolumeSliderWidgetAction(QWidget *parent = nullptr); protected: - qt_unique_ptr< QSlider > m_volumeSlider; + qt_unique_ptr< QWidget > m_widget; + QSlider *m_volumeSlider; + QLabel *m_label; + void updateLabelValue(bool checkMouseButtons = true); void updateSliderValue(float value); void displayTooltip(int value); void updateTooltip(int value); diff --git a/src/mumble/widgets/EventFilters.cpp b/src/mumble/widgets/EventFilters.cpp index c194ac957c9..f0d5021b3ac 100644 --- a/src/mumble/widgets/EventFilters.cpp +++ b/src/mumble/widgets/EventFilters.cpp @@ -7,7 +7,9 @@ #include +#include #include +#include #include #include @@ -66,3 +68,70 @@ bool MouseWheelEventObserver::eventFilter(QObject *obj, QEvent *event) { return m_consume; } + +MouseClickEventObserver::MouseClickEventObserver(QObject *parent, bool consume) : QObject(parent), m_consume(consume) { +} + +bool MouseClickEventObserver::eventFilter(QObject *obj, QEvent *event) { + if (event->type() == QEvent::MouseButtonRelease) { + QMouseEvent *mouseEvent = static_cast< QMouseEvent * >(event); + + emit clickEventObserved(mouseEvent->buttons()); + + return m_consume; + } + + return QObject::eventFilter(obj, event); +} + +UpDownKeyEventFilter::UpDownKeyEventFilter(QObject *parent) : QObject(parent) { +} + +bool UpDownKeyEventFilter::eventFilter(QObject *obj, QEvent *event) { + // Converts up/down to tab/backtab + // Useful when overriding interactive QWidgetActions such as sliders + + if (event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast< QKeyEvent * >(event); + + if (keyEvent->key() == Qt::Key_Up) { + QKeyEvent *keyPress = new QKeyEvent(QEvent::KeyPress, Qt::Key_Backtab, Qt::NoModifier); + QApplication::sendEvent(QApplication::focusWidget(), keyPress); + return true; + } + + if (keyEvent->key() == Qt::Key_Down) { + QKeyEvent *keyPress = new QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier); + QApplication::sendEvent(QApplication::focusWidget(), keyPress); + return true; + } + } + + return QObject::eventFilter(obj, event); +} + +SkipFocusEventFilter::SkipFocusEventFilter(QObject *parent) : QObject(parent) { +} + +bool SkipFocusEventFilter::eventFilter(QObject *obj, QEvent *event) { + // Detecting FocusIn events is glitchy, therefore we detect key release events + // and forward the focus to the next/previous element. + + if (event->type() == QEvent::KeyRelease && QApplication::focusWidget() == obj) { + QKeyEvent *keyEvent = static_cast< QKeyEvent * >(event); + + if (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Backtab) { + QKeyEvent *keyPress = new QKeyEvent(QEvent::KeyPress, Qt::Key_Backtab, Qt::NoModifier); + QApplication::sendEvent(QApplication::focusWidget(), keyPress); + return true; + } + + if (keyEvent->key() == Qt::Key_Down || keyEvent->key() == Qt::Key_Tab) { + QKeyEvent *keyPress = new QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier); + QApplication::sendEvent(QApplication::focusWidget(), keyPress); + return true; + } + } + + return QObject::eventFilter(obj, event); +} diff --git a/src/mumble/widgets/EventFilters.h b/src/mumble/widgets/EventFilters.h index ef1aa7076ae..fc39e8a90a6 100644 --- a/src/mumble/widgets/EventFilters.h +++ b/src/mumble/widgets/EventFilters.h @@ -47,4 +47,40 @@ class MouseWheelEventObserver : public QObject { bool m_consume; }; +class MouseClickEventObserver : public QObject { + Q_OBJECT + +public: + MouseClickEventObserver(QObject *parent, bool consume); + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; + +signals: + void clickEventObserved(Qt::MouseButtons buttons); + +private: + bool m_consume; +}; + +class UpDownKeyEventFilter : public QObject { + Q_OBJECT + +public: + UpDownKeyEventFilter(QObject *parent); + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; +}; + +class SkipFocusEventFilter : public QObject { + Q_OBJECT + +public: + SkipFocusEventFilter(QObject *parent); + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; +}; + #endif