1 /*
2  * Copyright (C) 2014-2018 Christopho, Solarus - http://www.solarus-games.org
3  *
4  * Solarus Quest Editor is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * Solarus Quest Editor is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program. If not, see <http://www.gnu.org/licenses/>.
16  */
17 #include "entities/destination.h"
18 #include "entities/entity_model.h"
19 #include "entities/dynamic_tile.h"
20 #include "widgets/gui_tools.h"
21 #include "widgets/map_editor.h"
22 #include "widgets/map_scene.h"
23 #include "widgets/pattern_picker_dialog.h"
24 #include "widgets/tileset_scene.h"
25 #include "audio.h"
26 #include "auto_tiler.h"
27 #include "editor_exception.h"
28 #include "editor_settings.h"
29 #include "file_tools.h"
30 #include "map_model.h"
31 #include "point.h"
32 #include "quest.h"
33 #include "quest_database.h"
34 #include "refactoring.h"
35 #include "tileset_model.h"
36 #include "view_settings.h"
37 #include <QFileDialog>
38 #include <QItemSelectionModel>
39 #include <QMessageBox>
40 #include <QStatusBar>
41 #include <QToolBar>
42 #include <QUndoStack>
43 #include <memory>
44 
45 namespace SolarusEditor {
46 
47 namespace {
48 
49 constexpr int move_entities_command_id = 1;
50 constexpr int resize_entities_command_id = 2;
51 
52 /**
53  * @brief Puts the map editor in bulk update mode for performance.
54  */
55 class BulkMapEditorChange {
56 
57 public:
BulkMapEditorChange(MapEditor & editor)58   BulkMapEditorChange(MapEditor& editor):
59     editor(editor),
60     was_bulk_mode(editor.get_map().is_bulk_mode()) {
61     editor.get_map().set_bulk_mode(true);
62   }
~BulkMapEditorChange()63   ~BulkMapEditorChange() {
64     editor.get_map().set_bulk_mode(was_bulk_mode);
65   }
66 
67 private:
68   MapEditor& editor;
69   bool was_bulk_mode;
70 };
71 
72 /**
73  * @brief Parent class of all undoable commands of the map editor.
74  */
75 class MapEditorCommand : public QUndoCommand {
76 
77 public:
78 
MapEditorCommand(MapEditor & editor,const QString & text)79   MapEditorCommand(MapEditor& editor, const QString& text) :
80     QUndoCommand(text),
81     editor(editor) {
82   }
83 
get_editor() const84   MapEditor& get_editor() const {
85     return editor;
86   }
87 
get_map() const88   MapModel& get_map() const {
89     return editor.get_map();
90   }
91 
get_map_view() const92   MapView& get_map_view() const {
93     return editor.get_map_view();
94   }
95 
get_view_settings() const96   ViewSettings& get_view_settings() const {
97     return editor.get_view_settings();
98   }
99 
100 private:
101 
102   MapEditor& editor;
103 
104 };
105 
106 /**
107  * @brief Changing the size of the map.
108  */
109 class SetSizeCommand : public MapEditorCommand {
110 
111 public:
SetSizeCommand(MapEditor & editor,const QSize & size)112   SetSizeCommand(MapEditor& editor, const QSize& size) :
113     MapEditorCommand(editor, MapEditor::tr("Map size")),
114     before(get_map().get_size()),
115     after(size) { }
116 
undo()117   void undo() override { get_map().set_size(before); }
redo()118   void redo() override { get_map().set_size(after); }
119 
120 private:
121   QSize before, after;
122 };
123 
124 /**
125  * @brief Changing the lowest layer of the map.
126  */
127 class SetMinLayerCommand : public MapEditorCommand {
128 
129 public:
SetMinLayerCommand(MapEditor & editor,int min_layer)130   SetMinLayerCommand(MapEditor& editor, int min_layer) :
131     MapEditorCommand(editor, MapEditor::tr("Lowest layer")),
132     min_layer_before(get_map().get_min_layer()),
133     min_layer_after(min_layer),
134     entities_removed() { }
135 
undo()136   void undo() override {
137     get_map().set_min_layer(min_layer_before);
138     // Restore entities.
139     get_map().add_entities(std::move(entities_removed));
140   }
141 
redo()142   void redo() override {
143     entities_removed = get_map().set_min_layer(min_layer_after);
144   }
145 
146 private:
147   int min_layer_before;
148   int min_layer_after;
149   AddableEntities entities_removed;  // Entities that were on removed layers.
150 };
151 
152 /**
153  * @brief Changing the highest layer of the map.
154  */
155 class SetMaxLayerCommand : public MapEditorCommand {
156 
157 public:
SetMaxLayerCommand(MapEditor & editor,int max_layer)158   SetMaxLayerCommand(MapEditor& editor, int max_layer) :
159     MapEditorCommand(editor, MapEditor::tr("Highest layer")),
160     max_layer_before(get_map().get_max_layer()),
161     max_layer_after(max_layer),
162     entities_removed() { }
163 
undo()164   void undo() override {
165     get_map().set_max_layer(max_layer_before);
166     // Restore entities.
167     get_map().add_entities(std::move(entities_removed));
168   }
169 
redo()170   void redo() override {
171     entities_removed = get_map().set_max_layer(max_layer_after);
172   }
173 
174 private:
175   int max_layer_before;
176   int max_layer_after;
177   AddableEntities entities_removed;  // Entities that were on removed layers.
178 };
179 
180 /**
181  * @brief Changing the world of the map.
182  */
183 class SetWorldCommand : public MapEditorCommand {
184 
185 public:
SetWorldCommand(MapEditor & editor,const QString & world)186   SetWorldCommand(MapEditor& editor, const QString& world) :
187     MapEditorCommand(editor, MapEditor::tr("Map world")),
188     before(get_map().get_world()),
189     after(world) { }
190 
undo()191   void undo() override { get_map().set_world(before); }
redo()192   void redo() override { get_map().set_world(after); }
193 
194 private:
195   QString before, after;
196 };
197 
198 /**
199  * @brief Changing the floor of the map.
200  */
201 class SetFloorCommand : public MapEditorCommand {
202 
203 public:
SetFloorCommand(MapEditor & editor,int floor)204   SetFloorCommand(MapEditor& editor, int floor) :
205     MapEditorCommand(editor, MapEditor::tr("Map floor")),
206     before(get_map().get_floor()),
207     after(floor) { }
208 
undo()209   void undo() override { get_map().set_floor(before); }
redo()210   void redo() override { get_map().set_floor(after); }
211 
212 private:
213   int before, after;
214 };
215 
216 /**
217  * @brief Changing the location of the map.
218  */
219 class SetLocationCommand : public MapEditorCommand {
220 
221 public:
SetLocationCommand(MapEditor & editor,const QPoint & location)222   SetLocationCommand(MapEditor& editor, const QPoint& location) :
223     MapEditorCommand(editor, MapEditor::tr("Map location")),
224     before(get_map().get_location()),
225     after(location) { }
226 
undo()227   void undo() override { get_map().set_location(before); }
redo()228   void redo() override { get_map().set_location(after); }
229 
230 private:
231   QPoint before, after;
232 };
233 
234 /**
235  * @brief Changing the tileset of the map.
236  */
237 class SetTilesetCommand : public MapEditorCommand {
238 
239 public:
SetTilesetCommand(MapEditor & editor,const QString & tileset_id)240   SetTilesetCommand(MapEditor& editor, const QString& tileset_id) :
241     MapEditorCommand(editor, MapEditor::tr("Tileset")),
242     before(get_map().get_tileset_id()),
243     after(tileset_id) { }
244 
undo()245   void undo() override { get_map().set_tileset_id(before); }
redo()246   void redo() override { get_map().set_tileset_id(after); }
247 
248 private:
249   QString before, after;
250 };
251 
252 /**
253  * @brief Changing the music of the map.
254  */
255 class SetMusicCommand : public MapEditorCommand {
256 
257 public:
SetMusicCommand(MapEditor & editor,const QString & music_id)258   SetMusicCommand(MapEditor& editor, const QString& music_id) :
259     MapEditorCommand(editor, MapEditor::tr("Music")),
260     before(get_map().get_music_id()),
261     after(music_id) { }
262 
undo()263   void undo() override { get_map().set_music_id(before); }
redo()264   void redo() override { get_map().set_music_id(after); }
265 
266 private:
267   QString before, after;
268 };
269 
270 /**
271  * @brief Editing an entity.
272  */
273 class EditEntityCommand : public MapEditorCommand {
274 
275 public:
EditEntityCommand(MapEditor & editor,const EntityIndex & index_before,EntityModelPtr entity_after)276   EditEntityCommand(MapEditor& editor, const EntityIndex& index_before, EntityModelPtr entity_after) :
277     MapEditorCommand(editor, MapEditor::tr("Edit entity")),
278     index_before(index_before),
279     entity_after(std::move(entity_after)) { }
280 
undo()281   void undo() override {
282 
283     MapModel& map = get_map();
284 
285     // Remove the new entity created by redo().
286     AddableEntities removed_entities = map.remove_entities(EntityIndexes() << index_after);
287     AddableEntity& removed_entity = *removed_entities.begin();
288     entity_after = std::move(removed_entity.entity);
289 
290     // Restore the old one.
291     AddableEntities addable_entities;
292     addable_entities.emplace_back(std::move(entity_before), index_before);
293     map.add_entities(std::move(addable_entities));
294 
295     // Restore the previous default destination.
296     if (entity_after->get_type() == EntityType::DESTINATION &&
297         entity_after->get_field("default").toBool() &&
298         default_destination_index_before.is_valid() &&
299         default_destination_index_before != index_before) {
300       map.set_entity_field(default_destination_index_before, "default", true);
301     }
302 
303     // Make it selected.
304     get_map_view().set_only_selected_entity(index_before);
305   }
306 
redo()307   void redo() override {
308 
309     MapModel& map = get_map();
310 
311     // To implement the change, remove the old entity and add the new one.
312     index_after = index_before;
313     if (entity_after->get_layer() != index_before.layer) {
314       // The layer changes: put the entity to the front.
315       index_after.layer = entity_after->get_layer();
316       index_after.order = entity_after->is_dynamic() ?
317             map.get_num_entities(index_after.layer) : map.get_num_tiles(index_after.layer);
318     }
319 
320     // Make sure there is only one destination.
321     default_destination_index_before = map.find_default_destination_index();
322     if (entity_after->get_type() == EntityType::DESTINATION &&
323         entity_after->get_field("default").toBool() &&
324         default_destination_index_before.is_valid() &&
325         default_destination_index_before != index_before
326     ) {
327       map.set_entity_field(default_destination_index_before, "default", false);
328     }
329 
330     // Remove the initial entity.
331     AddableEntities removed_entities = map.remove_entities(EntityIndexes() << index_before);
332     AddableEntity& removed_entity = *removed_entities.begin();
333     entity_before = std::move(removed_entity.entity);
334 
335     // Add the new one to replace it.
336     AddableEntities addable_entities;
337     addable_entities.emplace_back(std::move(entity_after), index_after);
338     map.add_entities(std::move(addable_entities));
339 
340     // Make the new one selected.
341     get_map_view().set_only_selected_entity(index_after);
342   }
343 
get_index_before() const344   EntityIndex get_index_before() const { return index_before; }
get_index_after() const345   EntityIndex get_index_after() const { return index_after; }
346 
347 private:
348   EntityIndex index_before;
349   EntityIndex index_after;
350   EntityModelPtr entity_before;
351   EntityModelPtr entity_after;
352   EntityIndex default_destination_index_before;
353 };
354 
355 /**
356  * @brief Moving entities on the map.
357  */
358 class MoveEntitiesCommand : public MapEditorCommand {
359 
360 public:
MoveEntitiesCommand(MapEditor & editor,const EntityIndexes & indexes,const QPoint & translation,bool allow_merge_to_previous)361   MoveEntitiesCommand(MapEditor& editor, const EntityIndexes& indexes, const QPoint& translation, bool allow_merge_to_previous) :
362     MapEditorCommand(editor, MapEditor::tr("Move entities")),
363     indexes(indexes),
364     translation(translation),
365     allow_merge_to_previous(allow_merge_to_previous) { }
366 
undo()367   void undo() override {
368     for (const EntityIndex& index : indexes) {
369       get_map().add_entity_xy(index, -translation);
370     }
371     // Select impacted entities.
372     get_map_view().set_selected_entities(indexes);
373   }
374 
redo()375   void redo() override {
376     for (const EntityIndex& index : indexes) {
377       get_map().add_entity_xy(index, translation);
378     }
379     // Select impacted entities.
380     get_map_view().set_selected_entities(indexes);
381   }
382 
id() const383   int id() const override {
384     return move_entities_command_id;
385   }
386 
mergeWith(const QUndoCommand * other)387   bool mergeWith(const QUndoCommand* other) override {
388 
389     if (other->id() != id()) {
390       return false;
391     }
392     const MoveEntitiesCommand& other_move = *static_cast<const MoveEntitiesCommand*>(other);
393     if (!other_move.allow_merge_to_previous) {
394       return false;
395     }
396 
397     translation += other_move.translation;
398     return true;
399   }
400 
401 private:
402   EntityIndexes indexes;
403   QPoint translation;
404   bool allow_merge_to_previous;
405 };
406 
407 /**
408  * @brief Resizing entities on the map.
409  */
410 class ResizeEntitiesCommand : public MapEditorCommand {
411 
412 public:
ResizeEntitiesCommand(MapEditor & editor,const QMap<EntityIndex,QRect> & boxes,bool allow_merge_to_previous)413   ResizeEntitiesCommand(MapEditor& editor, const QMap<EntityIndex, QRect>& boxes, bool allow_merge_to_previous) :
414     MapEditorCommand(editor, MapEditor::tr("Resize entities")),
415     boxes_before(),
416     boxes_after(boxes),
417     allow_merge_to_previous(allow_merge_to_previous) {
418 
419     // Remember the old box of each entity to allow undo.
420     MapModel& map = get_map();
421     for (auto it = boxes.begin(); it != boxes.end(); ++it) {
422       const EntityIndex& index = it.key();
423       Q_ASSERT(map.entity_exists(index));
424       boxes_before.insert(index, map.get_entity_bounding_box(index));
425     }
426   }
427 
undo()428   void undo() override {
429 
430     EntityIndexes indexes;
431     for (auto it = boxes_before.begin(); it != boxes_before.end(); ++it) {
432       const EntityIndex& index = it.key();
433       get_map().set_entity_bounding_box(index, it.value());
434       indexes.append(index);
435     }
436 
437     // Select impacted entities.
438     get_map_view().set_selected_entities(indexes);
439   }
440 
redo()441   void redo() override {
442 
443     EntityIndexes indexes;
444     for (auto it = boxes_after.begin(); it != boxes_after.end(); ++it) {
445       const EntityIndex& index = it.key();
446       QRect box_after = it.value();
447       QSize size = box_after.size();
448       if (!get_map().is_entity_size_valid(index, size)) {
449         // Invalid size: refuse the change.
450         box_after.setSize(boxes_before.value(index).size());
451       }
452       get_map().set_entity_bounding_box(index, it.value());
453       indexes.append(index);
454     }
455 
456     // Select impacted entities.
457     get_map_view().set_selected_entities(indexes);
458   }
459 
id() const460   int id() const override {
461     return resize_entities_command_id;
462   }
463 
mergeWith(const QUndoCommand * other)464   bool mergeWith(const QUndoCommand* other) override {
465 
466     if (other->id() != id()) {
467       return false;
468     }
469     const ResizeEntitiesCommand& other_resize = *static_cast<const ResizeEntitiesCommand*>(other);
470     if (!other_resize.allow_merge_to_previous) {
471       return false;
472     }
473 
474     for (auto it = other_resize.boxes_after.begin();
475          it != other_resize.boxes_after.end();
476          ++it) {
477       const EntityIndex& index = it.key();
478       boxes_after[index] = it.value();
479     }
480     return true;
481   }
482 
483 private:
484   QMap<EntityIndex, QRect> boxes_before;
485   QMap<EntityIndex, QRect> boxes_after;
486   bool allow_merge_to_previous;
487 };
488 
489 /**
490  * @brief Converting normal tiles to dynamic tiles.
491  */
492 class ConvertTilesToDynamicCommand : public MapEditorCommand {
493 
494 public:
ConvertTilesToDynamicCommand(MapEditor & editor,const EntityIndexes & indexes)495   ConvertTilesToDynamicCommand(MapEditor& editor, const EntityIndexes& indexes) :
496     MapEditorCommand(editor, MapEditor::tr("Convert tiles")),
497     indexes_before(indexes) {
498 
499     qSort(this->indexes_before);
500   }
501 
undo()502   void undo() override {
503     get_map().remove_entities(indexes_after);
504     get_map().add_entities(std::move(removed_tiles));
505     get_map_view().set_selected_entities(indexes_before);
506   }
507 
redo()508   void redo() override {
509     MapModel& map = get_map();
510     AddableEntities dynamic_tiles;
511 
512     // Create the dynamic tiles.
513     for (const EntityIndex& index_before : indexes_before) {
514       EntityModelPtr dynamic_tile = DynamicTile::create_from_normal_tile(map, index_before);
515       int layer = index_before.layer;
516       EntityIndex index_after = { layer, -1 };
517       dynamic_tiles.emplace_back(std::move(dynamic_tile), index_after);
518     }
519 
520     // Remove the static ones.
521     removed_tiles = map.remove_entities(indexes_before);
522 
523     // Determine the indexes where to place the dynamic ones.
524     indexes_after.clear();
525     std::map<int, int> order_after_by_layer;
526     for (int layer = map.get_min_layer(); layer <= map.get_max_layer(); ++layer) {
527       order_after_by_layer[layer] = map.get_num_entities(layer);
528     }
529     for (AddableEntity& addable : dynamic_tiles) {
530       addable.index.order = order_after_by_layer[addable.index.layer];
531       ++order_after_by_layer[addable.index.layer];
532       indexes_after.append(addable.index);
533     }
534 
535     // Add the dynamic tiles and make them selected.
536     map.add_entities(std::move(dynamic_tiles));
537     get_map_view().set_selected_entities(indexes_after);
538   }
539 
540 private:
541   EntityIndexes indexes_before;
542   EntityIndexes indexes_after;
543   AddableEntities removed_tiles;
544 };
545 
546 /**
547  * @brief Converting dynamic tiles to normal tiles.
548  */
549 class ConvertTilesFromDynamicCommand : public MapEditorCommand {
550 
551 public:
ConvertTilesFromDynamicCommand(MapEditor & editor,const EntityIndexes & indexes)552   ConvertTilesFromDynamicCommand(MapEditor& editor, const EntityIndexes& indexes) :
553     MapEditorCommand(editor, MapEditor::tr("Convert tiles")),
554     indexes_before(indexes) {
555 
556     qSort(this->indexes_before);
557   }
558 
undo()559   void undo() override {
560     get_map().remove_entities(indexes_after);
561     get_map().add_entities(std::move(removed_tiles));
562     get_map_view().set_selected_entities(indexes_before);
563   }
564 
redo()565   void redo() override {
566     MapModel& map = get_map();
567     AddableEntities tiles;
568 
569     // Create the dynamic tiles.
570     for (const EntityIndex& index_before : indexes_before) {
571       EntityModelPtr tile = Tile::create_from_dynamic_tile(map, index_before);
572       int layer = index_before.layer;
573       EntityIndex index_after = { layer, -1 };
574       tiles.emplace_back(std::move(tile), index_after);
575     }
576 
577     // Remove the dynamic ones.
578     removed_tiles = map.remove_entities(indexes_before);
579 
580     // Determine the indexes where to place the dynamic ones.
581     indexes_after.clear();
582     std::map<int, int> order_after_by_layer;
583     for (int layer = map.get_min_layer(); layer <= map.get_max_layer(); ++layer) {
584       order_after_by_layer[layer] = map.get_num_tiles(layer);
585     }
586     for (AddableEntity& addable : tiles) {
587       addable.index.order = order_after_by_layer[addable.index.layer];
588       ++order_after_by_layer[addable.index.layer];
589       indexes_after.append(addable.index);
590     }
591 
592     // Add the normal tiles and make them selected.
593     map.add_entities(std::move(tiles));
594     get_map_view().set_selected_entities(indexes_after);
595   }
596 
597 private:
598   EntityIndexes indexes_before;
599   EntityIndexes indexes_after;
600   AddableEntities removed_tiles;
601 };
602 
603 /**
604  * @brief Changing the pattern of some tiles.
605  */
606 class ChangeTilesPatternCommand : public MapEditorCommand {
607 
608 public:
ChangeTilesPatternCommand(MapEditor & editor,const EntityIndexes & indexes,const QString & pattern_id)609   ChangeTilesPatternCommand(
610       MapEditor& editor,
611       const EntityIndexes&
612       indexes,
613       const QString& pattern_id
614   ) :
615     MapEditorCommand(editor, MapEditor::tr("Change pattern")),
616     indexes(indexes),
617     pattern_ids_before(),
618     pattern_id_after(pattern_id) {
619 
620     for (const EntityIndex& index : indexes) {
621       const QString& pattern_id_before =
622           get_map().get_entity_field(index, "pattern").toString();
623       pattern_ids_before << pattern_id_before;
624       sizes_before << get_map().get_entity_size(index);
625     }
626   }
627 
undo()628   void undo() override {
629     MapModel& map = get_map();
630     int i = 0;
631     for (const EntityIndex& index : indexes) {
632       map.set_entity_field(index, "pattern", pattern_ids_before[i]);
633       map.set_entity_size(index, sizes_before[i]);
634       ++i;
635     }
636     get_map_view().set_selected_entities(indexes);
637     get_map_view().get_scene()->redraw_entities(indexes);
638   }
639 
redo()640   void redo() override {
641     MapModel& map = get_map();
642     for (const EntityIndex& index : indexes) {
643       map.set_entity_field(index, "pattern", pattern_id_after);
644       const QSize& size = map.get_entity_closest_valid_size(index);
645       if (map.is_entity_size_valid(index, size)) {
646         map.set_entity_size(index, size);
647       }
648     }
649     get_map_view().set_selected_entities(indexes);
650     get_map_view().get_scene()->redraw_entities(indexes);
651   }
652 
653 private:
654   EntityIndexes indexes;
655   QStringList pattern_ids_before;
656   QList<QSize> sizes_before;
657   QString pattern_id_after;
658 };
659 
660 /**
661  * @brief Changing the direction of entities on the map.
662  *
663  * For some entities, the size is also changed if it becomes invalid.
664  */
665 class SetEntitiesDirectionCommand : public MapEditorCommand {
666 
667 public:
SetEntitiesDirectionCommand(MapEditor & editor,const EntityIndexes & indexes,int direction)668   SetEntitiesDirectionCommand(MapEditor& editor, const EntityIndexes& indexes, int direction) :
669     MapEditorCommand(editor, MapEditor::tr("Set direction")),
670     indexes(indexes),
671     directions_before(),
672     direction_after(direction) {
673   }
674 
undo()675   void undo() override {
676     int i = 0;
677     for (const EntityIndex& index : indexes) {
678       get_map().set_entity_direction(index, directions_before.at(i));
679       get_map().set_entity_size(index, sizes_before.at(i));
680       ++i;
681     }
682     get_map_view().set_selected_entities(indexes);
683     get_map_view().get_scene()->redraw_entities(indexes);
684   }
685 
redo()686   void redo() override {
687 
688     MapModel& map = get_map();
689 
690     // Change the direction.
691     directions_before.clear();
692     sizes_before.clear();
693     for (const EntityIndex& index : indexes) {
694       bool was_size_valid = map.is_entity_size_valid(index);
695       directions_before.append(map.get_entity_direction(index));
696       sizes_before.append(map.get_entity_size(index));
697 
698       map.set_entity_direction(index, direction_after);
699 
700       // Check that the size is still okay in the new direction.
701       if (was_size_valid && !map.is_entity_size_valid(index)) {
702         // The entity size is no longer valid in the new direction:
703         // set a new size right now if there is only one entity selected.
704         map.set_entity_size(index, map.get_entity_valid_size(index));
705       }
706 
707       map.get_entity(index).reload_sprite();
708     }
709 
710     // Select impacted entities.
711     get_map_view().set_selected_entities(indexes);
712 
713     // Tell the scene to redraw the entities.
714     get_map_view().get_scene()->redraw_entities(indexes);
715   }
716 
717 private:
718   EntityIndexes indexes;
719   QList<int> directions_before;
720   int direction_after;
721   QList<QSize> sizes_before;
722 };
723 
724 /**
725  * @brief Changing the layer of entities on the map.
726  */
727 class SetEntitiesLayerCommand : public MapEditorCommand {
728 
729 public:
SetEntitiesLayerCommand(MapEditor & editor,const EntityIndexes & indexes,int layer)730   SetEntitiesLayerCommand(MapEditor& editor, const EntityIndexes& indexes, int layer) :
731     MapEditorCommand(editor, MapEditor::tr("Set layer")),
732     indexes_before(indexes),
733     indexes_after(),
734     layer_after(layer) {
735 
736     qSort(this->indexes_before);
737   }
738 
undo()739   void undo() override {
740     {
741       BulkMapEditorChange bulk(get_editor());
742       get_map().undo_set_entities_layer(indexes_after, indexes_before_gradual);
743     }
744     // Select impacted entities.
745     get_map_view().set_selected_entities(indexes_before);
746   }
747 
redo()748   void redo() override {
749     QList<int> layers_after;
750     for (int i = 0; i < indexes_before.size(); ++i) {
751       layers_after << layer_after;
752     }
753     {
754       BulkMapEditorChange bulk(get_editor());
755       get_map().set_entities_layer(indexes_before, layers_after, indexes_before_gradual, indexes_after);
756     }
757     // Select impacted entities.
758     get_map_view().set_selected_entities(indexes_after);
759   }
760 
761 private:
762   EntityIndexes indexes_before;          // Sorted indexes before the whole change.
763   EntityIndexes indexes_before_gradual;  // Indexes before each individual change, in the same order as before.
764   EntityIndexes indexes_after;           // Indexes after the whole change, in the same order as before.
765   int layer_after;
766 };
767 
768 /**
769  * @brief Moving entities one layer up.
770  */
771 class IncreaseEntitiesLayerCommand : public MapEditorCommand {
772 
773 public:
IncreaseEntitiesLayerCommand(MapEditor & editor,const EntityIndexes & indexes)774   IncreaseEntitiesLayerCommand(MapEditor& editor, const EntityIndexes& indexes) :
775     MapEditorCommand(editor, MapEditor::tr("Increment layer")),
776     indexes_before(indexes),
777     indexes_after() {
778 
779     qSort(this->indexes_before);
780   }
781 
undo()782   void undo() override {
783 
784     {
785       BulkMapEditorChange bulk(get_editor());
786       get_map().undo_set_entities_layer(indexes_after, indexes_before_gradual);
787     }
788     // Select impacted entities.
789     get_map_view().set_selected_entities(indexes_before);
790   }
791 
redo()792   void redo() override {
793 
794     QList<int> layers_after;
795     for (const EntityIndex& index_before : indexes_before) {
796       int layer_after = std::min(index_before.layer + 1, get_map().get_max_layer());
797       layers_after << layer_after;
798     }
799     {
800       BulkMapEditorChange bulk(get_editor());
801       get_map().set_entities_layer(indexes_before, layers_after, indexes_before_gradual, indexes_after);
802     }
803     // Select impacted entities.
804     get_map_view().set_selected_entities(indexes_after);
805   }
806 
807 private:
808   EntityIndexes indexes_before;          // Sorted indexes before the whole change.
809   EntityIndexes indexes_before_gradual;  // Indexes before each individual change, in the same order as before.
810   EntityIndexes indexes_after;           // Indexes after the whole change, in the same order as before.
811 };
812 
813 /**
814  * @brief Moving entities one layer down.
815  */
816 class DecreaseEntitiesLayerCommand : public MapEditorCommand {
817 
818 public:
DecreaseEntitiesLayerCommand(MapEditor & editor,const EntityIndexes & indexes)819   DecreaseEntitiesLayerCommand(MapEditor& editor, const EntityIndexes& indexes) :
820     MapEditorCommand(editor, MapEditor::tr("Decrement layer")),
821     indexes_before(indexes),
822     indexes_after() {
823 
824     qSort(this->indexes_before);
825   }
826 
undo()827   void undo() override {
828 
829     {
830       BulkMapEditorChange bulk(get_editor());
831       get_map().undo_set_entities_layer(indexes_after, indexes_before_gradual);
832     }
833     // Select impacted entities.
834     get_map_view().set_selected_entities(indexes_before);
835   }
836 
redo()837   void redo() override {
838 
839     QList<int> layers_after;
840     for (const EntityIndex& index_before : indexes_before) {
841       int layer_after = std::max(index_before.layer - 1, get_map().get_min_layer());
842       layers_after << layer_after;
843     }
844     {
845       BulkMapEditorChange bulk(get_editor());
846       get_map().set_entities_layer(indexes_before, layers_after, indexes_before_gradual, indexes_after);
847     }
848     // Select impacted entities.
849     get_map_view().set_selected_entities(indexes_after);
850   }
851 
852 private:
853   EntityIndexes indexes_before;          // Sorted indexes before the whole change.
854   EntityIndexes indexes_before_gradual;  // Indexes before each individual change, in the same order as before.
855   EntityIndexes indexes_after;           // Indexes after the whole change, in the same order as before.
856 };
857 
858 /**
859  * @brief Bringing some entities to the front.
860  */
861 class BringEntitiesToFrontCommand : public MapEditorCommand {
862 
863 public:
BringEntitiesToFrontCommand(MapEditor & editor,const EntityIndexes & indexes)864   BringEntitiesToFrontCommand(MapEditor& editor, const EntityIndexes& indexes) :
865     MapEditorCommand(editor, MapEditor::tr("Bring to front")),
866     indexes_before(indexes),
867     indexes_after(indexes) {
868 
869     qSort(this->indexes_before);
870   }
871 
undo()872   void undo() override {
873 
874     MapModel& map = get_map();
875     QList<EntityModel*> entities;
876     for (const EntityIndex& index_after: indexes_after) {
877       entities.append(&map.get_entity(index_after));
878     }
879 
880     int i = 0;
881     for (const EntityModel* entity : entities) {
882       map.set_entity_order(entity->get_index(), indexes_before[i].order);
883       ++i;
884     }
885 
886     // Select impacted entities.
887     get_map_view().set_selected_entities(indexes_before);
888   }
889 
redo()890   void redo() override {
891 
892     MapModel& map = get_map();
893     QList<EntityModel*> entities;
894     for (const EntityIndex& index_before: indexes_before) {
895       entities.append(&map.get_entity(index_before));
896     }
897 
898     for (const EntityModel* entity : entities) {
899       map.bring_entity_to_front(entity->get_index());
900     }
901 
902     indexes_after.clear();
903     for (const EntityModel* entity : entities) {
904       indexes_after.append(entity->get_index());
905     }
906 
907     // Select impacted entities.
908     get_map_view().set_selected_entities(indexes_after);
909   }
910 
911 private:
912   EntityIndexes indexes_before;  // Sorted indexes before the change.
913   EntityIndexes indexes_after;  // Indexes after the change, in the same order as before.
914 };
915 
916 /**
917  * @brief Bringing some entities to the back.
918  */
919 class BringEntitiesToBackCommand : public MapEditorCommand {
920 
921 public:
BringEntitiesToBackCommand(MapEditor & editor,const EntityIndexes & indexes)922   BringEntitiesToBackCommand(MapEditor& editor, const EntityIndexes& indexes) :
923     MapEditorCommand(editor, MapEditor::tr("Bring to back")),
924     indexes_before(indexes),
925     indexes_after(indexes) {
926 
927     qSort(this->indexes_before);
928   }
929 
undo()930   void undo() override {
931 
932     MapModel& map = get_map();
933     QList<EntityModel*> entities;
934     for (const EntityIndex& index_after: indexes_after) {
935       entities.append(&map.get_entity(index_after));
936     }
937 
938     for (int i = entities.size() - 1; i >= 0; --i) {
939       EntityModel* entity = entities[i];
940       map.set_entity_order(entity->get_index(), indexes_before[i].order);
941     }
942 
943     // Select impacted entities.
944     get_map_view().set_selected_entities(indexes_before);
945   }
946 
redo()947   void redo() override {
948 
949     MapModel& map = get_map();
950     QList<EntityModel*> entities;
951     for (const EntityIndex& index_before: indexes_before) {
952       entities.append(&map.get_entity(index_before));
953     }
954 
955     // Iterate from the end to preserve the relative order of entities.
956     for (auto it = entities.end(); it != entities.begin();) {
957       --it;
958       EntityModel* entity = *it;
959       map.bring_entity_to_back(entity->get_index());
960     }
961 
962     indexes_after.clear();
963     for (const EntityModel* entity : entities) {
964       indexes_after.append(entity->get_index());
965     }
966 
967     // Select impacted entities.
968     get_map_view().set_selected_entities(indexes_after);
969   }
970 
971 private:
972   EntityIndexes indexes_before;  // Sorted indexes before the change.
973   EntityIndexes indexes_after;  // Indexes after the change, in the same order as before.
974 };
975 
976 /**
977  * @brief Adding entities to the map.
978  */
979 class AddEntitiesCommand : public MapEditorCommand {
980 
981 public:
AddEntitiesCommand(MapEditor & editor,AddableEntities && entities,bool replace_selection)982   AddEntitiesCommand(MapEditor& editor, AddableEntities&& entities, bool replace_selection) :
983     MapEditorCommand(editor, MapEditor::tr("Add entities")),
984     entities(std::move(entities)),
985     indexes(),
986     previous_selected_indexes() {
987 
988     std::sort(this->entities.begin(), this->entities.end());
989 
990     // Store indexes alone in a separate list (useful for undo and for selection).
991     for (const AddableEntity& entity : this->entities) {
992       indexes.append(entity.index);
993     }
994 
995     if (!replace_selection) {
996       previous_selected_indexes = get_map_view().get_selected_entities();
997     }
998   }
999 
undo()1000   void undo() override {
1001     // Remove entities that were added, keep them in this class.
1002     entities = get_map().remove_entities(indexes);
1003     get_map_view().set_selected_entities(previous_selected_indexes);
1004   }
1005 
redo()1006   void redo() override {
1007     // Add entities and make them selected.
1008     get_map().add_entities(std::move(entities));
1009 
1010     EntityIndexes selected_indexes = indexes;
1011     for (const EntityIndex& index : previous_selected_indexes) {
1012       selected_indexes.append(index);
1013     }
1014     get_map_view().set_selected_entities(selected_indexes);
1015   }
1016 
1017 private:
1018   AddableEntities entities;    // Entities to be added and where (sorted).
1019   EntityIndexes indexes;  // Indexes where they should be added (redundant info).
1020   EntityIndexes previous_selected_indexes;  // Selection to keep after adding entities.
1021 };
1022 
1023 /**
1024  * @brief Removing entities from the map.
1025  */
1026 class RemoveEntitiesCommand : public MapEditorCommand {
1027 
1028 public:
RemoveEntitiesCommand(MapEditor & editor,const EntityIndexes & indexes)1029   RemoveEntitiesCommand(MapEditor& editor, const EntityIndexes& indexes) :
1030     MapEditorCommand(editor, MapEditor::tr("Delete entities")),
1031     entities(),
1032     indexes(indexes) {
1033 
1034     std::sort(this->indexes.begin(), this->indexes.end());
1035   }
1036 
undo()1037   void undo() override {
1038     // Restore entities with their old index.
1039     {
1040       BulkMapEditorChange bulk(get_editor());
1041       get_map().add_entities(std::move(entities));
1042     }
1043     get_map_view().set_selected_entities(indexes);
1044   }
1045 
redo()1046   void redo() override {
1047     // Remove entities from the map, keep them and their index in this class.
1048     {
1049       BulkMapEditorChange bulk(get_editor());
1050       entities = get_map().remove_entities(indexes);
1051     }
1052   }
1053 
1054 private:
1055   AddableEntities entities;    // Entities to remove and their indexes before removal (sorted).
1056   EntityIndexes indexes;  // Indexes before removal (redundant info).
1057 };
1058 
1059 }  // Anonymous namespace.
1060 
1061 /**
1062  * @brief Creates a map editor.
1063  * @param quest The quest containing the file.
1064  * @param path Path of the map data file to open.
1065  * @param parent The parent object or nullptr.
1066  * @throws EditorException If the file could not be opened.
1067  */
MapEditor(Quest & quest,const QString & path,QWidget * parent)1068 MapEditor::MapEditor(Quest& quest, const QString& path, QWidget* parent) :
1069   Editor(quest, path, parent),
1070   map_id(),
1071   map(nullptr),
1072   entity_creation_toolbar(nullptr),
1073   status_bar(nullptr) {
1074 
1075   ui.setupUi(this);
1076   build_entity_creation_toolbar();
1077   build_status_bar();
1078 
1079   // Get the map.
1080   ResourceType resource_type;
1081   QString map_id;
1082   quest.check_exists(path);
1083   if (!quest.is_resource_element(path, resource_type, map_id) ||
1084       resource_type != ResourceType::MAP) {
1085     throw EditorException(tr("File '%1' is not a map").arg(path));
1086   }
1087   this->map_id = map_id;
1088 
1089   // Editor properties.
1090   set_title(tr("Map %1").arg(get_file_name_without_extension()));
1091   set_icon(QIcon(":/images/icon_resource_map.png"));
1092   set_close_confirm_message(
1093         tr("Map '%1' has been modified. Save changes?").arg(map_id));
1094   set_select_all_supported(true);
1095   set_zoom_supported(true);
1096   set_grid_supported(true);
1097   set_traversables_visibility_supported(true);
1098   set_obstacles_visibility_supported(true);
1099   set_entity_type_visibility_supported(true);
1100   set_export_to_image_supported(true);
1101 
1102   // Shortcuts.
1103   QAction* open_script_action = new QAction(this);
1104   open_script_action->setShortcut(tr("F4"));
1105   open_script_action->setShortcutContext(Qt::WindowShortcut);
1106   connect(open_script_action, &QAction::triggered,
1107           this, &MapEditor::open_script_requested);
1108   addAction(open_script_action);
1109 
1110   // Open the file.
1111   map = new MapModel(quest, map_id, this);
1112   get_undo_stack().setClean();
1113 
1114   // Prepare the gui.
1115   const int side_width = ui.map_properties_view->minimumSizeHint().width();
1116   ui.splitter->setSizes({ side_width, width() - side_width });
1117   ui.map_side_splitter->setStretchFactor(0, 0);  // Don't expand the map properties view
1118   ui.map_side_splitter->setStretchFactor(1, 1);  // but only the tileset view.
1119   ui.music_field->set_quest(quest);
1120   ui.music_field->get_selector().add_special_value("none", tr("<No music>"), 0);
1121   ui.music_field->get_selector().add_special_value("same", tr("<Same as before>"), 1);
1122   ui.tileset_field->set_resource_type(ResourceType::TILESET);
1123   ui.tileset_field->set_quest(quest);
1124   ui.patterns_tileset_field->set_resource_type(ResourceType::TILESET);
1125   ui.patterns_tileset_field->set_quest(quest);
1126   ui.patterns_tileset_field->add_special_value("", tr("(Tileset of the map)"), 0);
1127   ui.patterns_tileset_field->set_selected_id("");
1128   ui.border_set_tileset_field->set_resource_type(ResourceType::TILESET);
1129   ui.border_set_tileset_field->set_quest(quest);
1130   ui.border_set_tileset_field->add_special_value("", tr("(Tileset of the map)"), 0);
1131   ui.border_set_tileset_field->set_selected_id("");
1132   ui.map_view->set_map(map);
1133   ui.map_view->set_view_settings(get_view_settings());
1134   ui.map_view->set_common_actions(&get_common_actions());
1135   ui.tileset_view->set_read_only(true);
1136   ui.tileset_view->set_view_settings(tileset_view_settings);
1137   ui.size_field->config("x", 0, std::numeric_limits<int>::max(), 8);
1138   ui.size_field->set_tooltips(
1139     tr("Width of the map in pixels"),
1140     tr("Height of the map in pixels"));
1141 
1142   ui.location_field->config(",", 0, std::numeric_limits<int>::max(), 8);
1143   ui.location_field->set_tooltips(
1144     tr("Coordinates of the map in its world (useful to make adjacent scrolling maps)"),
1145     tr("Coordinates of the map in its world (useful to make adjacent scrolling maps)"));
1146 
1147   set_layers_supported(map->get_min_layer(), map->get_max_layer());
1148 
1149   update();
1150   load_settings();
1151 
1152   // Make connections.
1153   connect(&get_database(), &QuestDatabase::element_description_changed,
1154           this, &MapEditor::update_description_to_gui);
1155   connect(ui.description_field, &QLineEdit::editingFinished,
1156           this, &MapEditor::set_description_from_gui);
1157 
1158   connect(ui.size_field, &PairSpinBox::editing_finished,
1159           this, &MapEditor::change_size_requested);
1160   connect(map, &MapModel::size_changed,
1161           this, &MapEditor::update_size_field);
1162 
1163   connect(ui.min_layer_field, &QSpinBox::editingFinished,
1164           this, &MapEditor::change_min_layer_requested);
1165   connect(ui.max_layer_field, &QSpinBox::editingFinished,
1166           this, &MapEditor::change_max_layer_requested);
1167   connect(map, &MapModel::layer_range_changed,
1168           this, &MapEditor::layer_range_changed);
1169 
1170   connect(ui.world_check_box, &QCheckBox::stateChanged,
1171           this, &MapEditor::world_check_box_changed);
1172   connect(ui.world_field, &QLineEdit::editingFinished,
1173           this, &MapEditor::change_world_requested);
1174   connect(map, &MapModel::world_changed,
1175           this, &MapEditor::update_world_field);
1176 
1177   connect(ui.floor_check_box, &QCheckBox::stateChanged,
1178           this, &MapEditor::floor_check_box_changed);
1179   connect(ui.floor_field, &QSpinBox::editingFinished,
1180           this, &MapEditor::change_floor_requested);
1181   connect(map, &MapModel::floor_changed,
1182           this, &MapEditor::update_floor_field);
1183 
1184   connect(ui.location_field, &PairSpinBox::editing_finished,
1185           this, &MapEditor::change_location_requested);
1186   connect(map, &MapModel::location_changed,
1187           this, &MapEditor::update_location_field);
1188 
1189   connect(ui.tileset_field, static_cast<void (ResourceSelector::*)(const QString&)>(&ResourceSelector::activated),
1190           this, &MapEditor::tileset_selector_activated);
1191   connect(map, &MapModel::tileset_id_changed,
1192           this, &MapEditor::tileset_id_changed);
1193   connect(ui.tileset_edit_button, &QToolButton::clicked,
1194           this, [this]() {
1195       open_tileset_requested(ui.tileset_field->get_selected_id());
1196   });
1197   connect(ui.patterns_tileset_field, static_cast<void (ResourceSelector::*)(const QString&)>(&ResourceSelector::currentIndexChanged),
1198           this, &MapEditor::update_tileset_view);
1199   connect(ui.patterns_tileset_edit_button, &QToolButton::clicked,
1200           this, [this]() {
1201       open_tileset_requested(ui.patterns_tileset_field->get_selected_id());
1202   });
1203   connect(ui.border_set_tileset_field, static_cast<void (ResourceSelector::*)(const QString&)>(&ResourceSelector::activated),
1204           this, &MapEditor::border_set_tileset_changed);
1205   connect(ui.border_set_tileset_edit_button, &QToolButton::clicked,
1206           this, [this]() {
1207       open_tileset_requested(ui.border_set_tileset_field->get_selected_id());
1208   });
1209 
1210   connect(ui.music_field, &MusicChooser::activated,
1211           this, &MapEditor::music_selector_activated);
1212   connect(map, &MapModel::music_id_changed,
1213           this, &MapEditor::update_music_field);
1214 
1215   connect(ui.generate_borders_button, &QPushButton::clicked,
1216           this, [this]() {
1217     generate_borders_requested(ui.map_view->get_selected_entities());
1218   });
1219 
1220   connect(ui.open_script_button, &QToolButton::clicked,
1221           this, &MapEditor::open_script_requested);
1222 
1223   connect(ui.map_view, &MapView::edit_entity_requested,
1224           this, &MapEditor::edit_entity_requested);
1225   connect(ui.map_view, &MapView::move_entities_requested,
1226           this, &MapEditor::move_entities_requested);
1227   connect(ui.map_view, &MapView::resize_entities_requested,
1228           this, &MapEditor::resize_entities_requested);
1229   connect(ui.map_view, &MapView::convert_tiles_requested,
1230           this, &MapEditor::convert_tiles_requested);
1231   connect(ui.map_view, &MapView::change_tiles_pattern_requested,
1232           this, &MapEditor::change_tiles_pattern_requested);
1233   connect(ui.map_view, &MapView::set_entities_direction_requested,
1234           this, &MapEditor::set_entities_direction_requested);
1235   connect(ui.map_view, &MapView::set_entities_layer_requested,
1236           this, &MapEditor::set_entities_layer_requested);
1237   connect(ui.map_view, &MapView::increase_entities_layer_requested,
1238           this, &MapEditor::increase_entities_layer_requested);
1239   connect(ui.map_view, &MapView::decrease_entities_layer_requested,
1240           this, &MapEditor::decrease_entities_layer_requested);
1241   connect(ui.map_view, &MapView::bring_entities_to_front_requested,
1242           this, &MapEditor::bring_entities_to_front_requested);
1243   connect(ui.map_view, &MapView::bring_entities_to_back_requested,
1244           this, &MapEditor::bring_entities_to_back_requested);
1245   connect(ui.map_view, &MapView::add_entities_requested,
1246           this, &MapEditor::add_entities_requested);
1247   connect(ui.map_view, &MapView::remove_entities_requested,
1248           this, &MapEditor::remove_entities_requested);
1249   connect(ui.map_view, &MapView::generate_borders_requested,
1250           this, &MapEditor::generate_borders_requested);
1251   connect(ui.map_view, &MapView::stopped_state,
1252           this, &MapEditor::uncheck_entity_creation_buttons);
1253   connect(ui.map_view, &MapView::undo_requested,
1254           this, &MapEditor::undo);
1255 
1256   connect(ui.map_view->get_scene(), &MapScene::selectionChanged,
1257           this, &MapEditor::map_selection_changed);
1258   connect(map, &MapModel::bulk_mode_changed,
1259           this, &MapEditor::map_bulk_mode_changed);
1260 }
1261 
1262 /**
1263  * @brief Returns the map model being edited.
1264  * @return The map model.
1265  */
get_map()1266 MapModel& MapEditor::get_map() {
1267   return *map;
1268 }
1269 
1270 /**
1271  * @brief Returns the map graphics view.
1272  * @return The graphics view.
1273  */
get_map_view()1274 MapView& MapEditor::get_map_view() {
1275   return *ui.map_view;
1276 }
1277 
1278 /**
1279  * @brief Initializes the entity creation toolbar.
1280  *
1281  * The entity creation toolbar is not made with Qt designer because
1282  * - one cannot create QToolBar widgets with Qt designer,
1283  * - we iterate on entity types to build all of them more easily.
1284  */
build_entity_creation_toolbar()1285 void MapEditor::build_entity_creation_toolbar() {
1286 
1287   entity_creation_toolbar = new QToolBar(this);
1288 
1289   // List of types proposed in the toolbar.
1290   // The list is specified here manually because we want to control the order
1291   // and all types are not included (dynamic tiles are omitted).
1292   const std::vector<std::pair<EntityType, QString>> types_in_toolbar = {
1293     { EntityType::TILE, tr("Add tile") },
1294     { EntityType::DESTINATION, tr("Add destination") },
1295     { EntityType::TELETRANSPORTER, tr("Add teletransporter") },
1296     { EntityType::PICKABLE, tr("Add pickable") },
1297     { EntityType::DESTRUCTIBLE, tr("Add destructible") },
1298     { EntityType::CHEST, tr("Add chest") },
1299     { EntityType::JUMPER, tr("Add jumper") },
1300     { EntityType::ENEMY, tr("Add enemy") },
1301     { EntityType::NPC, tr("Add non-playing character") },
1302     { EntityType::BLOCK, tr("Add block") },
1303     { EntityType::SWITCH, tr("Add switch") },
1304     { EntityType::WALL, tr("Add wall") },
1305     { EntityType::SENSOR, tr("Add sensor") },
1306     { EntityType::CRYSTAL, tr("Add crystal") },
1307     { EntityType::CRYSTAL_BLOCK, tr("Add crystal block") },
1308     { EntityType::SHOP_TREASURE, tr("Add shop treasure") },
1309     { EntityType::STREAM, tr("Add stream") },
1310     { EntityType::DOOR, tr("Add door") },
1311     { EntityType::STAIRS, tr("Add stairs") },
1312     { EntityType::SEPARATOR, tr("Add separator") },
1313     { EntityType::CUSTOM, tr("Add custom entity") }
1314   };
1315 
1316   for (const auto& pair : types_in_toolbar) {
1317     EntityType type = pair.first;
1318     QString text = pair.second;
1319     QString icon_name = ":/images/entity_" + EntityTraits::get_lua_name(type) + ".png";
1320     QAction* action = new QAction(QIcon(icon_name), text, nullptr);
1321     action->setCheckable(true);
1322     action->setData(static_cast<int>(type));
1323     entity_creation_toolbar->addAction(action);
1324     connect(action, &QAction::triggered, [this, type](bool checked) {
1325       entity_creation_button_triggered(type, checked);
1326     });
1327   }
1328   entity_creation_toolbar->setIconSize(QSize(32, 32));
1329   entity_creation_toolbar->setStyleSheet("spacing: 0");
1330 
1331   ui.entity_creation_layout->insertWidget(0, entity_creation_toolbar);
1332 }
1333 
1334 /**
1335  * @brief Creates a status bar in the map view.
1336  *
1337  * The status bar is not made with Qt designer because
1338  * one cannot create QStatusBar widgets with Qt designer.
1339  */
build_status_bar()1340 void MapEditor::build_status_bar() {
1341 
1342   status_bar = new QStatusBar();
1343   ui.entity_creation_layout->addWidget(status_bar);
1344 
1345   connect(ui.map_view, &MapView::mouse_map_coordinates_changed,
1346           this, &MapEditor::update_status_bar);
1347   connect(ui.map_view, &MapView::mouse_left,
1348           this, &MapEditor::update_status_bar);
1349 }
1350 
1351 /**
1352  * @copydoc Editor::save
1353  */
save()1354 void MapEditor::save() {
1355 
1356   map->save();
1357 }
1358 
1359 /**
1360  * @copydoc Editor::export_to_image
1361  */
export_to_image()1362 void MapEditor::export_to_image() {
1363 
1364   QString map_id_without_dirs = map->get_map_id().section("/", -1, -1);
1365   const QString& file_name = QFileDialog::getSaveFileName(
1366         this,
1367         tr("Save map as PNG file"),
1368         QString("%1/%2.png").arg(get_quest().get_root_path(), map_id_without_dirs),
1369         tr("PNG image (*.png)"));
1370 
1371   if (!file_name.isEmpty()) {
1372     QImage image = ui.map_view->export_to_image();
1373     image.save(file_name);
1374   }
1375 }
1376 
1377 /**
1378  * @copydoc Editor::can_cut
1379  */
can_cut() const1380 bool MapEditor::can_cut() const {
1381   return can_copy();
1382 }
1383 
1384 /**
1385  * @copydoc Editor::cut
1386  */
cut()1387 void MapEditor::cut() {
1388 
1389   ui.map_view->cut();
1390 }
1391 
1392 /**
1393  * @copydoc Editor::can_copy
1394  */
can_copy() const1395 bool MapEditor::can_copy() const {
1396 
1397   return !ui.map_view->is_selection_empty();
1398 }
1399 
1400 /**
1401  * @copydoc Editor::copy
1402  */
copy()1403 void MapEditor::copy() {
1404 
1405   ui.map_view->copy();
1406 }
1407 
1408 /**
1409  * @copydoc Editor::can_paste
1410  */
can_paste() const1411 bool MapEditor::can_paste() const {
1412   return true;
1413 }
1414 
1415 /**
1416  * @copydoc Editor::paste
1417  */
paste()1418 void MapEditor::paste() {
1419 
1420   ui.map_view->paste();
1421 }
1422 
1423 /**
1424  * @copydoc Editor::select_all
1425  */
select_all()1426 void MapEditor::select_all() {
1427 
1428   MapScene* scene = ui.map_view->get_scene();
1429   if (scene != nullptr) {
1430     scene->select_all_except_locked();
1431   }
1432 }
1433 
1434 /**
1435  * @copydoc Editor::unselect_all
1436  */
unselect_all()1437 void MapEditor::unselect_all() {
1438 
1439   MapScene* scene = ui.map_view->get_scene();
1440   if (scene != nullptr) {
1441     scene->unselect_all();
1442   }
1443 }
1444 
1445 /**
1446  * @copydoc Editor::reload_settings
1447  */
reload_settings()1448 void MapEditor::reload_settings() {
1449 
1450   EditorSettings settings;
1451 
1452   MapScene* main_scene = ui.map_view->get_scene();
1453   if (main_scene != nullptr) {
1454     QBrush brush(settings.get_value_color(EditorSettings::map_main_background));
1455     main_scene->setBackgroundBrush(brush);
1456   }
1457 
1458   TilesetScene* tileset_scene = ui.tileset_view->get_scene();
1459   if (tileset_scene != nullptr) {
1460     QBrush brush(
1461       settings.get_value_color(EditorSettings::map_tileset_background));
1462     tileset_scene->setBackgroundBrush(brush);
1463   }
1464 
1465   get_view_settings().set_grid_style(static_cast<GridStyle>(
1466     settings.get_value_int(EditorSettings::map_grid_style)));
1467   get_view_settings().set_grid_color(
1468     settings.get_value_color(EditorSettings::map_grid_color));
1469 }
1470 
1471 /**
1472  * @brief Updates everything in the gui.
1473  */
update()1474 void MapEditor::update() {
1475 
1476   update_map_id_field();
1477   update_description_to_gui();
1478   update_size_field();
1479   update_min_layer_field();
1480   update_max_layer_field();
1481   update_world_field();
1482   update_floor_field();
1483   update_location_field();
1484   update_tileset_field();
1485   update_music_field();
1486   border_set_tileset_changed();
1487   tileset_id_changed(map->get_tileset_id());
1488 }
1489 
1490 /**
1491  * @brief Updates the map id displaying.
1492  */
update_map_id_field()1493 void MapEditor::update_map_id_field() {
1494 
1495   ui.map_id_field->setText(map_id);
1496 }
1497 
1498 /**
1499  * @brief Slot called when the user wants to open the map script.
1500  */
open_script_requested()1501 void MapEditor::open_script_requested() {
1502 
1503   emit open_file_requested(
1504     get_quest(), get_quest().get_map_script_path(map->get_map_id()));
1505 }
1506 
1507 /**
1508  * @brief Updates the content of the map description text edit.
1509  */
update_description_to_gui()1510 void MapEditor::update_description_to_gui() {
1511 
1512   QString description = get_database().get_description(
1513     ResourceType::MAP, map_id);
1514   if (ui.description_field->text() != description) {
1515     ui.description_field->setText(description);
1516   }
1517 }
1518 
1519 /**
1520  * @brief Modifies the map description in the quest resource list with
1521  * the new text entered by the user.
1522  *
1523  * If the new description is invalid, an error dialog is shown.
1524  */
set_description_from_gui()1525 void MapEditor::set_description_from_gui() {
1526 
1527   QString description = ui.description_field->text();
1528   if (description == get_database().get_description(ResourceType::MAP, map_id)) {
1529     return;
1530   }
1531 
1532   if (description.isEmpty()) {
1533     GuiTools::error_dialog(tr("Invalid description"));
1534     update_description_to_gui();
1535     return;
1536   }
1537 
1538   const bool was_blocked = blockSignals(true);
1539   try {
1540     get_database().set_description(ResourceType::MAP, map_id, description);
1541     get_database().save();
1542   }
1543   catch (const EditorException& ex) {
1544     ex.print_message();
1545   }
1546   update_description_to_gui();
1547   blockSignals(was_blocked);
1548 }
1549 
1550 /**
1551  * @brief Updates the size field with the data from the model.
1552  */
update_size_field()1553 void MapEditor::update_size_field() {
1554 
1555   ui.size_field->set_size(map->get_size());
1556 }
1557 
1558 /**
1559  * @brief Modifies the map size with new values entered by the user.
1560  */
change_size_requested()1561 void MapEditor::change_size_requested() {
1562 
1563   QSize size = ui.size_field->get_size();
1564   if (size == map->get_size()) {
1565     return;
1566   }
1567   try_command(new SetSizeCommand(*this, size));
1568 }
1569 
1570 /**
1571  * @brief Updates the minimum layer field with the data from the model.
1572  */
update_min_layer_field()1573 void MapEditor::update_min_layer_field() {
1574 
1575   ui.min_layer_field->setValue(map->get_min_layer());
1576 }
1577 
1578 /**
1579  * @brief Modifies the minimum layer with new values entered by the user.
1580  */
change_min_layer_requested()1581 void MapEditor::change_min_layer_requested() {
1582 
1583   int min_layer = ui.min_layer_field->value();
1584 
1585   if (min_layer == map->get_min_layer()) {
1586     return;
1587   }
1588 
1589   if (min_layer > map->get_min_layer()) {
1590     // Reducing the number of layers: ask the user confirmation if entities are removed.
1591     int num_entities_removed = 0;
1592     for (int layer = map->get_min_layer(); layer < min_layer; ++layer) {
1593       num_entities_removed += map->get_num_entities(layer);
1594     }
1595     if (num_entities_removed > 0) {
1596       // Block spinbox signals to avoid reentrant calls to this function
1597       // (because the dialog box takes focus from the spinbox, trigerring
1598       // its signal again).
1599       const bool was_blocked = ui.min_layer_field->signalsBlocked();
1600       ui.min_layer_field->blockSignals(true);
1601 
1602       QMessageBox::StandardButton answer = QMessageBox::warning(
1603           this,
1604           tr("Layer not empty"),
1605           tr("This layer is not empty: %1 entities will be destroyed.").arg(num_entities_removed),
1606           QMessageBox::Ok | QMessageBox::Cancel,
1607           QMessageBox::Ok
1608       );
1609 
1610       ui.min_layer_field->blockSignals(was_blocked);
1611 
1612       if (answer == QMessageBox::Cancel) {
1613         update_min_layer_field();
1614         return;
1615       }
1616     }
1617   }
1618 
1619   try_command(new SetMinLayerCommand(*this, min_layer));
1620 }
1621 
1622 /**
1623  * @brief Updates the maximum layer field with the data from the model.
1624  */
update_max_layer_field()1625 void MapEditor::update_max_layer_field() {
1626 
1627   ui.max_layer_field->setValue(map->get_max_layer());
1628 }
1629 
1630 /**
1631  * @brief Updates the UI when the range of layers in the model has changed.
1632  */
layer_range_changed()1633 void MapEditor::layer_range_changed() {
1634 
1635   const int min_layer = get_map().get_min_layer();
1636   const int max_layer = get_map().get_max_layer();
1637 
1638   // Update the spinboxes.
1639   update_min_layer_field();
1640   update_max_layer_field();
1641 
1642   // Notify the editor.
1643   set_layers_supported(min_layer, max_layer);
1644 
1645   // Notify the view settings.
1646   get_view_settings().set_layer_range(min_layer, max_layer);
1647 }
1648 
1649 /**
1650  * @brief Modifies the maximum layer with new values entered by the user.
1651  */
change_max_layer_requested()1652 void MapEditor::change_max_layer_requested() {
1653 
1654   int max_layer = ui.max_layer_field->value();
1655 
1656   if (max_layer == map->get_max_layer()) {
1657     return;
1658   }
1659 
1660   if (max_layer < map->get_max_layer()) {
1661     // Reducing the number of layers: ask the user confirmation if entities are removed.
1662     int num_entities_removed = 0;
1663     for (int layer = max_layer + 1; layer <= map->get_max_layer(); ++layer) {
1664       num_entities_removed += map->get_num_entities(layer);
1665     }
1666     if (num_entities_removed > 0) {
1667       // Block spinbox signals to avoid reentrant calls to this function
1668       // (because the dialog box takes focus from the spinbox, trigerring
1669       // its signal again).
1670       const bool was_blocked = ui.max_layer_field->signalsBlocked();
1671       ui.max_layer_field->blockSignals(true);
1672 
1673       QMessageBox::StandardButton answer = QMessageBox::warning(
1674           this,
1675           tr("Layer not empty"),
1676           tr("This layer is not empty: %1 entities will be destroyed.").arg(num_entities_removed),
1677           QMessageBox::Ok | QMessageBox::Cancel,
1678           QMessageBox::Ok
1679       );
1680 
1681       ui.max_layer_field->blockSignals(was_blocked);
1682 
1683       if (answer == QMessageBox::Cancel) {
1684         update_max_layer_field();
1685         return;
1686       }
1687     }
1688   }
1689 
1690   try_command(new SetMaxLayerCommand(*this, max_layer));
1691 }
1692 
1693 /**
1694  * @brief Updates the world field with the data from the model.
1695  */
update_world_field()1696 void MapEditor::update_world_field() {
1697 
1698   const QString& world = map->get_world();
1699   if (world.isEmpty()) {
1700     ui.world_check_box->setChecked(false);
1701     ui.world_field->setEnabled(false);
1702   }
1703   else {
1704     ui.world_check_box->setChecked(true);
1705     ui.world_field->setEnabled(true);
1706     ui.world_field->setText(world);
1707   }
1708 }
1709 
1710 /**
1711  * @brief Slot called when the user clicks the "Set a world" checkbox.
1712  */
world_check_box_changed()1713 void MapEditor::world_check_box_changed() {
1714 
1715   bool checked = ui.world_check_box->isChecked();
1716   if (checked) {
1717     ui.world_field->setEnabled(true);
1718     if (!map->has_world() &&
1719         !ui.world_field->text().isEmpty()) {
1720       // Use the text that was still in the disabled field.
1721       try_command(new SetWorldCommand(*this, ui.world_field->text()));
1722     }
1723   }
1724   else {
1725     ui.world_field->setEnabled(false);
1726     if (map->has_world()) {
1727       // Remove the world but keep the text in the field.
1728       try_command(new SetWorldCommand(*this, ""));
1729     }
1730   }
1731 }
1732 
1733 /**
1734  * @brief Changes the world value with the new text entered by the user.
1735  */
change_world_requested()1736 void MapEditor::change_world_requested() {
1737 
1738   QString world = ui.world_field->text();
1739   if (world == map->get_world()) {
1740     return;
1741   }
1742   try_command(new SetWorldCommand(*this, world));
1743 }
1744 
1745 /**
1746  * @brief Updates the floor field with the data from the model.
1747  */
update_floor_field()1748 void MapEditor::update_floor_field() {
1749 
1750   int floor = map->get_floor();
1751   if (floor == MapModel::NO_FLOOR) {
1752     ui.floor_check_box->setChecked(false);
1753     ui.floor_field->setEnabled(false);
1754   }
1755   else {
1756     ui.floor_check_box->setChecked(true);
1757     ui.floor_field->setEnabled(true);
1758     ui.floor_field->setValue(floor);
1759   }
1760 }
1761 
1762 /**
1763  * @brief Slot called when the user clicks the "Set a floor" checkbox.
1764  */
floor_check_box_changed()1765 void MapEditor::floor_check_box_changed() {
1766 
1767   bool checked = ui.floor_check_box->isChecked();
1768   if (checked) {
1769     ui.floor_field->setEnabled(true);
1770     if (!map->has_floor()) {
1771       // Use the value that was still in the disabled field.
1772       try_command(new SetFloorCommand(*this, ui.floor_field->value()));
1773     }
1774   }
1775   else {
1776     ui.floor_field->setEnabled(false);
1777     if (map->has_floor()) {
1778       // Remove the floor but keep the value in the field.
1779       try_command(new SetFloorCommand(*this, MapModel::NO_FLOOR));
1780     }
1781   }
1782 }
1783 
1784 /**
1785  * @brief Changes the floor value with the new text entered by the user.
1786  */
change_floor_requested()1787 void MapEditor::change_floor_requested() {
1788 
1789   int floor = ui.floor_field->value();
1790   if (floor == map->get_floor()) {
1791     return;
1792   }
1793   try_command(new SetFloorCommand(*this, floor));
1794 }
1795 
1796 /**
1797  * @brief Updates the location field with the data from the model.
1798  */
update_location_field()1799 void MapEditor::update_location_field() {
1800 
1801   ui.location_field->set_point(map->get_location());
1802 }
1803 
1804 /**
1805  * @brief Modifies the map location with new values entered by the user.
1806  */
change_location_requested()1807 void MapEditor::change_location_requested() {
1808 
1809   QPoint location = ui.location_field->get_point();
1810   if (location == map->get_location()) {
1811     return;
1812   }
1813   try_command(new SetLocationCommand(*this, location));
1814 }
1815 
1816 /**
1817  * @brief Updates the tileset selector with the data from the model.
1818  */
update_tileset_field()1819 void MapEditor::update_tileset_field() {
1820 
1821   ui.tileset_field->set_selected_id(map->get_tileset_id());
1822 }
1823 
1824 /**
1825  * @brief Slot called when the user changes the tileset in the selector.
1826  */
tileset_selector_activated()1827 void MapEditor::tileset_selector_activated() {
1828 
1829   const QString& old_tileset_id = map->get_tileset_id();
1830   const QString& new_tileset_id = ui.tileset_field->get_selected_id();
1831   if (new_tileset_id == old_tileset_id) {
1832     // No change.
1833     return;
1834   }
1835 
1836   try_command(new SetTilesetCommand(*this, new_tileset_id));
1837 }
1838 
1839 /**
1840  * @brief Slot called when the user wants to open the given tileset.
1841  * @param Id of the tileset to open (empty means the one of the map).
1842  */
open_tileset_requested(const QString & tileset_id)1843 void MapEditor::open_tileset_requested(const QString& tileset_id) {
1844 
1845   QString id = !tileset_id.isEmpty() ? tileset_id : map->get_tileset_id();
1846   emit open_file_requested(
1847         get_quest(), get_quest().get_tileset_data_file_path(id));
1848 }
1849 
1850 /**
1851  * @brief Updates the music selector with the data from the model.
1852  */
update_music_field()1853 void MapEditor::update_music_field() {
1854 
1855   // Update the music selector.
1856   const QString& music_id = map->get_music_id();
1857   ui.music_field->set_selected_id(music_id);
1858 }
1859 
1860 /**
1861  * @brief Slot called when the user changes the music in the selector.
1862  */
music_selector_activated()1863 void MapEditor::music_selector_activated() {
1864 
1865   const QString& old_music_id = map->get_music_id();
1866   const QString& new_music_id = ui.music_field->get_selected_id();
1867   if (new_music_id == old_music_id) {
1868     // No change.
1869     return;
1870   }
1871 
1872   // Stop playing any music if there is a change.
1873   Audio::stop_music(get_quest());
1874 
1875   try_command(new SetMusicCommand(*this, new_music_id));
1876 }
1877 
1878 /**
1879  * @brief Updates the content of the tileset view.
1880  */
update_tileset_view()1881 void MapEditor::update_tileset_view() {
1882 
1883   QString tileset_id = ui.patterns_tileset_field->get_selected_id();
1884   if (tileset_id.isEmpty()) {
1885     tileset_id = get_map().get_tileset_id();
1886   }
1887   TilesetModel* tileset = get_quest().get_tileset(tileset_id);
1888   if (tileset != nullptr) {
1889     ui.tileset_view->set_model(tileset);
1890   }
1891 }
1892 
1893 /**
1894  * @brief Slot called when another tileset is set on the map.
1895  * @param tileset_id The new tileset id.
1896  */
tileset_id_changed(const QString & tileset_id)1897 void MapEditor::tileset_id_changed(const QString& tileset_id) {
1898 
1899   Q_UNUSED(tileset_id);
1900 
1901   // Show the correct tileset in the various comboboxes and views.
1902   update_tileset_field();
1903   update_tileset_view();
1904   border_set_tileset_changed();
1905 
1906   // Watch the pattern selection of the tileset view to correctly add new tiles.
1907   connect(ui.tileset_view, &TilesetView::selection_changed_by_user,
1908           this, &MapEditor::tileset_selection_changed);
1909 }
1910 
1911 /**
1912  * @brief Called when the user selects a tileset in the border sets view.
1913  */
border_set_tileset_changed()1914 void MapEditor::border_set_tileset_changed() {
1915 
1916   QString tileset_id = ui.border_set_tileset_field->get_selected_id();
1917   if (tileset_id.isEmpty()) {
1918     tileset_id = get_map().get_tileset_id();
1919   }
1920   ui.border_set_field->set_tileset_id(get_quest(), tileset_id);
1921 }
1922 
1923 /**
1924  * @brief Slot called when the user changes the patterns selection in the tileset view.
1925  */
tileset_selection_changed()1926 void MapEditor::tileset_selection_changed() {
1927 
1928   uncheck_entity_creation_buttons();
1929   QString optional_tileset_id = ui.patterns_tileset_field->get_selected_id();
1930   QString tileset_id = !optional_tileset_id.isEmpty() ? optional_tileset_id : get_map().get_tileset_id();
1931   TilesetModel* tileset = get_quest().get_tileset(tileset_id);
1932   if (tileset == nullptr) {
1933     return;
1934   }
1935   ui.map_view->tileset_selection_changed(optional_tileset_id, tileset->get_selected_indexes());
1936 }
1937 
1938 /**
1939  * @brief Slot called when the user changes the selection in the map.
1940  */
map_selection_changed()1941 void MapEditor::map_selection_changed() {
1942 
1943   if (get_map().is_bulk_mode()) {
1944     // Ignore selection changes during bulk updates.
1945     return;
1946   }
1947 
1948   // Update whether cut/copy are available.
1949   bool empty_selection = ui.map_view->is_selection_empty();
1950   can_cut_changed(!empty_selection);
1951   can_copy_changed(!empty_selection);
1952 
1953   // Update the tileset view with the selected tile patterns.
1954   const EntityIndexes& entity_indexes = ui.map_view->get_selected_entities();
1955 
1956   // See if all selected tiles have the same tileset.
1957   MapModel& map = get_map();
1958   QString optional_tileset_id = entity_indexes.isEmpty() ? QString() : map.get_entity_field(entity_indexes.first(), "tileset").toString();
1959   for (const EntityIndex& entity_index : entity_indexes) {
1960     EntityType entity_type = map.get_entity_type(entity_index);
1961     if (entity_type != EntityType::TILE && entity_type != EntityType::DYNAMIC_TILE) {
1962       continue;
1963     }
1964     if (map.get_entity_field(entity_index, "tileset").toString() != optional_tileset_id) {
1965       // Use the tileset of the map if the selection has multiple tilesets.
1966       optional_tileset_id = "";
1967     }
1968   }
1969 
1970   ui.patterns_tileset_field->set_selected_id(optional_tileset_id);
1971 
1972   QString tileset_id = !optional_tileset_id.isEmpty() ? optional_tileset_id : map.get_tileset_id();
1973   TilesetModel* tileset = get_quest().get_tileset(tileset_id);
1974   if (tileset == nullptr) {
1975     return;
1976   }
1977 
1978   QList<int> pattern_indexes;
1979   for (const EntityIndex& entity_index : entity_indexes) {
1980     if (!map.has_entity_field(entity_index, "tileset")) {
1981       continue;
1982     }
1983     QString pattern_id = map.get_entity_field(entity_index, "pattern").toString();
1984     if (pattern_id.isEmpty()) {
1985       continue;
1986     }
1987     if (map.get_entity_field(entity_index, "tileset").toString() == optional_tileset_id) {
1988       pattern_indexes << tileset->id_to_index(pattern_id);
1989     }
1990   }
1991   tileset->set_selected_indexes(pattern_indexes);
1992 }
1993 
1994 /**
1995  * @brief Called when bulk mode is enabled or disabled on the map editor.
1996  * @param bulk_mode Whether bulk mode is active.
1997  */
map_bulk_mode_changed(bool bulk_mode)1998 void MapEditor::map_bulk_mode_changed(bool bulk_mode) {
1999 
2000   if (!bulk_mode) {
2001     // map_selection_changed() was just unblocked: call it now in case we are not up to date.
2002     map_selection_changed();
2003   }
2004 }
2005 
2006 /**
2007  * @brief Updates existing teletransporters in all maps when a destination
2008  * of this map is renamed.
2009  * @param command The edit entity command that changes a destination's name.
2010  * @param name_before The old destination name.
2011  * @param name_after The new destination name.
2012  */
refactor_destination_name(QUndoCommand * command,const QString & name_before,const QString & name_after)2013 void MapEditor::refactor_destination_name(
2014     QUndoCommand* command,
2015     const QString& name_before,
2016     const QString& name_after
2017 ) {
2018   Refactoring refactoring([=]() {
2019 
2020     // Perform the entity edition.
2021     if (!try_command(command)) {
2022       return QStringList();
2023     }
2024 
2025     // Update teletransporters in this map.
2026     const EntityIndexes& teletransporter_indexes =
2027         map->find_entities_of_type(EntityType::TELETRANSPORTER);
2028     for (const EntityIndex& index : teletransporter_indexes) {
2029       const QString& destination_map_id =
2030           map->get_entity_field(index, "destination_map").toString();
2031       const QString& destination_name =
2032           map->get_entity_field(index, "destination").toString();
2033 
2034       if (destination_map_id == this->map_id &&
2035           destination_name == name_before) {
2036         map->set_entity_field(index, "destination", name_after);
2037       }
2038     }
2039 
2040     // Save the map and clear the undo history.
2041     save();
2042     get_undo_stack().clear();
2043 
2044     // Update teletransporters in all other maps.
2045     return update_destination_name_in_other_maps(name_before, name_after);
2046   });
2047 
2048   refactoring.set_file_unsaved_allowed(get_file_path(), true);
2049 
2050   emit refactoring_requested(refactoring);
2051 }
2052 
2053 /**
2054  * @brief Updates existing teletransporters in other maps when a destination
2055  * of this map is renamed.
2056  * @param name_before The old destination name.
2057  * @param name_after The new destination name.
2058  * @return The list of files that were modified.
2059  * @throws EditorException In case of error.
2060  */
update_destination_name_in_other_maps(const QString & name_before,const QString & name_after)2061 QStringList MapEditor::update_destination_name_in_other_maps(
2062     const QString& name_before,
2063     const QString& name_after
2064 ) {
2065   QStringList modified_paths;
2066   const QStringList& map_ids = get_database().get_elements(ResourceType::MAP);
2067   for (const QString& map_id : map_ids) {
2068     if (map_id != this->map_id) {
2069       if (update_destination_name_in_map(map_id, name_before, name_after)) {
2070         modified_paths << get_quest().get_map_data_file_path(map_id);
2071       }
2072     }
2073   }
2074   return modified_paths;
2075 }
2076 
2077 /**
2078  * @brief Updates existing teletransporters in a map when a destination
2079  * of this map is renamed.
2080  * @param map_id Id of the map to update.
2081  * @param name_before The old destination name.
2082  * @param name_after The new destination name.
2083  * @return @c true if there was a change.
2084  * @throws EditorException In case of error.
2085  */
update_destination_name_in_map(const QString & map_id,const QString & name_before,const QString & name_after)2086 bool MapEditor::update_destination_name_in_map(
2087     const QString& map_id,
2088     const QString& name_before,
2089     const QString& name_after
2090 ) {
2091   // We don't load the entire map with all its entities for performance.
2092   // Instead, we just find and replace the appropriate text in the map
2093   // data file.
2094 
2095   QString path = get_quest().get_map_data_file_path(map_id);
2096 
2097   QString pattern = QString(
2098         "\n  destination_map = \"?%1\"?,\n"
2099         "  destination = \"%2\",\n").arg(
2100         QRegularExpression::escape(this->map_id), QRegularExpression::escape(name_before));
2101 
2102   QString replacement;
2103   if (name_after.isEmpty()) {
2104     replacement = QString(
2105         "\n  destination_map = \"%1\",\n").arg(this->map_id);
2106   }
2107   else {
2108     replacement = QString(
2109         "\n  destination_map = \"%1\",\n"
2110         "  destination = \"%2\",\n").arg(
2111               this->map_id, name_after);
2112   }
2113 
2114   return FileTools::replace_in_file(path, QRegularExpression(pattern), replacement);
2115 }
2116 
2117 /**
2118  * @brief Shows in the status bar information about the cursor.
2119  */
update_status_bar()2120 void MapEditor::update_status_bar() {
2121 
2122   if (status_bar == nullptr) {
2123     return;
2124   }
2125 
2126   // Show mouse coordinates and information about the entity under the mouse.
2127   QString mouse_coordinates_string;
2128   QPoint view_xy = ui.map_view->mapFromGlobal(QCursor::pos());
2129   int layer_under_mouse = map->get_min_layer();
2130 
2131   QString entity_string;
2132   EntityIndex index = ui.map_view->get_entity_index_under_cursor();
2133   if (index.is_valid()) {
2134     QString name = map->get_entity_name(index);
2135     QString type_name = EntityTraits::get_friendly_name(map->get_entity_type(index));
2136     entity_string = tr(" - %1").arg(type_name);
2137     if (!name.isEmpty()) {
2138       entity_string += tr(": %1").arg(name);
2139     }
2140     layer_under_mouse = map->get_entity_layer(index);
2141   }
2142 
2143   if (view_xy.x() >= 0 &&
2144       view_xy.x() < ui.map_view->width() &&
2145       view_xy.y() >= 0 &&
2146       view_xy.y() < ui.map_view->height()) {
2147     QPoint map_xy = ui.map_view->mapToScene(view_xy).toPoint() - MapScene::get_margin_top_left();
2148     QPoint snapped_xy(Point::round_8(map_xy));
2149     mouse_coordinates_string = tr("%1,%2,%3 ").
2150         arg(snapped_xy.x()).
2151         arg(snapped_xy.y()).
2152         arg(layer_under_mouse);
2153   }
2154 
2155   QString message = mouse_coordinates_string + entity_string;
2156   if (message.isEmpty()) {
2157     status_bar->clearMessage();
2158   }
2159   else {
2160     status_bar->showMessage(message);
2161   }
2162 }
2163 
2164 /**
2165  * @brief Slot called when the user wants to edit an entity.
2166  * @param index Index of the entity to change.
2167  * @param entity_after An entity representing the new values to set.
2168  */
edit_entity_requested(const EntityIndex & index,EntityModelPtr & entity_after)2169 void MapEditor::edit_entity_requested(const EntityIndex& index,
2170                                       EntityModelPtr& entity_after) {
2171 
2172   const QString& name_before = map->get_entity_name(index);
2173   const QString& name_after = entity_after->get_name();
2174   bool update_teletransporters = false;
2175 
2176   if (!name_before.isEmpty() &&
2177       name_after != name_before) {
2178     // When renaming a destination, the user may want to update existing teletransporters.
2179     const EntityModel& entity_before = map->get_entity(index);
2180     if (entity_before.get_type() == EntityType::DESTINATION) {
2181       const Destination& destination = static_cast<const Destination&>(entity_before);
2182       update_teletransporters = destination.get_update_teletransporters();
2183     }
2184   }
2185 
2186   EditEntityCommand* command = new EditEntityCommand(*this, index, std::move(entity_after));
2187   if (!update_teletransporters) {
2188     try_command(command);
2189   }
2190   else {
2191     refactor_destination_name(command, name_before, name_after);
2192   }
2193 }
2194 
2195 /**
2196  * @brief Slot called when the user wants to move entities.
2197  * @param indexes Indexes of the entities to move.
2198  * @param translation XY translation to make.
2199  * @param allow_merge_to_previous @c true to merge this move with the previous one if any.
2200  */
move_entities_requested(const EntityIndexes & indexes,const QPoint & translation,bool allow_merge_to_previous)2201 void MapEditor::move_entities_requested(const EntityIndexes& indexes,
2202                                         const QPoint& translation,
2203                                         bool allow_merge_to_previous) {
2204 
2205   if (indexes.isEmpty()) {
2206     return;
2207   }
2208 
2209   try_command(new MoveEntitiesCommand(*this, indexes, translation, allow_merge_to_previous));
2210 }
2211 
2212 /**
2213  * @brief Slot called when the user wants to resize entities.
2214  * @param boxes New bounding box of each entity to change.
2215  * @param allow_merge_to_previous @c true to merge this resizing with the previous one if any.
2216  */
resize_entities_requested(const QMap<EntityIndex,QRect> & boxes,bool allow_merge_to_previous)2217 void MapEditor::resize_entities_requested(const QMap<EntityIndex, QRect>& boxes,
2218                                           bool allow_merge_to_previous) {
2219 
2220   if (boxes.isEmpty()) {
2221     return;
2222   }
2223 
2224   try_command(new ResizeEntitiesCommand(*this, boxes, allow_merge_to_previous));
2225 }
2226 
2227 /**
2228  * @brief Slot called when the user wants to convert tiles to or from dynamic ones.
2229  * @param indexes Indexes of the tiles or dynamic tiles to convert.
2230  */
convert_tiles_requested(const EntityIndexes & indexes)2231 void MapEditor::convert_tiles_requested(const EntityIndexes& indexes) {
2232 
2233   if (indexes.isEmpty()) {
2234     return;
2235   }
2236 
2237   const bool dynamic = map->get_entity(indexes.first()).is_dynamic();
2238 
2239   for (const EntityIndex& index : indexes) {
2240     EntityType current_type = map->get_entity_type(index);
2241     if (current_type != EntityType::TILE && current_type != EntityType::DYNAMIC_TILE) {
2242       return;
2243     }
2244     if (map->get_entity(index).is_dynamic() != dynamic) {
2245       return;
2246     }
2247   }
2248 
2249   if (dynamic) {
2250     try_command(new ConvertTilesFromDynamicCommand(*this, indexes));
2251   }
2252   else {
2253     try_command(new ConvertTilesToDynamicCommand(*this, indexes));
2254   }
2255 }
2256 
2257 /**
2258  * @brief Slot called when the user wants to change the pattern of some tiles.
2259  * @param indexes Indexes of the tiles or dynamic tiles to change.
2260  */
change_tiles_pattern_requested(const EntityIndexes & indexes)2261 void MapEditor::change_tiles_pattern_requested(const EntityIndexes& indexes) {
2262 
2263   if (indexes.isEmpty()) {
2264     return;
2265   }
2266 
2267   QString tileset_id = map->get_entity_field(indexes.first(), "tileset").toString();
2268   for (const EntityIndex& index : indexes) {
2269     EntityType type = map->get_entity_type(index);
2270     if (type != EntityType::TILE && type != EntityType::DYNAMIC_TILE) {
2271       return;
2272     }
2273     if (map->get_entity_field(index, "tileset") != tileset_id) {
2274       return;
2275     }
2276   }
2277 
2278   if (tileset_id.isEmpty()) {
2279     tileset_id = map->get_tileset_id();
2280   }
2281 
2282   PatternPickerDialog dialog(*get_quest().get_tileset(tileset_id));
2283   int result = dialog.exec();
2284 
2285   if (result != QDialog::Accepted) {
2286     return;
2287   }
2288 
2289   QString pattern_id = dialog.get_pattern_id();
2290 
2291   bool pattern_changed = false;
2292   for (const EntityIndex& index : indexes) {
2293     if (pattern_id != map->get_entity_field(index, "pattern").toString()) {
2294       pattern_changed = true;
2295       break;
2296     }
2297   }
2298   if (!pattern_changed) {
2299     // No change.
2300     return;
2301   }
2302 
2303   try_command(new ChangeTilesPatternCommand(*this, indexes, pattern_id));
2304 }
2305 
2306 /**
2307  * @brief Slot called when the user wants to change the direction of some entities.
2308  * @param indexes Indexes of the entities to change.
2309  * @param direction The direction to set.
2310  */
set_entities_direction_requested(const EntityIndexes & indexes,int direction)2311 void MapEditor::set_entities_direction_requested(const EntityIndexes& indexes,
2312                                                  int direction) {
2313 
2314   if (indexes.isEmpty()) {
2315     return;
2316   }
2317 
2318   int num_directions;
2319   QString no_direction_text;
2320   if (!map->is_common_direction_rules(indexes, num_directions, no_direction_text)) {
2321     // Incompatible direction rules.
2322     return;
2323   }
2324 
2325   try_command(new SetEntitiesDirectionCommand(*this, indexes, direction));
2326 }
2327 
2328 /**
2329  * @brief Slot called when the user wants to change the layer of some entities.
2330  * @param indexes Indexes of the entities to change.
2331  * @param layer The layer to set.
2332  */
set_entities_layer_requested(const EntityIndexes & indexes,int layer)2333 void MapEditor::set_entities_layer_requested(const EntityIndexes& indexes,
2334                                              int layer) {
2335 
2336   if (indexes.isEmpty()) {
2337     return;
2338   }
2339 
2340   int common_layer = 0;
2341   if (map->is_common_layer(indexes, common_layer) &&
2342       layer == common_layer) {
2343     // Nothing to do.
2344     return;
2345   }
2346 
2347   try_command(new SetEntitiesLayerCommand(*this, indexes, layer));
2348 }
2349 
2350 /**
2351  * @brief Slot called when the user wants to bring some entities one layer up.
2352  * @param indexes Indexes of the entities to change.
2353  */
increase_entities_layer_requested(const EntityIndexes & indexes)2354 void MapEditor::increase_entities_layer_requested(const EntityIndexes& indexes) {
2355 
2356   if (indexes.isEmpty()) {
2357     return;
2358   }
2359 
2360   try_command(new IncreaseEntitiesLayerCommand(*this, indexes));
2361 }
2362 
2363 /**
2364  * @brief Slot called when the user wants to bring some entities one layer down.
2365  * @param indexes Indexes of the entities to change.
2366  */
decrease_entities_layer_requested(const EntityIndexes & indexes)2367 void MapEditor::decrease_entities_layer_requested(const EntityIndexes& indexes) {
2368 
2369   if (indexes.isEmpty()) {
2370     return;
2371   }
2372 
2373   try_command(new DecreaseEntitiesLayerCommand(*this, indexes));
2374 }
2375 
2376 /**
2377  * @brief Slot called when the user wants to bring some entities to the front.
2378  * @param indexes Indexes of the entities to change.
2379  */
bring_entities_to_front_requested(const EntityIndexes & indexes)2380 void MapEditor::bring_entities_to_front_requested(const EntityIndexes& indexes) {
2381 
2382   if (indexes.isEmpty()) {
2383     return;
2384   }
2385 
2386   try_command(new BringEntitiesToFrontCommand(*this, indexes));
2387 }
2388 
2389 /**
2390  * @brief Slot called when the user wants to bring some entities to the back.
2391  * @param indexes Indexes of the entities to change.
2392  */
bring_entities_to_back_requested(const EntityIndexes & indexes)2393 void MapEditor::bring_entities_to_back_requested(const EntityIndexes& indexes) {
2394 
2395   if (indexes.isEmpty()) {
2396     return;
2397   }
2398 
2399   try_command(new BringEntitiesToBackCommand(*this, indexes));
2400 }
2401 
2402 /**
2403  * @brief Slot called when the user wants to add entities.
2404  * @param entities Entities ready to be added to the map.
2405  * @param replace_selection @c true to clear the previous selection.
2406  * Newly created entities will be selected in all cases.
2407  */
add_entities_requested(AddableEntities & entities,bool replace_selection)2408 void MapEditor::add_entities_requested(AddableEntities& entities, bool replace_selection) {
2409 
2410   if (entities.empty()) {
2411     return;
2412   }
2413 
2414   try_command(new AddEntitiesCommand(*this, std::move(entities), replace_selection));
2415 }
2416 
2417 /**
2418  * @brief Slot called when the user wants to delete entities.
2419  * @param indexes Indexes of entities to remove.
2420  */
remove_entities_requested(const EntityIndexes & indexes)2421 void MapEditor::remove_entities_requested(const EntityIndexes& indexes) {
2422 
2423   if (indexes.empty()) {
2424     return;
2425   }
2426 
2427   try_command(new RemoveEntitiesCommand(*this, indexes));
2428 }
2429 
2430 /**
2431  * @brief Slot called when the user wants to generate borders around entities.
2432  * @param indexes Indexes of entities where to create borders.
2433  */
generate_borders_requested(const EntityIndexes & indexes)2434 void MapEditor::generate_borders_requested(const EntityIndexes& indexes) {
2435 
2436   if (indexes.empty()) {
2437     return;
2438   }
2439 
2440   QString tileset_id = ui.border_set_tileset_field->get_selected_id();
2441   QString border_set_id = ui.border_set_field->get_selected_border_set_id();
2442 
2443   if (border_set_id.isEmpty()) {
2444     return;
2445   }
2446 
2447   AutoTiler auto_tiler(get_map(), tileset_id, border_set_id, indexes);
2448   try_command(new AddEntitiesCommand(*this, auto_tiler.generate_border_tiles(), false));
2449 }
2450 
2451 /**
2452  * @brief This function is called when the user checks or unchecks a button of
2453  * the entity creation toolbar.
2454  * @param type Type of entity corresponding to the button.
2455  * @param checked Whether the button is checked or unchecked.
2456  */
entity_creation_button_triggered(EntityType type,bool checked)2457 void MapEditor::entity_creation_button_triggered(EntityType type, bool checked) {
2458 
2459   if (checked) {
2460     // Create a new entity of this type.
2461     EntityModels entities;
2462     entities.emplace_back(EntityModel::create(*map, type));
2463     const bool guess_layer = true;
2464     ui.map_view->start_state_adding_entities(std::move(entities), guess_layer);
2465 
2466     // Unselect patterns in the tileset.
2467     TilesetModel* tileset = map->get_tileset_model();
2468     if (tileset != nullptr) {
2469       tileset->clear_selection();
2470     }
2471 
2472     // Uncheck other entity creation buttons.
2473     const QList<QAction*>& actions = entity_creation_toolbar->actions();
2474     for (QAction* action : actions) {
2475       bool ok = false;
2476       EntityType action_type = static_cast<EntityType>(action->data().toInt(&ok));
2477       if (ok) {
2478         bool action_checked = (action_type == type);
2479         action->setChecked(action_checked);
2480       }
2481     }
2482   }
2483   else {
2484     // Stop adding entities.
2485     ui.map_view->start_state_doing_nothing();
2486   }
2487 }
2488 
2489 /**
2490  * @brief Unchecks all buttons of the entity creation toolbar.
2491  */
uncheck_entity_creation_buttons()2492 void MapEditor::uncheck_entity_creation_buttons() {
2493 
2494   const QList<QAction*>& actions = entity_creation_toolbar->actions();
2495   for (QAction* action : actions) {
2496     action->setChecked(false);
2497   }
2498 }
2499 
2500 /**
2501  * @brief Loads settings.
2502  */
load_settings()2503 void MapEditor::load_settings() {
2504 
2505   ViewSettings& view = get_view_settings();
2506   EditorSettings settings;
2507 
2508   view.set_zoom(settings.get_value_double(EditorSettings::map_main_zoom));
2509   view.set_grid_visible(
2510     settings.get_value_bool(EditorSettings::map_grid_show_at_opening));
2511   view.set_grid_size(settings.get_value_size(EditorSettings::map_grid_size));
2512 
2513   tileset_view_settings.set_zoom(
2514     settings.get_value_double(EditorSettings::map_tileset_zoom));
2515 
2516   reload_settings();
2517 }
2518 
2519 }
2520