Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
1 parent
f60dc81
commit 907ad02
Showing
4 changed files
with
275 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <QEvent> | ||
#include <QMouseEvent> | ||
#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<QChildEvent *>( event ); | ||
addChild( ce->child() ); | ||
break; | ||
} | ||
|
||
case QEvent::ChildRemoved: | ||
{ | ||
QChildEvent *ce = static_cast<QChildEvent *>( 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <QScrollArea> | ||
#include "qgis_gui.h" | ||
#include <QTimer> | ||
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 |
Oops, something went wrong.