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