Skip to content

Commit

Permalink
Fix scroll area child widgets wheel event lock lasts too long after
Browse files Browse the repository at this point in the history
scrolling stops in some circumstances

Basically, if the user moves the mouse after scrolling the area
with the wheel, we should release the time based lock we use
to avoid unwanted widget changes mid-scroll immediately. Otherwise
it's impossible to scroll wheel->move mouse onto widget->wheel to change
widget value quickly.
  • Loading branch information
nyalldawson committed Jan 18, 2022
1 parent 6c1fa05 commit f4b7a93
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 1 deletion.
1 change: 1 addition & 0 deletions python/gui/auto_generated/qgsscrollarea.sip.in
Expand Up @@ -47,6 +47,7 @@ Returns ``True`` if a scroll recently occurred within
the QScrollArea or its child :py:func:`~QgsScrollArea.viewport`
%End


void setVerticalOnly( bool verticalOnly );
%Docstring
Sets whether the scroll area only applies vertical.
Expand Down
30 changes: 29 additions & 1 deletion src/gui/qgsscrollarea.cpp
Expand Up @@ -17,6 +17,7 @@
#include <QMouseEvent>
#include "qgsscrollarea.h"
#include <QScrollBar>
#include "qgslogger.h"

// milliseconds to swallow child wheel events for after a scroll occurs
#define TIMEOUT 1000
Expand All @@ -26,6 +27,7 @@ QgsScrollArea::QgsScrollArea( QWidget *parent )
, mFilter( new ScrollAreaFilter( this, viewport() ) )
{
viewport()->installEventFilter( mFilter );
setMouseTracking( true );
}

void QgsScrollArea::wheelEvent( QWheelEvent *e )
Expand Down Expand Up @@ -53,6 +55,11 @@ bool QgsScrollArea::hasScrolled() const
return mTimer.isActive();
}

void QgsScrollArea::resetHasScrolled()
{
mTimer.stop();
}

void QgsScrollArea::setVerticalOnly( bool verticalOnly )
{
mVerticalOnly = verticalOnly;
Expand All @@ -69,7 +76,10 @@ ScrollAreaFilter::ScrollAreaFilter( QgsScrollArea *parent, QWidget *viewPort )
: QObject( parent )
, mScrollAreaWidget( parent )
, mViewPort( viewPort )
{}
{
QFontMetrics fm( parent->font() );
mMoveDistanceThreshold = fm.horizontalAdvance( 'X' );
}

bool ScrollAreaFilter::eventFilter( QObject *obj, QEvent *event )
{
Expand All @@ -90,6 +100,22 @@ bool ScrollAreaFilter::eventFilter( QObject *obj, QEvent *event )
break;
}

case QEvent::MouseMove:
{
if ( obj == mViewPort )
{
const QPoint mouseDelta = QCursor::pos() - mPreviousViewportCursorPos;
if ( mouseDelta.manhattanLength() > mMoveDistanceThreshold )
{
// release time based child widget constraint -- user moved the mouse over the viewport (and not just an accidental "wiggle")
// so we no longer are in the 'possible unwanted mouse wheel event going to child widget mid-scroll' state
mScrollAreaWidget->resetHasScrolled();
}
mPreviousViewportCursorPos = QCursor::pos();
}
break;
}

case QEvent::Wheel:
{
if ( obj == mViewPort )
Expand Down Expand Up @@ -119,6 +145,8 @@ void ScrollAreaFilter::addChild( QObject *child )
if ( child && child->isWidgetType() )
{
child->installEventFilter( this );
if ( QWidget *w = qobject_cast< QWidget * >( child ) )
w->setMouseTracking( true );

// also install filter on existing children
const auto constChildren = child->children();
Expand Down
9 changes: 9 additions & 0 deletions src/gui/qgsscrollarea.h
Expand Up @@ -61,6 +61,13 @@ class GUI_EXPORT QgsScrollArea : public QScrollArea
*/
bool hasScrolled() const;

/**
* Resets the hasScrolled() flag.
*
* \since QGIS 3.24
*/
void resetHasScrolled() SIP_SKIP;

/**
* Sets whether the scroll area only applies vertical.
*
Expand Down Expand Up @@ -105,6 +112,8 @@ class ScrollAreaFilter : public QObject
private:
QgsScrollArea *mScrollAreaWidget = nullptr;
QWidget *mViewPort = nullptr;
QPoint mPreviousViewportCursorPos;
int mMoveDistanceThreshold = 0;

void addChild( QObject *child );
void removeChild( QObject *child );
Expand Down

0 comments on commit f4b7a93

Please sign in to comment.