aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShawn Rutledge <shawn.rutledge@qt.io>2015-07-28 15:24:29 +0200
committerShawn Rutledge <shawn.rutledge@qt.io>2017-02-08 14:30:14 +0000
commit2d61a39dedfb2718cdb6c26b370b7e4103db9c4a (patch)
treebea7b9b0fbc6e9ee57cd65c7b4922b1e4f3929c6
parent55970e2cd6f5a24f654e92febf27d6aeb245d03f (diff)
Introduce TapHandler
Device-agnostic tap/click detection. Also detect whether the taps or clicks occur close enough together in both time and space to be considered part of a multi-tap gesture. Change-Id: I41a378feea3340b9f0409118273746a289641d6c Reviewed-by: Jan Arve Sæther <jan-arve.saether@qt.io>
-rw-r--r--src/quick/handlers/handlers.pri3
-rw-r--r--src/quick/handlers/qquickhandlersmodule.cpp4
-rw-r--r--src/quick/handlers/qquicktaphandler.cpp177
-rw-r--r--src/quick/handlers/qquicktaphandler_p.h104
-rw-r--r--tests/manual/pointer/main.qml1
-rw-r--r--tests/manual/pointer/qml.qrc1
-rw-r--r--tests/manual/pointer/tapHandler.qml136
7 files changed, 425 insertions, 1 deletions
diff --git a/src/quick/handlers/handlers.pri b/src/quick/handlers/handlers.pri
index f32f330d4e..749e4cc4e0 100644
--- a/src/quick/handlers/handlers.pri
+++ b/src/quick/handlers/handlers.pri
@@ -7,6 +7,7 @@ HEADERS += \
$$PWD/qquickpointersinglehandler_p.h \
$$PWD/qquickhandlersmodule_p.h \
$$PWD/qquickpointerdevicehandler_p.h \
+ $$PWD/qquicktaphandler_p.h \
$$PWD/qquickhandlersmodule_p.h \
SOURCES += \
@@ -16,4 +17,6 @@ SOURCES += \
$$PWD/qquickmultipointerhandler.cpp \
$$PWD/qquickpinchhandler.cpp \
$$PWD/qquickpointersinglehandler.cpp \
+ $$PWD/qquicktaphandler.cpp \
$$PWD/qquickhandlersmodule.cpp \
+
diff --git a/src/quick/handlers/qquickhandlersmodule.cpp b/src/quick/handlers/qquickhandlersmodule.cpp
index 697a198e67..4bfdc020d8 100644
--- a/src/quick/handlers/qquickhandlersmodule.cpp
+++ b/src/quick/handlers/qquickhandlersmodule.cpp
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtQuick module of the Qt Toolkit.
@@ -41,6 +41,7 @@
#include "qquickpointerhandler_p.h"
#include "qquickdraghandler_p.h"
#include "qquickpinchhandler_p.h"
+#include "qquicktaphandler_p.h"
static void initResources()
{
@@ -80,6 +81,7 @@ static void qt_quickhandlers_defineModule(const char *uri, int major, int minor)
qmlRegisterUncreatableType<QQuickDragAxis>(uri, major, minor, "DragAxis",
QQuickDragHandler::tr("DragAxis is only available as a grouped property of DragHandler"));
qmlRegisterType<QQuickPinchHandler>(uri,major,minor,"PinchHandler");
+ qmlRegisterType<QQuickTapHandler>(uri,major,minor,"TapHandler");
}
void QQuickHandlersModule::defineModule()
diff --git a/src/quick/handlers/qquicktaphandler.cpp b/src/quick/handlers/qquicktaphandler.cpp
new file mode 100644
index 0000000000..e0e66be01c
--- /dev/null
+++ b/src/quick/handlers/qquicktaphandler.cpp
@@ -0,0 +1,177 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 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 "qquicktaphandler_p.h"
+#include <qpa/qplatformtheme.h>
+#include <private/qguiapplication_p.h>
+#include <QtGui/qstylehints.h>
+
+QT_BEGIN_NAMESPACE
+
+qreal QQuickTapHandler::m_multiTapInterval(0.0);
+// single tap distance is the same as the drag threshold
+int QQuickTapHandler::m_mouseMultiClickDistanceSquared(-1);
+int QQuickTapHandler::m_touchMultiTapDistanceSquared(-1);
+
+/*!
+ \qmltype TapHandler
+ \instantiates QQuickTapHandler
+ \inqmlmodule QtQuick
+ \ingroup qtquick-handlers
+ \brief Handler for taps and clicks
+
+ TapHandler is a handler for taps on a touchscreen or clicks on a mouse.
+
+ It requires that any movement between the press and release remains
+ less than the drag threshold for a single tap, and less than
+ QPlatformTheme::MouseDoubleClickDistance for multi-tap gestures
+ (double-tap, triple-tap, etc.) with the mouse, or 10 pixels with touch.
+ It also requires that the time between press and release remains
+ less than QStyleHints::mouseDoubleClickInterval() for a single tap,
+ and that the time from one press/release sequence to the next remains
+ less than QStyleHints::mouseDoubleClickInterval() for multi-tap gestures.
+
+ Note that buttons (such as QPushButton) are often implemented not to care
+ whether the press and release occur close together: if you press the button
+ and then change your mind, you need to drag all the way off the edge of the
+ button in order to cancel the click. If you want to achieve such behavior,
+ it's enough to use a PointerHandler and consider the button clicked on
+ every \l {QQuickPointerHandler:}{released} event. But TapHandler requires
+ that the events occur close together in both space and time, which is anyway
+ necessary to detect double clicks or multi-click gestures.
+
+ \sa MouseArea
+*/
+
+QQuickTapHandler::QQuickTapHandler(QObject *parent)
+ : QQuickPointerSingleHandler(parent)
+ , m_pressed(false)
+ , m_tapCount(0)
+ , m_lastTapTimestamp(0.0)
+{
+ setAcceptedButtons(Qt::LeftButton);
+ if (m_mouseMultiClickDistanceSquared < 0) {
+ m_multiTapInterval = qApp->styleHints()->mouseDoubleClickInterval() / 1000.0;
+ m_mouseMultiClickDistanceSquared = QGuiApplicationPrivate::platformTheme()->
+ themeHint(QPlatformTheme::MouseDoubleClickDistance).toInt();
+ m_mouseMultiClickDistanceSquared *= m_mouseMultiClickDistanceSquared;
+ m_touchMultiTapDistanceSquared = QGuiApplicationPrivate::platformTheme()->
+ themeHint(QPlatformTheme::TouchDoubleTapDistance).toInt();
+ m_touchMultiTapDistanceSquared *= m_touchMultiTapDistanceSquared;
+ }
+}
+
+QQuickTapHandler::~QQuickTapHandler()
+{
+}
+
+bool QQuickTapHandler::wantsEventPoint(QQuickEventPoint *point)
+{
+ if (point->state() == QQuickEventPoint::Pressed && parentContains(point))
+ return true;
+ // If the user has not dragged too far, it could be a tap.
+ // Otherwise we want to give up the grab so that a competing handler
+ // (e.g. DragHandler) gets a chance to take over.
+ // Don't forget to emit released in case of a cancel.
+ return !point->isDraggedOverThreshold();
+}
+
+void QQuickTapHandler::handleEventPoint(QQuickEventPoint *point)
+{
+ switch (point->state()) {
+ case QQuickEventPoint::Pressed:
+ setPressed(true, false, point);
+ break;
+ case QQuickEventPoint::Released:
+ if ((point->pointerEvent()->buttons() & m_acceptedButtons) == Qt::NoButton)
+ setPressed(false, false, point);
+ break;
+ default:
+ break;
+ }
+}
+
+void QQuickTapHandler::setPressed(bool press, bool cancel, QQuickEventPoint *point)
+{
+ if (m_pressed != press) {
+ m_pressed = press;
+ if (!cancel && !press && point->timeHeld() < m_multiTapInterval) {
+ // Assuming here that pointerEvent()->timestamp() is in ms.
+ qreal ts = point->pointerEvent()->timestamp() / 1000.0;
+ if (ts - m_lastTapTimestamp < m_multiTapInterval &&
+ QVector2D(point->scenePos() - m_lastTapPos).lengthSquared() <
+ (point->pointerEvent()->device()->type() == QQuickPointerDevice::Mouse ?
+ m_mouseMultiClickDistanceSquared : m_touchMultiTapDistanceSquared))
+ ++m_tapCount;
+ else
+ m_tapCount = 1;
+ emit tapped(point);
+ emit tapCountChanged();
+ m_lastTapTimestamp = ts;
+ m_lastTapPos = point->scenePos();
+ }
+ emit pressedChanged();
+ }
+}
+
+void QQuickTapHandler::handleGrabCancel(QQuickEventPoint *point)
+{
+ QQuickPointerSingleHandler::handleGrabCancel(point);
+ setPressed(false, true, point);
+}
+
+/*!
+ \qmlproperty tapCount
+
+ The number of taps which have occurred within the time and space
+ constraints to be considered a single gesture. For example, to detect
+ a double-tap, you can write
+
+ \qml
+ Rectangle {
+ width: 100; height: 30
+ signal doubleTap
+ TapHandler {
+ acceptedButtons: Qt.AllButtons
+ onTapped: if (tapCount == 2) doubleTap()
+ }
+ }
+*/
+
+QT_END_NAMESPACE
diff --git a/src/quick/handlers/qquicktaphandler_p.h b/src/quick/handlers/qquicktaphandler_p.h
new file mode 100644
index 0000000000..c11d51ee03
--- /dev/null
+++ b/src/quick/handlers/qquicktaphandler_p.h
@@ -0,0 +1,104 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 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 QQUICKTAPHANDLER_H
+#define QQUICKTAPHANDLER_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 "qquickpointersinglehandler_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class Q_AUTOTEST_EXPORT QQuickTapHandler : public QQuickPointerSingleHandler
+{
+ Q_OBJECT
+ Q_PROPERTY(bool isPressed READ isPressed NOTIFY pressedChanged)
+ Q_PROPERTY(int tapCount READ tapCount NOTIFY tapCountChanged)
+
+public:
+ QQuickTapHandler(QObject *parent = 0);
+ ~QQuickTapHandler();
+
+ bool wantsEventPoint(QQuickEventPoint *point) override;
+ void handleEventPoint(QQuickEventPoint *point) override;
+
+ bool isPressed() const { return m_pressed; }
+
+ int tapCount() const { return m_tapCount; }
+
+Q_SIGNALS:
+ void pressedChanged();
+ void tapCountChanged();
+ void tapped(QQuickEventPoint *point);
+
+protected:
+ void handleGrabCancel(QQuickEventPoint *point) override;
+
+private:
+ void setPressed(bool press, bool cancel, QQuickEventPoint *point);
+
+private:
+ bool m_pressed;
+ int m_tapCount;
+ QPointF m_lastTapPos;
+ qreal m_lastTapTimestamp;
+
+ static qreal m_tapInterval;
+ static qreal m_multiTapInterval;
+ static int m_mouseMultiClickDistanceSquared;
+ static int m_touchMultiTapDistanceSquared;
+};
+
+QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QQuickTapHandler)
+
+#endif // QQUICKTAPHANDLER_H
diff --git a/tests/manual/pointer/main.qml b/tests/manual/pointer/main.qml
index 601065081d..b06d3cf155 100644
--- a/tests/manual/pointer/main.qml
+++ b/tests/manual/pointer/main.qml
@@ -59,6 +59,7 @@ Window {
addExample("fling animation", "DragHandler: after dragging, use an animation to simulate momentum", Qt.resolvedUrl("flingAnimation.qml"))
addExample("fake Flickable", "implementation of a simplified Flickable using only Items, DragHandler and MomentumAnimation", Qt.resolvedUrl("fakeFlickable.qml"))
addExample("photo surface", "re-implementation of the existing photo surface demo using Handlers", Qt.resolvedUrl("photosurface.qml"))
+ addExample("tap", "TapHandler: device-agnostic tap/click detection for buttons", Qt.resolvedUrl("tapHandler.qml"))
}
}
}
diff --git a/tests/manual/pointer/qml.qrc b/tests/manual/pointer/qml.qrc
index 87fc021680..4535a26802 100644
--- a/tests/manual/pointer/qml.qrc
+++ b/tests/manual/pointer/qml.qrc
@@ -9,6 +9,7 @@
<file>photosurface.qml</file>
<file>pinchHandler.qml</file>
<file>singlePointHandlerProperties.qml</file>
+ <file>tapHandler.qml</file>
<file>content/FakeFlickable.qml</file>
<file>content/MomentumAnimation.qml</file>
<file>content/Slider.qml</file>
diff --git a/tests/manual/pointer/tapHandler.qml b/tests/manual/pointer/tapHandler.qml
new file mode 100644
index 0000000000..d099fc7faf
--- /dev/null
+++ b/tests/manual/pointer/tapHandler.qml
@@ -0,0 +1,136 @@
+/****************************************************************************
+**
+** 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.8
+import Qt.labs.handlers 1.0
+import "qrc:/quick/shared/" as Examples
+
+Item {
+ width: 480
+ height: 320
+
+ Rectangle {
+ id: rect
+ anchors.fill: parent; anchors.margins: 40
+ border.width: 3; border.color: "transparent"
+ color: handler.isPressed ? "lightsteelblue" : "darkgrey"
+
+ TapHandler {
+ id: handler
+ acceptedButtons: (leftAllowedCB.checked ? Qt.LeftButton : Qt.NoButton) |
+ (middleAllowedCB.checked ? Qt.MiddleButton : Qt.NoButton) |
+ (rightAllowedCB.checked ? Qt.RightButton : Qt.NoButton)
+ onCanceled: {
+ console.log("canceled @ " + pos)
+ borderBlink.blinkColor = "red"
+ borderBlink.start()
+ }
+ onTapped: {
+ console.log("tapped @ " + point.pos + " button(s) " + point.event.button + " tapCount " + tapCount)
+ if (tapCount > 1) {
+ tapCountLabel.text = tapCount
+ flashAnimation.start()
+ } else {
+ switch (point.event.button) {
+ case Qt.LeftButton: borderBlink.blinkColor = "green"; break;
+ case Qt.MiddleButton: borderBlink.blinkColor = "orange"; break;
+ case Qt.RightButton: borderBlink.blinkColor = "magenta"; break;
+ }
+ borderBlink.start()
+ }
+ }
+ }
+
+ Text {
+ id: tapCountLabel
+ anchors.centerIn: parent
+ font.pixelSize: 72
+ font.weight: Font.Black
+ SequentialAnimation {
+ id: flashAnimation
+ PropertyAction { target: tapCountLabel; property: "visible"; value: true }
+ PropertyAction { target: tapCountLabel; property: "opacity"; value: 1.0 }
+ PropertyAction { target: tapCountLabel; property: "scale"; value: 1.0 }
+ ParallelAnimation {
+ NumberAnimation {
+ target: tapCountLabel
+ property: "opacity"
+ to: 0
+ duration: 500
+ }
+ NumberAnimation {
+ target: tapCountLabel
+ property: "scale"
+ to: 1.5
+ duration: 500
+ }
+ }
+ }
+ }
+
+ SequentialAnimation {
+ id: borderBlink
+ property color blinkColor: "blue"
+ loops: 3
+ ScriptAction { script: rect.border.color = borderBlink.blinkColor }
+ PauseAnimation { duration: 100 }
+ ScriptAction { script: rect.border.color = "transparent" }
+ PauseAnimation { duration: 100 }
+ }
+ }
+
+ Row {
+ spacing: 6
+ Text { text: "accepted mouse clicks:"; anchors.verticalCenter: leftAllowedCB.verticalCenter }
+ Examples.CheckBox {
+ id: leftAllowedCB
+ checked: true
+ text: "left click"
+ }
+ Examples.CheckBox {
+ id: middleAllowedCB
+ text: "middle click"
+ }
+ Examples.CheckBox {
+ id: rightAllowedCB
+ text: "right click"
+ }
+ }
+}