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