1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Data Visualization module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 or (at your option) any later version
20 ** approved by the KDE Free Qt Foundation. The licenses are as published by
21 ** the Free Software Foundation and appearing in the file LICENSE.GPL3
22 ** included in the packaging of this file. Please review the following
23 ** information to ensure the GNU General Public License requirements will
24 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25 **
26 ** $QT_END_LICENSE$
27 **
28 ****************************************************************************/
29 
30 #include "qtouch3dinputhandler_p.h"
31 #include <QtCore/QTimer>
32 #include <QtCore/qmath.h>
33 
34 QT_BEGIN_NAMESPACE_DATAVISUALIZATION
35 
36 static const float maxTapAndHoldJitter = 20.0f;
37 static const int maxPinchJitter = 10;
38 #if defined (Q_OS_ANDROID) || defined(Q_OS_IOS)
39 static const int maxSelectionJitter = 10;
40 #else
41 static const int maxSelectionJitter = 5;
42 #endif
43 static const int tapAndHoldTime = 250;
44 static const float rotationSpeed = 200.0f;
45 static const float touchZoomDrift = 0.02f;
46 
47 /*!
48  * \class QTouch3DInputHandler
49  * \inmodule QtDataVisualization
50  * \brief Basic touch display based input handler.
51  * \since QtDataVisualization 1.0
52  *
53  * QTouch3DInputHandler is the basic input handler for touch screen devices.
54  *
55  * Default touch input handler has the following functionalty:
56  * \table
57  *      \header
58  *          \li Gesture
59  *          \li Action
60  *      \row
61  *          \li Touch-And-Move
62  *          \li Rotate graph within limits set for Q3DCamera
63  *      \row
64  *          \li Tap
65  *          \li Select the item tapped or remove selection if none.
66  *              May open the secondary view depending on the
67  *              \l {QAbstract3DGraph::selectionMode}{selection mode}.
68  *      \row
69  *          \li Tap-And-Hold
70  *          \li Same as tap.
71  *      \row
72  *          \li Pinch
73  *          \li Zoom in/out within the allowable zoom range set for Q3DCamera.
74  *      \row
75  *          \li Tap on the primary view when the secondary view is visible
76  *          \li Closes the secondary view.
77  *          \note Secondary view is available only for Q3DBars and Q3DSurface graphs.
78  * \endtable
79  *
80  * Rotation, zoom, and selection can each be individually disabled using
81  * corresponding Q3DInputHandler properties.
82  */
83 
84 /*!
85  * \qmltype TouchInputHandler3D
86  * \inqmlmodule QtDataVisualization
87  * \since QtDataVisualization 1.2
88  * \ingroup datavisualization_qml
89  * \instantiates QTouch3DInputHandler
90  * \inherits InputHandler3D
91  * \brief Basic touch display based input handler.
92  *
93  * TouchInputHandler3D is the basic input handler for touch screen devices.
94  *
95  * See QTouch3DInputHandler documentation for more details.
96  */
97 
98 /*!
99  * Constructs the basic touch display input handler. An optional \a parent parameter can be given
100  * and is then passed to QObject constructor.
101  */
QTouch3DInputHandler(QObject * parent)102 QTouch3DInputHandler::QTouch3DInputHandler(QObject *parent)
103     : Q3DInputHandler(parent),
104       d_ptr(new QTouch3DInputHandlerPrivate(this))
105 {
106 }
107 
108 /*!
109  *  Destroys the input handler.
110  */
~QTouch3DInputHandler()111 QTouch3DInputHandler::~QTouch3DInputHandler()
112 {
113 }
114 
115 /*!
116  * Override this to change handling of touch events.
117  * Touch event is given in the \a event.
118  */
touchEvent(QTouchEvent * event)119 void QTouch3DInputHandler::touchEvent(QTouchEvent *event)
120 {
121     QList<QTouchEvent::TouchPoint> points;
122     points = event->touchPoints();
123 
124     if (!scene()->isSlicingActive() && points.count() == 2) {
125         d_ptr->m_holdTimer->stop();
126         QPointF distance = points.at(0).pos() - points.at(1).pos();
127         QPoint midPoint = ((points.at(0).pos() + points.at(1).pos()) / 2.0).toPoint();
128         d_ptr->handlePinchZoom(distance.manhattanLength(), midPoint);
129     } else if (points.count() == 1) {
130         QPointF pointerPos = points.at(0).pos();
131         if (event->type() == QEvent::TouchBegin) {
132             // Flush input state
133             d_ptr->m_inputState = QAbstract3DInputHandlerPrivate::InputStateNone;
134             if (scene()->isSlicingActive()) {
135                 if (isSelectionEnabled()) {
136                     if (scene()->isPointInPrimarySubView(pointerPos.toPoint()))
137                         setInputView(InputViewOnPrimary);
138                     else if (scene()->isPointInSecondarySubView(pointerPos.toPoint()))
139                         setInputView(InputViewOnSecondary);
140                     else
141                         setInputView(InputViewNone);
142                 }
143             } else {
144                 // Handle possible tap-and-hold selection
145                 if (isSelectionEnabled()) {
146                     d_ptr->m_startHoldPos = pointerPos;
147                     d_ptr->m_touchHoldPos = d_ptr->m_startHoldPos;
148                     d_ptr->m_holdTimer->start();
149                     setInputView(InputViewOnPrimary);
150                 }
151                 // Start rotating
152                 if (isRotationEnabled()) {
153                     d_ptr->m_inputState = QAbstract3DInputHandlerPrivate::InputStateRotating;
154                     setInputPosition(pointerPos.toPoint());
155                     setInputView(InputViewOnPrimary);
156                 }
157             }
158         } else if (event->type() == QEvent::TouchEnd) {
159             setInputView(InputViewNone);
160             d_ptr->m_holdTimer->stop();
161             // Handle possible selection
162             if (!scene()->isSlicingActive()
163                     && QAbstract3DInputHandlerPrivate::InputStatePinching
164                     != d_ptr->m_inputState) {
165                 d_ptr->handleSelection(pointerPos);
166             }
167         } else if (event->type() == QEvent::TouchUpdate) {
168             if (!scene()->isSlicingActive()) {
169                 d_ptr->m_touchHoldPos = pointerPos;
170                 // Handle rotation
171                 d_ptr->handleRotation(pointerPos);
172             }
173         }
174     } else {
175         d_ptr->m_holdTimer->stop();
176     }
177 }
178 
QTouch3DInputHandlerPrivate(QTouch3DInputHandler * q)179 QTouch3DInputHandlerPrivate::QTouch3DInputHandlerPrivate(QTouch3DInputHandler *q)
180     : Q3DInputHandlerPrivate(q),
181       q_ptr(q),
182       m_holdTimer(0),
183       m_inputState(QAbstract3DInputHandlerPrivate::InputStateNone)
184 {
185     m_holdTimer = new QTimer();
186     m_holdTimer->setSingleShot(true);
187     m_holdTimer->setInterval(tapAndHoldTime);
188     connect(m_holdTimer, &QTimer::timeout, this, &QTouch3DInputHandlerPrivate::handleTapAndHold);
189 }
190 
~QTouch3DInputHandlerPrivate()191 QTouch3DInputHandlerPrivate::~QTouch3DInputHandlerPrivate()
192 {
193     m_holdTimer->stop();
194     delete m_holdTimer;
195 }
196 
handlePinchZoom(float distance,const QPoint & pos)197 void QTouch3DInputHandlerPrivate::handlePinchZoom(float distance, const QPoint &pos)
198 {
199     if (q_ptr->isZoomEnabled()) {
200         int newDistance = distance;
201         int prevDist = q_ptr->prevDistance();
202         if (prevDist > 0 && qAbs(prevDist - newDistance) < maxPinchJitter)
203             return;
204         m_inputState = QAbstract3DInputHandlerPrivate::InputStatePinching;
205         Q3DCamera *camera = q_ptr->scene()->activeCamera();
206         int zoomLevel = int(camera->zoomLevel());
207         const int minZoomLevel = int(camera->minZoomLevel());
208         const int maxZoomLevel = int(camera->maxZoomLevel());
209         float zoomRate = qSqrt(qSqrt(zoomLevel));
210         if (newDistance > prevDist)
211             zoomLevel += zoomRate;
212         else
213             zoomLevel -= zoomRate;
214         zoomLevel = qBound(minZoomLevel, zoomLevel, maxZoomLevel);
215 
216         if (q_ptr->isZoomAtTargetEnabled()) {
217             q_ptr->scene()->setGraphPositionQuery(pos);
218             m_zoomAtTargetPending = true;
219             // If zoom at target is enabled, we don't want to zoom yet, as that causes
220             // jitter. Instead, we zoom next frame, when we apply the camera position.
221             m_requestedZoomLevel = zoomLevel;
222             m_driftMultiplier = touchZoomDrift;
223         } else {
224             camera->setZoomLevel(zoomLevel);
225         }
226 
227         q_ptr->setPrevDistance(newDistance);
228     }
229 }
230 
handleTapAndHold()231 void QTouch3DInputHandlerPrivate::handleTapAndHold()
232 {
233     if (q_ptr->isSelectionEnabled()) {
234         QPointF distance = m_startHoldPos - m_touchHoldPos;
235         if (distance.manhattanLength() < maxTapAndHoldJitter) {
236             q_ptr->setInputPosition(m_touchHoldPos.toPoint());
237             q_ptr->scene()->setSelectionQueryPosition(m_touchHoldPos.toPoint());
238             m_inputState = QAbstract3DInputHandlerPrivate::InputStateSelecting;
239         }
240     }
241 }
242 
handleSelection(const QPointF & position)243 void QTouch3DInputHandlerPrivate::handleSelection(const QPointF &position)
244 {
245     if (q_ptr->isSelectionEnabled()) {
246         QPointF distance = m_startHoldPos - position;
247         if (distance.manhattanLength() < maxSelectionJitter) {
248             m_inputState = QAbstract3DInputHandlerPrivate::InputStateSelecting;
249             q_ptr->scene()->setSelectionQueryPosition(position.toPoint());
250         } else {
251             m_inputState = QAbstract3DInputHandlerPrivate::InputStateNone;
252             q_ptr->setInputView(QAbstract3DInputHandler::InputViewNone);
253         }
254         q_ptr->setPreviousInputPos(position.toPoint());
255     }
256 }
257 
handleRotation(const QPointF & position)258 void QTouch3DInputHandlerPrivate::handleRotation(const QPointF &position)
259 {
260     if (q_ptr->isRotationEnabled()
261             && QAbstract3DInputHandlerPrivate::InputStateRotating == m_inputState) {
262         Q3DScene *scene = q_ptr->scene();
263         Q3DCamera *camera = scene->activeCamera();
264         float xRotation = camera->xRotation();
265         float yRotation = camera->yRotation();
266         QPointF inputPos = q_ptr->inputPosition();
267         float mouseMoveX = float(inputPos.x() - position.x())
268                 / (scene->viewport().width() / rotationSpeed);
269         float mouseMoveY = float(inputPos.y() - position.y())
270                 / (scene->viewport().height() / rotationSpeed);
271         xRotation -= mouseMoveX;
272         yRotation -= mouseMoveY;
273         camera->setXRotation(xRotation);
274         camera->setYRotation(yRotation);
275 
276         q_ptr->setPreviousInputPos(inputPos.toPoint());
277         q_ptr->setInputPosition(position.toPoint());
278     }
279 }
280 
281 QT_END_NAMESPACE_DATAVISUALIZATION
282