1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtQml module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qquickmultipointtoucharea_p.h"
43 #include <QtQuick/qquickcanvas.h>
44 #include <private/qsgadaptationlayer_p.h>
45 #include <private/qquickitem_p.h>
47 #include <QMouseEvent>
53 DEFINE_BOOL_CONFIG_OPTION(qmlVisualTouchDebugging, QML_VISUAL_TOUCH_DEBUGGING)
56 \qmlclass TouchPoint QQuickTouchPoint
57 \inqmlmodule QtQuick 2
58 \ingroup qml-event-elements
59 \brief The TouchPoint element describes a touch point in a MultiPointTouchArea.
61 The TouchPoint element contains information about a touch point, such as the current
62 position, pressure, and area.
66 \qmlproperty int QtQuick2::TouchPoint::pointId
68 This property holds the point id of the touch point.
70 Each touch point within a MultiPointTouchArea will have a unique id.
72 void QQuickTouchPoint::setPointId(int id)
77 emit pointIdChanged();
81 \qmlproperty real QtQuick2::TouchPoint::x
82 \qmlproperty real QtQuick2::TouchPoint::y
84 These properties hold the current position of the touch point.
87 void QQuickTouchPoint::setX(qreal x)
95 void QQuickTouchPoint::setY(qreal y)
104 \qmlproperty real QtQuick2::TouchPoint::pressure
105 \qmlproperty vector2d QtQuick2::TouchPoint::velocity
106 \qmlproperty rectangle QtQuick2::TouchPoint::area
108 These properties hold additional information about the current state of the touch point.
111 \i \c pressure is a value in the range of 0.0 to 1.0.
112 \i \c velocity is a vector with magnitude reported in pixels per second.
113 \i \c area is a rectangle covering the area of the touch point, centered on the current position of the touch point.
116 Not all touch devices support velocity. If velocity is not supported, it will be reported
119 void QQuickTouchPoint::setPressure(qreal pressure)
121 if (_pressure == pressure)
123 _pressure = pressure;
124 emit pressureChanged();
127 void QQuickTouchPoint::setVelocity(const QVector2D &velocity)
129 if (_velocity == velocity)
131 _velocity = velocity;
132 emit velocityChanged();
135 void QQuickTouchPoint::setArea(const QRectF &area)
144 \qmlproperty bool QtQuick2::TouchPoint::pressed
146 This property holds whether the touch point is currently pressed.
148 void QQuickTouchPoint::setPressed(bool pressed)
150 if (_pressed == pressed)
153 emit pressedChanged();
157 \qmlproperty real QtQuick2::TouchPoint::startX
158 \qmlproperty real QtQuick2::TouchPoint::startY
160 These properties hold the starting position of the touch point.
163 void QQuickTouchPoint::setStartX(qreal startX)
165 if (_startX == startX)
168 emit startXChanged();
171 void QQuickTouchPoint::setStartY(qreal startY)
173 if (_startY == startY)
176 emit startYChanged();
180 \qmlproperty real QtQuick2::TouchPoint::previousX
181 \qmlproperty real QtQuick2::TouchPoint::previousY
183 These properties hold the previous position of the touch point.
185 void QQuickTouchPoint::setPreviousX(qreal previousX)
187 if (_previousX == previousX)
189 _previousX = previousX;
190 emit previousXChanged();
193 void QQuickTouchPoint::setPreviousY(qreal previousY)
195 if (_previousY == previousY)
197 _previousY = previousY;
198 emit previousYChanged();
202 \qmlproperty real QtQuick2::TouchPoint::sceneX
203 \qmlproperty real QtQuick2::TouchPoint::sceneY
205 These properties hold the current position of the touch point in scene coordinates.
208 void QQuickTouchPoint::setSceneX(qreal sceneX)
210 if (_sceneX == sceneX)
213 emit sceneXChanged();
216 void QQuickTouchPoint::setSceneY(qreal sceneY)
218 if (_sceneY == sceneY)
221 emit sceneYChanged();
225 \qmlclass MultiPointTouchArea QQuickMultiPointTouchArea
226 \inqmlmodule QtQuick 2
227 \brief The MultiPointTouchArea item enables handling of multiple touch points.
230 A MultiPointTouchArea is an invisible item that is used to track multiple touch points.
232 The \l enabled property is used to enable and disable touch handling. When disabled,
233 the touch area becomes transparent to mouse/touch events.
235 MultiPointTouchArea can be used in two ways:
238 \o setting \c touchPoints to provide touch point objects with properties that can be bound to
239 \o using the onTouchUpdated or onTouchPointsPressed, onTouchPointsUpdated and onTouchPointsReleased handlers
242 While a MultiPointTouchArea \i can take exclusive ownership of certain touch points, it is also possible to have
243 multiple MultiPointTouchAreas active at the same time, each operating on a different set of touch points.
249 \qmlsignal QtQuick2::MultiPointTouchArea::onPressed(list<TouchPoint> touchPoints)
251 This handler is called when new touch points are added. \a touchPoints is a list of these new points.
253 If minimumTouchPoints is set to a value greater than one, this handler will not be called until the minimum number
254 of required touch points has been reached. At that point, onPressed will be called with all the current touch points.
258 \qmlsignal QtQuick2::MultiPointTouchArea::onUpdated(list<TouchPoint> touchPoints)
260 This handler is called when existing touch points are updated. \a touchPoints is a list of these updated points.
264 \qmlsignal QtQuick2::MultiPointTouchArea::onReleased(list<TouchPoint> touchPoints)
266 This handler is called when existing touch points are removed. \a touchPoints is a list of these removed points.
270 \qmlsignal QtQuick2::MultiPointTouchArea::onCanceled(list<TouchPoint> touchPoints)
272 This handler is called when new touch events have been canceled because another element stole the touch event handling.
274 This signal is for advanced use: it is useful when there is more than one MultiPointTouchArea
275 that is handling input, or when there is a MultiPointTouchArea inside a \l Flickable. In the latter
276 case, if you execute some logic on the onPressed signal and then start dragging, the
277 \l Flickable may steal the touch handling from the MultiPointTouchArea. In these cases, to reset
278 the logic when the MultiPointTouchArea has lost the touch handling to the \l Flickable,
279 \c onCanceled should be used in addition to onReleased.
281 \a touchPoints is the list of canceled points.
285 \qmlsignal QtQuick2::MultiPointTouchArea::onGestureStarted(GestureEvent gesture)
287 This handler is called when the global drag threshold has been reached.
289 This function is typically used when a MultiPointTouchAreas has been nested in a Flickable or another MultiPointTouchArea.
290 Wnen the threshold has been reached, and the handler called, you can determine whether or not the touch
291 area should grab the current touch points. By default they will not be grabbed; to grab them call \c gesture.grab(). If the
292 gesture is not grabbed, the nesting Flickable, for example, would also have an opportunity to grab.
294 The gesture object also includes information on the current set of \c touchPoints and the \c dragThreshold.
298 \qmlsignal QtQuick2::MultiPointTouchArea::onTouchUpdated(list<TouchPoint> touchPoints)
300 This handler is called when the touch points handled by the MultiPointTouchArea change. This includes adding new touch points,
301 removing or canceling previous touch points, as well as updating current touch point data. \a touchPoints is the list of all current touch
306 \qmlproperty list<TouchPoint> QtQuick2::MultiPointTouchArea::touchPoints
308 This property holds a set of user-defined touch point objects that can be bound to.
310 In the following example, we have two small rectangles that follow our touch points.
312 \snippet doc/src/snippets/qml/multipointtoucharea/multipointtoucharea.qml 0
314 By default this property holds an empty list.
319 QQuickMultiPointTouchArea::QQuickMultiPointTouchArea(QQuickItem *parent)
320 : QQuickItem(parent),
321 _minimumTouchPoints(0),
322 _maximumTouchPoints(INT_MAX),
325 setAcceptedMouseButtons(Qt::LeftButton);
326 setFiltersChildMouseEvents(true);
327 if (qmlVisualTouchDebugging()) {
328 setFlag(QQuickItem::ItemHasContents);
332 QQuickMultiPointTouchArea::~QQuickMultiPointTouchArea()
335 foreach (QObject *obj, _touchPoints) {
336 QQuickTouchPoint *dtp = static_cast<QQuickTouchPoint*>(obj);
337 if (!dtp->isQmlDefined())
343 \qmlproperty int QtQuick2::MultiPointTouchArea::minimumTouchPoints
344 \qmlproperty int QtQuick2::MultiPointTouchArea::maximumTouchPoints
346 These properties hold the range of touch points to be handled by the touch area.
348 These are convenience that allow you to, for example, have nested MultiPointTouchAreas,
349 one handling two finger touches, and another handling three finger touches.
351 By default, all touch points within the touch area are handled.
354 int QQuickMultiPointTouchArea::minimumTouchPoints() const
356 return _minimumTouchPoints;
359 void QQuickMultiPointTouchArea::setMinimumTouchPoints(int num)
361 if (_minimumTouchPoints == num)
363 _minimumTouchPoints = num;
364 emit minimumTouchPointsChanged();
367 int QQuickMultiPointTouchArea::maximumTouchPoints() const
369 return _maximumTouchPoints;
372 void QQuickMultiPointTouchArea::setMaximumTouchPoints(int num)
374 if (_maximumTouchPoints == num)
376 _maximumTouchPoints = num;
377 emit maximumTouchPointsChanged();
380 void QQuickMultiPointTouchArea::touchEvent(QTouchEvent *event)
382 switch (event->type()) {
383 case QEvent::TouchBegin:
384 case QEvent::TouchUpdate:
385 case QEvent::TouchEnd: {
386 //if e.g. a parent Flickable has the mouse grab, don't process the touch events
387 QQuickCanvas *c = canvas();
388 QQuickItem *grabber = c ? c->mouseGrabberItem() : 0;
389 if (grabber && grabber != this && grabber->keepMouseGrab() && grabber->isEnabled()) {
390 QQuickItem *item = this;
391 while ((item = item->parentItem())) {
396 updateTouchData(event);
397 if (event->type() == QEvent::TouchEnd) {
398 //TODO: move to canvas
400 setKeepMouseGrab(false);
401 QQuickCanvas *c = canvas();
402 if (c && c->mouseGrabberItem() == this)
404 setKeepTouchGrab(false);
410 QQuickItem::touchEvent(event);
415 void QQuickMultiPointTouchArea::grabGesture()
420 setKeepMouseGrab(true);
422 grabTouchPoints(_touchPoints.keys());
423 setKeepTouchGrab(true);
426 void QQuickMultiPointTouchArea::updateTouchData(QEvent *event)
430 bool started = false;
433 QTouchEvent *e = static_cast<QTouchEvent*>(event);
434 QList<QTouchEvent::TouchPoint> touchPoints = e->touchPoints();
435 int numTouchPoints = touchPoints.count();
436 //always remove released touches, and make sure we handle all releases before adds.
437 foreach (const QTouchEvent::TouchPoint &p, touchPoints) {
438 Qt::TouchPointState touchPointState = p.state();
440 if (touchPointState & Qt::TouchPointReleased) {
441 QQuickTouchPoint* dtp = static_cast<QQuickTouchPoint*>(_touchPoints.value(id));
444 updateTouchPoint(dtp, &p);
445 dtp->setPressed(false);
446 _releasedTouchPoints.append(dtp);
447 _touchPoints.remove(id);
451 if (numTouchPoints >= _minimumTouchPoints && numTouchPoints <= _maximumTouchPoints) {
452 foreach (const QTouchEvent::TouchPoint &p, touchPoints) {
453 Qt::TouchPointState touchPointState = p.state();
455 if (touchPointState & Qt::TouchPointReleased) {
457 } else if (!_touchPoints.contains(id)) { //could be pressed, moved, or stationary
458 // (we may have just obtained enough points to start tracking them -- in that case moved or stationary count as newly pressed)
461 } else if (touchPointState & Qt::TouchPointMoved) {
462 QQuickTouchPoint* dtp = static_cast<QQuickTouchPoint*>(_touchPoints[id]);
464 _movedTouchPoints.append(dtp);
465 updateTouchPoint(dtp,&p);
468 QQuickTouchPoint* dtp = static_cast<QQuickTouchPoint*>(_touchPoints[id]);
470 updateTouchPoint(dtp,&p);
474 //see if we should be grabbing the gesture
475 if (!_stealMouse /* !ignoring gesture*/) {
476 bool offerGrab = false;
477 const int dragThreshold = qApp->styleHints()->startDragDistance();
478 foreach (const QTouchEvent::TouchPoint &p, touchPoints) {
479 if (p.state() == Qt::TouchPointReleased)
481 const QPointF ¤tPos = p.scenePos();
482 const QPointF &startPos = p.startScenePos();
483 if (qAbs(currentPos.x() - startPos.x()) > dragThreshold)
485 else if (qAbs(currentPos.y() - startPos.y()) > dragThreshold)
492 QQuickGrabGestureEvent event;
493 event._touchPoints = _touchPoints.values();
494 emit gestureStarted(&event);
495 if (event.wantsGrab())
501 emit released(_releasedTouchPoints);
502 emit touchPointsReleased(_releasedTouchPoints);
505 emit updated(_movedTouchPoints);
506 emit touchPointsUpdated(_movedTouchPoints);
509 emit pressed(_pressedTouchPoints);
510 emit touchPointsPressed(_pressedTouchPoints);
512 if (ended || moved || started) emit touchUpdated(_touchPoints.values());
516 void QQuickMultiPointTouchArea::clearTouchLists()
518 foreach (QObject *obj, _releasedTouchPoints) {
519 QQuickTouchPoint *dtp = static_cast<QQuickTouchPoint*>(obj);
520 if (!dtp->isQmlDefined())
523 dtp->setInUse(false);
525 _releasedTouchPoints.clear();
526 _pressedTouchPoints.clear();
527 _movedTouchPoints.clear();
530 void QQuickMultiPointTouchArea::addTouchPoint(const QTouchEvent::TouchPoint *p)
532 QQuickTouchPoint *dtp = 0;
533 foreach (QQuickTouchPoint* tp, _touchPrototypes) {
542 dtp = new QQuickTouchPoint(false);
543 dtp->setPointId(p->id());
544 updateTouchPoint(dtp,p);
545 dtp->setPressed(true);
546 _touchPoints.insert(p->id(),dtp);
547 _pressedTouchPoints.append(dtp);
550 void QQuickMultiPointTouchArea::addTouchPrototype(QQuickTouchPoint *prototype)
552 int id = _touchPrototypes.count();
553 prototype->setPointId(id);
554 _touchPrototypes.insert(id, prototype);
557 void QQuickMultiPointTouchArea::updateTouchPoint(QQuickTouchPoint *dtp, const QTouchEvent::TouchPoint *p)
559 //TODO: if !qmlDefined, could bypass setters.
560 // also, should only emit signals after all values have been set
561 dtp->setX(p->pos().x());
562 dtp->setY(p->pos().y());
563 dtp->setPressure(p->pressure());
564 dtp->setVelocity(p->velocity());
565 dtp->setArea(p->rect());
566 dtp->setStartX(p->startPos().x());
567 dtp->setStartY(p->startPos().y());
568 dtp->setPreviousX(p->lastPos().x());
569 dtp->setPreviousY(p->lastPos().y());
570 dtp->setSceneX(p->scenePos().x());
571 dtp->setSceneY(p->scenePos().y());
574 void QQuickMultiPointTouchArea::mousePressEvent(QMouseEvent *event)
577 QQuickItem::mousePressEvent(event);
582 setKeepMouseGrab(false);
583 event->setAccepted(true);
586 void QQuickMultiPointTouchArea::mouseMoveEvent(QMouseEvent *event)
589 QQuickItem::mouseMoveEvent(event);
596 void QQuickMultiPointTouchArea::mouseReleaseEvent(QMouseEvent *event)
600 QQuickItem::mouseReleaseEvent(event);
603 QQuickCanvas *c = canvas();
604 if (c && c->mouseGrabberItem() == this)
606 setKeepMouseGrab(false);
609 void QQuickMultiPointTouchArea::ungrab()
611 if (_touchPoints.count()) {
612 QQuickCanvas *c = canvas();
613 if (c && c->mouseGrabberItem() == this) {
615 setKeepMouseGrab(false);
617 setKeepTouchGrab(false);
618 foreach (QObject *obj, _touchPoints)
619 static_cast<QQuickTouchPoint*>(obj)->setPressed(false);
620 emit canceled(_touchPoints.values());
621 emit touchPointsCanceled(_touchPoints.values());
623 foreach (QObject *obj, _touchPoints) {
624 QQuickTouchPoint *dtp = static_cast<QQuickTouchPoint*>(obj);
625 if (!dtp->isQmlDefined())
628 dtp->setInUse(false);
630 _touchPoints.clear();
631 emit touchUpdated(QList<QObject*>());
635 void QQuickMultiPointTouchArea::mouseUngrabEvent()
640 void QQuickMultiPointTouchArea::touchUngrabEvent()
645 bool QQuickMultiPointTouchArea::sendMouseEvent(QMouseEvent *event)
647 QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height()));
649 QQuickCanvas *c = canvas();
650 QQuickItem *grabber = c ? c->mouseGrabberItem() : 0;
651 bool stealThisEvent = _stealMouse;
652 if ((stealThisEvent || myRect.contains(event->windowPos())) && (!grabber || !grabber->keepMouseGrab())) {
653 QMouseEvent mouseEvent(event->type(), mapFromScene(event->windowPos()), event->windowPos(), event->screenPos(),
654 event->button(), event->buttons(), event->modifiers());
655 mouseEvent.setAccepted(false);
657 switch (mouseEvent.type()) {
658 case QEvent::MouseMove:
659 mouseMoveEvent(&mouseEvent);
661 case QEvent::MouseButtonPress:
662 mousePressEvent(&mouseEvent);
664 case QEvent::MouseButtonRelease:
665 mouseReleaseEvent(&mouseEvent);
670 grabber = c->mouseGrabberItem();
671 if (grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this)
674 return stealThisEvent;
676 if (event->type() == QEvent::MouseButtonRelease) {
678 if (c && c->mouseGrabberItem() == this)
680 setKeepMouseGrab(false);
685 bool QQuickMultiPointTouchArea::childMouseEventFilter(QQuickItem *i, QEvent *event)
687 if (!isEnabled() || !isVisible())
688 return QQuickItem::childMouseEventFilter(i, event);
689 switch (event->type()) {
690 case QEvent::MouseButtonPress:
691 case QEvent::MouseMove:
692 case QEvent::MouseButtonRelease:
693 return sendMouseEvent(static_cast<QMouseEvent *>(event));
695 case QEvent::TouchBegin:
696 case QEvent::TouchUpdate:
697 if (!shouldFilter(event))
699 updateTouchData(event);
701 case QEvent::TouchEnd: {
702 if (!shouldFilter(event))
704 updateTouchData(event);
705 //TODO: verify this behavior
707 setKeepMouseGrab(false);
708 QQuickCanvas *c = canvas();
709 if (c && c->mouseGrabberItem() == this)
711 setKeepTouchGrab(false);
718 return QQuickItem::childMouseEventFilter(i, event);
721 bool QQuickMultiPointTouchArea::shouldFilter(QEvent *event)
723 QQuickCanvas *c = canvas();
724 QQuickItem *grabber = c ? c->mouseGrabberItem() : 0;
725 bool disabledItem = grabber && !grabber->isEnabled();
726 bool stealThisEvent = _stealMouse;
727 bool contains = false;
728 if (!stealThisEvent) {
729 switch (event->type()) {
730 case QEvent::MouseButtonPress:
731 case QEvent::MouseMove:
732 case QEvent::MouseButtonRelease: {
733 QMouseEvent *me = static_cast<QMouseEvent*>(event);
734 QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height()));
735 contains = myRect.contains(me->windowPos());
738 case QEvent::TouchBegin:
739 case QEvent::TouchUpdate:
740 case QEvent::TouchEnd: {
741 QTouchEvent *te = static_cast<QTouchEvent*>(event);
742 QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height()));
743 foreach (const QTouchEvent::TouchPoint &point, te->touchPoints()) {
744 if (myRect.contains(point.scenePos())) {
755 if ((stealThisEvent || contains) && (!grabber || !grabber->keepMouseGrab() || disabledItem)) {
762 QSGNode *QQuickMultiPointTouchArea::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
766 if (!qmlVisualTouchDebugging())
769 QSGRectangleNode *rectangle = static_cast<QSGRectangleNode *>(oldNode);
770 if (!rectangle) rectangle = QQuickItemPrivate::get(this)->sceneGraphContext()->createRectangleNode();
772 rectangle->setRect(QRectF(0, 0, width(), height()));
773 rectangle->setColor(QColor(255, 0, 0, 50));