- 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;
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 );

View File

@ -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* );

View File

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

View File

@ -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

View File

@ -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;