QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
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
18#include <QAbstractButton>
19#include <QAbstractItemView>
20#include <QAbstractSlider>
21#include <QAbstractSpinBox>
22#include <QComboBox>
23#include <QDateTimeEdit>
24#include <QEvent>
25#include <QMouseEvent>
26#include <QScrollBar>
27
28#include "moc_qgsscrollarea.cpp"
29
30// milliseconds to swallow child wheel events for after a scroll occurs
31constexpr qint64 TIMEOUT = 1000;
32
34 : QScrollArea( parent )
35 , mFilter( new ScrollAreaFilter( this, viewport() ) )
36{
37 viewport()->installEventFilter( mFilter );
38 setMouseTracking( true );
39}
40
41void QgsScrollArea::wheelEvent( QWheelEvent *e )
42{
43 //scroll occurred, reset timer
45 QScrollArea::wheelEvent( e );
46}
47
48void QgsScrollArea::resizeEvent( QResizeEvent *event )
49{
50 if ( mVerticalOnly && widget() )
51 widget()->setFixedWidth( event->size().width() );
52 QScrollArea::resizeEvent( event );
53}
54
56{
57 mTimer.restart();
58 mTimerActive = true;
59}
60
62{
63 return mTimerActive && mTimer.elapsed() < TIMEOUT;
64}
65
67{
68 mTimerActive = false;
69}
70
71void QgsScrollArea::setVerticalOnly( bool verticalOnly )
72{
73 mVerticalOnly = verticalOnly;
74 if ( mVerticalOnly )
75 setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
76
77 if ( mVerticalOnly && widget() )
78 widget()->setFixedWidth( size().width() );
79}
80
82
83ScrollAreaFilter::ScrollAreaFilter( QgsScrollArea *parent, QWidget *viewPort )
84 : QObject( parent )
85 , mScrollAreaWidget( parent )
86 , mViewPort( viewPort )
87{
88 QFontMetrics fm( parent->font() );
89 mMoveDistanceThreshold = fm.horizontalAdvance( 'X' );
90}
91
92bool ScrollAreaFilter::eventFilter( QObject *obj, QEvent *event )
93{
94 switch ( event->type() )
95 {
96 case QEvent::ChildAdded:
97 {
98 // need to install filter on all child widgets as well
99 QChildEvent *ce = static_cast<QChildEvent *>( event );
100 addChild( ce->child() );
101 break;
102 }
103
104 case QEvent::ChildRemoved:
105 {
106 QChildEvent *ce = static_cast<QChildEvent *>( event );
107 removeChild( ce->child() );
108 break;
109 }
110
111 case QEvent::MouseMove:
112 {
113 if ( obj == mViewPort )
114 {
115 const QPoint mouseDelta = QCursor::pos() - mPreviousViewportCursorPos;
116 if ( mouseDelta.manhattanLength() > mMoveDistanceThreshold )
117 {
118 // release time based child widget constraint -- user moved the mouse over the viewport (and not just an accidental "wiggle")
119 // so we no longer are in the 'possible unwanted mouse wheel event going to child widget mid-scroll' state
120 mScrollAreaWidget->resetHasScrolled();
121 }
122 mPreviousViewportCursorPos = QCursor::pos();
123 }
124 break;
125 }
126
127 case QEvent::Wheel:
128 {
129 if ( obj == mViewPort )
130 {
131 // scrolling scroll area - kick off the timer to block wheel events in children
132 mScrollAreaWidget->scrollOccurred();
133 }
134 else if ( qobject_cast< QComboBox * >( obj )
135 || qobject_cast< QAbstractSpinBox * >( obj )
136 || qobject_cast< QAbstractButton *>( obj )
137 || qobject_cast< QAbstractSlider *>( obj )
138 || qobject_cast< QDateTimeEdit * >( obj ) )
139 {
140 if ( mScrollAreaWidget->hasScrolled() )
141 {
142 // swallow wheel events for children shortly after scroll occurs
143 return true;
144 }
145 }
146 break;
147 }
148
149 default:
150 break;
151 }
152 return QObject::eventFilter( obj, event );
153}
154
155void ScrollAreaFilter::addChild( QObject *child )
156{
157 if ( child && child->isWidgetType() )
158 {
159 if ( qobject_cast<QScrollArea *>( child ) || qobject_cast<QAbstractItemView *>( child ) )
160 return;
161
162 child->installEventFilter( this );
163 if ( QWidget *w = qobject_cast<QWidget *>( child ) )
164 w->setMouseTracking( true );
165
166 // also install filter on existing children
167 const auto constChildren = child->children();
168 for ( QObject *c : constChildren )
169 {
170 addChild( c );
171 }
172 }
173}
174
175void ScrollAreaFilter::removeChild( QObject *child )
176{
177 if ( child && child->isWidgetType() )
178 {
179 if ( qobject_cast<QScrollArea *>( child ) || qobject_cast<QAbstractItemView *>( child ) )
180 return;
181
182 child->removeEventFilter( this );
183
184 // also remove filter on existing children
185 const auto constChildren = child->children();
186 for ( QObject *c : constChildren )
187 {
188 removeChild( c );
189 }
190 }
191}
192
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