| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtSG 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 "qquickpincharea_p_p.h" |
| #include "qquickwindow.h" |
| |
| #include <QtCore/qmath.h> |
| #include <QtGui/qevent.h> |
| #include <QtGui/qguiapplication.h> |
| #include <QtGui/qstylehints.h> |
| #include <qpa/qplatformintegration.h> |
| #include <qpa/qplatformnativeinterface.h> |
| #include <private/qguiapplication_p.h> |
| #include <QVariant> |
| |
| #include <float.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| /*! |
| \qmltype PinchEvent |
| \instantiates QQuickPinchEvent |
| \inqmlmodule QtQuick |
| \ingroup qtquick-input-events |
| \brief For specifying information about a pinch event. |
| |
| \b {The PinchEvent type was added in QtQuick 1.1} |
| |
| The \c center, \c startCenter, \c previousCenter properties provide the center position between the two touch points. |
| |
| The \c scale and \c previousScale properties provide the scale factor. |
| |
| The \c angle, \c previousAngle and \c rotation properties provide the angle between the two points and the amount of rotation. |
| |
| The \c point1, \c point2, \c startPoint1, \c startPoint2 properties provide the positions of the touch points. |
| |
| The \c accepted property may be set to false in the \c onPinchStarted handler if the gesture should not |
| be handled. |
| |
| \sa PinchArea |
| */ |
| |
| /*! |
| \qmlproperty QPointF QtQuick::PinchEvent::center |
| \qmlproperty QPointF QtQuick::PinchEvent::startCenter |
| \qmlproperty QPointF QtQuick::PinchEvent::previousCenter |
| |
| These properties hold the position of the center point between the two touch points. |
| |
| \list |
| \li \c center is the current center point |
| \li \c previousCenter is the center point of the previous event. |
| \li \c startCenter is the center point when the gesture began |
| \endlist |
| */ |
| |
| /*! |
| \qmlproperty real QtQuick::PinchEvent::scale |
| \qmlproperty real QtQuick::PinchEvent::previousScale |
| |
| These properties hold the scale factor determined by the change in distance between the two touch points. |
| |
| \list |
| \li \c scale is the current scale factor. |
| \li \c previousScale is the scale factor of the previous event. |
| \endlist |
| |
| When a pinch gesture is started, the scale is \c 1.0. |
| */ |
| |
| /*! |
| \qmlproperty real QtQuick::PinchEvent::angle |
| \qmlproperty real QtQuick::PinchEvent::previousAngle |
| \qmlproperty real QtQuick::PinchEvent::rotation |
| |
| These properties hold the angle between the two touch points. |
| |
| \list |
| \li \c angle is the current angle between the two points in the range -180 to 180. |
| \li \c previousAngle is the angle of the previous event. |
| \li \c rotation is the total rotation since the pinch gesture started. |
| \endlist |
| |
| When a pinch gesture is started, the rotation is \c 0.0. |
| */ |
| |
| /*! |
| \qmlproperty QPointF QtQuick::PinchEvent::point1 |
| \qmlproperty QPointF QtQuick::PinchEvent::startPoint1 |
| \qmlproperty QPointF QtQuick::PinchEvent::point2 |
| \qmlproperty QPointF QtQuick::PinchEvent::startPoint2 |
| |
| These properties provide the actual touch points generating the pinch. |
| |
| \list |
| \li \c point1 and \c point2 hold the current positions of the points. |
| \li \c startPoint1 and \c startPoint2 hold the positions of the points when the second point was touched. |
| \endlist |
| */ |
| |
| /*! |
| \qmlproperty bool QtQuick::PinchEvent::accepted |
| |
| Setting this property to false in the \c PinchArea::onPinchStarted handler |
| will result in no further pinch events being generated, and the gesture |
| ignored. |
| */ |
| |
| /*! |
| \qmlproperty int QtQuick::PinchEvent::pointCount |
| |
| Holds the number of points currently touched. The PinchArea will not react |
| until two touch points have initited a gesture, but will remain active until |
| all touch points have been released. |
| */ |
| |
| QQuickPinch::QQuickPinch() |
| : m_target(nullptr), m_minScale(1.0), m_maxScale(1.0) |
| , m_minRotation(0.0), m_maxRotation(0.0) |
| , m_axis(NoDrag), m_xmin(-FLT_MAX), m_xmax(FLT_MAX) |
| , m_ymin(-FLT_MAX), m_ymax(FLT_MAX), m_active(false) |
| { |
| } |
| |
| QQuickPinchAreaPrivate::~QQuickPinchAreaPrivate() |
| { |
| delete pinch; |
| } |
| |
| /*! |
| \qmltype PinchArea |
| \instantiates QQuickPinchArea |
| \inqmlmodule QtQuick |
| \ingroup qtquick-input |
| \inherits Item |
| \brief Enables simple pinch gesture handling. |
| |
| \b {The PinchArea type was added in QtQuick 1.1} |
| |
| A PinchArea is an invisible item that is typically used in conjunction with |
| a visible item in order to provide pinch gesture handling for that item. |
| |
| The \l enabled property is used to enable and disable pinch handling for |
| the proxied item. When disabled, the pinch area becomes transparent to |
| mouse/touch events. |
| |
| PinchArea can be used in two ways: |
| |
| \list |
| \li setting a \c pinch.target to provide automatic interaction with an item |
| \li using the onPinchStarted, onPinchUpdated and onPinchFinished handlers |
| \endlist |
| |
| Since Qt 5.5, PinchArea can react to native pinch gesture events from the |
| operating system if available; otherwise it reacts only to touch events. |
| |
| \sa PinchEvent, QNativeGestureEvent, QTouchEvent |
| */ |
| |
| /*! |
| \qmlsignal QtQuick::PinchArea::pinchStarted(PinchEvent pinch) |
| |
| This signal is emitted when the pinch area detects that a pinch gesture has |
| started: two touch points (fingers) have been detected, and they have moved |
| beyond the \l {QStyleHints}{startDragDistance} threshold for the gesture to begin. |
| |
| The \l {PinchEvent}{pinch} parameter (not the same as the \l {PinchArea}{pinch} |
| property) provides information about the pinch gesture, including the scale, |
| center and angle of the pinch. At the time of the \c pinchStarted signal, |
| these values are reset to the default values, regardless of the results |
| from previous gestures: pinch.scale will be \c 1.0 and pinch.rotation will be \c 0.0. |
| As the gesture progresses, \l pinchUpdated will report the deviation from those |
| defaults. |
| |
| To ignore this gesture set the \c pinch.accepted property to false. The gesture |
| will be canceled and no further events will be sent. |
| */ |
| |
| /*! |
| \qmlsignal QtQuick::PinchArea::pinchUpdated(PinchEvent pinch) |
| |
| This signal is emitted when the pinch area detects that a pinch gesture has changed. |
| |
| The \l {PinchEvent}{pinch} parameter provides information about the pinch |
| gesture, including the scale, center and angle of the pinch. These values |
| reflect changes only since the beginning of the current gesture, and |
| therefore are not limited by the minimum and maximum limits in the |
| \l {PinchArea}{pinch} property. |
| */ |
| |
| /*! |
| \qmlsignal QtQuick::PinchArea::pinchFinished(PinchEvent pinch) |
| |
| This signal is emitted when the pinch area detects that a pinch gesture has finished. |
| |
| The \l {PinchEvent}{pinch} parameter (not the same as the \l {PinchArea}{pinch} |
| property) provides information about the pinch gesture, including the |
| scale, center and angle of the pinch. |
| */ |
| |
| /*! |
| \qmlsignal QtQuick::PinchArea::smartZoom(PinchEvent pinch) |
| \since 5.5 |
| |
| This signal is emitted when the pinch area detects the smart zoom gesture. |
| This gesture occurs only on certain operating systems such as \macos. |
| |
| The \l {PinchEvent}{pinch} parameter provides information about the pinch |
| gesture, including the location where the gesture occurred. \c pinch.scale |
| will be greater than zero when the gesture indicates that the user wishes to |
| enter smart zoom, and zero when exiting (even though typically the same gesture |
| is used to toggle between the two states). |
| */ |
| |
| |
| /*! |
| \qmlpropertygroup QtQuick::PinchArea::pinch |
| \qmlproperty Item QtQuick::PinchArea::pinch.target |
| \qmlproperty bool QtQuick::PinchArea::pinch.active |
| \qmlproperty real QtQuick::PinchArea::pinch.minimumScale |
| \qmlproperty real QtQuick::PinchArea::pinch.maximumScale |
| \qmlproperty real QtQuick::PinchArea::pinch.minimumRotation |
| \qmlproperty real QtQuick::PinchArea::pinch.maximumRotation |
| \qmlproperty enumeration QtQuick::PinchArea::pinch.dragAxis |
| \qmlproperty real QtQuick::PinchArea::pinch.minimumX |
| \qmlproperty real QtQuick::PinchArea::pinch.maximumX |
| \qmlproperty real QtQuick::PinchArea::pinch.minimumY |
| \qmlproperty real QtQuick::PinchArea::pinch.maximumY |
| |
| \c pinch provides a convenient way to make an item react to pinch gestures. |
| |
| \list |
| \li \c pinch.target specifies the id of the item to drag. |
| \li \c pinch.active specifies if the target item is currently being dragged. |
| \li \c pinch.minimumScale and \c pinch.maximumScale limit the range of the Item.scale property, but not the \c PinchEvent \l {PinchEvent}{scale} property. |
| \li \c pinch.minimumRotation and \c pinch.maximumRotation limit the range of the Item.rotation property, but not the \c PinchEvent \l {PinchEvent}{rotation} property. |
| \li \c pinch.dragAxis specifies whether dragging in not allowed (\c Pinch.NoDrag), can be done horizontally (\c Pinch.XAxis), vertically (\c Pinch.YAxis), or both (\c Pinch.XAndYAxis) |
| \li \c pinch.minimum and \c pinch.maximum limit how far the target can be dragged along the corresponding axes. |
| \endlist |
| */ |
| |
| QQuickPinchArea::QQuickPinchArea(QQuickItem *parent) |
| : QQuickItem(*(new QQuickPinchAreaPrivate), parent) |
| { |
| Q_D(QQuickPinchArea); |
| d->init(); |
| setAcceptTouchEvents(true); |
| #ifdef Q_OS_OSX |
| setAcceptHoverEvents(true); // needed to enable touch events on mouse hover. |
| #endif |
| } |
| |
| QQuickPinchArea::~QQuickPinchArea() |
| { |
| } |
| /*! |
| \qmlproperty bool QtQuick::PinchArea::enabled |
| This property holds whether the item accepts pinch gestures. |
| |
| This property defaults to true. |
| */ |
| bool QQuickPinchArea::isEnabled() const |
| { |
| Q_D(const QQuickPinchArea); |
| return d->enabled; |
| } |
| |
| void QQuickPinchArea::setEnabled(bool a) |
| { |
| Q_D(QQuickPinchArea); |
| if (a != d->enabled) { |
| d->enabled = a; |
| emit enabledChanged(); |
| } |
| } |
| |
| void QQuickPinchArea::touchEvent(QTouchEvent *event) |
| { |
| Q_D(QQuickPinchArea); |
| if (!d->enabled || !isVisible()) { |
| QQuickItem::touchEvent(event); |
| return; |
| } |
| |
| // A common non-trivial starting scenario is the user puts down one finger, |
| // then that finger remains stationary while putting down a second one. |
| // However QQuickWindow will not send TouchUpdates for TouchPoints which |
| // were not initially accepted; that would be inefficient and noisy. |
| // So even if there is only one touchpoint so far, it's important to accept it |
| // in order to get updates later on (and it's accepted by default anyway). |
| // If the user puts down one finger, we're waiting for the other finger to drop. |
| // Therefore updatePinch() must do the right thing for any combination of |
| // points and states that may occur, and there is no reason to ignore any event. |
| // One consequence though is that if PinchArea is on top of something else, |
| // it's always going to accept the touches, and that means the item underneath |
| // will not get them (unless the PA's parent is doing parent filtering, |
| // as the Flickable does, for example). |
| switch (event->type()) { |
| case QEvent::TouchBegin: |
| case QEvent::TouchUpdate: |
| d->touchPoints.clear(); |
| for (int i = 0; i < event->touchPoints().count(); ++i) { |
| if (!(event->touchPoints().at(i).state() & Qt::TouchPointReleased)) { |
| d->touchPoints << event->touchPoints().at(i); |
| } |
| } |
| updatePinch(); |
| break; |
| case QEvent::TouchEnd: |
| clearPinch(); |
| break; |
| case QEvent::TouchCancel: |
| cancelPinch(); |
| break; |
| default: |
| QQuickItem::touchEvent(event); |
| } |
| } |
| |
| void QQuickPinchArea::clearPinch() |
| { |
| Q_D(QQuickPinchArea); |
| |
| d->touchPoints.clear(); |
| if (d->inPinch) { |
| d->inPinch = false; |
| QPointF pinchCenter = mapFromScene(d->sceneLastCenter); |
| QQuickPinchEvent pe(pinchCenter, d->pinchLastScale, d->pinchLastAngle, d->pinchRotation); |
| pe.setStartCenter(d->pinchStartCenter); |
| pe.setPreviousCenter(pinchCenter); |
| pe.setPreviousAngle(d->pinchLastAngle); |
| pe.setPreviousScale(d->pinchLastScale); |
| pe.setStartPoint1(mapFromScene(d->sceneStartPoint1)); |
| pe.setStartPoint2(mapFromScene(d->sceneStartPoint2)); |
| pe.setPoint1(mapFromScene(d->lastPoint1)); |
| pe.setPoint2(mapFromScene(d->lastPoint2)); |
| emit pinchFinished(&pe); |
| if (d->pinch && d->pinch->target()) |
| d->pinch->setActive(false); |
| } |
| d->pinchStartDist = 0; |
| d->pinchActivated = false; |
| d->initPinch = false; |
| d->pinchRejected = false; |
| d->stealMouse = false; |
| d->id1 = -1; |
| QQuickWindow *win = window(); |
| if (win && win->mouseGrabberItem() == this) |
| ungrabMouse(); |
| setKeepMouseGrab(false); |
| } |
| |
| void QQuickPinchArea::cancelPinch() |
| { |
| Q_D(QQuickPinchArea); |
| |
| d->touchPoints.clear(); |
| if (d->inPinch) { |
| d->inPinch = false; |
| QPointF pinchCenter = mapFromScene(d->sceneLastCenter); |
| QQuickPinchEvent pe(d->pinchStartCenter, d->pinchStartScale, d->pinchStartAngle, d->pinchStartRotation); |
| pe.setStartCenter(d->pinchStartCenter); |
| pe.setPreviousCenter(pinchCenter); |
| pe.setPreviousAngle(d->pinchLastAngle); |
| pe.setPreviousScale(d->pinchLastScale); |
| pe.setStartPoint1(mapFromScene(d->sceneStartPoint1)); |
| pe.setStartPoint2(mapFromScene(d->sceneStartPoint2)); |
| pe.setPoint1(pe.startPoint1()); |
| pe.setPoint2(pe.startPoint2()); |
| emit pinchFinished(&pe); |
| |
| d->pinchLastScale = d->pinchStartScale; |
| d->sceneLastCenter = d->sceneStartCenter; |
| d->pinchLastAngle = d->pinchStartAngle; |
| d->lastPoint1 = pe.startPoint1(); |
| d->lastPoint2 = pe.startPoint2(); |
| updatePinchTarget(); |
| |
| if (d->pinch && d->pinch->target()) |
| d->pinch->setActive(false); |
| } |
| d->pinchStartDist = 0; |
| d->pinchActivated = false; |
| d->initPinch = false; |
| d->pinchRejected = false; |
| d->stealMouse = false; |
| d->id1 = -1; |
| QQuickWindow *win = window(); |
| if (win && win->mouseGrabberItem() == this) |
| ungrabMouse(); |
| setKeepMouseGrab(false); |
| } |
| |
| void QQuickPinchArea::updatePinch() |
| { |
| Q_D(QQuickPinchArea); |
| |
| QQuickWindow *win = window(); |
| |
| if (d->touchPoints.count() < 2) { |
| setKeepMouseGrab(false); |
| QQuickWindow *c = window(); |
| if (c && c->mouseGrabberItem() == this) |
| ungrabMouse(); |
| } |
| |
| if (d->touchPoints.count() == 0) { |
| if (d->inPinch) { |
| d->inPinch = false; |
| QPointF pinchCenter = mapFromScene(d->sceneLastCenter); |
| QQuickPinchEvent pe(pinchCenter, d->pinchLastScale, d->pinchLastAngle, d->pinchRotation); |
| pe.setStartCenter(d->pinchStartCenter); |
| pe.setPreviousCenter(pinchCenter); |
| pe.setPreviousAngle(d->pinchLastAngle); |
| pe.setPreviousScale(d->pinchLastScale); |
| pe.setStartPoint1(mapFromScene(d->sceneStartPoint1)); |
| pe.setStartPoint2(mapFromScene(d->sceneStartPoint2)); |
| pe.setPoint1(mapFromScene(d->lastPoint1)); |
| pe.setPoint2(mapFromScene(d->lastPoint2)); |
| emit pinchFinished(&pe); |
| d->pinchStartDist = 0; |
| d->pinchActivated = false; |
| if (d->pinch && d->pinch->target()) |
| d->pinch->setActive(false); |
| } |
| d->initPinch = false; |
| d->pinchRejected = false; |
| d->stealMouse = false; |
| return; |
| } |
| |
| QTouchEvent::TouchPoint touchPoint1 = d->touchPoints.at(0); |
| QTouchEvent::TouchPoint touchPoint2 = d->touchPoints.at(d->touchPoints. count() >= 2 ? 1 : 0); |
| |
| if (touchPoint1.state() == Qt::TouchPointPressed) |
| d->sceneStartPoint1 = touchPoint1.scenePos(); |
| |
| if (touchPoint2.state() == Qt::TouchPointPressed) |
| d->sceneStartPoint2 = touchPoint2.scenePos(); |
| |
| QRectF bounds = clipRect(); |
| // Pinch is not started unless there are exactly two touch points |
| // AND one or more of the points has just now been pressed (wasn't pressed already) |
| // AND both points are inside the bounds. |
| if (d->touchPoints.count() == 2 |
| && (touchPoint1.state() & Qt::TouchPointPressed || touchPoint2.state() & Qt::TouchPointPressed) && |
| bounds.contains(touchPoint1.pos()) && bounds.contains(touchPoint2.pos())) { |
| d->id1 = touchPoint1.id(); |
| d->pinchActivated = true; |
| d->initPinch = true; |
| |
| int touchMouseId = QQuickWindowPrivate::get(win)->touchMouseId; |
| if (touchPoint1.id() == touchMouseId || touchPoint2.id() == touchMouseId) { |
| if (win && win->mouseGrabberItem() != this) { |
| grabMouse(); |
| } |
| } |
| } |
| if (d->pinchActivated && !d->pinchRejected) { |
| const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); |
| QPointF p1 = touchPoint1.scenePos(); |
| QPointF p2 = touchPoint2.scenePos(); |
| qreal dx = p1.x() - p2.x(); |
| qreal dy = p1.y() - p2.y(); |
| qreal dist = qSqrt(dx*dx + dy*dy); |
| QPointF sceneCenter = (p1 + p2)/2; |
| qreal angle = QLineF(p1, p2).angle(); |
| if (d->touchPoints.count() == 1) { |
| // If we only have one point then just move the center |
| if (d->id1 == touchPoint1.id()) |
| sceneCenter = d->sceneLastCenter + touchPoint1.scenePos() - d->lastPoint1; |
| else |
| sceneCenter = d->sceneLastCenter + touchPoint2.scenePos() - d->lastPoint2; |
| angle = d->pinchLastAngle; |
| } |
| d->id1 = touchPoint1.id(); |
| if (angle > 180) |
| angle -= 360; |
| if (!d->inPinch || d->initPinch) { |
| if (d->touchPoints.count() >= 2) { |
| if (d->initPinch) { |
| if (!d->inPinch) |
| d->pinchStartDist = dist; |
| d->initPinch = false; |
| } |
| d->sceneStartCenter = sceneCenter; |
| d->sceneLastCenter = sceneCenter; |
| d->pinchStartCenter = mapFromScene(sceneCenter); |
| d->pinchStartAngle = angle; |
| d->pinchLastScale = 1.0; |
| d->pinchLastAngle = angle; |
| d->pinchRotation = 0.0; |
| d->lastPoint1 = p1; |
| d->lastPoint2 = p2; |
| if (qAbs(dist - d->pinchStartDist) >= dragThreshold || |
| (pinch()->axis() != QQuickPinch::NoDrag && |
| (qAbs(p1.x()-d->sceneStartPoint1.x()) >= dragThreshold |
| || qAbs(p1.y()-d->sceneStartPoint1.y()) >= dragThreshold |
| || qAbs(p2.x()-d->sceneStartPoint2.x()) >= dragThreshold |
| || qAbs(p2.y()-d->sceneStartPoint2.y()) >= dragThreshold))) { |
| QQuickPinchEvent pe(d->pinchStartCenter, 1.0, angle, 0.0); |
| d->pinchStartDist = dist; |
| pe.setStartCenter(d->pinchStartCenter); |
| pe.setPreviousCenter(d->pinchStartCenter); |
| pe.setPreviousAngle(d->pinchLastAngle); |
| pe.setPreviousScale(d->pinchLastScale); |
| pe.setStartPoint1(mapFromScene(d->sceneStartPoint1)); |
| pe.setStartPoint2(mapFromScene(d->sceneStartPoint2)); |
| pe.setPoint1(mapFromScene(d->lastPoint1)); |
| pe.setPoint2(mapFromScene(d->lastPoint2)); |
| pe.setPointCount(d->touchPoints.count()); |
| emit pinchStarted(&pe); |
| if (pe.accepted()) { |
| d->inPinch = true; |
| d->stealMouse = true; |
| if (win && win->mouseGrabberItem() != this) |
| grabMouse(); |
| setKeepMouseGrab(true); |
| grabTouchPoints(QVector<int>() << touchPoint1.id() << touchPoint2.id()); |
| d->inPinch = true; |
| d->stealMouse = true; |
| if (d->pinch && d->pinch->target()) { |
| d->pinchStartPos = pinch()->target()->position(); |
| d->pinchStartScale = d->pinch->target()->scale(); |
| d->pinchStartRotation = d->pinch->target()->rotation(); |
| d->pinch->setActive(true); |
| } |
| } else { |
| d->pinchRejected = true; |
| } |
| } |
| } |
| } else if (d->pinchStartDist > 0) { |
| qreal scale = dist ? dist / d->pinchStartDist : d->pinchLastScale; |
| qreal da = d->pinchLastAngle - angle; |
| if (da > 180) |
| da -= 360; |
| else if (da < -180) |
| da += 360; |
| d->pinchRotation += da; |
| QPointF pinchCenter = mapFromScene(sceneCenter); |
| QQuickPinchEvent pe(pinchCenter, scale, angle, d->pinchRotation); |
| pe.setStartCenter(d->pinchStartCenter); |
| pe.setPreviousCenter(mapFromScene(d->sceneLastCenter)); |
| pe.setPreviousAngle(d->pinchLastAngle); |
| pe.setPreviousScale(d->pinchLastScale); |
| pe.setStartPoint1(mapFromScene(d->sceneStartPoint1)); |
| pe.setStartPoint2(mapFromScene(d->sceneStartPoint2)); |
| pe.setPoint1(touchPoint1.pos()); |
| pe.setPoint2(touchPoint2.pos()); |
| pe.setPointCount(d->touchPoints.count()); |
| d->pinchLastScale = scale; |
| d->sceneLastCenter = sceneCenter; |
| d->pinchLastAngle = angle; |
| d->lastPoint1 = touchPoint1.scenePos(); |
| d->lastPoint2 = touchPoint2.scenePos(); |
| emit pinchUpdated(&pe); |
| updatePinchTarget(); |
| } |
| } |
| } |
| |
| void QQuickPinchArea::updatePinchTarget() |
| { |
| Q_D(QQuickPinchArea); |
| if (d->pinch && d->pinch->target()) { |
| qreal s = d->pinchStartScale * d->pinchLastScale; |
| s = qMin(qMax(pinch()->minimumScale(),s), pinch()->maximumScale()); |
| pinch()->target()->setScale(s); |
| QPointF pos = d->sceneLastCenter - d->sceneStartCenter + d->pinchStartPos; |
| if (pinch()->axis() & QQuickPinch::XAxis) { |
| qreal x = pos.x(); |
| if (x < pinch()->xmin()) |
| x = pinch()->xmin(); |
| else if (x > pinch()->xmax()) |
| x = pinch()->xmax(); |
| pinch()->target()->setX(x); |
| } |
| if (pinch()->axis() & QQuickPinch::YAxis) { |
| qreal y = pos.y(); |
| if (y < pinch()->ymin()) |
| y = pinch()->ymin(); |
| else if (y > pinch()->ymax()) |
| y = pinch()->ymax(); |
| pinch()->target()->setY(y); |
| } |
| if (d->pinchStartRotation >= pinch()->minimumRotation() |
| && d->pinchStartRotation <= pinch()->maximumRotation()) { |
| qreal r = d->pinchRotation + d->pinchStartRotation; |
| r = qMin(qMax(pinch()->minimumRotation(),r), pinch()->maximumRotation()); |
| pinch()->target()->setRotation(r); |
| } |
| } |
| } |
| |
| bool QQuickPinchArea::childMouseEventFilter(QQuickItem *i, QEvent *e) |
| { |
| Q_D(QQuickPinchArea); |
| if (!d->enabled || !isVisible()) |
| return QQuickItem::childMouseEventFilter(i, e); |
| switch (e->type()) { |
| case QEvent::TouchBegin: |
| clearPinch(); |
| Q_FALLTHROUGH(); |
| case QEvent::TouchUpdate: { |
| QTouchEvent *touch = static_cast<QTouchEvent*>(e); |
| d->touchPoints.clear(); |
| for (int i = 0; i < touch->touchPoints().count(); ++i) |
| if (!(touch->touchPoints().at(i).state() & Qt::TouchPointReleased)) |
| d->touchPoints << touch->touchPoints().at(i); |
| updatePinch(); |
| } |
| e->setAccepted(d->inPinch); |
| return d->inPinch; |
| case QEvent::TouchEnd: |
| clearPinch(); |
| break; |
| default: |
| break; |
| } |
| |
| return QQuickItem::childMouseEventFilter(i, e); |
| } |
| |
| void QQuickPinchArea::geometryChanged(const QRectF &newGeometry, |
| const QRectF &oldGeometry) |
| { |
| QQuickItem::geometryChanged(newGeometry, oldGeometry); |
| } |
| |
| void QQuickPinchArea::itemChange(ItemChange change, const ItemChangeData &value) |
| { |
| QQuickItem::itemChange(change, value); |
| } |
| |
| bool QQuickPinchArea::event(QEvent *event) |
| { |
| Q_D(QQuickPinchArea); |
| if (!d->enabled || !isVisible()) |
| return QQuickItem::event(event); |
| |
| switch (event->type()) { |
| #if QT_CONFIG(gestures) |
| case QEvent::NativeGesture: { |
| QNativeGestureEvent *gesture = static_cast<QNativeGestureEvent *>(event); |
| switch (gesture->gestureType()) { |
| case Qt::BeginNativeGesture: |
| clearPinch(); // probably not necessary; JIC |
| d->pinchStartCenter = gesture->localPos(); |
| d->pinchStartAngle = 0.0; |
| d->pinchStartRotation = 0.0; |
| d->pinchRotation = 0.0; |
| d->pinchStartScale = 1.0; |
| d->pinchLastAngle = 0.0; |
| d->pinchLastScale = 1.0; |
| d->sceneStartPoint1 = gesture->windowPos(); |
| d->sceneStartPoint2 = gesture->windowPos(); // TODO we never really know |
| d->lastPoint1 = gesture->windowPos(); |
| d->lastPoint2 = gesture->windowPos(); // TODO we never really know |
| if (d->pinch && d->pinch->target()) { |
| d->pinchStartPos = d->pinch->target()->position(); |
| d->pinchStartScale = d->pinch->target()->scale(); |
| d->pinchStartRotation = d->pinch->target()->rotation(); |
| d->pinch->setActive(true); |
| } |
| break; |
| case Qt::EndNativeGesture: |
| clearPinch(); |
| break; |
| case Qt::ZoomNativeGesture: { |
| qreal scale = d->pinchLastScale * (1.0 + gesture->value()); |
| QQuickPinchEvent pe(d->pinchStartCenter, scale, d->pinchLastAngle, 0.0); |
| pe.setStartCenter(d->pinchStartCenter); |
| pe.setPreviousCenter(d->pinchStartCenter); |
| pe.setPreviousAngle(d->pinchLastAngle); |
| pe.setPreviousScale(d->pinchLastScale); |
| pe.setStartPoint1(mapFromScene(d->sceneStartPoint1)); |
| pe.setStartPoint2(mapFromScene(d->sceneStartPoint2)); |
| pe.setPoint1(mapFromScene(d->lastPoint1)); |
| pe.setPoint2(mapFromScene(d->lastPoint2)); |
| pe.setPointCount(2); |
| d->pinchLastScale = scale; |
| if (d->inPinch) |
| emit pinchUpdated(&pe); |
| else |
| emit pinchStarted(&pe); |
| d->inPinch = true; |
| updatePinchTarget(); |
| } break; |
| case Qt::SmartZoomNativeGesture: { |
| if (gesture->value() > 0.0 && d->pinch && d->pinch->target()) { |
| d->pinchStartPos = pinch()->target()->position(); |
| d->pinchStartCenter = mapToItem(pinch()->target()->parentItem(), pinch()->target()->boundingRect().center()); |
| d->pinchStartScale = d->pinch->target()->scale(); |
| d->pinchStartRotation = d->pinch->target()->rotation(); |
| d->pinchLastScale = d->pinchStartScale = d->pinch->target()->scale(); |
| d->pinchLastAngle = d->pinchStartRotation = d->pinch->target()->rotation(); |
| } |
| QQuickPinchEvent pe(gesture->localPos(), gesture->value(), d->pinchLastAngle, 0.0); |
| pe.setStartCenter(gesture->localPos()); |
| pe.setPreviousCenter(d->pinchStartCenter); |
| pe.setPreviousAngle(d->pinchLastAngle); |
| pe.setPreviousScale(d->pinchLastScale); |
| pe.setStartPoint1(gesture->localPos()); |
| pe.setStartPoint2(gesture->localPos()); |
| pe.setPoint1(mapFromScene(gesture->windowPos())); |
| pe.setPoint2(mapFromScene(gesture->windowPos())); |
| pe.setPointCount(2); |
| emit smartZoom(&pe); |
| } break; |
| case Qt::RotateNativeGesture: { |
| qreal angle = d->pinchLastAngle + gesture->value(); |
| QQuickPinchEvent pe(d->pinchStartCenter, d->pinchLastScale, angle, 0.0); |
| pe.setStartCenter(d->pinchStartCenter); |
| pe.setPreviousCenter(d->pinchStartCenter); |
| pe.setPreviousAngle(d->pinchLastAngle); |
| pe.setPreviousScale(d->pinchLastScale); |
| pe.setStartPoint1(mapFromScene(d->sceneStartPoint1)); |
| pe.setStartPoint2(mapFromScene(d->sceneStartPoint2)); |
| pe.setPoint1(mapFromScene(d->lastPoint1)); |
| pe.setPoint2(mapFromScene(d->lastPoint2)); |
| pe.setPointCount(2); |
| d->pinchLastAngle = angle; |
| if (d->inPinch) |
| emit pinchUpdated(&pe); |
| else |
| emit pinchStarted(&pe); |
| d->inPinch = true; |
| d->pinchRotation = angle; |
| updatePinchTarget(); |
| } break; |
| default: |
| return QQuickItem::event(event); |
| } |
| } break; |
| #endif // gestures |
| case QEvent::Wheel: |
| event->ignore(); |
| return false; |
| default: |
| return QQuickItem::event(event); |
| } |
| |
| return true; |
| } |
| |
| QQuickPinch *QQuickPinchArea::pinch() |
| { |
| Q_D(QQuickPinchArea); |
| if (!d->pinch) |
| d->pinch = new QQuickPinch; |
| return d->pinch; |
| } |
| |
| QT_END_NAMESPACE |
| |
| #include "moc_qquickpincharea_p.cpp" |