/**************************************************************************** ** ** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtQuick module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qquickmultipointtoucharea_p.h" #include #include #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE DEFINE_BOOL_CONFIG_OPTION(qmlVisualTouchDebugging, QML_VISUAL_TOUCH_DEBUGGING) /*! \qmltype TouchPoint \instantiates QQuickTouchPoint \inqmlmodule QtQuick \ingroup qtquick-input-events \brief Describes a touch point in a MultiPointTouchArea. The TouchPoint type contains information about a touch point, such as the current position, pressure, and area. \image touchpoint-metrics.png */ /*! \qmlproperty int QtQuick::TouchPoint::pointId This property holds the point id of the touch point. Each touch point within a MultiPointTouchArea will have a unique id. */ void QQuickTouchPoint::setPointId(int id) { if (_id == id) return; _id = id; emit pointIdChanged(); } /*! \qmlproperty real QtQuick::TouchPoint::x \qmlproperty real QtQuick::TouchPoint::y These properties hold the current position of the touch point. */ void QQuickTouchPoint::setPosition(QPointF p) { bool xch = (_x != p.x()); bool ych = (_y != p.y()); if (!xch && !ych) return; _x = p.x(); _y = p.y(); if (xch) emit xChanged(); if (ych) emit yChanged(); } /*! \qmlproperty size QtQuick::TouchPoint::ellipseDiameters \since 5.9 This property holds the major and minor axes of the ellipse representing the covered area of the touch point. */ void QQuickTouchPoint::setEllipseDiameters(const QSizeF &d) { if (_ellipseDiameters == d) return; _ellipseDiameters = d; emit ellipseDiametersChanged(); } /*! \qmlproperty real QtQuick::TouchPoint::pressure \qmlproperty vector2d QtQuick::TouchPoint::velocity These properties hold additional information about the current state of the touch point. \list \li \c pressure is a value in the range of 0.0 to 1.0. \li \c velocity is a vector with magnitude reported in pixels per second. \endlist Not all touch devices support velocity. If velocity is not supported, it will be reported as 0,0. */ void QQuickTouchPoint::setPressure(qreal pressure) { if (_pressure == pressure) return; _pressure = pressure; emit pressureChanged(); } /*! \qmlproperty real QtQuick::TouchPoint::rotation \since 5.9 This property holds the angular orientation of this touch point. The return value is in degrees, where zero (the default) indicates the finger or token is pointing upwards, a negative angle means it's rotated to the left, and a positive angle means it's rotated to the right. Most touchscreens do not detect rotation, so zero is the most common value. \sa QEventPoint::rotation() */ void QQuickTouchPoint::setRotation(qreal r) { if (_rotation == r) return; _rotation = r; emit rotationChanged(); } void QQuickTouchPoint::setVelocity(const QVector2D &velocity) { if (_velocity == velocity) return; _velocity = velocity; emit velocityChanged(); } /*! \deprecated \qmlproperty rectangle QtQuick::TouchPoint::area A rectangle covering the area of the touch point, centered on the current position of the touch point. It is deprecated because a touch point is more correctly modeled as an ellipse, whereas this rectangle represents the outer bounds of the ellipse after \l rotation. */ void QQuickTouchPoint::setArea(const QRectF &area) { if (_area == area) return; _area = area; emit areaChanged(); } /*! \qmlproperty bool QtQuick::TouchPoint::pressed This property holds whether the touch point is currently pressed. */ void QQuickTouchPoint::setPressed(bool pressed) { if (_pressed == pressed) return; _pressed = pressed; emit pressedChanged(); } /*! \qmlproperty real QtQuick::TouchPoint::startX \qmlproperty real QtQuick::TouchPoint::startY These properties hold the starting position of the touch point. */ void QQuickTouchPoint::setStartX(qreal startX) { if (_startX == startX) return; _startX = startX; emit startXChanged(); } void QQuickTouchPoint::setStartY(qreal startY) { if (_startY == startY) return; _startY = startY; emit startYChanged(); } /*! \qmlproperty real QtQuick::TouchPoint::previousX \qmlproperty real QtQuick::TouchPoint::previousY These properties hold the previous position of the touch point. */ void QQuickTouchPoint::setPreviousX(qreal previousX) { if (_previousX == previousX) return; _previousX = previousX; emit previousXChanged(); } void QQuickTouchPoint::setPreviousY(qreal previousY) { if (_previousY == previousY) return; _previousY = previousY; emit previousYChanged(); } /*! \qmlproperty real QtQuick::TouchPoint::sceneX \qmlproperty real QtQuick::TouchPoint::sceneY These properties hold the current position of the touch point in scene coordinates. */ void QQuickTouchPoint::setSceneX(qreal sceneX) { if (_sceneX == sceneX) return; _sceneX = sceneX; emit sceneXChanged(); } void QQuickTouchPoint::setSceneY(qreal sceneY) { if (_sceneY == sceneY) return; _sceneY = sceneY; emit sceneYChanged(); } /*! \qmlproperty PointingDeviceUniqueId QtQuick::TouchPoint::uniqueId \since 5.9 This property holds the unique ID of the touch point or token. It is normally empty, because touchscreens cannot uniquely identify fingers. But when it is set, it is expected to uniquely identify a specific token (fiducial object). Interpreting the contents of this ID requires knowledge of the hardware and drivers in use (e.g. various TUIO-based touch surfaces). */ void QQuickTouchPoint::setUniqueId(const QPointingDeviceUniqueId &id) { _uniqueId = id; emit uniqueIdChanged(); } /*! \qmltype GestureEvent \instantiates QQuickGrabGestureEvent \inqmlmodule QtQuick \ingroup qtquick-input-events \brief The parameter given with the gestureStarted signal. The GestureEvent object has the current touch points, which you may choose to interpret as a gesture, and an invokable method to grab the involved points exclusively. */ /*! \qmlproperty real QtQuick::GestureEvent::dragThreshold This property holds the system setting for the distance a finger must move before it is interpreted as a drag. It comes from QStyleHints::startDragDistance(). */ /*! \qmlproperty list QtQuick::GestureEvent::touchPoints This property holds the set of current touch points. */ /*! \qmlmethod QtQuick::GestureEvent::grab() Acquires an exclusive grab of the mouse and all the \l touchPoints, and calls \l {QQuickItem::setKeepTouchGrab()}{setKeepTouchGrab()} and \l {QQuickItem::setKeepMouseGrab()}{setKeepMouseGrab()} so that any parent Item that \l {QQuickItem::filtersChildMouseEvents()}{filters} its children's events will not be allowed to take over the grabs. */ /*! \qmltype MultiPointTouchArea \instantiates QQuickMultiPointTouchArea \inqmlmodule QtQuick \inherits Item \ingroup qtquick-input \brief Enables handling of multiple touch points. A MultiPointTouchArea is an invisible item that is used to track multiple touch points. The \l Item::enabled property is used to enable and disable touch handling. When disabled, the touch area becomes transparent to mouse and touch events. By default, the mouse will be handled the same way as a single touch point, and items under the touch area will not receive mouse events because the touch area is handling them. But if the \l mouseEnabled property is set to false, it becomes transparent to mouse events so that another mouse-sensitive Item (such as a MouseArea) can be used to handle mouse interaction separately. MultiPointTouchArea can be used in two ways: \list \li setting \c touchPoints to provide touch point objects with properties that can be bound to \li using the onTouchUpdated or onPressed, onUpdated and onReleased handlers \endlist While a MultiPointTouchArea \e can take exclusive ownership of certain touch points, it is also possible to have multiple MultiPointTouchAreas active at the same time, each operating on a different set of touch points. \sa TouchPoint */ /*! \qmlsignal QtQuick::MultiPointTouchArea::pressed(list touchPoints) This signal is emitted when new touch points are added. \a touchPoints is a list of these new points. If minimumTouchPoints is set to a value greater than one, this signal will not be emitted until the minimum number of required touch points has been reached. */ /*! \qmlsignal QtQuick::MultiPointTouchArea::updated(list touchPoints) This signal is emitted when existing touch points are updated. \a touchPoints is a list of these updated points. */ /*! \qmlsignal QtQuick::MultiPointTouchArea::released(list touchPoints) This signal is emitted when existing touch points are removed. \a touchPoints is a list of these removed points. */ /*! \qmlsignal QtQuick::MultiPointTouchArea::canceled(list touchPoints) This signal is emitted when new touch events have been canceled because another item stole the touch event handling. This signal is for advanced use: it is useful when there is more than one MultiPointTouchArea that is handling input, or when there is a MultiPointTouchArea inside a \l Flickable. In the latter case, if you execute some logic in the \c onPressed signal handler and then start dragging, the \l Flickable may steal the touch handling from the MultiPointTouchArea. In these cases, to reset the logic when the MultiPointTouchArea has lost the touch handling to the \l Flickable, \c canceled should be handled in addition to \l released. \a touchPoints is the list of canceled points. */ /*! \qmlsignal QtQuick::MultiPointTouchArea::gestureStarted(GestureEvent gesture) This signal is emitted when the global drag threshold has been reached. This signal is typically used when a MultiPointTouchArea has been nested in a Flickable or another MultiPointTouchArea. When the threshold has been reached and the signal is handled, you can determine whether or not the touch area should grab the current touch points. By default they will not be grabbed; to grab them call \c gesture.grab(). If the gesture is not grabbed, the nesting Flickable, for example, would also have an opportunity to grab. The \a gesture object also includes information on the current set of \c touchPoints and the \c dragThreshold. */ /*! \qmlsignal QtQuick::MultiPointTouchArea::touchUpdated(list touchPoints) This signal is emitted when the touch points handled by the MultiPointTouchArea change. This includes adding new touch points, removing or canceling previous touch points, as well as updating current touch point data. \a touchPoints is the list of all current touch points. */ /*! \qmlproperty list QtQuick::MultiPointTouchArea::touchPoints This property holds a set of user-defined touch point objects that can be bound to. If mouseEnabled is true (the default) and the left mouse button is pressed while the mouse is over the touch area, the current mouse position will be one of these touch points. In the following example, we have two small rectangles that follow our touch points. \snippet qml/multipointtoucharea/multipointtoucharea.qml 0 By default this property holds an empty list. \sa TouchPoint */ QQuickMultiPointTouchArea::QQuickMultiPointTouchArea(QQuickItem *parent) : QQuickItem(parent), _minimumTouchPoints(0), _maximumTouchPoints(INT_MAX), _touchMouseDevice(nullptr), _stealMouse(false), _mouseEnabled(true) { setAcceptedMouseButtons(Qt::LeftButton); setFiltersChildMouseEvents(true); if (qmlVisualTouchDebugging()) { setFlag(QQuickItem::ItemHasContents); } setAcceptTouchEvents(true); #ifdef Q_OS_OSX setAcceptHoverEvents(true); // needed to enable touch events on mouse hover. #endif } QQuickMultiPointTouchArea::~QQuickMultiPointTouchArea() { clearTouchLists(); for (QObject *obj : qAsConst(_touchPoints)) { QQuickTouchPoint *dtp = static_cast(obj); if (!dtp->isQmlDefined()) delete dtp; } } /*! \qmlproperty int QtQuick::MultiPointTouchArea::minimumTouchPoints \qmlproperty int QtQuick::MultiPointTouchArea::maximumTouchPoints These properties hold the range of touch points to be handled by the touch area. These are convenience that allow you to, for example, have nested MultiPointTouchAreas, one handling two finger touches, and another handling three finger touches. By default, all touch points within the touch area are handled. If mouseEnabled is true, the mouse acts as a touch point, so it is also subject to these constraints: for example if maximumTouchPoints is two, you can use the mouse as one touch point and a finger as another touch point for a total of two. */ int QQuickMultiPointTouchArea::minimumTouchPoints() const { return _minimumTouchPoints; } void QQuickMultiPointTouchArea::setMinimumTouchPoints(int num) { if (_minimumTouchPoints == num) return; _minimumTouchPoints = num; emit minimumTouchPointsChanged(); } int QQuickMultiPointTouchArea::maximumTouchPoints() const { return _maximumTouchPoints; } void QQuickMultiPointTouchArea::setMaximumTouchPoints(int num) { if (_maximumTouchPoints == num) return; _maximumTouchPoints = num; emit maximumTouchPointsChanged(); } /*! \qmlproperty bool QtQuick::MultiPointTouchArea::mouseEnabled This property controls whether the MultiPointTouchArea will handle mouse events too. If it is true (the default), the touch area will treat the mouse the same as a single touch point; if it is false, the touch area will ignore mouse events and allow them to "pass through" so that they can be handled by other items underneath. */ void QQuickMultiPointTouchArea::setMouseEnabled(bool arg) { if (_mouseEnabled != arg) { _mouseEnabled = arg; if (_mouseTouchPoint && !arg) _mouseTouchPoint = nullptr; emit mouseEnabledChanged(); } } void QQuickMultiPointTouchArea::touchEvent(QTouchEvent *event) { switch (event->type()) { case QEvent::TouchBegin: case QEvent::TouchUpdate: case QEvent::TouchEnd: { //if e.g. a parent Flickable has the mouse grab, don't process the touch events QQuickWindow *c = window(); QQuickItem *grabber = c ? c->mouseGrabberItem() : nullptr; if (grabber && grabber != this && grabber->keepMouseGrab() && grabber->isEnabled()) { QQuickItem *item = this; while ((item = item->parentItem())) { if (item == grabber) return; } } updateTouchData(event); if (event->type() == QEvent::TouchEnd) ungrab(true); break; } case QEvent::TouchCancel: ungrab(); break; default: QQuickItem::touchEvent(event); break; } } void QQuickMultiPointTouchArea::grabGesture() { _stealMouse = true; grabMouse(); setKeepMouseGrab(true); QList ids; ids.reserve(_touchPoints.size()); for (auto it = _touchPoints.keyBegin(), end = _touchPoints.keyEnd(); it != end; ++it) { if (*it != -1) // -1 might be the mouse-point, but we already grabbed the mouse above. ids.append(*it); } grabTouchPoints(ids); setKeepTouchGrab(true); } void QQuickMultiPointTouchArea::updateTouchData(QEvent *event) { bool ended = false; bool moved = false; bool started = false; clearTouchLists(); QList touchPoints; QQuickWindowPrivate *windowPriv = QQuickWindowPrivate::get(window()); switch (event->type()) { case QEvent::TouchBegin: case QEvent::TouchUpdate: case QEvent::TouchEnd: touchPoints = static_cast(event)->touchPoints(); break; case QEvent::MouseButtonPress: _mouseQpaTouchPoint = QEventPoint(windowPriv->touchMouseId); _touchMouseDevice = windowPriv->touchMouseDevice; Q_FALLTHROUGH(); case QEvent::MouseMove: case QEvent::MouseButtonRelease: { QMouseEvent *me = static_cast(event); _mouseQpaTouchPoint = me->point(0); if (event->type() == QEvent::MouseButtonPress) { addTouchPoint(me); started = true; } touchPoints << _mouseQpaTouchPoint; break; } default: qWarning("updateTouchData: unhandled event type %d", event->type()); break; } int numTouchPoints = touchPoints.count(); //always remove released touches, and make sure we handle all releases before adds. for (const QEventPoint &p : qAsConst(touchPoints)) { QEventPoint::State touchPointState = p.state(); int id = p.id(); if (touchPointState & QEventPoint::State::Released) { QQuickTouchPoint* dtp = static_cast(_touchPoints.value(id)); if (!dtp) continue; updateTouchPoint(dtp, &p); dtp->setPressed(false); _releasedTouchPoints.append(dtp); _touchPoints.remove(id); ended = true; } } if (numTouchPoints >= _minimumTouchPoints && numTouchPoints <= _maximumTouchPoints) { for (const QEventPoint &p : qAsConst(touchPoints)) { QEventPoint::State touchPointState = p.state(); int id = p.id(); if (touchPointState & QEventPoint::State::Released) { //handled above } else if (!_touchPoints.contains(id)) { //could be pressed, moved, or stationary // (we may have just obtained enough points to start tracking them -- in that case moved or stationary count as newly pressed) addTouchPoint(&p); started = true; } else if ((touchPointState & QEventPoint::State::Updated) || QMutableEventPoint::from(const_cast(p)).stationaryWithModifiedProperty()) { // React to a stationary point with a property change (velocity, pressure) as if the point moved. (QTBUG-77142) QQuickTouchPoint* dtp = static_cast(_touchPoints.value(id)); Q_ASSERT(dtp); _movedTouchPoints.append(dtp); updateTouchPoint(dtp,&p); moved = true; } else { QQuickTouchPoint* dtp = static_cast(_touchPoints.value(id)); Q_ASSERT(dtp); updateTouchPoint(dtp,&p); } } //see if we should be grabbing the gesture if (!_stealMouse /* !ignoring gesture*/) { bool offerGrab = false; const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); for (const QEventPoint &p : qAsConst(touchPoints)) { if (p.state() == QEventPoint::State::Released) continue; const QPointF ¤tPos = p.scenePosition(); const QPointF &startPos = p.scenePressPosition(); if (qAbs(currentPos.x() - startPos.x()) > dragThreshold) offerGrab = true; else if (qAbs(currentPos.y() - startPos.y()) > dragThreshold) offerGrab = true; if (offerGrab) break; } if (offerGrab) { QQuickGrabGestureEvent event; event._touchPoints = _touchPoints.values(); emit gestureStarted(&event); if (event.wantsGrab()) grabGesture(); } } if (ended) emit released(_releasedTouchPoints); if (moved) emit updated(_movedTouchPoints); if (started) emit pressed(_pressedTouchPoints); if (ended || moved || started) emit touchUpdated(_touchPoints.values()); } } void QQuickMultiPointTouchArea::clearTouchLists() { for (QObject *obj : qAsConst(_releasedTouchPoints)) { QQuickTouchPoint *dtp = static_cast(obj); if (!dtp->isQmlDefined()) { _touchPoints.remove(dtp->pointId()); delete dtp; } else { dtp->setInUse(false); } } _releasedTouchPoints.clear(); _pressedTouchPoints.clear(); _movedTouchPoints.clear(); } void QQuickMultiPointTouchArea::addTouchPoint(const QEventPoint *p) { QQuickTouchPoint *dtp = nullptr; for (QQuickTouchPoint* tp : qAsConst(_touchPrototypes)) { if (!tp->inUse()) { tp->setInUse(true); dtp = tp; break; } } if (dtp == nullptr) dtp = new QQuickTouchPoint(false); dtp->setPointId(p->id()); updateTouchPoint(dtp,p); dtp->setPressed(true); _touchPoints.insert(p->id(),dtp); _pressedTouchPoints.append(dtp); } void QQuickMultiPointTouchArea::addTouchPoint(const QMouseEvent *e) { QQuickTouchPoint *dtp = nullptr; for (QQuickTouchPoint *tp : qAsConst(_touchPrototypes)) if (!tp->inUse()) { tp->setInUse(true); dtp = tp; break; } if (dtp == nullptr) dtp = new QQuickTouchPoint(false); updateTouchPoint(dtp, e); dtp->setPressed(true); _touchPoints.insert(_mouseQpaTouchPoint.id(), dtp); _pressedTouchPoints.append(dtp); _mouseTouchPoint = dtp; } #ifdef Q_OS_OSX void QQuickMultiPointTouchArea::hoverEnterEvent(QHoverEvent *event) { Q_UNUSED(event); setTouchEventsEnabled(true); } void QQuickMultiPointTouchArea::hoverLeaveEvent(QHoverEvent *event) { Q_UNUSED(event); setTouchEventsEnabled(false); } void QQuickMultiPointTouchArea::setTouchEventsEnabled(bool enable) { // Resolve function for enabling touch events from the (cocoa) platform plugin. typedef void (*RegisterTouchWindowFunction)(QWindow *, bool); RegisterTouchWindowFunction registerTouchWindow = reinterpret_cast( QGuiApplication::platformNativeInterface()->nativeResourceFunctionForIntegration("registertouchwindow")); if (!registerTouchWindow) return; // Not necessarily an error, Qt might be using a different platform plugin. registerTouchWindow(window(), enable); } #endif // Q_OS_OSX void QQuickMultiPointTouchArea::addTouchPrototype(QQuickTouchPoint *prototype) { int id = _touchPrototypes.count(); prototype->setPointId(id); _touchPrototypes.insert(id, prototype); } void QQuickMultiPointTouchArea::updateTouchPoint(QQuickTouchPoint *dtp, const QEventPoint *p) { //TODO: if !qmlDefined, could bypass setters. // also, should only emit signals after all values have been set dtp->setUniqueId(p->uniqueId()); dtp->setPosition(p->position()); dtp->setEllipseDiameters(p->ellipseDiameters()); dtp->setPressure(p->pressure()); dtp->setRotation(p->rotation()); dtp->setVelocity(p->velocity()); QRectF area(QPointF(), p->ellipseDiameters()); area.moveCenter(p->position()); dtp->setArea(area); dtp->setStartX(p->pressPosition().x()); dtp->setStartY(p->pressPosition().y()); dtp->setPreviousX(p->lastPosition().x()); dtp->setPreviousY(p->lastPosition().y()); dtp->setSceneX(p->scenePosition().x()); dtp->setSceneY(p->scenePosition().y()); } void QQuickMultiPointTouchArea::updateTouchPoint(QQuickTouchPoint *dtp, const QMouseEvent *e) { dtp->setPreviousX(dtp->x()); dtp->setPreviousY(dtp->y()); dtp->setPosition(e->position()); if (e->type() == QEvent::MouseButtonPress) { dtp->setStartX(e->position().x()); dtp->setStartY(e->position().y()); } dtp->setSceneX(e->scenePosition().x()); dtp->setSceneY(e->scenePosition().y()); } void QQuickMultiPointTouchArea::mousePressEvent(QMouseEvent *event) { if (!isEnabled() || !_mouseEnabled || event->button() != Qt::LeftButton) { QQuickItem::mousePressEvent(event); return; } _stealMouse = false; setKeepMouseGrab(false); event->setAccepted(true); _mousePos = event->position(); if (event->source() != Qt::MouseEventNotSynthesized && event->source() != Qt::MouseEventSynthesizedByQt) return; if (_touchPoints.count() >= _minimumTouchPoints - 1 && _touchPoints.count() < _maximumTouchPoints) { updateTouchData(event); } } void QQuickMultiPointTouchArea::mouseMoveEvent(QMouseEvent *event) { if (!isEnabled() || !_mouseEnabled) { QQuickItem::mouseMoveEvent(event); return; } if (event->source() != Qt::MouseEventNotSynthesized && event->source() != Qt::MouseEventSynthesizedByQt) return; _movedTouchPoints.clear(); updateTouchData(event); } void QQuickMultiPointTouchArea::mouseReleaseEvent(QMouseEvent *event) { _stealMouse = false; if (!isEnabled() || !_mouseEnabled) { QQuickItem::mouseReleaseEvent(event); return; } if (event->source() != Qt::MouseEventNotSynthesized && event->source() != Qt::MouseEventSynthesizedByQt) return; if (_mouseTouchPoint) { updateTouchData(event); _mouseTouchPoint->setInUse(false); _releasedTouchPoints.removeAll(_mouseTouchPoint); _mouseTouchPoint = nullptr; } setKeepMouseGrab(false); } void QQuickMultiPointTouchArea::ungrab(bool normalRelease) { _stealMouse = false; setKeepMouseGrab(false); setKeepTouchGrab(false); if (!normalRelease) ungrabTouchPoints(); if (_touchPoints.count()) { for (QObject *obj : qAsConst(_touchPoints)) static_cast(obj)->setPressed(false); if (!normalRelease) emit canceled(_touchPoints.values()); clearTouchLists(); for (QObject *obj : qAsConst(_touchPoints)) { QQuickTouchPoint *dtp = static_cast(obj); if (!dtp->isQmlDefined()) delete dtp; else dtp->setInUse(false); } _touchPoints.clear(); emit touchUpdated(QList()); } } void QQuickMultiPointTouchArea::mouseUngrabEvent() { ungrab(); } void QQuickMultiPointTouchArea::touchUngrabEvent() { ungrab(); } bool QQuickMultiPointTouchArea::sendMouseEvent(QMouseEvent *event) { QPointF localPos = mapFromScene(event->scenePosition()); QQuickWindow *c = window(); QQuickItem *grabber = c ? c->mouseGrabberItem() : nullptr; bool stealThisEvent = _stealMouse; if ((stealThisEvent || contains(localPos)) && (!grabber || !grabber->keepMouseGrab())) { QMouseEvent mouseEvent = *event; auto mut = QMutableSinglePointEvent::from(&mouseEvent); mut->mutablePoint().setPosition(localPos); mut->setSource(Qt::MouseEventSynthesizedByQt); mouseEvent.setAccepted(false); switch (mouseEvent.type()) { case QEvent::MouseMove: mouseMoveEvent(&mouseEvent); break; case QEvent::MouseButtonPress: mousePressEvent(&mouseEvent); break; case QEvent::MouseButtonRelease: mouseReleaseEvent(&mouseEvent); break; default: break; } grabber = c ? c->mouseGrabberItem() : nullptr; if (grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this) grabMouse(); return stealThisEvent; } if (event->type() == QEvent::MouseButtonRelease) { _stealMouse = false; if (c && c->mouseGrabberItem() == this) ungrabMouse(); setKeepMouseGrab(false); } return false; } bool QQuickMultiPointTouchArea::childMouseEventFilter(QQuickItem *receiver, QEvent *event) { if (!isEnabled() || !isVisible()) return QQuickItem::childMouseEventFilter(receiver, event); switch (event->type()) { case QEvent::MouseButtonPress: { QQuickWindowPrivate *windowPriv = QQuickWindowPrivate::get(window()); // If we already got a chance to filter the touchpoint that generated this synth-mouse-press, // and chose not to filter it, ignore it now, too. if (static_cast(event)->source() == Qt::MouseEventSynthesizedByQt && _lastFilterableTouchPointIds.contains(windowPriv->touchMouseId)) return false; } Q_FALLTHROUGH(); case QEvent::MouseMove: case QEvent::MouseButtonRelease: return sendMouseEvent(static_cast(event)); case QEvent::TouchBegin: _lastFilterableTouchPointIds.clear(); Q_FALLTHROUGH(); case QEvent::TouchUpdate: for (auto tp : static_cast(event)->touchPoints()) { if (tp.state() == QEventPoint::State::Pressed) _lastFilterableTouchPointIds << tp.id(); } if (!shouldFilter(event)) return false; updateTouchData(event); return _stealMouse; case QEvent::TouchEnd: { if (!shouldFilter(event)) return false; updateTouchData(event); ungrab(true); } break; default: break; } return QQuickItem::childMouseEventFilter(receiver, event); } bool QQuickMultiPointTouchArea::shouldFilter(QEvent *event) { QQuickWindow *c = window(); QQuickItem *grabber = c ? c->mouseGrabberItem() : nullptr; bool disabledItem = grabber && !grabber->isEnabled(); bool stealThisEvent = _stealMouse; bool containsPoint = false; if (!stealThisEvent) { switch (event->type()) { case QEvent::MouseButtonPress: case QEvent::MouseMove: case QEvent::MouseButtonRelease: { QMouseEvent *me = static_cast(event); containsPoint = contains(mapFromScene(me->scenePosition())); } break; case QEvent::TouchBegin: case QEvent::TouchUpdate: case QEvent::TouchEnd: { QTouchEvent *te = static_cast(event); for (const QEventPoint &point : te->touchPoints()) { if (contains(mapFromScene(point.scenePosition()))) { containsPoint = true; break; } } } break; default: break; } } if ((stealThisEvent || containsPoint) && (!grabber || !grabber->keepMouseGrab() || disabledItem)) { return true; } ungrab(); return false; } QSGNode *QQuickMultiPointTouchArea::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) { Q_UNUSED(data); if (!qmlVisualTouchDebugging()) return nullptr; QSGInternalRectangleNode *rectangle = static_cast(oldNode); if (!rectangle) rectangle = QQuickItemPrivate::get(this)->sceneGraphContext()->createInternalRectangleNode(); rectangle->setRect(QRectF(0, 0, width(), height())); rectangle->setColor(QColor(255, 0, 0, 50)); rectangle->update(); return rectangle; } QT_END_NAMESPACE #include "moc_qquickmultipointtoucharea_p.cpp"