| 1 | /**************************************************************************/ |
| 2 | /* editor_feature_profile.cpp */ |
| 3 | /**************************************************************************/ |
| 4 | /* This file is part of: */ |
| 5 | /* GODOT ENGINE */ |
| 6 | /* https://godotengine.org */ |
| 7 | /**************************************************************************/ |
| 8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
| 9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
| 10 | /* */ |
| 11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
| 12 | /* a copy of this software and associated documentation files (the */ |
| 13 | /* "Software"), to deal in the Software without restriction, including */ |
| 14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
| 15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
| 16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
| 17 | /* the following conditions: */ |
| 18 | /* */ |
| 19 | /* The above copyright notice and this permission notice shall be */ |
| 20 | /* included in all copies or substantial portions of the Software. */ |
| 21 | /* */ |
| 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
| 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
| 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
| 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
| 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
| 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
| 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
| 29 | /**************************************************************************/ |
| 30 | |
| 31 | #include "editor_feature_profile.h" |
| 32 | |
| 33 | #include "core/io/dir_access.h" |
| 34 | #include "core/io/json.h" |
| 35 | #include "editor/editor_node.h" |
| 36 | #include "editor/editor_paths.h" |
| 37 | #include "editor/editor_property_name_processor.h" |
| 38 | #include "editor/editor_scale.h" |
| 39 | #include "editor/editor_settings.h" |
| 40 | #include "editor/editor_string_names.h" |
| 41 | #include "editor/gui/editor_file_dialog.h" |
| 42 | |
| 43 | const char *EditorFeatureProfile::feature_names[FEATURE_MAX] = { |
| 44 | TTRC("3D Editor" ), |
| 45 | TTRC("Script Editor" ), |
| 46 | TTRC("Asset Library" ), |
| 47 | TTRC("Scene Tree Editing" ), |
| 48 | TTRC("Node Dock" ), |
| 49 | TTRC("FileSystem Dock" ), |
| 50 | TTRC("Import Dock" ), |
| 51 | TTRC("History Dock" ), |
| 52 | }; |
| 53 | |
| 54 | const char *EditorFeatureProfile::feature_descriptions[FEATURE_MAX] = { |
| 55 | TTRC("Allows to view and edit 3D scenes." ), |
| 56 | TTRC("Allows to edit scripts using the integrated script editor." ), |
| 57 | TTRC("Provides built-in access to the Asset Library." ), |
| 58 | TTRC("Allows editing the node hierarchy in the Scene dock." ), |
| 59 | TTRC("Allows to work with signals and groups of the node selected in the Scene dock." ), |
| 60 | TTRC("Allows to browse the local file system via a dedicated dock." ), |
| 61 | TTRC("Allows to configure import settings for individual assets. Requires the FileSystem dock to function." ), |
| 62 | TTRC("Provides an overview of the editor's and each scene's undo history." ), |
| 63 | }; |
| 64 | |
| 65 | const char *EditorFeatureProfile::feature_identifiers[FEATURE_MAX] = { |
| 66 | "3d" , |
| 67 | "script" , |
| 68 | "asset_lib" , |
| 69 | "scene_tree" , |
| 70 | "node_dock" , |
| 71 | "filesystem_dock" , |
| 72 | "import_dock" , |
| 73 | "history_dock" , |
| 74 | }; |
| 75 | |
| 76 | void EditorFeatureProfile::set_disable_class(const StringName &p_class, bool p_disabled) { |
| 77 | if (p_disabled) { |
| 78 | disabled_classes.insert(p_class); |
| 79 | } else { |
| 80 | disabled_classes.erase(p_class); |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | bool EditorFeatureProfile::is_class_disabled(const StringName &p_class) const { |
| 85 | if (p_class == StringName()) { |
| 86 | return false; |
| 87 | } |
| 88 | return disabled_classes.has(p_class) || is_class_disabled(ClassDB::get_parent_class_nocheck(p_class)); |
| 89 | } |
| 90 | |
| 91 | void EditorFeatureProfile::set_disable_class_editor(const StringName &p_class, bool p_disabled) { |
| 92 | if (p_disabled) { |
| 93 | disabled_editors.insert(p_class); |
| 94 | } else { |
| 95 | disabled_editors.erase(p_class); |
| 96 | } |
| 97 | } |
| 98 | |
| 99 | bool EditorFeatureProfile::is_class_editor_disabled(const StringName &p_class) const { |
| 100 | if (p_class == StringName()) { |
| 101 | return false; |
| 102 | } |
| 103 | return disabled_editors.has(p_class) || is_class_editor_disabled(ClassDB::get_parent_class_nocheck(p_class)); |
| 104 | } |
| 105 | |
| 106 | void EditorFeatureProfile::set_disable_class_property(const StringName &p_class, const StringName &p_property, bool p_disabled) { |
| 107 | if (p_disabled) { |
| 108 | if (!disabled_properties.has(p_class)) { |
| 109 | disabled_properties[p_class] = HashSet<StringName>(); |
| 110 | } |
| 111 | |
| 112 | disabled_properties[p_class].insert(p_property); |
| 113 | } else { |
| 114 | ERR_FAIL_COND(!disabled_properties.has(p_class)); |
| 115 | disabled_properties[p_class].erase(p_property); |
| 116 | if (disabled_properties[p_class].is_empty()) { |
| 117 | disabled_properties.erase(p_class); |
| 118 | } |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | bool EditorFeatureProfile::is_class_property_disabled(const StringName &p_class, const StringName &p_property) const { |
| 123 | if (!disabled_properties.has(p_class)) { |
| 124 | return false; |
| 125 | } |
| 126 | |
| 127 | if (!disabled_properties[p_class].has(p_property)) { |
| 128 | return false; |
| 129 | } |
| 130 | |
| 131 | return true; |
| 132 | } |
| 133 | |
| 134 | bool EditorFeatureProfile::has_class_properties_disabled(const StringName &p_class) const { |
| 135 | return disabled_properties.has(p_class); |
| 136 | } |
| 137 | |
| 138 | void EditorFeatureProfile::set_item_collapsed(const StringName &p_class, bool p_collapsed) { |
| 139 | if (p_collapsed) { |
| 140 | collapsed_classes.insert(p_class); |
| 141 | } else { |
| 142 | collapsed_classes.erase(p_class); |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | bool EditorFeatureProfile::is_item_collapsed(const StringName &p_class) const { |
| 147 | return collapsed_classes.has(p_class); |
| 148 | } |
| 149 | |
| 150 | void EditorFeatureProfile::set_disable_feature(Feature p_feature, bool p_disable) { |
| 151 | ERR_FAIL_INDEX(p_feature, FEATURE_MAX); |
| 152 | features_disabled[p_feature] = p_disable; |
| 153 | } |
| 154 | |
| 155 | bool EditorFeatureProfile::is_feature_disabled(Feature p_feature) const { |
| 156 | ERR_FAIL_INDEX_V(p_feature, FEATURE_MAX, false); |
| 157 | return features_disabled[p_feature]; |
| 158 | } |
| 159 | |
| 160 | String EditorFeatureProfile::get_feature_name(Feature p_feature) { |
| 161 | ERR_FAIL_INDEX_V(p_feature, FEATURE_MAX, String()); |
| 162 | return feature_names[p_feature]; |
| 163 | } |
| 164 | |
| 165 | String EditorFeatureProfile::get_feature_description(Feature p_feature) { |
| 166 | ERR_FAIL_INDEX_V(p_feature, FEATURE_MAX, String()); |
| 167 | return feature_descriptions[p_feature]; |
| 168 | } |
| 169 | |
| 170 | Error EditorFeatureProfile::save_to_file(const String &p_path) { |
| 171 | Dictionary data; |
| 172 | data["type" ] = "feature_profile" ; |
| 173 | Array dis_classes; |
| 174 | for (const StringName &E : disabled_classes) { |
| 175 | dis_classes.push_back(String(E)); |
| 176 | } |
| 177 | dis_classes.sort(); |
| 178 | data["disabled_classes" ] = dis_classes; |
| 179 | |
| 180 | Array dis_editors; |
| 181 | for (const StringName &E : disabled_editors) { |
| 182 | dis_editors.push_back(String(E)); |
| 183 | } |
| 184 | dis_editors.sort(); |
| 185 | data["disabled_editors" ] = dis_editors; |
| 186 | |
| 187 | Array dis_props; |
| 188 | |
| 189 | for (KeyValue<StringName, HashSet<StringName>> &E : disabled_properties) { |
| 190 | for (const StringName &F : E.value) { |
| 191 | dis_props.push_back(String(E.key) + ":" + String(F)); |
| 192 | } |
| 193 | } |
| 194 | |
| 195 | data["disabled_properties" ] = dis_props; |
| 196 | |
| 197 | Array dis_features; |
| 198 | for (int i = 0; i < FEATURE_MAX; i++) { |
| 199 | if (features_disabled[i]) { |
| 200 | dis_features.push_back(feature_identifiers[i]); |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | data["disabled_features" ] = dis_features; |
| 205 | |
| 206 | Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE); |
| 207 | ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_CREATE, "Cannot create file '" + p_path + "'." ); |
| 208 | |
| 209 | JSON json; |
| 210 | String text = json.stringify(data, "\t" ); |
| 211 | f->store_string(text); |
| 212 | return OK; |
| 213 | } |
| 214 | |
| 215 | Error EditorFeatureProfile::load_from_file(const String &p_path) { |
| 216 | Error err; |
| 217 | String text = FileAccess::get_file_as_string(p_path, &err); |
| 218 | if (err != OK) { |
| 219 | return err; |
| 220 | } |
| 221 | |
| 222 | JSON json; |
| 223 | err = json.parse(text); |
| 224 | if (err != OK) { |
| 225 | ERR_PRINT("Error parsing '" + p_path + "' on line " + itos(json.get_error_line()) + ": " + json.get_error_message()); |
| 226 | return ERR_PARSE_ERROR; |
| 227 | } |
| 228 | |
| 229 | Dictionary data = json.get_data(); |
| 230 | |
| 231 | if (!data.has("type" ) || String(data["type" ]) != "feature_profile" ) { |
| 232 | ERR_PRINT("Error parsing '" + p_path + "', it's not a feature profile." ); |
| 233 | return ERR_PARSE_ERROR; |
| 234 | } |
| 235 | |
| 236 | disabled_classes.clear(); |
| 237 | |
| 238 | if (data.has("disabled_classes" )) { |
| 239 | Array disabled_classes_arr = data["disabled_classes" ]; |
| 240 | for (int i = 0; i < disabled_classes_arr.size(); i++) { |
| 241 | disabled_classes.insert(disabled_classes_arr[i]); |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | disabled_editors.clear(); |
| 246 | |
| 247 | if (data.has("disabled_editors" )) { |
| 248 | Array disabled_editors_arr = data["disabled_editors" ]; |
| 249 | for (int i = 0; i < disabled_editors_arr.size(); i++) { |
| 250 | disabled_editors.insert(disabled_editors_arr[i]); |
| 251 | } |
| 252 | } |
| 253 | |
| 254 | disabled_properties.clear(); |
| 255 | |
| 256 | if (data.has("disabled_properties" )) { |
| 257 | Array disabled_properties_arr = data["disabled_properties" ]; |
| 258 | for (int i = 0; i < disabled_properties_arr.size(); i++) { |
| 259 | String s = disabled_properties_arr[i]; |
| 260 | set_disable_class_property(s.get_slice(":" , 0), s.get_slice(":" , 1), true); |
| 261 | } |
| 262 | } |
| 263 | |
| 264 | if (data.has("disabled_features" )) { |
| 265 | Array disabled_features_arr = data["disabled_features" ]; |
| 266 | for (int i = 0; i < FEATURE_MAX; i++) { |
| 267 | bool found = false; |
| 268 | String f = feature_identifiers[i]; |
| 269 | for (int j = 0; j < disabled_features_arr.size(); j++) { |
| 270 | String fd = disabled_features_arr[j]; |
| 271 | if (fd == f) { |
| 272 | found = true; |
| 273 | break; |
| 274 | } |
| 275 | } |
| 276 | |
| 277 | features_disabled[i] = found; |
| 278 | } |
| 279 | } |
| 280 | |
| 281 | return OK; |
| 282 | } |
| 283 | |
| 284 | void EditorFeatureProfile::_bind_methods() { |
| 285 | ClassDB::bind_method(D_METHOD("set_disable_class" , "class_name" , "disable" ), &EditorFeatureProfile::set_disable_class); |
| 286 | ClassDB::bind_method(D_METHOD("is_class_disabled" , "class_name" ), &EditorFeatureProfile::is_class_disabled); |
| 287 | |
| 288 | ClassDB::bind_method(D_METHOD("set_disable_class_editor" , "class_name" , "disable" ), &EditorFeatureProfile::set_disable_class_editor); |
| 289 | ClassDB::bind_method(D_METHOD("is_class_editor_disabled" , "class_name" ), &EditorFeatureProfile::is_class_editor_disabled); |
| 290 | |
| 291 | ClassDB::bind_method(D_METHOD("set_disable_class_property" , "class_name" , "property" , "disable" ), &EditorFeatureProfile::set_disable_class_property); |
| 292 | ClassDB::bind_method(D_METHOD("is_class_property_disabled" , "class_name" , "property" ), &EditorFeatureProfile::is_class_property_disabled); |
| 293 | |
| 294 | ClassDB::bind_method(D_METHOD("set_disable_feature" , "feature" , "disable" ), &EditorFeatureProfile::set_disable_feature); |
| 295 | ClassDB::bind_method(D_METHOD("is_feature_disabled" , "feature" ), &EditorFeatureProfile::is_feature_disabled); |
| 296 | |
| 297 | ClassDB::bind_method(D_METHOD("get_feature_name" , "feature" ), &EditorFeatureProfile::_get_feature_name); |
| 298 | |
| 299 | ClassDB::bind_method(D_METHOD("save_to_file" , "path" ), &EditorFeatureProfile::save_to_file); |
| 300 | ClassDB::bind_method(D_METHOD("load_from_file" , "path" ), &EditorFeatureProfile::load_from_file); |
| 301 | |
| 302 | BIND_ENUM_CONSTANT(FEATURE_3D); |
| 303 | BIND_ENUM_CONSTANT(FEATURE_SCRIPT); |
| 304 | BIND_ENUM_CONSTANT(FEATURE_ASSET_LIB); |
| 305 | BIND_ENUM_CONSTANT(FEATURE_SCENE_TREE); |
| 306 | BIND_ENUM_CONSTANT(FEATURE_NODE_DOCK); |
| 307 | BIND_ENUM_CONSTANT(FEATURE_FILESYSTEM_DOCK); |
| 308 | BIND_ENUM_CONSTANT(FEATURE_IMPORT_DOCK); |
| 309 | BIND_ENUM_CONSTANT(FEATURE_HISTORY_DOCK); |
| 310 | BIND_ENUM_CONSTANT(FEATURE_MAX); |
| 311 | } |
| 312 | |
| 313 | EditorFeatureProfile::EditorFeatureProfile() { |
| 314 | for (int i = 0; i < FEATURE_MAX; i++) { |
| 315 | features_disabled[i] = false; |
| 316 | } |
| 317 | } |
| 318 | |
| 319 | ////////////////////////// |
| 320 | |
| 321 | void EditorFeatureProfileManager::_notification(int p_what) { |
| 322 | switch (p_what) { |
| 323 | case NOTIFICATION_READY: { |
| 324 | current_profile = EDITOR_GET("_default_feature_profile" ); |
| 325 | if (!current_profile.is_empty()) { |
| 326 | current.instantiate(); |
| 327 | Error err = current->load_from_file(EditorPaths::get_singleton()->get_feature_profiles_dir().path_join(current_profile + ".profile" )); |
| 328 | if (err != OK) { |
| 329 | ERR_PRINT("Error loading default feature profile: " + current_profile); |
| 330 | current_profile = String(); |
| 331 | current.unref(); |
| 332 | } |
| 333 | } |
| 334 | _update_profile_list(current_profile); |
| 335 | } break; |
| 336 | |
| 337 | case NOTIFICATION_THEME_CHANGED: { |
| 338 | // Make sure that the icons are correctly adjusted if the theme's lightness was switched. |
| 339 | _update_selected_profile(); |
| 340 | } break; |
| 341 | } |
| 342 | } |
| 343 | |
| 344 | String EditorFeatureProfileManager::_get_selected_profile() { |
| 345 | int idx = profile_list->get_selected(); |
| 346 | if (idx < 0) { |
| 347 | return String(); |
| 348 | } |
| 349 | |
| 350 | return profile_list->get_item_metadata(idx); |
| 351 | } |
| 352 | |
| 353 | void EditorFeatureProfileManager::_update_profile_list(const String &p_select_profile) { |
| 354 | String selected_profile; |
| 355 | if (p_select_profile.is_empty()) { //default, keep |
| 356 | if (profile_list->get_selected() >= 0) { |
| 357 | selected_profile = profile_list->get_item_metadata(profile_list->get_selected()); |
| 358 | if (!FileAccess::exists(EditorPaths::get_singleton()->get_feature_profiles_dir().path_join(selected_profile + ".profile" ))) { |
| 359 | selected_profile = String(); //does not exist |
| 360 | } |
| 361 | } |
| 362 | } else { |
| 363 | selected_profile = p_select_profile; |
| 364 | } |
| 365 | |
| 366 | Vector<String> profiles; |
| 367 | Ref<DirAccess> d = DirAccess::open(EditorPaths::get_singleton()->get_feature_profiles_dir()); |
| 368 | ERR_FAIL_COND_MSG(d.is_null(), "Cannot open directory '" + EditorPaths::get_singleton()->get_feature_profiles_dir() + "'." ); |
| 369 | |
| 370 | d->list_dir_begin(); |
| 371 | while (true) { |
| 372 | String f = d->get_next(); |
| 373 | if (f.is_empty()) { |
| 374 | break; |
| 375 | } |
| 376 | |
| 377 | if (!d->current_is_dir()) { |
| 378 | int last_pos = f.rfind(".profile" ); |
| 379 | if (last_pos != -1) { |
| 380 | profiles.push_back(f.substr(0, last_pos)); |
| 381 | } |
| 382 | } |
| 383 | } |
| 384 | |
| 385 | profiles.sort(); |
| 386 | |
| 387 | profile_list->clear(); |
| 388 | |
| 389 | for (int i = 0; i < profiles.size(); i++) { |
| 390 | String name = profiles[i]; |
| 391 | |
| 392 | if (i == 0 && selected_profile.is_empty()) { |
| 393 | selected_profile = name; |
| 394 | } |
| 395 | |
| 396 | if (name == current_profile) { |
| 397 | name += " " + TTR("(current)" ); |
| 398 | } |
| 399 | profile_list->add_item(name); |
| 400 | int index = profile_list->get_item_count() - 1; |
| 401 | profile_list->set_item_metadata(index, profiles[i]); |
| 402 | if (profiles[i] == selected_profile) { |
| 403 | profile_list->select(index); |
| 404 | } |
| 405 | } |
| 406 | |
| 407 | class_list_vbc->set_visible(!selected_profile.is_empty()); |
| 408 | property_list_vbc->set_visible(!selected_profile.is_empty()); |
| 409 | no_profile_selected_help->set_visible(selected_profile.is_empty()); |
| 410 | profile_actions[PROFILE_CLEAR]->set_disabled(current_profile.is_empty()); |
| 411 | profile_actions[PROFILE_ERASE]->set_disabled(selected_profile.is_empty()); |
| 412 | profile_actions[PROFILE_EXPORT]->set_disabled(selected_profile.is_empty()); |
| 413 | profile_actions[PROFILE_SET]->set_disabled(selected_profile.is_empty()); |
| 414 | |
| 415 | current_profile_name->set_text(!current_profile.is_empty() ? current_profile : TTR("(none)" )); |
| 416 | |
| 417 | _update_selected_profile(); |
| 418 | } |
| 419 | |
| 420 | void EditorFeatureProfileManager::_profile_action(int p_action) { |
| 421 | switch (p_action) { |
| 422 | case PROFILE_CLEAR: { |
| 423 | set_current_profile("" , false); |
| 424 | } break; |
| 425 | case PROFILE_SET: { |
| 426 | String selected = _get_selected_profile(); |
| 427 | ERR_FAIL_COND(selected.is_empty()); |
| 428 | if (selected == current_profile) { |
| 429 | return; // Nothing to do here. |
| 430 | } |
| 431 | set_current_profile(selected, false); |
| 432 | } break; |
| 433 | case PROFILE_IMPORT: { |
| 434 | import_profiles->popup_file_dialog(); |
| 435 | } break; |
| 436 | case PROFILE_EXPORT: { |
| 437 | export_profile->popup_file_dialog(); |
| 438 | export_profile->set_current_file(_get_selected_profile() + ".profile" ); |
| 439 | } break; |
| 440 | case PROFILE_NEW: { |
| 441 | new_profile_dialog->popup_centered(Size2(240, 60) * EDSCALE); |
| 442 | new_profile_name->clear(); |
| 443 | new_profile_name->grab_focus(); |
| 444 | } break; |
| 445 | case PROFILE_ERASE: { |
| 446 | String selected = _get_selected_profile(); |
| 447 | ERR_FAIL_COND(selected.is_empty()); |
| 448 | |
| 449 | erase_profile_dialog->set_text(vformat(TTR("Remove currently selected profile, '%s'? Cannot be undone." ), selected)); |
| 450 | erase_profile_dialog->popup_centered(Size2(240, 60) * EDSCALE); |
| 451 | } break; |
| 452 | } |
| 453 | } |
| 454 | |
| 455 | void EditorFeatureProfileManager::_erase_selected_profile() { |
| 456 | String selected = _get_selected_profile(); |
| 457 | ERR_FAIL_COND(selected.is_empty()); |
| 458 | Ref<DirAccess> da = DirAccess::open(EditorPaths::get_singleton()->get_feature_profiles_dir()); |
| 459 | ERR_FAIL_COND_MSG(da.is_null(), "Cannot open directory '" + EditorPaths::get_singleton()->get_feature_profiles_dir() + "'." ); |
| 460 | |
| 461 | da->remove(selected + ".profile" ); |
| 462 | if (selected == current_profile) { |
| 463 | _profile_action(PROFILE_CLEAR); |
| 464 | } else { |
| 465 | _update_profile_list(); |
| 466 | } |
| 467 | } |
| 468 | |
| 469 | void EditorFeatureProfileManager::_create_new_profile() { |
| 470 | String name = new_profile_name->get_text().strip_edges(); |
| 471 | if (!name.is_valid_filename() || name.contains("." )) { |
| 472 | EditorNode::get_singleton()->show_warning(TTR("Profile must be a valid filename and must not contain '.'" )); |
| 473 | return; |
| 474 | } |
| 475 | String file = EditorPaths::get_singleton()->get_feature_profiles_dir().path_join(name + ".profile" ); |
| 476 | if (FileAccess::exists(file)) { |
| 477 | EditorNode::get_singleton()->show_warning(TTR("Profile with this name already exists." )); |
| 478 | return; |
| 479 | } |
| 480 | |
| 481 | Ref<EditorFeatureProfile> new_profile; |
| 482 | new_profile.instantiate(); |
| 483 | new_profile->save_to_file(file); |
| 484 | |
| 485 | _update_profile_list(name); |
| 486 | // The newly created profile is the first one, make it the current profile automatically. |
| 487 | if (profile_list->get_item_count() == 1) { |
| 488 | _profile_action(PROFILE_SET); |
| 489 | } |
| 490 | } |
| 491 | |
| 492 | void EditorFeatureProfileManager::_profile_selected(int p_what) { |
| 493 | _update_selected_profile(); |
| 494 | } |
| 495 | |
| 496 | void EditorFeatureProfileManager::_fill_classes_from(TreeItem *p_parent, const String &p_class, const String &p_selected) { |
| 497 | TreeItem *class_item = class_list->create_item(p_parent); |
| 498 | class_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); |
| 499 | class_item->set_icon(0, EditorNode::get_singleton()->get_class_icon(p_class)); |
| 500 | String text = p_class; |
| 501 | |
| 502 | bool disabled = edited->is_class_disabled(p_class); |
| 503 | bool disabled_editor = edited->is_class_editor_disabled(p_class); |
| 504 | bool disabled_properties = edited->has_class_properties_disabled(p_class); |
| 505 | if (disabled) { |
| 506 | class_item->set_custom_color(0, class_list->get_theme_color(SNAME("disabled_font_color" ), EditorStringName(Editor))); |
| 507 | } else if (disabled_editor && disabled_properties) { |
| 508 | text += " " + TTR("(Editor Disabled, Properties Disabled)" ); |
| 509 | } else if (disabled_properties) { |
| 510 | text += " " + TTR("(Properties Disabled)" ); |
| 511 | } else if (disabled_editor) { |
| 512 | text += " " + TTR("(Editor Disabled)" ); |
| 513 | } |
| 514 | class_item->set_text(0, text); |
| 515 | class_item->set_editable(0, true); |
| 516 | class_item->set_selectable(0, true); |
| 517 | class_item->set_metadata(0, p_class); |
| 518 | |
| 519 | bool collapsed = edited->is_item_collapsed(p_class); |
| 520 | class_item->set_collapsed(collapsed); |
| 521 | |
| 522 | if (p_class == p_selected) { |
| 523 | class_item->select(0); |
| 524 | } |
| 525 | if (disabled) { |
| 526 | // Class disabled, do nothing else (do not show further). |
| 527 | return; |
| 528 | } |
| 529 | |
| 530 | class_item->set_checked(0, true); // If it's not disabled, it's checked. |
| 531 | |
| 532 | List<StringName> child_classes; |
| 533 | ClassDB::get_direct_inheriters_from_class(p_class, &child_classes); |
| 534 | child_classes.sort_custom<StringName::AlphCompare>(); |
| 535 | |
| 536 | for (const StringName &name : child_classes) { |
| 537 | if (String(name).begins_with("Editor" ) || ClassDB::get_api_type(name) != ClassDB::API_CORE) { |
| 538 | continue; |
| 539 | } |
| 540 | _fill_classes_from(class_item, name, p_selected); |
| 541 | } |
| 542 | } |
| 543 | |
| 544 | void EditorFeatureProfileManager::_class_list_item_selected() { |
| 545 | if (updating_features) { |
| 546 | return; |
| 547 | } |
| 548 | |
| 549 | property_list->clear(); |
| 550 | |
| 551 | TreeItem *item = class_list->get_selected(); |
| 552 | if (!item) { |
| 553 | return; |
| 554 | } |
| 555 | |
| 556 | Variant md = item->get_metadata(0); |
| 557 | if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) { |
| 558 | String class_name = md; |
| 559 | String class_description; |
| 560 | |
| 561 | DocTools *dd = EditorHelp::get_doc_data(); |
| 562 | HashMap<String, DocData::ClassDoc>::Iterator E = dd->class_list.find(class_name); |
| 563 | if (E) { |
| 564 | class_description = DTR(E->value.brief_description); |
| 565 | } |
| 566 | |
| 567 | description_bit->set_text(class_description); |
| 568 | } else if (md.get_type() == Variant::INT) { |
| 569 | int feature_id = md; |
| 570 | String feature_description = EditorFeatureProfile::get_feature_description(EditorFeatureProfile::Feature(feature_id)); |
| 571 | |
| 572 | description_bit->set_text(TTRGET(feature_description)); |
| 573 | return; |
| 574 | } else { |
| 575 | return; |
| 576 | } |
| 577 | |
| 578 | String class_name = md; |
| 579 | if (edited->is_class_disabled(class_name)) { |
| 580 | return; |
| 581 | } |
| 582 | |
| 583 | updating_features = true; |
| 584 | TreeItem *root = property_list->create_item(); |
| 585 | TreeItem *options = property_list->create_item(root); |
| 586 | options->set_text(0, TTR("Class Options:" )); |
| 587 | |
| 588 | { |
| 589 | TreeItem *option = property_list->create_item(options); |
| 590 | option->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); |
| 591 | option->set_editable(0, true); |
| 592 | option->set_selectable(0, true); |
| 593 | option->set_checked(0, !edited->is_class_editor_disabled(class_name)); |
| 594 | option->set_text(0, TTR("Enable Contextual Editor" )); |
| 595 | option->set_metadata(0, CLASS_OPTION_DISABLE_EDITOR); |
| 596 | } |
| 597 | |
| 598 | List<PropertyInfo> props; |
| 599 | ClassDB::get_property_list(class_name, &props, true); |
| 600 | |
| 601 | bool has_editor_props = false; |
| 602 | for (const PropertyInfo &E : props) { |
| 603 | if (E.usage & PROPERTY_USAGE_EDITOR) { |
| 604 | has_editor_props = true; |
| 605 | break; |
| 606 | } |
| 607 | } |
| 608 | |
| 609 | if (has_editor_props) { |
| 610 | TreeItem *properties = property_list->create_item(root); |
| 611 | properties->set_text(0, TTR("Class Properties:" )); |
| 612 | |
| 613 | const EditorPropertyNameProcessor::Style text_style = EditorPropertyNameProcessor::get_settings_style(); |
| 614 | const EditorPropertyNameProcessor::Style tooltip_style = EditorPropertyNameProcessor::get_tooltip_style(text_style); |
| 615 | |
| 616 | for (const PropertyInfo &E : props) { |
| 617 | String name = E.name; |
| 618 | if (!(E.usage & PROPERTY_USAGE_EDITOR)) { |
| 619 | continue; |
| 620 | } |
| 621 | const String text = EditorPropertyNameProcessor::get_singleton()->process_name(name, text_style); |
| 622 | const String tooltip = EditorPropertyNameProcessor::get_singleton()->process_name(name, tooltip_style); |
| 623 | |
| 624 | TreeItem *property = property_list->create_item(properties); |
| 625 | property->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); |
| 626 | property->set_editable(0, true); |
| 627 | property->set_selectable(0, true); |
| 628 | property->set_checked(0, !edited->is_class_property_disabled(class_name, name)); |
| 629 | property->set_text(0, text); |
| 630 | property->set_tooltip_text(0, tooltip); |
| 631 | property->set_metadata(0, name); |
| 632 | String icon_type = Variant::get_type_name(E.type); |
| 633 | property->set_icon(0, EditorNode::get_singleton()->get_class_icon(icon_type)); |
| 634 | } |
| 635 | } |
| 636 | |
| 637 | updating_features = false; |
| 638 | } |
| 639 | |
| 640 | void EditorFeatureProfileManager::_class_list_item_edited() { |
| 641 | if (updating_features) { |
| 642 | return; |
| 643 | } |
| 644 | |
| 645 | TreeItem *item = class_list->get_edited(); |
| 646 | if (!item) { |
| 647 | return; |
| 648 | } |
| 649 | |
| 650 | bool checked = item->is_checked(0); |
| 651 | |
| 652 | Variant md = item->get_metadata(0); |
| 653 | if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) { |
| 654 | String class_selected = md; |
| 655 | edited->set_disable_class(class_selected, !checked); |
| 656 | _save_and_update(); |
| 657 | _update_selected_profile(); |
| 658 | } else if (md.get_type() == Variant::INT) { |
| 659 | int feature_selected = md; |
| 660 | edited->set_disable_feature(EditorFeatureProfile::Feature(feature_selected), !checked); |
| 661 | _save_and_update(); |
| 662 | } |
| 663 | } |
| 664 | |
| 665 | void EditorFeatureProfileManager::_class_list_item_collapsed(Object *p_item) { |
| 666 | if (updating_features) { |
| 667 | return; |
| 668 | } |
| 669 | |
| 670 | TreeItem *item = Object::cast_to<TreeItem>(p_item); |
| 671 | if (!item) { |
| 672 | return; |
| 673 | } |
| 674 | |
| 675 | Variant md = item->get_metadata(0); |
| 676 | if (md.get_type() != Variant::STRING && md.get_type() != Variant::STRING_NAME) { |
| 677 | return; |
| 678 | } |
| 679 | |
| 680 | String class_name = md; |
| 681 | bool collapsed = item->is_collapsed(); |
| 682 | edited->set_item_collapsed(class_name, collapsed); |
| 683 | } |
| 684 | |
| 685 | void EditorFeatureProfileManager::_property_item_edited() { |
| 686 | if (updating_features) { |
| 687 | return; |
| 688 | } |
| 689 | |
| 690 | TreeItem *class_item = class_list->get_selected(); |
| 691 | if (!class_item) { |
| 692 | return; |
| 693 | } |
| 694 | |
| 695 | Variant md = class_item->get_metadata(0); |
| 696 | if (md.get_type() != Variant::STRING && md.get_type() != Variant::STRING_NAME) { |
| 697 | return; |
| 698 | } |
| 699 | |
| 700 | String class_name = md; |
| 701 | |
| 702 | TreeItem *item = property_list->get_edited(); |
| 703 | if (!item) { |
| 704 | return; |
| 705 | } |
| 706 | bool checked = item->is_checked(0); |
| 707 | |
| 708 | md = item->get_metadata(0); |
| 709 | if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) { |
| 710 | String property_selected = md; |
| 711 | edited->set_disable_class_property(class_name, property_selected, !checked); |
| 712 | _save_and_update(); |
| 713 | _update_selected_profile(); |
| 714 | } else if (md.get_type() == Variant::INT) { |
| 715 | int feature_selected = md; |
| 716 | switch (feature_selected) { |
| 717 | case CLASS_OPTION_DISABLE_EDITOR: { |
| 718 | edited->set_disable_class_editor(class_name, !checked); |
| 719 | _save_and_update(); |
| 720 | _update_selected_profile(); |
| 721 | } break; |
| 722 | } |
| 723 | } |
| 724 | } |
| 725 | |
| 726 | void EditorFeatureProfileManager::_update_selected_profile() { |
| 727 | String class_selected; |
| 728 | int feature_selected = -1; |
| 729 | |
| 730 | if (class_list->get_selected()) { |
| 731 | Variant md = class_list->get_selected()->get_metadata(0); |
| 732 | if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) { |
| 733 | class_selected = md; |
| 734 | } else if (md.get_type() == Variant::INT) { |
| 735 | feature_selected = md; |
| 736 | } |
| 737 | } |
| 738 | |
| 739 | class_list->clear(); |
| 740 | |
| 741 | String profile = _get_selected_profile(); |
| 742 | profile_actions[PROFILE_SET]->set_disabled(profile == current_profile); |
| 743 | |
| 744 | if (profile.is_empty()) { //nothing selected, nothing edited |
| 745 | property_list->clear(); |
| 746 | edited.unref(); |
| 747 | return; |
| 748 | } |
| 749 | |
| 750 | if (profile == current_profile) { |
| 751 | edited = current; //reuse current profile (which is what editor uses) |
| 752 | ERR_FAIL_COND(current.is_null()); //nothing selected, current should never be null |
| 753 | } else { |
| 754 | //reload edited, if different from current |
| 755 | edited.instantiate(); |
| 756 | Error err = edited->load_from_file(EditorPaths::get_singleton()->get_feature_profiles_dir().path_join(profile + ".profile" )); |
| 757 | ERR_FAIL_COND_MSG(err != OK, "Error when loading editor feature profile from file '" + EditorPaths::get_singleton()->get_feature_profiles_dir().path_join(profile + ".profile" ) + "'." ); |
| 758 | } |
| 759 | |
| 760 | updating_features = true; |
| 761 | |
| 762 | TreeItem *root = class_list->create_item(); |
| 763 | |
| 764 | TreeItem *features = class_list->create_item(root); |
| 765 | TreeItem *last_feature = nullptr; |
| 766 | features->set_text(0, TTR("Main Features:" )); |
| 767 | for (int i = 0; i < EditorFeatureProfile::FEATURE_MAX; i++) { |
| 768 | TreeItem *feature; |
| 769 | if (i == EditorFeatureProfile::FEATURE_IMPORT_DOCK) { |
| 770 | feature = class_list->create_item(last_feature); |
| 771 | } else { |
| 772 | feature = class_list->create_item(features); |
| 773 | last_feature = feature; |
| 774 | } |
| 775 | feature->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); |
| 776 | feature->set_text(0, TTRGET(EditorFeatureProfile::get_feature_name(EditorFeatureProfile::Feature(i)))); |
| 777 | feature->set_selectable(0, true); |
| 778 | feature->set_editable(0, true); |
| 779 | feature->set_metadata(0, i); |
| 780 | if (!edited->is_feature_disabled(EditorFeatureProfile::Feature(i))) { |
| 781 | feature->set_checked(0, true); |
| 782 | } |
| 783 | |
| 784 | if (i == feature_selected) { |
| 785 | feature->select(0); |
| 786 | } |
| 787 | } |
| 788 | |
| 789 | TreeItem *classes = class_list->create_item(root); |
| 790 | classes->set_text(0, TTR("Nodes and Classes:" )); |
| 791 | |
| 792 | _fill_classes_from(classes, "Node" , class_selected); |
| 793 | _fill_classes_from(classes, "Resource" , class_selected); |
| 794 | |
| 795 | updating_features = false; |
| 796 | |
| 797 | _class_list_item_selected(); |
| 798 | } |
| 799 | |
| 800 | void EditorFeatureProfileManager::_import_profiles(const Vector<String> &p_paths) { |
| 801 | //test it first |
| 802 | for (int i = 0; i < p_paths.size(); i++) { |
| 803 | Ref<EditorFeatureProfile> profile; |
| 804 | profile.instantiate(); |
| 805 | Error err = profile->load_from_file(p_paths[i]); |
| 806 | String basefile = p_paths[i].get_file(); |
| 807 | if (err != OK) { |
| 808 | EditorNode::get_singleton()->show_warning(vformat(TTR("File '%s' format is invalid, import aborted." ), basefile)); |
| 809 | return; |
| 810 | } |
| 811 | |
| 812 | String dst_file = EditorPaths::get_singleton()->get_feature_profiles_dir().path_join(basefile); |
| 813 | |
| 814 | if (FileAccess::exists(dst_file)) { |
| 815 | EditorNode::get_singleton()->show_warning(vformat(TTR("Profile '%s' already exists. Remove it first before importing, import aborted." ), basefile.get_basename())); |
| 816 | return; |
| 817 | } |
| 818 | } |
| 819 | |
| 820 | //do it second |
| 821 | for (int i = 0; i < p_paths.size(); i++) { |
| 822 | Ref<EditorFeatureProfile> profile; |
| 823 | profile.instantiate(); |
| 824 | Error err = profile->load_from_file(p_paths[i]); |
| 825 | ERR_CONTINUE(err != OK); |
| 826 | String basefile = p_paths[i].get_file(); |
| 827 | String dst_file = EditorPaths::get_singleton()->get_feature_profiles_dir().path_join(basefile); |
| 828 | profile->save_to_file(dst_file); |
| 829 | } |
| 830 | |
| 831 | _update_profile_list(); |
| 832 | // The newly imported profile is the first one, make it the current profile automatically. |
| 833 | if (profile_list->get_item_count() == 1) { |
| 834 | _profile_action(PROFILE_SET); |
| 835 | } |
| 836 | } |
| 837 | |
| 838 | void EditorFeatureProfileManager::_export_profile(const String &p_path) { |
| 839 | ERR_FAIL_COND(edited.is_null()); |
| 840 | Error err = edited->save_to_file(p_path); |
| 841 | if (err != OK) { |
| 842 | EditorNode::get_singleton()->show_warning(vformat(TTR("Error saving profile to path: '%s'." ), p_path)); |
| 843 | } |
| 844 | } |
| 845 | |
| 846 | void EditorFeatureProfileManager::_save_and_update() { |
| 847 | String edited_path = _get_selected_profile(); |
| 848 | ERR_FAIL_COND(edited_path.is_empty()); |
| 849 | ERR_FAIL_COND(edited.is_null()); |
| 850 | |
| 851 | edited->save_to_file(EditorPaths::get_singleton()->get_feature_profiles_dir().path_join(edited_path + ".profile" )); |
| 852 | |
| 853 | if (edited == current) { |
| 854 | update_timer->start(); |
| 855 | } |
| 856 | } |
| 857 | |
| 858 | void EditorFeatureProfileManager::_emit_current_profile_changed() { |
| 859 | emit_signal(SNAME("current_feature_profile_changed" )); |
| 860 | } |
| 861 | |
| 862 | void EditorFeatureProfileManager::notify_changed() { |
| 863 | _emit_current_profile_changed(); |
| 864 | } |
| 865 | |
| 866 | Ref<EditorFeatureProfile> EditorFeatureProfileManager::get_current_profile() { |
| 867 | return current; |
| 868 | } |
| 869 | |
| 870 | String EditorFeatureProfileManager::get_current_profile_name() const { |
| 871 | return current_profile; |
| 872 | } |
| 873 | |
| 874 | void EditorFeatureProfileManager::set_current_profile(const String &p_profile_name, bool p_validate_profile) { |
| 875 | if (p_validate_profile && !p_profile_name.is_empty()) { |
| 876 | // Profile may not exist. |
| 877 | Ref<DirAccess> da = DirAccess::open(EditorPaths::get_singleton()->get_feature_profiles_dir()); |
| 878 | ERR_FAIL_COND_MSG(da.is_null(), "Cannot open directory '" + EditorPaths::get_singleton()->get_feature_profiles_dir() + "'." ); |
| 879 | ERR_FAIL_COND_MSG(!da->file_exists(p_profile_name + ".profile" ), "Feature profile '" + p_profile_name + "' does not exist." ); |
| 880 | |
| 881 | // Change profile selection to emulate the UI interaction. Otherwise, the wrong profile would get activated. |
| 882 | // FIXME: Ideally, _update_selected_profile() should not rely on the user interface state to function properly. |
| 883 | for (int i = 0; i < profile_list->get_item_count(); i++) { |
| 884 | if (profile_list->get_item_metadata(i) == p_profile_name) { |
| 885 | profile_list->select(i); |
| 886 | break; |
| 887 | } |
| 888 | } |
| 889 | _update_selected_profile(); |
| 890 | } |
| 891 | |
| 892 | // Store in editor settings. |
| 893 | EditorSettings::get_singleton()->set("_default_feature_profile" , p_profile_name); |
| 894 | EditorSettings::get_singleton()->save(); |
| 895 | |
| 896 | current_profile = p_profile_name; |
| 897 | if (p_profile_name.is_empty()) { |
| 898 | current.unref(); |
| 899 | } else { |
| 900 | current = edited; |
| 901 | } |
| 902 | _update_profile_list(); |
| 903 | _emit_current_profile_changed(); |
| 904 | } |
| 905 | |
| 906 | EditorFeatureProfileManager *EditorFeatureProfileManager::singleton = nullptr; |
| 907 | |
| 908 | void EditorFeatureProfileManager::_bind_methods() { |
| 909 | ADD_SIGNAL(MethodInfo("current_feature_profile_changed" )); |
| 910 | } |
| 911 | |
| 912 | EditorFeatureProfileManager::EditorFeatureProfileManager() { |
| 913 | VBoxContainer *main_vbc = memnew(VBoxContainer); |
| 914 | add_child(main_vbc); |
| 915 | |
| 916 | HBoxContainer *name_hbc = memnew(HBoxContainer); |
| 917 | current_profile_name = memnew(LineEdit); |
| 918 | name_hbc->add_child(current_profile_name); |
| 919 | current_profile_name->set_text(TTR("(none)" )); |
| 920 | current_profile_name->set_editable(false); |
| 921 | current_profile_name->set_h_size_flags(Control::SIZE_EXPAND_FILL); |
| 922 | profile_actions[PROFILE_CLEAR] = memnew(Button(TTR("Reset to Default" ))); |
| 923 | name_hbc->add_child(profile_actions[PROFILE_CLEAR]); |
| 924 | profile_actions[PROFILE_CLEAR]->set_disabled(true); |
| 925 | profile_actions[PROFILE_CLEAR]->connect("pressed" , callable_mp(this, &EditorFeatureProfileManager::_profile_action).bind(PROFILE_CLEAR)); |
| 926 | |
| 927 | main_vbc->add_margin_child(TTR("Current Profile:" ), name_hbc); |
| 928 | |
| 929 | main_vbc->add_child(memnew(HSeparator)); |
| 930 | |
| 931 | HBoxContainer *profiles_hbc = memnew(HBoxContainer); |
| 932 | profile_list = memnew(OptionButton); |
| 933 | profile_list->set_h_size_flags(Control::SIZE_EXPAND_FILL); |
| 934 | profile_list->set_auto_translate(false); |
| 935 | profiles_hbc->add_child(profile_list); |
| 936 | profile_list->connect("item_selected" , callable_mp(this, &EditorFeatureProfileManager::_profile_selected)); |
| 937 | |
| 938 | profile_actions[PROFILE_NEW] = memnew(Button(TTR("Create Profile" ))); |
| 939 | profiles_hbc->add_child(profile_actions[PROFILE_NEW]); |
| 940 | profile_actions[PROFILE_NEW]->connect("pressed" , callable_mp(this, &EditorFeatureProfileManager::_profile_action).bind(PROFILE_NEW)); |
| 941 | |
| 942 | profile_actions[PROFILE_ERASE] = memnew(Button(TTR("Remove Profile" ))); |
| 943 | profiles_hbc->add_child(profile_actions[PROFILE_ERASE]); |
| 944 | profile_actions[PROFILE_ERASE]->set_disabled(true); |
| 945 | profile_actions[PROFILE_ERASE]->connect("pressed" , callable_mp(this, &EditorFeatureProfileManager::_profile_action).bind(PROFILE_ERASE)); |
| 946 | |
| 947 | main_vbc->add_margin_child(TTR("Available Profiles:" ), profiles_hbc); |
| 948 | |
| 949 | HBoxContainer *current_profile_hbc = memnew(HBoxContainer); |
| 950 | |
| 951 | profile_actions[PROFILE_SET] = memnew(Button(TTR("Make Current" ))); |
| 952 | current_profile_hbc->add_child(profile_actions[PROFILE_SET]); |
| 953 | profile_actions[PROFILE_SET]->set_disabled(true); |
| 954 | profile_actions[PROFILE_SET]->connect("pressed" , callable_mp(this, &EditorFeatureProfileManager::_profile_action).bind(PROFILE_SET)); |
| 955 | |
| 956 | current_profile_hbc->add_child(memnew(VSeparator)); |
| 957 | |
| 958 | profile_actions[PROFILE_IMPORT] = memnew(Button(TTR("Import" ))); |
| 959 | current_profile_hbc->add_child(profile_actions[PROFILE_IMPORT]); |
| 960 | profile_actions[PROFILE_IMPORT]->connect("pressed" , callable_mp(this, &EditorFeatureProfileManager::_profile_action).bind(PROFILE_IMPORT)); |
| 961 | |
| 962 | profile_actions[PROFILE_EXPORT] = memnew(Button(TTR("Export" ))); |
| 963 | current_profile_hbc->add_child(profile_actions[PROFILE_EXPORT]); |
| 964 | profile_actions[PROFILE_EXPORT]->set_disabled(true); |
| 965 | profile_actions[PROFILE_EXPORT]->connect("pressed" , callable_mp(this, &EditorFeatureProfileManager::_profile_action).bind(PROFILE_EXPORT)); |
| 966 | |
| 967 | main_vbc->add_child(current_profile_hbc); |
| 968 | |
| 969 | h_split = memnew(HSplitContainer); |
| 970 | h_split->set_v_size_flags(Control::SIZE_EXPAND_FILL); |
| 971 | main_vbc->add_child(h_split); |
| 972 | |
| 973 | class_list_vbc = memnew(VBoxContainer); |
| 974 | h_split->add_child(class_list_vbc); |
| 975 | class_list_vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL); |
| 976 | |
| 977 | class_list = memnew(Tree); |
| 978 | class_list_vbc->add_margin_child(TTR("Configure Selected Profile:" ), class_list, true); |
| 979 | class_list->set_hide_root(true); |
| 980 | class_list->set_edit_checkbox_cell_only_when_checkbox_is_pressed(true); |
| 981 | class_list->connect("cell_selected" , callable_mp(this, &EditorFeatureProfileManager::_class_list_item_selected)); |
| 982 | class_list->connect("item_edited" , callable_mp(this, &EditorFeatureProfileManager::_class_list_item_edited), CONNECT_DEFERRED); |
| 983 | class_list->connect("item_collapsed" , callable_mp(this, &EditorFeatureProfileManager::_class_list_item_collapsed)); |
| 984 | // It will be displayed once the user creates or chooses a profile. |
| 985 | class_list_vbc->hide(); |
| 986 | |
| 987 | property_list_vbc = memnew(VBoxContainer); |
| 988 | h_split->add_child(property_list_vbc); |
| 989 | property_list_vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL); |
| 990 | |
| 991 | description_bit = memnew(EditorHelpBit); |
| 992 | property_list_vbc->add_margin_child(TTR("Description:" ), description_bit, false); |
| 993 | description_bit->set_custom_minimum_size(Size2(0, 80) * EDSCALE); |
| 994 | |
| 995 | property_list = memnew(Tree); |
| 996 | property_list_vbc->add_margin_child(TTR("Extra Options:" ), property_list, true); |
| 997 | property_list->set_hide_root(true); |
| 998 | property_list->set_hide_folding(true); |
| 999 | property_list->set_edit_checkbox_cell_only_when_checkbox_is_pressed(true); |
| 1000 | property_list->connect("item_edited" , callable_mp(this, &EditorFeatureProfileManager::_property_item_edited), CONNECT_DEFERRED); |
| 1001 | // It will be displayed once the user creates or chooses a profile. |
| 1002 | property_list_vbc->hide(); |
| 1003 | |
| 1004 | no_profile_selected_help = memnew(Label(TTR("Create or import a profile to edit available classes and properties." ))); |
| 1005 | // Add some spacing above the help label. |
| 1006 | Ref<StyleBoxEmpty> sb = memnew(StyleBoxEmpty); |
| 1007 | sb->set_content_margin(SIDE_TOP, 20 * EDSCALE); |
| 1008 | no_profile_selected_help->add_theme_style_override("normal" , sb); |
| 1009 | no_profile_selected_help->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); |
| 1010 | no_profile_selected_help->set_v_size_flags(Control::SIZE_EXPAND_FILL); |
| 1011 | h_split->add_child(no_profile_selected_help); |
| 1012 | |
| 1013 | new_profile_dialog = memnew(ConfirmationDialog); |
| 1014 | new_profile_dialog->set_title(TTR("Create Profile" )); |
| 1015 | VBoxContainer *new_profile_vb = memnew(VBoxContainer); |
| 1016 | new_profile_dialog->add_child(new_profile_vb); |
| 1017 | Label *new_profile_label = memnew(Label); |
| 1018 | new_profile_label->set_text(TTR("New profile name:" )); |
| 1019 | new_profile_vb->add_child(new_profile_label); |
| 1020 | new_profile_name = memnew(LineEdit); |
| 1021 | new_profile_vb->add_child(new_profile_name); |
| 1022 | new_profile_name->set_custom_minimum_size(Size2(300 * EDSCALE, 1)); |
| 1023 | add_child(new_profile_dialog); |
| 1024 | new_profile_dialog->connect("confirmed" , callable_mp(this, &EditorFeatureProfileManager::_create_new_profile)); |
| 1025 | new_profile_dialog->register_text_enter(new_profile_name); |
| 1026 | new_profile_dialog->set_ok_button_text(TTR("Create" )); |
| 1027 | |
| 1028 | erase_profile_dialog = memnew(ConfirmationDialog); |
| 1029 | add_child(erase_profile_dialog); |
| 1030 | erase_profile_dialog->set_title(TTR("Remove Profile" )); |
| 1031 | erase_profile_dialog->connect("confirmed" , callable_mp(this, &EditorFeatureProfileManager::_erase_selected_profile)); |
| 1032 | |
| 1033 | import_profiles = memnew(EditorFileDialog); |
| 1034 | add_child(import_profiles); |
| 1035 | import_profiles->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES); |
| 1036 | import_profiles->add_filter("*.profile" , TTR("Godot Feature Profile" )); |
| 1037 | import_profiles->connect("files_selected" , callable_mp(this, &EditorFeatureProfileManager::_import_profiles)); |
| 1038 | import_profiles->set_title(TTR("Import Profile(s)" )); |
| 1039 | import_profiles->set_access(EditorFileDialog::ACCESS_FILESYSTEM); |
| 1040 | |
| 1041 | export_profile = memnew(EditorFileDialog); |
| 1042 | add_child(export_profile); |
| 1043 | export_profile->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); |
| 1044 | export_profile->add_filter("*.profile" , TTR("Godot Feature Profile" )); |
| 1045 | export_profile->connect("file_selected" , callable_mp(this, &EditorFeatureProfileManager::_export_profile)); |
| 1046 | export_profile->set_title(TTR("Export Profile" )); |
| 1047 | export_profile->set_access(EditorFileDialog::ACCESS_FILESYSTEM); |
| 1048 | |
| 1049 | set_title(TTR("Manage Editor Feature Profiles" )); |
| 1050 | EDITOR_DEF("_default_feature_profile" , "" ); |
| 1051 | |
| 1052 | update_timer = memnew(Timer); |
| 1053 | update_timer->set_wait_time(1); //wait a second before updating editor |
| 1054 | add_child(update_timer); |
| 1055 | update_timer->connect("timeout" , callable_mp(this, &EditorFeatureProfileManager::_emit_current_profile_changed)); |
| 1056 | update_timer->set_one_shot(true); |
| 1057 | |
| 1058 | singleton = this; |
| 1059 | } |
| 1060 | |