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.
68 static const int FlickThreshold = 20;
69 // Really slow flicks can be annoying.
70 static const qreal MinimumFlickVelocity = 75.0;
71 // Tolerance for detecting two finger sliding start
72 static const qreal MaximumParallelPosition = 40.0; // in degrees
73 // Tolerance for detecting parallel sliding
74 static const qreal MaximumParallelSlidingAngle = 4.0; // in degrees
75 // Tolerance for starting rotation
76 static const qreal MinimumRotationStartingAngle = 15.0; // in degrees
77 // Tolerance for starting pinch
78 static const qreal MinimumPinchDelta = 40; // in pixels
79 // Tolerance for starting tilt when sliding vertical
80 static const qreal MinimumPanToTiltDelta = 80; // in pixels;
81 
distanceBetweenTouchPoints(const QPointF & p1,const QPointF & p2)82 static qreal distanceBetweenTouchPoints(const QPointF &p1, const QPointF &p2)
83 {
84     return QLineF(p1, p2).length();
85 }
86 
angleFromPoints(const QPointF & p1,const QPointF & p2)87 static qreal angleFromPoints(const QPointF &p1, const QPointF &p2)
88 {
89     return QLineF(p1, p2).angle();
90 }
91 
92 // Keeps it in +- 180
touchAngle(const QPointF & p1,const QPointF & p2)93 static 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
angleDelta(const qreal angle1,const qreal angle2)102 static 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 
pointDragged(const QPointF & pOld,const QPointF & pNew)113 static bool pointDragged(const QPointF &pOld, const QPointF &pNew)
114 {
115     static const int startDragDistance = qApp->styleHints()->startDragDistance();
116     return ( qAbs(pNew.x() - pOld.x()) > startDragDistance
117              || qAbs(pNew.y() - pOld.y()) > startDragDistance);
118 }
119 
vectorSize(const QPointF & vector)120 static qreal vectorSize(const QPointF &vector)
121 {
122     return std::sqrt(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)
touchAngleTilting(const QPointF & p1,const QPointF & p2)127 static 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 
movingParallelVertical(const QPointF & p1old,const QPointF & p1new,const QPointF & p2old,const QPointF & p2new)135 static bool movingParallelVertical(const QPointF &p1old, const QPointF &p1new, const QPointF &p2old, const QPointF &p2new)
136 {
137     if (!pointDragged(p1old, p1new) || !pointDragged(p2old, p2new))
138         return false;
139 
140     QPointF v1 = p1new - p1old;
141     QPointF v2 = p2new - p2old;
142     qreal v1v2size = vectorSize(v1 + v2);
143 
144     if (v1v2size < vectorSize(v1) || v1v2size < vectorSize(v2)) // going in opposite directions
145         return false;
146 
147     const qreal newAngle = touchAngleTilting(p1new, p2new);
148     const qreal oldAngle = touchAngleTilting(p1old, p2old);
149     const qreal angleDiff = angleDelta(newAngle, oldAngle);
150 
151     if (qAbs(angleDiff) > MaximumParallelSlidingAngle)
152         return false;
153 
154     return true;
155 }
156 
157 QT_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 
QQuickGeoMapGestureArea(QDeclarativeGeoMap * map)501 QQuickGeoMapGestureArea::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 */
setMap(QGeoMap * map)519 void 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(m_flick.m_animation, &QQuickAbstractAnimation::stopped, this, &QQuickGeoMapGestureArea::handleFlickAnimationStopped);
530     m_map->setAcceptedGestures(panEnabled(), flickEnabled(), pinchEnabled(), rotationEnabled(), 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 
preventStealing() const550 bool QQuickGeoMapGestureArea::preventStealing() const
551 {
552     return m_preventStealing;
553 }
554 
setPreventStealing(bool prevent)555 void 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 
~QQuickGeoMapGestureArea()565 QQuickGeoMapGestureArea::~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 
acceptedGestures() const594 QQuickGeoMapGestureArea::AcceptedGestures QQuickGeoMapGestureArea::acceptedGestures() const
595 {
596     return m_acceptedGestures;
597 }
598 
599 
setAcceptedGestures(AcceptedGestures acceptedGestures)600 void 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(panEnabled(), flickEnabled(), pinchEnabled(), rotationEnabled(), tiltEnabled());
616 
617     emit acceptedGesturesChanged();
618 }
619 
620 /*!
621     \internal
622 */
isPinchActive() const623 bool QQuickGeoMapGestureArea::isPinchActive() const
624 {
625     return m_pinchState == pinchActive;
626 }
627 
628 /*!
629     \internal
630 */
isRotationActive() const631 bool QQuickGeoMapGestureArea::isRotationActive() const
632 {
633     return m_rotationState == rotationActive;
634 }
635 
636 /*!
637     \internal
638 */
isTiltActive() const639 bool QQuickGeoMapGestureArea::isTiltActive() const
640 {
641     return m_tiltState == tiltActive;
642 }
643 
644 /*!
645     \internal
646 */
isPanActive() const647 bool QQuickGeoMapGestureArea::isPanActive() const
648 {
649     return m_flickState == panActive || m_flickState == flickActive;
650 }
651 
652 /*!
653     \internal
654 */
enabled() const655 bool QQuickGeoMapGestureArea::enabled() const
656 {
657     return m_enabled;
658 }
659 
660 /*!
661     \internal
662 */
setEnabled(bool enabled)663 void 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(panEnabled(), flickEnabled(), pinchEnabled(), rotationEnabled(), tiltEnabled());
684 
685     emit enabledChanged();
686 }
687 
688 /*!
689     \internal
690 */
pinchEnabled() const691 bool QQuickGeoMapGestureArea::pinchEnabled() const
692 {
693     return m_pinch.m_pinchEnabled;
694 }
695 
696 /*!
697     \internal
698 */
setPinchEnabled(bool enabled)699 void QQuickGeoMapGestureArea::setPinchEnabled(bool enabled)
700 {
701     m_pinch.m_pinchEnabled = enabled;
702 }
703 
704 /*!
705     \internal
706 */
rotationEnabled() const707 bool QQuickGeoMapGestureArea::rotationEnabled() const
708 {
709     return m_pinch.m_rotationEnabled;
710 }
711 
712 /*!
713     \internal
714 */
setRotationEnabled(bool enabled)715 void QQuickGeoMapGestureArea::setRotationEnabled(bool enabled)
716 {
717     m_pinch.m_rotationEnabled = enabled;
718 }
719 
720 /*!
721     \internal
722 */
tiltEnabled() const723 bool QQuickGeoMapGestureArea::tiltEnabled() const
724 {
725     return m_pinch.m_tiltEnabled;
726 }
727 
728 /*!
729     \internal
730 */
setTiltEnabled(bool enabled)731 void QQuickGeoMapGestureArea::setTiltEnabled(bool enabled)
732 {
733     m_pinch.m_tiltEnabled = enabled;
734 }
735 
736 /*!
737     \internal
738 */
panEnabled() const739 bool QQuickGeoMapGestureArea::panEnabled() const
740 {
741     return m_flick.m_panEnabled;
742 }
743 
744 /*!
745     \internal
746 */
setPanEnabled(bool enabled)747 void 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 */
flickEnabled() const763 bool QQuickGeoMapGestureArea::flickEnabled() const
764 {
765     return m_flick.m_flickEnabled;
766 }
767 
768 /*!
769     \internal
770 */
setFlickEnabled(bool enabled)771 void 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  */
setMinimumZoomLevel(qreal min)795 void 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  */
minimumZoomLevel() const805 qreal 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  */
setMaximumZoomLevel(qreal max)816 void QQuickGeoMapGestureArea::setMaximumZoomLevel(qreal max)
817 {
818     if (max >= 0)
819         m_pinch.m_zoom.m_maximum = max;
820 }
821 
822 /*!
823    \internal
824  */
maximumZoomLevel() const825 qreal QQuickGeoMapGestureArea::maximumZoomLevel() const
826 {
827     return m_pinch.m_zoom.m_maximum;
828 }
829 
830 /*!
831     \internal
832 */
maximumZoomLevelChange() const833 qreal QQuickGeoMapGestureArea::maximumZoomLevelChange() const
834 {
835     return m_pinch.m_zoom.maximumChange;
836 }
837 
838 /*!
839     \internal
840 */
setMaximumZoomLevelChange(qreal maxChange)841 void 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 */
flickDeceleration() const852 qreal QQuickGeoMapGestureArea::flickDeceleration() const
853 {
854     return m_flick.m_deceleration;
855 }
856 
857 /*!
858     \internal
859 */
setFlickDeceleration(qreal deceleration)860 void 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 */
createTouchPointFromMouseEvent(QMouseEvent * event,Qt::TouchPointState state)875 QTouchEvent::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 */
handleMousePressEvent(QMouseEvent * event)891 void QQuickGeoMapGestureArea::handleMousePressEvent(QMouseEvent *event)
892 {
893     if (m_map && m_map->handleEvent(event)) {
894         event->accept();
895         return;
896     }
897 
898     m_mousePoint.reset(createTouchPointFromMouseEvent(event, Qt::TouchPointPressed));
899     if (m_touchPoints.isEmpty())
900         update();
901     event->accept();
902 }
903 
904 /*!
905     \internal
906 */
handleMouseMoveEvent(QMouseEvent * event)907 void QQuickGeoMapGestureArea::handleMouseMoveEvent(QMouseEvent *event)
908 {
909     if (m_map && m_map->handleEvent(event)) {
910         event->accept();
911         return;
912     }
913 
914     m_mousePoint.reset(createTouchPointFromMouseEvent(event, Qt::TouchPointMoved));
915     if (m_touchPoints.isEmpty())
916         update();
917     event->accept();
918 }
919 
920 /*!
921     \internal
922 */
handleMouseReleaseEvent(QMouseEvent * event)923 void 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(createTouchPointFromMouseEvent(event, Qt::TouchPointReleased));
934         if (m_touchPoints.isEmpty())
935             update();
936     }
937     event->accept();
938 }
939 
940 /*!
941     \internal
942 */
handleMouseUngrabEvent()943 void 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 */
handleTouchUngrabEvent()957 void 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 */
handleTouchEvent(QTouchEvent * event)969 void 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)
handleWheelEvent(QWheelEvent * event)992 void 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(event->position(), 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(&m_pinch.m_event);
1008         // First set bearing
1009         const double bearingDelta = event->angleDelta().y() * qreal(0.05);
1010         m_declarativeMap->setBearing(m_declarativeMap->bearing() + bearingDelta,  wheelGeoPos);
1011         emit rotationUpdated(&m_pinch.m_event);
1012         emit rotationFinished(&m_pinch.m_event);
1013     } else if (event->modifiers() & Qt::ControlModifier && tiltEnabled()) {
1014         emit tiltStarted(&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(&m_pinch.m_event);
1018         emit tiltFinished(&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(qMin<qreal>(m_declarativeMap->zoomLevel() + zoomLevelDelta, maximumZoomLevel()),
1023                                        false);
1024         const QPointF &postZoomPoint = m_declarativeMap->fromCoordinate(wheelGeoPos, false);
1025 
1026         if (preZoomPoint != postZoomPoint) // need to re-anchor the wheel geoPos to the event position
1027             m_declarativeMap->alignCoordinateToPoint(wheelGeoPos, preZoomPoint);
1028     }
1029     event->accept();
1030 }
1031 #endif
1032 
1033 /*!
1034     \internal
1035 */
clearTouchData()1036 void 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 */
updateFlickParameters(const QPointF & pos)1051 void 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(pos, m_lastPos) / elapsed;
1060         m_flickVector = (QVector2D(pos) - QVector2D(m_lastPos)).normalized();
1061         m_flickVector *= qBound<qreal>(-m_flick.m_maxVelocity, vel, m_flick.m_maxVelocity);
1062 
1063         m_lastPos = pos;
1064         m_lastPosTime.restart();
1065     }
1066 }
1067 
setTouchPointState(const QQuickGeoMapGestureArea::TouchPointState state)1068 void QQuickGeoMapGestureArea::setTouchPointState(const QQuickGeoMapGestureArea::TouchPointState state)
1069 {
1070     m_touchPointState = state;
1071 }
1072 
setFlickState(const QQuickGeoMapGestureArea::FlickState state)1073 void QQuickGeoMapGestureArea::setFlickState(const QQuickGeoMapGestureArea::FlickState state)
1074 {
1075     m_flickState = state;
1076 }
1077 
setTiltState(const QQuickGeoMapGestureArea::TiltState state)1078 void QQuickGeoMapGestureArea::setTiltState(const QQuickGeoMapGestureArea::TiltState state)
1079 {
1080     m_tiltState = state;
1081 }
1082 
setRotationState(const QQuickGeoMapGestureArea::RotationState state)1083 void QQuickGeoMapGestureArea::setRotationState(const QQuickGeoMapGestureArea::RotationState state)
1084 {
1085     m_rotationState = state;
1086 }
1087 
setPinchState(const QQuickGeoMapGestureArea::PinchState state)1088 void QQuickGeoMapGestureArea::setPinchState(const QQuickGeoMapGestureArea::PinchState state)
1089 {
1090     m_pinchState = state;
1091 }
1092 
1093 /*!
1094     \internal
1095 */
1096 
isActive() const1097 bool 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)
update()1106 void 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(m_allPoints.begin(), m_allPoints.end(), [](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 */
touchPointStateMachine()1147 void 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(m_touchPointsCentroid, 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(m_touchPointsCentroid, 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 */
startOneTouchPoint()1198 void QQuickGeoMapGestureArea::startOneTouchPoint()
1199 {
1200     m_sceneStartPoint1 = mapFromScene(m_allPoints.at(0).scenePos());
1201     m_lastPos = m_sceneStartPoint1;
1202     m_lastPosTime.start();
1203     QGeoCoordinate startCoord = m_declarativeMap->toCoordinate(m_sceneStartPoint1, 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 */
updateOneTouchPoint()1214 void QQuickGeoMapGestureArea::updateOneTouchPoint()
1215 {
1216     m_touchPointsCentroid = mapFromScene(m_allPoints.at(0).scenePos());
1217     updateFlickParameters(m_touchPointsCentroid);
1218 }
1219 
1220 /*!
1221     \internal
1222 */
startTwoTouchPoints()1223 void QQuickGeoMapGestureArea::startTwoTouchPoints()
1224 {
1225     m_sceneStartPoint1 = mapFromScene(m_allPoints.at(0).scenePos());
1226     m_sceneStartPoint2 = mapFromScene(m_allPoints.at(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(startPos, 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(m_sceneStartPoint1, m_sceneStartPoint2); // Initial angle used for calculating rotation
1236     m_distanceBetweenTouchPointsStart = distanceBetweenTouchPoints(m_sceneStartPoint1, m_sceneStartPoint2);
1237     m_twoTouchPointsCentroidStart = (m_sceneStartPoint1 + m_sceneStartPoint2) / 2;
1238 }
1239 
1240 /*!
1241     \internal
1242 */
updateTwoTouchPoints()1243 void QQuickGeoMapGestureArea::updateTwoTouchPoints()
1244 {
1245     QPointF p1 = mapFromScene(m_allPoints.at(0).scenePos());
1246     QPointF p2 = mapFromScene(m_allPoints.at(1).scenePos());
1247     m_distanceBetweenTouchPoints = distanceBetweenTouchPoints(p1, p2);
1248     m_touchPointsCentroid = (p1 + p2) / 2;
1249     updateFlickParameters(m_touchPointsCentroid);
1250 
1251     m_twoTouchAngle = touchAngle(p1, p2);
1252 }
1253 
1254 /*!
1255     \internal
1256 */
tiltStateMachine()1257 void 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 
validateTouchAngleForTilting(const qreal angle)1313 bool validateTouchAngleForTilting(const qreal angle)
1314 {
1315     return ((qAbs(angle) - 180.0) < MaximumParallelPosition) || (qAbs(angle) < MaximumParallelPosition);
1316 }
1317 
1318 /*!
1319     \internal
1320 */
canStartTilt()1321 bool QQuickGeoMapGestureArea::canStartTilt()
1322 {
1323     if (m_allPoints.count() >= 2) {
1324         QPointF p1 = mapFromScene(m_allPoints.at(0).scenePos());
1325         QPointF p2 = mapFromScene(m_allPoints.at(1).scenePos());
1326         if (validateTouchAngleForTilting(m_twoTouchAngle)
1327                 && movingParallelVertical(m_sceneStartPoint1, p1, m_sceneStartPoint2, p2)
1328                 && qAbs(m_twoTouchPointsCentroidStart.y() - m_touchPointsCentroid.y()) > MinimumPanToTiltDelta) {
1329             m_pinch.m_event.setCenter(mapFromScene(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(&m_pinch.m_event);
1336             return true;
1337         }
1338     }
1339     return false;
1340 }
1341 
1342 /*!
1343     \internal
1344 */
startTilt()1345 void 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 */
updateTilt()1359 void 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(m_touchPointsCentroid));
1370     m_pinch.m_event.setAngle(m_twoTouchAngle);
1371     m_pinch.m_lastPoint1 = mapFromScene(m_allPoints.at(0).scenePos());
1372     m_pinch.m_lastPoint2 = mapFromScene(m_allPoints.at(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(&m_pinch.m_event);
1379 }
1380 
1381 /*!
1382     \internal
1383 */
endTilt()1384 void QQuickGeoMapGestureArea::endTilt()
1385 {
1386     QPointF p1 = mapFromScene(m_pinch.m_lastPoint1);
1387     QPointF p2 = mapFromScene(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(&m_pinch.m_event);
1395 }
1396 
1397 /*!
1398     \internal
1399 */
rotationStateMachine()1400 void 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 */
canStartRotation()1459 bool QQuickGeoMapGestureArea::canStartRotation()
1460 {
1461     if (m_allPoints.count() >= 2) {
1462         QPointF p1 = mapFromScene(m_allPoints.at(0).scenePos());
1463         QPointF p2 = mapFromScene(m_allPoints.at(1).scenePos());
1464         if (pointDragged(m_sceneStartPoint1, p1) || pointDragged(m_sceneStartPoint2, p2)) {
1465             qreal delta = angleDelta(m_twoTouchAngleStart, m_twoTouchAngle);
1466             if (qAbs(delta) < MinimumRotationStartingAngle) {
1467                 return false;
1468             }
1469             m_pinch.m_event.setCenter(mapFromScene(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(&m_pinch.m_event);
1476             return m_pinch.m_event.accepted();
1477         }
1478     }
1479     return false;
1480 }
1481 
1482 /*!
1483     \internal
1484 */
startRotation()1485 void 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 */
updateRotation()1495 void QQuickGeoMapGestureArea::updateRotation()
1496 {
1497     // Calculate the new bearing
1498     qreal angle = angleDelta(m_pinch.m_rotation.m_previousTouchAngle, m_twoTouchAngle);
1499     if (qAbs(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(m_touchPointsCentroid));
1508     m_pinch.m_event.setAngle(m_twoTouchAngle);
1509     m_pinch.m_lastPoint1 = mapFromScene(m_allPoints.at(0).scenePos());
1510     m_pinch.m_lastPoint2 = mapFromScene(m_allPoints.at(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(&m_pinch.m_event);
1517 }
1518 
1519 /*!
1520     \internal
1521 */
endRotation()1522 void QQuickGeoMapGestureArea::endRotation()
1523 {
1524     QPointF p1 = mapFromScene(m_pinch.m_lastPoint1);
1525     QPointF p2 = mapFromScene(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(&m_pinch.m_event);
1533 }
1534 
1535 /*!
1536     \internal
1537 */
pinchStateMachine()1538 void 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 */
canStartPinch()1597 bool QQuickGeoMapGestureArea::canStartPinch()
1598 {
1599     if (m_allPoints.count() >= 2) {
1600         QPointF p1 = mapFromScene(m_allPoints.at(0).scenePos());
1601         QPointF p2 = mapFromScene(m_allPoints.at(1).scenePos());
1602         if (qAbs(m_distanceBetweenTouchPoints - m_distanceBetweenTouchPointsStart) > MinimumPinchDelta) {
1603             m_pinch.m_event.setCenter(mapFromScene(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(&m_pinch.m_event);
1610             return m_pinch.m_event.accepted();
1611         }
1612     }
1613     return false;
1614 }
1615 
1616 /*!
1617     \internal
1618 */
startPinch()1619 void 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(m_allPoints.at(0).scenePos());
1626     m_pinch.m_lastPoint2 = mapFromScene(m_allPoints.at(1).scenePos());
1627 
1628     m_pinch.m_zoom.m_start = m_declarativeMap->zoomLevel();
1629 }
1630 
1631 /*!
1632     \internal
1633 */
updatePinch()1634 void 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(m_touchPointsCentroid));
1649     m_pinch.m_event.setAngle(m_twoTouchAngle);
1650 
1651     m_pinch.m_lastPoint1 = mapFromScene(m_allPoints.at(0).scenePos());
1652     m_pinch.m_lastPoint2 = mapFromScene(m_allPoints.at(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(&m_pinch.m_event);
1660 
1661     if (m_acceptedGestures & PinchGesture) {
1662         // Take maximum and minimumzoomlevel into account
1663         qreal perPinchMinimumZoomLevel = qMax(m_pinch.m_zoom.m_start - m_pinch.m_zoom.maximumChange, m_pinch.m_zoom.m_minimum);
1664         qreal perPinchMaximumZoomLevel = qMin(m_pinch.m_zoom.m_start + m_pinch.m_zoom.maximumChange, m_pinch.m_zoom.m_maximum);
1665         newZoomLevel = qMin(qMax(perPinchMinimumZoomLevel, newZoomLevel), perPinchMaximumZoomLevel);
1666         m_declarativeMap->setZoomLevel(qMin<qreal>(newZoomLevel, maximumZoomLevel()), false);
1667         m_pinch.m_zoom.m_previous = newZoomLevel;
1668     }
1669 }
1670 
1671 /*!
1672     \internal
1673 */
endPinch()1674 void QQuickGeoMapGestureArea::endPinch()
1675 {
1676     QPointF p1 = mapFromScene(m_pinch.m_lastPoint1);
1677     QPointF p2 = mapFromScene(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(&m_pinch.m_event);
1685     m_pinch.m_startDist = 0;
1686 }
1687 
1688 /*!
1689     \internal
1690 */
panStateMachine()1691 void 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(m_touchPointsCentroid, 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 */
canStartPan()1754 bool 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(m_allPoints.at(0).scenePos());
1764     int dyFromPress = int(p1.y() - m_sceneStartPoint1.y());
1765     int dxFromPress = int(p1.x() - m_sceneStartPoint1.x());
1766     if ((qAbs(dyFromPress) >= startDragDistance || qAbs(dxFromPress) >= startDragDistance))
1767         return true;
1768     return false;
1769 }
1770 
1771 /*!
1772     \internal
1773 */
updatePan()1774 void QQuickGeoMapGestureArea::updatePan()
1775 {
1776     m_declarativeMap->alignCoordinateToPoint(m_startCoord, m_touchPointsCentroid);
1777 }
1778 
1779 /*!
1780     \internal
1781 */
tryStartFlick()1782 bool 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(flickSpeed) > MinimumFlickVelocity
1796             && distanceBetweenTouchPoints(m_touchPointsCentroid, 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(flickVector.x(), flickVector.y(), flickTime);
1807         return true;
1808     }
1809     return false;
1810 }
1811 
1812 /*!
1813     \internal
1814 */
startFlick(int dx,int dy,int timeMs)1815 void 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(m_map->cameraData().bearing(), 0, 0, 1);
1832     delta = matBearing * delta;
1833 
1834     double zoom = pow(2.0, 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(longitude));
1845     animationEndCoordinate.setLatitude(QLocationUtils::clipLat(latitude, 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 
stopPan()1852 void 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 */
stopFlick()1869 void 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 
handleFlickAnimationStopped()1880 void 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 
1891 QT_END_NAMESPACE
1892