1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtQuick module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qquickmultipointtoucharea_p.h"
41 #include <QtQuick/qquickwindow.h>
42 #include <private/qsgadaptationlayer_p.h>
43 #include <private/qevent_p.h>
44 #include <private/qquickitem_p.h>
45 #include <private/qquickwindow_p.h>
46 #include <private/qguiapplication_p.h>
47 #include <QEvent>
48 #include <QMouseEvent>
49 #include <QDebug>
50 #include <qpa/qplatformnativeinterface.h>
51 
52 QT_BEGIN_NAMESPACE
53 
DEFINE_BOOL_CONFIG_OPTION(qmlVisualTouchDebugging,QML_VISUAL_TOUCH_DEBUGGING)54 DEFINE_BOOL_CONFIG_OPTION(qmlVisualTouchDebugging, QML_VISUAL_TOUCH_DEBUGGING)
55 
56 /*!
57     \qmltype TouchPoint
58     \instantiates QQuickTouchPoint
59     \inqmlmodule QtQuick
60     \ingroup qtquick-input-events
61     \brief Describes a touch point in a MultiPointTouchArea.
62 
63     The TouchPoint type contains information about a touch point, such as the current
64     position, pressure, and area.
65 
66     \image touchpoint-metrics.png
67 */
68 
69 /*!
70     \qmlproperty int QtQuick::TouchPoint::pointId
71 
72     This property holds the point id of the touch point.
73 
74     Each touch point within a MultiPointTouchArea will have a unique id.
75 */
76 void QQuickTouchPoint::setPointId(int id)
77 {
78     if (_id == id)
79         return;
80     _id = id;
81     emit pointIdChanged();
82 }
83 
84 /*!
85     \qmlproperty real QtQuick::TouchPoint::x
86     \qmlproperty real QtQuick::TouchPoint::y
87 
88     These properties hold the current position of the touch point.
89 */
90 
setPosition(QPointF p)91 void QQuickTouchPoint::setPosition(QPointF p)
92 {
93     bool xch = (_x != p.x());
94     bool ych = (_y != p.y());
95     if (!xch && !ych)
96         return;
97     _x = p.x();
98     _y = p.y();
99     if (xch)
100         emit xChanged();
101     if (ych)
102         emit yChanged();
103 }
104 
105 /*!
106     \qmlproperty size QtQuick::TouchPoint::ellipseDiameters
107     \since 5.9
108 
109     This property holds the major and minor axes of the ellipse representing
110     the covered area of the touch point.
111 */
setEllipseDiameters(const QSizeF & d)112 void QQuickTouchPoint::setEllipseDiameters(const QSizeF &d)
113 {
114     if (_ellipseDiameters == d)
115         return;
116     _ellipseDiameters = d;
117     emit ellipseDiametersChanged();
118 }
119 
120 /*!
121     \qmlproperty real QtQuick::TouchPoint::pressure
122     \qmlproperty vector2d QtQuick::TouchPoint::velocity
123 
124     These properties hold additional information about the current state of the touch point.
125 
126     \list
127     \li \c pressure is a value in the range of 0.0 to 1.0.
128     \li \c velocity is a vector with magnitude reported in pixels per second.
129     \endlist
130 
131     Not all touch devices support velocity. If velocity is not supported, it will be reported
132     as 0,0.
133 */
setPressure(qreal pressure)134 void QQuickTouchPoint::setPressure(qreal pressure)
135 {
136     if (_pressure == pressure)
137         return;
138     _pressure = pressure;
139     emit pressureChanged();
140 }
141 
142 /*!
143     \qmlproperty real QtQuick::TouchPoint::rotation
144     \since 5.9
145 
146     This property holds the angular orientation of this touch point. The return
147     value is in degrees, where zero (the default) indicates the finger or token
148     is pointing upwards, a negative angle means it's rotated to the left, and a
149     positive angle means it's rotated to the right. Most touchscreens do not
150     detect rotation, so zero is the most common value.
151 
152     \sa QTouchEvent::TouchPoint::rotation()
153 */
setRotation(qreal r)154 void QQuickTouchPoint::setRotation(qreal r)
155 {
156     if (_rotation == r)
157         return;
158     _rotation = r;
159     emit rotationChanged();
160 }
161 
setVelocity(const QVector2D & velocity)162 void QQuickTouchPoint::setVelocity(const QVector2D &velocity)
163 {
164     if (_velocity == velocity)
165         return;
166     _velocity = velocity;
167     emit velocityChanged();
168 }
169 
170 /*!
171     \deprecated
172     \qmlproperty rectangle QtQuick::TouchPoint::area
173 
174     A rectangle covering the area of the touch point, centered on the current
175     position of the touch point.
176 
177     It is deprecated because a touch point is more correctly modeled as an ellipse,
178     whereas this rectangle represents the outer bounds of the ellipse after \l rotation.
179 */
setArea(const QRectF & area)180 void QQuickTouchPoint::setArea(const QRectF &area)
181 {
182     if (_area == area)
183         return;
184     _area = area;
185     emit areaChanged();
186 }
187 
188 /*!
189     \qmlproperty bool QtQuick::TouchPoint::pressed
190 
191     This property holds whether the touch point is currently pressed.
192 */
setPressed(bool pressed)193 void QQuickTouchPoint::setPressed(bool pressed)
194 {
195     if (_pressed == pressed)
196         return;
197     _pressed = pressed;
198     emit pressedChanged();
199 }
200 
201 /*!
202     \qmlproperty real QtQuick::TouchPoint::startX
203     \qmlproperty real QtQuick::TouchPoint::startY
204 
205     These properties hold the starting position of the touch point.
206 */
207 
setStartX(qreal startX)208 void QQuickTouchPoint::setStartX(qreal startX)
209 {
210     if (_startX == startX)
211         return;
212     _startX = startX;
213     emit startXChanged();
214 }
215 
setStartY(qreal startY)216 void QQuickTouchPoint::setStartY(qreal startY)
217 {
218     if (_startY == startY)
219         return;
220     _startY = startY;
221     emit startYChanged();
222 }
223 
224 /*!
225     \qmlproperty real QtQuick::TouchPoint::previousX
226     \qmlproperty real QtQuick::TouchPoint::previousY
227 
228     These properties hold the previous position of the touch point.
229 */
setPreviousX(qreal previousX)230 void QQuickTouchPoint::setPreviousX(qreal previousX)
231 {
232     if (_previousX == previousX)
233         return;
234     _previousX = previousX;
235     emit previousXChanged();
236 }
237 
setPreviousY(qreal previousY)238 void QQuickTouchPoint::setPreviousY(qreal previousY)
239 {
240     if (_previousY == previousY)
241         return;
242     _previousY = previousY;
243     emit previousYChanged();
244 }
245 
246 /*!
247     \qmlproperty real QtQuick::TouchPoint::sceneX
248     \qmlproperty real QtQuick::TouchPoint::sceneY
249 
250     These properties hold the current position of the touch point in scene coordinates.
251 */
252 
setSceneX(qreal sceneX)253 void QQuickTouchPoint::setSceneX(qreal sceneX)
254 {
255     if (_sceneX == sceneX)
256         return;
257     _sceneX = sceneX;
258     emit sceneXChanged();
259 }
260 
setSceneY(qreal sceneY)261 void QQuickTouchPoint::setSceneY(qreal sceneY)
262 {
263     if (_sceneY == sceneY)
264         return;
265     _sceneY = sceneY;
266     emit sceneYChanged();
267 }
268 
269 /*!
270     \qmlproperty PointingDeviceUniqueId QtQuick::TouchPoint::uniqueId
271     \since 5.9
272 
273     This property holds the unique ID of the touch point or token.
274 
275     It is normally empty, because touchscreens cannot uniquely identify fingers.
276     But when it is set, it is expected to uniquely identify a specific token
277     (fiducial object).
278 
279     Interpreting the contents of this ID requires knowledge of the hardware and
280     drivers in use (e.g. various TUIO-based touch surfaces).
281 */
setUniqueId(const QPointingDeviceUniqueId & id)282 void QQuickTouchPoint::setUniqueId(const QPointingDeviceUniqueId &id)
283 {
284     _uniqueId = id;
285     emit uniqueIdChanged();
286 }
287 
288 
289 /*!
290     \qmltype GestureEvent
291     \instantiates QQuickGrabGestureEvent
292     \inqmlmodule QtQuick
293     \ingroup qtquick-input-events
294     \brief The parameter given with the gestureStarted signal.
295 
296     The GestureEvent object has the current touch points, which you may choose
297     to interpret as a gesture, and an invokable method to grab the involved
298     points exclusively.
299 */
300 
301 /*!
302     \qmlproperty real QtQuick::GestureEvent::dragThreshold
303 
304     This property holds the system setting for the distance a finger must move
305     before it is interpreted as a drag. It comes from
306     QStyleHints::startDragDistance().
307 */
308 
309 /*!
310     \qmlproperty list<TouchPoint> QtQuick::GestureEvent::touchPoints
311 
312     This property holds the set of current touch points.
313 */
314 
315 /*!
316     \qmlmethod QtQuick::GestureEvent::grab()
317 
318     Acquires an exclusive grab of the mouse and all the \l touchPoints, and
319     calls \l {QQuickItem::setKeepTouchGrab()}{setKeepTouchGrab()} and
320     \l {QQuickItem::setKeepMouseGrab()}{setKeepMouseGrab()} so that any
321     parent Item that \l {QQuickItem::filtersChildMouseEvents()}{filters} its
322     children's events will not be allowed to take over the grabs.
323 */
324 
325 /*!
326     \qmltype MultiPointTouchArea
327     \instantiates QQuickMultiPointTouchArea
328     \inqmlmodule QtQuick
329     \inherits Item
330     \ingroup qtquick-input
331     \brief Enables handling of multiple touch points.
332 
333 
334     A MultiPointTouchArea is an invisible item that is used to track multiple touch points.
335 
336     The \l Item::enabled property is used to enable and disable touch handling. When disabled,
337     the touch area becomes transparent to mouse and touch events.
338 
339     By default, the mouse will be handled the same way as a single touch point,
340     and items under the touch area will not receive mouse events because the
341     touch area is handling them. But if the \l mouseEnabled property is set to
342     false, it becomes transparent to mouse events so that another
343     mouse-sensitive Item (such as a MouseArea) can be used to handle mouse
344     interaction separately.
345 
346     MultiPointTouchArea can be used in two ways:
347 
348     \list
349     \li setting \c touchPoints to provide touch point objects with properties that can be bound to
350     \li using the onTouchUpdated or onPressed, onUpdated and onReleased handlers
351     \endlist
352 
353     While a MultiPointTouchArea \e can take exclusive ownership of certain touch points, it is also possible to have
354     multiple MultiPointTouchAreas active at the same time, each operating on a different set of touch points.
355 
356     \sa TouchPoint
357 */
358 
359 /*!
360     \qmlsignal QtQuick::MultiPointTouchArea::pressed(list<TouchPoint> touchPoints)
361 
362     This signal is emitted when new touch points are added. \a touchPoints is a list of these new points.
363 
364     If minimumTouchPoints is set to a value greater than one, this signal will not be emitted until the minimum number
365     of required touch points has been reached.
366 */
367 
368 /*!
369     \qmlsignal QtQuick::MultiPointTouchArea::updated(list<TouchPoint> touchPoints)
370 
371     This signal is emitted when existing touch points are updated. \a touchPoints is a list of these updated points.
372 */
373 
374 /*!
375     \qmlsignal QtQuick::MultiPointTouchArea::released(list<TouchPoint> touchPoints)
376 
377     This signal is emitted when existing touch points are removed. \a touchPoints is a list of these removed points.
378 */
379 
380 /*!
381     \qmlsignal QtQuick::MultiPointTouchArea::canceled(list<TouchPoint> touchPoints)
382 
383     This signal is emitted when new touch events have been canceled because another item stole the touch event handling.
384 
385     This signal is for advanced use: it is useful when there is more than one MultiPointTouchArea
386     that is handling input, or when there is a MultiPointTouchArea inside a \l Flickable. In the latter
387     case, if you execute some logic in the \c onPressed signal handler and then start dragging, the
388     \l Flickable may steal the touch handling from the MultiPointTouchArea. In these cases, to reset
389     the logic when the MultiPointTouchArea has lost the touch handling to the \l Flickable,
390     \c canceled should be handled in addition to \l released.
391 
392     \a touchPoints is the list of canceled points.
393 */
394 
395 /*!
396     \qmlsignal QtQuick::MultiPointTouchArea::gestureStarted(GestureEvent gesture)
397 
398     This signal is emitted when the global drag threshold has been reached.
399 
400     This signal is typically used when a MultiPointTouchArea has been nested in a Flickable or another MultiPointTouchArea.
401     When the threshold has been reached and the signal is handled, you can determine whether or not the touch
402     area should grab the current touch points. By default they will not be grabbed; to grab them call \c gesture.grab(). If the
403     gesture is not grabbed, the nesting Flickable, for example, would also have an opportunity to grab.
404 
405     The \a gesture object also includes information on the current set of \c touchPoints and the \c dragThreshold.
406 */
407 
408 /*!
409     \qmlsignal QtQuick::MultiPointTouchArea::touchUpdated(list<TouchPoint> touchPoints)
410 
411     This signal is emitted when the touch points handled by the MultiPointTouchArea change. This includes adding new touch points,
412     removing or canceling previous touch points, as well as updating current touch point data. \a touchPoints is the list of all current touch
413     points.
414 */
415 
416 /*!
417     \qmlproperty list<TouchPoint> QtQuick::MultiPointTouchArea::touchPoints
418 
419     This property holds a set of user-defined touch point objects that can be bound to.
420 
421     If mouseEnabled is true (the default) and the left mouse button is pressed
422     while the mouse is over the touch area, the current mouse position will be
423     one of these touch points.
424 
425     In the following example, we have two small rectangles that follow our touch points.
426 
427     \snippet qml/multipointtoucharea/multipointtoucharea.qml 0
428 
429     By default this property holds an empty list.
430 
431     \sa TouchPoint
432 */
433 
QQuickMultiPointTouchArea(QQuickItem * parent)434 QQuickMultiPointTouchArea::QQuickMultiPointTouchArea(QQuickItem *parent)
435     : QQuickItem(parent),
436       _minimumTouchPoints(0),
437       _maximumTouchPoints(INT_MAX),
438       _touchMouseDevice(nullptr),
439       _stealMouse(false),
440       _mouseEnabled(true)
441 {
442     setAcceptedMouseButtons(Qt::LeftButton);
443     setFiltersChildMouseEvents(true);
444     if (qmlVisualTouchDebugging()) {
445         setFlag(QQuickItem::ItemHasContents);
446     }
447     setAcceptTouchEvents(true);
448 #ifdef Q_OS_OSX
449     setAcceptHoverEvents(true); // needed to enable touch events on mouse hover.
450 #endif
451 }
452 
~QQuickMultiPointTouchArea()453 QQuickMultiPointTouchArea::~QQuickMultiPointTouchArea()
454 {
455     clearTouchLists();
456     for (QObject *obj : qAsConst(_touchPoints)) {
457         QQuickTouchPoint *dtp = static_cast<QQuickTouchPoint*>(obj);
458         if (!dtp->isQmlDefined())
459             delete dtp;
460     }
461 }
462 
463 /*!
464     \qmlproperty int QtQuick::MultiPointTouchArea::minimumTouchPoints
465     \qmlproperty int QtQuick::MultiPointTouchArea::maximumTouchPoints
466 
467     These properties hold the range of touch points to be handled by the touch area.
468 
469     These are convenience that allow you to, for example, have nested MultiPointTouchAreas,
470     one handling two finger touches, and another handling three finger touches.
471 
472     By default, all touch points within the touch area are handled.
473 
474     If mouseEnabled is true, the mouse acts as a touch point, so it is also
475     subject to these constraints: for example if maximumTouchPoints is two, you
476     can use the mouse as one touch point and a finger as another touch point
477     for a total of two.
478 */
479 
minimumTouchPoints() const480 int QQuickMultiPointTouchArea::minimumTouchPoints() const
481 {
482     return _minimumTouchPoints;
483 }
484 
setMinimumTouchPoints(int num)485 void QQuickMultiPointTouchArea::setMinimumTouchPoints(int num)
486 {
487     if (_minimumTouchPoints == num)
488         return;
489     _minimumTouchPoints = num;
490     emit minimumTouchPointsChanged();
491 }
492 
maximumTouchPoints() const493 int QQuickMultiPointTouchArea::maximumTouchPoints() const
494 {
495     return _maximumTouchPoints;
496 }
497 
setMaximumTouchPoints(int num)498 void QQuickMultiPointTouchArea::setMaximumTouchPoints(int num)
499 {
500     if (_maximumTouchPoints == num)
501         return;
502     _maximumTouchPoints = num;
503     emit maximumTouchPointsChanged();
504 }
505 
506 /*!
507     \qmlproperty bool QtQuick::MultiPointTouchArea::mouseEnabled
508 
509     This property controls whether the MultiPointTouchArea will handle mouse
510     events too. If it is true (the default), the touch area will treat the
511     mouse the same as a single touch point; if it is false, the touch area will
512     ignore mouse events and allow them to "pass through" so that they can be
513     handled by other items underneath.
514 */
setMouseEnabled(bool arg)515 void QQuickMultiPointTouchArea::setMouseEnabled(bool arg)
516 {
517     if (_mouseEnabled != arg) {
518         _mouseEnabled = arg;
519         if (_mouseTouchPoint && !arg)
520             _mouseTouchPoint = nullptr;
521         emit mouseEnabledChanged();
522     }
523 }
524 
touchEvent(QTouchEvent * event)525 void QQuickMultiPointTouchArea::touchEvent(QTouchEvent *event)
526 {
527     switch (event->type()) {
528     case QEvent::TouchBegin:
529     case QEvent::TouchUpdate:
530     case QEvent::TouchEnd: {
531         //if e.g. a parent Flickable has the mouse grab, don't process the touch events
532         QQuickWindow *c = window();
533         QQuickItem *grabber = c ? c->mouseGrabberItem() : nullptr;
534         if (grabber && grabber != this && grabber->keepMouseGrab() && grabber->isEnabled()) {
535             QQuickItem *item = this;
536             while ((item = item->parentItem())) {
537                 if (item == grabber)
538                     return;
539             }
540         }
541         updateTouchData(event);
542         if (event->type() == QEvent::TouchEnd)
543             ungrab();
544         break;
545     }
546     case QEvent::TouchCancel:
547         ungrab();
548         break;
549     default:
550         QQuickItem::touchEvent(event);
551         break;
552     }
553 }
554 
grabGesture()555 void QQuickMultiPointTouchArea::grabGesture()
556 {
557     _stealMouse = true;
558 
559     grabMouse();
560     setKeepMouseGrab(true);
561 
562     QVector<int> ids;
563     ids.reserve(_touchPoints.size());
564     for (auto it = _touchPoints.keyBegin(), end = _touchPoints.keyEnd(); it != end; ++it) {
565         if (*it != -1) // -1 might be the mouse-point, but we already grabbed the mouse above.
566             ids.append(*it);
567     }
568     grabTouchPoints(ids);
569     setKeepTouchGrab(true);
570 }
571 
updateTouchData(QEvent * event)572 void QQuickMultiPointTouchArea::updateTouchData(QEvent *event)
573 {
574     bool ended = false;
575     bool moved = false;
576     bool started = false;
577 
578     clearTouchLists();
579     QList<QTouchEvent::TouchPoint> touchPoints;
580     QQuickWindowPrivate *windowPriv = QQuickWindowPrivate::get(window());
581 
582     switch (event->type()) {
583     case QEvent::TouchBegin:
584     case QEvent::TouchUpdate:
585     case QEvent::TouchEnd:
586         touchPoints = static_cast<QTouchEvent*>(event)->touchPoints();
587         break;
588     case QEvent::MouseButtonPress:
589         _mouseQpaTouchPoint = QTouchEvent::TouchPoint(windowPriv->touchMouseId);
590         _touchMouseDevice = windowPriv->touchMouseDevice->qTouchDevice();
591         Q_FALLTHROUGH();
592     case QEvent::MouseMove:
593     case QEvent::MouseButtonRelease: {
594         QMouseEvent *me = static_cast<QMouseEvent*>(event);
595         _mouseQpaTouchPoint.setPos(me->localPos());
596         _mouseQpaTouchPoint.setScenePos(me->windowPos());
597         _mouseQpaTouchPoint.setScreenPos(me->screenPos());
598         if (event->type() == QEvent::MouseMove)
599             _mouseQpaTouchPoint.setState(Qt::TouchPointMoved);
600         else if (event->type() == QEvent::MouseButtonRelease)
601             _mouseQpaTouchPoint.setState(Qt::TouchPointReleased);
602         else { // QEvent::MouseButtonPress
603             addTouchPoint(me);
604             started = true;
605             _mouseQpaTouchPoint.setStartPos(me->localPos());
606             _mouseQpaTouchPoint.setStartScenePos(me->windowPos());
607             _mouseQpaTouchPoint.setStartScreenPos(me->screenPos());
608             _mouseQpaTouchPoint.setState(Qt::TouchPointPressed);
609         }
610         touchPoints << _mouseQpaTouchPoint;
611         break;
612     }
613     default:
614         qWarning("updateTouchData: unhandled event type %d", event->type());
615         break;
616     }
617 
618     int numTouchPoints = touchPoints.count();
619     //always remove released touches, and make sure we handle all releases before adds.
620     for (const QTouchEvent::TouchPoint &p : qAsConst(touchPoints)) {
621         Qt::TouchPointState touchPointState = p.state();
622         int id = p.id();
623         if (touchPointState & Qt::TouchPointReleased) {
624             QQuickTouchPoint* dtp = static_cast<QQuickTouchPoint*>(_touchPoints.value(id));
625             if (!dtp)
626                 continue;
627             updateTouchPoint(dtp, &p);
628             dtp->setPressed(false);
629             _releasedTouchPoints.append(dtp);
630             _touchPoints.remove(id);
631             ended = true;
632         }
633     }
634     if (numTouchPoints >= _minimumTouchPoints && numTouchPoints <= _maximumTouchPoints) {
635         for (const QTouchEvent::TouchPoint &p : qAsConst(touchPoints)) {
636             Qt::TouchPointState touchPointState = p.state();
637             int id = p.id();
638             if (touchPointState & Qt::TouchPointReleased) {
639                 //handled above
640             } else if (!_touchPoints.contains(id)) { //could be pressed, moved, or stationary
641                 // (we may have just obtained enough points to start tracking them -- in that case moved or stationary count as newly pressed)
642                 addTouchPoint(&p);
643                 started = true;
644             } else if ((touchPointState & Qt::TouchPointMoved) || p.d->stationaryWithModifiedProperty) {
645                 // React to a stationary point with a property change (velocity, pressure) as if the point moved. (QTBUG-77142)
646                 QQuickTouchPoint* dtp = static_cast<QQuickTouchPoint*>(_touchPoints.value(id));
647                 Q_ASSERT(dtp);
648                 _movedTouchPoints.append(dtp);
649                 updateTouchPoint(dtp,&p);
650                 moved = true;
651             } else {
652                 QQuickTouchPoint* dtp = static_cast<QQuickTouchPoint*>(_touchPoints.value(id));
653                 Q_ASSERT(dtp);
654                 updateTouchPoint(dtp,&p);
655             }
656         }
657 
658         //see if we should be grabbing the gesture
659         if (!_stealMouse /* !ignoring gesture*/) {
660             bool offerGrab = false;
661             const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
662             for (const QTouchEvent::TouchPoint &p : qAsConst(touchPoints)) {
663                 if (p.state() == Qt::TouchPointReleased)
664                     continue;
665                 const QPointF &currentPos = p.scenePos();
666                 const QPointF &startPos = p.startScenePos();
667                 if (qAbs(currentPos.x() - startPos.x()) > dragThreshold)
668                     offerGrab = true;
669                 else if (qAbs(currentPos.y() - startPos.y()) > dragThreshold)
670                     offerGrab = true;
671                 if (offerGrab)
672                     break;
673             }
674 
675             if (offerGrab) {
676                 QQuickGrabGestureEvent event;
677                 event._touchPoints = _touchPoints.values();
678                 emit gestureStarted(&event);
679                 if (event.wantsGrab())
680                     grabGesture();
681             }
682         }
683 
684         if (ended)
685             emit released(_releasedTouchPoints);
686         if (moved)
687             emit updated(_movedTouchPoints);
688         if (started)
689             emit pressed(_pressedTouchPoints);
690         if (ended || moved || started) emit touchUpdated(_touchPoints.values());
691     }
692 }
693 
clearTouchLists()694 void QQuickMultiPointTouchArea::clearTouchLists()
695 {
696     for (QObject *obj : qAsConst(_releasedTouchPoints)) {
697         QQuickTouchPoint *dtp = static_cast<QQuickTouchPoint*>(obj);
698         if (!dtp->isQmlDefined()) {
699             _touchPoints.remove(dtp->pointId());
700             delete dtp;
701         } else {
702             dtp->setInUse(false);
703         }
704     }
705     _releasedTouchPoints.clear();
706     _pressedTouchPoints.clear();
707     _movedTouchPoints.clear();
708 }
709 
addTouchPoint(const QTouchEvent::TouchPoint * p)710 void QQuickMultiPointTouchArea::addTouchPoint(const QTouchEvent::TouchPoint *p)
711 {
712     QQuickTouchPoint *dtp = nullptr;
713     for (QQuickTouchPoint* tp : qAsConst(_touchPrototypes)) {
714         if (!tp->inUse()) {
715             tp->setInUse(true);
716             dtp = tp;
717             break;
718         }
719     }
720 
721     if (dtp == nullptr)
722         dtp = new QQuickTouchPoint(false);
723     dtp->setPointId(p->id());
724     updateTouchPoint(dtp,p);
725     dtp->setPressed(true);
726     _touchPoints.insert(p->id(),dtp);
727     _pressedTouchPoints.append(dtp);
728 }
729 
addTouchPoint(const QMouseEvent * e)730 void QQuickMultiPointTouchArea::addTouchPoint(const QMouseEvent *e)
731 {
732     QQuickTouchPoint *dtp = nullptr;
733     for (QQuickTouchPoint *tp : qAsConst(_touchPrototypes))
734         if (!tp->inUse()) {
735             tp->setInUse(true);
736             dtp = tp;
737             break;
738         }
739 
740     if (dtp == nullptr)
741         dtp = new QQuickTouchPoint(false);
742     updateTouchPoint(dtp, e);
743     dtp->setPressed(true);
744     _touchPoints.insert(_touchMouseDevice && _mouseQpaTouchPoint.id() > 0 ? _mouseQpaTouchPoint.id() : -1, dtp);
745     _pressedTouchPoints.append(dtp);
746     _mouseTouchPoint = dtp;
747 }
748 
749 #ifdef Q_OS_OSX
hoverEnterEvent(QHoverEvent * event)750 void QQuickMultiPointTouchArea::hoverEnterEvent(QHoverEvent *event)
751 {
752     Q_UNUSED(event);
753     setTouchEventsEnabled(true);
754 }
755 
hoverLeaveEvent(QHoverEvent * event)756 void QQuickMultiPointTouchArea::hoverLeaveEvent(QHoverEvent *event)
757 {
758     Q_UNUSED(event);
759     setTouchEventsEnabled(false);
760 }
761 
setTouchEventsEnabled(bool enable)762 void QQuickMultiPointTouchArea::setTouchEventsEnabled(bool enable)
763 {
764     // Resolve function for enabling touch events from the (cocoa) platform plugin.
765     typedef void (*RegisterTouchWindowFunction)(QWindow *, bool);
766     RegisterTouchWindowFunction registerTouchWindow = reinterpret_cast<RegisterTouchWindowFunction>(
767         QGuiApplication::platformNativeInterface()->nativeResourceFunctionForIntegration("registertouchwindow"));
768     if (!registerTouchWindow)
769         return; // Not necessarily an error, Qt might be using a different platform plugin.
770 
771     registerTouchWindow(window(), enable);
772 }
773 #endif // Q_OS_OSX
774 
addTouchPrototype(QQuickTouchPoint * prototype)775 void QQuickMultiPointTouchArea::addTouchPrototype(QQuickTouchPoint *prototype)
776 {
777     int id = _touchPrototypes.count();
778     prototype->setPointId(id);
779     _touchPrototypes.insert(id, prototype);
780 }
781 
updateTouchPoint(QQuickTouchPoint * dtp,const QTouchEvent::TouchPoint * p)782 void QQuickMultiPointTouchArea::updateTouchPoint(QQuickTouchPoint *dtp, const QTouchEvent::TouchPoint *p)
783 {
784     //TODO: if !qmlDefined, could bypass setters.
785     //      also, should only emit signals after all values have been set
786     dtp->setUniqueId(p->uniqueId());
787     dtp->setPosition(p->pos());
788     dtp->setEllipseDiameters(p->ellipseDiameters());
789     dtp->setPressure(p->pressure());
790     dtp->setRotation(p->rotation());
791     dtp->setVelocity(p->velocity());
792     QRectF area(QPointF(), p->ellipseDiameters());
793     area.moveCenter(p->pos());
794     dtp->setArea(area);
795     dtp->setStartX(p->startPos().x());
796     dtp->setStartY(p->startPos().y());
797     dtp->setPreviousX(p->lastPos().x());
798     dtp->setPreviousY(p->lastPos().y());
799     dtp->setSceneX(p->scenePos().x());
800     dtp->setSceneY(p->scenePos().y());
801 }
802 
updateTouchPoint(QQuickTouchPoint * dtp,const QMouseEvent * e)803 void QQuickMultiPointTouchArea::updateTouchPoint(QQuickTouchPoint *dtp, const QMouseEvent *e)
804 {
805     dtp->setPreviousX(dtp->x());
806     dtp->setPreviousY(dtp->y());
807     dtp->setPosition(e->localPos());
808     if (e->type() == QEvent::MouseButtonPress) {
809         dtp->setStartX(e->localPos().x());
810         dtp->setStartY(e->localPos().y());
811     }
812     dtp->setSceneX(e->windowPos().x());
813     dtp->setSceneY(e->windowPos().y());
814 }
815 
mousePressEvent(QMouseEvent * event)816 void QQuickMultiPointTouchArea::mousePressEvent(QMouseEvent *event)
817 {
818     if (!isEnabled() || !_mouseEnabled || event->button() != Qt::LeftButton) {
819         QQuickItem::mousePressEvent(event);
820         return;
821     }
822 
823     _stealMouse = false;
824     setKeepMouseGrab(false);
825     event->setAccepted(true);
826     _mousePos = event->localPos();
827     if (event->source() != Qt::MouseEventNotSynthesized && event->source() != Qt::MouseEventSynthesizedByQt)
828         return;
829 
830     if (_touchPoints.count() >= _minimumTouchPoints - 1 && _touchPoints.count() < _maximumTouchPoints) {
831         updateTouchData(event);
832     }
833 }
834 
mouseMoveEvent(QMouseEvent * event)835 void QQuickMultiPointTouchArea::mouseMoveEvent(QMouseEvent *event)
836 {
837     if (!isEnabled() || !_mouseEnabled) {
838         QQuickItem::mouseMoveEvent(event);
839         return;
840     }
841 
842     if (event->source() != Qt::MouseEventNotSynthesized && event->source() != Qt::MouseEventSynthesizedByQt)
843         return;
844 
845     _movedTouchPoints.clear();
846     updateTouchData(event);
847 }
848 
mouseReleaseEvent(QMouseEvent * event)849 void QQuickMultiPointTouchArea::mouseReleaseEvent(QMouseEvent *event)
850 {
851     _stealMouse = false;
852     if (!isEnabled() || !_mouseEnabled) {
853         QQuickItem::mouseReleaseEvent(event);
854         return;
855     }
856 
857     if (event->source() != Qt::MouseEventNotSynthesized && event->source() != Qt::MouseEventSynthesizedByQt)
858         return;
859 
860     if (_mouseTouchPoint) {
861         updateTouchData(event);
862         _mouseTouchPoint->setInUse(false);
863         _releasedTouchPoints.removeAll(_mouseTouchPoint);
864         _mouseTouchPoint = nullptr;
865     }
866 
867     setKeepMouseGrab(false);
868 }
869 
ungrab(bool normalRelease)870 void QQuickMultiPointTouchArea::ungrab(bool normalRelease)
871 {
872     _stealMouse = false;
873     setKeepMouseGrab(false);
874     setKeepTouchGrab(false);
875     if (!normalRelease)
876         ungrabTouchPoints();
877 
878     if (_touchPoints.count()) {
879         for (QObject *obj : qAsConst(_touchPoints))
880             static_cast<QQuickTouchPoint*>(obj)->setPressed(false);
881         emit canceled(_touchPoints.values());
882         clearTouchLists();
883         for (QObject *obj : qAsConst(_touchPoints)) {
884             QQuickTouchPoint *dtp = static_cast<QQuickTouchPoint*>(obj);
885             if (!dtp->isQmlDefined())
886                 delete dtp;
887             else
888                 dtp->setInUse(false);
889         }
890         _touchPoints.clear();
891         emit touchUpdated(QList<QObject*>());
892     }
893 }
894 
mouseUngrabEvent()895 void QQuickMultiPointTouchArea::mouseUngrabEvent()
896 {
897     ungrab();
898 }
899 
touchUngrabEvent()900 void QQuickMultiPointTouchArea::touchUngrabEvent()
901 {
902     ungrab();
903 }
904 
sendMouseEvent(QMouseEvent * event)905 bool QQuickMultiPointTouchArea::sendMouseEvent(QMouseEvent *event)
906 {
907     QPointF localPos = mapFromScene(event->windowPos());
908 
909     QQuickWindow *c = window();
910     QQuickItem *grabber = c ? c->mouseGrabberItem() : nullptr;
911     bool stealThisEvent = _stealMouse;
912     if ((stealThisEvent || contains(localPos)) && (!grabber || !grabber->keepMouseGrab())) {
913         QMouseEvent mouseEvent(event->type(), localPos, event->windowPos(), event->screenPos(),
914                                event->button(), event->buttons(), event->modifiers());
915         mouseEvent.setAccepted(false);
916         QGuiApplicationPrivate::setMouseEventCapsAndVelocity(&mouseEvent,
917                                                              QGuiApplicationPrivate::mouseEventCaps(event),
918                                                              QGuiApplicationPrivate::mouseEventVelocity(event));
919         QGuiApplicationPrivate::setMouseEventSource(&mouseEvent, Qt::MouseEventSynthesizedByQt);
920 
921         switch (mouseEvent.type()) {
922         case QEvent::MouseMove:
923             mouseMoveEvent(&mouseEvent);
924             break;
925         case QEvent::MouseButtonPress:
926             mousePressEvent(&mouseEvent);
927             break;
928         case QEvent::MouseButtonRelease:
929             mouseReleaseEvent(&mouseEvent);
930             break;
931         default:
932             break;
933         }
934         grabber = c ? c->mouseGrabberItem() : nullptr;
935         if (grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this)
936             grabMouse();
937 
938         return stealThisEvent;
939     }
940     if (event->type() == QEvent::MouseButtonRelease) {
941         _stealMouse = false;
942         if (c && c->mouseGrabberItem() == this)
943             ungrabMouse();
944         setKeepMouseGrab(false);
945     }
946     return false;
947 }
948 
childMouseEventFilter(QQuickItem * receiver,QEvent * event)949 bool QQuickMultiPointTouchArea::childMouseEventFilter(QQuickItem *receiver, QEvent *event)
950 {
951     if (!isEnabled() || !isVisible())
952         return QQuickItem::childMouseEventFilter(receiver, event);
953     switch (event->type()) {
954     case QEvent::MouseButtonPress: {
955         QQuickWindowPrivate *windowPriv = QQuickWindowPrivate::get(window());
956         // If we already got a chance to filter the touchpoint that generated this synth-mouse-press,
957         // and chose not to filter it, ignore it now, too.
958         if (static_cast<QMouseEvent *>(event)->source() == Qt::MouseEventSynthesizedByQt &&
959                 _lastFilterableTouchPointIds.contains(windowPriv->touchMouseId))
960             return false;
961         } Q_FALLTHROUGH();
962     case QEvent::MouseMove:
963     case QEvent::MouseButtonRelease:
964         return sendMouseEvent(static_cast<QMouseEvent *>(event));
965     case QEvent::TouchBegin:
966         _lastFilterableTouchPointIds.clear();
967         Q_FALLTHROUGH();
968     case QEvent::TouchUpdate:
969         for (auto tp : static_cast<QTouchEvent*>(event)->touchPoints()) {
970             if (tp.state() == Qt::TouchPointPressed)
971                 _lastFilterableTouchPointIds << tp.id();
972         }
973         if (!shouldFilter(event))
974             return false;
975         updateTouchData(event);
976         return _stealMouse;
977     case QEvent::TouchEnd: {
978             if (!shouldFilter(event))
979                 return false;
980             updateTouchData(event);
981             ungrab(true);
982         }
983         break;
984     default:
985         break;
986     }
987     return QQuickItem::childMouseEventFilter(receiver, event);
988 }
989 
shouldFilter(QEvent * event)990 bool QQuickMultiPointTouchArea::shouldFilter(QEvent *event)
991 {
992     QQuickWindow *c = window();
993     QQuickItem *grabber = c ? c->mouseGrabberItem() : nullptr;
994     bool disabledItem = grabber && !grabber->isEnabled();
995     bool stealThisEvent = _stealMouse;
996     bool containsPoint = false;
997     if (!stealThisEvent) {
998         switch (event->type()) {
999         case QEvent::MouseButtonPress:
1000         case QEvent::MouseMove:
1001         case QEvent::MouseButtonRelease: {
1002                 QMouseEvent *me = static_cast<QMouseEvent*>(event);
1003                 containsPoint = contains(mapFromScene(me->windowPos()));
1004             }
1005             break;
1006         case QEvent::TouchBegin:
1007         case QEvent::TouchUpdate:
1008         case QEvent::TouchEnd: {
1009                 QTouchEvent *te = static_cast<QTouchEvent*>(event);
1010                 for (const QTouchEvent::TouchPoint &point : te->touchPoints()) {
1011                     if (contains(mapFromScene(point.scenePos()))) {
1012                         containsPoint = true;
1013                         break;
1014                     }
1015                 }
1016             }
1017             break;
1018         default:
1019             break;
1020         }
1021     }
1022     if ((stealThisEvent || containsPoint) && (!grabber || !grabber->keepMouseGrab() || disabledItem)) {
1023         return true;
1024     }
1025     ungrab();
1026     return false;
1027 }
1028 
updatePaintNode(QSGNode * oldNode,UpdatePaintNodeData * data)1029 QSGNode *QQuickMultiPointTouchArea::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
1030 {
1031     Q_UNUSED(data);
1032 
1033     if (!qmlVisualTouchDebugging())
1034         return nullptr;
1035 
1036     QSGInternalRectangleNode *rectangle = static_cast<QSGInternalRectangleNode *>(oldNode);
1037     if (!rectangle) rectangle = QQuickItemPrivate::get(this)->sceneGraphContext()->createInternalRectangleNode();
1038 
1039     rectangle->setRect(QRectF(0, 0, width(), height()));
1040     rectangle->setColor(QColor(255, 0, 0, 50));
1041     rectangle->update();
1042     return rectangle;
1043 }
1044 
1045 QT_END_NAMESPACE
1046 
1047 #include "moc_qquickmultipointtoucharea_p.cpp"
1048