Add MultiPointTouchArea element.
authorMichael Brasser <michael.brasser@nokia.com>
Tue, 23 Aug 2011 03:30:38 +0000 (13:30 +1000)
committerQt by Nokia <qt-info@nokia.com>
Tue, 25 Oct 2011 04:06:14 +0000 (06:06 +0200)
Change-Id: I3a4f774cd96ab7f5d08e85c965f59e1416f02e0e
Reviewed-by: Martin Jones <martin.jones@nokia.com>
18 files changed:
src/declarative/items/items.pri
src/declarative/items/qquickcanvas.cpp
src/declarative/items/qquickcanvas_p.h
src/declarative/items/qquickitem.cpp
src/declarative/items/qquickitem.h
src/declarative/items/qquickitem_p.h
src/declarative/items/qquickitemsmodule.cpp
src/declarative/items/qquickmultipointtoucharea.cpp [new file with mode: 0644]
src/declarative/items/qquickmultipointtoucharea_p.h [new file with mode: 0644]
tests/auto/declarative/declarative.pro
tests/auto/declarative/qquickcanvas/tst_qquickcanvas.cpp
tests/auto/declarative/qquickmultipointtoucharea/data/inFlickable.qml [new file with mode: 0644]
tests/auto/declarative/qquickmultipointtoucharea/data/nested.qml [new file with mode: 0644]
tests/auto/declarative/qquickmultipointtoucharea/data/nonOverlapping.qml [new file with mode: 0644]
tests/auto/declarative/qquickmultipointtoucharea/data/properties.qml [new file with mode: 0644]
tests/auto/declarative/qquickmultipointtoucharea/data/signalTest.qml [new file with mode: 0644]
tests/auto/declarative/qquickmultipointtoucharea/qquickmultipointtoucharea.pro [new file with mode: 0644]
tests/auto/declarative/qquickmultipointtoucharea/tst_qquickmultipointtoucharea.cpp [new file with mode: 0644]

index c3d6a2a..f83c65c 100644 (file)
@@ -64,6 +64,7 @@ HEADERS += \
     $$PWD/qquickspriteimage_p.h \
     $$PWD/qquickdrag_p.h \
     $$PWD/qquickdroparea_p.h \
+    $$PWD/qquickmultipointtoucharea_p.h \
     $$PWD/qquickitemview_p.h \
     $$PWD/qquickitemview_p_p.h
 
@@ -110,6 +111,7 @@ SOURCES += \
     $$PWD/qquickspriteimage.cpp \
     $$PWD/qquickdrag.cpp \
     $$PWD/qquickdroparea.cpp \
+    $$PWD/qquickmultipointtoucharea.cpp \
     $$PWD/qquickitemview.cpp
 
 SOURCES += \
index 52ef421..6df0c65 100644 (file)
@@ -1496,7 +1496,7 @@ bool QQuickCanvasPrivate::deliverDragEvent(QQuickDragGrabber *grabber, QQuickIte
     return accepted;
 }
 
-bool QQuickCanvasPrivate::sendFilteredMouseEvent(QQuickItem *target, QQuickItem *item, QMouseEvent *event)
+bool QQuickCanvasPrivate::sendFilteredMouseEvent(QQuickItem *target, QQuickItem *item, QEvent *event)
 {
     if (!target)
         return false;
@@ -1550,12 +1550,9 @@ bool QQuickCanvas::sendEvent(QQuickItem *item, QEvent *e)
     case QEvent::MouseButtonDblClick:
     case QEvent::MouseMove:
         // XXX todo - should sendEvent be doing this?  how does it relate to forwarded events?
-        {
-            QMouseEvent *se = static_cast<QMouseEvent *>(e);
-            if (!d->sendFilteredMouseEvent(item->parentItem(), item, se)) {
-                se->accept();
-                QQuickItemPrivate::get(item)->deliverMouseEvent(se);
-            }
+        if (!d->sendFilteredMouseEvent(item->parentItem(), item, e)) {
+            e->accept();
+            QQuickItemPrivate::get(item)->deliverMouseEvent(static_cast<QMouseEvent *>(e));
         }
         break;
     case QEvent::Wheel:
@@ -1569,7 +1566,11 @@ bool QQuickCanvas::sendEvent(QQuickItem *item, QEvent *e)
     case QEvent::TouchBegin:
     case QEvent::TouchUpdate:
     case QEvent::TouchEnd:
-        QQuickItemPrivate::get(item)->deliverTouchEvent(static_cast<QTouchEvent *>(e));
+        // XXX todo - should sendEvent be doing this?  how does it relate to forwarded events?
+        if (!d->sendFilteredMouseEvent(item->parentItem(), item, e)) {
+            e->accept();
+            QQuickItemPrivate::get(item)->deliverTouchEvent(static_cast<QTouchEvent *>(e));
+        }
         break;
     case QEvent::DragEnter:
     case QEvent::DragMove:
index 5c68442..610ebe7 100644 (file)
@@ -110,7 +110,7 @@ public:
     static void transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints, const QTransform &transform);
     bool deliverInitialMousePressEvent(QQuickItem *, QMouseEvent *);
     bool deliverMouseEvent(QMouseEvent *);
-    bool sendFilteredMouseEvent(QQuickItem *, QQuickItem *, QMouseEvent *);
+    bool sendFilteredMouseEvent(QQuickItem *, QQuickItem *, QEvent *);
     bool deliverWheelEvent(QQuickItem *, QWheelEvent *);
     bool deliverTouchPoints(QQuickItem *, QTouchEvent *, const QList<QTouchEvent::TouchPoint> &, QSet<int> *,
             QHash<QQuickItem *, QList<QTouchEvent::TouchPoint> > *);
index b6951d9..f50a4f1 100644 (file)
@@ -2185,7 +2185,7 @@ QQuickItemPrivate::QQuickItemPrivate()
 : _anchors(0), _contents(0), baselineOffset(0), _anchorLines(0), _stateGroup(0), origin(QQuickItem::Center),
 
   flags(0), widthValid(false), heightValid(false), componentComplete(true),
-  keepMouse(false), hoverEnabled(false), smooth(false), focus(false), activeFocus(false), notifiedFocus(false),
+  keepMouse(false), keepTouch(false), hoverEnabled(false), smooth(false), focus(false), activeFocus(false), notifiedFocus(false),
   notifiedActiveFocus(false), filtersChildMouseEvents(false), explicitVisible(true),
   effectiveVisible(true), explicitEnable(true), effectiveEnable(true), polishScheduled(false),
   inheritedLayoutMirror(false), effectiveLayoutMirror(false), isMirrorImplicit(true),
@@ -2856,6 +2856,11 @@ void QQuickItem::mouseUngrabEvent()
     // XXX todo
 }
 
