Path: blob/master/editor/settings/editor_feature_profile.cpp
9905 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"),49TTRC("Node Dock"),50TTRC("FileSystem Dock"),51TTRC("Import Dock"),52TTRC("History Dock"),53TTRC("Game View"),54};5556const char *EditorFeatureProfile::feature_descriptions[FEATURE_MAX] = {57TTRC("Allows to view and edit 3D scenes."),58TTRC("Allows to edit scripts using the integrated script editor."),59TTRC("Provides built-in access to the Asset Library."),60TTRC("Allows editing the node hierarchy in the Scene dock."),61TTRC("Allows to work with signals and groups of the node selected in the Scene dock."),62TTRC("Allows to browse the local file system via a dedicated dock."),63TTRC("Allows to configure import settings for individual assets. Requires the FileSystem dock to function."),64TTRC("Provides an overview of the editor's and each scene's undo history."),65TTRC("Provides tools for selecting and debugging nodes at runtime."),66};6768const char *EditorFeatureProfile::feature_identifiers[FEATURE_MAX] = {69"3d",70"script",71"asset_lib",72"scene_tree",73"node_dock",74"filesystem_dock",75"import_dock",76"history_dock",77"game",78};7980void EditorFeatureProfile::set_disable_class(const StringName &p_class, bool p_disabled) {81if (p_disabled) {82disabled_classes.insert(p_class);83} else {84disabled_classes.erase(p_class);85}86}8788bool EditorFeatureProfile::is_class_disabled(const StringName &p_class) const {89if (p_class == StringName()) {90return false;91}92return disabled_classes.has(p_class) || is_class_disabled(ClassDB::get_parent_class_nocheck(p_class));93}9495void EditorFeatureProfile::set_disable_class_editor(const StringName &p_class, bool p_disabled) {96if (p_disabled) {97disabled_editors.insert(p_class);98} else {99disabled_editors.erase(p_class);100}101}102103bool EditorFeatureProfile::is_class_editor_disabled(const StringName &p_class) const {104if (p_class == StringName()) {105return false;106}107return disabled_editors.has(p_class) || is_class_editor_disabled(ClassDB::get_parent_class_nocheck(p_class));108}109110void EditorFeatureProfile::set_disable_class_property(const StringName &p_class, const StringName &p_property, bool p_disabled) {111if (p_disabled) {112if (!disabled_properties.has(p_class)) {113disabled_properties[p_class] = HashSet<StringName>();114}115116disabled_properties[p_class].insert(p_property);117} else {118ERR_FAIL_COND(!disabled_properties.has(p_class));119disabled_properties[p_class].erase(p_property);120if (disabled_properties[p_class].is_empty()) {121disabled_properties.erase(p_class);122}123}124}125126bool EditorFeatureProfile::is_class_property_disabled(const StringName &p_class, const StringName &p_property) const {127if (!disabled_properties.has(p_class)) {128return false;129}130131if (!disabled_properties[p_class].has(p_property)) {132return false;133}134135return true;136}137138bool EditorFeatureProfile::has_class_properties_disabled(const StringName &p_class) const {139return disabled_properties.has(p_class);140}141142void EditorFeatureProfile::set_item_collapsed(const StringName &p_class, bool p_collapsed) {143if (p_collapsed) {144collapsed_classes.insert(p_class);145} else {146collapsed_classes.erase(p_class);147}148}149150bool EditorFeatureProfile::is_item_collapsed(const StringName &p_class) const {151return collapsed_classes.has(p_class);152}153154void EditorFeatureProfile::set_disable_feature(Feature p_feature, bool p_disable) {155ERR_FAIL_INDEX(p_feature, FEATURE_MAX);156features_disabled[p_feature] = p_disable;157}158159bool EditorFeatureProfile::is_feature_disabled(Feature p_feature) const {160ERR_FAIL_INDEX_V(p_feature, FEATURE_MAX, false);161return features_disabled[p_feature];162}163164String EditorFeatureProfile::get_feature_name(Feature p_feature) {165ERR_FAIL_INDEX_V(p_feature, FEATURE_MAX, String());166return feature_names[p_feature];167}168169String EditorFeatureProfile::get_feature_description(Feature p_feature) {170ERR_FAIL_INDEX_V(p_feature, FEATURE_MAX, String());171return feature_descriptions[p_feature];172}173174Error EditorFeatureProfile::save_to_file(const String &p_path) {175Dictionary data;176data["type"] = "feature_profile";177Array dis_classes;178for (const StringName &E : disabled_classes) {179dis_classes.push_back(String(E));180}181dis_classes.sort();182data["disabled_classes"] = dis_classes;183184Array dis_editors;185for (const StringName &E : disabled_editors) {186dis_editors.push_back(String(E));187}188dis_editors.sort();189data["disabled_editors"] = dis_editors;190191Array dis_props;192193for (KeyValue<StringName, HashSet<StringName>> &E : disabled_properties) {194for (const StringName &F : E.value) {195dis_props.push_back(String(E.key) + ":" + String(F));196}197}198199data["disabled_properties"] = dis_props;200201Array dis_features;202for (int i = 0; i < FEATURE_MAX; i++) {203if (features_disabled[i]) {204dis_features.push_back(feature_identifiers[i]);205}206}207208data["disabled_features"] = dis_features;209210Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE);211ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_CREATE, "Cannot create file '" + p_path + "'.");212213JSON json;214String text = json.stringify(data, "\t");215f->store_string(text);216return OK;217}218219Error EditorFeatureProfile::load_from_file(const String &p_path) {220Error err;221String text = FileAccess::get_file_as_string(p_path, &err);222if (err != OK) {223return err;224}225226JSON json;227err = json.parse(text);228if (err != OK) {229ERR_PRINT("Error parsing '" + p_path + "' on line " + itos(json.get_error_line()) + ": " + json.get_error_message());230return ERR_PARSE_ERROR;231}232233Dictionary data = json.get_data();234235if (!data.has("type") || String(data["type"]) != "feature_profile") {236ERR_PRINT("Error parsing '" + p_path + "', it's not a feature profile.");237return ERR_PARSE_ERROR;238}239240disabled_classes.clear();241242if (data.has("disabled_classes")) {243Array disabled_classes_arr = data["disabled_classes"];244for (int i = 0; i < disabled_classes_arr.size(); i++) {245disabled_classes.insert(disabled_classes_arr[i]);246}247}248249disabled_editors.clear();250251if (data.has("disabled_editors")) {252Array disabled_editors_arr = data["disabled_editors"];253for (int i = 0; i < disabled_editors_arr.size(); i++) {254disabled_editors.insert(disabled_editors_arr[i]);255}256}257258disabled_properties.clear();259260if (data.has("disabled_properties")) {261Array disabled_properties_arr = data["disabled_properties"];262for (int i = 0; i < disabled_properties_arr.size(); i++) {263String s = disabled_properties_arr[i];264set_disable_class_property(s.get_slicec(':', 0), s.get_slicec(':', 1), true);265}266}267268if (data.has("disabled_features")) {269Array disabled_features_arr = data["disabled_features"];270for (int i = 0; i < FEATURE_MAX; i++) {271bool found = false;272String f = feature_identifiers[i];273for (int j = 0; j < disabled_features_arr.size(); j++) {274String fd = disabled_features_arr[j];275if (fd == f) {276found = true;277break;278}279}280281features_disabled[i] = found;282}283}284285return OK;286}287288void EditorFeatureProfile::_bind_methods() {289ClassDB::bind_method(D_METHOD("set_disable_class", "class_name", "disable"), &EditorFeatureProfile::set_disable_class);290ClassDB::bind_method(D_METHOD("is_class_disabled", "class_name"), &EditorFeatureProfile::is_class_disabled);291292ClassDB::bind_method(D_METHOD("set_disable_class_editor", "class_name", "disable"), &EditorFeatureProfile::set_disable_class_editor);293ClassDB::bind_method(D_METHOD("is_class_editor_disabled", "class_name"), &EditorFeatureProfile::is_class_editor_disabled);294295ClassDB::bind_method(D_METHOD("set_disable_class_property", "class_name", "property", "disable"), &EditorFeatureProfile::set_disable_class_property);296ClassDB::bind_method(D_METHOD("is_class_property_disabled", "class_name", "property"), &EditorFeatureProfile::is_class_property_disabled);297298ClassDB::bind_method(D_METHOD("set_disable_feature", "feature", "disable"), &EditorFeatureProfile::set_disable_feature);299ClassDB::bind_method(D_METHOD("is_feature_disabled", "feature"), &EditorFeatureProfile::is_feature_disabled);300301ClassDB::bind_method(D_METHOD("get_feature_name", "feature"), &EditorFeatureProfile::_get_feature_name);302303ClassDB::bind_method(D_METHOD("save_to_file", "path"), &EditorFeatureProfile::save_to_file);304ClassDB::bind_method(D_METHOD("load_from_file", "path"), &EditorFeatureProfile::load_from_file);305306BIND_ENUM_CONSTANT(FEATURE_3D);307BIND_ENUM_CONSTANT(FEATURE_SCRIPT);308BIND_ENUM_CONSTANT(FEATURE_ASSET_LIB);309BIND_ENUM_CONSTANT(FEATURE_SCENE_TREE);310BIND_ENUM_CONSTANT(FEATURE_NODE_DOCK);311BIND_ENUM_CONSTANT(FEATURE_FILESYSTEM_DOCK);312BIND_ENUM_CONSTANT(FEATURE_IMPORT_DOCK);313BIND_ENUM_CONSTANT(FEATURE_HISTORY_DOCK);314BIND_ENUM_CONSTANT(FEATURE_GAME);315BIND_ENUM_CONSTANT(FEATURE_MAX);316}317318EditorFeatureProfile::EditorFeatureProfile() {319for (int i = 0; i < FEATURE_MAX; i++) {320features_disabled[i] = false;321}322}323324//////////////////////////325326void EditorFeatureProfileManager::_notification(int p_what) {327switch (p_what) {328case NOTIFICATION_READY: {329current_profile = EDITOR_GET("_default_feature_profile");330if (!current_profile.is_empty()) {331current.instantiate();332Error err = current->load_from_file(EditorPaths::get_singleton()->get_feature_profiles_dir().path_join(current_profile + ".profile"));333if (err != OK) {334ERR_PRINT("Error loading default feature profile: " + current_profile);335current_profile = String();336current.unref();337}338}339_update_profile_list(current_profile);340} break;341342case NOTIFICATION_THEME_CHANGED: {343// Make sure that the icons are correctly adjusted if the theme's lightness was switched.344_update_selected_profile();345} break;346}347}348349String EditorFeatureProfileManager::_get_selected_profile() {350int idx = profile_list->get_selected();351if (idx < 0) {352return String();353}354355return profile_list->get_item_metadata(idx);356}357358void EditorFeatureProfileManager::_update_profile_list(const String &p_select_profile) {359String selected_profile;360if (p_select_profile.is_empty()) { //default, keep361if (profile_list->get_selected() >= 0) {362selected_profile = profile_list->get_item_metadata(profile_list->get_selected());363if (!FileAccess::exists(EditorPaths::get_singleton()->get_feature_profiles_dir().path_join(selected_profile + ".profile"))) {364selected_profile = String(); //does not exist365}366}367} else {368selected_profile = p_select_profile;369}370371Vector<String> profiles;372Ref<DirAccess> d = DirAccess::open(EditorPaths::get_singleton()->get_feature_profiles_dir());373ERR_FAIL_COND_MSG(d.is_null(), "Cannot open directory '" + EditorPaths::get_singleton()->get_feature_profiles_dir() + "'.");374375d->list_dir_begin();376while (true) {377String f = d->get_next();378if (f.is_empty()) {379break;380}381382if (!d->current_is_dir()) {383int last_pos = f.rfind(".profile");384if (last_pos != -1) {385profiles.push_back(f.substr(0, last_pos));386}387}388}389390profiles.sort();391392profile_list->clear();393394for (int i = 0; i < profiles.size(); i++) {395String name = profiles[i];396397if (i == 0 && selected_profile.is_empty()) {398selected_profile = name;399}400401if (name == current_profile) {402name += " " + TTR("(current)");403}404profile_list->add_item(name);405int index = profile_list->get_item_count() - 1;406profile_list->set_item_metadata(index, profiles[i]);407if (profiles[i] == selected_profile) {408profile_list->select(index);409}410}411412class_list_vbc->set_visible(!selected_profile.is_empty());413property_list_vbc->set_visible(!selected_profile.is_empty());414no_profile_selected_help->set_visible(selected_profile.is_empty());415profile_actions[PROFILE_CLEAR]->set_disabled(current_profile.is_empty());416profile_actions[PROFILE_ERASE]->set_disabled(selected_profile.is_empty());417profile_actions[PROFILE_EXPORT]->set_disabled(selected_profile.is_empty());418profile_actions[PROFILE_SET]->set_disabled(selected_profile.is_empty());419420current_profile_name->set_text(!current_profile.is_empty() ? current_profile : TTR("(none)"));421422_update_selected_profile();423}424425void EditorFeatureProfileManager::_profile_action(int p_action) {426switch (p_action) {427case PROFILE_CLEAR: {428set_current_profile("", false);429} break;430case PROFILE_SET: {431String selected = _get_selected_profile();432ERR_FAIL_COND(selected.is_empty());433if (selected == current_profile) {434return; // Nothing to do here.435}436set_current_profile(selected, false);437} break;438case PROFILE_IMPORT: {439import_profiles->popup_file_dialog();440} break;441case PROFILE_EXPORT: {442export_profile->popup_file_dialog();443export_profile->set_current_file(_get_selected_profile() + ".profile");444} break;445case PROFILE_NEW: {446new_profile_dialog->popup_centered(Size2(240, 60) * EDSCALE);447new_profile_name->clear();448new_profile_name->grab_focus();449} break;450case PROFILE_ERASE: {451String selected = _get_selected_profile();452ERR_FAIL_COND(selected.is_empty());453454erase_profile_dialog->set_text(vformat(TTR("Remove currently selected profile, '%s'? Cannot be undone."), selected));455erase_profile_dialog->popup_centered(Size2(240, 60) * EDSCALE);456} break;457}458}459460void EditorFeatureProfileManager::_erase_selected_profile() {461String selected = _get_selected_profile();462ERR_FAIL_COND(selected.is_empty());463Ref<DirAccess> da = DirAccess::open(EditorPaths::get_singleton()->get_feature_profiles_dir());464ERR_FAIL_COND_MSG(da.is_null(), "Cannot open directory '" + EditorPaths::get_singleton()->get_feature_profiles_dir() + "'.");465466da->remove(selected + ".profile");467if (selected == current_profile) {468_profile_action(PROFILE_CLEAR);469} else {470_update_profile_list();471}472}473474void EditorFeatureProfileManager::_create_new_profile() {475String name = new_profile_name->get_text().strip_edges();476if (!name.is_valid_filename() || name.contains_char('.')) {477EditorNode::get_singleton()->show_warning(TTR("Profile must be a valid filename and must not contain '.'"));478return;479}480String file = EditorPaths::get_singleton()->get_feature_profiles_dir().path_join(name + ".profile");481if (FileAccess::exists(file)) {482EditorNode::get_singleton()->show_warning(TTR("Profile with this name already exists."));483return;484}485486Ref<EditorFeatureProfile> new_profile;487new_profile.instantiate();488new_profile->save_to_file(file);489490_update_profile_list(name);491// The newly created profile is the first one, make it the current profile automatically.492if (profile_list->get_item_count() == 1) {493_profile_action(PROFILE_SET);494}495}496497void EditorFeatureProfileManager::_profile_selected(int p_what) {498_update_selected_profile();499}500501void EditorFeatureProfileManager::_hide_requested() {502_cancel_pressed(); // From AcceptDialog.503}504505void EditorFeatureProfileManager::_fill_classes_from(TreeItem *p_parent, const String &p_class, const String &p_selected, int p_class_insert_index) {506TreeItem *class_item = class_list->create_item(p_parent, p_class_insert_index);507class_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);508class_item->set_icon(0, EditorNode::get_singleton()->get_class_icon(p_class));509String text = p_class;510511bool disabled = edited->is_class_disabled(p_class);512bool disabled_editor = edited->is_class_editor_disabled(p_class);513bool disabled_properties = edited->has_class_properties_disabled(p_class);514if (disabled) {515class_item->set_custom_color(0, class_list->get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor)));516} else if (disabled_editor && disabled_properties) {517text += " " + TTR("(Editor Disabled, Properties Disabled)");518} else if (disabled_properties) {519text += " " + TTR("(Properties Disabled)");520} else if (disabled_editor) {521text += " " + TTR("(Editor Disabled)");522}523class_item->set_text(0, text);524class_item->set_editable(0, true);525class_item->set_selectable(0, true);526class_item->set_metadata(0, p_class);527528bool collapsed = edited->is_item_collapsed(p_class);529class_item->set_collapsed(collapsed);530531if (p_class == p_selected) {532class_item->select(0);533}534if (disabled) {535// Class disabled, do nothing else (do not show further).536return;537}538539class_item->set_checked(0, true); // If it's not disabled, it's checked.540541List<StringName> child_classes;542ClassDB::get_direct_inheriters_from_class(p_class, &child_classes);543child_classes.sort_custom<StringName::AlphCompare>();544545for (const StringName &name : child_classes) {546if (String(name).begins_with("Editor") || ClassDB::get_api_type(name) != ClassDB::API_CORE) {547continue;548}549_fill_classes_from(class_item, name, p_selected);550}551}552553void EditorFeatureProfileManager::_class_list_item_selected() {554if (updating_features) {555return;556}557558property_list->clear();559560TreeItem *item = class_list->get_selected();561if (!item) {562return;563}564565Variant md = item->get_metadata(0);566if (md.is_string()) {567description_bit->parse_symbol("class|" + md.operator String() + "|");568} else if (md.get_type() == Variant::INT) {569String feature_description = EditorFeatureProfile::get_feature_description(EditorFeatureProfile::Feature((int)md));570description_bit->set_custom_text(TTR(item->get_text(0)), String(), TTRGET(feature_description));571return;572} else {573return;574}575576String class_name = md;577if (edited->is_class_disabled(class_name)) {578return;579}580581updating_features = true;582TreeItem *root = property_list->create_item();583TreeItem *options = property_list->create_item(root);584options->set_text(0, TTR("Class Options:"));585586{587TreeItem *option = property_list->create_item(options);588option->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);589option->set_editable(0, true);590option->set_selectable(0, true);591option->set_checked(0, !edited->is_class_editor_disabled(class_name));592option->set_text(0, TTR("Enable Contextual Editor"));593option->set_metadata(0, CLASS_OPTION_DISABLE_EDITOR);594}595596List<PropertyInfo> props;597ClassDB::get_property_list(class_name, &props, true);598599bool has_editor_props = false;600for (const PropertyInfo &E : props) {601if (E.usage & PROPERTY_USAGE_EDITOR) {602has_editor_props = true;603break;604}605}606607if (has_editor_props) {608TreeItem *properties = property_list->create_item(root);609properties->set_text(0, TTR("Class Properties:"));610611const EditorPropertyNameProcessor::Style text_style = EditorPropertyNameProcessor::get_settings_style();612const EditorPropertyNameProcessor::Style tooltip_style = EditorPropertyNameProcessor::get_tooltip_style(text_style);613614for (const PropertyInfo &E : props) {615String name = E.name;616if (!(E.usage & PROPERTY_USAGE_EDITOR)) {617continue;618}619const String text = EditorPropertyNameProcessor::get_singleton()->process_name(name, text_style, name, class_name);620const String tooltip = EditorPropertyNameProcessor::get_singleton()->process_name(name, tooltip_style, name, class_name);621622TreeItem *property = property_list->create_item(properties);623property->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);624property->set_editable(0, true);625property->set_selectable(0, true);626property->set_checked(0, !edited->is_class_property_disabled(class_name, name));627property->set_text(0, text);628property->set_tooltip_text(0, tooltip);629property->set_metadata(0, name);630String icon_type = Variant::get_type_name(E.type);631property->set_icon(0, EditorNode::get_singleton()->get_class_icon(icon_type));632}633}634635updating_features = false;636}637638void EditorFeatureProfileManager::_class_list_item_edited() {639if (updating_features) {640return;641}642643TreeItem *item = class_list->get_edited();644if (!item) {645return;646}647648bool checked = item->is_checked(0);649650Variant md = item->get_metadata(0);651if (md.is_string()) {652String class_selected = md;653edited->set_disable_class(class_selected, !checked);654_save_and_update();655_update_profile_tree_from(item);656} else if (md.get_type() == Variant::INT) {657int feature_selected = md;658edited->set_disable_feature(EditorFeatureProfile::Feature(feature_selected), !checked);659_save_and_update();660}661}662663void EditorFeatureProfileManager::_class_list_item_collapsed(Object *p_item) {664if (updating_features) {665return;666}667668TreeItem *item = Object::cast_to<TreeItem>(p_item);669if (!item) {670return;671}672673Variant md = item->get_metadata(0);674if (!md.is_string()) {675return;676}677678String class_name = md;679bool collapsed = item->is_collapsed();680edited->set_item_collapsed(class_name, collapsed);681}682683void EditorFeatureProfileManager::_property_item_edited() {684if (updating_features) {685return;686}687688TreeItem *class_item = class_list->get_selected();689if (!class_item) {690return;691}692693Variant md = class_item->get_metadata(0);694if (!md.is_string()) {695return;696}697698String class_name = md;699700TreeItem *item = property_list->get_edited();701if (!item) {702return;703}704bool checked = item->is_checked(0);705706md = item->get_metadata(0);707if (md.is_string()) {708String property_selected = md;709edited->set_disable_class_property(class_name, property_selected, !checked);710_save_and_update();711_update_profile_tree_from(class_list->get_selected());712} else if (md.get_type() == Variant::INT) {713int feature_selected = md;714switch (feature_selected) {715case CLASS_OPTION_DISABLE_EDITOR: {716edited->set_disable_class_editor(class_name, !checked);717_save_and_update();718_update_profile_tree_from(class_list->get_selected());719} break;720}721}722}723724void EditorFeatureProfileManager::_update_profile_tree_from(TreeItem *p_edited) {725String edited_class = p_edited->get_metadata(0);726727TreeItem *edited_parent = p_edited->get_parent();728int class_insert_index = p_edited->get_index();729p_edited->get_parent()->remove_child(p_edited);730731_fill_classes_from(edited_parent, edited_class, edited_class, class_insert_index);732}733734void EditorFeatureProfileManager::_update_selected_profile() {735String class_selected;736int feature_selected = -1;737738if (class_list->get_selected()) {739Variant md = class_list->get_selected()->get_metadata(0);740if (md.is_string()) {741class_selected = md;742} else if (md.get_type() == Variant::INT) {743feature_selected = md;744}745}746747class_list->clear();748749String profile = _get_selected_profile();750profile_actions[PROFILE_SET]->set_disabled(profile == current_profile);751752if (profile.is_empty()) { //nothing selected, nothing edited753property_list->clear();754edited.unref();755return;756}757758if (profile == current_profile) {759edited = current; //reuse current profile (which is what editor uses)760ERR_FAIL_COND(current.is_null()); //nothing selected, current should never be null761} else {762//reload edited, if different from current763edited.instantiate();764Error err = edited->load_from_file(EditorPaths::get_singleton()->get_feature_profiles_dir().path_join(profile + ".profile"));765ERR_FAIL_COND_MSG(err != OK, "Error when loading editor feature profile from file '" + EditorPaths::get_singleton()->get_feature_profiles_dir().path_join(profile + ".profile") + "'.");766}767768updating_features = true;769770TreeItem *root = class_list->create_item();771772TreeItem *features = class_list->create_item(root);773TreeItem *last_feature = nullptr;774features->set_text(0, TTR("Main Features:"));775for (int i = 0; i < EditorFeatureProfile::FEATURE_MAX; i++) {776TreeItem *feature;777if (i == EditorFeatureProfile::FEATURE_IMPORT_DOCK) {778feature = class_list->create_item(last_feature);779} else {780feature = class_list->create_item(features);781last_feature = feature;782}783feature->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);784feature->set_text(0, TTRGET(EditorFeatureProfile::get_feature_name(EditorFeatureProfile::Feature(i))));785feature->set_selectable(0, true);786feature->set_editable(0, true);787feature->set_metadata(0, i);788if (!edited->is_feature_disabled(EditorFeatureProfile::Feature(i))) {789feature->set_checked(0, true);790}791792if (i == feature_selected) {793feature->select(0);794}795}796797TreeItem *classes = class_list->create_item(root);798classes->set_text(0, TTR("Nodes and Classes:"));799800_fill_classes_from(classes, "Node", class_selected);801_fill_classes_from(classes, "Resource", class_selected);802803updating_features = false;804805_class_list_item_selected();806}807808void EditorFeatureProfileManager::_import_profiles(const Vector<String> &p_paths) {809//test it first810for (int i = 0; i < p_paths.size(); i++) {811Ref<EditorFeatureProfile> profile;812profile.instantiate();813Error err = profile->load_from_file(p_paths[i]);814String basefile = p_paths[i].get_file();815if (err != OK) {816EditorNode::get_singleton()->show_warning(vformat(TTR("File '%s' format is invalid, import aborted."), basefile));817return;818}819820String dst_file = EditorPaths::get_singleton()->get_feature_profiles_dir().path_join(basefile);821822if (FileAccess::exists(dst_file)) {823EditorNode::get_singleton()->show_warning(vformat(TTR("Profile '%s' already exists. Remove it first before importing, import aborted."), basefile.get_basename()));824return;825}826}827828//do it second829for (int i = 0; i < p_paths.size(); i++) {830Ref<EditorFeatureProfile> profile;831profile.instantiate();832Error err = profile->load_from_file(p_paths[i]);833ERR_CONTINUE(err != OK);834String basefile = p_paths[i].get_file();835String dst_file = EditorPaths::get_singleton()->get_feature_profiles_dir().path_join(basefile);836profile->save_to_file(dst_file);837}838839_update_profile_list();840// The newly imported profile is the first one, make it the current profile automatically.841if (profile_list->get_item_count() == 1) {842_profile_action(PROFILE_SET);843}844}845846void EditorFeatureProfileManager::_export_profile(const String &p_path) {847ERR_FAIL_COND(edited.is_null());848Error err = edited->save_to_file(p_path);849if (err != OK) {850EditorNode::get_singleton()->show_warning(vformat(TTR("Error saving profile to path: '%s'."), p_path));851}852}853854void EditorFeatureProfileManager::_save_and_update() {855String edited_path = _get_selected_profile();856ERR_FAIL_COND(edited_path.is_empty());857ERR_FAIL_COND(edited.is_null());858859edited->save_to_file(EditorPaths::get_singleton()->get_feature_profiles_dir().path_join(edited_path + ".profile"));860861if (edited == current) {862update_timer->start();863}864}865866void EditorFeatureProfileManager::_emit_current_profile_changed() {867emit_signal(SNAME("current_feature_profile_changed"));868}869870void EditorFeatureProfileManager::notify_changed() {871_emit_current_profile_changed();872}873874Ref<EditorFeatureProfile> EditorFeatureProfileManager::get_current_profile() {875return current;876}877878String EditorFeatureProfileManager::get_current_profile_name() const {879return current_profile;880}881882void EditorFeatureProfileManager::set_current_profile(const String &p_profile_name, bool p_validate_profile) {883if (p_validate_profile && !p_profile_name.is_empty()) {884// Profile may not exist.885Ref<DirAccess> da = DirAccess::open(EditorPaths::get_singleton()->get_feature_profiles_dir());886ERR_FAIL_COND_MSG(da.is_null(), "Cannot open directory '" + EditorPaths::get_singleton()->get_feature_profiles_dir() + "'.");887ERR_FAIL_COND_MSG(!da->file_exists(p_profile_name + ".profile"), "Feature profile '" + p_profile_name + "' does not exist.");888889// Change profile selection to emulate the UI interaction. Otherwise, the wrong profile would get activated.890// FIXME: Ideally, _update_selected_profile() should not rely on the user interface state to function properly.891for (int i = 0; i < profile_list->get_item_count(); i++) {892if (profile_list->get_item_metadata(i) == p_profile_name) {893profile_list->select(i);894break;895}896}897_update_selected_profile();898}899900// Store in editor settings.901EditorSettings::get_singleton()->set("_default_feature_profile", p_profile_name);902EditorSettings::get_singleton()->save();903904current_profile = p_profile_name;905if (p_profile_name.is_empty()) {906current.unref();907} else {908current = edited;909}910_update_profile_list();911_emit_current_profile_changed();912}913914EditorFeatureProfileManager *EditorFeatureProfileManager::singleton = nullptr;915916void EditorFeatureProfileManager::_bind_methods() {917ADD_SIGNAL(MethodInfo("current_feature_profile_changed"));918}919920EditorFeatureProfileManager::EditorFeatureProfileManager() {921VBoxContainer *main_vbc = memnew(VBoxContainer);922add_child(main_vbc);923924HBoxContainer *name_hbc = memnew(HBoxContainer);925current_profile_name = memnew(LineEdit);926name_hbc->add_child(current_profile_name);927current_profile_name->set_accessibility_name(TTRC("Current Profile:"));928current_profile_name->set_text(TTR("(none)"));929current_profile_name->set_editable(false);930current_profile_name->set_h_size_flags(Control::SIZE_EXPAND_FILL);931profile_actions[PROFILE_CLEAR] = memnew(Button(TTR("Reset to Default")));932name_hbc->add_child(profile_actions[PROFILE_CLEAR]);933profile_actions[PROFILE_CLEAR]->set_disabled(true);934profile_actions[PROFILE_CLEAR]->connect(SceneStringName(pressed), callable_mp(this, &EditorFeatureProfileManager::_profile_action).bind(PROFILE_CLEAR));935936main_vbc->add_margin_child(TTR("Current Profile:"), name_hbc);937938main_vbc->add_child(memnew(HSeparator));939940HBoxContainer *profiles_hbc = memnew(HBoxContainer);941profile_list = memnew(OptionButton);942profile_list->set_accessibility_name(TTRC("Available Profiles:"));943profile_list->set_h_size_flags(Control::SIZE_EXPAND_FILL);944profile_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);945profiles_hbc->add_child(profile_list);946profile_list->connect(SceneStringName(item_selected), callable_mp(this, &EditorFeatureProfileManager::_profile_selected));947948profile_actions[PROFILE_NEW] = memnew(Button(TTR("Create Profile")));949profiles_hbc->add_child(profile_actions[PROFILE_NEW]);950profile_actions[PROFILE_NEW]->connect(SceneStringName(pressed), callable_mp(this, &EditorFeatureProfileManager::_profile_action).bind(PROFILE_NEW));951952profile_actions[PROFILE_ERASE] = memnew(Button(TTR("Remove Profile")));953profiles_hbc->add_child(profile_actions[PROFILE_ERASE]);954profile_actions[PROFILE_ERASE]->set_disabled(true);955profile_actions[PROFILE_ERASE]->connect(SceneStringName(pressed), callable_mp(this, &EditorFeatureProfileManager::_profile_action).bind(PROFILE_ERASE));956957main_vbc->add_margin_child(TTR("Available Profiles:"), profiles_hbc);958959HBoxContainer *current_profile_hbc = memnew(HBoxContainer);960961profile_actions[PROFILE_SET] = memnew(Button(TTR("Make Current")));962current_profile_hbc->add_child(profile_actions[PROFILE_SET]);963profile_actions[PROFILE_SET]->set_disabled(true);964profile_actions[PROFILE_SET]->connect(SceneStringName(pressed), callable_mp(this, &EditorFeatureProfileManager::_profile_action).bind(PROFILE_SET));965966current_profile_hbc->add_child(memnew(VSeparator));967968profile_actions[PROFILE_IMPORT] = memnew(Button(TTR("Import")));969current_profile_hbc->add_child(profile_actions[PROFILE_IMPORT]);970profile_actions[PROFILE_IMPORT]->connect(SceneStringName(pressed), callable_mp(this, &EditorFeatureProfileManager::_profile_action).bind(PROFILE_IMPORT));971972profile_actions[PROFILE_EXPORT] = memnew(Button(TTR("Export")));973current_profile_hbc->add_child(profile_actions[PROFILE_EXPORT]);974profile_actions[PROFILE_EXPORT]->set_disabled(true);975profile_actions[PROFILE_EXPORT]->connect(SceneStringName(pressed), callable_mp(this, &EditorFeatureProfileManager::_profile_action).bind(PROFILE_EXPORT));976977main_vbc->add_child(current_profile_hbc);978979h_split = memnew(HSplitContainer);980h_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);981main_vbc->add_child(h_split);982983class_list_vbc = memnew(VBoxContainer);984h_split->add_child(class_list_vbc);985class_list_vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL);986987class_list = memnew(Tree);988class_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);989class_list_vbc->add_margin_child(TTR("Configure Selected Profile:"), class_list, true);990class_list->set_hide_root(true);991class_list->set_edit_checkbox_cell_only_when_checkbox_is_pressed(true);992class_list->connect("cell_selected", callable_mp(this, &EditorFeatureProfileManager::_class_list_item_selected));993class_list->connect("item_edited", callable_mp(this, &EditorFeatureProfileManager::_class_list_item_edited), CONNECT_DEFERRED);994class_list->connect("item_collapsed", callable_mp(this, &EditorFeatureProfileManager::_class_list_item_collapsed));995class_list->set_theme_type_variation("TreeSecondary");996// It will be displayed once the user creates or chooses a profile.997class_list_vbc->hide();998999property_list_vbc = memnew(VBoxContainer);1000h_split->add_child(property_list_vbc);1001property_list_vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL);10021003description_bit = memnew(EditorHelpBit);1004description_bit->set_content_height_limits(80 * EDSCALE, 80 * EDSCALE);1005description_bit->connect("request_hide", callable_mp(this, &EditorFeatureProfileManager::_hide_requested));1006property_list_vbc->add_margin_child(TTR("Description:"), description_bit, false);10071008property_list = memnew(Tree);1009property_list_vbc->add_margin_child(TTR("Extra Options:"), property_list, true);1010property_list->set_hide_root(true);1011property_list->set_hide_folding(true);1012property_list->set_edit_checkbox_cell_only_when_checkbox_is_pressed(true);1013property_list->connect("item_edited", callable_mp(this, &EditorFeatureProfileManager::_property_item_edited), CONNECT_DEFERRED);1014// It will be displayed once the user creates or chooses a profile.1015property_list_vbc->hide();10161017no_profile_selected_help = memnew(Label(TTR("Create or import a profile to edit available classes and properties.")));1018// Add some spacing above the help label.1019Ref<StyleBoxEmpty> sb = memnew(StyleBoxEmpty);1020sb->set_content_margin(SIDE_TOP, 20 * EDSCALE);1021no_profile_selected_help->add_theme_style_override(CoreStringName(normal), sb);1022no_profile_selected_help->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);1023no_profile_selected_help->set_v_size_flags(Control::SIZE_EXPAND_FILL);1024h_split->add_child(no_profile_selected_help);10251026new_profile_dialog = memnew(ConfirmationDialog);1027new_profile_dialog->set_title(TTR("Create Profile"));1028VBoxContainer *new_profile_vb = memnew(VBoxContainer);1029new_profile_dialog->add_child(new_profile_vb);1030Label *new_profile_label = memnew(Label);1031new_profile_label->set_text(TTR("New profile name:"));1032new_profile_vb->add_child(new_profile_label);1033new_profile_name = memnew(LineEdit);1034new_profile_vb->add_child(new_profile_name);1035new_profile_name->set_custom_minimum_size(Size2(300 * EDSCALE, 1));1036new_profile_name->set_accessibility_name(TTRC("New profile name:"));1037add_child(new_profile_dialog);1038new_profile_dialog->connect(SceneStringName(confirmed), callable_mp(this, &EditorFeatureProfileManager::_create_new_profile));1039new_profile_dialog->register_text_enter(new_profile_name);1040new_profile_dialog->set_ok_button_text(TTR("Create"));10411042erase_profile_dialog = memnew(ConfirmationDialog);1043add_child(erase_profile_dialog);1044erase_profile_dialog->set_title(TTR("Remove Profile"));1045erase_profile_dialog->connect(SceneStringName(confirmed), callable_mp(this, &EditorFeatureProfileManager::_erase_selected_profile));10461047import_profiles = memnew(EditorFileDialog);1048add_child(import_profiles);1049import_profiles->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES);1050import_profiles->add_filter("*.profile", TTR("Godot Feature Profile"));1051import_profiles->connect("files_selected", callable_mp(this, &EditorFeatureProfileManager::_import_profiles));1052import_profiles->set_title(TTR("Import Profile(s)"));1053import_profiles->set_access(EditorFileDialog::ACCESS_FILESYSTEM);10541055export_profile = memnew(EditorFileDialog);1056add_child(export_profile);1057export_profile->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);1058export_profile->add_filter("*.profile", TTR("Godot Feature Profile"));1059export_profile->connect("file_selected", callable_mp(this, &EditorFeatureProfileManager::_export_profile));1060export_profile->set_title(TTR("Export Profile"));1061export_profile->set_access(EditorFileDialog::ACCESS_FILESYSTEM);10621063set_title(TTR("Manage Editor Feature Profiles"));1064EDITOR_DEF("_default_feature_profile", "");10651066update_timer = memnew(Timer);1067update_timer->set_wait_time(1); //wait a second before updating editor1068add_child(update_timer);1069update_timer->connect("timeout", callable_mp(this, &EditorFeatureProfileManager::_emit_current_profile_changed));1070update_timer->set_one_shot(true);10711072singleton = this;1073}107410751076