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