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 "datavisualizationglobal_p.h" |
31 | #include "q3dinputhandler_p.h" |
32 | #include "abstract3dcontroller_p.h" |
33 | |
34 | QT_BEGIN_NAMESPACE_DATAVISUALIZATION |
35 | |
36 | static const int halfSizeZoomLevel = 50; |
37 | static const int oneToOneZoomLevel = 100; |
38 | static const int driftTowardCenterLevel = 175; |
39 | static const float wheelZoomDrift = 0.1f; |
40 | |
41 | static const int nearZoomRangeDivider = 12; |
42 | static const int midZoomRangeDivider = 60; |
43 | static const int farZoomRangeDivider = 120; |
44 | |
45 | static const float rotationSpeed = 100.0f; |
46 | |
47 | /*! |
48 | * \class Q3DInputHandler |
49 | * \inmodule QtDataVisualization |
50 | * \brief Basic wheel mouse based input handler. |
51 | * \since QtDataVisualization 1.0 |
52 | * |
53 | * Q3DInputHandler is the basic input handler for wheel mouse type of input devices. |
54 | * |
55 | * Default input handler has the following functionalty: |
56 | * \table |
57 | * \header |
58 | * \li Mouse action |
59 | * \li Action |
60 | * \row |
61 | * \li Drag with right button pressed |
62 | * \li Rotate graph within limits set for Q3DCamera. |
63 | * \row |
64 | * \li Left click |
65 | * \li Select item under cursor or remove selection if none. |
66 | * May open the secondary view depending on the |
67 | * \l {QAbstract3DGraph::selectionMode}{selection mode}. |
68 | * \row |
69 | * \li Mouse wheel |
70 | * \li Zoom in/out within the allowable zoom range set for Q3DCamera. |
71 | * \row |
72 | * \li Left click on the primary view when the secondary view is visible |
73 | * \li Closes the secondary view. |
74 | * \note Secondary view is available only for Q3DBars and Q3DSurface graphs. |
75 | * \endtable |
76 | * |
77 | * Rotation, zoom, and selection can each be individually disabled using |
78 | * corresponding properties of this class. |
79 | */ |
80 | |
81 | /*! |
82 | * \qmltype InputHandler3D |
83 | * \inqmlmodule QtDataVisualization |
84 | * \since QtDataVisualization 1.2 |
85 | * \ingroup datavisualization_qml |
86 | * \instantiates Q3DInputHandler |
87 | * \brief Basic wheel mouse based input handler. |
88 | * |
89 | * InputHandler3D is the basic input handler for wheel mouse type of input devices. |
90 | * |
91 | * See Q3DInputHandler documentation for more details. |
92 | */ |
93 | |
94 | /*! |
95 | * \qmlproperty bool InputHandler3D::rotationEnabled |
96 | * \since QtDataVisualization 1.2 |
97 | * |
98 | * Defines whether this input handler allows graph rotation. |
99 | * Defaults to \c{true}. |
100 | */ |
101 | |
102 | /*! |
103 | * \qmlproperty bool InputHandler3D::zoomEnabled |
104 | * \since QtDataVisualization 1.2 |
105 | * |
106 | * Defines whether this input handler allows graph zooming. |
107 | * Defaults to \c{true}. |
108 | */ |
109 | |
110 | /*! |
111 | * \qmlproperty bool InputHandler3D::selectionEnabled |
112 | * \since QtDataVisualization 1.2 |
113 | * |
114 | * Defines whether this input handler allows selection from the graph. |
115 | * Defaults to \c{true}. |
116 | */ |
117 | |
118 | /*! |
119 | * \qmlproperty bool InputHandler3D::zoomAtTargetEnabled |
120 | * \since QtDataVisualization 1.2 |
121 | * |
122 | * Defines whether zooming changes the camera target to the position of the input |
123 | * at the time of the zoom. |
124 | * Defaults to \c{true}. |
125 | */ |
126 | |
127 | /*! |
128 | * Constructs the basic mouse input handler. An optional \a parent parameter can be given |
129 | * and is then passed to QObject constructor. |
130 | */ |
131 | Q3DInputHandler::Q3DInputHandler(QObject *parent) : |
132 | QAbstract3DInputHandler(parent), |
133 | d_ptr(new Q3DInputHandlerPrivate(this)) |
134 | { |
135 | } |
136 | |
137 | /*! |
138 | * Destroys the input handler. |
139 | */ |
140 | Q3DInputHandler::~Q3DInputHandler() |
141 | { |
142 | } |
143 | |
144 | // Input event listeners |
145 | /*! |
146 | * Override this to change handling of mouse press events. |
147 | * Mouse press event is given in the \a event and the mouse position in \a mousePos. |
148 | */ |
149 | void Q3DInputHandler::mousePressEvent(QMouseEvent *event, const QPoint &mousePos) |
150 | { |
151 | #if defined(Q_OS_IOS) |
152 | Q_UNUSED(event); |
153 | Q_UNUSED(mousePos); |
154 | #else |
155 | if (Qt::LeftButton == event->button()) { |
156 | if (isSelectionEnabled()) { |
157 | if (scene()->isSlicingActive()) { |
158 | if (scene()->isPointInPrimarySubView(point: mousePos)) |
159 | setInputView(InputViewOnPrimary); |
160 | else if (scene()->isPointInSecondarySubView(point: mousePos)) |
161 | setInputView(InputViewOnSecondary); |
162 | else |
163 | setInputView(InputViewNone); |
164 | } else { |
165 | // update mouse positions to prevent jumping when releasing or repressing a button |
166 | setInputPosition(mousePos); |
167 | scene()->setSelectionQueryPosition(mousePos); |
168 | setInputView(InputViewOnPrimary); |
169 | d_ptr->m_inputState = QAbstract3DInputHandlerPrivate::InputStateSelecting; |
170 | } |
171 | } |
172 | } else if (Qt::MiddleButton == event->button()) { |
173 | if (isRotationEnabled()) { |
174 | // reset rotations |
175 | setInputPosition(QPoint(0, 0)); |
176 | } |
177 | } else if (Qt::RightButton == event->button()) { |
178 | if (isRotationEnabled()) { |
179 | // disable rotating when in slice view |
180 | if (!scene()->isSlicingActive()) |
181 | d_ptr->m_inputState = QAbstract3DInputHandlerPrivate::InputStateRotating; |
182 | // update mouse positions to prevent jumping when releasing or repressing a button |
183 | setInputPosition(mousePos); |
184 | } |
185 | } |
186 | #endif |
187 | } |
188 | |
189 | /*! |
190 | * Override this to change handling of mouse release events. |
191 | * Mouse release event is given in the \a event and the mouse position in \a mousePos. |
192 | */ |
193 | void Q3DInputHandler::mouseReleaseEvent(QMouseEvent *event, const QPoint &mousePos) |
194 | { |
195 | Q_UNUSED(event); |
196 | #if defined(Q_OS_IOS) |
197 | Q_UNUSED(mousePos); |
198 | #else |
199 | if (QAbstract3DInputHandlerPrivate::InputStateRotating == d_ptr->m_inputState) { |
200 | // update mouse positions to prevent jumping when releasing or repressing a button |
201 | setInputPosition(mousePos); |
202 | } |
203 | d_ptr->m_inputState = QAbstract3DInputHandlerPrivate::InputStateNone; |
204 | setInputView(InputViewNone); |
205 | #endif |
206 | } |
207 | |
208 | /*! |
209 | * Override this to change handling of mouse move events. |
210 | * Mouse move event is given in the \a event and the mouse position in \a mousePos. |
211 | */ |
212 | void Q3DInputHandler::mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos) |
213 | { |
214 | Q_UNUSED(event); |
215 | #if defined(Q_OS_IOS) |
216 | Q_UNUSED(mousePos); |
217 | #else |
218 | if (QAbstract3DInputHandlerPrivate::InputStateRotating == d_ptr->m_inputState |
219 | && isRotationEnabled()) { |
220 | // Calculate mouse movement since last frame |
221 | float xRotation = scene()->activeCamera()->xRotation(); |
222 | float yRotation = scene()->activeCamera()->yRotation(); |
223 | float mouseMoveX = float(inputPosition().x() - mousePos.x()) |
224 | / (scene()->viewport().width() / rotationSpeed); |
225 | float mouseMoveY = float(inputPosition().y() - mousePos.y()) |
226 | / (scene()->viewport().height() / rotationSpeed); |
227 | // Apply to rotations |
228 | xRotation -= mouseMoveX; |
229 | yRotation -= mouseMoveY; |
230 | scene()->activeCamera()->setXRotation(xRotation); |
231 | scene()->activeCamera()->setYRotation(yRotation); |
232 | |
233 | setPreviousInputPos(inputPosition()); |
234 | setInputPosition(mousePos); |
235 | } |
236 | #endif |
237 | } |
238 | |
239 | #if QT_CONFIG(wheelevent) |
240 | /*! |
241 | * Override this to change handling of wheel events. |
242 | * The wheel event is given in the \a event. |
243 | */ |
244 | void Q3DInputHandler::wheelEvent(QWheelEvent *event) |
245 | { |
246 | if (isZoomEnabled()) { |
247 | // disable zooming if in slice view |
248 | if (scene()->isSlicingActive()) |
249 | return; |
250 | |
251 | // Adjust zoom level based on what zoom range we're in. |
252 | Q3DCamera *camera = scene()->activeCamera(); |
253 | int zoomLevel = int(camera->zoomLevel()); |
254 | const int minZoomLevel = int(camera->minZoomLevel()); |
255 | const int maxZoomLevel = int(camera->maxZoomLevel()); |
256 | if (zoomLevel > oneToOneZoomLevel) |
257 | zoomLevel += event->angleDelta().y() / nearZoomRangeDivider; |
258 | else if (zoomLevel > halfSizeZoomLevel) |
259 | zoomLevel += event->angleDelta().y() / midZoomRangeDivider; |
260 | else |
261 | zoomLevel += event->angleDelta().y() / farZoomRangeDivider; |
262 | zoomLevel = qBound(min: minZoomLevel, val: zoomLevel, max: maxZoomLevel); |
263 | |
264 | if (isZoomAtTargetEnabled()) { |
265 | scene()->setGraphPositionQuery(event->position().toPoint()); |
266 | d_ptr->m_zoomAtTargetPending = true; |
267 | // If zoom at target is enabled, we don't want to zoom yet, as that causes |
268 | // jitter. Instead, we zoom next frame, when we apply the camera position. |
269 | d_ptr->m_requestedZoomLevel = zoomLevel; |
270 | d_ptr->m_driftMultiplier = wheelZoomDrift; |
271 | } else { |
272 | camera->setZoomLevel(zoomLevel); |
273 | } |
274 | } |
275 | } |
276 | #endif |
277 | |
278 | /*! |
279 | * \property Q3DInputHandler::rotationEnabled |
280 | * \since QtDataVisualization 1.2 |
281 | * |
282 | * \brief Whether this input handler allows graph rotation. |
283 | * |
284 | * Defaults to \c{true}. |
285 | */ |
286 | void Q3DInputHandler::setRotationEnabled(bool enable) |
287 | { |
288 | if (d_ptr->m_rotationEnabled != enable) { |
289 | d_ptr->m_rotationEnabled = enable; |
290 | emit rotationEnabledChanged(enable); |
291 | } |
292 | } |
293 | |
294 | bool Q3DInputHandler::isRotationEnabled() const |
295 | { |
296 | return d_ptr->m_rotationEnabled; |
297 | } |
298 | |
299 | /*! |
300 | * \property Q3DInputHandler::zoomEnabled |
301 | * \since QtDataVisualization 1.2 |
302 | * |
303 | * \brief Whether this input handler allows graph zooming. |
304 | * |
305 | * Defaults to \c{true}. |
306 | */ |
307 | void Q3DInputHandler::setZoomEnabled(bool enable) |
308 | { |
309 | if (d_ptr->m_zoomEnabled != enable) { |
310 | d_ptr->m_zoomEnabled = enable; |
311 | emit zoomEnabledChanged(enable); |
312 | } |
313 | } |
314 | |
315 | bool Q3DInputHandler::isZoomEnabled() const |
316 | { |
317 | return d_ptr->m_zoomEnabled; |
318 | } |
319 | |
320 | /*! |
321 | * \property Q3DInputHandler::selectionEnabled |
322 | * \since QtDataVisualization 1.2 |
323 | * |
324 | * \brief Whether this input handler allows selection from the graph. |
325 | * |
326 | * Defaults to \c{true}. |
327 | */ |
328 | void Q3DInputHandler::setSelectionEnabled(bool enable) |
329 | { |
330 | if (d_ptr->m_selectionEnabled != enable) { |
331 | d_ptr->m_selectionEnabled = enable; |
332 | emit selectionEnabledChanged(enable); |
333 | } |
334 | } |
335 | |
336 | bool Q3DInputHandler::isSelectionEnabled() const |
337 | { |
338 | return d_ptr->m_selectionEnabled; |
339 | } |
340 | |
341 | /*! |
342 | * \property Q3DInputHandler::zoomAtTargetEnabled |
343 | * \since QtDataVisualization 1.2 |
344 | * |
345 | * \brief Whether zooming should change the camera target so that the zoomed point |
346 | * of the graph stays at the same location after the zoom. |
347 | * |
348 | * Defaults to \c{true}. |
349 | */ |
350 | void Q3DInputHandler::setZoomAtTargetEnabled(bool enable) |
351 | { |
352 | if (d_ptr->m_zoomAtTargetEnabled != enable) { |
353 | d_ptr->m_zoomAtTargetEnabled = enable; |
354 | emit zoomAtTargetEnabledChanged(enable); |
355 | } |
356 | } |
357 | |
358 | bool Q3DInputHandler::isZoomAtTargetEnabled() const |
359 | { |
360 | return d_ptr->m_zoomAtTargetEnabled; |
361 | } |
362 | |
363 | Q3DInputHandlerPrivate::Q3DInputHandlerPrivate(Q3DInputHandler *q) |
364 | : q_ptr(q), |
365 | m_inputState(QAbstract3DInputHandlerPrivate::InputStateNone), |
366 | m_rotationEnabled(true), |
367 | m_zoomEnabled(true), |
368 | m_selectionEnabled(true), |
369 | m_zoomAtTargetEnabled(true), |
370 | m_zoomAtTargetPending(false), |
371 | m_controller(0), |
372 | m_requestedZoomLevel(0.0f), |
373 | m_driftMultiplier(0.0f) |
374 | { |
375 | QObject::connect(sender: q, signal: &QAbstract3DInputHandler::sceneChanged, |
376 | receiver: this, slot: &Q3DInputHandlerPrivate::handleSceneChange); |
377 | } |
378 | |
379 | Q3DInputHandlerPrivate::~Q3DInputHandlerPrivate() |
380 | { |
381 | } |
382 | |
383 | void Q3DInputHandlerPrivate::handleSceneChange(Q3DScene *scene) |
384 | { |
385 | if (scene) { |
386 | if (m_controller) { |
387 | QObject::disconnect(sender: m_controller, signal: &Abstract3DController::queriedGraphPositionChanged, |
388 | receiver: this, slot: &Q3DInputHandlerPrivate::handleQueriedGraphPositionChange); |
389 | } |
390 | |
391 | m_controller = qobject_cast<Abstract3DController *>(object: scene->parent()); |
392 | |
393 | if (m_controller) { |
394 | QObject::connect(sender: m_controller, signal: &Abstract3DController::queriedGraphPositionChanged, |
395 | receiver: this, slot: &Q3DInputHandlerPrivate::handleQueriedGraphPositionChange); |
396 | } |
397 | } |
398 | } |
399 | |
400 | void Q3DInputHandlerPrivate::handleQueriedGraphPositionChange() |
401 | { |
402 | if (m_zoomAtTargetPending) { |
403 | // Check if the zoom point is on graph |
404 | QVector3D newTarget = m_controller->queriedGraphPosition(); |
405 | float currentZoom = m_requestedZoomLevel; |
406 | float previousZoom = q_ptr->scene()->activeCamera()->zoomLevel(); |
407 | q_ptr->scene()->activeCamera()->setZoomLevel(currentZoom); |
408 | float diffAdj = 0.0f; |
409 | |
410 | // If zooming in/out outside the graph, or zooming out after certain point, |
411 | // move towards the center. |
412 | if ((qAbs(t: newTarget.x()) > 1.0f |
413 | || qAbs(t: newTarget.y()) > 1.0f |
414 | || qAbs(t: newTarget.z()) > 1.0f) |
415 | || (previousZoom > currentZoom && currentZoom <= driftTowardCenterLevel)) { |
416 | newTarget = zeroVector; |
417 | // Add some extra correction so that we actually reach the center eventually |
418 | diffAdj = m_driftMultiplier; |
419 | if (previousZoom > currentZoom) |
420 | diffAdj *= 2.0f; // Correct towards center little more when zooming out |
421 | } |
422 | |
423 | float zoomFraction = 1.0f - (previousZoom / currentZoom); |
424 | |
425 | // Adjust camera towards the zoom point, attempting to keep the cursor at same graph point |
426 | QVector3D oldTarget = q_ptr->scene()->activeCamera()->target(); |
427 | QVector3D origDiff = newTarget - oldTarget; |
428 | QVector3D diff = origDiff * zoomFraction + (origDiff.normalized() * diffAdj); |
429 | if (diff.length() > origDiff.length()) |
430 | diff = origDiff; |
431 | q_ptr->scene()->activeCamera()->setTarget(oldTarget + diff); |
432 | |
433 | if (q_ptr->scene()->selectionQueryPosition() == Q3DScene::invalidSelectionPoint()) |
434 | m_zoomAtTargetPending = false; |
435 | } |
436 | } |
437 | |
438 | QT_END_NAMESPACE_DATAVISUALIZATION |
439 | |