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/tile.h"
18 #include "widgets/edit_entity_dialog.h"
19 #include "widgets/entity_item.h"
20 #include "widgets/enum_menus.h"
21 #include "widgets/gui_tools.h"
22 #include "widgets/map_scene.h"
23 #include "widgets/map_view.h"
24 #include "widgets/mouse_coordinates_tracking_tool.h"
25 #include "widgets/pan_tool.h"
26 #include "widgets/zoom_tool.h"
27 #include "point.h"
28 #include "quest.h"
29 #include "rectangle.h"
30 #include "tileset_model.h"
31 #include "view_settings.h"
32 #include <QAction>
33 #include <QActionGroup>
34 #include <QApplication>
35 #include <QClipboard>
36 #include <QDebug>
37 #include <QGraphicsItem>
38 #include <QMap>
39 #include <QMenu>
40 #include <QMouseEvent>
41 #include <QScrollBar>
42 #include <QtMath>
43 #include <QPainterPath>
44 
45 namespace SolarusEditor {
46 
47 namespace {
48 
49 /**
50  * @brief State of the map view corresponding to the user doing nothing special.
51  *
52  * He can select or unselect entities.
53  */
54 class DoingNothingState : public MapView::State {
55 
56 public:
57   explicit DoingNothingState(MapView& view);
58 
59   void mouse_pressed(const QMouseEvent& event) override;
60   void mouse_moved(const QMouseEvent& event) override;
61   void mouse_released(const QMouseEvent& event) override;
62   void context_menu_requested(const QPoint& where) override;
63   void tileset_selection_changed(const QString& tileset_id, const QList<int>& indexes) override;
64 
65 private:
66   QPoint mouse_pressed_point;               /**< Point where the mouse was pressed, in view coordinates. */
67   bool clicked_with_control_or_shift;
68 };
69 
70 /**
71  * @brief State of the map view of drawing a selection rectangle.
72  */
73 class DrawingRectangleState : public MapView::State {
74 
75 public:
76   DrawingRectangleState(MapView& view, const QPoint& initial_point);
77 
78   void start() override;
79   void stop() override;
80 
81   void mouse_moved(const QMouseEvent& event) override;
82   void mouse_released(const QMouseEvent& event) override;
83 
84 private:
85   QPoint initial_point;                     /**< Point where the drawing started, in scene coordinates. */
86   QPoint current_point;                     /**< Point where the dragging currently is, in scene coordinates. */
87   QGraphicsRectItem* current_area_item;     /**< Graphic item of the rectangle the user is drawing
88                                              * (belongs to the scene). */
89   QList<QGraphicsItem*> initial_selection;  /**< Items that were selected before the drawing started. */
90 };
91 
92 /**
93  * @brief State of the map view of moving the selected entities.
94  */
95 class MovingEntitiesState : public MapView::State {
96 
97 public:
98   MovingEntitiesState(MapView& view, const QPoint& initial_point);
99 
100   void cancel() override;
101 
102   void mouse_moved(const QMouseEvent& event) override;
103   void mouse_released(const QMouseEvent& event) override;
104 
105 private:
106   QPoint initial_point;      /**< Point where the dragging started, in scene coordinates. */
107   QPoint last_point;         /**< Point where the mouse was last time it moved, in scene coordinates. */
108   bool first_move_done;      /**< Whether at least one move was done during the state. */
109 };
110 
111 /**
112  * @brief State of the map view of resizing entities.
113  */
114 class ResizingEntitiesState : public MapView::State {
115 
116 public:
117   ResizingEntitiesState(MapView& view, const EntityIndexes& entities);
118   void start() override;
119   void cancel() override;
120 
121   void mouse_moved(const QMouseEvent& event) override;
122   void mouse_released(const QMouseEvent& event) override;
123 
124 private:
125   void compute_center();
126   void compute_leader();
127   void compute_fixed_corner();
128   void update_boxes(
129       const QPoint& leader_expansion,
130       bool horizontal_preferred
131   );
132   QRect update_box(
133       const EntityIndex& index,
134       const QPoint& leader_expansion,
135       bool horizontal_preferred);
136   QRect apply_smart_resizing(
137       const EntityIndex& index,
138       ResizeMode resize_mode,
139       bool horizontal_preferred,
140       const QPoint& leader_expansion
141   );
142   QRect get_box_from_expansion_and_translation(
143       const EntityIndex& index,
144       const QPoint& expansion,
145       const QPoint& translation
146   );
147   QRect apply_constraints(
148       const EntityIndex& index,
149       ResizeMode resize_mode,
150       bool horizontal_preferred,
151       const QRect& box
152   );
153   static bool is_horizontally_resizable(
154       ResizeMode resize_mode, bool horizontal_preferred);
155   static bool is_vertically_resizable(
156       ResizeMode resize_mode, bool horizontal_preferred);
157 
158   EntityIndexes entities;         /**< Entities to resize. */
159   QMap<EntityIndex, QRect>
160       old_boxes;                  /**< Bounding rectangle of each entity before resizing. */
161   EntityIndex leader_index;       /**< Entity whose resizing follows the cursor position.
162                                    * Other ones reproduce an equivalent change. */
163   QPoint center;                  /**< Center of the bounding box of entities to resize. */
164   QPoint fixed_corner;            /**< Which corner of the initial entities box
165                                    * is fixed (+-1, +-1).
166                                    * The opposite one follows the mouse. */
167   bool first_resize_done;         /**< Whether at least one resizing was done during the state. */
168   int num_free_entities;          /**< Number of entities freely resizable (mode ResizeMode::MULTI_DIMENSION_ALL). */
169 
170 };
171 
172 /**
173  * @brief State of the map view of adding new entities.
174  */
175 class AddingEntitiesState : public MapView::State {
176 
177 public:
178   AddingEntitiesState(MapView& view, EntityModels&& entities, bool guess_layer);
179   void start() override;
180   void stop() override;
181   void mouse_pressed(const QMouseEvent& event) override;
182   void mouse_moved(const QMouseEvent& event) override;
183   void tileset_selection_changed(const QString& tileset_id, const QList<int>& indexes) override;
184 
185 private:
186   QPoint get_entities_center() const;
187   void sort_entities();
188   int find_best_layer(const EntityModel& entity) const;
189 
190   EntityModels entities;                    /**< Entities to be added. */
191   std::vector<EntityItem*> entity_items;    /**< Graphic items of entities to be added. */
192   QPoint last_point;                        /**< Point where the mouse was last time it moved, in scene coordinates. */
193   bool guess_layer;                         /**< Whether the layer should be guessed or kept unchanged. */
194 };
195 
196 }  // Anonymous namespace.
197 
198 /**
199  * @brief Creates a map view.
200  * @param parent The parent widget or nullptr.
201  */
MapView(QWidget * parent)202 MapView::MapView(QWidget* parent) :
203   QGraphicsView(parent),
204   map(),
205   scene(nullptr),
206   view_settings(nullptr),
207   zoom(1.0),
208   state(),
209   common_actions(nullptr),
210   edit_action(nullptr),
211   resize_action(nullptr),
212   convert_tiles_action(nullptr),
213   set_layer_actions(),
214   set_layer_actions_group(nullptr),
215   up_one_layer_action(nullptr),
216   down_one_layer_action(nullptr),
217   bring_to_front_action(nullptr),
218   bring_to_back_action(nullptr),
219   remove_action(nullptr),
220   cancel_action(nullptr) {
221 
222   setAlignment(Qt::AlignTop | Qt::AlignLeft);
223 
224   ViewSettings* view_settings = new ViewSettings(this);
225   set_view_settings(*view_settings);
226 
227   // Necessary because we draw a custom background.
228   setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
229 
230   // Initialize actions.
231   build_context_menu_actions();
232 }
233 
234 /**
235  * @brief Returns the map represented in this view.
236  * @return The map model or nullptr if none was set.
237  */
get_map()238 MapModel* MapView::get_map() {
239   return map.data();
240 }
241 
242 /**
243  * @brief Sets the map to represent in this view.
244  * @param map The map model, or nullptr to remove any model.
245  * This class does not take ownership on the map.
246  */
set_map(MapModel * map)247 void MapView::set_map(MapModel* map) {
248 
249   if (this->map != nullptr) {
250     this->map = nullptr;
251     this->scene = nullptr;
252   }
253 
254   this->map = map;
255 
256   if (map != nullptr) {
257     // Create the scene from the map.
258     scene = new MapScene(*map, this);
259     setScene(scene);
260 
261     // Initialize layers.
262     connect(map, &MapModel::layer_range_changed, [this]() {
263       build_context_menu_layer_actions();
264     });
265     build_context_menu_layer_actions();
266 
267     // Enable useful features if there is an image.
268     if (view_settings != nullptr) {
269       view_settings->set_zoom(2.0);  // Initial zoom: x2.
270     }
271     horizontalScrollBar()->setValue(0);
272     verticalScrollBar()->setValue(0);
273 
274     // Install panning and zooming helpers.
275     new PanTool(this);
276     new ZoomTool(this);
277     new MouseCoordinatesTrackingTool(this);
278 
279     // Connect signals.
280     connect(map, &MapModel::tileset_id_changed,
281             this, &MapView::tileset_id_changed);
282     tileset_id_changed(map->get_tileset_id());
283 
284     // Start the state mechanism.
285     start_state_doing_nothing();
286   }
287 }
288 
289 /**
290  * @brief Returns the map scene represented in this view.
291  * @return The scene or nullptr if no map was set.
292  */
get_scene()293 MapScene* MapView::get_scene() {
294   return scene;
295 }
296 
297 /**
298  * @brief Returns the view settings for this map view.
299  * @return The view settings, or nullptr if none were set.
300  */
get_view_settings() const301 const ViewSettings* MapView::get_view_settings() const {
302   return view_settings;
303 }
304 
305 /**
306  * @brief Sets the view settings for this map view.
307  *
308  * When they change, the map view is updated accordingly.
309  *
310  * @param view_settings The settings to watch.
311  */
set_view_settings(ViewSettings & view_settings)312 void MapView::set_view_settings(ViewSettings& view_settings) {
313 
314   this->view_settings = &view_settings;
315 
316   connect(this->view_settings, &ViewSettings::zoom_changed,
317           this, &MapView::update_zoom);
318   update_zoom();
319 
320   connect(this->view_settings, &ViewSettings::grid_visibility_changed,
321           this, &MapView::update_grid_visibility);
322   connect(this->view_settings, &ViewSettings::grid_size_changed,
323           this, &MapView::update_grid_visibility);
324   connect(this->view_settings, &ViewSettings::grid_style_changed,
325           this, &MapView::update_grid_visibility);
326   connect(this->view_settings, &ViewSettings::grid_color_changed,
327           this, &MapView::update_grid_visibility);
328   update_grid_visibility();
329 
330   connect(this->view_settings, &ViewSettings::layer_visibility_changed,
331           this, &MapView::update_layer_visibility);
332   connect(this->view_settings, &ViewSettings::layer_locking_changed,
333           this, &MapView::update_layer_locking);
334 
335   connect(this->view_settings, &ViewSettings::traversables_visibility_changed,
336           this, &MapView::update_traversables_visibility);
337   connect(this->view_settings, &ViewSettings::obstacles_visibility_changed,
338           this, &MapView::update_obstacles_visibility);
339   connect(this->view_settings, &ViewSettings::entity_type_visibility_changed,
340           this, &MapView::update_entity_type_visibility);
341 
342   horizontalScrollBar()->setValue(0);
343   verticalScrollBar()->setValue(0);
344 }
345 
346 /**
347  * @brief Returns the common actions of the editor.
348  * @return The common actions or nullptr if unset.
349  */
get_common_actions() const350 const QMap<QString, QAction*>* MapView::get_common_actions() const {
351   return common_actions;
352 }
353 
354 /**
355  * @brief Sets the common actions of the editor.
356  *
357  * This function should be called at initialization time to make actions
358  * available in the context menu.
359  *
360  * @param common_actions The common actions.
361  */
set_common_actions(const QMap<QString,QAction * > * common_actions)362 void MapView::set_common_actions(const QMap<QString, QAction*>* common_actions) {
363   this->common_actions = common_actions;
364 }
365 
366 /**
367  * @brief Changes the state of the view.
368  *
369  * The previous state if any is destroyed.
370  *
371  * @param state The new state.
372  */
set_state(std::unique_ptr<State> state)373 void MapView::set_state(std::unique_ptr<State> state) {
374 
375   if (this->state != nullptr) {
376     this->state->stop();
377   }
378 
379   this->state = std::move(state);
380 
381   if (this->state != nullptr) {
382     this->state->start();
383   }
384 }
385 
386 /**
387  * @brief Moves to the normal state of the map view.
388  */
start_state_doing_nothing()389 void MapView::start_state_doing_nothing() {
390 
391   set_state(std::unique_ptr<State>(new DoingNothingState(*this)));
392 
393   emit stopped_state();
394 }
395 
396 /**
397  * @brief Moves to the state of drawing a rectangle for a selection.
398  * @param initial_point Where the user starts drawing the rectangle,
399  * in view coordinates.
400  */
start_state_drawing_rectangle(const QPoint & initial_point)401 void MapView::start_state_drawing_rectangle(const QPoint& initial_point) {
402 
403   set_state(std::unique_ptr<State>(new DrawingRectangleState(*this, initial_point)));
404 }
405 
406 /**
407  * @brief Moves to the state of moving the selected entities.
408  * @param initial_point Where the user starts dragging the entities,
409  */
start_state_moving_entities(const QPoint & initial_point)410 void MapView::start_state_moving_entities(const QPoint& initial_point) {
411 
412   set_state(std::unique_ptr<State>(new MovingEntitiesState(*this, initial_point)));
413 }
414 
415 /**
416  * @brief Moves to the state of resizing the selected entities.
417  *
418  * Does nothing if there is no selected entity or if the selection is not
419  * resizable.
420  */
start_state_resizing_entities()421 void MapView::start_state_resizing_entities() {
422 
423   const EntityIndexes selection = get_selected_entities();
424   if (!are_entities_resizable(selection)) {
425     // The selection is empty or not resizable.
426     start_state_doing_nothing();
427     return;
428   }
429 
430   set_state(std::unique_ptr<State>(new ResizingEntitiesState(*this, selection)));
431 }
432 
433 /**
434  * @brief Moves to the state of adding new entities.
435  * @param entities The entities to be added.
436  * They must not belong to the map yet.
437  * @param guess_layer Whether a layer should be guessed from the preferred
438  * layer of entities and the mouse position.
439  */
start_state_adding_entities(EntityModels && entities,bool guess_layer)440 void MapView::start_state_adding_entities(EntityModels&& entities, bool guess_layer) {
441 
442   set_state(std::unique_ptr<State>(new AddingEntitiesState(
443        *this,
444        std::move(entities),
445        guess_layer)));
446 }
447 
448 /**
449  * @brief Moves to the state of adding new entities, adding the specified tiles.
450  * @param tileset_id Id of the tileset to use (empty means the one of the map).
451  * @param indexes Indexes of the selected patterns.
452  */
start_adding_entities_from_tileset(const QString & tileset_id,const QList<int> & indexes)453 void MapView::start_adding_entities_from_tileset(const QString& tileset_id, const QList<int>& indexes) {
454 
455   if (indexes.isEmpty()) {
456     return;
457   }
458 
459   MapModel* map = get_map();
460   if (map == nullptr) {
461     return;
462   }
463 
464   QString id = !tileset_id.isEmpty() ? tileset_id : map->get_tileset_id();
465   TilesetModel* tileset = map->get_quest().get_tileset(id);
466   if (tileset == nullptr) {
467     return;
468   }
469 
470   // Create a tile from each selected pattern.
471   // Arrange the relative position of tiles as in the tileset.
472   EntityModels tiles;
473 
474   bool has_common_preferred_layer = true;
475   int common_preferred_layer = tileset->get_pattern_default_layer(indexes.first());
476   for (int pattern_index : indexes) {
477     QString pattern_id = tileset->index_to_id(pattern_index);
478     if (pattern_id.isEmpty()) {
479       continue;
480     }
481 
482     // Create a tile from the pattern.
483     QRect pattern_frame = tileset->get_pattern_frame(pattern_index);
484     EntityModelPtr tile = EntityModel::create(*map, EntityType::TILE);
485     tile->set_field("pattern", pattern_id);
486     tile->set_size(pattern_frame.size());
487     tile->set_xy(pattern_frame.topLeft());
488     int preferred_layer = tileset->get_pattern_default_layer(pattern_index);
489     tile->set_layer(preferred_layer);
490     if (!tileset_id.isEmpty()) {
491       // Not the default tileset of the map.
492       tile->set_field("tileset", tileset_id);
493     }
494     tiles.emplace_back(std::move(tile));
495 
496     // Also check if they all have the same preferred layer.
497     if (preferred_layer != common_preferred_layer) {
498       has_common_preferred_layer = false;
499     }
500   }
501 
502   // Don't try to choose other layers if they are different at start.
503   bool guess_layer = has_common_preferred_layer;
504 
505   start_state_adding_entities(std::move(tiles), guess_layer);
506 }
507 
508 /**
509  * @brief Returns whether at least one entity of a list is resizable.
510  * @param indexes Indexes of entities to resize.
511  * @return @c true at least one is resizable.
512  */
are_entities_resizable(const EntityIndexes & indexes) const513 bool MapView::are_entities_resizable(const EntityIndexes& indexes) const {
514 
515   for (const EntityIndex& index : indexes) {
516     if (map->get_entity(index).is_resizable()) {
517       return true;
518     }
519   }
520   return false;
521 }
522 
523 /**
524  * @brief Creates all actions to be used by context menus.
525  */
build_context_menu_actions()526 void MapView::build_context_menu_actions() {
527 
528   edit_action = new QAction(
529         tr("Edit"), this);
530   edit_action->setShortcut(Qt::Key_Return);
531   edit_action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
532   connect(edit_action, &QAction::triggered,
533           this, &MapView::edit_selected_entity);
534   addAction(edit_action);
535 
536   resize_action = new QAction(
537         tr("Resize"), this);
538   resize_action->setShortcut(tr("R"));
539   resize_action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
540   connect(resize_action, &QAction::triggered,
541           this, &MapView::start_state_resizing_entities);
542   addAction(resize_action);
543 
544   convert_tiles_action = new QAction(
545         tr("Convert to dynamic tile"), this);
546   connect(convert_tiles_action, &QAction::triggered,
547           this, &MapView::convert_selected_tiles);
548   addAction(convert_tiles_action);
549 
550   change_pattern_action = new QAction(
551         tr("Change pattern..."), this);
552   connect(change_pattern_action, &QAction::triggered, [this]() {
553     emit change_tiles_pattern_requested(get_selected_entities());
554   });
555   addAction(change_pattern_action);
556 
557   change_pattern_all_action = new QAction(
558         tr("Change pattern of similar tiles..."), this);
559   connect(change_pattern_all_action, &QAction::triggered,
560           this, &MapView::change_pattern_of_similar_tiles);
561   addAction(change_pattern_action);
562 
563   add_border_action = new QAction(
564         tr("Generate borders around selection"), this);
565   add_border_action->setShortcut(tr("Ctrl+B"));
566   add_border_action->setShortcutContext(Qt::WindowShortcut);
567   connect(add_border_action, &QAction::triggered, [this]() {
568     emit generate_borders_requested(get_selected_entities());
569   });
570   addAction(add_border_action);
571 
572   up_one_layer_action = new QAction(
573         tr("One layer up"), this);
574   up_one_layer_action->setShortcut(tr("+"));
575   up_one_layer_action->setShortcutContext(Qt::WindowShortcut);
576   connect(up_one_layer_action, &QAction::triggered, [this]() {
577     emit increase_entities_layer_requested(get_selected_entities());
578   });
579   addAction(up_one_layer_action);
580 
581   down_one_layer_action = new QAction(
582         tr("One layer down"), this);
583   down_one_layer_action->setShortcut(tr("-"));
584   down_one_layer_action->setShortcutContext(Qt::WindowShortcut);
585   connect(down_one_layer_action, &QAction::triggered, [this]() {
586     emit decrease_entities_layer_requested(get_selected_entities());
587   });
588   addAction(down_one_layer_action);
589 
590   bring_to_front_action = new QAction(
591         tr("Bring to front"), this);
592   bring_to_front_action->setShortcut(tr("T"));
593   bring_to_front_action->setShortcutContext(Qt::WindowShortcut);
594   connect(bring_to_front_action, &QAction::triggered, [this]() {
595     emit bring_entities_to_front_requested(get_selected_entities());
596   });
597   addAction(bring_to_front_action);
598 
599   bring_to_back_action = new QAction(
600         tr("Bring to back"), this);
601   bring_to_back_action->setShortcut(tr("B"));
602   bring_to_back_action->setShortcutContext(Qt::WindowShortcut);
603   connect(bring_to_back_action, &QAction::triggered, [this]() {
604     emit bring_entities_to_back_requested(get_selected_entities());
605   });
606   addAction(bring_to_back_action);
607 
608   remove_action = new QAction(
609         QIcon(":/images/icon_delete.png"), tr("Delete"), this);
610   remove_action->setShortcut(QKeySequence::Delete);
611   remove_action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
612   connect(remove_action, &QAction::triggered,
613           this, &MapView::remove_selected_entities);
614   addAction(remove_action);
615 
616   cancel_action = new QAction(tr("Cancel"), this);
617   cancel_action->setShortcut(Qt::Key_Escape);
618   cancel_action->setShortcutContext(Qt::WindowShortcut);
619   connect(cancel_action, &QAction::triggered,
620           this, &MapView::cancel_state_requested);
621   addAction(cancel_action);
622 
623   build_context_menu_layer_actions();
624 }
625 
626 /**
627  * @brief Creates all layer actions to be used by context menus.
628  *
629  * This function should be called when the number of layers of the map changes.
630  */
build_context_menu_layer_actions()631 void MapView::build_context_menu_layer_actions() {
632 
633   if (get_map() == nullptr) {
634     return;
635   }
636 
637   // Clean the old actions.
638   delete set_layer_actions_group;
639   set_layer_actions.clear();
640 
641   // Create new ones.
642   set_layer_actions_group = new QActionGroup(this);
643   set_layer_actions_group->setExclusive(true);
644   for (int layer = get_map()->get_min_layer(); layer <= get_map()->get_max_layer(); ++layer) {
645     QAction* action = new QAction(tr("Layer %1").arg(layer), set_layer_actions_group);
646     action->setCheckable(true);
647     connect(action, &QAction::triggered, [this, layer]() {
648       emit set_entities_layer_requested(get_selected_entities(), layer);
649     });
650     set_layer_actions.push_back(action);
651   }
652 }
653 
654 /**
655  * @brief Creates a context menu for the selected entities.
656  * @return A context menu.
657  */
create_context_menu()658 QMenu* MapView::create_context_menu() {
659 
660   // Layout of the context menu (line breaks are separators):
661   //
662   // Edit, Resize, Direction
663   // Change pattern, Change pattern of all, Convert to dynamic/static tile(s)
664   // Cut, Copy, Paste
665   // Borders
666   // Layers, One layer up, One layer down
667   // Bring to front, Bring to back
668   // Delete
669 
670   QMenu* menu = new QMenu(this);
671   const EntityIndexes& indexes = get_selected_entities();
672 
673   if (!is_selection_empty()) {
674 
675     // Edit.
676     const bool single_selection = indexes.size() <= 1;
677     Q_ASSERT(edit_action != nullptr);
678     edit_action->setEnabled(single_selection);
679     menu->addAction(edit_action);
680 
681     // Resize.
682     const bool resizable = are_entities_resizable(indexes);
683     resize_action->setEnabled(resizable);
684     menu->addAction(resize_action);
685 
686     // Direction.
687     QMenu* direction_menu = create_direction_context_menu(indexes);
688     Q_ASSERT(direction_menu != nullptr);
689     menu->addMenu(direction_menu);
690     menu->addSeparator();
691 
692     // Convert to dynamic/static tile(s).
693     bool tile_action_added = false;
694     EntityType type;
695     const bool show_convert_tiles_action = map->is_common_type(indexes, type) &&
696         (type == EntityType::TILE || type == EntityType::DYNAMIC_TILE);
697     if (show_convert_tiles_action) {
698       QString text;
699       if (type == EntityType::TILE) {
700         text = single_selection ? tr("Convert to dynamic tile") : tr("Convert to dynamic tiles");
701       }
702       else {
703         text = single_selection ? tr("Convert to static tile") : tr("Convert to static tiles");
704       }
705       convert_tiles_action->setText(text);
706       menu->addAction(convert_tiles_action);
707       tile_action_added = true;
708     }
709 
710     if (map->are_tiles(indexes)) {
711 
712       bool same_pattern = true;
713       const QString& pattern_id = map->get_entity_field(indexes.first(), "pattern").toString();
714       for (const EntityIndex& index : indexes) {
715         if (map->get_entity_field(index, "pattern").toString() != pattern_id) {
716           same_pattern = false;
717           break;
718         }
719       }
720 
721       bool same_tileset = true;
722       const QString& tileset_id = map->get_entity_field(indexes.first(), "tileset").toString();
723       for (const EntityIndex& index : indexes) {
724         if (map->get_entity_field(index, "tileset").toString() != tileset_id) {
725           same_tileset = false;
726           break;
727         }
728       }
729 
730       // Change pattern.
731       if (same_tileset) {
732         menu->addAction(change_pattern_action);
733         tile_action_added = true;
734         // Change pattern of all similar tiles.
735         if (same_pattern) {
736           menu->addAction(change_pattern_all_action);
737         }
738       }
739     }
740 
741     if (tile_action_added) {
742       menu->addSeparator();
743     }
744   }
745 
746   // Cut, copy, paste.
747   if (common_actions != nullptr) {
748     // Global actions are available.
749     menu->addAction(common_actions->value("cut"));
750     menu->addAction(common_actions->value("copy"));
751     menu->addAction(common_actions->value("paste"));
752     menu->addSeparator();
753   }
754 
755   if (!is_selection_empty()) {
756 
757     // Borders.
758     menu->addAction(add_border_action);
759     menu->addSeparator();
760 
761     // Layer.
762     int common_layer = -1;
763     bool has_common_layer = map->is_common_layer(indexes, common_layer);
764     for (int i = 0; i < set_layer_actions.size(); ++i) {
765       QAction* action = set_layer_actions[i];
766       int layer = i + map->get_min_layer();
767       action->setChecked(false);
768       if (has_common_layer && layer == common_layer) {
769         action->setChecked(true);
770       }
771       menu->addAction(action);
772     }
773 
774     up_one_layer_action->setEnabled(!has_common_layer || common_layer < get_map()->get_max_layer());
775     down_one_layer_action->setEnabled(!has_common_layer || common_layer > get_map()->get_min_layer());
776     menu->addAction(up_one_layer_action);
777     menu->addAction(down_one_layer_action);
778 
779     // Bring to front/back.
780     menu->addAction(bring_to_front_action);
781     menu->addAction(bring_to_back_action);
782     menu->addSeparator();
783 
784     // Remove.
785     menu->addAction(remove_action);
786   }
787 
788   return menu;
789 }
790 
791 /**
792  * @brief Creates a context menu to select the direction of entities.
793  *
794  * Returns a disabled menu if the direction rules of the given entities are incompatible.
795  *
796  * @param indexes Indexes of entity to treat.
797  * @return The direction context menu.
798  */
create_direction_context_menu(const EntityIndexes & indexes)799 QMenu* MapView::create_direction_context_menu(const EntityIndexes& indexes) {
800 
801   QMenu* menu = new QMenu(tr("Direction"), this);
802 
803   int num_directions = 0;
804   QString no_direction_text;
805   if (!map->is_common_direction_rules(indexes, num_directions, no_direction_text)) {
806     // Directions rules are incompatible.
807     menu->setEnabled(false);
808     return menu;
809   }
810 
811   if (num_directions == 0) {
812     // There is no direction field on these entities.
813     menu->setEnabled(false);
814     return menu;
815   }
816 
817   QStringList texts;
818   if (num_directions == 4) {
819     texts = QStringList{
820       tr("Right"),
821       tr("Up"),
822       tr("Left"),
823       tr("Down")
824     };
825   }
826   else if (num_directions == 8) {
827     texts = QStringList{
828       tr("Right"),
829       tr("Right-up"),
830       tr("Up"),
831       tr("Left-up"),
832       tr("Left"),
833       tr("Left-down"),
834       tr("Down"),
835       tr("Right-down"),
836     };
837   }
838   else {
839     for (int i = 0; i < num_directions; ++num_directions) {
840       texts.append(QString::number(i));
841     }
842   }
843 
844   // Create the actions.
845   if (!no_direction_text.isEmpty()) {
846     // Special no-direction value.
847     QAction* action = new QAction(no_direction_text, menu);
848     action->setCheckable(true);
849     connect(action, &QAction::triggered, [this, indexes]() {
850       emit set_entities_direction_requested(indexes, -1);
851     });
852     menu->addAction(action);
853   }
854   for (int i = 0; i < num_directions; ++i) {
855     // Normal direction.
856     QAction* action = new QAction(texts[i], menu);
857     action->setCheckable(true);
858     connect(action, &QAction::triggered, [this, indexes, i]() {
859       emit set_entities_direction_requested(indexes, i);
860     });
861     menu->addAction(action);
862   }
863 
864   // Check the common direction if any.
865   int direction = -1;
866   if (map->is_common_direction(indexes, direction)) {
867     int i = direction;
868     if (!no_direction_text.isEmpty()) {
869       ++i;
870     }
871     menu->actions().at(i)->setChecked(true);
872   }
873 
874   return menu;
875 }
876 
877 /**
878  * @brief Exports the current view to an image.
879  * @return The map image.
880  */
export_to_image()881 QImage MapView::export_to_image() {
882 
883   if (scene == nullptr) {
884     return QImage();
885   }
886 
887   // Clear the selection first (we don't want selection markers.
888   EntityIndexes selected_indexes = get_selected_entities();
889   set_selected_entities(EntityIndexes());
890 
891   // Create the image.
892   QImage image(map->get_size(), QImage::Format_ARGB32);
893   image.fill(Qt::transparent);
894   QPainter painter(&image);
895   scene->render(&painter, image.rect(),
896                 QRect(scene->get_margin_top_left(), map->get_size()));
897 
898   // Restore the selection.
899   set_selected_entities(selected_indexes);
900 
901   return image;
902 }
903 
904 /**
905  * @brief Copies the selected entities to the clipboard and removes them.
906  */
cut()907 void MapView::cut() {
908 
909   if (is_selection_empty()) {
910     return;
911   }
912 
913   copy();
914   remove_selected_entities();
915 }
916 
917 /**
918  * @brief Copies the selected entities to the clipboard.
919  */
copy()920 void MapView::copy() {
921 
922   if (map == nullptr) {
923     return;
924   }
925 
926   EntityIndexes indexes = get_selected_entities();
927   if (indexes.isEmpty()) {
928     return;
929   }
930 
931   // Sort entities to respect their relative order on the map when pasting.
932   std::sort(indexes.begin(), indexes.end());
933 
934   QStringList entity_strings;
935   for (const EntityIndex& index : indexes) {
936     Q_ASSERT(map->entity_exists(index));
937     const EntityModel& entity = map->get_entity(index);
938     QString entity_string = entity.to_string();
939     Q_ASSERT(!entity_string.isEmpty());
940     entity_strings << entity_string;
941   }
942 
943   QString text = entity_strings.join("");
944   QApplication::clipboard()->setText(text);
945 }
946 
947 /**
948  * @brief Adds entities from the clipboard.
949  */
paste()950 void MapView::paste() {
951 
952   if (scene == nullptr) {
953     return;
954   }
955 
956   QString text = QApplication::clipboard()->text();
957   if (text.isEmpty()) {
958     return;
959   }
960 
961   QStringList entity_strings = text.split(QRegExp("[\n\r]\\}[\n\r]"), QString::SkipEmptyParts);
962 
963   EntityModels entities;
964   for (int i = 0; i < entity_strings.size(); ++i) {
965 
966     QString entity_string = entity_strings.at(i);
967 
968     if (entity_string.simplified().isEmpty()) {
969       // Only whitespaces: skip.
970       continue;
971     }
972 
973     if (i < entity_strings.size() - 1) {
974       entity_string = entity_string + "}";  // Restore the closing brace removed by split().
975     }
976     EntityModelPtr entity = EntityModel::create(*get_map(), entity_string);
977     if (entity == nullptr) {
978       // The text data from the clipboard is not a valid entity.
979       return;
980     }
981 
982     entities.push_back(std::move(entity));
983   }
984 
985   if (entities.empty()) {
986     return;
987   }
988 
989   const bool guess_layer = false;  // Paste entities on the same layer.
990   start_state_adding_entities(std::move(entities), guess_layer);
991 }
992 
993 /**
994  * @brief Sets the zoom level of the view from the settings.
995  *
996  * Zooming will be anchored at the mouse position.
997  * The zoom value will be clamped between 0.25 and 4.0.
998  */
update_zoom()999 void MapView::update_zoom() {
1000 
1001   if (view_settings == nullptr) {
1002     return;
1003   }
1004 
1005   double zoom = view_settings->get_zoom();
1006   zoom = qMin(4.0, qMax(0.25, zoom));
1007 
1008   if (zoom == this->zoom) {
1009     return;
1010   }
1011 
1012   setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
1013   double scale_factor = zoom / this->zoom;
1014   scale(scale_factor, scale_factor);
1015   this->zoom = zoom;
1016 }
1017 
1018 /**
1019  * @brief Scales the view by a factor of 2.
1020  *
1021  * Zooming will be anchored at the mouse position.
1022  * The maximum zoom value is 4.0: this function does nothing if you try to
1023  * zoom more.
1024  */
zoom_in()1025 void MapView::zoom_in() {
1026 
1027   if (view_settings == nullptr) {
1028     return;
1029   }
1030 
1031   view_settings->set_zoom(view_settings->get_zoom() * 2.0);
1032 }
1033 
1034 /**
1035  * @brief Scales the view by a factor of 0.5.
1036  *
1037  * Zooming will be anchored at the mouse position.
1038  * The maximum zoom value is 0.25: this function does nothing if you try to
1039  * zoom less.
1040  */
zoom_out()1041 void MapView::zoom_out() {
1042 
1043   if (view_settings == nullptr) {
1044     return;
1045   }
1046 
1047   view_settings->set_zoom(view_settings->get_zoom() / 2.0);
1048 }
1049 
1050 /**
1051  * @brief Slot called when the mouse coordinates on the view have changed.
1052  *
1053  * Translates the coordinates relative to the view into coordinates relative
1054  * to the map and emits mouse_map_coordinates_changed().
1055  *
1056  * @param xy The mouse coordinates relative to the widget.
1057  */
mouse_coordinates_changed(const QPoint & xy)1058 void MapView::mouse_coordinates_changed(const QPoint& xy) {
1059 
1060   QPoint map_xy = mapToScene(xy).toPoint() - MapScene::get_margin_top_left();
1061   emit mouse_map_coordinates_changed(map_xy);
1062 }
1063 
1064 /**
1065  * @brief Shows or hides the grid according to the view settings.
1066  */
update_grid_visibility()1067 void MapView::update_grid_visibility() {
1068 
1069   if (view_settings == nullptr) {
1070     return;
1071   }
1072 
1073   if (scene != nullptr) {
1074     // The foreground has changed.
1075     scene->invalidate();
1076   }
1077 }
1078 
1079 /**
1080  * @brief Shows or hides entities on a layer according to the view settings.
1081  * @param layer The layer to update.
1082  */
update_layer_visibility(int layer)1083 void MapView::update_layer_visibility(int layer) {
1084 
1085   if (scene == nullptr) {
1086     return;
1087   }
1088 
1089   scene->update_layer_visibility(layer, *view_settings);
1090 }
1091 
1092 /**
1093  * @brief Locks or unlock a layer according to the view settings.
1094  * @param layer The layer to update.
1095  */
update_layer_locking(int layer)1096 void MapView::update_layer_locking(int layer) {
1097 
1098   if (scene == nullptr) {
1099     return;
1100   }
1101 
1102   scene->update_layer_locking(layer, *view_settings);
1103 }
1104 
1105 /**
1106  * @brief Shows or hides traversables according to the view settings.
1107  */
update_traversables_visibility()1108 void MapView::update_traversables_visibility() {
1109 
1110   if (scene == nullptr) {
1111     return;
1112   }
1113 
1114   scene->update_traversables_visibility(*view_settings);
1115 }
1116 
1117 /**
1118  * @brief Shows or hides obstacles according to the view settings.
1119  */
update_obstacles_visibility()1120 void MapView::update_obstacles_visibility() {
1121 
1122   if (scene == nullptr) {
1123     return;
1124   }
1125 
1126   scene->update_obstacles_visibility(*view_settings);
1127 }
1128 
1129 /**
1130  * @brief Shows or hides entities of a type according to the view settings.
1131  * @param type The entity type to update.
1132  */
update_entity_type_visibility(EntityType type)1133 void MapView::update_entity_type_visibility(EntityType type) {
1134 
1135   scene->update_entity_type_visibility(type, *view_settings);
1136 }
1137 /**
1138 
1139  * @brief Function called when the pattern selection of the tileset is changed
1140  * by the user.
1141  *
1142  * Tiles with these new patterns are added if possible.
1143  *
1144  * @param tileset_id Id of the tileset to use (empty means the one of the map).
1145  * @param indexes Indexes of the selected patterns.
1146  */
tileset_selection_changed(const QString & tileset_id,const QList<int> & indexes)1147 void MapView::tileset_selection_changed(const QString& tileset_id, const QList<int>& indexes) {
1148 
1149   if (state == nullptr) {
1150     return;
1151   }
1152 
1153   state->tileset_selection_changed(tileset_id, indexes);
1154 }
1155 
1156 /**
1157  * @brief Slot called when another tileset is set on the map.
1158  * @param tileset_id The new tileset id.
1159  */
tileset_id_changed(const QString & tileset_id)1160 void MapView::tileset_id_changed(const QString& tileset_id) {
1161 
1162   Q_UNUSED(tileset_id);
1163   disconnect(this, SLOT(notify_tileset_changed()));
1164 
1165   if (map->get_tileset_model() != nullptr) {
1166     // Watch changes of this tileset.
1167     connect(map->get_tileset_model(), &TilesetModel::modelReset,
1168             this, &MapView::notify_tileset_changed);
1169   }
1170 
1171   // TODO only if there is really a change
1172   notify_tileset_changed();
1173 }
1174 
1175 /**
1176  * @brief Slot called when the tileset has changed.
1177  */
notify_tileset_changed()1178 void MapView::notify_tileset_changed() {
1179 
1180   if (scene == nullptr) {
1181     return;
1182   }
1183   scene->update();
1184 
1185   start_state_doing_nothing();
1186 }
1187 
1188 /**
1189  * @brief Draws the map view.
1190  * @param event The paint event.
1191  */
paintEvent(QPaintEvent * event)1192 void MapView::paintEvent(QPaintEvent* event) {
1193 
1194   QGraphicsView::paintEvent(event);
1195 
1196   if (view_settings == nullptr || !view_settings->is_grid_visible()) {
1197     return;
1198   }
1199 
1200   // Get the rect and convert to the scene.
1201   QRect rect = mapToScene(event->rect()).boundingRect().toRect();
1202 
1203   // Get grid and margin.
1204   QSize grid = view_settings->get_grid_size();
1205   QSize margin = scene != nullptr ? scene->get_margin_size() : QSize(0, 0);
1206 
1207   // Adjust the rect.
1208   rect.setTopLeft(QPoint(
1209     (margin.width() % grid.width()) - grid.width(),
1210     (margin.height() % grid.height()) - grid.height()));
1211 
1212   // Convert the rect from the scene.
1213   rect = mapFromScene(rect).boundingRect();
1214   grid *= zoom;
1215 
1216   // Draw the grid.
1217   QPainter painter(viewport());
1218   GuiTools::draw_grid(
1219     painter, rect, grid, view_settings->get_grid_color(),
1220     view_settings->get_grid_style());
1221 }
1222 
1223 /**
1224  * @brief Receives a key press event.
1225  * @param event The event to handle.
1226  */
keyPressEvent(QKeyEvent * event)1227 void MapView::keyPressEvent(QKeyEvent* event) {
1228 
1229   switch (event->key()) {
1230 
1231   case Qt::Key_Enter:
1232     // Numpad enter key.
1233     // For some reason, this particular key does not work as a QAction shortcut
1234     // on all systems.
1235     edit_selected_entity();
1236     break;
1237 
1238   case Qt::Key_Plus:
1239     // Make sure that the numpad plus key works too.
1240     emit increase_entities_layer_requested(get_selected_entities());
1241     break;
1242 
1243   case Qt::Key_Minus:
1244     // Make sure that the numpad minus key works too.
1245     emit decrease_entities_layer_requested(get_selected_entities());
1246     break;
1247 
1248   default:
1249     break;
1250   }
1251 }
1252 
1253 /**
1254  * @brief Receives a mouse press event.
1255  * @param event The event to handle.
1256  */
mousePressEvent(QMouseEvent * event)1257 void MapView::mousePressEvent(QMouseEvent* event) {
1258 
1259   if (map == nullptr || get_scene() == nullptr) {
1260     return;
1261   }
1262 
1263   if (!(QApplication::mouseButtons() & event->button())) {
1264     // The button that triggered this event is no longer pressed.
1265     // This is possible if pressing the button already triggered something
1266     // else like a modal dialog.
1267     return;
1268   }
1269 
1270   state->mouse_pressed(*event);
1271 
1272   // Don't forward the event to the parent because it would select the item
1273   // clicked. We only do this explicitly from specific states.
1274 }
1275 
1276 /**
1277  * @brief Receives a mouse release event.
1278  * @param event The event to handle.
1279  */
mouseReleaseEvent(QMouseEvent * event)1280 void MapView::mouseReleaseEvent(QMouseEvent* event) {
1281 
1282   if (map != nullptr && get_scene() != nullptr) {
1283     state->mouse_released(*event);
1284   }
1285 
1286   QGraphicsView::mouseReleaseEvent(event);
1287 }
1288 
1289 /**
1290  * @brief Receives a mouse move event.
1291  * @param event The event to handle.
1292  */
mouseMoveEvent(QMouseEvent * event)1293 void MapView::mouseMoveEvent(QMouseEvent* event) {
1294 
1295   if (map != nullptr && get_scene() != nullptr) {
1296     state->mouse_moved(*event);
1297   }
1298 
1299   // The parent class tracks the mouse movements for internal needs
1300   // such as anchoring the viewport to the mouse when zooming.
1301   QGraphicsView::mouseMoveEvent(event);
1302 }
1303 
1304 /**
1305  * @brief Receives a mouse double click event.
1306  * @param event The event to handle.
1307  */
mouseDoubleClickEvent(QMouseEvent * event)1308 void MapView::mouseDoubleClickEvent(QMouseEvent* event) {
1309 
1310   if (get_num_selected_entities() == 1) {
1311 
1312     QList<QGraphicsItem*> items_under_mouse = items(
1313           QRect(event->pos(), QSize(1, 1)),
1314           Qt::IntersectsItemBoundingRect  // Pick transparent items too.
1315     );
1316     if (!items_under_mouse.isEmpty()) {
1317       QGraphicsItem* item = items_under_mouse.first();
1318       EntityModel* entity = scene->get_entity_from_item(*item);
1319       if (entity != nullptr) {
1320         start_state_doing_nothing();
1321         edit_selected_entity();
1322       }
1323     }
1324   }
1325 }
1326 
1327 /**
1328  * @brief Receives a context menu event.
1329  * @param event The event to handle.
1330  */
contextMenuEvent(QContextMenuEvent * event)1331 void MapView::contextMenuEvent(QContextMenuEvent* event) {
1332 
1333   if (map == nullptr || get_scene() == nullptr) {
1334     return;
1335   }
1336 
1337   QPoint where;
1338   if (event->pos() != QPoint(0, 0)) {
1339     where = event->pos() + QPoint(1, 1);
1340   }
1341   else {
1342     QList<QGraphicsItem*> selected_items = scene->selectedItems();
1343     where = mapFromScene(selected_items.first()->pos() + QPoint(8, 8));
1344   }
1345 
1346   state->context_menu_requested(viewport()->mapToGlobal(where));
1347 }
1348 
1349 /**
1350  * @brief Returns whether the selection is empty.
1351  * @return @c true if the number of selected entities is zero.
1352  */
is_selection_empty() const1353 bool MapView::is_selection_empty() const {
1354 
1355   if (scene == nullptr) {
1356     return true;
1357   }
1358 
1359   return scene->selectedItems().isEmpty();
1360 }
1361 
1362 /**
1363  * @brief Returns the number of selected entities.
1364  * @return The number of selected entities.
1365  */
get_num_selected_entities() const1366 int MapView::get_num_selected_entities() const {
1367 
1368   if (scene == nullptr) {
1369     return 0;
1370   }
1371 
1372   return scene->selectedItems().size();
1373 }
1374 
1375 /**
1376  * @brief Returns the indexes of selected entities.
1377  * @return The selected entities.
1378  */
get_selected_entities() const1379 EntityIndexes MapView::get_selected_entities() const {
1380 
1381   if (scene == nullptr) {
1382     return EntityIndexes();
1383   }
1384 
1385   return scene->get_selected_entities();
1386 }
1387 
1388 /**
1389  * @brief Selects the specified entities and unselect the rest.
1390  * @param indexes Indexes of the entities to make selecteded.
1391  */
set_selected_entities(const EntityIndexes & indexes)1392 void MapView::set_selected_entities(const EntityIndexes& indexes) {
1393 
1394   if (scene == nullptr) {
1395     return;
1396   }
1397 
1398   scene->set_selected_entities(indexes);
1399 }
1400 
1401 /**
1402  * @brief Selects the specified entity and unselects the rest.
1403  * @param index Index of the entity to make selecteded.
1404  */
set_only_selected_entity(const EntityIndex & index)1405 void MapView::set_only_selected_entity(const EntityIndex& index) {
1406 
1407   EntityIndexes indexes;
1408   indexes << index;
1409 
1410   set_selected_entities(indexes);
1411 }
1412 
1413 /**
1414  * @brief Selects or unselects an entity.
1415  * @param entity The entity to change.
1416  * @param selected @c true to select it.
1417  */
select_entity(const EntityIndex & index,bool selected)1418 void MapView::select_entity(const EntityIndex& index, bool selected) {
1419 
1420   if (scene == nullptr) {
1421     return;
1422   }
1423 
1424   scene->select_entity(index, selected);
1425 }
1426 
1427 /**
1428  * @brief Creates copies of all selected entities.
1429  *
1430  * The created copies are not on the map.
1431  *
1432  * @return The created copies.
1433  */
clone_selected_entities() const1434 EntityModels MapView::clone_selected_entities() const {
1435 
1436   EntityModels clones;
1437   const EntityIndexes& indexes = get_selected_entities();
1438   for (const EntityIndex& index : indexes) {
1439     EntityModelPtr clone = EntityModel::clone(*map, index);
1440     clones.emplace_back(std::move(clone));
1441   }
1442   return clones;
1443 }
1444 
1445 /**
1446  * @brief Returns the index of the entity under the cursor if any.
1447  * @return The index of the entity under the cursor of an invalid index.
1448  */
get_entity_index_under_cursor() const1449 EntityIndex MapView::get_entity_index_under_cursor() const {
1450 
1451   if (scene == nullptr) {
1452     return EntityIndex();
1453   }
1454 
1455   QPoint xy = mapFromGlobal(QCursor::pos());
1456   if (xy.x() < 0 ||
1457       xy.x() >= width() ||
1458       xy.y() < 0 ||
1459       xy.y() >= height()) {
1460     // The mouse is outside the widget.
1461     return EntityIndex();
1462   }
1463 
1464   QGraphicsItem* item = itemAt(xy);
1465   if (item == nullptr) {
1466     // No entity under the mouse.
1467     return EntityIndex();
1468   }
1469 
1470   EntityModel* entity = scene->get_entity_from_item(*item);
1471   if (entity == nullptr) {
1472     // Not an entity.
1473     return EntityIndex();
1474   }
1475 
1476   return entity->get_index();
1477 }
1478 
1479 /**
1480  * @brief Slot called when the user wants to cancel the current state.
1481  */
cancel_state_requested()1482 void MapView::cancel_state_requested() {
1483 
1484   if (state != nullptr) {
1485     state->cancel();
1486   }
1487   start_state_doing_nothing();
1488 }
1489 
1490 /**
1491  * @brief Undoes the last command in the undo/redo history.
1492  */
undo_last_command()1493 void MapView::undo_last_command() {
1494   emit undo_requested();
1495 }
1496 
1497 /**
1498  * @brief Opens a dialog to edit the selected entity.
1499  *
1500  * Does nothing if the number of selected entities is not 1.
1501  */
edit_selected_entity()1502 void MapView::edit_selected_entity() {
1503 
1504   if (map == nullptr) {
1505     return;
1506   }
1507 
1508   EntityIndexes indexes = get_selected_entities();
1509   if (indexes.size() != 1) {
1510     return;
1511   }
1512 
1513   EntityIndex index = indexes.first();
1514   EditEntityDialog dialog(map->get_entity(index));
1515   int result = dialog.exec();
1516   if (result == QDialog::Rejected) {
1517     return;
1518   }
1519 
1520   EntityModelPtr entity_after = dialog.get_entity_after();
1521   emit edit_entity_requested(index, entity_after);
1522 }
1523 
1524 /**
1525  * @brief Converts the selected tiles to dynamic tiles or to normal tiles.
1526  */
convert_selected_tiles()1527 void MapView::convert_selected_tiles() {
1528 
1529   emit convert_tiles_requested(get_selected_entities());
1530 }
1531 
1532 /**
1533  * @brief Changes the pattern of tiles having the same pattern as the selection.
1534  */
change_pattern_of_similar_tiles()1535 void MapView::change_pattern_of_similar_tiles() {
1536 
1537   const EntityIndexes& indexes = get_selected_entities();
1538   if (indexes.empty()) {
1539     return;
1540   }
1541 
1542   const QString& pattern_id = get_map()->get_entity_field(indexes.first(), "pattern").toString();
1543   const QString& tileset_id = get_map()->get_entity_field(indexes.first(), "tileset").toString();
1544 
1545   // Find all tiles and dynamic tiles that also have this pattern.
1546   const EntityIndexes& tiles = map->find_entities_of_type(EntityType::TILE);
1547   EntityIndexes similar_tiles;
1548   for (const EntityIndex& tile : tiles) {
1549     if (get_map()->get_entity_field(tile, "pattern").toString() == pattern_id &&
1550         get_map()->get_entity_field(tile, "tileset").toString() == tileset_id ) {
1551       similar_tiles << tile;
1552     }
1553   }
1554   const EntityIndexes& dynamic_tiles = map->find_entities_of_type(EntityType::DYNAMIC_TILE);
1555   for (const EntityIndex& dynamic_tile : dynamic_tiles) {
1556     if (get_map()->get_entity_field(dynamic_tile, "pattern").toString() == pattern_id &&
1557         get_map()->get_entity_field(dynamic_tile, "tileset").toString() == tileset_id) {
1558       similar_tiles << dynamic_tile;
1559     }
1560   }
1561 
1562   emit change_tiles_pattern_requested(similar_tiles);
1563 }
1564 
1565 /**
1566  * @brief Requests to resize the given entities with the specified bounding boxes.
1567  * @param translation XY coordinates to add.
1568  * @param allow_merge_to_previous @c true to merge this move with the previous one if any.
1569  */
move_selected_entities(const QPoint & translation,bool allow_merge_to_previous)1570 void MapView::move_selected_entities(const QPoint& translation, bool allow_merge_to_previous) {
1571 
1572   emit move_entities_requested(get_selected_entities(), translation, allow_merge_to_previous);
1573 }
1574 
1575 /**
1576  * @brief Requests to move the selected entities with the specified translation.
1577  * @param boxes The new bounding box to set to each entity.
1578  * @param allow_merge_to_previous @c true to merge this resizing with the previous one if any.
1579  */
resize_entities(const QMap<EntityIndex,QRect> & boxes,bool allow_merge_to_previous)1580 void MapView::resize_entities(const QMap<EntityIndex, QRect>& boxes, bool allow_merge_to_previous) {
1581 
1582   emit resize_entities_requested(boxes, allow_merge_to_previous);
1583 }
1584 
1585 /**
1586  * @brief Requests to delete the selected entities.
1587  */
remove_selected_entities()1588 void MapView::remove_selected_entities() {
1589 
1590   emit remove_entities_requested(get_selected_entities());
1591 }
1592 
1593 /**
1594  * @brief Creates a state.
1595  * @param view The map view to manage.
1596  */
State(MapView & view)1597 MapView::State::State(MapView& view) :
1598   view(view) {
1599 
1600 }
1601 
1602 /**
1603  * @brief Returns the map view managed by this state.
1604  * @return The map view.
1605  */
get_view() const1606 const MapView& MapView::State::get_view() const {
1607   return view;
1608 }
1609 
1610 /**
1611  * @overload
1612  *
1613  * Non-const version.
1614  */
get_view()1615 MapView& MapView::State::get_view() {
1616   return view;
1617 }
1618 
1619 /**
1620  * @brief Returns the map scene managed by this state.
1621  * @return The map scene.
1622  */
get_scene() const1623 const MapScene& MapView::State::get_scene() const {
1624 
1625   return *view.get_scene();
1626 }
1627 
1628 /**
1629  * @overload
1630  *
1631  * Non-const version.
1632  */
get_scene()1633 MapScene& MapView::State::get_scene() {
1634 
1635   return *view.get_scene();
1636 }
1637 
1638 /**
1639  * @brief Returns the map model represented in the view.
1640  * @return The map model.
1641  */
get_map() const1642 const MapModel& MapView::State::get_map() const {
1643   return *view.get_map();
1644 }
1645 
1646 /**
1647  * @overload
1648  *
1649  * Non-const version.
1650  */
get_map()1651 MapModel& MapView::State::get_map() {
1652   return *view.get_map();
1653 }
1654 
1655 /**
1656  * @brief Returns the point of a mouse event in map coordinates.
1657  * @param mouse_event A mouse event.
1658  * @return The point in mouse coordinates.
1659  */
to_map_point(const QMouseEvent & mouse_event) const1660 QPoint MapView::State::to_map_point(const QMouseEvent& mouse_event) const {
1661 
1662   // Need to floor to know exactly the square that has the mouse.
1663   QPointF map_point_f = view.mapToScene(mouse_event.pos()) - MapScene::get_margin_top_left();
1664   return QPoint(
1665         qFloor(map_point_f.x()),
1666         qFloor(map_point_f.y())
1667   );
1668 }
1669 
1670 /**
1671  * @brief Called when entering this state.
1672  *
1673  * Subclasses can reimplement this function to initialize data.
1674  */
start()1675 void MapView::State::start() {
1676 }
1677 
1678 /**
1679  * @brief Called when leaving this state.
1680  *
1681  * Subclasses can reimplement this function to clean data.
1682  */
stop()1683 void MapView::State::stop() {
1684 }
1685 
1686 /**
1687  * @brief Called when the user cancels this state.
1688  *
1689  * Any ongoing action the state has should be undone here.
1690  */
cancel()1691 void MapView::State::cancel() {
1692 }
1693 
1694 /**
1695  * @brief Called when the mouse is pressed in the map view during this state.
1696  *
1697  * Subclasses can reimplement this function to define what happens.
1698  *
1699  * @param event The event to handle.
1700  */
mouse_pressed(const QMouseEvent & event)1701 void MapView::State::mouse_pressed(const QMouseEvent& event) {
1702   Q_UNUSED(event);
1703 }
1704 
1705 /**
1706  * @brief Called when the mouse is released in the map view during this state.
1707  *
1708  * Subclasses can reimplement this function to define what happens.
1709  *
1710  * @param event The event to handle.
1711  */
mouse_released(const QMouseEvent & event)1712 void MapView::State::mouse_released(const QMouseEvent& event) {
1713   Q_UNUSED(event);
1714 }
1715 
1716 /**
1717  * @brief Called when the mouse is moved in the map view during this state.
1718  *
1719  * Subclasses can reimplement this function to define what happens.
1720  *
1721  * @param event The event to handle.
1722  */
mouse_moved(const QMouseEvent & event)1723 void MapView::State::mouse_moved(const QMouseEvent& event) {
1724   Q_UNUSED(event);
1725 }
1726 
1727 /**
1728  * @brief Called when a context menu is requested in the map view during this
1729  * state.
1730  *
1731  * Subclasses can reimplement this function to show a context menu.
1732  *
1733  * @param where Where to show the context menu, in global coordinates.
1734  */
context_menu_requested(const QPoint & where)1735 void MapView::State::context_menu_requested(const QPoint& where) {
1736   Q_UNUSED(where);
1737 }
1738 
1739 /**
1740  * @brief Called when the user changes the selection in the tileset.
1741  *
1742  * States may start or stop adding entities.
1743  *
1744  * @param tileset_id Id of the tileset to use (empty means the one of the map).
1745  * @param indexes Indexes of the selected patterns.
1746  */
tileset_selection_changed(const QString & tileset_id,const QList<int> & indexes)1747 void MapView::State::tileset_selection_changed(const QString& tileset_id, const QList<int>& indexes) {
1748   Q_UNUSED(tileset_id);
1749   Q_UNUSED(indexes);
1750 }
1751 
1752 /**
1753  * @brief Constructor.
1754  * @param view The map view to manage.
1755  */
DoingNothingState(MapView & view)1756 DoingNothingState::DoingNothingState(MapView& view) :
1757   MapView::State(view),
1758   clicked_with_control_or_shift(false) {
1759 
1760 }
1761 
1762 /**
1763  * @copydoc MapView::State::mouse_pressed
1764  */
mouse_pressed(const QMouseEvent & event)1765 void DoingNothingState::mouse_pressed(const QMouseEvent& event) {
1766 
1767   if (event.button() != Qt::LeftButton && event.button() != Qt::RightButton) {
1768     return;
1769   }
1770 
1771   MapView& view = get_view();
1772   MapScene& scene = get_scene();
1773 
1774   mouse_pressed_point = event.pos();
1775 
1776   // Left or right button: possibly change the selection.
1777   QList<QGraphicsItem*> items_under_mouse = view.items(
1778         QRect(event.pos(), QSize(1, 1)),
1779         Qt::IntersectsItemBoundingRect  // Pick transparent items too.
1780   );
1781   QGraphicsItem* item = items_under_mouse.isEmpty() ? nullptr : items_under_mouse.first();
1782   const EntityItem* entity_item = qgraphicsitem_cast<const EntityItem*>(item);
1783 
1784   const bool control_or_shift = (event.modifiers() & (Qt::ControlModifier | Qt::ShiftModifier));
1785 
1786   bool keep_selected = false;
1787   if (control_or_shift) {
1788     // If ctrl or shift is pressed, keep the existing selection.
1789     keep_selected = true;
1790   }
1791   else if (item != nullptr && item->isSelected()) {
1792     // When clicking an already selected item, keep the existing selection too.
1793     keep_selected = true;
1794   }
1795 
1796   if (!keep_selected) {
1797     scene.clearSelection();
1798   }
1799 
1800   if (event.button() == Qt::LeftButton) {
1801 
1802     if (item != nullptr) {
1803 
1804       if (control_or_shift) {
1805         // Either toggle the clicked item or start a selection rectangle.
1806         // If will depend on wether the mouse moves before it is released.
1807         clicked_with_control_or_shift = true;
1808       }
1809       else {
1810         if (!item->isSelected()) {
1811           // Select the item.
1812           if (entity_item != nullptr) {
1813             const EntityIndex& index = entity_item->get_index();
1814             if (!view.get_view_settings()->is_layer_locked(index.layer)) {
1815               view.select_entity(index, true);
1816             }
1817             else {
1818               // Left click on a locked layer: trace a selection rectangle.
1819               view.start_state_drawing_rectangle(event.pos());
1820               return;
1821             }
1822           }
1823         }
1824         // Allow to move selected items.
1825         view.start_state_moving_entities(event.pos());
1826       }
1827     }
1828     else {
1829       // Left click outside items: trace a selection rectangle.
1830       view.start_state_drawing_rectangle(event.pos());
1831     }
1832   }
1833 
1834   else if (event.button() == Qt::RightButton) {
1835 
1836     if (entity_item != nullptr) {
1837       if (!entity_item->isSelected()) {
1838         // Select the right-clicked item.
1839         const EntityIndex& index = entity_item->get_index();
1840         if (!view.get_view_settings()->is_layer_locked(index.layer)) {
1841           view.select_entity(index, true);
1842         }
1843       }
1844     }
1845   }
1846 }
1847 
1848 /**
1849  * @copydoc MapView::State::mouse_moved
1850  */
mouse_moved(const QMouseEvent & event)1851 void DoingNothingState::mouse_moved(const QMouseEvent& event) {
1852 
1853   if (clicked_with_control_or_shift) {
1854 
1855     // Moving the mouse while control or shift is pressed:
1856     // start a selection rectangle after a small distance threshold.
1857     QPoint current_point = event.pos();
1858     if ((current_point - mouse_pressed_point).manhattanLength() >= 4) {
1859       // Significant move: not a click.
1860       // Start a selection rectangle.
1861       MapView& view = get_view();
1862       view.start_state_drawing_rectangle(mouse_pressed_point);
1863     }
1864   }
1865 }
1866 
1867 /**
1868  * @copydoc MapView::State::mouse_released
1869  */
mouse_released(const QMouseEvent & event)1870 void DoingNothingState::mouse_released(const QMouseEvent& event) {
1871 
1872   if (event.button() != Qt::LeftButton) {
1873     return;
1874   }
1875 
1876   if (clicked_with_control_or_shift) {
1877     // Left-clicking an item while pressing control or shift: toggle it.
1878     // If the mouse had moved in the meantime, mouse_moved() would have started
1879     // a selection rectangle.
1880     MapView& view = get_view();
1881 
1882     QList<QGraphicsItem*> items_under_mouse = view.items(
1883           QRect(event.pos(), QSize(1, 1)),
1884           Qt::IntersectsItemBoundingRect  // Pick transparent items too.
1885     );
1886     QGraphicsItem* item = items_under_mouse.isEmpty() ? nullptr : items_under_mouse.first();
1887     const EntityItem* entity_item = qgraphicsitem_cast<const EntityItem*>(item);
1888     if (entity_item != nullptr) {
1889       const bool was_selected = item->isSelected();
1890       if (was_selected) {
1891         view.select_entity(entity_item->get_index(), false);
1892       }
1893       else {
1894         const bool layer_locked = view.get_view_settings()->is_layer_locked(entity_item->get_index().layer);
1895         if (!layer_locked) {
1896           view.select_entity(entity_item->get_index(), true);
1897         }
1898       }
1899     }
1900     clicked_with_control_or_shift = false;
1901   }
1902 }
1903 
1904 /**
1905  * @copydoc MapView::State::context_menu_requested
1906  */
context_menu_requested(const QPoint & where)1907 void DoingNothingState::context_menu_requested(const QPoint& where) {
1908 
1909   MapView& view = get_view();
1910   QMenu* menu = view.create_context_menu();
1911   menu->popup(where);
1912 }
1913 
1914 /**
1915  * @copydoc MapView::State::tileset_selection_changed
1916  */
tileset_selection_changed(const QString & tileset_id,const QList<int> & indexes)1917 void DoingNothingState::tileset_selection_changed(const QString& tileset_id, const QList<int>& indexes) {
1918 
1919   // Create corresponding tiles.
1920   get_view().start_adding_entities_from_tileset(tileset_id, indexes);
1921 }
1922 
1923 /**
1924  * @brief Constructor.
1925  * @param view The map view to manage.
1926  * @param initial_point Point where the drawing started, in view coordinates.
1927  */
DrawingRectangleState(MapView & view,const QPoint & initial_point)1928 DrawingRectangleState::DrawingRectangleState(MapView& view, const QPoint& initial_point) :
1929   MapView::State(view),
1930   initial_point(view.mapToScene(initial_point).toPoint()),
1931   current_area_item(nullptr),
1932   initial_selection() {
1933 
1934 }
1935 
1936 /**
1937  * @copydoc MapView::State::start
1938  */
start()1939 void DrawingRectangleState::start() {
1940 
1941   current_area_item = new QGraphicsRectItem();
1942   current_area_item->setZValue(get_map().get_max_layer() + 2);
1943   current_area_item->setPen(QPen(Qt::yellow));
1944   get_scene().addItem(current_area_item);
1945   initial_selection = get_scene().selectedItems();
1946 }
1947 
1948 /**
1949  * @copydoc MapView::State::stop
1950  */
stop()1951 void DrawingRectangleState::stop() {
1952 
1953   get_scene().removeItem(current_area_item);
1954   delete current_area_item;
1955   current_area_item = nullptr;
1956 }
1957 
1958 /**
1959  * @copydoc MapView::State::mouse_moved
1960  */
mouse_moved(const QMouseEvent & event)1961 void DrawingRectangleState::mouse_moved(const QMouseEvent& event) {
1962 
1963   MapView& view = get_view();
1964   MapScene& scene = get_scene();
1965 
1966   // Compute the selected area.
1967   QPoint previous_point = current_point;
1968   current_point = view.mapToScene(event.pos()).toPoint();
1969 
1970   if (current_point == previous_point) {
1971     // No change.
1972     return;
1973   }
1974 
1975   // The area has changed: recalculate the rectangle.
1976   QRect area = Rectangle::from_two_points(initial_point, current_point);
1977   current_area_item->setRect(area);
1978 
1979   bool was_blocked = scene.signalsBlocked();
1980   if (!initial_selection.isEmpty()) {
1981     // Block QGraphicsScene::selectionChanged() signal for individual selects.
1982     scene.blockSignals(true);
1983   }
1984 
1985   // Select items strictly in the rectangle.
1986   scene.clearSelection();
1987   QPainterPath path;
1988   path.addRect(QRect(area.topLeft() - QPoint(1, 1),
1989                      area.size() + QSize(2, 2)));
1990   scene.setSelectionArea(path, Qt::ContainsItemBoundingRect);
1991 
1992   // But don't select entities on locked layers.
1993   const EntityIndexes selected_indexes = scene.get_selected_entities();
1994   const ViewSettings& view_settings = *view.get_view_settings();
1995   for (const EntityIndex& index : selected_indexes) {
1996     if (view_settings.is_layer_locked(index.layer)) {
1997       view.select_entity(index, false);
1998     }
1999   }
2000 
2001   // Also restore the initial selection.
2002   for (int i = 0; i < initial_selection.size(); ++i) {
2003     const EntityItem* entity_item = qgraphicsitem_cast<const EntityItem*>(initial_selection[i]);
2004 
2005     // Unblock signals before the last select.
2006     if (i == initial_selection.size() - 1 ) {
2007       // Last element.
2008       scene.blockSignals(was_blocked);
2009     }
2010 
2011     view.select_entity(entity_item->get_index(), true);
2012   }
2013 }
2014 
2015 /**
2016  * @copydoc MapView::State::mouse_released
2017  */
mouse_released(const QMouseEvent & event)2018 void DrawingRectangleState::mouse_released(const QMouseEvent& event) {
2019 
2020   Q_UNUSED(event);
2021 
2022   get_view().start_state_doing_nothing();
2023 }
2024 
2025 /**
2026  * @brief Constructor.
2027  * @param view The map view to manage.
2028  * @param initial_point Point where the dragging started, in view coordinates.
2029  */
MovingEntitiesState(MapView & view,const QPoint & initial_point)2030 MovingEntitiesState::MovingEntitiesState(MapView& view, const QPoint& initial_point) :
2031   MapView::State(view),
2032   initial_point(Point::floor_8(view.mapToScene(initial_point))),
2033   last_point(this->initial_point),
2034   first_move_done(false) {
2035 
2036 }
2037 
2038 /**
2039  * @copydoc MapView::State::cancel
2040  */
cancel()2041 void MovingEntitiesState::cancel() {
2042 
2043   get_view().undo_last_command();
2044 }
2045 
2046 /**
2047  * @copydoc MapView::State::mouse_moved
2048  */
mouse_moved(const QMouseEvent & event)2049 void MovingEntitiesState::mouse_moved(const QMouseEvent& event) {
2050 
2051   MapView& view = get_view();
2052 
2053   QPoint current_point = Point::floor_8(view.mapToScene(event.pos()));
2054   if (current_point == last_point) {
2055     // No change after rounding.
2056     return;
2057   }
2058 
2059   // Make selected entities follow the mouse while dragging.
2060   QPoint translation = current_point - last_point;
2061   last_point = current_point;
2062 
2063   // Merge undo actions of successive moves,
2064   // but don't merge the first one of this state instance to potential previous states.
2065   const bool allow_merge_to_previous = first_move_done;
2066   view.move_selected_entities(translation, allow_merge_to_previous);
2067   first_move_done = true;
2068 }
2069 
2070 /**
2071  * @copydoc MapView::State::mouse_released
2072  */
mouse_released(const QMouseEvent & event)2073 void MovingEntitiesState::mouse_released(const QMouseEvent& event) {
2074 
2075   Q_UNUSED(event);
2076 
2077   get_view().start_state_doing_nothing();
2078 }
2079 
2080 /**
2081  * @brief Constructor.
2082  * @param view The map view to manage.
2083  * @param entities The entities to resize.
2084  */
ResizingEntitiesState(MapView & view,const EntityIndexes & entities)2085 ResizingEntitiesState::ResizingEntitiesState(
2086     MapView& view, const EntityIndexes& entities) :
2087   MapView::State(view),
2088   entities(entities),
2089   old_boxes(),
2090   leader_index(),
2091   center(),
2092   first_resize_done(false),
2093   num_free_entities(0) {
2094 }
2095 
2096 /**
2097  * @copydoc MapView::State::cancel
2098  */
cancel()2099 void ResizingEntitiesState::cancel() {
2100 
2101   get_view().undo_last_command();
2102 }
2103 
2104 /**
2105  * @brief Determines the center of the entities to resize.
2106  */
compute_center()2107 void ResizingEntitiesState::compute_center() {
2108 
2109   // Compute the total bounding box to determine its center.
2110   MapModel& map = get_map();
2111   QRect total_box = map.get_entity_bounding_box(entities.first());
2112   for (const EntityIndex& index : entities) {
2113     total_box |= map.get_entity_bounding_box(index);
2114   }
2115   center = total_box.center();
2116 }
2117 
2118 /**
2119  * @brief Determines the entity whose resizing follows the cursor position.
2120  * Other ones will reproduce an equivalent change.
2121  */
compute_leader()2122 void ResizingEntitiesState::compute_leader() {
2123 
2124   // Among the most freely resizable entities,
2125   // the leader will be the closest to the mouse.
2126   const std::vector<ResizeMode> resize_modes_by_priority = {
2127     ResizeMode::MULTI_DIMENSION_ALL,
2128     ResizeMode::MULTI_DIMENSION_ONE,
2129     ResizeMode::SINGLE_DIMENSION,
2130     ResizeMode::SQUARE,
2131     ResizeMode::HORIZONTAL_ONLY,
2132     ResizeMode::VERTICAL_ONLY,
2133     ResizeMode::NONE
2134   };
2135 
2136   MapModel& map = get_map();
2137   MapView& view = get_view();
2138 
2139   const QPoint mouse_position_in_view = view.mapFromGlobal(QCursor::pos());
2140   QPoint mouse_position = view.mapToScene(mouse_position_in_view).toPoint() - MapScene::get_margin_top_left();
2141 
2142   bool found_leader = false;
2143   for (ResizeMode wanted_resize_mode : resize_modes_by_priority) {
2144     int min_distance = std::numeric_limits<int>::max();
2145     if (found_leader) {
2146       min_distance = 0;  // Don't search a leader with this resize mode.
2147     }
2148     for (const EntityIndex& index : entities) {
2149       const EntityModel& entity = map.get_entity(index);
2150 
2151       if (entity.get_resize_mode() != wanted_resize_mode) {
2152         continue;
2153       }
2154 
2155       // Save the initial position of entities.
2156       old_boxes.insert(index, entity.get_bounding_box());
2157 
2158       // Count entities whose resize mode is MULTI_DIMENSION_ALL.
2159       if (entity.get_resize_mode() == ResizeMode::MULTI_DIMENSION_ALL) {
2160         ++num_free_entities;
2161       }
2162 
2163       // Determine a leader if none was found with previous resize modes.
2164       found_leader = true;
2165       const QPoint& entity_center = entity.get_center();
2166       int distance = (entity_center - mouse_position).manhattanLength();
2167       if (distance < min_distance) {
2168         leader_index = index;
2169         min_distance = distance;
2170       }
2171     }
2172   }
2173   Q_ASSERT(leader_index.is_valid());
2174 }
2175 
2176 /**
2177  * Determines which corner of entities should be fixed
2178  * and which one should follow the mouse.
2179  */
compute_fixed_corner()2180 void ResizingEntitiesState::compute_fixed_corner() {
2181 
2182   // Decide the resizing directions depending
2183   // on the mouse position relative to the leader.
2184   Q_ASSERT(leader_index.is_valid());
2185 
2186   MapModel& map = get_map();
2187   MapView& view = get_view();
2188 
2189   const QPoint mouse_position_in_view = view.mapFromGlobal(QCursor::pos());
2190   QPoint mouse_position = view.mapToScene(mouse_position_in_view).toPoint() - MapScene::get_margin_top_left();
2191 
2192   const EntityModel& entity = map.get_entity(leader_index);
2193   const QPoint& entity_center = entity.get_center();
2194 
2195   fixed_corner.setX(mouse_position.x() >= entity_center.x() ? -1 : 1);
2196   fixed_corner.setY(mouse_position.y() >= entity_center.y() ? -1 : 1);
2197 }
2198 
2199 /**
2200  * @copydoc MapView::State::start
2201  */
start()2202 void ResizingEntitiesState::start() {
2203 
2204   // Determine the center of the entities to resize.
2205   compute_center();
2206 
2207   // Determine the leader entity.
2208   compute_leader();
2209 
2210   // Determine which corner of entities will be fixed
2211   // and which one will follow the mouse.
2212   compute_fixed_corner();
2213 }
2214 
2215 /**
2216  * @copydoc MapView::State::mouse_moved
2217  */
mouse_moved(const QMouseEvent & event)2218 void ResizingEntitiesState::mouse_moved(const QMouseEvent& event) {
2219 
2220   const MapModel& map = get_map();
2221   const EntityModel& leader = map.get_entity(leader_index);
2222   const QSize& leader_base_size = leader.get_base_size();
2223 
2224   // Need to floor to know exactly which 8x8 square has the mouse.
2225   QPoint current_point = to_map_point(event);
2226 
2227   const QRect& old_leader_box = old_boxes.value(leader_index);
2228 
2229   // See which corner of the leader is following the mouse.
2230   QPoint leader_free_corner = old_leader_box.topLeft();
2231   if (fixed_corner.x() == -1) {
2232     // The left side is fixed, the right side moves.
2233     leader_free_corner.rx() += old_leader_box.width() - leader_base_size.width();
2234   }
2235   if (fixed_corner.y() == -1) {
2236     // The top side is fixed, the bottom side moves.
2237     leader_free_corner.ry() += old_leader_box.height() - leader_base_size.height();
2238   }
2239 
2240   QPoint leader_distance_to_mouse = current_point - leader_free_corner;
2241 
2242   // Choose once for all entitites the preferred dimension to use
2243   // in case resizing is constrained.
2244   bool horizontal_preferred = qAbs(leader_distance_to_mouse.x()) > qAbs(leader_distance_to_mouse.y());
2245 
2246   // Determine the change to apply to all selected entities.
2247   QSize reference_base_size(8, 8);
2248   ResizeMode leader_resize_mode = leader.get_resize_mode();
2249   if (is_horizontally_resizable(leader_resize_mode, horizontal_preferred)) {
2250     // If the leader has a base size of 16x16, it is better to make all
2251     // entities resize this way as well (if they can).
2252     reference_base_size.setWidth(leader_base_size.width());
2253   }
2254   if (is_vertically_resizable(leader_resize_mode, horizontal_preferred)) {
2255     reference_base_size.setHeight(leader_base_size.height());
2256   }
2257 
2258   QPoint leader_expansion = Point::round_down(leader_distance_to_mouse, reference_base_size);
2259 
2260   // Determine if at least one entity is resizable horizontally and
2261   // if at least one entity is resizable vertically.
2262   bool is_resizing_horizontally = false;
2263   bool is_resizing_vertically = false;
2264   for (auto it = old_boxes.constBegin(); it != old_boxes.constEnd(); ++it) {
2265     const EntityIndex& index = it.key();
2266     const EntityModel& entity = map.get_entity(index);
2267     is_resizing_horizontally |= is_horizontally_resizable(entity.get_resize_mode(), horizontal_preferred);
2268     is_resizing_vertically |= is_vertically_resizable(entity.get_resize_mode(), horizontal_preferred);
2269 
2270     if (is_resizing_horizontally && is_resizing_vertically) {
2271       break;
2272     }
2273   }
2274   if (!is_resizing_horizontally) {
2275     // Don't move anything horizontally if nothing can change horizontally.
2276     // We need to take care of this because with smart resizing,
2277     // non horizontally resizable entities could still be moved
2278     // to follow the ones that are.
2279     leader_expansion.setX(0);
2280   }
2281   if (!is_resizing_vertically) {
2282     // Same thing vertically.
2283     leader_expansion.setY(0);
2284   }
2285 
2286   // Apply the change to the entities.
2287   update_boxes(leader_expansion, horizontal_preferred);
2288 }
2289 
2290 /**
2291  * @copydoc MapView::State::mouse_released
2292  */
mouse_released(const QMouseEvent & event)2293 void ResizingEntitiesState::mouse_released(const QMouseEvent& event) {
2294 
2295   MapView& view = get_view();
2296   if (event.button() == Qt::RightButton) {
2297     // The right button was pressed during the resize:
2298     // add copies of the entities.
2299     EntityModels clones = view.clone_selected_entities();
2300 
2301     if (clones.size() == 1 && first_resize_done) {
2302       // A real resize was done.
2303       // We are probably adding successive entities like tiles and resizing
2304       // each one (contrary to just copying entities).
2305       // In this case, each new clone should be reset to the base size.
2306       const EntityModelPtr& clone = *clones.begin();
2307       clone->set_size(clone->get_base_size());
2308     }
2309     const bool guess_layer = false;
2310     view.start_state_adding_entities(std::move(clones), guess_layer);
2311   }
2312   else {
2313     get_view().start_state_doing_nothing();
2314   }
2315 }
2316 
2317 /**
2318  * @brief Applies the resizing to the bounding boxes of entities.
2319  * @param leader_expansion How much to expand the current box.
2320  * @param horizontal_preferred When only one of both dimensions can be resized, whether
2321  * this should be the horizontal or vertical dimension.
2322  *
2323  * This change will be applied at best, respecting the constraints of
2324  * each entity.
2325  */
update_boxes(const QPoint & leader_expansion,bool horizontal_preferred)2326 void ResizingEntitiesState::update_boxes(
2327     const QPoint& leader_expansion,
2328     bool horizontal_preferred
2329 ) {
2330 
2331   // Compute the new size and position of each entity.
2332   QMap<EntityIndex, QRect> new_boxes;
2333   for (auto it = old_boxes.constBegin(); it != old_boxes.constEnd(); ++it) {
2334     const EntityIndex& index = it.key();
2335     QRect new_box = update_box(
2336           index,
2337           leader_expansion,
2338           horizontal_preferred
2339     );
2340     new_boxes.insert(index, new_box);
2341   }
2342 
2343   const bool allow_merge_to_previous = first_resize_done;
2344   get_view().resize_entities(new_boxes, allow_merge_to_previous);
2345   first_resize_done = true;
2346 }
2347 
2348 /**
2349  * @brief Updates with new coordinates the rectangle of one entity.
2350  * @param index Index of the entity to resize.
2351  * @param leader_expansion How much the user wants to expand.
2352  * This expansion will be applied at best, preserving constraints on
2353  * the specific entity: its size will remain a multiple of its base size,
2354  * and its resizing mode will be respected.
2355  * @param horizontal_preferred When only one of both dimensions can be resized,
2356  * whether this should be the horizontal or vertical dimension.
2357  * @return A new bounding box for the entity.
2358  * Returns a null rectangle in case of error.
2359  */
update_box(const EntityIndex & index,const QPoint & leader_expansion,bool horizontal_preferred)2360 QRect ResizingEntitiesState::update_box(
2361     const EntityIndex& index,
2362     const QPoint& leader_expansion,
2363     bool horizontal_preferred) {
2364 
2365   MapModel& map = get_map();
2366   Q_ASSERT(map.entity_exists(index));
2367   EntityModel& entity = map.get_entity(index);
2368 
2369   // Choose the appropriate resize mode.
2370   ResizeMode resize_mode = entity.get_resize_mode();
2371   if (num_free_entities > 1 && resize_mode == ResizeMode::MULTI_DIMENSION_ALL) {
2372     // Multiple resize: restrict the resizing to only one dimension.
2373     resize_mode = ResizeMode::MULTI_DIMENSION_ONE;
2374   }
2375 
2376   QRect new_box = apply_smart_resizing(
2377       index,
2378       resize_mode,
2379       horizontal_preferred,
2380       leader_expansion
2381   );
2382 
2383   // Apply resize mode constraints to the bounding box.
2384   new_box = apply_constraints(index, resize_mode, horizontal_preferred, new_box);
2385 
2386   return new_box;
2387 }
2388 
2389 /**
2390  * @brief Decides how to expand or translate an entity.
2391  *
2392  * When trying to resize a non horizontally resizable entity located
2393  * on the right of horizontally resizable things, we move it instead.
2394  * This allows to resize a full room in only one operation:
2395  *     ______          ________________
2396  *     |....|          |..............|
2397  *     |....|   ===>   |..............|
2398  *     |____|          |______________|
2399  *
2400  * We call this "smart resizing".
2401  * Here, the right wall is not horizontally resizable,
2402  * but when resizing the full room, it moves
2403  * to the right instead of expanding to the right.
2404  * The expansion is replaced by a translation.
2405  *
2406  * @param index Index of the entity to resize.
2407  * @param resize_mode Current resize mode for the entity.
2408  * @param horizontal_preferred When only one of both dimensions can be resized,
2409  * whether this should be the horizontal or vertical dimension.
2410  * @param leader_expansion How the leader entity gets expanded
2411  * @return The resulting bounding box.
2412  */
apply_smart_resizing(const EntityIndex & index,ResizeMode resize_mode,bool horizontal_preferred,const QPoint & leader_expansion)2413 QRect ResizingEntitiesState::apply_smart_resizing(
2414     const EntityIndex& index,
2415     ResizeMode resize_mode,
2416     bool horizontal_preferred,
2417     const QPoint& leader_expansion
2418 ) {
2419   MapModel& map = get_map();
2420   EntityModel& entity = map.get_entity(index);
2421   const QSize& base_size = entity.get_base_size();
2422   const QRect& old_box = old_boxes.value(index);
2423 
2424   // Expand like the leader, but keeping a multiple of the base size.
2425   QPoint expansion = Point::ceil(leader_expansion, base_size);
2426   QPoint translation(0, 0);
2427 
2428   // Horizontal smart resizing.
2429   if (!is_horizontally_resizable(resize_mode, horizontal_preferred)) {
2430 
2431     if (fixed_corner.x() == -1 &&
2432         old_box.center().x() > center.x()) {
2433       translation.setX(leader_expansion.x());
2434     }
2435     else if (fixed_corner.x() == 1 &&
2436         old_box.center().x() < center.x()) {
2437       translation.setX(leader_expansion.x());
2438     }
2439     expansion.setX(0);
2440   }
2441 
2442   // Vertical smart resizing.
2443   if (!is_vertically_resizable(resize_mode, horizontal_preferred)) {
2444     // Smart resizing.
2445     if (fixed_corner.y() == -1 &&
2446         old_box.center().y() > center.y()) {
2447       translation.setY(leader_expansion.y());
2448     }
2449     else if (fixed_corner.y() == 1 &&
2450         old_box.center().y() < center.y()) {
2451       translation.setY(leader_expansion.y());
2452     }
2453     expansion.setY(0);
2454   }
2455 
2456   // Compute the bounding box from the expansion and translation.
2457   return get_box_from_expansion_and_translation(index, expansion, translation);
2458 }
2459 
2460 /**
2461  * @brief Returns the bounding box of an entity expanded with the given amount.
2462  * @param index Index of an entity.
2463  * @param expansion How much to extend it in both directions, keeping the
2464  * current fixed corner.
2465  * @param translation How much to translate the entity.
2466  * @return The corresponding new bounding box.
2467  * The resize mode is not taken into account at this point
2468  * (this function acts like if the resize mode is the most permissive one:
2469  * ResizeMode::MULTI_DIMENSION_ALL).
2470  */
get_box_from_expansion_and_translation(const EntityIndex & index,const QPoint & expansion,const QPoint & translation)2471 QRect ResizingEntitiesState::get_box_from_expansion_and_translation(
2472     const EntityIndex& index,
2473     const QPoint& expansion,
2474     const QPoint& translation
2475 ) {
2476   MapModel& map = get_map();
2477   EntityModel& entity = map.get_entity(index);
2478   const QSize& base_size = entity.get_base_size();
2479   const QRect& old_box = old_boxes.value(index);
2480   QRect new_box = old_box;
2481 
2482   // Expansion.
2483   if (fixed_corner.x() == -1) {
2484     // Left side fixed, right side free.
2485     int width = old_box.width() + expansion.x();
2486     if (width > 0) {
2487       new_box.setWidth(width);
2488     }
2489     else {
2490       new_box.setWidth(-width + 2 * base_size.width());
2491       new_box.translate(width - base_size.width(), 0);
2492     }
2493   }
2494   else {
2495     // Right side fixed, left side free.
2496     int width = old_box.width() - expansion.x();
2497     if (width > 0) {
2498       new_box.setWidth(width);
2499       new_box.translate(expansion.x(), 0);
2500     }
2501     else {
2502       new_box.setWidth(-width + 2 * base_size.width());
2503       new_box.translate(old_box.width() - base_size.width(), 0);
2504     }
2505   }
2506   if (fixed_corner.y() == -1) {
2507     // Top side fixed, bottom side free.
2508     int height = old_box.height() + expansion.y();
2509     if (height > 0) {
2510       new_box.setHeight(height);
2511     }
2512     else {
2513       new_box.setHeight(-height + 2 * base_size.height());
2514       new_box.translate(0, height - base_size.height());
2515     }
2516   }
2517   else {
2518     // Bottom side fixed, top side free.
2519     int height = old_box.height() - expansion.y();
2520     if (height > 0) {
2521       new_box.setHeight(height);
2522       new_box.translate(0, expansion.y());
2523     }
2524     else {
2525       new_box.setHeight(-height + 2 * base_size.height());
2526       new_box.translate(0, old_box.height() - base_size.height());
2527     }
2528   }
2529 
2530   // Translation.
2531   if (!translation.isNull()) {
2532     new_box.translate(translation);
2533   }
2534 
2535   return new_box;
2536 }
2537 
2538 /**
2539  * Ensures that a bounding box respects the constraints of the resize mode.
2540  * @param index Index of the entity to resize.
2541  * @param resize_mode Current resize mode for thi entity.
2542  * @param horizontal_preferred When only one of both dimensions can be resized,
2543  * whether this should be the horizontal or vertical dimension.
2544  * @param box The candidate bounding box, before applying constraints.
2545  * Assumed to be a multiple of the base size.
2546  * @return The resulting bounding box.
2547  */
apply_constraints(const EntityIndex & index,ResizeMode resize_mode,bool horizontal_preferred,const QRect & box)2548 QRect ResizingEntitiesState::apply_constraints(
2549     const EntityIndex& index,
2550     ResizeMode resize_mode,
2551     bool horizontal_preferred,
2552     const QRect& box
2553 ) {
2554   MapModel& map = get_map();
2555   EntityModel& entity = map.get_entity(index);
2556   const QSize& base_size = entity.get_base_size();
2557   const QRect& old_box = old_boxes.value(index);
2558   QRect new_box = box;
2559 
2560   // Only in one direction: choose which one.
2561   if (resize_mode == ResizeMode::SINGLE_DIMENSION) {
2562     resize_mode = horizontal_preferred ? ResizeMode::HORIZONTAL_ONLY : ResizeMode::VERTICAL_ONLY;
2563   }
2564 
2565   // If we are going to fix the size, first we need to decide where to anchor
2566   // the entity after the change of size.
2567   QRect base_box = old_box;
2568   base_box.setSize(base_size);
2569   if (fixed_corner.x() == 1) {
2570     base_box.setRight(old_box.right());
2571   }
2572   if (fixed_corner.y() == 1) {
2573     base_box.setBottom(old_box.bottom());
2574   }
2575   bool anchor_to_right = box.right() == base_box.right();
2576   bool anchor_to_bottom = box.bottom() == base_box.bottom();
2577 
2578   // Fix the size or not depending on the resize mode.
2579   switch (resize_mode) {
2580 
2581   case ResizeMode::SINGLE_DIMENSION:
2582   {
2583     // Already handled (see above).
2584     break;
2585   }
2586 
2587   case ResizeMode::MULTI_DIMENSION_ALL:
2588   {
2589     // No additional constraint.
2590     break;
2591   }
2592 
2593   case ResizeMode::NONE:
2594   {
2595     // The size must always be the base size.
2596     if (new_box.size() != base_size) {
2597       new_box.setSize(base_size);
2598       if (anchor_to_right) {
2599         new_box.moveRight(base_box.right());
2600       }
2601       if (anchor_to_bottom) {
2602         new_box.moveBottom(base_box.bottom());
2603       }
2604     }
2605     break;
2606   }
2607 
2608   case ResizeMode::HORIZONTAL_ONLY:
2609   {
2610     // The height must be the base height.
2611     if (new_box.height() != base_size.height()) {
2612       new_box.setHeight(base_size.height());
2613       if (anchor_to_bottom) {
2614         new_box.moveBottom(base_box.bottom());
2615       }
2616     }
2617     break;
2618   }
2619 
2620   case ResizeMode::VERTICAL_ONLY:
2621   {
2622     // The width must be the base width.
2623     if (new_box.width() != base_size.width()) {
2624       new_box.setWidth(base_size.width());
2625       if (anchor_to_right) {
2626         new_box.moveRight(base_box.right());
2627       }
2628     }
2629     break;
2630   }
2631 
2632   case ResizeMode::MULTI_DIMENSION_ONE:
2633   {
2634     // The width or height must remain unchanged.
2635     if (horizontal_preferred) {
2636       // The width must be the previous width.
2637       if (new_box.width() != old_box.width()) {
2638         new_box.setWidth(old_box.width());
2639         if (anchor_to_right) {
2640           new_box.moveBottom(base_box.bottom());
2641         }
2642       }
2643     }
2644     else {
2645       // The height must be the previous height.
2646       if (new_box.height() != old_box.height()) {
2647         new_box.setHeight(old_box.height());
2648         if (anchor_to_bottom) {
2649           new_box.moveBottom(base_box.bottom());
2650         }
2651       }
2652     }
2653     break;
2654   }
2655 
2656   case ResizeMode::SQUARE:
2657   {
2658     // The width and height must be equal.
2659     if (new_box.width() != new_box.height()) {
2660       int max = qMax(new_box.width(), new_box.height());
2661       new_box.setSize(QSize(max, max));
2662       if (anchor_to_right) {
2663         new_box.moveRight(base_box.right());
2664       }
2665       if (anchor_to_bottom) {
2666         new_box.moveBottom(base_box.bottom());
2667       }
2668     }
2669     break;
2670   }
2671 
2672   }  // switch
2673 
2674   return new_box;
2675 }
2676 
2677 /**
2678  * @brief Returns whether the given settings allow to resize horizontally.
2679  * @param resize_mode The resize mode to test.
2680  * @param horizontal_preferred When only one of both dimensions can be resized, whether
2681  * this should be the horizontal or vertical dimension.
2682  */
is_horizontally_resizable(ResizeMode resize_mode,bool horizontal_preferred)2683 bool ResizingEntitiesState::is_horizontally_resizable(
2684     ResizeMode resize_mode, bool horizontal_preferred) {
2685 
2686   return resize_mode == ResizeMode::HORIZONTAL_ONLY ||
2687       resize_mode == ResizeMode::MULTI_DIMENSION_ALL ||
2688       resize_mode == ResizeMode::MULTI_DIMENSION_ONE ||
2689       resize_mode == ResizeMode::SQUARE ||
2690       (resize_mode == ResizeMode::SINGLE_DIMENSION && horizontal_preferred);
2691 }
2692 
2693 /**
2694  * @brief Returns whether the given settings allow to resize vertically.
2695  * @param resize_mode The resize mode to test.
2696  * @param horizontal_preferred When only one of both dimensions can be resized, whether
2697  * this should be the horizontal or vertical dimension.
2698  */
is_vertically_resizable(ResizeMode resize_mode,bool horizontal_preferred)2699 bool ResizingEntitiesState::is_vertically_resizable(
2700     ResizeMode resize_mode, bool horizontal_preferred) {
2701 
2702   return resize_mode == ResizeMode::VERTICAL_ONLY ||
2703       resize_mode == ResizeMode::MULTI_DIMENSION_ALL ||
2704       resize_mode == ResizeMode::MULTI_DIMENSION_ONE ||
2705       resize_mode == ResizeMode::SQUARE ||
2706       (resize_mode == ResizeMode::SINGLE_DIMENSION && !horizontal_preferred);
2707 }
2708 
2709 /**
2710  * @brief Constructor.
2711  * @param view The map view to manage.
2712  * @param entities The entities to be added to the map.
2713  * @param guess_layer Whether a layer should be guessed from the preferred
2714  * layer of entities and the mouse position.
2715  * If @c false, they will be added on their layer indicated by
2716  * EntityModel::get_layer().
2717  */
AddingEntitiesState(MapView & view,EntityModels && entities,bool guess_layer)2718 AddingEntitiesState::AddingEntitiesState(MapView& view, EntityModels&& entities, bool guess_layer) :
2719   MapView::State(view),
2720   entities(std::move(entities)),
2721   entity_items(),
2722   guess_layer(guess_layer) {
2723 
2724   for (const EntityModelPtr& entity : this->entities) {
2725     EntityItem* item = new EntityItem(*entity);
2726     item->setZValue(get_map().get_max_layer() + 1);
2727     entity_items.push_back(item);
2728   }
2729 }
2730 
2731 /**
2732  * @copydoc MapView::State::start
2733  */
start()2734 void AddingEntitiesState::start() {
2735 
2736   MapView& view = get_view();
2737   QPoint mouse_position = view.mapFromGlobal(QCursor::pos());
2738   last_point = Point::floor_8(view.mapToScene(mouse_position));
2739 
2740   // Determine the center of all entities in their current position.
2741   QPoint center = get_entities_center();
2742 
2743   // Add the graphic item of each entity.
2744   for (EntityItem* item : entity_items) {
2745     get_scene().addItem(item);
2746     EntityModel& entity = item->get_entity();
2747     QPoint top_left_in_group = center - entity.get_top_left();
2748     QPoint top_left = last_point - top_left_in_group - MapScene::get_margin_top_left();
2749     top_left = Point::round_8(top_left);
2750     entity.set_top_left(top_left);
2751     item->update_xy();
2752   }
2753 }
2754 
2755 /**
2756  * @copydoc MapView::State::stop
2757  */
stop()2758 void AddingEntitiesState::stop() {
2759 
2760   for (EntityItem* item : entity_items) {
2761     get_scene().removeItem(item);
2762   }
2763 }
2764 
2765 /**
2766  * @brief Computes the center point of all entities to be added.
2767  * @return The center point.
2768  */
get_entities_center() const2769 QPoint AddingEntitiesState::get_entities_center() const {
2770 
2771   QPoint top_left(1e9, 1e9);
2772   QPoint bottom_right(-1e9, -1e9);
2773   for (EntityItem* item : entity_items) {
2774     EntityModel& entity = item->get_entity();
2775     QRect box = entity.get_bounding_box();
2776     top_left.setX(qMin(box.left(), top_left.x()));
2777     top_left.setY(qMin(box.top(), top_left.y()));
2778     bottom_right.setX(qMax(box.right(), bottom_right.x()));
2779     bottom_right.setY(qMax(box.bottom(), bottom_right.y()));
2780   }
2781   return (top_left + bottom_right) / 2;
2782 }
2783 
2784 /**
2785  * @brief Ensures that the entities to be added are correctly sorted.
2786  *
2787  * The ones on lower layers come first, and on the same layer,
2788  * the non-dynamic ones come first.
2789  */
sort_entities()2790 void AddingEntitiesState::sort_entities() {
2791 
2792   const size_t num_before = entities.size();
2793 
2794   std::map<int, EntityModels> entities_by_layer;
2795   for (EntityModelPtr& entity : entities) {
2796     if (!entity->is_dynamic()) {  // Non-dynamic ones first.
2797       int layer = entity->get_layer();
2798       entities_by_layer[layer].emplace_back(std::move(entity));
2799     }
2800   }
2801   for (EntityModelPtr& entity : entities) {
2802     if (entity != nullptr && entity->is_dynamic()) {  // Non-dynamic ones first.
2803       int layer = entity->get_layer();
2804       entities_by_layer[layer].emplace_back(std::move(entity));
2805     }
2806   }
2807   entities.clear();
2808   for (int layer = get_map().get_min_layer(); layer <= get_map().get_max_layer(); ++layer) {
2809     for (EntityModelPtr& entity : entities_by_layer[layer]) {
2810       entities.emplace_back(std::move(entity));
2811     }
2812   }
2813 
2814   Q_UNUSED(num_before);
2815   Q_ASSERT(entities.size() == num_before);
2816 }
2817 
2818 /**
2819  * @copydoc MapView::State::mouse_pressed
2820  */
mouse_pressed(const QMouseEvent & event)2821 void AddingEntitiesState::mouse_pressed(const QMouseEvent& event) {
2822 
2823   Q_UNUSED(event);
2824   MapModel& map = get_map();
2825   MapView& view = get_view();
2826 
2827   // Store the number of tiles and dynamic entities of each layer,
2828   // because every entity added will increment one of them.
2829   QMap<int, int> num_tiles_by_layer;     // Index where to append a tile.
2830   QMap<int, int> num_dynamic_entities_by_layer;  // Index where to append a dynamic entity.
2831   for (int layer = map.get_min_layer(); layer <= map.get_max_layer(); ++layer) {
2832     num_tiles_by_layer[layer] = map.get_num_tiles(layer);
2833     num_dynamic_entities_by_layer[layer] = map.get_num_dynamic_entities(layer);
2834   }
2835 
2836   // Determine the best layer of each entity.
2837   for (EntityModelPtr& entity : entities) {
2838     int layer = find_best_layer(*entity);
2839     entity->set_layer(layer);
2840   }
2841   // Now that their layer is known, sort them
2842   // to compute correct indexes below.
2843   sort_entities();
2844 
2845   // Clone them in case the user wants to add more entities.
2846   EntityModels clones;
2847   if (event.button() == Qt::RightButton) {
2848     for (EntityModelPtr& entity : entities) {
2849       Q_ASSERT(entity != nullptr);
2850       clones.emplace_back(entity->clone());
2851     }
2852   }
2853 
2854   // Make entities ready to be added at their specific index.
2855   AddableEntities addable_entities;
2856   EntityIndex previous_index;
2857   for (EntityModelPtr& entity : entities) {
2858     Q_ASSERT(entity != nullptr);
2859     int layer = entity->get_layer();
2860 
2861     int i = 0;
2862     if (entity->is_dynamic()) {
2863       i = num_tiles_by_layer[layer] + num_dynamic_entities_by_layer[layer];
2864       ++num_dynamic_entities_by_layer[layer];
2865     }
2866     else {
2867       i = num_tiles_by_layer[layer];
2868       ++num_tiles_by_layer[layer];
2869     }
2870 
2871     EntityIndex index = { layer, i };
2872     if (previous_index.is_valid()) {
2873       // Double-check that we are traversing entities in ascending order.
2874       // (If not, then sort_entities() above did not make its job and we risk
2875       // invalid indexes).
2876       Q_ASSERT(index > previous_index);
2877     }
2878     previous_index = index;
2879     addable_entities.emplace_back(std::move(entity), index);
2880   }
2881 
2882   // Add them.
2883   const bool control_or_shift = (event.modifiers() & (Qt::ControlModifier | Qt::ShiftModifier));
2884   const bool keep_selection = control_or_shift;
2885   view.add_entities_requested(addable_entities, !keep_selection);
2886 
2887   // Decide what to do next: resize them, add new ones or do nothing.
2888   if (view.are_entities_resizable(view.get_selected_entities()) &&
2889       !control_or_shift) {
2890     // Start resizing the newly added entities
2891     // (until the mouse button is released).
2892     view.start_state_resizing_entities();
2893   }
2894   else {
2895     if (event.button() == Qt::RightButton) {
2896       // Entities were added with the right mouse button: add new ones again.
2897       const bool guess_layer = false;
2898       view.start_state_adding_entities(std::move(clones), guess_layer);
2899     }
2900     else {
2901       // Get back to normal state.
2902       view.start_state_doing_nothing();
2903     }
2904   }
2905 }
2906 
2907 /**
2908  * @copydoc MapView::State::mouse_moved
2909  */
mouse_moved(const QMouseEvent & event)2910 void AddingEntitiesState::mouse_moved(const QMouseEvent& event) {
2911 
2912   MapView& view = get_view();
2913 
2914   QPoint current_point = Point::floor_8(view.mapToScene(event.pos()));
2915   if (current_point == last_point) {
2916     // No change after rounding.
2917     return;
2918   }
2919 
2920   // Make entities being added follow the mouse.
2921   QPoint translation = current_point - last_point;
2922   last_point = current_point;
2923 
2924   for (EntityItem* item : entity_items) {
2925     EntityModel& entity = item->get_entity();
2926     QPoint xy = entity.get_xy() + translation;
2927     entity.set_xy(xy);
2928     item->update_xy();
2929   }
2930 }
2931 
2932 /**
2933  * @copydoc MapView::State::tileset_selection_changed
2934  */
tileset_selection_changed(const QString & tileset_id,const QList<int> & indexes)2935 void AddingEntitiesState::tileset_selection_changed(const QString& tileset_id, const QList<int>& indexes) {
2936 
2937   if (indexes.isEmpty()) {
2938     // Stop adding the tiles that were selected.
2939     get_view().start_state_doing_nothing();
2940     return;
2941   }
2942 
2943   // The user just selected some patterns in the tileset: create corresponding tiles.
2944   get_view().start_adding_entities_from_tileset(tileset_id, indexes);
2945 }
2946 
2947 /**
2948  * @brief Determines the appropriate layer where to add an entity.
2949  * @param entity The entity to add.
2950  * @return The layer. It is always a valid layer of the map.
2951  */
find_best_layer(const EntityModel & entity) const2952 int AddingEntitiesState::find_best_layer(const EntityModel& entity) const {
2953 
2954   if (!guess_layer && get_map().is_valid_layer(entity.get_layer())) {
2955     // The entity does not want us to guess a layer.
2956     return entity.get_layer();
2957   }
2958 
2959   int layer_under = get_scene().get_layer_in_rectangle(
2960         entity.get_bounding_box()
2961   );
2962   if (!entity.get_has_preferred_layer()) {
2963     // The entity has no preferred layer.
2964     return layer_under;
2965   }
2966 
2967   // The entity has a preferred layer:
2968   // see if there is something above its preferred layer.
2969   int preferred_layer = entity.get_preferred_layer();
2970   if (!get_map().is_valid_layer(preferred_layer)) {
2971       // The preferred layer does not exist on this map.
2972       return layer_under;
2973   }
2974 
2975   if (layer_under > preferred_layer) {
2976       // The preferred layer is covered by other entities
2977       // on a higher layer.
2978       // Don't use the preferred layer in this case.
2979       return layer_under;
2980   }
2981 
2982   return preferred_layer;
2983 }
2984 
2985 }
2986