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