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