diff --git a/src/widgets/util/qflickgesture.cpp b/src/widgets/util/qflickgesture.cpp index 064be873..e970a85a 100644 --- a/src/widgets/util/qflickgesture.cpp +++ b/src/widgets/util/qflickgesture.cpp @@ -47,12 +47,14 @@ #include "qgraphicssceneevent.h" #include "qgraphicsview.h" #endif +#include "qstylehints.h" #include "qscroller.h" #include #include "private/qapplication_p.h" #include "private/qevent_p.h" #include "private/qflickgesture_p.h" #include "qdebug.h" +#include "qtimer.h" #ifndef QT_NO_GESTURES @@ -68,13 +70,258 @@ QT_BEGIN_NAMESPACE extern bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event); -static QMouseEvent *copyMouseEvent(QEvent *e) +QTimer* PressDelayHandler::m_pressDelayTimer; +bool PressDelayHandler::m_sendingEvent = false; +QScopedPointer PressDelayHandler::m_pressDelayEvent; +QPointer PressDelayHandler::m_pressTarget; + +PressDelayHandler::PressDelayHandler(QObject *parent) + : QObject(parent) +{ } + +PressDelayHandler* PressDelayHandler::create(Qt::MouseButton button) +{ + switch (button) { + case Qt::LeftButton: + return new MousePressDelayHandler(QCoreApplication::instance()); + break; + case Qt::NoButton: + return new TouchPressDelayHandler(QCoreApplication::instance()); + default: + break; + } + return new MousePressDelayHandler(QCoreApplication::instance()); +} + +bool PressDelayHandler::shouldEventBeIgnored() const +{ + return m_sendingEvent; +} + +bool PressDelayHandler::isDelaying() const +{ + return hasSavedDelayEvent(); +} + +void PressDelayHandler::pressed(QEvent *e, int delay) +{ + if (!hasSavedDelayEvent()) { + qFGDebug("QFG: consuming/delaying press"); + m_pressDelayEvent.reset(copyEvent(e)); + saveDelayEventInfo(e); + startTimer(delay); + } else { + qFGDebug("QFG: NOT consuming/delaying press"); + } + +} + +bool PressDelayHandler::released(QEvent *e, bool scrollerWasActive, bool scrollerIsActive) +{ + // stop the timer + stopTimer(); + + bool result = scrollerWasActive || scrollerIsActive; + + // we still haven't even sent the press, so do it now + if (hasSavedDelayEvent() && pressTarget() && !scrollerIsActive) { + + qFGDebug() << "QFG: re-sending press (due to release) for " << pressTarget(); + sendSpontaneousEvent(m_pressDelayEvent.data(), pressTarget(), UngrabMouseBefore); + + qFGDebug() << "QFG: faking release (due to release) for " << pressTarget(); + sendSpontaneousEvent(e, pressTarget(), 0); + + result = true; // consume this event + } else if (pressTarget() && scrollerIsActive) { + // we grabbed the mouse expicitly when the scroller became active, so undo that now + qFGDebug() << "QFG: ungrab mouse which grabbed when the scroller became active"; + sendSpontaneousEvent(nullptr, pressTarget(), UngrabMouseBefore); + } + + resetDelayEvent(); + resetPressTarget(); + return result; +} + +void PressDelayHandler::scrollerWasIntercepted() +{ + qFGDebug("QFG: deleting delayed press, since scroller was only intercepted"); + if (hasSavedDelayEvent()) { + // we still haven't even sent the press, so just throw it away now + stopTimer(); + } + resetPressTarget(); +} + +void PressDelayHandler::scrollerBecameActive() +{ + if (hasSavedDelayEvent()) { + // we still haven't even sent the press, so just throw it away now + qFGDebug("QFG: deleting delayed mouse press, since scroller is active now"); + stopTimer(); + resetDelayEvent(); + resetPressTarget(); + } else if (pressTarget()) { + // we did send a press, so we need to fake a release now + releaseAllPressed(); + // don't clear the pressTarget just yet, since we need to explicitly ungrab the mouse on release! + } +} + +void PressDelayHandler::onDelayTimeout() +{ + if (hasSavedDelayEvent() && pressTarget()) { + qFGDebug() << "QFG: timer event: re-sending press to " << pressTarget(); + sendSpontaneousEvent(m_pressDelayEvent.data(), pressTarget(), UngrabMouseBefore); + } + resetDelayEvent(); + + stopTimer(); +} + +void PressDelayHandler::stopTimer() +{ + if (!m_pressDelayTimer) + return; + if (!m_pressDelayTimer->isActive()) + return; + m_pressDelayTimer->stop(); +} + +void PressDelayHandler::startTimer(int timeout) +{ + if (!m_pressDelayTimer) + m_pressDelayTimer = new QTimer; + if (m_pressDelayTimer->isActive()) { + qFGDebug() << "QFG: timer is active, don't start again"; + return; + } + m_pressDelayTimer->singleShot(timeout, this, &PressDelayHandler::onDelayTimeout); +} + +void PressDelayHandler::sendSpontaneousEvent(QEvent *e, QObject *target, int flags) +{ +#if QT_CONFIG(graphicsview) + if (flags & UngrabMouseBefore) + ungrabMouse(mouseGrabberItem()); +#else + Q_UNUSED(flags); +#endif + + m_sendingEvent = true; + sendEvent(e, target); + m_sendingEvent = false; + +#if QT_CONFIG(graphicsview) + if (flags & RegrabMouseAfterwards) + grabMouse(mouseGrabberItem()); +#endif +} + +QPointer PressDelayHandler::pressTarget() const +{ + return m_pressTarget; +} + +void PressDelayHandler::setPressTarget(QObject* pressTarget) +{ + m_pressTarget = pressTarget; +} + +void PressDelayHandler::resetPressTarget() +{ + m_pressTarget = nullptr; +} + +bool PressDelayHandler::hasSavedDelayEvent() const +{ + return !m_pressDelayEvent.isNull(); +} + +void PressDelayHandler::resetDelayEvent() +{ + m_pressDelayEvent.reset(nullptr); +} + +QGraphicsItem* PressDelayHandler::mouseGrabberItem() const +{ + QGraphicsItem *grabber = nullptr; + + QWidget* targetWidget = nullptr; + if (pressTarget().data()->isWidgetType()) { + targetWidget = dynamic_cast(pressTarget().data()); + } + else { + targetWidget = QApplication::widgetAt(QCursor::pos()); + } + if (!targetWidget) + return nullptr; + + if (targetWidget->parentWidget()) { + if (QGraphicsView *gv = qobject_cast(targetWidget->parentWidget())) { + if (gv->scene()) + grabber = gv->scene()->mouseGrabberItem(); + } + } + return grabber; +} + +void PressDelayHandler::grabMouse(QGraphicsItem* grabber) const +{ + if (!grabber) + return; + + // GraphicsView Mouse Handling Workaround #2: + // we need to re-grab the mouse after sending a faked mouse + // release, since we still need the mouse moves for the gesture + // (the scene will clear the item's mouse grabber status on + // release). + qFGDebug() << "QFG: re-grabbing" << grabber; + grabber->grabMouse(); +} + +void PressDelayHandler::ungrabMouse(QGraphicsItem* grabber) const +{ + if (!grabber) + return; + + // GraphicsView Mouse Handling Workaround #1: + // we need to ungrab the mouse before re-sending the press, + // since the scene had already set the mouse grabber to the + // original (and consumed) event's receiver + qFGDebug() << "QFG: ungrabbing" << grabber; + grabber->ungrabMouse(); +} + +MousePressDelayHandler::MousePressDelayHandler(QObject *parent) + : PressDelayHandler(parent) + , m_mouseButton(Qt::NoButton) + , m_mouseEventSource(Qt::MouseEventNotSynthesized) +{ + qFGDebug("QFG: create MousePressDelayHandler"); +} + +void MousePressDelayHandler::saveDelayEventInfo(QEvent* e) +{ + QMouseEvent* me = dynamic_cast(e); + if (!me) { + qWarning() << "MousePressDelayHandler handling event not QMouseEvent"; + return; + } + + setPressTarget(QApplication::widgetAt(me->globalPos())); + m_mouseButton = me->button(); + m_mouseEventSource = me->source(); +} + +QEvent* MousePressDelayHandler::copyEvent(QEvent *e) { switch (e->type()) { case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseMove: { - QMouseEvent *me = static_cast(e); + QMouseEvent *me = dynamic_cast(e); QMouseEvent *cme = new QMouseEvent(me->type(), QPoint(0, 0), me->windowPos(), me->screenPos(), me->button(), me->buttons(), me->modifiers(), me->source()); return cme; @@ -83,10 +330,10 @@ static QMouseEvent *copyMouseEvent(QEvent *e) case QEvent::GraphicsSceneMousePress: case QEvent::GraphicsSceneMouseRelease: case QEvent::GraphicsSceneMouseMove: { - QGraphicsSceneMouseEvent *me = static_cast(e); + QGraphicsSceneMouseEvent *me = dynamic_cast(e); #if 1 QEvent::Type met = me->type() == QEvent::GraphicsSceneMousePress ? QEvent::MouseButtonPress : - (me->type() == QEvent::GraphicsSceneMouseRelease ? QEvent::MouseButtonRelease : QEvent::MouseMove); + (me->type() == QEvent::GraphicsSceneMouseRelease ? QEvent::MouseButtonRelease : QEvent::MouseMove); QMouseEvent *cme = new QMouseEvent(met, QPoint(0, 0), QPoint(0, 0), me->screenPos(), me->button(), me->buttons(), me->modifiers(), me->source()); return cme; @@ -118,216 +365,193 @@ static QMouseEvent *copyMouseEvent(QEvent *e) } } -class PressDelayHandler : public QObject +void MousePressDelayHandler::sendEvent(QEvent *e, QObject* target) { -private: - PressDelayHandler(QObject *parent = nullptr) - : QObject(parent) - , pressDelayTimer(0) - , sendingEvent(false) - , mouseButton(Qt::NoButton) - , mouseTarget(nullptr) - , mouseEventSource(Qt::MouseEventNotSynthesized) - { } + QMouseEvent* me = dynamic_cast(e); + QWidget* targetWidget = dynamic_cast(target); -public: - enum { - UngrabMouseBefore = 1, - RegrabMouseAfterwards = 2 - }; - - static PressDelayHandler *instance() - { - static PressDelayHandler *inst = nullptr; - if (!inst) - inst = new PressDelayHandler(QCoreApplication::instance()); - return inst; + if (!targetWidget) { + return; } - bool shouldEventBeIgnored(QEvent *) const - { - return sendingEvent; + if (me) { + QMouseEvent copy(me->type(), targetWidget->mapFromGlobal(me->globalPos()), + targetWidget->topLevelWidget()->mapFromGlobal(me->globalPos()), me->screenPos(), + me->button(), me->buttons(), me->modifiers(), me->source()); + qt_sendSpontaneousEvent(targetWidget, ©); + } +} + +void MousePressDelayHandler::releaseAllPressed() +{ + QPoint farFarAway(-QWIDGETSIZE_MAX, -QWIDGETSIZE_MAX); + + qFGDebug() << "QFG: sending a fake mouse release at far-far-away to " << pressTarget(); + QMouseEvent re(QEvent::MouseButtonRelease, QPoint(), farFarAway, farFarAway, + m_mouseButton, QApplication::mouseButtons() & ~m_mouseButton, + QApplication::keyboardModifiers(), m_mouseEventSource); + sendSpontaneousEvent(&re, pressTarget(), RegrabMouseAfterwards); +} + +TouchPressDelayHandler::TouchPressDelayHandler(QObject *parent) + : PressDelayHandler(parent) +{ + qFGDebug("QFG: create TouchPressDelayHandler"); +} + +void TouchPressDelayHandler::saveDelayEventInfo(QEvent* e) +{ + QTouchEvent* te = dynamic_cast(e); + if (!te) { + qWarning() << "TouchPressDelayHandler handling event not QTouchEvent"; + } + setPressTarget(te->window()); + m_touchBeginPoint = te->touchPoints().at(0); + m_device = te->device(); +} + +void TouchPressDelayHandler::releaseAllPressed() +{ + qFGDebug() << "QFG: sending a fake touch cancel to " << pressTarget(); + QTouchEvent te(QEvent::TouchCancel, m_device, QApplication::keyboardModifiers(), + Qt::TouchPointReleased, QList{m_touchBeginPoint}); + te.setWindow(dynamic_cast(pressTarget().data())); + sendSpontaneousEvent(&te, pressTarget(), 0); +} + +void TouchPressDelayHandler::sendEvent(QEvent *e, QObject* target) +{ + QTouchEvent* te = dynamic_cast(e); + QWindow* targetWindow = dynamic_cast(target); + + if (!targetWindow) { + return; + } + if (!te) { + return; } - bool isDelaying() const - { - return !pressDelayEvent.isNull(); + // map to window pos for send to window + QTouchEvent::TouchPoint touchPoint = (!te->touchPoints().empty()) ? te->touchPoints().at(0) : QTouchEvent::TouchPoint(); + const QPointF screenPos = touchPoint.screenPos(); + const QPointF delta = screenPos - screenPos.toPoint(); + const QPointF windowPos = targetWindow->mapFromGlobal(screenPos.toPoint()) + delta; + const QPointF startPos = targetWindow->mapFromGlobal(touchPoint.startScreenPos().toPoint()) + delta; + const QPointF lastPos = targetWindow->mapFromGlobal(touchPoint.lastScreenPos().toPoint()) + delta; + + touchPoint.setPos(windowPos); + touchPoint.setStartPos(startPos); + touchPoint.setLastPos(lastPos); + + qFGDebug() << "QFG: sending" << te->type() << "event to" << targetWindow; + // send touch event to window because there is a grab mechanism for touch events + QTouchEvent copy(te->type(), te->device(), te->modifiers(), + te->touchPointStates(), QList{touchPoint}); + copy.setWindow(targetWindow); + qt_sendSpontaneousEvent(targetWindow, ©); + + // when Qt::AA_SynthesizeMouseForUnhandledTouchEvents is set, + // Qt will send a fake mouse eventif touch event not accept. + // The touch event we send here will not send the fake mouse event, + // so send it here. + if ((qApp->testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents) && !copy.isAccepted()) + || copy.type() == QEvent::TouchCancel) { + sendMouseEventFromTouch(©, targetWindow); + } +} + +void TouchPressDelayHandler::sendMouseEventFromTouch(QTouchEvent *te, QWindow *target) +{ + if (te->type() == QEvent::TouchCancel) { + QPoint farFarAway(-QWIDGETSIZE_MAX, -QWIDGETSIZE_MAX); + + qFGDebug() << "QFG: sending a fake mouse release at far-far-away to " << pressTarget(); + QMouseEvent fake(QEvent::MouseButtonRelease, QPoint(), farFarAway, farFarAway, + Qt::LeftButton, QApplication::mouseButtons() & ~Qt::LeftButton, + QApplication::keyboardModifiers(), Qt::MouseEventSynthesizedByQt); + qt_sendSpontaneousEvent(target, &fake); + return; } - void pressed(QEvent *e, int delay) - { - if (!pressDelayEvent) { - pressDelayEvent.reset(copyMouseEvent(e)); - pressDelayTimer = startTimer(delay); - mouseTarget = QApplication::widgetAt(pressDelayEvent->globalPos()); - mouseButton = pressDelayEvent->button(); - mouseEventSource = pressDelayEvent->source(); - qFGDebug("QFG: consuming/delaying mouse press"); - } else { - qFGDebug("QFG: NOT consuming/delaying mouse press"); + QTouchEvent::TouchPoint touchPoint = te->touchPoints().at(0); + const QPointF screenPos = touchPoint.screenPos(); + const QPointF delta = screenPos - screenPos.toPoint(); + const QPointF pos = target->mapFromGlobal(screenPos.toPoint()) + delta; + + QEvent::Type mouseEventType = QEvent::MouseMove; + Qt::MouseButton button = Qt::NoButton; + Qt::MouseButtons buttons = Qt::LeftButton; + + switch (touchPoint.state()) { + case Qt::TouchPointPressed: + if (isDoubleClick()) { + mouseEventType = QEvent::MouseButtonDblClick; + button = Qt::NoButton; + m_mousePressTime = QTime(); } - e->setAccepted(true); + else { + mouseEventType = QEvent::MouseButtonPress; + button = Qt::LeftButton; + m_mousePressTime = QTime::currentTime(); + } + break; + case Qt::TouchPointReleased: + mouseEventType = QEvent::MouseButtonRelease; + button = Qt::LeftButton; + buttons = Qt::NoButton; + break; + default: + break; } - bool released(QEvent *e, bool scrollerWasActive, bool scrollerIsActive) - { - // consume this event if the scroller was or is active - bool result = scrollerWasActive || scrollerIsActive; - - // stop the timer - if (pressDelayTimer) { - killTimer(pressDelayTimer); - pressDelayTimer = 0; - } - // we still haven't even sent the press, so do it now - if (pressDelayEvent && mouseTarget && !scrollerIsActive) { - QScopedPointer releaseEvent(copyMouseEvent(e)); - - qFGDebug() << "QFG: re-sending mouse press (due to release) for " << mouseTarget; - sendMouseEvent(pressDelayEvent.data(), UngrabMouseBefore); - - qFGDebug() << "QFG: faking mouse release (due to release) for " << mouseTarget; - sendMouseEvent(releaseEvent.data()); - - result = true; // consume this event - } else if (mouseTarget && scrollerIsActive) { - // we grabbed the mouse expicitly when the scroller became active, so undo that now - sendMouseEvent(nullptr, UngrabMouseBefore); - } - pressDelayEvent.reset(nullptr); - mouseTarget = nullptr; - return result; + if (mouseEventType == QEvent::MouseButtonPress) { + qFGDebug() << "QFG: send fake move event to" << target; + QMouseEvent fake(QEvent::MouseMove, pos, pos, screenPos, + Qt::NoButton, Qt::NoButton, + QApplication::keyboardModifiers(), Qt::MouseEventSynthesizedByQt); + qt_sendSpontaneousEvent(target, &fake); } - void scrollerWasIntercepted() - { - qFGDebug("QFG: deleting delayed mouse press, since scroller was only intercepted"); - if (pressDelayEvent) { - // we still haven't even sent the press, so just throw it away now - if (pressDelayTimer) { - killTimer(pressDelayTimer); - pressDelayTimer = 0; - } - pressDelayEvent.reset(nullptr); - } - mouseTarget = nullptr; + // will not synthesize QEvent::NonClientAreaMouseButtonDblClick from touch + qFGDebug() << "QFG: send fake " << mouseEventType << "to" << target; + QMouseEvent fake(mouseEventType, pos, pos, screenPos, + button, buttons, QApplication::keyboardModifiers(), Qt::MouseEventSynthesizedByQt); + qt_sendSpontaneousEvent(target, &fake); +} + +bool TouchPressDelayHandler::isDoubleClick() const +{ + // if not save time, return false then save current time + if (m_mousePressTime.isNull()) + return false; + + int doubleClickInterval = static_cast(QGuiApplication::styleHints()->mouseDoubleClickInterval()); + int elapsed = m_mousePressTime.msecsTo(QTime::currentTime()); + bool doubleClick = elapsed < doubleClickInterval; + + return doubleClick; +} + +QEvent* TouchPressDelayHandler::copyEvent(QEvent *e) +{ + switch (e->type()) { + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + case QEvent::TouchCancel: { + QTouchEvent* te = dynamic_cast(e); + QTouchEvent* copy = new QTouchEvent(te->type(), te->device(), te->modifiers(), + te->touchPointStates(), te->touchPoints()); + copy->setWindow(te->window()); + return copy; + break; + } + default: + break; } - void scrollerBecameActive() - { - if (pressDelayEvent) { - // we still haven't even sent the press, so just throw it away now - qFGDebug("QFG: deleting delayed mouse press, since scroller is active now"); - if (pressDelayTimer) { - killTimer(pressDelayTimer); - pressDelayTimer = 0; - } - pressDelayEvent.reset(nullptr); - mouseTarget = nullptr; - } else if (mouseTarget) { - // we did send a press, so we need to fake a release now - - // release all pressed mouse buttons - /* Qt::MouseButtons mouseButtons = QGuiApplication::mouseButtons(); - for (int i = 0; i < 32; ++i) { - if (mouseButtons & (1 << i)) { - Qt::MouseButton b = static_cast(1 << i); - mouseButtons &= ~b; - QPoint farFarAway(-QWIDGETSIZE_MAX, -QWIDGETSIZE_MAX); - - qFGDebug() << "QFG: sending a fake mouse release at far-far-away to " << mouseTarget; - QMouseEvent re(QEvent::MouseButtonRelease, QPoint(), farFarAway, - b, mouseButtons, QGuiApplication::keyboardModifiers()); - sendMouseEvent(&re); - } - }*/ - - QPoint farFarAway(-QWIDGETSIZE_MAX, -QWIDGETSIZE_MAX); - - qFGDebug() << "QFG: sending a fake mouse release at far-far-away to " << mouseTarget; - QMouseEvent re(QEvent::MouseButtonRelease, QPoint(), farFarAway, farFarAway, - mouseButton, QGuiApplication::mouseButtons() & ~mouseButton, - QGuiApplication::keyboardModifiers(), mouseEventSource); - sendMouseEvent(&re, RegrabMouseAfterwards); - // don't clear the mouseTarget just yet, since we need to explicitly ungrab the mouse on release! - } - } - -protected: - void timerEvent(QTimerEvent *e) override - { - if (e->timerId() == pressDelayTimer) { - if (pressDelayEvent && mouseTarget) { - qFGDebug() << "QFG: timer event: re-sending mouse press to " << mouseTarget; - sendMouseEvent(pressDelayEvent.data(), UngrabMouseBefore); - } - pressDelayEvent.reset(nullptr); - - if (pressDelayTimer) { - killTimer(pressDelayTimer); - pressDelayTimer = 0; - } - } - } - - void sendMouseEvent(QMouseEvent *me, int flags = 0) - { - if (mouseTarget) { - sendingEvent = true; - -#if QT_CONFIG(graphicsview) - QGraphicsItem *grabber = nullptr; - if (mouseTarget->parentWidget()) { - if (QGraphicsView *gv = qobject_cast(mouseTarget->parentWidget())) { - if (gv->scene()) - grabber = gv->scene()->mouseGrabberItem(); - } - } - - if (grabber && (flags & UngrabMouseBefore)) { - // GraphicsView Mouse Handling Workaround #1: - // we need to ungrab the mouse before re-sending the press, - // since the scene had already set the mouse grabber to the - // original (and consumed) event's receiver - qFGDebug() << "QFG: ungrabbing" << grabber; - grabber->ungrabMouse(); - } -#else - Q_UNUSED(flags); -#endif // QT_CONFIG(graphicsview) - - if (me) { - QMouseEvent copy(me->type(), mouseTarget->mapFromGlobal(me->globalPos()), - mouseTarget->topLevelWidget()->mapFromGlobal(me->globalPos()), me->screenPos(), - me->button(), me->buttons(), me->modifiers(), me->source()); - qt_sendSpontaneousEvent(mouseTarget, ©); - } - -#if QT_CONFIG(graphicsview) - if (grabber && (flags & RegrabMouseAfterwards)) { - // GraphicsView Mouse Handling Workaround #2: - // we need to re-grab the mouse after sending a faked mouse - // release, since we still need the mouse moves for the gesture - // (the scene will clear the item's mouse grabber status on - // release). - qFGDebug() << "QFG: re-grabbing" << grabber; - grabber->grabMouse(); - } -#endif - sendingEvent = false; - } - } - - -private: - int pressDelayTimer; - QScopedPointer pressDelayEvent; - bool sendingEvent; - Qt::MouseButton mouseButton; - QPointer mouseTarget; - Qt::MouseEventSource mouseEventSource; -}; - + return nullptr; +} /*! \internal @@ -350,15 +574,16 @@ QFlickGesture::QFlickGesture(QObject *receiver, Qt::MouseButton button, QObject { d_func()->q_ptr = this; d_func()->receiver = receiver; - d_func()->receiverScroller = (receiver && QScroller::hasScroller(receiver)) ? QScroller::scroller(receiver) : nullptr; + d_func()->receiverScroller = (receiver && QScroller::hasScroller(receiver)) ? QScroller::scroller(receiver) : 0; d_func()->button = button; + d_func()->pressDelayHandler = PressDelayHandler::create(button); } QFlickGesture::~QFlickGesture() { } QFlickGesturePrivate::QFlickGesturePrivate() - : receiverScroller(nullptr), button(Qt::NoButton), macIgnoreWheel(false) + : receiverScroller(0), button(Qt::NoButton), macIgnoreWheel(false), pressDelayHandler(nullptr) { } @@ -415,16 +640,16 @@ QGestureRecognizer::Result QFlickGestureRecognizer::recognize(QGesture *state, #endif // this is only set for events that we inject into the event loop via sendEvent() - if (PressDelayHandler::instance()->shouldEventBeIgnored(event)) { + if (d->pressDelayHandler->shouldEventBeIgnored()) { //qFGDebug() << state << "QFG: ignored event: " << event->type(); return Ignore; } - const QMouseEvent *me = nullptr; + const QMouseEvent *me = 0; #if QT_CONFIG(graphicsview) - const QGraphicsSceneMouseEvent *gsme = nullptr; + const QGraphicsSceneMouseEvent *gsme = 0; #endif - const QTouchEvent *te = nullptr; + const QTouchEvent *te = 0; QPoint globalPos; // qFGDebug() << "FlickGesture "<receiver<<"event"<type()<<"button"<scrollerBecameActive(); + d->pressDelayHandler->scrollerBecameActive(); else if (scrollerWasScrolling && (scroller->state() == QScroller::Dragging || scroller->state() == QScroller::Inactive)) - PressDelayHandler::instance()->scrollerWasIntercepted(); + d->pressDelayHandler->scrollerWasIntercepted(); } if (!inputType) { @@ -644,6 +869,7 @@ QGestureRecognizer::Result QFlickGestureRecognizer::recognize(QGesture *state, } else { switch (event->type()) { case QEvent::MouseButtonPress: + case QEvent::TouchBegin: #if QT_CONFIG(graphicsview) case QEvent::GraphicsSceneMousePress: #endif @@ -652,24 +878,21 @@ QGestureRecognizer::Result QFlickGestureRecognizer::recognize(QGesture *state, if (pressDelay > 0) { result |= ConsumeEventHint; - PressDelayHandler::instance()->pressed(event, pressDelay); + d->pressDelayHandler->pressed(event, pressDelay); event->accept(); } } - Q_FALLTHROUGH(); - case QEvent::TouchBegin: q->setHotSpot(globalPos); result |= scrollerIsActive ? TriggerGesture : MayBeGesture; break; case QEvent::MouseMove: + case QEvent::TouchUpdate: #if QT_CONFIG(graphicsview) case QEvent::GraphicsSceneMouseMove: #endif - if (PressDelayHandler::instance()->isDelaying()) + if (d->pressDelayHandler->isDelaying()) result |= ConsumeEventHint; - Q_FALLTHROUGH(); - case QEvent::TouchUpdate: result |= scrollerIsActive ? TriggerGesture : Ignore; break; @@ -677,10 +900,9 @@ QGestureRecognizer::Result QFlickGestureRecognizer::recognize(QGesture *state, case QEvent::GraphicsSceneMouseRelease: #endif case QEvent::MouseButtonRelease: - if (PressDelayHandler::instance()->released(event, scrollerWasDragging || scrollerWasScrolling, scrollerIsActive)) - result |= ConsumeEventHint; - Q_FALLTHROUGH(); case QEvent::TouchEnd: + if (d->pressDelayHandler->released(event, scrollerWasDragging || scrollerWasScrolling, scrollerIsActive)) + result |= ConsumeEventHint; result |= scrollerIsActive ? FinishGesture : CancelGesture; break; diff --git a/src/widgets/util/qflickgesture_p.h b/src/widgets/util/qflickgesture_p.h index 0b475160..a41633db 100644 --- a/src/widgets/util/qflickgesture_p.h +++ b/src/widgets/util/qflickgesture_p.h @@ -71,12 +71,65 @@ class Q_WIDGETS_EXPORT QFlickGesture : public QGesture Q_DECLARE_PRIVATE(QFlickGesture) public: - QFlickGesture(QObject *receiver, Qt::MouseButton button, QObject *parent = nullptr); + QFlickGesture(QObject *receiver, Qt::MouseButton button, QObject *parent = 0); ~QFlickGesture(); friend class QFlickGestureRecognizer; }; +class PressDelayHandler : public QObject +{ + Q_OBJECT +public: + PressDelayHandler(QObject *parent = nullptr); + enum { + UngrabMouseBefore = 1, + RegrabMouseAfterwards = 2 + }; + + static PressDelayHandler* create(Qt::MouseButton button = Qt::LeftButton); + + bool shouldEventBeIgnored() const; + bool isDelaying() const; + + void pressed(QEvent *e, int delay); + bool released(QEvent *e, bool scrollerWasActive, bool scrollerIsActive); + + void scrollerWasIntercepted(); + void scrollerBecameActive(); + +protected: + void sendSpontaneousEvent(QEvent *e, QObject *target, int flags); + QPointer pressTarget() const; + void setPressTarget(QObject* pressTarget); + void resetPressTarget(); + +private slots: + void onDelayTimeout(); +private: + virtual void saveDelayEventInfo(QEvent* e) = 0; + virtual QEvent* copyEvent(QEvent* e) = 0; + + virtual void sendEvent(QEvent *e, QObject* target) = 0; + virtual void releaseAllPressed() = 0; + + QGraphicsItem* mouseGrabberItem() const; + void grabMouse(QGraphicsItem* grabber) const; + void ungrabMouse(QGraphicsItem* grabber) const; + + bool hasSavedDelayEvent() const; + void resetDelayEvent(); + void stopTimer(); + void startTimer(int timeout); + +private: + // there will only be one QScroller active at same time + static QTimer* m_pressDelayTimer; + static bool m_sendingEvent; + static QScopedPointer m_pressDelayEvent; + static QPointer m_pressTarget; +}; + class QFlickGesturePrivate : public QGesturePrivate { Q_DECLARE_PUBLIC(QFlickGesture) @@ -87,6 +140,7 @@ public: QScroller *receiverScroller; Qt::MouseButton button; // NoButton == Touch bool macIgnoreWheel; + PressDelayHandler *pressDelayHandler; }; class QFlickGestureRecognizer : public QGestureRecognizer @@ -102,6 +156,43 @@ private: Qt::MouseButton button; // NoButton == Touch }; +class MousePressDelayHandler : public PressDelayHandler +{ +public: + MousePressDelayHandler(QObject *parent = nullptr); + +private: + void saveDelayEventInfo(QEvent* e) override; + + QEvent* copyEvent(QEvent *e) override; + void sendEvent(QEvent *e, QObject* target) override; + void releaseAllPressed() override; + + Qt::MouseButton m_mouseButton; + Qt::MouseEventSource m_mouseEventSource; +}; + +class TouchPressDelayHandler : public PressDelayHandler +{ +public: + TouchPressDelayHandler(QObject *parent = nullptr); + +private: + void saveDelayEventInfo(QEvent* e) override; + + QEvent* copyEvent(QEvent *e) override; + void sendEvent(QEvent *e, QObject* target) override; + void releaseAllPressed() override; + + void sendMouseEventFromTouch(QTouchEvent *te, QWindow *target); + bool isDoubleClick() const; + + QPointer m_targetWidget; + QTouchDevice* m_device; + QTouchEvent::TouchPoint m_touchBeginPoint; + QTime m_mousePressTime; +}; + QT_END_NAMESPACE #endif // QT_NO_GESTURES