/**************************************************************************** ** ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/ ** ** This file is part of the QtQml 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 #include #include #include #include #include #include QT_BEGIN_NAMESPACE DEFINE_BOOL_CONFIG_OPTION(qmlVisualTouchDebugging, QML_VISUAL_TOUCH_DEBUGGING) /*! \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 vector2d QtQuick2::TouchPoint::velocity \qmlproperty rectangle QtQuick2::TouchPoint::area These properties hold additional information about the current state of the touch point. \list \li \c pressure is a value in the range of 0.0 to 1.0. \li \c velocity is a vector with magnitude reported in pixels per second. \li \c area is a rectangle covering the area of the touch point, centered on the current position of the touch point. \endlist Not all touch devices support velocity. If velocity is not supported, it will be reported as 0,0. */ void QQuickTouchPoint::setPressure(qreal pressure) { if (_pressure == pressure) return; _pressure = pressure; emit pressureChanged(); } void QQuickTouchPoint::setVelocity(const QVector2D &velocity) { if (_velocity == velocity) return; _velocity = velocity; emit velocityChanged(); } void QQuickTouchPoint::setArea(const QRectF &area) { if (_area == area) return; _area = area; emit areaChanged(); } /*! \qmlproperty bool QtQuick2::TouchPoint::pressed This property holds whether the touch point is currently pressed. */ void QQuickTouchPoint::setPressed(bool pressed) { if (_pressed == pressed) return; _pressed = pressed; emit pressedChanged(); } /*! \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 \li setting \c touchPoints to provide touch point objects with properties that can be bound to \li using the onTouchUpdated or onTouchPointsPressed, onTouchPointsUpdated and onTouchPointsReleased handlers \endlist While a MultiPointTouchArea \e 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::onPressed(list 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, onPressed will be called with all the current touch points. */ /*! \qmlsignal QtQuick2::MultiPointTouchArea::onUpdated(list touchPoints) This handler is called when existing touch points are updated. \a touchPoints is a list of these updated points. */ /*! \qmlsignal QtQuick2::MultiPointTouchArea::onReleased(list touchPoints) This handler is called when existing touch points are removed. \a touchPoints is a list of these removed points. */ /*! \qmlsignal QtQuick2::MultiPointTouchArea::onCanceled(list 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 onPressed 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 onCanceled should be used in addition to onReleased. \a touchPoints is the list of canceled points. */ /*! \qmlsignal QtQuick2::MultiPointTouchArea::onGestureStarted(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::onTouchUpdated(list touchPoints) This handler is called when the touch points handled by the MultiPointTouchArea change. This includes adding new touch points, removing or canceling previous touch points, as well as updating current touch point data. \a touchPoints is the list of all current touch points. */ /*! \qmlproperty list 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/qml/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); if (qmlVisualTouchDebugging()) { setFlag(QQuickItem::ItemHasContents); } } QQuickMultiPointTouchArea::~QQuickMultiPointTouchArea() { clearTouchLists(); foreach (QObject *obj, _touchPoints) { QQuickTouchPoint *dtp = static_cast(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(event); QList touchPoints = e->touchPoints(); int numTouchPoints = touchPoints.count(); //always remove released touches, and make sure we handle all releases before adds. foreach (const QTouchEvent::TouchPoint &p, touchPoints) { Qt::TouchPointState touchPointState = p.state(); int id = p.id(); if (touchPointState & Qt::TouchPointReleased) { QQuickTouchPoint* dtp = static_cast(_touchPoints.value(id)); if (!dtp) continue; updateTouchPoint(dtp, &p); dtp->setPressed(false); _releasedTouchPoints.append(dtp); _touchPoints.remove(id); ended = true; } } if (numTouchPoints >= _minimumTouchPoints && numTouchPoints <= _maximumTouchPoints) { foreach (const 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 // (we may have just obtained enough points to start tracking them -- in that case moved or stationary count as newly pressed) addTouchPoint(&p); started = true; } else if (touchPointState & Qt::TouchPointMoved) { QQuickTouchPoint* dtp = static_cast(_touchPoints[id]); Q_ASSERT(dtp); _movedTouchPoints.append(dtp); updateTouchPoint(dtp,&p); moved = true; } else { QQuickTouchPoint* dtp = static_cast(_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 released(_releasedTouchPoints); emit touchPointsReleased(_releasedTouchPoints); } if (moved) { emit updated(_movedTouchPoints); emit touchPointsUpdated(_movedTouchPoints); } if (started) { emit pressed(_pressedTouchPoints); emit touchPointsPressed(_pressedTouchPoints); } if (ended || moved || started) emit touchUpdated(_touchPoints.values()); } } void QQuickMultiPointTouchArea::clearTouchLists() { foreach (QObject *obj, _releasedTouchPoints) { QQuickTouchPoint *dtp = static_cast(obj); if (!dtp->isQmlDefined()) delete dtp; else dtp->setInUse(false); } _releasedTouchPoints.clear(); _pressedTouchPoints.clear(); _movedTouchPoints.clear(); } void QQuickMultiPointTouchArea::addTouchPoint(const QTouchEvent::TouchPoint *p) { QQuickTouchPoint *dtp = 0; foreach (QQuickTouchPoint* tp, _touchPrototypes) { if (!tp->inUse()) { tp->setInUse(true); dtp = tp; break; } } if (dtp == 0) dtp = new QQuickTouchPoint(false); dtp->setPointId(p->id()); updateTouchPoint(dtp,p); dtp->setPressed(true); _touchPoints.insert(p->id(),dtp); _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->setVelocity(p->velocity()); 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); foreach (QObject *obj, _touchPoints) static_cast(obj)->setPressed(false); emit canceled(_touchPoints.values()); emit touchPointsCanceled(_touchPoints.values()); clearTouchLists(); foreach (QObject *obj, _touchPoints) { QQuickTouchPoint *dtp = static_cast(obj); if (!dtp->isQmlDefined()) delete dtp; else dtp->setInUse(false); } _touchPoints.clear(); emit touchUpdated(QList()); } } 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(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(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(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; } QSGNode *QQuickMultiPointTouchArea::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) { Q_UNUSED(data); if (!qmlVisualTouchDebugging()) return 0; QSGRectangleNode *rectangle = static_cast(oldNode); if (!rectangle) rectangle = QQuickItemPrivate::get(this)->sceneGraphContext()->createRectangleNode(); rectangle->setRect(QRectF(0, 0, width(), height())); rectangle->setColor(QColor(255, 0, 0, 50)); rectangle->update(); return rectangle; } QT_END_NAMESPACE