Path: blob/master/editor/scene/group_settings_editor.cpp
9898 views
/**************************************************************************/1/* group_settings_editor.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "group_settings_editor.h"3132#include "core/config/project_settings.h"33#include "editor/docks/filesystem_dock.h"34#include "editor/docks/scene_tree_dock.h"35#include "editor/editor_node.h"36#include "editor/editor_undo_redo_manager.h"37#include "editor/gui/editor_validation_panel.h"38#include "editor/themes/editor_scale.h"39#include "scene/gui/line_edit.h"40#include "scene/resources/packed_scene.h"4142void GroupSettingsEditor::_notification(int p_what) {43switch (p_what) {44case NOTIFICATION_ENTER_TREE: {45update_groups();46} break;47case NOTIFICATION_THEME_CHANGED: {48add_button->set_button_icon(get_editor_theme_icon(SNAME("Add")));49} break;50}51}5253void GroupSettingsEditor::_item_edited() {54if (updating_groups) {55return;56}5758TreeItem *ti = tree->get_edited();59int column = tree->get_edited_column();6061if (!ti) {62return;63}6465EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();66if (column == 1) {67// Description Edited.68String name = ti->get_text(0);69String new_description = ti->get_text(1);70String old_description = ti->get_meta("__description");7172if (new_description == old_description) {73return;74}7576name = GLOBAL_GROUP_PREFIX + name;7778undo_redo->create_action(TTR("Set Group Description"));7980undo_redo->add_do_property(ProjectSettings::get_singleton(), name, new_description);81undo_redo->add_undo_property(ProjectSettings::get_singleton(), name, old_description);8283undo_redo->add_do_method(this, CoreStringName(call_deferred), "update_groups");84undo_redo->add_undo_method(this, CoreStringName(call_deferred), "update_groups");8586undo_redo->add_do_method(this, "emit_signal", group_changed);87undo_redo->add_undo_method(this, "emit_signal", group_changed);8889undo_redo->commit_action();90}91}9293void GroupSettingsEditor::_item_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button) {94if (p_button != MouseButton::LEFT) {95return;96}9798TreeItem *ti = Object::cast_to<TreeItem>(p_item);99100if (!ti) {101return;102}103ti->select(0);104_show_remove_dialog();105}106107String GroupSettingsEditor::_check_new_group_name(const String &p_name) {108if (p_name.is_empty()) {109return TTR("Invalid group name. It cannot be empty.");110}111112if (ProjectSettings::get_singleton()->has_global_group(p_name)) {113return vformat(TTR("A group with the name '%s' already exists."), p_name);114}115116return "";117}118119void GroupSettingsEditor::_check_rename() {120String new_name = rename_group->get_text().strip_edges();121String old_name = rename_group_dialog->get_meta("__name");122123if (new_name == old_name) {124return;125}126127if (new_name.is_empty()) {128rename_validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Group can't be empty."), EditorValidationPanel::MSG_ERROR);129} else if (ProjectSettings::get_singleton()->has_global_group(new_name)) {130rename_validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Group already exists."), EditorValidationPanel::MSG_ERROR);131}132}133134void GroupSettingsEditor::_bind_methods() {135ClassDB::bind_method(D_METHOD("remove_references"), &GroupSettingsEditor::remove_references);136ClassDB::bind_method(D_METHOD("rename_references"), &GroupSettingsEditor::rename_references);137138ClassDB::bind_method(D_METHOD("update_groups"), &GroupSettingsEditor::update_groups);139140ADD_SIGNAL(MethodInfo("group_changed"));141}142143void GroupSettingsEditor::_add_group(const String &p_name, const String &p_description) {144String name = p_name.strip_edges();145146String error = _check_new_group_name(name);147if (!error.is_empty()) {148show_message(error);149return;150}151152EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();153undo_redo->create_action(TTR("Add Group"));154155name = GLOBAL_GROUP_PREFIX + name;156157undo_redo->add_do_property(ProjectSettings::get_singleton(), name, p_description);158undo_redo->add_undo_property(ProjectSettings::get_singleton(), name, Variant());159160undo_redo->add_do_method(this, CoreStringName(call_deferred), "update_groups");161undo_redo->add_undo_method(this, CoreStringName(call_deferred), "update_groups");162163undo_redo->add_do_method(this, "emit_signal", group_changed);164undo_redo->add_undo_method(this, "emit_signal", group_changed);165166undo_redo->commit_action();167168group_name->clear();169group_description->clear();170}171172void GroupSettingsEditor::_add_group() {173_add_group(group_name->get_text(), group_description->get_text());174}175176void GroupSettingsEditor::_text_submitted(const String &p_text) {177if (!add_button->is_disabled()) {178_add_group();179}180}181182void GroupSettingsEditor::_group_name_text_changed(const String &p_name) {183String error = _check_new_group_name(p_name.strip_edges());184add_button->set_tooltip_text(error);185add_button->set_disabled(!error.is_empty());186}187188void GroupSettingsEditor::_modify_references(const StringName &p_name, const StringName &p_new_name, bool p_is_rename) {189HashSet<String> scenes;190191HashMap<StringName, HashSet<StringName>> scene_groups_cache = ProjectSettings::get_singleton()->get_scene_groups_cache();192for (const KeyValue<StringName, HashSet<StringName>> &E : scene_groups_cache) {193if (E.value.has(p_name)) {194scenes.insert(E.key);195}196}197198int steps = scenes.size();199Vector<EditorData::EditedScene> edited_scenes = EditorNode::get_editor_data().get_edited_scenes();200for (const EditorData::EditedScene &es : edited_scenes) {201if (!es.root) {202continue;203}204if (es.path.is_empty()) {205++steps;206} else if (!scenes.has(es.path)) {207++steps;208}209}210211String progress_task = p_is_rename ? "rename_reference" : "remove_references";212String progress_label = p_is_rename ? TTR("Renaming Group References") : TTR("Removing Group References");213EditorProgress progress(progress_task, progress_label, steps);214215int step = 0;216// Update opened scenes.217HashSet<String> edited_scenes_path;218for (const EditorData::EditedScene &es : edited_scenes) {219if (!es.root) {220continue;221}222progress.step(es.path, step++);223bool edited = p_is_rename ? rename_node_references(es.root, p_name, p_new_name) : remove_node_references(es.root, p_name);224if (!es.path.is_empty()) {225scenes.erase(es.path);226if (edited) {227edited_scenes_path.insert(es.path);228}229}230}231if (!edited_scenes_path.is_empty()) {232EditorNode::get_singleton()->save_scene_list(edited_scenes_path);233SceneTreeDock::get_singleton()->get_tree_editor()->update_tree();234}235236for (const String &E : scenes) {237Ref<PackedScene> packed_scene = ResourceLoader::load(E);238progress.step(E, step++);239ERR_CONTINUE(packed_scene.is_null());240if (p_is_rename) {241if (packed_scene->get_state()->rename_group_references(p_name, p_new_name)) {242ResourceSaver::save(packed_scene, E);243}244} else {245if (packed_scene->get_state()->remove_group_references(p_name)) {246ResourceSaver::save(packed_scene, E);247}248}249}250}251252void GroupSettingsEditor::remove_references(const StringName &p_name) {253_modify_references(p_name, StringName(), false);254}255256void GroupSettingsEditor::rename_references(const StringName &p_old_name, const StringName &p_new_name) {257_modify_references(p_old_name, p_new_name, true);258}259260bool GroupSettingsEditor::remove_node_references(Node *p_node, const StringName &p_name) {261bool edited = false;262if (p_node->is_in_group(p_name)) {263p_node->remove_from_group(p_name);264edited = true;265}266267for (int i = 0; i < p_node->get_child_count(); i++) {268edited |= remove_node_references(p_node->get_child(i), p_name);269}270return edited;271}272273bool GroupSettingsEditor::rename_node_references(Node *p_node, const StringName &p_old_name, const StringName &p_new_name) {274bool edited = false;275if (p_node->is_in_group(p_old_name)) {276p_node->remove_from_group(p_old_name);277p_node->add_to_group(p_new_name, true);278edited = true;279}280281for (int i = 0; i < p_node->get_child_count(); i++) {282edited |= rename_node_references(p_node->get_child(i), p_old_name, p_new_name);283}284return edited;285}286287void GroupSettingsEditor::update_groups() {288if (updating_groups) {289return;290}291updating_groups = true;292groups_cache = ProjectSettings::get_singleton()->get_global_groups_list();293294tree->clear();295TreeItem *root = tree->create_item();296297List<StringName> keys;298for (const KeyValue<StringName, String> &E : groups_cache) {299keys.push_back(E.key);300}301keys.sort_custom<NoCaseComparator>();302303for (const StringName &E : keys) {304TreeItem *item = tree->create_item(root);305item->set_meta("__name", E);306item->set_meta("__description", groups_cache[E]);307308item->set_text(0, E);309item->set_editable(0, false);310311item->set_text(1, groups_cache[E]);312item->set_editable(1, true);313item->add_button(2, get_editor_theme_icon(SNAME("Remove")));314item->set_selectable(2, false);315}316317updating_groups = false;318}319320void GroupSettingsEditor::connect_filesystem_dock_signals(FileSystemDock *p_fs_dock) {321p_fs_dock->connect("files_moved", callable_mp(ProjectSettings::get_singleton(), &ProjectSettings::remove_scene_groups_cache).unbind(1));322p_fs_dock->connect("file_removed", callable_mp(ProjectSettings::get_singleton(), &ProjectSettings::remove_scene_groups_cache));323}324325void GroupSettingsEditor::_confirm_rename() {326TreeItem *ti = tree->get_selected();327if (!ti) {328return;329}330331String old_name = ti->get_meta("__name");332String new_name = rename_group->get_text().strip_edges();333334if (old_name == new_name) {335return;336}337338EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();339undo_redo->create_action(TTR("Rename Group"));340341String property_new_name = GLOBAL_GROUP_PREFIX + new_name;342String property_old_name = GLOBAL_GROUP_PREFIX + old_name;343344String description = ti->get_meta("__description");345346undo_redo->add_do_property(ProjectSettings::get_singleton(), property_new_name, description);347undo_redo->add_undo_property(ProjectSettings::get_singleton(), property_new_name, Variant());348349undo_redo->add_do_property(ProjectSettings::get_singleton(), property_old_name, Variant());350undo_redo->add_undo_property(ProjectSettings::get_singleton(), property_old_name, description);351352if (rename_check_box->is_pressed()) {353undo_redo->add_do_method(this, "rename_references", old_name, new_name);354undo_redo->add_undo_method(this, "rename_references", new_name, old_name);355}356357undo_redo->add_do_method(this, CoreStringName(call_deferred), "update_groups");358undo_redo->add_undo_method(this, CoreStringName(call_deferred), "update_groups");359360undo_redo->add_do_method(this, "emit_signal", group_changed);361undo_redo->add_undo_method(this, "emit_signal", group_changed);362363undo_redo->commit_action();364}365366void GroupSettingsEditor::_confirm_delete() {367TreeItem *ti = tree->get_selected();368if (!ti) {369return;370}371372String name = ti->get_text(0);373String description = groups_cache[name];374String property_name = GLOBAL_GROUP_PREFIX + name;375376EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();377undo_redo->create_action(TTR("Remove Group"));378379undo_redo->add_do_property(ProjectSettings::get_singleton(), property_name, Variant());380undo_redo->add_undo_property(ProjectSettings::get_singleton(), property_name, description);381382if (remove_check_box->is_pressed()) {383undo_redo->add_do_method(this, "remove_references", name);384}385386undo_redo->add_do_method(this, CoreStringName(call_deferred), "update_groups");387undo_redo->add_undo_method(this, CoreStringName(call_deferred), "update_groups");388389undo_redo->add_do_method(this, "emit_signal", group_changed);390undo_redo->add_undo_method(this, "emit_signal", group_changed);391392undo_redo->commit_action();393}394395void GroupSettingsEditor::show_message(const String &p_message) {396message->set_text(p_message);397message->popup_centered();398}399400void GroupSettingsEditor::_show_remove_dialog() {401if (!remove_dialog) {402remove_dialog = memnew(ConfirmationDialog);403remove_dialog->connect(SceneStringName(confirmed), callable_mp(this, &GroupSettingsEditor::_confirm_delete));404405VBoxContainer *vbox = memnew(VBoxContainer);406remove_label = memnew(Label);407remove_label->set_focus_mode(FOCUS_ACCESSIBILITY);408vbox->add_child(remove_label);409410remove_check_box = memnew(CheckBox);411remove_check_box->set_text(TTRC("Delete references from all scenes"));412vbox->add_child(remove_check_box);413414remove_dialog->add_child(vbox);415416add_child(remove_dialog);417}418419TreeItem *ti = tree->get_selected();420if (!ti) {421return;422}423424remove_check_box->set_pressed(false);425remove_label->set_text(vformat(TTR("Delete group \"%s\"?"), ti->get_text(0)));426427remove_dialog->reset_size();428remove_dialog->popup_centered();429}430431void GroupSettingsEditor::_show_rename_dialog() {432if (!rename_group_dialog) {433rename_group_dialog = memnew(ConfirmationDialog);434rename_group_dialog->set_title(TTRC("Rename Group"));435rename_group_dialog->connect(SceneStringName(confirmed), callable_mp(this, &GroupSettingsEditor::_confirm_rename));436437VBoxContainer *vbc = memnew(VBoxContainer);438rename_group_dialog->add_child(vbc);439440HBoxContainer *hbc = memnew(HBoxContainer);441hbc->add_child(memnew(Label(TTRC("Name:"))));442443rename_group = memnew(LineEdit);444rename_group->set_custom_minimum_size(Size2(300 * EDSCALE, 1));445hbc->add_child(rename_group);446vbc->add_child(hbc);447448rename_group_dialog->register_text_enter(rename_group);449450rename_validation_panel = memnew(EditorValidationPanel);451rename_validation_panel->add_line(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Group name is valid."));452rename_validation_panel->set_update_callback(callable_mp(this, &GroupSettingsEditor::_check_rename));453rename_validation_panel->set_accept_button(rename_group_dialog->get_ok_button());454455rename_group->connect(SceneStringName(text_changed), callable_mp(rename_validation_panel, &EditorValidationPanel::update).unbind(1));456457vbc->add_child(rename_validation_panel);458459rename_check_box = memnew(CheckBox);460rename_check_box->set_text(TTRC("Rename references in all scenes"));461vbc->add_child(rename_check_box);462463add_child(rename_group_dialog);464}465466TreeItem *ti = tree->get_selected();467if (!ti) {468return;469}470471rename_check_box->set_pressed(false);472473String name = ti->get_meta("__name");474475rename_group->set_text(name);476rename_group_dialog->set_meta("__name", name);477478rename_validation_panel->update();479480rename_group_dialog->reset_size();481rename_group_dialog->popup_centered();482rename_group->select_all();483rename_group->grab_focus();484}485486LineEdit *GroupSettingsEditor::get_name_box() const {487return group_name;488}489490GroupSettingsEditor::GroupSettingsEditor() {491ProjectSettings::get_singleton()->add_hidden_prefix("global_group/");492493HBoxContainer *hbc = memnew(HBoxContainer);494add_child(hbc);495496Label *l = memnew(Label);497l->set_text(TTRC("Name:"));498hbc->add_child(l);499500group_name = memnew(LineEdit);501group_name->set_h_size_flags(SIZE_EXPAND_FILL);502group_name->set_clear_button_enabled(true);503group_name->set_accessibility_name(TTRC("Name:"));504group_name->connect(SceneStringName(text_changed), callable_mp(this, &GroupSettingsEditor::_group_name_text_changed));505group_name->connect(SceneStringName(text_submitted), callable_mp(this, &GroupSettingsEditor::_text_submitted));506hbc->add_child(group_name);507508l = memnew(Label);509l->set_text(TTRC("Description:"));510hbc->add_child(l);511512group_description = memnew(LineEdit);513group_description->set_clear_button_enabled(true);514group_description->set_accessibility_name(TTRC("Description:"));515group_description->set_h_size_flags(SIZE_EXPAND_FILL);516group_description->connect(SceneStringName(text_submitted), callable_mp(this, &GroupSettingsEditor::_text_submitted));517hbc->add_child(group_description);518519add_button = memnew(Button);520add_button->set_text(TTRC("Add"));521add_button->set_disabled(true);522add_button->connect(SceneStringName(pressed), callable_mp(this, &GroupSettingsEditor::_add_group));523hbc->add_child(add_button);524525tree = memnew(Tree);526tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);527tree->set_hide_root(true);528tree->set_select_mode(Tree::SELECT_SINGLE);529tree->set_allow_reselect(true);530531tree->set_columns(3);532tree->set_column_titles_visible(true);533534tree->set_column_title(0, TTRC("Name"));535tree->set_column_title(1, TTRC("Description"));536tree->set_column_expand(2, false);537538tree->connect("item_edited", callable_mp(this, &GroupSettingsEditor::_item_edited));539tree->connect("item_activated", callable_mp(this, &GroupSettingsEditor::_show_rename_dialog));540tree->connect("button_clicked", callable_mp(this, &GroupSettingsEditor::_item_button_pressed));541tree->set_v_size_flags(SIZE_EXPAND_FILL);542543add_child(tree, true);544545message = memnew(AcceptDialog);546add_child(message);547}548549550