1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the QtGui module of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file. Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights. These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
40 ****************************************************************************/
43 #include "qapplication.h"
46 #include "qgraphicsitem.h"
47 #include "qgraphicsscene.h"
48 #include "qgraphicssceneevent.h"
49 #include "qgraphicsview.h"
50 #include "qscroller.h"
51 #include "private/qevent_p.h"
52 #include "private/qflickgesture_p.h"
55 #ifndef QT_NO_GESTURES
59 //#define QFLICKGESTURE_DEBUG
61 #ifdef QFLICKGESTURE_DEBUG
62 # define qFGDebug qDebug
64 # define qFGDebug while (false) qDebug
67 extern bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event);
69 static QMouseEvent *copyMouseEvent(QEvent *e)
72 case QEvent::MouseButtonPress:
73 case QEvent::MouseButtonRelease:
74 case QEvent::MouseMove: {
75 QMouseEvent *me = static_cast<QMouseEvent *>(e);
76 return new QMouseEvent(me->type(), QPoint(0, 0), me->globalPos(), me->button(), me->buttons(), me->modifiers());
78 #ifndef QT_NO_GRAPHICSVIEW
79 case QEvent::GraphicsSceneMousePress:
80 case QEvent::GraphicsSceneMouseRelease:
81 case QEvent::GraphicsSceneMouseMove: {
82 QGraphicsSceneMouseEvent *me = static_cast<QGraphicsSceneMouseEvent *>(e);
84 QEvent::Type met = me->type() == QEvent::GraphicsSceneMousePress ? QEvent::MouseButtonPress :
85 (me->type() == QEvent::GraphicsSceneMouseRelease ? QEvent::MouseButtonRelease : QEvent::MouseMove);
86 return new QMouseEvent(met, QPoint(0, 0), me->screenPos(), me->button(), me->buttons(), me->modifiers());
88 QGraphicsSceneMouseEvent *copy = new QGraphicsSceneMouseEvent(me->type());
89 copy->setPos(me->pos());
90 copy->setScenePos(me->scenePos());
91 copy->setScreenPos(me->screenPos());
92 for (int i = 0x1; i <= 0x10; i <<= 1) {
93 Qt::MouseButton button = Qt::MouseButton(i);
94 copy->setButtonDownPos(button, me->buttonDownPos(button));
95 copy->setButtonDownScenePos(button, me->buttonDownScenePos(button));
96 copy->setButtonDownScreenPos(button, me->buttonDownScreenPos(button));
98 copy->setLastPos(me->lastPos());
99 copy->setLastScenePos(me->lastScenePos());
100 copy->setLastScreenPos(me->lastScreenPos());
101 copy->setButtons(me->buttons());
102 copy->setButton(me->button());
103 copy->setModifiers(me->modifiers());
107 #endif // QT_NO_GRAPHICSVIEW
113 class PressDelayHandler : public QObject
116 PressDelayHandler(QObject *parent = 0)
119 , sendingEvent(false)
120 , mouseButton(Qt::NoButton)
124 static PressDelayHandler *inst;
128 UngrabMouseBefore = 1,
129 RegrabMouseAfterwards = 2
132 static PressDelayHandler *instance()
134 static PressDelayHandler *inst = 0;
136 inst = new PressDelayHandler(QCoreApplication::instance());
140 bool shouldEventBeIgnored(QEvent *) const
145 bool isDelaying() const
147 return !pressDelayEvent.isNull();
150 void pressed(QEvent *e, int delay)
152 if (!pressDelayEvent) {
153 pressDelayEvent.reset(copyMouseEvent(e));
154 pressDelayTimer = startTimer(delay);
155 mouseTarget = QApplication::widgetAt(pressDelayEvent->globalPos());
156 mouseButton = pressDelayEvent->button();
157 qFGDebug() << "QFG: consuming/delaying mouse press";
159 qFGDebug() << "QFG: NOT consuming/delaying mouse press";
161 e->setAccepted(true);
164 bool released(QEvent *e, bool scrollerWasActive, bool scrollerIsActive)
166 // consume this event if the scroller was or is active
167 bool result = scrollerWasActive || scrollerIsActive;
170 if (pressDelayTimer) {
171 killTimer(pressDelayTimer);
174 // we still haven't even sent the press, so do it now
175 if (pressDelayEvent && mouseTarget && !scrollerIsActive) {
176 QScopedPointer<QMouseEvent> releaseEvent(copyMouseEvent(e));
178 qFGDebug() << "QFG: re-sending mouse press (due to release) for " << mouseTarget;
179 sendMouseEvent(pressDelayEvent.data(), UngrabMouseBefore);
181 qFGDebug() << "QFG: faking mouse release (due to release) for " << mouseTarget;
182 sendMouseEvent(releaseEvent.data());
184 result = true; // consume this event
185 } else if (mouseTarget && scrollerIsActive) {
186 // we grabbed the mouse expicitly when the scroller became active, so undo that now
187 sendMouseEvent(0, UngrabMouseBefore);
189 pressDelayEvent.reset(0);
194 void scrollerWasIntercepted()
196 qFGDebug() << "QFG: deleting delayed mouse press, since scroller was only intercepted";
197 if (pressDelayEvent) {
198 // we still haven't even sent the press, so just throw it away now
199 if (pressDelayTimer) {
200 killTimer(pressDelayTimer);
203 pressDelayEvent.reset(0);
208 void scrollerBecameActive()
210 if (pressDelayEvent) {
211 // we still haven't even sent the press, so just throw it away now
212 qFGDebug() << "QFG: deleting delayed mouse press, since scroller is active now";
213 if (pressDelayTimer) {
214 killTimer(pressDelayTimer);
217 pressDelayEvent.reset(0);
219 } else if (mouseTarget) {
220 // we did send a press, so we need to fake a release now
222 // release all pressed mouse buttons
223 /* Qt::MouseButtons mouseButtons = QApplication::mouseButtons();
224 for (int i = 0; i < 32; ++i) {
225 if (mouseButtons & (1 << i)) {
226 Qt::MouseButton b = static_cast<Qt::MouseButton>(1 << i);
228 QPoint farFarAway(-QWIDGETSIZE_MAX, -QWIDGETSIZE_MAX);
230 qFGDebug() << "QFG: sending a fake mouse release at far-far-away to " << mouseTarget;
231 QMouseEvent re(QEvent::MouseButtonRelease, QPoint(), farFarAway,
232 b, mouseButtons, QApplication::keyboardModifiers());
237 QPoint farFarAway(-QWIDGETSIZE_MAX, -QWIDGETSIZE_MAX);
239 qFGDebug() << "QFG: sending a fake mouse release at far-far-away to " << mouseTarget;
240 QMouseEvent re(QEvent::MouseButtonRelease, QPoint(), farFarAway,
241 mouseButton, QApplication::mouseButtons() & ~mouseButton,
242 QApplication::keyboardModifiers());
243 sendMouseEvent(&re, RegrabMouseAfterwards);
244 // don't clear the mouseTarget just yet, since we need to explicitly ungrab the mouse on release!
249 void timerEvent(QTimerEvent *e)
251 if (e->timerId() == pressDelayTimer) {
252 if (pressDelayEvent && mouseTarget) {
253 qFGDebug() << "QFG: timer event: re-sending mouse press to " << mouseTarget;
254 sendMouseEvent(pressDelayEvent.data(), UngrabMouseBefore);
256 pressDelayEvent.reset(0);
258 if (pressDelayTimer) {
259 killTimer(pressDelayTimer);
265 void sendMouseEvent(QMouseEvent *me, int flags = 0)
270 #ifndef QT_NO_GRAPHICSVIEW
271 QGraphicsItem *grabber = 0;
272 if (mouseTarget->parentWidget()) {
273 if (QGraphicsView *gv = qobject_cast<QGraphicsView *>(mouseTarget->parentWidget())) {
275 grabber = gv->scene()->mouseGrabberItem();
279 if (grabber && (flags & UngrabMouseBefore)) {
280 // GraphicsView Mouse Handling Workaround #1:
281 // we need to ungrab the mouse before re-sending the press,
282 // since the scene had already set the mouse grabber to the
283 // original (and consumed) event's receiver
284 qFGDebug() << "QFG: ungrabbing" << grabber;
285 grabber->ungrabMouse();
287 #endif // QT_NO_GRAPHICSVIEW
290 QMouseEvent copy(me->type(), mouseTarget->mapFromGlobal(me->globalPos()), me->globalPos(), me->button(), me->buttons(), me->modifiers());
291 qt_sendSpontaneousEvent(mouseTarget, ©);
294 #ifndef QT_NO_GRAPHICSVIEW
295 if (grabber && (flags & RegrabMouseAfterwards)) {
296 // GraphicsView Mouse Handling Workaround #2:
297 // we need to re-grab the mouse after sending a faked mouse
298 // release, since we still need the mouse moves for the gesture
299 // (the scene will clear the item's mouse grabber status on
301 qFGDebug() << "QFG: re-grabbing" << grabber;
302 grabber->grabMouse();
305 sendingEvent = false;
312 QScopedPointer<QMouseEvent> pressDelayEvent;
314 Qt::MouseButton mouseButton;
315 QPointer<QWidget> mouseTarget;
323 \brief The QFlickGesture class describes a flicking gesture made by the user.
325 The QFlickGesture is more complex than the QPanGesture that uses QScroller and QScrollerProperties
326 to decide if it is triggered.
327 This gesture is reacting on touch event as compared to the QMouseFlickGesture.
329 \sa {Gestures Programming}, QScroller, QScrollerProperties, QMouseFlickGesture
335 QFlickGesture::QFlickGesture(QObject *receiver, Qt::MouseButton button, QObject *parent)
336 : QGesture(*new QFlickGesturePrivate, parent)
338 d_func()->q_ptr = this;
339 d_func()->receiver = receiver;
340 d_func()->receiverScroller = (receiver && QScroller::hasScroller(receiver)) ? QScroller::scroller(receiver) : 0;
341 d_func()->button = button;
344 QFlickGesture::~QFlickGesture()
347 QFlickGesturePrivate::QFlickGesturePrivate()
348 : receiverScroller(0), button(Qt::NoButton), macIgnoreWheel(false)
353 // QFlickGestureRecognizer
357 QFlickGestureRecognizer::QFlickGestureRecognizer(Qt::MouseButton button)
359 this->button = button;
364 QGesture *QFlickGestureRecognizer::create(QObject *target)
366 #ifndef QT_NO_GRAPHICSVIEW
367 QGraphicsObject *go = qobject_cast<QGraphicsObject*>(target);
368 if (go && button == Qt::NoButton) {
369 go->setAcceptTouchEvents(true);
372 return new QFlickGesture(target, button);
376 The recognize function detects a touch event suitable to start the attached QScroller.
377 The QFlickGesture will be triggered as soon as the scroller is no longer in the state
378 QScroller::Inactive or QScroller::Pressed. It will be finished or canceled
379 at the next QEvent::TouchEnd.
380 Note that the QScroller might continue scrolling (kinetically) at this point.
382 QGestureRecognizer::Result QFlickGestureRecognizer::recognize(QGesture *state,
388 static QElapsedTimer monotonicTimer;
389 if (!monotonicTimer.isValid())
390 monotonicTimer.start();
392 QFlickGesture *q = static_cast<QFlickGesture *>(state);
393 QFlickGesturePrivate *d = q->d_func();
395 QScroller *scroller = d->receiverScroller;
397 return Ignore; // nothing to do without a scroller?
399 QWidget *receiverWidget = qobject_cast<QWidget *>(d->receiver);
400 #ifndef QT_NO_GRAPHICSVIEW
401 QGraphicsObject *receiverGraphicsObject = qobject_cast<QGraphicsObject *>(d->receiver);
404 // this is only set for events that we inject into the event loop via sendEvent()
405 if (PressDelayHandler::instance()->shouldEventBeIgnored(event)) {
406 //qFGDebug() << state << "QFG: ignored event: " << event->type();
410 const QMouseEvent *me = 0;
411 #ifndef QT_NO_GRAPHICSVIEW
412 const QGraphicsSceneMouseEvent *gsme = 0;
414 const QTouchEvent *te = 0;
417 // qFGDebug() << "FlickGesture "<<state<<"watched:"<<watched<<"receiver"<<d->receiver<<"event"<<event->type()<<"button"<<button;
419 switch (event->type()) {
420 case QEvent::MouseButtonPress:
421 case QEvent::MouseButtonRelease:
422 case QEvent::MouseMove:
425 if (button != Qt::NoButton) {
426 me = static_cast<const QMouseEvent *>(event);
427 globalPos = me->globalPos();
430 #ifndef QT_NO_GRAPHICSVIEW
431 case QEvent::GraphicsSceneMousePress:
432 case QEvent::GraphicsSceneMouseRelease:
433 case QEvent::GraphicsSceneMouseMove:
434 if (!receiverGraphicsObject)
436 if (button != Qt::NoButton) {
437 gsme = static_cast<const QGraphicsSceneMouseEvent *>(event);
438 globalPos = gsme->screenPos();
442 case QEvent::TouchBegin:
443 case QEvent::TouchEnd:
444 case QEvent::TouchUpdate:
445 if (button == Qt::NoButton) {
446 te = static_cast<const QTouchEvent *>(event);
447 if (!te->touchPoints().isEmpty())
448 globalPos = te->touchPoints().at(0).screenPos().toPoint();
452 #if defined(Q_WS_MAC)
453 // the only way to distinguish between real mouse wheels and wheel
454 // events generated by the native 2 finger swipe gesture is to listen
455 // for these events (according to Apple's Cocoa Event-Handling Guide)
457 case QEvent::NativeGesture: {
458 QNativeGestureEvent *nge = static_cast<QNativeGestureEvent *>(event);
459 if (nge->gestureType == QNativeGestureEvent::GestureBegin)
460 d->macIgnoreWheel = true;
461 else if (nge->gestureType == QNativeGestureEvent::GestureEnd)
462 d->macIgnoreWheel = false;
467 // consume all wheel events if the scroller is active
469 if (d->macIgnoreWheel || (scroller->state() != QScroller::Inactive))
470 return Ignore | ConsumeEventHint;
473 // consume all dbl click events if the scroller is active
474 case QEvent::MouseButtonDblClick:
475 if (scroller->state() != QScroller::Inactive)
476 return Ignore | ConsumeEventHint;
484 #ifndef QT_NO_GRAPHICSVIEW
487 && !te) // Neither mouse nor touch
490 // get the current pointer position in local coordinates.
492 QScroller::Input inputType = (QScroller::Input) 0;
494 switch (event->type()) {
495 case QEvent::MouseButtonPress:
496 if (me && me->button() == button && me->buttons() == button) {
497 point = me->globalPos();
498 inputType = QScroller::InputPress;
501 return CancelGesture;
504 case QEvent::MouseButtonRelease:
505 if (me && me->button() == button) {
506 point = me->globalPos();
507 inputType = QScroller::InputRelease;
510 case QEvent::MouseMove:
512 // Qt on Symbian tracks the button state internally, while Qt on Win/Mac/Unix
513 // relies on the windowing system to report the current buttons state.
514 if (me && (me->buttons() == button || !me->buttons())) {
516 if (me && me->buttons() == button) {
518 point = me->globalPos();
519 inputType = QScroller::InputMove;
523 #ifndef QT_NO_GRAPHICSVIEW
524 case QEvent::GraphicsSceneMousePress:
525 if (gsme && gsme->button() == button && gsme->buttons() == button) {
526 point = gsme->scenePos();
527 inputType = QScroller::InputPress;
530 return CancelGesture;
533 case QEvent::GraphicsSceneMouseRelease:
534 if (gsme && gsme->button() == button) {
535 point = gsme->scenePos();
536 inputType = QScroller::InputRelease;
539 case QEvent::GraphicsSceneMouseMove:
541 // Qt on Symbian tracks the button state internally, while Qt on Win/Mac/Unix
542 // relies on the windowing system to report the current buttons state.
543 if (gsme && (gsme->buttons() == button || !me->buttons())) {
545 if (gsme && gsme->buttons() == button) {
547 point = gsme->scenePos();
548 inputType = QScroller::InputMove;
553 case QEvent::TouchBegin:
554 inputType = QScroller::InputPress;
556 case QEvent::TouchEnd:
558 inputType = QScroller::InputRelease;
560 case QEvent::TouchUpdate:
562 inputType = QScroller::InputMove;
564 if (te->deviceType() == QTouchEvent::TouchPad) {
565 if (te->touchPoints().count() != 2) // 2 fingers on pad
568 point = te->touchPoints().at(0).startScenePos() +
569 ((te->touchPoints().at(0).scenePos() - te->touchPoints().at(0).startScenePos()) +
570 (te->touchPoints().at(1).scenePos() - te->touchPoints().at(1).startScenePos())) / 2;
571 } else { // TouchScreen
572 if (te->touchPoints().count() != 1) // 1 finger on screen
575 point = te->touchPoints().at(0).scenePos();
583 // Check for an active scroller at globalPos
584 if (inputType == QScroller::InputPress) {
585 foreach (QScroller *as, QScroller::activeScrollers()) {
586 if (as != scroller) {
587 QRegion scrollerRegion;
589 if (QWidget *w = qobject_cast<QWidget *>(as->target())) {
590 scrollerRegion = QRect(w->mapToGlobal(QPoint(0, 0)), w->size());
591 #ifndef QT_NO_GRAPHICSVIEW
592 } else if (QGraphicsObject *go = qobject_cast<QGraphicsObject *>(as->target())) {
593 if (go->scene() && !go->scene()->views().isEmpty()) {
594 foreach (QGraphicsView *gv, go->scene()->views())
595 scrollerRegion |= gv->mapFromScene(go->mapToScene(go->boundingRect()))
596 .translated(gv->mapToGlobal(QPoint(0, 0)));
600 // active scrollers always have priority
601 if (scrollerRegion.contains(globalPos))
607 bool scrollerWasDragging = (scroller->state() == QScroller::Dragging);
608 bool scrollerWasScrolling = (scroller->state() == QScroller::Scrolling);
611 if (QWidget *w = qobject_cast<QWidget *>(d->receiver))
612 point = w->mapFromGlobal(point.toPoint());
613 #ifndef QT_NO_GRAPHICSVIEW
614 else if (QGraphicsObject *go = qobject_cast<QGraphicsObject *>(d->receiver))
615 point = go->mapFromScene(point);
618 // inform the scroller about the new event
619 scroller->handleInput(inputType, point, monotonicTimer.elapsed());
622 // depending on the scroller state return the gesture state
624 bool scrollerIsActive = (scroller->state() == QScroller::Dragging ||
625 scroller->state() == QScroller::Scrolling);
627 // Consume all mouse events while dragging or scrolling to avoid nasty
628 // side effects with Qt's standard widgets.
630 #ifndef QT_NO_GRAPHICSVIEW
633 ) && scrollerIsActive)
634 result |= ConsumeEventHint;
636 // The only problem with this approach is that we consume the
637 // MouseRelease when we start the scrolling with a flick gesture, so we
638 // have to fake a MouseRelease "somewhere" to not mess with the internal
639 // states of Qt's widgets (a QPushButton would stay in 'pressed' state
640 // forever, if it doesn't receive a MouseRelease).
642 #ifndef QT_NO_GRAPHICSVIEW
646 if (!scrollerWasDragging && !scrollerWasScrolling && scrollerIsActive)
647 PressDelayHandler::instance()->scrollerBecameActive();
648 else if (scrollerWasScrolling && (scroller->state() == QScroller::Dragging || scroller->state() == QScroller::Inactive))
649 PressDelayHandler::instance()->scrollerWasIntercepted();
655 switch (event->type()) {
656 case QEvent::MouseButtonPress:
657 #ifndef QT_NO_GRAPHICSVIEW
658 case QEvent::GraphicsSceneMousePress:
660 if (scroller->state() == QScroller::Pressed) {
661 int pressDelay = int(1000 * scroller->scrollerProperties().scrollMetric(QScrollerProperties::MousePressEventDelay).toReal());
662 if (pressDelay > 0) {
663 result |= ConsumeEventHint;
665 PressDelayHandler::instance()->pressed(event, pressDelay);
670 case QEvent::TouchBegin:
671 q->setHotSpot(globalPos);
672 result |= scrollerIsActive ? TriggerGesture : MayBeGesture;
675 case QEvent::MouseMove:
676 #ifndef QT_NO_GRAPHICSVIEW
677 case QEvent::GraphicsSceneMouseMove:
679 if (PressDelayHandler::instance()->isDelaying())
680 result |= ConsumeEventHint;
682 case QEvent::TouchUpdate:
683 result |= scrollerIsActive ? TriggerGesture : Ignore;
686 #ifndef QT_NO_GRAPHICSVIEW
687 case QEvent::GraphicsSceneMouseRelease:
689 case QEvent::MouseButtonRelease:
690 if (PressDelayHandler::instance()->released(event, scrollerWasDragging || scrollerWasScrolling, scrollerIsActive))
691 result |= ConsumeEventHint;
693 case QEvent::TouchEnd:
694 result |= scrollerIsActive ? FinishGesture : CancelGesture;
708 void QFlickGestureRecognizer::reset(QGesture *state)
710 QGestureRecognizer::reset(state);
715 #endif // QT_NO_GESTURES