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
52QT_BEGIN_NAMESPACE
53
54DEFINE_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*/
76void 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
91void 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*/
112void 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*/
134void 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*/
154void QQuickTouchPoint::setRotation(qreal r)
155{
156 if (_rotation == r)
157 return;
158 _rotation = r;
159 emit rotationChanged();
160}
161
162void 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*/
180void 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*/
193void 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
208void QQuickTouchPoint::setStartX(qreal startX)
209{
210 if (_startX == startX)
211 return;
212 _startX = startX;
213 emit startXChanged();
214}
215
216void 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*/
230void QQuickTouchPoint::setPreviousX(qreal previousX)
231{
232 if (_previousX == previousX)
233 return;
234 _previousX = previousX;
235 emit previousXChanged();
236}
237
238void 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
253void QQuickTouchPoint::setSceneX(qreal sceneX)
254{
255 if (_sceneX == sceneX)
256 return;
257 _sceneX = sceneX;
258 emit sceneXChanged();
259}
260
261void 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*/
282void 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
434QQuickMultiPointTouchArea::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(flag: 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
453QQuickMultiPointTouchArea::~QQuickMultiPointTouchArea()
454{
455 clearTouchLists();
456 for (QObject *obj : qAsConst(t&: _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
480int QQuickMultiPointTouchArea::minimumTouchPoints() const
481{
482 return _minimumTouchPoints;
483}
484
485void QQuickMultiPointTouchArea::setMinimumTouchPoints(int num)
486{
487 if (_minimumTouchPoints == num)
488 return;
489 _minimumTouchPoints = num;
490 emit minimumTouchPointsChanged();
491}
492
493int QQuickMultiPointTouchArea::maximumTouchPoints() const
494{
495 return _maximumTouchPoints;
496}
497
498void 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*/
515void 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
525void 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
555void QQuickMultiPointTouchArea::grabGesture()
556{
557 _stealMouse = true;
558
559 grabMouse();
560 setKeepMouseGrab(true);
561
562 QVector<int> ids;
563 ids.reserve(size: _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(t: *it);
567 }
568 grabTouchPoints(ids);
569 setKeepTouchGrab(true);
570}
571
572void 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(c: 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(e: 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(msg: "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(t&: 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(akey: id));
625 if (!dtp)
626 continue;
627 updateTouchPoint(dtp, &p);
628 dtp->setPressed(false);
629 _releasedTouchPoints.append(t: dtp);
630 _touchPoints.remove(key: id);
631 ended = true;
632 }
633 }
634 if (numTouchPoints >= _minimumTouchPoints && numTouchPoints <= _maximumTouchPoints) {
635 for (const QTouchEvent::TouchPoint &p : qAsConst(t&: 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(key: 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: &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(akey: id));
647 Q_ASSERT(dtp);
648 _movedTouchPoints.append(t: dtp);
649 updateTouchPoint(dtp,&p);
650 moved = true;
651 } else {
652 QQuickTouchPoint* dtp = static_cast<QQuickTouchPoint*>(_touchPoints.value(akey: 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(t&: touchPoints)) {
663 if (p.state() == Qt::TouchPointReleased)
664 continue;
665 const QPointF &currentPos = p.scenePos();
666 const QPointF &startPos = p.startScenePos();
667 if (qAbs(t: currentPos.x() - startPos.x()) > dragThreshold)
668 offerGrab = true;
669 else if (qAbs(t: 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(gesture: &event);
679 if (event.wantsGrab())
680 grabGesture();
681 }
682 }
683
684 if (ended)
685 emit released(touchPoints: _releasedTouchPoints);
686 if (moved)
687 emit updated(touchPoints: _movedTouchPoints);
688 if (started)
689 emit pressed(touchPoints: _pressedTouchPoints);
690 if (ended || moved || started) emit touchUpdated(touchPoints: _touchPoints.values());
691 }
692}
693
694void QQuickMultiPointTouchArea::clearTouchLists()
695{
696 for (QObject *obj : qAsConst(t&: _releasedTouchPoints)) {
697 QQuickTouchPoint *dtp = static_cast<QQuickTouchPoint*>(obj);
698 if (!dtp->isQmlDefined()) {
699 _touchPoints.remove(key: dtp->pointId());
700 delete dtp;
701 } else {
702 dtp->setInUse(false);
703 }
704 }
705 _releasedTouchPoints.clear();
706 _pressedTouchPoints.clear();
707 _movedTouchPoints.clear();
708}
709
710void QQuickMultiPointTouchArea::addTouchPoint(const QTouchEvent::TouchPoint *p)
711{
712 QQuickTouchPoint *dtp = nullptr;
713 for (QQuickTouchPoint* tp : qAsConst(t&: _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(key: p->id(),value: dtp);
727 _pressedTouchPoints.append(t: dtp);
728}
729
730void QQuickMultiPointTouchArea::addTouchPoint(const QMouseEvent *e)
731{
732 QQuickTouchPoint *dtp = nullptr;
733 for (QQuickTouchPoint *tp : qAsConst(t&: _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(key: _touchMouseDevice && _mouseQpaTouchPoint.id() > 0 ? _mouseQpaTouchPoint.id() : -1, value: dtp);
745 _pressedTouchPoints.append(t: dtp);
746 _mouseTouchPoint = dtp;
747}
748
749#ifdef Q_OS_OSX
750void QQuickMultiPointTouchArea::hoverEnterEvent(QHoverEvent *event)
751{
752 Q_UNUSED(event);
753 setTouchEventsEnabled(true);
754}
755
756void QQuickMultiPointTouchArea::hoverLeaveEvent(QHoverEvent *event)
757{
758 Q_UNUSED(event);
759 setTouchEventsEnabled(false);
760}
761
762void 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
775void QQuickMultiPointTouchArea::addTouchPrototype(QQuickTouchPoint *prototype)
776{
777 int id = _touchPrototypes.count();
778 prototype->setPointId(id);
779 _touchPrototypes.insert(key: id, value: prototype);
780}
781
782void 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: 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
803void 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
816void 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
835void 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
849void 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(t: _mouseTouchPoint);
864 _mouseTouchPoint = nullptr;
865 }
866
867 setKeepMouseGrab(false);
868}
869
870void 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(t&: _touchPoints))
880 static_cast<QQuickTouchPoint*>(obj)->setPressed(false);
881 emit canceled(touchPoints: _touchPoints.values());
882 clearTouchLists();
883 for (QObject *obj : qAsConst(t&: _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(touchPoints: QList<QObject*>());
892 }
893}
894
895void QQuickMultiPointTouchArea::mouseUngrabEvent()
896{
897 ungrab();
898}
899
900void QQuickMultiPointTouchArea::touchUngrabEvent()
901{
902 ungrab();
903}
904
905bool QQuickMultiPointTouchArea::sendMouseEvent(QMouseEvent *event)
906{
907 QPointF localPos = mapFromScene(point: event->windowPos());
908
909 QQuickWindow *c = window();
910 QQuickItem *grabber = c ? c->mouseGrabberItem() : nullptr;
911 bool stealThisEvent = _stealMouse;
912 if ((stealThisEvent || contains(point: 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(event: &mouseEvent,
917 caps: QGuiApplicationPrivate::mouseEventCaps(event),
918 velocity: QGuiApplicationPrivate::mouseEventVelocity(event));
919 QGuiApplicationPrivate::setMouseEventSource(event: &mouseEvent, source: Qt::MouseEventSynthesizedByQt);
920
921 switch (mouseEvent.type()) {
922 case QEvent::MouseMove:
923 mouseMoveEvent(event: &mouseEvent);
924 break;
925 case QEvent::MouseButtonPress:
926 mousePressEvent(event: &mouseEvent);
927 break;
928 case QEvent::MouseButtonRelease:
929 mouseReleaseEvent(event: &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
949bool 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(c: 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(t: windowPriv->touchMouseId))
960 return false;
961 } Q_FALLTHROUGH();
962 case QEvent::MouseMove:
963 case QEvent::MouseButtonRelease:
964 return sendMouseEvent(event: 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(normalRelease: true);
982 }
983 break;
984 default:
985 break;
986 }
987 return QQuickItem::childMouseEventFilter(receiver, event);
988}
989
990bool 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(point: mapFromScene(point: 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(point: mapFromScene(point: 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
1029QSGNode *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(item: 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
1045QT_END_NAMESPACE
1046
1047#include "moc_qquickmultipointtoucharea_p.cpp"
1048

source code of qtdeclarative/src/quick/items/qquickmultipointtoucharea.cpp