| 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 |  | 
| 82 | static qreal distanceBetweenTouchPoints(const QPointF &p1, const QPointF &p2) | 
| 83 | { | 
| 84 |     return QLineF(p1, p2).length(); | 
| 85 | } | 
| 86 |  | 
| 87 | static qreal angleFromPoints(const QPointF &p1, const QPointF &p2) | 
| 88 | { | 
| 89 |     return QLineF(p1, p2).angle(); | 
| 90 | } | 
| 91 |  | 
| 92 | // Keeps it in +- 180 | 
| 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 | 
| 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 |  | 
| 113 | static 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 |  | 
| 120 | static 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) | 
| 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 |  | 
| 135 | static 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 |  | 
| 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 |  | 
| 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 | */ | 
| 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(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 |  | 
| 550 | bool QQuickGeoMapGestureArea::preventStealing() const | 
| 551 | { | 
| 552 |     return m_preventStealing; | 
| 553 | } | 
| 554 |  | 
| 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 |  | 
| 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 |  | 
| 594 | QQuickGeoMapGestureArea::AcceptedGestures QQuickGeoMapGestureArea::acceptedGestures() const | 
| 595 | { | 
| 596 |     return m_acceptedGestures; | 
| 597 | } | 
| 598 |  | 
| 599 |  | 
| 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(pan: panEnabled(), flick: flickEnabled(), pinch: pinchEnabled(), rotate: rotationEnabled(), tilt: tiltEnabled()); | 
| 616 |  | 
| 617 |     emit acceptedGesturesChanged(); | 
| 618 | } | 
| 619 |  | 
| 620 | /*! | 
| 621 |     \internal | 
| 622 | */ | 
| 623 | bool QQuickGeoMapGestureArea::isPinchActive() const | 
| 624 | { | 
| 625 |     return m_pinchState == pinchActive; | 
| 626 | } | 
| 627 |  | 
| 628 | /*! | 
| 629 |     \internal | 
| 630 | */ | 
| 631 | bool QQuickGeoMapGestureArea::isRotationActive() const | 
| 632 | { | 
| 633 |     return m_rotationState == rotationActive; | 
| 634 | } | 
| 635 |  | 
| 636 | /*! | 
| 637 |     \internal | 
| 638 | */ | 
| 639 | bool QQuickGeoMapGestureArea::isTiltActive() const | 
| 640 | { | 
| 641 |     return m_tiltState == tiltActive; | 
| 642 | } | 
| 643 |  | 
| 644 | /*! | 
| 645 |     \internal | 
| 646 | */ | 
| 647 | bool QQuickGeoMapGestureArea::isPanActive() const | 
| 648 | { | 
| 649 |     return m_flickState == panActive || m_flickState == flickActive; | 
| 650 | } | 
| 651 |  | 
| 652 | /*! | 
| 653 |     \internal | 
| 654 | */ | 
| 655 | bool QQuickGeoMapGestureArea::enabled() const | 
| 656 | { | 
| 657 |     return m_enabled; | 
| 658 | } | 
| 659 |  | 
| 660 | /*! | 
| 661 |     \internal | 
| 662 | */ | 
| 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(pan: panEnabled(), flick: flickEnabled(), pinch: pinchEnabled(), rotate: rotationEnabled(), tilt: tiltEnabled()); | 
| 684 |  | 
| 685 |     emit enabledChanged(); | 
| 686 | } | 
| 687 |  | 
| 688 | /*! | 
| 689 |     \internal | 
| 690 | */ | 
| 691 | bool QQuickGeoMapGestureArea::pinchEnabled() const | 
| 692 | { | 
| 693 |     return m_pinch.m_pinchEnabled; | 
| 694 | } | 
| 695 |  | 
| 696 | /*! | 
| 697 |     \internal | 
| 698 | */ | 
| 699 | void QQuickGeoMapGestureArea::setPinchEnabled(bool enabled) | 
| 700 | { | 
| 701 |     m_pinch.m_pinchEnabled = enabled; | 
| 702 | } | 
| 703 |  | 
| 704 | /*! | 
| 705 |     \internal | 
| 706 | */ | 
| 707 | bool QQuickGeoMapGestureArea::rotationEnabled() const | 
| 708 | { | 
| 709 |     return m_pinch.m_rotationEnabled; | 
| 710 | } | 
| 711 |  | 
| 712 | /*! | 
| 713 |     \internal | 
| 714 | */ | 
| 715 | void QQuickGeoMapGestureArea::setRotationEnabled(bool enabled) | 
| 716 | { | 
| 717 |     m_pinch.m_rotationEnabled = enabled; | 
| 718 | } | 
| 719 |  | 
| 720 | /*! | 
| 721 |     \internal | 
| 722 | */ | 
| 723 | bool QQuickGeoMapGestureArea::tiltEnabled() const | 
| 724 | { | 
| 725 |     return m_pinch.m_tiltEnabled; | 
| 726 | } | 
| 727 |  | 
| 728 | /*! | 
| 729 |     \internal | 
| 730 | */ | 
| 731 | void QQuickGeoMapGestureArea::setTiltEnabled(bool enabled) | 
| 732 | { | 
| 733 |     m_pinch.m_tiltEnabled = enabled; | 
| 734 | } | 
| 735 |  | 
| 736 | /*! | 
| 737 |     \internal | 
| 738 | */ | 
| 739 | bool QQuickGeoMapGestureArea::panEnabled() const | 
| 740 | { | 
| 741 |     return m_flick.m_panEnabled; | 
| 742 | } | 
| 743 |  | 
| 744 | /*! | 
| 745 |     \internal | 
| 746 | */ | 
| 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 | */ | 
| 763 | bool QQuickGeoMapGestureArea::flickEnabled() const | 
| 764 | { | 
| 765 |     return m_flick.m_flickEnabled; | 
| 766 | } | 
| 767 |  | 
| 768 | /*! | 
| 769 |     \internal | 
| 770 | */ | 
| 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 |  */ | 
| 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 |  */ | 
| 805 | 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 |  */ | 
| 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 |  */ | 
| 825 | qreal QQuickGeoMapGestureArea::maximumZoomLevel() const | 
| 826 | { | 
| 827 |     return m_pinch.m_zoom.m_maximum; | 
| 828 | } | 
| 829 |  | 
| 830 | /*! | 
| 831 |     \internal | 
| 832 | */ | 
| 833 | qreal QQuickGeoMapGestureArea::maximumZoomLevelChange() const | 
| 834 | { | 
| 835 |     return m_pinch.m_zoom.maximumChange; | 
| 836 | } | 
| 837 |  | 
| 838 | /*! | 
| 839 |     \internal | 
| 840 | */ | 
| 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 | */ | 
| 852 | qreal QQuickGeoMapGestureArea::flickDeceleration() const | 
| 853 | { | 
| 854 |     return m_flick.m_deceleration; | 
| 855 | } | 
| 856 |  | 
| 857 | /*! | 
| 858 |     \internal | 
| 859 | */ | 
| 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 | */ | 
| 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 | */ | 
| 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(other: createTouchPointFromMouseEvent(event, state: Qt::TouchPointPressed)); | 
| 899 |     if (m_touchPoints.isEmpty()) | 
| 900 |         update(); | 
| 901 |     event->accept(); | 
| 902 | } | 
| 903 |  | 
| 904 | /*! | 
| 905 |     \internal | 
| 906 | */ | 
| 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(other: createTouchPointFromMouseEvent(event, state: Qt::TouchPointMoved)); | 
| 915 |     if (m_touchPoints.isEmpty()) | 
| 916 |         update(); | 
| 917 |     event->accept(); | 
| 918 | } | 
| 919 |  | 
| 920 | /*! | 
| 921 |     \internal | 
| 922 | */ | 
| 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(other: createTouchPointFromMouseEvent(event, state: Qt::TouchPointReleased)); | 
| 934 |         if (m_touchPoints.isEmpty()) | 
| 935 |             update(); | 
| 936 |     } | 
| 937 |     event->accept(); | 
| 938 | } | 
| 939 |  | 
| 940 | /*! | 
| 941 |     \internal | 
| 942 | */ | 
| 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 | */ | 
| 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 | */ | 
| 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) | 
| 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(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 | */ | 
| 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 | */ | 
| 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(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 |  | 
| 1068 | void QQuickGeoMapGestureArea::setTouchPointState(const QQuickGeoMapGestureArea::TouchPointState state) | 
| 1069 | { | 
| 1070 |     m_touchPointState = state; | 
| 1071 | } | 
| 1072 |  | 
| 1073 | void QQuickGeoMapGestureArea::setFlickState(const QQuickGeoMapGestureArea::FlickState state) | 
| 1074 | { | 
| 1075 |     m_flickState = state; | 
| 1076 | } | 
| 1077 |  | 
| 1078 | void QQuickGeoMapGestureArea::setTiltState(const QQuickGeoMapGestureArea::TiltState state) | 
| 1079 | { | 
| 1080 |     m_tiltState = state; | 
| 1081 | } | 
| 1082 |  | 
| 1083 | void QQuickGeoMapGestureArea::setRotationState(const QQuickGeoMapGestureArea::RotationState state) | 
| 1084 | { | 
| 1085 |     m_rotationState = state; | 
| 1086 | } | 
| 1087 |  | 
| 1088 | void QQuickGeoMapGestureArea::setPinchState(const QQuickGeoMapGestureArea::PinchState state) | 
| 1089 | { | 
| 1090 |     m_pinchState = state; | 
| 1091 | } | 
| 1092 |  | 
| 1093 | /*! | 
| 1094 |     \internal | 
| 1095 | */ | 
| 1096 |  | 
| 1097 | 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) | 
| 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(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 | */ | 
| 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(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 | */ | 
| 1198 | void 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 | */ | 
| 1214 | void 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 | */ | 
| 1223 | void 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 | */ | 
| 1243 | void 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 | */ | 
| 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 |  | 
| 1313 | bool validateTouchAngleForTilting(const qreal angle) | 
| 1314 | { | 
| 1315 |     return ((qAbs(t: angle) - 180.0) < MaximumParallelPosition) || (qAbs(t: angle) < MaximumParallelPosition); | 
| 1316 | } | 
| 1317 |  | 
| 1318 | /*! | 
| 1319 |     \internal | 
| 1320 | */ | 
| 1321 | bool 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 | */ | 
| 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 | */ | 
| 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(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 | */ | 
| 1384 | void 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 | */ | 
| 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 | */ | 
| 1459 | bool 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 | */ | 
| 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 | */ | 
| 1495 | void 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 | */ | 
| 1522 | void 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 | */ | 
| 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 | */ | 
| 1597 | bool 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 | */ | 
| 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(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 | */ | 
| 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(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 | */ | 
| 1674 | void 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 | */ | 
| 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(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 | */ | 
| 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(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 | */ | 
| 1774 | void QQuickGeoMapGestureArea::updatePan() | 
| 1775 | { | 
| 1776 |     m_declarativeMap->alignCoordinateToPoint(coordinate: m_startCoord, point: m_touchPointsCentroid); | 
| 1777 | } | 
| 1778 |  | 
| 1779 | /*! | 
| 1780 |     \internal | 
| 1781 | */ | 
| 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(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 | */ | 
| 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(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 |  | 
| 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 | */ | 
| 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 |  | 
| 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 |  |