/**************************************************************************/ /* 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_string_names.h" #include "editor/editor_undo_redo_manager.h" #include "editor/multi_node_edit.h" #include "editor/plugins/editor_context_menu_plugin.h" #include "editor/plugins/editor_plugin.h" #include "editor/plugins/script_editor_plugin.h" #include "editor/themes/editor_scale.h" #include "scene/gui/popup_menu.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++; } void EditorSelectionHistory::replace_object(ObjectID p_old_object, ObjectID p_new_object) { for (HistoryElement &element : history) { for (int index = 0; index < element.path.size(); index++) { if (element.path[index].object == p_old_object) { element.path.write[index].object = p_new_object; } } } } 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_context_menu_plugin(ContextMenuSlot p_slot, const Ref &p_plugin) { ContextMenu cm; cm.p_slot = p_slot; cm.plugin = p_plugin; p_plugin->start_idx = context_menu_plugins.size() * EditorContextMenuPlugin::MAX_ITEMS; context_menu_plugins.push_back(cm); } void EditorData::remove_context_menu_plugin(ContextMenuSlot p_slot, const Ref &p_plugin) { for (int i = context_menu_plugins.size() - 1; i > -1; i--) { if (context_menu_plugins[i].p_slot == p_slot && context_menu_plugins[i].plugin == p_plugin) { context_menu_plugins.remove_at(i); } } } int EditorData::match_context_menu_shortcut(ContextMenuSlot p_slot, const Ref &p_event) { for (ContextMenu &cm : context_menu_plugins) { if (cm.p_slot != p_slot) { continue; } HashMap, Callable> &cms = cm.plugin->context_menu_shortcuts; int shortcut_idx = 0; for (KeyValue, Callable> &E : cms) { const Ref &p_shortcut = E.key; if (p_shortcut->matches_event(p_event)) { return EditorData::CONTEXT_MENU_ITEM_ID_BASE + cm.plugin->start_idx + shortcut_idx; } shortcut_idx++; } } return 0; } void EditorData::add_options_from_plugins(PopupMenu *p_popup, ContextMenuSlot p_slot, const Vector &p_paths) { bool add_separator = false; for (ContextMenu &cm : context_menu_plugins) { if (cm.p_slot != p_slot) { continue; } cm.plugin->clear_context_menu_items(); cm.plugin->add_options(p_paths); HashMap &items = cm.plugin->context_menu_items; if (items.size() > 0 && !add_separator) { add_separator = true; p_popup->add_separator(); } for (KeyValue &E : items) { EditorContextMenuPlugin::ContextMenuItem &item = E.value; if (item.icon.is_valid()) { p_popup->add_icon_item(item.icon, item.item_name, item.idx); const int icon_size = p_popup->get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor)); p_popup->set_item_icon_max_width(-1, icon_size); } else { p_popup->add_item(item.item_name, item.idx); } if (item.shortcut.is_valid()) { p_popup->set_item_shortcut(-1, item.shortcut, true); } } } } template void EditorData::invoke_plugin_callback(ContextMenuSlot p_slot, int p_option, const T &p_arg) { Variant arg = p_arg; Variant *argptr = &arg; for (int i = 0; i < context_menu_plugins.size(); i++) { if (context_menu_plugins[i].p_slot != p_slot || context_menu_plugins[i].plugin.is_null()) { continue; } Ref plugin = context_menu_plugins[i].plugin; // Shortcut callback. int shortcut_idx = 0; int shortcut_base_idx = EditorData::CONTEXT_MENU_ITEM_ID_BASE + plugin->start_idx; for (KeyValue, Callable> &E : plugin->context_menu_shortcuts) { if (shortcut_base_idx + shortcut_idx == p_option) { const Callable &callable = E.value; Callable::CallError ce; Variant result; callable.callp((const Variant **)&argptr, 1, result, ce); } shortcut_idx++; } if (p_option < shortcut_base_idx + shortcut_idx) { return; } HashMap &items = plugin->context_menu_items; for (KeyValue &E : items) { EditorContextMenuPlugin::ContextMenuItem &item = E.value; if (p_option != item.idx || !item.callable.is_valid()) { continue; } Callable::CallError ce; Variant result; item.callable.callp((const Variant **)&argptr, 1, result, ce); if (ce.error != Callable::CallError::CALL_OK) { String err = Variant::get_callable_error_text(item.callable, nullptr, 0, ce); ERR_PRINT("Error calling function from context menu: " + err); } } } // Invoke submenu items. if (p_slot == CONTEXT_SLOT_FILESYSTEM) { invoke_plugin_callback(CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE, p_option, p_arg); } } void EditorData::filesystem_options_pressed(ContextMenuSlot p_slot, int p_option, const Vector &p_selected) { invoke_plugin_callback(p_slot, p_option, p_selected); } void EditorData::scene_tree_options_pressed(ContextMenuSlot p_slot, int p_option, const List &p_selected) { TypedArray nodes; for (Node *selected : p_selected) { nodes.append(selected); } invoke_plugin_callback(p_slot, p_option, nodes); } void EditorData::script_editor_options_pressed(ContextMenuSlot p_slot, int p_option, const Ref &p_script) { invoke_plugin_callback(p_slot, p_option, p_script); } void EditorData::add_custom_type(const String &p_type, const String &p_inherits, const Ref