+void QQuickItem::touchUngrabEvent()
+{
+    // XXX todo
+}
+
 void QQuickItem::wheelEvent(QWheelEvent *event)
 {
     event->ignore();
@@ -4529,6 +4534,94 @@ void QQuickItem::setKeepMouseGrab(bool keep)
 }
 
 /*!
+    Grabs the touch points specified by \a ids.
+
+    These touch points will be owned by the item until
+    they are released. Alternatively, the grab can be stolen
+    by a filtering item like Flickable. Use setKeepTouchGrab()
+    to prevent the grab from being stolen.
+
+    \sa ungrabTouchPoints(), setKeepTouchGrab()
+*/
+void QQuickItem::grabTouchPoints(const QList<int> &ids)
+{
+    Q_D(QQuickItem);
+    if (!d->canvas)
+        return;
+    QQuickCanvasPrivate *canvasPriv = QQuickCanvasPrivate::get(d->canvas);
+
+    QSet<QQuickItem*> ungrab;
+    for (int i = 0; i < ids.count(); ++i) {
+        QQuickItem *oldGrabber = canvasPriv->itemForTouchPointId.value(ids.at(i));
+        if (oldGrabber == this)
+            return;
+
+        canvasPriv->itemForTouchPointId[ids.at(i)] = this;
+        if (oldGrabber)
+            ungrab.insert(oldGrabber);
+    }
+    foreach (QQuickItem *oldGrabber, ungrab)
+        oldGrabber->touchUngrabEvent();
+}
+
+/*!
+    Ungrabs the touch points owned by this item.
+
+    \sa grabTouchPoints()
+*/
+void QQuickItem::ungrabTouchPoints()
+{
+    Q_D(QQuickItem);
+    if (!d->canvas)
+        return;
+    QQuickCanvasPrivate *canvasPriv = QQuickCanvasPrivate::get(d->canvas);
+
+    QMutableHashIterator<int, QQuickItem*> i(canvasPriv->itemForTouchPointId);
+    while (i.hasNext()) {
+        i.next();
+        if (i.value() == this)
+            i.remove();
+    }
+    touchUngrabEvent();
+}
+
+/*!
+    Returns a value indicating whether the touch points grabbed by this item
+    should remain with this item exclusively.
+
+    \sa setKeepTouchGrab(), keepMouseGrab()
+*/
+bool QQuickItem::keepTouchGrab() const
+{
+    Q_D(const QQuickItem);
+    return d->keepTouch;
+}
+
+/*!
+  The flag indicating whether the touch points grabbed
+  by this item should remain with this item is set to \a keep.
+
+  This is useful for items that wish to grab and keep specific touch
+  points following a predefined gesture.  For example,
+  an item that is interested in horizontal touch point movement
+  may set setKeepTouchGrab to true once a threshold has been
+  exceeded.  Once setKeepTouchGrab has been set to true, filtering
+  items will not react to the relevant touch points.
+
+  If the item does not indicate that it wishes to retain touch point grab,
+  a filtering item may steal the grab. For example, Flickable may attempt
+  to steal a touch point grab if it detects that the user has begun to
+  move the viewport.
+
+  \sa keepTouchGrab(), setKeepMouseGrab()
+ */
+void QQuickItem::setKeepTouchGrab(bool keep)
+{
+    Q_D(QQuickItem);
+    d->keepTouch = keep;
+}
+
+/*!
     \qmlmethod object QtQuick2::Item::mapFromItem(Item item, real x, real y)
 
     Maps the point (\a x, \a y), which is in \a item's coordinate system, to
index be50677..0cbcfb8 100644 (file)
@@ -282,6 +282,11 @@ public:
     bool filtersChildMouseEvents() const;
     void setFiltersChildMouseEvents(bool filter);
 
+    void grabTouchPoints(const QList<int> &ids);
+    void ungrabTouchPoints();
+    bool keepTouchGrab() const;
+    void setKeepTouchGrab(bool);
+
     QTransform itemTransform(QQuickItem *, bool *) const;
     QPointF mapToItem(const QQuickItem *item, const QPointF &point) const;
     QPointF mapToScene(const QPointF &point) const;
@@ -368,6 +373,7 @@ protected:
     virtual void mouseReleaseEvent(QMouseEvent *event);
     virtual void mouseDoubleClickEvent(QMouseEvent *event);
     virtual void mouseUngrabEvent(); // XXX todo - params?
+    virtual void touchUngrabEvent();
     virtual void wheelEvent(QWheelEvent *event);
     virtual void touchEvent(QTouchEvent *event);
     virtual void hoverEnterEvent(QHoverEvent *event);
index 94b195f..47f86c4 100644 (file)
@@ -237,6 +237,7 @@ public:
     bool heightValid:1;
     bool componentComplete:1;
     bool keepMouse:1;
+    bool keepTouch:1;
     bool hoverEnabled:1;
     bool smooth:1;
     bool focus:1;
@@ -255,7 +256,6 @@ public:
     bool inheritMirrorFromParent:1;
     bool inheritMirrorFromItem:1;
     bool childrenDoNotOverlap:1;
-    quint32 dummy:1;
 
     QQuickCanvas *canvas;
     QSGContext *sceneGraphContext() const { Q_ASSERT(canvas); return static_cast<QQuickCanvasPrivate *>(QObjectPrivate::get(canvas))->context; }
index 38b5a91..0a04e88 100644 (file)
@@ -80,6 +80,7 @@
 #include "qquickspriteimage_p.h"
 #include "qquickdrag_p.h"
 #include "qquickdroparea_p.h"
+#include "qquickmultipointtoucharea_p.h"
 
 static QDeclarativePrivate::AutoParentResult qquickitem_autoParent(QObject *obj, QObject *parent)
 {
@@ -201,6 +202,10 @@ static void qt_quickitems_defineModule(const char *uri, int major, int minor)
     qmlRegisterType<QQuickDropEvent>();
     qmlRegisterType<QQuickDropAreaDrag>();
     qmlRegisterUncreatableType<QQuickDrag>("QtQuick", 2, 0, "Drag", QQuickDragAttached::tr("Drag is only available via attached properties"));
+
+    qmlRegisterType<QQuickMultiPointTouchArea>("QtQuick", 2, 0, "MultiPointTouchArea");
+    qmlRegisterType<QQuickTouchPoint>("QtQuick", 2, 0, "TouchPoint");
+    qmlRegisterType<QQuickGrabGestureEvent>();
 }
 
 void QQuickItemsModule::defineModule()
diff --git a/src/declarative/items/qquickmultipointtoucharea.cpp b/src/declarative/items/qquickmultipointtoucharea.cpp
new file mode 100644 (file)
index 0000000..f7fdf97
--- /dev/null
@@ -0,0 +1,729 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtDeclarative module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qquickmultipointtoucharea_p.h"
+#include <qquickcanvas.h>
+#include <QEvent>
+#include <QMouseEvent>
+#include <math.h>
+#include <QDebug>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+    \qmlclass TouchPoint QQuickTouchPoint
+    \inqmlmodule QtQuick 2
+    \ingroup qml-event-elements
+    \brief The TouchPoint element describes a touch point in a MultiPointTouchArea.
+
+    The TouchPoint element contains information about a touch point, such as the current
+    position, pressure, and area.
+*/
+
+/*!
+    \qmlproperty int QtQuick2::TouchPoint::pointId
+
+    This property holds the point id of the touch point.
+
+    Each touch point within a MultiPointTouchArea will have a unique id.
+*/
+void QQuickTouchPoint::setPointId(int id)
+{
+    if (_id == id)
+        return;
+    _id = id;
+    emit pointIdChanged();
+}
+
+/*!
+    \qmlproperty real QtQuick2::TouchPoint::x
+    \qmlproperty real QtQuick2::TouchPoint::y
+
+    These properties hold the current position of the touch point.
+*/
+
+void QQuickTouchPoint::setX(qreal x)
+{
+    if (_x == x)
+        return;
+    _x = x;
+    emit xChanged();
+}
+
+void QQuickTouchPoint::setY(qreal y)
+{
+    if (_y == y)
+        return;
+    _y = y;
+    emit yChanged();
+}
+
+/*!
+    \qmlproperty real QtQuick2::TouchPoint::pressure
+    \qmlproperty rectangle QtQuick2::TouchPoint::area
+
+    These properties hold additional information about the current state of the touch point.
+
+    \list
+    \i \c pressure is a value in the range of 0.0 to 1.0.
+    \i \c area is a rectangle covering the area of the touch point, centered on the current position of the touch point.
+    \endlist
+*/
+void QQuickTouchPoint::setPressure(qreal pressure)
+{
+    if (_pressure == pressure)
+        return;
+    _pressure = pressure;
+    emit pressureChanged();
+}
+
+void QQuickTouchPoint::setArea(const QRectF &area)
+{
+    if (_area == area)
+        return;
+    _area = area;
+    emit areaChanged();
+}
+
+/*!
+    \qmlproperty bool QtQuick2::TouchPoint::valid
+
+    This property holds whether the touch point is valid.
+
+    An invalid touch point is one that has not yet been pressed,
+    or has already been released.
+*/
+void QQuickTouchPoint::setValid(bool valid)
+{
+    if (_valid == valid)
+        return;
+    _valid = valid;
+    emit validityChanged();
+}
+
+/*!
+    \qmlproperty real QtQuick2::TouchPoint::startX
+    \qmlproperty real QtQuick2::TouchPoint::startY
+
+    These properties hold the starting position of the touch point.
+*/
+
+void QQuickTouchPoint::setStartX(qreal startX)
+{
+    if (_startX == startX)
+        return;
+    _startX = startX;
+    emit startXChanged();
+}
+
+void QQuickTouchPoint::setStartY(qreal startY)
+{
+    if (_startY == startY)
+        return;
+    _startY = startY;
+    emit startYChanged();
+}
+
+/*!
+    \qmlproperty real QtQuick2::TouchPoint::previousX
+    \qmlproperty real QtQuick2::TouchPoint::previousY
+
+    These properties hold the previous position of the touch point.
+*/
+void QQuickTouchPoint::setPreviousX(qreal previousX)
+{
+    if (_previousX == previousX)
+        return;
+    _previousX = previousX;
+    emit previousXChanged();
+}
+
+void QQuickTouchPoint::setPreviousY(qreal previousY)
+{
+    if (_previousY == previousY)
+        return;
+    _previousY = previousY;
+    emit previousYChanged();
+}
+
+/*!
+    \qmlproperty real QtQuick2::TouchPoint::sceneX
+    \qmlproperty real QtQuick2::TouchPoint::sceneY
+
+    These properties hold the current position of the touch point in scene coordinates.
+*/
+
+void QQuickTouchPoint::setSceneX(qreal sceneX)
+{
+    if (_sceneX == sceneX)
+        return;
+    _sceneX = sceneX;
+    emit sceneXChanged();
+}
+
+void QQuickTouchPoint::setSceneY(qreal sceneY)
+{
+    if (_sceneY == sceneY)
+        return;
+    _sceneY = sceneY;
+    emit sceneYChanged();
+}
+
+/*!
+    \qmlclass MultiPointTouchArea QQuickMultiPointTouchArea
+    \inqmlmodule QtQuick 2
+    \brief The MultiPointTouchArea item enables handling of multiple touch points.
+    \inherits Item
+
+    A MultiPointTouchArea is an invisible item that is used to track multiple touch points.
+
+    The \l enabled property is used to enable and disable touch handling. When disabled,
+    the touch area becomes transparent to mouse/touch events.
+
+    MultiPointTouchArea can be used in two ways:
+
+    \list
+    \o setting \c touchPoints to provide touch point objects with properties that can be bound to
+    \o using the onTouchUpdated or onTouchPointsPressed, onTouchPointsUpdated and onTouchPointsReleased handlers
+    \endlist
+
+    While a MultiPointTouchArea \i can take exclusive ownership of certain touch points, it is also possible to have
+    multiple MultiPointTouchAreas active at the same time, each operating on a different set of touch points.
+
+    \sa TouchPoint
+*/
+
+/*!
+    \qmlsignal QtQuick2::MultiPointTouchArea::touchPointsPressed(list<TouchPoint> touchPoints)
+
+    This handler is called when new touch points are added. \a touchPoints is a list of these new points.
+
+    If minimumTouchPoints is set to a value greater than one, this handler will not be called until the minimum number
+    of required touch points has been reached. At that point, touchPointsPressed will be called with all the current touch points.
+*/
+
+/*!
+    \qmlsignal QtQuick2::MultiPointTouchArea::touchPointsUpdated(list<TouchPoint> touchPoints)
+
+    This handler is called when existing touch points are updated. \a touchPoints is a list of these updated points.
+*/
+
+/*!
+    \qmlsignal QtQuick2::MultiPointTouchArea::touchPointsReleased(list<TouchPoint> touchPoints)
+
+    This handler is called when existing touch points are removed. \a touchPoints is a list of these removed points.
+*/
+
+/*!
+    \qmlsignal QtQuick2::MultiPointTouchArea::touchPointsCanceled(list<TouchPoint> touchPoints)
+
+    This handler is called when new touch events have been canceled because another element stole the touch event handling.
+
+    This signal is for advanced use: it is useful when there is more than one MultiPointTouchArea
+    that is handling input, or when there is a MultiPointTouchArea inside a \l Flickable. In the latter
+    case, if you execute some logic on the touchPointsPressed signal and then start dragging, the
+    \l Flickable may steal the touch handling from the MultiPointTouchArea. In these cases, to reset
+    the logic when the MultiPointTouchArea has lost the touch handling to the \l Flickable,
+    \c onTouchPointsCanceled should be used in addition to onTouchPointsReleased.
+
+    \a touchPoints is the list of canceled points.
+*/
+
+/*!
+    \qmlsignal QtQuick2::MultiPointTouchArea::gestureStarted(GestureEvent gesture)
+
+    This handler is called when the global drag threshold has been reached.
+
+    This function is typically used when a MultiPointTouchAreas has been nested in a Flickable or another MultiPointTouchArea.
+    Wnen the threshold has been reached, and the handler called, you can determine whether or not the touch
+    area should grab the current touch points. By default they will not be grabbed; to grab them call \c gesture.grab(). If the
+    gesture is not grabbed, the nesting Flickable, for example, would also have an opportunity to grab.
+
+    The gesture object also includes information on the current set of \c touchPoints and the \c dragThreshold.
+*/
+
+/*!
+    \qmlsignal QtQuick2::MultiPointTouchArea::touchUpdated(list<TouchPoint> touchPoints)
+
+    This handler is called when the touch points handled by the MultiPointTouchArea change. This includes adding new touch points,
+    removing previous touch points, as well as updating current touch point data. \a touchPoints is the list of all current touch
+    points.
+*/
+
+/*!
+    \qmlproperty list<TouchPoint> QtQuick2::MultiPointTouchArea::touchPoints
+
+    This property holds a set of user-defined touch point objects that can be bound to.
+
+    In the following example, we have two small rectangles that follow our touch points.
+
+    \snippet doc/src/snippets/declarative/multipointtoucharea/multipointtoucharea.qml 0
+
+    By default this property holds an empty list.
+
+    \sa TouchPoint
+*/
+
+QQuickMultiPointTouchArea::QQuickMultiPointTouchArea(QQuickItem *parent)
+    : QQuickItem(parent),
+      _minimumTouchPoints(0),
+      _maximumTouchPoints(INT_MAX),
+      _stealMouse(false)
+{
+    setAcceptedMouseButtons(Qt::LeftButton);
+    setFiltersChildMouseEvents(true);
+}
+
+QQuickMultiPointTouchArea::~QQuickMultiPointTouchArea()
+{
+    clearTouchLists();
+    foreach (QObject *obj, _touchPoints) {
+        QQuickTouchPoint *dtp = static_cast<QQuickTouchPoint*>(obj);
+        if (!dtp->isQmlDefined())
+            delete dtp;
+    }
+}
+
+/*!
+    \qmlproperty int QtQuick2::MultiPointTouchArea::minimumTouchPoints
+    \qmlproperty int QtQuick2::MultiPointTouchArea::maximumTouchPoints
+
+    These properties hold the range of touch points to be handled by the touch area.
+
+    These are convenience that allow you to, for example, have nested MultiPointTouchAreas,
+    one handling two finger touches, and another handling three finger touches.
+
+    By default, all touch points within the touch area are handled.
+*/
+
+int QQuickMultiPointTouchArea::minimumTouchPoints() const
+{
+    return _minimumTouchPoints;
+}
+
+void QQuickMultiPointTouchArea::setMinimumTouchPoints(int num)
+{
+    if (_minimumTouchPoints == num)
+        return;
+    _minimumTouchPoints = num;
+    emit minimumTouchPointsChanged();
+}
+
+int QQuickMultiPointTouchArea::maximumTouchPoints() const
+{
+    return _maximumTouchPoints;
+}
+
+void QQuickMultiPointTouchArea::setMaximumTouchPoints(int num)
+{
+    if (_maximumTouchPoints == num)
+        return;
+    _maximumTouchPoints = num;
+    emit maximumTouchPointsChanged();
+}
+
+void QQuickMultiPointTouchArea::touchEvent(QTouchEvent *event)
+{
+    switch (event->type()) {
+    case QEvent::TouchBegin:
+    case QEvent::TouchUpdate:
+    case QEvent::TouchEnd: {
+        //if e.g. a parent Flickable has the mouse grab, don't process the touch events
+        QQuickCanvas *c = canvas();
+        QQuickItem *grabber = c ? c->mouseGrabberItem() : 0;
+        if (grabber && grabber != this && grabber->keepMouseGrab() && grabber->isEnabled()) {
+            QQuickItem *item = this;
+            while ((item = item->parentItem())) {
+                if (item == grabber)
+                    return;
+            }
+        }
+        updateTouchData(event);
+        if (event->type() == QEvent::TouchEnd) {
+            //TODO: move to canvas
+            _stealMouse = false;
+            setKeepMouseGrab(false);
+            QQuickCanvas *c = canvas();
+            if (c && c->mouseGrabberItem() == this)
+                ungrabMouse();
+            setKeepTouchGrab(false);
+            ungrabTouchPoints();
+        }
+        break;
+    }
+    default:
+        QQuickItem::touchEvent(event);
+        break;
+    }
+}
+
+void QQuickMultiPointTouchArea::grabGesture()
+{
+    _stealMouse = true;
+
+    grabMouse();
+    setKeepMouseGrab(true);
+
+    grabTouchPoints(_touchPoints.keys());
+    setKeepTouchGrab(true);
+}
+
+void QQuickMultiPointTouchArea::updateTouchData(QEvent *event)
+{
+    bool ended = false;
+    bool moved = false;
+    bool started = false;
+
+    clearTouchLists();
+    QTouchEvent *e = static_cast<QTouchEvent*>(event);
+    QList<QTouchEvent::TouchPoint> touchPoints = e->touchPoints();
+    int numTouchPoints = touchPoints.count();
+    //always remove released touches, and make sure we handle all releases before adds.
+    foreach (QTouchEvent::TouchPoint p, touchPoints) {
+        Qt::TouchPointState touchPointState = p.state();
+        int id = p.id();
+        if (touchPointState & Qt::TouchPointReleased) {
+            QQuickTouchPoint* dtp = static_cast<QQuickTouchPoint*>(_touchPoints.value(id));
+            if (!dtp)
+                continue;
+            _releasedTouchPoints.append(dtp);
+            _touchPoints.remove(id);
+            ended = true;
+        }
+    }
+    if (numTouchPoints >= _minimumTouchPoints && numTouchPoints <= _maximumTouchPoints) {
+        foreach (QTouchEvent::TouchPoint p, touchPoints) {
+            Qt::TouchPointState touchPointState = p.state();
+            int id = p.id();
+            if (touchPointState & Qt::TouchPointReleased) {
+                //handled above
+            } else if (!_touchPoints.contains(id)) { //could be pressed, moved, or stationary
+                addTouchPoint(&p);
+                started = true;
+            } else if (touchPointState & Qt::TouchPointMoved) {
+                QQuickTouchPoint* dtp = static_cast<QQuickTouchPoint*>(_touchPoints[id]);
+                Q_ASSERT(dtp);
+                _movedTouchPoints.append(dtp);
+                updateTouchPoint(dtp,&p);
+                moved = true;
+            } else {
+                QQuickTouchPoint* dtp = static_cast<QQuickTouchPoint*>(_touchPoints[id]);
+                Q_ASSERT(dtp);
+                updateTouchPoint(dtp,&p);
+            }
+        }
+
+        //see if we should be grabbing the gesture
+        if (!_stealMouse /* !ignoring gesture*/) {
+            bool offerGrab = false;
+            const int dragThreshold = qApp->styleHints()->startDragDistance();
+            foreach (const QTouchEvent::TouchPoint &p, touchPoints) {
+                if (p.state() == Qt::TouchPointReleased)
+                    continue;
+                const QPointF &currentPos = p.scenePos();
+                const QPointF &startPos = p.startScenePos();
+                if (qAbs(currentPos.x() - startPos.x()) > dragThreshold)
+                    offerGrab = true;
+                else if (qAbs(currentPos.y() - startPos.y()) > dragThreshold)
+                    offerGrab = true;
+                if (offerGrab)
+                    break;
+            }
+
+            if (offerGrab) {
+                QQuickGrabGestureEvent event;
+                event._touchPoints = _touchPoints.values();
+                emit gestureStarted(&event);
+                if (event.wantsGrab())
+                    grabGesture();
+            }
+        }
+
+        if (ended) emit(touchPointsReleased(_releasedTouchPoints));
+        if (moved) emit(touchPointsUpdated(_movedTouchPoints));
+        if (started) emit(touchPointsPressed(_pressedTouchPoints));
+        if (!_touchPoints.isEmpty()) emit touchUpdated(_touchPoints.values());
+    }
+}
+
+void QQuickMultiPointTouchArea::clearTouchLists()
+{
+    foreach (QObject *obj, _releasedTouchPoints) {
+        QQuickTouchPoint *dtp = static_cast<QQuickTouchPoint*>(obj);
+        if (!dtp->isQmlDefined())
+            delete dtp;
+        else
+            dtp->setValid(false);
+    }
+    _releasedTouchPoints.clear();
+    _pressedTouchPoints.clear();
+    _movedTouchPoints.clear();
+}
+
+void QQuickMultiPointTouchArea::addTouchPoint(const QTouchEvent::TouchPoint *p)
+{
+    QQuickTouchPoint *dtp = 0;
+    foreach (QQuickTouchPoint* tp, _touchPrototypes) {
+        if (!tp->isValid()) {
+            tp->setValid(true);
+            dtp = tp;
+            break;
+        }
+    }
+
+    if (dtp == 0)
+        dtp = new QQuickTouchPoint(false);
+    dtp->setPointId(p->id());
+    updateTouchPoint(dtp,p);
+    _touchPoints.insert(p->id(),dtp);
+    //we may have just obtained enough points to start tracking them -- in that case moved or stationary count as newly pressed
+    if (p->state() & Qt::TouchPointPressed || p->state() & Qt::TouchPointMoved || p->state() & Qt::TouchPointStationary)
+        _pressedTouchPoints.append(dtp);
+}
+
+void QQuickMultiPointTouchArea::addTouchPrototype(QQuickTouchPoint *prototype)
+{
+    int id = _touchPrototypes.count();
+    prototype->setPointId(id);
+    _touchPrototypes.insert(id, prototype);
+}
+
+void QQuickMultiPointTouchArea::updateTouchPoint(QQuickTouchPoint *dtp, const QTouchEvent::TouchPoint *p)
+{
+    //TODO: if !qmlDefined, could bypass setters.
+    //      also, should only emit signals after all values have been set
+    dtp->setX(p->pos().x());
+    dtp->setY(p->pos().y());
+    dtp->setPressure(p->pressure());
+    dtp->setArea(p->rect());
+    dtp->setStartX(p->startPos().x());
+    dtp->setStartY(p->startPos().y());
+    dtp->setPreviousX(p->lastPos().x());
+    dtp->setPreviousY(p->lastPos().y());
+    dtp->setSceneX(p->scenePos().x());
+    dtp->setSceneY(p->scenePos().y());
+}
+
+void QQuickMultiPointTouchArea::mousePressEvent(QMouseEvent *event)
+{
+    if (!isEnabled()) {
+        QQuickItem::mousePressEvent(event);
+        return;
+    }
+
+    _stealMouse = false;
+    setKeepMouseGrab(false);
+    event->setAccepted(true);
+}
+
+void QQuickMultiPointTouchArea::mouseMoveEvent(QMouseEvent *event)
+{
+    if (!isEnabled()) {
+        QQuickItem::mouseMoveEvent(event);
+        return;
+    }
+
+    //do nothing
+}
+
+void QQuickMultiPointTouchArea::mouseReleaseEvent(QMouseEvent *event)
+{
+    _stealMouse = false;
+    if (!isEnabled()) {
+        QQuickItem::mouseReleaseEvent(event);
+        return;
+    }
+    QQuickCanvas *c = canvas();
+    if (c && c->mouseGrabberItem() == this)
+        ungrabMouse();
+    setKeepMouseGrab(false);
+}
+
+void QQuickMultiPointTouchArea::ungrab()
+{
+    if (_touchPoints.count()) {
+        QQuickCanvas *c = canvas();
+        if (c && c->mouseGrabberItem() == this) {
+            _stealMouse = false;
+            setKeepMouseGrab(false);
+        }
+        setKeepTouchGrab(false);
+        emit touchPointsCanceled(_touchPoints.values());
+        clearTouchLists();
+        foreach (QObject *obj, _touchPoints) {
+            QQuickTouchPoint *dtp = static_cast<QQuickTouchPoint*>(obj);
+            if (!dtp->isQmlDefined())
+                delete dtp;
+            else
+                dtp->setValid(false);
+        }
+        _touchPoints.clear();
+    }
+}
+
+void QQuickMultiPointTouchArea::mouseUngrabEvent()
+{
+    ungrab();
+}
+
+void QQuickMultiPointTouchArea::touchUngrabEvent()
+{
+    ungrab();
+}
+
+bool QQuickMultiPointTouchArea::sendMouseEvent(QMouseEvent *event)
+{
+    QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height()));
+
+    QQuickCanvas *c = canvas();
+    QQuickItem *grabber = c ? c->mouseGrabberItem() : 0;
+    bool stealThisEvent = _stealMouse;
+    if ((stealThisEvent || myRect.contains(event->windowPos())) && (!grabber || !grabber->keepMouseGrab())) {
+        QMouseEvent mouseEvent(event->type(), mapFromScene(event->windowPos()), event->windowPos(), event->screenPos(),
+                               event->button(), event->buttons(), event->modifiers());
+        mouseEvent.setAccepted(false);
+
+        switch (mouseEvent.type()) {
+        case QEvent::MouseMove:
+            mouseMoveEvent(&mouseEvent);
+            break;
+        case QEvent::MouseButtonPress:
+            mousePressEvent(&mouseEvent);
+            break;
+        case QEvent::MouseButtonRelease:
+            mouseReleaseEvent(&mouseEvent);
+            break;
+        default:
+            break;
+        }
+        grabber = c->mouseGrabberItem();
+        if (grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this)
+            grabMouse();
+
+        return stealThisEvent;
+    }
+    if (event->type() == QEvent::MouseButtonRelease) {
+        _stealMouse = false;
+        if (c && c->mouseGrabberItem() == this)
+            ungrabMouse();
+        setKeepMouseGrab(false);
+    }
+    return false;
+}
+
+bool QQuickMultiPointTouchArea::childMouseEventFilter(QQuickItem *i, QEvent *event)
+{
+    if (!isEnabled() || !isVisible())
+        return QQuickItem::childMouseEventFilter(i, event);
+    switch (event->type()) {
+    case QEvent::MouseButtonPress:
+    case QEvent::MouseMove:
+    case QEvent::MouseButtonRelease:
+        return sendMouseEvent(static_cast<QMouseEvent *>(event));
+        break;
+    case QEvent::TouchBegin:
+    case QEvent::TouchUpdate:
+        if (!shouldFilter(event))
+            return false;
+        updateTouchData(event);
+        return _stealMouse;
+    case QEvent::TouchEnd: {
+            if (!shouldFilter(event))
+                return false;
+            updateTouchData(event);
+            //TODO: verify this behavior
+            _stealMouse = false;
+            setKeepMouseGrab(false);
+            QQuickCanvas *c = canvas();
+            if (c && c->mouseGrabberItem() == this)
+                ungrabMouse();
+            setKeepTouchGrab(false);
+            ungrabTouchPoints();
+        }
+        break;
+    default:
+        break;
+    }
+    return QQuickItem::childMouseEventFilter(i, event);
+}
+
+bool QQuickMultiPointTouchArea::shouldFilter(QEvent *event)
+{
+    QQuickCanvas *c = canvas();
+    QQuickItem *grabber = c ? c->mouseGrabberItem() : 0;
+    bool disabledItem = grabber && !grabber->isEnabled();
+    bool stealThisEvent = _stealMouse;
+    bool contains = false;
+    if (!stealThisEvent) {
+        switch (event->type()) {
+        case QEvent::MouseButtonPress:
+        case QEvent::MouseMove:
+        case QEvent::MouseButtonRelease: {
+                QMouseEvent *me = static_cast<QMouseEvent*>(event);
+                QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height()));
+                contains = myRect.contains(me->windowPos());
+            }
+            break;
+        case QEvent::TouchBegin:
+        case QEvent::TouchUpdate:
+        case QEvent::TouchEnd: {
+                QTouchEvent *te = static_cast<QTouchEvent*>(event);
+                QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height()));
+                foreach (const QTouchEvent::TouchPoint &point, te->touchPoints()) {
+                    if (myRect.contains(point.scenePos())) {
+                        contains = true;
+                        break;
+                    }
+                }
+            }
+            break;
+        default:
+            break;
+        }
+    }
+    if ((stealThisEvent || contains) && (!grabber || !grabber->keepMouseGrab() || disabledItem)) {
+        return true;
+    }
+    ungrab();
+    return false;
+}
+
+QT_END_NAMESPACE
diff --git a/src/declarative/items/qquickmultipointtoucharea_p.h b/src/declarative/items/qquickmultipointtoucharea_p.h
new file mode 100644 (file)
index 0000000..dac7045
--- /dev/null
@@ -0,0 +1,267 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtDeclarative module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQUICKMULTIPOINTTOUCHAREA_H
+#define QQUICKMULTIPOINTTOUCHAREA_H
+
+#include "qquickitem.h"
+#include "qevent.h"
+
+#include <QMap>
+#include <QList>
+#include <QtGui/qguiapplication.h>
+#include <QtGui/qstylehints.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Declarative)
+
+class QQuickMultiPointTouchArea;
+class Q_AUTOTEST_EXPORT QQuickTouchPoint : public QObject
+{
+    Q_OBJECT
+    Q_PROPERTY(bool valid READ isValid NOTIFY validityChanged)
+    Q_PROPERTY(int pointId READ pointId NOTIFY pointIdChanged)
+    Q_PROPERTY(qreal x READ x NOTIFY xChanged)
+    Q_PROPERTY(qreal y READ y NOTIFY yChanged)
+    Q_PROPERTY(qreal pressure READ pressure NOTIFY pressureChanged)
+    Q_PROPERTY(QRectF area READ area NOTIFY areaChanged)
+
+    Q_PROPERTY(qreal startX READ startX NOTIFY startXChanged)
+    Q_PROPERTY(qreal startY READ startY NOTIFY startYChanged)
+    Q_PROPERTY(qreal previousX READ previousX NOTIFY previousXChanged)
+    Q_PROPERTY(qreal previousY READ previousY NOTIFY previousYChanged)
+    Q_PROPERTY(qreal sceneX READ sceneX NOTIFY sceneXChanged)
+    Q_PROPERTY(qreal sceneY READ sceneY NOTIFY sceneYChanged)
+
+public:
+    QQuickTouchPoint(bool qmlDefined = true)
+        : _id(0),
+          _x(0.0), _y(0.0),
+          _pressure(0.0),
+          _qmlDefined(qmlDefined),
+          _valid(!qmlDefined),
+          _previousX(0.0), _previousY(0.0),
+          _sceneX(0.0), _sceneY(0.0)
+    {}
+
+    int pointId() const { return _id; }
+    void setPointId(int id);
+
+    qreal x() const { return _x; }
+    void setX(qreal x);
+
+    qreal y() const { return _y; }
+    void setY(qreal y);
+
+    qreal pressure() const { return _pressure; }
+    void setPressure(qreal pressure);
+
+    QRectF area() const { return _area; }
+    void setArea(const QRectF &area);
+
+    bool isQmlDefined() { return _qmlDefined; }
+
+    bool isValid() { return _valid; }
+    void setValid(bool valid);
+
+    qreal startX() const { return _startX; }
+    void setStartX(qreal startX);
+
+    qreal startY() const { return _startY; }
+    void setStartY(qreal startY);
+
+    qreal previousX() const { return _previousX; }
+    void setPreviousX(qreal previousX);
+
+    qreal previousY() const { return _previousY; }
+    void setPreviousY(qreal previousY);
+
+    qreal sceneX() const { return _sceneX; }
+    void setSceneX(qreal sceneX);
+
+    qreal sceneY() const { return _sceneY; }
+    void setSceneY(qreal sceneY);
+
+
+Q_SIGNALS:
+    void pointIdChanged();
+    void xChanged();
+    void yChanged();
+    void pressureChanged();
+    void areaChanged();
+    void validityChanged();
+    void startXChanged();
+    void startYChanged();
+    void previousXChanged();
+    void previousYChanged();
+    void sceneXChanged();
+    void sceneYChanged();
+
+private:
+    friend class QQuickMultiPointTouchArea;
+    int _id;
+    qreal _x;
+    qreal _y;
+    qreal _pressure;
+    QRectF _area;
+    bool _qmlDefined;
+    bool _valid;
+    qreal _startX;
+    qreal _startY;
+    qreal _previousX;
+    qreal _previousY;
+    qreal _sceneX;
+    qreal _sceneY;
+};
+
+class QQuickGrabGestureEvent : public QObject
+{
+    Q_OBJECT
+    Q_PROPERTY(QDeclarativeListProperty<QObject> touchPoints READ touchPoints)
+    Q_PROPERTY(qreal dragThreshold READ dragThreshold)
+public:
+    QQuickGrabGestureEvent() : _grab(false), _dragThreshold(qApp->styleHints()->startDragDistance()) {}
+
+    Q_INVOKABLE void grab() { _grab = true; }
+    bool wantsGrab() const { return _grab; }
+
+    QDeclarativeListProperty<QObject> touchPoints() {
+        return QDeclarativeListProperty<QObject>(this, _touchPoints);
+    }
+    qreal dragThreshold() const { return _dragThreshold; }
+
+private:
+    friend class QQuickMultiPointTouchArea;
+    bool _grab;
+    qreal _dragThreshold;
+    QList<QObject*> _touchPoints;
+};
+
+class Q_AUTOTEST_EXPORT QQuickMultiPointTouchArea : public QQuickItem
+{
+    Q_OBJECT
+
+    Q_PROPERTY(QDeclarativeListProperty<QQuickTouchPoint> touchPoints READ touchPoints)
+    Q_PROPERTY(int minimumTouchPoints READ minimumTouchPoints WRITE setMinimumTouchPoints NOTIFY minimumTouchPointsChanged)
+    Q_PROPERTY(int maximumTouchPoints READ maximumTouchPoints WRITE setMaximumTouchPoints NOTIFY maximumTouchPointsChanged)
+
+public:
+    QQuickMultiPointTouchArea(QQuickItem *parent=0);
+    ~QQuickMultiPointTouchArea();
+
+    int minimumTouchPoints() const;
+    void setMinimumTouchPoints(int num);
+    int maximumTouchPoints() const;
+    void setMaximumTouchPoints(int num);
+
+    QDeclarativeListProperty<QQuickTouchPoint> touchPoints() {
+        return QDeclarativeListProperty<QQuickTouchPoint>(this, 0, QQuickMultiPointTouchArea::touchPoint_append, QQuickMultiPointTouchArea::touchPoint_count, QQuickMultiPointTouchArea::touchPoint_at, 0);
+    }
+
+    static void touchPoint_append(QDeclarativeListProperty<QQuickTouchPoint> *list, QQuickTouchPoint* touch) {
+        QQuickMultiPointTouchArea *q = static_cast<QQuickMultiPointTouchArea*>(list->object);
+        q->addTouchPrototype(touch);
+    }
+
+    static int touchPoint_count(QDeclarativeListProperty<QQuickTouchPoint> *list) {
+        QQuickMultiPointTouchArea *q = static_cast<QQuickMultiPointTouchArea*>(list->object);
+        return q->_touchPrototypes.count();
+    }
+
+    static QQuickTouchPoint* touchPoint_at(QDeclarativeListProperty<QQuickTouchPoint> *list, int index) {
+        QQuickMultiPointTouchArea *q = static_cast<QQuickMultiPointTouchArea*>(list->object);
+        return q->_touchPrototypes[index];
+    }
+
+Q_SIGNALS:
+    void touchPointsPressed(const QList<QObject*> &touchPoints);
+    void touchPointsUpdated(const QList<QObject*> &touchPoints);
+    void touchPointsReleased(const QList<QObject*> &touchPoints);
+    void touchPointsCanceled(const QList<QObject*> &touchPoints);
+    void gestureStarted(QQuickGrabGestureEvent *gesture);
+    void touchUpdated(const QList<QObject*> &touchPoints);
+    void minimumTouchPointsChanged();
+    void maximumTouchPointsChanged();
+
+protected:
+    void touchEvent(QTouchEvent *);
+    bool childMouseEventFilter(QQuickItem *i, QEvent *event);
+    void mousePressEvent(QMouseEvent *event);
+    void mouseReleaseEvent(QMouseEvent *event);
+    void mouseMoveEvent(QMouseEvent *event);
+    void mouseUngrabEvent();
+    void touchUngrabEvent();
+
+    void addTouchPrototype(QQuickTouchPoint* prototype);
+    void addTouchPoint(const QTouchEvent::TouchPoint *p);
+    void clearTouchLists();
+
+    void updateTouchPoint(QQuickTouchPoint*, const QTouchEvent::TouchPoint*);
+    void updateTouchData(QEvent*);
+
+    bool sendMouseEvent(QMouseEvent *event);
+    bool shouldFilter(QEvent *event);
+    void grabGesture();
+
+private:
+    void ungrab();
+    QMap<int,QQuickTouchPoint*> _touchPrototypes;  //TouchPoints defined in QML
+    QMap<int,QObject*> _touchPoints;            //All current touch points
+    QList<QObject*> _releasedTouchPoints;
+    QList<QObject*> _pressedTouchPoints;
+    QList<QObject*> _movedTouchPoints;
+    int _minimumTouchPoints;
+    int _maximumTouchPoints;
+    bool _stealMouse;
+};
+
+QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QQuickTouchPoint)
+QML_DECLARE_TYPE(QQuickGrabGestureEvent)
+QML_DECLARE_TYPE(QQuickMultiPointTouchArea)
+
+QT_END_HEADER
+
+#endif // QQUICKMULTIPOINTTOUCHAREA_H
index cd43094..148c4ad 100644 (file)
@@ -77,6 +77,7 @@ QUICKTESTS =  \
     qquicklistview \
     qquickloader \
     qquickmousearea \
