/**************************************************************************/ /* editor_data.cpp */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /**************************************************************************/ /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ /* */ /* 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 "editor_data.h" #include "core/config/project_settings.h" #include "core/extension/gdextension_manager.h" #include "core/io/file_access.h" #include "core/io/image_loader.h" #include "core/io/resource_loader.h" #include "editor/editor_node.h" #include "editor/editor_undo_redo_manager.h" #include "editor/multi_node_edit.h" #include "editor/plugins/editor_plugin.h" #include "editor/plugins/script_editor_plugin.h" #include "editor/themes/editor_scale.h" #include "scene/resources/packed_scene.h" void EditorSelectionHistory::cleanup_history() { for (int i = 0; i < history.size(); i++) { bool fail = false; for (int j = 0; j < history[i].path.size(); j++) { if (!history[i].path[j].ref.is_null()) { // If the node is a MultiNodeEdit node, examine it and see if anything is missing from it. Ref multi_node_edit = history[i].path[j].ref; if (multi_node_edit.is_valid()) { Node *root = EditorNode::get_singleton()->get_edited_scene(); if (root) { for (int k = 0; k < multi_node_edit->get_node_count(); k++) { NodePath np = multi_node_edit->get_node(k); Node *multi_node_selected_node = root->get_node_or_null(np); if (!multi_node_selected_node) { fail = true; break; } } } else { fail = true; } } else { // Reference is not null - object still alive. continue; } } if (!fail) { Object *obj = ObjectDB::get_instance(history[i].path[j].object); if (obj) { Node *n = Object::cast_to(obj); if (n && n->is_inside_tree()) { // Node valid and inside tree - object still alive. continue; } if (!n) { // Node possibly still alive. continue; } } // Else: object not valid - not alive. fail = true; } if (fail) { break; } } if (fail) { history.remove_at(i); i--; } } if (current_elem_idx >= history.size()) { current_elem_idx = history.size() - 1; } } void EditorSelectionHistory::add_object(ObjectID p_object, const String &p_property, bool p_inspector_only) { Object *obj = ObjectDB::get_instance(p_object); ERR_FAIL_NULL(obj); RefCounted *r = Object::cast_to(obj); _Object o; if (r) { o.ref = Ref(r); } o.object = p_object; o.property = p_property; o.inspector_only = p_inspector_only; bool has_prev = current_elem_idx >= 0 && current_elem_idx < history.size(); if (has_prev) { history.resize(current_elem_idx + 1); // Clip history to next. } HistoryElement h; if (!p_property.is_empty() && has_prev) { // Add a sub property. HistoryElement &prev_element = history.write[current_elem_idx]; h = prev_element; h.path.resize(h.level + 1); h.path.push_back(o); h.level++; } else { // Create a new history item. h.path.push_back(o); h.level = 0; } history.push_back(h); current_elem_idx++; } int EditorSelectionHistory::get_history_len() { return history.size(); } int EditorSelectionHistory::get_history_pos() { return current_elem_idx; } ObjectID EditorSelectionHistory::get_history_obj(int p_obj) const { ERR_FAIL_INDEX_V(p_obj, history.size(), ObjectID()); ERR_FAIL_INDEX_V(history[p_obj].level, history[p_obj].path.size(), ObjectID()); return history[p_obj].path[history[p_obj].level].object; } bool EditorSelectionHistory::is_at_beginning() const { return current_elem_idx <= 0; } bool EditorSelectionHistory::is_at_end() const { return ((current_elem_idx + 1) >= history.size()); } bool EditorSelectionHistory::next() { cleanup_history(); if ((current_elem_idx + 1) < history.size()) { current_elem_idx++; } else { return false; } return true; } bool EditorSelectionHistory::previous() { cleanup_history(); if (current_elem_idx > 0) { current_elem_idx--; } else { return false; } return true; } bool EditorSelectionHistory::is_current_inspector_only() const { if (current_elem_idx < 0 || current_elem_idx >= history.size()) { return false; } const HistoryElement &h = history[current_elem_idx]; return h.path[h.level].inspector_only; } ObjectID EditorSelectionHistory::get_current() { if (current_elem_idx < 0 || current_elem_idx >= history.size()) { return ObjectID(); } Object *obj = ObjectDB::get_instance(get_history_obj(current_elem_idx)); return obj ? obj->get_instance_id() : ObjectID(); } int EditorSelectionHistory::get_path_size() const { if (current_elem_idx < 0 || current_elem_idx >= history.size()) { return 0; } return history[current_elem_idx].path.size(); } ObjectID EditorSelectionHistory::get_path_object(int p_index) const { if (current_elem_idx < 0 || current_elem_idx >= history.size()) { return ObjectID(); } ERR_FAIL_INDEX_V(p_index, history[current_elem_idx].path.size(), ObjectID()); Object *obj = ObjectDB::get_instance(history[current_elem_idx].path[p_index].object); return obj ? obj->get_instance_id() : ObjectID(); } String EditorSelectionHistory::get_path_property(int p_index) const { if (current_elem_idx < 0 || current_elem_idx >= history.size()) { return ""; } ERR_FAIL_INDEX_V(p_index, history[current_elem_idx].path.size(), ""); return history[current_elem_idx].path[p_index].property; } void EditorSelectionHistory::clear() { history.clear(); current_elem_idx = -1; } EditorSelectionHistory::EditorSelectionHistory() { current_elem_idx = -1; } //////////////////////////////////////////////////////////// EditorPlugin *EditorData::get_handling_main_editor(Object *p_object) { // We need to iterate backwards so that we can check user-created plugins first. // Otherwise, it would not be possible for plugins to handle CanvasItem and Spatial nodes. for (int i = editor_plugins.size() - 1; i > -1; i--) { if (editor_plugins[i]->has_main_screen() && editor_plugins[i]->handles(p_object)) { return editor_plugins[i]; } } return nullptr; } Vector EditorData::get_handling_sub_editors(Object *p_object) { Vector sub_plugins; for (int i = editor_plugins.size() - 1; i > -1; i--) { if (!editor_plugins[i]->has_main_screen() && editor_plugins[i]->handles(p_object)) { sub_plugins.push_back(editor_plugins[i]); } } return sub_plugins; } EditorPlugin *EditorData::get_editor_by_name(const String &p_name) { for (int i = editor_plugins.size() - 1; i > -1; i--) { if (editor_plugins[i]->get_name() == p_name) { return editor_plugins[i]; } } return nullptr; } void EditorData::copy_object_params(Object *p_object) { clipboard.clear(); List pinfo; p_object->get_property_list(&pinfo); for (const PropertyInfo &E : pinfo) { if (!(E.usage & PROPERTY_USAGE_EDITOR) || E.name == "script" || E.name == "scripts" || E.name == "resource_path") { continue; } PropertyData pd; pd.name = E.name; pd.value = p_object->get(pd.name); clipboard.push_back(pd); } } void EditorData::get_editor_breakpoints(List *p_breakpoints) { for (int i = 0; i < editor_plugins.size(); i++) { editor_plugins[i]->get_breakpoints(p_breakpoints); } } Dictionary EditorData::get_editor_plugin_states() const { Dictionary metadata; for (int i = 0; i < editor_plugins.size(); i++) { Dictionary state = editor_plugins[i]->get_state(); if (state.is_empty()) { continue; } metadata[editor_plugins[i]->get_name()] = state; } return metadata; } Dictionary EditorData::get_scene_editor_states(int p_idx) const { ERR_FAIL_INDEX_V(p_idx, edited_scene.size(), Dictionary()); EditedScene es = edited_scene[p_idx]; return es.editor_states; } void EditorData::set_editor_plugin_states(const Dictionary &p_states) { if (p_states.is_empty()) { for (EditorPlugin *ep : editor_plugins) { ep->clear(); } return; } List keys; p_states.get_key_list(&keys); List::Element *E = keys.front(); for (; E; E = E->next()) { String name = E->get(); int idx = -1; for (int i = 0; i < editor_plugins.size(); i++) { if (editor_plugins[i]->get_name() == name) { idx = i; break; } } if (idx == -1) { continue; } editor_plugins[idx]->set_state(p_states[name]); } } void EditorData::notify_edited_scene_changed() { for (int i = 0; i < editor_plugins.size(); i++) { editor_plugins[i]->edited_scene_changed(); editor_plugins[i]->notify_scene_changed(get_edited_scene_root()); } } void EditorData::notify_resource_saved(const Ref &p_resource) { for (int i = 0; i < editor_plugins.size(); i++) { editor_plugins[i]->notify_resource_saved(p_resource); } } void EditorData::notify_scene_saved(const String &p_path) { for (int i = 0; i < editor_plugins.size(); i++) { editor_plugins[i]->notify_scene_saved(p_path); } } void EditorData::clear_editor_states() { for (int i = 0; i < editor_plugins.size(); i++) { editor_plugins[i]->clear(); } } void EditorData::save_editor_external_data() { for (int i = 0; i < editor_plugins.size(); i++) { editor_plugins[i]->save_external_data(); } } void EditorData::apply_changes_in_editors() { for (int i = 0; i < editor_plugins.size(); i++) { editor_plugins[i]->apply_changes(); } } void EditorData::paste_object_params(Object *p_object) { ERR_FAIL_NULL(p_object); undo_redo_manager->create_action(TTR("Paste Params")); for (const PropertyData &E : clipboard) { String name = E.name; undo_redo_manager->add_do_property(p_object, name, E.value); undo_redo_manager->add_undo_property(p_object, name, p_object->get(name)); } undo_redo_manager->commit_action(); } bool EditorData::call_build() { bool result = true; for (int i = 0; i < editor_plugins.size() && result; i++) { result &= editor_plugins[i]->build(); } return result; } void EditorData::set_scene_as_saved(int p_idx) { if (p_idx == -1) { p_idx = current_edited_scene; } ERR_FAIL_INDEX(p_idx, edited_scene.size()); undo_redo_manager->set_history_as_saved(edited_scene[p_idx].history_id); } bool EditorData::is_scene_changed(int p_idx) { if (p_idx == -1) { p_idx = current_edited_scene; } ERR_FAIL_INDEX_V(p_idx, edited_scene.size(), false); uint64_t current_scene_version = undo_redo_manager->get_or_create_history(edited_scene[p_idx].history_id).undo_redo->get_version(); bool is_changed = edited_scene[p_idx].last_checked_version != current_scene_version; edited_scene.write[p_idx].last_checked_version = current_scene_version; return is_changed; } int EditorData::get_scene_history_id_from_path(const String &p_path) const { for (const EditedScene &E : edited_scene) { if (E.path == p_path) { return E.history_id; } } return 0; } int EditorData::get_current_edited_scene_history_id() const { if (current_edited_scene != -1) { return edited_scene[current_edited_scene].history_id; } return 0; } int EditorData::get_scene_history_id(int p_idx) const { return edited_scene[p_idx].history_id; } void EditorData::add_undo_redo_inspector_hook_callback(Callable p_callable) { undo_redo_callbacks.push_back(p_callable); } void EditorData::remove_undo_redo_inspector_hook_callback(Callable p_callable) { undo_redo_callbacks.erase(p_callable); } const Vector EditorData::get_undo_redo_inspector_hook_callback() { return undo_redo_callbacks; } void EditorData::add_move_array_element_function(const StringName &p_class, Callable p_callable) { move_element_functions.insert(p_class, p_callable); } void EditorData::remove_move_array_element_function(const StringName &p_class) { move_element_functions.erase(p_class); } Callable EditorData::get_move_array_element_function(const StringName &p_class) const { if (move_element_functions.has(p_class)) { return move_element_functions[p_class]; } return Callable(); } void EditorData::remove_editor_plugin(EditorPlugin *p_plugin) { editor_plugins.erase(p_plugin); } void EditorData::add_editor_plugin(EditorPlugin *p_plugin) { editor_plugins.push_back(p_plugin); } int EditorData::get_editor_plugin_count() const { return editor_plugins.size(); } EditorPlugin *EditorData::get_editor_plugin(int p_idx) { ERR_FAIL_INDEX_V(p_idx, editor_plugins.size(), nullptr); return editor_plugins[p_idx]; } void EditorData::add_extension_editor_plugin(const StringName &p_class_name, EditorPlugin *p_plugin) { ERR_FAIL_COND(extension_editor_plugins.has(p_class_name)); extension_editor_plugins.insert(p_class_name, p_plugin); } void EditorData::remove_extension_editor_plugin(const StringName &p_class_name) { extension_editor_plugins.erase(p_class_name); } bool EditorData::has_extension_editor_plugin(const StringName &p_class_name) { return extension_editor_plugins.has(p_class_name); } EditorPlugin *EditorData::get_extension_editor_plugin(const StringName &p_class_name) { EditorPlugin **plugin = extension_editor_plugins.getptr(p_class_name); return plugin == nullptr ? nullptr : *plugin; } void EditorData::add_custom_type(const String &p_type, const String &p_inherits, const Ref