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 #include "map_widget.h"
23
24 #include <cmath>
25 #include <stdexcept>
26
27 #include <QApplication>
28 #include <QColor>
29 #include <QContextMenuEvent>
30 #include <QEvent>
31 #include <QFlags>
32 #include <QFont>
33 #include <QGestureEvent>
34 #include <QKeyEvent>
35 #include <QLabel>
36 #include <QLatin1String>
37 #include <QList>
38 #include <QLocale>
39 #include <QMouseEvent>
40 #include <QObjectList>
41 #include <QPainter>
42 #include <QPaintEvent>
43 #include <QPinchGesture>
44 #include <QPixmap>
45 #include <QResizeEvent>
46 #include <QSizePolicy>
47 #include <QTimer>
48 #include <QTouchEvent>
49 #include <QTransform>
50 #include <QVariant>
51 #include <QWheelEvent>
52
53 #include "settings.h"
54 #include "core/georeferencing.h"
55 #include "core/latlon.h"
56 #include "core/map.h"
57 #include "core/renderables/renderable.h"
58 #include "gui/touch_cursor.h"
59 #include "gui/map/map_editor_activity.h"
60 #include "gui/widgets/action_grid_bar.h"
61 #include "gui/widgets/key_button_bar.h"
62 #include "gui/widgets/pie_menu.h"
63 #include "sensors/gps_display.h"
64 #include "sensors/gps_temporary_markers.h"
65 #include "templates/template.h" // IWYU pragma: keep
66 #include "tools/tool.h"
67 #include "util/backports.h" // IWYU pragma: keep
68 #include "util/util.h"
69
70 class QGesture;
71 // IWYU pragma: no_forward_declare QPinchGesture
72
73
74 #ifdef __clang_analyzer__
75 #define singleShot(A, B, C) singleShot(A, B, #C) // NOLINT
76 #endif
77
78
79 namespace OpenOrienteering {
80
MapWidget(bool show_help,bool force_antialiasing,QWidget * parent)81 MapWidget::MapWidget(bool show_help, bool force_antialiasing, QWidget* parent)
82 : QWidget(parent)
83 , view(nullptr)
84 , tool(nullptr)
85 , activity(nullptr)
86 , coords_type(MAP_COORDS)
87 , cursorpos_label(nullptr)
88 , show_help(show_help)
89 , force_antialiasing(force_antialiasing)
90 , dragging(false)
91 , pinching(false)
92 , pinching_factor(1.0)
93 , below_template_cache_dirty_rect(rect())
94 , above_template_cache_dirty_rect(rect())
95 , map_cache_dirty_rect(rect())
96 , drawing_dirty_rect_border(0)
97 , activity_dirty_rect_border(0)
98 , last_mouse_release_time(QTime::currentTime())
99 , current_pressed_buttons(0)
100 , gps_display(nullptr)
101 , marker_display(nullptr)
102 {
103 context_menu = new PieMenu(this);
104 // context_menu->setMinimumActionCount(8);
105 // context_menu->setIconSize(24);
106
107 setAttribute(Qt::WA_OpaquePaintEvent);
108 setAttribute(Qt::WA_AcceptTouchEvents, true);
109 setGesturesEnabled(true);
110 setAutoFillBackground(false);
111 setMouseTracking(true);
112 setFocusPolicy(Qt::ClickFocus);
113 setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
114 }
115
~MapWidget()116 MapWidget::~MapWidget()
117 {
118 // nothing, not inlined
119 }
120
setMapView(MapView * view)121 void MapWidget::setMapView(MapView* view)
122 {
123 if (this->view != view)
124 {
125 if (this->view)
126 {
127 auto map = view->getMap();
128 map->removeMapWidget(this);
129
130 disconnect(this->view, &MapView::viewChanged, this, &MapWidget::viewChanged);
131 disconnect(this->view, &MapView::panOffsetChanged, this, &MapWidget::setPanOffset);
132 disconnect(this->view, &MapView::visibilityChanged, this, &MapWidget::updateEverything);
133 }
134
135 this->view = view;
136
137 if (view)
138 {
139 connect(this->view, &MapView::viewChanged, this, &MapWidget::viewChanged);
140 connect(this->view, &MapView::panOffsetChanged, this, &MapWidget::setPanOffset);
141 connect(this->view, &MapView::visibilityChanged, this, &MapWidget::updateEverything);
142
143 auto map = this->view->getMap();
144 map->addMapWidget(this);
145 }
146
147 update();
148 }
149 }
150
setTool(MapEditorTool * tool)151 void MapWidget::setTool(MapEditorTool* tool)
152 {
153 // Redraw if touch cursor usage changes
154 bool redrawTouchCursor = (touch_cursor && this->tool && tool
155 && (this->tool->usesTouchCursor() || tool->usesTouchCursor()));
156
157 this->tool = tool;
158
159 if (tool)
160 setCursor(tool->getCursor());
161 else
162 unsetCursor();
163 if (redrawTouchCursor)
164 touch_cursor->updateMapWidget(false);
165 }
166
setActivity(MapEditorActivity * activity)167 void MapWidget::setActivity(MapEditorActivity* activity)
168 {
169 this->activity = activity;
170 }
171
172
setGesturesEnabled(bool enabled)173 void MapWidget::setGesturesEnabled(bool enabled)
174 {
175 gestures_enabled = enabled;
176 if (enabled)
177 {
178 grabGesture(Qt::PinchGesture);
179 }
180 else
181 {
182 ungrabGesture(Qt::PinchGesture);
183 }
184 }
185
186
applyMapTransform(QPainter * painter) const187 void MapWidget::applyMapTransform(QPainter* painter) const
188 {
189 painter->translate(width() / 2.0 + getMapView()->panOffset().x(),
190 height() / 2.0 + getMapView()->panOffset().y());
191 painter->setWorldTransform(getMapView()->worldTransform(), true);
192 }
193
viewportToView(const QRect & input) const194 QRectF MapWidget::viewportToView(const QRect& input) const
195 {
196 return QRectF(input.left() - 0.5*width() - pan_offset.x(), input.top() - 0.5*height() - pan_offset.y(), input.width(), input.height());
197 }
198
viewportToView(const QPoint & input) const199 QPointF MapWidget::viewportToView(const QPoint& input) const
200 {
201 return QPointF(input.x() - 0.5*width() - pan_offset.x(), input.y() - 0.5*height() - pan_offset.y());
202 }
203
viewportToView(QPointF input) const204 QPointF MapWidget::viewportToView(QPointF input) const
205 {
206 input.rx() -= 0.5*width() + pan_offset.x();
207 input.ry() -= 0.5*height() + pan_offset.y();
208 return input;
209 }
210
viewToViewport(const QRectF & input) const211 QRectF MapWidget::viewToViewport(const QRectF& input) const
212 {
213 return QRectF(input.left() + 0.5*width() + pan_offset.x(), input.top() + 0.5*height() + pan_offset.y(), input.width(), input.height());
214 }
215
viewToViewport(const QRect & input) const216 QRectF MapWidget::viewToViewport(const QRect& input) const
217 {
218 return QRectF(input.left() + 0.5*width() + pan_offset.x(), input.top() + 0.5*height() + pan_offset.y(), input.width(), input.height());
219 }
220
viewToViewport(const QPoint & input) const221 QPointF MapWidget::viewToViewport(const QPoint& input) const
222 {
223 return QPointF(input.x() + 0.5*width() + pan_offset.x(), input.y() + 0.5*height() + pan_offset.y());
224 }
225
viewToViewport(QPointF input) const226 QPointF MapWidget::viewToViewport(QPointF input) const
227 {
228 input.rx() += 0.5*width() + pan_offset.x();
229 input.ry() += 0.5*height() + pan_offset.y();
230 return input;
231 }
232
233
viewportToMap(const QPoint & input) const234 MapCoord MapWidget::viewportToMap(const QPoint& input) const
235 {
236 return view->viewToMap(viewportToView(input));
237 }
238
viewportToMapF(const QPoint & input) const239 MapCoordF MapWidget::viewportToMapF(const QPoint& input) const
240 {
241 return view->viewToMapF(viewportToView(input));
242 }
243
viewportToMapF(const QPointF & input) const244 MapCoordF MapWidget::viewportToMapF(const QPointF& input) const
245 {
246 return view->viewToMapF(viewportToView(input));
247 }
248
mapToViewport(const MapCoord & input) const249 QPointF MapWidget::mapToViewport(const MapCoord& input) const
250 {
251 return viewToViewport(view->mapToView(input));
252 }
253
mapToViewport(const QPointF & input) const254 QPointF MapWidget::mapToViewport(const QPointF& input) const
255 {
256 return viewToViewport(view->mapToView(input));
257 }
258
mapToViewport(const QRectF & input) const259 QRectF MapWidget::mapToViewport(const QRectF& input) const
260 {
261 QRectF result;
262 rectIncludeSafe(result, mapToViewport(input.topLeft()));
263 rectIncludeSafe(result, mapToViewport(input.bottomRight()));
264 if (view->getRotation() != 0)
265 {
266 rectIncludeSafe(result, mapToViewport(input.topRight()));
267 rectIncludeSafe(result, mapToViewport(input.bottomLeft()));
268 }
269 return result;
270 }
271
viewChanged(MapView::ChangeFlags changes)272 void MapWidget::viewChanged(MapView::ChangeFlags changes)
273 {
274 setDrawingBoundingBox(drawing_dirty_rect_map, drawing_dirty_rect_border, true);
275 setActivityBoundingBox(activity_dirty_rect_map, activity_dirty_rect_border, true);
276 updateEverything();
277 if (changes.testFlag(MapView::ZoomChange))
278 updateZoomDisplay();
279 }
280
setPanOffset(const QPoint & offset)281 void MapWidget::setPanOffset(const QPoint& offset)
282 {
283 pan_offset = offset;
284 update();
285 }
286
startDragging(const QPoint & cursor_pos)287 void MapWidget::startDragging(const QPoint& cursor_pos)
288 {
289 Q_ASSERT(!dragging);
290 Q_ASSERT(!pinching);
291 dragging = true;
292 drag_start_pos = cursor_pos;
293 normal_cursor = cursor();
294 setCursor(Qt::ClosedHandCursor);
295 }
296
updateDragging(const QPoint & cursor_pos)297 void MapWidget::updateDragging(const QPoint& cursor_pos)
298 {
299 Q_ASSERT(dragging);
300 view->setPanOffset(cursor_pos - drag_start_pos);
301 }
302
finishDragging(const QPoint & cursor_pos)303 void MapWidget::finishDragging(const QPoint& cursor_pos)
304 {
305 Q_ASSERT(dragging);
306 dragging = false;
307 view->finishPanning(cursor_pos - drag_start_pos);
308 setCursor(normal_cursor);
309 }
310
cancelDragging()311 void MapWidget::cancelDragging()
312 {
313 dragging = false;
314 view->setPanOffset(QPoint());
315 setCursor(normal_cursor);
316 }
317
startPinching(const QPoint & center)318 qreal MapWidget::startPinching(const QPoint& center)
319 {
320 Q_ASSERT(!dragging);
321 Q_ASSERT(!pinching);
322 pinching = true;
323 drag_start_pos = center;
324 pinching_center = center;
325 pinching_factor = 1.0;
326 return pinching_factor;
327 }
328
updatePinching(const QPoint & center,qreal factor)329 void MapWidget::updatePinching(const QPoint& center, qreal factor)
330 {
331 Q_ASSERT(pinching);
332 pinching_center = center;
333 pinching_factor = factor;
334 updateZoomDisplay();
335 update();
336 }
337
finishPinching(const QPoint & center,qreal factor)338 void MapWidget::finishPinching(const QPoint& center, qreal factor)
339 {
340 pinching = false;
341 view->finishPanning(center - drag_start_pos);
342 view->setZoom(factor * view->getZoom(), viewportToView(center));
343 }
344
cancelPinching()345 void MapWidget::cancelPinching()
346 {
347 pinching = false;
348 pinching_factor = 1.0;
349 update();
350 }
351
moveMap(int steps_x,int steps_y)352 void MapWidget::moveMap(int steps_x, int steps_y)
353 {
354 if (steps_x != 0 || steps_y != 0)
355 {
356 try
357 {
358 constexpr auto move_factor = 0.25;
359 auto offset = MapCoord::fromNative64( qRound64(view->pixelToLength(width() * steps_x * move_factor)),
360 qRound64(view->pixelToLength(height() * steps_y * move_factor)) );
361 view->setCenter(view->center() + offset);
362 }
363 catch (std::range_error&)
364 {
365 // Do nothing
366 }
367 }
368 }
369
ensureVisibilityOfRect(QRectF map_rect,ZoomOption zoom_option)370 void MapWidget::ensureVisibilityOfRect(QRectF map_rect, ZoomOption zoom_option)
371 {
372 // Amount in pixels that is scrolled "too much" if the rect is not completely visible
373 // TODO: change to absolute size using dpi value
374 const int pixel_border = 70;
375 auto viewport_rect = mapToViewport(map_rect).toAlignedRect();
376
377 // TODO: this method assumes that the viewport is not rotated.
378
379 if (rect().contains(viewport_rect.topLeft()) && rect().contains(viewport_rect.bottomRight()))
380 return;
381
382 auto offset = MapCoordF{ 0, 0 };
383
384 if (viewport_rect.left() < 0)
385 offset.rx() = view->pixelToLength(viewport_rect.left() - pixel_border) / 1000.0;
386 else if (viewport_rect.right() > width())
387 offset.rx() = view->pixelToLength(viewport_rect.right() - width() + pixel_border) / 1000.0;
388
389 if (viewport_rect.top() < 0)
390 offset.ry() = view->pixelToLength(viewport_rect.top() - pixel_border) / 1000.0;
391 else if (viewport_rect.bottom() > height())
392 offset.ry() = view->pixelToLength(viewport_rect.bottom() - height() + pixel_border) / 1000.0;
393
394 if (!qIsNull(offset.lengthSquared()))
395 view->setCenter(view->center() + offset);
396
397 // If the rect is still not completely in view, we have to zoom out
398 viewport_rect = mapToViewport(map_rect).toAlignedRect();
399 if (!(rect().contains(viewport_rect.topLeft()) && rect().contains(viewport_rect.bottomRight())))
400 adjustViewToRect(map_rect, zoom_option);
401 }
402
adjustViewToRect(QRectF map_rect,ZoomOption zoom_option)403 void MapWidget::adjustViewToRect(QRectF map_rect, ZoomOption zoom_option)
404 {
405 view->setCenter(MapCoord{ map_rect.center() });
406
407 if (map_rect.isValid())
408 {
409 // NOTE: The loop is an inelegant way to fight inaccuracies that occur somewhere ...
410 const int pixel_border = 15;
411 const float initial_zoom = view->getZoom();
412 for (int i = 0; i < 10; ++i)
413 {
414 float zoom_factor = qMin(height() / (view->lengthToPixel(1000.0 * map_rect.height()) + 2*pixel_border),
415 width() / (view->lengthToPixel(1000.0 * map_rect.width()) + 2*pixel_border));
416 float zoom = view->getZoom() * zoom_factor;
417 if (zoom_option == DiscreteZoom)
418 {
419 zoom = pow(2, 0.5 * floor(2.0 * (std::log2(zoom) - std::log2(initial_zoom))) + std::log2(initial_zoom));
420 }
421 view->setZoom(zoom);
422 }
423 }
424 }
425
moveDirtyRect(QRect & dirty_rect,qreal x,qreal y)426 void MapWidget::moveDirtyRect(QRect& dirty_rect, qreal x, qreal y)
427 {
428 if (dirty_rect.isValid())
429 dirty_rect = dirty_rect.translated(x, y).intersected(rect());
430 }
431
markTemplateCacheDirty(const QRectF & view_rect,int pixel_border,bool front_cache)432 void MapWidget::markTemplateCacheDirty(const QRectF& view_rect, int pixel_border, bool front_cache)
433 {
434 QRect& cache_dirty_rect = front_cache ? above_template_cache_dirty_rect : below_template_cache_dirty_rect;
435 QRectF viewport_rect = viewToViewport(view_rect);
436 QRect integer_rect = QRect(viewport_rect.left() - (1+pixel_border), viewport_rect.top() - (1+pixel_border),
437 viewport_rect.width() + 2*(1+pixel_border), viewport_rect.height() + 2*(1+pixel_border));
438
439 if (!integer_rect.intersects(rect()))
440 return;
441
442 if (cache_dirty_rect.isValid())
443 cache_dirty_rect = cache_dirty_rect.united(integer_rect);
444 else
445 cache_dirty_rect = integer_rect;
446
447 update(integer_rect);
448 }
449
markObjectAreaDirty(const QRectF & map_rect)450 void MapWidget::markObjectAreaDirty(const QRectF& map_rect)
451 {
452 updateMapRect(map_rect, 0, map_cache_dirty_rect);
453 }
454
setDrawingBoundingBox(QRectF map_rect,int pixel_border,bool do_update)455 void MapWidget::setDrawingBoundingBox(QRectF map_rect, int pixel_border, bool do_update)
456 {
457 Q_UNUSED(do_update);
458 clearDrawingBoundingBox();
459 if (map_rect.isValid())
460 {
461 drawing_dirty_rect_map = map_rect;
462 drawing_dirty_rect_border = pixel_border;
463 updateMapRect(drawing_dirty_rect_map, drawing_dirty_rect_border, drawing_dirty_rect);
464 }
465 }
466
clearDrawingBoundingBox()467 void MapWidget::clearDrawingBoundingBox()
468 {
469 drawing_dirty_rect_map.setWidth(0);
470 if (drawing_dirty_rect.isValid())
471 {
472 update(drawing_dirty_rect);
473 drawing_dirty_rect.setWidth(0);
474 }
475 }
476
setActivityBoundingBox(QRectF map_rect,int pixel_border,bool do_update)477 void MapWidget::setActivityBoundingBox(QRectF map_rect, int pixel_border, bool do_update)
478 {
479 Q_UNUSED(do_update);
480 clearActivityBoundingBox();
481 if (map_rect.isValid())
482 {
483 activity_dirty_rect_map = map_rect;
484 activity_dirty_rect_border = pixel_border;
485 updateMapRect(activity_dirty_rect_map, activity_dirty_rect_border, activity_dirty_rect);
486 }
487 }
488
clearActivityBoundingBox()489 void MapWidget::clearActivityBoundingBox()
490 {
491 activity_dirty_rect_map.setWidth(0);
492 if (activity_dirty_rect.isValid())
493 {
494 update(activity_dirty_rect);
495 activity_dirty_rect.setWidth(0);
496 }
497 }
498
updateDrawing(const QRectF & map_rect,int pixel_border)499 void MapWidget::updateDrawing(const QRectF& map_rect, int pixel_border)
500 {
501 QRect viewport_rect = calculateViewportBoundingBox(map_rect, pixel_border);
502
503 if (viewport_rect.intersects(rect()))
504 update(viewport_rect);
505 }
506
updateMapRect(const QRectF & map_rect,int pixel_border,QRect & cache_dirty_rect)507 void MapWidget::updateMapRect(const QRectF& map_rect, int pixel_border, QRect& cache_dirty_rect)
508 {
509 QRect viewport_rect = calculateViewportBoundingBox(map_rect, pixel_border);
510 updateViewportRect(viewport_rect, cache_dirty_rect);
511 }
512
updateViewportRect(QRect viewport_rect,QRect & cache_dirty_rect)513 void MapWidget::updateViewportRect(QRect viewport_rect, QRect& cache_dirty_rect)
514 {
515 if (viewport_rect.intersects(rect()))
516 {
517 if (cache_dirty_rect.isValid())
518 cache_dirty_rect = cache_dirty_rect.united(viewport_rect);
519 else
520 cache_dirty_rect = viewport_rect;
521
522 update(viewport_rect);
523 }
524 }
525
updateDrawingLater(const QRectF & map_rect,int pixel_border)526 void MapWidget::updateDrawingLater(const QRectF& map_rect, int pixel_border)
527 {
528 QRect viewport_rect = calculateViewportBoundingBox(map_rect, pixel_border);
529
530 if (viewport_rect.intersects(rect()))
531 {
532 if (!cached_update_rect.isValid())
533 {
534 // Start the update timer
535 QTimer::singleShot(15, this, &MapWidget::updateDrawingLaterSlot);
536 }
537
538 // NOTE: this may require a mutex for concurrent access with updateDrawingLaterSlot()?
539 rectIncludeSafe(cached_update_rect, viewport_rect);
540 }
541 }
542
updateDrawingLaterSlot()543 void MapWidget::updateDrawingLaterSlot()
544 {
545 updateEverythingInRect(cached_update_rect);
546 cached_update_rect = QRect();
547 }
548
updateEverything()549 void MapWidget::updateEverything()
550 {
551 map_cache_dirty_rect = rect();
552 below_template_cache_dirty_rect = map_cache_dirty_rect;
553 above_template_cache_dirty_rect = map_cache_dirty_rect;
554 update(map_cache_dirty_rect);
555 }
556
updateEverythingInRect(const QRect & dirty_rect)557 void MapWidget::updateEverythingInRect(const QRect& dirty_rect)
558 {
559 rectIncludeSafe(map_cache_dirty_rect, dirty_rect);
560 rectIncludeSafe(below_template_cache_dirty_rect, dirty_rect);
561 rectIncludeSafe(above_template_cache_dirty_rect, dirty_rect);
562 update(dirty_rect);
563 }
564
calculateViewportBoundingBox(const QRectF & map_rect,int pixel_border) const565 QRect MapWidget::calculateViewportBoundingBox(const QRectF& map_rect, int pixel_border) const
566 {
567 QRectF view_rect = view->calculateViewBoundingBox(map_rect);
568 view_rect.adjust(-pixel_border, -pixel_border, +pixel_border, +pixel_border);
569 return viewToViewport(view_rect).toAlignedRect();
570 }
571
setZoomDisplay(std::function<void (const QString &)> setter)572 void MapWidget::setZoomDisplay(std::function<void(const QString&)> setter)
573 {
574 this->zoom_display = setter;
575 updateZoomDisplay();
576 }
577
setCursorposLabel(QLabel * cursorpos_label)578 void MapWidget::setCursorposLabel(QLabel* cursorpos_label)
579 {
580 this->cursorpos_label = cursorpos_label;
581 }
582
updateZoomDisplay()583 void MapWidget::updateZoomDisplay()
584 {
585 if (zoom_display)
586 {
587 auto zoom = view->getZoom();
588 if (pinching)
589 zoom *= pinching_factor;
590 zoom_display(tr("%1x", "Zoom factor").arg(zoom, 0, 'g', 3));
591 }
592 }
593
setCoordsDisplay(CoordsType type)594 void MapWidget::setCoordsDisplay(CoordsType type)
595 {
596 coords_type = type;
597 updateCursorposLabel(last_cursor_pos);
598 }
599
updateCursorposLabel(const MapCoordF & pos)600 void MapWidget::updateCursorposLabel(const MapCoordF& pos)
601 {
602 last_cursor_pos = pos;
603
604 if (!cursorpos_label)
605 return;
606
607 if (coords_type == MAP_COORDS)
608 {
609 cursorpos_label->setText( QStringLiteral("%1 %2 (%3)").
610 arg(locale().toString(pos.x(), 'f', 2),
611 locale().toString(-pos.y(), 'f', 2),
612 tr("mm", "millimeters")) );
613 }
614 else
615 {
616 const Georeferencing& georef = view->getMap()->getGeoreferencing();
617 bool ok = true;
618 if (coords_type == PROJECTED_COORDS)
619 {
620 const QPointF projected_point(georef.toProjectedCoords(pos));
621 if (qAbs(georef.getCombinedScaleFactor() - 1.0) < 0.02)
622 {
623 // Grid unit differs less than 2% from meter.
624 cursorpos_label->setText(
625 QStringLiteral("%1 %2 (%3)").
626 arg(QString::number(projected_point.x(), 'f', 0),
627 QString::number(projected_point.y(), 'f', 0),
628 tr("m", "meters"))
629 );
630 }
631 else
632 {
633 cursorpos_label->setText(
634 QStringLiteral("%1 %2").
635 arg(QString::number(projected_point.x(), 'f', 0),
636 QString::number(projected_point.y(), 'f', 0))
637 );
638 }
639 }
640 else if (coords_type == GEOGRAPHIC_COORDS)
641 {
642 const LatLon lat_lon(georef.toGeographicCoords(pos, &ok));
643 cursorpos_label->setText(
644 QString::fromUtf8("%1° %2°").
645 arg(locale().toString(lat_lon.latitude(), 'f', 6),
646 locale().toString(lat_lon.longitude(), 'f', 6))
647 );
648 }
649 else if (coords_type == GEOGRAPHIC_COORDS_DMS)
650 {
651 const LatLon lat_lon(georef.toGeographicCoords(pos, &ok));
652 cursorpos_label->setText(
653 QStringLiteral("%1 %2").
654 arg(georef.degToDMS(lat_lon.latitude()),
655 georef.degToDMS(lat_lon.longitude()))
656 );
657 }
658 else
659 {
660 // shall never happen
661 ok = false;
662 }
663
664 if (!ok)
665 cursorpos_label->setText(tr("Error"));
666 }
667 }
668
getTimeSinceLastInteraction()669 int MapWidget::getTimeSinceLastInteraction()
670 {
671 if (current_pressed_buttons != 0)
672 return 0;
673 else
674 return last_mouse_release_time.msecsTo(QTime::currentTime());
675 }
676
setGPSDisplay(GPSDisplay * gps_display)677 void MapWidget::setGPSDisplay(GPSDisplay* gps_display)
678 {
679 this->gps_display = gps_display;
680 }
681
setTemporaryMarkerDisplay(GPSTemporaryMarkers * marker_display)682 void MapWidget::setTemporaryMarkerDisplay(GPSTemporaryMarkers* marker_display)
683 {
684 this->marker_display = marker_display;
685 }
686
getContextMenu()687 QWidget* MapWidget::getContextMenu()
688 {
689 return context_menu;
690 }
691
sizeHint() const692 QSize MapWidget::sizeHint() const
693 {
694 return QSize(640, 480);
695 }
696
showHelpMessage(QPainter * painter,const QString & text) const697 void MapWidget::showHelpMessage(QPainter* painter, const QString& text) const
698 {
699 painter->fillRect(rect(), QColor(Qt::gray));
700
701 QFont font = painter->font();
702 int pixel_size = font.pixelSize();
703 if (pixel_size > 0)
704 {
705 font.setPixelSize(pixel_size * 2);
706 }
707 else
708 {
709 pixel_size = font.pointSize();
710 font.setPointSize(pixel_size * 2);
711 }
712 font.setBold(true);
713 painter->setFont(font);
714 painter->drawText(QRect(0, 0, width(), height()), Qt::AlignCenter, text);
715 }
716
event(QEvent * event)717 bool MapWidget::event(QEvent* event)
718 {
719 switch (event->type())
720 {
721 case QEvent::Gesture:
722 gestureEvent(static_cast<QGestureEvent*>(event));
723 return event->isAccepted();
724
725 case QEvent::TouchBegin:
726 case QEvent::TouchUpdate:
727 case QEvent::TouchEnd:
728 case QEvent::TouchCancel:
729 if (static_cast<QTouchEvent*>(event)->touchPoints().count() >= 2)
730 return true;
731 break;
732
733 case QEvent::KeyPress:
734 // No focus changing in QWidget::event if Tab is handled by tool.
735 if (static_cast<QKeyEvent*>(event)->key() == Qt::Key_Tab
736 && keyPressEventFilter(static_cast<QKeyEvent*>(event)))
737 return true;
738 break;
739
740 default:
741 ; // nothing
742 }
743
744 return QWidget::event(event);
745 }
746
gestureEvent(QGestureEvent * event)747 void MapWidget::gestureEvent(QGestureEvent* event)
748 {
749 if (tool && tool->gestureEvent(event, this))
750 {
751 event->accept();
752 return;
753 }
754
755 if (QGesture* gesture = event->gesture(Qt::PinchGesture))
756 {
757 QPinchGesture* pinch = static_cast<QPinchGesture *>(gesture);
758 QPoint center = pinch->centerPoint().toPoint();
759 qreal factor = pinch->totalScaleFactor();
760 switch (pinch->state())
761 {
762 case Qt::GestureStarted:
763 if (dragging)
764 cancelDragging();
765 if (pinching)
766 cancelPinching();
767 if (tool)
768 tool->gestureStarted();
769 factor = startPinching(center);
770 pinch->setTotalScaleFactor(factor);
771 break;
772 case Qt::GestureUpdated:
773 updatePinching(center, factor);
774 break;
775 case Qt::GestureFinished:
776 finishPinching(center, factor);
777 break;
778 case Qt::GestureCanceled:
779 cancelPinching();
780 break;
781 default:
782 Q_UNREACHABLE(); // unknown gesture state
783 }
784 event->accept();
785 }
786 else
787 {
788 event->ignore();
789 }
790 }
791
paintEvent(QPaintEvent * event)792 void MapWidget::paintEvent(QPaintEvent* event)
793 {
794 // Draw on the widget
795 QPainter painter(this);
796 QRect exposed = event->rect();
797
798 if (!view)
799 {
800 painter.fillRect(exposed, QColor(Qt::gray));
801 return;
802 }
803
804 // No colors, symbols, or objects? Provide a little help message ...
805 bool no_contents = view->getMap()->getNumObjects() == 0 && view->getMap()->getNumTemplates() == 0 && !view->isGridVisible();
806
807 QTransform transform = painter.worldTransform();
808
809 // Update all dirty caches
810 // TODO: It would be an idea to do these updates in a background thread and use the old caches in the meantime
811 updateAllDirtyCaches();
812
813 QRect target = exposed;
814 if (pinching)
815 {
816 // Just draw the scaled map and templates
817 painter.fillRect(exposed, QColor(Qt::gray));
818 painter.translate(pinching_center.x(), pinching_center.y());
819 painter.scale(pinching_factor, pinching_factor);
820 painter.translate(-drag_start_pos.x(), -drag_start_pos.y());
821 }
822 else if (pan_offset != QPoint())
823 {
824 // Background color
825 if (pan_offset.x() > 0)
826 painter.fillRect(QRect(0, pan_offset.y(), pan_offset.x(), height() - pan_offset.y()), QColor(Qt::gray));
827 else if (pan_offset.x() < 0)
828 painter.fillRect(QRect(width() + pan_offset.x(), pan_offset.y(), -pan_offset.x(), height() - pan_offset.y()), QColor(Qt::gray));
829
830 if (pan_offset.y() > 0)
831 painter.fillRect(QRect(0, 0, width(), pan_offset.y()), QColor(Qt::gray));
832 else if (pan_offset.y() < 0)
833 painter.fillRect(QRect(0, height() + pan_offset.y(), width(), -pan_offset.y()), QColor(Qt::gray));
834
835 target.translate(pan_offset);
836 }
837
838 if (!view->areAllTemplatesHidden() && isBelowTemplateVisible() && !below_template_cache.isNull() && view->getMap()->getFirstFrontTemplate() > 0)
839 {
840 painter.drawImage(target, below_template_cache, exposed);
841 }
842 else if (show_help && no_contents)
843 {
844 painter.save();
845 painter.setTransform(transform);
846 if (view->getMap()->getNumColors() == 0)
847 showHelpMessage(&painter, tr("Empty map!\n\nStart by defining some colors:\nSelect Symbols -> Color window to\nopen the color dialog and\ndefine the colors there."));
848 else if (view->getMap()->getNumSymbols() == 0)
849 showHelpMessage(&painter, tr("No symbols!\n\nNow define some symbols:\nRight-click in the symbol bar\nand select \"New symbol\"\nto create one."));
850 else
851 showHelpMessage(&painter, tr("Ready to draw!\n\nStart drawing or load a base map.\nTo load a base map, click\nTemplates -> Open template...") + QLatin1String("\n\n") + tr("Hint: Hold the middle mouse button to drag the map,\nzoom using the mouse wheel, if available."));
852 painter.restore();
853 }
854 else
855 {
856 painter.fillRect(target, Qt::white);
857 }
858
859 const auto map_visibility = view->effectiveMapVisibility();
860 if (!map_cache.isNull() && map_visibility.visible)
861 {
862 qreal saved_opacity = painter.opacity();
863 painter.setOpacity(map_visibility.opacity);
864 painter.drawImage(target, map_cache, exposed);
865 painter.setOpacity(saved_opacity);
866 }
867
868 if (!view->areAllTemplatesHidden() && isAboveTemplateVisible() && !above_template_cache.isNull() && view->getMap()->getNumTemplates() - view->getMap()->getFirstFrontTemplate() > 0)
869 painter.drawImage(target, above_template_cache, exposed);
870
871 //painter.setClipRect(exposed);
872
873 // Show current drawings
874 if (activity_dirty_rect.isValid())
875 activity->draw(&painter, this);
876
877 if (drawing_dirty_rect.isValid())
878 tool->draw(&painter, this);
879
880
881 // Draw temporary GPS marker display
882 if (marker_display)
883 marker_display->paint(&painter);
884
885 // Draw GPS display
886 if (gps_display)
887 gps_display->paint(&painter);
888
889 // Draw touch cursor
890 if (touch_cursor && tool && tool->usesTouchCursor())
891 touch_cursor->paint(&painter);
892
893
894 painter.setWorldTransform(transform, false);
895 }
896
resizeEvent(QResizeEvent * event)897 void MapWidget::resizeEvent(QResizeEvent* event)
898 {
899 map_cache_dirty_rect = rect();
900 below_template_cache_dirty_rect = map_cache_dirty_rect;
901 above_template_cache_dirty_rect = map_cache_dirty_rect;
902
903 if (map_cache.width() < map_cache_dirty_rect.width() ||
904 map_cache.height() < map_cache_dirty_rect.height())
905 {
906 map_cache = QImage();
907 below_template_cache = QImage();
908 above_template_cache = QImage();
909 }
910
911 for (QObject* const child : children())
912 {
913 if (QWidget* child_widget = qobject_cast<ActionGridBar*>(child))
914 {
915 child_widget->resize(event->size().width(), child_widget->sizeHint().height());
916 }
917 else if (QWidget* child_widget = qobject_cast<KeyButtonBar*>(child))
918 {
919 QSize size = child_widget->sizeHint();
920 QRect map_widget_rect = rect();
921 child_widget->setGeometry(
922 qMax(0, qRound(map_widget_rect.center().x() - 0.5f * size.width())),
923 qMax(0, map_widget_rect.bottom() - size.height()),
924 qMin(size.width(), map_widget_rect.width()),
925 qMin(size.height(), map_widget_rect.height()) );
926 }
927 }
928
929 QWidget::resizeEvent(event);
930 }
931
mousePressEvent(QMouseEvent * event)932 void MapWidget::mousePressEvent(QMouseEvent* event)
933 {
934 current_pressed_buttons = event->buttons();
935 if (touch_cursor && tool && tool->usesTouchCursor())
936 {
937 touch_cursor->mousePressEvent(event);
938 if (event->type() == QEvent::MouseMove)
939 {
940 _mouseMoveEvent(event);
941 return;
942 }
943 }
944 _mousePressEvent(event);
945 }
946
_mousePressEvent(QMouseEvent * event)947 void MapWidget::_mousePressEvent(QMouseEvent* event)
948 {
949 if (dragging || pinching)
950 {
951 event->accept();
952 return;
953 }
954
955 if (tool && tool->mousePressEvent(event, view->viewToMapF(viewportToView(event->pos())), this))
956 {
957 event->accept();
958 return;
959 }
960
961 if (event->button() == Qt::MiddleButton)
962 {
963 startDragging(event->pos());
964 event->accept();
965 }
966 else if (event->button() == Qt::RightButton)
967 {
968 if (!context_menu->isEmpty())
969 context_menu->popup(event->globalPos());
970 }
971 }
972
mouseMoveEvent(QMouseEvent * event)973 void MapWidget::mouseMoveEvent(QMouseEvent* event)
974 {
975 if (touch_cursor && tool && tool->usesTouchCursor())
976 {
977 if (!touch_cursor->mouseMoveEvent(event))
978 return;
979 }
980 _mouseMoveEvent(event);
981 }
982
_mouseMoveEvent(QMouseEvent * event)983 void MapWidget::_mouseMoveEvent(QMouseEvent* event)
984 {
985 if (pinching)
986 {
987 event->accept();
988 return;
989 }
990 else if (dragging)
991 {
992 updateDragging(event->pos());
993 return;
994 }
995 else
996 {
997 updateCursorposLabel(view->viewToMapF(viewportToView(event->pos())));
998 }
999
1000 if (tool && tool->mouseMoveEvent(event, view->viewToMapF(viewportToView(event->pos())), this))
1001 {
1002 event->accept();
1003 return;
1004 }
1005 }
1006
mouseReleaseEvent(QMouseEvent * event)1007 void MapWidget::mouseReleaseEvent(QMouseEvent* event)
1008 {
1009 current_pressed_buttons = event->buttons();
1010 last_mouse_release_time = QTime::currentTime();
1011 if (touch_cursor && tool && tool->usesTouchCursor())
1012 {
1013 if (!touch_cursor->mouseReleaseEvent(event))
1014 return;
1015 }
1016 _mouseReleaseEvent(event);
1017 }
1018
_mouseReleaseEvent(QMouseEvent * event)1019 void MapWidget::_mouseReleaseEvent(QMouseEvent* event)
1020 {
1021 if (dragging)
1022 {
1023 finishDragging(event->pos());
1024 event->accept();
1025 return;
1026 }
1027
1028 if (tool && tool->mouseReleaseEvent(event, view->viewToMapF(viewportToView(event->pos())), this))
1029 {
1030 event->accept();
1031 return;
1032 }
1033 }
1034
mouseDoubleClickEvent(QMouseEvent * event)1035 void MapWidget::mouseDoubleClickEvent(QMouseEvent* event)
1036 {
1037 if (touch_cursor && tool && tool->usesTouchCursor())
1038 {
1039 if (!touch_cursor->mouseDoubleClickEvent(event))
1040 return;
1041 }
1042 _mouseDoubleClickEvent(event);
1043 }
1044
_mouseDoubleClickEvent(QMouseEvent * event)1045 void MapWidget::_mouseDoubleClickEvent(QMouseEvent* event)
1046 {
1047 if (tool && tool->mouseDoubleClickEvent(event, view->viewToMapF(viewportToView(event->pos())), this))
1048 {
1049 event->accept();
1050 return;
1051 }
1052
1053 QWidget::mouseDoubleClickEvent(event);
1054 }
1055
wheelEvent(QWheelEvent * event)1056 void MapWidget::wheelEvent(QWheelEvent* event)
1057 {
1058 if (event->orientation() == Qt::Vertical)
1059 {
1060 if (view)
1061 {
1062 auto degrees = event->delta() / 8.0;
1063 auto num_steps = degrees / 15.0;
1064 auto cursor_pos_view = viewportToView(event->pos());
1065 bool preserve_cursor_pos = (event->modifiers() & Qt::ControlModifier) == 0;
1066 if (num_steps < 0 && !Settings::getInstance().getSettingCached(Settings::MapEditor_ZoomOutAwayFromCursor).toBool())
1067 preserve_cursor_pos = !preserve_cursor_pos;
1068 if (preserve_cursor_pos)
1069 {
1070 view->zoomSteps(num_steps, cursor_pos_view);
1071 }
1072 else
1073 {
1074 view->zoomSteps(num_steps);
1075 updateCursorposLabel(view->viewToMapF(cursor_pos_view));
1076 }
1077
1078 // Send a mouse move event to the current tool as zooming out can move the mouse position on the map
1079 if (tool)
1080 {
1081 QMouseEvent mouse_event{ QEvent::HoverMove, event->pos(), Qt::NoButton, QApplication::mouseButtons(), Qt::NoModifier };
1082 tool->mouseMoveEvent(&mouse_event, view->viewToMapF(cursor_pos_view), this);
1083 }
1084 }
1085
1086 event->accept();
1087 }
1088 else
1089 event->ignore();
1090 }
1091
leaveEvent(QEvent * event)1092 void MapWidget::leaveEvent(QEvent* event)
1093 {
1094 if (tool)
1095 tool->leaveEvent(event);
1096 }
1097
keyPressEventFilter(QKeyEvent * event)1098 bool MapWidget::keyPressEventFilter(QKeyEvent* event)
1099 {
1100 if (tool && tool->keyPressEvent(event))
1101 {
1102 return true;
1103 }
1104
1105 switch (event->key())
1106 {
1107 case Qt::Key_F6:
1108 if (dragging)
1109 finishDragging(mapFromGlobal(QCursor::pos()));
1110 else
1111 startDragging(mapFromGlobal(QCursor::pos()));
1112 return true;
1113
1114 case Qt::Key_Up:
1115 moveMap(0, -1);
1116 return true;
1117
1118 case Qt::Key_Down:
1119 moveMap(0, 1);
1120 return true;
1121
1122 case Qt::Key_Left:
1123 moveMap(-1, 0);
1124 return true;
1125
1126 case Qt::Key_Right:
1127 moveMap(1, 0);
1128 return true;
1129
1130 default:
1131 return false;
1132 }
1133 }
1134
keyReleaseEventFilter(QKeyEvent * event)1135 bool MapWidget::keyReleaseEventFilter(QKeyEvent* event)
1136 {
1137 if (tool && tool->keyReleaseEvent(event))
1138 {
1139 return true; // NOLINT
1140 }
1141
1142 return false;
1143 }
1144
inputMethodQuery(Qt::InputMethodQuery property) const1145 QVariant MapWidget::inputMethodQuery(Qt::InputMethodQuery property) const
1146 {
1147 return inputMethodQuery(property, {});
1148 }
1149
inputMethodQuery(Qt::InputMethodQuery property,const QVariant & argument) const1150 QVariant MapWidget::inputMethodQuery(Qt::InputMethodQuery property, const QVariant& argument) const
1151 {
1152 QVariant result;
1153 if (tool)
1154 result = tool->inputMethodQuery(property, argument);
1155 if (!result.isValid())
1156 result = QWidget::inputMethodQuery(property);
1157 return result;
1158 }
1159
inputMethodEvent(QInputMethodEvent * event)1160 void MapWidget::inputMethodEvent(QInputMethodEvent* event)
1161 {
1162 if (tool)
1163 tool->inputMethodEvent(event);
1164 }
1165
enableTouchCursor(bool enabled)1166 void MapWidget::enableTouchCursor(bool enabled)
1167 {
1168 if (enabled && !touch_cursor)
1169 {
1170 touch_cursor.reset(new TouchCursor(this));
1171 }
1172 else if (!enabled && touch_cursor)
1173 {
1174 touch_cursor->updateMapWidget(false);
1175 touch_cursor.reset(nullptr);
1176 }
1177 }
1178
focusOutEvent(QFocusEvent * event)1179 void MapWidget::focusOutEvent(QFocusEvent* event)
1180 {
1181 if (tool)
1182 tool->focusOutEvent(event);
1183 QWidget::focusOutEvent(event);
1184 }
1185
contextMenuEvent(QContextMenuEvent * event)1186 void MapWidget::contextMenuEvent(QContextMenuEvent* event)
1187 {
1188 if (event->reason() == QContextMenuEvent::Mouse)
1189 {
1190 // HACK: Ignore context menu events caused by the mouse, because right click
1191 // events need to be sent to the current tool first.
1192 event->ignore();
1193 return;
1194 }
1195
1196 if (!context_menu->isEmpty())
1197 context_menu->popup(event->globalPos());
1198
1199 event->accept();
1200 }
1201
containsVisibleTemplate(int first_template,int last_template) const1202 bool MapWidget::containsVisibleTemplate(int first_template, int last_template) const
1203 {
1204 if (first_template > last_template)
1205 return false; // no template visible
1206
1207 Map* map = view->getMap();
1208 for (int i = first_template; i <= last_template; ++i)
1209 {
1210 if (view->isTemplateVisible(map->getTemplate(i)))
1211 return true;
1212 }
1213
1214 return false;
1215 }
1216
1217 inline
isAboveTemplateVisible() const1218 bool MapWidget::isAboveTemplateVisible() const
1219 {
1220 return containsVisibleTemplate(view->getMap()->getFirstFrontTemplate(), view->getMap()->getNumTemplates() - 1);
1221 }
1222
1223 inline
isBelowTemplateVisible() const1224 bool MapWidget::isBelowTemplateVisible() const
1225 {
1226 return containsVisibleTemplate(0, view->getMap()->getFirstFrontTemplate() - 1);
1227 }
1228
updateTemplateCache(QImage & cache,QRect & dirty_rect,int first_template,int last_template,bool use_background)1229 void MapWidget::updateTemplateCache(QImage& cache, QRect& dirty_rect, int first_template, int last_template, bool use_background)
1230 {
1231 Q_ASSERT(containsVisibleTemplate(first_template, last_template));
1232
1233 if (cache.isNull())
1234 {
1235 // Lazy allocation of cache image
1236 cache = QImage(size(), QImage::Format_ARGB32_Premultiplied);
1237 dirty_rect = rect();
1238 }
1239 else
1240 {
1241 // Make sure not to use a bigger draw rect than necessary
1242 dirty_rect = dirty_rect.intersected(rect());
1243 }
1244
1245 // Start drawing
1246 QPainter painter(&cache);
1247 painter.setClipRect(dirty_rect);
1248
1249 // Fill with background color (TODO: make configurable)
1250 if (use_background)
1251 painter.fillRect(dirty_rect, Qt::white);
1252 else
1253 {
1254 QPainter::CompositionMode mode = painter.compositionMode();
1255 painter.setCompositionMode(QPainter::CompositionMode_Clear);
1256 painter.fillRect(dirty_rect, Qt::transparent);
1257 painter.setCompositionMode(mode);
1258 }
1259
1260 // Draw templates
1261 painter.translate(width() / 2.0, height() / 2.0);
1262 painter.setWorldTransform(view->worldTransform(), true);
1263
1264 Map* map = view->getMap();
1265 QRectF map_view_rect = view->calculateViewedRect(viewportToView(dirty_rect));
1266
1267 map->drawTemplates(&painter, map_view_rect, first_template, last_template, view, true);
1268
1269 dirty_rect.setWidth(-1); // => !dirty_rect.isValid()
1270 }
1271
updateMapCache(bool use_background)1272 void MapWidget::updateMapCache(bool use_background)
1273 {
1274 if (map_cache.isNull())
1275 {
1276 // Lazy allocation of cache image
1277 map_cache = QImage(size(), QImage::Format_ARGB32_Premultiplied);
1278 map_cache_dirty_rect = rect();
1279 }
1280 else
1281 {
1282 // Make sure not to use a bigger draw rect than necessary
1283 map_cache_dirty_rect = map_cache_dirty_rect.intersected(rect());
1284 }
1285
1286 // Start drawing
1287 QPainter painter;
1288 painter.begin(&map_cache);
1289 painter.setClipRect(map_cache_dirty_rect);
1290
1291 // Fill with background color (TODO: make configurable)
1292 if (use_background)
1293 {
1294 painter.fillRect(map_cache_dirty_rect, Qt::white);
1295 }
1296 else
1297 {
1298 QPainter::CompositionMode mode = painter.compositionMode();
1299 painter.setCompositionMode(QPainter::CompositionMode_Clear);
1300 painter.fillRect(map_cache_dirty_rect, Qt::transparent);
1301 painter.setCompositionMode(mode);
1302 }
1303
1304 RenderConfig::Options options(RenderConfig::Screen | RenderConfig::HelperSymbols);
1305 bool use_antialiasing = force_antialiasing || Settings::getInstance().getSettingCached(Settings::MapDisplay_Antialiasing).toBool();
1306 if (use_antialiasing)
1307 painter.setRenderHint(QPainter::Antialiasing);
1308 else
1309 options |= RenderConfig::DisableAntialiasing | RenderConfig::ForceMinSize;
1310
1311 Map* map = view->getMap();
1312 QRectF map_view_rect = view->calculateViewedRect(viewportToView(map_cache_dirty_rect));
1313
1314 RenderConfig config = { *map, map_view_rect, view->calculateFinalZoomFactor(), options, 1.0 };
1315
1316 painter.translate(width() / 2.0, height() / 2.0);
1317 painter.setWorldTransform(view->worldTransform(), true);
1318 #ifndef Q_OS_ANDROID
1319 if (view->isOverprintingSimulationEnabled())
1320 map->drawOverprintingSimulation(&painter, config);
1321 else
1322 #endif
1323 map->draw(&painter, config);
1324
1325 if (view->isGridVisible())
1326 map->drawGrid(&painter, map_view_rect);
1327
1328 // Finish drawing
1329 painter.end();
1330
1331 map_cache_dirty_rect.setWidth(-1); // => !map_cache_dirty_rect.isValid()
1332 }
1333
updateAllDirtyCaches()1334 void MapWidget::updateAllDirtyCaches()
1335 {
1336 if (map_cache_dirty_rect.isValid())
1337 updateMapCache(false);
1338
1339 if (!view->areAllTemplatesHidden())
1340 {
1341 if (below_template_cache_dirty_rect.isValid() && isBelowTemplateVisible())
1342 updateTemplateCache(below_template_cache, below_template_cache_dirty_rect, 0, view->getMap()->getFirstFrontTemplate() - 1, true);
1343
1344 if (above_template_cache_dirty_rect.isValid() && isAboveTemplateVisible())
1345 updateTemplateCache(above_template_cache, above_template_cache_dirty_rect, view->getMap()->getFirstFrontTemplate(), view->getMap()->getNumTemplates() - 1, false);
1346 }
1347 }
1348
shiftCache(int sx,int sy,QImage & cache)1349 void MapWidget::shiftCache(int sx, int sy, QImage& cache)
1350 {
1351 if (!cache.isNull())
1352 {
1353 QImage new_cache(cache.size(), cache.format());
1354 QPainter painter(&new_cache);
1355 painter.setCompositionMode(QPainter::CompositionMode_Source);
1356 painter.drawImage(sx, sy, cache);
1357 painter.end();
1358 cache = new_cache;
1359 }
1360 }
1361
shiftCache(int sx,int sy,QPixmap & cache)1362 void MapWidget::shiftCache(int sx, int sy, QPixmap& cache)
1363 {
1364 if (!cache.isNull())
1365 {
1366 cache.scroll(sx, sy, cache.rect());
1367 }
1368 }
1369
1370
1371 } // namespace OpenOrienteering
1372