1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qgesture.h"
5#include "qapplication.h"
6#include "qevent.h"
7#include "qwidget.h"
8#if QT_CONFIG(graphicsview)
9#include "qgraphicsitem.h"
10#include "qgraphicsscene.h"
11#include "qgraphicssceneevent.h"
12#include "qgraphicsview.h"
13#endif
14#include "qscroller.h"
15#include <QtGui/qpointingdevice.h>
16#include "private/qapplication_p.h"
17#include "private/qevent_p.h"
18#include "private/qflickgesture_p.h"
19#include "qdebug.h"
20
21#ifndef QT_NO_GESTURES
22
23QT_BEGIN_NAMESPACE
24
25//#define QFLICKGESTURE_DEBUG
26
27#ifdef QFLICKGESTURE_DEBUG
28# define qFGDebug qDebug
29#else
30# define qFGDebug while (false) qDebug
31#endif
32
33extern bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event);
34
35static QMouseEvent *copyMouseEvent(QEvent *e)
36{
37 switch (e->type()) {
38 case QEvent::MouseButtonPress:
39 case QEvent::MouseButtonRelease:
40 case QEvent::MouseMove: {
41 return static_cast<QMouseEvent *>(e->clone());
42 }
43#if QT_CONFIG(graphicsview)
44 case QEvent::GraphicsSceneMousePress:
45 case QEvent::GraphicsSceneMouseRelease:
46 case QEvent::GraphicsSceneMouseMove: {
47 QGraphicsSceneMouseEvent *me = static_cast<QGraphicsSceneMouseEvent *>(e);
48 QEvent::Type met = me->type() == QEvent::GraphicsSceneMousePress ? QEvent::MouseButtonPress :
49 (me->type() == QEvent::GraphicsSceneMouseRelease ? QEvent::MouseButtonRelease : QEvent::MouseMove);
50 QMouseEvent *cme = new QMouseEvent(met, QPoint(0, 0), QPoint(0, 0), me->screenPos(),
51 me->button(), me->buttons(), me->modifiers(), me->source());
52 cme->setTimestamp(me->timestamp());
53 return cme;
54 }
55#endif // QT_CONFIG(graphicsview)
56 default:
57 return nullptr;
58 }
59}
60
61class PressDelayHandler : public QObject
62{
63private:
64 PressDelayHandler(QObject *parent = nullptr)
65 : QObject(parent)
66 , pressDelayTimer(0)
67 , sendingEvent(false)
68 , mouseButton(Qt::NoButton)
69 , mouseTarget(nullptr)
70 , mouseEventSource(Qt::MouseEventNotSynthesized)
71 { }
72
73public:
74 enum {
75 UngrabMouseBefore = 1,
76 RegrabMouseAfterwards = 2
77 };
78
79 static PressDelayHandler *instance()
80 {
81 static PressDelayHandler *inst = nullptr;
82 if (!inst)
83 inst = new PressDelayHandler(QCoreApplication::instance());
84 return inst;
85 }
86
87 bool shouldEventBeIgnored(QEvent *) const
88 {
89 return sendingEvent;
90 }
91
92 bool isDelaying() const
93 {
94 return !pressDelayEvent.isNull();
95 }
96
97 void pressed(QEvent *e, int delay)
98 {
99 if (!pressDelayEvent) {
100 pressDelayEvent.reset(other: copyMouseEvent(e));
101 pressDelayTimer = startTimer(interval: delay);
102 mouseTarget = QApplication::widgetAt(p: pressDelayEvent->globalPosition().toPoint());
103 mouseButton = pressDelayEvent->button();
104 mouseEventSource = pressDelayEvent->source();
105 qFGDebug(msg: "QFG: consuming/delaying mouse press");
106 } else {
107 qFGDebug(msg: "QFG: NOT consuming/delaying mouse press");
108 }
109 e->setAccepted(true);
110 }
111
112 bool released(QEvent *e, bool scrollerWasActive, bool scrollerIsActive)
113 {
114 // consume this event if the scroller was or is active
115 bool result = scrollerWasActive || scrollerIsActive;
116
117 // stop the timer
118 if (pressDelayTimer) {
119 killTimer(id: pressDelayTimer);
120 pressDelayTimer = 0;
121 }
122 // we still haven't even sent the press, so do it now
123 if (pressDelayEvent && mouseTarget && !scrollerIsActive) {
124 QScopedPointer<QMouseEvent> releaseEvent(copyMouseEvent(e));
125
126 qFGDebug() << "QFG: re-sending mouse press (due to release) for " << mouseTarget;
127 sendMouseEvent(me: pressDelayEvent.data(), flags: UngrabMouseBefore);
128
129 qFGDebug() << "QFG: faking mouse release (due to release) for " << mouseTarget;
130 sendMouseEvent(me: releaseEvent.data());
131
132 result = true; // consume this event
133 } else if (mouseTarget && scrollerIsActive) {
134 // we grabbed the mouse explicitly when the scroller became active, so undo that now
135 sendMouseEvent(me: nullptr, flags: UngrabMouseBefore);
136 }
137 pressDelayEvent.reset(other: nullptr);
138 mouseTarget = nullptr;
139 return result;
140 }
141
142 void scrollerWasIntercepted()
143 {
144 qFGDebug(msg: "QFG: deleting delayed mouse press, since scroller was only intercepted");
145 if (pressDelayEvent) {
146 // we still haven't even sent the press, so just throw it away now
147 if (pressDelayTimer) {
148 killTimer(id: pressDelayTimer);
149 pressDelayTimer = 0;
150 }
151 pressDelayEvent.reset(other: nullptr);
152 }
153 mouseTarget = nullptr;
154 }
155
156 void scrollerBecameActive(Qt::KeyboardModifiers eventModifiers, Qt::MouseButtons eventButtons)
157 {
158 if (pressDelayEvent) {
159 // we still haven't even sent the press, so just throw it away now
160 qFGDebug(msg: "QFG: deleting delayed mouse press, since scroller is active now");
161 if (pressDelayTimer) {
162 killTimer(id: pressDelayTimer);
163 pressDelayTimer = 0;
164 }
165 pressDelayEvent.reset(other: nullptr);
166 mouseTarget = nullptr;
167 } else if (mouseTarget) {
168 // we did send a press, so we need to fake a release now
169 QPoint farFarAway(-QWIDGETSIZE_MAX, -QWIDGETSIZE_MAX);
170
171 qFGDebug() << "QFG: sending a fake mouse release at far-far-away to " << mouseTarget;
172 QMouseEvent re(QEvent::MouseButtonRelease, QPoint(), farFarAway, farFarAway,
173 mouseButton, eventButtons & ~mouseButton,
174 eventModifiers, mouseEventSource);
175 sendMouseEvent(me: &re, flags: RegrabMouseAfterwards);
176 // don't clear the mouseTarget just yet, since we need to explicitly ungrab the mouse on release!
177 }
178 }
179
180protected:
181 void timerEvent(QTimerEvent *e) override
182 {
183 if (e->timerId() == pressDelayTimer) {
184 if (pressDelayEvent && mouseTarget) {
185 qFGDebug() << "QFG: timer event: re-sending mouse press to " << mouseTarget;
186 sendMouseEvent(me: pressDelayEvent.data(), flags: UngrabMouseBefore);
187 }
188 pressDelayEvent.reset(other: nullptr);
189
190 if (pressDelayTimer) {
191 killTimer(id: pressDelayTimer);
192 pressDelayTimer = 0;
193 }
194 }
195 }
196
197 void sendMouseEvent(QMouseEvent *me, int flags = 0)
198 {
199 if (mouseTarget) {
200 sendingEvent = true;
201
202#if QT_CONFIG(graphicsview)
203 QGraphicsItem *grabber = nullptr;
204 if (mouseTarget->parentWidget()) {
205 if (QGraphicsView *gv = qobject_cast<QGraphicsView *>(object: mouseTarget->parentWidget())) {
206 if (gv->scene())
207 grabber = gv->scene()->mouseGrabberItem();
208 }
209 }
210
211 if (grabber && (flags & UngrabMouseBefore)) {
212 // GraphicsView Mouse Handling Workaround #1:
213 // we need to ungrab the mouse before re-sending the press,
214 // since the scene had already set the mouse grabber to the
215 // original (and consumed) event's receiver
216 qFGDebug() << "QFG: ungrabbing" << grabber;
217 grabber->ungrabMouse();
218 }
219#else
220 Q_UNUSED(flags);
221#endif // QT_CONFIG(graphicsview)
222
223 if (me) {
224 QMouseEvent copy(me->type(), mouseTarget->mapFromGlobal(me->globalPosition()),
225 mouseTarget->topLevelWidget()->mapFromGlobal(me->globalPosition()), me->globalPosition(),
226 me->button(), me->buttons(), me->modifiers(),
227 me->source(), me->pointingDevice());
228 copy.setTimestamp(me->timestamp());
229 qt_sendSpontaneousEvent(receiver: mouseTarget, event: &copy);
230 }
231
232#if QT_CONFIG(graphicsview)
233 if (grabber && (flags & RegrabMouseAfterwards)) {
234 // GraphicsView Mouse Handling Workaround #2:
235 // we need to re-grab the mouse after sending a faked mouse
236 // release, since we still need the mouse moves for the gesture
237 // (the scene will clear the item's mouse grabber status on
238 // release).
239 qFGDebug() << "QFG: re-grabbing" << grabber;
240 grabber->grabMouse();
241 }
242#endif
243 sendingEvent = false;
244 }
245 }
246
247
248private:
249 int pressDelayTimer;
250 QScopedPointer<QMouseEvent> pressDelayEvent;
251 bool sendingEvent;
252 Qt::MouseButton mouseButton;
253 QPointer<QWidget> mouseTarget;
254 Qt::MouseEventSource mouseEventSource;
255};
256
257
258/*!
259 \internal
260 \class QFlickGesture
261 \since 4.8
262 \brief The QFlickGesture class describes a flicking gesture made by the user.
263 \ingroup gestures
264 The QFlickGesture is more complex than the QPanGesture that uses QScroller and QScrollerProperties
265 to decide if it is triggered.
266 This gesture is reacting on touch event as compared to the QMouseFlickGesture.
267
268 \sa {Gestures in Widgets and Graphics View}, QScroller, QScrollerProperties, QMouseFlickGesture
269*/
270
271/*!
272 \internal
273*/
274QFlickGesture::QFlickGesture(QObject *receiver, Qt::MouseButton button, QObject *parent)
275 : QGesture(*new QFlickGesturePrivate, parent)
276{
277 d_func()->q_ptr = this;
278 d_func()->receiver = receiver;
279 d_func()->receiverScroller = (receiver && QScroller::hasScroller(target: receiver)) ? QScroller::scroller(target: receiver) : nullptr;
280 d_func()->button = button;
281}
282
283QFlickGesture::~QFlickGesture()
284{ }
285
286QFlickGesturePrivate::QFlickGesturePrivate()
287 : receiverScroller(nullptr), button(Qt::NoButton), macIgnoreWheel(false)
288{ }
289
290
291//
292// QFlickGestureRecognizer
293//
294
295
296QFlickGestureRecognizer::QFlickGestureRecognizer(Qt::MouseButton button)
297{
298 this->button = button;
299}
300
301/*! \reimp
302 */
303QGesture *QFlickGestureRecognizer::create(QObject *target)
304{
305#if QT_CONFIG(graphicsview)
306 QGraphicsObject *go = qobject_cast<QGraphicsObject*>(object: target);
307 if (go && button == Qt::NoButton) {
308 go->setAcceptTouchEvents(true);
309 }
310#endif
311 return new QFlickGesture(target, button);
312}
313
314/*! \internal
315 The recognize function detects a touch event suitable to start the attached QScroller.
316 The QFlickGesture will be triggered as soon as the scroller is no longer in the state
317 QScroller::Inactive or QScroller::Pressed. It will be finished or canceled
318 at the next QEvent::TouchEnd.
319 Note that the QScroller might continue scrolling (kinetically) at this point.
320 */
321QGestureRecognizer::Result QFlickGestureRecognizer::recognize(QGesture *state,
322 QObject *watched,
323 QEvent *event)
324{
325 Q_UNUSED(watched);
326
327 Q_CONSTINIT static QElapsedTimer monotonicTimer;
328 if (!monotonicTimer.isValid())
329 monotonicTimer.start();
330
331 QFlickGesture *q = static_cast<QFlickGesture *>(state);
332 QFlickGesturePrivate *d = q->d_func();
333
334 QScroller *scroller = d->receiverScroller;
335 if (!scroller)
336 return Ignore; // nothing to do without a scroller?
337
338 QWidget *receiverWidget = qobject_cast<QWidget *>(o: d->receiver);
339#if QT_CONFIG(graphicsview)
340 QGraphicsObject *receiverGraphicsObject = qobject_cast<QGraphicsObject *>(object: d->receiver);
341#endif
342
343 // this is only set for events that we inject into the event loop via sendEvent()
344 if (PressDelayHandler::instance()->shouldEventBeIgnored(event)) {
345 //qFGDebug() << state << "QFG: ignored event: " << event->type();
346 return Ignore;
347 }
348
349 const QMouseEvent *me = nullptr;
350#if QT_CONFIG(graphicsview)
351 const QGraphicsSceneMouseEvent *gsme = nullptr;
352#endif
353 const QTouchEvent *te = nullptr;
354 QPoint globalPos;
355
356 // qFGDebug() << "FlickGesture "<<state<<"watched:"<<watched<<"receiver"<<d->receiver<<"event"<<event->type()<<"button"<<button;
357
358 Qt::KeyboardModifiers keyboardModifiers = Qt::NoModifier;
359 Qt::MouseButtons mouseButtons = Qt::NoButton;
360 switch (event->type()) {
361 case QEvent::MouseButtonPress:
362 case QEvent::MouseButtonRelease:
363 case QEvent::MouseMove:
364 if (!receiverWidget)
365 return Ignore;
366 if (button != Qt::NoButton) {
367 me = static_cast<const QMouseEvent *>(event);
368 keyboardModifiers = me->modifiers();
369 mouseButtons = me->buttons();
370 globalPos = me->globalPosition().toPoint();
371 }
372 break;
373#if QT_CONFIG(graphicsview)
374 case QEvent::GraphicsSceneMousePress:
375 case QEvent::GraphicsSceneMouseRelease:
376 case QEvent::GraphicsSceneMouseMove:
377 if (!receiverGraphicsObject)
378 return Ignore;
379 if (button != Qt::NoButton) {
380 gsme = static_cast<const QGraphicsSceneMouseEvent *>(event);
381 keyboardModifiers = gsme->modifiers();
382 mouseButtons = gsme->buttons();
383 globalPos = gsme->screenPos();
384 }
385 break;
386#endif
387 case QEvent::TouchBegin:
388 case QEvent::TouchEnd:
389 case QEvent::TouchUpdate:
390 if (button == Qt::NoButton) {
391 te = static_cast<const QTouchEvent *>(event);
392 keyboardModifiers = te->modifiers();
393 if (!te->points().isEmpty())
394 globalPos = te->points().at(i: 0).globalPosition().toPoint();
395 }
396 break;
397
398 // consume all wheel events if the scroller is active
399 case QEvent::Wheel:
400 if (d->macIgnoreWheel || (scroller->state() != QScroller::Inactive))
401 return Ignore | ConsumeEventHint;
402 break;
403
404 // consume all dbl click events if the scroller is active
405 case QEvent::MouseButtonDblClick:
406 if (scroller->state() != QScroller::Inactive)
407 return Ignore | ConsumeEventHint;
408 break;
409
410 default:
411 break;
412 }
413
414 if (!me
415#if QT_CONFIG(graphicsview)
416 && !gsme
417#endif
418 && !te) // Neither mouse nor touch
419 return Ignore;
420
421 // get the current pointer position in local coordinates.
422 QPointF point;
423 QScroller::Input inputType = (QScroller::Input) 0;
424
425 switch (event->type()) {
426 case QEvent::MouseButtonPress:
427 if (me && me->button() == button && me->buttons() == button) {
428 point = me->globalPosition().toPoint();
429 inputType = QScroller::InputPress;
430 } else if (me) {
431 scroller->stop();
432 return CancelGesture;
433 }
434 break;
435 case QEvent::MouseButtonRelease:
436 if (me && me->button() == button) {
437 point = me->globalPosition().toPoint();
438 inputType = QScroller::InputRelease;
439 }
440 break;
441 case QEvent::MouseMove:
442 if (me && me->buttons() == button) {
443 point = me->globalPosition().toPoint();
444 inputType = QScroller::InputMove;
445 }
446 break;
447
448#if QT_CONFIG(graphicsview)
449 case QEvent::GraphicsSceneMousePress:
450 if (gsme && gsme->button() == button && gsme->buttons() == button) {
451 point = gsme->scenePos();
452 inputType = QScroller::InputPress;
453 } else if (gsme) {
454 scroller->stop();
455 return CancelGesture;
456 }
457 break;
458 case QEvent::GraphicsSceneMouseRelease:
459 if (gsme && gsme->button() == button) {
460 point = gsme->scenePos();
461 inputType = QScroller::InputRelease;
462 }
463 break;
464 case QEvent::GraphicsSceneMouseMove:
465 if (gsme && gsme->buttons() == button) {
466 point = gsme->scenePos();
467 inputType = QScroller::InputMove;
468 }
469 break;
470#endif
471
472 case QEvent::TouchBegin:
473 inputType = QScroller::InputPress;
474 Q_FALLTHROUGH();
475 case QEvent::TouchEnd:
476 if (!inputType)
477 inputType = QScroller::InputRelease;
478 Q_FALLTHROUGH();
479 case QEvent::TouchUpdate:
480 if (!inputType)
481 inputType = QScroller::InputMove;
482
483 if (te->pointingDevice()->type() == QInputDevice::DeviceType::TouchPad) {
484 if (te->points().size() != 2) // 2 fingers on pad
485 return Ignore;
486
487 point = te->points().at(i: 0).scenePressPosition() +
488 ((te->points().at(i: 0).scenePosition() - te->points().at(i: 0).scenePressPosition()) +
489 (te->points().at(i: 1).scenePosition() - te->points().at(i: 1).scenePressPosition())) / 2;
490 } else { // TouchScreen
491 if (te->points().size() != 1) // 1 finger on screen
492 return Ignore;
493
494 point = te->points().at(i: 0).scenePosition();
495 }
496 break;
497
498 default:
499 break;
500 }
501
502 // Check for an active scroller at globalPos
503 if (inputType == QScroller::InputPress) {
504 const auto activeScrollers = QScroller::activeScrollers();
505 for (QScroller *as : activeScrollers) {
506 if (as != scroller) {
507 QRegion scrollerRegion;
508
509 if (QWidget *w = qobject_cast<QWidget *>(o: as->target())) {
510 scrollerRegion = QRect(w->mapToGlobal(QPoint(0, 0)), w->size());
511#if QT_CONFIG(graphicsview)
512 } else if (QGraphicsObject *go = qobject_cast<QGraphicsObject *>(object: as->target())) {
513 if (const auto *scene = go->scene()) {
514 const auto goBoundingRectMappedToScene = go->mapToScene(rect: go->boundingRect());
515 const auto views = scene->views();
516 for (QGraphicsView *gv : views) {
517 scrollerRegion |= gv->mapFromScene(polygon: goBoundingRectMappedToScene)
518 .translated(offset: gv->mapToGlobal(QPoint(0, 0)));
519 }
520 }
521#endif
522 }
523 // active scrollers always have priority
524 if (scrollerRegion.contains(p: globalPos))
525 return Ignore;
526 }
527 }
528 }
529
530 bool scrollerWasDragging = (scroller->state() == QScroller::Dragging);
531 bool scrollerWasScrolling = (scroller->state() == QScroller::Scrolling);
532
533 if (inputType) {
534 if (QWidget *w = qobject_cast<QWidget *>(o: d->receiver))
535 point = w->mapFromGlobal(point.toPoint());
536#if QT_CONFIG(graphicsview)
537 else if (QGraphicsObject *go = qobject_cast<QGraphicsObject *>(object: d->receiver))
538 point = go->mapFromScene(point);
539#endif
540
541 // inform the scroller about the new event
542 scroller->handleInput(input: inputType, position: point, timestamp: monotonicTimer.elapsed());
543 }
544
545 // depending on the scroller state return the gesture state
546 Result result;
547 bool scrollerIsActive = (scroller->state() == QScroller::Dragging ||
548 scroller->state() == QScroller::Scrolling);
549
550 // Consume all mouse events while dragging or scrolling to avoid nasty
551 // side effects with Qt's standard widgets.
552 if ((me
553#if QT_CONFIG(graphicsview)
554 || gsme
555#endif
556 ) && scrollerIsActive)
557 result |= ConsumeEventHint;
558
559 // The only problem with this approach is that we consume the
560 // MouseRelease when we start the scrolling with a flick gesture, so we
561 // have to fake a MouseRelease "somewhere" to not mess with the internal
562 // states of Qt's widgets (a QPushButton would stay in 'pressed' state
563 // forever, if it doesn't receive a MouseRelease).
564 if (me
565#if QT_CONFIG(graphicsview)
566 || gsme
567#endif
568 ) {
569 if (!scrollerWasDragging && !scrollerWasScrolling && scrollerIsActive)
570 PressDelayHandler::instance()->scrollerBecameActive(eventModifiers: keyboardModifiers, eventButtons: mouseButtons);
571 else if (scrollerWasScrolling && (scroller->state() == QScroller::Dragging || scroller->state() == QScroller::Inactive))
572 PressDelayHandler::instance()->scrollerWasIntercepted();
573 }
574
575 if (!inputType) {
576 result |= Ignore;
577 } else {
578 switch (event->type()) {
579 case QEvent::MouseButtonPress:
580#if QT_CONFIG(graphicsview)
581 case QEvent::GraphicsSceneMousePress:
582#endif
583 if (scroller->state() == QScroller::Pressed) {
584 int pressDelay = int(1000 * scroller->scrollerProperties().scrollMetric(metric: QScrollerProperties::MousePressEventDelay).toReal());
585 if (pressDelay > 0) {
586 result |= ConsumeEventHint;
587
588 PressDelayHandler::instance()->pressed(e: event, delay: pressDelay);
589 event->accept();
590 }
591 }
592 Q_FALLTHROUGH();
593 case QEvent::TouchBegin:
594 q->setHotSpot(globalPos);
595 result |= scrollerIsActive ? TriggerGesture : MayBeGesture;
596 break;
597
598 case QEvent::MouseMove:
599#if QT_CONFIG(graphicsview)
600 case QEvent::GraphicsSceneMouseMove:
601#endif
602 if (PressDelayHandler::instance()->isDelaying())
603 result |= ConsumeEventHint;
604 Q_FALLTHROUGH();
605 case QEvent::TouchUpdate:
606 result |= scrollerIsActive ? TriggerGesture : Ignore;
607 break;
608
609#if QT_CONFIG(graphicsview)
610 case QEvent::GraphicsSceneMouseRelease:
611#endif
612 case QEvent::MouseButtonRelease:
613 if (PressDelayHandler::instance()->released(e: event, scrollerWasActive: scrollerWasDragging || scrollerWasScrolling, scrollerIsActive))
614 result |= ConsumeEventHint;
615 Q_FALLTHROUGH();
616 case QEvent::TouchEnd:
617 result |= scrollerIsActive ? FinishGesture : CancelGesture;
618 break;
619
620 default:
621 result |= Ignore;
622 break;
623 }
624 }
625 return result;
626}
627
628
629/*! \reimp
630 */
631void QFlickGestureRecognizer::reset(QGesture *state)
632{
633 QGestureRecognizer::reset(state);
634}
635
636QT_END_NAMESPACE
637
638#include "moc_qflickgesture_p.cpp"
639
640#endif // QT_NO_GESTURES
641

source code of qtbase/src/widgets/util/qflickgesture.cpp