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