diff options
Diffstat (limited to 'src/quick/items/qquickmultipointtoucharea.cpp')
-rw-r--r-- | src/quick/items/qquickmultipointtoucharea.cpp | 729 |
1 files changed, 729 insertions, 0 deletions
diff --git a/src/quick/items/qquickmultipointtoucharea.cpp b/src/quick/items/qquickmultipointtoucharea.cpp new file mode 100644 index 0000000000..c94abc6c9d --- /dev/null +++ b/src/quick/items/qquickmultipointtoucharea.cpp @@ -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 <QtQuick/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 ¤tPos = 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 |