From b80aed9c921f60fec40d94349c59e1a55b027c3f Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Thu, 11 Jan 2018 14:16:20 +0100 Subject: [PATCH] - QskGestureRecognizer replaying all mouse events, when being aborted. - QskScrollView gesture handling improved to handle replayed events from children that actively ignore mouse events --- src/controls/QskGestureRecognizer.cpp | 108 ++++++++++++++------------ src/controls/QskGestureRecognizer.h | 4 +- src/controls/QskHintAnimator.cpp | 1 - src/controls/QskScrollView.cpp | 78 ++++++++++++++++--- src/controls/QskScrollView.h | 3 + 5 files changed, 133 insertions(+), 61 deletions(-) diff --git a/src/controls/QskGestureRecognizer.cpp b/src/controls/QskGestureRecognizer.cpp index 39151f02..dd8b185e 100644 --- a/src/controls/QskGestureRecognizer.cpp +++ b/src/controls/QskGestureRecognizer.cpp @@ -82,6 +82,21 @@ namespace QBasicTimer m_timer; QskGestureRecognizer* m_recognizer; }; + + class PendingEvents : public QVector< QMouseEvent* > + { + public: + ~PendingEvents() + { + qDeleteAll( *this ); + } + + void reset() + { + qDeleteAll( *this ); + clear(); + } + }; } class QskGestureRecognizer::PrivateData @@ -89,7 +104,6 @@ class QskGestureRecognizer::PrivateData public: PrivateData(): watchedItem( nullptr ), - pendingPress( nullptr ), timestamp( 0 ), timeout( -1 ), buttons( Qt::NoButton ), @@ -98,14 +112,9 @@ public: { } - ~PrivateData() - { - delete pendingPress; - } - QQuickItem* watchedItem; - QMouseEvent* pendingPress; + PendingEvents pendingEvents; ulong timestamp; int timeout; // ms @@ -161,6 +170,11 @@ ulong QskGestureRecognizer::timestamp() const return m_data->timestamp; } +bool QskGestureRecognizer::isReplaying() const +{ + return m_data->isReplayingEvents; +} + void QskGestureRecognizer::setState( State state ) { if ( state != m_data->state ) @@ -177,9 +191,10 @@ QskGestureRecognizer::State QskGestureRecognizer::state() const return static_cast< QskGestureRecognizer::State >( m_data->state ); } -bool QskGestureRecognizer::processEvent( QQuickItem* item, QEvent* event ) +bool QskGestureRecognizer::processEvent( + QQuickItem* item, QEvent* event, bool blockReplayedEvents ) { - if ( m_data->isReplayingEvents ) + if ( m_data->isReplayingEvents && blockReplayedEvents ) { /* This one is a replayed event after we had decided @@ -261,8 +276,6 @@ bool QskGestureRecognizer::processEvent( QQuickItem* item, QEvent* event ) out, that we don't want to handle the mouse event sequence, */ - m_data->pendingPress = qskClonedMouseEvent( mouseEvent ); - if ( m_data->timeout > 0 ) Timer::instance()->start( m_data->timeout, this ); @@ -280,42 +293,30 @@ bool QskGestureRecognizer::processEvent( QQuickItem* item, QEvent* event ) { case QEvent::MouseButtonPress: { - pressEvent( static_cast< QMouseEvent* >( event ) ); + auto mouseEvent = static_cast< QMouseEvent* >( event ); + m_data->pendingEvents += qskClonedMouseEvent( mouseEvent ); + + pressEvent( mouseEvent ); return true; } case QEvent::MouseMove: { - moveEvent( static_cast< QMouseEvent* >( event ) ); + auto mouseEvent = static_cast< QMouseEvent* >( event ); + m_data->pendingEvents += qskClonedMouseEvent( mouseEvent ); + + moveEvent( mouseEvent ); return true; } case QEvent::MouseButtonRelease: { auto mouseEvent = static_cast< QMouseEvent* >( event ); + m_data->pendingEvents += qskClonedMouseEvent( mouseEvent ); if ( m_data->state == Pending ) { - reject(); // sending the pending press - - auto mouseGrabber = watchedItem->window()->mouseGrabberItem(); - if ( mouseGrabber && ( mouseGrabber != watchedItem ) ) - { - /* - After resending the initial press someone else - might be interested in this sequence. Then we - also have to resend the release event, being translated - into the coordinate system of the new grabber. - */ - - QScopedPointer< QMouseEvent > clonedRelease( - qskClonedMouseEvent( mouseEvent, mouseGrabber ) ); - - m_data->isReplayingEvents = true; - QCoreApplication::sendEvent( - watchedItem->window(), clonedRelease.data() ); - m_data->isReplayingEvents = false; - } + reject(); } else { @@ -361,30 +362,42 @@ void QskGestureRecognizer::stateChanged( State from, State to ) void QskGestureRecognizer::accept() { Timer::instance()->stop( this ); - - delete m_data->pendingPress; - m_data->pendingPress = nullptr; + m_data->pendingEvents.reset(); setState( Accepted ); } void QskGestureRecognizer::reject() { - QScopedPointer< QMouseEvent > mousePress( m_data->pendingPress ); - m_data->pendingPress = nullptr; + const auto events = m_data->pendingEvents; + m_data->pendingEvents.clear(); reset(); - if ( mousePress.data() ) - { - const auto window = m_data->watchedItem->window(); - if ( window->mouseGrabberItem() == m_data->watchedItem ) - m_data->watchedItem->ungrabMouse(); + m_data->isReplayingEvents = true; - m_data->isReplayingEvents = true; - QCoreApplication::sendEvent( window, mousePress.data() ); - m_data->isReplayingEvents = false; + const auto window = m_data->watchedItem->window(); + + if ( window->mouseGrabberItem() == m_data->watchedItem ) + m_data->watchedItem->ungrabMouse(); + + if ( !events.isEmpty() && events[0]->type() == QEvent::MouseButtonPress ) + { + QCoreApplication::sendEvent( window, events[0] ); + + /* + After resending the initial press someone else + might be interested in this sequence. + */ + + if ( window->mouseGrabberItem() ) + { + for ( int i = 1; i < events.size(); i++ ) + QCoreApplication::sendEvent( window, events[i] ); + } } + + m_data->isReplayingEvents = false; } void QskGestureRecognizer::abort() @@ -396,9 +409,8 @@ void QskGestureRecognizer::reset() { Timer::instance()->stop( this ); m_data->watchedItem->setKeepMouseGrab( false ); + m_data->pendingEvents.reset(); - delete m_data->pendingPress; - m_data->pendingPress = nullptr; m_data->timestamp = 0; setState( Idle ); diff --git a/src/controls/QskGestureRecognizer.h b/src/controls/QskGestureRecognizer.h index ca44ae35..52af7676 100644 --- a/src/controls/QskGestureRecognizer.h +++ b/src/controls/QskGestureRecognizer.h @@ -39,7 +39,7 @@ public: ulong timestamp() const; - bool processEvent( QQuickItem*, QEvent* ); + bool processEvent( QQuickItem*, QEvent*, bool blockReplayedEvents = true ); void reject(); void accept(); @@ -47,6 +47,8 @@ public: State state() const; + bool isReplaying() const; + protected: virtual void pressEvent( const QMouseEvent* ); virtual void moveEvent( const QMouseEvent* ); diff --git a/src/controls/QskHintAnimator.cpp b/src/controls/QskHintAnimator.cpp index 78afb92c..2443c143 100644 --- a/src/controls/QskHintAnimator.cpp +++ b/src/controls/QskHintAnimator.cpp @@ -96,7 +96,6 @@ static inline bool qskCheckReceiverThread( const QObject *receiver ) return ( thread == QThread::currentThread() ); } - QskHintAnimator::QskHintAnimator() { } diff --git a/src/controls/QskScrollView.cpp b/src/controls/QskScrollView.cpp index 73caa6d8..e1598b18 100644 --- a/src/controls/QskScrollView.cpp +++ b/src/controls/QskScrollView.cpp @@ -57,6 +57,7 @@ public: horizontalScrollBarPolicy( Qt::ScrollBarAsNeeded ), verticalScrollBarPolicy( Qt::ScrollBarAsNeeded ), scrollableSize( 0.0, 0.0 ), + panRecognizerTimeout( 100 ), // value coming from the platform ??? isScrolling( 0 ) { } @@ -68,6 +69,8 @@ public: QSizeF scrollableSize; QskPanGestureRecognizer panRecognizer; + int panRecognizerTimeout; + FlickAnimator flicker; qreal scrollPressPos; @@ -93,6 +96,19 @@ QskScrollView::~QskScrollView() { } +void QskScrollView::setFlickRecognizerTimeout( int timeout ) +{ + if ( timeout < 0 ) + timeout = -1; + + m_data->panRecognizerTimeout = timeout; +} + +int QskScrollView::flickRecognizerTimeout() const +{ + return m_data->panRecognizerTimeout; +} + void QskScrollView::setFlickableOrientations( Qt::Orientations orientations ) { if ( m_data->panRecognizer.orientations() != orientations ) @@ -388,28 +404,68 @@ void QskScrollView::wheelEvent( QWheelEvent* event ) bool QskScrollView::gestureFilter( QQuickItem* item, QEvent* event ) { - const auto o = m_data->panRecognizer.orientations(); - if ( o ) + if ( event->type() == QEvent::MouseButtonPress ) { - bool maybeGesture = m_data->panRecognizer.state() > QskGestureRecognizer::Idle; + // Checking first if panning is possible at all - if ( !maybeGesture && ( event->type() == QEvent::MouseButtonPress ) ) + bool maybeGesture = false; + + const auto orientations = m_data->panRecognizer.orientations(); + if ( orientations ) { - const QRectF vr = viewContentsRect(); + const QSizeF viewSize = viewContentsRect().size(); const QSizeF& scrollableSize = m_data->scrollableSize; - if ( ( ( o & Qt::Vertical ) && ( vr.height() < scrollableSize.height() ) ) - || ( ( o & Qt::Horizontal ) && ( vr.width() < scrollableSize.width() ) ) ) + if ( orientations & Qt::Vertical ) { - maybeGesture = true; + if ( viewSize.height() < scrollableSize.height() ) + maybeGesture = true; + } + + if ( orientations & Qt::Horizontal ) + { + if ( viewSize.width() < scrollableSize.width() ) + maybeGesture = true; } } - if ( maybeGesture ) - return m_data->panRecognizer.processEvent( item, event ); + if ( !maybeGesture ) + return false; } - return false; + /* + This code is a bit tricky as the filter is called in different situations: + + a) The press was on a child of the view + b) The press was on the view + + In case of b) things are simple and we can let the recognizer + decide without timeout if it is was a gesture or not. + + In case of a) we give the recognizer some time to decide - usually + based on the distances of the following mouse events. If no decision + could be made the recognizer aborts and replays the mouse events, so + that the children can process them. + + But if a child does not accept a mouse event it will be sent to + its parent. So we might finally receive the reposted events, but then + we can proceed as in b). + */ + + auto& recognizer = m_data->panRecognizer; + + if ( event->type() == QEvent::MouseButtonPress ) + { + if ( recognizer.isReplaying() ) + { + if ( ( item != this ) || ( recognizer.timeout() < 0 ) ) + return false; + } + + recognizer.setTimeout( ( item == this ) ? -1 : m_data->panRecognizerTimeout ); + } + + return m_data->panRecognizer.processEvent( item, event ); } QPointF QskScrollView::boundedScrollPos( const QPointF& pos ) const diff --git a/src/controls/QskScrollView.h b/src/controls/QskScrollView.h index 04a167fc..666ce2b9 100644 --- a/src/controls/QskScrollView.h +++ b/src/controls/QskScrollView.h @@ -44,6 +44,9 @@ public: void setFlickableOrientations( Qt::Orientations ); Qt::Orientations flickableOrientations() const; + int flickRecognizerTimeout() const; + void setFlickRecognizerTimeout( int timeout ); + QPointF scrollPos() const; bool isScrolling( Qt::Orientation ) const;