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