+    qquickmultipointtoucharea \
     qquickpathview \
     qquickpincharea \
     qquickpositioners \
index d2b691d..640b4b4 100644 (file)
@@ -153,8 +153,9 @@ protected:
         mousePressId = ++mousePressNum;
     }
 
-    bool childMouseEventFilter(QQuickItem *, QEvent *) {
-        mousePressId = ++mousePressNum;
+    bool childMouseEventFilter(QQuickItem *, QEvent *event) {
+        if (event->type() == QEvent::MouseButtonPress)
+            mousePressId = ++mousePressNum;
         return false;
     }
 
diff --git a/tests/auto/declarative/qquickmultipointtoucharea/data/inFlickable.qml b/tests/auto/declarative/qquickmultipointtoucharea/data/inFlickable.qml
new file mode 100644 (file)
index 0000000..53a2bf8
--- /dev/null
@@ -0,0 +1,25 @@
+import QtQuick 2.0
+
+Flickable {
+    width: 240
+    height: 320
+
+    contentWidth: width
+    contentHeight: height * 2
+
+    MultiPointTouchArea {
+        anchors.fill: parent
+        minimumTouchPoints: 2
+        maximumTouchPoints: 2
+        onGestureStarted: {
+            if ((Math.abs(point2.x - point2.startX) > gesture.dragThreshold/2) && (Math.abs(point1.x - point1.startX) > gesture.dragThreshold/2)) {
+                gesture.grab()
+            }
+        }
+        touchPoints: [
+            TouchPoint { id: point1; objectName: "point1" },
+            TouchPoint { id: point2; objectName: "point2" }
+        ]
+    }
+}
+
diff --git a/tests/auto/declarative/qquickmultipointtoucharea/data/nested.qml b/tests/auto/declarative/qquickmultipointtoucharea/data/nested.qml
new file mode 100644 (file)
index 0000000..37b8820
--- /dev/null
@@ -0,0 +1,27 @@
+import QtQuick 2.0
+
+MultiPointTouchArea {
+    width: 240
+    height: 320
+
+    property bool grabInnerArea: true
+
+    minimumTouchPoints: 2
+    maximumTouchPoints: 3
+    touchPoints: [
+        TouchPoint { objectName: "point11" },
+        TouchPoint { objectName: "point12" }
+    ]
+
+    MultiPointTouchArea {
+        anchors.fill: parent
+        minimumTouchPoints: 3
+        maximumTouchPoints: 3
+        onGestureStarted: if (grabInnerArea) gesture.grab()
+        touchPoints: [
+            TouchPoint { objectName: "point21" },
+            TouchPoint { objectName: "point22" },
+            TouchPoint { objectName: "point23" }
+        ]
+    }
+}
diff --git a/tests/auto/declarative/qquickmultipointtoucharea/data/nonOverlapping.qml b/tests/auto/declarative/qquickmultipointtoucharea/data/nonOverlapping.qml
new file mode 100644 (file)
index 0000000..039607e
--- /dev/null
@@ -0,0 +1,32 @@
+import QtQuick 2.0
+
+Rectangle {
+    width: 240
+    height: 320
+
+    MultiPointTouchArea {
+        width: parent.width
+        height: 160
+        minimumTouchPoints: 2
+        maximumTouchPoints: 2
+        onGestureStarted: gesture.grab()
+        touchPoints: [
+            TouchPoint { objectName: "point11" },
+            TouchPoint { objectName: "point12" }
+        ]
+    }
+
+    MultiPointTouchArea {
+        width: parent.width
+        height: 160
+        y: 160
+        minimumTouchPoints: 3
+        maximumTouchPoints: 3
+        onGestureStarted: gesture.grab()
+        touchPoints: [
+            TouchPoint { objectName: "point21" },
+            TouchPoint { objectName: "point22" },
+            TouchPoint { objectName: "point23" }
+        ]
+    }
+}
diff --git a/tests/auto/declarative/qquickmultipointtoucharea/data/properties.qml b/tests/auto/declarative/qquickmultipointtoucharea/data/properties.qml
new file mode 100644 (file)
index 0000000..98ef1a9
--- /dev/null
@@ -0,0 +1,15 @@
+import QtQuick 2.0
+
+MultiPointTouchArea {
+    width: 240
+    height: 320
+
+    minimumTouchPoints: 2
+    maximumTouchPoints: 4
+    touchPoints: [
+        TouchPoint {},
+        TouchPoint {},
+        TouchPoint {},
+        TouchPoint {}
+    ]
+}
diff --git a/tests/auto/declarative/qquickmultipointtoucharea/data/signalTest.qml b/tests/auto/declarative/qquickmultipointtoucharea/data/signalTest.qml
new file mode 100644 (file)
index 0000000..3a6aa86
--- /dev/null
@@ -0,0 +1,25 @@
+import QtQuick 2.0
+
+MultiPointTouchArea {
+    width: 240
+    height: 320
+
+    function clearCounts() {
+        touchPointPressCount = 0;
+        touchPointUpdateCount = 0;
+        touchPointReleaseCount = 0;
+        touchCount = 0;
+    }
+
+    property int touchPointPressCount: 0
+    property int touchPointUpdateCount: 0
+    property int touchPointReleaseCount: 0
+    property int touchCount: 0
+
+    maximumTouchPoints: 5
+
+    onTouchPointsPressed: { touchPointPressCount = touchPoints.length }
+    onTouchPointsUpdated: { touchPointUpdateCount = touchPoints.length }
+    onTouchPointsReleased: { touchPointReleaseCount = touchPoints.length }
+    onTouchUpdated: { touchCount = touchPoints.length }
+}
diff --git a/tests/auto/declarative/qquickmultipointtoucharea/qquickmultipointtoucharea.pro b/tests/auto/declarative/qquickmultipointtoucharea/qquickmultipointtoucharea.pro
new file mode 100644 (file)
index 0000000..06be9d4
--- /dev/null
@@ -0,0 +1,11 @@
+load(qttest_p4)
+contains(QT_CONFIG,declarative): QT += declarative gui
+macx:CONFIG -= app_bundle
+
+SOURCES += tst_qquickmultipointtoucharea.cpp
+
+importFiles.files = data
+importFiles.path = .
+DEPLOYMENT += importFiles
+
+QT += core-private gui-private declarative-private
diff --git a/tests/auto/declarative/qquickmultipointtoucharea/tst_qquickmultipointtoucharea.cpp b/tests/auto/declarative/qquickmultipointtoucharea/tst_qquickmultipointtoucharea.cpp
new file mode 100644 (file)
index 0000000..9acef50
--- /dev/null
@@ -0,0 +1,586 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtTest/QtTest>
+#include <QtTest/QSignalSpy>
+#include <private/qquickmultipointtoucharea_p.h>
+#include <private/qquickflickable_p.h>
+#include <QtDeclarative/qquickview.h>
+
+class tst_QQuickMultiPointTouchArea: public QObject
+{
+    Q_OBJECT
+private slots:
+    void initTestCase() {}
+    void cleanupTestCase() {}
+
+    void properties();
+    void signalTest();
+    void nonOverlapping();
+    void nested();
+    void inFlickable();
+
+private:
+    QQuickView *createAndShowView(const QString &file);
+};
+
+void tst_QQuickMultiPointTouchArea::properties()
+{
+    QQuickView *canvas = createAndShowView("properties.qml");
+    QVERIFY(canvas->rootObject() != 0);
+
+    QQuickMultiPointTouchArea *area = qobject_cast<QQuickMultiPointTouchArea *>(canvas->rootObject());
+    QVERIFY(area != 0);
+
+    QCOMPARE(area->minimumTouchPoints(), 2);
+    QCOMPARE(area->maximumTouchPoints(), 4);
+
+    QDeclarativeListReference ref(area, "touchPoints");
+    QCOMPARE(ref.count(), 4);
+
+    delete canvas;
+}
+
+void tst_QQuickMultiPointTouchArea::signalTest()
+{
+    QQuickView *canvas = createAndShowView("signalTest.qml");
+    QVERIFY(canvas->rootObject() != 0);
+
+    QQuickMultiPointTouchArea *area = qobject_cast<QQuickMultiPointTouchArea *>(canvas->rootObject());
+    QVERIFY(area != 0);
+
+    QPoint p1(20,100);
+    QPoint p2(40,100);
+    QPoint p3(60,100);
+    QPoint p4(80,100);
+    QPoint p5(100,100);
+
+    QTest::QTouchEventSequence sequence = QTest::touchEvent(canvas);
+
+    sequence.press(0, p1).press(1, p2).commit();
+
+    QCOMPARE(area->property("touchPointPressCount").toInt(), 2);
+    QCOMPARE(area->property("touchPointUpdateCount").toInt(), 0);
+    QCOMPARE(area->property("touchPointReleaseCount").toInt(), 0);
+    QCOMPARE(area->property("touchCount").toInt(), 2);
+    QMetaObject::invokeMethod(area, "clearCounts");
+
+    sequence.stationary(0).stationary(1).press(2, p3).commit();
+
+    QCOMPARE(area->property("touchPointPressCount").toInt(), 1);
+    QCOMPARE(area->property("touchPointUpdateCount").toInt(), 0);
+    QCOMPARE(area->property("touchPointReleaseCount").toInt(), 0);
+    QCOMPARE(area->property("touchCount").toInt(), 3);
+    QMetaObject::invokeMethod(area, "clearCounts");
+
+    p1 -= QPoint(10,10);
+    p2 += QPoint(10,10);
+    sequence.move(0, p1).move(1, p2).stationary(2).commit();
+
+    QCOMPARE(area->property("touchPointPressCount").toInt(), 0);
+    QCOMPARE(area->property("touchPointUpdateCount").toInt(), 2);
+    QCOMPARE(area->property("touchPointReleaseCount").toInt(), 0);
+    QCOMPARE(area->property("touchCount").toInt(), 3);
+    QMetaObject::invokeMethod(area, "clearCounts");
+
+    p3 += QPoint(10,10);
+    sequence.release(0, p1).release(1, p2)
+            .move(2, p3).press(3, p4).press(4, p5).commit();
+
+    QCOMPARE(area->property("touchPointPressCount").toInt(), 2);
+    QCOMPARE(area->property("touchPointUpdateCount").toInt(), 1);
+    QCOMPARE(area->property("touchPointReleaseCount").toInt(), 2);
+    QCOMPARE(area->property("touchCount").toInt(), 3);
+    QMetaObject::invokeMethod(area, "clearCounts");
+
+    sequence.release(2, p3).release(3, p4).release(4, p5).commit();
+
+    QCOMPARE(area->property("touchPointPressCount").toInt(), 0);
+    QCOMPARE(area->property("touchPointUpdateCount").toInt(), 0);
+    QCOMPARE(area->property("touchPointReleaseCount").toInt(), 3);
+    QCOMPARE(area->property("touchCount").toInt(), 0);
+    QMetaObject::invokeMethod(area, "clearCounts");
+
+    delete canvas;
+}
+
+void tst_QQuickMultiPointTouchArea::nonOverlapping()
+{
+    QQuickView *canvas = createAndShowView("nonOverlapping.qml");
+    QVERIFY(canvas->rootObject() != 0);
+
+    QQuickTouchPoint *point11 = canvas->rootObject()->findChild<QQuickTouchPoint*>("point11");
+    QQuickTouchPoint *point12 = canvas->rootObject()->findChild<QQuickTouchPoint*>("point12");
+    QQuickTouchPoint *point21 = canvas->rootObject()->findChild<QQuickTouchPoint*>("point21");
+    QQuickTouchPoint *point22 = canvas->rootObject()->findChild<QQuickTouchPoint*>("point22");
+    QQuickTouchPoint *point23 = canvas->rootObject()->findChild<QQuickTouchPoint*>("point23");
+
+    QCOMPARE(point11->isValid(), false);
+    QCOMPARE(point12->isValid(), false);
+    QCOMPARE(point21->isValid(), false);
+    QCOMPARE(point22->isValid(), false);
+    QCOMPARE(point23->isValid(), false);
+
+    QPoint p1(20,100);
+    QPoint p2(40,100);
+    QPoint p3(60,180);
+    QPoint p4(80,180);
+    QPoint p5(100,180);
+
+    QTest::QTouchEventSequence sequence = QTest::touchEvent(canvas);
+
+    sequence.press(0, p1).commit();
+
+    QCOMPARE(point11->isValid(), false);
+    QCOMPARE(point12->isValid(), false);
+    QCOMPARE(point21->isValid(), false);
+    QCOMPARE(point22->isValid(), false);
+    QCOMPARE(point23->isValid(), false);
+
+    sequence.stationary(0).press(1, p2).commit();
+
+    QCOMPARE(point11->isValid(), true);
+    QCOMPARE(point12->isValid(), true);
+    QCOMPARE(point21->isValid(), false);
+    QCOMPARE(point22->isValid(), false);
+    QCOMPARE(point23->isValid(), false);
+
+    QCOMPARE(point11->x(), qreal(20)); QCOMPARE(point11->y(), qreal(100));
+    QCOMPARE(point12->x(), qreal(40)); QCOMPARE(point12->y(), qreal(100));
+
+    p1 += QPoint(0,10);
+    p2 += QPoint(5,0);
+    sequence.move(0, p1).move(1, p2).commit();
+
+    QCOMPARE(point11->isValid(), true);
+    QCOMPARE(point12->isValid(), true);
+    QCOMPARE(point21->isValid(), false);
+    QCOMPARE(point22->isValid(), false);
+    QCOMPARE(point23->isValid(), false);
+
+    QCOMPARE(point11->x(), qreal(20)); QCOMPARE(point11->y(), qreal(110));
+    QCOMPARE(point12->x(), qreal(45)); QCOMPARE(point12->y(), qreal(100));
+
+    sequence.stationary(0).stationary(1).press(2, p3).commit();
+
+    QCOMPARE(point11->isValid(), true);
+    QCOMPARE(point12->isValid(), true);
+    QCOMPARE(point21->isValid(), false);
+    QCOMPARE(point22->isValid(), false);
+    QCOMPARE(point23->isValid(), false);
+
+    sequence.stationary(0).stationary(1).stationary(2).press(3, p4).press(4, p5).commit();
+
+    QCOMPARE(point11->isValid(), true);
+    QCOMPARE(point12->isValid(), true);
+    QCOMPARE(point21->isValid(), true);
+    QCOMPARE(point22->isValid(), true);
+    QCOMPARE(point23->isValid(), true);
+
+    QCOMPARE(point11->x(), qreal(20)); QCOMPARE(point11->y(), qreal(110));
+    QCOMPARE(point12->x(), qreal(45)); QCOMPARE(point12->y(), qreal(100));
+    QCOMPARE(point21->x(), qreal(60)); QCOMPARE(point21->y(), qreal(20));
+    QCOMPARE(point22->x(), qreal(80)); QCOMPARE(point22->y(), qreal(20));
+    QCOMPARE(point23->x(), qreal(100)); QCOMPARE(point23->y(), qreal(20));
+
+    p1 += QPoint(4,10);
+    p2 += QPoint(17,17);
+    p3 += QPoint(3,0);
+    p4 += QPoint(1,-1);
+    p5 += QPoint(-7,10);
+    sequence.move(0, p1).move(1, p2).move(2, p3).move(3, p4).move(4, p5).commit();
+
+    QCOMPARE(point11->isValid(), true);
+    QCOMPARE(point12->isValid(), true);
+    QCOMPARE(point21->isValid(), true);
+    QCOMPARE(point22->isValid(), true);
+    QCOMPARE(point23->isValid(), true);
+
+    QCOMPARE(point11->x(), qreal(24)); QCOMPARE(point11->y(), qreal(120));
+    QCOMPARE(point12->x(), qreal(62)); QCOMPARE(point12->y(), qreal(117));
+    QCOMPARE(point21->x(), qreal(63)); QCOMPARE(point21->y(), qreal(20));
+    QCOMPARE(point22->x(), qreal(81)); QCOMPARE(point22->y(), qreal(19));
+    QCOMPARE(point23->x(), qreal(93)); QCOMPARE(point23->y(), qreal(30));
+
+    sequence.release(0, p1).release(1, p2).release(2, p3).release(3, p4).release(4, p5).commit();
+
+    //points remain valid immediately after release
+    QCOMPARE(point11->isValid(), true);
+    QCOMPARE(point12->isValid(), true);
+    QCOMPARE(point21->isValid(), true);
+    QCOMPARE(point22->isValid(), true);
+    QCOMPARE(point23->isValid(), true);
+
+    delete canvas;
+}
+
+void tst_QQuickMultiPointTouchArea::nested()
+{
+    QQuickView *canvas = createAndShowView("nested.qml");
+    QVERIFY(canvas->rootObject() != 0);
+
+    QQuickTouchPoint *point11 = canvas->rootObject()->findChild<QQuickTouchPoint*>("point11");
+    QQuickTouchPoint *point12 = canvas->rootObject()->findChild<QQuickTouchPoint*>("point12");
+    QQuickTouchPoint *point21 = canvas->rootObject()->findChild<QQuickTouchPoint*>("point21");
+    QQuickTouchPoint *point22 = canvas->rootObject()->findChild<QQuickTouchPoint*>("point22");
+    QQuickTouchPoint *point23 = canvas->rootObject()->findChild<QQuickTouchPoint*>("point23");
+
+    QCOMPARE(point11->isValid(), false);
+    QCOMPARE(point12->isValid(), false);
+    QCOMPARE(point21->isValid(), false);
+    QCOMPARE(point22->isValid(), false);
+    QCOMPARE(point23->isValid(), false);
+
+    QPoint p1(20,100);
+    QPoint p2(40,100);
+    QPoint p3(60,180);
+
+    QTest::QTouchEventSequence sequence = QTest::touchEvent(canvas);
+
+    sequence.press(0, p1).commit();
+
+    QCOMPARE(point11->isValid(), false);
+    QCOMPARE(point12->isValid(), false);
+    QCOMPARE(point21->isValid(), false);
+    QCOMPARE(point22->isValid(), false);
+    QCOMPARE(point23->isValid(), false);
+
+    sequence.stationary(0).press(1, p2).commit();
+
+    QCOMPARE(point11->isValid(), true);
+    QCOMPARE(point12->isValid(), true);
+    QCOMPARE(point21->isValid(), false);
+    QCOMPARE(point22->isValid(), false);
+    QCOMPARE(point23->isValid(), false);
+
+    QCOMPARE(point11->x(), qreal(20)); QCOMPARE(point11->y(), qreal(100));
+    QCOMPARE(point12->x(), qreal(40)); QCOMPARE(point12->y(), qreal(100));
+
+    p1 += QPoint(0,10);
+    p2 += QPoint(5,0);
+    sequence.move(0, p1).move(1, p2).commit();
+
+    QCOMPARE(point11->isValid(), true);
+    QCOMPARE(point12->isValid(), true);
+    QCOMPARE(point21->isValid(), false);
+    QCOMPARE(point22->isValid(), false);
+    QCOMPARE(point23->isValid(), false);
+
+    QCOMPARE(point11->x(), qreal(20)); QCOMPARE(point11->y(), qreal(110));
+    QCOMPARE(point12->x(), qreal(45)); QCOMPARE(point12->y(), qreal(100));
+
+    sequence.stationary(0).stationary(1).press(2, p3).commit();
+
+    QCOMPARE(point11->isValid(), true);
+    QCOMPARE(point12->isValid(), true);
+    QCOMPARE(point21->isValid(), true);
+    QCOMPARE(point22->isValid(), true);
+    QCOMPARE(point23->isValid(), true);
+
+    //point11 should be same as point21, point12 same as point22
+    QCOMPARE(point11->x(), qreal(20)); QCOMPARE(point11->y(), qreal(110));
+    QCOMPARE(point12->x(), qreal(45)); QCOMPARE(point12->y(), qreal(100));
+    QCOMPARE(point21->x(), qreal(20)); QCOMPARE(point21->y(), qreal(110));
+    QCOMPARE(point22->x(), qreal(45)); QCOMPARE(point22->y(), qreal(100));
+    QCOMPARE(point23->x(), qreal(60)); QCOMPARE(point23->y(), qreal(180));
+
+    sequence.stationary(0).stationary(1).stationary(2).press(3, QPoint(80,180)).press(4, QPoint(100,180)).commit();
+
+    QCOMPARE(point11->isValid(), true);
+    QCOMPARE(point12->isValid(), true);
+    QCOMPARE(point21->isValid(), true);
+    QCOMPARE(point22->isValid(), true);
+    QCOMPARE(point23->isValid(), true);
+
+    //new touch points should be ignored (have no impact on our existing touch points)
+    QCOMPARE(point11->x(), qreal(20)); QCOMPARE(point11->y(), qreal(110));
+    QCOMPARE(point12->x(), qreal(45)); QCOMPARE(point12->y(), qreal(100));
+    QCOMPARE(point21->x(), qreal(20)); QCOMPARE(point21->y(), qreal(110));
+    QCOMPARE(point22->x(), qreal(45)); QCOMPARE(point22->y(), qreal(100));
+    QCOMPARE(point23->x(), qreal(60)); QCOMPARE(point23->y(), qreal(180));
+
+    sequence.stationary(0).stationary(1).stationary(2).release(3, QPoint(80,180)).release(4, QPoint(100,180)).commit();
+
+    p1 += QPoint(4,10);
+    p2 += QPoint(17,17);
+    p3 += QPoint(3,0);
+    sequence.move(0, p1).move(1, p2).move(2, p3).commit();
+
+    QCOMPARE(point11->isValid(), true);
+    QCOMPARE(point12->isValid(), true);
+    QCOMPARE(point21->isValid(), true);
+    QCOMPARE(point22->isValid(), true);
+    QCOMPARE(point23->isValid(), true);
+
+    QCOMPARE(point21->x(), qreal(24)); QCOMPARE(point21->y(), qreal(120));
+    QCOMPARE(point22->x(), qreal(62)); QCOMPARE(point22->y(), qreal(117));
+    QCOMPARE(point21->x(), qreal(24)); QCOMPARE(point21->y(), qreal(120));
+    QCOMPARE(point22->x(), qreal(62)); QCOMPARE(point22->y(), qreal(117));
+    QCOMPARE(point23->x(), qreal(63)); QCOMPARE(point23->y(), qreal(180));
+
+    p1 += QPoint(4,10);
+    p2 += QPoint(17,17);
+    p3 += QPoint(3,0);
+    sequence.move(0, p1).move(1, p2).move(2, p3).commit();
+
+    QCOMPARE(point11->isValid(), false);
+    QCOMPARE(point12->isValid(), false);
+    QCOMPARE(point21->isValid(), true);
+    QCOMPARE(point22->isValid(), true);
+    QCOMPARE(point23->isValid(), true);
+
+    //first two remain the same (touches now grabbed by inner touch area)
+    QCOMPARE(point11->x(), qreal(24)); QCOMPARE(point11->y(), qreal(120));
+    QCOMPARE(point12->x(), qreal(62)); QCOMPARE(point12->y(), qreal(117));
+    QCOMPARE(point21->x(), qreal(28)); QCOMPARE(point21->y(), qreal(130));
+    QCOMPARE(point22->x(), qreal(79)); QCOMPARE(point22->y(), qreal(134));
+    QCOMPARE(point23->x(), qreal(66)); QCOMPARE(point23->y(), qreal(180));
+
+    sequence.release(0, p1).release(1, p2).release(2, p3).commit();
+
+    sequence.press(0, p1).commit();
+
+    QCOMPARE(point11->isValid(), false);
+    QCOMPARE(point12->isValid(), false);
+    QCOMPARE(point21->isValid(), false);
+    QCOMPARE(point22->isValid(), false);
+    QCOMPARE(point23->isValid(), false);
+
+    sequence.release(0, p1).commit();
+
+    //test with grabbing turned off
+    canvas->rootObject()->setProperty("grabInnerArea", false);
+
+    sequence.press(0, p1).press(1, p2).press(2, p3).commit();
+
+    QCOMPARE(point11->isValid(), true);
+    QCOMPARE(point12->isValid(), true);
+    QCOMPARE(point21->isValid(), true);
+    QCOMPARE(point22->isValid(), true);
+    QCOMPARE(point23->isValid(), true);
+
+    p1 -= QPoint(4,10);
+    p2 -= QPoint(17,17);
+    p3 -= QPoint(3,0);
+    sequence.move(0, p1).move(1, p2).move(2, p3).commit();
+
+    QCOMPARE(point11->isValid(), true);
+    QCOMPARE(point12->isValid(), true);
+    QCOMPARE(point21->isValid(), true);
+    QCOMPARE(point22->isValid(), true);
+    QCOMPARE(point23->isValid(), true);
+
+    QCOMPARE(point21->x(), qreal(24)); QCOMPARE(point21->y(), qreal(120));
+    QCOMPARE(point22->x(), qreal(62)); QCOMPARE(point22->y(), qreal(117));
+    QCOMPARE(point21->x(), qreal(24)); QCOMPARE(point21->y(), qreal(120));
+    QCOMPARE(point22->x(), qreal(62)); QCOMPARE(point22->y(), qreal(117));
+    QCOMPARE(point23->x(), qreal(63)); QCOMPARE(point23->y(), qreal(180));
+
+    p1 -= QPoint(4,10);
+    p2 -= QPoint(17,17);
+    p3 -= QPoint(3,0);
+    sequence.move(0, p1).move(1, p2).move(2, p3).commit();
+
+    QCOMPARE(point11->isValid(), true);
+    QCOMPARE(point12->isValid(), true);
+    QCOMPARE(point21->isValid(), true);
+    QCOMPARE(point22->isValid(), true);
+    QCOMPARE(point23->isValid(), true);
+
+    //all change (touches not grabbed by inner touch area)
+    QCOMPARE(point11->x(), qreal(20)); QCOMPARE(point11->y(), qreal(110));
+    QCOMPARE(point12->x(), qreal(45)); QCOMPARE(point12->y(), qreal(100));
+    QCOMPARE(point21->x(), qreal(20)); QCOMPARE(point21->y(), qreal(110));
+    QCOMPARE(point22->x(), qreal(45)); QCOMPARE(point22->y(), qreal(100));
+    QCOMPARE(point23->x(), qreal(60)); QCOMPARE(point23->y(), qreal(180));
+
+    sequence.release(0, p1).release(1, p2).release(2, p3).commit();
+
+    delete canvas;
+}
+
+void tst_QQuickMultiPointTouchArea::inFlickable()
+{
+    QQuickView *canvas = createAndShowView("inFlickable.qml");
+    QVERIFY(canvas->rootObject() != 0);
+
+    QQuickFlickable *flickable = qobject_cast<QQuickFlickable *>(canvas->rootObject());
+    QVERIFY(flickable != 0);
+
+    QQuickTouchPoint *point11 = canvas->rootObject()->findChild<QQuickTouchPoint*>("point1");
+    QQuickTouchPoint *point12 = canvas->rootObject()->findChild<QQuickTouchPoint*>("point2");
+
+    QCOMPARE(point11->isValid(), false);
+    QCOMPARE(point12->isValid(), false);
+
+    QPoint p1(20,100);
+    QPoint p2(40,100);
+
+    //moving one point vertically
+    QTest::touchEvent(canvas).press(0, p1);
+    QTest::mousePress(canvas, Qt::LeftButton, 0, p1);
+
+    p1 += QPoint(0,15);
+    QTest::touchEvent(canvas).move(0, p1);
+    QTest::mouseMove(canvas, p1);
+
+    p1 += QPoint(0,15);
+    QTest::touchEvent(canvas).move(0, p1);
+    QTest::mouseMove(canvas, p1);
+
+    p1 += QPoint(0,15);
+    QTest::touchEvent(canvas).move(0, p1);
+    QTest::mouseMove(canvas, p1);
+
+    p1 += QPoint(0,15);
+    QTest::touchEvent(canvas).move(0, p1);
+    QTest::mouseMove(canvas, p1);
+
+    QVERIFY(flickable->contentY() < 0);
+    QCOMPARE(point11->isValid(), false);
+    QCOMPARE(point12->isValid(), false);
+
+    QTest::touchEvent(canvas).release(0, p1);
+    QTest::mouseRelease(canvas,Qt::LeftButton, 0, p1);
+    QTest::qWait(50);
+
+    QTRY_VERIFY(!flickable->isMoving());
+
+    //moving two points vertically
+    p1 = QPoint(20,100);
+    QTest::touchEvent(canvas).press(0, p1).press(1, p2);
+    QTest::mousePress(canvas, Qt::LeftButton, 0, p1);
+
+    QCOMPARE(point11->isValid(), true);
+    QCOMPARE(point12->isValid(), true);
+
+    p1 += QPoint(0,15); p2 += QPoint(0,15);
+    QTest::touchEvent(canvas).move(0, p1).move(1, p2);
+    QTest::mouseMove(canvas, p1);
+
+    p1 += QPoint(0,15); p2 += QPoint(0,15);
+    QTest::touchEvent(canvas).move(0, p1).move(1, p2);
+    QTest::mouseMove(canvas, p1);
+
+    p1 += QPoint(0,15); p2 += QPoint(0,15);
+    QTest::touchEvent(canvas).move(0, p1).move(1, p2);
+    QTest::mouseMove(canvas, p1);
+
+    p1 += QPoint(0,15); p2 += QPoint(0,15);
+    QTest::touchEvent(canvas).move(0, p1).move(1, p2);
+    QTest::mouseMove(canvas, p1);
+
+    QVERIFY(flickable->contentY() < 0);
+    QCOMPARE(point11->isValid(), false);
+    QCOMPARE(point12->isValid(), false);
+
+    QTest::touchEvent(canvas).release(0, p1).release(1, p2);
+    QTest::mouseRelease(canvas,Qt::LeftButton, 0, p1);
+    QTest::qWait(50);
+
+    QTRY_VERIFY(!flickable->isMoving());
+
+    //moving two points horizontally, then one point vertically
+    p1 = QPoint(20,100);
+    p2 = QPoint(40,100);
+    QTest::touchEvent(canvas).press(0, p1).press(1, p2);
+    QTest::mousePress(canvas, Qt::LeftButton, 0, p1);
+
+    QCOMPARE(point11->isValid(), true);
+    QCOMPARE(point12->isValid(), true);
+
+    p1 += QPoint(15,0); p2 += QPoint(15,0);
+    QTest::touchEvent(canvas).move(0, p1).move(1, p2);
+    QTest::mouseMove(canvas, p1);
+
+    p1 += QPoint(15,0); p2 += QPoint(15,0);
+    QTest::touchEvent(canvas).move(0, p1).move(1, p2);
+    QTest::mouseMove(canvas, p1);
+
+    p1 += QPoint(15,0); p2 += QPoint(15,0);
+    QTest::touchEvent(canvas).move(0, p1).move(1, p2);
+    QTest::mouseMove(canvas, p1);
+
+    p1 += QPoint(15,0); p2 += QPoint(15,0);
+    QTest::touchEvent(canvas).move(0, p1).move(1, p2);
+    QTest::mouseMove(canvas, p1);
+
+    p1 += QPoint(0,15); p2 += QPoint(0,15);
+    QTest::touchEvent(canvas).move(0, p1).move(1, p2);
+    QTest::mouseMove(canvas, p1);
+
+    p1 += QPoint(0,15); p2 += QPoint(0,15);
+    QTest::touchEvent(canvas).move(0, p1).move(1, p2);
+    QTest::mouseMove(canvas, p1);
+
+    p1 += QPoint(0,15); p2 += QPoint(0,15);
+    QTest::touchEvent(canvas).move(0, p1).move(1, p2);
+    QTest::mouseMove(canvas, p1);
+
+    p1 += QPoint(0,15); p2 += QPoint(0,15);
+    QTest::touchEvent(canvas).move(0, p1).move(1, p2);
+    QTest::mouseMove(canvas, p1);
+
+    QVERIFY(flickable->contentY() == 0);
+    QCOMPARE(point11->isValid(), true);
+    QCOMPARE(point12->isValid(), true);
+
+    QTest::touchEvent(canvas).release(0, p1).release(1, p2);
+    QTest::mouseRelease(canvas,Qt::LeftButton, 0, p1);
+    QTest::qWait(50);
+
+    delete canvas;
+}
+
+QQuickView *tst_QQuickMultiPointTouchArea::createAndShowView(const QString &file)
+{
+    QQuickView *canvas = new QQuickView(0);
+    canvas->setSource(QUrl::fromLocalFile(QCoreApplication::applicationDirPath() + QLatin1String("/data/") + file));
+    canvas->show();
+    canvas->requestActivateWindow();
+    QTest::qWaitForWindowShown(canvas);
+
+    return canvas;
+}
+
+QTEST_MAIN(tst_QQuickMultiPointTouchArea)
+
+#include "tst_qquickmultipointtoucharea.moc"