/*************************************************************************/ /* scene_tree_dock.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ /* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ /* "Software"), to deal in the Software without restriction, including */ /* without limitation the rights to use, copy, modify, merge, publish, */ /* distribute, sublicense, and/or sell copies of the Software, and to */ /* permit persons to whom the Software is furnished to do so, subject to */ /* the following conditions: */ /* */ /* The above copyright notice and this permission notice shall be */ /* included in all copies or substantial portions of the Software. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ #include "scene_tree_dock.h" #include "core/io/resource_saver.h" #include "core/os/input.h" #include "core/os/keyboard.h" #include "core/project_settings.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" #include "editor/multi_node_edit.h" #include "editor/plugins/animation_player_editor_plugin.h" #include "editor/plugins/canvas_item_editor_plugin.h" #include "editor/plugins/script_editor_plugin.h" #include "editor/plugins/spatial_editor_plugin.h" #include "editor/script_editor_debugger.h" #include "scene/main/viewport.h" #include "scene/resources/packed_scene.h" void SceneTreeDock::_nodes_drag_begin() { if (restore_script_editor_on_drag) { EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT); restore_script_editor_on_drag = false; } } void SceneTreeDock::_quick_open() { Vector files = quick_open->get_selected_files(); for (int i = 0; i < files.size(); i++) { instance(files[i]); } } void SceneTreeDock::_input(Ref p_event) { Ref mb = p_event; if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { restore_script_editor_on_drag = false; //lost chance } } void SceneTreeDock::_unhandled_key_input(Ref p_event) { if (get_viewport()->get_modal_stack_top()) return; //ignore because of modal window if (get_focus_owner() && get_focus_owner()->is_text_field()) return; if (!p_event->is_pressed() || p_event->is_echo()) return; if (ED_IS_SHORTCUT("scene_tree/batch_rename", p_event)) { _tool_selected(TOOL_BATCH_RENAME); } else if (ED_IS_SHORTCUT("scene_tree/rename", p_event)) { _tool_selected(TOOL_RENAME); } else if (ED_IS_SHORTCUT("scene_tree/add_child_node", p_event)) { _tool_selected(TOOL_NEW); } else if (ED_IS_SHORTCUT("scene_tree/instance_scene", p_event)) { _tool_selected(TOOL_INSTANCE); } else if (ED_IS_SHORTCUT("scene_tree/expand_collapse_all", p_event)) { _tool_selected(TOOL_EXPAND_COLLAPSE); } else if (ED_IS_SHORTCUT("scene_tree/change_node_type", p_event)) { _tool_selected(TOOL_REPLACE); } else if (ED_IS_SHORTCUT("scene_tree/duplicate", p_event)) { _tool_selected(TOOL_DUPLICATE); } else if (ED_IS_SHORTCUT("scene_tree/attach_script", p_event)) { _tool_selected(TOOL_ATTACH_SCRIPT); } else if (ED_IS_SHORTCUT("scene_tree/clear_script", p_event)) { _tool_selected(TOOL_CLEAR_SCRIPT); } else if (ED_IS_SHORTCUT("scene_tree/move_up", p_event)) { _tool_selected(TOOL_MOVE_UP); } else if (ED_IS_SHORTCUT("scene_tree/move_down", p_event)) { _tool_selected(TOOL_MOVE_DOWN); } else if (ED_IS_SHORTCUT("scene_tree/reparent", p_event)) { _tool_selected(TOOL_REPARENT); } else if (ED_IS_SHORTCUT("scene_tree/merge_from_scene", p_event)) { _tool_selected(TOOL_MERGE_FROM_SCENE); } else if (ED_IS_SHORTCUT("scene_tree/save_branch_as_scene", p_event)) { _tool_selected(TOOL_NEW_SCENE_FROM); } else if (ED_IS_SHORTCUT("scene_tree/delete_no_confirm", p_event)) { _tool_selected(TOOL_ERASE, true); } else if (ED_IS_SHORTCUT("scene_tree/copy_node_path", p_event)) { _tool_selected(TOOL_COPY_NODE_PATH); } else if (ED_IS_SHORTCUT("scene_tree/delete", p_event)) { _tool_selected(TOOL_ERASE); } } void SceneTreeDock::instance(const String &p_file) { Node *parent = scene_tree->get_selected(); if (!parent) { parent = edited_scene; }; if (!edited_scene) { current_option = -1; accept->set_text(TTR("No parent to instance a child at.")); accept->popup_centered_minsize(); return; }; ERR_FAIL_COND(!parent); Vector scenes; scenes.push_back(p_file); _perform_instance_scenes(scenes, parent, -1); } void SceneTreeDock::instance_scenes(const Vector &p_files, Node *p_parent) { Node *parent = p_parent; if (!parent) { parent = scene_tree->get_selected(); } if (!parent || !edited_scene) { accept->set_text(TTR("No parent to instance the scenes at.")); accept->popup_centered_minsize(); return; }; _perform_instance_scenes(p_files, parent, -1); } void SceneTreeDock::_perform_instance_scenes(const Vector &p_files, Node *parent, int p_pos) { ERR_FAIL_COND(!parent); Vector instances; bool error = false; for (int i = 0; i < p_files.size(); i++) { Ref sdata = ResourceLoader::load(p_files[i]); if (!sdata.is_valid()) { current_option = -1; accept->set_text(vformat(TTR("Error loading scene from %s"), p_files[i])); accept->popup_centered_minsize(); error = true; break; } Node *instanced_scene = sdata->instance(PackedScene::GEN_EDIT_STATE_INSTANCE); if (!instanced_scene) { current_option = -1; accept->set_text(vformat(TTR("Error instancing scene from %s"), p_files[i])); accept->popup_centered_minsize(); error = true; break; } if (edited_scene->get_filename() != "") { if (_cyclical_dependency_exists(edited_scene->get_filename(), instanced_scene)) { accept->set_text(vformat(TTR("Cannot instance the scene '%s' because the current scene exists within one of its nodes."), p_files[i])); accept->popup_centered_minsize(); error = true; break; } } instanced_scene->set_filename(ProjectSettings::get_singleton()->localize_path(p_files[i])); instances.push_back(instanced_scene); } if (error) { for (int i = 0; i < instances.size(); i++) { memdelete(instances[i]); } return; } editor_data->get_undo_redo().create_action(TTR("Instance Scene(s)")); for (int i = 0; i < instances.size(); i++) { Node *instanced_scene = instances[i]; editor_data->get_undo_redo().add_do_method(parent, "add_child", instanced_scene); if (p_pos >= 0) { editor_data->get_undo_redo().add_do_method(parent, "move_child", instanced_scene, p_pos + i); } editor_data->get_undo_redo().add_do_method(instanced_scene, "set_owner", edited_scene); editor_data->get_undo_redo().add_do_method(editor_selection, "clear"); editor_data->get_undo_redo().add_do_method(editor_selection, "add_node", instanced_scene); editor_data->get_undo_redo().add_do_reference(instanced_scene); editor_data->get_undo_redo().add_undo_method(parent, "remove_child", instanced_scene); String new_name = parent->validate_child_name(instanced_scene); ScriptEditorDebugger *sed = ScriptEditor::get_singleton()->get_debugger(); editor_data->get_undo_redo().add_do_method(sed, "live_debug_instance_node", edited_scene->get_path_to(parent), p_files[i], new_name); editor_data->get_undo_redo().add_undo_method(sed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(parent)).plus_file(new_name))); } editor_data->get_undo_redo().commit_action(); } void SceneTreeDock::_replace_with_branch_scene(const String &p_file, Node *base) { Ref sdata = ResourceLoader::load(p_file); if (!sdata.is_valid()) { accept->set_text(vformat(TTR("Error loading scene from %s"), p_file)); accept->popup_centered_minsize(); return; } Node *instanced_scene = sdata->instance(PackedScene::GEN_EDIT_STATE_INSTANCE); if (!instanced_scene) { accept->set_text(vformat(TTR("Error instancing scene from %s"), p_file)); accept->popup_centered_minsize(); return; } Node *parent = base->get_parent(); int pos = base->get_index(); parent->remove_child(base); parent->add_child(instanced_scene); parent->move_child(instanced_scene, pos); instanced_scene->set_owner(edited_scene); editor_selection->clear(); editor_selection->add_node(instanced_scene); scene_tree->set_selected(instanced_scene); // Delete the node as late as possible because before another one is selected // an editor plugin could be referencing it to do something with it before // switching to another (or to none); and since some steps of changing the // editor state are deferred, the safest thing is to do this is as the last // step of this function and also by enqueing instead of memdelete()-ing it here base->queue_delete(); } bool SceneTreeDock::_cyclical_dependency_exists(const String &p_target_scene_path, Node *p_desired_node) { int childCount = p_desired_node->get_child_count(); if (_track_inherit(p_target_scene_path, p_desired_node)) { return true; } for (int i = 0; i < childCount; i++) { Node *child = p_desired_node->get_child(i); if (_cyclical_dependency_exists(p_target_scene_path, child)) { return true; } } return false; } bool SceneTreeDock::_track_inherit(const String &p_target_scene_path, Node *p_desired_node) { Node *p = p_desired_node; bool result = false; Vector instances; while (true) { if (p->get_filename() == p_target_scene_path) { result = true; break; } Ref ss = p->get_scene_inherited_state(); if (ss.is_valid()) { String path = ss->get_path(); Ref data = ResourceLoader::load(path); if (data.is_valid()) { p = data->instance(PackedScene::GEN_EDIT_STATE_INSTANCE); instances.push_back(p); } else break; } else break; } for (int i = 0; i < instances.size(); i++) { memdelete(instances[i]); } return result; } void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { current_option = p_tool; switch (p_tool) { case TOOL_BATCH_RENAME: { if (!profile_allow_editing) { break; } Tree *tree = scene_tree->get_scene_tree(); if (tree->is_anything_selected()) { rename_dialog->popup_centered(); } } break; case TOOL_RENAME: { if (!profile_allow_editing) { break; } Tree *tree = scene_tree->get_scene_tree(); if (tree->is_anything_selected()) { tree->grab_focus(); tree->edit_selected(); } } break; case TOOL_NEW: { if (!profile_allow_editing) { break; } String preferred = ""; Node *current_edited_scene_root = EditorNode::get_singleton()->get_edited_scene(); if (current_edited_scene_root) { if (ClassDB::is_parent_class(current_edited_scene_root->get_class_name(), "Node2D")) preferred = "Node2D"; else if (ClassDB::is_parent_class(current_edited_scene_root->get_class_name(), "Spatial")) preferred = "Spatial"; } create_dialog->set_preferred_search_result_type(preferred); create_dialog->popup_create(true); } break; case TOOL_INSTANCE: { if (!profile_allow_editing) { break; } Node *scene = edited_scene; if (!scene) { EditorNode::get_singleton()->new_inherited_scene(); break; } quick_open->popup_dialog("PackedScene", true); quick_open->set_title(TTR("Instance Child Scene")); } break; case TOOL_EXPAND_COLLAPSE: { if (!scene_tree->get_selected()) break; Tree *tree = scene_tree->get_scene_tree(); TreeItem *selected_item = tree->get_selected(); if (!selected_item) selected_item = tree->get_root(); bool collapsed = _is_collapsed_recursive(selected_item); _set_collapsed_recursive(selected_item, !collapsed); tree->ensure_cursor_is_visible(); } break; case TOOL_REPLACE: { if (!profile_allow_editing) { break; } Node *selected = scene_tree->get_selected(); if (selected) create_dialog->popup_create(false, true, selected->get_class()); } break; case TOOL_ATTACH_SCRIPT: { if (!profile_allow_script_editing) { break; } List selection = editor_selection->get_selected_node_list(); if (selection.empty()) break; Node *selected = scene_tree->get_selected(); if (!selected) selected = selection.front()->get(); Ref