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 qtquick-input-events
59 \brief Describes a touch point in a MultiPointTouchArea
61 The TouchPoint type 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 \li \c pressure is a value in the range of 0.0 to 1.0.
112 \li \c velocity is a vector with magnitude reported in pixels per second.
113 \li \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
228 \ingroup qtquick-input
229 \brief Enables handling of multiple touch points
232 A MultiPointTouchArea is an invisible item that is used to track multiple touch points.
234 The \l enabled property is used to enable and disable touch handling. When disabled,
235 the touch area becomes transparent to mouse/touch events.
237 MultiPointTouchArea can be used in two ways:
240 \li setting \c touchPoints to provide touch point objects with properties that can be bound to
241 \li using the onTouchUpdated or onPressed, onUpdated and onReleased handlers
244 While a MultiPointTouchArea \e can take exclusive ownership of certain touch points, it is also possible to have
245 multiple MultiPointTouchAreas active at the same time, each operating on a different set of touch points.
251 \qmlsignal QtQuick2::MultiPointTouchArea::onPressed(list<TouchPoint> touchPoints)
253 This handler is called when new touch points are added. \a touchPoints is a list of these new points.
255 If minimumTouchPoints is set to a value greater than one, this handler will not be called until the minimum number
256 of required touch points has been reached. At that point, onPressed will be called with all the current touch points.
260 \qmlsignal QtQuick2::MultiPointTouchArea::onUpdated(list<TouchPoint> touchPoints)
262 This handler is called when existing touch points are updated. \a touchPoints is a list of these updated points.
266 \qmlsignal QtQuick2::MultiPointTouchArea::onReleased(list<TouchPoint> touchPoints)
268 This handler is called when existing touch points are removed. \a touchPoints is a list of these removed points.
272 \qmlsignal QtQuick2::MultiPointTouchArea::onCanceled(list<TouchPoint> touchPoints)
274 This handler is called when new touch events have been canceled because another item stole the touch event handling.
276 This signal is for advanced use: it is useful when there is more than one MultiPointTouchArea
277 that is handling input, or when there is a MultiPointTouchArea inside a \l Flickable. In the latter
278 case, if you execute some logic on the onPressed signal and then start dragging, the
279 \l Flickable may steal the touch handling from the MultiPointTouchArea. In these cases, to reset
280 the logic when the MultiPointTouchArea has lost the touch handling to the \l Flickable,
281 \c onCanceled should be used in addition to onReleased.
283 \a touchPoints is the list of canceled points.
287 \qmlsignal QtQuick2::MultiPointTouchArea::onGestureStarted(GestureEvent gesture)
289 This handler is called when the global drag threshold has been reached.
291 This function is typically used when a MultiPointTouchAreas has been nested in a Flickable or another MultiPointTouchArea.
292 When the threshold has been reached, and the handler called, you can determine whether or not the touch
293 area should grab the current touch points. By default they will not be grabbed; to grab them call \c gesture.grab(). If the
294 gesture is not grabbed, the nesting Flickable, for example, would also have an opportunity to grab.
296 The gesture object also includes information on the current set of \c touchPoints and the \c dragThreshold.
300 \qmlsignal QtQuick2::MultiPointTouchArea::onTouchUpdated(list<TouchPoint> touchPoints)
302 This handler is called when the touch points handled by the MultiPointTouchArea change. This includes adding new touch points,
303 removing or canceling previous touch points, as well as updating current touch point data. \a touchPoints is the list of all current touch
308 \qmlproperty list<TouchPoint> QtQuick2::MultiPointTouchArea::touchPoints
310 This property holds a set of user-defined touch point objects that can be bound to.
312 In the following example, we have two small rectangles that follow our touch points.
314 \snippet qml/multipointtoucharea/multipointtoucharea.qml 0
316 By default this property holds an empty list.
321 QQuickMultiPointTouchArea::QQuickMultiPointTouchArea(QQuickItem *parent)
322 : QQuickItem(parent),
323 _minimumTouchPoints(0),
324 _maximumTouchPoints(INT_MAX),
327 setAcceptedMouseButtons(Qt::LeftButton);
328 setFiltersChildMouseEvents(true);
329 if (qmlVisualTouchDebugging()) {
330 setFlag(QQuickItem::ItemHasContents);
334 QQuickMultiPointTouchArea::~QQuickMultiPointTouchArea()
337 foreach (QObject *obj, _touchPoints) {
338 QQuickTouchPoint *dtp = static_cast<QQuickTouchPoint*>(obj);
339 if (!dtp->isQmlDefined())
345 \qmlproperty int QtQuick2::MultiPointTouchArea::minimumTouchPoints
346 \qmlproperty int QtQuick2::MultiPointTouchArea::maximumTouchPoints
348 These properties hold the range of touch points to be handled by the touch area.
350 These are convenience that allow you to, for example, have nested MultiPointTouchAreas,
351 one handling two finger touches, and another handling three finger touches.
353 By default, all touch points within the touch area are handled.
356 int QQuickMultiPointTouchArea::minimumTouchPoints() const
358 return _minimumTouchPoints;
361 void QQuickMultiPointTouchArea::setMinimumTouchPoints(int num)
363 if (_minimumTouchPoints == num)
365 _minimumTouchPoints = num;
366 emit minimumTouchPointsChanged();
369 int QQuickMultiPointTouchArea::maximumTouchPoints() const
371 return _maximumTouchPoints;
374 void QQuickMultiPointTouchArea::setMaximumTouchPoints(int num)
376 if (_maximumTouchPoints == num)
378 _maximumTouchPoints = num;
379 emit maximumTouchPointsChanged();
382 void QQuickMultiPointTouchArea::touchEvent(QTouchEvent *event)
384 switch (event->type()) {
385 case QEvent::TouchBegin:
386 case QEvent::TouchUpdate:
387 case QEvent::TouchEnd: {
388 //if e.g. a parent Flickable has the mouse grab, don't process the touch events
389 QQuickCanvas *c = canvas();
390 QQuickItem *grabber = c ? c->mouseGrabberItem() : 0;
391 if (grabber && grabber != this && grabber->keepMouseGrab() && grabber->isEnabled()) {
392 QQuickItem *item = this;
393 while ((item = item->parentItem())) {
398 updateTouchData(event);
399 if (event->type() == QEvent::TouchEnd) {
400 //TODO: move to canvas
402 setKeepMouseGrab(false);
403 setKeepTouchGrab(false);
409 QQuickItem::touchEvent(event);
414 void QQuickMultiPointTouchArea::grabGesture()
419 setKeepMouseGrab(true);
421 grabTouchPoints(_touchPoints.keys().toVector());
422 setKeepTouchGrab(true);
425 void QQuickMultiPointTouchArea::updateTouchData(QEvent *event)
429 bool started = false;
432 QTouchEvent *e = static_cast<QTouchEvent*>(event);
433 QList<QTouchEvent::TouchPoint> touchPoints = e->touchPoints();
434 int numTouchPoints = touchPoints.count();
435 //always remove released touches, and make sure we handle all releases before adds.
436 foreach (const QTouchEvent::TouchPoint &p, touchPoints) {
437 Qt::TouchPointState touchPointState = p.state();
439 if (touchPointState & Qt::TouchPointReleased) {
440 QQuickTouchPoint* dtp = static_cast<QQuickTouchPoint*>(_touchPoints.value(id));
443 updateTouchPoint(dtp, &p);
444 dtp->setPressed(false);
445 _releasedTouchPoints.append(dtp);
446 _touchPoints.remove(id);
450 if (numTouchPoints >= _minimumTouchPoints && numTouchPoints <= _maximumTouchPoints) {
451 foreach (const QTouchEvent::TouchPoint &p, touchPoints) {
452 Qt::TouchPointState touchPointState = p.state();
454 if (touchPointState & Qt::TouchPointReleased) {
456 } else if (!_touchPoints.contains(id)) { //could be pressed, moved, or stationary
457 // (we may have just obtained enough points to start tracking them -- in that case moved or stationary count as newly pressed)
460 } else if (touchPointState & Qt::TouchPointMoved) {
461 QQuickTouchPoint* dtp = static_cast<QQuickTouchPoint*>(_touchPoints[id]);
463 _movedTouchPoints.append(dtp);
464 updateTouchPoint(dtp,&p);
467 QQuickTouchPoint* dtp = static_cast<QQuickTouchPoint*>(_touchPoints[id]);
469 updateTouchPoint(dtp,&p);
473 //see if we should be grabbing the gesture
474 if (!_stealMouse /* !ignoring gesture*/) {
475 bool offerGrab = false;
476 const int dragThreshold = qApp->styleHints()->startDragDistance();
477 foreach (const QTouchEvent::TouchPoint &p, touchPoints) {
478 if (p.state() == Qt::TouchPointReleased)
480 const QPointF ¤tPos = p.scenePos();
481 const QPointF &startPos = p.startScenePos();
482 if (qAbs(currentPos.x() - startPos.x()) > dragThreshold)
484 else if (qAbs(currentPos.y() - startPos.y()) > dragThreshold)
491 QQuickGrabGestureEvent event;
492 event._touchPoints = _touchPoints.values();
493 emit gestureStarted(&event);
494 if (event.wantsGrab())
500 emit released(_releasedTouchPoints);
502 emit updated(_movedTouchPoints);
504 emit pressed(_pressedTouchPoints);
505 if (ended || moved || started) emit touchUpdated(_touchPoints.values());
509 void QQuickMultiPointTouchArea::clearTouchLists()
511 foreach (QObject *obj, _releasedTouchPoints) {
512 QQuickTouchPoint *dtp = static_cast<QQuickTouchPoint*>(obj);
513 if (!dtp->isQmlDefined())
516 dtp->setInUse(false);
518 _releasedTouchPoints.clear();
519 _pressedTouchPoints.clear();
520 _movedTouchPoints.clear();
523 void QQuickMultiPointTouchArea::addTouchPoint(const QTouchEvent::TouchPoint *p)
525 QQuickTouchPoint *dtp = 0;
526 foreach (QQuickTouchPoint* tp, _touchPrototypes) {
535 dtp = new QQuickTouchPoint(false);
536 dtp->setPointId(p->id());
537 updateTouchPoint(dtp,p);
538 dtp->setPressed(true);
539 _touchPoints.insert(p->id(),dtp);
540 _pressedTouchPoints.append(dtp);
543 void QQuickMultiPointTouchArea::addTouchPrototype(QQuickTouchPoint *prototype)
545 int id = _touchPrototypes.count();
546 prototype->setPointId(id);
547 _touchPrototypes.insert(id, prototype);
550 void QQuickMultiPointTouchArea::updateTouchPoint(QQuickTouchPoint *dtp, const QTouchEvent::TouchPoint *p)
552 //TODO: if !qmlDefined, could bypass setters.
553 // also, should only emit signals after all values have been set
554 dtp->setX(p->pos().x());
555 dtp->setY(p->pos().y());
556 dtp->setPressure(p->pressure());
557 dtp->setVelocity(p->velocity());
558 dtp->setArea(p->rect());
559 dtp->setStartX(p->startPos().x());
560 dtp->setStartY(p->startPos().y());
561 dtp->setPreviousX(p->lastPos().x());
562 dtp->setPreviousY(p->lastPos().y());
563 dtp->setSceneX(p->scenePos().x());
564 dtp->setSceneY(p->scenePos().y());
567 void QQuickMultiPointTouchArea::mousePressEvent(QMouseEvent *event)
570 QQuickItem::mousePressEvent(event);
575 setKeepMouseGrab(false);
576 event->setAccepted(true);
579 void QQuickMultiPointTouchArea::mouseMoveEvent(QMouseEvent *event)
582 QQuickItem::mouseMoveEvent(event);
589 void QQuickMultiPointTouchArea::mouseReleaseEvent(QMouseEvent *event)
593 QQuickItem::mouseReleaseEvent(event);
596 QQuickCanvas *c = canvas();
597 if (c && c->mouseGrabberItem() == this)
599 setKeepMouseGrab(false);
602 void QQuickMultiPointTouchArea::ungrab()
604 if (_touchPoints.count()) {
605 QQuickCanvas *c = canvas();
606 if (c && c->mouseGrabberItem() == this) {
608 setKeepMouseGrab(false);
610 setKeepTouchGrab(false);
611 foreach (QObject *obj, _touchPoints)
612 static_cast<QQuickTouchPoint*>(obj)->setPressed(false);
613 emit canceled(_touchPoints.values());
615 foreach (QObject *obj, _touchPoints) {
616 QQuickTouchPoint *dtp = static_cast<QQuickTouchPoint*>(obj);
617 if (!dtp->isQmlDefined())
620 dtp->setInUse(false);
622 _touchPoints.clear();
623 emit touchUpdated(QList<QObject*>());
627 void QQuickMultiPointTouchArea::mouseUngrabEvent()
632 void QQuickMultiPointTouchArea::touchUngrabEvent()
637 bool QQuickMultiPointTouchArea::sendMouseEvent(QMouseEvent *event)
639 QPointF localPos = mapFromScene(event->windowPos());
641 QQuickCanvas *c = canvas();
642 QQuickItem *grabber = c ? c->mouseGrabberItem() : 0;
643 bool stealThisEvent = _stealMouse;
644 if ((stealThisEvent || contains(localPos)) && (!grabber || !grabber->keepMouseGrab())) {
645 QMouseEvent mouseEvent(event->type(), localPos, event->windowPos(), event->screenPos(),
646 event->button(), event->buttons(), event->modifiers());
647 mouseEvent.setAccepted(false);
649 switch (mouseEvent.type()) {
650 case QEvent::MouseMove:
651 mouseMoveEvent(&mouseEvent);
653 case QEvent::MouseButtonPress:
654 mousePressEvent(&mouseEvent);
656 case QEvent::MouseButtonRelease:
657 mouseReleaseEvent(&mouseEvent);
662 grabber = c->mouseGrabberItem();
663 if (grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this)
666 return stealThisEvent;
668 if (event->type() == QEvent::MouseButtonRelease) {
670 if (c && c->mouseGrabberItem() == this)
672 setKeepMouseGrab(false);
677 bool QQuickMultiPointTouchArea::childMouseEventFilter(QQuickItem *i, QEvent *event)
679 if (!isEnabled() || !isVisible())
680 return QQuickItem::childMouseEventFilter(i, event);
681 switch (event->type()) {
682 case QEvent::MouseButtonPress:
683 case QEvent::MouseMove:
684 case QEvent::MouseButtonRelease:
685 return sendMouseEvent(static_cast<QMouseEvent *>(event));
687 case QEvent::TouchBegin:
688 case QEvent::TouchUpdate:
689 if (!shouldFilter(event))
691 updateTouchData(event);
693 case QEvent::TouchEnd: {
694 if (!shouldFilter(event))
696 updateTouchData(event);
697 //TODO: verify this behavior
699 setKeepMouseGrab(false);
700 setKeepTouchGrab(false);
707 return QQuickItem::childMouseEventFilter(i, event);
710 bool QQuickMultiPointTouchArea::shouldFilter(QEvent *event)
712 QQuickCanvas *c = canvas();
713 QQuickItem *grabber = c ? c->mouseGrabberItem() : 0;
714 bool disabledItem = grabber && !grabber->isEnabled();
715 bool stealThisEvent = _stealMouse;
716 bool containsPoint = false;
717 if (!stealThisEvent) {
718 switch (event->type()) {
719 case QEvent::MouseButtonPress:
720 case QEvent::MouseMove:
721 case QEvent::MouseButtonRelease: {
722 QMouseEvent *me = static_cast<QMouseEvent*>(event);
723 containsPoint = contains(mapFromScene(me->windowPos()));
726 case QEvent::TouchBegin:
727 case QEvent::TouchUpdate:
728 case QEvent::TouchEnd: {
729 QTouchEvent *te = static_cast<QTouchEvent*>(event);
730 foreach (const QTouchEvent::TouchPoint &point, te->touchPoints()) {
731 if (contains(mapFromScene(point.scenePos()))) {
732 containsPoint = true;
742 if ((stealThisEvent || containsPoint) && (!grabber || !grabber->keepMouseGrab() || disabledItem)) {
749 QSGNode *QQuickMultiPointTouchArea::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
753 if (!qmlVisualTouchDebugging())
756 QSGRectangleNode *rectangle = static_cast<QSGRectangleNode *>(oldNode);
757 if (!rectangle) rectangle = QQuickItemPrivate::get(this)->sceneGraphContext()->createRectangleNode();
759 rectangle->setRect(QRectF(0, 0, width(), height()));
760 rectangle->setColor(QColor(255, 0, 0, 50));