diff options
author | Aaron McCarthy <aaron.mccarthy@jollamobile.com> | 2015-01-29 19:06:59 +1000 |
---|---|---|
committer | Alex Blasche <alexander.blasche@theqtcompany.com> | 2015-02-06 14:46:33 +0000 |
commit | 521e03b50e6f4c89c5daffbc28a53598f4a02b03 (patch) | |
tree | a31eff5f37fc91436d7245cd2ca7aa58d965d0fd | |
parent | 73de5f12a329b155b888b6aa42d059408e1e5202 (diff) | |
download | qtlocation-521e03b50e6f4c89c5daffbc28a53598f4a02b03.tar.gz |
Improve filtering of touch and mouse events.
This change is based on b34bee26c932b9f7579e99d1dca632cb8c47d85f which
was reverted by 7f1067c97f55da45ffe3da7ec91ad32a2bcef255 as it
introduced regressions on some platforms.
All synthesized mouse events are now ignored, map handles touch events
directly.
Improve mouse and touch id grabbing in gesture state machines.
Only accept left mouse button.
Only accept one type of input at a time. Touch input is preferred. If
mouse input is started before touch it will be cancelled. If mouse
input is started after it will be ignored.
Change-Id: I31c1c30a49fc747875d37a9e643c118f05d78772
Reviewed-by: Michal Klocek <michal.klocek@theqtcompany.com>
Reviewed-by: Alex Blasche <alexander.blasche@theqtcompany.com>
-rw-r--r-- | src/imports/location/qdeclarativegeomap.cpp | 76 | ||||
-rw-r--r-- | src/imports/location/qdeclarativegeomap_p.h | 4 | ||||
-rw-r--r-- | src/imports/location/qdeclarativegeomapgesturearea.cpp | 174 | ||||
-rw-r--r-- | src/imports/location/qdeclarativegeomapgesturearea_p.h | 21 | ||||
-rw-r--r-- | tests/auto/declarative_ui/tst_map_mouse.qml | 26 |
5 files changed, 212 insertions, 89 deletions
diff --git a/src/imports/location/qdeclarativegeomap.cpp b/src/imports/location/qdeclarativegeomap.cpp index 4c4b8888..a2d139e9 100644 --- a/src/imports/location/qdeclarativegeomap.cpp +++ b/src/imports/location/qdeclarativegeomap.cpp @@ -58,7 +58,6 @@ #include <QtQml/QQmlContext> #include <QtQml/qqmlinfo.h> #include <QModelIndex> -#include <QtQuick/QQuickWindow> #include <QtQuick/QSGSimpleRectNode> #include <QtGui/QGuiApplication> #include <QCoreApplication> @@ -196,7 +195,7 @@ QDeclarativeGeoMap::QDeclarativeGeoMap(QQuickItem *parent) { QLOC_TRACE0; setAcceptHoverEvents(false); - setAcceptedMouseButtons(Qt::LeftButton | Qt::MidButton | Qt::RightButton); + setAcceptedMouseButtons(Qt::LeftButton); setFlags(QQuickItem::ItemHasContents | QQuickItem::ItemClipsChildrenToShape); setFiltersChildMouseEvents(true); @@ -322,8 +321,7 @@ void QDeclarativeGeoMap::componentComplete() */ void QDeclarativeGeoMap::mousePressEvent(QMouseEvent *event) { - if (!mouseEvent(event)) - event->ignore(); + event->setAccepted(gestureArea_->mousePressEvent(event)); } /*! @@ -331,8 +329,7 @@ void QDeclarativeGeoMap::mousePressEvent(QMouseEvent *event) */ void QDeclarativeGeoMap::mouseMoveEvent(QMouseEvent *event) { - if (!mouseEvent(event)) - event->ignore(); + event->setAccepted(gestureArea_->mouseMoveEvent(event)); } /*! @@ -340,31 +337,17 @@ void QDeclarativeGeoMap::mouseMoveEvent(QMouseEvent *event) */ void QDeclarativeGeoMap::mouseReleaseEvent(QMouseEvent *event) { - if (!mouseEvent(event)) - event->ignore(); + event->setAccepted(gestureArea_->mouseReleaseEvent(event)); } /*! \internal - returns whether flickable used the event */ -bool QDeclarativeGeoMap::mouseEvent(QMouseEvent *event) +void QDeclarativeGeoMap::mouseUngrabEvent() { - if (!mappingManagerInitialized_) - return false; - switch (event->type()) { - case QEvent::MouseButtonPress: - return gestureArea_->mousePressEvent(event); - case QEvent::MouseButtonRelease: - return gestureArea_->mouseReleaseEvent(event); - case QEvent::MouseMove: - return gestureArea_->mouseMoveEvent(event); - default: - return false; - } + gestureArea_->mouseUngrabEvent(); } - /*! \qmlproperty MapGestureArea QtLocation::Map::gesture @@ -839,8 +822,7 @@ void QDeclarativeGeoMap::touchEvent(QTouchEvent *event) return; } QLOC_TRACE0; - event->accept(); - gestureArea_->touchEvent(event); + event->setAccepted(gestureArea_->touchEvent(event)); } /*! @@ -859,29 +841,51 @@ void QDeclarativeGeoMap::wheelEvent(QWheelEvent *event) */ bool QDeclarativeGeoMap::childMouseEventFilter(QQuickItem *item, QEvent *event) { - Q_UNUSED(item) + if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseMove || + event->type() == QEvent::MouseButtonRelease || + event->type() == QEvent::MouseButtonDblClick) { + // Check if we have already grabbed input. This can happen if the previous touch event + // immediately triggers a gesture, the synthesized mouse event is still generated. Filter + // these events so that child items do no receive them. + if (gestureArea_->hasGrabbedInput()) + return true; + + // Ignore synthesized mouse events, but don't filter them, as child items may not handle + // touch events directly. After a gesture is recognized and an input grab obtained these + // events should cease to be generated, except for the case above. + QMouseEvent *me = static_cast<QMouseEvent *>(event); + if (me->source() != Qt::MouseEventNotSynthesized) + return false; + } else if (event->type() == QEvent::TouchBegin || event->type() == QEvent::TouchUpdate || + event->type() == QEvent::TouchEnd) { + // Check if already grabbed mouse, if so filter out touch events. + // FIXME: Returning true here gives priority to touch input, which is not what is intended. + // In response to returning true ungrabMouse() is called and touch input will be handled + // thereafter. + if (gestureArea_->hasGrabbedInput()) + return true; + } + QLOC_TRACE0; switch (event->type()) { case QEvent::MouseButtonPress: - case QEvent::MouseButtonRelease: case QEvent::MouseMove: if (item->keepMouseGrab()) return false; - if (!gestureArea_->filterMapChildMouseEvent(static_cast<QMouseEvent *>(event))) - return false; - grabMouse(); - return true; - case QEvent::UngrabMouse: return gestureArea_->filterMapChildMouseEvent(static_cast<QMouseEvent *>(event)); + case QEvent::MouseButtonRelease: + return gestureArea_->filterMapChildMouseEvent(static_cast<QMouseEvent *>(event)); + case QEvent::UngrabMouse: + // Never filter ungrab mouse events. This event notifies 'item' that the mouse has been + // grabbed and it should cancel any outstanding input event processing. For example, press + // and hold timers. + return false; case QEvent::TouchBegin: case QEvent::TouchUpdate: case QEvent::TouchEnd: if (item->keepMouseGrab()) return false; - if (!gestureArea_->filterMapChildTouchEvent(static_cast<QTouchEvent *>(event))) - return false; - grabMouse(); - return true; + return gestureArea_->filterMapChildTouchEvent(static_cast<QTouchEvent *>(event)); case QEvent::Wheel: return gestureArea_->wheelEvent(static_cast<QWheelEvent *>(event)); default: diff --git a/src/imports/location/qdeclarativegeomap_p.h b/src/imports/location/qdeclarativegeomap_p.h index 256ddcfb..ce66acaa 100644 --- a/src/imports/location/qdeclarativegeomap_p.h +++ b/src/imports/location/qdeclarativegeomap_p.h @@ -146,9 +146,6 @@ public: QT_DEPRECATED Q_INVOKABLE QPointF toScreenPosition(const QGeoCoordinate &coordinate) const; #endif - // callback for map mouse areas - bool mouseEvent(QMouseEvent *event); - QDeclarativeGeoMapGestureArea *gesture(); Q_INVOKABLE void fitViewportToGeoShape(const QVariant &shape); @@ -163,6 +160,7 @@ protected: void mousePressEvent(QMouseEvent *event); void mouseMoveEvent(QMouseEvent *event); void mouseReleaseEvent(QMouseEvent *event); + void mouseUngrabEvent(); void touchEvent(QTouchEvent *event); void wheelEvent(QWheelEvent *event); diff --git a/src/imports/location/qdeclarativegeomapgesturearea.cpp b/src/imports/location/qdeclarativegeomapgesturearea.cpp index 0fbf5557..bdba9110 100644 --- a/src/imports/location/qdeclarativegeomapgesturearea.cpp +++ b/src/imports/location/qdeclarativegeomapgesturearea.cpp @@ -40,6 +40,7 @@ #include <QtGui/QWheelEvent> #include <QtGui/QStyleHints> #include <QtQml/qqmlinfo.h> +#include <QtQuick/QQuickWindow> #include <QPropertyAnimation> #include <QDebug> #include "math.h" @@ -329,6 +330,8 @@ QDeclarativeGeoMapGestureArea::QDeclarativeGeoMapGestureArea(QDeclarativeGeoMap : QObject(parent), declarativeMap_(map), enabled_(true), + hasGrab_(false), + activeInput_(NoInput), activeGestures_(ZoomGesture | PanGesture | FlickGesture) { map_ = 0; @@ -339,8 +342,8 @@ QDeclarativeGeoMapGestureArea::QDeclarativeGeoMapGestureArea(QDeclarativeGeoMap touchPointState_ = touchPoints0; pinchState_ = pinchInactive; panState_ = panInactive; - } + /*! \internal */ @@ -356,6 +359,11 @@ void QDeclarativeGeoMapGestureArea::setMap(QGeoMap *map) map_, SLOT(cameraStopped())); } +bool QDeclarativeGeoMapGestureArea::hasGrabbedInput() const +{ + return hasGrab_; +} + QDeclarativeGeoMapGestureArea::~QDeclarativeGeoMapGestureArea() { } @@ -563,6 +571,11 @@ bool QDeclarativeGeoMapGestureArea::mousePressEvent(QMouseEvent *event) if (!(enabled_ && activeGestures_)) return false; + if (activeInput_ == TouchInput) + return false; + + activeInput_ = MouseInput; + touchPoints_.clear(); touchPoints_ << makeTouchPointFromMouseEvent(event, Qt::TouchPointPressed); @@ -578,6 +591,9 @@ bool QDeclarativeGeoMapGestureArea::mouseMoveEvent(QMouseEvent *event) if (!(enabled_ && activeGestures_)) return false; + if (activeInput_ != MouseInput) + return false; + touchPoints_.clear(); touchPoints_ << makeTouchPointFromMouseEvent(event, Qt::TouchPointMoved); @@ -593,23 +609,63 @@ bool QDeclarativeGeoMapGestureArea::mouseReleaseEvent(QMouseEvent *) if (!(enabled_ && activeGestures_)) return false; + if (activeInput_ == TouchInput) + return false; + touchPoints_.clear(); update(); + + activeInput_ = NoInput; + return true; } /*! \internal */ -void QDeclarativeGeoMapGestureArea::touchEvent(QTouchEvent *event) +void QDeclarativeGeoMapGestureArea::mouseUngrabEvent() { + touchPoints_.clear(); + hasGrab_ = false; + if (activeInput_ == MouseInput) + activeInput_ = NoInput; + update(); +} + +/*! + \internal +*/ +bool QDeclarativeGeoMapGestureArea::touchEvent(QTouchEvent *event) +{ + if (!(enabled_ && activeGestures_)) + return false; + + if (activeInput_ == MouseInput) + return false; + switch (event->type()) { case QEvent::TouchBegin: case QEvent::TouchUpdate: - touchPoints_.clear(); - for (int i = 0; i < event->touchPoints().count(); ++i) { - if (!(event->touchPoints().at(i).state() & Qt::TouchPointReleased)) { - touchPoints_ << event->touchPoints().at(i); + activeInput_ = TouchInput; + foreach (const QTouchEvent::TouchPoint &p, event->touchPoints()) { + QList<QTouchEvent::TouchPoint>::iterator i; + for (i = touchPoints_.begin(); i != touchPoints_.end(); ++i) { + if (i->id() == p.id()) { + i = touchPoints_.erase(i); + break; + } + } + switch (p.state()) { + case Qt::TouchPointPressed: + case Qt::TouchPointMoved: + case Qt::TouchPointStationary: + touchPoints_.insert(i, p); + break; + case Qt::TouchPointReleased: + // already removed + break; + default: + break; } } update(); @@ -617,11 +673,14 @@ void QDeclarativeGeoMapGestureArea::touchEvent(QTouchEvent *event) case QEvent::TouchEnd: touchPoints_.clear(); update(); + activeInput_ = NoInput; break; default: // no-op break; } + + return true; } bool QDeclarativeGeoMapGestureArea::wheelEvent(QWheelEvent *event) @@ -635,26 +694,25 @@ bool QDeclarativeGeoMapGestureArea::wheelEvent(QWheelEvent *event) */ bool QDeclarativeGeoMapGestureArea::filterMapChildMouseEvent(QMouseEvent *event) { - bool used = false; + if (!(enabled_ && activeGestures_)) + return false; + + // We have not grabbed the mouse yet. Process it but don't filter it so the child can use it + // (until we grab it). switch (event->type()) { case QEvent::MouseButtonPress: - used = mousePressEvent(event); + mousePressEvent(event); break; case QEvent::MouseButtonRelease: - used = mouseReleaseEvent(event); + mouseReleaseEvent(event); break; case QEvent::MouseMove: - used = mouseMoveEvent(event); - break; - case QEvent::UngrabMouse: - touchPoints_.clear(); - update(); + mouseMoveEvent(event); break; default: - used = false; break; } - return used && (isPanActive() || isPinchActive()); + return false; } /*! @@ -662,8 +720,13 @@ bool QDeclarativeGeoMapGestureArea::filterMapChildMouseEvent(QMouseEvent *event) */ bool QDeclarativeGeoMapGestureArea::filterMapChildTouchEvent(QTouchEvent *event) { + if (!(enabled_ && activeGestures_)) + return false; + + // We have not grabbed the touch id associated with this touch event yet. Process it but don't + // filter it so the child can use it (until we grab it). touchEvent(event); - return isPanActive() || isPinchActive(); + return false; } /*! @@ -724,6 +787,31 @@ void QDeclarativeGeoMapGestureArea::update() // the whole gesture (enabled_ flag), this keeps the enabled_ consistent with the pinch if (isPanActive() || (enabled_ && pan_.enabled_ && (activeGestures_ & (PanGesture | FlickGesture)))) panStateMachine(); + + if (pinchState_ != pinchInactive || panState_ == panActive) { + if (!hasGrab_) { + if (activeInput_ == MouseInput) { + hasGrab_ = true; + declarativeMap_->grabMouse(); + } else if (activeInput_ == TouchInput) { + hasGrab_ = true; + QVector<int> ids; + foreach (const QTouchEvent::TouchPoint &tp, touchPoints_) + ids.append(tp.id()); + declarativeMap_->grabTouchPoints(ids); + } + } + } else { + if (hasGrab_) { + if (activeInput_ == MouseInput) { + hasGrab_ = false; + declarativeMap_->ungrabMouse(); + } else if (activeInput_ == TouchInput) { + hasGrab_ = false; + declarativeMap_->ungrabTouchPoints(); + } + } + } } /*! @@ -738,7 +826,7 @@ void QDeclarativeGeoMapGestureArea::touchPointStateMachine() clearTouchData(); startOneTouchPoint(); touchPointState_ = touchPoints1; - } else if (touchPoints_.count() == 2) { + } else if (touchPoints_.count() >= 2) { clearTouchData(); startTwoTouchPoints(); touchPointState_ = touchPoints2; @@ -842,43 +930,49 @@ void QDeclarativeGeoMapGestureArea::updateTwoTouchPoints() /*! \internal */ -void QDeclarativeGeoMapGestureArea::setPinchActive(bool active) -{ - if ((active && pinchState_ == pinchActive) || (!active && pinchState_ != pinchActive)) - return; - pinchState_ = active ? pinchActive : pinchInactive; - emit pinchActiveChanged(); -} - - -/*! - \internal -*/ void QDeclarativeGeoMapGestureArea::pinchStateMachine() { PinchState lastState = pinchState_; // Transitions: switch (pinchState_) { case pinchInactive: - if (canStartPinch()) { - startPinch(); - setPinchActive(true); + if (touchPoints_.count() >= 2) { + if (canStartPinch()) { + startPinch(); + pinchState_ = pinchActive; + } else { + pinchState_ = pinchInactiveTwoPoints; + } + } + break; + case pinchInactiveTwoPoints: + if (touchPoints_.count() <= 1) { + pinchState_ = pinchInactive; + } else { + if (canStartPinch()) { + startPinch(); + pinchState_ = pinchActive; + } } break; case pinchActive: if (touchPoints_.count() <= 1) { endPinch(); - setPinchActive(false); + pinchState_ = pinchInactive; } break; } // This line implements an exclusive state machine, where the transitions and updates don't // happen on the same frame - if (pinchState_ != lastState) - return; + if (pinchState_ != lastState) { + emit pinchActiveChanged(); + return; + } + // Update switch (pinchState_) { case pinchInactive: + case pinchInactiveTwoPoints: break; // do nothing case pinchActive: updatePinch(); @@ -1026,6 +1120,10 @@ void QDeclarativeGeoMapGestureArea::panStateMachine() } break; } + + if (panState_ != lastState) + emit panActiveChanged(); + // Update switch (panState_) { case panInactive: // do nothing @@ -1147,10 +1245,11 @@ void QDeclarativeGeoMapGestureArea::stopPan() if (panState_ == panFlick) { endFlick(); } else if (panState_ == panActive) { + panState_ = panInactive; emit panFinished(); + emit panActiveChanged(); emit movementStopped(); } - panState_ = panInactive; } /*! @@ -1163,6 +1262,7 @@ void QDeclarativeGeoMapGestureArea::endFlick() pan_.animation_->stop(); emit flickFinished(); panState_ = panInactive; + emit panActiveChanged(); emit movementStopped(); } diff --git a/src/imports/location/qdeclarativegeomapgesturearea_p.h b/src/imports/location/qdeclarativegeomapgesturearea_p.h index 24b209d3..863d4bdd 100644 --- a/src/imports/location/qdeclarativegeomapgesturearea_p.h +++ b/src/imports/location/qdeclarativegeomapgesturearea_p.h @@ -109,7 +109,7 @@ class QDeclarativeGeoMapGestureArea: public QObject Q_PROPERTY(bool pinchEnabled READ pinchEnabled WRITE setPinchEnabled NOTIFY pinchEnabledChanged) Q_PROPERTY(bool panEnabled READ panEnabled WRITE setPanEnabled NOTIFY panEnabledChanged) Q_PROPERTY(bool isPinchActive READ isPinchActive NOTIFY pinchActiveChanged) - Q_PROPERTY(bool isPanActive READ isPanActive) + Q_PROPERTY(bool isPanActive READ isPanActive NOTIFY panActiveChanged) Q_PROPERTY(ActiveGestures activeGestures READ activeGestures WRITE setActiveGestures NOTIFY activeGesturesChanged) Q_PROPERTY(qreal maximumZoomLevelChange READ maximumZoomLevelChange WRITE setMaximumZoomLevelChange NOTIFY maximumZoomLevelChangeChanged) Q_PROPERTY(qreal flickDeceleration READ flickDeceleration WRITE setFlickDeceleration NOTIFY flickDecelerationChanged) @@ -129,7 +129,6 @@ public: void setActiveGestures(ActiveGestures activeGestures); bool isPinchActive() const; - void setPinchActive(bool active); bool isPanActive() const; bool enabled() const; @@ -147,13 +146,14 @@ public: qreal flickDeceleration() const; void setFlickDeceleration(qreal deceleration); - void touchEvent(QTouchEvent *event); + bool touchEvent(QTouchEvent *event); bool wheelEvent(QWheelEvent *event); bool mousePressEvent(QMouseEvent *event); bool mouseMoveEvent(QMouseEvent *event); bool mouseReleaseEvent(QMouseEvent *event); + void mouseUngrabEvent(); bool filterMapChildMouseEvent(QMouseEvent *event); bool filterMapChildTouchEvent(QTouchEvent *event); @@ -166,7 +166,10 @@ public: void setMap(QGeoMap *map); + bool hasGrabbedInput() const; + Q_SIGNALS: + void panActiveChanged(); void pinchActiveChanged(); void enabledChanged(); void maximumZoomLevelChangeChanged(); @@ -222,6 +225,15 @@ private: QGeoMap *map_; QDeclarativeGeoMap *declarativeMap_; bool enabled_; + bool hasGrab_; + + enum InputType + { + NoInput, + MouseInput, + TouchInput + }; + InputType activeInput_; struct Pinch { @@ -283,6 +295,7 @@ private: enum PinchState { pinchInactive, + pinchInactiveTwoPoints, pinchActive } pinchState_; @@ -295,6 +308,6 @@ private: }; QT_END_NAMESPACE -QML_DECLARE_TYPE(QDeclarativeGeoMapGestureArea); +QML_DECLARE_TYPE(QDeclarativeGeoMapGestureArea) #endif // QDECLARATIVEGEOMAPGESTUREAREA_P_H diff --git a/tests/auto/declarative_ui/tst_map_mouse.qml b/tests/auto/declarative_ui/tst_map_mouse.qml index 0e2472c8..8841c45a 100644 --- a/tests/auto/declarative_ui/tst_map_mouse.qml +++ b/tests/auto/declarative_ui/tst_map_mouse.qml @@ -494,14 +494,21 @@ Item { compare(mouseUpper.lastWasHeld, false) compare(mouseUpper.lastX, 5) compare(mouseUpper.lastY, 5) // remember 20 offset of the mouse area - mousePress(map, 5, 26) + + mouseRelease(map, 5, 25) compare(mouseUpperPressedSpy.count, 1) + compare(mouseUpperReleasedSpy.count, 1) + compare(mouseLowerPressedSpy.count, 0) + compare(mouseLowerReleasedSpy.count, 0) + + mousePress(map, 5, 26) + compare(mouseUpperPressedSpy.count, 2) compare(mouseLowerPressedSpy.count, 0) compare(mouseOverlapperPressedSpy.count, 0) mouseRelease(map, 5, 26) - compare(mouseUpperPressedSpy.count, 1) - compare(mouseUpperReleasedSpy.count, 1) + compare(mouseUpperPressedSpy.count, 2) + compare(mouseUpperReleasedSpy.count, 2) compare(mouseLowerPressedSpy.count, 0) compare(mouseLowerReleasedSpy.count, 0) compare(mouseUpper.lastAccepted, true) @@ -512,7 +519,7 @@ Item { compare(mouseUpper.lastY, 6) // remember 20 offset of the mouse area mousePress(map, 5, 75) - compare(mouseUpperPressedSpy.count, 1) + compare(mouseUpperPressedSpy.count, 2) compare(mouseLowerPressedSpy.count, 1) compare(mouseOverlapperPressedSpy.count, 0) compare(mouseLower.lastAccepted, true) @@ -523,18 +530,19 @@ Item { compare(mouseLower.lastY, 25) // remember 50 offset of the mouse area mouseRelease(map, 5, 75) - compare(mouseUpperPressedSpy.count, 1) - compare(mouseUpperReleasedSpy.count, 1) + compare(mouseUpperPressedSpy.count, 2) + compare(mouseUpperReleasedSpy.count, 2) compare(mouseLowerPressedSpy.count, 1) compare(mouseLowerReleasedSpy.count, 1) + mousePress(map, 55, 75) - compare(mouseUpperPressedSpy.count, 1) + compare(mouseUpperPressedSpy.count, 2) compare(mouseLowerPressedSpy.count, 1) compare(mouseOverlapperPressedSpy.count, 1) compare(mouseOverlapperReleasedSpy.count, 0) mouseRelease(map, 55, 25) - compare(mouseUpperPressedSpy.count, 1) - compare(mouseUpperReleasedSpy.count, 1) + compare(mouseUpperPressedSpy.count, 2) + compare(mouseUpperReleasedSpy.count, 2) compare(mouseLowerPressedSpy.count, 1) compare(mouseLowerReleasedSpy.count, 1) compare(mouseOverlapperReleasedSpy.count, 1) |