1 /*
2  *    Copyright 2012-2014 Thomas Schöps
3  *    Copyright 2013-2017 Kai Pastor
4  *
5  *    This file is part of OpenOrienteering.
6  *
7  *    OpenOrienteering is free software: you can redistribute it and/or modify
8  *    it under the terms of the GNU General Public License as published by
9  *    the Free Software Foundation, either version 3 of the License, or
10  *    (at your option) any later version.
11  *
12  *    OpenOrienteering 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 OpenOrienteering.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 
22 #ifndef OPENORIENTEERING_MAP_WIDGET_H
23 #define OPENORIENTEERING_MAP_WIDGET_H
24 
25 #include <functional>
26 
27 #include <Qt>
28 #include <QtGlobal>
29 #include <QCursor>
30 #include <QImage>
31 #include <QObject>
32 #include <QPoint>
33 #include <QPointF>
34 #include <QRect>
35 #include <QRectF>
36 #include <QScopedPointer>
37 #include <QSize>
38 #include <QString>
39 #include <QTime>
40 #include <QVariant>
41 #include <QWidget>
42 
43 #include "core/map_coord.h"
44 #include "core/map_view.h"
45 
46 class QContextMenuEvent;
47 class QEvent;
48 class QFocusEvent;
49 class QGestureEvent;
50 class QInputMethodEvent;
51 class QKeyEvent;
52 class QLabel;
53 class QMouseEvent;
54 class QPaintEvent;
55 class QPainter;
56 class QPixmap;
57 class QResizeEvent;
58 class QWheelEvent;
59 
60 namespace OpenOrienteering {
61 
62 class GPSDisplay;
63 class GPSTemporaryMarkers;
64 class MapEditorActivity;
65 class MapEditorTool;
66 class PieMenu;
67 class TouchCursor;
68 
69 
70 /**
71  * QWidget for displaying a map. Needs a pointer to a MapView which defines
72  * the view properties.
73  *
74  * For faster display, the widget keeps some cached image internally which
75  * are of the same size as the widget area. If then for example the map changes,
76  * the other caches do not need to be redrawn.
77  * <ul>
78  * <li>The <b>map cache</b> contains the currently visible part of the map</li>
79  * <li>The <b>below template cache</b> contains the currently
80  *     visible part of all templates below the map</li>
81  * <li>The <b>above template cache</b> contains the currently
82  *     visible part of all templates above the map</li>
83  * </ul>
84  */
85 class MapWidget : public QWidget
86 {
87 Q_OBJECT
88 friend class MapView;
89 public:
90 	/** Describes different display formats for coordinates. */
91 	enum CoordsType
92 	{
93 		/** Map coordinates: millimeters on map paper */
94 		MAP_COORDS,
95 		/** Projected coordinates, e.g. UTM */
96 		PROJECTED_COORDS,
97 		/** Geographic WGS84 coordinates */
98 		GEOGRAPHIC_COORDS,
99 		/** Geographic WGS84 coordinates in degrees, minutes, seconds */
100 		GEOGRAPHIC_COORDS_DMS
101 	};
102 
103 	/** Describes how a zoom level can be determined. */
104 	enum ZoomOption
105 	{
106 		ContinuousZoom, ///< Allow any zoom value in the valid range.
107 		DiscreteZoom,   ///< Adjust the zoom to the closes valid step.
108 	};
109 
110 	/**
111 	 * Constructs a new MapWidget.
112 	 *
113 	 * @param show_help If set to true, the map widget shows help texts for
114 	 *     empty maps.
115 	 * @param force_antialiasing If set to true, the map widget uses antialiasing
116 	 *     for display, even if it is disabled in the program settings.
117 	 *     Useful for the symbol editor.
118 	 * @param parent Optional QWidget parent.
119 	 */
120 	MapWidget(bool show_help, bool force_antialiasing, QWidget* parent = nullptr);
121 
122 	/** Destructs the MapWidget. */
123 	~MapWidget() override;
124 
125 	/** Sets the map view to use for display. Does not take ownership of the view. */
126 	void setMapView(MapView* view);
127 
128 	/** Returns the map view used for display. */
129 	MapView* getMapView() const;
130 
131 
132 	/** Sets the tool to use in this widget. Does not take ownership of the tool. */
133 	void setTool(MapEditorTool* tool);
134 
135 	/** Sets the activity to use in this widget. Does not take ownership of the activity. */
136 	void setActivity(MapEditorActivity* activity);
137 
138 
139 	/**
140 	 * @brief Enables or disables gesture recognition.
141 	 *
142 	 * MapWidget can recognize gestures, such as two-finger gestures for panning
143 	 * and zooming. However, this may disturb the work with editing tools. So gestures
144 	 * may be disabled.
145 	 *
146 	 * @param enabled If true, enables gesture recognition. Otherwise gestures are disabled.
147 	 */
148 	void setGesturesEnabled(bool enabled);
149 
150 	/**
151 	 * @brief Returns true if gesture recognition is enabled.
152 	 *
153 	 */
154 	bool gesturesEnabled() const;
155 
156 
157 	/**
158 	 * Applies the complete transform to the painter which enables to draw
159 	 * map objects with map coordinates and have them correctly displayed in
160 	 * the widget with the settings of the used MapView.
161 	 */
162 	void applyMapTransform(QPainter* painter) const;
163 
164 	// Coordinate transformations
165 
166 	/** Maps viewport (GUI) coordinates to view coordinates (see MapView). */
167 	QRectF viewportToView(const QRect& input) const;
168 	/** Maps viewport (GUI) coordinates to view coordinates (see MapView). */
169 	QPointF viewportToView(const QPoint& input) const;
170 	/** Maps viewport (GUI) coordinates to view coordinates (see MapView). */
171 	QPointF viewportToView(QPointF input) const;
172 	/** Maps view coordinates (see MapView) to viewport (GUI) coordinates. */
173 	QRectF viewToViewport(const QRectF& input) const;
174 	/** Maps view coordinates (see MapView) to viewport (GUI) coordinates. */
175 	QRectF viewToViewport(const QRect& input) const;
176 	/** Maps view coordinates (see MapView) to viewport (GUI) coordinates. */
177 	QPointF viewToViewport(const QPoint& input) const;
178 	/** Maps view coordinates (see MapView) to viewport (GUI) coordinates. */
179 	QPointF viewToViewport(QPointF input) const;
180 
181 	/** Maps viewport (GUI) coordinates to map coordinates. */
182 	MapCoord viewportToMap(const QPoint& input) const;
183 	/** Maps viewport (GUI) coordinates to map coordinates. */
184 	MapCoordF viewportToMapF(const QPoint& input) const;
185 	/** Maps viewport (GUI) coordinates to map coordinates. */
186 	MapCoordF viewportToMapF(const QPointF& input) const;
187 	/** Maps map coordinates to viewport (GUI) coordinates. */
188 	QPointF mapToViewport(const MapCoord& input) const;
189 	/** Maps map coordinates to viewport (GUI) coordinates. */
190 	QPointF mapToViewport(const QPointF& input) const;
191 	/** Maps map coordinates to viewport (GUI) coordinates. */
192 	QRectF mapToViewport(const QRectF& input) const;
193 
194 
195 	/** Notifies the MapWidget of the view having zoomed, moved or rotated. */
196 	void viewChanged(MapView::ChangeFlags changes);
197 
198 
199 	/**
200 	 * Returns the current offset (in pixel) during a map pan operation.
201 	 */
202 	QPoint panOffset() const;
203 
204 	/**
205 	 * Sets the current offset (in pixel) during a map pan operation.
206 	 */
207 	void setPanOffset(const QPoint& offset);
208 
209 
210 	/**
211 	 * Adjusts the viewport so the given rect is inside the view.
212 	 */
213 	void ensureVisibilityOfRect(QRectF map_rect, ZoomOption zoom_option);  // clazy:exclude=function-args-by-ref
214 
215 	/**
216 	 * Sets the view so the rect is centered and zooomed to fill the widget.
217 	 */
218 	void adjustViewToRect(QRectF map_rect, ZoomOption zoom_option);  // clazy:exclude=function-args-by-ref
219 
220 	/**
221 	 * Mark a rectangular region of a template cache as "dirty", i.e. redraw needed.
222 	 * This rect is united with possible previous dirty rects of that cache.
223 	 * @param view_rect Affected rect in view coordinates.
224 	 * @param pixel_border Additional affected extent around the view rect in
225 	 *     pixels. Allows to specify zoom-independent extents.
226 	 * @param front_cache If set to true, invalidates the cache for templates
227 	 *     in front of the map, else invalidates the cache for templates behind the map.
228 	 */
229 	void markTemplateCacheDirty(const QRectF& view_rect, int pixel_border, bool front_cache);
230 
231 	/**
232 	 * Mark a rectangular region given in map coordinates of the map cache
233 	 * as dirty, i.e. redraw needed.
234 	 * This rect is united with possible previous dirty rects of that cache.
235 	 */
236 	void markObjectAreaDirty(const QRectF& map_rect);
237 
238 	/**
239 	 * Set the given rect as bounding box for the current drawing, i.e. the
240 	 * graphical display of the active tool.
241 	 * NOTE: Unlike for markTemplateCacheDirty(), multiple calls to
242 	 * these methodsdo not result in uniting all given rects,
243 	 * instead only the last rect is used!
244 	 * Pass QRect() to disable the current drawing.
245 	 * @param map_rect Affected rect in map coordinates.
246 	 * @param pixel_border Additional affected extent around the map rect in
247 	 *     pixels. Allows to specify zoom-independent extents.
248 	 * @param do_update If set to true, triggers a redraw of the widget.
249 	 */
250 	void setDrawingBoundingBox(QRectF map_rect, int pixel_border, bool do_update);  // clazy:exclude=function-args-by-ref
251 	/**
252 	 * Removes the area set with setDrawingBoundingBox() and triggers a redraw
253 	 * of the widget, if needed.
254 	 */
255 	void clearDrawingBoundingBox();
256 
257 	/** Analogon to setDrawingBoundingBox() for activities. */
258 	void setActivityBoundingBox(QRectF map_rect, int pixel_border, bool do_update);  // clazy:exclude=function-args-by-ref
259 	/** Analogon to clearDrawingBoundingBox() for activities. */
260 	void clearActivityBoundingBox();
261 
262 	/**
263 	 * Triggers a redraw of the MapWidget at the given area.
264 	 * @param map_rect Affected rect in map coordinates.
265 	 * @param pixel_border Additional affected extent around the map rect in
266 	 *     pixels. Allows to specify zoom-independent extents.
267 	 */
268 	void updateDrawing(const QRectF& map_rect, int pixel_border);
269 	/**
270 	 * Triggers a redraw of the MapWidget at the given area.
271 	 */
272 	void updateMapRect(const QRectF& map_rect, int pixel_border, QRect& cache_dirty_rect);
273 	/**
274 	 * Triggers a redraw of the MapWidget at the given area.
275 	 */
276 	void updateViewportRect(QRect viewport_rect, QRect& cache_dirty_rect);
277 	/**
278 	 * Variant of updateDrawing() which waits for some milliseconds before
279 	 * calling update() in order to avoid excessive redraws.
280 	 */
281 	void updateDrawingLater(const QRectF& map_rect, int pixel_border);
282 
283 	/**
284 	 * Invalidates all caches and redraws the whole widget. Very slow, try to
285 	 * avoid this.
286 	 */
287 	void updateEverything();
288 	/**
289 	 * Sets all "dirty" region markers to the given rect in viewport coordinates
290 	 * and triggers a redraw of the MapWidget there.
291 	 */
292 	void updateEverythingInRect(const QRect& dirty_rect);
293 
294 	/**
295 	 * Sets the function which will be called to display zoom information.
296 	 */
297 	void setZoomDisplay(std::function<void(const QString&)> setter);
298 
299 	/** Specify the label where the MapWidget will display cursor position information. */
300 	void setCursorposLabel(QLabel* cursorpos_label);
301 	/**
302 	 * Specify the system and format for displaying coordinates in
303 	 * the cursorpos label. See CoordsType for the available types.
304 	 */
305 	void setCoordsDisplay(CoordsType type);
306 	/** Returns the coordinate display type set by setCoordsDisplay(). */
307 	inline CoordsType getCoordsDisplay() const;
308 
309 	/** Returns the time in milliseconds since the last user interaction
310 	 *  (mouse press or drag) with the widget. */
311 	int getTimeSinceLastInteraction();
312 
313 	/** Sets the GPS display to use. This is called internally by the GPSDisplay constructor. */
314 	void setGPSDisplay(GPSDisplay* gps_display);
315 	/** Sets the GPS temporary markers display to use. This is called internally by the GPSTemporaryMarkers constructor. */
316 	void setTemporaryMarkerDisplay(GPSTemporaryMarkers* marker_display);
317 
318 	/** Returns the widget's context menu widget. */
319 	QWidget* getContextMenu();
320 
321 	/** Returns the widget's preferred size. */
322 	QSize sizeHint() const override;
323 
324 	/**
325 	 * @copybrief MainWindowController::keyPressEventFilter
326 	 * Delegates the keyPress to the active tool, or handles some shortcuts itself.
327 	 */
328 	bool keyPressEventFilter(QKeyEvent* event);
329 
330 	/**
331 	 * @copybrief MainWindowController::keyPressEventFilter
332 	 * Delegates the keyRelease to the active tool.
333 	 */
334 	bool keyReleaseEventFilter(QKeyEvent* event);
335 
336 	/**
337 	 * Support function for input methods.
338 	 */
339 	QVariant inputMethodQuery(Qt::InputMethodQuery property) const override;
340 
341 public slots:
342 	/**
343 	 * Support function for input methods.
344 	 *
345 	 * This two-argument form is undocumented but attempted to call in
346 	 * QInputMethod::queryFocusObject before doing the query via an event.
347 	 */
348 	QVariant inputMethodQuery(Qt::InputMethodQuery property, const QVariant& argument) const;  // clazy:exclude=const-signal-or-slot
349 
350 	/** Enables or disables the touch cursor. */
351 	void enableTouchCursor(bool enabled);
352 
353 signals:
354 	/**
355 	 * Support function for input methods.
356 	 */
357 	void cursorPositionChanged();
358 
359 private slots:
360 	void updateDrawingLaterSlot();
361 
362 protected:
363 	bool event(QEvent *event) override;
364 
365 	virtual void gestureEvent(QGestureEvent* event);
366 
367 	void paintEvent(QPaintEvent* event) override;
368 	void resizeEvent(QResizeEvent* event) override;
369 
370 	// Mouse input
371 	void mousePressEvent(QMouseEvent* event) override;
372 	void _mousePressEvent(QMouseEvent* event);
373 	void mouseMoveEvent(QMouseEvent* event) override;
374 	void _mouseMoveEvent(QMouseEvent* event);
375 	void mouseReleaseEvent(QMouseEvent* event) override;
376 	void _mouseReleaseEvent(QMouseEvent* event);
377 	void mouseDoubleClickEvent(QMouseEvent* event) override;
378 	void _mouseDoubleClickEvent(QMouseEvent* event);
379 	void wheelEvent(QWheelEvent* event) override;
380 	void leaveEvent(QEvent* event) override;
381 
382 	// Key input (see also slots)
383 	void inputMethodEvent(QInputMethodEvent *event) override;
384 	void focusOutEvent(QFocusEvent* event) override;
385 
386 	void contextMenuEvent(QContextMenuEvent* event) override;
387 
388 private:
389 	/** Checks if there is a visible template in the range
390 	 *  from first_template to last_template. */
391 	bool containsVisibleTemplate(int first_template, int last_template) const;
392 	/** Checks if there is any visible template above the map. */
393 	bool isAboveTemplateVisible() const;
394 	/** Checks if there is any visible template below the map. */
395 	bool isBelowTemplateVisible() const;
396 	/**
397 	 * Redraws the template cache.
398 	 * @param cache Reference to pointer to the cache.
399 	 * @param dirty_rect Rectangle of the cache to redraw, in viewport coordinates.
400 	 * @param first_template Lowest template index to draw.
401 	 * @param last_template Highest template index to draw.
402 	 * @param use_background If set to true, fills the cache with white before
403 	 *     drawing the templates, else makes it transparent.
404 	 */
405 	void updateTemplateCache(QImage& cache, QRect& dirty_rect, int first_template, int last_template, bool use_background);
406 	/**
407 	 * Redraws the map cache in the map cache dirty rect.
408 	 * @param use_background If set to true, fills the cache with white before
409 	 *     drawing the map, else makes it transparent.
410 	 */
411 	void updateMapCache(bool use_background);
412 	/** Redraws all dirty caches. */
413 	void updateAllDirtyCaches();
414 	/** Shifts the content in the cache by the given amount of pixels. */
415 	void shiftCache(int sx, int sy, QImage& cache);
416 	void shiftCache(int sx, int sy, QPixmap& cache);
417 
418 	/**
419 	 * Calculates the bounding box of the given map coordinates rect and
420 	 * additional pixel extent in integer viewport coordinates.
421 	 */
422 	QRect calculateViewportBoundingBox(const QRectF& map_rect, int pixel_border) const;
423 	/** Internal method for setting a part of a cache as dirty. */
424 	void setDynamicBoundingBox(QRectF map_rect, int pixel_border, QRect& dirty_rect_old, QRectF& dirty_rect_new, int& dirty_rect_new_border, bool do_update);
425 	/** Internal method for removing the dirty state of a cache. */
426 	void clearDynamicBoundingBox(QRect& dirty_rect_old, QRectF& dirty_rect_new, int& dirty_rect_new_border);
427 
428 	/** Moves the dirty rect by the given amount of pixels. */
429 	void moveDirtyRect(QRect& dirty_rect, qreal x, qreal y);
430 
431 	/** Starts a dragging interaction at the given cursor position. */
432 	void startDragging(const QPoint& cursor_pos);
433 	/** Submits a new cursor position during a dragging interaction. */
434 	void updateDragging(const QPoint& cursor_pos);
435 	/** Ends a dragging interaction at the given cursor position. */
436 	void finishDragging(const QPoint& cursor_pos);
437 	/** Cancels a dragging interaction. */
438 	void cancelDragging();
439 
440 	/** Starts a pinching interaction at the given cursor position.
441 	 *  Returns the initial zoom factor. */
442 	qreal startPinching(const QPoint& center);
443 	/** Updates a pinching interaction at the given cursor position. */
444 	void updatePinching(const QPoint& center, qreal factor);
445 	/** Ends a pinching interaction at the given cursor position. */
446 	void finishPinching(const QPoint& center, qreal factor);
447 	/** Cancels a pinching interaction. */
448 	void cancelPinching();
449 
450 	/** Moves the map a given number of big "steps" in x and/or y direction. */
451 	void moveMap(int steps_x, int steps_y);
452 
453 	/** Draws a help message at the center of the MapWidget. */
454 	void showHelpMessage(QPainter* painter, const QString& text) const;
455 
456 	/**
457 	 * Updates the content of the zoom display.
458 	 *
459 	 * \see setZoomDisplay()
460 	 */
461 	void updateZoomDisplay();
462 	/** Updates the content of the cursorpos label, set by setCursorposLabel(). */
463 	void updateCursorposLabel(const MapCoordF& pos);
464 
465 	MapView* view;
466 	MapEditorTool* tool;
467 	MapEditorActivity* activity;
468 
469 	CoordsType coords_type;
470 
471 	std::function<void(const QString&)> zoom_display;
472 	QLabel* cursorpos_label;
473 	QLabel* objecttag_label;
474 	MapCoordF last_cursor_pos;
475 
476 	bool show_help;
477 	bool force_antialiasing;
478 
479 	// Dragging (interaction)
480 	bool dragging;
481 	QPoint drag_start_pos;
482 	/** Cursor used when not dragging */
483 	QCursor normal_cursor;
484 
485 	// Pinching (interaction)
486 	bool pinching;
487 	qreal pinching_factor;
488 	QPoint pinching_center;
489 
490 	// Panning (operation)
491 	QPoint pan_offset;
492 
493 	// Template caches
494 	/** Cache for templates below map layer */
495 	QImage below_template_cache;
496 	QRect below_template_cache_dirty_rect;
497 
498 	/** Cache for templates above map layer */
499 	QImage above_template_cache;
500 	QRect above_template_cache_dirty_rect;
501 
502 	/** Map layer cache  */
503 	QImage map_cache;
504 	QRect map_cache_dirty_rect;
505 
506 	// Dirty regions for drawings (tools) and activities
507 	/** Dirty rect for the current tool, in viewport coordinates (pixels). */
508 	QRect drawing_dirty_rect;
509 
510 	/** Dirty rect for the current tool, in map coordinates. */
511 	QRectF drawing_dirty_rect_map;
512 
513 	/** Additional pixel border for the tool dirty rect, in pixels. */
514 	int drawing_dirty_rect_border;
515 
516 	/** Dirty rect for the current activity, in viewport coordinates (pixels). */
517 	QRect activity_dirty_rect;
518 
519 	/** Dirty rect for the current activity, in map coordinates. */
520 	QRectF activity_dirty_rect_map;
521 
522 	/** Additional pixel border for the activity dirty rect, in pixels. */
523 	int activity_dirty_rect_border;
524 
525 	/** Cached updates */
526 	QRect cached_update_rect;
527 
528 	/** Right-click menu */
529 	PieMenu* context_menu;
530 
531 	/** Optional touch cursor for mobile devices */
532 	QScopedPointer<TouchCursor> touch_cursor;
533 
534 	/** For checking for interaction with the widget: the last QTime where
535 	 *  a mouse release event happened. Check for current_pressed_buttons == 0
536 	 *  and a last_mouse_release_time a given time interval in the past to check
537 	 *  whether the user interacts or recently interacted with the widget. */
538 	QTime last_mouse_release_time;
539 	int current_pressed_buttons;
540 
541 	/** Optional GPS display */
542 	GPSDisplay* gps_display;
543 	/** Optional temporary GPS marker display. */
544 	GPSTemporaryMarkers* marker_display;
545 
546 	/** @brief Indicates whether gesture recognition is enabled. */
547 	bool gestures_enabled;
548 };
549 
550 
551 
552 // ### MapWidget inline code ###
553 
554 inline
getMapView()555 MapView* MapWidget::getMapView() const
556 {
557 	return view;
558 }
559 
560 inline
gesturesEnabled()561 bool MapWidget::gesturesEnabled() const
562 {
563 	return gestures_enabled;
564 }
565 
566 inline
panOffset()567 QPoint MapWidget::panOffset() const
568 {
569 	return pan_offset;
570 }
571 
572 inline
getCoordsDisplay()573 MapWidget::CoordsType MapWidget::getCoordsDisplay() const
574 {
575 	return coords_type;
576 }
577 
578 
579 }  // namespace OpenOrienteering
580 
581 #endif
582