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

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