/**
 * @brief Updates everything in the gui.
 */
void MapEditor::update() {

  update_map_id_field();
  update_description_to_gui();
  update_size_field();
  update_world_field();
  update_floor_field();
  update_location_field();
  update_tileset_field();
  update_music_field();
  tileset_id_changed(map->get_tileset_id());
}
/**
 * @brief Modifies the map description in the quest resource list with
 * the new text entered by the user.
 *
 * If the new description is invalid, an error dialog is shown.
 */
void MapEditor::set_description_from_gui() {

  QString description = ui.description_field->text();
  if (description == get_resources().get_description(ResourceType::MAP, map_id)) {
    return;
  }

  if (description.isEmpty()) {
    GuiTools::error_dialog(tr("Invalid description"));
    update_description_to_gui();
    return;
  }

  const bool was_blocked = blockSignals(true);
  try {
    get_resources().set_description(ResourceType::MAP, map_id, description);
    get_resources().save();
  }
  catch (const EditorException& ex) {
    ex.print_message();
  }
  update_description_to_gui();
  blockSignals(was_blocked);
}
/**
 * @brief Creates a map editor.
 * @param quest The quest containing the file.
 * @param path Path of the map data file to open.
 * @param parent The parent object or nullptr.
 * @throws EditorException If the file could not be opened.
 */
