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