Path: blob/master/editor/settings/editor_feature_profile.cpp
20841 views
/**************************************************************************/1/* editor_feature_profile.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "editor_feature_profile.h"3132#include "core/io/dir_access.h"33#include "core/io/json.h"34#include "editor/editor_node.h"35#include "editor/editor_string_names.h"36#include "editor/file_system/editor_paths.h"37#include "editor/gui/editor_file_dialog.h"38#include "editor/inspector/editor_property_name_processor.h"39#include "editor/settings/editor_settings.h"40#include "editor/themes/editor_scale.h"41#include "scene/gui/line_edit.h"42#include "scene/gui/separator.h"4344const char *EditorFeatureProfile::feature_names[FEATURE_MAX] = {45TTRC("3D Editor"),46TTRC("Script Editor"),47TTRC("Asset Library"),48TTRC("Scene Tree Editing"),49#ifndef DISABLE_DEPRECATED50TTRC("Node Dock (deprecated)"),51#endif52TTRC("FileSystem Dock"),53TTRC("Import Dock"),54TTRC("History Dock"),55TTRC("Game View"),56TTRC("Signals Dock"),57TTRC("Groups Dock"),58};5960const char *EditorFeatureProfile::feature_descriptions[FEATURE_MAX] = {61TTRC("Allows to view and edit 3D scenes."),62TTRC("Allows to edit scripts using the integrated script editor."),63TTRC("Provides built-in access to the Asset Library."),64TTRC("Allows editing the node hierarchy in the Scene dock."),65#ifndef DISABLE_DEPRECATED66TTRC("Allows to work with signals and groups of the node selected in the Scene dock."),67#endif68TTRC("Allows to browse the local file system via a dedicated dock."),69TTRC("Allows to configure import settings for individual assets. Requires the FileSystem dock to function."),70TTRC("Provides an overview of the editor's and each scene's undo history."),71TTRC("Provides tools for selecting and debugging nodes at runtime."),72TTRC("Allows to work with signals of the node selected in the Scene dock."),73TTRC("Allows to manage groups of the node selected in the Scene dock."),74};7576const char *EditorFeatureProfile::feature_identifiers[FEATURE_MAX] = {77"3d",78"script",79"asset_lib",80"scene_tree",81#ifndef DISABLE_DEPRECATED82"node_dock",83#endif84"filesystem_dock",85"import_dock",86"history_dock",87"game",88"signals_dock",89"groups_dock",90};9192void EditorFeatureProfile::set_disable_class(const StringName &p_class, bool p_disabled) {93if (p_disabled) {94disabled_classes.insert(p_class);95} else {96disabled_classes.erase(p_class);97}98}99100bool EditorFeatureProfile::is_class_disabled(const StringName &p_class) const {101if (p_class == StringName()) {102return false;103}104return disabled_classes.has(p_class) || is_class_disabled(ClassDB::get_parent_class_nocheck(p_class));105}106107void EditorFeatureProfile::set_disable_class_editor(const StringName &p_class, bool p_disabled) {108if (p_disabled) {109disabled_editors.insert(p_class);110} else {111disabled_editors.erase(p_class);112}113}114115bool EditorFeatureProfile::is_class_editor_disabled(const StringName &p_class) const {116if (p_class == StringName()) {117return false;118}119return disabled_editors.has(p_class) || is_class_editor_disabled(ClassDB::get_parent_class_nocheck(p_class));120}121122void EditorFeatureProfile::set_disable_class_property(const StringName &p_class, const StringName &p_property, bool p_disabled) {123if (p_disabled) {124if (!disabled_properties.has(p_class)) {125disabled_properties[p_class] = HashSet<StringName>();126}127128disabled_properties[p_class].insert(p_property);129} else {130ERR_FAIL_COND(!disabled_properties.has(p_class));131disabled_properties[p_class].erase(p_property);132if (disabled_properties[p_class].is_empty()) {133disabled_properties.erase(p_class);134}135}136}137138bool EditorFeatureProfile::is_class_property_disabled(const StringName &p_class, const StringName &p_property) const {139if (!disabled_properties.has(p_class)) {140return false;141}142143if (!disabled_properties[p_class].has(p_property)) {144return false;145}146147return true;148}149150bool EditorFeatureProfile::has_class_properties_disabled(const StringName &p_class) const {151return disabled_properties.has(p_class);152}153154void EditorFeatureProfile::set_item_collapsed(const StringName &p_class, bool p_collapsed) {155if (p_collapsed) {156collapsed_classes.insert(p_class);157} else {158collapsed_classes.erase(p_class);159}160}161162bool EditorFeatureProfile::is_item_collapsed(const StringName &p_class) const {163return collapsed_classes.has(p_class);164}165166void EditorFeatureProfile::set_disable_feature(Feature p_feature, bool p_disable) {167ERR_FAIL_INDEX(p_feature, FEATURE_MAX);168features_disabled[p_feature] = p_disable;169}170171bool EditorFeatureProfile::is_feature_disabled(Feature p_feature) const {172ERR_FAIL_INDEX_V(p_feature, FEATURE_MAX, false);173return features_disabled[p_feature];174}175176String EditorFeatureProfile::get_feature_name(Feature p_feature) {177ERR_FAIL_INDEX_V(p_feature, FEATURE_MAX, String());178return feature_names[p_feature];179}180181String EditorFeatureProfile::get_feature_description(Feature p_feature) {182ERR_FAIL_INDEX_V(p_feature, FEATURE_MAX, String());183return feature_descriptions[p_feature];184}185186Error EditorFeatureProfile::save_to_file(const String &p_path) {187Dictionary data;188data["type"] = "feature_profile";189Array dis_classes;190for (const StringName &E : disabled_classes) {191dis_classes.push_back(String(E));192}193dis_classes.sort();194data["disabled_classes"] = dis_classes;195196Array dis_editors;197for (const StringName &E : disabled_editors) {198dis_editors.push_back(String(E));199}200dis_editors.sort();201data["disabled_editors"] = dis_editors;202203Array dis_props;204205for (KeyValue<StringName, HashSet<StringName>> &E : disabled_properties) {206for (const StringName &F : E.value) {207dis_props.push_back(String(E.key) + ":" + String(F));208}209}210211data["disabled_properties"] = dis_props;212213Array dis_features;214for (int i = 0; i < FEATURE_MAX; i++) {215if (features_disabled[i]) {216dis_features.push_back(feature_identifiers[i]);217}218}219220data["disabled_features"] = dis_features;221222Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE);223ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_CREATE, "Cannot create file '" + p_path + "'.");224225JSON json;226String text = json.stringify(data, "\t");227f->store_string(text);228return OK;229}230231Error EditorFeatureProfile::load_from_file(const String &p_path) {232Error err;233String text = FileAccess::get_file_as_string(p_path, &err);234if (err != OK) {235return err;236}237238JSON json;239err = json.parse(text);240if (err != OK) {241ERR_PRINT("Error parsing '" + p_path + "' on line " + itos(json.get_error_line()) + ": " + json.get_error_message());242return ERR_PARSE_ERROR;243}244245Dictionary data = json.get_data();246247if (!data.has("type") || String(data["type"]) != "feature_profile") {248ERR_PRINT("Error parsing '" + p_path + "', it's not a feature profile.");249return ERR_PARSE_ERROR;250}251252disabled_classes.clear();253254if (data.has("disabled_classes")) {255Array disabled_classes_arr = data["disabled_classes"];256for (int i = 0; i < disabled_classes_arr.size(); i++) {257disabled_classes.insert(disabled_classes_arr[i]);258}259}260261disabled_editors.clear();262263if (data.has("disabled_editors")) {264Array disabled_editors_arr = data["disabled_editors"];265for (int i = 0; i < disabled_editors_arr.size(); i++) {266disabled_editors.insert(disabled_editors_arr[i]);267}268}269270disabled_properties.clear();271272if (data.has("disabled_properties")) {273Array disabled_properties_arr = data["disabled_properties"];274for (int i = 0; i < disabled_properties_arr.size(); i++) {275String s = disabled_properties_arr[i];276set_disable_class_property(s.get_slicec(':', 0), s.get_slicec(':', 1), true);277}278}279280if (data.has("disabled_features")) {281Array disabled_features_arr = data["disabled_features"];282for (int i = 0; i < FEATURE_MAX; i++) {283bool found = false;284String f = feature_identifiers[i];285for (int j = 0; j < disabled_features_arr.size(); j++) {286String fd = disabled_features_arr[j];287if (fd == f) {288found = true;289break;290}291}292293features_disabled[i] = found;294}295}296297return OK;298}299300void EditorFeatureProfile::_bind_methods() {301ClassDB::bind_method(D_METHOD("set_disable_class", "class_name", "disable"), &EditorFeatureProfile::set_disable_class);302ClassDB::bind_method(D_METHOD("is_class_disabled", "class_name"), &EditorFeatureProfile::is_class_disabled);303304ClassDB::bind_method(D_METHOD("set_disable_class_editor", "class_name", "disable"), &EditorFeatureProfile::set_disable_class_editor);305ClassDB::bind_method(D_METHOD("is_class_editor_disabled", "class_name"), &EditorFeatureProfile::is_class_editor_disabled);306307ClassDB::bind_method(D_METHOD("set_disable_class_property", "class_name", "property", "disable"), &EditorFeatureProfile::set_disable_class_property);308ClassDB::bind_method(D_METHOD("is_class_property_disabled", "class_name", "property"), &EditorFeatureProfile::is_class_property_disabled);309310ClassDB::bind_method(D_METHOD("set_disable_feature", "feature", "disable"), &EditorFeatureProfile::set_disable_feature);311ClassDB::bind_method(D_METHOD("is_feature_disabled", "feature"), &EditorFeatureProfile::is_feature_disabled);312313ClassDB::bind_method(D_METHOD("get_feature_name", "feature"), &EditorFeatureProfile::_get_feature_name);314315ClassDB::bind_method(D_METHOD("save_to_file", "path"), &EditorFeatureProfile::save_to_file);316ClassDB::bind_method(D_METHOD("load_from_file", "path"), &EditorFeatureProfile::load_from_file);317318BIND_ENUM_CONSTANT(FEATURE_3D);319BIND_ENUM_CONSTANT(FEATURE_SCRIPT);320BIND_ENUM_CONSTANT(FEATURE_ASSET_LIB);321BIND_ENUM_CONSTANT(FEATURE_SCENE_TREE);322#ifndef DISABLE_DEPRECATED323BIND_ENUM_CONSTANT(FEATURE_NODE_DOCK);324#endif325BIND_ENUM_CONSTANT(FEATURE_FILESYSTEM_DOCK);326BIND_ENUM_CONSTANT(FEATURE_IMPORT_DOCK);327BIND_ENUM_CONSTANT(FEATURE_HISTORY_DOCK);328BIND_ENUM_CONSTANT(FEATURE_GAME);329BIND_ENUM_CONSTANT(FEATURE_SIGNALS_DOCK);330BIND_ENUM_CONSTANT(FEATURE_GROUPS_DOCK);331BIND_ENUM_CONSTANT(FEATURE_MAX);332}333334EditorFeatureProfile::EditorFeatureProfile() {335for (int i = 0; i < FEATURE_MAX; i++) {336features_disabled[i] = false;337}338}339340//////////////////////////341342void EditorFeatureProfileManager::_notification(int p_what) {343switch (p_what) {344case NOTIFICATION_READY: {345current_profile = EDITOR_GET("_default_feature_profile");346if (!current_profile.is_empty()) {347current.instantiate();348Error err = current->load_from_file(EditorPaths::get_singleton()->get_feature_profiles_dir().path_join(current_profile + ".profile"));349if (err != OK) {350ERR_PRINT("Error loading default feature profile: " + current_profile);351current_profile = String();352current.unref();353}354}355_update_profile_list(current_profile);356} break;357358case NOTIFICATION_THEME_CHANGED: {359// Make sure that the icons are correctly adjusted if the theme's lightness was switched.360_update_selected_profile();361} break;362}363}364365String EditorFeatureProfileManager::_get_selected_profile() {366int idx = profile_list->get_selected();367if (idx < 0) {368return String();369}370371return profile_list->get_item_metadata(idx);372}373374void EditorFeatureProfileManager::_update_profile_list(const String &p_select_profile) {375String selected_profile;376if (p_select_profile.is_empty()) { //default, keep377if (profile_list->get_selected() >= 0) {378selected_profile = profile_list->get_item_metadata(profile_list->get_selected());379if (!FileAccess::exists(EditorPaths::get_singleton()->get_feature_profiles_dir().path_join(selected_profile + ".profile"))) {380selected_profile = String(); //does not exist381}382}383} else {384selected_profile = p_select_profile;385}386387Vector<String> profiles;388Ref<DirAccess> d = DirAccess::open(EditorPaths::get_singleton()->get_feature_profiles_dir());389ERR_FAIL_COND_MSG(d.is_null(), "Cannot open directory '" + EditorPaths::get_singleton()->get_feature_profiles_dir() + "'.");390391d->list_dir_begin();392while (true) {393String f = d->get_next();394if (f.is_empty()) {395break;396}397398if (!d->current_is_dir()) {399int last_pos = f.rfind(".profile");400if (last_pos != -1) {401profiles.push_back(f.substr(0, last_pos));402}403}404}405406profiles.sort();407408profile_list->clear();409410for (int i = 0; i < profiles.size(); i++) {411String name = profiles[i];412413if (i == 0 && selected_profile.is_empty()) {414selected_profile = name;415}416417if (name == current_profile) {418name += " " + TTR("(current)");419}420profile_list->add_item(name);421int index = profile_list->get_item_count() - 1;422profile_list->set_item_metadata(index, profiles[i]);423if (profiles[i] == selected_profile) {424profile_list->select(index);425}426}427428class_list_vbc->set_visible(!selected_profile.is_empty());429property_list_vbc->set_visible(!selected_profile.is_empty());430no_profile_selected_help->set_visible(selected_profile.is_empty());431profile_actions[PROFILE_CLEAR]->set_disabled(current_profile.is_empty());432profile_actions[PROFILE_ERASE]->set_disabled(selected_profile.is_empty());433profile_actions[PROFILE_EXPORT]->set_disabled(selected_profile.is_empty());434profile_actions[PROFILE_SET]->set_disabled(selected_profile.is_empty());435436current_profile_name->set_text(!current_profile.is_empty() ? current_profile : TTR("(none)"));437438_update_selected_profile();439}440441void EditorFeatureProfileManager::_profile_action(int p_action) {442switch (p_action) {443case PROFILE_CLEAR: {444set_current_profile("", false);445} break;446case PROFILE_SET: {447String selected = _get_selected_profile();448ERR_FAIL_COND(selected.is_empty());449if (selected == current_profile) {450return; // Nothing to do here.451}452set_current_profile(selected, false);453} break;454case PROFILE_IMPORT: {455import_profiles->popup_file_dialog();456} break;457case PROFILE_EXPORT: {458export_profile->popup_file_dialog();459export_profile->set_current_file(_get_selected_profile() + ".profile");460} break;461case PROFILE_NEW: {462new_profile_dialog->popup_centered(Size2(240, 60) * EDSCALE);463new_profile_name->clear();464new_profile_name->grab_focus();465} break;466case PROFILE_ERASE: {467String selected = _get_selected_profile();468ERR_FAIL_COND(selected.is_empty());469470erase_profile_dialog->set_text(vformat(TTR("Remove currently selected profile, '%s'? Cannot be undone."), selected));471erase_profile_dialog->popup_centered(Size2(240, 60) * EDSCALE);472} break;473}474}475476void EditorFeatureProfileManager::_erase_selected_profile() {477String selected = _get_selected_profile();478ERR_FAIL_COND(selected.is_empty());479Ref<DirAccess> da = DirAccess::open(EditorPaths::get_singleton()->get_feature_profiles_dir());480ERR_FAIL_COND_MSG(da.is_null(), "Cannot open directory '" + EditorPaths::get_singleton()->get_feature_profiles_dir() + "'.");481482da->remove(selected + ".profile");483if (selected == current_profile) {484_profile_action(PROFILE_CLEAR);485} else {486_update_profile_list();487}488}489490void EditorFeatureProfileManager::_create_new_profile() {491String name = new_profile_name->get_text().strip_edges();492if (!name.is_valid_filename() || name.contains_char('.')) {493EditorNode::get_singleton()->show_warning(TTR("Profile must be a valid filename and must not contain '.'"));494return;495}496String file = EditorPaths::get_singleton()->get_feature_profiles_dir().path_join(name + ".profile");497if (FileAccess::exists(file)) {498EditorNode::get_singleton()->show_warning(TTR("Profile with this name already exists."));499return;500}501502Ref<EditorFeatureProfile> new_profile;503new_profile.instantiate();504new_profile->save_to_file(file);505506_update_profile_list(name);507// The newly created profile is the first one, make it the current profile automatically.508if (profile_list->get_item_count() == 1) {509_profile_action(PROFILE_SET);510}511}512513void EditorFeatureProfileManager::_profile_selected(int p_what) {514_update_selected_profile();515}516517void EditorFeatureProfileManager::_hide_requested() {518_cancel_pressed(); // From AcceptDialog.519}520521void EditorFeatureProfileManager::_fill_classes_from(TreeItem *p_parent, const String &p_class, const String &p_selected, int p_class_insert_index) {522TreeItem *class_item = class_list->create_item(p_parent, p_class_insert_index);523class_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);524class_item->set_icon(0, EditorNode::get_singleton()->get_class_icon(p_class));525String text = p_class;526527bool disabled = edited->is_class_disabled(p_class);528bool disabled_editor = edited->is_class_editor_disabled(p_class);529bool disabled_properties = edited->has_class_properties_disabled(p_class);530if (disabled) {531class_item->set_custom_color(0, class_list->get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor)));532} else if (disabled_editor && disabled_properties) {533text += " " + TTR("(Editor Disabled, Properties Disabled)");534} else if (disabled_properties) {535text += " " + TTR("(Properties Disabled)");536} else if (disabled_editor) {537text += " " + TTR("(Editor Disabled)");538}539class_item->set_text(0, text);540class_item->set_editable(0, true);541class_item->set_selectable(0, true);542class_item->set_metadata(0, p_class);543544bool collapsed = edited->is_item_collapsed(p_class);545class_item->set_collapsed(collapsed);546547if (p_class == p_selected) {548class_item->select(0);549}550if (disabled) {551// Class disabled, do nothing else (do not show further).552return;553}554555class_item->set_checked(0, true); // If it's not disabled, it's checked.556557List<StringName> child_classes;558ClassDB::get_direct_inheriters_from_class(p_class, &child_classes);559child_classes.sort_custom<StringName::AlphCompare>();560561for (const StringName &name : child_classes) {562if (String(name).begins_with("Editor") || ClassDB::get_api_type(name) != ClassDB::API_CORE) {563continue;564}565_fill_classes_from(class_item, name, p_selected);566}567}568569void EditorFeatureProfileManager::_class_list_item_selected() {570if (updating_features) {571return;572}573574property_list->clear();575576TreeItem *item = class_list->get_selected();577if (!item) {578return;579}580581Variant md = item->get_metadata(0);582if (md.is_string()) {583description_bit->parse_symbol("class|" + md.operator String() + "|");584} else if (md.get_type() == Variant::INT) {585String feature_description = EditorFeatureProfile::get_feature_description(EditorFeatureProfile::Feature((int)md));586description_bit->set_custom_text(TTR(item->get_text(0)), String(), TTRGET(feature_description));587return;588} else {589return;590}591592String class_name = md;593if (edited->is_class_disabled(class_name)) {594return;595}596597updating_features = true;598TreeItem *root = property_list->create_item();599TreeItem *options = property_list->create_item(root);600options->set_text(0, TTR("Class Options:"));601602{603TreeItem *option = property_list->create_item(options);604option->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);605option->set_editable(0, true);606option->set_selectable(0, true);607option->set_checked(0, !edited->is_class_editor_disabled(class_name));608option->set_text(0, TTR("Enable Contextual Editor"));609option->set_metadata(0, CLASS_OPTION_DISABLE_EDITOR);610}611612List<PropertyInfo> props;613ClassDB::get_property_list(class_name, &props, true);614615bool has_editor_props = false;616for (const PropertyInfo &E : props) {617if (E.usage & PROPERTY_USAGE_EDITOR) {618has_editor_props = true;619break;620}621}622623if (has_editor_props) {624TreeItem *properties = property_list->create_item(root);625properties->set_text(0, TTR("Class Properties:"));626627const EditorPropertyNameProcessor::Style text_style = EditorPropertyNameProcessor::get_settings_style();628const EditorPropertyNameProcessor::Style tooltip_style = EditorPropertyNameProcessor::get_tooltip_style(text_style);629630for (const PropertyInfo &E : props) {631String name = E.name;632if (!(E.usage & PROPERTY_USAGE_EDITOR)) {633continue;634}635const String text = EditorPropertyNameProcessor::get_singleton()->process_name(name, text_style, name, class_name);636const String tooltip = EditorPropertyNameProcessor::get_singleton()->process_name(name, tooltip_style, name, class_name);637638TreeItem *property = property_list->create_item(properties);639property->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);640property->set_editable(0, true);641property->set_selectable(0, true);642property->set_checked(0, !edited->is_class_property_disabled(class_name, name));643property->set_text(0, text);644property->set_tooltip_text(0, tooltip);645property->set_metadata(0, name);646String icon_type = Variant::get_type_name(E.type);647property->set_icon(0, EditorNode::get_singleton()->get_class_icon(icon_type));648}649}650651updating_features = false;652}653654void EditorFeatureProfileManager::_class_list_item_edited() {655if (updating_features) {656return;657}658659TreeItem *item = class_list->get_edited();660if (!item) {661return;662}663664bool checked = item->is_checked(0);665666Variant md = item->get_metadata(0);667if (md.is_string()) {668String class_selected = md;669edited->set_disable_class(class_selected, !checked);670_save_and_update();671_update_profile_tree_from(item);672} else if (md.get_type() == Variant::INT) {673int feature_selected = md;674edited->set_disable_feature(EditorFeatureProfile::Feature(feature_selected), !checked);675_save_and_update();676}677}678679void EditorFeatureProfileManager::_class_list_item_collapsed(Object *p_item) {680if (updating_features) {681return;682}683684TreeItem *item = Object::cast_to<TreeItem>(p_item);685if (!item) {686return;687}688689Variant md = item->get_metadata(0);690if (!md.is_string()) {691return;692}693694String class_name = md;695bool collapsed = item->is_collapsed();696edited->set_item_collapsed(class_name, collapsed);697}698699void EditorFeatureProfileManager::_property_item_edited() {700if (updating_features) {701return;702}703704TreeItem *class_item = class_list->get_selected();705if (!class_item) {706return;707}708709Variant md = class_item->get_metadata(0);710if (!md.is_string()) {711return;712}713714String class_name = md;715716TreeItem *item = property_list->get_edited();717if (!item) {718return;719}720bool checked = item->is_checked(0);721722md = item->get_metadata(0);723if (md.is_string()) {724String property_selected = md;725edited->set_disable_class_property(class_name, property_selected, !checked);726_save_and_update();727_update_profile_tree_from(class_list->get_selected());728} else if (md.get_type() == Variant::INT) {729int feature_selected = md;730switch (feature_selected) {731case CLASS_OPTION_DISABLE_EDITOR: {732edited->set_disable_class_editor(class_name, !checked);733_save_and_update();734_update_profile_tree_from(class_list->get_selected());735} break;736}737}738}739740void EditorFeatureProfileManager::_update_profile_tree_from(TreeItem *p_edited) {741String edited_class = p_edited->get_metadata(0);742743TreeItem *edited_parent = p_edited->get_parent();744int class_insert_index = p_edited->get_index();745p_edited->get_parent()->remove_child(p_edited);746747_fill_classes_from(edited_parent, edited_class, edited_class, class_insert_index);748}749750void EditorFeatureProfileManager::_update_selected_profile() {751String class_selected;752int feature_selected = -1;753754if (class_list->get_selected()) {755Variant md = class_list->get_selected()->get_metadata(0);756if (md.is_string()) {757class_selected = md;758} else if (md.get_type() == Variant::INT) {759feature_selected = md;760}761}762763class_list->clear();764765String profile = _get_selected_profile();766profile_actions[PROFILE_SET]->set_disabled(profile == current_profile);767768if (profile.is_empty()) { //nothing selected, nothing edited769property_list->clear();770edited.unref();771return;772}773774if (profile == current_profile) {775edited = current; //reuse current profile (which is what editor uses)776ERR_FAIL_COND(current.is_null()); //nothing selected, current should never be null777} else {778//reload edited, if different from current779edited.instantiate();780Error err = edited->load_from_file(EditorPaths::get_singleton()->get_feature_profiles_dir().path_join(profile + ".profile"));781ERR_FAIL_COND_MSG(err != OK, "Error when loading editor feature profile from file '" + EditorPaths::get_singleton()->get_feature_profiles_dir().path_join(profile + ".profile") + "'.");782}783784updating_features = true;785786TreeItem *root = class_list->create_item();787788TreeItem *features = class_list->create_item(root);789TreeItem *last_feature = nullptr;790features->set_text(0, TTR("Main Features:"));791for (int i = 0; i < EditorFeatureProfile::FEATURE_MAX; i++) {792TreeItem *feature;793if (i == EditorFeatureProfile::FEATURE_IMPORT_DOCK) {794feature = class_list->create_item(last_feature);795} else {796feature = class_list->create_item(features);797last_feature = feature;798}799feature->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);800feature->set_text(0, TTRGET(EditorFeatureProfile::get_feature_name(EditorFeatureProfile::Feature(i))));801feature->set_selectable(0, true);802feature->set_editable(0, true);803feature->set_metadata(0, i);804if (!edited->is_feature_disabled(EditorFeatureProfile::Feature(i))) {805feature->set_checked(0, true);806}807808if (i == feature_selected) {809feature->select(0);810}811}812813TreeItem *classes = class_list->create_item(root);814classes->set_text(0, TTR("Nodes and Classes:"));815816_fill_classes_from(classes, "Node", class_selected);817_fill_classes_from(classes, "Resource", class_selected);818819updating_features = false;820821_class_list_item_selected();822}823824void EditorFeatureProfileManager::_import_profiles(const Vector<String> &p_paths) {825//test it first826for (int i = 0; i < p_paths.size(); i++) {827Ref<EditorFeatureProfile> profile;828profile.instantiate();829Error err = profile->load_from_file(p_paths[i]);830String basefile = p_paths[i].get_file();831if (err != OK) {832EditorNode::get_singleton()->show_warning(vformat(TTR("File '%s' format is invalid, import aborted."), basefile));833return;834}835836String dst_file = EditorPaths::get_singleton()->get_feature_profiles_dir().path_join(basefile);837838if (FileAccess::exists(dst_file)) {839EditorNode::get_singleton()->show_warning(vformat(TTR("Profile '%s' already exists. Remove it first before importing, import aborted."), basefile.get_basename()));840return;841}842}843844//do it second845for (int i = 0; i < p_paths.size(); i++) {846Ref<EditorFeatureProfile> profile;847profile.instantiate();848Error err = profile->load_from_file(p_paths[i]);849ERR_CONTINUE(err != OK);850String basefile = p_paths[i].get_file();851String dst_file = EditorPaths::get_singleton()->get_feature_profiles_dir().path_join(basefile);852profile->save_to_file(dst_file);853}854855_update_profile_list();856// The newly imported profile is the first one, make it the current profile automatically.857if (profile_list->get_item_count() == 1) {858_profile_action(PROFILE_SET);859}860}861862void EditorFeatureProfileManager::_export_profile(const String &p_path) {863ERR_FAIL_COND(edited.is_null());864Error err = edited->save_to_file(p_path);865if (err != OK) {866EditorNode::get_singleton()->show_warning(vformat(TTR("Error saving profile to path: '%s'."), p_path));867}868}869870void EditorFeatureProfileManager::_save_and_update() {871String edited_path = _get_selected_profile();872ERR_FAIL_COND(edited_path.is_empty());873ERR_FAIL_COND(edited.is_null());874875edited->save_to_file(EditorPaths::get_singleton()->get_feature_profiles_dir().path_join(edited_path + ".profile"));876877if (edited == current) {878update_timer->start();879}880}881882void EditorFeatureProfileManager::_emit_current_profile_changed() {883emit_signal(SNAME("current_feature_profile_changed"));884}885886void EditorFeatureProfileManager::notify_changed() {887_emit_current_profile_changed();888}889890Ref<EditorFeatureProfile> EditorFeatureProfileManager::get_current_profile() {891return current;892}893894String EditorFeatureProfileManager::get_current_profile_name() const {895return current_profile;896}897898void EditorFeatureProfileManager::set_current_profile(const String &p_profile_name, bool p_validate_profile) {899if (p_validate_profile && !p_profile_name.is_empty()) {900// Profile may not exist.901Ref<DirAccess> da = DirAccess::open(EditorPaths::get_singleton()->get_feature_profiles_dir());902ERR_FAIL_COND_MSG(da.is_null(), "Cannot open directory '" + EditorPaths::get_singleton()->get_feature_profiles_dir() + "'.");903ERR_FAIL_COND_MSG(!da->file_exists(p_profile_name + ".profile"), "Feature profile '" + p_profile_name + "' does not exist.");904905// Change profile selection to emulate the UI interaction. Otherwise, the wrong profile would get activated.906// FIXME: Ideally, _update_selected_profile() should not rely on the user interface state to function properly.907for (int i = 0; i < profile_list->get_item_count(); i++) {908if (profile_list->get_item_metadata(i) == p_profile_name) {909profile_list->select(i);910break;911}912}913_update_selected_profile();914}915916// Store in editor settings.917EditorSettings::get_singleton()->set("_default_feature_profile", p_profile_name);918EditorSettings::get_singleton()->save();919920current_profile = p_profile_name;921if (p_profile_name.is_empty()) {922current.unref();923} else {924current = edited;925}926_update_profile_list();927_emit_current_profile_changed();928}929930EditorFeatureProfileManager *EditorFeatureProfileManager::singleton = nullptr;931932void EditorFeatureProfileManager::_bind_methods() {933ADD_SIGNAL(MethodInfo("current_feature_profile_changed"));934}935936EditorFeatureProfileManager::EditorFeatureProfileManager() {937VBoxContainer *main_vbc = memnew(VBoxContainer);938add_child(main_vbc);939940HBoxContainer *name_hbc = memnew(HBoxContainer);941current_profile_name = memnew(LineEdit);942name_hbc->add_child(current_profile_name);943current_profile_name->set_accessibility_name(TTRC("Current Profile:"));944current_profile_name->set_text(TTR("(none)"));945current_profile_name->set_editable(false);946current_profile_name->set_h_size_flags(Control::SIZE_EXPAND_FILL);947profile_actions[PROFILE_CLEAR] = memnew(Button(TTR("Reset to Default")));948name_hbc->add_child(profile_actions[PROFILE_CLEAR]);949profile_actions[PROFILE_CLEAR]->set_disabled(true);950profile_actions[PROFILE_CLEAR]->connect(SceneStringName(pressed), callable_mp(this, &EditorFeatureProfileManager::_profile_action).bind(PROFILE_CLEAR));951952main_vbc->add_margin_child(TTR("Current Profile:"), name_hbc);953954main_vbc->add_child(memnew(HSeparator));955956HBoxContainer *profiles_hbc = memnew(HBoxContainer);957profile_list = memnew(OptionButton);958profile_list->set_accessibility_name(TTRC("Available Profiles:"));959profile_list->set_h_size_flags(Control::SIZE_EXPAND_FILL);960profile_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);961profiles_hbc->add_child(profile_list);962profile_list->connect(SceneStringName(item_selected), callable_mp(this, &EditorFeatureProfileManager::_profile_selected));963964profile_actions[PROFILE_NEW] = memnew(Button(TTR("Create Profile")));965profiles_hbc->add_child(profile_actions[PROFILE_NEW]);966profile_actions[PROFILE_NEW]->connect(SceneStringName(pressed), callable_mp(this, &EditorFeatureProfileManager::_profile_action).bind(PROFILE_NEW));967968profile_actions[PROFILE_ERASE] = memnew(Button(TTR("Remove Profile")));969profiles_hbc->add_child(profile_actions[PROFILE_ERASE]);970profile_actions[PROFILE_ERASE]->set_disabled(true);971profile_actions[PROFILE_ERASE]->connect(SceneStringName(pressed), callable_mp(this, &EditorFeatureProfileManager::_profile_action).bind(PROFILE_ERASE));972973main_vbc->add_margin_child(TTR("Available Profiles:"), profiles_hbc);974975HBoxContainer *current_profile_hbc = memnew(HBoxContainer);976977profile_actions[PROFILE_SET] = memnew(Button(TTR("Make Current")));978current_profile_hbc->add_child(profile_actions[PROFILE_SET]);979profile_actions[PROFILE_SET]->set_disabled(true);980profile_actions[PROFILE_SET]->connect(SceneStringName(pressed), callable_mp(this, &EditorFeatureProfileManager::_profile_action).bind(PROFILE_SET));981982current_profile_hbc->add_child(memnew(VSeparator));983984profile_actions[PROFILE_IMPORT] = memnew(Button(TTR("Import")));985current_profile_hbc->add_child(profile_actions[PROFILE_IMPORT]);986profile_actions[PROFILE_IMPORT]->connect(SceneStringName(pressed), callable_mp(this, &EditorFeatureProfileManager::_profile_action).bind(PROFILE_IMPORT));987988profile_actions[PROFILE_EXPORT] = memnew(Button(TTR("Export")));989current_profile_hbc->add_child(profile_actions[PROFILE_EXPORT]);990profile_actions[PROFILE_EXPORT]->set_disabled(true);991profile_actions[PROFILE_EXPORT]->connect(SceneStringName(pressed), callable_mp(this, &EditorFeatureProfileManager::_profile_action).bind(PROFILE_EXPORT));992993main_vbc->add_child(current_profile_hbc);994995h_split = memnew(HSplitContainer);996h_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);997main_vbc->add_child(h_split);998999class_list_vbc = memnew(VBoxContainer);1000h_split->add_child(class_list_vbc);1001class_list_vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL);10021003class_list = memnew(Tree);1004class_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);1005class_list_vbc->add_margin_child(TTR("Configure Selected Profile:"), class_list, true);1006class_list->set_hide_root(true);1007class_list->set_edit_checkbox_cell_only_when_checkbox_is_pressed(true);1008class_list->connect("cell_selected", callable_mp(this, &EditorFeatureProfileManager::_class_list_item_selected));1009class_list->connect("item_edited", callable_mp(this, &EditorFeatureProfileManager::_class_list_item_edited), CONNECT_DEFERRED);1010class_list->connect("item_collapsed", callable_mp(this, &EditorFeatureProfileManager::_class_list_item_collapsed));1011class_list->set_theme_type_variation("TreeSecondary");1012// It will be displayed once the user creates or chooses a profile.1013class_list_vbc->hide();10141015property_list_vbc = memnew(VBoxContainer);1016h_split->add_child(property_list_vbc);1017property_list_vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL);10181019description_bit = memnew(EditorHelpBit);1020description_bit->set_content_height_limits(80 * EDSCALE, 80 * EDSCALE);1021description_bit->connect("request_hide", callable_mp(this, &EditorFeatureProfileManager::_hide_requested));1022property_list_vbc->add_margin_child(TTR("Description:"), description_bit, false);10231024property_list = memnew(Tree);1025property_list_vbc->add_margin_child(TTR("Extra Options:"), property_list, true);1026property_list->set_hide_root(true);1027property_list->set_hide_folding(true);1028property_list->set_edit_checkbox_cell_only_when_checkbox_is_pressed(true);1029property_list->connect("item_edited", callable_mp(this, &EditorFeatureProfileManager::_property_item_edited), CONNECT_DEFERRED);1030property_list->set_theme_type_variation("TreeSecondary");1031// It will be displayed once the user creates or chooses a profile.1032property_list_vbc->hide();10331034no_profile_selected_help = memnew(Label(TTR("Create or import a profile to edit available classes and properties.")));1035// Add some spacing above the help label.1036Ref<StyleBoxEmpty> sb = memnew(StyleBoxEmpty);1037sb->set_content_margin(SIDE_TOP, 20 * EDSCALE);1038no_profile_selected_help->add_theme_style_override(CoreStringName(normal), sb);1039no_profile_selected_help->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);1040no_profile_selected_help->set_v_size_flags(Control::SIZE_EXPAND_FILL);1041h_split->add_child(no_profile_selected_help);10421043new_profile_dialog = memnew(ConfirmationDialog);1044new_profile_dialog->set_title(TTR("Create Profile"));1045VBoxContainer *new_profile_vb = memnew(VBoxContainer);1046new_profile_dialog->add_child(new_profile_vb);1047Label *new_profile_label = memnew(Label);1048new_profile_label->set_text(TTR("New profile name:"));1049new_profile_vb->add_child(new_profile_label);1050new_profile_name = memnew(LineEdit);1051new_profile_vb->add_child(new_profile_name);1052new_profile_name->set_custom_minimum_size(Size2(300 * EDSCALE, 1));1053new_profile_name->set_accessibility_name(TTRC("New profile name:"));1054add_child(new_profile_dialog);1055new_profile_dialog->connect(SceneStringName(confirmed), callable_mp(this, &EditorFeatureProfileManager::_create_new_profile));1056new_profile_dialog->register_text_enter(new_profile_name);1057new_profile_dialog->set_ok_button_text(TTR("Create"));10581059erase_profile_dialog = memnew(ConfirmationDialog);1060add_child(erase_profile_dialog);1061erase_profile_dialog->set_title(TTR("Remove Profile"));1062erase_profile_dialog->connect(SceneStringName(confirmed), callable_mp(this, &EditorFeatureProfileManager::_erase_selected_profile));10631064import_profiles = memnew(EditorFileDialog);1065add_child(import_profiles);1066import_profiles->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES);1067import_profiles->add_filter("*.profile", TTR("Godot Feature Profile"));1068import_profiles->connect("files_selected", callable_mp(this, &EditorFeatureProfileManager::_import_profiles));1069import_profiles->set_title(TTR("Import Profile(s)"));1070import_profiles->set_access(EditorFileDialog::ACCESS_FILESYSTEM);10711072export_profile = memnew(EditorFileDialog);1073add_child(export_profile);1074export_profile->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);1075export_profile->add_filter("*.profile", TTR("Godot Feature Profile"));1076export_profile->connect("file_selected", callable_mp(this, &EditorFeatureProfileManager::_export_profile));1077export_profile->set_title(TTR("Export Profile"));1078export_profile->set_access(EditorFileDialog::ACCESS_FILESYSTEM);10791080set_title(TTR("Manage Editor Feature Profiles"));1081set_flag(FLAG_MAXIMIZE_DISABLED, false);1082EDITOR_DEF("_default_feature_profile", "");10831084update_timer = memnew(Timer);1085update_timer->set_wait_time(1); //wait a second before updating editor1086add_child(update_timer);1087update_timer->connect("timeout", callable_mp(this, &EditorFeatureProfileManager::_emit_current_profile_changed));1088update_timer->set_one_shot(true);10891090singleton = this;1091}109210931094