- QskGestureRecognizer replaying all mouse events, when being aborted.

- QskScrollView gesture handling improved to handle replayed events from children that actively ignore mouse events
This commit is contained in:
Uwe Rathmann 2018-01-11 14:16:20 +01:00
parent b00edfce28
commit b80aed9c92
5 changed files with 133 additions and 61 deletions

View File

@ -82,6 +82,21 @@ namespace
QBasicTimer m_timer; QBasicTimer m_timer;
QskGestureRecognizer* m_recognizer; QskGestureRecognizer* m_recognizer;
}; };
class PendingEvents : public QVector< QMouseEvent* >
{
public:
~PendingEvents()
{
qDeleteAll( *this );
}
void reset()
{
qDeleteAll( *this );
clear();
}
};
} }
class QskGestureRecognizer::PrivateData class QskGestureRecognizer::PrivateData
@ -89,7 +104,6 @@ class QskGestureRecognizer::PrivateData
public: public:
PrivateData(): PrivateData():
watchedItem( nullptr ), watchedItem( nullptr ),
pendingPress( nullptr ),
timestamp( 0 ), timestamp( 0 ),
timeout( -1 ), timeout( -1 ),
buttons( Qt::NoButton ), buttons( Qt::NoButton ),
@ -98,14 +112,9 @@ public:
{ {
} }
~PrivateData()
{
delete pendingPress;
}
QQuickItem* watchedItem; QQuickItem* watchedItem;
QMouseEvent* pendingPress; PendingEvents pendingEvents;
ulong timestamp; ulong timestamp;
int timeout; // ms int timeout; // ms
@ -161,6 +170,11 @@ ulong QskGestureRecognizer::timestamp() const
return m_data->timestamp; return m_data->timestamp;
} }
bool QskGestureRecognizer::isReplaying() const
{
return m_data->isReplayingEvents;
}
void QskGestureRecognizer::setState( State state ) void QskGestureRecognizer::setState( State state )
{ {
if ( state != m_data->state ) if ( state != m_data->state )
@ -177,9 +191,10 @@ QskGestureRecognizer::State QskGestureRecognizer::state() const
return static_cast< QskGestureRecognizer::State >( m_data->state ); 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 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, out, that we don't want to handle the mouse event sequence,
*/ */
m_data->pendingPress = qskClonedMouseEvent( mouseEvent );
if ( m_data->timeout > 0 ) if ( m_data->timeout > 0 )
Timer::instance()->start( m_data->timeout, this ); Timer::instance()->start( m_data->timeout, this );
@ -280,42 +293,30 @@ bool QskGestureRecognizer::processEvent( QQuickItem* item, QEvent* event )
{ {
case QEvent::MouseButtonPress: case QEvent::MouseButtonPress:
{ {
pressEvent( static_cast< QMouseEvent* >( event ) ); auto mouseEvent = static_cast< QMouseEvent* >( event );
m_data->pendingEvents += qskClonedMouseEvent( mouseEvent );
pressEvent( mouseEvent );
return true; return true;
} }
case QEvent::MouseMove: case QEvent::MouseMove:
{ {
moveEvent( static_cast< QMouseEvent* >( event ) ); auto mouseEvent = static_cast< QMouseEvent* >( event );
m_data->pendingEvents += qskClonedMouseEvent( mouseEvent );
moveEvent( mouseEvent );
return true; return true;
} }
case QEvent::MouseButtonRelease: case QEvent::MouseButtonRelease:
{ {
auto mouseEvent = static_cast< QMouseEvent* >( event ); auto mouseEvent = static_cast< QMouseEvent* >( event );
m_data->pendingEvents += qskClonedMouseEvent( mouseEvent );
if ( m_data->state == Pending ) if ( m_data->state == Pending )
{ {
reject(); // sending the pending press reject();
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;
}
} }
else else
{ {
@ -361,32 +362,44 @@ void QskGestureRecognizer::stateChanged( State from, State to )
void QskGestureRecognizer::accept() void QskGestureRecognizer::accept()
{ {
Timer::instance()->stop( this ); Timer::instance()->stop( this );
m_data->pendingEvents.reset();
delete m_data->pendingPress;
m_data->pendingPress = nullptr;
setState( Accepted ); setState( Accepted );
} }
void QskGestureRecognizer::reject() void QskGestureRecognizer::reject()
{ {
QScopedPointer< QMouseEvent > mousePress( m_data->pendingPress ); const auto events = m_data->pendingEvents;
m_data->pendingPress = nullptr; m_data->pendingEvents.clear();
reset(); reset();
if ( mousePress.data() ) m_data->isReplayingEvents = true;
{
const auto window = m_data->watchedItem->window(); const auto window = m_data->watchedItem->window();
if ( window->mouseGrabberItem() == m_data->watchedItem ) if ( window->mouseGrabberItem() == m_data->watchedItem )
m_data->watchedItem->ungrabMouse(); m_data->watchedItem->ungrabMouse();
m_data->isReplayingEvents = true; if ( !events.isEmpty() && events[0]->type() == QEvent::MouseButtonPress )
QCoreApplication::sendEvent( window, mousePress.data() ); {
m_data->isReplayingEvents = false; 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() void QskGestureRecognizer::abort()
{ {
reset(); reset();
@ -396,9 +409,8 @@ void QskGestureRecognizer::reset()
{ {
Timer::instance()->stop( this ); Timer::instance()->stop( this );
m_data->watchedItem->setKeepMouseGrab( false ); m_data->watchedItem->setKeepMouseGrab( false );
m_data->pendingEvents.reset();
delete m_data->pendingPress;
m_data->pendingPress = nullptr;
m_data->timestamp = 0; m_data->timestamp = 0;
setState( Idle ); setState( Idle );

View File

@ -39,7 +39,7 @@ public:
ulong timestamp() const; ulong timestamp() const;
bool processEvent( QQuickItem*, QEvent* ); bool processEvent( QQuickItem*, QEvent*, bool blockReplayedEvents = true );
void reject(); void reject();
void accept(); void accept();
@ -47,6 +47,8 @@ public:
State state() const; State state() const;
bool isReplaying() const;
protected: protected:
virtual void pressEvent( const QMouseEvent* ); virtual void pressEvent( const QMouseEvent* );
virtual void moveEvent( const QMouseEvent* ); virtual void moveEvent( const QMouseEvent* );

View File

@ -96,7 +96,6 @@ static inline bool qskCheckReceiverThread( const QObject *receiver )
return ( thread == QThread::currentThread() ); return ( thread == QThread::currentThread() );
} }
QskHintAnimator::QskHintAnimator() QskHintAnimator::QskHintAnimator()
{ {
} }

View File

@ -57,6 +57,7 @@ public:
horizontalScrollBarPolicy( Qt::ScrollBarAsNeeded ), horizontalScrollBarPolicy( Qt::ScrollBarAsNeeded ),
verticalScrollBarPolicy( Qt::ScrollBarAsNeeded ), verticalScrollBarPolicy( Qt::ScrollBarAsNeeded ),
scrollableSize( 0.0, 0.0 ), scrollableSize( 0.0, 0.0 ),
panRecognizerTimeout( 100 ), // value coming from the platform ???
isScrolling( 0 ) isScrolling( 0 )
{ {
} }
@ -68,6 +69,8 @@ public:
QSizeF scrollableSize; QSizeF scrollableSize;
QskPanGestureRecognizer panRecognizer; QskPanGestureRecognizer panRecognizer;
int panRecognizerTimeout;
FlickAnimator flicker; FlickAnimator flicker;
qreal scrollPressPos; 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 ) void QskScrollView::setFlickableOrientations( Qt::Orientations orientations )
{ {
if ( m_data->panRecognizer.orientations() != orientations ) if ( m_data->panRecognizer.orientations() != orientations )
@ -388,30 +404,70 @@ void QskScrollView::wheelEvent( QWheelEvent* event )
bool QskScrollView::gestureFilter( QQuickItem* item, QEvent* event ) bool QskScrollView::gestureFilter( QQuickItem* item, QEvent* event )
{ {
const auto o = m_data->panRecognizer.orientations(); if ( event->type() == QEvent::MouseButtonPress )
if ( o )
{ {
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; const QSizeF& scrollableSize = m_data->scrollableSize;
if ( ( ( o & Qt::Vertical ) && ( vr.height() < scrollableSize.height() ) ) if ( orientations & Qt::Vertical )
|| ( ( o & Qt::Horizontal ) && ( vr.width() < scrollableSize.width() ) ) )
{ {
if ( viewSize.height() < scrollableSize.height() )
maybeGesture = true;
}
if ( orientations & Qt::Horizontal )
{
if ( viewSize.width() < scrollableSize.width() )
maybeGesture = true; maybeGesture = true;
} }
} }
if ( maybeGesture ) if ( !maybeGesture )
return m_data->panRecognizer.processEvent( item, event ); 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; return false;
} }
recognizer.setTimeout( ( item == this ) ? -1 : m_data->panRecognizerTimeout );
}
return m_data->panRecognizer.processEvent( item, event );
}
QPointF QskScrollView::boundedScrollPos( const QPointF& pos ) const QPointF QskScrollView::boundedScrollPos( const QPointF& pos ) const
{ {
const QRectF vr = viewContentsRect(); const QRectF vr = viewContentsRect();

View File

@ -44,6 +44,9 @@ public:
void setFlickableOrientations( Qt::Orientations ); void setFlickableOrientations( Qt::Orientations );
Qt::Orientations flickableOrientations() const; Qt::Orientations flickableOrientations() const;
int flickRecognizerTimeout() const;
void setFlickRecognizerTimeout( int timeout );
QPointF scrollPos() const; QPointF scrollPos() const;
bool isScrolling( Qt::Orientation ) const; bool isScrolling( Qt::Orientation ) const;