From 907ad02af87d180de3e7758718b4d19dac1393b6 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 17 Mar 2017 16:01:31 +1000 Subject: [PATCH] Avoid accidental child widget value changes when scrolling scroll areas This adds a new QgsScrollArea widget which is a subclass of QScrollArea. QgsScrollArea has extra logic which temporarily blocks wheel events from hitting child widgets for a short period following a scroll of the area. This means that when scrolling a scroll area using the mouse wheel, values won't be accidentally changed if the mouse cursor momentarily lands on top of a widget. QScrollArea should no longer be used in any QGIS code or plugins, instead use QgsScrollArea to benefit from this fix. --- src/gui/CMakeLists.txt | 2 + src/gui/qgsscrollarea.cpp | 127 ++++++++++++++++++++++++++++++++++++++ src/gui/qgsscrollarea.h | 100 ++++++++++++++++++++++++++++++ src/ui/qgsoptionsbase.ui | 86 ++++++++++++++------------ 4 files changed, 275 insertions(+), 40 deletions(-) mode change 100644 => 100755 src/gui/CMakeLists.txt create mode 100644 src/gui/qgsscrollarea.cpp create mode 100644 src/gui/qgsscrollarea.h mode change 100644 => 100755 src/ui/qgsoptionsbase.ui diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt old mode 100644 new mode 100755 index 6d150bf8573..a5e60cc5541 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -296,6 +296,7 @@ SET(QGIS_GUI_SRCS qgsscalerangewidget.cpp qgsscalevisibilitydialog.cpp qgsscalewidget.cpp + qgsscrollarea.cpp qgssearchquerybuilder.cpp qgsshortcutsmanager.cpp qgsslider.cpp @@ -442,6 +443,7 @@ SET(QGIS_GUI_MOC_HDRS qgsscalerangewidget.h qgsscalevisibilitydialog.h qgsscalewidget.h + qgsscrollarea.h qgssearchquerybuilder.h qgsshortcutsmanager.h qgsslider.h diff --git a/src/gui/qgsscrollarea.cpp b/src/gui/qgsscrollarea.cpp new file mode 100644 index 00000000000..4e80d10656c --- /dev/null +++ b/src/gui/qgsscrollarea.cpp @@ -0,0 +1,127 @@ +/*************************************************************************** + qgsscrollarea.cpp + ----------------- + begin : March 2017 + copyright : (C) 2017 by Nyall Dawson + email : nyall dot dawson at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include +#include +#include "qgsscrollarea.h" + +// milliseconds to swallow child wheel events for after a scroll occurs +#define TIMEOUT 1000 + +QgsScrollArea::QgsScrollArea( QWidget *parent ) + : QScrollArea( parent ) + , mFilter( new ScrollAreaFilter( this, viewport() ) ) +{ + viewport()->installEventFilter( mFilter ); +} + +void QgsScrollArea::wheelEvent( QWheelEvent *e ) +{ + //scroll occurred, reset timer + scrollOccurred(); + QScrollArea::wheelEvent( e ); +} + +void QgsScrollArea::scrollOccurred() +{ + mTimer.setSingleShot( true ); + mTimer.start( TIMEOUT ); +} + +bool QgsScrollArea::hasScrolled() const +{ + return mTimer.isActive(); +} + +///@cond PRIVATE + +ScrollAreaFilter::ScrollAreaFilter( QgsScrollArea *parent, QWidget *viewPort ) + : QObject( parent ) + , mScrollAreaWidget( parent ) + , mViewPort( viewPort ) +{} + +bool ScrollAreaFilter::eventFilter( QObject *obj, QEvent *event ) +{ + switch ( event->type() ) + { + case QEvent::ChildAdded: + { + // need to install filter on all child widgets as well + QChildEvent *ce = static_cast( event ); + addChild( ce->child() ); + break; + } + + case QEvent::ChildRemoved: + { + QChildEvent *ce = static_cast( event ); + removeChild( ce->child() ); + break; + } + + case QEvent::Wheel: + { + if ( obj == mViewPort ) + { + // scrolling scroll area - kick off the timer to block wheel events in children + mScrollAreaWidget->scrollOccurred(); + } + else + { + if ( mScrollAreaWidget->hasScrolled() ) + { + // swallow wheel events for children shortly after scroll occurs + return true; + } + } + break; + } + + default: + break; + } + return QObject::eventFilter( obj, event ); +} + +void ScrollAreaFilter::addChild( QObject *child ) +{ + if ( child && child->isWidgetType() ) + { + child->installEventFilter( this ); + + // also install filter on existing children + Q_FOREACH ( QObject *c, child->children() ) + { + addChild( c ); + } + } +} + +void ScrollAreaFilter::removeChild( QObject *child ) +{ + if ( child && child->isWidgetType() ) + { + child->removeEventFilter( this ); + + // also remove filter on existing children + Q_FOREACH ( QObject *c, child->children() ) + { + removeChild( c ); + } + } +} + +///@endcond PRIVATE diff --git a/src/gui/qgsscrollarea.h b/src/gui/qgsscrollarea.h new file mode 100644 index 00000000000..603881b1b06 --- /dev/null +++ b/src/gui/qgsscrollarea.h @@ -0,0 +1,100 @@ +/*************************************************************************** + qgsscrollarea.h + --------------- + begin : March 2017 + copyright : (C) 2017 by Nyall Dawson + email : nyall dot dawson at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSSCROLLAREA_H +#define QGSSCROLLAREA_H + +#include +#include "qgis_gui.h" +#include +class ScrollAreaFilter; + +/** + * \class QgsScrollArea + * \ingroup gui + * A QScrollArea subclass with improved scrolling behavior. + * + * QgsScrollArea should be used instead of QScrollArea widgets. + * In most cases the use is identical, however QgsScrollArea + * has extra logic to avoid wheel events changing child widget + * values when the mouse cursor is temporarily located over + * a child widget during a scroll event. + * + * All QGIS code and plugins should use QgsScrollArea in place + * of QScrollArea. + * + * \note added in QGIS 3.0 + */ +class GUI_EXPORT QgsScrollArea : public QScrollArea +{ + Q_OBJECT + + public: + + /** + * Constructor for QgsScrollArea. + */ + explicit QgsScrollArea( QWidget *parent = nullptr ); + + /** + * Should be called when a scroll occurs on with the + * QScrollArea itself or its child viewport(). + */ + void scrollOccurred(); + + /** + * Returns true if a scroll recently occurred within + * the QScrollArea or its child viewport() + */ + bool hasScrolled() const; + + protected: + void wheelEvent( QWheelEvent *event ) override; + + private: + QTimer mTimer; + ScrollAreaFilter *mFilter = nullptr; +}; + +///@cond PRIVATE + +/** + * \class ScrollAreaFilter + * Swallows wheel events for QScrollArea children for a short period + * following a scroll. + */ +class ScrollAreaFilter : public QObject +{ + Q_OBJECT + public: + + ScrollAreaFilter( QgsScrollArea *parent = nullptr, + QWidget *viewPort = nullptr ); + + protected: + bool eventFilter( QObject *obj, QEvent *event ) override; + + private: + QgsScrollArea *mScrollAreaWidget = nullptr; + QWidget *mViewPort = nullptr; + + void addChild( QObject *child ); + void removeChild( QObject *child ); + +}; + +///@endcond PRIVATE + +#endif // QGSSCROLLAREA_H diff --git a/src/ui/qgsoptionsbase.ui b/src/ui/qgsoptionsbase.ui old mode 100644 new mode 100755 index 95cba7b878f..0a9dd543ca6 --- a/src/ui/qgsoptionsbase.ui +++ b/src/ui/qgsoptionsbase.ui @@ -311,7 +311,7 @@ - 13 + 2 @@ -328,7 +328,7 @@ 0 - + QFrame::NoFrame @@ -340,8 +340,8 @@ 0 0 - 857 - 680 + 856 + 679 @@ -1014,7 +1014,7 @@ 0 - + QFrame::NoFrame @@ -1026,8 +1026,8 @@ 0 0 - 541 - 1065 + 839 + 1009 @@ -1544,7 +1544,7 @@ 0 - + QFrame::NoFrame @@ -1556,8 +1556,8 @@ 0 0 - 495 - 685 + 856 + 679 @@ -1912,7 +1912,7 @@ 0 - + QFrame::NoFrame @@ -1924,8 +1924,8 @@ 0 0 - 674 - 936 + 839 + 827 @@ -2663,7 +2663,7 @@ 0 - + QFrame::NoFrame @@ -2675,8 +2675,8 @@ 0 0 - 142 - 239 + 112 + 219 @@ -2814,7 +2814,7 @@ 0 - + QFrame::NoFrame @@ -2826,8 +2826,8 @@ 0 0 - 501 - 316 + 453 + 281 @@ -3157,7 +3157,7 @@ 0 - + QFrame::NoFrame @@ -3169,8 +3169,8 @@ 0 0 - 604 - 589 + 506 + 533 @@ -3601,7 +3601,7 @@ The bigger the number, the faster zooming with the mouse wheel will be. 0 - + QFrame::NoFrame @@ -3613,8 +3613,8 @@ The bigger the number, the faster zooming with the mouse wheel will be. 0 0 - 480 - 571 + 393 + 530 @@ -3870,7 +3870,7 @@ The bigger the number, the faster zooming with the mouse wheel will be. 0 - + QFrame::NoFrame @@ -3882,8 +3882,8 @@ The bigger the number, the faster zooming with the mouse wheel will be. 0 0 - 543 - 696 + 488 + 612 @@ -4448,7 +4448,7 @@ The bigger the number, the faster zooming with the mouse wheel will be. 0 - + QFrame::NoFrame @@ -4460,8 +4460,8 @@ The bigger the number, the faster zooming with the mouse wheel will be. 0 0 - 412 - 368 + 345 + 350 @@ -4587,7 +4587,7 @@ The bigger the number, the faster zooming with the mouse wheel will be. 0 - + QFrame::NoFrame @@ -4599,8 +4599,8 @@ The bigger the number, the faster zooming with the mouse wheel will be. 0 0 - 408 - 531 + 332 + 499 @@ -4800,7 +4800,7 @@ The bigger the number, the faster zooming with the mouse wheel will be. 0 - + QFrame::NoFrame @@ -4812,8 +4812,8 @@ The bigger the number, the faster zooming with the mouse wheel will be. 0 0 - 273 - 236 + 221 + 201 @@ -4909,7 +4909,7 @@ The bigger the number, the faster zooming with the mouse wheel will be. 0 - + QFrame::NoFrame @@ -4921,8 +4921,8 @@ The bigger the number, the faster zooming with the mouse wheel will be. 0 0 - 843 - 689 + 856 + 679 @@ -5435,6 +5435,12 @@ The bigger the number, the faster zooming with the mouse wheel will be. + + QgsScrollArea + QScrollArea +
qgsscrollarea.h
+ 1 +
QgsPasswordLineEdit QLineEdit