952fb060bd761efc3e2069f6a42f35d0eee16e34
[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 {
50     qDebug() << __PRETTY_FUNCTION__;
51
52     setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
53     setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
54
55     m_zoomAnimation = new QPropertyAnimation(this, "viewScale", this);
56     connect(m_zoomAnimation, SIGNAL(finished()),
57         this, SIGNAL(viewZoomFinished()));
58     setOptimizationFlag(QGraphicsView::DontAdjustForAntialiasing);
59
60     m_scroller = &MapScroller::getInstance();
61
62     m_scrollAndZoomAnimation = new QParallelAnimationGroup();
63     m_scrollAndZoomAnimation->addAnimation(m_scroller);
64     m_scrollAndZoomAnimation->addAnimation(m_zoomAnimation);
65     connect(m_scrollAndZoomAnimation, SIGNAL(finished()),
66             this, SLOT(doubleTapZoomFinished()));
67
68     m_centerShiftAnimation = new QPropertyAnimation(this, "viewShift", this);
69     if (m_centerShiftAnimation) {
70         m_centerShiftAnimation->setStartValue(0.0);
71         m_centerShiftAnimation->setDuration(ZOOM_TIME_MS);
72     }
73 }
74
75 MapView::~MapView()
76 {
77     qDebug() << __PRETTY_FUNCTION__;
78
79     m_scrollAndZoomAnimation->removeAnimation(m_scroller);
80     delete m_scrollAndZoomAnimation;
81 }
82
83 void MapView::centerToSceneCoordinates(const SceneCoordinate &coordinate, bool isUserDragAction)
84 {
85     qDebug() << __PRETTY_FUNCTION__ << "coordinate" << coordinate;
86
87     QPointF target = coordinate.toPointF();
88     m_lastSetScenePosition = coordinate;
89
90     if (!isUserDragAction)
91         target += m_centerHorizontalShiftPoint;
92
93     centerOn(target);
94 }
95
96 void MapView::disableCenterShift()
97 {
98     qDebug() << __PRETTY_FUNCTION__;
99
100     if (m_centerShiftAnimation) {
101         m_centerShiftAnimation->setDirection(QAbstractAnimation::Backward);
102         m_centerShiftAnimation->start();
103     }
104 }
105
106 void MapView::doubleTapZoomFinished()
107 {
108     qDebug() << __PRETTY_FUNCTION__;
109
110     m_doubleTapZoomRunning = false;
111     emit zoomIn();
112 }
113
114 void MapView::enableCenterShift()
115 {
116     qDebug() << __PRETTY_FUNCTION__;
117
118     if (m_centerShiftAnimation) {
119         m_centerShiftAnimation->setDirection(QAbstractAnimation::Forward);
120         m_centerShiftAnimation->start();
121     }
122 }
123
124 void MapView::mouseDoubleClickEvent(QMouseEvent *event)
125 {
126     qDebug() << __PRETTY_FUNCTION__;
127
128     if (m_zoomLevel + 1 <= OSM_MAX_ZOOM_LEVEL) {
129         QPoint pressPosition = mapToScene(event->pos()).toPoint();
130         QPoint viewCenterPosition = mapToScene(width() / 2 - 1, height() / 2 - 1).toPoint();
131         QPoint zoomPosition = viewCenterPosition - ((viewCenterPosition - pressPosition) / 2);
132
133         m_scrollAndZoomAnimation->stop();
134         m_doubleTapZoomRunning = true;
135
136         m_scroller->setEasingCurve(QEasingCurve::Linear);
137         m_scroller->setDuration(ZOOM_TIME_MS);
138         m_scroller->setStartValue(SceneCoordinate(m_internalScenePosition.x(),
139                                                   m_internalScenePosition.y()));
140         m_scroller->setEndValue(SceneCoordinate(zoomPosition.x(), zoomPosition.y()));
141
142         m_zoomAnimation->setEasingCurve(QEasingCurve::InQuad);
143         m_zoomAnimation->setDuration(ZOOM_TIME_MS);
144         m_zoomAnimation->setStartValue(viewScale());
145         m_zoomAnimation->setEndValue(pow(2, m_zoomLevel+1 - OSM_MAX_ZOOM_LEVEL));
146
147         m_scrollAndZoomAnimation->start();
148     }
149 }
150
151 void MapView::mouseMoveEvent(QMouseEvent *event)
152 {
153     qDebug() << __PRETTY_FUNCTION__;
154
155     if (m_doubleTapZoomRunning)
156         return;
157
158     m_internalScenePosition += m_lastMouseEventScenePosition - mapToScene(event->pos()).toPoint();
159
160     if (m_index >= VALUES)
161         m_index = 0;
162
163     m_dragMovement[m_index] = m_lastMouseEventViewPosition - event->pos();
164     m_dragTime[m_index] = m_time.elapsed();
165     m_time.start();
166     m_index++;
167
168     emit viewScrolled(SceneCoordinate(m_internalScenePosition.x(), m_internalScenePosition.y()),
169                       true);
170
171     m_lastMouseEventScenePosition = mapToScene(event->pos()).toPoint();
172     m_lastMouseEventViewPosition = event->pos();
173 }
174
175 void MapView::mousePressEvent(QMouseEvent *event)
176 {
177     qDebug() << __PRETTY_FUNCTION__;
178
179     if (m_doubleTapZoomRunning)
180         return;
181
182     m_time.start();
183
184     m_scroller->stop();
185
186     QGraphicsView::mousePressEvent(event);
187
188     m_lastMouseEventScenePosition = mapToScene(event->pos()).toPoint();
189     m_lastMouseEventViewPosition = event->pos();
190     m_internalScenePosition = mapToScene(width() / 2 - 1, height() / 2 - 1).toPoint();
191
192     for (int i = 0; i < VALUES; i++) {
193         m_dragMovement[i] = QPoint();
194         m_dragTime[i] = 0;
195     }
196     m_index = 0;
197 }
198
199 void MapView::mouseReleaseEvent(QMouseEvent *event)
200 {
201     qDebug() << __PRETTY_FUNCTION__;
202
203     if (m_doubleTapZoomRunning)
204         return;
205
206     int elapsed = m_time.elapsed();
207
208     QGraphicsView::mouseReleaseEvent(event);
209
210     // start kinetic scroll only if there isn't too much time elapsed from the last mouse move event
211     if (elapsed <= KINETIC_MAX_TIME_FROM_LAST_MOUSE_EVENT_MS) {
212         QPointF dragViewSpeed;
213         int dragLength = 0;
214         int values = 0;
215         for (int i = 0; i < VALUES; i++) {
216             if (m_dragTime[i] > 0) {
217                 dragViewSpeed += m_dragMovement[i] / (m_dragTime[i] / MS_PER_S);
218                 dragLength += m_dragMovement[i].manhattanLength();
219                 values++;
220             }
221         }
222
223         if (dragLength >= KINETIC_MIN_DRAG_LENGTH_VIEW_PIXELS) {
224             dragViewSpeed /= values;
225             QPointF effectViewDistance = dragViewSpeed * KINETIC_SPEED_TO_DISTANCE_FACTOR;
226
227             // limit the scroll distance in screen pixels
228             qreal biggerDistance = qMax(abs(effectViewDistance.x()), abs(effectViewDistance.y()));
229             if (biggerDistance > m_kineticMaxViewDistance)
230                 effectViewDistance /= biggerDistance / m_kineticMaxViewDistance;
231
232             QPointF effectSceneDistance = effectViewDistance
233                                           * (1 << (OSM_MAX_ZOOM_LEVEL - m_zoomLevel));
234
235             m_scroller->setEasingCurve(QEasingCurve::OutCirc);
236             m_scroller->setDuration(KINETIC_SCROLL_TIME_MS);
237             m_scroller->setStartValue(SceneCoordinate(m_internalScenePosition.x(),
238                                                       m_internalScenePosition.y()));
239             QPointF endValue = QPointF(m_internalScenePosition) + effectSceneDistance;
240             m_scroller->setEndValue(SceneCoordinate(endValue.x(), endValue.y()));
241             m_scroller->setKineticScrollFlag(true);
242             m_scroller->start();
243         }
244     }
245 }
246
247 void MapView::resizeEvent(QResizeEvent *event)
248 {
249     qDebug() << __PRETTY_FUNCTION__ << "Resize:" << event->size();
250
251     m_kineticMaxViewDistance = qMax(width(), height()) * KINETIC_MAX_VIEW_DISTANCE_FACTOR;
252
253     emit viewResized(event->size());
254
255     if (m_centerShiftAnimation) {
256         m_centerShiftAnimation->setEndValue(event->size().width() / 4);
257         updateCenterShift();
258     }
259 }
260
261 void MapView::setViewScale(qreal viewScale)
262 {
263     qDebug() << __PRETTY_FUNCTION__;
264
265     QTransform transform;
266     transform.scale(viewScale, viewScale);
267     setTransform(transform);
268
269     updateCenterShift();
270 }
271
272 void MapView::setViewShift(qreal viewShift)
273 {
274     qDebug() << __PRETTY_FUNCTION__;
275
276     m_centerHorizontalShiftViewPixels = viewShift;
277     emit horizontalShiftingChanged(m_centerHorizontalShiftViewPixels);
278
279     updateCenterShift();
280 }
281
282 void MapView::setZoomLevel(int zoomLevel)
283 {
284     qDebug() << __PRETTY_FUNCTION__;
285
286     m_zoomLevel = zoomLevel;
287
288     if (m_zoomAnimation) {
289         m_zoomAnimation->stop();
290         m_zoomAnimation->setEasingCurve(QEasingCurve::InQuad);
291         m_zoomAnimation->setDuration(ZOOM_TIME_MS);
292         m_zoomAnimation->setStartValue(viewScale());
293         m_zoomAnimation->setEndValue(pow(2, zoomLevel - OSM_MAX_ZOOM_LEVEL));
294
295         m_zoomAnimation->start();
296     }
297 }
298
299 void MapView::updateCenterShift()
300 {
301     qDebug() << __PRETTY_FUNCTION__;
302
303     m_centerHorizontalShiftPoint = QPointF(m_centerHorizontalShiftViewPixels * (1.0 / viewScale()),
304                                            0);
305
306     centerToSceneCoordinates(m_lastSetScenePosition);
307 }
308
309 qreal MapView::viewScale() const
310 {
311     qDebug() << __PRETTY_FUNCTION__;
312
313     return transform().m11();
314 }
315
316 qreal MapView::viewShift() const
317 {
318     qDebug() << __PRETTY_FUNCTION__;
319
320     return m_centerHorizontalShiftViewPixels;
321 }