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 {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 for (const QEventPoint &p : std::as_const(touchPoints)) {
663 if (p.state() == QEventPoint::State::Released)
664 continue;
665 const QPointF currentPos = mapFromScene(p.scenePosition());
666 const QPointF startPos = mapFromScene(p.scenePressPosition());
667 if (qAbs(currentPos.x() - startPos.x()) > dragThreshold)
668 offerGrab = true;
669 else if (qAbs(currentPos.y() - startPos.y()) > dragThreshold)
670 offerGrab = true;
671 if (offerGrab)
672 break;
673 }
674
675 if (offerGrab) {
676 QQuickGrabGestureEvent event;
677 event._touchPoints = _touchPoints.values();
678 emit gestureStarted(&event);
679 if (event.wantsGrab() && dev)
680 grabGesture(dev);
681 }
682 }
683
684 if (ended)
685 emit released(_releasedTouchPoints);
686 if (moved)
687 emit updated(_movedTouchPoints);
688 if (started && !_pressedTouchPoints.isEmpty())
689 emit pressed(_pressedTouchPoints);
690 if (ended || moved || started) emit touchUpdated(_touchPoints.values());
691 }
692}
693
694void QQuickMultiPointTouchArea::clearTouchLists()
695{
696 for (QObject *obj : std::as_const(_releasedTouchPoints)) {
697 QQuickTouchPoint *dtp = static_cast<QQuickTouchPoint*>(obj);
698 if (!dtp->isQmlDefined()) {
699 _touchPoints.remove(dtp->pointId());
700 delete dtp;
701 } else {
702 dtp->setInUse(false);
703 }
704 }
705 _releasedTouchPoints.clear();
706 _pressedTouchPoints.clear();
707 _movedTouchPoints.clear();
708}
709
710void QQuickMultiPointTouchArea::addTouchPoint(const QEventPoint *p)
711{
712 QQuickTouchPoint *dtp = nullptr;
713 for (QQuickTouchPoint* tp : std::as_const(_touchPrototypes)) {
714 if (!tp->inUse()) {
715 tp->setInUse(true);
716 dtp = tp;
717 break;
718 }
719 }
720
721 if (dtp == nullptr)
722 dtp = new QQuickTouchPoint(false);
723 dtp->setPointId(p->id());
724 updateTouchPoint(dtp,p);
725 dtp->setPressed(true);
726 _touchPoints.insert(p->id(),dtp);
727 _pressedTouchPoints.append(dtp);
728}
729
730void QQuickMultiPointTouchArea::addTouchPoint(const QMouseEvent *e)
731{
732 QQuickTouchPoint *dtp = nullptr;
733 for (QQuickTouchPoint *tp : std::as_const(_touchPrototypes)) {
734 if (!tp->inUse()) {
735 tp->setInUse(true);
736 dtp = tp;
737 break;
738 } else if (_mouseTouchPoint == tp) {
739 return; // do not allow more than one touchpoint to react to the mouse (QTBUG-83662)
740 }
741 }
742
743 if (dtp == nullptr)
744 dtp = new QQuickTouchPoint(false);
745 updateTouchPoint(dtp, e);
746 dtp->setPressed(true);
747 _touchPoints.insert(_mouseQpaTouchPoint.id(), dtp);
748 _pressedTouchPoints.append(dtp);
749 _mouseTouchPoint = dtp;
750}
751
752#ifdef Q_OS_MACOS
753void QQuickMultiPointTouchArea::hoverEnterEvent(QHoverEvent *event)
754{
755 setTouchEventsEnabled(isEnabled());
756 QQuickItem::hoverEnterEvent(event);
757}
758
759void QQuickMultiPointTouchArea::hoverLeaveEvent(QHoverEvent *event)
760{
761 setTouchEventsEnabled(false);
762 QQuickItem::hoverLeaveEvent(event);
763}
764
765void QQuickMultiPointTouchArea::setTouchEventsEnabled(bool enable)
766{
767 // Resolve function for enabling touch events from the (cocoa) platform plugin.
768 typedef void (*RegisterTouchWindowFunction)(QWindow *, bool);
769 RegisterTouchWindowFunction registerTouchWindow = reinterpret_cast<RegisterTouchWindowFunction>(
770 QFunctionPointer(QGuiApplication::platformNativeInterface()->nativeResourceFunctionForIntegration("registertouchwindow")));
771 if (!registerTouchWindow)
772 return; // Not necessarily an error, Qt might be using a different platform plugin.
773
774 registerTouchWindow(window(), enable);
775}
776
777void QQuickMultiPointTouchArea::itemChange(ItemChange change, const ItemChangeData &data)
778{
779 if (change == ItemEnabledHasChanged)
780 setAcceptHoverEvents(data.boolValue);
781 QQuickItem::itemChange(change, data);
782}
783#endif // Q_OS_MACOS
784
785void QQuickMultiPointTouchArea::addTouchPrototype(QQuickTouchPoint *prototype)
786{
787 int id = _touchPrototypes.size();
788 prototype->setPointId(id);
789 _touchPrototypes.insert(id, prototype);
790}
791
792void QQuickMultiPointTouchArea::updateTouchPoint(QQuickTouchPoint *dtp, const QEventPoint *p)
793{
794 //TODO: if !qmlDefined, could bypass setters.
795 // also, should only emit signals after all values have been set
796 dtp->setUniqueId(p->uniqueId());
797 dtp->setPosition(p->position());
798 dtp->setEllipseDiameters(p->ellipseDiameters());
799 dtp->setPressure(p->pressure());
800 dtp->setRotation(p->rotation());
801 dtp->setVelocity(p->velocity());
802 QRectF area(QPointF(), p->ellipseDiameters());
803 area.moveCenter(p->position());
804 dtp->setArea(area);
805
806 if (p->state() == QEventPoint::State::Pressed) {
807 // Note that QEventPoint::startPosition() is not stored in it's own
808 // member variable, but derived from QEventPoint::globalPosition().
809 // It will therefore be wrong if the MPTA is transformed.
810 dtp->setStartX(p->position().x());
811 dtp->setStartY(p->position().y());
812 }
813 dtp->setPreviousX(p->lastPosition().x());
814 dtp->setPreviousY(p->lastPosition().y());
815 dtp->setSceneX(p->scenePosition().x());
816 dtp->setSceneY(p->scenePosition().y());
817}
818
819void QQuickMultiPointTouchArea::updateTouchPoint(QQuickTouchPoint *dtp, const QMouseEvent *e)
820{
821 dtp->setPreviousX(dtp->x());
822 dtp->setPreviousY(dtp->y());
823 dtp->setPosition(e->position());
824 if (e->type() == QEvent::MouseButtonPress) {
825 dtp->setStartX(e->position().x());
826 dtp->setStartY(e->position().y());
827 }
828 dtp->setSceneX(e->scenePosition().x());
829 dtp->setSceneY(e->scenePosition().y());
830}
831
832void QQuickMultiPointTouchArea::mousePressEvent(QMouseEvent *event)
833{
834 if (!isEnabled() || !_mouseEnabled || event->button() != Qt::LeftButton) {
835 QQuickItem::mousePressEvent(event);
836 return;
837 }
838
839 _stealMouse = false;
840 setKeepMouseGrab(false);
841 event->setAccepted(true);
842 _mousePos = event->position();
843 if (event->source() != Qt::MouseEventNotSynthesized && event->source() != Qt::MouseEventSynthesizedByQt)
844 return;
845
846 if (_touchPoints.size() >= _minimumTouchPoints - 1 && _touchPoints.size() < _maximumTouchPoints) {
847 updateTouchData(event);
848 }
849}
850
851void QQuickMultiPointTouchArea::mouseMoveEvent(QMouseEvent *event)
852{
853 if (!isEnabled() || !_mouseEnabled) {
854 QQuickItem::mouseMoveEvent(event);
855 return;
856 }
857
858 if (event->source() != Qt::MouseEventNotSynthesized && event->source() != Qt::MouseEventSynthesizedByQt)
859 return;
860
861 _movedTouchPoints.clear();
862 updateTouchData(event);
863}
864
865void QQuickMultiPointTouchArea::mouseReleaseEvent(QMouseEvent *event)
866{
867 _stealMouse = false;
868 if (!isEnabled() || !_mouseEnabled) {
869 QQuickItem::mouseReleaseEvent(event);
870 return;
871 }
872
873 if (event->source() != Qt::MouseEventNotSynthesized && event->source() != Qt::MouseEventSynthesizedByQt)
874 return;
875
876 if (_mouseTouchPoint) {
877 updateTouchData(event);
878 _mouseTouchPoint->setInUse(false);
879 _releasedTouchPoints.removeAll(_mouseTouchPoint);
880 _mouseTouchPoint = nullptr;
881 }
882
883 setKeepMouseGrab(false);
884}
885
886void QQuickMultiPointTouchArea::ungrab(bool normalRelease)
887{
888 _stealMouse = false;
889 setKeepMouseGrab(false);
890 setKeepTouchGrab(false);
891 if (!normalRelease)
892 ungrabTouchPoints();
893
894 if (_touchPoints.size()) {
895 for (QObject *obj : std::as_const(_touchPoints))
896 static_cast<QQuickTouchPoint*>(obj)->setPressed(false);
897 if (!normalRelease)
898 emit canceled(_touchPoints.values());
899 clearTouchLists();
900 for (QObject *obj : std::as_const(_touchPoints)) {
901 QQuickTouchPoint *dtp = static_cast<QQuickTouchPoint*>(obj);
902 if (!dtp->isQmlDefined())
903 delete dtp;
904 else
905 dtp->setInUse(false);
906 }
907 _touchPoints.clear();
908 emit touchUpdated(QList<QObject*>());
909 }
910}
911
912void QQuickMultiPointTouchArea::mouseUngrabEvent()
913{
914 ungrab();
915}
916
917void QQuickMultiPointTouchArea::touchUngrabEvent()
918{
919 ungrab();
920}
921
922bool QQuickMultiPointTouchArea::sendMouseEvent(QMouseEvent *event)
923{
924 const QPointF localPos = mapFromScene(event->scenePosition());
925
926 QQuickWindow *c = window();
927 QQuickItem *grabber = c ? c->mouseGrabberItem() : nullptr;
928 bool stealThisEvent = _stealMouse;
929 if ((stealThisEvent || contains(localPos)) && (!grabber || !grabber->keepMouseGrab())) {
930 QMutableSinglePointEvent mouseEvent(*event);
931 const auto oldPosition = mouseEvent.position();
932 QMutableEventPoint::setPosition(mouseEvent.point(0), localPos);
933 mouseEvent.setSource(Qt::MouseEventSynthesizedByQt);
934 mouseEvent.setAccepted(false);
935 QMouseEvent *pmouseEvent = static_cast<QMouseEvent *>(static_cast<QSinglePointEvent *>(&mouseEvent));
936
937 switch (mouseEvent.type()) {
938 case QEvent::MouseMove:
939 mouseMoveEvent(pmouseEvent);
940 break;
941 case QEvent::MouseButtonPress:
942 mousePressEvent(pmouseEvent);
943 break;
944 case QEvent::MouseButtonRelease:
945 mouseReleaseEvent(pmouseEvent);
946 break;
947 default:
948 break;
949 }
950 grabber = c ? c->mouseGrabberItem() : nullptr;
951 if (grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this)
952 grabMouse();
953
954 QMutableEventPoint::setPosition(mouseEvent.point(0), oldPosition);
955 return stealThisEvent;
956 }
957 if (event->type() == QEvent::MouseButtonRelease) {
958 _stealMouse = false;
959 if (c && c->mouseGrabberItem() == this)
960 ungrabMouse();
961 setKeepMouseGrab(false);
962 }
963 return false;
964}
965
966bool QQuickMultiPointTouchArea::childMouseEventFilter(QQuickItem *receiver, QEvent *event)
967{
968 if (!isEnabled() || !isVisible())
969 return QQuickItem::childMouseEventFilter(receiver, event);
970 switch (event->type()) {
971 case QEvent::MouseButtonPress: {
972 auto da = QQuickItemPrivate::get(this)->deliveryAgentPrivate();
973 // If we already got a chance to filter the touchpoint that generated this synth-mouse-press,
974 // and chose not to filter it, ignore it now, too.
975 if (static_cast<QMouseEvent *>(event)->source() == Qt::MouseEventSynthesizedByQt &&
976 _lastFilterableTouchPointIds.contains(da->touchMouseId))
977 return false;
978 } Q_FALLTHROUGH();
979 case QEvent::MouseMove:
980 case QEvent::MouseButtonRelease:
981 return sendMouseEvent(static_cast<QMouseEvent *>(event));
982 case QEvent::TouchBegin:
983 _lastFilterableTouchPointIds.clear();
984 Q_FALLTHROUGH();
985 case QEvent::TouchUpdate:
986 for (const auto &tp : static_cast<QTouchEvent*>(event)->points()) {
987 if (tp.state() == QEventPoint::State::Pressed)
988 _lastFilterableTouchPointIds << tp.id();
989 }
990 if (!shouldFilter(event))
991 return false;
992 updateTouchData(event, RemapEventPoints::ToLocal);
993 return _stealMouse;
994 case QEvent::TouchEnd: {
995 if (!shouldFilter(event))
996 return false;
997 updateTouchData(event, RemapEventPoints::ToLocal);
998 ungrab(true);
999 }
1000 break;
1001 default:
1002 break;
1003 }
1004 return QQuickItem::childMouseEventFilter(receiver, event);
1005}
1006
1007bool QQuickMultiPointTouchArea::shouldFilter(QEvent *event)
1008{
1009 QQuickWindow *c = window();
1010 QQuickItem *grabber = c ? c->mouseGrabberItem() : nullptr;
1011 bool disabledItem = grabber && !grabber->isEnabled();
1012 bool stealThisEvent = _stealMouse;
1013 bool containsPoint = false;
1014 if (!stealThisEvent) {
1015 switch (event->type()) {
1016 case QEvent::MouseButtonPress:
1017 case QEvent::MouseMove:
1018 case QEvent::MouseButtonRelease: {
1019 QMouseEvent *me = static_cast<QMouseEvent*>(event);
1020 containsPoint = contains(mapFromScene(me->scenePosition()));
1021 }
1022 break;
1023 case QEvent::TouchBegin:
1024 case QEvent::TouchUpdate:
1025 case QEvent::TouchEnd: {
1026 QTouchEvent *te = static_cast<QTouchEvent*>(event);
1027 for (const QEventPoint &point : te->points()) {
1028 if (contains(mapFromScene(point.scenePosition()))) {
1029 containsPoint = true;
1030 break;
1031 }
1032 }
1033 }
1034 break;
1035 default:
1036 break;
1037 }
1038 }
1039 if ((stealThisEvent || containsPoint) && (!grabber || !grabber->keepMouseGrab() || disabledItem)) {
1040 return true;
1041 }
1042 ungrab();
1043 return false;
1044}
1045
1046QSGNode *QQuickMultiPointTouchArea::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
1047{
1048 Q_UNUSED(data);
1049
1050 if (!qmlMptaVisualTouchDebugging())
1051 return nullptr;
1052
1053 QSGInternalRectangleNode *rectangle = static_cast<QSGInternalRectangleNode *>(oldNode);
1054 if (!rectangle) rectangle = QQuickItemPrivate::get(this)->sceneGraphContext()->createInternalRectangleNode();
1055
1056 rectangle->setRect(QRectF(0, 0, width(), height()));
1057 rectangle->setColor(QColor(255, 0, 0, 50));
1058 rectangle->update();
1059 return rectangle;
1060}
1061
1062QT_END_NAMESPACE
1063
1064#include "moc_qquickmultipointtoucharea_p.cpp"
Combined button and popup list for selecting options.