MapEditor::MapEditor(Quest& quest, const QString& path, QWidget* parent) :
  Editor(quest, path, parent),
  map_id(),
  map(nullptr),
  entity_creation_toolbar(nullptr),
  status_bar(nullptr),
  ignore_tileset_selection_changes(false) {

  ui.setupUi(this);
  build_entity_creation_toolbar();
  build_status_bar();

  // Get the map.
  ResourceType resource_type;
  QString map_id;
  quest.check_exists(path);
  if (!quest.is_resource_element(path, resource_type, map_id) ||
      resource_type != ResourceType::MAP) {
    throw EditorException(tr("File '%1' is not a map").arg(path));
  }
  this->map_id = map_id;

  // Editor properties.
  set_title(tr("Map %1").arg(get_file_name_without_extension()));
  set_icon(QIcon(":/images/icon_resource_map.png"));
  set_close_confirm_message(
        tr("Map '%1' has been modified. Save changes?").arg(map_id));
  set_select_all_supported(true);
  set_zoom_supported(true);
  get_view_settings().set_zoom(2.0);
  set_grid_supported(true);
  set_entity_type_visibility_supported(true);

  // Shortcuts.
  QAction* open_script_action = new QAction(this);
  open_script_action->setShortcut(tr("F4"));
  open_script_action->setShortcutContext(Qt::WindowShortcut);
  connect(open_script_action, SIGNAL(triggered(bool)),
          this, SLOT(open_script_requested()));
  addAction(open_script_action);

  // Open the file.
  map = new MapModel(quest, map_id, this);
  get_undo_stack().setClean();

  // Prepare the gui.
  const int side_width = 350;
  ui.splitter->setSizes({ side_width, width() - side_width });
  ui.map_side_splitter->setStretchFactor(0, 0);  // Don't expand the map properties view
  ui.map_side_splitter->setStretchFactor(1, 1);  // but only the tileset view.
  ui.tileset_field->set_resource_type(ResourceType::TILESET);
  ui.tileset_field->set_quest(quest);
  ui.music_field->set_resource_type(ResourceType::MUSIC);
  ui.music_field->set_quest(quest);
  ui.music_field->add_special_value("none", tr("<No music>"), 0);
  ui.music_field->add_special_value("same", tr("<Same as before>"), 1);
  ui.tileset_view->set_read_only(true);
  ui.map_view->set_map(map);
  ui.map_view->set_view_settings(get_view_settings());
  ui.map_view->set_common_actions(&get_common_actions());

  ui.size_field->config("x", 0, 99999, 8);
  ui.size_field->set_tooltips(
    tr("Width of the map in pixels"),
    tr("Height of the map in pixels"));

  ui.location_field->config(",", 0, 99999, 8);
  ui.location_field->set_tooltips(
    tr("Coordinates of the map in its world (useful to make adjacent scrolling maps)"),
    tr("Coordinates of the map in its world (useful to make adjacent scrolling maps)"));

  set_num_layers_visibility_supported(map->get_num_layers());

  load_settings();
  update();

  // Make connections.
  connect(&get_resources(), SIGNAL(element_description_changed(ResourceType, QString, QString)),
          this, SLOT(update_description_to_gui()));
  connect(ui.description_field, SIGNAL(editingFinished()),
          this, SLOT(set_description_from_gui()));

  connect(ui.size_field, SIGNAL(editing_finished()),
          this, SLOT(change_size_requested()));
  connect(map, SIGNAL(size_changed(QSize)),
          this, SLOT(update_size_field()));

  connect(ui.world_check_box, SIGNAL(stateChanged(int)),
          this, SLOT(world_check_box_changed()));
  connect(ui.world_field, SIGNAL(editingFinished()),
          this, SLOT(change_world_requested()));
  connect(map, SIGNAL(world_changed(QString)),
          this, SLOT(update_world_field()));

  connect(ui.floor_check_box, SIGNAL(stateChanged(int)),
          this, SLOT(floor_check_box_changed()));
  connect(ui.floor_field, SIGNAL(editingFinished()),
          this, SLOT(change_floor_requested()));
  connect(map, SIGNAL(floor_changed(int)),
          this, SLOT(update_floor_field()));

  connect(ui.location_field, SIGNAL(editing_finished()),
          this, SLOT(change_location_requested()));
  connect(map, SIGNAL(location_changed(QPoint)),
          this, SLOT(update_location_field()));

  connect(ui.tileset_field, SIGNAL(activated(QString)),
          this, SLOT(tileset_selector_activated()));
  connect(map, SIGNAL(tileset_id_changed(QString)),
          this, SLOT(tileset_id_changed(QString)));
  connect(ui.tileset_refresh_button, SIGNAL(clicked()),
          map, SLOT(tileset_modified()));
  connect(ui.tileset_edit_button, SIGNAL(clicked()),
          this, SLOT(open_tileset_requested()));

  connect(ui.music_field, SIGNAL(activated(QString)),
          this, SLOT(music_selector_activated()));
  connect(map, SIGNAL(music_id_changed(QString)),
          this, SLOT(update_music_field()));

  connect(ui.open_script_button, SIGNAL(clicked()),
          this, SLOT(open_script_requested()));

  connect(ui.map_view, SIGNAL(edit_entity_requested(EntityIndex, EntityModelPtr&)),
          this, SLOT(edit_entity_requested(EntityIndex, EntityModelPtr&)));
  connect(ui.map_view, SIGNAL(move_entities_requested(EntityIndexes, QPoint, bool)),
          this, SLOT(move_entities_requested(EntityIndexes, QPoint, bool)));
  connect(ui.map_view, SIGNAL(resize_entities_requested(QMap<EntityIndex, QRect>, bool)),
          this, SLOT(resize_entities_requested(QMap<EntityIndex, QRect>, bool)));
  connect(ui.map_view, SIGNAL(convert_tiles_requested(EntityIndexes)),
          this, SLOT(convert_tiles_requested(EntityIndexes)));
  connect(ui.map_view, SIGNAL(set_entities_direction_requested(EntityIndexes, int)),
          this, SLOT(set_entities_direction_requested(EntityIndexes, int)));
  connect(ui.map_view, SIGNAL(set_entities_layer_requested(EntityIndexes, int)),
          this, SLOT(set_entities_layer_requested(EntityIndexes, int)));
  connect(ui.map_view, SIGNAL(bring_entities_to_front_requested(EntityIndexes)),
          this, SLOT(bring_entities_to_front_requested(EntityIndexes)));
  connect(ui.map_view, SIGNAL(bring_entities_to_back_requested(EntityIndexes)),
          this, SLOT(bring_entities_to_back_requested(EntityIndexes)));
  connect(ui.map_view, SIGNAL(add_entities_requested(AddableEntities&)),
          this, SLOT(add_entities_requested(AddableEntities&)));
  connect(ui.map_view, SIGNAL(remove_entities_requested(EntityIndexes)),
          this, SLOT(remove_entities_requested(EntityIndexes)));

  connect(ui.map_view->get_scene(), SIGNAL(selectionChanged()),
          this, SLOT(map_selection_changed()));
}
/**
 * @brief Updates everything in the gui.
 */
void SpriteEditor::update() {

  update_sprite_id_field();
  update_description_to_gui();
  update_selection();
}