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