1/****************************************************************************
2**
3** Copyright (C) 2015 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the QtLocation module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL3$
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 http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or later as published by the Free
28** Software Foundation and appearing in the file LICENSE.GPL included in
29** the packaging of this file. Please review the following information to
30** ensure the GNU General Public License version 2.0 requirements will be
31** met: http://www.gnu.org/licenses/gpl-2.0.html.
32**
33** $QT_END_LICENSE$
34**
35****************************************************************************/
36
37#include "qquickgeomapgesturearea_p.h"
38#include <QtPositioningQuick/private/qquickgeocoordinateanimation_p.h>
39#include "qdeclarativegeomap_p.h"
40#include "error_messages_p.h"
41
42#include <QtGui/QGuiApplication>
43#include <QtGui/qevent.h>
44#if QT_CONFIG(wheelevent)
45#include <QtGui/QWheelEvent>
46#endif
47#include <QtGui/QStyleHints>
48#include <QtQml/qqmlinfo.h>
49#include <QtQuick/QQuickWindow>
50#include <QPropertyAnimation>
51#include <QDebug>
52#include "math.h"
53#include <cmath>
54#include "qgeomap_p.h"
55#include "qdoublevector2d_p.h"
56#include "qlocationutils_p.h"
57#include <QtGui/QMatrix4x4>
58
59
60#define QML_MAP_FLICK_DEFAULTMAXVELOCITY 2500
61#define QML_MAP_FLICK_MINIMUMDECELERATION 500
62#define QML_MAP_FLICK_DEFAULTDECELERATION 2500
63#define QML_MAP_FLICK_MAXIMUMDECELERATION 10000
64
65#define QML_MAP_FLICK_VELOCITY_SAMPLE_PERIOD 38
66// FlickThreshold determines how far the "mouse" must have moved
67// before we perform a flick.
68static const int FlickThreshold = 20;
69// Really slow flicks can be annoying.
70static const qreal MinimumFlickVelocity = 75.0;
71// Tolerance for detecting two finger sliding start
72static const qreal MaximumParallelPosition = 40.0; // in degrees
73// Tolerance for detecting parallel sliding
74static const qreal MaximumParallelSlidingAngle = 4.0; // in degrees
75// Tolerance for starting rotation
76static const qreal MinimumRotationStartingAngle = 15.0; // in degrees
77// Tolerance for starting pinch
78static const qreal MinimumPinchDelta = 40; // in pixels
79// Tolerance for starting tilt when sliding vertical
80static const qreal MinimumPanToTiltDelta = 80; // in pixels;
81
82static qreal distanceBetweenTouchPoints(const QPointF &p1, const QPointF &p2)
83{
84 return QLineF(p1, p2).length();
85}
86
87static qreal angleFromPoints(const QPointF &p1, const QPointF &p2)
88{
89 return QLineF(p1, p2).angle();
90}
91
92// Keeps it in +- 180
93static qreal touchAngle(const QPointF &p1, const QPointF &p2)
94{
95 qreal angle = angleFromPoints(p1, p2);
96 if (angle > 180)
97 angle -= 360;
98 return angle;
99}
100
101// Deals with angles crossing the +-180 edge, assumes that the delta can't be > 180
102static qreal angleDelta(const qreal angle1, const qreal angle2)
103{
104 qreal delta = angle1 - angle2;
105 if (delta > 180.0) // detect crossing angle1 positive, angle2 negative, rotation counterclockwise, difference negative
106 delta = angle1 - angle2 - 360.0;
107 else if (delta < -180.0) // detect crossing angle1 negative, angle2 positive, rotation clockwise, difference positive
108 delta = angle1 - angle2 + 360.0;
109
110 return delta;
111}
112
113static bool pointDragged(const QPointF &pOld, const QPointF &pNew)
114{
115 static const int startDragDistance = qApp->styleHints()->startDragDistance();
116 return ( qAbs(t: pNew.x() - pOld.x()) > startDragDistance
117 || qAbs(t: pNew.y() - pOld.y()) > startDragDistance);
118}
119
120static qreal vectorSize(const QPointF &vector)
121{
122 return std::sqrt(x: vector.x() * vector.x() + vector.y() * vector.y());
123}
124
125// This linearizes the angles around 0, and keep it linear around 180, allowing to differentiate
126// touch angles that are supposed to be parallel (0 or 180 depending on what finger goes first)
127static qreal touchAngleTilting(const QPointF &p1, const QPointF &p2)
128{
129 qreal angle = angleFromPoints(p1, p2);
130 if (angle > 270)
131 angle -= 360;
132 return angle;
133}
134
135static bool movingParallelVertical(const QPointF &p1old, const QPointF &p1new, const QPointF &p2old, const QPointF &p2new)
136{
137 if (!pointDragged(pOld: p1old, pNew: p1new) || !pointDragged(pOld: p2old, pNew: p2new))
138 return false;
139
140 QPointF v1 = p1new - p1old;
141 QPointF v2 = p2new - p2old;
142 qreal v1v2size = vectorSize(vector: v1 + v2);
143
144 if (v1v2size < vectorSize(vector: v1) || v1v2size < vectorSize(vector: v2)) // going in opposite directions
145 return false;
146
147 const qreal newAngle = touchAngleTilting(p1: p1new, p2: p2new);
148 const qreal oldAngle = touchAngleTilting(p1: p1old, p2: p2old);
149 const qreal angleDiff = angleDelta(angle1: newAngle, angle2: oldAngle);
150
151 if (qAbs(t: angleDiff) > MaximumParallelSlidingAngle)
152 return false;
153
154 return true;
155}
156
157QT_BEGIN_NAMESPACE
158
159
160/*!
161 \qmltype MapPinchEvent
162 \instantiates QGeoMapPinchEvent
163 \inqmlmodule QtLocation
164
165 \brief MapPinchEvent type provides basic information about pinch event.
166
167 MapPinchEvent type provides basic information about pinch event. They are
168 present in handlers of MapPinch (for example pinchStarted/pinchUpdated). Events are only
169 guaranteed to be valid for the duration of the handler.
170
171 Except for the \l accepted property, all properties are read-only.
172
173 \section2 Example Usage
174
175 The following example enables the pinch gesture on a map and reacts to the
176 finished event.
177
178 \code
179 Map {
180 id: map
181 gesture.enabled: true
182 gesture.onPinchFinished:{
183 var coordinate1 = map.toCoordinate(gesture.point1)
184 var coordinate2 = map.toCoordinate(gesture.point2)
185 console.log("Pinch started at:")
186 console.log(" Points (" + gesture.point1.x + ", " + gesture.point1.y + ") - (" + gesture.point2.x + ", " + gesture.point2.y + ")")
187 console.log(" Coordinates (" + coordinate1.latitude + ", " + coordinate1.longitude + ") - (" + coordinate2.latitude + ", " + coordinate2.longitude + ")")
188 }
189 }
190 \endcode
191
192 \ingroup qml-QtLocation5-maps
193 \since QtLocation 5.0
194*/
195
196/*!
197 \qmlproperty QPoint QtLocation::MapPinchEvent::center
198
199 This read-only property holds the current center point.
200*/
201
202/*!
203 \qmlproperty real QtLocation::MapPinchEvent::angle
204
205 This read-only property holds the current angle between the two points in
206 the range -180 to 180. Positive values for the angles mean counter-clockwise
207 while negative values mean the clockwise direction. Zero degrees is at the
208 3 o'clock position.
209*/
210
211/*!
212 \qmlproperty QPoint QtLocation::MapPinchEvent::point1
213 \qmlproperty QPoint QtLocation::MapPinchEvent::point2
214
215 These read-only properties hold the actual touch points generating the pinch.
216 The points are not in any particular order.
217*/
218
219/*!
220 \qmlproperty int QtLocation::MapPinchEvent::pointCount
221
222 This read-only property holds the number of points currently touched.
223 The MapPinch will not react until two touch points have initiated a gesture,
224 but will remain active until all touch points have been released.
225*/
226
227/*!
228 \qmlproperty bool QtLocation::MapPinchEvent::accepted
229
230 Setting this property to false in the \c MapPinch::onPinchStarted handler
231 will result in no further pinch events being generated, and the gesture
232 ignored.
233*/
234
235/*!
236 \qmltype MapGestureArea
237 \instantiates QQuickGeoMapGestureArea
238
239 \inqmlmodule QtLocation
240
241 \brief The MapGestureArea type provides Map gesture interaction.
242
243 MapGestureArea objects are used as part of a Map, to provide for panning,
244 flicking and pinch-to-zoom gesture used on touch displays, as well as two finger rotation
245 and two finger parallel vertical sliding to tilt the map.
246 On platforms supporting \l QWheelEvent, using the scroll wheel alone, or in combination with
247 key modifiers Shift or Control will also zoom, rotate or tilt the map, respectively.
248
249 A MapGestureArea is automatically created with a new Map and available with
250 the \l{Map::gesture}{gesture} property. This is the only way
251 to create a MapGestureArea, and once created this way cannot be destroyed
252 without its parent Map.
253
254 The two most commonly used properties of the MapGestureArea are the \l enabled
255 and \l acceptedGestures properties. Both of these must be set before a
256 MapGestureArea will have any effect upon interaction with the Map.
257 The \l flickDeceleration property controls how quickly the map pan slows after contact
258 is released while panning the map.
259
260 \section2 Performance
261
262 The MapGestureArea, when enabled, must process all incoming touch events in
263 order to track the shape and size of the "pinch". The overhead added on
264 touch events can be considered constant time.
265
266 \section2 Example Usage
267
268 The following example enables the pinch and pan gestures on the map, but not flicking. So the
269 map scrolling will halt immediately on releasing the mouse button / touch.
270
271 \code
272 Map {
273 gesture.enabled: true
274 gesture.acceptedGestures: MapGestureArea.PinchGesture | MapGestureArea.PanGesture
275 }
276 \endcode
277
278 \ingroup qml-QtLocation5-maps
279 \since QtLocation 5.0
280*/
281
282/*!
283 \qmlproperty bool QtLocation::MapGestureArea::enabled
284
285 This property holds whether the gestures are enabled.
286*/
287
288/*!
289 \qmlproperty bool QtLocation::MapGestureArea::pinchActive
290
291 This read-only property holds whether the pinch gesture is active.
292*/
293
294/*!
295 \qmlproperty bool QtLocation::MapGestureArea::panActive
296
297 This read-only property holds whether the pan gesture is active.
298
299 \note Change notifications for this property were introduced in Qt 5.5.
300*/
301
302/*!
303 \qmlproperty bool QtLocation::MapGestureArea::rotationActive
304
305 This read-only property holds whether the two-finger rotation gesture is active.
306
307 \since QtLocation 5.9
308*/
309
310/*!
311 \qmlproperty bool QtLocation::MapGestureArea::tiltActive
312
313 This read-only property holds whether the two-finger tilt gesture is active.
314
315 \since QtLocation 5.9
316*/
317
318/*!
319 \qmlproperty real QtLocation::MapGestureArea::maximumZoomLevelChange
320
321 This property holds the maximum zoom level change per pinch, essentially
322 meant to be used for setting the zoom sensitivity.
323
324 It is an indicative measure calculated from the dimensions of the
325 map area, roughly corresponding how much zoom level could change with
326 maximum pinch zoom. Default value is 4.0, maximum value is 10.0
327*/
328
329/*!
330 \qmlproperty real MapGestureArea::flickDeceleration
331
332 This property holds the rate at which a flick will decelerate.
333
334 The default value is 2500.
335*/
336
337/*!
338 \qmlsignal QtLocation::MapGestureArea::pinchStarted(PinchEvent event)
339
340 This signal is emitted when a pinch gesture is started.
341
342 Information about the pinch event is provided in \a event.
343
344 The corresponding handler is \c onPinchStarted.
345
346 \sa pinchUpdated, pinchFinished
347*/
348
349/*!
350 \qmlsignal QtLocation::MapGestureArea::pinchUpdated(PinchEvent event)
351
352 This signal is emitted as the user's fingers move across the map,
353 after the \l pinchStarted signal is emitted.
354
355 Information about the pinch event is provided in \a event.
356
357 The corresponding handler is \c onPinchUpdated.
358
359 \sa pinchStarted, pinchFinished
360*/
361
362/*!
363 \qmlsignal QtLocation::MapGestureArea::pinchFinished(PinchEvent event)
364
365 This signal is emitted at the end of a pinch gesture.
366
367 Information about the pinch event is provided in \a event.
368
369 The corresponding handler is \c onPinchFinished.
370
371 \sa pinchStarted, pinchUpdated
372*/
373
374/*!
375 \qmlsignal QtLocation::MapGestureArea::panStarted()
376
377 This signal is emitted when the map begins to move due to user
378 interaction. Typically this means that the user is dragging a finger -
379 or a mouse with one of more mouse buttons pressed - on the map.
380
381 The corresponding handler is \c onPanStarted.
382*/
383
384/*!
385 \qmlsignal QtLocation::MapGestureArea::panFinished()
386
387 This signal is emitted when the map stops moving due to user
388 interaction. If a flick was generated, this signal is
389 emitted before flick starts. If a flick was not
390 generated, this signal is emitted when the
391 user stops dragging - that is a mouse or touch release.
392
393 The corresponding handler is \c onPanFinished.
394
395*/
396
397/*!
398 \qmlsignal QtLocation::MapGestureArea::flickStarted()
399
400 This signal is emitted when the map is flicked. A flick
401 starts from the point where the mouse or touch was released,
402 while still in motion.
403
404 The corresponding handler is \c onFlickStarted.
405*/
406
407/*!
408 \qmlsignal QtLocation::MapGestureArea::flickFinished()
409
410 This signal is emitted when the map stops moving due to a flick.
411
412 The corresponding handler is \c onFlickFinished.
413*/
414
415/*!
416 \qmlsignal QtLocation::MapGestureArea::rotationStarted(PinchEvent event)
417
418 This signal is emitted when a two-finger rotation gesture is started.
419
420 Information about the pinch event is provided in \a event.
421
422 The corresponding handler is \c onRotationStarted.
423
424 \sa rotationUpdated(), rotationFinished()
425
426 \since QtLocation 5.9
427*/
428
429/*!
430 \qmlsignal QtLocation::MapGestureArea::rotationUpdated(PinchEvent event)
431
432 This signal is emitted as the user's fingers move across the map,
433 after the \l rotationStarted() signal is emitted.
434
435 Information about the pinch event is provided in \a event.
436
437 The corresponding handler is \c onRotationUpdated.
438
439 \sa rotationStarted(), rotationFinished()
440
441 \since QtLocation 5.9
442*/
443
444/*!
445 \qmlsignal QtLocation::MapGestureArea::rotationFinished(PinchEvent event)
446
447 This signal is emitted at the end of a two-finger rotation gesture.
448
449 Information about the pinch event is provided in \a event.
450
451 The corresponding handler is \c onRotationFinished.
452
453 \sa rotationStarted(), rotationUpdated()
454
455 \since QtLocation 5.9
456*/
457
458/*!
459 \qmlsignal QtLocation::MapGestureArea::tiltStarted(PinchEvent event)
460
461 This signal is emitted when a two-finger tilt gesture is started.
462
463 Information about the pinch event is provided in \a event.
464
465 The corresponding handler is \c onTiltStarted.
466
467 \sa tiltUpdated(), tiltFinished()
468
469 \since QtLocation 5.9
470*/
471
472/*!
473 \qmlsignal QtLocation::MapGestureArea::tiltUpdated(PinchEvent event)
474
475 This signal is emitted as the user's fingers move across the map,
476 after the \l tiltStarted signal is emitted.
477
478 Information about the pinch event is provided in \a event.
479
480 The corresponding handler is \c onTiltUpdated.
481
482 \sa tiltStarted(), tiltFinished()
483
484 \since QtLocation 5.9
485*/
486
487/*!
488 \qmlsignal QtLocation::MapGestureArea::tiltFinished(PinchEvent event)
489
490 This signal is emitted at the end of a two-finger tilt gesture.
491
492 Information about the pinch event is provided in \a event.
493
494 The corresponding handler is \c onTiltFinished.
495
496 \sa tiltStarted(), tiltUpdated()
497
498 \since QtLocation 5.9
499*/
500
501QQuickGeoMapGestureArea::QQuickGeoMapGestureArea(QDeclarativeGeoMap *map)
502 : QQuickItem(map),
503 m_map(0),
504 m_declarativeMap(map),
505 m_enabled(true),
506 m_acceptedGestures(PinchGesture | PanGesture | FlickGesture | RotationGesture | TiltGesture),
507 m_preventStealing(false)
508{
509 m_touchPointState = touchPoints0;
510 m_pinchState = pinchInactive;
511 m_flickState = flickInactive;
512 m_rotationState = rotationInactive;
513 m_tiltState = tiltInactive;
514}
515
516/*!
517 \internal
518*/
519void QQuickGeoMapGestureArea::setMap(QGeoMap *map)
520{
521 if (m_map || !map)
522 return;
523
524 m_map = map;
525 m_flick.m_animation = new QQuickGeoCoordinateAnimation(this);
526 m_flick.m_animation->setTargetObject(m_declarativeMap);
527 m_flick.m_animation->setProperty(QStringLiteral("center"));
528 m_flick.m_animation->setEasing(QEasingCurve(QEasingCurve::OutQuad));
529 connect(sender: m_flick.m_animation, signal: &QQuickAbstractAnimation::stopped, receiver: this, slot: &QQuickGeoMapGestureArea::handleFlickAnimationStopped);
530 m_map->setAcceptedGestures(pan: panEnabled(), flick: flickEnabled(), pinch: pinchEnabled(), rotate: rotationEnabled(), tilt: tiltEnabled());
531}
532
533/*!
534 \qmlproperty bool QtQuick::MapGestureArea::preventStealing
535 This property holds whether the mouse events may be stolen from this
536 MapGestureArea.
537
538 If a Map is placed within an item that filters child mouse
539 and touch events, such as Flickable, the mouse and touch events
540 may be stolen from the MapGestureArea if a gesture is recognized
541 by the parent item, e.g. a flick gesture. If preventStealing is
542 set to \c true, no item will steal the mouse and touch events.
543
544 Note that setting preventStealing to \c true once an item has started
545 stealing events has no effect until the next press event.
546
547 By default this property is set to \c false.
548*/
549
550bool QQuickGeoMapGestureArea::preventStealing() const
551{
552 return m_preventStealing;
553}
554
555void QQuickGeoMapGestureArea::setPreventStealing(bool prevent)
556{
557 if (prevent != m_preventStealing) {
558 m_preventStealing = prevent;
559 m_declarativeMap->setKeepMouseGrab(m_preventStealing && m_enabled);
560 m_declarativeMap->setKeepTouchGrab(m_preventStealing && m_enabled);
561 emit preventStealingChanged();
562 }
563}
564
565QQuickGeoMapGestureArea::~QQuickGeoMapGestureArea()
566{
567}
568
569/*!
570 \qmlproperty enumeration QtLocation::MapGestureArea::acceptedGestures
571
572 This property holds a bit field of gestures that are accepted. By default,
573 all gestures are enabled.
574
575 \value MapGestureArea.NoGesture
576 Don't support any additional gestures (value: 0x0000).
577
578 \value MapGestureArea.PinchGesture
579 Support the map pinch gesture (value: 0x0001).
580
581 \value MapGestureArea.PanGesture
582 Support the map pan gesture (value: 0x0002).
583
584 \value MapGestureArea.FlickGesture
585 Support the map flick gesture (value: 0x0004).
586
587 \value MapGestureArea.RotationGesture
588 Support the map rotation gesture (value: 0x0008).
589
590 \value MapGestureArea.TiltGesture
591 Support the map tilt gesture (value: 0x0010).
592*/
593
594QQuickGeoMapGestureArea::AcceptedGestures QQuickGeoMapGestureArea::acceptedGestures() const
595{
596 return m_acceptedGestures;
597}
598
599
600void QQuickGeoMapGestureArea::setAcceptedGestures(AcceptedGestures acceptedGestures)
601{
602 if (acceptedGestures == m_acceptedGestures)
603 return;
604 m_acceptedGestures = acceptedGestures;
605
606 if (enabled()) {
607 setPanEnabled(acceptedGestures & PanGesture);
608 setFlickEnabled(acceptedGestures & FlickGesture);
609 setPinchEnabled(acceptedGestures & PinchGesture);
610 setRotationEnabled(acceptedGestures & RotationGesture);
611 setTiltEnabled(acceptedGestures & TiltGesture);
612 }
613
614 if (m_map)
615 m_map->setAcceptedGestures(pan: panEnabled(), flick: flickEnabled(), pinch: pinchEnabled(), rotate: rotationEnabled(), tilt: tiltEnabled());
616
617 emit acceptedGesturesChanged();
618}
619
620/*!
621 \internal
622*/
623bool QQuickGeoMapGestureArea::isPinchActive() const
624{
625 return m_pinchState == pinchActive;
626}
627
628/*!
629 \internal
630*/
631bool QQuickGeoMapGestureArea::isRotationActive() const
632{
633 return m_rotationState == rotationActive;
634}
635
636/*!
637 \internal
638*/
639bool QQuickGeoMapGestureArea::isTiltActive() const
640{
641 return m_tiltState == tiltActive;
642}
643
644/*!
645 \internal
646*/
647bool QQuickGeoMapGestureArea::isPanActive() const
648{
649 return m_flickState == panActive || m_flickState == flickActive;
650}
651
652/*!
653 \internal
654*/
655bool QQuickGeoMapGestureArea::enabled() const
656{
657 return m_enabled;
658}
659
660/*!
661 \internal
662*/
663void QQuickGeoMapGestureArea::setEnabled(bool enabled)
664{
665 if (enabled == m_enabled)
666 return;
667 m_enabled = enabled;
668
669 if (enabled) {
670 setPanEnabled(m_acceptedGestures & PanGesture);
671 setFlickEnabled(m_acceptedGestures & FlickGesture);
672 setPinchEnabled(m_acceptedGestures & PinchGesture);
673 setRotationEnabled(m_acceptedGestures & RotationGesture);
674 setTiltEnabled(m_acceptedGestures & TiltGesture);
675 } else {
676 setPanEnabled(false);
677 setFlickEnabled(false);
678 setPinchEnabled(false);
679 setRotationEnabled(false);
680 setTiltEnabled(false);
681 }
682 if (m_map)
683 m_map->setAcceptedGestures(pan: panEnabled(), flick: flickEnabled(), pinch: pinchEnabled(), rotate: rotationEnabled(), tilt: tiltEnabled());
684
685 emit enabledChanged();
686}
687
688/*!
689 \internal
690*/
691bool QQuickGeoMapGestureArea::pinchEnabled() const
692{
693 return m_pinch.m_pinchEnabled;
694}
695
696/*!
697 \internal
698*/
699void QQuickGeoMapGestureArea::setPinchEnabled(bool enabled)
700{
701 m_pinch.m_pinchEnabled = enabled;
702}
703
704/*!
705 \internal
706*/
707bool QQuickGeoMapGestureArea::rotationEnabled() const
708{
709 return m_pinch.m_rotationEnabled;
710}
711
712/*!
713 \internal
714*/
715void QQuickGeoMapGestureArea::setRotationEnabled(bool enabled)
716{
717 m_pinch.m_rotationEnabled = enabled;
718}
719
720/*!
721 \internal
722*/
723bool QQuickGeoMapGestureArea::tiltEnabled() const
724{
725 return m_pinch.m_tiltEnabled;
726}
727
728/*!
729 \internal
730*/
731void QQuickGeoMapGestureArea::setTiltEnabled(bool enabled)
732{
733 m_pinch.m_tiltEnabled = enabled;
734}
735
736/*!
737 \internal
738*/
739bool QQuickGeoMapGestureArea::panEnabled() const
740{
741 return m_flick.m_panEnabled;
742}
743
744/*!
745 \internal
746*/
747void QQuickGeoMapGestureArea::setPanEnabled(bool enabled)
748{
749 if (enabled == m_flick.m_panEnabled)
750 return;
751 m_flick.m_panEnabled = enabled;
752
753 // unlike the pinch, the pan existing functionality is to stop immediately
754 if (!enabled) {
755 stopPan();
756 m_flickState = flickInactive;
757 }
758}
759
760/*!
761 \internal
762*/
763bool QQuickGeoMapGestureArea::flickEnabled() const
764{
765 return m_flick.m_flickEnabled;
766}
767
768/*!
769 \internal
770*/
771void QQuickGeoMapGestureArea::setFlickEnabled(bool enabled)
772{
773 if (enabled == m_flick.m_flickEnabled)
774 return;
775 m_flick.m_flickEnabled = enabled;
776 // unlike the pinch, the flick existing functionality is to stop immediately
777 if (!enabled) {
778 bool stateActive = (m_flickState != flickInactive);
779 stopFlick();
780 if (stateActive) {
781 if (m_flick.m_panEnabled)
782 m_flickState = panActive;
783 else
784 m_flickState = flickInactive;
785 }
786 }
787}
788
789/*!
790 \internal
791 Used internally to set the minimum zoom level of the gesture area.
792 The caller is responsible to only send values that are valid
793 for the map plugin. Negative values are ignored.
794 */
795void QQuickGeoMapGestureArea::setMinimumZoomLevel(qreal min)
796{
797 // TODO: remove m_zoom.m_minimum and m_maximum and use m_declarativeMap directly instead.
798 if (min >= 0)
799 m_pinch.m_zoom.m_minimum = min;
800}
801
802/*!
803 \internal
804 */
805qreal QQuickGeoMapGestureArea::minimumZoomLevel() const
806{
807 return m_pinch.m_zoom.m_minimum;
808}
809
810/*!
811 \internal
812 Used internally to set the maximum zoom level of the gesture area.
813 The caller is responsible to only send values that are valid
814 for the map plugin. Negative values are ignored.
815 */
816void QQuickGeoMapGestureArea::setMaximumZoomLevel(qreal max)
817{
818 if (max >= 0)
819 m_pinch.m_zoom.m_maximum = max;
820}
821
822/*!
823 \internal
824 */
825qreal QQuickGeoMapGestureArea::maximumZoomLevel() const
826{
827 return m_pinch.m_zoom.m_maximum;
828}
829
830/*!
831 \internal
832*/
833qreal QQuickGeoMapGestureArea::maximumZoomLevelChange() const
834{
835 return m_pinch.m_zoom.maximumChange;
836}
837
838/*!
839 \internal
840*/
841void QQuickGeoMapGestureArea::setMaximumZoomLevelChange(qreal maxChange)
842{
843 if (maxChange == m_pinch.m_zoom.maximumChange || maxChange < 0.1 || maxChange > 10.0)
844 return;
845 m_pinch.m_zoom.maximumChange = maxChange;
846 emit maximumZoomLevelChangeChanged();
847}
848
849/*!
850 \internal
851*/
852qreal QQuickGeoMapGestureArea::flickDeceleration() const
853{
854 return m_flick.m_deceleration;
855}
856
857/*!
858 \internal
859*/
860void QQuickGeoMapGestureArea::setFlickDeceleration(qreal deceleration)
861{
862 if (deceleration < QML_MAP_FLICK_MINIMUMDECELERATION)
863 deceleration = QML_MAP_FLICK_MINIMUMDECELERATION;
864 else if (deceleration > QML_MAP_FLICK_MAXIMUMDECELERATION)
865 deceleration = QML_MAP_FLICK_MAXIMUMDECELERATION;
866 if (deceleration == m_flick.m_deceleration)
867 return;
868 m_flick.m_deceleration = deceleration;
869 emit flickDecelerationChanged();
870}
871
872/*!
873 \internal
874*/
875QTouchEvent::TouchPoint* createTouchPointFromMouseEvent(QMouseEvent *event, Qt::TouchPointState state)
876{
877 // this is only partially filled. But since it is only partially used it works
878 // more robust would be to store a list of QPointFs rather than TouchPoints
879 QTouchEvent::TouchPoint* newPoint = new QTouchEvent::TouchPoint();
880 newPoint->setPos(event->localPos());
881 newPoint->setScenePos(event->windowPos());
882 newPoint->setScreenPos(event->screenPos());
883 newPoint->setState(state);
884 newPoint->setId(0);
885 return newPoint;
886}
887
888/*!
889 \internal
890*/
891void QQuickGeoMapGestureArea::handleMousePressEvent(QMouseEvent *event)
892{
893 if (m_map && m_map->handleEvent(event)) {
894 event->accept();
895 return;
896 }
897
898 m_mousePoint.reset(other: createTouchPointFromMouseEvent(event, state: Qt::TouchPointPressed));
899 if (m_touchPoints.isEmpty())
900 update();
901 event->accept();
902}
903
904/*!
905 \internal
906*/
907void QQuickGeoMapGestureArea::handleMouseMoveEvent(QMouseEvent *event)
908{
909 if (m_map && m_map->handleEvent(event)) {
910 event->accept();
911 return;
912 }
913
914 m_mousePoint.reset(other: createTouchPointFromMouseEvent(event, state: Qt::TouchPointMoved));
915 if (m_touchPoints.isEmpty())
916 update();
917 event->accept();
918}
919
920/*!
921 \internal
922*/
923void QQuickGeoMapGestureArea::handleMouseReleaseEvent(QMouseEvent *event)
924{
925 if (m_map && m_map->handleEvent(event)) {
926 event->accept();
927 return;
928 }
929
930 if (!m_mousePoint.isNull()) {
931 //this looks super ugly , however is required in case we do not get synthesized MouseReleaseEvent
932 //and we reset the point already in handleTouchUngrabEvent
933 m_mousePoint.reset(other: createTouchPointFromMouseEvent(event, state: Qt::TouchPointReleased));
934 if (m_touchPoints.isEmpty())
935 update();
936 }
937 event->accept();
938}
939
940/*!
941 \internal
942*/
943void QQuickGeoMapGestureArea::handleMouseUngrabEvent()
944{
945
946 if (m_touchPoints.isEmpty() && !m_mousePoint.isNull()) {
947 m_mousePoint.reset();
948 update();
949 } else {
950 m_mousePoint.reset();
951 }
952}
953
954/*!
955 \internal
956*/
957void QQuickGeoMapGestureArea::handleTouchUngrabEvent()
958{
959 m_touchPoints.clear();
960 //this is needed since in some cases mouse release is not delivered
961 //(second touch point breaks mouse synthesized events)
962 m_mousePoint.reset();
963 update();
964}
965
966/*!
967 \internal
968*/
969void QQuickGeoMapGestureArea::handleTouchEvent(QTouchEvent *event)
970{
971 if (m_map && m_map->handleEvent(event)) {
972 event->accept();
973 return;
974 }
975
976 m_touchPoints.clear();
977 m_mousePoint.reset();
978
979 for (int i = 0; i < event->touchPoints().count(); ++i) {
980 auto point = event->touchPoints().at(i);
981 if (point.state() != Qt::TouchPointReleased)
982 m_touchPoints << point;
983 }
984 if (event->touchPoints().count() >= 2)
985 event->accept();
986 else
987 event->ignore();
988 update();
989}
990
991#if QT_CONFIG(wheelevent)
992void QQuickGeoMapGestureArea::handleWheelEvent(QWheelEvent *event)
993{
994 if (!m_map)
995 return;
996
997 if (m_map->handleEvent(event)) {
998 event->accept();
999 return;
1000 }
1001
1002 const QGeoCoordinate &wheelGeoPos = m_declarativeMap->toCoordinate(position: event->position(), clipToViewPort: false);
1003 const QPointF &preZoomPoint = event->position();
1004
1005 // Not using AltModifier as, for some reason, it causes angleDelta to be 0
1006 if (event->modifiers() & Qt::ShiftModifier && rotationEnabled()) {
1007 emit rotationStarted(pinch: &m_pinch.m_event);
1008 // First set bearing
1009 const double bearingDelta = event->angleDelta().y() * qreal(0.05);
1010 m_declarativeMap->setBearing(bearing: m_declarativeMap->bearing() + bearingDelta, coordinate: wheelGeoPos);
1011 emit rotationUpdated(pinch: &m_pinch.m_event);
1012 emit rotationFinished(pinch: &m_pinch.m_event);
1013 } else if (event->modifiers() & Qt::ControlModifier && tiltEnabled()) {
1014 emit tiltStarted(pinch: &m_pinch.m_event);
1015 const double tiltDelta = event->angleDelta().y() * qreal(0.05);
1016 m_declarativeMap->setTilt(m_declarativeMap->tilt() + tiltDelta);
1017 emit tiltUpdated(pinch: &m_pinch.m_event);
1018 emit tiltFinished(pinch: &m_pinch.m_event);
1019 } else if (pinchEnabled()) {
1020 const double zoomLevelDelta = event->angleDelta().y() * qreal(0.001);
1021 // Gesture area should always honor maxZL, but Map might not.
1022 m_declarativeMap->setZoomLevel(zoomLevel: qMin<qreal>(a: m_declarativeMap->zoomLevel() + zoomLevelDelta, b: maximumZoomLevel()),
1023 overzoom: false);
1024 const QPointF &postZoomPoint = m_declarativeMap->fromCoordinate(coordinate: wheelGeoPos, clipToViewPort: false);
1025
1026 if (preZoomPoint != postZoomPoint) // need to re-anchor the wheel geoPos to the event position
1027 m_declarativeMap->alignCoordinateToPoint(coordinate: wheelGeoPos, point: preZoomPoint);
1028 }
1029 event->accept();
1030}
1031#endif
1032
1033/*!
1034 \internal
1035*/
1036void QQuickGeoMapGestureArea::clearTouchData()
1037{
1038 m_flickVector = QVector2D();
1039 m_touchPointsCentroid.setX(0);
1040 m_touchPointsCentroid.setY(0);
1041 m_touchCenterCoord.setLongitude(0);
1042 m_touchCenterCoord.setLatitude(0);
1043 m_startCoord.setLongitude(0);
1044 m_startCoord.setLatitude(0);
1045}
1046
1047
1048/*!
1049 \internal
1050*/
1051void QQuickGeoMapGestureArea::updateFlickParameters(const QPointF &pos)
1052{
1053 // Take velocity samples every sufficient period of time, used later to determine the flick
1054 // duration and speed (when mouse is released).
1055 qreal elapsed = qreal(m_lastPosTime.elapsed());
1056
1057 if (elapsed >= QML_MAP_FLICK_VELOCITY_SAMPLE_PERIOD) {
1058 elapsed /= 1000.;
1059 qreal vel = distanceBetweenTouchPoints(p1: pos, p2: m_lastPos) / elapsed;
1060 m_flickVector = (QVector2D(pos) - QVector2D(m_lastPos)).normalized();
1061 m_flickVector *= qBound<qreal>(min: -m_flick.m_maxVelocity, val: vel, max: m_flick.m_maxVelocity);
1062
1063 m_lastPos = pos;
1064 m_lastPosTime.restart();
1065 }
1066}
1067
1068void QQuickGeoMapGestureArea::setTouchPointState(const QQuickGeoMapGestureArea::TouchPointState state)
1069{
1070 m_touchPointState = state;
1071}
1072
1073void QQuickGeoMapGestureArea::setFlickState(const QQuickGeoMapGestureArea::FlickState state)
1074{
1075 m_flickState = state;
1076}
1077
1078void QQuickGeoMapGestureArea::setTiltState(const QQuickGeoMapGestureArea::TiltState state)
1079{
1080 m_tiltState = state;
1081}
1082
1083void QQuickGeoMapGestureArea::setRotationState(const QQuickGeoMapGestureArea::RotationState state)
1084{
1085 m_rotationState = state;
1086}
1087
1088void QQuickGeoMapGestureArea::setPinchState(const QQuickGeoMapGestureArea::PinchState state)
1089{
1090 m_pinchState = state;
1091}
1092
1093/*!
1094 \internal
1095*/
1096
1097bool QQuickGeoMapGestureArea::isActive() const
1098{
1099 return isPanActive() || isPinchActive() || isRotationActive() || isTiltActive();
1100}
1101
1102/*!
1103 \internal
1104*/
1105// simplify the gestures by using a state-machine format (easy to move to a future state machine)
1106void QQuickGeoMapGestureArea::update()
1107{
1108 if (!m_map)
1109 return;
1110 // First state machine is for the number of touch points
1111
1112 //combine touch with mouse event
1113 m_allPoints.clear();
1114 m_allPoints << m_touchPoints;
1115 if (m_allPoints.isEmpty() && !m_mousePoint.isNull())
1116 m_allPoints << *m_mousePoint.data();
1117 std::sort(first: m_allPoints.begin(), last: m_allPoints.end(), comp: [](const QTouchEvent::TouchPoint &tp1, const QTouchEvent::TouchPoint &tp2) { return tp1.id() < tp2.id(); });
1118
1119 touchPointStateMachine();
1120
1121 // Parallel state machine for tilt. Tilt goes first as it blocks anything else, when started.
1122 // But tilting can also only start if nothing else is active.
1123 if (isTiltActive() || m_pinch.m_tiltEnabled)
1124 tiltStateMachine();
1125
1126 // Parallel state machine for pinch
1127 if (isPinchActive() || m_pinch.m_pinchEnabled)
1128 pinchStateMachine();
1129
1130 // Parallel state machine for rotation.
1131 if (isRotationActive() || m_pinch.m_rotationEnabled)
1132 rotationStateMachine();
1133
1134 // Parallel state machine for pan (since you can pan at the same time as pinching)
1135 // The stopPan function ensures that pan stops immediately when disabled,
1136 // but the isPanActive() below allows pan continue its current gesture if you disable
1137 // the whole gesture.
1138 // Pan goes last because it does reanchoring in updatePan() which makes the map
1139 // properly rotate around the touch point centroid.
1140 if (isPanActive() || m_flick.m_flickEnabled || m_flick.m_panEnabled)
1141 panStateMachine();
1142}
1143
1144/*!
1145 \internal
1146*/
1147void QQuickGeoMapGestureArea::touchPointStateMachine()
1148{
1149 // Transitions:
1150 switch (m_touchPointState) {
1151 case touchPoints0:
1152 if (m_allPoints.count() == 1) {
1153 clearTouchData();
1154 startOneTouchPoint();
1155 setTouchPointState(touchPoints1);
1156 } else if (m_allPoints.count() >= 2) {
1157 clearTouchData();
1158 startTwoTouchPoints();
1159 setTouchPointState(touchPoints2);
1160 }
1161 break;
1162 case touchPoints1:
1163 if (m_allPoints.count() == 0) {
1164 setTouchPointState(touchPoints0);
1165 } else if (m_allPoints.count() == 2) {
1166 m_touchCenterCoord = m_declarativeMap->toCoordinate(position: m_touchPointsCentroid, clipToViewPort: false);
1167 startTwoTouchPoints();
1168 setTouchPointState(touchPoints2);
1169 }
1170 break;
1171 case touchPoints2:
1172 if (m_allPoints.count() == 0) {
1173 setTouchPointState(touchPoints0);
1174 } else if (m_allPoints.count() == 1) {
1175 m_touchCenterCoord = m_declarativeMap->toCoordinate(position: m_touchPointsCentroid, clipToViewPort: false);
1176 startOneTouchPoint();
1177 setTouchPointState(touchPoints1);
1178 }
1179 break;
1180 };
1181
1182 // Update
1183 switch (m_touchPointState) {
1184 case touchPoints0:
1185 break; // do nothing if no touch points down
1186 case touchPoints1:
1187 updateOneTouchPoint();
1188 break;
1189 case touchPoints2:
1190 updateTwoTouchPoints();
1191 break;
1192 }
1193}
1194
1195/*!
1196 \internal
1197*/
1198void QQuickGeoMapGestureArea::startOneTouchPoint()
1199{
1200 m_sceneStartPoint1 = mapFromScene(point: m_allPoints.at(i: 0).scenePos());
1201 m_lastPos = m_sceneStartPoint1;
1202 m_lastPosTime.start();
1203 QGeoCoordinate startCoord = m_declarativeMap->toCoordinate(position: m_sceneStartPoint1, clipToViewPort: false);
1204 // ensures a smooth transition for panning
1205 m_startCoord.setLongitude(m_startCoord.longitude() + startCoord.longitude() -
1206 m_touchCenterCoord.longitude());
1207 m_startCoord.setLatitude(m_startCoord.latitude() + startCoord.latitude() -
1208 m_touchCenterCoord.latitude());
1209}
1210
1211/*!
1212 \internal
1213*/
1214void QQuickGeoMapGestureArea::updateOneTouchPoint()
1215{
1216 m_touchPointsCentroid = mapFromScene(point: m_allPoints.at(i: 0).scenePos());
1217 updateFlickParameters(pos: m_touchPointsCentroid);
1218}
1219
1220/*!
1221 \internal
1222*/
1223void QQuickGeoMapGestureArea::startTwoTouchPoints()
1224{
1225 m_sceneStartPoint1 = mapFromScene(point: m_allPoints.at(i: 0).scenePos());
1226 m_sceneStartPoint2 = mapFromScene(point: m_allPoints.at(i: 1).scenePos());
1227 QPointF startPos = (m_sceneStartPoint1 + m_sceneStartPoint2) * 0.5;
1228 m_lastPos = startPos;
1229 m_lastPosTime.start();
1230 QGeoCoordinate startCoord = m_declarativeMap->toCoordinate(position: startPos, clipToViewPort: false);
1231 m_startCoord.setLongitude(m_startCoord.longitude() + startCoord.longitude() -
1232 m_touchCenterCoord.longitude());
1233 m_startCoord.setLatitude(m_startCoord.latitude() + startCoord.latitude() -
1234 m_touchCenterCoord.latitude());
1235 m_twoTouchAngleStart = touchAngle(p1: m_sceneStartPoint1, p2: m_sceneStartPoint2); // Initial angle used for calculating rotation
1236 m_distanceBetweenTouchPointsStart = distanceBetweenTouchPoints(p1: m_sceneStartPoint1, p2: m_sceneStartPoint2);
1237 m_twoTouchPointsCentroidStart = (m_sceneStartPoint1 + m_sceneStartPoint2) / 2;
1238}
1239
1240/*!
1241 \internal
1242*/
1243void QQuickGeoMapGestureArea::updateTwoTouchPoints()
1244{
1245 QPointF p1 = mapFromScene(point: m_allPoints.at(i: 0).scenePos());
1246 QPointF p2 = mapFromScene(point: m_allPoints.at(i: 1).scenePos());
1247 m_distanceBetweenTouchPoints = distanceBetweenTouchPoints(p1, p2);
1248 m_touchPointsCentroid = (p1 + p2) / 2;
1249 updateFlickParameters(pos: m_touchPointsCentroid);
1250
1251 m_twoTouchAngle = touchAngle(p1, p2);
1252}
1253
1254/*!
1255 \internal
1256*/
1257void QQuickGeoMapGestureArea::tiltStateMachine()
1258{
1259 TiltState lastState = m_tiltState;
1260 // Transitions:
1261 switch (m_tiltState) {
1262 case tiltInactive:
1263 if (m_allPoints.count() >= 2) {
1264 if (!isRotationActive() && !isPinchActive() && canStartTilt()) { // only gesture that can be overridden: pan/flick
1265 m_declarativeMap->setKeepMouseGrab(true);
1266 m_declarativeMap->setKeepTouchGrab(true);
1267 startTilt();
1268 setTiltState(tiltActive);
1269 } else {
1270 setTiltState(tiltInactiveTwoPoints);
1271 }
1272 }
1273 break;
1274 case tiltInactiveTwoPoints:
1275 if (m_allPoints.count() <= 1) {
1276 setTiltState(tiltInactive);
1277 } else {
1278 if (!isRotationActive() && !isPinchActive() && canStartTilt()) { // only gesture that can be overridden: pan/flick
1279 m_declarativeMap->setKeepMouseGrab(true);
1280 m_declarativeMap->setKeepTouchGrab(true);
1281 startTilt();
1282 setTiltState(tiltActive);
1283 }
1284 }
1285 break;
1286 case tiltActive:
1287 if (m_allPoints.count() <= 1) {
1288 setTiltState(tiltInactive);
1289 m_declarativeMap->setKeepMouseGrab(m_preventStealing);
1290 m_declarativeMap->setKeepTouchGrab(m_preventStealing);
1291 endTilt();
1292 }
1293 break;
1294 }
1295 // This line implements an exclusive state machine, where the transitions and updates don't
1296 // happen on the same frame
1297 if (m_tiltState != lastState) {
1298 emit tiltActiveChanged();
1299 return;
1300 }
1301
1302 // Update
1303 switch (m_tiltState) {
1304 case tiltInactive:
1305 case tiltInactiveTwoPoints:
1306 break; // do nothing
1307 case tiltActive:
1308 updateTilt();
1309 break;
1310 }
1311}
1312
1313bool validateTouchAngleForTilting(const qreal angle)
1314{
1315 return ((qAbs(t: angle) - 180.0) < MaximumParallelPosition) || (qAbs(t: angle) < MaximumParallelPosition);
1316}
1317
1318/*!
1319 \internal
1320*/
1321bool QQuickGeoMapGestureArea::canStartTilt()
1322{
1323 if (m_allPoints.count() >= 2) {
1324 QPointF p1 = mapFromScene(point: m_allPoints.at(i: 0).scenePos());
1325 QPointF p2 = mapFromScene(point: m_allPoints.at(i: 1).scenePos());
1326 if (validateTouchAngleForTilting(angle: m_twoTouchAngle)
1327 && movingParallelVertical(p1old: m_sceneStartPoint1, p1new: p1, p2old: m_sceneStartPoint2, p2new: p2)
1328 && qAbs(t: m_twoTouchPointsCentroidStart.y() - m_touchPointsCentroid.y()) > MinimumPanToTiltDelta) {
1329 m_pinch.m_event.setCenter(mapFromScene(point: m_touchPointsCentroid));
1330 m_pinch.m_event.setAngle(m_twoTouchAngle);
1331 m_pinch.m_event.setPoint1(p1);
1332 m_pinch.m_event.setPoint2(p2);
1333 m_pinch.m_event.setPointCount(m_allPoints.count());
1334 m_pinch.m_event.setAccepted(true);
1335 emit tiltStarted(pinch: &m_pinch.m_event);
1336 return true;
1337 }
1338 }
1339 return false;
1340}
1341
1342/*!
1343 \internal
1344*/
1345void QQuickGeoMapGestureArea::startTilt()
1346{
1347 if (isPanActive()) {
1348 stopPan();
1349 setFlickState(flickInactive);
1350 }
1351
1352 m_pinch.m_tilt.m_startTouchCentroid = m_touchPointsCentroid;
1353 m_pinch.m_tilt.m_startTilt = m_declarativeMap->tilt();
1354}
1355
1356/*!
1357 \internal
1358*/
1359void QQuickGeoMapGestureArea::updateTilt()
1360{
1361 // Calculate the new tilt
1362 qreal verticalDisplacement = (m_touchPointsCentroid - m_pinch.m_tilt.m_startTouchCentroid).y();
1363
1364 // Approach: 10pixel = 1 degree.
1365 qreal tilt = verticalDisplacement / 10.0;
1366 qreal newTilt = m_pinch.m_tilt.m_startTilt - tilt;
1367 m_declarativeMap->setTilt(newTilt);
1368
1369 m_pinch.m_event.setCenter(mapFromScene(point: m_touchPointsCentroid));
1370 m_pinch.m_event.setAngle(m_twoTouchAngle);
1371 m_pinch.m_lastPoint1 = mapFromScene(point: m_allPoints.at(i: 0).scenePos());
1372 m_pinch.m_lastPoint2 = mapFromScene(point: m_allPoints.at(i: 1).scenePos());
1373 m_pinch.m_event.setPoint1(m_pinch.m_lastPoint1);
1374 m_pinch.m_event.setPoint2(m_pinch.m_lastPoint2);
1375 m_pinch.m_event.setPointCount(m_allPoints.count());
1376 m_pinch.m_event.setAccepted(true);
1377
1378 emit tiltUpdated(pinch: &m_pinch.m_event);
1379}
1380
1381/*!
1382 \internal
1383*/
1384void QQuickGeoMapGestureArea::endTilt()
1385{
1386 QPointF p1 = mapFromScene(point: m_pinch.m_lastPoint1);
1387 QPointF p2 = mapFromScene(point: m_pinch.m_lastPoint2);
1388 m_pinch.m_event.setCenter((p1 + p2) / 2);
1389 m_pinch.m_event.setAngle(m_pinch.m_lastAngle);
1390 m_pinch.m_event.setPoint1(p1);
1391 m_pinch.m_event.setPoint2(p2);
1392 m_pinch.m_event.setAccepted(true);
1393 m_pinch.m_event.setPointCount(0);
1394 emit tiltFinished(pinch: &m_pinch.m_event);
1395}
1396
1397/*!
1398 \internal
1399*/
1400void QQuickGeoMapGestureArea::rotationStateMachine()
1401{
1402 RotationState lastState = m_rotationState;
1403 // Transitions:
1404 switch (m_rotationState) {
1405 case rotationInactive:
1406 if (m_allPoints.count() >= 2) {
1407 if (!isTiltActive() && canStartRotation()) {
1408 m_declarativeMap->setKeepMouseGrab(true);
1409 m_declarativeMap->setKeepTouchGrab(true);
1410 startRotation();
1411 setRotationState(rotationActive);
1412 } else {
1413 setRotationState(rotationInactiveTwoPoints);
1414 }
1415 }
1416 break;
1417 case rotationInactiveTwoPoints:
1418 if (m_allPoints.count() <= 1) {
1419 setRotationState(rotationInactive);
1420 } else {
1421 if (!isTiltActive() && canStartRotation()) {
1422 m_declarativeMap->setKeepMouseGrab(true);
1423 m_declarativeMap->setKeepTouchGrab(true);
1424 startRotation();
1425 setRotationState(rotationActive);
1426 }
1427 }
1428 break;
1429 case rotationActive:
1430 if (m_allPoints.count() <= 1) {
1431 setRotationState(rotationInactive);
1432 m_declarativeMap->setKeepMouseGrab(m_preventStealing);
1433 m_declarativeMap->setKeepTouchGrab(m_preventStealing);
1434 endRotation();
1435 }
1436 break;
1437 }
1438 // This line implements an exclusive state machine, where the transitions and updates don't
1439 // happen on the same frame
1440 if (m_rotationState != lastState) {
1441 emit rotationActiveChanged();
1442 return;
1443 }
1444
1445 // Update
1446 switch (m_rotationState) {
1447 case rotationInactive:
1448 case rotationInactiveTwoPoints:
1449 break; // do nothing
1450 case rotationActive:
1451 updateRotation();
1452 break;
1453 }
1454}
1455
1456/*!
1457 \internal
1458*/
1459bool QQuickGeoMapGestureArea::canStartRotation()
1460{
1461 if (m_allPoints.count() >= 2) {
1462 QPointF p1 = mapFromScene(point: m_allPoints.at(i: 0).scenePos());
1463 QPointF p2 = mapFromScene(point: m_allPoints.at(i: 1).scenePos());
1464 if (pointDragged(pOld: m_sceneStartPoint1, pNew: p1) || pointDragged(pOld: m_sceneStartPoint2, pNew: p2)) {
1465 qreal delta = angleDelta(angle1: m_twoTouchAngleStart, angle2: m_twoTouchAngle);
1466 if (qAbs(t: delta) < MinimumRotationStartingAngle) {
1467 return false;
1468 }
1469 m_pinch.m_event.setCenter(mapFromScene(point: m_touchPointsCentroid));
1470 m_pinch.m_event.setAngle(m_twoTouchAngle);
1471 m_pinch.m_event.setPoint1(p1);
1472 m_pinch.m_event.setPoint2(p2);
1473 m_pinch.m_event.setPointCount(m_allPoints.count());
1474 m_pinch.m_event.setAccepted(true);
1475 emit rotationStarted(pinch: &m_pinch.m_event);
1476 return m_pinch.m_event.accepted();
1477 }
1478 }
1479 return false;
1480}
1481
1482/*!
1483 \internal
1484*/
1485void QQuickGeoMapGestureArea::startRotation()
1486{
1487 m_pinch.m_rotation.m_startBearing = m_declarativeMap->bearing();
1488 m_pinch.m_rotation.m_previousTouchAngle = m_twoTouchAngle;
1489 m_pinch.m_rotation.m_totalAngle = 0.0;
1490}
1491
1492/*!
1493 \internal
1494*/
1495void QQuickGeoMapGestureArea::updateRotation()
1496{
1497 // Calculate the new bearing
1498 qreal angle = angleDelta(angle1: m_pinch.m_rotation.m_previousTouchAngle, angle2: m_twoTouchAngle);
1499 if (qAbs(t: angle) < 0.2) // avoiding too many updates
1500 return;
1501
1502 m_pinch.m_rotation.m_previousTouchAngle = m_twoTouchAngle;
1503 m_pinch.m_rotation.m_totalAngle += angle;
1504 qreal newBearing = m_pinch.m_rotation.m_startBearing - m_pinch.m_rotation.m_totalAngle;
1505 m_declarativeMap->setBearing(newBearing);
1506
1507 m_pinch.m_event.setCenter(mapFromScene(point: m_touchPointsCentroid));
1508 m_pinch.m_event.setAngle(m_twoTouchAngle);
1509 m_pinch.m_lastPoint1 = mapFromScene(point: m_allPoints.at(i: 0).scenePos());
1510 m_pinch.m_lastPoint2 = mapFromScene(point: m_allPoints.at(i: 1).scenePos());
1511 m_pinch.m_event.setPoint1(m_pinch.m_lastPoint1);
1512 m_pinch.m_event.setPoint2(m_pinch.m_lastPoint2);
1513 m_pinch.m_event.setPointCount(m_allPoints.count());
1514 m_pinch.m_event.setAccepted(true);
1515
1516 emit rotationUpdated(pinch: &m_pinch.m_event);
1517}
1518
1519/*!
1520 \internal
1521*/
1522void QQuickGeoMapGestureArea::endRotation()
1523{
1524 QPointF p1 = mapFromScene(point: m_pinch.m_lastPoint1);
1525 QPointF p2 = mapFromScene(point: m_pinch.m_lastPoint2);
1526 m_pinch.m_event.setCenter((p1 + p2) / 2);
1527 m_pinch.m_event.setAngle(m_pinch.m_lastAngle);
1528 m_pinch.m_event.setPoint1(p1);
1529 m_pinch.m_event.setPoint2(p2);
1530 m_pinch.m_event.setAccepted(true);
1531 m_pinch.m_event.setPointCount(0);
1532 emit rotationFinished(pinch: &m_pinch.m_event);
1533}
1534
1535/*!
1536 \internal
1537*/
1538void QQuickGeoMapGestureArea::pinchStateMachine()
1539{
1540 PinchState lastState = m_pinchState;
1541 // Transitions:
1542 switch (m_pinchState) {
1543 case pinchInactive:
1544 if (m_allPoints.count() >= 2) {
1545 if (!isTiltActive() && canStartPinch()) {
1546 m_declarativeMap->setKeepMouseGrab(true);
1547 m_declarativeMap->setKeepTouchGrab(true);
1548 startPinch();
1549 setPinchState(pinchActive);
1550 } else {
1551 setPinchState(pinchInactiveTwoPoints);
1552 }
1553 }
1554 break;
1555 case pinchInactiveTwoPoints:
1556 if (m_allPoints.count() <= 1) {
1557 setPinchState(pinchInactive);
1558 } else {
1559 if (!isTiltActive() && canStartPinch()) {
1560 m_declarativeMap->setKeepMouseGrab(true);
1561 m_declarativeMap->setKeepTouchGrab(true);
1562 startPinch();
1563 setPinchState(pinchActive);
1564 }
1565 }
1566 break;
1567 case pinchActive:
1568 if (m_allPoints.count() <= 1) { // Once started, pinch goes off only when finger(s) are release
1569 setPinchState(pinchInactive);
1570 m_declarativeMap->setKeepMouseGrab(m_preventStealing);
1571 m_declarativeMap->setKeepTouchGrab(m_preventStealing);
1572 endPinch();
1573 }
1574 break;
1575 }
1576 // This line implements an exclusive state machine, where the transitions and updates don't
1577 // happen on the same frame
1578 if (m_pinchState != lastState) {
1579 emit pinchActiveChanged();
1580 return;
1581 }
1582
1583 // Update
1584 switch (m_pinchState) {
1585 case pinchInactive:
1586 case pinchInactiveTwoPoints:
1587 break; // do nothing
1588 case pinchActive:
1589 updatePinch();
1590 break;
1591 }
1592}
1593
1594/*!
1595 \internal
1596*/
1597bool QQuickGeoMapGestureArea::canStartPinch()
1598{
1599 if (m_allPoints.count() >= 2) {
1600 QPointF p1 = mapFromScene(point: m_allPoints.at(i: 0).scenePos());
1601 QPointF p2 = mapFromScene(point: m_allPoints.at(i: 1).scenePos());
1602 if (qAbs(t: m_distanceBetweenTouchPoints - m_distanceBetweenTouchPointsStart) > MinimumPinchDelta) {
1603 m_pinch.m_event.setCenter(mapFromScene(point: m_touchPointsCentroid));
1604 m_pinch.m_event.setAngle(m_twoTouchAngle);
1605 m_pinch.m_event.setPoint1(p1);
1606 m_pinch.m_event.setPoint2(p2);
1607 m_pinch.m_event.setPointCount(m_allPoints.count());
1608 m_pinch.m_event.setAccepted(true);
1609 emit pinchStarted(pinch: &m_pinch.m_event);
1610 return m_pinch.m_event.accepted();
1611 }
1612 }
1613 return false;
1614}
1615
1616/*!
1617 \internal
1618*/
1619void QQuickGeoMapGestureArea::startPinch()
1620{
1621 m_pinch.m_startDist = m_distanceBetweenTouchPoints;
1622 m_pinch.m_zoom.m_previous = m_declarativeMap->zoomLevel();
1623 m_pinch.m_lastAngle = m_twoTouchAngle;
1624
1625 m_pinch.m_lastPoint1 = mapFromScene(point: m_allPoints.at(i: 0).scenePos());
1626 m_pinch.m_lastPoint2 = mapFromScene(point: m_allPoints.at(i: 1).scenePos());
1627
1628 m_pinch.m_zoom.m_start = m_declarativeMap->zoomLevel();
1629}
1630
1631/*!
1632 \internal
1633*/
1634void QQuickGeoMapGestureArea::updatePinch()
1635{
1636 // Calculate the new zoom level if we have distance ( >= 2 touchpoints), otherwise stick with old.
1637 qreal newZoomLevel = m_pinch.m_zoom.m_previous;
1638 if (m_distanceBetweenTouchPoints) {
1639 newZoomLevel =
1640 // How much further/closer the current touchpoints are (in pixels) compared to pinch start
1641 ((m_distanceBetweenTouchPoints - m_pinch.m_startDist) *
1642 // How much one pixel corresponds in units of zoomlevel (and multiply by above delta)
1643 (m_pinch.m_zoom.maximumChange / ((width() + height()) / 2))) +
1644 // Add to starting zoom level. Sign of (dist-pinchstartdist) takes care of zoom in / out
1645 m_pinch.m_zoom.m_start;
1646 }
1647
1648 m_pinch.m_event.setCenter(mapFromScene(point: m_touchPointsCentroid));
1649 m_pinch.m_event.setAngle(m_twoTouchAngle);
1650
1651 m_pinch.m_lastPoint1 = mapFromScene(point: m_allPoints.at(i: 0).scenePos());
1652 m_pinch.m_lastPoint2 = mapFromScene(point: m_allPoints.at(i: 1).scenePos());
1653 m_pinch.m_event.setPoint1(m_pinch.m_lastPoint1);
1654 m_pinch.m_event.setPoint2(m_pinch.m_lastPoint2);
1655 m_pinch.m_event.setPointCount(m_allPoints.count());
1656 m_pinch.m_event.setAccepted(true);
1657
1658 m_pinch.m_lastAngle = m_twoTouchAngle;
1659 emit pinchUpdated(pinch: &m_pinch.m_event);
1660
1661 if (m_acceptedGestures & PinchGesture) {
1662 // Take maximum and minimumzoomlevel into account
1663 qreal perPinchMinimumZoomLevel = qMax(a: m_pinch.m_zoom.m_start - m_pinch.m_zoom.maximumChange, b: m_pinch.m_zoom.m_minimum);
1664 qreal perPinchMaximumZoomLevel = qMin(a: m_pinch.m_zoom.m_start + m_pinch.m_zoom.maximumChange, b: m_pinch.m_zoom.m_maximum);
1665 newZoomLevel = qMin(a: qMax(a: perPinchMinimumZoomLevel, b: newZoomLevel), b: perPinchMaximumZoomLevel);
1666 m_declarativeMap->setZoomLevel(zoomLevel: qMin<qreal>(a: newZoomLevel, b: maximumZoomLevel()), overzoom: false);
1667 m_pinch.m_zoom.m_previous = newZoomLevel;
1668 }
1669}
1670
1671/*!
1672 \internal
1673*/
1674void QQuickGeoMapGestureArea::endPinch()
1675{
1676 QPointF p1 = mapFromScene(point: m_pinch.m_lastPoint1);
1677 QPointF p2 = mapFromScene(point: m_pinch.m_lastPoint2);
1678 m_pinch.m_event.setCenter((p1 + p2) / 2);
1679 m_pinch.m_event.setAngle(m_pinch.m_lastAngle);
1680 m_pinch.m_event.setPoint1(p1);
1681 m_pinch.m_event.setPoint2(p2);
1682 m_pinch.m_event.setAccepted(true);
1683 m_pinch.m_event.setPointCount(0);
1684 emit pinchFinished(pinch: &m_pinch.m_event);
1685 m_pinch.m_startDist = 0;
1686}
1687
1688/*!
1689 \internal
1690*/
1691void QQuickGeoMapGestureArea::panStateMachine()
1692{
1693 FlickState lastState = m_flickState;
1694
1695 // Transitions
1696 switch (m_flickState) {
1697 case flickInactive:
1698 if (!isTiltActive() && canStartPan()) {
1699 // Update startCoord_ to ensure smooth start for panning when going over startDragDistance
1700 QGeoCoordinate newStartCoord = m_declarativeMap->toCoordinate(position: m_touchPointsCentroid, clipToViewPort: false);
1701 m_startCoord.setLongitude(newStartCoord.longitude());
1702 m_startCoord.setLatitude(newStartCoord.latitude());
1703 m_declarativeMap->setKeepMouseGrab(true);
1704 setFlickState(panActive);
1705 }
1706 break;
1707 case panActive:
1708 if (m_allPoints.count() == 0) {
1709 if (!tryStartFlick())
1710 {
1711 setFlickState(flickInactive);
1712 // mark as inactive for use by camera
1713 if (m_pinchState == pinchInactive && m_rotationState == rotationInactive && m_tiltState == tiltInactive) {
1714 m_declarativeMap->setKeepMouseGrab(m_preventStealing);
1715 m_map->prefetchData();
1716 }
1717 emit panFinished();
1718 } else {
1719 setFlickState(flickActive);
1720 emit panFinished();
1721 emit flickStarted();
1722 }
1723 }
1724 break;
1725 case flickActive:
1726 if (m_allPoints.count() > 0) { // re touched before movement ended
1727 stopFlick();
1728 m_declarativeMap->setKeepMouseGrab(true);
1729 setFlickState(panActive);
1730 }
1731 break;
1732 }
1733
1734 if (m_flickState != lastState)
1735 emit panActiveChanged();
1736
1737 // Update
1738 switch (m_flickState) {
1739 case flickInactive: // do nothing
1740 break;
1741 case panActive:
1742 updatePan();
1743 // this ensures 'panStarted' occurs after the pan has actually started
1744 if (lastState != panActive)
1745 emit panStarted();
1746 break;
1747 case flickActive:
1748 break;
1749 }
1750}
1751/*!
1752 \internal
1753*/
1754bool QQuickGeoMapGestureArea::canStartPan()
1755{
1756 if (m_allPoints.count() == 0 || (m_acceptedGestures & PanGesture) == 0
1757 || (m_mousePoint && m_mousePoint->state() == Qt::TouchPointReleased)) // mouseReleaseEvent handling does not clear m_mousePoint, only ungrabMouse does -- QTBUG-66534
1758 return false;
1759
1760 // Check if thresholds for normal panning are met.
1761 // (normal panning vs flicking: flicking will start from mouse release event).
1762 const int startDragDistance = qApp->styleHints()->startDragDistance() * 2;
1763 QPointF p1 = mapFromScene(point: m_allPoints.at(i: 0).scenePos());
1764 int dyFromPress = int(p1.y() - m_sceneStartPoint1.y());
1765 int dxFromPress = int(p1.x() - m_sceneStartPoint1.x());
1766 if ((qAbs(t: dyFromPress) >= startDragDistance || qAbs(t: dxFromPress) >= startDragDistance))
1767 return true;
1768 return false;
1769}
1770
1771/*!
1772 \internal
1773*/
1774void QQuickGeoMapGestureArea::updatePan()
1775{
1776 m_declarativeMap->alignCoordinateToPoint(coordinate: m_startCoord, point: m_touchPointsCentroid);
1777}
1778
1779/*!
1780 \internal
1781*/
1782bool QQuickGeoMapGestureArea::tryStartFlick()
1783{
1784 if ((m_acceptedGestures & FlickGesture) == 0)
1785 return false;
1786 // if we drag then pause before release we should not cause a flick.
1787 qreal flickSpeed = 0.0;
1788 if (m_lastPosTime.elapsed() < QML_MAP_FLICK_VELOCITY_SAMPLE_PERIOD)
1789 flickSpeed = m_flickVector.length();
1790
1791 int flickTime = 0;
1792 int flickPixels = 0;
1793 QVector2D flickVector;
1794
1795 if (qAbs(t: flickSpeed) > MinimumFlickVelocity
1796 && distanceBetweenTouchPoints(p1: m_touchPointsCentroid, p2: m_sceneStartPoint1) > FlickThreshold) {
1797 qreal acceleration = m_flick.m_deceleration;
1798 if ((flickSpeed > 0.0f) == (m_flick.m_deceleration > 0.0f))
1799 acceleration = acceleration * -1.0f;
1800 flickTime = static_cast<int>(-1000 * flickSpeed / acceleration);
1801 flickPixels = (flickTime * flickSpeed) / 2000.0;
1802 flickVector = m_flickVector.normalized() * flickPixels;
1803 }
1804
1805 if (flickTime > 0) {
1806 startFlick(dx: flickVector.x(), dy: flickVector.y(), timeMs: flickTime);
1807 return true;
1808 }
1809 return false;
1810}
1811
1812/*!
1813 \internal
1814*/
1815void QQuickGeoMapGestureArea::startFlick(int dx, int dy, int timeMs)
1816{
1817 if (!m_flick.m_animation)
1818 return;
1819 if (timeMs < 0)
1820 return;
1821
1822 QGeoCoordinate animationStartCoordinate = m_declarativeMap->center();
1823
1824 if (m_flick.m_animation->isRunning())
1825 m_flick.m_animation->stop();
1826 QGeoCoordinate animationEndCoordinate = m_declarativeMap->center();
1827 m_flick.m_animation->setDuration(timeMs);
1828
1829 QPointF delta(dx, dy);
1830 QMatrix4x4 matBearing;
1831 matBearing.rotate(angle: m_map->cameraData().bearing(), x: 0, y: 0, z: 1);
1832 delta = matBearing * delta;
1833
1834 double zoom = pow(x: 2.0, y: m_declarativeMap->zoomLevel());
1835 double longitude = animationStartCoordinate.longitude() - (delta.x() / zoom);
1836 double latitude = animationStartCoordinate.latitude() + (delta.y() / zoom);
1837
1838 if (delta.x() > 0)
1839 m_flick.m_animation->setDirection(QQuickGeoCoordinateAnimation::East);
1840 else
1841 m_flick.m_animation->setDirection(QQuickGeoCoordinateAnimation::West);
1842
1843 //keep animation in correct bounds
1844 animationEndCoordinate.setLongitude(QLocationUtils::wrapLong(lng: longitude));
1845 animationEndCoordinate.setLatitude(QLocationUtils::clipLat(lat: latitude, clipValue: QLocationUtils::mercatorMaxLatitude()));
1846
1847 m_flick.m_animation->setFrom(animationStartCoordinate);
1848 m_flick.m_animation->setTo(animationEndCoordinate);
1849 m_flick.m_animation->start();
1850}
1851
1852void QQuickGeoMapGestureArea::stopPan()
1853{
1854 if (m_flickState == flickActive) {
1855 stopFlick();
1856 } else if (m_flickState == panActive) {
1857 m_flickVector = QVector2D();
1858 setFlickState(flickInactive);
1859 m_declarativeMap->setKeepMouseGrab(m_preventStealing);
1860 emit panFinished();
1861 emit panActiveChanged();
1862 m_map->prefetchData();
1863 }
1864}
1865
1866/*!
1867 \internal
1868*/
1869void QQuickGeoMapGestureArea::stopFlick()
1870{
1871 if (!m_flick.m_animation)
1872 return;
1873 m_flickVector = QVector2D();
1874 if (m_flick.m_animation->isRunning())
1875 m_flick.m_animation->stop();
1876 else
1877 handleFlickAnimationStopped();
1878}
1879
1880void QQuickGeoMapGestureArea::handleFlickAnimationStopped()
1881{
1882 m_declarativeMap->setKeepMouseGrab(m_preventStealing);
1883 if (m_flickState == flickActive) {
1884 setFlickState(flickInactive);
1885 emit flickFinished();
1886 emit panActiveChanged();
1887 m_map->prefetchData();
1888 }
1889}
1890
1891QT_END_NAMESPACE
1892

source code of qtlocation/src/location/declarativemaps/qquickgeomapgesturearea.cpp