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