diff options
54 files changed, 4050 insertions, 102 deletions
diff --git a/src/imports/handlers/handlers.pro b/src/imports/handlers/handlers.pro new file mode 100644 index 0000000000..0e32644773 --- /dev/null +++ b/src/imports/handlers/handlers.pro @@ -0,0 +1,11 @@ +CXX_MODULE = qml +TARGET = handlersplugin +TARGETPATH = Qt/labs/handlers +IMPORT_VERSION = 1.0 + +SOURCES += \ + plugin.cpp + +QT += quick-private qml-private + +load(qml_plugin) diff --git a/src/imports/handlers/plugin.cpp b/src/imports/handlers/plugin.cpp new file mode 100644 index 0000000000..21b9764611 --- /dev/null +++ b/src/imports/handlers/plugin.cpp @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins 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 <QtQml/qqmlextensionplugin.h> + +#include <private/qquickhandlersmodule_p.h> + +static void initResources() +{ +#ifdef QT_STATIC + Q_INIT_RESOURCE(qmake_Qt_labs_handlers_1); +#endif +} + +QT_BEGIN_NAMESPACE + +/*! + \qmlmodule Qt.labs.handlers 1.0 + \title Qt Quick Pointer Handlers + \ingroup qmlmodules + \brief Provides QML types for handling pointer events. + + This QML module contains types for handling pointer events, which are an abstraction + of mouse, touch and tablet events. + + To use the types in this module, import the module with the following line: + + \code + import Qt.labs.handlers 1.0 + \endcode +*/ + + +//![class decl] +class QtQuickHandlersPlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) +public: + QtQuickHandlersPlugin(QObject *parent = 0) : QQmlExtensionPlugin(parent) { initResources(); } + void registerTypes(const char *uri) Q_DECL_OVERRIDE + { + Q_ASSERT(QLatin1String(uri) == QLatin1String("Qt.labs.handlers")); + Q_UNUSED(uri); + QQuickHandlersModule::defineModule(); + } +}; +//![class decl] + +QT_END_NAMESPACE + +#include "plugin.moc" diff --git a/src/imports/handlers/qmldir b/src/imports/handlers/qmldir new file mode 100644 index 0000000000..4896348c2e --- /dev/null +++ b/src/imports/handlers/qmldir @@ -0,0 +1,5 @@ +module Qt.labs.handlers +plugin handlersplugin +classname QtQuickHandlersPlugin +typeinfo plugins.qmltypes + diff --git a/src/imports/imports.pro b/src/imports/imports.pro index c49c05956d..0d84bf538f 100644 --- a/src/imports/imports.pro +++ b/src/imports/imports.pro @@ -12,6 +12,7 @@ qtConfig(statemachine): SUBDIRS += statemachine qtHaveModule(quick) { SUBDIRS += \ + handlers \ layouts \ qtquick2 \ window \ diff --git a/src/quick/handlers/handlers.pri b/src/quick/handlers/handlers.pri new file mode 100644 index 0000000000..f32f330d4e --- /dev/null +++ b/src/quick/handlers/handlers.pri @@ -0,0 +1,19 @@ +HEADERS += \ + $$PWD/qquickdraghandler_p.h \ + $$PWD/qquickpointerhandler_p.h \ + $$PWD/qquickpointerdevicehandler_p.h \ + $$PWD/qquickmultipointerhandler_p.h \ + $$PWD/qquickpinchhandler_p.h \ + $$PWD/qquickpointersinglehandler_p.h \ + $$PWD/qquickhandlersmodule_p.h \ + $$PWD/qquickpointerdevicehandler_p.h \ + $$PWD/qquickhandlersmodule_p.h \ + +SOURCES += \ + $$PWD/qquickdraghandler.cpp \ + $$PWD/qquickpointerhandler.cpp \ + $$PWD/qquickpointerdevicehandler.cpp \ + $$PWD/qquickmultipointerhandler.cpp \ + $$PWD/qquickpinchhandler.cpp \ + $$PWD/qquickpointersinglehandler.cpp \ + $$PWD/qquickhandlersmodule.cpp \ diff --git a/src/quick/handlers/qquickdraghandler.cpp b/src/quick/handlers/qquickdraghandler.cpp new file mode 100644 index 0000000000..6caee6be13 --- /dev/null +++ b/src/quick/handlers/qquickdraghandler.cpp @@ -0,0 +1,169 @@ +/**************************************************************************** +** +** 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 "qquickdraghandler_p.h" +#include <private/qquickwindow_p.h> +#include <QDebug> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype DragHandler + \instantiates QQuickDragHandler + \inqmlmodule QtQuick + \ingroup qtquick-handlers + \brief Handler for dragging + + DragHandler is a handler that is used to interactively move an Item. + + At this time, drag-and-drop is not yet supported. + + \sa Drag, MouseArea +*/ + +QQuickDragHandler::QQuickDragHandler(QObject *parent) + : QQuickPointerSingleHandler(parent) +{ +} + +QQuickDragHandler::~QQuickDragHandler() +{ +} + +bool QQuickDragHandler::wantsEventPoint(QQuickEventPoint *point) +{ + // If we've already been interested in a point, stay interested, even if it has strayed outside bounds. + return ((point->state() != QQuickEventPoint::Pressed && pointId() == point->pointId()) + || QQuickPointerSingleHandler::wantsEventPoint(point)); +} + +void QQuickDragHandler::handleEventPoint(QQuickEventPoint *point) +{ + point->setAccepted(); + switch (point->state()) { + case QQuickEventPoint::Pressed: + if (target() && target()->parentItem()) + m_startPos = target()->parentItem()->mapToScene(target()->position()); + break; + case QQuickEventPoint::Updated: { + QPointF delta = point->scenePos() - point->scenePressPos(); + if (!m_xAxis.enabled()) + delta.setX(0); + if (!m_yAxis.enabled()) + delta.setY(0); + if (active()) { + setTranslation(delta); + if (target() && target()->parentItem()) { + QPointF pos = target()->parentItem()->mapFromScene(m_startPos + delta); + enforceAxisConstraints(&pos); + target()->setPosition(pos); + } + } else if ((m_xAxis.enabled() && QQuickWindowPrivate::dragOverThreshold(delta.x(), Qt::XAxis, point)) || + (m_yAxis.enabled() && QQuickWindowPrivate::dragOverThreshold(delta.y(), Qt::YAxis, point))) { + setGrab(point, true); + } + } break; + default: + break; + } +} + +void QQuickDragHandler::enforceConstraints() +{ + if (!target() || !target()->parentItem()) + return; + QPointF pos = target()->position(); + QPointF copy(pos); + enforceAxisConstraints(&pos); + if (pos != copy) + target()->setPosition(pos); +} + +void QQuickDragHandler::enforceAxisConstraints(QPointF *localPos) +{ + if (m_xAxis.enabled()) + localPos->setX(qBound(m_xAxis.minimum(), localPos->x(), m_xAxis.maximum())); + if (m_yAxis.enabled()) + localPos->setY(qBound(m_yAxis.minimum(), localPos->y(), m_yAxis.maximum())); +} + +void QQuickDragHandler::setTranslation(const QPointF &trans) +{ + if (trans == m_translation) // fuzzy compare? + return; + m_translation = trans; + emit translationChanged(); +} + + +QQuickDragAxis::QQuickDragAxis() + : m_minimum(-DBL_MAX) + , m_maximum(DBL_MAX) + , m_enabled(true) +{ +} + +void QQuickDragAxis::setMinimum(qreal minimum) +{ + if (m_minimum == minimum) + return; + + m_minimum = minimum; + emit minimumChanged(); +} + +void QQuickDragAxis::setMaximum(qreal maximum) +{ + if (m_maximum == maximum) + return; + + m_maximum = maximum; + emit maximumChanged(); +} + +void QQuickDragAxis::setEnabled(bool enabled) +{ + if (m_enabled == enabled) + return; + + m_enabled = enabled; + emit enabledChanged(); +} + +QT_END_NAMESPACE diff --git a/src/quick/handlers/qquickdraghandler_p.h b/src/quick/handlers/qquickdraghandler_p.h new file mode 100644 index 0000000000..69fa297b3c --- /dev/null +++ b/src/quick/handlers/qquickdraghandler_p.h @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QQUICKDRAGHANDLER_H +#define QQUICKDRAGHANDLER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qquickpointersinglehandler_p.h" + +QT_BEGIN_NAMESPACE + +class Q_AUTOTEST_EXPORT QQuickDragAxis : public QObject +{ + Q_OBJECT + Q_PROPERTY(qreal minimum READ minimum WRITE setMinimum NOTIFY minimumChanged) + Q_PROPERTY(qreal maximum READ maximum WRITE setMaximum NOTIFY maximumChanged) + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) + +public: + QQuickDragAxis(); + + qreal minimum() const { return m_minimum; } + void setMinimum(qreal minimum); + + qreal maximum() const { return m_maximum; } + void setMaximum(qreal maximum); + + bool enabled() const { return m_enabled; } + void setEnabled(bool enabled); + +signals: + void minimumChanged(); + void maximumChanged(); + void enabledChanged(); + +private: + qreal m_minimum; + qreal m_maximum; + bool m_enabled; +}; + +class Q_AUTOTEST_EXPORT QQuickDragHandler : public QQuickPointerSingleHandler +{ + Q_OBJECT + Q_PROPERTY(QQuickDragAxis * xAxis READ xAxis CONSTANT) + Q_PROPERTY(QQuickDragAxis * yAxis READ yAxis CONSTANT) + Q_PROPERTY(QPointF translation READ translation NOTIFY translationChanged) + +public: + QQuickDragHandler(QObject *parent = 0); + ~QQuickDragHandler(); + + void handleEventPoint(QQuickEventPoint *point) override; + + QQuickDragAxis *xAxis() { return &m_xAxis; } + QQuickDragAxis *yAxis() { return &m_yAxis; } + + QPointF translation() const { return m_translation; } + void setTranslation(const QPointF &trans); + + Q_INVOKABLE void enforceConstraints(); + +Q_SIGNALS: +// void gestureStarted(QQuickGestureEvent *gesture); + void translationChanged(); + +protected: + bool wantsEventPoint(QQuickEventPoint *point) override; + +private: + void ungrab(); + void enforceAxisConstraints(QPointF *localPos); + +private: + QPointF m_startPos; + QPointF m_translation; + QQuickDragAxis m_xAxis; + QQuickDragAxis m_yAxis; + + friend class QQuickDragAxis; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickDragHandler) +QML_DECLARE_TYPE(QQuickDragAxis) + +#endif // QQUICKDRAGHANDLER_H diff --git a/src/quick/handlers/qquickhandlersmodule.cpp b/src/quick/handlers/qquickhandlersmodule.cpp new file mode 100644 index 0000000000..697a198e67 --- /dev/null +++ b/src/quick/handlers/qquickhandlersmodule.cpp @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** 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 "qquickhandlersmodule_p.h" +#include "qquickpointerhandler_p.h" +#include "qquickdraghandler_p.h" +#include "qquickpinchhandler_p.h" + +static void initResources() +{ +#ifdef QT_STATIC + Q_INIT_RESOURCE(qmake_Qt_labs_handlers); +#endif +} + +QT_BEGIN_NAMESPACE + +static QQmlPrivate::AutoParentResult handler_autoParent(QObject *obj, QObject *parent) +{ + if (qmlobject_cast<QQuickItem *>(parent)) { + QQuickPointerHandler *handler = qmlobject_cast<QQuickPointerHandler *>(obj); + if (handler) { + handler->setParent(parent); + return QQmlPrivate::Parented; + } + } + return QQmlPrivate::IncompatibleObject; +} + +static void qt_quickhandlers_defineModule(const char *uri, int major, int minor) +{ + QQmlPrivate::RegisterAutoParent autoparent = { 0, &handler_autoParent }; + QQmlPrivate::qmlregister(QQmlPrivate::AutoParentRegistration, &autoparent); + qmlRegisterUncreatableType<QQuickPointerEvent>(uri, major, minor, "PointerEvent", + QQuickPointerHandler::tr("PointerEvent is only available as a parameter of several signals in PointerHandler")); + qmlRegisterUncreatableType<QQuickPointerDevice>(uri, major, minor, "PointerDevice", + QQuickPointerHandler::tr("PointerDevice is only available as a property of PointerEvent")); + qRegisterMetaType<QPointingDeviceUniqueId>("QPointingDeviceUniqueId"); + qmlRegisterUncreatableType<QPointingDeviceUniqueId>(uri, major, minor, "PointingDeviceUniqueId", + QQuickPointerHandler::tr("PointingDeviceUniqueId is only available as a property of PointerEvent")); + + qmlRegisterType<QQuickPointerHandler>(uri,major,minor,"PointerHandler"); + qmlRegisterType<QQuickDragHandler>(uri,major,minor,"DragHandler"); + qmlRegisterUncreatableType<QQuickDragAxis>(uri, major, minor, "DragAxis", + QQuickDragHandler::tr("DragAxis is only available as a grouped property of DragHandler")); + qmlRegisterType<QQuickPinchHandler>(uri,major,minor,"PinchHandler"); +} + +void QQuickHandlersModule::defineModule() +{ + initResources(); + + const char uri[] = "Qt.labs.handlers"; + int majorVersion = 1; + int minorVersion = 0; + + qt_quickhandlers_defineModule(uri, majorVersion, minorVersion); +} + +QT_END_NAMESPACE diff --git a/src/quick/handlers/qquickhandlersmodule_p.h b/src/quick/handlers/qquickhandlersmodule_p.h new file mode 100644 index 0000000000..7eb8d39b98 --- /dev/null +++ b/src/quick/handlers/qquickhandlersmodule_p.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QQUICKHANDLERSMODULE_P_H +#define QQUICKHANDLERSMODULE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <qqml.h> +#include <private/qtquickglobal_p.h> + +QT_BEGIN_NAMESPACE + +class Q_QUICK_PRIVATE_EXPORT QQuickHandlersModule +{ +public: + static void defineModule(); +}; + +QT_END_NAMESPACE + +#endif // QQUICKHANDLERSMODULE_P_H + diff --git a/src/quick/handlers/qquickmultipointerhandler.cpp b/src/quick/handlers/qquickmultipointerhandler.cpp new file mode 100644 index 0000000000..d3b986bcce --- /dev/null +++ b/src/quick/handlers/qquickmultipointerhandler.cpp @@ -0,0 +1,219 @@ +/**************************************************************************** +** +** 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 "qquickmultipointerhandler_p.h" +#include <private/qquickitem_p.h> +#include <QLineF> +#include <QMouseEvent> +#include <QDebug> + +QT_BEGIN_NAMESPACE + +/*! + An intermediate class (not registered as a QML type) + for the type of handler which requires and acts upon a specific number + of multiple touchpoints. +*/ +QQuickMultiPointerHandler::QQuickMultiPointerHandler(QObject *parent, int requiredPointCount) + : QQuickPointerDeviceHandler(parent) + , m_requiredPointCount(requiredPointCount) + , m_pointDistanceThreshold(0) +{ +} + +QQuickMultiPointerHandler::~QQuickMultiPointerHandler() +{ +} + +bool QQuickMultiPointerHandler::wantsPointerEvent(QQuickPointerEvent *event) +{ + if (!QQuickPointerDeviceHandler::wantsPointerEvent(event)) + return false; + if (event->pointCount() < m_requiredPointCount) + return false; + if (sameAsCurrentPoints(event)) + return true; + + const QVector<QQuickEventPoint *> candidatePoints = pointsInsideOrNearTarget(event); + const bool ret = (candidatePoints.size() == m_requiredPointCount); + if (ret) + m_currentPoints = candidatePoints; + return ret; +} + +QVector<QQuickEventPoint *> QQuickMultiPointerHandler::pointsInsideOrNearTarget(QQuickPointerEvent *event) +{ + QVector<QQuickEventPoint *> ret; + int c = event->pointCount(); + QRectF targetBounds = target()->mapRectToScene(target()->boundingRect()) + .marginsAdded(QMarginsF(m_pointDistanceThreshold, m_pointDistanceThreshold, m_pointDistanceThreshold, m_pointDistanceThreshold)); + for (int i = 0; i < c; ++i) { + QQuickEventPoint *p = event->point(i); + if (targetBounds.contains(p->scenePos())) + ret << p; + } + return ret; +} + +void QQuickMultiPointerHandler::setRequiredPointCount(int c) +{ + if (m_requiredPointCount == c) + return; + + m_requiredPointCount = c; + emit requiredPointCountChanged(); +} + +void QQuickMultiPointerHandler::setPointDistanceThreshold(qreal pointDistanceThreshold) +{ + if (m_pointDistanceThreshold == pointDistanceThreshold) + return; + + m_pointDistanceThreshold = pointDistanceThreshold; + emit pointDistanceThresholdChanged(); +} + +bool QQuickMultiPointerHandler::sameAsCurrentPoints(QQuickPointerEvent *event) +{ + bool ret = true; + int c = event->pointCount(); + if (c != m_currentPoints.size()) + return false; + // TODO optimize: either ensure the points are sorted, + // or use std::equal with a predicate + for (int i = 0; ret && i < c; ++i) { + bool found = false; + quint64 pointId = event->point(i)->pointId(); + for (QQuickEventPoint *o : qAsConst(m_currentPoints)) + if (o && pointId == o->pointId()) + found = true; + if (!found) + ret = false; + } + return ret; +} + +// TODO make templates for these functions somehow? +QPointF QQuickMultiPointerHandler::touchPointCentroid() +{ + QPointF ret; + if (Q_UNLIKELY(m_currentPoints.size() == 0)) + return ret; + for (QQuickEventPoint *point : qAsConst(m_currentPoints)) + ret += point->scenePos(); + return ret / m_currentPoints.size(); +} + +qreal QQuickMultiPointerHandler::averageTouchPointDistance(const QPointF &ref) +{ + qreal ret = 0; + if (Q_UNLIKELY(m_currentPoints.size() == 0)) + return ret; + for (QQuickEventPoint *point : qAsConst(m_currentPoints)) + ret += QVector2D(point->scenePos() - ref).length(); + return ret / m_currentPoints.size(); +} + +qreal QQuickMultiPointerHandler::averageStartingDistance(const QPointF &ref) +{ + // TODO cache it in setActive()? + qreal ret = 0; + if (Q_UNLIKELY(m_currentPoints.size() == 0)) + return ret; + for (QQuickEventPoint *point : qAsConst(m_currentPoints)) + ret += QVector2D(point->sceneGrabPos() - ref).length(); + return ret / m_currentPoints.size(); +} + +QVector<QQuickMultiPointerHandler::PointData> QQuickMultiPointerHandler::angles(const QPointF &ref) const +{ + QVector<PointData> angles; + angles.reserve(m_currentPoints.count()); + for (QQuickEventPoint *point : qAsConst(m_currentPoints)) { + qreal angle = QLineF(ref, point->scenePos()).angle(); + angles.append(PointData(point->pointId(), -angle)); // convert to clockwise, to be consistent with QQuickItem::rotation + } + return angles; +} + +qreal QQuickMultiPointerHandler::averageAngleDelta(const QVector<PointData> &old, const QVector<PointData> &newAngles) +{ + qreal avgAngleDelta = 0; + int numSamples = 0; + + auto oldBegin = old.constBegin(); + + for (PointData newData : newAngles) { + quint64 id = newData.id; + auto it = std::find_if(oldBegin, old.constEnd(), [id] (PointData pd) { return pd.id == id; }); + qreal angleD = 0; + if (it != old.constEnd()) { + PointData oldData = *it; + // We might rotate from 359 degrees to 1 degree. However, this + // should be interpreted as a rotation of +2 degrees instead of + // -358 degrees. Therefore, we call remainder() to translate the angle + // to be in the range [-180, 180] (-350 to +10 etc) + angleD = remainder(newData.angle - oldData.angle, qreal(360)); + // optimization: narrow down the O(n^2) search to optimally O(n) + // if both vectors have the same points and they are in the same order + if (it == oldBegin) + ++oldBegin; + numSamples++; + } + avgAngleDelta += angleD; + } + if (numSamples > 1) + avgAngleDelta /= numSamples; + + return avgAngleDelta; +} + +void QQuickMultiPointerHandler::acceptPoints(const QVector<QQuickEventPoint *> &points) +{ + for (QQuickEventPoint* point : points) + point->setAccepted(); +} + +void QQuickMultiPointerHandler::grabPoints(QVector<QQuickEventPoint *> points) +{ + for (QQuickEventPoint* point : points) + setGrab(point, true); +} + +QT_END_NAMESPACE diff --git a/src/quick/handlers/qquickmultipointerhandler_p.h b/src/quick/handlers/qquickmultipointerhandler_p.h new file mode 100644 index 0000000000..2a23a14f8a --- /dev/null +++ b/src/quick/handlers/qquickmultipointerhandler_p.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QQUICKPOINTERMULTIHANDLER_H +#define QQUICKPOINTERMULTIHANDLER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qquickitem.h" +#include "qevent.h" +#include "qquickpointerdevicehandler_p.h" +#include "../items/qquickmultipointtoucharea_p.h" + +QT_BEGIN_NAMESPACE + +class Q_AUTOTEST_EXPORT QQuickMultiPointerHandler : public QQuickPointerDeviceHandler +{ + Q_OBJECT + Q_PROPERTY(int requiredPointCount READ requiredPointCount WRITE setRequiredPointCount NOTIFY requiredPointCountChanged) + Q_PROPERTY(qreal pointDistanceThreshold READ pointDistanceThreshold WRITE setPointDistanceThreshold NOTIFY pointDistanceThresholdChanged) + +public: + QQuickMultiPointerHandler(QObject *parent = 0, int requiredPointCount = 2); + ~QQuickMultiPointerHandler(); + + int requiredPointCount() const { return m_requiredPointCount; } + void setRequiredPointCount(int c); + + qreal pointDistanceThreshold() const { return m_pointDistanceThreshold; } + void setPointDistanceThreshold(qreal pointDistanceThreshold); + +signals: + void requiredPointCountChanged(); + void pointDistanceThresholdChanged(); + +protected: + struct PointData { + PointData() : id(0), angle(0) {} + PointData(quint64 id, qreal angle) : id(id), angle(angle) {} + quint64 id; + qreal angle; + }; + + bool wantsPointerEvent(QQuickPointerEvent *event) override; + bool sameAsCurrentPoints(QQuickPointerEvent *event); + QVector<QQuickEventPoint *> pointsInsideOrNearTarget(QQuickPointerEvent *event); + QPointF touchPointCentroid(); + qreal averageTouchPointDistance(const QPointF &ref); + qreal averageStartingDistance(const QPointF &ref); + qreal averageTouchPointAngle(const QPointF &ref); + qreal averageStartingAngle(const QPointF &ref); + QVector<PointData> angles(const QPointF &ref) const; + static qreal averageAngleDelta(const QVector<PointData> &old, const QVector<PointData> &newAngles); + void acceptPoints(const QVector<QQuickEventPoint *> &points); + void grabPoints(QVector<QQuickEventPoint *> points); + +protected: + QVector<QQuickEventPoint *> m_currentPoints; + int m_requiredPointCount; + qreal m_pointDistanceThreshold; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickMultiPointerHandler) + +#endif // QQUICKPOINTERMULTIHANDLER_H diff --git a/src/quick/handlers/qquickpinchhandler.cpp b/src/quick/handlers/qquickpinchhandler.cpp new file mode 100644 index 0000000000..4832d300c2 --- /dev/null +++ b/src/quick/handlers/qquickpinchhandler.cpp @@ -0,0 +1,302 @@ +/**************************************************************************** +** +** 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 "qquickpinchhandler_p.h" +#include <QtQuick/qquickwindow.h> +#include <private/qsgadaptationlayer_p.h> +#include <private/qquickitem_p.h> +#include <private/qguiapplication_p.h> +#include <private/qquickwindow_p.h> +#include <QEvent> +#include <QMouseEvent> +#include <QDebug> +#include <qpa/qplatformnativeinterface.h> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcPinchHandler, "qt.quick.handler.pinch") + +/*! + \qmltype PinchHandler + \instantiates QQuickPinchHandler + \inqmlmodule QtQuick + \ingroup qtquick-handlers + \brief Handler for pinch gestures + + PinchHandler is a handler that is used to interactively rotate and zoom an Item. +*/ + +QQuickPinchHandler::QQuickPinchHandler(QObject *parent) + : QQuickMultiPointerHandler(parent, 2) + , m_activeScale(1) + , m_activeRotation(0) + , m_activeTranslation(0,0) + , m_minimumScale(-qInf()) + , m_maximumScale(qInf()) + , m_minimumRotation(-qInf()) + , m_maximumRotation(qInf()) + , m_minimumX(-qInf()) + , m_maximumX(qInf()) + , m_minimumY(-qInf()) + , m_maximumY(qInf()) + , m_pinchOrigin(PinchCenter) + , m_startScale(1) + , m_startRotation(0) +{ +} + +QQuickPinchHandler::~QQuickPinchHandler() +{ +} + +void QQuickPinchHandler::setMinimumScale(qreal minimumScale) +{ + if (m_minimumScale == minimumScale) + return; + + m_minimumScale = minimumScale; + emit minimumScaleChanged(); +} + +void QQuickPinchHandler::setMaximumScale(qreal maximumScale) +{ + if (m_maximumScale == maximumScale) + return; + + m_maximumScale = maximumScale; + emit maximumScaleChanged(); +} + +void QQuickPinchHandler::setMinimumRotation(qreal minimumRotation) +{ + if (m_minimumRotation == minimumRotation) + return; + + m_minimumRotation = minimumRotation; + emit minimumRotationChanged(); +} + +void QQuickPinchHandler::setMaximumRotation(qreal maximumRotation) +{ + if (m_maximumRotation == maximumRotation) + return; + + m_maximumRotation = maximumRotation; + emit maximumRotationChanged(); +} + +void QQuickPinchHandler::setPinchOrigin(QQuickPinchHandler::PinchOrigin pinchOrigin) +{ + if (m_pinchOrigin == pinchOrigin) + return; + + m_pinchOrigin = pinchOrigin; + emit pinchOriginChanged(); +} + +/*! + \qmlproperty QQuickPinchHandler::minimumX + + The minimum acceptable x coordinate of the centroid + */ +void QQuickPinchHandler::setMinimumX(qreal minX) +{ + if (m_minimumX == minX) + return; + m_minimumX = minX; + emit minimumXChanged(); +} + +/*! + \qmlproperty QQuickPinchHandler::maximumX + + The maximum acceptable x coordinate of the centroid + */ +void QQuickPinchHandler::setMaximumX(qreal maxX) +{ + if (m_maximumX == maxX) + return; + m_maximumX = maxX; + emit maximumXChanged(); +} + +/*! + \qmlproperty QQuickPinchHandler::minimumY + + The minimum acceptable y coordinate of the centroid + */ +void QQuickPinchHandler::setMinimumY(qreal minY) +{ + if (m_minimumY == minY) + return; + m_minimumY = minY; + emit minimumYChanged(); +} + +/*! + \qmlproperty QQuickPinchHandler::maximumY + + The maximum acceptable y coordinate of the centroid + */ +void QQuickPinchHandler::setMaximumY(qreal maxY) +{ + if (m_maximumY == maxY) + return; + m_maximumY = maxY; + emit maximumYChanged(); +} + +/*! + \qmlproperty QQuickPinchHandler::minimumTouchPoints + + The pinch begins when this number of fingers are pressed. + Until then, PinchHandler tracks the positions of any pressed fingers, + but if it's an insufficient number, it does not scale or rotate + its \l target, and the \l active property will remain false. +*/ + +/*! + \qmlproperty QQuickPinchHandler::active +*/ + +void QQuickPinchHandler::onActiveChanged() +{ + if (active()) { + if (const QQuickItem *t = target()) { + m_startScale = t->scale(); // TODO incompatible with independent x/y scaling + m_startRotation = t->rotation(); + m_startCentroid = touchPointCentroid(); + m_startAngles = angles(m_startCentroid); + m_startDistance = averageTouchPointDistance(m_startCentroid); + QVector3D xformOrigin(t->transformOriginPoint()); + m_startMatrix = QMatrix4x4(); + m_startMatrix.translate(t->x(), t->y()); + m_startMatrix.translate(xformOrigin); + m_startMatrix.scale(m_startScale); + m_startMatrix.rotate(m_startRotation, 0, 0, -1); + m_startMatrix.translate(-xformOrigin); + m_activeRotation = 0; + m_activeTranslation = QPointF(0,0); + qCInfo(lcPinchHandler) << "activated with starting scale" << m_startScale << "rotation" << m_startRotation; + grabPoints(m_currentPoints); + } + } else { + qCInfo(lcPinchHandler) << "deactivated with scale" << m_activeScale << "rotation" << m_activeRotation; + } +} + +void QQuickPinchHandler::handlePointerEventImpl(QQuickPointerEvent *event) +{ + Q_UNUSED(event) + // TODO wait for the drag threshold before setting active + // but the old behavior was that whenever we "want" all the points, we're active + // so that behavior is retained here temporarily + setActive(m_currentPoints.count() > 0 && m_currentPoints.at(0)->state() != QQuickEventPoint::Released); + + if (Q_UNLIKELY(lcPinchHandler().isDebugEnabled())) { + for (QQuickEventPoint *point : qAsConst(m_currentPoints)) + qCDebug(lcPinchHandler) << point->state() << point->sceneGrabPos() << "->" << point->scenePos(); + } + + // TODO check m_pinchOrigin: right now it acts like it's set to PinchCenter + m_centroid = touchPointCentroid(); + QRectF bounds(m_minimumX, m_minimumY, m_maximumX, m_maximumY); + // avoid mapping the minima and maxima, as they might have unmappable values + // such as -inf/+inf. Because of this we perform the bounding to min/max in local coords. + QPointF centroidParentPos; + if (target() && target()->parentItem()) { + centroidParentPos = target()->parentItem()->mapFromScene(m_centroid); + centroidParentPos = QPointF(qBound(bounds.left(), centroidParentPos.x(), bounds.right()), + qBound(bounds.top(), centroidParentPos.y(), bounds.bottom())); + } + // 1. scale + const qreal dist = averageTouchPointDistance(m_centroid); + m_activeScale = dist / m_startDistance; + m_activeScale = qBound(m_minimumScale/m_startScale, m_activeScale, m_maximumScale/m_startScale); + const qreal scale = m_startScale * m_activeScale; + + // 2. rotate + QVector<PointData> newAngles = angles(m_centroid); + const qreal angleDelta = averageAngleDelta(m_startAngles, newAngles); + m_activeRotation += angleDelta; + const qreal totalRotation = m_startRotation + m_activeRotation; + const qreal rotation = qBound(m_minimumRotation, totalRotation, m_maximumRotation); + m_activeRotation += (rotation - totalRotation); //adjust for the potential bounding above + m_startAngles = std::move(newAngles); + + if (target() && target()->parentItem()) { + // 3. Drag/translate + const QPointF centroidStartParentPos = target()->parentItem()->mapFromScene(m_startCentroid); + m_activeTranslation = centroidParentPos - centroidStartParentPos; + + // apply rotation + scaling around the centroid - then apply translation. + QMatrix4x4 mat; + + const QVector3D centroidParentVector(centroidParentPos); + mat.translate(centroidParentVector); + mat.rotate(m_activeRotation, 0, 0, 1); + mat.scale(m_activeScale); + mat.translate(-centroidParentVector); + mat.translate(QVector3D(m_activeTranslation)); + + mat = mat * m_startMatrix; + + QPointF xformOriginPoint = target()->transformOriginPoint(); + QPointF pos = mat * xformOriginPoint; + pos -= xformOriginPoint; + + target()->setPosition(pos); + target()->setRotation(rotation); + target()->setScale(scale); + + + // TODO some translation inadvertently happens; try to hold the chosen pinch origin in place + + qCDebug(lcPinchHandler) << "centroid" << m_startCentroid << "->" << m_centroid + << ", distance" << m_startDistance << "->" << dist + << ", startScale" << m_startScale << "->" << scale + << ", activeRotation" << m_activeRotation + << ", rotation" << rotation; + } + + acceptPoints(m_currentPoints); + emit updated(); +} + +QT_END_NAMESPACE diff --git a/src/quick/handlers/qquickpinchhandler_p.h b/src/quick/handlers/qquickpinchhandler_p.h new file mode 100644 index 0000000000..0861368682 --- /dev/null +++ b/src/quick/handlers/qquickpinchhandler_p.h @@ -0,0 +1,170 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QQUICKPINCHHANDLER_H +#define QQUICKPINCHHANDLER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qquickitem.h" +#include "qevent.h" +#include "qquickmultipointerhandler_p.h" +#include <private/qquicktranslate_p.h> + +QT_BEGIN_NAMESPACE + +class Q_AUTOTEST_EXPORT QQuickPinchHandler : public QQuickMultiPointerHandler +{ + Q_OBJECT + Q_PROPERTY(qreal minimumScale READ minimumScale WRITE setMinimumScale NOTIFY minimumScaleChanged) + Q_PROPERTY(qreal maximumScale READ maximumScale WRITE setMaximumScale NOTIFY maximumScaleChanged) + Q_PROPERTY(qreal minimumRotation READ minimumRotation WRITE setMinimumRotation NOTIFY minimumRotationChanged) + Q_PROPERTY(qreal maximumRotation READ maximumRotation WRITE setMaximumRotation NOTIFY maximumRotationChanged) + Q_PROPERTY(PinchOrigin pinchOrigin READ pinchOrigin WRITE setPinchOrigin NOTIFY pinchOriginChanged) + Q_PROPERTY(QPointF centroid READ centroid NOTIFY updated) + Q_PROPERTY(qreal scale READ scale NOTIFY updated) + Q_PROPERTY(qreal rotation READ rotation NOTIFY updated) + Q_PROPERTY(QPointF translation READ translation NOTIFY updated) + Q_PROPERTY(qreal minimumX READ minimumX WRITE setMinimumX NOTIFY minimumXChanged) + Q_PROPERTY(qreal maximumX READ maximumX WRITE setMaximumX NOTIFY maximumXChanged) + Q_PROPERTY(qreal minimumY READ minimumY WRITE setMinimumY NOTIFY minimumYChanged) + Q_PROPERTY(qreal maximumY READ maximumY WRITE setMaximumY NOTIFY maximumYChanged) + +public: + enum PinchOrigin { + FirstPoint, PinchCenter, TargetCenter + }; + Q_ENUM(PinchOrigin) + + QQuickPinchHandler(QObject *parent = 0); + ~QQuickPinchHandler(); + + qreal minimumScale() const { return m_minimumScale; } + void setMinimumScale(qreal minimumScale); + + qreal maximumScale() const { return m_maximumScale; } + void setMaximumScale(qreal maximumScale); + + qreal minimumRotation() const { return m_minimumRotation; } + void setMinimumRotation(qreal minimumRotation); + + qreal maximumRotation() const { return m_maximumRotation; } + void setMaximumRotation(qreal maximumRotation); + + PinchOrigin pinchOrigin() const { return m_pinchOrigin; } + void setPinchOrigin(PinchOrigin pinchOrigin); + + QPointF translation() const { return m_activeTranslation; } + qreal scale() const { return m_activeScale; } + qreal rotation() const { return m_activeRotation; } + QPointF centroid() const { return m_centroid; } + + qreal minimumX() const { return m_minimumX; } + void setMinimumX(qreal minX); + qreal maximumX() const { return m_maximumX; } + void setMaximumX(qreal maxX); + qreal minimumY() const { return m_minimumY; } + void setMinimumY(qreal minY); + qreal maximumY() const { return m_maximumY; } + void setMaximumY(qreal maxY); + +signals: + void requiredPointCountChanged(); + void minimumScaleChanged(); + void maximumScaleChanged(); + void minimumRotationChanged(); + void maximumRotationChanged(); + void minimumXChanged(); + void maximumXChanged(); + void minimumYChanged(); + void maximumYChanged(); + void pinchOriginChanged(); + void updated(); + +protected: + void onActiveChanged() override; + void handlePointerEventImpl(QQuickPointerEvent *event) override; + +private: + // properties + qreal m_activeScale; + qreal m_activeRotation; + QPointF m_activeTranslation; + QPointF m_centroid; + + qreal m_minimumScale; + qreal m_maximumScale; + + qreal m_minimumRotation; + qreal m_maximumRotation; + + qreal m_minimumX; + qreal m_maximumX; + qreal m_minimumY; + qreal m_maximumY; + + PinchOrigin m_pinchOrigin; + + // internal + qreal m_startScale; + qreal m_startRotation; + QPointF m_startCentroid; + qreal m_startDistance; + QPointF m_startPos; + + QVector<PointData> m_startAngles; + QMatrix4x4 m_startMatrix; + QQuickMatrix4x4 m_transform; + +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickPinchHandler) + +#endif // QQUICKPINCHHANDLER_H diff --git a/src/quick/handlers/qquickpointerdevicehandler.cpp b/src/quick/handlers/qquickpointerdevicehandler.cpp new file mode 100644 index 0000000000..03704ac09e --- /dev/null +++ b/src/quick/handlers/qquickpointerdevicehandler.cpp @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** 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 "qquickpointerdevicehandler_p.h" +#include <private/qquickitem_p.h> +#include <QMouseEvent> +#include <QDebug> + +QT_BEGIN_NAMESPACE + +/*! + An intermediate class (not registered as a QML type) + for handlers which allow filtering based on device type, + pointer type, or device-specific buttons (such as mouse or stylus buttons). + */ +QQuickPointerDeviceHandler::QQuickPointerDeviceHandler(QObject *parent) + : QQuickPointerHandler(parent) + , m_acceptedDevices(QQuickPointerDevice::AllDevices) + , m_acceptedPointerTypes(QQuickPointerDevice::AllPointerTypes) + , m_acceptedButtons(Qt::AllButtons) +{ +} + +QQuickPointerDeviceHandler::~QQuickPointerDeviceHandler() +{ +} + +void QQuickPointerDeviceHandler::setAcceptedDevices(QQuickPointerDevice::DeviceTypes acceptedDevices) +{ + if (m_acceptedDevices == acceptedDevices) + return; + + m_acceptedDevices = acceptedDevices; + emit acceptedDevicesChanged(); +} + +void QQuickPointerDeviceHandler::setAcceptedPointerTypes(QQuickPointerDevice::PointerTypes acceptedPointerTypes) +{ + if (m_acceptedPointerTypes == acceptedPointerTypes) + return; + + m_acceptedPointerTypes = acceptedPointerTypes; + emit acceptedPointerTypesChanged(); +} + +void QQuickPointerDeviceHandler::setAcceptedButtons(Qt::MouseButtons buttons) +{ + if (m_acceptedButtons == buttons) + return; + + m_acceptedButtons = buttons; + emit acceptedButtonsChanged(); +} + +bool QQuickPointerDeviceHandler::wantsPointerEvent(QQuickPointerEvent *event) +{ + if (!QQuickPointerHandler::wantsPointerEvent(event)) + return false; + qCDebug(lcPointerHandlerDispatch) << objectName() + << "checking device type" << m_acceptedDevices + << "pointer type" << m_acceptedPointerTypes + << "buttons" << m_acceptedButtons; + if ((event->device()->type() & m_acceptedDevices) == 0) + return false; + if ((event->device()->pointerType() & m_acceptedPointerTypes) == 0) + return false; + if (event->device()->pointerType() != QQuickPointerDevice::Finger && + (event->buttons() & m_acceptedButtons) == 0 && (event->button() & m_acceptedButtons) == 0) + return false; + return true; +} + +QT_END_NAMESPACE diff --git a/src/quick/handlers/qquickpointerdevicehandler_p.h b/src/quick/handlers/qquickpointerdevicehandler_p.h new file mode 100644 index 0000000000..bb12d3ccdb --- /dev/null +++ b/src/quick/handlers/qquickpointerdevicehandler_p.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QQUICKPOINTERDEVICEHANDLER_H +#define QQUICKPOINTERDEVICEHANDLER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qquickpointerhandler_p.h" + +QT_BEGIN_NAMESPACE + +class Q_AUTOTEST_EXPORT QQuickPointerDeviceHandler : public QQuickPointerHandler +{ + Q_OBJECT + Q_PROPERTY(QQuickPointerDevice::DeviceTypes acceptedDevices READ acceptedDevices WRITE setAcceptedDevices NOTIFY acceptedDevicesChanged) + Q_PROPERTY(QQuickPointerDevice::PointerTypes acceptedPointerTypes READ acceptedPointerTypes WRITE setAcceptedPointerTypes NOTIFY acceptedPointerTypesChanged) + Q_PROPERTY(Qt::MouseButtons acceptedButtons READ acceptedButtons WRITE setAcceptedButtons NOTIFY acceptedButtonsChanged) + +public: + QQuickPointerDeviceHandler(QObject *parent = 0); + ~QQuickPointerDeviceHandler(); + + QQuickPointerDevice::DeviceTypes acceptedDevices() const { return m_acceptedDevices; } + QQuickPointerDevice::PointerTypes acceptedPointerTypes() const { return m_acceptedPointerTypes; } + Qt::MouseButtons acceptedButtons() const { return m_acceptedButtons; } + +public slots: + void setAcceptedDevices(QQuickPointerDevice::DeviceTypes acceptedDevices); + void setAcceptedPointerTypes(QQuickPointerDevice::PointerTypes acceptedPointerTypes); + void setAcceptedButtons(Qt::MouseButtons buttons); + +Q_SIGNALS: + void acceptedDevicesChanged(); + void acceptedPointerTypesChanged(); + void acceptedButtonsChanged(); + +protected: + bool wantsPointerEvent(QQuickPointerEvent *event) override; + void setPressed(bool pressed); + +protected: + QQuickPointerDevice::DeviceTypes m_acceptedDevices; + QQuickPointerDevice::PointerTypes m_acceptedPointerTypes; + Qt::MouseButtons m_acceptedButtons; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickPointerDeviceHandler) + +#endif // QQUICKPOINTERDEVICEHANDLER_H diff --git a/src/quick/handlers/qquickpointerhandler.cpp b/src/quick/handlers/qquickpointerhandler.cpp new file mode 100644 index 0000000000..0398863ba3 --- /dev/null +++ b/src/quick/handlers/qquickpointerhandler.cpp @@ -0,0 +1,190 @@ +/**************************************************************************** +** +** 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 "qquickpointerhandler_p.h" + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcPointerHandlerDispatch, "qt.quick.handler.dispatch") + +/*! + \qmltype PointerHandler + \instantiates QQuickPointerHandler + \inqmlmodule QtQuick + \ingroup qtquick-handlers + \brief Handler for pointer events + + PointerHandler is a handler for pointer events regardless of source. + They may represent events from a touch, mouse or tablet device. +*/ + +QQuickPointerHandler::QQuickPointerHandler(QObject *parent) + : QObject(parent) + , m_currentEvent(nullptr) + , m_target(nullptr) + , m_enabled(true) + , m_active(false) + , m_targetExplicitlySet(false) +{ +} + +QQuickPointerHandler::~QQuickPointerHandler() +{ + QQuickItem *parItem = parentItem(); + if (parItem) { + QQuickItemPrivate *p = QQuickItemPrivate::get(parItem); + p->extra.value().pointerHandlers.removeOne(this); + } +} + +void QQuickPointerHandler::setGrab(QQuickEventPoint *point, bool grab) +{ + QQuickPointerHandler *oldGrabber = point->grabberPointerHandler(); + if (grab && oldGrabber != this) { + if (oldGrabber) + oldGrabber->handleGrabCancel(point); + point->setGrabberPointerHandler(this); + onGrabChanged(point); + emit grabChanged(point); + } else if (!grab && oldGrabber == this) { + point->setGrabberPointerHandler(nullptr); + onGrabChanged(point); + emit grabChanged(point); + } +} + +QPointF QQuickPointerHandler::eventPos(const QQuickEventPoint *point) const +{ + return (target() ? target()->mapFromScene(point->scenePos()) : point->scenePos()); +} + +bool QQuickPointerHandler::parentContains(const QQuickEventPoint *point) const +{ + if (point) { + if (QQuickItem *par = parentItem()) + return par->contains(par->mapFromScene(point->scenePos())); + } + return false; +} + +/*! + \qmlproperty QQuickPointerHandler::enabled + + If a PointerHandler is disabled, it will reject all events + and no signals will be emitted. + + TODO is it too extreme not even to emit pressed/updated/released? + or should we disable only the higher-level interpretation, in subclasses? +*/ +void QQuickPointerHandler::setEnabled(bool enabled) +{ + if (m_enabled == enabled) + return; + + m_enabled = enabled; + emit enabledChanged(); +} + +void QQuickPointerHandler::setTarget(QQuickItem *target) +{ + m_targetExplicitlySet = true; + if (m_target == target) + return; + + m_target = target; + emit targetChanged(); +} + +QQuickItem *QQuickPointerHandler::target() const +{ + if (!m_targetExplicitlySet) + return parentItem(); + return m_target; +} + +void QQuickPointerHandler::handlePointerEvent(QQuickPointerEvent *event) +{ + if (wantsPointerEvent(event)) + handlePointerEventImpl(event); + else + setActive(false); +} + +void QQuickPointerHandler::handleGrabCancel(QQuickEventPoint *point) +{ + qCDebug(lcPointerHandlerDispatch) << point; + Q_ASSERT(point); + setActive(false); + point->setAccepted(false); + emit canceled(point); +} + +bool QQuickPointerHandler::wantsPointerEvent(QQuickPointerEvent *event) +{ + Q_UNUSED(event) + return m_enabled; +} + +void QQuickPointerHandler::setActive(bool active) +{ + if (m_active != active) { + m_active = active; + onActiveChanged(); + emit activeChanged(); + } +} + +void QQuickPointerHandler::handlePointerEventImpl(QQuickPointerEvent *event) +{ + m_currentEvent = event; +} + +/*! + \qmlproperty QQuickPointerHandler::parent + + The \l Item which is the scope of the handler; the Item in which it was declared. + The handler will handle events on behalf of this Item, which means a + pointer event is relevant if at least one of its event points occurs within + the Item's interior. Initially \l target() is the same, but target() + can be reassigned. + + \sa QQuickPointerHandler::target(), QObject::parent() +*/ + +QT_END_NAMESPACE diff --git a/src/quick/handlers/qquickpointerhandler_p.h b/src/quick/handlers/qquickpointerhandler_p.h new file mode 100644 index 0000000000..85f1096410 --- /dev/null +++ b/src/quick/handlers/qquickpointerhandler_p.h @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QQUICKPOINTERHANDLER_H +#define QQUICKPOINTERHANDLER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qevent.h" + +#include <QtQuick/private/qquickevents_p_p.h> +#include <QtQuick/private/qquickitem_p.h> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(lcPointerHandlerDispatch) + +class Q_QUICK_PRIVATE_EXPORT QQuickPointerHandler : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) + Q_PROPERTY(bool active READ active NOTIFY activeChanged) + Q_PROPERTY(QQuickItem * target READ target WRITE setTarget NOTIFY targetChanged) + Q_PROPERTY(QQuickItem * parent READ parentItem CONSTANT) + +public: + QQuickPointerHandler(QObject *parent = 0); + virtual ~QQuickPointerHandler(); + +public: + bool enabled() const { return m_enabled; } + void setEnabled(bool enabled); + + bool active() const { return m_active; } + + QQuickItem *target() const; + void setTarget(QQuickItem *target); + + QQuickItem * parentItem() const { return static_cast<QQuickItem *>(QObject::parent()); } + + void handlePointerEvent(QQuickPointerEvent *event); + +Q_SIGNALS: + void enabledChanged(); + void activeChanged(); + void targetChanged(); + void grabChanged(QQuickEventPoint *point); + void canceled(QQuickEventPoint *point); + +protected: + QQuickPointerEvent *currentEvent() { return m_currentEvent; } + virtual bool wantsPointerEvent(QQuickPointerEvent *event); + virtual void handlePointerEventImpl(QQuickPointerEvent *event); + void setActive(bool active); + virtual void onActiveChanged() { } + virtual void onGrabChanged(QQuickEventPoint *) { } + void setGrab(QQuickEventPoint *point, bool grab); + virtual void handleGrabCancel(QQuickEventPoint *point); + QPointF eventPos(const QQuickEventPoint *point) const; + bool parentContains(const QQuickEventPoint *point) const; + +private: + QQuickPointerEvent *m_currentEvent; + QQuickItem *m_target; + bool m_enabled : 1; + bool m_active : 1; + bool m_targetExplicitlySet : 1; + + friend class QQuickEventPoint; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickPointerHandler) + +#endif // QQUICKPOINTERHANDLER_H diff --git a/src/quick/handlers/qquickpointersinglehandler.cpp b/src/quick/handlers/qquickpointersinglehandler.cpp new file mode 100644 index 0000000000..f39d8ac976 --- /dev/null +++ b/src/quick/handlers/qquickpointersinglehandler.cpp @@ -0,0 +1,180 @@ +/**************************************************************************** +** +** 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 "qquickpointersinglehandler_p.h" + +QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(DBG_TOUCH_TARGET) + +/*! + An intermediate class (not registered as a QML type) + for the most common handlers: those which expect only a single point. + wantsPointerEvent() will choose the first point which is inside the + \l target item, and return true as long as the event contains that point. + Override handleEventPoint() to implement a single-point handler. +*/ + +QQuickPointerSingleHandler::QQuickPointerSingleHandler(QObject *parent) + : QQuickPointerDeviceHandler(parent) + , m_pointId(0) + , m_rotation(0) + , m_pressure(0) +{ +} + +bool QQuickPointerSingleHandler::wantsPointerEvent(QQuickPointerEvent *event) +{ + if (!QQuickPointerDeviceHandler::wantsPointerEvent(event)) + return false; + if (m_pointId) { + // We already know which one we want, so check whether it's there. + // It's expected to be an update or a release. + // If we no longer want it, cancel the grab. + if (auto point = event->pointById(m_pointId)) { + if (wantsEventPoint(point)) { + point->setAccepted(); + return true; + } else if (point->grabber() == this) { + point->cancelGrab(); + } + } else { + qCWarning(DBG_TOUCH_TARGET) << this << "pointId" << m_pointId + << "is missing from current event, but was neither canceled nor released"; + return false; + } + } else { + // We have not yet chosen a point; choose the first one for which wantsEventPoint() returns true. + int c = event->pointCount(); + for (int i = 0; i < c && !m_pointId; ++i) { + QQuickEventPoint *p = event->point(i); + if (!p->grabber() && wantsEventPoint(p)) { + m_pointId = p->pointId(); + p->setAccepted(); + } + } + } + return m_pointId; +} + +void QQuickPointerSingleHandler::handlePointerEventImpl(QQuickPointerEvent *event) +{ + QQuickPointerDeviceHandler::handlePointerEventImpl(event); + QQuickEventPoint *currentPoint = event->pointById(m_pointId); + Q_ASSERT(currentPoint); + bool grab = false; + if (!m_pointId || !currentPoint->isAccepted() || currentPoint->state() == QQuickEventPoint::Released) { + reset(); + grab = false; + } else { + if (event->asPointerTouchEvent()) { + QQuickEventTouchPoint *tp = static_cast<QQuickEventTouchPoint *>(currentPoint); + m_uniquePointId = tp->uniqueId(); + m_rotation = tp->rotation(); + m_pressure = tp->pressure(); + m_ellipseDiameters = tp->ellipseDiameters(); + } else if (event->asPointerTabletEvent()) { + // TODO + } else { + m_uniquePointId = event->device()->uniqueId(); + m_rotation = 0; + m_pressure = event->buttons() ? 1 : 0; + m_ellipseDiameters = QSizeF(); + } + m_pos = currentPoint->pos(); + m_velocity = currentPoint->velocity(); + handleEventPoint(currentPoint); + if (currentPoint->state() == QQuickEventPoint::Pressed) { + m_pressPos = currentPoint->pos(); + emit pointIdChanged(); + } + setPressedButtons(event->buttons()); + grab = true; + } + emit eventPointHandled(); + // TODO don't call setGrab(true) here, only setGrab(false), because concrete subclasses may + // wait for the drag threshold to be exceeded, for example + setGrab(currentPoint, grab); +} + +bool QQuickPointerSingleHandler::wantsEventPoint(QQuickEventPoint *point) +{ + return parentContains(point); +} + +void QQuickPointerSingleHandler::handleGrabCancel(QQuickEventPoint *point) +{ + QQuickPointerHandler::handleGrabCancel(point); + reset(); +} + +void QQuickPointerSingleHandler::onGrabChanged(QQuickEventPoint *point) +{ + bool grabbing = (point->grabber() == this); + setActive(grabbing); + if (grabbing) + m_sceneGrabPos = point->sceneGrabPos(); + emit singlePointGrabChanged(); +} + +void QQuickPointerSingleHandler::setPressedButtons(Qt::MouseButtons buttons) +{ + if (buttons != m_pressedButtons) { + m_pressedButtons = buttons; + emit pressedButtonsChanged(); + } +} + +void QQuickPointerSingleHandler::reset() +{ + bool pointIdChange = m_pointId != 0; + m_pointId = 0; + m_uniquePointId = QPointingDeviceUniqueId(); + m_pos = QPointF(); + m_pressPos = QPointF(); + m_sceneGrabPos = QPointF(); + m_velocity = QVector2D(); + m_rotation = 0; + m_pressure = 0; + m_ellipseDiameters = QSizeF(); + setPressedButtons(Qt::NoButton); + if (pointIdChange) + emit pointIdChanged(); +} + +QT_END_NAMESPACE diff --git a/src/quick/handlers/qquickpointersinglehandler_p.h b/src/quick/handlers/qquickpointersinglehandler_p.h new file mode 100644 index 0000000000..8858b2c080 --- /dev/null +++ b/src/quick/handlers/qquickpointersinglehandler_p.h @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QQUICKPOINTERSINGLEHANDLER_H +#define QQUICKPOINTERSINGLEHANDLER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qquickpointerdevicehandler_p.h" + +QT_BEGIN_NAMESPACE + +class Q_QUICK_PRIVATE_EXPORT QQuickPointerSingleHandler : public QQuickPointerDeviceHandler +{ + Q_OBJECT + Q_PROPERTY(quint64 pointId READ pointId NOTIFY pointIdChanged) + Q_PROPERTY(QPointingDeviceUniqueId uniquePointId READ uniquePointId NOTIFY pointIdChanged) + Q_PROPERTY(QPointF pos READ pos NOTIFY eventPointHandled) + Q_PROPERTY(QPointF scenePos READ scenePos NOTIFY eventPointHandled) + Q_PROPERTY(QPointF pressPos READ pressPos NOTIFY pressedButtonsChanged) + Q_PROPERTY(QPointF scenePressPos READ scenePressPos NOTIFY pressedButtonsChanged) + Q_PROPERTY(QPointF sceneGrabPos READ sceneGrabPos NOTIFY singlePointGrabChanged) + Q_PROPERTY(Qt::MouseButtons pressedButtons READ pressedButtons NOTIFY pressedButtonsChanged) + Q_PROPERTY(QVector2D velocity READ velocity NOTIFY eventPointHandled) + Q_PROPERTY(qreal rotation READ rotation NOTIFY eventPointHandled) + Q_PROPERTY(qreal pressure READ pressure NOTIFY eventPointHandled) + Q_PROPERTY(QSizeF ellipseDiameters READ ellipseDiameters NOTIFY eventPointHandled) + +public: + QQuickPointerSingleHandler(QObject *parent = 0); + virtual ~QQuickPointerSingleHandler() { } + + Qt::MouseButtons pressedButtons() const { return m_pressedButtons; } + QPointF pressPos() const { return m_pressPos; } + QPointF scenePressPos() const { return parentItem()->mapToScene(m_pressPos); } + QPointF sceneGrabPos() const { return m_sceneGrabPos; } + QPointF pos() const { return m_pos; } + QPointF scenePos() const { return parentItem()->mapToScene(m_pos); } + QVector2D velocity() const { return m_velocity; } + qreal rotation() const { return m_rotation; } + qreal pressure() const { return m_pressure; } + QSizeF ellipseDiameters() const { return m_ellipseDiameters; } + QPointingDeviceUniqueId uniquePointId() const { return m_uniquePointId; } + +signals: + void pointIdChanged(); + void pressedButtonsChanged(); + void singlePointGrabChanged(); // QQuickPointerHandler::grabChanged signal can't be a property notifier here + void eventPointHandled(); + +protected: + bool wantsPointerEvent(QQuickPointerEvent *event) override; + virtual bool wantsEventPoint(QQuickEventPoint *point); + void handlePointerEventImpl(QQuickPointerEvent *event) override; + virtual void handleEventPoint(QQuickEventPoint *point) = 0; + quint64 pointId() const { return m_pointId; } + QQuickEventPoint *currentPoint(QQuickPointerEvent *ev) { return ev->pointById(m_pointId); } + void handleGrabCancel(QQuickEventPoint *point) override; + void onGrabChanged(QQuickEventPoint *point) override; + +private: + void setPressedButtons(Qt::MouseButtons buttons); + void reset(); + +private: + quint64 m_pointId; + QPointingDeviceUniqueId m_uniquePointId; + Qt::MouseButtons m_pressedButtons; + QPointF m_pos; + QPointF m_pressPos; + QPointF m_sceneGrabPos; + QVector2D m_velocity; + qreal m_rotation; + qreal m_pressure; + QSizeF m_ellipseDiameters; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickPointerSingleHandler) + +#endif // QQUICKPOINTERSINGLEHANDLER_H diff --git a/src/quick/items/qquickevents.cpp b/src/quick/items/qquickevents.cpp index b6c45c40a8..0c18cb4104 100644 --- a/src/quick/items/qquickevents.cpp +++ b/src/quick/items/qquickevents.cpp @@ -38,8 +38,10 @@ ****************************************************************************/ #include "qquickevents_p_p.h" +#include <QtCore/qmap.h> #include <QtGui/private/qguiapplication_p.h> #include <QtQuick/private/qquickitem_p.h> +#include <QtQuick/private/qquickpointerhandler_p.h> #include <QtQuick/private/qquickwindow_p.h> #include <private/qdebug_p.h> @@ -448,7 +450,15 @@ Item { typedef QHash<QTouchDevice *, QQuickPointerDevice *> PointerDeviceForTouchDeviceHash; Q_GLOBAL_STATIC(PointerDeviceForTouchDeviceHash, g_touchDevices) -Q_GLOBAL_STATIC_WITH_ARGS(QQuickPointerDevice, g_genericMouseDevice, +struct ConstructableQQuickPointerDevice : public QQuickPointerDevice +{ + ConstructableQQuickPointerDevice(DeviceType devType, PointerType pType, Capabilities caps, + int maxPoints, int buttonCount, const QString &name, + qint64 uniqueId = 0) + : QQuickPointerDevice(devType, pType, caps, maxPoints, buttonCount, name, uniqueId) {} + +}; +Q_GLOBAL_STATIC_WITH_ARGS(ConstructableQQuickPointerDevice, g_genericMouseDevice, (QQuickPointerDevice::Mouse, QQuickPointerDevice::GenericPointer, QQuickPointerDevice::Position | QQuickPointerDevice::Scroll | QQuickPointerDevice::Hover, @@ -505,35 +515,107 @@ QQuickPointerDevice *QQuickPointerDevice::tabletDevice(qint64 id) return nullptr; } -void QQuickEventPoint::reset(Qt::TouchPointState state, QPointF scenePos, quint64 pointId, ulong timestamp) +void QQuickEventPoint::reset(Qt::TouchPointState state, const QPointF &scenePos, quint64 pointId, ulong timestamp, const QVector2D &velocity) { m_scenePos = scenePos; - m_pointId = pointId; + if (m_pointId != pointId) { + if (m_grabber) { + qWarning() << m_grabber << "failed to ungrab previous point" << m_pointId; + cancelGrab(); + } + m_pointId = pointId; + } m_valid = true; m_accept = false; m_state = static_cast<QQuickEventPoint::State>(state); m_timestamp = timestamp; - if (state == Qt::TouchPointPressed) + if (state == Qt::TouchPointPressed) { m_pressTimestamp = timestamp; - // TODO calculate velocity + m_scenePressPos = scenePos; + } + m_velocity = (Q_LIKELY(velocity.isNull()) ? estimatedVelocity() : velocity); } -QQuickItem *QQuickEventPoint::grabber() const +void QQuickEventPoint::localize(QQuickItem *target) +{ + if (target) + m_pos = target->mapFromScene(scenePos()); + else + m_pos = QPointF(); +} + +void QQuickEventPoint::invalidate() +{ + m_valid = false; + m_pointId = 0; +} + +QObject *QQuickEventPoint::grabber() const { return m_grabber.data(); } -void QQuickEventPoint::setGrabber(QQuickItem *grabber) +void QQuickEventPoint::setGrabber(QObject *grabber) +{ + if (QQuickPointerHandler *phGrabber = qmlobject_cast<QQuickPointerHandler *>(grabber)) + setGrabberPointerHandler(phGrabber); + else + setGrabberItem(static_cast<QQuickItem *>(grabber)); +} + +QQuickItem *QQuickEventPoint::grabberItem() const { - if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled()) && m_grabber.data() != grabber) { - auto device = static_cast<const QQuickPointerEvent *>(parent())->device(); - static const QMetaEnum stateMetaEnum = metaObject()->enumerator(metaObject()->indexOfEnumerator("State")); - QString deviceName = (device ? device->name() : QLatin1String("null device")); - deviceName.resize(16, ' '); // shorten, and align in case of sequential output - qCDebug(lcPointerGrab) << deviceName << "point" << hex << m_pointId << stateMetaEnum.valueToKey(state()) - << ": grab" << m_grabber << "->" << grabber; + return (m_grabberIsHandler ? nullptr : static_cast<QQuickItem *>(m_grabber.data())); +} + +void QQuickEventPoint::setGrabberItem(QQuickItem *grabber) +{ + if (grabber != m_grabber.data()) { + if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled())) { + auto device = static_cast<const QQuickPointerEvent *>(parent())->device(); + static const QMetaEnum stateMetaEnum = metaObject()->enumerator(metaObject()->indexOfEnumerator("State")); + QString deviceName = (device ? device->name() : QLatin1String("null device")); + deviceName.resize(16, ' '); // shorten, and align in case of sequential output + qCDebug(lcPointerGrab) << deviceName << "point" << hex << m_pointId << stateMetaEnum.valueToKey(state()) + << ": grab" << m_grabber << "->" << grabber; + } + m_grabber = QPointer<QObject>(grabber); + m_grabberIsHandler = false; + m_sceneGrabPos = m_scenePos; } - m_grabber = QPointer<QQuickItem>(grabber); +} + +QQuickPointerHandler *QQuickEventPoint::grabberPointerHandler() const +{ + return (m_grabberIsHandler ? static_cast<QQuickPointerHandler *>(m_grabber.data()) : nullptr); +} + +void QQuickEventPoint::setGrabberPointerHandler(QQuickPointerHandler *grabber) +{ + if (grabber != m_grabber.data()) { + if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled())) { + auto device = static_cast<const QQuickPointerEvent *>(parent())->device(); + static const QMetaEnum stateMetaEnum = metaObject()->enumerator(metaObject()->indexOfEnumerator("State")); + QString deviceName = (device ? device->name() : QLatin1String("null device")); + deviceName.resize(16, ' '); // shorten, and align in case of sequential output + qCDebug(lcPointerGrab) << deviceName << "point" << hex << m_pointId << stateMetaEnum.valueToKey(state()) + << ": grab" << m_grabber << "->" << grabber; + } + m_grabber = QPointer<QObject>(grabber); + m_grabberIsHandler = true; + m_sceneGrabPos = m_scenePos; + } +} + +void QQuickEventPoint::cancelGrab() +{ + if (m_grabber.isNull()) { + qWarning("cancelGrab: no grabber"); + return; + } + if (auto handler = grabberPointerHandler()) + handler->handleGrabCancel(this); + m_grabber.clear(); } void QQuickEventPoint::setAccepted(bool accepted) @@ -544,18 +626,86 @@ void QQuickEventPoint::setAccepted(bool accepted) } } +bool QQuickEventPoint::isDraggedOverThreshold() const +{ + QPointF delta = scenePos() - scenePressPos(); + return (QQuickWindowPrivate::dragOverThreshold(delta.x(), Qt::XAxis, this) || + QQuickWindowPrivate::dragOverThreshold(delta.y(), Qt::YAxis, this)); +} + QQuickEventTouchPoint::QQuickEventTouchPoint(QQuickPointerTouchEvent *parent) : QQuickEventPoint(parent), m_rotation(0), m_pressure(0) {} void QQuickEventTouchPoint::reset(const QTouchEvent::TouchPoint &tp, ulong timestamp) { - QQuickEventPoint::reset(tp.state(), tp.scenePos(), tp.id(), timestamp); + QQuickEventPoint::reset(tp.state(), tp.scenePos(), tp.id(), timestamp, tp.velocity()); m_rotation = tp.rotation(); m_pressure = tp.pressure(); + m_ellipseDiameters = tp.ellipseDiameters(); m_uniqueId = tp.uniqueId(); } +struct PointVelocityData { + QVector2D velocity; + QPointF pos; + ulong timestamp; +}; + +typedef QMap<quint64, PointVelocityData*> PointDataForPointIdMap; +Q_GLOBAL_STATIC(PointDataForPointIdMap, g_previousPointData) +static const int PointVelocityAgeLimit = 500; // milliseconds + +/*! + * \interal + * \brief Estimates the velocity based on a weighted average of all previous velocities. + * The older the velocity is, the less significant it becomes for the estimate. + * \return + */ +QVector2D QQuickEventPoint::estimatedVelocity() const +{ + PointVelocityData *prevPoint = g_previousPointData->value(m_pointId); + if (!prevPoint) { + // cleanup events older than PointVelocityAgeLimit + auto end = g_previousPointData->end(); + for (auto it = g_previousPointData->begin(); it != end; ) { + PointVelocityData *data = it.value(); + if (m_timestamp - data->timestamp > PointVelocityAgeLimit) { + it = g_previousPointData->erase(it); + delete data; + } else { + ++it; + } + } + // TODO optimize: stop this dynamic memory thrashing + prevPoint = new PointVelocityData; + prevPoint->velocity = QVector2D(); + prevPoint->timestamp = 0; + prevPoint->pos = QPointF(); + g_previousPointData->insert(m_pointId, prevPoint); + } + if (prevPoint) { + const ulong timeElapsed = m_timestamp - prevPoint->timestamp; + if (timeElapsed == 0) // in case we call estimatedVelocity() twice on the same QQuickEventPoint + return m_velocity; + + QVector2D newVelocity; + if (prevPoint->timestamp != 0) + newVelocity = QVector2D(m_scenePos - prevPoint->pos)/timeElapsed; + + // VERY simple kalman filter: does a weighted average + // where the older velocities get less and less significant + static const float KalmanGain = 0.7f; + QVector2D filteredVelocity = newVelocity * KalmanGain + m_velocity * (1.0f - KalmanGain); + + prevPoint->velocity = filteredVelocity; + prevPoint->pos = m_scenePos; + prevPoint->timestamp = m_timestamp; + return filteredVelocity; + } + return QVector2D(); +} + /*! \internal \class QQuickPointerEvent @@ -598,10 +748,15 @@ QQuickPointerEvent *QQuickPointerMouseEvent::reset(QEvent *event) default: break; } - m_mousePoint->reset(state, ev->windowPos(), 0, ev->timestamp()); // mouse is 0 + m_mousePoint->reset(state, ev->windowPos(), quint64(1) << 24, ev->timestamp()); // mouse has device ID 1 return this; } +void QQuickPointerMouseEvent::localize(QQuickItem *target) +{ + m_mousePoint->localize(target); +} + QQuickPointerEvent *QQuickPointerTouchEvent::reset(QEvent *event) { auto ev = static_cast<QTouchEvent*>(event); @@ -621,11 +776,11 @@ QQuickPointerEvent *QQuickPointerTouchEvent::reset(QEvent *event) m_touchPoints.insert(i, new QQuickEventTouchPoint(this)); // Make sure the grabbers are right from one event to the next - QVector<QQuickItem*> grabbers; + QVector<QObject*> grabbers; // Copy all grabbers, because the order of points might have changed in the event. // The ID is all that we can rely on (release might remove the first point etc). for (int i = 0; i < newPointCount; ++i) { - QQuickItem *grabber = nullptr; + QObject *grabber = nullptr; if (auto point = pointById(tps.at(i).id())) grabber = point->grabber(); grabbers.append(grabber); @@ -637,7 +792,7 @@ QQuickPointerEvent *QQuickPointerTouchEvent::reset(QEvent *event) if (point->state() == QQuickEventPoint::Pressed) { if (grabbers.at(i)) qWarning() << "TouchPointPressed without previous release event" << point; - point->setGrabber(nullptr); + point->setGrabberItem(nullptr); } else { point->setGrabber(grabbers.at(i)); } @@ -646,6 +801,12 @@ QQuickPointerEvent *QQuickPointerTouchEvent::reset(QEvent *event) return this; } +void QQuickPointerTouchEvent::localize(QQuickItem *target) +{ + for (auto point : qAsConst(m_touchPoints)) + point->localize(target); +} + QQuickEventPoint *QQuickPointerMouseEvent::point(int i) const { if (i == 0) return m_mousePoint; @@ -660,7 +821,7 @@ QQuickEventPoint *QQuickPointerTouchEvent::point(int i) const { QQuickEventPoint::QQuickEventPoint(QQuickPointerEvent *parent) : QObject(parent), m_pointId(0), m_grabber(nullptr), m_timestamp(0), m_pressTimestamp(0), - m_state(QQuickEventPoint::Released), m_valid(false), m_accept(false) + m_state(QQuickEventPoint::Released), m_valid(false), m_accept(false), m_grabberIsHandler(false) { Q_UNUSED(m_reserved); } @@ -681,16 +842,16 @@ QMouseEvent *QQuickPointerMouseEvent::asMouseEvent(const QPointF &localPos) cons return event; } -QVector<QQuickItem *> QQuickPointerMouseEvent::grabbers() const +QVector<QObject *> QQuickPointerMouseEvent::grabbers() const { - QVector<QQuickItem *> result; - if (QQuickItem *grabber = m_mousePoint->grabber()) + QVector<QObject *> result; + if (QObject *grabber = m_mousePoint->grabber()) result << grabber; return result; } void QQuickPointerMouseEvent::clearGrabbers() const { - m_mousePoint->setGrabber(nullptr); + m_mousePoint->setGrabberItem(nullptr); } bool QQuickPointerMouseEvent::isPressEvent() const @@ -708,12 +869,12 @@ bool QQuickPointerTouchEvent::allPointsAccepted() const { return true; } -QVector<QQuickItem *> QQuickPointerTouchEvent::grabbers() const +QVector<QObject *> QQuickPointerTouchEvent::grabbers() const { - QVector<QQuickItem *> result; + QVector<QObject *> result; for (int i = 0; i < m_pointCount; ++i) { auto point = m_touchPoints.at(i); - if (QQuickItem *grabber = point->grabber()) { + if (QObject *grabber = point->grabber()) { if (!result.contains(grabber)) result << grabber; } diff --git a/src/quick/items/qquickevents_p_p.h b/src/quick/items/qquickevents_p_p.h index cf6f83e5b1..4a78e98705 100644 --- a/src/quick/items/qquickevents_p_p.h +++ b/src/quick/items/qquickevents_p_p.h @@ -68,6 +68,7 @@ class QQuickPointerEvent; class QQuickPointerMouseEvent; class QQuickPointerTabletEvent; class QQuickPointerTouchEvent; +class QQuickPointerHandler; class QQuickKeyEvent : public QObject { @@ -251,12 +252,18 @@ private: class Q_QUICK_PRIVATE_EXPORT QQuickEventPoint : public QObject { Q_OBJECT + Q_PROPERTY(QQuickPointerEvent *event READ pointerEvent) + Q_PROPERTY(QPointF pos READ pos) Q_PROPERTY(QPointF scenePos READ scenePos) + Q_PROPERTY(QPointF scenePressPos READ scenePressPos) + Q_PROPERTY(QPointF sceneGrabPos READ sceneGrabPos) + Q_PROPERTY(QVector2D velocity READ velocity) Q_PROPERTY(State state READ state) Q_PROPERTY(quint64 pointId READ pointId) Q_PROPERTY(qreal timeHeld READ timeHeld) + Q_PROPERTY(QVector2D velocity READ velocity) Q_PROPERTY(bool accepted READ isAccepted WRITE setAccepted) - Q_PROPERTY(QQuickItem *grabber READ grabber WRITE setGrabber) + Q_PROPERTY(QObject *grabber READ grabber WRITE setGrabber) public: enum State { @@ -270,31 +277,53 @@ public: QQuickEventPoint(QQuickPointerEvent *parent); - void reset(Qt::TouchPointState state, QPointF scenePos, quint64 pointId, ulong timestamp); + void reset(Qt::TouchPointState state, const QPointF &scenePos, quint64 pointId, ulong timestamp, const QVector2D &velocity = QVector2D()); + void localize(QQuickItem *target); - void invalidate() { m_valid = false; } + void invalidate(); QQuickPointerEvent *pointerEvent() const; + QPointF pos() const { return m_pos; } QPointF scenePos() const { return m_scenePos; } + QPointF scenePressPos() const { return m_scenePressPos; } + QPointF sceneGrabPos() const { return m_sceneGrabPos; } + QVector2D velocity() const { return m_velocity; } State state() const { return m_state; } quint64 pointId() const { return m_pointId; } bool isValid() const { return m_valid; } qreal timeHeld() const { return (m_timestamp - m_pressTimestamp) / 1000.0; } bool isAccepted() const { return m_accept; } void setAccepted(bool accepted = true); - QQuickItem *grabber() const; - void setGrabber(QQuickItem *grabber); + bool isDraggedOverThreshold() const; + QObject *grabber() const; + void setGrabber(QObject *grabber); + + QQuickItem *grabberItem() const; + void setGrabberItem(QQuickItem *grabber); + + QQuickPointerHandler *grabberPointerHandler() const; + void setGrabberPointerHandler(QQuickPointerHandler *grabber); + + Q_INVOKABLE void cancelGrab(); + +private: + QVector2D estimatedVelocity() const; private: + QPointF m_pos; QPointF m_scenePos; + QPointF m_scenePressPos; + QPointF m_sceneGrabPos; + QVector2D m_velocity; quint64 m_pointId; - QPointer<QQuickItem> m_grabber; + QPointer<QObject> m_grabber; ulong m_timestamp; ulong m_pressTimestamp; State m_state; bool m_valid : 1; bool m_accept : 1; - int m_reserved : 30; + bool m_grabberIsHandler : 1; + int m_reserved : 29; Q_DISABLE_COPY(QQuickEventPoint) }; @@ -304,6 +333,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickEventTouchPoint : public QQuickEventPoint Q_OBJECT Q_PROPERTY(qreal rotation READ rotation) Q_PROPERTY(qreal pressure READ pressure) + Q_PROPERTY(QSizeF ellipseDiameters READ ellipseDiameters) Q_PROPERTY(QPointingDeviceUniqueId uniqueId READ uniqueId) public: @@ -313,11 +343,13 @@ public: qreal rotation() const { return m_rotation; } qreal pressure() const { return m_pressure; } + QSizeF ellipseDiameters() const { return m_ellipseDiameters; } QPointingDeviceUniqueId uniqueId() const { return m_uniqueId; } private: qreal m_rotation; qreal m_pressure; + QSizeF m_ellipseDiameters; QPointingDeviceUniqueId m_uniqueId; Q_DISABLE_COPY(QQuickEventTouchPoint) @@ -350,6 +382,7 @@ public: // property accessors public: // helpers for C++ only (during event delivery) virtual QQuickPointerEvent *reset(QEvent *ev) = 0; + virtual void localize(QQuickItem *target) = 0; virtual bool isPressEvent() const = 0; virtual QQuickPointerMouseEvent *asPointerMouseEvent() { return nullptr; } @@ -367,7 +400,7 @@ public: // helpers for C++ only (during event delivery) virtual int pointCount() const = 0; virtual QQuickEventPoint *point(int i) const = 0; virtual QQuickEventPoint *pointById(quint64 pointId) const = 0; - virtual QVector<QQuickItem *> grabbers() const = 0; + virtual QVector<QObject *> grabbers() const = 0; virtual void clearGrabbers() const = 0; ulong timestamp() const { return m_event->timestamp(); } @@ -389,6 +422,7 @@ public: : QQuickPointerEvent(parent), m_mousePoint(new QQuickEventPoint(this)) { } QQuickPointerEvent *reset(QEvent *) override; + void localize(QQuickItem *target) override; bool isPressEvent() const override; QQuickPointerMouseEvent *asPointerMouseEvent() override { return this; } const QQuickPointerMouseEvent *asPointerMouseEvent() const override { return this; } @@ -396,7 +430,7 @@ public: QQuickEventPoint *point(int i) const override; QQuickEventPoint *pointById(quint64 pointId) const override; bool allPointsAccepted() const override; - QVector<QQuickItem *> grabbers() const override; + QVector<QObject *> grabbers() const override; void clearGrabbers() const override; QMouseEvent *asMouseEvent(const QPointF& localPos) const; @@ -418,6 +452,7 @@ public: { } QQuickPointerEvent *reset(QEvent *) override; + void localize(QQuickItem *target) override; bool isPressEvent() const override; QQuickPointerTouchEvent *asPointerTouchEvent() override { return this; } const QQuickPointerTouchEvent *asPointerTouchEvent() const override { return this; } @@ -426,7 +461,7 @@ public: QQuickEventPoint *pointById(quint64 pointId) const override; const QTouchEvent::TouchPoint *touchPointById(int pointId) const; bool allPointsAccepted() const override; - QVector<QQuickItem *> grabbers() const override; + QVector<QObject *> grabbers() const override; void clearGrabbers() const override; QMouseEvent *syntheticMouseEvent(int pointID, QQuickItem *relativeTo) const; @@ -497,21 +532,6 @@ public: Q_ENUM(CapabilityFlag) Q_FLAG(Capabilities) - QQuickPointerDevice(DeviceType devType, PointerType pType, Capabilities caps, int maxPoints, int buttonCount, const QString &name, qint64 uniqueId = 0) - : m_deviceType(devType), m_pointerType(pType), m_capabilities(caps) - , m_maximumTouchPoints(maxPoints), m_buttonCount(buttonCount), m_name(name) - , m_uniqueId(QPointingDeviceUniqueId::fromNumericId(uniqueId)), m_event(nullptr) - { - if (m_deviceType == Mouse) { - m_event = new QQuickPointerMouseEvent; - } else if (m_deviceType == TouchScreen || m_deviceType == TouchPad) { - m_event = new QQuickPointerTouchEvent; - } else { - Q_ASSERT(false); - } - } - - ~QQuickPointerDevice() { delete m_event; } DeviceType type() const { return m_deviceType; } PointerType pointerType() const { return m_pointerType; } Capabilities capabilities() const { return m_capabilities; } @@ -528,6 +548,22 @@ public: static QQuickPointerDevice *tabletDevice(qint64); private: + QQuickPointerDevice(DeviceType devType, PointerType pType, Capabilities caps, int maxPoints, int buttonCount, const QString &name, qint64 uniqueId = 0) + : m_deviceType(devType), m_pointerType(pType), m_capabilities(caps) + , m_maximumTouchPoints(maxPoints), m_buttonCount(buttonCount), m_name(name) + , m_uniqueId(QPointingDeviceUniqueId::fromNumericId(uniqueId)), m_event(nullptr) + { + if (m_deviceType == Mouse) { + m_event = new QQuickPointerMouseEvent; + } else if (m_deviceType == TouchScreen || m_deviceType == TouchPad) { + m_event = new QQuickPointerTouchEvent; + } else { + Q_ASSERT(false); + } + } + ~QQuickPointerDevice() { delete m_event; } + +private: DeviceType m_deviceType; PointerType m_pointerType; Capabilities m_capabilities; @@ -539,6 +575,7 @@ private: QQuickPointerEvent *m_event; Q_DISABLE_COPY(QQuickPointerDevice) + friend struct ConstructableQQuickPointerDevice; }; Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickPointerDevice::DeviceTypes) diff --git a/src/quick/items/qquickflickable.cpp b/src/quick/items/qquickflickable.cpp index 4ab604b30f..45bb2a367d 100644 --- a/src/quick/items/qquickflickable.cpp +++ b/src/quick/items/qquickflickable.cpp @@ -44,6 +44,7 @@ #include "qquickwindow_p.h" #include "qquickevents_p_p.h" +#include <QtQuick/private/qquickpointerhandler_p.h> #include <QtQuick/private/qquicktransition_p.h> #include <private/qqmlglobal_p.h> diff --git a/src/quick/items/qquickitem.cpp b/src/quick/items/qquickitem.cpp index f7b9a58329..98cc3112df 100644 --- a/src/quick/items/qquickitem.cpp +++ b/src/quick/items/qquickitem.cpp @@ -67,6 +67,7 @@ #include <QtQuick/private/qquickstate_p.h> #include <private/qquickitem_p.h> #include <QtQuick/private/qquickaccessibleattached_p.h> +#include <QtQuick/private/qquickpointerhandler_p.h> #include <private/qv4engine_p.h> #include <private/qv4object_p.h> @@ -86,6 +87,7 @@ QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(DBG_MOUSE_TARGET) Q_DECLARE_LOGGING_CATEGORY(DBG_HOVER_TRACE) +Q_DECLARE_LOGGING_CATEGORY(lcPointerHandlerDispatch) void debugFocusTree(QQuickItem *item, QQuickItem *scope = 0, int depth = 1) { @@ -3219,7 +3221,14 @@ void QQuickItemPrivate::data_append(QQmlListProperty<QObject> *prop, QObject *o) } else { if (o->inherits("QGraphicsItem")) qWarning("Cannot add a QtQuick 1.0 item (%s) into a QtQuick 2.0 scene!", o->metaObject()->className()); - else { + else if (QQuickPointerHandler *pointerHandler = qmlobject_cast<QQuickPointerHandler *>(o)) { + Q_ASSERT(pointerHandler->parentItem() == that); + // Accept all buttons, and leave filtering to pointerEvent() and/or user JS, + // because there can be multiple handlers... + that->setAcceptedMouseButtons(Qt::AllButtons); + QQuickItemPrivate *p = QQuickItemPrivate::get(that); + p->extra.value().pointerHandlers.append(pointerHandler); + } else { QQuickWindow *thisWindow = qmlobject_cast<QQuickWindow *>(o); QQuickItem *item = that; QQuickWindow *itemWindow = that->window(); @@ -5082,6 +5091,17 @@ void QQuickItemPrivate::deliverShortcutOverrideEvent(QKeyEvent *event) } } +void QQuickItemPrivate::handlePointerEvent(QQuickPointerEvent *event) +{ + Q_Q(QQuickItem); + if (extra.isAllocated()) { + for (QQuickPointerHandler *handler : extra->pointerHandlers) { + qCDebug(lcPointerHandlerDispatch) << " delivering" << event << "to" << handler << "on" << q; + handler->handlePointerEvent(event); + } + } +} + /*! Called when \a change occurs for this item. diff --git a/src/quick/items/qquickitem_p.h b/src/quick/items/qquickitem_p.h index c0c9bd46bd..e7ead7fa87 100644 --- a/src/quick/items/qquickitem_p.h +++ b/src/quick/items/qquickitem_p.h @@ -87,6 +87,7 @@ class QQuickItemKeyFilter; class QQuickLayoutMirroringAttached; class QQuickEnterKeyAttached; class QQuickScreenAttached; +class QQuickPointerHandler; class QQuickContents : public QQuickItemChangeListener { @@ -344,6 +345,7 @@ public: QQuickLayoutMirroringAttached* layoutDirectionAttached; QQuickEnterKeyAttached *enterKeyAttached; QQuickItemKeyFilter *keyHandler; + QVector<QQuickPointerHandler *> pointerHandlers; #if QT_CONFIG(quick_shadereffect) mutable QQuickItemLayer *layer; #endif @@ -561,6 +563,8 @@ public: #endif void deliverShortcutOverrideEvent(QKeyEvent *); + virtual void handlePointerEvent(QQuickPointerEvent *); + bool isTransparentForPositioner() const; void setTransparentForPositioner(bool trans); diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp index 660c5f8067..fa3093c245 100644 --- a/src/quick/items/qquickwindow.cpp +++ b/src/quick/items/qquickwindow.cpp @@ -46,6 +46,7 @@ #include "qquickevents_p_p.h" #include <private/qquickdrag_p.h> +#include <private/qquickpointerhandler_p.h> #include <QtQuick/private/qsgrenderer_p.h> #include <QtQuick/private/qsgtexture_p.h> @@ -659,7 +660,7 @@ bool QQuickWindowPrivate::deliverTouchAsMouse(QQuickItem *item, QQuickPointerEve if (!q->mouseGrabberItem()) item->grabMouse(); auto pointerEventPoint = pointerEvent->pointById(p.id()); - pointerEventPoint->setGrabber(item); + pointerEventPoint->setGrabberItem(item); if (checkIfDoubleClicked(event->timestamp())) { QScopedPointer<QMouseEvent> mouseDoubleClick(touchToMouseEvent(QEvent::MouseButtonDblClick, p, event, item, false)); @@ -747,11 +748,11 @@ void QQuickWindowPrivate::setMouseGrabber(QQuickItem *grabber) qCDebug(DBG_TOUCH_TARGET) << "TP (mouse)" << touchMouseId << "->" << q->mouseGrabberItem(); auto point = touchMouseDevice->pointerEvent()->pointById(touchMouseId); if (point) - point->setGrabber(grabber); + point->setGrabberItem(grabber); } else { QQuickPointerEvent *event = QQuickPointerDevice::genericMouseDevice()->pointerEvent(); Q_ASSERT(event->pointCount() == 1); - event->point(0)->setGrabber(grabber); + event->point(0)->setGrabberItem(grabber); } if (oldGrabber) { @@ -762,9 +763,9 @@ void QQuickWindowPrivate::setMouseGrabber(QQuickItem *grabber) } } -void QQuickWindowPrivate::grabTouchPoints(QQuickItem *grabber, const QVector<int> &ids) +void QQuickWindowPrivate::grabTouchPoints(QObject *grabber, const QVector<int> &ids) { - QSet<QQuickItem*> ungrab; + QSet<QObject*> ungrab; for (int i = 0; i < ids.count(); ++i) { // FIXME: deprecate this function, we need a device int id = ids.at(i); @@ -774,7 +775,7 @@ void QQuickWindowPrivate::grabTouchPoints(QQuickItem *grabber, const QVector<int } if (id == touchMouseId) { auto point = touchMouseDevice->pointerEvent()->pointById(id); - auto touchMouseGrabber = point->grabber(); + auto touchMouseGrabber = point->grabberItem(); if (touchMouseGrabber) { point->setGrabber(nullptr); touchMouseGrabber->mouseUngrabEvent(); @@ -790,7 +791,7 @@ void QQuickWindowPrivate::grabTouchPoints(QQuickItem *grabber, const QVector<int auto point = device->pointerEvent()->pointById(id); if (!point) continue; - QQuickItem *oldGrabber = point->grabber(); + QObject *oldGrabber = point->grabber(); if (oldGrabber == grabber) continue; @@ -799,8 +800,10 @@ void QQuickWindowPrivate::grabTouchPoints(QQuickItem *grabber, const QVector<int ungrab.insert(oldGrabber); } } - for (QQuickItem *oldGrabber : qAsConst(ungrab)) - oldGrabber->touchUngrabEvent(); + for (QObject *oldGrabber : qAsConst(ungrab)) + if (QQuickItem *item = qmlobject_cast<QQuickItem *>(oldGrabber)) + item->touchUngrabEvent(); + // TODO else if the old grabber was a PointerHandler, notify it somehow? } void QQuickWindowPrivate::removeGrabber(QQuickItem *grabber, bool mouse, bool touch) @@ -812,7 +815,7 @@ void QQuickWindowPrivate::removeGrabber(QQuickItem *grabber, bool mouse, bool to auto pointerEvent = device->pointerEvent(); for (int i = 0; i < pointerEvent->pointCount(); ++i) { if (pointerEvent->point(i)->grabber() == grabber) { - pointerEvent->point(i)->setGrabber(nullptr); + pointerEvent->point(i)->setGrabberItem(nullptr); // FIXME send ungrab event only once grabber->touchUngrabEvent(); } @@ -1485,12 +1488,12 @@ QQuickItem *QQuickWindow::mouseGrabberItem() const QQuickPointerEvent *event = d->touchMouseDevice->pointerEvent(); auto point = event->pointById(d->touchMouseId); Q_ASSERT(point); - return point->grabber(); + return point->grabberItem(); } QQuickPointerEvent *event = QQuickPointerDevice::genericMouseDevice()->pointerEvent(); Q_ASSERT(event->pointCount()); - return event->point(0)->grabber(); + return event->point(0)->grabberItem(); } @@ -1642,7 +1645,14 @@ void QQuickWindowPrivate::deliverMouseEvent(QQuickPointerMouseEvent *pointerEven Q_Q(QQuickWindow); auto point = pointerEvent->point(0); lastMousePosition = point->scenePos(); - QQuickItem *grabber = point->grabber(); + + if (auto handler = point->grabberPointerHandler()) { + pointerEvent->localize(handler->parentItem()); + handler->handlePointerEvent(pointerEvent); + return; + } + + QQuickItem *grabber = point->grabberItem(); if (grabber) { // if the update consists of changing button state, then don't accept it // unless the button is one in which the item is interested @@ -1873,10 +1883,14 @@ bool QQuickWindowPrivate::deliverTouchCancelEvent(QTouchEvent *event) // A TouchCancel event will typically not contain any points. // Deliver it to all items that have active touches. QQuickPointerEvent *pointerEvent = QQuickPointerDevice::touchDevice(event->device())->pointerEvent(); - QVector<QQuickItem *> grabbers = pointerEvent->grabbers(); - - for (QQuickItem *grabber: qAsConst(grabbers)) { - q->sendEvent(grabber, event); + QVector<QObject *> grabbers = pointerEvent->grabbers(); + + for (QObject *grabber: qAsConst(grabbers)) { + if (QQuickItem *grabberItem = qmlobject_cast<QQuickItem *>(grabber)) + q->sendEvent(grabberItem, event); + else //if (QQuickPointerHandler *grabberHandler = qmlobject_cast<QQuickPointerHandler *>(grabber)) +// grabberHandler->handlePointerEvent() + qWarning("unexpected: can't deliver touch cancel to a PointerHandler (yet?)"); } touchMouseId = -1; touchMouseDevice = nullptr; @@ -2014,8 +2028,6 @@ void QQuickWindow::mouseReleaseEvent(QMouseEvent *event) void QQuickWindowPrivate::handleMouseEvent(QMouseEvent *event) { - Q_Q(QQuickWindow); - if (event->source() == Qt::MouseEventSynthesizedBySystem) { event->accept(); return; @@ -2048,7 +2060,7 @@ void QQuickWindowPrivate::handleMouseEvent(QMouseEvent *event) updateCursor(event->windowPos()); #endif - if (!q->mouseGrabberItem()) { + if (!QQuickPointerDevice::genericMouseDevice()->pointerEvent()->point(0)->grabber()) { QPointF last = lastMousePosition.isNull() ? event->windowPos() : lastMousePosition; lastMousePosition = event->windowPos(); @@ -2217,7 +2229,7 @@ void QQuickWindowPrivate::deliverTouchEvent(QQuickPointerTouchEvent *event) if (point->state() == QQuickEventPoint::Released) { int id = point->pointId(); qCDebug(DBG_TOUCH_TARGET) << "TP" << id << "released"; - point->setGrabber(nullptr); + point->setGrabberItem(nullptr); if (id == touchMouseId) { touchMouseId = -1; touchMouseDevice = nullptr; @@ -2237,8 +2249,14 @@ void QQuickWindowPrivate::deliverTouchEvent(QQuickPointerTouchEvent *event) bool QQuickWindowPrivate::deliverUpdatedTouchPoints(QQuickPointerTouchEvent *event, QSet<QQuickItem *> *hasFiltered) { const auto grabbers = event->grabbers(); - for (auto grabber : grabbers) - deliverMatchingPointsToItem(grabber, event, hasFiltered); + for (auto grabber : grabbers) { + // The grabber is guaranteed to be either an item or a handler, but + // we need the item in order to call deliverMatchingPointsToItem(). + QQuickItem *receiver = qmlobject_cast<QQuickItem *>(grabber); + if (!receiver) + receiver = static_cast<QQuickPointerHandler *>(grabber)->parentItem(); + deliverMatchingPointsToItem(receiver, event, hasFiltered); + } return false; } @@ -2269,6 +2287,13 @@ bool QQuickWindowPrivate::deliverPressEvent(QQuickPointerEvent *event, QSet<QQui bool QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, QQuickPointerEvent *pointerEvent, QSet<QQuickItem *> *hasFiltered) { Q_Q(QQuickWindow); + QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); + pointerEvent->localize(item); + + // Let the Item's handlers (if any) have the event first. + itemPrivate->handlePointerEvent(pointerEvent); + if (pointerEvent->allPointsAccepted()) + return true; // TODO: unite this mouse point delivery with the synthetic mouse event below if (auto event = pointerEvent->asPointerMouseEvent()) { @@ -2276,7 +2301,6 @@ bool QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, QQuickPo auto point = event->point(0); if (point->isAccepted()) return false; - // The only reason to already have a mouse grabber here is // synthetic events - flickable sends one when setPressDelay is used. auto oldMouseGrabber = q->mouseGrabberItem(); @@ -2328,7 +2352,6 @@ bool QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, QQuickPo eventAccepted = touchEvent->isAccepted(); // If the touch event wasn't accepted, synthesize a mouse event and see if the item wants it. - QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); if (!eventAccepted && (itemPrivate->acceptedMouseButtons() & Qt::LeftButton)) { // send mouse event if (deliverTouchAsMouse(item, event)) @@ -2342,7 +2365,7 @@ bool QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, QQuickPo auto pointerEventPoint = event->pointById(point.id()); pointerEventPoint->setAccepted(); if (point.state() == Qt::TouchPointPressed) - pointerEventPoint->setGrabber(item); + pointerEventPoint->setGrabberItem(item); } } else { // But if the event was not accepted then we know this item @@ -2351,7 +2374,7 @@ bool QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, QQuickPo if (point.state() == Qt::TouchPointPressed) { if (event->pointById(point.id())->grabber() == item) { qCDebug(DBG_TOUCH_TARGET) << "TP" << point.id() << "disassociated"; - event->pointById(point.id())->setGrabber(nullptr); + event->pointById(point.id())->setGrabberItem(nullptr); } } } @@ -2590,7 +2613,7 @@ bool QQuickWindowPrivate::sendFilteredTouchEvent(QQuickItem *target, QQuickItem qCDebug(DBG_TOUCH_TARGET) << "TP" << tp.id() << "->" << target; touchMouseId = tp.id(); touchMouseDevice = event->device(); - touchMouseDevice->pointerEvent()->pointById(tp.id())->setGrabber(target); + touchMouseDevice->pointerEvent()->pointById(tp.id())->setGrabberItem(target); target->grabMouse(); } filtered = true; diff --git a/src/quick/items/qquickwindow_p.h b/src/quick/items/qquickwindow_p.h index be915903c6..30e3b71d0a 100644 --- a/src/quick/items/qquickwindow_p.h +++ b/src/quick/items/qquickwindow_p.h @@ -143,7 +143,7 @@ public: bool deliverTouchAsMouse(QQuickItem *item, QQuickPointerEvent *pointerEvent); void translateTouchEvent(QTouchEvent *touchEvent); void setMouseGrabber(QQuickItem *grabber); - void grabTouchPoints(QQuickItem *grabber, const QVector<int> &ids); + void grabTouchPoints(QObject *grabber, const QVector<int> &ids); void removeGrabber(QQuickItem *grabber, bool mouse = true, bool touch = true); static QMouseEvent *cloneMouseEvent(QMouseEvent *event, QPointF *transformedLocalPos = 0); void deliverMouseEvent(QQuickPointerMouseEvent *pointerEvent); diff --git a/src/quick/quick.pro b/src/quick/quick.pro index eae9b09b2f..e9a8b84b2a 100644 --- a/src/quick/quick.pro +++ b/src/quick/quick.pro @@ -30,6 +30,7 @@ ANDROID_BUNDLED_FILES += \ include(util/util.pri) include(scenegraph/scenegraph.pri) include(items/items.pri) +include(handlers/handlers.pri) qtConfig(quick-designer): \ include(designer/designer.pri) qtConfig(accessibility) { diff --git a/tests/auto/quick/handlers/qquickdraghandler/data/reparenting.qml b/tests/auto/quick/handlers/qquickdraghandler/data/reparenting.qml new file mode 100644 index 0000000000..3545badd86 --- /dev/null +++ b/tests/auto/quick/handlers/qquickdraghandler/data/reparenting.qml @@ -0,0 +1,60 @@ +import QtQuick 2.8 +import Qt.labs.handlers 1.0 + +Grid { + id: root + objectName: "root" + property bool reparentOnDrag: true + width: 200; height: 200 + columns: 3 + spacing: 10 + Repeater { + model: 9 + anchors.fill: parent + Item { + id: gridPlaceholder + objectName: "gridPlaceholder" + index + width: 60 + height: 60 + Rectangle { + id: icon + border.color: "black" + color: "beige" + radius: 3 + width: 60 + height: 60 + onParentChanged :console.log("parent " + parent) + anchors { + horizontalCenter: parent.horizontalCenter + verticalCenter: parent.verticalCenter + } + DragHandler { + id: dragArea + } + Text { + anchors.centerIn: parent + text: index + "@" + Math.round(icon.x) + "," + Math.round(icon.y) + font.pointSize: 8 + } + states: [ + State { + when: dragArea.dragging + AnchorChanges { + target: icon + anchors.horizontalCenter: undefined + anchors.verticalCenter: undefined + } + ParentChange { + target: root.reparentOnDrag ? icon : null + parent: root + } + PropertyChanges { + target: icon + color: "yellow" + } + } + ] + } + } + } +} diff --git a/tests/auto/quick/pointerhandlers/pointerhandlers.pro b/tests/auto/quick/pointerhandlers/pointerhandlers.pro new file mode 100644 index 0000000000..4a3f99765e --- /dev/null +++ b/tests/auto/quick/pointerhandlers/pointerhandlers.pro @@ -0,0 +1,7 @@ +TEMPLATE = subdirs + +qtConfig(private_tests) { + SUBDIRS += \ + qquickpointerhandler \ +} + diff --git a/tests/auto/quick/pointerhandlers/qquickpointerhandler/data/singleitem.qml b/tests/auto/quick/pointerhandlers/qquickpointerhandler/data/singleitem.qml new file mode 100644 index 0000000000..fe05a3f935 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquickpointerhandler/data/singleitem.qml @@ -0,0 +1,22 @@ +import QtQuick 2.8 +import Qt.test 1.0 + +Rectangle { + id: root + width: 320 + height: 480 + color: "green" + + EventItem { + objectName: "eventItem1" + x: 5 + y: 5 + height: 30 + width: 30 + + EventHandler { + + } + } +} + diff --git a/tests/auto/quick/pointerhandlers/qquickpointerhandler/qquickpointerhandler.pro b/tests/auto/quick/pointerhandlers/qquickpointerhandler/qquickpointerhandler.pro new file mode 100644 index 0000000000..c386969206 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquickpointerhandler/qquickpointerhandler.pro @@ -0,0 +1,16 @@ +CONFIG += testcase + +TARGET = tst_qquickpointerhandler +QT += core-private gui-private qml-private quick-private testlib + +macos:CONFIG -= app_bundle + +SOURCES += tst_qquickpointerhandler.cpp + +include (../../../shared/util.pri) +include (../../shared/util.pri) + +TESTDATA = data/* + +# OTHER_FILES += data/foo.qml + diff --git a/tests/auto/quick/pointerhandlers/qquickpointerhandler/tst_qquickpointerhandler.cpp b/tests/auto/quick/pointerhandlers/qquickpointerhandler/tst_qquickpointerhandler.cpp new file mode 100644 index 0000000000..94bb44a12b --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquickpointerhandler/tst_qquickpointerhandler.cpp @@ -0,0 +1,465 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QtTest/QtTest> + +#include <QtGui/qstylehints.h> + +#include <QtQuick/qquickview.h> +#include <QtQuick/qquickitem.h> +#include <QtQuick/private/qquickmousearea_p.h> +#include <QtQuick/private/qquickmultipointtoucharea_p.h> +#include <QtQuick/private/qquickpincharea_p.h> +#include <QtQuick/private/qquickflickable_p.h> +#include <QtQuick/private/qquickpointerhandler_p.h> +#include <qpa/qwindowsysteminterface.h> + +#include <private/qquickwindow_p.h> + +#include <QtQml/qqmlengine.h> +#include <QtQml/qqmlproperty.h> + +#include "../../../shared/util.h" +#include "../../shared/viewtestutil.h" + +Q_LOGGING_CATEGORY(lcPointerTests, "qt.quick.pointer.tests") + +struct Event +{ + Event(QEvent::Type t, QPointF item, QPointF scene) + :type(t), posWrtItem(item), posWrtScene(scene) + {} + + QEvent::Type type; + QPointF posWrtItem; + QPointF posWrtScene; + +}; + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const struct Event &event) { + QDebugStateSaver saver(dbg); + dbg.nospace(); + dbg << "Event(" << event.type << " @" << event.posWrtScene << ")"; + return dbg; +} +#endif + +class EventItem : public QQuickItem +{ + Q_OBJECT +public: + EventItem(QQuickItem *parent = 0) + : QQuickItem(parent), acceptPointer(false), acceptMouse(false), acceptTouch(false), filterTouch(false) + {} + + void touchEvent(QTouchEvent *event) + { + qCDebug(lcPointerTests) << event << "will accept?" << acceptTouch; + for (const QTouchEvent::TouchPoint &tp : event->touchPoints()) + eventList.append(Event(event->type(), tp.pos(), tp.scenePos())); + event->setAccepted(acceptTouch); + } + void mousePressEvent(QMouseEvent *event) + { + qCDebug(lcPointerTests) << event; + eventList.append(Event(event->type(), event->pos(), event->windowPos())); + event->setAccepted(acceptMouse); + } + void mouseMoveEvent(QMouseEvent *event) + { + qCDebug(lcPointerTests) << event; + eventList.append(Event(event->type(), event->pos(), event->windowPos())); + event->setAccepted(acceptMouse); + } + void mouseReleaseEvent(QMouseEvent *event) + { + qCDebug(lcPointerTests) << event; + eventList.append(Event(event->type(), event->pos(), event->windowPos())); + event->setAccepted(acceptMouse); + } + void mouseDoubleClickEvent(QMouseEvent *event) + { + qCDebug(lcPointerTests) << event; + eventList.append(Event(event->type(), event->pos(), event->windowPos())); + event->setAccepted(acceptMouse); + } + + void mouseUngrabEvent() + { + qCDebug(lcPointerTests); + eventList.append(Event(QEvent::UngrabMouse, QPoint(0,0), QPoint(0,0))); + } + + bool event(QEvent *event) + { + qCDebug(lcPointerTests) << event; + return QQuickItem::event(event); + } + + QList<Event> eventList; + bool acceptPointer; + bool acceptMouse; + bool acceptTouch; + bool filterTouch; // when used as event filter + + bool eventFilter(QObject *o, QEvent *event) + { + qCDebug(lcPointerTests) << event << o; + if (event->type() == QEvent::TouchBegin || + event->type() == QEvent::TouchUpdate || + event->type() == QEvent::TouchCancel || + event->type() == QEvent::TouchEnd) { + QTouchEvent *touch = static_cast<QTouchEvent*>(event); + for (const QTouchEvent::TouchPoint &tp : touch->touchPoints()) + eventList.append(Event(event->type(), tp.pos(), tp.scenePos())); + if (filterTouch) + event->accept(); + return true; + } + return false; + } +}; + +class EventHandler : public QQuickPointerHandler +{ + void handlePointerEventImpl(QQuickPointerEvent *event) override + { + QQuickPointerHandler::handlePointerEventImpl(event); + if (!enabled()) + return; + EventItem *item = static_cast<EventItem *>(target()); + qCDebug(lcPointerTests) << item->objectName() << event; + int c = event->pointCount(); + for (int i = 0; i < c; ++i) { + QQuickEventPoint *point = event->point(i); + point->setAccepted(item->acceptPointer); // has no effect: grabbing is up to us + if (item->acceptPointer) + setGrab(point, true); + qCDebug(lcPointerTests) << " " << i << ":" << point << "grabbed?" << item->acceptPointer; + item->eventList.append(Event(QEvent::Pointer, eventPos(point), point->scenePos())); + } + } +}; + +class tst_PointerHandlers : public QQmlDataTest +{ + Q_OBJECT +public: + tst_PointerHandlers() + :touchDevice(QTest::createTouchDevice()) + {} + +private slots: + void initTestCase(); + + void touchEventDelivery(); + void mouseEventDelivery(); + +protected: + bool eventFilter(QObject *, QEvent *event) + { + if (event->type() == QEvent::MouseButtonPress || + event->type() == QEvent::MouseMove || + event->type() == QEvent::MouseButtonRelease) { + QMouseEvent *me = static_cast<QMouseEvent*>(event); + filteredEventList.append(Event(me->type(), me->pos(), me->globalPos())); + } + return false; + } + +private: + QQuickView *createView(); + QTouchDevice *touchDevice; + QList<Event> filteredEventList; +}; + +QQuickView *tst_PointerHandlers::createView() +{ + QQuickView *window = new QQuickView(0); + window->setGeometry(0,0,240,320); + + return window; +} + +void tst_PointerHandlers::initTestCase() +{ + // This test assumes that we don't get synthesized mouse events from QGuiApplication + qApp->setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents, false); + + QQmlDataTest::initTestCase(); + qmlRegisterType<EventItem>("Qt.test", 1, 0, "EventItem"); + qmlRegisterType<EventHandler>("Qt.test", 1, 0, "EventHandler"); +} + +void tst_PointerHandlers::touchEventDelivery() +{ + QQuickView *window = createView(); + + window->setSource(testFileUrl("singleitem.qml")); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + window->requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(window->rootObject() != 0); + + EventItem *eventItem1 = window->rootObject()->findChild<EventItem*>("eventItem1"); + QVERIFY(eventItem1); + + // Do not accept anything + QPoint p1 = QPoint(20, 20); + QTest::touchEvent(window, touchDevice).press(0, p1, window); + QQuickTouchUtils::flush(window); + QTRY_COMPARE(eventItem1->eventList.size(), 3); + QCOMPARE(eventItem1->eventList.at(0).type, QEvent::Pointer); + QCOMPARE(eventItem1->eventList.at(1).type, QEvent::TouchBegin); + QCOMPARE(eventItem1->eventList.at(2).type, QEvent::MouseButtonPress); + p1 += QPoint(10, 0); + QTest::touchEvent(window, touchDevice).move(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 3); + QTest::touchEvent(window, touchDevice).release(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 3); + eventItem1->eventList.clear(); + + // Accept touch + eventItem1->acceptTouch = true; + p1 = QPoint(20, 20); + QTest::touchEvent(window, touchDevice).press(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 2); + QCOMPARE(eventItem1->eventList.at(0).type, QEvent::Pointer); + QCOMPARE(eventItem1->eventList.at(1).type, QEvent::TouchBegin); + auto pointerEvent = QQuickPointerDevice::touchDevices().at(0)->pointerEvent(); + QCOMPARE(pointerEvent->point(0)->grabber(), eventItem1); + p1 += QPoint(10, 0); + QTest::touchEvent(window, touchDevice).move(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 4); + QCOMPARE(eventItem1->eventList.at(2).type, QEvent::Pointer); + QCOMPARE(eventItem1->eventList.at(3).type, QEvent::TouchUpdate); + QTest::touchEvent(window, touchDevice).release(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 6); + QCOMPARE(eventItem1->eventList.at(4).type, QEvent::Pointer); + QCOMPARE(eventItem1->eventList.at(5).type, QEvent::TouchEnd); + eventItem1->eventList.clear(); + + // wait to avoid getting a double click event + QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 10); + + // Accept mouse + eventItem1->acceptTouch = false; + eventItem1->acceptMouse = true; + eventItem1->setAcceptedMouseButtons(Qt::LeftButton); + p1 = QPoint(20, 20); + QTest::touchEvent(window, touchDevice).press(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 3); + QCOMPARE(eventItem1->eventList.at(0).type, QEvent::Pointer); + QCOMPARE(eventItem1->eventList.at(1).type, QEvent::TouchBegin); + QCOMPARE(eventItem1->eventList.at(2).type, QEvent::MouseButtonPress); + QCOMPARE(window->mouseGrabberItem(), eventItem1); + + QPointF localPos = eventItem1->mapFromScene(p1); + QPointF scenePos = p1; // item is at 0,0 + QCOMPARE(eventItem1->eventList.at(1).posWrtItem, localPos); + QCOMPARE(eventItem1->eventList.at(1).posWrtScene, scenePos); + QCOMPARE(eventItem1->eventList.at(2).posWrtItem, localPos); + QCOMPARE(eventItem1->eventList.at(2).posWrtScene, scenePos); + + p1 += QPoint(10, 0); + QTest::touchEvent(window, touchDevice).move(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 6); + QCOMPARE(eventItem1->eventList.at(3).type, QEvent::Pointer); + QCOMPARE(eventItem1->eventList.at(4).type, QEvent::TouchUpdate); + QCOMPARE(eventItem1->eventList.at(5).type, QEvent::MouseMove); + QTest::touchEvent(window, touchDevice).release(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 10); + QCOMPARE(eventItem1->eventList.at(6).type, QEvent::Pointer); + QCOMPARE(eventItem1->eventList.at(7).type, QEvent::TouchEnd); + QCOMPARE(eventItem1->eventList.at(8).type, QEvent::MouseButtonRelease); + QCOMPARE(eventItem1->eventList.at(9).type, QEvent::UngrabMouse); + eventItem1->eventList.clear(); + + // wait to avoid getting a double click event + QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 10); + + // Accept mouse buttons but not the touch event + eventItem1->acceptTouch = false; + eventItem1->acceptMouse = false; + eventItem1->setAcceptedMouseButtons(Qt::LeftButton); + p1 = QPoint(20, 20); + QTest::touchEvent(window, touchDevice).press(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 3); + QCOMPARE(eventItem1->eventList.at(0).type, QEvent::Pointer); + QCOMPARE(eventItem1->eventList.at(1).type, QEvent::TouchBegin); + QCOMPARE(eventItem1->eventList.at(2).type, QEvent::MouseButtonPress); + QCOMPARE(pointerEvent->point(0)->grabber(), nullptr); + p1 += QPoint(10, 0); + QTest::touchEvent(window, touchDevice).move(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 3); + QTest::touchEvent(window, touchDevice).release(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 3); + eventItem1->eventList.clear(); + + // wait to avoid getting a double click event + QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 10); + + // Accept touch + eventItem1->acceptTouch = true; + eventItem1->setAcceptedMouseButtons(Qt::LeftButton); + p1 = QPoint(20, 20); + QTest::touchEvent(window, touchDevice).press(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 2); + QCOMPARE(eventItem1->eventList.at(0).type, QEvent::Pointer); + QCOMPARE(eventItem1->eventList.at(1).type, QEvent::TouchBegin); + p1 += QPoint(10, 0); + QTest::touchEvent(window, touchDevice).move(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 4); + QCOMPARE(eventItem1->eventList.at(2).type, QEvent::Pointer); + QCOMPARE(eventItem1->eventList.at(3).type, QEvent::TouchUpdate); + QTest::touchEvent(window, touchDevice).release(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 6); + QCOMPARE(eventItem1->eventList.at(4).type, QEvent::Pointer); + QCOMPARE(eventItem1->eventList.at(5).type, QEvent::TouchEnd); + eventItem1->eventList.clear(); + + // Accept pointer events + eventItem1->acceptPointer = true; + p1 = QPoint(20, 20); + QTest::touchEvent(window, touchDevice).press(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 1); + QCOMPARE(eventItem1->eventList.at(0).type, QEvent::Pointer); + p1 += QPoint(10, 0); + QTest::touchEvent(window, touchDevice).move(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 2); + QCOMPARE(eventItem1->eventList.at(1).type, QEvent::Pointer); + QTest::touchEvent(window, touchDevice).release(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 3); + QCOMPARE(eventItem1->eventList.at(2).type, QEvent::Pointer); + eventItem1->eventList.clear(); + + delete window; +} + +void tst_PointerHandlers::mouseEventDelivery() +{ + QQuickView *window = createView(); + + window->setSource(testFileUrl("singleitem.qml")); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + window->requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(window->rootObject() != 0); + + EventItem *eventItem1 = window->rootObject()->findChild<EventItem*>("eventItem1"); + QVERIFY(eventItem1); + + // Do not accept anything + QPoint p1 = QPoint(20, 20); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); + QCOMPARE(eventItem1->eventList.size(), 2); + p1 += QPoint(10, 0); + QTest::mouseMove(window, p1); + QCOMPARE(eventItem1->eventList.size(), 2); + QTest::mouseRelease(window, Qt::LeftButton); + QCOMPARE(eventItem1->eventList.size(), 2); + eventItem1->eventList.clear(); + + // wait to avoid getting a double click event + QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 10); + + // Accept mouse + eventItem1->acceptTouch = false; + eventItem1->acceptMouse = true; + eventItem1->setAcceptedMouseButtons(Qt::LeftButton); + p1 = QPoint(20, 20); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); + QCOMPARE(eventItem1->eventList.size(), 2); + QCOMPARE(eventItem1->eventList.at(0).type, QEvent::Pointer); + QCOMPARE(eventItem1->eventList.at(1).type, QEvent::MouseButtonPress); + QCOMPARE(window->mouseGrabberItem(), eventItem1); + + QPointF localPos = eventItem1->mapFromScene(p1); + QPointF scenePos = p1; // item is at 0,0 + QCOMPARE(eventItem1->eventList.at(0).posWrtItem, localPos); + QCOMPARE(eventItem1->eventList.at(0).posWrtScene, scenePos); + QCOMPARE(eventItem1->eventList.at(1).posWrtItem, localPos); + QCOMPARE(eventItem1->eventList.at(1).posWrtScene, scenePos); + + p1 += QPoint(10, 0); + QTest::mouseMove(window, p1); + QCOMPARE(eventItem1->eventList.size(), 3); + QCOMPARE(eventItem1->eventList.at(2).type, QEvent::MouseMove); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); + QCOMPARE(eventItem1->eventList.size(), 5); + QCOMPARE(eventItem1->eventList.at(3).type, QEvent::MouseButtonRelease); + QCOMPARE(eventItem1->eventList.at(4).type, QEvent::UngrabMouse); + eventItem1->eventList.clear(); + + // wait to avoid getting a double click event + QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 10); + + // Accept pointer events + eventItem1->acceptMouse = false; + eventItem1->acceptPointer = true; + p1 = QPoint(20, 20); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_COMPARE(eventItem1->eventList.size(), 1); + QCOMPARE(eventItem1->eventList.at(0).type, QEvent::Pointer); + p1 += QPoint(10, 0); + QTest::mouseMove(window, p1); + QCOMPARE(eventItem1->eventList.size(), 2); + QCOMPARE(eventItem1->eventList.at(1).type, QEvent::Pointer); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); + QCOMPARE(eventItem1->eventList.size(), 3); + QCOMPARE(eventItem1->eventList.at(2).type, QEvent::Pointer); + eventItem1->eventList.clear(); + + delete window; +} + +QTEST_MAIN(tst_PointerHandlers) + +#include "tst_qquickpointerhandler.moc" + diff --git a/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp b/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp index dd00154935..d3f43fcd4d 100644 --- a/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp +++ b/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp @@ -46,6 +46,8 @@ #include <QOpenGLFunctions> #include <QSGRendererInterface> +Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests") + struct TouchEventData { QEvent::Type type; QWidget *widget; @@ -139,7 +141,7 @@ class TestTouchItem : public QQuickRectangle public: TestTouchItem(QQuickItem *parent = 0) : QQuickRectangle(parent), acceptTouchEvents(true), acceptMouseEvents(true), - mousePressId(0), + mousePressCount(0), mouseMoveCount(0), spinLoopWhenPressed(false), touchEventCount(0) { border()->setWidth(1); @@ -158,9 +160,10 @@ public: lastMousePos = QPointF(); lastMouseCapabilityFlags = 0; touchEventCount = 0; + mouseMoveCount = 0; } - static void clearMousePressCounter() + static void clearMouseEventCounters() { mousePressNum = mouseMoveNum = mouseReleaseNum = 0; } @@ -173,7 +176,8 @@ public: bool acceptTouchEvents; bool acceptMouseEvents; TouchEventData lastEvent; - int mousePressId; + int mousePressCount; + int mouseMoveCount; bool spinLoopWhenPressed; int touchEventCount; QVector2D lastVelocity; @@ -203,7 +207,7 @@ public: e->ignore(); return; } - mousePressId = ++mousePressNum; + mousePressCount = ++mousePressNum; lastMousePos = e->pos(); lastMouseCapabilityFlags = QGuiApplicationPrivate::mouseEventCaps(e); } @@ -213,7 +217,7 @@ public: e->ignore(); return; } - ++mouseMoveNum; + mouseMoveCount = ++mouseMoveNum; lastVelocityFromMouseMove = QGuiApplicationPrivate::mouseEventVelocity(e); lastMouseCapabilityFlags = QGuiApplicationPrivate::mouseEventCaps(e); lastMousePos = e->pos(); @@ -229,10 +233,19 @@ public: lastMouseCapabilityFlags = QGuiApplicationPrivate::mouseEventCaps(e); } - bool childMouseEventFilter(QQuickItem *, QEvent *event) { - // TODO Is it a bug if a QTouchEvent comes here? - if (event->type() == QEvent::MouseButtonPress) - mousePressId = ++mousePressNum; + bool childMouseEventFilter(QQuickItem *item, QEvent *e) { + qCDebug(lcTests) << objectName() << "filtering" << e << "ahead of delivery to" << item->metaObject()->className() << item->objectName(); + switch (e->type()) { + case QEvent::MouseButtonPress: + mousePressCount = ++mousePressNum; + break; + case QEvent::MouseMove: + mouseMoveCount = ++mouseMoveNum; + break; + default: + break; + } + return false; } @@ -502,7 +515,7 @@ void tst_qquickwindow::constantUpdatesOnWindow() void tst_qquickwindow::touchEvent_basic() { - TestTouchItem::clearMousePressCounter(); + TestTouchItem::clearMouseEventCounters(); QQuickWindow *window = new QQuickWindow; QScopedPointer<QQuickWindow> cleanup(window); @@ -631,7 +644,7 @@ void tst_qquickwindow::touchEvent_basic() void tst_qquickwindow::touchEvent_propagation() { - TestTouchItem::clearMousePressCounter(); + TestTouchItem::clearMouseEventCounters(); QFETCH(bool, acceptTouchEvents); QFETCH(bool, acceptMouseEvents); @@ -778,7 +791,7 @@ void tst_qquickwindow::touchEvent_propagation_data() void tst_qquickwindow::touchEvent_cancel() { - TestTouchItem::clearMousePressCounter(); + TestTouchItem::clearMouseEventCounters(); QQuickWindow *window = new QQuickWindow; QScopedPointer<QQuickWindow> cleanup(window); @@ -812,7 +825,7 @@ void tst_qquickwindow::touchEvent_cancel() void tst_qquickwindow::touchEvent_reentrant() { - TestTouchItem::clearMousePressCounter(); + TestTouchItem::clearMouseEventCounters(); QQuickWindow *window = new QQuickWindow; QScopedPointer<QQuickWindow> cleanup(window); @@ -851,7 +864,7 @@ void tst_qquickwindow::touchEvent_reentrant() void tst_qquickwindow::touchEvent_velocity() { - TestTouchItem::clearMousePressCounter(); + TestTouchItem::clearMouseEventCounters(); QQuickWindow *window = new QQuickWindow; QScopedPointer<QQuickWindow> cleanup(window); @@ -977,7 +990,7 @@ void tst_qquickwindow::mouseFromTouch_basic() // should result in sending mouse events generated from the touch // with the new event propagation system. - TestTouchItem::clearMousePressCounter(); + TestTouchItem::clearMouseEventCounters(); QQuickWindow *window = new QQuickWindow; QScopedPointer<QQuickWindow> cleanup(window); window->resize(250, 250); @@ -1066,7 +1079,7 @@ void tst_qquickwindow::clearWindow() void tst_qquickwindow::mouseFiltering() { - TestTouchItem::clearMousePressCounter(); + TestTouchItem::clearMouseEventCounters(); QQuickWindow *window = new QQuickWindow; QScopedPointer<QQuickWindow> cleanup(window); @@ -1080,6 +1093,11 @@ void tst_qquickwindow::mouseFiltering() bottomItem->setObjectName("Bottom Item"); bottomItem->setSize(QSizeF(150, 150)); + TestTouchItem *siblingItem = new TestTouchItem(bottomItem); + siblingItem->setObjectName("Sibling of Middle Item"); + siblingItem->setPosition(QPointF(90, 25)); + siblingItem->setSize(QSizeF(150, 150)); + TestTouchItem *middleItem = new TestTouchItem(bottomItem); middleItem->setObjectName("Middle Item"); middleItem->setPosition(QPointF(50, 50)); @@ -1099,9 +1117,41 @@ void tst_qquickwindow::mouseFiltering() // 1. middleItem filters event // 2. bottomItem filters event // 3. topItem receives event - QTRY_COMPARE(middleItem->mousePressId, 1); - QTRY_COMPARE(bottomItem->mousePressId, 2); - QTRY_COMPARE(topItem->mousePressId, 3); + QTRY_COMPARE(middleItem->mousePressCount, 1); + QTRY_COMPARE(bottomItem->mousePressCount, 2); + QTRY_COMPARE(topItem->mousePressCount, 3); + QCOMPARE(siblingItem->mousePressCount, 0); + + QTest::mouseRelease(window, Qt::LeftButton, 0, pos); + topItem->clearMouseEventCounters(); + middleItem->clearMouseEventCounters(); + bottomItem->clearMouseEventCounters(); + siblingItem->clearMouseEventCounters(); + + // Repeat, but this time have the top item accept the press + topItem->acceptMouseEvents = true; + + QTest::mousePress(window, Qt::LeftButton, 0, pos); + + // Mouse filtering propagates down the stack, so the + // correct order is + // 1. middleItem filters event + // 2. bottomItem filters event + // 3. topItem receives event + QTRY_COMPARE(middleItem->mousePressCount, 1); + QTRY_COMPARE(bottomItem->mousePressCount, 2); + QTRY_COMPARE(topItem->mousePressCount, 3); + QCOMPARE(siblingItem->mousePressCount, 0); + + pos += QPoint(50, 50); + QTest::mouseMove(window, pos); + + // The top item has grabbed, so the move goes there, but again + // all the ancestors can filter, even when the mouse is outside their bounds + QTRY_COMPARE(middleItem->mouseMoveCount, 1); + QTRY_COMPARE(bottomItem->mouseMoveCount, 2); + QTRY_COMPARE(topItem->mouseMoveCount, 3); + QCOMPARE(siblingItem->mouseMoveCount, 0); // clean up mouse press state for the next tests QTest::mouseRelease(window, Qt::LeftButton, 0, pos); diff --git a/tests/auto/quick/quick.pro b/tests/auto/quick/quick.pro index 6e9998c061..93a938b5e7 100644 --- a/tests/auto/quick/quick.pro +++ b/tests/auto/quick/quick.pro @@ -47,6 +47,7 @@ PRIVATETESTS += \ !qtHaveModule(xmlpatterns): PRIVATETESTS -= qquickxmllistmodel QUICKTESTS = \ + pointerhandlers \ qquickaccessible \ qquickanchors \ qquickanimatedimage \ diff --git a/tests/manual/pointer/content/Slider.qml b/tests/manual/pointer/content/Slider.qml new file mode 100644 index 0000000000..cd52dfac80 --- /dev/null +++ b/tests/manual/pointer/content/Slider.qml @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the manual tests of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.8 +import Qt.labs.handlers 1.0 + +Item { + id: root + property int value: 50 + property int maximumValue: 99 + property alias label: label.text + + Rectangle { + id: slot + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.margins: 10 + anchors.topMargin: 30 + anchors.bottomMargin: 30 + anchors.horizontalCenter: parent.horizontalCenter + width: 10 + color: "black" + radius: width / 2 + smooth: true + } + + Rectangle { + // RectangularGlow is better, but that's a different module + id: glow + anchors.fill: knob + anchors.margins: -5 + anchors.leftMargin: -2 + anchors.horizontalCenterOffset: 1 + radius: 5 + color: "#4400FFFF" + opacity: dragHandler.active ? 1 : 0 + } + Image { + id: knob + source: "../resources/mixer-knob.png" + antialiasing: true + x: slot.x - width / 2 + slot.width / 2 + height: root.width / 2 + width: implicitWidth / implicitHeight * height + property bool programmatic: false + property real multiplier: root.maximumValue / (dragHandler.yAxis.maximum - dragHandler.yAxis.minimum) + onYChanged: if (!programmatic) root.value = root.maximumValue - (knob.y - dragHandler.yAxis.minimum) * multiplier + transformOrigin: Item.Center + function setValue(value) { knob.y = dragHandler.yAxis.maximum - value / knob.multiplier } + DragHandler { + id: dragHandler + xAxis.enabled: false + yAxis.minimum: slot.y + yAxis.maximum: slot.height + slot.y - knob.height + } + } + + Text { + font.pointSize: 16 + color: "red" + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + text: root.value + } + + Text { + id: label + font.pointSize: 12 + color: "red" + anchors.top: parent.top + anchors.topMargin: 5 + anchors.horizontalCenter: parent.horizontalCenter + } + + onHeightChanged: { + knob.programmatic = true + knob.setValue(root.value) + knob.programmatic = false + } +} diff --git a/tests/manual/pointer/joystick.qml b/tests/manual/pointer/joystick.qml new file mode 100644 index 0000000000..bcc4564471 --- /dev/null +++ b/tests/manual/pointer/joystick.qml @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the manual tests of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.8 +import Qt.labs.handlers 1.0 + +Rectangle { + width: 480 + height: 480 + color: "black" + + Image { + id: knob + source: "resources/redball.png" + anchors { + horizontalCenter: parent.horizontalCenter + verticalCenter: parent.verticalCenter + } + DragHandler { + id: dragHandler + } + states: [ + State { + when: dragHandler.active + AnchorChanges { + target: knob + anchors.horizontalCenter: undefined + anchors.verticalCenter: undefined + } + } + ] + transitions: [ + Transition { + AnchorAnimation { easing.type: Easing.OutElastic } + } + ] + } +} diff --git a/tests/manual/pointer/main.cpp b/tests/manual/pointer/main.cpp new file mode 100644 index 0000000000..a4e1060cf5 --- /dev/null +++ b/tests/manual/pointer/main.cpp @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the manual tests of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <QGuiApplication> +#include <QQmlApplicationEngine> + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + + QQmlApplicationEngine engine; + engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); + + return app.exec(); +} diff --git a/tests/manual/pointer/main.qml b/tests/manual/pointer/main.qml new file mode 100644 index 0000000000..62202c39b1 --- /dev/null +++ b/tests/manual/pointer/main.qml @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the manual tests of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.8 +import QtQuick.Window 2.2 +import "qrc:/quick/shared/" as Examples + +Window { + width: 800 + height: 600 + visible: true + Examples.LauncherList { + id: ll + anchors.fill: parent + Component.onCompleted: { + addExample("single point handler", "QQuickPointerSingleHandler: test properties copied from events", Qt.resolvedUrl("singlePointHandlerProperties.qml")) + addExample("joystick", "DragHandler: move one item inside another with any pointing device", Qt.resolvedUrl("joystick.qml")) + addExample("mixer", "mixing console", Qt.resolvedUrl("mixer.qml")) + addExample("pinch", "PinchHandler: scale, rotate and drag", Qt.resolvedUrl("pinchHandler.qml")) + addExample("map", "scale and pan", Qt.resolvedUrl("map.qml")) + addExample("custom map", "scale and pan", Qt.resolvedUrl("map2.qml")) + } + } +} diff --git a/tests/manual/pointer/map.qml b/tests/manual/pointer/map.qml new file mode 100644 index 0000000000..e1ca889064 --- /dev/null +++ b/tests/manual/pointer/map.qml @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the manual tests of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.8 +import Qt.labs.handlers 1.0 + +Item { + width: 640 + height: 480 + + Rectangle { + id: map + color: "aqua" + x: (parent.width - width) / 2 + y: (parent.height - height) / 2 + width: image.implicitWidth + height: image.implicitHeight + + Image { + id: image + anchors.centerIn: parent + fillMode: Image.PreserveAspectFit + source: "resources/map.svgz" + } + } + + PinchHandler { + id: pinch + target: map + minimumScale: 0.1 + maximumScale: 10 + } + + DragHandler { + target: map + } +} diff --git a/tests/manual/pointer/map2.qml b/tests/manual/pointer/map2.qml new file mode 100644 index 0000000000..fcd144bd7f --- /dev/null +++ b/tests/manual/pointer/map2.qml @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the manual tests of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.8 +import Qt.labs.handlers 1.0 + +Item { + width: 640 + height: 480 + + Rectangle { + id: map + color: "aqua" + x: (parent.width - width) / 2 + y: (parent.height - height) / 2 + width: image.implicitWidth + height: image.implicitHeight + property point center : Qt.point(x + map.width/2, y + map.height/2) + + function setCenter(xx, yy) { + map.x = xx - map.width/2 + map.y = yy - map.height/2 + } + + + Image { + id: image + anchors.centerIn: parent + fillMode: Image.PreserveAspectFit + source: "resources/map.svgz" + } + } + + PinchHandler { + id: pinch + target: map + minimumScale: 0.1 + maximumScale: 10 + } + + DragHandler { + property point startDrag + target: null + onActiveChanged: { + if (active) + startDrag = map.center + } + + onTranslationChanged: { + if (!target) + map.setCenter(startDrag.x + translation.x, startDrag.y + translation.y) + } + } +} diff --git a/tests/manual/pointer/mixer.qml b/tests/manual/pointer/mixer.qml new file mode 100644 index 0000000000..84ad975340 --- /dev/null +++ b/tests/manual/pointer/mixer.qml @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the manual tests of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.8 +import Qt.labs.handlers 1.0 +import "content" + +Rectangle { + id: root + width: 1280 + height: 960 + objectName: "root" + color: "#222222" + + ListView { + id: list + objectName: "listView" + anchors.fill: parent + anchors.margins: 10 + orientation: Qt.Horizontal + + model: 20 + + delegate: Item { + objectName: "delegateItem" + index + width: 154 + height: list.height + + Slider { + anchors.fill: parent + label: "Channel " + (index + 1) + } + } + } +} diff --git a/tests/manual/pointer/pinchHandler.qml b/tests/manual/pointer/pinchHandler.qml new file mode 100644 index 0000000000..f317f361e2 --- /dev/null +++ b/tests/manual/pointer/pinchHandler.qml @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the manual tests of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.8 +import Qt.labs.handlers 1.0 + +Rectangle { + width: 1024; height: 600 + color: "#eee" + + function getTransformationDetails(item, pinchhandler) { + return "\n\npinch.scale:" + pinchhandler.scale.toFixed(2) + + "\npinch.rotation:" + pinchhandler.rotation.toFixed(2) + + "\npinch.translation:" + "(" + pinchhandler.translation.x.toFixed(2) + "," + pinchhandler.translation.y.toFixed(2) + ")" + + "\nrect.scale: " + item.scale.toFixed(2) + + "\nrect.rotation: " + item.rotation.toFixed(2) + + "\nrect.position: " + "(" + item.x.toFixed(2) + "," + item.y.toFixed(2) + ")" + } + + Rectangle { + // Purpose of this item is just to make sure the rectangles are transformed into + // a coordinate system that is different from the scene coordinate system. + anchors.fill: parent + anchors.margins: 50 + color: "#ffe0e0e0" + + Rectangle { + id: rect2 + width: 400 + height: 300 + color: "lightsteelblue" + antialiasing: true + x: 100 + y: 200 + rotation: 30 + transformOrigin: Item.TopRight + + Text { + anchors.centerIn: parent + text: "Pinch with 2 fingers to scale, rotate and translate" + + getTransformationDetails(rect2, pinch2) + } + + PinchHandler { + id: pinch2 + objectName: "2-finger pinch" + minimumRotation: -45 + maximumRotation: 45 + minimumScale: 0.5 + maximumScale: 3 + minimumX: 0 + maximumX: 600 + pointDistanceThreshold: 0 + } + } + + Rectangle { + id: rect3 + x: 500 + width: 400 + height: 300 + color: "wheat" + antialiasing: true + + Text { + anchors.centerIn: parent + text: "Pinch with 3 fingers to scale, rotate and translate\nDrag with 1 finger" + + getTransformationDetails(rect3, pinch3) + } + DragHandler { objectName: "DragHandler" } + + PinchHandler { + id: pinch3 + objectName: "3-finger pinch" + requiredPointCount: 3 + minimumScale: 0.1 + maximumScale: 10 + } + } + } + Rectangle { + id: centroidIndicator + property QtObject pincher: pinch2.active ? pinch2 : pinch3 + x: pincher.centroid.x - radius + y: pincher.centroid.y - radius + z: 1 + visible: pincher.active + radius: width / 2 + width: 10 + height: width + color: "red" + } +} diff --git a/tests/manual/pointer/pointer.pro b/tests/manual/pointer/pointer.pro new file mode 100644 index 0000000000..3705d41df0 --- /dev/null +++ b/tests/manual/pointer/pointer.pro @@ -0,0 +1,7 @@ +TEMPLATE = app + +QT += qml quick + +SOURCES += main.cpp + +RESOURCES += qml.qrc ../../../examples/quick/shared/quick_shared.qrc diff --git a/tests/manual/pointer/qml.qrc b/tests/manual/pointer/qml.qrc new file mode 100644 index 0000000000..adf255ea1f --- /dev/null +++ b/tests/manual/pointer/qml.qrc @@ -0,0 +1,21 @@ +<RCC> + <qresource prefix="/"> + <file>main.qml</file> + <file>joystick.qml</file> + <file>map.qml</file> + <file>mixer.qml</file> + <file>pinchHandler.qml</file> + <file>singlePointHandlerProperties.qml</file> + <file>content/Slider.qml</file> + <file>resources/arrowhead.png</file> + <file>resources/grabbing-location.svg</file> + <file>resources/map.svgz</file> + <file>resources/mixer-knob.png</file> + <file>resources/mouse.png</file> + <file>resources/mouse_left.png</file> + <file>resources/mouse_middle.png</file> + <file>resources/mouse_right.png</file> + <file>resources/redball.png</file> + <file>map2.qml</file> + </qresource> +</RCC> diff --git a/tests/manual/pointer/resources/arrowhead.png b/tests/manual/pointer/resources/arrowhead.png Binary files differnew file mode 100644 index 0000000000..7719bc6d6a --- /dev/null +++ b/tests/manual/pointer/resources/arrowhead.png diff --git a/tests/manual/pointer/resources/grabbing-location.svg b/tests/manual/pointer/resources/grabbing-location.svg new file mode 100644 index 0000000000..c26881e9ba --- /dev/null +++ b/tests/manual/pointer/resources/grabbing-location.svg @@ -0,0 +1 @@ +<svg width="3408" height="3124"><path d="M1517 1562c0-126-93-229-208-229s-208 102-208 229c0 126 93 229 208 229s208-102 208-229zm123-172c-58-206-221-365-424-412l-270 531H380l203-223 219-241 346-380c42 14 82 32 121 54 232 128 402 375 449 671h-77zm-551-933c448 123 782 546 802 1055h1517C3386 673 2787 0 2050 0H696L0 1367h120l826-938 146 25c-1 1-2 2-3 4zm551 1277c-58 206-221 365-424 412l-270-531H380l203 223 219 241 346 380c42-14 82-32 121-54 232-128 402-375 449-671h-77zm-548 936l-146 25-826-938H0l696 1367h1354c737 0 1337-673 1358-1512H1891c-19 509-354 933-802 1055 1 1 2 2 3 4z" fill="#ee832b"/></svg> diff --git a/tests/manual/pointer/resources/map.svgz b/tests/manual/pointer/resources/map.svgz Binary files differnew file mode 100644 index 0000000000..64d509c106 --- /dev/null +++ b/tests/manual/pointer/resources/map.svgz diff --git a/tests/manual/pointer/resources/mixer-knob.png b/tests/manual/pointer/resources/mixer-knob.png Binary files differnew file mode 100644 index 0000000000..02cc9fc72b --- /dev/null +++ b/tests/manual/pointer/resources/mixer-knob.png diff --git a/tests/manual/pointer/resources/mouse.png b/tests/manual/pointer/resources/mouse.png Binary files differnew file mode 100644 index 0000000000..268946df0a --- /dev/null +++ b/tests/manual/pointer/resources/mouse.png diff --git a/tests/manual/pointer/resources/mouse_left.png b/tests/manual/pointer/resources/mouse_left.png Binary files differnew file mode 100644 index 0000000000..9292301b47 --- /dev/null +++ b/tests/manual/pointer/resources/mouse_left.png diff --git a/tests/manual/pointer/resources/mouse_middle.png b/tests/manual/pointer/resources/mouse_middle.png Binary files differnew file mode 100644 index 0000000000..064e8b9c16 --- /dev/null +++ b/tests/manual/pointer/resources/mouse_middle.png diff --git a/tests/manual/pointer/resources/mouse_right.png b/tests/manual/pointer/resources/mouse_right.png Binary files differnew file mode 100644 index 0000000000..cab1a36ba6 --- /dev/null +++ b/tests/manual/pointer/resources/mouse_right.png diff --git a/tests/manual/pointer/resources/redball.png b/tests/manual/pointer/resources/redball.png Binary files differnew file mode 100644 index 0000000000..68d2e1d638 --- /dev/null +++ b/tests/manual/pointer/resources/redball.png diff --git a/tests/manual/pointer/singlePointHandlerProperties.qml b/tests/manual/pointer/singlePointHandlerProperties.qml new file mode 100644 index 0000000000..f5a938e401 --- /dev/null +++ b/tests/manual/pointer/singlePointHandlerProperties.qml @@ -0,0 +1,162 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the manual tests of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.9 +import Qt.labs.handlers 1.0 + +Rectangle { + id: root + width: 480 + height: 480 + color: "black" + + Item { + id: crosshairs + x: dragHandler.pos.x - width / 2 + y: dragHandler.pos.y - height / 2 + width: parent.width / 2; height: parent.height / 2 + visible: dragHandler.active + rotation: dragHandler.rotation + + Rectangle { + color: "goldenrod" + anchors.centerIn: parent + width: 2; height: parent.height + antialiasing: true + } + Rectangle { + color: "goldenrod" + anchors.centerIn: parent + width: parent.width; height: 2 + antialiasing: true + } + Rectangle { + color: "goldenrod" + width: Math.max(2, 50 * dragHandler.pressure) + height: width + radius: width / 2 + anchors.centerIn: parent + antialiasing: true + Rectangle { + y: -40 + anchors.horizontalCenter: parent.horizontalCenter + color: "lightsteelblue" + implicitWidth: label.implicitWidth + implicitHeight: label.implicitHeight + Text { + id: label + text: 'id: ' + dragHandler.pointId.toString(16) + " uid: " + dragHandler.uniquePointId.numericId + + '\npos: (' + dragHandler.pos.x.toFixed(2) + ', ' + dragHandler.pos.y.toFixed(2) + ')' + } + } + } + Rectangle { + color: "transparent" + border.color: "white" + antialiasing: true + width: dragHandler.ellipseDiameters.width + height: dragHandler.ellipseDiameters.height + radius: Math.min(width / 2, height / 2) + anchors.centerIn: parent + } + } + Rectangle { + id: velocityVector + visible: width > 0 + width: dragHandler.velocity.length() * 100 + height: 2 + x: dragHandler.pos.x + y: dragHandler.pos.y + rotation: Math.atan2(dragHandler.velocity.y, dragHandler.velocity.x) * 180 / Math.PI + transformOrigin: Item.BottomLeft + antialiasing: true + + Image { + source: "resources/arrowhead.png" + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + width: 16 + height: 12 + antialiasing: true + } + } + + Component { + id: grabbingLocationIndicator + Image { + source: "resources/grabbing-location.svg" + sourceSize.width: 32 + sourceSize.height: 32 + } + } + + Component { + id: mouseButtonIndicator + Image { + property int buttons + source: "resources/mouse.png" + Image { + source: "resources/mouse_left.png" + visible: buttons & Qt.LeftButton + } + Image { + source: "resources/mouse_middle.png" + visible: buttons & Qt.MidButton + } + Image { + source: "resources/mouse_right.png" + visible: buttons & Qt.RightButton + } + } + } + + DragHandler { + id: dragHandler + target: null + onGrabChanged: if (active) { + console.log("grabbed " + point.pointId + " @ " + sceneGrabPos) + grabbingLocationIndicator.createObject(root, {"x": sceneGrabPos.x, "y": sceneGrabPos.y - 16}) + } + onPressedButtonsChanged: { + if (pressedButtons) + mouseButtonIndicator.createObject(root, {"x": pressPos.x - 44, "y": pressPos.y - 64, "buttons": pressedButtons}) + } + } +} |