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