qskinny/src/controls/QskGestureRecognizer.cpp
2023-10-11 09:18:09 +02:00

477 lines
11 KiB
C++

/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#include "QskGestureRecognizer.h"
#include "QskEvent.h"
#include "QskQuick.h"
#include <qcoreapplication.h>
#include <qquickitem.h>
#include <qquickwindow.h>
#include <qvector.h>
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
QSK_QT_PRIVATE_BEGIN
#include <private/qquickwindow_p.h>
QSK_QT_PRIVATE_END
#endif
static QMouseEvent* qskClonedMouseEvent( const QMouseEvent* event )
{
QMouseEvent* clonedEvent;
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
clonedEvent = QQuickWindowPrivate::cloneMouseEvent(
const_cast< QMouseEvent* >( event ), nullptr );
#else
clonedEvent = event->clone();
#endif
clonedEvent->setAccepted( false );
return clonedEvent;
}
class QskGestureRecognizer::PrivateData
{
public:
void startTimer( QskGestureRecognizer* recognizer )
{
if ( timeoutId >= 0 )
{
// warning
}
if ( timeout <= 0 )
{
// warning
}
timeoutId = recognizer->startTimer( timeout );
}
void stopTimer( QskGestureRecognizer* recognizer )
{
if ( timeoutId >= 0 )
{
recognizer->killTimer( timeoutId );
timeoutId = -1;
}
}
inline Qt::MouseButtons effectiveMouseButtons() const
{
if ( buttons != Qt::NoButton )
return buttons;
return watchedItem->acceptedMouseButtons();
}
QPointer< QQuickItem > watchedItem = nullptr;
QPointer< QQuickItem > targetItem = nullptr;
QVector< QMouseEvent* > pendingEvents;
quint64 timestampStarted = 0;
quint64 timestampProcessed = 0;
int timeoutId = -1;
int timeout = -1; // ms
Qt::MouseButtons buttons = Qt::NoButton;
unsigned char state = QskGestureRecognizer::Idle;
bool rejectOnTimeout = true;
bool expired = false;
};
QskGestureRecognizer::QskGestureRecognizer( QObject* parent )
: QObject( parent )
, m_data( new PrivateData() )
{
}
QskGestureRecognizer::~QskGestureRecognizer()
{
qDeleteAll( m_data->pendingEvents );
}
void QskGestureRecognizer::setWatchedItem( QQuickItem* item )
{
if ( m_data->watchedItem )
{
m_data->watchedItem->removeEventFilter( this );
reset();
}
m_data->watchedItem = item;
if ( m_data->watchedItem )
m_data->watchedItem->installEventFilter( this );
/*
// doing those here: ???
m_data->watchedItem->setFiltersChildMouseEvents();
m_data->watchedItem->setAcceptedMouseButtons();
*/
}
QQuickItem* QskGestureRecognizer::watchedItem() const
{
return m_data->watchedItem;
}
void QskGestureRecognizer::setTargetItem( QQuickItem* item )
{
m_data->targetItem = item;
}
QQuickItem* QskGestureRecognizer::targetItem() const
{
return m_data->targetItem;
}
void QskGestureRecognizer::setAcceptedMouseButtons( Qt::MouseButtons buttons )
{
m_data->buttons = buttons;
}
Qt::MouseButtons QskGestureRecognizer::acceptedMouseButtons() const
{
return m_data->buttons;
}
bool QskGestureRecognizer::isAcceptedPos( const QPointF& pos ) const
{
return m_data->watchedItem && m_data->watchedItem->contains( pos );
}
void QskGestureRecognizer::setRejectOnTimeout( bool on )
{
m_data->rejectOnTimeout = on;
}
bool QskGestureRecognizer::rejectOnTimeout() const
{
return m_data->rejectOnTimeout;
}
void QskGestureRecognizer::setTimeout( int ms )
{
m_data->timeout = ms;
}
int QskGestureRecognizer::timeout() const
{
return m_data->timeout;
}
void QskGestureRecognizer::timerEvent( QTimerEvent* event )
{
if ( event->timerId() == m_data->timeoutId )
{
m_data->stopTimer( this );
if ( m_data->rejectOnTimeout )
{
reject();
m_data->expired = true;
}
return;
}
Inherited::timerEvent( event );
}
quint64 QskGestureRecognizer::timestampStarted() const
{
return m_data->timestampStarted;
}
void QskGestureRecognizer::setState( State state )
{
if ( state != m_data->state )
{
const auto oldState = static_cast< State >( m_data->state );
m_data->state = state;
Q_EMIT stateChanged( oldState, state );
}
}
QskGestureRecognizer::State QskGestureRecognizer::state() const
{
return static_cast< QskGestureRecognizer::State >( m_data->state );
}
bool QskGestureRecognizer::eventFilter( QObject* object, QEvent* event)
{
auto& watchedItem = m_data->watchedItem;
if ( ( object == watchedItem ) &&
( static_cast< int >( event->type() ) == QskEvent::GestureFilter ) )
{
if ( !watchedItem->isEnabled() || !watchedItem->isVisible()
|| watchedItem->window() == nullptr )
{
reset();
return false;
}
auto ev = static_cast< QskGestureFilterEvent* >( event );
ev->setMaybeGesture( maybeGesture( ev->item(), ev->event() ) );
return ev->maybeGesture();
}
return false;
}
bool QskGestureRecognizer::maybeGesture(
const QQuickItem* item, const QEvent* event )
{
switch( static_cast< int >( event->type() ) )
{
case QEvent::UngrabMouse:
{
if ( m_data->state != Idle )
{
// someone took our grab away, we have to give up
reset();
return true;
}
return false;
}
case QEvent::MouseButtonPress:
{
if ( state() != Idle )
{
qWarning() << "QskGestureRecognizer: pressed, while not being idle";
return false;
}
const auto mouseEvent = static_cast< const QMouseEvent* >( event );
if ( mouseEvent->timestamp() > m_data->timestampProcessed )
{
m_data->timestampProcessed = mouseEvent->timestamp();
}
else
{
/*
A mouse event might appear several times:
- we ran into a timeout and reposted the events
- we rejected because the event sequence does
not match the acceptance criterions and reposted
the events
- another gesture recognizer for a child item
has reposted the events because of the reasons above
For most situations we can simply skip processing already
processed events with the exception of a timeout. Here we might
have to retry without timeout, when none of the items was
accepting the events. This specific situation is indicated by
item set to null.
*/
if ( item || !m_data->expired )
return false;
}
return processMouseEvent( item, mouseEvent );
}
case QEvent::MouseMove:
case QEvent::MouseButtonRelease:
{
if ( state() <= Idle )
return false;
const auto mouseEvent = static_cast< const QMouseEvent* >( event );
return processMouseEvent( item, mouseEvent );
}
}
return false;
}
bool QskGestureRecognizer::processMouseEvent(
const QQuickItem* item, const QMouseEvent* event )
{
auto& watchedItem = m_data->watchedItem;
const auto pos = watchedItem->mapFromScene( qskMouseScenePosition( event ) );
const auto timestamp = event->timestamp();
if ( event->type() == QEvent::MouseButtonPress )
{
if ( !isAcceptedPos( pos ) )
return false;
if ( m_data->state != Idle )
{
// should not happen, when using the recognizer correctly
qWarning() << "QskGestureRecognizer: pressed, while not being idle";
abort();
return false;
}
if ( !( m_data->effectiveMouseButtons() & event->button() ) )
return false;
/*
We try to grab the mouse for watchedItem and indicate, that we want
to keep it. Then all mouse events should end up at watchedItem.
*/
if ( !qskGrabMouse( watchedItem ) )
return false;
m_data->timestampStarted = timestamp;
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,
*/
m_data->stopTimer( this );
if ( m_data->timeout > 0 )
m_data->startTimer( this );
setState( Pending );
}
else
{
accept();
}
}
if ( m_data->state <= Idle )
return false;
if ( m_data->state == Pending )
m_data->pendingEvents += qskClonedMouseEvent( event );
switch( static_cast< int >( event->type() ) )
{
case QEvent::MouseButtonPress:
processPress( pos, timestamp, item == nullptr );
break;
case QEvent::MouseMove:
processMove( pos, timestamp );
break;
case QEvent::MouseButtonRelease:
{
if ( m_data->state == Pending )
{
reject();
}
else
{
processRelease( pos, timestamp );
reset();
}
break;
}
}
return true;
}
void QskGestureRecognizer::processPress(
const QPointF& pos, quint64 timestamp, bool isFinal )
{
Q_UNUSED( pos );
Q_UNUSED( timestamp );
Q_UNUSED( isFinal );
}
void QskGestureRecognizer::processMove( const QPointF& pos, quint64 timestamp )
{
Q_UNUSED( pos );
Q_UNUSED( timestamp );
}
void QskGestureRecognizer::processRelease( const QPointF& pos, quint64 timestamp )
{
Q_UNUSED( pos );
Q_UNUSED( timestamp );
}
void QskGestureRecognizer::accept()
{
m_data->stopTimer( this );
qDeleteAll( m_data->pendingEvents );
m_data->pendingEvents.clear();
setState( Accepted );
}
void QskGestureRecognizer::reject()
{
/*
Moving the events to a local buffer, so that we can clear
m_data->pendingEvents before replaying them.
*/
const auto events = m_data->pendingEvents;
m_data->pendingEvents.clear();
reset();
/*
Not 100% sure if we should send or post the events
Posting the events adds an extra round trip but we avoid
recursive event handler calls, that might not be expected
from the implementation of a control.
*/
if ( !events.isEmpty() )
{
if ( auto watchedItem = m_data->watchedItem )
{
if ( const auto window = watchedItem->window() )
{
for ( auto event : events )
QCoreApplication::postEvent( window, event );
}
}
#if 0
// when using QCoreApplication::sendEvent above
qDeleteAll( events );
#endif
}
}
void QskGestureRecognizer::abort()
{
reset();
}
void QskGestureRecognizer::reset()
{
m_data->stopTimer( this );
qskUngrabMouse( m_data->watchedItem );
qDeleteAll( m_data->pendingEvents );
m_data->pendingEvents.clear();
m_data->timestampStarted = 0;
m_data->expired = false;
setState( Idle );
}
#include "moc_QskGestureRecognizer.cpp"