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