/*************************************************************************/ /* editor_node.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ /* Copyright (c) 2014-2017 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 "editor_node.h" #include "core/bind/core_bind.h" #include "core/class_db.h" #include "core/io/config_file.h" #include "core/io/resource_loader.h" #include "core/io/resource_saver.h" #include "core/io/stream_peer_ssl.h" #include "core/io/zip_io.h" #include "core/message_queue.h" #include "core/os/file_access.h" #include "core/os/input.h" #include "core/os/keyboard.h" #include "core/os/os.h" #include "core/path_remap.h" #include "core/print_string.h" #include "core/project_settings.h" #include "core/translation.h" #include "core/version.h" #include "main/input_default.h" #include "scene/resources/packed_scene.h" #include "servers/physics_2d_server.h" #include "editor/animation_editor.h" #include "editor/editor_audio_buses.h" #include "editor/editor_file_system.h" #include "editor/editor_help.h" #include "editor/editor_initialize_ssl.h" #include "editor/editor_settings.h" #include "editor/editor_themes.h" #include "editor/import/editor_import_collada.h" #include "editor/import/editor_scene_importer_gltf.h" #include "editor/import/resource_importer_csv_translation.h" #include "editor/import/resource_importer_obj.h" #include "editor/import/resource_importer_scene.h" #include "editor/import/resource_importer_texture.h" #include "editor/import/resource_importer_wav.h" #include "editor/plugins/animation_player_editor_plugin.h" #include "editor/plugins/animation_tree_editor_plugin.h" #include "editor/plugins/asset_library_editor_plugin.h" #include "editor/plugins/camera_editor_plugin.h" #include "editor/plugins/canvas_item_editor_plugin.h" #include "editor/plugins/collision_polygon_2d_editor_plugin.h" #include "editor/plugins/collision_polygon_editor_plugin.h" #include "editor/plugins/collision_shape_2d_editor_plugin.h" #include "editor/plugins/cube_grid_theme_editor_plugin.h" #include "editor/plugins/curve_editor_plugin.h" #include "editor/plugins/editor_preview_plugins.h" #include "editor/plugins/gi_probe_editor_plugin.h" #include "editor/plugins/gradient_editor_plugin.h" #include "editor/plugins/item_list_editor_plugin.h" #include "editor/plugins/light_occluder_2d_editor_plugin.h" #include "editor/plugins/line_2d_editor_plugin.h" #include "editor/plugins/material_editor_plugin.h" #include "editor/plugins/mesh_editor_plugin.h" #include "editor/plugins/mesh_instance_editor_plugin.h" #include "editor/plugins/multimesh_editor_plugin.h" #include "editor/plugins/navigation_polygon_editor_plugin.h" #include "editor/plugins/particles_2d_editor_plugin.h" #include "editor/plugins/particles_editor_plugin.h" #include "editor/plugins/path_2d_editor_plugin.h" #include "editor/plugins/path_editor_plugin.h" #include "editor/plugins/polygon_2d_editor_plugin.h" #include "editor/plugins/resource_preloader_editor_plugin.h" #include "editor/plugins/script_editor_plugin.h" #include "editor/plugins/script_text_editor.h" #include "editor/plugins/shader_editor_plugin.h" #include "editor/plugins/shader_graph_editor_plugin.h" #include "editor/plugins/spatial_editor_plugin.h" #include "editor/plugins/sprite_frames_editor_plugin.h" #include "editor/plugins/style_box_editor_plugin.h" #include "editor/plugins/texture_editor_plugin.h" #include "editor/plugins/texture_region_editor_plugin.h" #include "editor/plugins/theme_editor_plugin.h" #include "editor/plugins/tile_map_editor_plugin.h" #include "editor/plugins/tile_set_editor_plugin.h" #include "editor/pvrtc_compress.h" #include "editor/register_exporters.h" #include "editor/script_editor_debugger.h" #include EditorNode *EditorNode::singleton = NULL; void EditorNode::_update_scene_tabs() { bool show_rb = EditorSettings::get_singleton()->get("interface/show_script_in_scene_tabs"); scene_tabs->clear_tabs(); Ref script_icon = gui_base->get_icon("Script", "EditorIcons"); for (int i = 0; i < editor_data.get_edited_scene_count(); i++) { String type = editor_data.get_scene_type(i); Ref icon; if (type != String()) { if (!gui_base->has_icon(type, "EditorIcons")) { type = "Node"; } icon = gui_base->get_icon(type, "EditorIcons"); } int current = editor_data.get_edited_scene(); bool unsaved = (i == current) ? saved_version != editor_data.get_undo_redo().get_version() : editor_data.get_scene_version(i) != 0; scene_tabs->add_tab(editor_data.get_scene_title(i) + (unsaved ? "(*)" : ""), icon); if (show_rb && editor_data.get_scene_root_script(i).is_valid()) { scene_tabs->set_tab_right_button(i, script_icon); } } scene_tabs->set_current_tab(editor_data.get_edited_scene()); scene_tabs->ensure_tab_visible(editor_data.get_edited_scene()); } void EditorNode::_update_title() { String appname = ProjectSettings::get_singleton()->get("application/config/name"); String title = appname.empty() ? String(VERSION_FULL_NAME) : String(_MKSTR(VERSION_NAME) + String(" - ") + appname); String edited = editor_data.get_edited_scene_root() ? editor_data.get_edited_scene_root()->get_filename() : String(); if (!edited.empty()) title += " - " + String(edited.get_file()); if (unsaved_cache) title += " (*)"; OS::get_singleton()->set_window_title(title); } void EditorNode::_unhandled_input(const Ref &p_event) { if (Node::get_viewport()->get_modal_stack_top()) return; //ignore because of modal window Ref k = p_event; if (k.is_valid() && k->is_pressed() && !k->is_echo() && !gui_base->get_viewport()->gui_has_modal_stack()) { if (ED_IS_SHORTCUT("editor/next_tab", p_event)) { int next_tab = editor_data.get_edited_scene() + 1; next_tab %= editor_data.get_edited_scene_count(); _scene_tab_changed(next_tab); } if (ED_IS_SHORTCUT("editor/prev_tab", p_event)) { int next_tab = editor_data.get_edited_scene() - 1; next_tab = next_tab >= 0 ? next_tab : editor_data.get_edited_scene_count() - 1; _scene_tab_changed(next_tab); } if (ED_IS_SHORTCUT("editor/filter_files", p_event)) { filesystem_dock->focus_on_filter(); } if (ED_IS_SHORTCUT("editor/editor_2d", p_event)) { _editor_select(EDITOR_2D); } else if (ED_IS_SHORTCUT("editor/editor_3d", p_event)) { _editor_select(EDITOR_3D); } else if (ED_IS_SHORTCUT("editor/editor_script", p_event)) { _editor_select(EDITOR_SCRIPT); } else if (ED_IS_SHORTCUT("editor/editor_help", p_event)) { emit_signal("request_help_search", ""); } else if (ED_IS_SHORTCUT("editor/editor_assetlib", p_event)) { _editor_select(EDITOR_ASSETLIB); } else if (ED_IS_SHORTCUT("editor/editor_next", p_event)) { _editor_select_next(); } else if (ED_IS_SHORTCUT("editor/editor_prev", p_event)) { _editor_select_prev(); } } } void EditorNode::_notification(int p_what) { if (p_what == NOTIFICATION_EXIT_TREE) { editor_data.save_editor_external_data(); FileAccess::set_file_close_fail_notify_callback(NULL); log->deinit(); // do not get messages anymore } if (p_what == NOTIFICATION_PROCESS) { if (opening_prev && !confirmation->is_visible()) opening_prev = false; if (unsaved_cache != (saved_version != editor_data.get_undo_redo().get_version())) { unsaved_cache = (saved_version != editor_data.get_undo_redo().get_version()); _update_title(); } if (last_checked_version != editor_data.get_undo_redo().get_version()) { _update_scene_tabs(); last_checked_version = editor_data.get_undo_redo().get_version(); } //update the circle uint64_t frame = Engine::get_singleton()->get_frames_drawn(); uint32_t tick = OS::get_singleton()->get_ticks_msec(); if (frame != circle_step_frame && (tick - circle_step_msec) > (1000 / 8)) { circle_step++; if (circle_step >= 8) circle_step = 0; circle_step_msec = tick; circle_step_frame = frame + 1; // update the circle itself only when its enabled if (!update_menu->get_popup()->is_item_checked(3)) { update_menu->set_icon(gui_base->get_icon("Progress" + itos(circle_step + 1), "EditorIcons")); } } editor_selection->update(); scene_root->set_size_override(true, Size2(ProjectSettings::get_singleton()->get("display/window/size/width"), ProjectSettings::get_singleton()->get("display/window/size/height"))); ResourceImporterTexture::get_singleton()->update_imports(); } if (p_what == NOTIFICATION_ENTER_TREE) { Engine::get_singleton()->set_editor_hint(true); get_tree()->get_root()->set_disable_3d(true); get_tree()->get_root()->set_as_audio_listener(false); get_tree()->get_root()->set_as_audio_listener_2d(false); get_tree()->set_auto_accept_quit(false); get_tree()->connect("files_dropped", this, "_dropped_files"); } if (p_what == NOTIFICATION_EXIT_TREE) { editor_data.clear_edited_scenes(); } if (p_what == NOTIFICATION_READY) { VisualServer::get_singleton()->viewport_set_hide_scenario(get_scene_root()->get_viewport_rid(), true); VisualServer::get_singleton()->viewport_set_hide_canvas(get_scene_root()->get_viewport_rid(), true); VisualServer::get_singleton()->viewport_set_disable_environment(get_viewport()->get_viewport_rid(), true); _editor_select(EDITOR_3D); _update_debug_options(); } if (p_what == MainLoop::NOTIFICATION_WM_FOCUS_IN) { EditorFileSystem::get_singleton()->scan_changes(); } if (p_what == MainLoop::NOTIFICATION_WM_QUIT_REQUEST) { _menu_option_confirm(FILE_QUIT, false); } if (p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) { scene_tabs->set_tab_close_display_policy((bool(EDITOR_DEF("interface/always_show_close_button_in_scene_tabs", false)) ? Tabs::CLOSE_BUTTON_SHOW_ALWAYS : Tabs::CLOSE_BUTTON_SHOW_ACTIVE_ONLY)); property_editor->set_enable_capitalize_paths(bool(EDITOR_DEF("interface/capitalize_properties", true))); Ref theme = create_editor_theme(theme_base->get_theme()); theme_base->set_theme(theme); gui_base->add_style_override("panel", gui_base->get_stylebox("Background", "EditorStyles")); play_button_panel->add_style_override("panel", gui_base->get_stylebox("PlayButtonPanel", "EditorStyles")); scene_root_parent->add_style_override("panel", gui_base->get_stylebox("Content", "EditorStyles")); bottom_panel->add_style_override("panel", gui_base->get_stylebox("panel", "TabContainer")); scene_tabs->add_style_override("tab_fg", gui_base->get_stylebox("SceneTabFG", "EditorStyles")); scene_tabs->add_style_override("tab_bg", gui_base->get_stylebox("SceneTabBG", "EditorStyles")); if (bool(EDITOR_DEF("interface/scene_tabs/resize_if_many_tabs", true))) { scene_tabs->set_min_width(int(EDITOR_DEF("interface/scene_tabs/minimum_width", 50)) * EDSCALE); } else { scene_tabs->set_min_width(0); } _update_scene_tabs(); } } void EditorNode::_fs_changed() { for (Set::Element *E = file_dialogs.front(); E; E = E->next()) { E->get()->invalidate(); } for (Set::Element *E = editor_file_dialogs.front(); E; E = E->next()) { E->get()->invalidate(); } if (export_defer.preset != "") { Ref preset; for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); ++i) { preset = EditorExport::get_singleton()->get_export_preset(i); if (preset->get_name() == export_defer.preset) { break; } preset.unref(); } if (preset.is_null()) { String err = "Unknown export preset: " + export_defer.preset; ERR_PRINT(err.utf8().get_data()); } else { Ref platform = preset->get_platform(); if (platform.is_null()) { String err = "Preset \"" + export_defer.preset + "\" doesn't have a platform."; ERR_PRINT(err.utf8().get_data()); } else { platform->export_project(preset, export_defer.debug, export_defer.path, /*p_flags*/ 0); } } get_tree()->quit(); } { //reload changed resources List > changed; List > cached; ResourceCache::get_cached_resources(&cached); // FIXME: This should be done in a thread. for (List >::Element *E = cached.front(); E; E = E->next()) { if (!E->get()->editor_can_reload_from_file()) continue; if (!E->get()->get_path().is_resource_file() && !E->get()->get_path().is_abs_path()) continue; if (!FileAccess::exists(E->get()->get_path())) continue; if (E->get()->get_import_path() != String()) { //imported resource uint64_t mt = FileAccess::get_modified_time(E->get()->get_import_path()); if (mt != E->get()->get_import_last_modified_time()) { print_line("success"); changed.push_back(E->get()); } } else { uint64_t mt = FileAccess::get_modified_time(E->get()->get_path()); if (mt != E->get()->get_last_modified_time()) { changed.push_back(E->get()); } } } if (changed.size()) { int idx = 0; for (List >::Element *E = changed.front(); E; E = E->next()) { E->get()->reload_from_file(); } } } _mark_unsaved_scenes(); } void EditorNode::_sources_changed(bool p_exist) { if (waiting_for_first_scan) { if (defer_load_scene != "") { print_line("loading scene DEFERRED"); load_scene(defer_load_scene); defer_load_scene = ""; } waiting_for_first_scan = false; } } void EditorNode::_vp_resized() { } void EditorNode::_node_renamed() { if (property_editor) property_editor->update_tree(); } void EditorNode::_editor_select_next() { int editor = _get_current_main_editor(); if (editor == editor_table.size() - 1) { editor = 0; } else { editor++; } _editor_select(editor); } void EditorNode::_editor_select_prev() { int editor = _get_current_main_editor(); if (editor == 0) { editor = editor_table.size() - 1; } else { editor--; } _editor_select(editor); } Error EditorNode::load_resource(const String &p_scene) { RES res = ResourceLoader::load(p_scene); ERR_FAIL_COND_V(!res.is_valid(), ERR_CANT_OPEN); edit_resource(res); return OK; } void EditorNode::edit_resource(const Ref &p_resource) { _resource_selected(p_resource, ""); } void EditorNode::edit_node(Node *p_node) { push_item(p_node); } void EditorNode::open_resource(const String &p_type) { file->set_mode(EditorFileDialog::MODE_OPEN_FILE); List extensions; ResourceLoader::get_recognized_extensions_for_type(p_type, &extensions); file->clear_filters(); for (int i = 0; i < extensions.size(); i++) { file->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper()); } file->popup_centered_ratio(); current_option = RESOURCE_LOAD; } void EditorNode::save_resource_in_path(const Ref &p_resource, const String &p_path) { editor_data.apply_changes_in_editors(); int flg = 0; if (EditorSettings::get_singleton()->get("filesystem/on_save/compress_binary_resources")) flg |= ResourceSaver::FLAG_COMPRESS; String path = ProjectSettings::get_singleton()->localize_path(p_path); Error err = ResourceSaver::save(path, p_resource, flg | ResourceSaver::FLAG_REPLACE_SUBRESOURCE_PATHS); if (err != OK) { accept->set_text(TTR("Error saving resource!")); accept->popup_centered_minsize(); return; } ((Resource *)p_resource.ptr())->set_path(path); emit_signal("resource_saved", p_resource); } void EditorNode::save_resource(const Ref &p_resource) { if (p_resource->get_path().is_resource_file()) { save_resource_in_path(p_resource, p_resource->get_path()); } else { save_resource_as(p_resource); } } void EditorNode::save_resource_as(const Ref &p_resource, const String &p_at_path) { file->set_mode(EditorFileDialog::MODE_SAVE_FILE); current_option = RESOURCE_SAVE_AS; List extensions; Ref sd = memnew(PackedScene); ResourceSaver::get_recognized_extensions(p_resource, &extensions); file->clear_filters(); List preferred; for (int i = 0; i < extensions.size(); i++) { if (p_resource->is_class("Script") && (extensions[i] == "tres" || extensions[i] == "res" || extensions[i] == "xml")) { //this serves no purpose and confused people continue; } file->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper()); preferred.push_back(extensions[i]); } if (p_at_path != String()) { file->set_current_dir(p_at_path); if (p_resource->get_path().is_resource_file()) { file->set_current_file(p_resource->get_path().get_file()); } else { if (extensions.size()) { file->set_current_file("new_" + p_resource->get_class().to_lower() + "." + preferred.front()->get().to_lower()); } else { file->set_current_file(String()); } } } else if (p_resource->get_path() != "") { file->set_current_path(p_resource->get_path()); if (extensions.size()) { String ext = p_resource->get_path().get_extension().to_lower(); if (extensions.find(ext) == NULL) { file->set_current_path(p_resource->get_path().replacen("." + ext, "." + extensions.front()->get())); } } } else if (preferred.size()) { String existing; if (extensions.size()) { existing = "new_" + p_resource->get_class().to_lower() + "." + preferred.front()->get().to_lower(); } file->set_current_path(existing); } file->popup_centered_ratio(); file->set_title(TTR("Save Resource As..")); } void EditorNode::_menu_option(int p_option) { _menu_option_confirm(p_option, false); } void EditorNode::_menu_confirm_current() { _menu_option_confirm(current_option, true); } void EditorNode::_dialog_display_save_error(String p_file, Error p_error) { if (p_error) { current_option = -1; accept->get_ok()->set_text(TTR("I see..")); switch (p_error) { case ERR_FILE_CANT_WRITE: { accept->set_text(TTR("Can't open file for writing:") + " " + p_file.get_extension()); } break; case ERR_FILE_UNRECOGNIZED: { accept->set_text(TTR("Requested file format unknown:") + " " + p_file.get_extension()); } break; default: { accept->set_text(TTR("Error while saving.")); } break; } accept->popup_centered_minsize(); } } void EditorNode::_dialog_display_load_error(String p_file, Error p_error) { if (p_error) { current_option = -1; accept->get_ok()->set_text(TTR("I see..")); switch (p_error) { case ERR_CANT_OPEN: { accept->set_text(vformat(TTR("Can't open '%s'."), p_file.get_file())); } break; case ERR_PARSE_ERROR: { accept->set_text(vformat(TTR("Error while parsing '%s'."), p_file.get_file())); } break; case ERR_FILE_CORRUPT: { accept->set_text(vformat(TTR("Unexpected end of file '%s'."), p_file.get_file())); } break; case ERR_FILE_NOT_FOUND: { accept->set_text(vformat(TTR("Missing '%s' or its dependencies."), p_file.get_file())); } break; default: { accept->set_text(vformat(TTR("Error while loading '%s'."), p_file.get_file())); } break; } accept->popup_centered_minsize(); } } void EditorNode::_get_scene_metadata(const String &p_file) { Node *scene = editor_data.get_edited_scene_root(); if (!scene) return; String path = EditorSettings::get_singleton()->get_project_settings_path().plus_file(p_file.get_file() + "-editstate-" + p_file.md5_text() + ".cfg"); Ref cf; cf.instance(); Error err = cf->load(path); if (err != OK || !cf->has_section("editor_states")) return; //must not exist List esl; cf->get_section_keys("editor_states", &esl); Dictionary md; for (List::Element *E = esl.front(); E; E = E->next()) { Variant st = cf->get_value("editor_states", E->get()); if (st.get_type()) { md[E->get()] = st; } } editor_data.set_editor_states(md); } void EditorNode::_set_scene_metadata(const String &p_file, int p_idx) { Node *scene = editor_data.get_edited_scene_root(p_idx); if (!scene) return; scene->set_meta("__editor_run_settings__", Variant()); //clear it (no point in keeping it) scene->set_meta("__editor_plugin_states__", Variant()); String path = EditorSettings::get_singleton()->get_project_settings_path().plus_file(p_file.get_file() + "-editstate-" + p_file.md5_text() + ".cfg"); Ref cf; cf.instance(); Dictionary md; if (p_idx < 0 || editor_data.get_edited_scene() == p_idx) { md = editor_data.get_editor_states(); } else { md = editor_data.get_scene_editor_states(p_idx); } List keys; md.get_key_list(&keys); for (List::Element *E = keys.front(); E; E = E->next()) { cf->set_value("editor_states", E->get(), md[E->get()]); } Error err = cf->save(path); ERR_FAIL_COND(err != OK); } bool EditorNode::_find_and_save_resource(RES p_res, Map &processed, int32_t flags) { if (p_res.is_null()) return false; if (processed.has(p_res)) { return processed[p_res]; } bool changed = p_res->is_edited(); p_res->set_edited(false); bool subchanged = _find_and_save_edited_subresources(p_res.ptr(), processed, flags); if (p_res->get_path().is_resource_file()) { if (changed || subchanged) { //save print_line("Also saving modified external resource: " + p_res->get_path()); ResourceSaver::save(p_res->get_path(), p_res, flags); } processed[p_res] = false; //because it's a file return false; } else { processed[p_res] = changed; return changed; } } bool EditorNode::_find_and_save_edited_subresources(Object *obj, Map &processed, int32_t flags) { bool ret_changed = false; List pi; obj->get_property_list(&pi); for (List::Element *E = pi.front(); E; E = E->next()) { if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) continue; switch (E->get().type) { case Variant::OBJECT: { RES res = obj->get(E->get().name); if (_find_and_save_resource(res, processed, flags)) ret_changed = true; } break; case Variant::ARRAY: { Array varray = obj->get(E->get().name); int len = varray.size(); for (int i = 0; i < len; i++) { Variant v = varray.get(i); RES res = v; if (_find_and_save_resource(res, processed, flags)) ret_changed = true; } } break; case Variant::DICTIONARY: { Dictionary d = obj->get(E->get().name); List keys; d.get_key_list(&keys); for (List::Element *E = keys.front(); E; E = E->next()) { Variant v = d[E->get()]; RES res = v; if (_find_and_save_resource(res, processed, flags)) ret_changed = true; } } break; default: {} } } return ret_changed; } void EditorNode::_save_edited_subresources(Node *scene, Map &processed, int32_t flags) { _find_and_save_edited_subresources(scene, processed, flags); for (int i = 0; i < scene->get_child_count(); i++) { Node *n = scene->get_child(i); if (n->get_owner() != editor_data.get_edited_scene_root()) continue; _save_edited_subresources(n, processed, flags); } } void EditorNode::_find_node_types(Node *p_node, int &count_2d, int &count_3d) { if (p_node->is_class("Viewport") || (p_node != editor_data.get_edited_scene_root() && p_node->get_owner() != editor_data.get_edited_scene_root())) return; if (p_node->is_class("CanvasItem")) count_2d++; else if (p_node->is_class("Spatial")) count_3d++; for (int i = 0; i < p_node->get_child_count(); i++) _find_node_types(p_node->get_child(i), count_2d, count_3d); } void EditorNode::_save_scene_with_preview(String p_file) { EditorProgress save("save", TTR("Saving Scene"), 4); save.step(TTR("Analyzing"), 0); int c2d = 0; int c3d = 0; _find_node_types(editor_data.get_edited_scene_root(), c2d, c3d); RID viewport; bool is2d; if (c3d < c2d) { viewport = scene_root->get_viewport_rid(); is2d = true; } else { viewport = SpatialEditor::get_singleton()->get_editor_viewport(0)->get_viewport_node()->get_viewport_rid(); is2d = false; } save.step(TTR("Creating Thumbnail"), 1); //current view? Ref img; if (is2d) { img = scene_root->get_texture()->get_data(); } else { img = SpatialEditor::get_singleton()->get_editor_viewport(0)->get_viewport_node()->get_texture()->get_data(); } if (img.is_valid()) { save.step(TTR("Creating Thumbnail"), 2); save.step(TTR("Creating Thumbnail"), 3); int preview_size = EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size"); preview_size *= EDSCALE; int width, height; if (img->get_width() > preview_size && img->get_width() >= img->get_height()) { width = preview_size; height = img->get_height() * preview_size / img->get_width(); } else if (img->get_height() > preview_size && img->get_height() >= img->get_width()) { height = preview_size; width = img->get_width() * preview_size / img->get_height(); } else { width = img->get_width(); height = img->get_height(); } img->convert(Image::FORMAT_RGB8); img->resize(width, height); img->flip_y(); //save thumbnail directly, as thumbnailer may not update due to actual scene not changing md5 String temp_path = EditorSettings::get_singleton()->get_settings_path().plus_file("tmp"); String cache_base = ProjectSettings::get_singleton()->globalize_path(p_file).md5_text(); cache_base = temp_path.plus_file("resthumb-" + cache_base); //does not have it, try to load a cached thumbnail String file = cache_base + ".png"; img->save_png(file); } save.step(TTR("Saving Scene"), 4); _save_scene(p_file); EditorResourcePreview::get_singleton()->check_for_invalidation(p_file); } void EditorNode::_save_scene(String p_file, int idx) { Node *scene = editor_data.get_edited_scene_root(idx); if (!scene) { current_option = -1; accept->get_ok()->set_text(TTR("I see..")); accept->set_text(TTR("This operation can't be done without a tree root.")); accept->popup_centered_minsize(); return; } editor_data.apply_changes_in_editors(); _save_default_environment(); _set_scene_metadata(p_file, idx); Ref sdata; if (ResourceCache::has(p_file)) { // something may be referencing this resource and we are good with that. // we must update it, but also let the previous scene state go, as // old version still work for referencing changes in instanced or inherited scenes sdata = Ref(Object::cast_to(ResourceCache::get(p_file))); if (sdata.is_valid()) sdata->recreate_state(); else sdata.instance(); } else { sdata.instance(); } Error err = sdata->pack(scene); if (err != OK) { current_option = -1; accept->get_ok()->set_text(TTR("I see..")); accept->set_text(TTR("Couldn't save scene. Likely dependencies (instances) couldn't be satisfied.")); accept->popup_centered_minsize(); return; } // force creation of node path cache // (hacky but needed for the tree to update properly) Node *dummy_scene = sdata->instance(PackedScene::GEN_EDIT_STATE_INSTANCE); memdelete(dummy_scene); int flg = 0; if (EditorSettings::get_singleton()->get("filesystem/on_save/compress_binary_resources")) flg |= ResourceSaver::FLAG_COMPRESS; flg |= ResourceSaver::FLAG_REPLACE_SUBRESOURCE_PATHS; err = ResourceSaver::save(p_file, sdata, flg); Map processed; _save_edited_subresources(scene, processed, flg); editor_data.save_editor_external_data(); if (err == OK) { scene->set_filename(ProjectSettings::get_singleton()->localize_path(p_file)); if (idx < 0 || idx == editor_data.get_edited_scene()) set_current_version(editor_data.get_undo_redo().get_version()); else editor_data.set_edited_scene_version(0, idx); _update_title(); _update_scene_tabs(); } else { _dialog_display_save_error(p_file, err); } } void EditorNode::_save_all_scenes() { for (int i = 0; i < editor_data.get_edited_scene_count(); i++) { Node *scene = editor_data.get_edited_scene_root(i); if (scene && scene->get_filename() != "") { if (i != editor_data.get_edited_scene()) _save_scene(scene->get_filename(), i); else _save_scene_with_preview(scene->get_filename()); } // else: ignore new scenes } _save_default_environment(); } void EditorNode::_mark_unsaved_scenes() { for (int i = 0; i < editor_data.get_edited_scene_count(); i++) { Node *node = editor_data.get_edited_scene_root(i); if (!node) continue; String path = node->get_filename(); if (!(path == String() || FileAccess::exists(path))) { node->set_filename(""); if (i == editor_data.get_edited_scene()) set_current_version(-1); else editor_data.set_edited_scene_version(-1, i); } } _update_title(); _update_scene_tabs(); } void EditorNode::_dialog_action(String p_file) { switch (current_option) { case RESOURCE_LOAD: { RES res = ResourceLoader::load(p_file); if (res.is_null()) { current_option = -1; accept->get_ok()->set_text("ok :("); accept->set_text(TTR("Failed to load resource.")); return; }; push_item(res.operator->()); } break; case FILE_NEW_INHERITED_SCENE: { load_scene(p_file, false, true); } break; case FILE_OPEN_SCENE: { load_scene(p_file); } break; case SETTINGS_PICK_MAIN_SCENE: { ProjectSettings::get_singleton()->set("application/run/main_scene", p_file); ProjectSettings::get_singleton()->save(); //would be nice to show the project manager opened with the highlighted field.. _run(false, ""); // automatically run the project } break; case FILE_CLOSE: case FILE_CLOSE_ALL_AND_QUIT: case FILE_CLOSE_ALL_AND_RUN_PROJECT_MANAGER: case SCENE_TAB_CLOSE: case FILE_SAVE_SCENE: case FILE_SAVE_AS_SCENE: { int scene_idx = (current_option == FILE_SAVE_SCENE || current_option == FILE_SAVE_AS_SCENE) ? -1 : tab_closing; if (file->get_mode() == EditorFileDialog::MODE_SAVE_FILE) { _save_default_environment(); if (scene_idx != editor_data.get_edited_scene()) _save_scene(p_file, scene_idx); else _save_scene_with_preview(p_file); if (scene_idx != -1) _discard_changes(); } } break; case FILE_SAVE_AND_RUN: { if (file->get_mode() == EditorFileDialog::MODE_SAVE_FILE) { _save_default_environment(); _save_scene_with_preview(p_file); _call_build(); _run(true); } } break; case FILE_EXPORT_MESH_LIBRARY: { Ref ml; if (file_export_lib_merge->is_pressed() && FileAccess::exists(p_file)) { ml = ResourceLoader::load(p_file, "MeshLibrary"); if (ml.is_null()) { current_option = -1; accept->get_ok()->set_text(TTR("I see..")); accept->set_text(TTR("Can't load MeshLibrary for merging!")); accept->popup_centered_minsize(); return; } } if (ml.is_null()) { ml = Ref(memnew(MeshLibrary)); } MeshLibraryEditor::update_library_file(editor_data.get_edited_scene_root(), ml, true); Error err = ResourceSaver::save(p_file, ml); if (err) { accept->get_ok()->set_text(TTR("I see..")); accept->set_text(TTR("Error saving MeshLibrary!")); accept->popup_centered_minsize(); return; } } break; case FILE_EXPORT_TILESET: { Ref ml; if (FileAccess::exists(p_file)) { ml = ResourceLoader::load(p_file, "TileSet"); if (ml.is_null()) { if (file_export_lib_merge->is_pressed()) { current_option = -1; accept->get_ok()->set_text(TTR("I see..")); accept->set_text(TTR("Can't load TileSet for merging!")); accept->popup_centered_minsize(); return; } } else if (!file_export_lib_merge->is_pressed()) { ml->clear(); } } else { ml = Ref(memnew(TileSet)); } TileSetEditor::update_library_file(editor_data.get_edited_scene_root(), ml, true); Error err = ResourceSaver::save(p_file, ml); if (err) { accept->get_ok()->set_text(TTR("I see..")); accept->set_text(TTR("Error saving TileSet!")); accept->popup_centered_minsize(); return; } } break; case RESOURCE_SAVE: case RESOURCE_SAVE_AS: { uint32_t current = editor_history.get_current(); Object *current_obj = current > 0 ? ObjectDB::get_instance(current) : NULL; ERR_FAIL_COND(!Object::cast_to(current_obj)) RES current_res = RES(Object::cast_to(current_obj)); save_resource_in_path(current_res, p_file); } break; case SETTINGS_LAYOUT_SAVE: { if (p_file.empty()) return; Ref config; config.instance(); Error err = config->load(EditorSettings::get_singleton()->get_settings_path().plus_file("editor_layouts-3.cfg")); if (err == ERR_CANT_OPEN) { config.instance(); // new config } else if (err != OK) { show_warning(TTR("Error trying to save layout!")); return; } _save_docks_to_config(config, p_file); config->save(EditorSettings::get_singleton()->get_settings_path().plus_file("editor_layouts-3.cfg")); layout_dialog->hide(); _update_layouts_menu(); if (p_file == "Default") { show_warning(TTR("Default editor layout overridden.")); } } break; case SETTINGS_LAYOUT_DELETE: { if (p_file.empty()) return; Ref config; config.instance(); Error err = config->load(EditorSettings::get_singleton()->get_settings_path().plus_file("editor_layouts-3.cfg")); if (err != OK || !config->has_section(p_file)) { show_warning(TTR("Layout name not found!")); return; } // erase List keys; config->get_section_keys(p_file, &keys); for (List::Element *E = keys.front(); E; E = E->next()) { config->set_value(p_file, E->get(), Variant()); } config->save(EditorSettings::get_singleton()->get_settings_path().plus_file("editor_layouts-3.cfg")); layout_dialog->hide(); _update_layouts_menu(); if (p_file == "Default") { show_warning(TTR("Restored default layout to base settings.")); } } break; default: { //save scene? if (file->get_mode() == EditorFileDialog::MODE_SAVE_FILE) { _save_scene_with_preview(p_file); } } break; } } void EditorNode::push_item(Object *p_object, const String &p_property) { if (!p_object) { property_editor->edit(NULL); node_dock->set_node(NULL); scene_tree_dock->set_selected(NULL); return; } uint32_t id = p_object->get_instance_id(); if (id != editor_history.get_current()) { if (p_property == "") editor_history.add_object(id); else editor_history.add_object(id, p_property); } _edit_current(); } void EditorNode::_select_history(int p_idx) { //push it to the top, it is not correct, but it's more useful ObjectID id = editor_history.get_history_obj(p_idx); Object *obj = ObjectDB::get_instance(id); if (!obj) return; push_item(obj); } void EditorNode::_prepare_history() { int history_to = MAX(0, editor_history.get_history_len() - 25); editor_history_menu->get_popup()->clear(); Ref base_icon = gui_base->get_icon("Object", "EditorIcons"); Set already; for (int i = editor_history.get_history_len() - 1; i >= history_to; i--) { ObjectID id = editor_history.get_history_obj(i); Object *obj = ObjectDB::get_instance(id); if (!obj || already.has(id)) { if (history_to > 0) { history_to--; } continue; } already.insert(id); Ref icon = gui_base->get_icon("Object", "EditorIcons"); if (gui_base->has_icon(obj->get_class(), "EditorIcons")) icon = gui_base->get_icon(obj->get_class(), "EditorIcons"); else icon = base_icon; String text; if (Object::cast_to(obj)) { Resource *r = Object::cast_to(obj); if (r->get_path().is_resource_file()) text = r->get_path().get_file(); else if (r->get_name() != String()) { text = r->get_name(); } else { text = r->get_class(); } } else if (Object::cast_to(obj)) { text = Object::cast_to(obj)->get_name(); } else { text = obj->get_class(); } if (i == editor_history.get_history_pos()) { text = "[" + text + "]"; } editor_history_menu->get_popup()->add_icon_item(icon, text, i); } } void EditorNode::_property_editor_forward() { if (editor_history.next()) _edit_current(); } void EditorNode::_property_editor_back() { if (editor_history.previous()) _edit_current(); } void EditorNode::_save_default_environment() { Ref fallback = get_tree()->get_root()->get_world()->get_fallback_environment(); if (fallback.is_valid() && fallback->get_path().is_resource_file()) { Map processed; _find_and_save_edited_subresources(fallback.ptr(), processed, 0); save_resource_in_path(fallback, fallback->get_path()); } } void EditorNode::_hide_top_editors() { _display_top_editors(false); editor_plugins_over->clear(); } void EditorNode::_display_top_editors(bool p_display) { editor_plugins_over->make_visible(p_display); } void EditorNode::_set_top_editors(Vector p_editor_plugins_over) { editor_plugins_over->set_plugins_list(p_editor_plugins_over); } void EditorNode::_set_editing_top_editors(Object *p_current_object) { editor_plugins_over->edit(p_current_object); } void EditorNode::_edit_current() { uint32_t current = editor_history.get_current(); Object *current_obj = current > 0 ? ObjectDB::get_instance(current) : NULL; property_back->set_disabled(editor_history.is_at_begining()); property_forward->set_disabled(editor_history.is_at_end()); this->current = current_obj; editor_path->update_path(); if (!current_obj) { scene_tree_dock->set_selected(NULL); property_editor->edit(NULL); node_dock->set_node(NULL); object_menu->set_disabled(true); _display_top_editors(false); return; } object_menu->set_disabled(true); bool is_resource = current_obj->is_class("Resource"); bool is_node = current_obj->is_class("Node"); resource_save_button->set_disabled(!is_resource); if (is_resource) { Resource *current_res = Object::cast_to(current_obj); ERR_FAIL_COND(!current_res); scene_tree_dock->set_selected(NULL); property_editor->edit(current_res); node_dock->set_node(NULL); object_menu->set_disabled(false); EditorNode::get_singleton()->get_import_dock()->set_edit_path(current_res->get_path()); } else if (is_node) { Node *current_node = Object::cast_to(current_obj); ERR_FAIL_COND(!current_node); property_editor->edit(current_node); if (current_node->is_inside_tree()) { node_dock->set_node(current_node); scene_tree_dock->set_selected(current_node); } else { node_dock->set_node(NULL); scene_tree_dock->set_selected(NULL); } object_menu->get_popup()->clear(); } else { property_editor->edit(current_obj); node_dock->set_node(NULL); } /* Take care of PLUGIN EDITOR */ EditorPlugin *main_plugin = editor_data.get_editor(current_obj); if (main_plugin) { // special case if use of external editor is true if (main_plugin->get_name() == "Script" && bool(EditorSettings::get_singleton()->get("text_editor/external/use_external_editor"))) { main_plugin->edit(current_obj); } else if (main_plugin != editor_plugin_screen && (!ScriptEditor::get_singleton() || !ScriptEditor::get_singleton()->is_visible_in_tree() || ScriptEditor::get_singleton()->can_take_away_focus())) { // update screen main_plugin if (!changing_scene) { if (editor_plugin_screen) editor_plugin_screen->make_visible(false); editor_plugin_screen = main_plugin; editor_plugin_screen->edit(current_obj); editor_plugin_screen->make_visible(true); int plugin_count = editor_data.get_editor_plugin_count(); for (int i = 0; i < plugin_count; i++) { editor_data.get_editor_plugin(i)->notify_main_screen_changed(editor_plugin_screen->get_name()); } for (int i = 0; i < editor_table.size(); i++) { main_editor_buttons[i]->set_pressed(editor_table[i] == main_plugin); } } } else { editor_plugin_screen->edit(current_obj); } } Vector sub_plugins = editor_data.get_subeditors(current_obj); if (!sub_plugins.empty()) { _display_top_editors(false); _set_top_editors(sub_plugins); _set_editing_top_editors(current_obj); _display_top_editors(true); } else if (!editor_plugins_over->get_plugins_list().empty()) { _hide_top_editors(); } object_menu->set_disabled(false); PopupMenu *p = object_menu->get_popup(); p->clear(); p->add_shortcut(ED_SHORTCUT("property_editor/copy_params", TTR("Copy Params")), OBJECT_COPY_PARAMS); p->add_shortcut(ED_SHORTCUT("property_editor/paste_params", TTR("Paste Params")), OBJECT_PASTE_PARAMS); p->add_separator(); p->add_shortcut(ED_SHORTCUT("property_editor/paste_resource", TTR("Paste Resource")), RESOURCE_PASTE); if (is_resource) { p->add_shortcut(ED_SHORTCUT("property_editor/copy_resource", TTR("Copy Resource")), RESOURCE_COPY); p->add_shortcut(ED_SHORTCUT("property_editor/unref_resource", TTR("Make Built-In")), RESOURCE_UNREF); } if (is_resource || is_node) { p->add_separator(); p->add_shortcut(ED_SHORTCUT("property_editor/make_subresources_unique", TTR("Make Sub-Resources Unique")), OBJECT_UNIQUE_RESOURCES); p->add_separator(); p->add_icon_shortcut(gui_base->get_icon("HelpSearch", "EditorIcons"), ED_SHORTCUT("property_editor/open_help", TTR("Open in Help")), OBJECT_REQUEST_HELP); } List methods; current_obj->get_method_list(&methods); if (!methods.empty()) { bool found = false; List::Element *I = methods.front(); int i = 0; while (I) { if (I->get().flags & METHOD_FLAG_EDITOR) { if (!found) { p->add_separator(); found = true; } p->add_item(I->get().name.capitalize(), OBJECT_METHOD_BASE + i); } i++; I = I->next(); } } update_keying(); } void EditorNode::_resource_created() { Object *c = create_dialog->instance_selected(); ERR_FAIL_COND(!c); Resource *r = Object::cast_to(c); ERR_FAIL_COND(!r); REF res(r); push_item(c); } void EditorNode::_resource_selected(const RES &p_res, const String &p_property) { if (p_res.is_null()) return; RES r = p_res; push_item(r.operator->(), p_property); } void EditorNode::_run(bool p_current, const String &p_custom) { if (editor_run.get_status() == EditorRun::STATUS_PLAY) { play_button->set_pressed(!_playing_edited); play_scene_button->set_pressed(_playing_edited); return; } play_button->set_pressed(false); play_button->set_icon(gui_base->get_icon("MainPlay", "EditorIcons")); play_scene_button->set_pressed(false); play_scene_button->set_icon(gui_base->get_icon("PlayScene", "EditorIcons")); play_custom_scene_button->set_pressed(false); play_custom_scene_button->set_icon(gui_base->get_icon("PlayCustom", "EditorIcons")); String main_scene; String run_filename; String args; if (p_current || (editor_data.get_edited_scene_root() && p_custom == editor_data.get_edited_scene_root()->get_filename())) { Node *scene = editor_data.get_edited_scene_root(); if (!scene) { current_option = -1; accept->get_ok()->set_text(TTR("I see..")); accept->set_text(TTR("There is no defined scene to run.")); accept->popup_centered_minsize(); return; } if (scene->get_filename() == "") { current_option = -1; _menu_option_confirm(FILE_SAVE_BEFORE_RUN, false); return; } run_filename = scene->get_filename(); } else if (p_custom != "") { run_filename = p_custom; } if (run_filename == "") { //evidently, run the scene main_scene = GLOBAL_DEF("application/run/main_scene", ""); if (main_scene == "") { current_option = -1; pick_main_scene->set_text(TTR("No main scene has ever been defined, select one?\nYou can change it later in \"Project Settings\" under the 'application' category.")); pick_main_scene->popup_centered_minsize(); return; } if (!FileAccess::exists(main_scene)) { current_option = -1; pick_main_scene->set_text(vformat(TTR("Selected scene '%s' does not exist, select a valid one?\nYou can change it later in \"Project Settings\" under the 'application' category."), main_scene)); pick_main_scene->popup_centered_minsize(); return; } if (ResourceLoader::get_resource_type(main_scene) != "PackedScene") { current_option = -1; pick_main_scene->set_text(vformat(TTR("Selected scene '%s' is not a scene file, select a valid one?\nYou can change it later in \"Project Settings\" under the 'application' category."), main_scene)); pick_main_scene->popup_centered_minsize(); return; } } if (bool(EDITOR_DEF("run/auto_save/save_before_running", true))) { if (unsaved_cache) { Node *scene = editor_data.get_edited_scene_root(); if (scene) { //only autosave if there is a scene obviously if (scene->get_filename() == "") { current_option = -1; accept->get_ok()->set_text(TTR("I see..")); accept->set_text(TTR("Current scene was never saved, please save it prior to running.")); accept->popup_centered_minsize(); return; } _save_scene_with_preview(scene->get_filename()); } } _menu_option(FILE_SAVE_ALL_SCENES); editor_data.save_editor_external_data(); } if (bool(EDITOR_DEF("run/output/always_clear_output_on_play", true))) { log->clear(); } if (bool(EDITOR_DEF("run/output/always_open_output_on_play", true))) { make_bottom_panel_item_visible(log); } List breakpoints; editor_data.get_editor_breakpoints(&breakpoints); args = ProjectSettings::get_singleton()->get("editor/main_run_args"); Error error = editor_run.run(run_filename, args, breakpoints); if (error != OK) { current_option = -1; accept->get_ok()->set_text(TTR("I see..")); accept->set_text(TTR("Could not start subprocess!")); accept->popup_centered_minsize(); return; } emit_signal("play_pressed"); if (p_current) { play_scene_button->set_pressed(true); play_scene_button->set_icon(gui_base->get_icon("Reload", "EditorIcons")); } else if (p_custom != "") { run_custom_filename = p_custom; play_custom_scene_button->set_pressed(true); play_custom_scene_button->set_icon(gui_base->get_icon("Reload", "EditorIcons")); } else { play_button->set_pressed(true); play_button->set_icon(gui_base->get_icon("Reload", "EditorIcons")); } _playing_edited = p_current; } void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { if (!p_confirmed) //this may be a hack.. current_option = (MenuOptions)p_option; switch (p_option) { case FILE_NEW_SCENE: { int idx = editor_data.add_edited_scene(-1); _scene_tab_changed(idx); editor_data.clear_editor_states(); } break; case FILE_NEW_INHERITED_SCENE: case FILE_OPEN_SCENE: { file->set_mode(EditorFileDialog::MODE_OPEN_FILE); List extensions; ResourceLoader::get_recognized_extensions_for_type("PackedScene", &extensions); file->clear_filters(); for (int i = 0; i < extensions.size(); i++) { file->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper()); } Node *scene = editor_data.get_edited_scene_root(); if (scene) { file->set_current_path(scene->get_filename()); }; file->set_title(p_option == FILE_OPEN_SCENE ? TTR("Open Scene") : TTR("Open Base Scene")); file->popup_centered_ratio(); } break; case FILE_QUICK_OPEN_SCENE: { quick_open->popup("PackedScene", true); quick_open->set_title(TTR("Quick Open Scene..")); } break; case FILE_QUICK_OPEN_SCRIPT: { quick_open->popup("Script", true); quick_open->set_title(TTR("Quick Open Script..")); } break; case FILE_OPEN_PREV: { if (previous_scenes.empty()) break; opening_prev = true; open_request(previous_scenes.back()->get()); } break; case FILE_CLOSE_ALL_AND_QUIT: case FILE_CLOSE_ALL_AND_RUN_PROJECT_MANAGER: case FILE_CLOSE: { if (!p_confirmed && (unsaved_cache || p_option == FILE_CLOSE_ALL_AND_QUIT || p_option == FILE_CLOSE_ALL_AND_RUN_PROJECT_MANAGER)) { tab_closing = p_option == FILE_CLOSE ? editor_data.get_edited_scene() : _next_unsaved_scene(false); String scene_filename = editor_data.get_edited_scene_root(tab_closing)->get_filename(); save_confirmation->get_ok()->set_text(TTR("Save & Close")); save_confirmation->set_text(vformat(TTR("Save changes to '%s' before closing?"), scene_filename != "" ? scene_filename : "unsaved scene")); save_confirmation->popup_centered_minsize(); break; } } // fallthrough case SCENE_TAB_CLOSE: case FILE_SAVE_SCENE: { int scene_idx = (p_option == FILE_SAVE_SCENE) ? -1 : tab_closing; Node *scene = editor_data.get_edited_scene_root(scene_idx); if (scene && scene->get_filename() != "") { if (scene_idx != editor_data.get_edited_scene()) _save_scene(scene->get_filename(), scene_idx); else _save_scene_with_preview(scene->get_filename()); if (scene_idx != -1) _discard_changes(); break; } // fallthrough to save_as }; case FILE_SAVE_AS_SCENE: { int scene_idx = (p_option == FILE_SAVE_SCENE || p_option == FILE_SAVE_AS_SCENE) ? -1 : tab_closing; Node *scene = editor_data.get_edited_scene_root(scene_idx); if (!scene) { current_option = -1; //confirmation->get_cancel()->hide(); accept->get_ok()->set_text(TTR("I see..")); accept->set_text(TTR("This operation can't be done without a tree root.")); accept->popup_centered_minsize(); break; } file->set_mode(EditorFileDialog::MODE_SAVE_FILE); List extensions; Ref sd = memnew(PackedScene); ResourceSaver::get_recognized_extensions(sd, &extensions); file->clear_filters(); for (int i = 0; i < extensions.size(); i++) { file->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper()); } //file->set_current_path(current_path); if (scene->get_filename() != "") { file->set_current_path(scene->get_filename()); if (extensions.size()) { String ext = scene->get_filename().get_extension().to_lower(); if (extensions.find(ext) == NULL) { file->set_current_path(scene->get_filename().replacen("." + ext, "." + extensions.front()->get())); } } } else { String existing; if (extensions.size()) { String root_name(scene->get_name()); existing = root_name + "." + extensions.front()->get().to_lower(); } file->set_current_path(existing); } file->popup_centered_ratio(); file->set_title(TTR("Save Scene As..")); } break; case FILE_SAVE_ALL_SCENES: { _save_all_scenes(); } break; case FILE_SAVE_BEFORE_RUN: { if (!p_confirmed) { confirmation->get_cancel()->set_text(TTR("No")); confirmation->get_ok()->set_text(TTR("Yes")); confirmation->set_text(TTR("This scene has never been saved. Save before running?")); confirmation->popup_centered_minsize(); break; } _menu_option(FILE_SAVE_AS_SCENE); _menu_option_confirm(FILE_SAVE_AND_RUN, false); } break; case FILE_EXPORT_PROJECT: { project_export->popup_export(); } break; case FILE_EXPORT_MESH_LIBRARY: { if (!editor_data.get_edited_scene_root()) { current_option = -1; //confirmation->get_cancel()->hide(); accept->get_ok()->set_text(TTR("I see..")); accept->set_text(TTR("This operation can't be done without a scene.")); accept->popup_centered_minsize(); break; } List extensions; Ref ml(memnew(MeshLibrary)); ResourceSaver::get_recognized_extensions(ml, &extensions); file_export_lib->clear_filters(); for (List::Element *E = extensions.front(); E; E = E->next()) { file_export_lib->add_filter("*." + E->get()); } file_export_lib->popup_centered_ratio(); file_export_lib->set_title(TTR("Export Mesh Library")); } break; case FILE_EXPORT_TILESET: { List extensions; Ref ml(memnew(TileSet)); ResourceSaver::get_recognized_extensions(ml, &extensions); file_export_lib->clear_filters(); for (List::Element *E = extensions.front(); E; E = E->next()) { file_export_lib->add_filter("*." + E->get()); } file_export_lib->popup_centered_ratio(); file_export_lib->set_title(TTR("Export Tile Set")); } break; case SETTINGS_EXPORT_PREFERENCES: { //project_export_settings->popup_centered_ratio(); } break; case FILE_IMPORT_SUBSCENE: { //import_subscene->popup_centered_ratio(); if (!editor_data.get_edited_scene_root()) { current_option = -1; accept->get_ok()->set_text(TTR("I see..")); accept->set_text(TTR("This operation can't be done without a selected node.")); accept->popup_centered_minsize(); break; } scene_tree_dock->import_subscene(); } break; case FILE_EXTERNAL_OPEN_SCENE: { if (unsaved_cache && !p_confirmed) { confirmation->get_ok()->set_text(TTR("Open")); //confirmation->get_cancel()->show(); confirmation->set_text(TTR("Current scene not saved. Open anyway?")); confirmation->popup_centered_minsize(); break; } bool oprev = opening_prev; Error err = load_scene(external_file); if (err == OK && oprev) { previous_scenes.pop_back(); opening_prev = false; } } break; case EDIT_UNDO: { if (Input::get_singleton()->get_mouse_button_mask() & 0x7) { print_line("no because state"); break; // can't undo while mouse buttons are pressed } String action = editor_data.get_undo_redo().get_current_action_name(); if (action != "") log->add_message("UNDO: " + action); editor_data.get_undo_redo().undo(); } break; case EDIT_REDO: { if (Input::get_singleton()->get_mouse_button_mask() & 0x7) break; // can't redo while mouse buttons are pressed editor_data.get_undo_redo().redo(); String action = editor_data.get_undo_redo().get_current_action_name(); if (action != "") log->add_message("REDO: " + action); } break; case TOOLS_ORPHAN_RESOURCES: { orphan_resources->show(); } break; case EDIT_REVERT: { Node *scene = get_edited_scene(); if (!scene) break; String filename = scene->get_filename(); if (filename == String()) { show_warning(TTR("Can't reload a scene that was never saved.")); break; } if (unsaved_cache && !p_confirmed) { confirmation->get_ok()->set_text(TTR("Revert")); confirmation->set_text(TTR("This action cannot be undone. Revert anyway?")); confirmation->popup_centered_minsize(); break; } int cur_idx = editor_data.get_edited_scene(); _remove_edited_scene(); Error err = load_scene(filename); editor_data.move_edited_scene_to_index(cur_idx); get_undo_redo()->clear_history(); scene_tabs->set_current_tab(cur_idx); } break; case RESOURCE_NEW: { create_dialog->popup_create(true); } break; case RESOURCE_LOAD: { open_resource(); } break; case RESOURCE_SAVE: { uint32_t current = editor_history.get_current(); Object *current_obj = current > 0 ? ObjectDB::get_instance(current) : NULL; ERR_FAIL_COND(!Object::cast_to(current_obj)) RES current_res = RES(Object::cast_to(current_obj)); save_resource(current_res); } break; case RESOURCE_SAVE_AS: { uint32_t current = editor_history.get_current(); Object *current_obj = current > 0 ? ObjectDB::get_instance(current) : NULL; ERR_FAIL_COND(!Object::cast_to(current_obj)) RES current_res = RES(Object::cast_to(current_obj)); save_resource_as(current_res); } break; case RESOURCE_UNREF: { uint32_t current = editor_history.get_current(); Object *current_obj = current > 0 ? ObjectDB::get_instance(current) : NULL; ERR_FAIL_COND(!Object::cast_to(current_obj)) RES current_res = RES(Object::cast_to(current_obj)); current_res->set_path(""); _edit_current(); } break; case RESOURCE_COPY: { uint32_t current = editor_history.get_current(); Object *current_obj = current > 0 ? ObjectDB::get_instance(current) : NULL; ERR_FAIL_COND(!Object::cast_to(current_obj)) RES current_res = RES(Object::cast_to(current_obj)); EditorSettings::get_singleton()->set_resource_clipboard(current_res); } break; case RESOURCE_PASTE: { RES r = EditorSettings::get_singleton()->get_resource_clipboard(); if (r.is_valid()) { push_item(EditorSettings::get_singleton()->get_resource_clipboard().ptr(), String()); } } break; case OBJECT_REQUEST_HELP: { if (current) { _editor_select(EDITOR_SCRIPT); emit_signal("request_help", current->get_class()); } } break; case OBJECT_COPY_PARAMS: { editor_data.apply_changes_in_editors(); if (current) editor_data.copy_object_params(current); } break; case OBJECT_PASTE_PARAMS: { editor_data.apply_changes_in_editors(); if (current) editor_data.paste_object_params(current); editor_data.get_undo_redo().clear_history(); } break; case OBJECT_UNIQUE_RESOURCES: { editor_data.apply_changes_in_editors(); if (current) { List props; current->get_property_list(&props); Map duplicates; for (List::Element *E = props.front(); E; E = E->next()) { if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) continue; Variant v = current->get(E->get().name); if (v.is_ref()) { REF ref = v; if (ref.is_valid()) { RES res = ref; if (res.is_valid()) { if (!duplicates.has(res)) { duplicates[res] = res->duplicate(); } res = duplicates[res]; current->set(E->get().name, res); } } } } } editor_data.get_undo_redo().clear_history(); _set_editing_top_editors(NULL); _set_editing_top_editors(current); } break; case RUN_PLAY: { _menu_option_confirm(RUN_STOP, true); _call_build(); _run(false); } break; case RUN_PLAY_CUSTOM_SCENE: { if (run_custom_filename.empty() || editor_run.get_status() == EditorRun::STATUS_STOP) { _menu_option_confirm(RUN_STOP, true); quick_run->popup("PackedScene", true); quick_run->set_title(TTR("Quick Run Scene..")); play_custom_scene_button->set_pressed(false); } else { String last_custom_scene = run_custom_filename; _menu_option_confirm(RUN_STOP, true); _run(false, last_custom_scene); } } break; case RUN_STOP: { if (editor_run.get_status() == EditorRun::STATUS_STOP) break; editor_run.stop(); run_custom_filename.clear(); play_button->set_pressed(false); play_button->set_icon(gui_base->get_icon("MainPlay", "EditorIcons")); play_scene_button->set_pressed(false); play_scene_button->set_icon(gui_base->get_icon("PlayScene", "EditorIcons")); play_custom_scene_button->set_pressed(false); play_custom_scene_button->set_icon(gui_base->get_icon("PlayCustom", "EditorIcons")); if (bool(EDITOR_DEF("run/output/always_close_output_on_stop", true))) { for (int i = 0; i < bottom_panel_items.size(); i++) { if (bottom_panel_items[i].control == log) { _bottom_panel_switch(false, i); break; } } } emit_signal("stop_pressed"); } break; case RUN_PLAY_SCENE: { _save_default_environment(); _menu_option_confirm(RUN_STOP, true); _call_build(); _run(true); } break; case RUN_PLAY_NATIVE: { bool autosave = EDITOR_DEF("run/auto_save/save_before_running", true); if (autosave) { _menu_option_confirm(FILE_SAVE_ALL_SCENES, false); } if (run_native->is_deploy_debug_remote_enabled()) { _menu_option_confirm(RUN_STOP, true); _call_build(); emit_signal("play_pressed"); editor_run.run_native_notify(); } } break; case RUN_SCENE_SETTINGS: { run_settings_dialog->popup_run_settings(); } break; case RUN_SETTINGS: { project_settings->popup_project_settings(); } break; case FILE_QUIT: case RUN_PROJECT_MANAGER: { if (!p_confirmed) { bool save_each = EDITOR_DEF("interface/save_each_scene_on_quit", true); if (_next_unsaved_scene(!save_each) == -1) { bool confirm = EDITOR_DEF("interface/quit_confirmation", true); if (confirm) { confirmation->get_ok()->set_text(p_option == FILE_QUIT ? TTR("Quit") : TTR("Yes")); confirmation->set_text(p_option == FILE_QUIT ? TTR("Exit the editor?") : TTR("Open Project Manager?")); confirmation->popup_centered_minsize(); } else { _discard_changes(); break; } } else { if (save_each) { _menu_option_confirm(p_option == FILE_QUIT ? FILE_CLOSE_ALL_AND_QUIT : FILE_CLOSE_ALL_AND_RUN_PROJECT_MANAGER, false); } else { String unsaved_scenes; int i = _next_unsaved_scene(true, 0); while (i != -1) { unsaved_scenes += "\n " + editor_data.get_edited_scene_root(i)->get_filename(); i = _next_unsaved_scene(true, ++i); } save_confirmation->get_ok()->set_text(TTR("Save & Quit")); save_confirmation->set_text((p_option == FILE_QUIT ? TTR("Save changes to the following scene(s) before quitting?") : TTR("Save changes the following scene(s) before opening Project Manager?")) + unsaved_scenes); save_confirmation->popup_centered_minsize(); } } OS::get_singleton()->request_attention(); break; } if (_next_unsaved_scene(true) != -1) { _save_all_scenes(); } _discard_changes(); } break; case RUN_FILE_SERVER: { bool ischecked = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(RUN_FILE_SERVER)); if (ischecked) { file_server->stop(); run_native->set_deploy_dumb(false); } else { file_server->start(); run_native->set_deploy_dumb(true); } debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_FILE_SERVER), !ischecked); EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_file_server", !ischecked); } break; case RUN_LIVE_DEBUG: { bool ischecked = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(RUN_LIVE_DEBUG)); debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_LIVE_DEBUG), !ischecked); ScriptEditor::get_singleton()->get_debugger()->set_live_debugging(!ischecked); EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_live_debug", !ischecked); } break; case RUN_DEPLOY_REMOTE_DEBUG: { bool ischecked = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEPLOY_REMOTE_DEBUG)); debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEPLOY_REMOTE_DEBUG), !ischecked); run_native->set_deploy_debug_remote(!ischecked); EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_deploy_remote_debug", !ischecked); } break; case RUN_DEBUG_COLLISONS: { bool ischecked = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEBUG_COLLISONS)); debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEBUG_COLLISONS), !ischecked); run_native->set_debug_collisions(!ischecked); editor_run.set_debug_collisions(!ischecked); EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_debug_collisons", !ischecked); } break; case RUN_DEBUG_NAVIGATION: { bool ischecked = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEBUG_NAVIGATION)); debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEBUG_NAVIGATION), !ischecked); run_native->set_debug_navigation(!ischecked); editor_run.set_debug_navigation(!ischecked); EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_debug_navigation", !ischecked); } break; case RUN_RELOAD_SCRIPTS: { bool ischecked = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(RUN_RELOAD_SCRIPTS)); debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_RELOAD_SCRIPTS), !ischecked); ScriptEditor::get_singleton()->set_live_auto_reload_running_scripts(!ischecked); EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_reload_scripts", !ischecked); } break; case SETTINGS_UPDATE_ALWAYS: { update_menu->get_popup()->set_item_checked(0, true); update_menu->get_popup()->set_item_checked(1, false); OS::get_singleton()->set_low_processor_usage_mode(false); EditorSettings::get_singleton()->set_project_metadata("editor_options", "update_always", true); current_option = -1; accept->get_ok()->set_text(TTR("I see..")); accept->set_text(TTR("This option is deprecated. Situations where refresh must be forced are now considered a bug. Please report.")); accept->popup_centered_minsize(); } break; case SETTINGS_UPDATE_CHANGES: { update_menu->get_popup()->set_item_checked(0, false); update_menu->get_popup()->set_item_checked(1, true); OS::get_singleton()->set_low_processor_usage_mode(true); EditorSettings::get_singleton()->set_project_metadata("editor_options", "update_always", false); } break; case SETTINGS_UPDATE_SPINNER_HIDE: { update_menu->set_icon(gui_base->get_icon("Collapse", "EditorIcons")); update_menu->get_popup()->toggle_item_checked(3); bool checked = update_menu->get_popup()->is_item_checked(3); EditorSettings::get_singleton()->set_project_metadata("editor_options", "update_spinner_hide", checked); } break; case SETTINGS_PREFERENCES: { settings_config_dialog->popup_edit_settings(); } break; case SETTINGS_MANAGE_EXPORT_TEMPLATES: { export_template_manager->popup_manager(); } break; case SETTINGS_TOGGLE_FULLSCREN: { OS::get_singleton()->set_window_fullscreen(!OS::get_singleton()->is_window_fullscreen()); } break; case SETTINGS_PICK_MAIN_SCENE: { file->set_mode(EditorFileDialog::MODE_OPEN_FILE); List extensions; ResourceLoader::get_recognized_extensions_for_type("PackedScene", &extensions); file->clear_filters(); for (int i = 0; i < extensions.size(); i++) { file->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper()); } Node *scene = editor_data.get_edited_scene_root(); if (scene) { file->set_current_path(scene->get_filename()); }; file->set_title(TTR("Pick a Main Scene")); file->popup_centered_ratio(); } break; case HELP_CLASSES: { emit_signal("request_help_index", ""); } break; case HELP_SEARCH: { emit_signal("request_help_search", ""); } break; case HELP_DOCS: { OS::get_singleton()->shell_open("http://docs.godotengine.org/"); } break; case HELP_QA: { OS::get_singleton()->shell_open("https://godotengine.org/qa/"); } break; case HELP_ISSUES: { OS::get_singleton()->shell_open("https://github.com/godotengine/godot/issues"); } break; case HELP_COMMUNITY: { OS::get_singleton()->shell_open("https://godotengine.org/community"); } break; case HELP_ABOUT: { about->popup_centered_minsize(Size2(780, 500) * EDSCALE); } break; default: { if (p_option >= OBJECT_METHOD_BASE) { ERR_FAIL_COND(!current); int idx = p_option - OBJECT_METHOD_BASE; List methods; current->get_method_list(&methods); ERR_FAIL_INDEX(idx, methods.size()); String name = methods[idx].name; if (current) current->call(name); } else if (p_option >= IMPORT_PLUGIN_BASE) { } } } } int EditorNode::_next_unsaved_scene(bool p_valid_filename, int p_start) { for (int i = p_start; i < editor_data.get_edited_scene_count(); i++) { if (!editor_data.get_edited_scene_root(i)) continue; int current = editor_data.get_edited_scene(); bool unsaved = (i == current) ? saved_version != editor_data.get_undo_redo().get_version() : editor_data.get_scene_version(i) != 0; if (unsaved) { String scene_filename = editor_data.get_edited_scene_root(i)->get_filename(); if (p_valid_filename && scene_filename.length() == 0) continue; return i; } } return -1; } void EditorNode::_discard_changes(const String &p_str) { switch (current_option) { case FILE_CLOSE_ALL_AND_QUIT: case FILE_CLOSE_ALL_AND_RUN_PROJECT_MANAGER: case FILE_CLOSE: case SCENE_TAB_CLOSE: { _remove_scene(tab_closing); _update_scene_tabs(); if (current_option == FILE_CLOSE_ALL_AND_QUIT || current_option == FILE_CLOSE_ALL_AND_RUN_PROJECT_MANAGER) { if (_next_unsaved_scene(false) == -1) { current_option = current_option == FILE_CLOSE_ALL_AND_QUIT ? FILE_QUIT : RUN_PROJECT_MANAGER; _discard_changes(); } else { _menu_option_confirm(current_option, false); } } else { current_option = -1; save_confirmation->hide(); } } break; case FILE_QUIT: { _menu_option_confirm(RUN_STOP, true); exiting = true; get_tree()->quit(); } break; case RUN_PROJECT_MANAGER: { _menu_option_confirm(RUN_STOP, true); exiting = true; get_tree()->quit(); String exec = OS::get_singleton()->get_executable_path(); List args; args.push_back("--path"); args.push_back(exec.get_base_dir()); args.push_back("--project-manager"); OS::ProcessID pid = 0; Error err = OS::get_singleton()->execute(exec, args, false, &pid); ERR_FAIL_COND(err); } break; } } void EditorNode::_update_debug_options() { bool check_deploy_remote = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_deploy_remote_debug", false); bool check_file_server = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_file_server", false); bool check_debug_collisons = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_collisons", false); bool check_debug_navigation = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_navigation", false); bool check_live_debug = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_live_debug", false); bool check_reload_scripts = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_reload_scripts", false); if (check_deploy_remote) _menu_option_confirm(RUN_DEPLOY_REMOTE_DEBUG, true); if (check_file_server) _menu_option_confirm(RUN_FILE_SERVER, true); if (check_debug_collisons) _menu_option_confirm(RUN_DEBUG_COLLISONS, true); if (check_debug_navigation) _menu_option_confirm(RUN_DEBUG_NAVIGATION, true); if (check_live_debug) _menu_option_confirm(RUN_LIVE_DEBUG, true); if (check_reload_scripts) _menu_option_confirm(RUN_RELOAD_SCRIPTS, true); } Control *EditorNode::get_viewport() { return viewport; } void EditorNode::_editor_select(int p_which) { static bool selecting = false; if (selecting || changing_scene) return; selecting = true; ERR_FAIL_INDEX(p_which, editor_table.size()); for (int i = 0; i < main_editor_buttons.size(); i++) { main_editor_buttons[i]->set_pressed(i == p_which); } selecting = false; EditorPlugin *new_editor = editor_table[p_which]; ERR_FAIL_COND(!new_editor); if (editor_plugin_screen == new_editor) return; if (editor_plugin_screen) { editor_plugin_screen->make_visible(false); } editor_plugin_screen = new_editor; editor_plugin_screen->make_visible(true); editor_plugin_screen->selected_notify(); int plugin_count = editor_data.get_editor_plugin_count(); for (int i = 0; i < plugin_count; i++) { editor_data.get_editor_plugin(i)->notify_main_screen_changed(editor_plugin_screen->get_name()); } if (EditorSettings::get_singleton()->get("interface/separate_distraction_mode")) { if (p_which == EDITOR_SCRIPT) { set_distraction_free_mode(script_distraction); } else { set_distraction_free_mode(scene_distraction); } } } void EditorNode::add_editor_plugin(EditorPlugin *p_editor) { if (p_editor->has_main_screen()) { ToolButton *tb = memnew(ToolButton); tb->set_toggle_mode(true); tb->connect("pressed", singleton, "_editor_select", varray(singleton->main_editor_buttons.size())); tb->set_text(p_editor->get_name()); tb->set_icon(singleton->gui_base->get_icon(p_editor->get_name(), "EditorIcons")); tb->set_name(p_editor->get_name()); singleton->main_editor_buttons.push_back(tb); singleton->main_editor_button_vb->add_child(tb); singleton->editor_table.push_back(p_editor); singleton->distraction_free->raise(); } singleton->editor_data.add_editor_plugin(p_editor); singleton->add_child(p_editor); } void EditorNode::remove_editor_plugin(EditorPlugin *p_editor) { if (p_editor->has_main_screen()) { for (int i = 0; i < singleton->main_editor_buttons.size(); i++) { if (p_editor->get_name() == singleton->main_editor_buttons[i]->get_text()) { if (singleton->main_editor_buttons[i]->is_pressed()) { singleton->_editor_select(EDITOR_SCRIPT); } memdelete(singleton->main_editor_buttons[i]); singleton->main_editor_buttons.remove(i); break; } } singleton->editor_table.erase(p_editor); } p_editor->make_visible(false); p_editor->clear(); singleton->editor_plugins_over->get_plugins_list().erase(p_editor); singleton->remove_child(p_editor); singleton->editor_data.remove_editor_plugin(p_editor); } void EditorNode::_update_addon_config() { if (_initializing_addons) return; Vector enabled_addons; for (Map::Element *E = plugin_addons.front(); E; E = E->next()) { enabled_addons.push_back(E->key()); } if (enabled_addons.size() == 0) { ProjectSettings::get_singleton()->set("editor_plugins/enabled", Variant()); } else { ProjectSettings::get_singleton()->set("editor_plugins/enabled", enabled_addons); } project_settings->queue_save(); } void EditorNode::set_addon_plugin_enabled(const String &p_addon, bool p_enabled) { ERR_FAIL_COND(p_enabled && plugin_addons.has(p_addon)); ERR_FAIL_COND(!p_enabled && !plugin_addons.has(p_addon)); if (!p_enabled) { EditorPlugin *addon = plugin_addons[p_addon]; remove_editor_plugin(addon); memdelete(addon); //bye plugin_addons.erase(p_addon); _update_addon_config(); return; } Ref cf; cf.instance(); String addon_path = "res://addons/" + p_addon + "/plugin.cfg"; Error err = cf->load(addon_path); if (err != OK) { show_warning(TTR("Unable to enable addon plugin at: '") + addon_path + TTR("' parsing of config failed.")); return; } if (!cf->has_section_key("plugin", "script")) { show_warning(TTR("Unable to find script field for addon plugin at: 'res://addons/") + p_addon + "''."); return; } String path = cf->get_value("plugin", "script"); path = "res://addons/" + p_addon + "/" + path; Ref