1 /*
2  *    Copyright 2012-2014 Thomas Schöps
3  *    Copyright 2013-2020 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.h"
23 
24 #include <algorithm>
25 #include <cmath>
26 #include <iterator>
27 #include <memory>
28 #include <type_traits>
29 
30 #include <Qt>
31 #include <QtGlobal>
32 #include <QtMath>
33 #include <QByteArray>
34 #include <QDebug>
35 #include <QIODevice>
36 #include <QPaintEngine>
37 #include <QPainter>
38 #include <QPoint>
39 #include <QPointF>
40 #include <QTimer>
41 #include <QTranslator>
42 
43 #include "core/georeferencing.h"
44 #include "core/map_color.h"
45 #include "core/map_coord.h"
46 #include "core/map_grid.h"
47 #include "core/map_part.h"
48 #include "core/map_printer.h"
49 #include "core/map_view.h"
50 #include "core/objects/object.h"
51 #include "core/objects/object_operations.h"
52 #include "core/renderables/renderable.h"
53 #include "core/symbols/combined_symbol.h"
54 #include "core/symbols/line_symbol.h"
55 #include "core/symbols/point_symbol.h"
56 #include "core/symbols/symbol.h"
57 #include "core/symbols/text_symbol.h"
58 #include "fileformats/file_format_registry.h"
59 #include "fileformats/file_import_export.h"
60 #include "fileformats/xml_file_format_p.h"
61 #include "gui/map/map_widget.h"
62 #include "templates/template.h"
63 #include "undo/map_part_undo.h"
64 #include "undo/object_undo.h"
65 #include "undo/undo.h"
66 #include "undo/undo_manager.h"
67 #include "util/util.h"
68 #include "util/transformation.h"
69 
70 // IWYU pragma: no_forward_declare QRectF
71 
72 
73 #ifdef __clang_analyzer__
74 #define singleShot(A, B, C) singleShot(A, B, #C) // NOLINT
75 #endif
76 
77 
78 namespace OpenOrienteering {
79 
80 QPointer<QTranslator> map_symbol_translator{};
81 
82 
83 
84 namespace {
85 
86 // ### Misc ###
87 
88 /** A record of information about the mapping of a color in a source MapColorSet
89  *  to a color in a destination MapColorSet.
90  */
91 struct MapColorSetMergeItem
92 {
93 	MapColor* src_color;
94 	MapColor* dest_color;
95 	std::size_t dest_index;
96 	std::size_t lower_bound;
97 	std::size_t upper_bound;
98 	int lower_errors;
99 	int upper_errors;
100 	bool filter;
101 
MapColorSetMergeItemOpenOrienteering::__anon19cacafe0111::MapColorSetMergeItem102 	MapColorSetMergeItem()
103 	 : src_color(nullptr),
104 	   dest_color(nullptr),
105 	   dest_index(0),
106 	   lower_bound(0),
107 	   upper_bound(0),
108 	   lower_errors(0),
109 	   upper_errors(0),
110 	   filter(false)
111 	{ }
112 };
113 
114 /** The mapping of all colors in a source MapColorSet
115  *  to colors in a destination MapColorSet. */
116 typedef std::vector<MapColorSetMergeItem> MapColorSetMergeList;
117 
118 
119 }  // namespace
120 
121 
122 
123 // ### MapColorSet ###
124 
MapColorSet()125 Map::MapColorSet::MapColorSet()
126 {
127 	// nothing
128 }
129 
~MapColorSet()130 Map::MapColorSet::~MapColorSet()
131 {
132 	for (MapColor* color : colors)
133 		delete color;
134 }
135 
insert(int pos,MapColor * color)136 void Map::MapColorSet::insert(int pos, MapColor* color)
137 {
138 	colors.insert(colors.begin() + pos, color);
139 	adjustColorPriorities(pos + 1, colors.size());
140 }
141 
erase(int pos)142 void Map::MapColorSet::erase(int pos)
143 {
144 	colors.erase(colors.begin() + pos);
145 	adjustColorPriorities(pos, colors.size());
146 }
147 
adjustColorPriorities(int first,int last)148 void Map::MapColorSet::adjustColorPriorities(int first, int last)
149 {
150 	// TODO: delete or update RenderStates with these colors
151 	for (int i = first; i < last; ++i)
152 		colors[i]->setPriority(i);
153 }
154 
155 // This algorithm tries to maintain the relative order of colors.
importSet(const Map::MapColorSet & other,std::vector<bool> * filter,Map * map)156 MapColorMap Map::MapColorSet::importSet(const Map::MapColorSet& other, std::vector< bool >* filter, Map* map)
157 {
158 	MapColorMap out_pointermap;
159 
160 	// Determine number of colors to import
161 	std::size_t import_count = other.colors.size();
162 	if (filter)
163 	{
164 		Q_ASSERT(filter->size() == other.colors.size());
165 
166 		for (std::size_t i = 0, end = other.colors.size(); i != end; ++i)
167 		{
168 			MapColor* color = other.colors[i];
169 			if (!(*filter)[i] || out_pointermap.contains(color))
170 			{
171 				continue;
172 			}
173 
174 			out_pointermap[color] = nullptr; // temporary used as a flag
175 
176 			// Determine referenced spot colors, and add them to the filter
177 			if (color->getSpotColorMethod() == MapColor::CustomColor)
178 			{
179 				for (const SpotColorComponent& component : color->getComponents())
180 				{
181 					if (!out_pointermap.contains(component.spot_color))
182 					{
183 						// Add this spot color to the filter
184 						int j = 0;
185 						while (other.colors[j] != component.spot_color)
186 							++j;
187 						(*filter)[j] = true;
188 						out_pointermap[component.spot_color] = nullptr;
189 					}
190 				}
191 			}
192 		}
193 		import_count = out_pointermap.size();
194 		out_pointermap.clear();
195 	}
196 
197 	if (import_count > 0)
198 	{
199 		colors.reserve(colors.size() + import_count);
200 
201 		// The conflict resolution algorithm below is simplified by setting
202 		// iterator `selected_item` to a real list element which is not related
203 		// to the actual color sets we are merging. This is the extra element
204 		// identified as `end_of_merge_list`.
205 		MapColorSetMergeList merge_list{other.colors.size() + 1};
206 		const auto end_of_merge_list = end(merge_list) - 1;
207 
208 		bool priorities_changed = false;
209 
210 		// Initialize merge_list
211 		auto merge_list_item = merge_list.begin();
212 		for (std::size_t i = 0; i < other.colors.size(); ++i)
213 		{
214 			merge_list_item->filter = (!filter || (*filter)[i]);
215 
216 			MapColor* src_color = other.colors[i];
217 			merge_list_item->src_color = src_color;
218 			for (std::size_t k = 0, colors_size = colors.size(); k < colors_size; ++k)
219 			{
220 				if (colors[k]->equals(*src_color))
221 				{
222 					merge_list_item->dest_color = colors[k];
223 					merge_list_item->dest_index = k;
224 					out_pointermap[src_color] = colors[k];
225 					// Prefer a matching color at the same priority,
226 					// so just abort early if priority matches
227 					if (merge_list_item->dest_color->getPriority() == merge_list_item->src_color->getPriority())
228 						break;
229 				}
230 			}
231 			++merge_list_item;
232 		}
233 		Q_ASSERT(merge_list_item == end_of_merge_list);
234 
235 		size_t iteration_number = 1;
236 		while (true)
237 		{
238 			// Evaluate bounds and conflicting order of colors
239 			int max_conflict_reduction = 0;
240 			auto selected_item = end_of_merge_list;  // Note: non-const copy of an iterator
241 			for (merge_list_item = begin(merge_list); merge_list_item != end_of_merge_list; ++merge_list_item)
242 			{
243 				// Check all lower colors for a higher dest_index
244 				std::size_t& lower_bound(merge_list_item->lower_bound);
245 				lower_bound = merge_list_item->dest_color ? merge_list_item->dest_index : 0;
246 				auto it = merge_list.begin();
247 				for (; it != merge_list_item; ++it)
248 				{
249 					if (it->dest_color)
250 					{
251 						if (it->dest_index > lower_bound)
252 						{
253 							lower_bound = it->dest_index;
254 						}
255 						if (merge_list_item->dest_color && merge_list_item->dest_index < it->dest_index)
256 						{
257 							++merge_list_item->lower_errors;
258 						}
259 					}
260 				}
261 
262 				// Check all higher colors for a lower dest_index
263 				std::size_t& upper_bound(merge_list_item->upper_bound);
264 				upper_bound = merge_list_item->dest_color ? merge_list_item->dest_index : colors.size();
265 				for (++it; it != end_of_merge_list; ++it)
266 				{
267 					if (it->dest_color)
268 					{
269 						if (it->dest_index < upper_bound)
270 						{
271 							upper_bound = it->dest_index;
272 						}
273 						if (merge_list_item->dest_color && merge_list_item->dest_index > it->dest_index)
274 						{
275 							++merge_list_item->upper_errors;
276 						}
277 					}
278 				}
279 
280 				if (merge_list_item->filter)
281 				{
282 					if (merge_list_item->lower_errors == 0 && merge_list_item->upper_errors > max_conflict_reduction)
283 					{
284 						int conflict_reduction = merge_list_item->upper_errors;
285 						// Check new conflicts with insertion index: selected_item->upper_bound
286 						for (it = merge_list.begin(); it != merge_list_item; ++it)
287 						{
288 							if (it->dest_color && selected_item->upper_bound <= it->dest_index)
289 								--conflict_reduction;
290 						}
291 						// Also allow = here to make two-step resolves work
292 						if (conflict_reduction >= max_conflict_reduction)
293 						{
294 							selected_item = merge_list_item;
295 							max_conflict_reduction = conflict_reduction;
296 						}
297 					}
298 					else if (merge_list_item->upper_errors == 0 && merge_list_item->lower_errors > max_conflict_reduction)
299 					{
300 						int conflict_reduction = merge_list_item->lower_errors;
301 						// Check new conflicts with insertion index: (selected_item->lower_bound+1)
302 						it = merge_list_item;
303 						for (++it; it != end_of_merge_list; ++it)
304 						{
305 							if (it->dest_color && (selected_item->lower_bound+1) > it->dest_index)
306 								--conflict_reduction;
307 						}
308 						// Also allow = here to make two-step resolves work
309 						if (conflict_reduction >= max_conflict_reduction)
310 						{
311 							selected_item = merge_list_item;
312 							max_conflict_reduction = conflict_reduction;
313 						}
314 					}
315 				}
316 			}
317 
318 			// Abort if no conflicts or maximum iteration count reached.
319 			// The latter condition is just to prevent endless loops in
320 			// case of bugs and should not occur theoretically.
321 			if (selected_item == end_of_merge_list
322 			    || iteration_number > merge_list.size())
323 				break;
324 
325 			// Solve selected conflict item
326 			auto new_color = new MapColor(*selected_item->dest_color);
327 			selected_item->dest_color = new_color;
328 			out_pointermap[selected_item->src_color] = new_color;
329 			std::size_t insertion_index = (selected_item->lower_errors == 0) ? selected_item->upper_bound : (selected_item->lower_bound+1);
330 
331 			if (map)
332 				map->addColor(new_color, insertion_index);
333 			else
334 				colors.insert(colors.begin() + insertion_index, new_color);
335 			priorities_changed = true;
336 
337 			for (merge_list_item = merge_list.begin(); merge_list_item != merge_list.end(); ++merge_list_item)
338 			{
339 				merge_list_item->lower_errors = 0;
340 				merge_list_item->upper_errors = 0;
341 				if (merge_list_item->dest_color && merge_list_item->dest_index >= insertion_index)
342 					++merge_list_item->dest_index;
343 			}
344 			selected_item->dest_index = insertion_index;
345 
346 			++iteration_number;
347 		}
348 
349 		merge_list.erase(end_of_merge_list);  // no longer needed
350 
351 		// Some missing colors may be spot color compositions which can be
352 		// resolved to new colors only after all colors have been created.
353 		// That is why we create all missing colors first.
354 		for (auto it = merge_list.rbegin(); it != merge_list.rend(); ++it)
355 		{
356 			if (it->filter && !it->dest_color)
357 			{
358 				it->dest_color = new MapColor(*it->src_color);
359 				out_pointermap[it->src_color] = it->dest_color;
360 			}
361 			else
362 			{
363 				// Existing colors don't need to be touched again.
364 				it->dest_color = nullptr;
365 			}
366 		}
367 
368 		// Now process all new colors for spot color resolution and insertion
369 		for (auto it = merge_list.rbegin(); it != merge_list.rend(); ++it)
370 		{
371 			MapColor* new_color = it->dest_color;
372 			if (new_color)
373 			{
374 				if (new_color->getSpotColorMethod() == MapColor::CustomColor)
375 				{
376 					SpotColorComponents components = new_color->getComponents();
377 					for (SpotColorComponent& component : components)
378 					{
379 						Q_ASSERT(out_pointermap.contains(component.spot_color));
380 						component.spot_color = out_pointermap[component.spot_color];
381 					}
382 					new_color->setSpotColorComposition(components);
383 				}
384 
385 				std::size_t insertion_index = it->upper_bound;
386 				if (map)
387 					map->addColor(new_color, insertion_index);
388 				else
389 					colors.insert(colors.begin() + insertion_index, new_color);
390 				priorities_changed = true;
391 			}
392 		}
393 
394 		if (map && priorities_changed)
395 		{
396 			map->updateAllObjects();
397 		}
398 	}
399 
400 	return out_pointermap;
401 }
402 
403 
404 
405 // ### Map ###
406 
407 bool Map::static_initialized = false;
408 MapColor Map::covering_white(MapColor::CoveringWhite);
409 MapColor Map::covering_red(MapColor::CoveringRed);
410 MapColor Map::undefined_symbol_color(MapColor::Undefined);
411 MapColor Map::registration_color(MapColor::Registration);
412 LineSymbol* Map::covering_white_line;
413 LineSymbol* Map::covering_red_line;
414 LineSymbol* Map::undefined_line;
415 PointSymbol* Map::undefined_point;
416 TextSymbol* Map::undefined_text;
417 CombinedSymbol* Map::covering_combined_line;
418 
Map()419 Map::Map()
420  : color_set()
421  , has_spot_colors(false)
422  , undo_manager(new UndoManager(this))
423  , renderables(new MapRenderables(this))
424  , selection_renderables(new MapRenderables(this))
425  , renderable_options(Symbol::RenderNormal)
426  , printer_config(nullptr)
427 {
428 	if (!static_initialized)
429 		initStatic();
430 
431 	georeferencing.reset(new Georeferencing());
432 	init();
433 
434 	connect(this, &Map::colorAdded, this, &Map::checkSpotColorPresence);
435 	connect(this, &Map::colorChanged, this, &Map::checkSpotColorPresence);
436 	connect(this, &Map::colorDeleted, this, &Map::checkSpotColorPresence);
437 	connect(undo_manager.data(), &UndoManager::cleanChanged, this, &Map::undoCleanChanged);
438 }
439 
~Map()440 Map::~Map()
441 {
442 	clear();  // properly destruct all children
443 }
444 
clear()445 void Map::clear()
446 {
447 	undo_manager->clear();
448 
449 	for (auto temp : templates)
450 		delete temp;
451 	templates.clear();
452 	first_front_template = 0;
453 
454 	for (auto temp : closed_templates)
455 		delete temp;
456 	closed_templates.clear();
457 
458 	object_selection.clear();
459 	first_selected_object = nullptr;
460 	selection_renderables->clear();
461 
462 	renderables->clear();
463 
464 	for (MapPart* part : parts)
465 		delete part;
466 	parts.clear();
467 	current_part_index = 0;
468 
469 	for (auto symbol : symbols)
470 		delete symbol;
471 	symbols.clear();
472 
473 	// Don't clear() color_set: It is shared.
474 }
475 
init()476 void Map::init()
477 {
478 	color_set = new MapColorSet();
479 
480 	parts.push_back(new MapPart(tr("default part"), this));
481 
482 	widgets.clear();
483 
484 	undo_manager->setClean();
485 
486 	symbol_set_id = QString();
487 	map_notes = QString();
488 
489 	printer_config.reset();
490 
491 	image_template_use_meters_per_pixel = true;
492 	image_template_meters_per_pixel = 0;
493 	image_template_dpi = 0;
494 	image_template_scale = 0;
495 
496 	colors_dirty = false;
497 	symbols_dirty = false;
498 	templates_dirty = false;
499 	objects_dirty = false;
500 	other_dirty = false;
501 	unsaved_changes = false;
502 }
503 
reset()504 void Map::reset()
505 {
506 	clear();
507 	init();
508 }
509 
setScaleDenominator(unsigned int value)510 void Map::setScaleDenominator(unsigned int value)
511 {
512 	georeferencing->setScaleDenominator(value);
513 }
514 
getScaleDenominator() const515 unsigned int Map::getScaleDenominator() const
516 {
517 	return georeferencing->getScaleDenominator();
518 }
519 
changeScale(unsigned int new_scale_denominator,double additional_stretch,const MapCoord & scaling_center,bool scale_symbols,bool scale_objects,bool scale_georeferencing,bool scale_templates)520 void Map::changeScale(unsigned int new_scale_denominator, double additional_stretch, const MapCoord& scaling_center, bool scale_symbols, bool scale_objects, bool scale_georeferencing, bool scale_templates)
521 {
522 	if (new_scale_denominator == getScaleDenominator() && additional_stretch == 1.0)
523 		return;
524 
525 	double factor = (getScaleDenominator() / (double)new_scale_denominator) * additional_stretch;
526 
527 	if (scale_symbols)
528 		scaleAllSymbols(factor);
529 	if (scale_objects)
530 	{
531 		undo_manager->clear();
532 		scaleAllObjects(factor, scaling_center);
533 		if (hasPrinterConfig())
534 		{
535 			auto print_area = printer_config->print_area;
536 			auto center = QPointF(scaling_center);
537 			print_area.setTopLeft(center + factor * (print_area.topLeft() - center));
538 			print_area.setBottomRight(center + factor * (print_area.bottomRight() - center));
539 			printer_config->print_area = print_area;
540 		}
541 	}
542 	if (scale_georeferencing)
543 		georeferencing->setMapRefPoint(scaling_center + factor * (georeferencing->getMapRefPoint() - scaling_center));
544 	if (scale_templates)
545 	{
546 		for (int i = 0; i < getNumTemplates(); ++i)
547 		{
548 			Template* temp = getTemplate(i);
549 			if (temp->isTemplateGeoreferenced())
550 				continue;
551 			setTemplateAreaDirty(i);
552 			temp->scale(factor, scaling_center);
553 			setTemplateAreaDirty(i);
554 		}
555 		for (int i = 0; i < getNumClosedTemplates(); ++i)
556 		{
557 			Template* temp = getClosedTemplate(i);
558 			if (temp->isTemplateGeoreferenced())
559 				continue;
560 			temp->scale(factor, scaling_center);
561 		}
562 	}
563 
564 	setScaleDenominator(new_scale_denominator);
565 	setOtherDirty();
566 	updateAllMapWidgets();
567 }
568 
rotateMap(double rotation,const MapCoord & center,bool adjust_georeferencing,bool adjust_declination,bool adjust_templates)569 void Map::rotateMap(double rotation, const MapCoord& center, bool adjust_georeferencing, bool adjust_declination, bool adjust_templates)
570 {
571 	if (std::fmod(rotation, 2 * M_PI) == 0)
572 		return;
573 
574 	undo_manager->clear();
575 	rotateAllObjects(rotation, center);
576 
577 	if (adjust_georeferencing)
578 	{
579 		MapCoordF reference_point = MapCoordF(georeferencing->getMapRefPoint() - center);
580 		reference_point.rotate(-rotation);
581 		georeferencing->setMapRefPoint(MapCoord(MapCoordF(center) + reference_point));
582 	}
583 	if (adjust_declination)
584 	{
585 		auto rotation_degrees = qRadiansToDegrees(rotation);
586 		georeferencing->setDeclination(georeferencing->getDeclination() + rotation_degrees);
587 	}
588 	if (adjust_templates)
589 	{
590 		for (int i = 0; i < getNumTemplates(); ++i)
591 		{
592 			Template* temp = getTemplate(i);
593 			if (temp->isTemplateGeoreferenced())
594 				continue;
595 			setTemplateAreaDirty(i);
596 			temp->rotate(rotation, center);
597 			setTemplateAreaDirty(i);
598 		}
599 		for (int i = 0; i < getNumClosedTemplates(); ++i)
600 		{
601 			Template* temp = getClosedTemplate(i);
602 			if (temp->isTemplateGeoreferenced())
603 				continue;
604 			temp->rotate(rotation, center);
605 		}
606 	}
607 
608 	setOtherDirty();
609 	updateAllMapWidgets();
610 }
611 
setMapNotes(const QString & text)612 void Map::setMapNotes(const QString& text)
613 {
614 	map_notes = text;
615 }
616 
617 
importMap(const Map & imported_map,ImportMode mode,std::vector<bool> * filter,int symbol_insert_pos,bool merge_duplicate_symbols)618 QHash<const Symbol*, Symbol*> Map::importMap(
619         const Map& imported_map,
620         ImportMode mode,
621         std::vector<bool>* filter,
622         int symbol_insert_pos,
623         bool merge_duplicate_symbols)
624 {
625 	QTransform q_transform;
626 	if (mode.testFlag(GeorefImport))
627 	{
628 		/// \todo Test and review import of georeferenced and non-georeferenced maps, in all combinations.
629 		/// \todo Handle rotation of patterns and text, cf. Object::transform.
630 		const auto& georef = getGeoreferencing();
631 		const auto& other_georef = imported_map.getGeoreferencing();
632 		const auto src_origin = MapCoordF { other_georef.getMapRefPoint() };
633 
634 		bool ok0, ok1, ok2;
635 		PassPointList passpoints;
636 		passpoints.resize(3);
637 		passpoints[0].src_coords  = src_origin;
638 		passpoints[0].dest_coords = georef.toMapCoordF(&other_georef, passpoints[0].src_coords, &ok0);
639 		passpoints[1].src_coords  = src_origin + MapCoordF { 128.0, 0.0 }; // 128 mm off horizontally
640 		passpoints[1].dest_coords = georef.toMapCoordF(&other_georef, passpoints[1].src_coords, &ok1);
641 		passpoints[2].src_coords  = src_origin + MapCoordF { 0.0, 128.0 }; // 128 mm off vertically
642 		passpoints[2].dest_coords = georef.toMapCoordF(&other_georef, passpoints[2].src_coords, &ok2);
643 		if (ok0 && ok1 && ok2
644 		    && !passpoints.estimateNonIsometricSimilarityTransform(&q_transform))
645 		{
646 			/// \todo proper error message
647 			qDebug("Failed to calculate transformation");
648 			q_transform.reset();
649 		}
650 	}
651 
652 	return importMap(imported_map, mode & ~GeorefImport, q_transform, filter, symbol_insert_pos, merge_duplicate_symbols);
653 }
654 
655 
loadFrom(const QString & path,MapView * view)656 bool Map::loadFrom(const QString& path, MapView* view)
657 {
658 	auto importer = FileFormats.makeImporter(path, *this, view);
659 	return importer && importer->doImport();
660 }
661 
662 
importMap(const Map & imported_map,ImportMode mode,const QTransform & transform,std::vector<bool> * filter,int symbol_insert_pos,bool merge_duplicate_symbols)663 QHash<const Symbol*, Symbol*> Map::importMap(
664         const Map& imported_map,
665         ImportMode mode,
666         const QTransform& transform,
667         std::vector<bool>* filter,
668         int symbol_insert_pos,
669         bool merge_duplicate_symbols)
670 {
671 	if (imported_map.getScaleDenominator() != getScaleDenominator())
672 		qWarning("Map::importMap() called for different map scale");
673 
674 	if (mode.testFlag(GeorefImport))
675 		qWarning("Map::importMap() called with GeorefImport flag set");
676 
677 	// Determine which symbols and colors to import
678 	std::vector<bool> color_filter(std::size_t(imported_map.getNumColors()), true);
679 	std::vector<bool> symbol_filter(std::size_t(imported_map.getNumSymbols()), true);
680 	if (mode.testFlag(MinimalImport))
681 	{
682 		switch (mode & 0x0f)
683 		{
684 		case ObjectImport:
685 			if (imported_map.getNumObjects() > 0)
686 				imported_map.determineSymbolsInUse(symbol_filter);
687 			imported_map.determineColorsInUse(symbol_filter, color_filter);
688 			break;
689 		case SymbolImport:
690 			if (filter)
691 			{
692 				Q_ASSERT(filter->size() == symbol_filter.size());
693 				symbol_filter = *filter;
694 				imported_map.determineSymbolUseClosure(symbol_filter);
695 			}
696 			imported_map.determineColorsInUse(symbol_filter, color_filter);
697 			break;
698 		case ColorImport:
699 			if (filter)
700 			{
701 				Q_ASSERT(filter->size() == color_filter.size());
702 				color_filter = *filter;
703 			}
704 			break;
705 		default:
706 			Q_UNREACHABLE();
707 		}
708 	}
709 
710 	// Import colors
711 	auto color_map = color_set->importSet(*imported_map.color_set, &color_filter, this);
712 
713 	QHash<const Symbol*, Symbol*> symbol_map;
714 	if ((mode & 0x0f) != ColorImport)
715 	{
716 		if (imported_map.getNumSymbols() > 0)
717 		{
718 			// Import symbols
719 			symbol_map = importSymbols(imported_map, color_map, symbol_insert_pos, merge_duplicate_symbols, symbol_filter);
720 		}
721 
722 		if ((mode & 0x0f) != SymbolImport
723 		    && imported_map.getNumObjects() > 0)
724 		{
725 			// Import parts like this:
726 			//  - if the other map has only one part, import it into the current part
727 			//  - else check if there is already a part with an equal name for every part to import and import into this part if found, else create a new part
728 			auto* undo_step = new CombinedUndoStep(this);
729 			for (const auto* part_to_import : imported_map.parts)
730 			{
731 				MapPart* dest_part = nullptr;
732 				if (imported_map.parts.size() == 1)
733 				{
734 					dest_part = getCurrentPart();
735 				}
736 				else
737 				{
738 					for (auto* check_part : parts)
739 					{
740 						if (check_part->getName().compare(part_to_import->getName(), Qt::CaseInsensitive) == 0)
741 						{
742 							dest_part = check_part;
743 							break;
744 						}
745 					}
746 					if (!dest_part)
747 					{
748 						// Import as new part
749 						dest_part = new MapPart(part_to_import->getName(), this);
750 						addPart(dest_part, 0);
751 						undo_step->push(new MapPartUndoStep(this, MapPartUndoStep::RemoveMapPart, 0));
752 					}
753 				}
754 
755 				// Temporarily switch the current part for importing so the undo step gets created for the right part
756 				MapPart* temp_current_part = getCurrentPart();
757 				current_part_index = std::size_t(findPartIndex(dest_part));
758 
759 				bool select_and_center_objects = dest_part == temp_current_part;
760 				if (auto import_undo = dest_part->importPart(part_to_import, symbol_map, transform, select_and_center_objects))
761 				{
762 					undo_step->push(import_undo.release());
763 					if (select_and_center_objects)
764 						ensureVisibilityOfSelectedObjects(Map::FullVisibility);
765 				}
766 
767 				current_part_index = std::size_t(findPartIndex(temp_current_part));
768 			}
769 			push(undo_step);
770 		}
771 	}
772 
773 	return symbol_map;
774 }
775 
776 
777 
exportToIODevice(QIODevice & device) const778 bool Map::exportToIODevice(QIODevice& device) const
779 {
780 	XMLFileExporter exporter({}, this, nullptr);
781 	exporter.setDevice(&device);
782 	auto success = exporter.doExport();
783 	device.close();
784 	return success;
785 }
786 
787 
importFromIODevice(QIODevice & device)788 bool Map::importFromIODevice(QIODevice& device)
789 {
790 	XMLFileImporter importer {{}, this, nullptr};
791 	importer.setDevice(&device);
792 	auto success = importer.doImport();
793 	device.close();
794 	return success;
795 }
796 
797 
798 
draw(QPainter * painter,const RenderConfig & config)799 void Map::draw(QPainter* painter, const RenderConfig& config)
800 {
801 	// Update the renderables of all objects marked as dirty
802 	updateObjects();
803 
804 	// The actual drawing
805 	renderables->draw(painter, config);
806 }
807 
drawOverprintingSimulation(QPainter * painter,const RenderConfig & config)808 void Map::drawOverprintingSimulation(QPainter* painter, const RenderConfig& config)
809 {
810 	// Update the renderables of all objects marked as dirty
811 	updateObjects();
812 
813 	// The actual drawing
814 	renderables->drawOverprintingSimulation(painter, config);
815 }
816 
drawColorSeparation(QPainter * painter,const RenderConfig & config,const MapColor * spot_color,bool use_color)817 void Map::drawColorSeparation(QPainter* painter, const RenderConfig& config, const MapColor* spot_color, bool use_color)
818 {
819 	// Update the renderables of all objects marked as dirty
820 	updateObjects();
821 
822 	// The actual drawing
823 	renderables->drawColorSeparation(painter, config, spot_color, use_color);
824 }
825 
drawGrid(QPainter * painter,const QRectF & bounding_box)826 void Map::drawGrid(QPainter* painter, const QRectF& bounding_box)
827 {
828 	grid.draw(painter, bounding_box, this);
829 }
830 
drawTemplates(QPainter * painter,const QRectF & bounding_box,int first_template,int last_template,const MapView * view,bool on_screen) const831 void Map::drawTemplates(QPainter* painter, const QRectF& bounding_box, int first_template, int last_template, const MapView* view, bool on_screen) const
832 {
833 	for (int i = first_template; i <= last_template; ++i)
834 	{
835 		const Template* temp = getTemplate(i);
836 		if (temp->getTemplateState() != Template::Loaded)
837 			continue;
838 
839 		double scale  = std::max(temp->getTemplateScaleX(), temp->getTemplateScaleY());
840 		auto visibility = TemplateVisibility{ 1, true };
841 		if (view)
842 		{
843 			visibility = view->getTemplateVisibility(temp);
844 			visibility.visible &= visibility.opacity > 0;
845 			scale *= view->getZoom();
846 		}
847 		if (visibility.visible)
848 		{
849 			Q_ASSERT(visibility.opacity == 1 || painter->paintEngine()->hasFeature(QPaintEngine::ConstantOpacity));
850 			painter->save();
851 			temp->drawTemplate(painter, bounding_box, scale, on_screen, visibility.opacity);
852 			painter->restore();
853 		}
854 	}
855 }
856 
updateObjects()857 void Map::updateObjects()
858 {
859 	// TODO: It maybe would be better if the objects entered themselves into a separate list when they get dirty so not all objects have to be traversed here
860 	applyOnAllObjects(&Object::update);
861 }
862 
removeRenderablesOfObject(const Object * object,bool mark_area_as_dirty)863 void Map::removeRenderablesOfObject(const Object* object, bool mark_area_as_dirty)
864 {
865 	renderables->removeRenderablesOfObject(object, mark_area_as_dirty);
866 	if (isObjectSelected(object))
867 		removeSelectionRenderables(object);
868 }
insertRenderablesOfObject(const Object * object)869 void Map::insertRenderablesOfObject(const Object* object)
870 {
871 	renderables->insertRenderablesOfObject(object);
872 	if (isObjectSelected(object))
873 		addSelectionRenderables(object);
874 }
875 
876 
markAsIrregular(Object * object)877 void Map::markAsIrregular(Object* object)
878 {
879 	irregular_objects.insert(object);
880 }
881 
irregularObjects() const882 const std::set<Object*> Map::irregularObjects() const
883 {
884 	return irregular_objects;
885 }
886 
deleteIrregularObjects()887 std::size_t Map::deleteIrregularObjects()
888 {
889 	std::size_t result = 0;
890 	std::set<Object*> unhandled;
891 	for (auto object : irregular_objects)
892 	{
893 		for (auto part : parts)
894 		{
895 			if (part->deleteObject(object))
896 			{
897 				++result;
898 				goto next_object;
899 			}
900 		}
901 		unhandled.insert(object);
902 next_object:
903 		; // nothing else
904 	}
905 
906 	irregular_objects.swap(unhandled);
907 	return result;
908 }
909 
910 
getSelectionToSymbolCompatibility(const Symbol * symbol,bool & out_compatible,bool & out_different) const911 void Map::getSelectionToSymbolCompatibility(const Symbol* symbol, bool& out_compatible, bool& out_different) const
912 {
913 	out_compatible = symbol && !object_selection.empty();
914 	out_different = false;
915 
916 	if (symbol)
917 	{
918 		for (const Object* object : object_selection)
919 		{
920 			if (!symbol->isTypeCompatibleTo(object))
921 			{
922 				out_compatible = false;
923 				out_different = true;
924 				return;
925 			}
926 			else if (symbol != object->getSymbol())
927 				out_different = true;
928 		}
929 	}
930 }
931 
deleteSelectedObjects()932 void Map::deleteSelectedObjects()
933 {
934 	auto obj = selectedObjectsBegin();
935 	auto end = selectedObjectsEnd();
936 	if (obj != end)
937 	{
938 		// FIXME: this is not ready for multiple map parts.
939 		auto undo_step = new AddObjectsUndoStep(this);
940 		MapPart* part = getCurrentPart();
941 
942 		for (; obj != end; ++obj)
943 		{
944 			int index = part->findObjectIndex(*obj);
945 			if (index >= 0)
946 			{
947 				undo_step->addObject(index, *obj);
948 				part->releaseObject(index);
949 			}
950 			else
951 			{
952 				qDebug() << this << "::deleteSelectedObjects(): Object" << *obj << "not found in current map part.";
953 			}
954 		}
955 
956 		setObjectsDirty();
957 		clearObjectSelection(true);
958 		push(undo_step);
959 	}
960 }
961 
includeSelectionRect(QRectF & rect) const962 void Map::includeSelectionRect(QRectF& rect) const
963 {
964 	for (const Object* object : object_selection)
965 		rectIncludeSafe(rect, object->getExtent());
966 }
967 
drawSelection(QPainter * painter,bool force_min_size,MapWidget * widget,MapRenderables * replacement_renderables,bool draw_normal)968 void Map::drawSelection(QPainter* painter, bool force_min_size, MapWidget* widget, MapRenderables* replacement_renderables, bool draw_normal)
969 {
970 	MapView* view = widget->getMapView();
971 
972 	painter->save();
973 	painter->translate(widget->width() / 2.0 + view->panOffset().x(), widget->height() / 2.0 + view->panOffset().y());
974 	painter->setWorldTransform(view->worldTransform(), true);
975 
976 	if (!replacement_renderables)
977 		replacement_renderables = selection_renderables.data();
978 
979 	RenderConfig::Options options = RenderConfig::Screen | RenderConfig::HelperSymbols;
980 	qreal selection_opacity = 1.0;
981 	if (force_min_size)
982 		options |= RenderConfig::ForceMinSize;
983 	if (!draw_normal)
984 	{
985 		options |= RenderConfig::Highlighted;
986 		selection_opacity = 0.4;
987 	}
988 	RenderConfig config = { *this, view->calculateViewedRect(widget->viewportToView(widget->rect())), view->calculateFinalZoomFactor(), options, selection_opacity };
989 	replacement_renderables->draw(painter, config);
990 
991 	painter->restore();
992 }
993 
addObjectToSelection(Object * object,bool emit_selection_changed)994 void Map::addObjectToSelection(Object* object, bool emit_selection_changed)
995 {
996 	Q_ASSERT(!isObjectSelected(object));
997 
998 	// we omit hidden and protected objects from any kind of selection
999 	const auto* object_symbol = object->getSymbol();
1000 	if (object_symbol->isProtected() || object_symbol->isHidden())
1001 		return;
1002 
1003 	object_selection.insert(object);
1004 	addSelectionRenderables(object);
1005 	if (!first_selected_object)
1006 		first_selected_object = object;
1007 	if (emit_selection_changed)
1008 		emit objectSelectionChanged();
1009 }
1010 
removeObjectFromSelection(Object * object,bool emit_selection_changed)1011 void Map::removeObjectFromSelection(Object* object, bool emit_selection_changed)
1012 {
1013 	bool removed = object_selection.erase(object);
1014 	Q_ASSERT(removed && "Map::removeObjectFromSelection: object was not selected!");
1015 	Q_UNUSED(removed);
1016 	removeSelectionRenderables(object);
1017 	if (first_selected_object == object)
1018 		first_selected_object = object_selection.empty() ? nullptr : *object_selection.begin();
1019 	if (emit_selection_changed)
1020 		emit objectSelectionChanged();
1021 }
1022 
removeSymbolFromSelection(const Symbol * symbol,bool emit_selection_changed)1023 bool Map::removeSymbolFromSelection(const Symbol* symbol, bool emit_selection_changed)
1024 {
1025 	bool removed_at_least_one_object = false;
1026 	auto it_end = object_selection.end();
1027 	for (auto it = object_selection.begin(); it != it_end; )
1028 	{
1029 		if ((*it)->getSymbol() != symbol)
1030 		{
1031 			++it;
1032 			continue;
1033 		}
1034 
1035 		removed_at_least_one_object = true;
1036 		removeSelectionRenderables(*it);
1037 		Object* removed_object = *it;
1038 		it = object_selection.erase(it);
1039 		if (first_selected_object == removed_object)
1040 			first_selected_object = object_selection.empty() ? nullptr : *object_selection.begin();
1041 	}
1042 	if (emit_selection_changed && removed_at_least_one_object)
1043 		emit objectSelectionChanged();
1044 	return removed_at_least_one_object;
1045 }
1046 
isObjectSelected(const Object * object) const1047 bool Map::isObjectSelected(const Object* object) const
1048 {
1049 	return object_selection.find(const_cast<Object*>(object)) != object_selection.end();
1050 }
1051 
toggleObjectSelection(Object * object,bool emit_selection_changed)1052 bool Map::toggleObjectSelection(Object* object, bool emit_selection_changed)
1053 {
1054 	if (isObjectSelected(object))
1055 	{
1056 		removeObjectFromSelection(object, emit_selection_changed);
1057 		return false;
1058 	}
1059 	else
1060 	{
1061 		addObjectToSelection(object, emit_selection_changed);
1062 		return true;
1063 	}
1064 }
1065 
clearObjectSelection(bool emit_selection_changed)1066 void Map::clearObjectSelection(bool emit_selection_changed)
1067 {
1068 	selection_renderables->clear();
1069 	object_selection.clear();
1070 	first_selected_object = nullptr;
1071 
1072 	if (emit_selection_changed)
1073 		emit objectSelectionChanged();
1074 }
1075 
emitSelectionChanged()1076 void Map::emitSelectionChanged()
1077 {
1078 	emit objectSelectionChanged();
1079 }
1080 
emitSelectionEdited()1081 void Map::emitSelectionEdited()
1082 {
1083 	emit selectedObjectEdited();
1084 }
1085 
addMapWidget(MapWidget * widget)1086 void Map::addMapWidget(MapWidget* widget)
1087 {
1088 	widgets.push_back(widget);
1089 }
1090 
removeMapWidget(MapWidget * widget)1091 void Map::removeMapWidget(MapWidget* widget)
1092 {
1093 	widgets.erase(std::remove(begin(widgets), end(widgets), widget), end(widgets));
1094 }
1095 
1096 
updateAllMapWidgets()1097 void Map::updateAllMapWidgets()
1098 {
1099 	for (MapWidget* widget : widgets)
1100 		widget->updateEverything();
1101 }
1102 
1103 
ensureVisibilityOfSelectedObjects(SelectionVisibility visibility)1104 void Map::ensureVisibilityOfSelectedObjects(SelectionVisibility visibility)
1105 {
1106 	if (!object_selection.empty())
1107 	{
1108 		QRectF rect;
1109 		includeSelectionRect(rect);
1110 
1111 		for (MapWidget* widget : widgets)
1112 		{
1113 			switch (visibility)
1114 			{
1115 			case FullVisibility:
1116 				widget->ensureVisibilityOfRect(rect, MapWidget::DiscreteZoom);
1117 				break;
1118 
1119 			case PartialVisibility:
1120 				if (!widget->getMapView()->calculateViewedRect(widget->viewportToView(widget->geometry())).intersects(rect))
1121 					widget->ensureVisibilityOfRect(rect, MapWidget::DiscreteZoom);
1122 				break;
1123 
1124 			case IgnoreVisibilty:
1125 				break; // Do nothing
1126 
1127 			default:
1128 				Q_UNREACHABLE();
1129 			}
1130 		}
1131 	}
1132 }
1133 
1134 
setDrawingBoundingBox(const QRectF & map_coords_rect,int pixel_border,bool do_update)1135 void Map::setDrawingBoundingBox(const QRectF& map_coords_rect, int pixel_border, bool do_update)
1136 {
1137 	for (MapWidget* widget : widgets)
1138 		widget->setDrawingBoundingBox(map_coords_rect, pixel_border, do_update);
1139 }
1140 
clearDrawingBoundingBox()1141 void Map::clearDrawingBoundingBox()
1142 {
1143 	for (MapWidget* widget : widgets)
1144 		widget->clearDrawingBoundingBox();
1145 }
1146 
1147 
setActivityBoundingBox(const QRectF & map_coords_rect,int pixel_border,bool do_update)1148 void Map::setActivityBoundingBox(const QRectF& map_coords_rect, int pixel_border, bool do_update)
1149 {
1150 	for (MapWidget* widget : widgets)
1151 		widget->setActivityBoundingBox(map_coords_rect, pixel_border, do_update);
1152 }
1153 
clearActivityBoundingBox()1154 void Map::clearActivityBoundingBox()
1155 {
1156 	for (MapWidget* widget : widgets)
1157 		widget->clearActivityBoundingBox();
1158 }
1159 
1160 
updateDrawing(const QRectF & map_coords_rect,int pixel_border)1161 void Map::updateDrawing(const QRectF& map_coords_rect, int pixel_border)
1162 {
1163 	for (MapWidget* widget : widgets)
1164 		widget->updateDrawing(map_coords_rect, pixel_border);
1165 }
1166 
1167 
1168 
translate(const QString & symbol_text) const1169 QString Map::translate(const QString& symbol_text) const
1170 {
1171 	auto result = raw_translation(symbol_text);
1172 	if (result.isEmpty())
1173 		result = symbol_text;
1174 	return result;
1175 }
1176 
raw_translation(const QString & symbol_text) const1177 QString Map::raw_translation(const QString& symbol_text) const
1178 {
1179 	auto result = QString{};
1180 	if (map_symbol_translator)
1181 		result = map_symbol_translator->translate(symbol_set_id.toUtf8(), symbol_text.toUtf8());
1182 	return result;
1183 }
1184 
1185 
1186 
setColor(MapColor * color,int pos)1187 void Map::setColor(MapColor* color, int pos)
1188 {
1189 	// MapColor* old_color = color_set->colors[pos];
1190 
1191 	color_set->colors[pos] = color;
1192 	color->setPriority(pos);
1193 
1194 	if (color->getSpotColorMethod() == MapColor::SpotColor)
1195 	{
1196 		// Update dependent colors
1197 		for (MapColor* map_color : color_set->colors)
1198 		{
1199 			if (map_color->getSpotColorMethod() == MapColor::CustomColor)
1200 			{
1201 				for (const SpotColorComponent& component : map_color->getComponents())
1202 				{
1203 					if (component.spot_color == color)
1204 					{
1205 						// Assuming each spot color is rarely used more than once per composition
1206 						if (map_color->getCmykColorMethod() == MapColor::SpotColor)
1207 							map_color->setCmykFromSpotColors();
1208 						if (map_color->getRgbColorMethod() == MapColor::SpotColor)
1209 							map_color->setRgbFromSpotColors();
1210 						updateSymbolIcons(map_color);
1211 						emit colorChanged(map_color->getPriority(), map_color);
1212 					}
1213 				}
1214 			}
1215 		}
1216 	}
1217 	else
1218 	{
1219 		// Remove from dependent colors
1220 		for (MapColor* map_color : color_set->colors)
1221 		{
1222 			if (map_color->getSpotColorMethod() == MapColor::CustomColor
1223 			    && map_color->removeSpotColorComponent(color))
1224 			{
1225 				updateSymbolIcons(map_color);
1226 				emit colorChanged(map_color->getPriority(), map_color);
1227 			}
1228 		}
1229 	}
1230 
1231 	updateSymbolIcons(color);
1232 	emit colorChanged(pos, color);
1233 }
1234 
addColor(MapColor * color,int pos)1235 void Map::addColor(MapColor* color, int pos)
1236 {
1237 	color_set->insert(pos, color);
1238 	if (getNumColors() == 1)
1239 	{
1240 		// This is the first color - the help text in the map widget(s) should be updated
1241 		updateAllMapWidgets();
1242 	}
1243 	setColorsDirty();
1244 	emit colorAdded(pos, color);
1245 	color->setPriority(pos);
1246 }
1247 
deleteColor(int pos)1248 void Map::deleteColor(int pos)
1249 {
1250 	MapColor* color = color_set->colors[pos];
1251 	if (color->getSpotColorMethod() == MapColor::SpotColor)
1252 	{
1253 		// Update dependent colors
1254 		for (MapColor* map_color : color_set->colors)
1255 		{
1256 			if (map_color->removeSpotColorComponent(color))
1257 			{
1258 				updateSymbolIcons(map_color);
1259 				emit colorChanged(map_color->getPriority(), map_color);
1260 			}
1261 		}
1262 	}
1263 
1264 	color_set->erase(pos);
1265 
1266 	if (getNumColors() == 0)
1267 	{
1268 		// That was the last color - the help text in the map widget(s) should be updated
1269 		updateAllMapWidgets();
1270 	}
1271 
1272 	// Treat combined symbols first before their parts
1273 	for (Symbol* symbol : symbols)
1274 	{
1275 		if (symbol->getType() == Symbol::Combined)
1276 			symbol->colorDeletedEvent(color);
1277 	}
1278 	for (Symbol* symbol : symbols)
1279 	{
1280 		if (symbol->getType() != Symbol::Combined)
1281 			symbol->colorDeletedEvent(color);
1282 	}
1283 	emit colorDeleted(pos, color);
1284 
1285 	delete color;
1286 }
1287 
findColorIndex(const MapColor * color) const1288 int Map::findColorIndex(const MapColor* color) const
1289 {
1290 	std::size_t size = color_set->colors.size();
1291 	for (std::size_t i = 0; i < size; ++i)
1292 	{
1293 		if (color_set->colors[i] == color)
1294 			return (int)i;
1295 	}
1296 	if (color && color->getPriority() == MapColor::Registration)
1297 	{
1298 		return MapColor::Registration;
1299 	}
1300 	return -1;
1301 }
1302 
setColorsDirty()1303 void Map::setColorsDirty()
1304 {
1305 	colors_dirty = true;
1306 	setHasUnsavedChanges(true);
1307 }
1308 
useColorsFrom(Map * map)1309 void Map::useColorsFrom(Map* map)
1310 {
1311 	color_set = map->color_set;
1312 }
1313 
isColorUsedByASymbol(const MapColor * color) const1314 bool Map::isColorUsedByASymbol(const MapColor* color) const
1315 {
1316 	for (const Symbol* symbol : symbols)
1317 	{
1318 		if (symbol->containsColor(color))
1319 			return true;
1320 	}
1321 	return false;
1322 }
1323 
determineColorsInUse(const std::vector<bool> & by_which_symbols,std::vector<bool> & out) const1324 void Map::determineColorsInUse(const std::vector< bool >& by_which_symbols, std::vector< bool >& out) const
1325 {
1326 	if (getNumSymbols() == 0)
1327 	{
1328 		out.clear();
1329 		return;
1330 	}
1331 
1332 	Q_ASSERT(int(by_which_symbols.size()) == getNumSymbols());
1333 	out.assign(std::size_t(getNumColors()), false);
1334 	for (std::size_t c = 0, last = std::size_t(getNumColors()); c != last; ++c)
1335 	{
1336 		for (std::size_t s = 0, last_s = std::size_t(getNumSymbols()); s != last_s; ++s)
1337 		{
1338 			if (by_which_symbols[s] && getSymbol(int(s))->containsColor(getColor(int(c))))
1339 			{
1340 				out[c] = true;
1341 				break;
1342 			}
1343 		}
1344 	}
1345 
1346 	// Include required spot colors, too
1347 	for (std::size_t c = 0, last_c = std::size_t(getNumColors()); c != last_c; ++c)
1348 	{
1349 		if (out[c])
1350 			continue;
1351 
1352 		const auto* color = getColor(int(c));
1353 		if (color->getSpotColorMethod() != MapColor::SpotColor)
1354 			continue;
1355 
1356 		for (std::size_t o = 0, last_o = std::size_t(getNumColors()); o != last_o; ++o)
1357 		{
1358 			if (!out[o])
1359 				continue;
1360 
1361 			const auto* other = getColor(int(o));
1362 			if (other->getSpotColorMethod() != MapColor::CustomColor)
1363 				continue;
1364 
1365 			const auto& components = other->getComponents();
1366 			if (std::any_of(begin(components), end(components), [color](auto& component) {
1367 			    return component.spot_color == color;
1368 			}))
1369 			{
1370 				out[c] = true;
1371 				break;
1372 			}
1373 		}
1374 	}
1375 }
1376 
checkSpotColorPresence()1377 void Map::checkSpotColorPresence()
1378 {
1379 	const bool has_spot_colors = hasSpotColors();
1380 	if (this->has_spot_colors != has_spot_colors)
1381 	{
1382 		this->has_spot_colors = has_spot_colors;
1383 		emit spotColorPresenceChanged(has_spot_colors);
1384 	}
1385 }
1386 
hasSpotColors() const1387 bool Map::hasSpotColors() const
1388 {
1389 	for (const MapColor* color : color_set->colors)
1390 	{
1391 		if (color->getSpotColorMethod() == MapColor::SpotColor)
1392 			return true;
1393 	}
1394 	return false;
1395 }
1396 
hasAlpha() const1397 bool Map::hasAlpha() const
1398 {
1399 	return std::any_of(begin(color_set->colors), end(color_set->colors), [this](const MapColor* color) {
1400 		const auto opacity = color->getOpacity();
1401 		return opacity > 0 && opacity < 1
1402 		       && std::any_of(begin(symbols), end(symbols), [this, color](const Symbol* symbol) {
1403 			return !symbol->isHidden()
1404 			       && symbol->containsColor(color)
1405 			       && this->existsObjectWithSymbol(symbol);
1406 		});
1407 	});
1408 }
1409 
1410 
setSymbolSetId(const QString & id)1411 void Map::setSymbolSetId(const QString& id)
1412 {
1413 	symbol_set_id = id;
1414 	symbols_dirty = true;
1415 }
1416 
1417 
importSymbols(const Map & other,const MapColorMap & color_map,int insert_pos,bool merge_duplicates,const std::vector<bool> & filter)1418 QHash<const Symbol*, Symbol*> Map::importSymbols(
1419         const Map& other,
1420         const MapColorMap& color_map,
1421         int insert_pos,
1422         bool merge_duplicates,
1423         const std::vector<bool>& filter )
1424 {
1425 	QHash<const Symbol*, Symbol*> out_pointermap;
1426 
1427 	std::vector<Symbol*> created_symbols;
1428 	created_symbols.reserve(other.symbols.size());
1429 	for (std::size_t i = 0, last = other.symbols.size(); i < last; ++i)
1430 	{
1431 		if (filter.empty() || filter[i])
1432 		{
1433 			const Symbol* symbol = other.symbols[i];
1434 			if (merge_duplicates)
1435 			{
1436 				// Check if symbol is already present
1437 				auto match = std::find_if(begin(symbols), end(symbols), [symbol](auto s) {
1438 					return s->equals(symbol, Qt::CaseInsensitive);
1439 				});
1440 				if (match != end(symbols))
1441 				{
1442 					// Symbol is already present
1443 					out_pointermap.insert(symbol, *match);
1444 					continue;
1445 				}
1446 			}
1447 
1448 			auto new_symbol = duplicate(*symbol).release();  /// \todo Leverage unique_ptr
1449 			new_symbol->replaceColors(color_map);
1450 			out_pointermap.insert(symbol, new_symbol);
1451 			created_symbols.push_back(new_symbol);
1452 		}
1453 	}
1454 
1455 
1456 	// Add the created symbols
1457 	if (insert_pos < 0)
1458 		insert_pos = getNumSymbols();
1459 	for (auto* symbol : created_symbols)
1460 	{
1461 		addSymbol(symbol, insert_pos);
1462 		++insert_pos;
1463 	}
1464 
1465 	// Notify the created symbols of the new context (mind combined symbols)
1466 	for (auto* symbol : created_symbols)
1467 	{
1468 		for (auto it = out_pointermap.constBegin(); it != out_pointermap.constEnd(); ++it)
1469 		{
1470 			// symbol is what was created by duplicate() above.
1471 			symbol->symbolChangedEvent(it.key(), it.value());
1472 		}
1473 	}
1474 
1475 	return out_pointermap;
1476 }
1477 
1478 
1479 
addSelectionRenderables(const Object * object)1480 void Map::addSelectionRenderables(const Object* object)
1481 {
1482 	object->update();
1483 	selection_renderables->insertRenderablesOfObject(object);
1484 }
1485 
updateSelectionRenderables(const Object * object)1486 void Map::updateSelectionRenderables(const Object* object)
1487 {
1488 	removeSelectionRenderables(object);
1489 	addSelectionRenderables(object);
1490 }
1491 
removeSelectionRenderables(const Object * object)1492 void Map::removeSelectionRenderables(const Object* object)
1493 {
1494 	selection_renderables->removeRenderablesOfObject(object, false);
1495 }
1496 
initStatic()1497 void Map::initStatic()
1498 {
1499 	static_initialized = true;
1500 
1501 	covering_white_line = new LineSymbol();
1502 	covering_white_line->setColor(&covering_white);
1503 	covering_white_line->setLineWidth(3.0);
1504 
1505 	covering_red_line = new LineSymbol();
1506 	covering_red_line->setColor(&covering_red);
1507 	covering_red_line->setLineWidth(1.0);
1508 
1509 	covering_combined_line = new CombinedSymbol();
1510 	covering_combined_line->setNumParts(2);
1511 	covering_combined_line->setPart(0, covering_white_line, false);
1512 	covering_combined_line->setPart(1, covering_red_line, false);
1513 
1514 	// Undefined symbols
1515 	undefined_line = new LineSymbol();
1516 	undefined_line->setColor(&undefined_symbol_color);
1517 	undefined_line->setLineWidth(1);
1518 	undefined_line->setIsHelperSymbol(true);
1519 
1520 	undefined_point = new PointSymbol();
1521 	undefined_point->setInnerRadius(100);
1522 	undefined_point->setInnerColor(&undefined_symbol_color);
1523 	undefined_point->setIsHelperSymbol(true);
1524 
1525 	undefined_text = new TextSymbol();
1526 	undefined_text->setColor(&undefined_symbol_color);
1527 	undefined_text->setIsHelperSymbol(true);
1528 }
1529 
addSymbol(Symbol * symbol,int pos)1530 void Map::addSymbol(Symbol* symbol, int pos)
1531 {
1532 	symbols.insert(symbols.begin() + pos, symbol);
1533 	if (symbols.size() == 1)
1534 	{
1535 		// This is the first symbol - the help text in the map widget(s) should be updated
1536 		updateAllMapWidgets();
1537 	}
1538 
1539 	emit symbolAdded(pos, symbol);
1540 	setSymbolsDirty();
1541 }
1542 
moveSymbol(int from,int to)1543 void Map::moveSymbol(int from, int to)
1544 {
1545 	symbols.insert(symbols.begin() + to, symbols[from]);
1546 	if (from > to)
1547 		++from;
1548 	symbols.erase(symbols.begin() + from);
1549 	// TODO: emit symbolChanged(pos, symbol); ?
1550 	setSymbolsDirty();
1551 }
1552 
getSymbol(int i) const1553 const Symbol* Map::getSymbol(int i) const
1554 {
1555 	if (i >= 0)
1556 		return symbols[i];
1557 	else if (i == -1)
1558 		return nullptr;
1559 	else if (i == -2)
1560 		return getUndefinedPoint();
1561 	else if (i == -3)
1562 		return getUndefinedLine();
1563 	else
1564 	{
1565 		Q_ASSERT(!"Invalid symbol index given");
1566 		return getUndefinedLine();
1567 	}
1568 }
1569 
getSymbol(int i)1570 Symbol* Map::getSymbol(int i)
1571 {
1572 	return const_cast<Symbol*>(static_cast<const Map*>(this)->getSymbol(i));
1573 }
1574 
setSymbol(Symbol * symbol,int pos)1575 void Map::setSymbol(Symbol* symbol, int pos)
1576 {
1577 	Symbol* old_symbol = symbols[pos];
1578 
1579 	// Check if an object with this symbol is selected
1580 	bool object_with_symbol_selected = false;
1581 	for (const Object* object : object_selection)
1582 	{
1583 		if (object->getSymbol() == old_symbol || object->getSymbol()->containsSymbol(old_symbol))
1584 		{
1585 			object_with_symbol_selected = true;
1586 			break;
1587 		}
1588 	}
1589 
1590 	changeSymbolForAllObjects(old_symbol, symbol);
1591 
1592 	int size = (int)symbols.size();
1593 	for (int i = 0; i < size; ++i)
1594 	{
1595 		if (i == pos)
1596 			continue;
1597 
1598 		if (symbols[i]->symbolChangedEvent(symbols[pos], symbol))
1599 			updateAllObjectsWithSymbol(symbols[i]);
1600 	}
1601 
1602 	// Change the symbol
1603 	symbols[pos] = symbol;
1604 	emit symbolChanged(pos, symbol, old_symbol);
1605 	setSymbolsDirty();
1606 	delete old_symbol;
1607 
1608 	if (object_with_symbol_selected)
1609 		emit selectedObjectEdited();
1610 }
1611 
deleteSymbol(int pos)1612 void Map::deleteSymbol(int pos)
1613 {
1614 	if (deleteAllObjectsWithSymbol(symbols[pos]))
1615 		undo_manager->clear();
1616 
1617 	int size = (int)symbols.size();
1618 	for (int i = 0; i < size; ++i)
1619 	{
1620 		if (i == pos)
1621 			continue;
1622 
1623 		if (symbols[i]->symbolChangedEvent(symbols[pos], nullptr))
1624 			updateAllObjectsWithSymbol(symbols[i]);
1625 	}
1626 
1627 	// Delete the symbol
1628 	Symbol* temp = symbols[pos];
1629 	delete symbols[pos];
1630 	symbols.erase(symbols.begin() + pos);
1631 
1632 	if (symbols.empty())
1633 	{
1634 		// That was the last symbol - the help text in the map widget(s) should be updated
1635 		updateAllMapWidgets();
1636 	}
1637 
1638 	emit symbolDeleted(pos, temp);
1639 	setSymbolsDirty();
1640 }
1641 
findSymbolIndex(const Symbol * symbol) const1642 int Map::findSymbolIndex(const Symbol* symbol) const
1643 {
1644 	if (!symbol)
1645 		return -1;
1646 	int size = (int)symbols.size();
1647 	for (int i = 0; i < size; ++i)
1648 	{
1649 		if (symbols[i] == symbol)
1650 			return i;
1651 	}
1652 
1653 	if (symbol == undefined_point)
1654 		return -2;
1655 	else if (symbol == undefined_line)
1656 		return -3;
1657 	else if (symbol == undefined_text)
1658 		return -4;
1659 
1660 	// maybe element of point symbol
1661 	return -1;
1662 }
1663 
setSymbolsDirty()1664 void Map::setSymbolsDirty()
1665 {
1666 	if (symbol_icon_scale > 0)
1667 	{
1668 		symbol_icon_scale = 0;
1669 		QTimer::singleShot(0, this, &Map::updateSymbolIconZoom);
1670 	}
1671 	symbols_dirty = true;
1672 	setHasUnsavedChanges(true);
1673 }
1674 
updateSymbolIcons(const MapColor * color)1675 void Map::updateSymbolIcons(const MapColor* color)
1676 {
1677 	for (std::size_t i = 0, size = symbols.size(); i < size; ++i)
1678 	{
1679 		if (symbols[i]->containsColor(color))
1680 		{
1681 			symbols[i]->resetIcon();
1682 			emit symbolIconChanged(i);
1683 		}
1684 	}
1685 }
1686 
scaleAllSymbols(double factor)1687 void Map::scaleAllSymbols(double factor)
1688 {
1689 	int size = getNumSymbols();
1690 	for (int i = 0; i < size; ++i)
1691 	{
1692 		Symbol* symbol = getSymbol(i);
1693 		symbol->scale(factor);
1694 		emit symbolChanged(i, symbol, symbol);
1695 	}
1696 	updateAllObjects();
1697 
1698 	setSymbolsDirty();
1699 }
1700 
determineSymbolsInUse(std::vector<bool> & out) const1701 void Map::determineSymbolsInUse(std::vector< bool >& out) const
1702 {
1703 	out.assign(symbols.size(), false);
1704 	for (auto part : parts)
1705 	{
1706 		for (int o = 0; o < part->getNumObjects(); ++o)
1707 		{
1708 			const Symbol* symbol = part->getObject(o)->getSymbol();
1709 			int index = findSymbolIndex(symbol);
1710 			if (index >= 0)
1711 				out[index] = true;
1712 		}
1713 	}
1714 
1715 	determineSymbolUseClosure(out);
1716 }
1717 
determineSymbolUseClosure(std::vector<bool> & symbol_bitfield) const1718 void Map::determineSymbolUseClosure(std::vector< bool >& symbol_bitfield) const
1719 {
1720 	bool change;
1721 	do
1722 	{
1723 		change = false;
1724 
1725 		for (size_t i = 0, end = symbol_bitfield.size(); i < end; ++i)
1726 		{
1727 			if (symbol_bitfield[i])
1728 				continue;
1729 
1730 			// Check if this symbol is needed by any included symbol
1731 			for (size_t k = 0; k < end; ++k)
1732 			{
1733 				if (i == k)
1734 					continue;
1735 				if (!symbol_bitfield[k])
1736 					continue;
1737 				if (symbols[k]->containsSymbol(symbols[i]))
1738 				{
1739 					symbol_bitfield[i] = true;
1740 					change = true;
1741 					break;
1742 				}
1743 			}
1744 		}
1745 
1746 	} while (change);
1747 }
1748 
1749 
symbolIconZoom() const1750 qreal Map::symbolIconZoom() const
1751 {
1752 	if (symbol_icon_scale <= 0)
1753 		const_cast<Map*>(this)->updateSymbolIconZoom();
1754 
1755 	return symbol_icon_scale;
1756 }
1757 
1758 
updateSymbolIconZoom()1759 void Map::updateSymbolIconZoom()
1760 {
1761 	// A simple heuristics which determines the symbol icon scale from
1762 	// the mean of the line symbol widths.
1763 	auto values = std::vector<qreal>();
1764 	values.reserve(symbols.size());
1765 	for (const auto* symbol : symbols)
1766 	{
1767 		if (symbol->isHelperSymbol())
1768 			continue;
1769 		auto size = symbol->dimensionForIcon();
1770 		if (size > 0)
1771 			values.push_back(size);
1772 	}
1773 	std::sort(begin(values), end(values));
1774 	auto percentile = [](const auto& v, quint8 p) { return v[v.size() * p / 100]; };
1775 
1776 	auto new_scale = qreal(0);
1777 	if (!values.empty())
1778 	{
1779 		// Scale the symbol size at the 80th percentile to 90%.
1780 		new_scale = std::max(qreal(1), 90 / percentile(values, 80));
1781 
1782 		// The symbol size at the 20th percentile shall not get much below 10%.
1783 		auto small_scale = std::max(qreal(1), 10 / percentile(values, 20));
1784 		if (small_scale > new_scale)
1785 			new_scale = (new_scale + small_scale) / 2;
1786 	}
1787 	// Convert from % to factor, and discretize to filter out minor changes.
1788 	new_scale = qreal(0.05) * std::max(1, qRound(new_scale / 5));
1789 
1790 	if (!qFuzzyCompare(new_scale, symbol_icon_scale))
1791 	{
1792 		symbol_icon_scale = new_scale;
1793 		for (auto* symbol : symbols)
1794 			symbol->resetIcon();
1795 		emit symbolIconZoomChanged();
1796 	}
1797 }
1798 
1799 
1800 
setTemplate(Template * temp,int pos)1801 void Map::setTemplate(Template* temp, int pos)
1802 {
1803 	templates[pos] = temp;
1804 	emit templateChanged(pos, templates[pos]);
1805 }
1806 
addTemplate(Template * temp,int pos)1807 void Map::addTemplate(Template* temp, int pos)
1808 {
1809 	templates.insert(templates.begin() + pos, temp);
1810 	if (templates.size() == 1)
1811 	{
1812 		// This is the first template - the help text in the map widget(s) should be updated
1813 		updateAllMapWidgets();
1814 	}
1815 
1816 	emit templateAdded(pos, temp);
1817 }
1818 
removeTemplate(int pos)1819 void Map::removeTemplate(int pos)
1820 {
1821 	auto it = templates.begin() + pos;
1822 	Template* temp = *it;
1823 	templates.erase(it);
1824 	if (templates.empty())
1825 	{
1826 		// That was the last template - the help text in the map widget(s) should maybe be updated (if there are no objects)
1827 		updateAllMapWidgets();
1828 	}
1829 
1830 	emit templateDeleted(pos, temp);
1831 }
1832 
deleteTemplate(int pos)1833 void Map::deleteTemplate(int pos)
1834 {
1835 	Template* temp = getTemplate(pos);
1836 	removeTemplate(pos);
1837 	delete temp;
1838 }
1839 
setTemplateAreaDirty(Template * temp,const QRectF & area,int pixel_border)1840 void Map::setTemplateAreaDirty(Template* temp, const QRectF& area, int pixel_border)
1841 {
1842 	bool front_cache = findTemplateIndex(temp) >= getFirstFrontTemplate();	// TODO: is there a better way to find out if that is a front or back template?
1843 
1844 	for (MapWidget* widget : widgets)
1845 	{
1846 		const MapView* map_view = widget->getMapView();
1847 		if (map_view->isTemplateVisible(temp))
1848 			widget->markTemplateCacheDirty(map_view->calculateViewBoundingBox(area), pixel_border, front_cache);
1849 	}
1850 }
1851 
setTemplateAreaDirty(int i)1852 void Map::setTemplateAreaDirty(int i)
1853 {
1854 	if (i == -1)
1855 		return;	// no assert here as convenience, so setTemplateAreaDirty(-1) can be called without effect for the map part
1856 	Q_ASSERT(i >= 0 && i < (int)templates.size());
1857 
1858 	templates[i]->setTemplateAreaDirty();
1859 }
1860 
findTemplateIndex(const Template * temp) const1861 int Map::findTemplateIndex(const Template* temp) const
1862 {
1863 	int size = (int)templates.size();
1864 	for (int i = 0; i < size; ++i)
1865 	{
1866 		if (templates[i] == temp)
1867 			return i;
1868 	}
1869 	Q_ASSERT(false);
1870 	return -1;
1871 }
1872 
setTemplatesDirty()1873 void Map::setTemplatesDirty()
1874 {
1875 	templates_dirty = true;
1876 	setHasUnsavedChanges(true);
1877 }
1878 
emitTemplateChanged(Template * temp)1879 void Map::emitTemplateChanged(Template* temp)
1880 {
1881 	emit templateChanged(findTemplateIndex(temp), temp);
1882 }
1883 
clearClosedTemplates()1884 void Map::clearClosedTemplates()
1885 {
1886 	if (closed_templates.empty())
1887 		return;
1888 
1889 	for (Template* temp : closed_templates)
1890 		delete temp;
1891 
1892 	closed_templates.clear();
1893 	setTemplatesDirty();
1894 	emit closedTemplateAvailabilityChanged();
1895 }
1896 
closeTemplate(int i)1897 void Map::closeTemplate(int i)
1898 {
1899 	Template* temp = getTemplate(i);
1900 	removeTemplate(i);
1901 
1902 	if (temp->getTemplateState() == Template::Loaded)
1903 		temp->unloadTemplateFile();
1904 
1905 	closed_templates.push_back(temp);
1906 	setTemplatesDirty();
1907 	if (closed_templates.size() == 1)
1908 		emit closedTemplateAvailabilityChanged();
1909 }
1910 
reloadClosedTemplate(int i,int target_pos,QWidget * dialog_parent,const QString & map_path)1911 bool Map::reloadClosedTemplate(int i, int target_pos, QWidget* dialog_parent, const QString& map_path)
1912 {
1913 	Template* temp = closed_templates[i];
1914 
1915 	// Try to find and load the template again
1916 	if (temp->getTemplateState() != Template::Loaded)
1917 	{
1918 		if (!temp->tryToFindAndReloadTemplateFile(map_path))
1919 		{
1920 			if (!temp->execSwitchTemplateFileDialog(dialog_parent))
1921 				return false;
1922 		}
1923 	}
1924 
1925 	// If successfully loaded, add to template list again
1926 	if (temp->getTemplateState() == Template::Loaded)
1927 	{
1928 		closed_templates.erase(closed_templates.begin() + i);
1929 		addTemplate(temp, target_pos);
1930 		temp->setTemplateAreaDirty();
1931 		setTemplatesDirty();
1932 		if (closed_templates.empty())
1933 			emit closedTemplateAvailabilityChanged();
1934 		return true;
1935 	}
1936 	return false;
1937 }
1938 
push(UndoStep * step)1939 void Map::push(UndoStep *step)
1940 {
1941 	undo_manager->push(std::unique_ptr<UndoStep>(step));
1942 }
1943 
1944 
addPart(MapPart * part,std::size_t index)1945 void Map::addPart(MapPart* part, std::size_t index)
1946 {
1947 	Q_ASSERT(index <= parts.size());
1948 
1949 	parts.insert(parts.begin() + index, part);
1950 	if (current_part_index >= index)
1951 		setCurrentPartIndex(current_part_index + 1);
1952 
1953 	emit mapPartAdded(index, part);
1954 
1955 	setOtherDirty();
1956 	updateAllMapWidgets();
1957 }
1958 
removePart(std::size_t index)1959 void Map::removePart(std::size_t index)
1960 {
1961 	Q_ASSERT(index < parts.size());
1962 	Q_ASSERT(parts.size() > 1);
1963 
1964 	if (current_part_index == index)
1965 		// First switch to another part when removing the current part
1966 		setCurrentPartIndex((index == parts.size() - 1) ? (parts.size() - 2) : (index + 1));
1967 
1968 	MapPart* part = parts[index];
1969 
1970 	// FIXME: This loop should move to MapPart.
1971 	while(part->getNumObjects())
1972 		part->deleteObject(0);
1973 
1974 	parts.erase(parts.begin() + index);
1975 	if (current_part_index >= index)
1976 		setCurrentPartIndex((index == parts.size()) ? (parts.size() - 1) : index);
1977 
1978 	emit mapPartDeleted(index, part);
1979 
1980 	delete part;
1981 
1982 	setOtherDirty();
1983 	updateAllMapWidgets();
1984 }
1985 
findPartIndex(const MapPart * part) const1986 int Map::findPartIndex(const MapPart* part) const
1987 {
1988 	std::size_t const size = parts.size();
1989 	for (std::size_t i = 0; i < size; ++i)
1990 	{
1991 		if (parts[i] == part)
1992 			return i;
1993 	}
1994 	Q_ASSERT(false);
1995 	return -1;
1996 }
1997 
setCurrentPartIndex(std::size_t index)1998 void Map::setCurrentPartIndex(std::size_t index)
1999 {
2000 	Q_ASSERT(index < parts.size());
2001 
2002 	MapPart* const old_part = parts[current_part_index];
2003 	if (index != current_part_index)
2004 	{
2005 		current_part_index = index;
2006 		emit currentMapPartIndexChanged(index);
2007 	}
2008 
2009 	MapPart* const new_part = parts[current_part_index];
2010 	if (new_part != old_part)
2011 	{
2012 		clearObjectSelection(true);
2013 		emit currentMapPartChanged(new_part);
2014 	}
2015 }
2016 
reassignObjectsToMapPart(std::vector<int>::const_iterator first,std::vector<int>::const_iterator last,std::size_t source,std::size_t destination)2017 int Map::reassignObjectsToMapPart(std::vector<int>::const_iterator first, std::vector<int>::const_iterator last, std::size_t source, std::size_t destination)
2018 {
2019 	Q_ASSERT(source < parts.size());
2020 	Q_ASSERT(destination < parts.size());
2021 
2022 	MapPart* const source_part = parts[source];
2023 	MapPart* const target_part = parts[destination];
2024 	auto first_object = target_part->getNumObjects();
2025 	auto selection_size = getNumSelectedObjects();
2026 	for (auto it = first; it != last; ++it)
2027 	{
2028 		Q_ASSERT(*it < source_part->getNumObjects());
2029 		Object* const object = source_part->getObject(*it);
2030 
2031 		if (current_part_index == source && isObjectSelected(object))
2032 			removeObjectFromSelection(object, false);
2033 
2034 		source_part->releaseObject(object);
2035 		target_part->addObject(object);
2036 	}
2037 
2038 	setOtherDirty();
2039 
2040 	if (getNumSelectedObjects() != selection_size)
2041 		emit objectSelectionChanged();
2042 
2043 	return first_object;
2044 }
2045 
mergeParts(std::size_t source,std::size_t destination)2046 int Map::mergeParts(std::size_t source, std::size_t destination)
2047 {
2048 	Q_ASSERT(source < parts.size());
2049 	Q_ASSERT(destination < parts.size());
2050 
2051 	int count = 0;
2052 	MapPart* const source_part = parts[source];
2053 	MapPart* const target_part = parts[destination];
2054 	// Preserve order (but not efficient)
2055 	for (auto i = source_part->getNumObjects(); i > 0 ; --i)
2056 	{
2057 		Object* object = source_part->getObject(0);
2058 		source_part->releaseObject(0);
2059 
2060 		int index = target_part->getNumObjects();
2061 		target_part->addObject(object, index);
2062 
2063 		++count;
2064 	}
2065 
2066 	if (current_part_index == source)
2067 		setCurrentPartIndex(destination);
2068 
2069 	if (destination != source)
2070 		removePart(source);
2071 
2072 	return target_part->getNumObjects() - count;
2073 }
2074 
2075 
getNumObjects() const2076 int Map::getNumObjects() const
2077 {
2078 	int num_objects = 0;
2079 	for (const MapPart* part : parts)
2080 		num_objects += part->getNumObjects();
2081 	return num_objects;
2082 }
2083 
addObject(Object * object,int part_index)2084 int Map::addObject(Object* object, int part_index)
2085 {
2086 	MapPart* part = parts[(part_index < 0) ? current_part_index : part_index];
2087 	int object_index = part->getNumObjects();
2088 	part->addObject(object, object_index);
2089 
2090 	return object_index;
2091 }
2092 
deleteObject(Object * object)2093 void Map::deleteObject(Object* object)
2094 {
2095 	delete releaseObject(object);
2096 }
2097 
releaseObject(Object * object)2098 Object* Map::releaseObject(Object* object)
2099 {
2100 	for (MapPart* part : parts)
2101 	{
2102 		if (auto object_found = part->releaseObject(object))
2103 			return object_found;
2104 	}
2105 
2106 	qCritical().nospace() << this << "::deleteObject(" << object << "): Object not found. This is a bug.";
2107 
2108 	return nullptr;
2109 }
2110 
setObjectsDirty()2111 void Map::setObjectsDirty()
2112 {
2113 	objects_dirty = true;
2114 	setHasUnsavedChanges(true);
2115 }
2116 
calculateExtent(bool include_helper_symbols,bool include_templates,const MapView * view) const2117 QRectF Map::calculateExtent(bool include_helper_symbols, bool include_templates, const MapView* view) const
2118 {
2119 	QRectF rect;
2120 
2121 	// Objects
2122 	for (const MapPart* part : parts)
2123 		rectIncludeSafe(rect, part->calculateExtent(include_helper_symbols));
2124 
2125 	// Templates
2126 	if (include_templates)
2127 	{
2128 		for (const Template* temp : templates)
2129 		{
2130 			if (view && !view->isTemplateVisible(temp))
2131 				continue;
2132 			if (temp->getTemplateState() != Template::Loaded)
2133 				continue;
2134 
2135 			rectIncludeSafe(rect, temp->calculateTemplateBoundingBox());
2136 		}
2137 	}
2138 
2139 	return rect;
2140 }
2141 
setObjectAreaDirty(const QRectF & map_coords_rect)2142 void Map::setObjectAreaDirty(const QRectF& map_coords_rect)
2143 {
2144 	for (MapWidget* widget : widgets)
2145 		widget->markObjectAreaDirty(map_coords_rect);
2146 }
2147 
findObjectsAt(const MapCoordF & coord,qreal tolerance,bool treat_areas_as_paths,bool extended_selection,bool include_hidden_objects,bool include_protected_objects,SelectionInfoVector & out) const2148 void Map::findObjectsAt(
2149         const MapCoordF& coord,
2150         qreal tolerance,
2151         bool treat_areas_as_paths,
2152         bool extended_selection,
2153         bool include_hidden_objects,
2154         bool include_protected_objects,
2155         SelectionInfoVector& out ) const
2156 {
2157 	getCurrentPart()->findObjectsAt(coord, tolerance, treat_areas_as_paths, extended_selection, include_hidden_objects, include_protected_objects, out);
2158 }
2159 
findAllObjectsAt(const MapCoordF & coord,qreal tolerance,bool treat_areas_as_paths,bool extended_selection,bool include_hidden_objects,bool include_protected_objects,SelectionInfoVector & out) const2160 void Map::findAllObjectsAt(
2161         const MapCoordF& coord,
2162         qreal tolerance,
2163         bool treat_areas_as_paths,
2164         bool extended_selection,
2165         bool include_hidden_objects,
2166         bool include_protected_objects,
2167         SelectionInfoVector& out ) const
2168 {
2169 	for (const MapPart* part : parts)
2170 		part->findObjectsAt(coord, tolerance, treat_areas_as_paths, extended_selection, include_hidden_objects, include_protected_objects, out);
2171 }
2172 
findObjectsAtBox(const MapCoordF & corner1,const MapCoordF & corner2,bool include_hidden_objects,bool include_protected_objects,std::vector<Object * > & out) const2173 void Map::findObjectsAtBox(
2174         const MapCoordF& corner1,
2175         const MapCoordF& corner2,
2176         bool include_hidden_objects,
2177         bool include_protected_objects,
2178         std::vector< Object* >& out ) const
2179 {
2180 	getCurrentPart()->findObjectsAtBox(corner1, corner2, include_hidden_objects, include_protected_objects, out);
2181 }
2182 
countObjectsInRect(const QRectF & map_coord_rect,bool include_hidden_objects)2183 int Map::countObjectsInRect(const QRectF& map_coord_rect, bool include_hidden_objects)
2184 {
2185 	int count = 0;
2186 	for (const MapPart* part : parts)
2187 		count += part->countObjectsInRect(map_coord_rect, include_hidden_objects);
2188 	return count;
2189 }
2190 
2191 
2192 
existsObject(const std::function<bool (const Object *)> & condition) const2193 bool Map::existsObject(const std::function<bool (const Object*)>& condition) const
2194 {
2195 	return std::any_of(begin(parts), end(parts), [&condition](auto part) { return part->existsObject(condition); });
2196 }
2197 
2198 
applyOnMatchingObjects(const std::function<void (Object *)> & operation,const std::function<bool (const Object *)> & condition)2199 void Map::applyOnMatchingObjects(const std::function<void (Object*)>& operation, const std::function<bool (const Object*)>& condition)
2200 {
2201 	for (auto part : parts)
2202 		part->applyOnMatchingObjects(operation, condition);
2203 }
2204 
2205 
applyOnMatchingObjects(const std::function<void (const Object *)> & operation,const std::function<bool (const Object *)> & condition) const2206 void Map::applyOnMatchingObjects(const std::function<void (const Object*)>& operation, const std::function<bool (const Object*)>& condition) const
2207 {
2208 	for (const MapPart* part : parts)
2209 		part->applyOnMatchingObjects(operation, condition);
2210 }
2211 
2212 
applyOnMatchingObjects(const std::function<void (Object *,MapPart *,int)> & operation,const std::function<bool (const Object *)> & condition)2213 void Map::applyOnMatchingObjects(const std::function<void (Object*, MapPart*, int)>& operation, const std::function<bool (const Object*)>& condition)
2214 {
2215 	for (auto part : parts)
2216 		part->applyOnMatchingObjects(operation, condition);
2217 }
2218 
2219 
applyOnAllObjects(const std::function<void (Object *)> & operation)2220 void Map::applyOnAllObjects(const std::function<void (Object*)>& operation)
2221 {
2222 	for (auto part : parts)
2223 		part->applyOnAllObjects(operation);
2224 }
2225 
2226 
applyOnAllObjects(const std::function<void (const Object *)> & operation) const2227 void Map::applyOnAllObjects(const std::function<void (const Object*)>& operation) const
2228 {
2229 	for (const MapPart* part : parts)
2230 		part->applyOnAllObjects(operation);
2231 }
2232 
2233 
applyOnAllObjects(const std::function<void (Object *,MapPart *,int)> & operation)2234 void Map::applyOnAllObjects(const std::function<void (Object*, MapPart*, int)>& operation)
2235 {
2236 	for (auto part : parts)
2237 		part->applyOnAllObjects(operation);
2238 }
2239 
2240 
2241 
scaleAllObjects(double factor,const MapCoord & scaling_center)2242 void Map::scaleAllObjects(double factor, const MapCoord& scaling_center)
2243 {
2244 	applyOnAllObjects(ObjectOp::Scale{factor, MapCoordF{scaling_center}});
2245 }
2246 
rotateAllObjects(double rotation,const MapCoord & center)2247 void Map::rotateAllObjects(double rotation, const MapCoord& center)
2248 {
2249 	applyOnAllObjects(ObjectOp::Rotate{rotation, MapCoordF{center}});
2250 }
2251 
updateAllObjects()2252 void Map::updateAllObjects()
2253 {
2254 	applyOnAllObjects(&Object::forceUpdate);
2255 }
2256 
updateAllObjectsWithSymbol(const Symbol * symbol)2257 void Map::updateAllObjectsWithSymbol(const Symbol* symbol)
2258 {
2259 	applyOnMatchingObjects(&Object::forceUpdate, ObjectOp::HasSymbol{symbol});
2260 }
2261 
changeSymbolForAllObjects(const Symbol * old_symbol,const Symbol * new_symbol)2262 void Map::changeSymbolForAllObjects(const Symbol* old_symbol, const Symbol* new_symbol)
2263 {
2264 	applyOnMatchingObjects(ObjectOp::ChangeSymbol{new_symbol}, ObjectOp::HasSymbol{old_symbol});
2265 }
2266 
deleteAllObjectsWithSymbol(const Symbol * symbol)2267 bool Map::deleteAllObjectsWithSymbol(const Symbol* symbol)
2268 {
2269 	bool exists = existsObject(ObjectOp::HasSymbol{symbol});
2270 	if (exists)
2271 	{
2272 		// Remove objects from selection
2273 		removeSymbolFromSelection(symbol, true);
2274 
2275 		// Delete objects from map
2276 		applyOnMatchingObjects(ObjectOp::Delete(), ObjectOp::HasSymbol{symbol});
2277 	}
2278 	return exists;
2279 }
2280 
existsObjectWithSymbol(const Symbol * symbol) const2281 bool Map::existsObjectWithSymbol(const Symbol* symbol) const
2282 {
2283 	return existsObject(ObjectOp::HasSymbol{symbol});
2284 }
2285 
setGeoreferencing(const Georeferencing & georeferencing)2286 void Map::setGeoreferencing(const Georeferencing& georeferencing)
2287 {
2288 	*this->georeferencing = georeferencing;
2289 	setOtherDirty();
2290 }
2291 
setGrid(const MapGrid & grid)2292 void Map::setGrid(const MapGrid &grid)
2293 {
2294 	if (grid != this->grid)
2295 	{
2296 		this->grid = grid;
2297 		for (MapWidget* widget : widgets)
2298 		{
2299 			MapView* view = widget->getMapView();
2300 			if (view && view->isGridVisible())
2301 				view->updateAllMapWidgets();
2302 		}
2303 		setOtherDirty();
2304 	}
2305 }
2306 
2307 
isAreaHatchingEnabled() const2308 bool Map::isAreaHatchingEnabled() const
2309 {
2310 	return renderable_options & Symbol::RenderAreasHatched;
2311 }
2312 
setAreaHatchingEnabled(bool enabled)2313 void Map::setAreaHatchingEnabled(bool enabled)
2314 {
2315 	if (enabled)
2316 		renderable_options |= Symbol::RenderAreasHatched;
2317 	else
2318 		renderable_options &= ~Symbol::RenderAreasHatched;
2319 }
2320 
2321 
isBaselineViewEnabled() const2322 bool Map::isBaselineViewEnabled() const
2323 {
2324 	return renderable_options & Symbol::RenderBaselines;
2325 }
2326 
setBaselineViewEnabled(bool enabled)2327 void Map::setBaselineViewEnabled(bool enabled)
2328 {
2329 	if (enabled)
2330 		renderable_options |= Symbol::RenderBaselines;
2331 	else
2332 		renderable_options &= ~Symbol::RenderBaselines;
2333 }
2334 
2335 
printerConfig()2336 const MapPrinterConfig& Map::printerConfig()
2337 {
2338 	if (printer_config.isNull())
2339 		printer_config.reset(new MapPrinterConfig(*this));
2340 
2341 	return *printer_config;
2342 }
2343 
printerConfig() const2344 MapPrinterConfig Map::printerConfig() const
2345 {
2346 	MapPrinterConfig ret = printer_config.isNull() ? MapPrinterConfig{ *this } : *printer_config;
2347 	return ret;
2348 }
2349 
setPrinterConfig(const MapPrinterConfig & config)2350 void Map::setPrinterConfig(const MapPrinterConfig& config)
2351 {
2352 	if (printer_config.isNull())
2353 	{
2354 		printer_config.reset(new MapPrinterConfig(config));
2355 		setOtherDirty();
2356 	}
2357 	else if (*printer_config != config)
2358 	{
2359 		*printer_config = config;
2360 		setOtherDirty();
2361 	}
2362 }
2363 
resetPrinterConfig()2364 void Map::resetPrinterConfig()
2365 {
2366 	if (printer_config)
2367 	{
2368 		printer_config.reset();
2369 		setOtherDirty();
2370 	}
2371 }
2372 
setImageTemplateDefaults(bool use_meters_per_pixel,double meters_per_pixel,double dpi,double scale)2373 void Map::setImageTemplateDefaults(bool use_meters_per_pixel, double meters_per_pixel, double dpi, double scale)
2374 {
2375 	image_template_use_meters_per_pixel = use_meters_per_pixel;
2376 	image_template_meters_per_pixel = meters_per_pixel;
2377 	image_template_dpi = dpi;
2378 	image_template_scale = scale;
2379 }
2380 
getImageTemplateDefaults(bool & use_meters_per_pixel,double & meters_per_pixel,double & dpi,double & scale)2381 void Map::getImageTemplateDefaults(bool& use_meters_per_pixel, double& meters_per_pixel, double& dpi, double& scale)
2382 {
2383 	use_meters_per_pixel = image_template_use_meters_per_pixel;
2384 	meters_per_pixel = image_template_meters_per_pixel;
2385 	dpi = image_template_dpi;
2386 	scale = image_template_scale;
2387 }
2388 
setHasUnsavedChanges(bool has_unsaved_changes)2389 void Map::setHasUnsavedChanges(bool has_unsaved_changes)
2390 {
2391 	if (!has_unsaved_changes)
2392 	{
2393 		colors_dirty = false;
2394 		symbols_dirty = false;
2395 		templates_dirty = false;
2396 		objects_dirty = false;
2397 		other_dirty = false;
2398 		if (unsaved_changes)
2399 		{
2400 			unsaved_changes = false;
2401 			emit hasUnsavedChanged(unsaved_changes);
2402 		}
2403 	}
2404 	else if (!unsaved_changes)
2405 	{
2406 		unsaved_changes = true;
2407 		emit hasUnsavedChanged(unsaved_changes);
2408 	}
2409 }
2410 
setOtherDirty()2411 void Map::setOtherDirty()
2412 {
2413 	other_dirty = true;
2414 	setHasUnsavedChanges(true);
2415 }
2416 
2417 // slot
undoCleanChanged(bool is_clean)2418 void Map::undoCleanChanged(bool is_clean)
2419 {
2420 	if (is_clean && unsaved_changes && !(colors_dirty || symbols_dirty || templates_dirty || other_dirty))
2421 	{
2422 		setHasUnsavedChanges(false);
2423 	}
2424 	else if (!is_clean && !unsaved_changes)
2425 	{
2426 		setHasUnsavedChanges(true);
2427 	}
2428 }
2429 
2430 
2431 }  // namespace OpenOrienteering
2432