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