| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 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 <QtQuick/qquickwindow.h> |
| #include <private/qsgadaptationlayer_p.h> |
| #include <private/qevent_p.h> |
| #include <private/qquickitem_p.h> |
| #include <private/qquickwindow_p.h> |
| #include <private/qguiapplication_p.h> |
| #include <QEvent> |
| #include <QMouseEvent> |
| #include <QDebug> |
| #include <qpa/qplatformnativeinterface.h> |
| |
| 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::setX(qreal x) |
| { |
| if (_x == x) |
| return; |
| _x = x; |
| emit xChanged(); |
| } |
| |
| void QQuickTouchPoint::setY(qreal y) |
| { |
| if (_y == y) |
| return; |
| _y = y; |
| 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 QTouchEvent::TouchPoint::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<TouchPoint> 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<TouchPoint> 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. |
| |
| The corresponding handler is \c onPressed. |
| */ |
| |
| /*! |
| \qmlsignal QtQuick::MultiPointTouchArea::updated(list<TouchPoint> touchPoints) |
| |
| This signal is emitted when existing touch points are updated. \a touchPoints is a list of these updated points. |
| |
| The corresponding handler is \c onUpdated. |
| */ |
| |
| /*! |
| \qmlsignal QtQuick::MultiPointTouchArea::released(list<TouchPoint> touchPoints) |
| |
| This signal is emitted when existing touch points are removed. \a touchPoints is a list of these removed points. |
| |
| The corresponding handler is \c onReleased. |
| */ |
| |
| /*! |
| \qmlsignal QtQuick::MultiPointTouchArea::canceled(list<TouchPoint> 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. |
| |
| The corresponding handler is \c onCanceled. |
| */ |
| |
| /*! |
| \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. |
| |
| The corresponding handler is \c onGestureStarted. |
| */ |
| |
| /*! |
| \qmlsignal QtQuick::MultiPointTouchArea::touchUpdated(list<TouchPoint> 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. |
| |
| The corresponding handler is \c onTouchUpdated. |
| */ |
| |
| /*! |
| \qmlproperty list<TouchPoint> 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<QQuickTouchPoint*>(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(); |
| break; |
| } |
| case QEvent::TouchCancel: |
| ungrab(); |
| break; |
| default: |
| QQuickItem::touchEvent(event); |
| break; |
| } |
| } |
| |
| void QQuickMultiPointTouchArea::grabGesture() |
| { |
| _stealMouse = true; |
| |
| grabMouse(); |
| setKeepMouseGrab(true); |
| |
| QVector<int> 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<QTouchEvent::TouchPoint> touchPoints; |
| QQuickWindowPrivate *windowPriv = QQuickWindowPrivate::get(window()); |
| |
| switch (event->type()) { |
| case QEvent::TouchBegin: |
| case QEvent::TouchUpdate: |
| case QEvent::TouchEnd: |
| touchPoints = static_cast<QTouchEvent*>(event)->touchPoints(); |
| break; |
| case QEvent::MouseButtonPress: |
| _mouseQpaTouchPoint = QTouchEvent::TouchPoint(windowPriv->touchMouseId); |
| _touchMouseDevice = windowPriv->touchMouseDevice->qTouchDevice(); |
| Q_FALLTHROUGH(); |
| case QEvent::MouseMove: |
| case QEvent::MouseButtonRelease: { |
| QMouseEvent *me = static_cast<QMouseEvent*>(event); |
| _mouseQpaTouchPoint.setPos(me->localPos()); |
| _mouseQpaTouchPoint.setScenePos(me->windowPos()); |
| _mouseQpaTouchPoint.setScreenPos(me->screenPos()); |
| if (event->type() == QEvent::MouseMove) |
| _mouseQpaTouchPoint.setState(Qt::TouchPointMoved); |
| else if (event->type() == QEvent::MouseButtonRelease) |
| _mouseQpaTouchPoint.setState(Qt::TouchPointReleased); |
| else { // QEvent::MouseButtonPress |
| addTouchPoint(me); |
| started = true; |
| _mouseQpaTouchPoint.setStartPos(me->localPos()); |
| _mouseQpaTouchPoint.setStartScenePos(me->windowPos()); |
| _mouseQpaTouchPoint.setStartScreenPos(me->screenPos()); |
| _mouseQpaTouchPoint.setState(Qt::TouchPointPressed); |
| } |
| 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 QTouchEvent::TouchPoint &p : qAsConst(touchPoints)) { |
| Qt::TouchPointState touchPointState = p.state(); |
| int id = p.id(); |
| if (touchPointState & Qt::TouchPointReleased) { |
| QQuickTouchPoint* dtp = static_cast<QQuickTouchPoint*>(_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 QTouchEvent::TouchPoint &p : qAsConst(touchPoints)) { |
| Qt::TouchPointState touchPointState = p.state(); |
| int id = p.id(); |
| if (touchPointState & Qt::TouchPointReleased) { |
| //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 & Qt::TouchPointMoved) || p.d->stationaryWithModifiedProperty) { |
| // React to a stationary point with a property change (velocity, pressure) as if the point moved. (QTBUG-77142) |
| QQuickTouchPoint* dtp = static_cast<QQuickTouchPoint*>(_touchPoints.value(id)); |
| Q_ASSERT(dtp); |
| _movedTouchPoints.append(dtp); |
| updateTouchPoint(dtp,&p); |
| moved = true; |
| } else { |
| QQuickTouchPoint* dtp = static_cast<QQuickTouchPoint*>(_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 QTouchEvent::TouchPoint &p : qAsConst(touchPoints)) { |
| if (p.state() == Qt::TouchPointReleased) |
| continue; |
| const QPointF ¤tPos = p.scenePos(); |
| const QPointF &startPos = p.startScenePos(); |
| 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<QQuickTouchPoint*>(obj); |
| if (!dtp->isQmlDefined()) { |
| _touchPoints.remove(dtp->pointId()); |
| delete dtp; |
| } else { |
| dtp->setInUse(false); |
| } |
| } |
| _releasedTouchPoints.clear(); |
| _pressedTouchPoints.clear(); |
| _movedTouchPoints.clear(); |
| } |
| |
| void QQuickMultiPointTouchArea::addTouchPoint(const QTouchEvent::TouchPoint *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(_touchMouseDevice && _mouseQpaTouchPoint.id() > 0 ? _mouseQpaTouchPoint.id() : -1, 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<RegisterTouchWindowFunction>( |
| 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 QTouchEvent::TouchPoint *p) |
| { |
| //TODO: if !qmlDefined, could bypass setters. |
| // also, should only emit signals after all values have been set |
| dtp->setUniqueId(p->uniqueId()); |
| dtp->setX(p->pos().x()); |
| dtp->setY(p->pos().y()); |
| dtp->setEllipseDiameters(p->ellipseDiameters()); |
| dtp->setPressure(p->pressure()); |
| dtp->setRotation(p->rotation()); |
| dtp->setVelocity(p->velocity()); |
| dtp->setArea(p->rect()); |
| dtp->setStartX(p->startPos().x()); |
| dtp->setStartY(p->startPos().y()); |
| dtp->setPreviousX(p->lastPos().x()); |
| dtp->setPreviousY(p->lastPos().y()); |
| dtp->setSceneX(p->scenePos().x()); |
| dtp->setSceneY(p->scenePos().y()); |
| } |
| |
| void QQuickMultiPointTouchArea::updateTouchPoint(QQuickTouchPoint *dtp, const QMouseEvent *e) |
| { |
| dtp->setPreviousX(dtp->x()); |
| dtp->setPreviousY(dtp->y()); |
| dtp->setX(e->localPos().x()); |
| dtp->setY(e->localPos().y()); |
| if (e->type() == QEvent::MouseButtonPress) { |
| dtp->setStartX(e->localPos().x()); |
| dtp->setStartY(e->localPos().y()); |
| } |
| dtp->setSceneX(e->windowPos().x()); |
| dtp->setSceneY(e->windowPos().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->localPos(); |
| 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<QQuickTouchPoint*>(obj)->setPressed(false); |
| emit canceled(_touchPoints.values()); |
| clearTouchLists(); |
| for (QObject *obj : qAsConst(_touchPoints)) { |
| QQuickTouchPoint *dtp = static_cast<QQuickTouchPoint*>(obj); |
| if (!dtp->isQmlDefined()) |
| delete dtp; |
| else |
| dtp->setInUse(false); |
| } |
| _touchPoints.clear(); |
| emit touchUpdated(QList<QObject*>()); |
| } |
| } |
| |
| void QQuickMultiPointTouchArea::mouseUngrabEvent() |
| { |
| ungrab(); |
| } |
| |
| void QQuickMultiPointTouchArea::touchUngrabEvent() |
| { |
| ungrab(); |
| } |
| |
| bool QQuickMultiPointTouchArea::sendMouseEvent(QMouseEvent *event) |
| { |
| QPointF localPos = mapFromScene(event->windowPos()); |
| |
| QQuickWindow *c = window(); |
| QQuickItem *grabber = c ? c->mouseGrabberItem() : nullptr; |
| bool stealThisEvent = _stealMouse; |
| if ((stealThisEvent || contains(localPos)) && (!grabber || !grabber->keepMouseGrab())) { |
| QMouseEvent mouseEvent(event->type(), localPos, event->windowPos(), event->screenPos(), |
| event->button(), event->buttons(), event->modifiers()); |
| mouseEvent.setAccepted(false); |
| QGuiApplicationPrivate::setMouseEventCapsAndVelocity(&mouseEvent, |
| QGuiApplicationPrivate::mouseEventCaps(event), |
| QGuiApplicationPrivate::mouseEventVelocity(event)); |
| QGuiApplicationPrivate::setMouseEventSource(&mouseEvent, Qt::MouseEventSynthesizedByQt); |
| |
| 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<QMouseEvent *>(event)->source() == Qt::MouseEventSynthesizedByQt && |
| _lastFilterableTouchPointIds.contains(windowPriv->touchMouseId)) |
| return false; |
| } Q_FALLTHROUGH(); |
| case QEvent::MouseMove: |
| case QEvent::MouseButtonRelease: |
| return sendMouseEvent(static_cast<QMouseEvent *>(event)); |
| case QEvent::TouchBegin: |
| _lastFilterableTouchPointIds.clear(); |
| Q_FALLTHROUGH(); |
| case QEvent::TouchUpdate: |
| for (auto tp : static_cast<QTouchEvent*>(event)->touchPoints()) { |
| if (tp.state() == Qt::TouchPointPressed) |
| _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<QMouseEvent*>(event); |
| containsPoint = contains(mapFromScene(me->windowPos())); |
| } |
| break; |
| case QEvent::TouchBegin: |
| case QEvent::TouchUpdate: |
| case QEvent::TouchEnd: { |
| QTouchEvent *te = static_cast<QTouchEvent*>(event); |
| for (const QTouchEvent::TouchPoint &point : te->touchPoints()) { |
| if (contains(mapFromScene(point.scenePos()))) { |
| 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<QSGInternalRectangleNode *>(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" |