QGIS API Documentation 3.43.0-Master (a93bf8b6462)
qgsscrollarea.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsscrollarea.cpp
3 -----------------
4 begin : March 2017
5 copyright : (C) 2017 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgsscrollarea.h"
17#include "moc_qgsscrollarea.cpp"
18
19#include <QEvent>
20#include <QMouseEvent>
21#include <QScrollBar>
22#include <QAbstractItemView>
23#include <QComboBox>
24#include <QAbstractSpinBox>
25#include <QAbstractButton>
26#include <QAbstractSlider>
27#include <QDateTimeEdit>
28
29// milliseconds to swallow child wheel events for after a scroll occurs
30constexpr qint64 TIMEOUT = 1000;
31
33 : QScrollArea( parent )
34 , mFilter( new ScrollAreaFilter( this, viewport() ) )
35{
36 viewport()->installEventFilter( mFilter );
37 setMouseTracking( true );
38}
39
40void QgsScrollArea::wheelEvent( QWheelEvent *e )
41{
42 //scroll occurred, reset timer
44 QScrollArea::wheelEvent( e );
45}
46
47void QgsScrollArea::resizeEvent( QResizeEvent *event )
48{
49 if ( mVerticalOnly && widget() )
50 widget()->setFixedWidth( event->size().width() );
51 QScrollArea::resizeEvent( event );
52}
53
55{
56 mTimer.restart();
57 mTimerActive = true;
58}
59
61{
62 return mTimerActive && mTimer.elapsed() < TIMEOUT;
63}
64
66{
67 mTimerActive = false;
68}
69
70void QgsScrollArea::setVerticalOnly( bool verticalOnly )
71{
72 mVerticalOnly = verticalOnly;
73 if ( mVerticalOnly )
74 setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
75
76 if ( mVerticalOnly && widget() )
77 widget()->setFixedWidth( size().width() );
78}
79
81
82ScrollAreaFilter::ScrollAreaFilter( QgsScrollArea *parent, QWidget *viewPort )
83 : QObject( parent )
84 , mScrollAreaWidget( parent )
85 , mViewPort( viewPort )
86{
87 QFontMetrics fm( parent->font() );
88 mMoveDistanceThreshold = fm.horizontalAdvance( 'X' );
89}
90
91bool ScrollAreaFilter::eventFilter( QObject *obj, QEvent *event )
92{
93 switch ( event->type() )
94 {
95 case QEvent::ChildAdded:
96 {
97 // need to install filter on all child widgets as well
98 QChildEvent *ce = static_cast<QChildEvent *>( event );
99 addChild( ce->child() );
100 break;
101 }
102
103 case QEvent::ChildRemoved:
104 {
105 QChildEvent *ce = static_cast<QChildEvent *>( event );
106 removeChild( ce->child() );
107 break;
108 }
109
110 case QEvent::MouseMove:
111 {
112 if ( obj == mViewPort )
113 {
114 const QPoint mouseDelta = QCursor::pos() - mPreviousViewportCursorPos;
115 if ( mouseDelta.manhattanLength() > mMoveDistanceThreshold )
116 {
117 // release time based child widget constraint -- user moved the mouse over the viewport (and not just an accidental "wiggle")
118 // so we no longer are in the 'possible unwanted mouse wheel event going to child widget mid-scroll' state
119 mScrollAreaWidget->resetHasScrolled();
120 }
121 mPreviousViewportCursorPos = QCursor::pos();
122 }
123 break;
124 }
125
126 case QEvent::Wheel:
127 {
128 if ( obj == mViewPort )
129 {
130 // scrolling scroll area - kick off the timer to block wheel events in children
131 mScrollAreaWidget->scrollOccurred();
132 }
133 else if ( qobject_cast< QComboBox * >( obj )
134 || qobject_cast< QAbstractSpinBox * >( obj )
135 || qobject_cast< QAbstractButton *>( obj )
136 || qobject_cast< QAbstractSlider *>( obj )
137 || qobject_cast< QDateTimeEdit * >( obj ) )
138 {
139 if ( mScrollAreaWidget->hasScrolled() )
140 {
141 // swallow wheel events for children shortly after scroll occurs
142 return true;
143 }
144 }
145 break;
146 }
147
148 default:
149 break;
150 }
151 return QObject::eventFilter( obj, event );
152}
153
154void ScrollAreaFilter::addChild( QObject *child )
155{
156 if ( child && child->isWidgetType() )
157 {
158 if ( qobject_cast<QScrollArea *>( child ) || qobject_cast<QAbstractItemView *>( child ) )
159 return;
160
161 child->installEventFilter( this );
162 if ( QWidget *w = qobject_cast<QWidget *>( child ) )
163 w->setMouseTracking( true );
164
165 // also install filter on existing children
166 const auto constChildren = child->children();
167 for ( QObject *c : constChildren )
168 {
169 addChild( c );
170 }
171 }
172}
173
174void ScrollAreaFilter::removeChild( QObject *child )
175{
176 if ( child && child->isWidgetType() )
177 {
178 if ( qobject_cast<QScrollArea *>( child ) || qobject_cast<QAbstractItemView *>( child ) )
179 return;
180
181 child->removeEventFilter( this );
182
183 // also remove filter on existing children
184 const auto constChildren = child->children();
185 for ( QObject *c : constChildren )
186 {
187 removeChild( c );
188 }
189 }
190}
191
A QScrollArea subclass with improved scrolling behavior.
void setVerticalOnly(bool verticalOnly)
Sets whether the scroll area only applies vertical.
QgsScrollArea(QWidget *parent=nullptr)
Constructor for QgsScrollArea.
void resetHasScrolled()
Resets the hasScrolled() flag.
bool hasScrolled() const
Returns true if a scroll recently occurred within the QScrollArea or its child viewport()
void wheelEvent(QWheelEvent *event) override
void scrollOccurred()
Should be called when a scroll occurs on with the QScrollArea itself or its child viewport().
void resizeEvent(QResizeEvent *event) override
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
constexpr qint64 TIMEOUT