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