Fixed map jumping when starting a drag
[situare] / src / map / mapview.cpp
1 /*
2    Situare - A location system for Facebook
3    Copyright (C) 2010  Ixonos Plc. Authors:
4
5        Sami Rämö - sami.ramo@ixonos.com
6        Pekka Nissinen - pekka.nissinen@ixonos.com
7
8    Situare is free software; you can redistribute it and/or
9    modify it under the terms of the GNU General Public License
10    version 2 as published by the Free Software Foundation.
11
12    Situare is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with Situare; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
20    USA.
21 */
22
23 #include <cmath>
24
25 #include <QDebug>
26 #include <QMouseEvent>
27 #include <QParallelAnimationGroup>
28
29 #include "coordinates/scenecoordinate.h"
30 #include "mapcommon.h"
31 #include "mapscroller.h"
32
33 #include "mapview.h"
34
35 const qreal MS_PER_S = 1000;
36
37 // const values for tuning the kinetic scroll effect
38 const int KINETIC_MIN_DRAG_LENGTH_VIEW_PIXELS = 30;
39 const int KINETIC_MAX_TIME_FROM_LAST_MOUSE_EVENT_MS = 100;
40 const qreal KINETIC_MAX_VIEW_DISTANCE_FACTOR = 0.8;
41 const int KINETIC_SCROLL_TIME_MS = 750;
42 const qreal KINETIC_SPEED_TO_DISTANCE_FACTOR = 0.15 * sqrt(KINETIC_SCROLL_TIME_MS / MS_PER_S);
43
44 const qreal ZOOM_TIME_MS = 350; ///< Length of the zoom effect (ms)
45
46 MapView::MapView(QWidget *parent)
47     : QGraphicsView(parent),
48       m_doubleTapZoomRunning(false),
49       m_panelIsOpen(false)
50 {
51     qDebug() << __PRETTY_FUNCTION__;
52
53     setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
54     setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
55
56     m_zoomAnimation = new QPropertyAnimation(this, "viewScale", this);
57     connect(m_zoomAnimation, SIGNAL(finished()),
58         this, SIGNAL(viewZoomFinished()));
59     setOptimizationFlag(QGraphicsView::DontAdjustForAntialiasing);
60
61     m_scroller = &MapScroller::getInstance();
62
63     m_scrollAndZoomAnimation = new QParallelAnimationGroup();
64     m_scrollAndZoomAnimation->addAnimation(m_scroller);
65     m_scrollAndZoomAnimation->addAnimation(m_zoomAnimation);
66     connect(m_scrollAndZoomAnimation, SIGNAL(finished()),
67             this, SLOT(doubleTapZoomFinished()));
68 }
69
70 void MapView::centerToSceneCoordinates(const SceneCoordinate &coordinate, bool isUserDragAction)
71 {
72     qDebug() << __PRETTY_FUNCTION__ << "coordinate" << coordinate;
73
74     QPointF target = coordinate.toPointF();
75
76     if (!isUserDragAction) {
77         target += m_centerHorizontalShift;
78     }
79
80     centerOn(target);
81 }
82
83 void MapView::disableCenterShift()
84 {
85     qWarning() << __PRETTY_FUNCTION__;
86
87     m_panelIsOpen = false;
88     updateCenterShift();
89
90     ///< @todo Update center position
91 }
92
93 void MapView::doubleTapZoomFinished()
94 {
95     qDebug() << __PRETTY_FUNCTION__;
96
97     m_doubleTapZoomRunning = false;
98     emit zoomIn();
99 }
100
101 void MapView::enableCenterShift()
102 {
103     qWarning() << __PRETTY_FUNCTION__;
104
105     m_panelIsOpen = true;
106     updateCenterShift();
107
108     ///< @todo Update center position
109 }
110
111 void MapView::mouseDoubleClickEvent(QMouseEvent *event)
112 {
113     qDebug() << __PRETTY_FUNCTION__;
114
115     if (m_zoomLevel + 1 <= OSM_MAX_ZOOM_LEVEL) {
116         QPoint pressPosition = mapToScene(event->pos()).toPoint();
117         QPoint viewCenterPosition = mapToScene(width() / 2 - 1, height() / 2 - 1).toPoint();
118         QPoint zoomPosition = viewCenterPosition - ((viewCenterPosition - pressPosition) / 2);
119
120         m_scrollAndZoomAnimation->stop();
121         m_doubleTapZoomRunning = true;
122
123         m_scroller->setEasingCurve(QEasingCurve::Linear);
124         m_scroller->setDuration(ZOOM_TIME_MS);
125         m_scroller->setStartValue(SceneCoordinate(m_scenePosition.x(), m_scenePosition.y()));
126         m_scroller->setEndValue(SceneCoordinate(zoomPosition.x(), zoomPosition.y()));
127
128         m_zoomAnimation->setEasingCurve(QEasingCurve::InQuad);
129         m_zoomAnimation->setDuration(ZOOM_TIME_MS);
130         m_zoomAnimation->setStartValue(viewScale());
131         m_zoomAnimation->setEndValue(pow(2, m_zoomLevel+1 - OSM_MAX_ZOOM_LEVEL));
132
133         m_scrollAndZoomAnimation->start();
134     }
135 }
136
137 void MapView::mouseMoveEvent(QMouseEvent *event)
138 {
139     qDebug() << __PRETTY_FUNCTION__;
140
141     if (m_doubleTapZoomRunning)
142         return;
143
144     m_scenePosition += m_mouseLastScenePosition - mapToScene(event->pos()).toPoint();
145
146     if (m_index >= VALUES)
147         m_index = 0;
148
149     m_dragMovement[m_index] = m_mouseLastViewPosition - event->pos();
150     m_dragTime[m_index] = m_time.elapsed();
151     m_time.start();
152     m_index++;
153
154     emit viewScrolled(SceneCoordinate(m_scenePosition.x(), m_scenePosition.y()), true);
155
156     m_mouseLastScenePosition = mapToScene(event->pos()).toPoint();
157     m_mouseLastViewPosition = event->pos();
158 }
159
160 void MapView::mousePressEvent(QMouseEvent *event)
161 {
162     qDebug() << __PRETTY_FUNCTION__;
163
164     if (m_doubleTapZoomRunning)
165         return;
166
167     m_time.start();
168
169     m_scroller->stop();
170
171     QGraphicsView::mousePressEvent(event);
172
173     m_mouseLastScenePosition = mapToScene(event->pos()).toPoint();
174     m_mouseLastViewPosition = event->pos();
175     m_scenePosition = mapToScene(width() / 2 - 1, height() / 2 - 1).toPoint();
176
177     for (int i = 0; i < VALUES; i++) {
178         m_dragMovement[i] = QPoint();
179         m_dragTime[i] = 0;
180     }
181     m_index = 0;
182 }
183
184 void MapView::mouseReleaseEvent(QMouseEvent *event)
185 {
186     qDebug() << __PRETTY_FUNCTION__;
187
188     if (m_doubleTapZoomRunning)
189         return;
190
191     int elapsed = m_time.elapsed();
192
193     QGraphicsView::mouseReleaseEvent(event);
194
195     // start kinetic scroll only if there isn't too much time elapsed from the last mouse move event
196     if (elapsed <= KINETIC_MAX_TIME_FROM_LAST_MOUSE_EVENT_MS) {
197         QPointF dragViewSpeed;
198         int dragLength = 0;
199         int values = 0;
200         for (int i = 0; i < VALUES; i++) {
201             if (m_dragTime[i] > 0) {
202                 dragViewSpeed += m_dragMovement[i] / (m_dragTime[i] / MS_PER_S);
203                 dragLength += m_dragMovement[i].manhattanLength();
204                 values++;
205             }
206         }
207
208         if (dragLength >= KINETIC_MIN_DRAG_LENGTH_VIEW_PIXELS) {
209             dragViewSpeed /= values;
210             QPointF effectViewDistance = dragViewSpeed * KINETIC_SPEED_TO_DISTANCE_FACTOR;
211
212             // limit the scroll distance in screen pixels
213             qreal biggerDistance = qMax(abs(effectViewDistance.x()), abs(effectViewDistance.y()));
214             if (biggerDistance > m_kineticMaxViewDistance)
215                 effectViewDistance /= biggerDistance / m_kineticMaxViewDistance;
216
217             QPointF effectSceneDistance = effectViewDistance
218                                           * (1 << (OSM_MAX_ZOOM_LEVEL - m_zoomLevel));
219
220             m_scroller->setEasingCurve(QEasingCurve::OutCirc);
221             m_scroller->setDuration(KINETIC_SCROLL_TIME_MS);
222             m_scroller->setStartValue(SceneCoordinate(m_scenePosition.x(), m_scenePosition.y()));
223             QPointF endValue = QPointF(m_scenePosition) + effectSceneDistance;
224             m_scroller->setEndValue(SceneCoordinate(endValue.x(), endValue.y()));
225             m_scroller->start();
226         }
227     }
228 }
229
230 void MapView::resizeEvent(QResizeEvent *event)
231 {
232     qDebug() << __PRETTY_FUNCTION__ << "Resize:" << event->size();
233
234     m_kineticMaxViewDistance = qMax(width(), height()) * KINETIC_MAX_VIEW_DISTANCE_FACTOR;
235
236     emit viewResized(event->size());
237
238     updateCenterShift();
239 }
240
241 void MapView::setViewScale(qreal viewScale)
242 {
243     qDebug() << __PRETTY_FUNCTION__;
244
245     QTransform transform;
246     transform.scale(viewScale, viewScale);
247     setTransform(transform);
248 }
249
250 void MapView::setZoomLevel(int zoomLevel)
251 {
252     qDebug() << __PRETTY_FUNCTION__;
253
254     m_zoomLevel = zoomLevel;
255
256     if (m_zoomAnimation) {
257         m_zoomAnimation->stop();
258         m_zoomAnimation->setEasingCurve(QEasingCurve::InQuad);
259         m_zoomAnimation->setDuration(ZOOM_TIME_MS);
260         m_zoomAnimation->setStartValue(viewScale());
261         m_zoomAnimation->setEndValue(pow(2, zoomLevel - OSM_MAX_ZOOM_LEVEL));
262
263         m_zoomAnimation->start();
264     }
265
266     updateCenterShift();
267 }
268
269 void MapView::updateCenterShift()
270 {
271     qDebug() << __PRETTY_FUNCTION__;
272
273     int shift = 0;
274
275     if (m_panelIsOpen)
276         shift = 200;
277
278     m_centerHorizontalShift = QPointF(shift * (1 << (OSM_MAX_ZOOM_LEVEL - m_zoomLevel)), 0);
279 }
280
281 qreal MapView::viewScale()
282 {
283     qDebug() << __PRETTY_FUNCTION__;
284
285     return transform().m11();
286 }
287
288 MapView::~MapView()
289 {
290     qDebug() << __PRETTY_FUNCTION__;
291
292     m_scrollAndZoomAnimation->removeAnimation(m_scroller);
293     delete m_scrollAndZoomAnimation;
294 }