qskinny/src/controls/QskGestureRecognizer.cpp

555 lines
14 KiB
C++
Raw Normal View History

2017-07-21 18:21:34 +02:00
#include "QskGestureRecognizer.h"
2020-10-25 15:56:22 +01:00
#include "QskEvent.h"
2017-07-21 18:21:34 +02:00
2018-08-03 08:15:28 +02:00
#include <qbasictimer.h>
2018-07-19 14:10:48 +02:00
#include <qcoreapplication.h>
#include <qcoreevent.h>
2018-08-03 08:15:28 +02:00
#include <qquickitem.h>
#include <qquickwindow.h>
2018-07-19 14:10:48 +02:00
#include <qscopedpointer.h>
#include <qvector.h>
2017-07-21 18:21:34 +02:00
QSK_QT_PRIVATE_BEGIN
#include <private/qquickwindow_p.h>
QSK_QT_PRIVATE_END
2020-11-21 20:36:47 +01:00
static QMouseEvent* qskClonedMouseEventAt(
2020-12-09 12:44:08 +01:00
const QMouseEvent* event, QPointF* localPos )
2020-11-21 20:36:47 +01:00
{
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
auto clonedEvent = QQuickWindowPrivate::cloneMouseEvent(
const_cast< QMouseEvent* >( event ), localPos );
#else
2020-12-09 12:44:08 +01:00
auto clonedEvent = event->clone();
2020-11-21 20:36:47 +01:00
auto& point = QMutableEventPoint::from( clonedEvent->point( 0 ) );
point.detach();
point.setTimestamp( event->timestamp() );
2020-12-09 12:44:08 +01:00
if ( localPos )
point.setPosition( *localPos );
2020-11-21 20:36:47 +01:00
#endif
return clonedEvent;
}
2017-07-21 18:21:34 +02:00
static inline QMouseEvent* qskClonedMouseEvent(
const QMouseEvent* mouseEvent, const QQuickItem* item = nullptr )
{
QMouseEvent* clonedEvent;
2020-11-21 20:36:47 +01:00
auto event = const_cast< QMouseEvent* >( mouseEvent );
2017-07-21 18:21:34 +02:00
if ( item )
{
2020-11-21 20:36:47 +01:00
auto localPos = item->mapFromScene( qskMouseScenePosition( event ) );
clonedEvent = qskClonedMouseEventAt( event, &localPos );
2017-07-21 18:21:34 +02:00
}
else
{
2020-11-21 20:36:47 +01:00
clonedEvent = qskClonedMouseEventAt( event, nullptr );
2017-07-21 18:21:34 +02:00
}
clonedEvent->setAccepted( false );
return clonedEvent;
}
namespace
{
/*
As we don't want QskGestureRecognizer being a QObject
we need some extra timers - usually one per screen.
*/
2017-07-21 18:21:34 +02:00
class Timer final : public QObject
{
2018-08-03 08:15:28 +02:00
public:
2017-07-21 18:21:34 +02:00
void start( int ms, QskGestureRecognizer* recognizer )
{
if ( m_timer.isActive() )
qWarning() << "QskGestureRecognizer: resetting an active timer";
2017-07-21 18:21:34 +02:00
m_recognizer = recognizer;
m_timer.start( ms, this );
}
void stop()
2017-07-21 18:21:34 +02:00
{
m_timer.stop();
m_recognizer = nullptr;
}
const QskGestureRecognizer* recognizer() const
{
return m_recognizer;
2017-07-21 18:21:34 +02:00
}
2018-08-03 08:15:28 +02:00
protected:
2018-07-31 17:32:25 +02:00
void timerEvent( QTimerEvent* ) override
2017-07-21 18:21:34 +02:00
{
m_timer.stop();
if ( m_recognizer )
{
auto recognizer = m_recognizer;
2017-07-21 18:21:34 +02:00
m_recognizer = nullptr;
recognizer->reject();
2017-07-21 18:21:34 +02:00
}
}
QBasicTimer m_timer;
2018-07-31 17:32:25 +02:00
QskGestureRecognizer* m_recognizer = nullptr;
2017-07-21 18:21:34 +02:00
};
class TimerTable
{
2018-08-03 08:15:28 +02:00
public:
~TimerTable()
{
qDeleteAll( m_table );
}
void startTimer( int ms, QskGestureRecognizer* recognizer )
{
Timer* timer = nullptr;
for ( auto t : qskAsConst( m_table ) )
{
2018-08-03 08:15:28 +02:00
if ( t->recognizer() == nullptr ||
t->recognizer() == recognizer )
{
timer = t;
break;
}
}
if ( timer == nullptr )
{
timer = new Timer();
m_table += timer;
}
timer->start( ms, recognizer );
}
void stopTimer( const QskGestureRecognizer* recognizer )
{
for ( auto timer : qskAsConst( m_table ) )
{
if ( timer->recognizer() == recognizer )
{
// we keep the timer to be used later again
timer->stop();
return;
}
}
}
2018-08-03 08:15:28 +02:00
private:
/*
Usually we have not more than one entry.
Only when having more than one screen we
might have mouse events to be processed
simultaneously.
*/
QVector< Timer* > m_table;
};
class PendingEvents : public QVector< QMouseEvent* >
{
2018-08-03 08:15:28 +02:00
public:
~PendingEvents()
{
qDeleteAll( *this );
}
void reset()
{
qDeleteAll( *this );
clear();
}
};
2017-07-21 18:21:34 +02:00
}
Q_GLOBAL_STATIC( TimerTable, qskTimerTable )
2017-07-21 18:21:34 +02:00
class QskGestureRecognizer::PrivateData
{
2018-08-03 08:15:28 +02:00
public:
PrivateData()
: watchedItem( nullptr )
, timestamp( 0 )
, timestampProcessed( 0 )
, timeout( -1 )
, buttons( Qt::NoButton )
, state( QskGestureRecognizer::Idle )
, isReplayingEvents( false )
2017-07-21 18:21:34 +02:00
{
}
QQuickItem* watchedItem;
PendingEvents pendingEvents;
2017-07-21 18:21:34 +02:00
ulong timestamp;
ulong timestampProcessed;
2017-07-21 18:21:34 +02:00
int timeout; // ms
Qt::MouseButtons buttons;
int state : 4;
bool isReplayingEvents : 1; // not exception safe !!!
};
2018-08-03 08:15:28 +02:00
QskGestureRecognizer::QskGestureRecognizer()
: m_data( new PrivateData() )
2017-07-21 18:21:34 +02:00
{
}
QskGestureRecognizer::~QskGestureRecognizer()
{
qskTimerTable->stopTimer( this );
2017-07-21 18:21:34 +02:00
}
void QskGestureRecognizer::setWatchedItem( QQuickItem* item )
{
2019-05-16 13:57:55 +02:00
if ( m_data->watchedItem )
reset();
2017-07-21 18:21:34 +02:00
m_data->watchedItem = item;
}
QQuickItem* QskGestureRecognizer::watchedItem() const
{
return m_data->watchedItem;
}
void QskGestureRecognizer::setAcceptedMouseButtons( Qt::MouseButtons buttons )
{
m_data->buttons = buttons;
}
Qt::MouseButtons QskGestureRecognizer::acceptedMouseButtons() const
{
return m_data->buttons;
}
void QskGestureRecognizer::setTimeout( int ms )
{
m_data->timeout = ms;
}
int QskGestureRecognizer::timeout() const
{
return m_data->timeout;
}
ulong QskGestureRecognizer::timestamp() const
{
return m_data->timestamp;
}
bool QskGestureRecognizer::hasProcessedBefore( const QMouseEvent* event ) const
{
return event && ( event->timestamp() <= m_data->timestampProcessed );
}
bool QskGestureRecognizer::isReplaying() const
{
return m_data->isReplayingEvents;
}
2017-07-21 18:21:34 +02:00
void QskGestureRecognizer::setState( State state )
{
if ( state != m_data->state )
{
const State oldState = static_cast< QskGestureRecognizer::State >( m_data->state );
m_data->state = state;
stateChanged( oldState, state );
}
}
QskGestureRecognizer::State QskGestureRecognizer::state() const
{
return static_cast< QskGestureRecognizer::State >( m_data->state );
}
bool QskGestureRecognizer::processEvent(
QQuickItem* item, QEvent* event, bool blockReplayedEvents )
2017-07-21 18:21:34 +02:00
{
if ( m_data->isReplayingEvents && blockReplayedEvents )
2017-07-21 18:21:34 +02:00
{
/*
This one is a replayed event after we had decided
that this interaction is to be ignored
*/
return false;
}
auto& watchedItem = m_data->watchedItem;
2018-08-03 08:15:28 +02:00
if ( watchedItem == nullptr || !watchedItem->isEnabled() ||
!watchedItem->isVisible() || watchedItem->window() == nullptr )
2017-07-21 18:21:34 +02:00
{
reset();
return false;
}
QScopedPointer< QMouseEvent > clonedPress;
if ( event->type() == QEvent::MouseButtonPress )
{
if ( m_data->state != Idle )
{
// should not happen, when using the recognizer correctly
qWarning() << "QskGestureRecognizer: pressed, while not being idle";
2018-06-14 13:19:04 +02:00
abort();
2017-07-21 18:21:34 +02:00
return false;
}
auto mouseGrabber = watchedItem->window()->mouseGrabberItem();
if ( mouseGrabber && ( mouseGrabber != watchedItem ) )
{
if ( mouseGrabber->keepMouseGrab() || mouseGrabber->keepTouchGrab() )
{
/*
Another child has grabbed mouse/touch and is not willing to
be intercepted: we respect this.
*/
return false;
}
}
Qt::MouseButtons buttons = m_data->buttons;
if ( buttons == Qt::NoButton )
buttons = watchedItem->acceptedMouseButtons();
auto mouseEvent = static_cast< QMouseEvent* >( event );
if ( !( buttons & mouseEvent->button() ) )
return false;
/*
We grab the mouse for watchedItem and indicate, that we want
to keep it. From now on all mouse events should end up at watchedItem.
*/
watchedItem->grabMouse();
watchedItem->setKeepMouseGrab( true );
m_data->timestamp = mouseEvent->timestamp();
if ( item != watchedItem )
{
/*
The first press happens before having the mouse grab and might
have been for a child of watchedItem. Then we create a clone
of the event with positions translated into the coordinate system
of watchedItem.
*/
clonedPress.reset( qskClonedMouseEvent( mouseEvent, watchedItem ) );
item = watchedItem;
event = clonedPress.data();
}
if ( m_data->timeout != 0 )
{
/*
We need to be able to replay the press in case we later find
out, that we don't want to handle the mouse event sequence,
*/
if ( m_data->timeout > 0 )
qskTimerTable->startTimer( m_data->timeout, this );
2017-07-21 18:21:34 +02:00
setState( Pending );
}
else
{
setState( Accepted );
}
}
if ( ( item == watchedItem ) && ( m_data->state > Idle ) )
{
2018-08-03 08:15:28 +02:00
switch ( event->type() )
2017-07-21 18:21:34 +02:00
{
case QEvent::MouseButtonPress:
{
auto mouseEvent = static_cast< QMouseEvent* >( event );
m_data->pendingEvents += qskClonedMouseEvent( mouseEvent );
pressEvent( mouseEvent );
2017-07-21 18:21:34 +02:00
return true;
}
case QEvent::MouseMove:
{
auto mouseEvent = static_cast< QMouseEvent* >( event );
m_data->pendingEvents += qskClonedMouseEvent( mouseEvent );
moveEvent( mouseEvent );
2017-07-21 18:21:34 +02:00
return true;
}
case QEvent::MouseButtonRelease:
{
auto mouseEvent = static_cast< QMouseEvent* >( event );
m_data->pendingEvents += qskClonedMouseEvent( mouseEvent );
2017-07-21 18:21:34 +02:00
if ( m_data->state == Pending )
{
#if QT_VERSION >= QT_VERSION_CHECK( 5, 10, 0 )
if ( mouseEvent->source() == Qt::MouseEventSynthesizedByQt )
{
/*
When replaying mouse events inside of handling synthesized
mouse event Qt runs into a situation where
QQuickWindow::mouseGrabberItem() returns the value from the
wrong input device. So we can't call reject() immediately.
Unfortunately this introduces a gap where other events might
be delivered before the QEvent::timer gets processed.
In the long run it might be better to record and replay
real touch events - what is necessary for multi touch gestures
anyway.
*/
qskTimerTable->stopTimer( this );
qskTimerTable->startTimer( 0, this );
}
else
#endif
{
reject();
}
2017-07-21 18:21:34 +02:00
}
else
{
releaseEvent( mouseEvent );
reset();
}
return true;
}
case QEvent::UngrabMouse:
{
// someone took away our grab, we have to give up
reset();
break;
}
default:
break;
}
}
return false;
}
void QskGestureRecognizer::pressEvent( const QMouseEvent* )
{
}
void QskGestureRecognizer::moveEvent( const QMouseEvent* )
{
}
void QskGestureRecognizer::releaseEvent( const QMouseEvent* )
{
}
void QskGestureRecognizer::stateChanged( State from, State to )
{
Q_UNUSED( from )
Q_UNUSED( to )
}
void QskGestureRecognizer::accept()
{
qskTimerTable->stopTimer( this );
m_data->pendingEvents.reset();
2017-07-21 18:21:34 +02:00
setState( Accepted );
}
void QskGestureRecognizer::reject()
{
const auto events = m_data->pendingEvents;
m_data->pendingEvents.clear();
2017-07-21 18:21:34 +02:00
reset();
2019-05-16 13:57:55 +02:00
auto watchedItem = m_data->watchedItem;
if ( watchedItem == nullptr )
return;
2019-05-16 13:57:55 +02:00
const auto window = watchedItem->window();
if ( window == nullptr )
return;
m_data->isReplayingEvents = true;
2019-05-16 13:57:55 +02:00
if ( window->mouseGrabberItem() == watchedItem )
watchedItem->ungrabMouse();
2018-08-03 08:15:28 +02:00
if ( !events.isEmpty() &&
( events[ 0 ]->type() == QEvent::MouseButtonPress ) )
2017-07-21 18:21:34 +02:00
{
/*
In a situation of several recognizers ( f.e a vertical
scroll view inside a horizontal swipe view ), we might receive
cloned events from another recognizer.
To avoid to process them twice we store the most recent timestamp
of the cloned events we have already processed, but reposted.
*/
m_data->timestampProcessed = events.last()->timestamp();
2018-08-03 08:15:28 +02:00
QCoreApplication::sendEvent( window, events[ 0 ] );
/*
After resending the initial press someone else
might be interested in this sequence.
*/
2017-07-21 18:21:34 +02:00
if ( window->mouseGrabberItem() )
{
for ( int i = 1; i < events.size(); i++ )
2018-08-03 08:15:28 +02:00
QCoreApplication::sendEvent( window, events[ i ] );
}
2017-07-21 18:21:34 +02:00
}
m_data->isReplayingEvents = false;
2017-07-21 18:21:34 +02:00
}
void QskGestureRecognizer::abort()
{
reset();
}
void QskGestureRecognizer::reset()
{
qskTimerTable->stopTimer( this );
2019-05-16 13:57:55 +02:00
if ( auto watchedItem = m_data->watchedItem )
2018-06-19 10:46:51 +02:00
{
2019-05-16 13:57:55 +02:00
watchedItem->setKeepMouseGrab( false );
if ( auto window = watchedItem->window() )
{
if ( window->mouseGrabberItem() == m_data->watchedItem )
watchedItem->ungrabMouse();
}
2018-06-19 10:46:51 +02:00
}
m_data->pendingEvents.reset();
2017-07-21 18:21:34 +02:00
m_data->timestamp = 0;
setState( Idle );
}