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/qpointingdevice.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->scenePosition(), me->globalPosition(), |
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(copyMouseEvent(e)); |
161 | pressDelayTimer = startTimer(delay); |
162 | mouseTarget = QApplication::widgetAt(pressDelayEvent->globalPosition().toPoint()); |
163 | mouseButton = pressDelayEvent->button(); |
164 | mouseEventSource = pressDelayEvent->source(); |
165 | qFGDebug("QFG: consuming/delaying mouse press" ); |
166 | } else { |
167 | qFGDebug("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(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(pressDelayEvent.data(), UngrabMouseBefore); |
188 | |
189 | qFGDebug() << "QFG: faking mouse release (due to release) for " << mouseTarget; |
190 | sendMouseEvent(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(nullptr, UngrabMouseBefore); |
196 | } |
197 | pressDelayEvent.reset(nullptr); |
198 | mouseTarget = nullptr; |
199 | return result; |
200 | } |
201 | |
202 | void scrollerWasIntercepted() |
203 | { |
204 | qFGDebug("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(pressDelayTimer); |
209 | pressDelayTimer = 0; |
210 | } |
211 | pressDelayEvent.reset(nullptr); |
212 | } |
213 | mouseTarget = nullptr; |
214 | } |
215 | |
216 | void scrollerBecameActive(Qt::KeyboardModifiers eventModifiers, Qt::MouseButtons eventButtons) |
217 | { |
218 | if (pressDelayEvent) { |
219 | // we still haven't even sent the press, so just throw it away now |
220 | qFGDebug("QFG: deleting delayed mouse press, since scroller is active now" ); |
221 | if (pressDelayTimer) { |
222 | killTimer(pressDelayTimer); |
223 | pressDelayTimer = 0; |
224 | } |
225 | pressDelayEvent.reset(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, eventButtons & ~mouseButton, |
250 | eventModifiers, mouseEventSource); |
251 | sendMouseEvent(&re, 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(pressDelayEvent.data(), UngrabMouseBefore); |
263 | } |
264 | pressDelayEvent.reset(nullptr); |
265 | |
266 | if (pressDelayTimer) { |
267 | killTimer(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 *>(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->globalPosition()), |
301 | mouseTarget->topLevelWidget()->mapFromGlobal(me->globalPosition()), me->globalPosition(), |
302 | me->button(), me->buttons(), me->modifiers(), me->source()); |
303 | qt_sendSpontaneousEvent(mouseTarget, ©); |
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(receiver)) ? QScroller::scroller(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*>(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 *>(d->receiver); |
413 | #if QT_CONFIG(graphicsview) |
414 | QGraphicsObject *receiverGraphicsObject = qobject_cast<QGraphicsObject *>(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 | Qt::KeyboardModifiers keyboardModifiers = Qt::NoModifier; |
433 | Qt::MouseButtons mouseButtons = Qt::NoButton; |
434 | switch (event->type()) { |
435 | case QEvent::MouseButtonPress: |
436 | case QEvent::MouseButtonRelease: |
437 | case QEvent::MouseMove: |
438 | if (!receiverWidget) |
439 | return Ignore; |
440 | if (button != Qt::NoButton) { |
441 | me = static_cast<const QMouseEvent *>(event); |
442 | keyboardModifiers = me->modifiers(); |
443 | mouseButtons = me->buttons(); |
444 | globalPos = me->globalPosition().toPoint(); |
445 | } |
446 | break; |
447 | #if QT_CONFIG(graphicsview) |
448 | case QEvent::GraphicsSceneMousePress: |
449 | case QEvent::GraphicsSceneMouseRelease: |
450 | case QEvent::GraphicsSceneMouseMove: |
451 | if (!receiverGraphicsObject) |
452 | return Ignore; |
453 | if (button != Qt::NoButton) { |
454 | gsme = static_cast<const QGraphicsSceneMouseEvent *>(event); |
455 | keyboardModifiers = gsme->modifiers(); |
456 | mouseButtons = gsme->buttons(); |
457 | globalPos = gsme->screenPos(); |
458 | } |
459 | break; |
460 | #endif |
461 | case QEvent::TouchBegin: |
462 | case QEvent::TouchEnd: |
463 | case QEvent::TouchUpdate: |
464 | if (button == Qt::NoButton) { |
465 | te = static_cast<const QTouchEvent *>(event); |
466 | keyboardModifiers = te->modifiers(); |
467 | if (!te->points().isEmpty()) |
468 | globalPos = te->points().at(0).globalPosition().toPoint(); |
469 | } |
470 | break; |
471 | |
472 | // consume all wheel events if the scroller is active |
473 | case QEvent::Wheel: |
474 | if (d->macIgnoreWheel || (scroller->state() != QScroller::Inactive)) |
475 | return Ignore | ConsumeEventHint; |
476 | break; |
477 | |
478 | // consume all dbl click events if the scroller is active |
479 | case QEvent::MouseButtonDblClick: |
480 | if (scroller->state() != QScroller::Inactive) |
481 | return Ignore | ConsumeEventHint; |
482 | break; |
483 | |
484 | default: |
485 | break; |
486 | } |
487 | |
488 | if (!me |
489 | #if QT_CONFIG(graphicsview) |
490 | && !gsme |
491 | #endif |
492 | && !te) // Neither mouse nor touch |
493 | return Ignore; |
494 | |
495 | // get the current pointer position in local coordinates. |
496 | QPointF point; |
497 | QScroller::Input inputType = (QScroller::Input) 0; |
498 | |
499 | switch (event->type()) { |
500 | case QEvent::MouseButtonPress: |
501 | if (me && me->button() == button && me->buttons() == button) { |
502 | point = me->globalPosition().toPoint(); |
503 | inputType = QScroller::InputPress; |
504 | } else if (me) { |
505 | scroller->stop(); |
506 | return CancelGesture; |
507 | } |
508 | break; |
509 | case QEvent::MouseButtonRelease: |
510 | if (me && me->button() == button) { |
511 | point = me->globalPosition().toPoint(); |
512 | inputType = QScroller::InputRelease; |
513 | } |
514 | break; |
515 | case QEvent::MouseMove: |
516 | if (me && me->buttons() == button) { |
517 | point = me->globalPosition().toPoint(); |
518 | inputType = QScroller::InputMove; |
519 | } |
520 | break; |
521 | |
522 | #if QT_CONFIG(graphicsview) |
523 | case QEvent::GraphicsSceneMousePress: |
524 | if (gsme && gsme->button() == button && gsme->buttons() == button) { |
525 | point = gsme->scenePos(); |
526 | inputType = QScroller::InputPress; |
527 | } else if (gsme) { |
528 | scroller->stop(); |
529 | return CancelGesture; |
530 | } |
531 | break; |
532 | case QEvent::GraphicsSceneMouseRelease: |
533 | if (gsme && gsme->button() == button) { |
534 | point = gsme->scenePos(); |
535 | inputType = QScroller::InputRelease; |
536 | } |
537 | break; |
538 | case QEvent::GraphicsSceneMouseMove: |
539 | if (gsme && gsme->buttons() == button) { |
540 | point = gsme->scenePos(); |
541 | inputType = QScroller::InputMove; |
542 | } |
543 | break; |
544 | #endif |
545 | |
546 | case QEvent::TouchBegin: |
547 | inputType = QScroller::InputPress; |
548 | Q_FALLTHROUGH(); |
549 | case QEvent::TouchEnd: |
550 | if (!inputType) |
551 | inputType = QScroller::InputRelease; |
552 | Q_FALLTHROUGH(); |
553 | case QEvent::TouchUpdate: |
554 | if (!inputType) |
555 | inputType = QScroller::InputMove; |
556 | |
557 | if (te->pointingDevice()->type() == QInputDevice::DeviceType::TouchPad) { |
558 | if (te->points().count() != 2) // 2 fingers on pad |
559 | return Ignore; |
560 | |
561 | point = te->points().at(0).scenePressPosition() + |
562 | ((te->points().at(0).scenePosition() - te->points().at(0).scenePressPosition()) + |
563 | (te->points().at(1).scenePosition() - te->points().at(1).scenePressPosition())) / 2; |
564 | } else { // TouchScreen |
565 | if (te->points().count() != 1) // 1 finger on screen |
566 | return Ignore; |
567 | |
568 | point = te->points().at(0).scenePosition(); |
569 | } |
570 | break; |
571 | |
572 | default: |
573 | break; |
574 | } |
575 | |
576 | // Check for an active scroller at globalPos |
577 | if (inputType == QScroller::InputPress) { |
578 | const auto activeScrollers = QScroller::activeScrollers(); |
579 | for (QScroller *as : activeScrollers) { |
580 | if (as != scroller) { |
581 | QRegion scrollerRegion; |
582 | |
583 | if (QWidget *w = qobject_cast<QWidget *>(as->target())) { |
584 | scrollerRegion = QRect(w->mapToGlobal(QPoint(0, 0)), w->size()); |
585 | #if QT_CONFIG(graphicsview) |
586 | } else if (QGraphicsObject *go = qobject_cast<QGraphicsObject *>(as->target())) { |
587 | if (const auto *scene = go->scene()) { |
588 | const auto goBoundingRectMappedToScene = go->mapToScene(go->boundingRect()); |
589 | const auto views = scene->views(); |
590 | for (QGraphicsView *gv : views) { |
591 | scrollerRegion |= gv->mapFromScene(goBoundingRectMappedToScene) |
592 | .translated(gv->mapToGlobal(QPoint(0, 0))); |
593 | } |
594 | } |
595 | #endif |
596 | } |
597 | // active scrollers always have priority |
598 | if (scrollerRegion.contains(globalPos)) |
599 | return Ignore; |
600 | } |
601 | } |
602 | } |
603 | |
604 | bool scrollerWasDragging = (scroller->state() == QScroller::Dragging); |
605 | bool scrollerWasScrolling = (scroller->state() == QScroller::Scrolling); |
606 | |
607 | if (inputType) { |
608 | if (QWidget *w = qobject_cast<QWidget *>(d->receiver)) |
609 | point = w->mapFromGlobal(point.toPoint()); |
610 | #if QT_CONFIG(graphicsview) |
611 | else if (QGraphicsObject *go = qobject_cast<QGraphicsObject *>(d->receiver)) |
612 | point = go->mapFromScene(point); |
613 | #endif |
614 | |
615 | // inform the scroller about the new event |
616 | scroller->handleInput(inputType, point, monotonicTimer.elapsed()); |
617 | } |
618 | |
619 | // depending on the scroller state return the gesture state |
620 | Result result; |
621 | bool scrollerIsActive = (scroller->state() == QScroller::Dragging || |
622 | scroller->state() == QScroller::Scrolling); |
623 | |
624 | // Consume all mouse events while dragging or scrolling to avoid nasty |
625 | // side effects with Qt's standard widgets. |
626 | if ((me |
627 | #if QT_CONFIG(graphicsview) |
628 | || gsme |
629 | #endif |
630 | ) && scrollerIsActive) |
631 | result |= ConsumeEventHint; |
632 | |
633 | // The only problem with this approach is that we consume the |
634 | // MouseRelease when we start the scrolling with a flick gesture, so we |
635 | // have to fake a MouseRelease "somewhere" to not mess with the internal |
636 | // states of Qt's widgets (a QPushButton would stay in 'pressed' state |
637 | // forever, if it doesn't receive a MouseRelease). |
638 | if (me |
639 | #if QT_CONFIG(graphicsview) |
640 | || gsme |
641 | #endif |
642 | ) { |
643 | if (!scrollerWasDragging && !scrollerWasScrolling && scrollerIsActive) |
644 | PressDelayHandler::instance()->scrollerBecameActive(keyboardModifiers, mouseButtons); |
645 | else if (scrollerWasScrolling && (scroller->state() == QScroller::Dragging || scroller->state() == QScroller::Inactive)) |
646 | PressDelayHandler::instance()->scrollerWasIntercepted(); |
647 | } |
648 | |
649 | if (!inputType) { |
650 | result |= Ignore; |
651 | } else { |
652 | switch (event->type()) { |
653 | case QEvent::MouseButtonPress: |
654 | #if QT_CONFIG(graphicsview) |
655 | case QEvent::GraphicsSceneMousePress: |
656 | #endif |
657 | if (scroller->state() == QScroller::Pressed) { |
658 | int pressDelay = int(1000 * scroller->scrollerProperties().scrollMetric(QScrollerProperties::MousePressEventDelay).toReal()); |
659 | if (pressDelay > 0) { |
660 | result |= ConsumeEventHint; |
661 | |
662 | PressDelayHandler::instance()->pressed(event, pressDelay); |
663 | event->accept(); |
664 | } |
665 | } |
666 | Q_FALLTHROUGH(); |
667 | case QEvent::TouchBegin: |
668 | q->setHotSpot(globalPos); |
669 | result |= scrollerIsActive ? TriggerGesture : MayBeGesture; |
670 | break; |
671 | |
672 | case QEvent::MouseMove: |
673 | #if QT_CONFIG(graphicsview) |
674 | case QEvent::GraphicsSceneMouseMove: |
675 | #endif |
676 | if (PressDelayHandler::instance()->isDelaying()) |
677 | result |= ConsumeEventHint; |
678 | Q_FALLTHROUGH(); |
679 | case QEvent::TouchUpdate: |
680 | result |= scrollerIsActive ? TriggerGesture : Ignore; |
681 | break; |
682 | |
683 | #if QT_CONFIG(graphicsview) |
684 | case QEvent::GraphicsSceneMouseRelease: |
685 | #endif |
686 | case QEvent::MouseButtonRelease: |
687 | if (PressDelayHandler::instance()->released(event, scrollerWasDragging || scrollerWasScrolling, scrollerIsActive)) |
688 | result |= ConsumeEventHint; |
689 | Q_FALLTHROUGH(); |
690 | case QEvent::TouchEnd: |
691 | result |= scrollerIsActive ? FinishGesture : CancelGesture; |
692 | break; |
693 | |
694 | default: |
695 | result |= Ignore; |
696 | break; |
697 | } |
698 | } |
699 | return result; |
700 | } |
701 | |
702 | |
703 | /*! \reimp |
704 | */ |
705 | void QFlickGestureRecognizer::reset(QGesture *state) |
706 | { |
707 | QGestureRecognizer::reset(state); |
708 | } |
709 | |
710 | QT_END_NAMESPACE |
711 | |
712 | #include "moc_qflickgesture_p.cpp" |
713 | |
714 | #endif // QT_NO_GESTURES |
715 | |