/****************************************************************************** * QSkinny - Copyright (C) 2016 Uwe Rathmann * SPDX-License-Identifier: BSD-3-Clause *****************************************************************************/ #include "QskGestureRecognizer.h" #include "QskEvent.h" #include "QskQuick.h" #include #include #include #include #if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) QSK_QT_PRIVATE_BEGIN #include 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"