1 /*************************************************************************/
2 /* editor_feature_profile.cpp */
3 /*************************************************************************/
4 /* This file is part of: */
5 /* GODOT ENGINE */
6 /* https://godotengine.org */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
9 /* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
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 #include "core/io/json.h"
33 #include "core/os/dir_access.h"
34 #include "editor/editor_settings.h"
35 #include "editor_node.h"
36 #include "editor_scale.h"
37
38 const char *EditorFeatureProfile::feature_names[FEATURE_MAX] = {
39 TTRC("3D Editor"),
40 TTRC("Script Editor"),
41 TTRC("Asset Library"),
42 TTRC("Scene Tree Editing"),
43 TTRC("Import Dock"),
44 TTRC("Node Dock"),
45 TTRC("FileSystem and Import Docks")
46 };
47
48 const char *EditorFeatureProfile::feature_identifiers[FEATURE_MAX] = {
49 "3d",
50 "script",
51 "asset_lib",
52 "scene_tree",
53 "import_dock",
54 "node_dock",
55 "filesystem_dock"
56 };
57
set_disable_class(const StringName & p_class,bool p_disabled)58 void EditorFeatureProfile::set_disable_class(const StringName &p_class, bool p_disabled) {
59 if (p_disabled) {
60 disabled_classes.insert(p_class);
61 } else {
62 disabled_classes.erase(p_class);
63 }
64 }
65
is_class_disabled(const StringName & p_class) const66 bool EditorFeatureProfile::is_class_disabled(const StringName &p_class) const {
67 if (p_class == StringName()) {
68 return false;
69 }
70 return disabled_classes.has(p_class) || is_class_disabled(ClassDB::get_parent_class_nocheck(p_class));
71 }
72
set_disable_class_editor(const StringName & p_class,bool p_disabled)73 void EditorFeatureProfile::set_disable_class_editor(const StringName &p_class, bool p_disabled) {
74 if (p_disabled) {
75 disabled_editors.insert(p_class);
76 } else {
77 disabled_editors.erase(p_class);
78 }
79 }
80
is_class_editor_disabled(const StringName & p_class) const81 bool EditorFeatureProfile::is_class_editor_disabled(const StringName &p_class) const {
82 if (p_class == StringName()) {
83 return false;
84 }
85 return disabled_editors.has(p_class) || is_class_editor_disabled(ClassDB::get_parent_class_nocheck(p_class));
86 }
87
set_disable_class_property(const StringName & p_class,const StringName & p_property,bool p_disabled)88 void EditorFeatureProfile::set_disable_class_property(const StringName &p_class, const StringName &p_property, bool p_disabled) {
89
90 if (p_disabled) {
91 if (!disabled_properties.has(p_class)) {
92 disabled_properties[p_class] = Set<StringName>();
93 }
94
95 disabled_properties[p_class].insert(p_property);
96 } else {
97 ERR_FAIL_COND(!disabled_properties.has(p_class));
98 disabled_properties[p_class].erase(p_property);
99 if (disabled_properties[p_class].empty()) {
100 disabled_properties.erase(p_class);
101 }
102 }
103 }
is_class_property_disabled(const StringName & p_class,const StringName & p_property) const104 bool EditorFeatureProfile::is_class_property_disabled(const StringName &p_class, const StringName &p_property) const {
105
106 if (!disabled_properties.has(p_class)) {
107 return false;
108 }
109
110 if (!disabled_properties[p_class].has(p_property)) {
111 return false;
112 }
113
114 return true;
115 }
116
has_class_properties_disabled(const StringName & p_class) const117 bool EditorFeatureProfile::has_class_properties_disabled(const StringName &p_class) const {
118 return disabled_properties.has(p_class);
119 }
120
set_disable_feature(Feature p_feature,bool p_disable)121 void EditorFeatureProfile::set_disable_feature(Feature p_feature, bool p_disable) {
122
123 ERR_FAIL_INDEX(p_feature, FEATURE_MAX);
124 features_disabled[p_feature] = p_disable;
125 }
is_feature_disabled(Feature p_feature) const126 bool EditorFeatureProfile::is_feature_disabled(Feature p_feature) const {
127 ERR_FAIL_INDEX_V(p_feature, FEATURE_MAX, false);
128 return features_disabled[p_feature];
129 }
130
get_feature_name(Feature p_feature)131 String EditorFeatureProfile::get_feature_name(Feature p_feature) {
132 ERR_FAIL_INDEX_V(p_feature, FEATURE_MAX, String());
133 return feature_names[p_feature];
134 }
135
save_to_file(const String & p_path)136 Error EditorFeatureProfile::save_to_file(const String &p_path) {
137
138 Dictionary json;
139 json["type"] = "feature_profile";
140 Array dis_classes;
141 for (Set<StringName>::Element *E = disabled_classes.front(); E; E = E->next()) {
142 dis_classes.push_back(String(E->get()));
143 }
144 dis_classes.sort();
145 json["disabled_classes"] = dis_classes;
146
147 Array dis_editors;
148 for (Set<StringName>::Element *E = disabled_editors.front(); E; E = E->next()) {
149 dis_editors.push_back(String(E->get()));
150 }
151 dis_editors.sort();
152 json["disabled_editors"] = dis_editors;
153
154 Array dis_props;
155
156 for (Map<StringName, Set<StringName> >::Element *E = disabled_properties.front(); E; E = E->next()) {
157 for (Set<StringName>::Element *F = E->get().front(); F; F = F->next()) {
158 dis_props.push_back(String(E->key()) + ":" + String(F->get()));
159 }
160 }
161
162 json["disabled_properties"] = dis_props;
163
164 Array dis_features;
165 for (int i = 0; i < FEATURE_MAX; i++) {
166 if (features_disabled[i]) {
167 dis_features.push_back(feature_identifiers[i]);
168 }
169 }
170
171 json["disabled_features"] = dis_features;
172
173 FileAccessRef f = FileAccess::open(p_path, FileAccess::WRITE);
174 ERR_FAIL_COND_V_MSG(!f, ERR_CANT_CREATE, "Cannot create file '" + p_path + "'.");
175
176 String text = JSON::print(json, "\t");
177 f->store_string(text);
178 f->close();
179 return OK;
180 }
181
load_from_file(const String & p_path)182 Error EditorFeatureProfile::load_from_file(const String &p_path) {
183
184 Error err;
185 String text = FileAccess::get_file_as_string(p_path, &err);
186 if (err != OK) {
187 return err;
188 }
189
190 String err_str;
191 int err_line;
192 Variant v;
193 err = JSON::parse(text, v, err_str, err_line);
194 if (err != OK) {
195 ERR_PRINTS("Error parsing '" + p_path + "' on line " + itos(err_line) + ": " + err_str);
196 return ERR_PARSE_ERROR;
197 }
198
199 Dictionary json = v;
200
201 if (!json.has("type") || String(json["type"]) != "feature_profile") {
202 ERR_PRINTS("Error parsing '" + p_path + "', it's not a feature profile.");
203 return ERR_PARSE_ERROR;
204 }
205
206 disabled_classes.clear();
207
208 if (json.has("disabled_classes")) {
209 Array disabled_classes_arr = json["disabled_classes"];
210 for (int i = 0; i < disabled_classes_arr.size(); i++) {
211 disabled_classes.insert(disabled_classes_arr[i]);
212 }
213 }
214
215 disabled_editors.clear();
216
217 if (json.has("disabled_editors")) {
218 Array disabled_editors_arr = json["disabled_editors"];
219 for (int i = 0; i < disabled_editors_arr.size(); i++) {
220 disabled_editors.insert(disabled_editors_arr[i]);
221 }
222 }
223
224 disabled_properties.clear();
225
226 if (json.has("disabled_properties")) {
227 Array disabled_properties_arr = json["disabled_properties"];
228 for (int i = 0; i < disabled_properties_arr.size(); i++) {
229 String s = disabled_properties_arr[i];
230 set_disable_class_property(s.get_slice(":", 0), s.get_slice(":", 1), true);
231 }
232 }
233
234 if (json.has("disabled_features")) {
235
236 Array disabled_features_arr = json["disabled_features"];
237 for (int i = 0; i < FEATURE_MAX; i++) {
238 bool found = false;
239 String f = feature_identifiers[i];
240 for (int j = 0; j < disabled_features_arr.size(); j++) {
241 String fd = disabled_features_arr[j];
242 if (fd == f) {
243 found = true;
244 break;
245 }
246 }
247
248 features_disabled[i] = found;
249 }
250 }
251
252 return OK;
253 }
254
_bind_methods()255 void EditorFeatureProfile::_bind_methods() {
256
257 ClassDB::bind_method(D_METHOD("set_disable_class", "class_name", "disable"), &EditorFeatureProfile::set_disable_class);
258 ClassDB::bind_method(D_METHOD("is_class_disabled", "class_name"), &EditorFeatureProfile::is_class_disabled);
259
260 ClassDB::bind_method(D_METHOD("set_disable_class_editor", "class_name", "disable"), &EditorFeatureProfile::set_disable_class_editor);
261 ClassDB::bind_method(D_METHOD("is_class_editor_disabled", "class_name"), &EditorFeatureProfile::is_class_editor_disabled);
262
263 ClassDB::bind_method(D_METHOD("set_disable_class_property", "class_name", "property", "disable"), &EditorFeatureProfile::set_disable_class_property);
264 ClassDB::bind_method(D_METHOD("is_class_property_disabled", "class_name", "property"), &EditorFeatureProfile::is_class_property_disabled);
265
266 ClassDB::bind_method(D_METHOD("set_disable_feature", "feature", "disable"), &EditorFeatureProfile::set_disable_feature);
267 ClassDB::bind_method(D_METHOD("is_feature_disabled", "feature"), &EditorFeatureProfile::is_feature_disabled);
268
269 ClassDB::bind_method(D_METHOD("get_feature_name", "feature"), &EditorFeatureProfile::_get_feature_name);
270
271 ClassDB::bind_method(D_METHOD("save_to_file", "path"), &EditorFeatureProfile::save_to_file);
272 ClassDB::bind_method(D_METHOD("load_from_file", "path"), &EditorFeatureProfile::load_from_file);
273
274 BIND_ENUM_CONSTANT(FEATURE_3D);
275 BIND_ENUM_CONSTANT(FEATURE_SCRIPT);
276 BIND_ENUM_CONSTANT(FEATURE_ASSET_LIB);
277 BIND_ENUM_CONSTANT(FEATURE_SCENE_TREE);
278 BIND_ENUM_CONSTANT(FEATURE_IMPORT_DOCK);
279 BIND_ENUM_CONSTANT(FEATURE_NODE_DOCK);
280 BIND_ENUM_CONSTANT(FEATURE_FILESYSTEM_DOCK);
281 BIND_ENUM_CONSTANT(FEATURE_MAX);
282 }
283
EditorFeatureProfile()284 EditorFeatureProfile::EditorFeatureProfile() {
285
286 for (int i = 0; i < FEATURE_MAX; i++) {
287 features_disabled[i] = false;
288 }
289 }
290
291 //////////////////////////
292
_notification(int p_what)293 void EditorFeatureProfileManager::_notification(int p_what) {
294 if (p_what == NOTIFICATION_READY) {
295
296 current_profile = EDITOR_GET("_default_feature_profile");
297 if (current_profile != String()) {
298 current.instance();
299 Error err = current->load_from_file(EditorSettings::get_singleton()->get_feature_profiles_dir().plus_file(current_profile + ".profile"));
300 if (err != OK) {
301 ERR_PRINTS("Error loading default feature profile: " + current_profile);
302 current_profile = String();
303 current.unref();
304 }
305 }
306 _update_profile_list(current_profile);
307 }
308 }
309
_get_selected_profile()310 String EditorFeatureProfileManager::_get_selected_profile() {
311 int idx = profile_list->get_selected();
312 if (idx < 0) {
313 return String();
314 }
315
316 return profile_list->get_item_metadata(idx);
317 }
318
_update_profile_list(const String & p_select_profile)319 void EditorFeatureProfileManager::_update_profile_list(const String &p_select_profile) {
320
321 String selected_profile;
322 if (p_select_profile == String()) { //default, keep
323 if (profile_list->get_selected() >= 0) {
324 selected_profile = profile_list->get_item_metadata(profile_list->get_selected());
325 if (!FileAccess::exists(EditorSettings::get_singleton()->get_feature_profiles_dir().plus_file(selected_profile + ".profile"))) {
326 selected_profile = String(); //does not exist
327 }
328 }
329 } else {
330 selected_profile = p_select_profile;
331 }
332
333 Vector<String> profiles;
334 DirAccessRef d = DirAccess::open(EditorSettings::get_singleton()->get_feature_profiles_dir());
335 ERR_FAIL_COND_MSG(!d, "Cannot open directory '" + EditorSettings::get_singleton()->get_feature_profiles_dir() + "'.");
336
337 d->list_dir_begin();
338 while (true) {
339 String f = d->get_next();
340 if (f == String()) {
341 break;
342 }
343
344 if (!d->current_is_dir()) {
345 int last_pos = f.find_last(".profile");
346 if (last_pos != -1) {
347 profiles.push_back(f.substr(0, last_pos));
348 }
349 }
350 }
351
352 profiles.sort();
353
354 profile_list->clear();
355
356 for (int i = 0; i < profiles.size(); i++) {
357 String name = profiles[i];
358
359 if (i == 0 && selected_profile == String()) {
360 selected_profile = name;
361 }
362
363 if (name == current_profile) {
364 name += " (current)";
365 }
366 profile_list->add_item(name);
367 int index = profile_list->get_item_count() - 1;
368 profile_list->set_item_metadata(index, profiles[i]);
369 if (profiles[i] == selected_profile) {
370 profile_list->select(index);
371 }
372 }
373
374 profile_actions[PROFILE_CLEAR]->set_disabled(current_profile == String());
375 profile_actions[PROFILE_ERASE]->set_disabled(selected_profile == String());
376 profile_actions[PROFILE_EXPORT]->set_disabled(selected_profile == String());
377 profile_actions[PROFILE_SET]->set_disabled(selected_profile == String());
378
379 current_profile_name->set_text(current_profile);
380
381 _update_selected_profile();
382 }
383
_profile_action(int p_action)384 void EditorFeatureProfileManager::_profile_action(int p_action) {
385
386 switch (p_action) {
387 case PROFILE_CLEAR: {
388
389 EditorSettings::get_singleton()->set("_default_feature_profile", "");
390 EditorSettings::get_singleton()->save();
391 current_profile = "";
392 current.unref();
393
394 _update_profile_list();
395 _emit_current_profile_changed();
396 } break;
397 case PROFILE_SET: {
398
399 String selected = _get_selected_profile();
400 ERR_FAIL_COND(selected == String());
401 if (selected == current_profile) {
402 return; // Nothing to do here.
403 }
404 EditorSettings::get_singleton()->set("_default_feature_profile", selected);
405 EditorSettings::get_singleton()->save();
406 current_profile = selected;
407 current = edited;
408
409 _update_profile_list();
410 _emit_current_profile_changed();
411 } break;
412 case PROFILE_IMPORT: {
413
414 import_profiles->popup_centered_ratio();
415 } break;
416 case PROFILE_EXPORT: {
417
418 export_profile->popup_centered_ratio();
419 export_profile->set_current_file(_get_selected_profile() + ".profile");
420 } break;
421 case PROFILE_NEW: {
422
423 new_profile_dialog->popup_centered_minsize();
424 new_profile_name->clear();
425 new_profile_name->grab_focus();
426 } break;
427 case PROFILE_ERASE: {
428
429 String selected = _get_selected_profile();
430 ERR_FAIL_COND(selected == String());
431
432 erase_profile_dialog->set_text(vformat(TTR("Erase profile '%s'? (no undo)"), selected));
433 erase_profile_dialog->popup_centered_minsize();
434 } break;
435 }
436 }
437
_erase_selected_profile()438 void EditorFeatureProfileManager::_erase_selected_profile() {
439
440 String selected = _get_selected_profile();
441 ERR_FAIL_COND(selected == String());
442 DirAccessRef da = DirAccess::open(EditorSettings::get_singleton()->get_feature_profiles_dir());
443 ERR_FAIL_COND_MSG(!da, "Cannot open directory '" + EditorSettings::get_singleton()->get_feature_profiles_dir() + "'.");
444
445 da->remove(selected + ".profile");
446 if (selected == current_profile) {
447 _profile_action(PROFILE_CLEAR);
448 } else {
449 _update_profile_list();
450 }
451 }
452
_create_new_profile()453 void EditorFeatureProfileManager::_create_new_profile() {
454 String name = new_profile_name->get_text().strip_edges();
455 if (!name.is_valid_filename() || name.find(".") != -1) {
456 EditorNode::get_singleton()->show_warning(TTR("Profile must be a valid filename and must not contain '.'"));
457 return;
458 }
459 String file = EditorSettings::get_singleton()->get_feature_profiles_dir().plus_file(name + ".profile");
460 if (FileAccess::exists(file)) {
461 EditorNode::get_singleton()->show_warning(TTR("Profile with this name already exists."));
462 return;
463 }
464
465 Ref<EditorFeatureProfile> new_profile;
466 new_profile.instance();
467 new_profile->save_to_file(file);
468
469 _update_profile_list(name);
470 }
471
_profile_selected(int p_what)472 void EditorFeatureProfileManager::_profile_selected(int p_what) {
473
474 _update_selected_profile();
475 }
476
_fill_classes_from(TreeItem * p_parent,const String & p_class,const String & p_selected)477 void EditorFeatureProfileManager::_fill_classes_from(TreeItem *p_parent, const String &p_class, const String &p_selected) {
478
479 TreeItem *class_item = class_list->create_item(p_parent);
480 class_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
481 class_item->set_icon(0, EditorNode::get_singleton()->get_class_icon(p_class, "Node"));
482 String text = p_class;
483
484 bool disabled = edited->is_class_disabled(p_class);
485 bool disabled_editor = edited->is_class_editor_disabled(p_class);
486 bool disabled_properties = edited->has_class_properties_disabled(p_class);
487 if (disabled) {
488 class_item->set_custom_color(0, get_color("disabled_font_color", "Editor"));
489 } else if (disabled_editor && disabled_properties) {
490 text += " " + TTR("(Editor Disabled, Properties Disabled)");
491 } else if (disabled_properties) {
492 text += " " + TTR("(Properties Disabled)");
493 } else if (disabled_editor) {
494 text += " " + TTR("(Editor Disabled)");
495 }
496 class_item->set_text(0, text);
497 class_item->set_editable(0, true);
498 class_item->set_selectable(0, true);
499 class_item->set_metadata(0, p_class);
500
501 if (p_class == p_selected) {
502 class_item->select(0);
503 }
504 if (disabled) {
505 //class disabled, do nothing else (do not show further)
506 return;
507 }
508
509 class_item->set_checked(0, true); // if its not disabled, its checked
510
511 List<StringName> child_classes;
512 ClassDB::get_direct_inheriters_from_class(p_class, &child_classes);
513 child_classes.sort_custom<StringName::AlphCompare>();
514
515 for (List<StringName>::Element *E = child_classes.front(); E; E = E->next()) {
516 String name = E->get();
517 if (name.begins_with("Editor") || ClassDB::get_api_type(name) != ClassDB::API_CORE) {
518 continue;
519 }
520 _fill_classes_from(class_item, name, p_selected);
521 }
522 }
523
_class_list_item_selected()524 void EditorFeatureProfileManager::_class_list_item_selected() {
525
526 if (updating_features)
527 return;
528
529 property_list->clear();
530
531 TreeItem *item = class_list->get_selected();
532 if (!item) {
533 return;
534 }
535
536 Variant md = item->get_metadata(0);
537 if (md.get_type() != Variant::STRING) {
538 return;
539 }
540
541 String class_name = md;
542
543 if (edited->is_class_disabled(class_name)) {
544 return;
545 }
546
547 updating_features = true;
548 TreeItem *root = property_list->create_item();
549 TreeItem *options = property_list->create_item(root);
550 options->set_text(0, TTR("Class Options:"));
551
552 {
553 TreeItem *option = property_list->create_item(options);
554 option->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
555 option->set_editable(0, true);
556 option->set_selectable(0, true);
557 option->set_checked(0, !edited->is_class_editor_disabled(class_name));
558 option->set_text(0, TTR("Enable Contextual Editor"));
559 option->set_metadata(0, CLASS_OPTION_DISABLE_EDITOR);
560 }
561
562 TreeItem *properties = property_list->create_item(root);
563 properties->set_text(0, TTR("Enabled Properties:"));
564
565 List<PropertyInfo> props;
566
567 ClassDB::get_property_list(class_name, &props, true);
568
569 for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
570
571 String name = E->get().name;
572 if (!(E->get().usage & PROPERTY_USAGE_EDITOR))
573 continue;
574 TreeItem *property = property_list->create_item(properties);
575 property->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
576 property->set_editable(0, true);
577 property->set_selectable(0, true);
578 property->set_checked(0, !edited->is_class_property_disabled(class_name, name));
579 property->set_text(0, name.capitalize());
580 property->set_metadata(0, name);
581 String icon_type = Variant::get_type_name(E->get().type);
582 property->set_icon(0, EditorNode::get_singleton()->get_class_icon(icon_type));
583 }
584
585 updating_features = false;
586 }
587
_class_list_item_edited()588 void EditorFeatureProfileManager::_class_list_item_edited() {
589
590 if (updating_features)
591 return;
592
593 TreeItem *item = class_list->get_edited();
594 if (!item) {
595 return;
596 }
597
598 bool checked = item->is_checked(0);
599
600 Variant md = item->get_metadata(0);
601 if (md.get_type() == Variant::STRING) {
602 String class_selected = md;
603 edited->set_disable_class(class_selected, !checked);
604 _save_and_update();
605 _update_selected_profile();
606 } else if (md.get_type() == Variant::INT) {
607 int feature_selected = md;
608 edited->set_disable_feature(EditorFeatureProfile::Feature(feature_selected), !checked);
609 _save_and_update();
610 }
611 }
612
_property_item_edited()613 void EditorFeatureProfileManager::_property_item_edited() {
614 if (updating_features)
615 return;
616
617 TreeItem *class_item = class_list->get_selected();
618 if (!class_item) {
619 return;
620 }
621
622 Variant md = class_item->get_metadata(0);
623 if (md.get_type() != Variant::STRING) {
624 return;
625 }
626
627 String class_name = md;
628
629 TreeItem *item = property_list->get_edited();
630 if (!item) {
631 return;
632 }
633 bool checked = item->is_checked(0);
634
635 md = item->get_metadata(0);
636 if (md.get_type() == Variant::STRING) {
637 String property_selected = md;
638 edited->set_disable_class_property(class_name, property_selected, !checked);
639 _save_and_update();
640 _update_selected_profile();
641 } else if (md.get_type() == Variant::INT) {
642 int feature_selected = md;
643 switch (feature_selected) {
644 case CLASS_OPTION_DISABLE_EDITOR: {
645 edited->set_disable_class_editor(class_name, !checked);
646 _save_and_update();
647 _update_selected_profile();
648 } break;
649 }
650 }
651 }
652
_update_selected_profile()653 void EditorFeatureProfileManager::_update_selected_profile() {
654
655 String class_selected;
656 int feature_selected = -1;
657
658 if (class_list->get_selected()) {
659 Variant md = class_list->get_selected()->get_metadata(0);
660 if (md.get_type() == Variant::STRING) {
661 class_selected = md;
662 } else if (md.get_type() == Variant::INT) {
663 feature_selected = md;
664 }
665 }
666
667 class_list->clear();
668
669 String profile = _get_selected_profile();
670 if (profile == String()) { //nothing selected, nothing edited
671 property_list->clear();
672 edited.unref();
673 return;
674 }
675
676 if (profile == current_profile) {
677 edited = current; //reuse current profile (which is what editor uses)
678 ERR_FAIL_COND(current.is_null()); //nothing selected, current should never be null
679 } else {
680 //reload edited, if different from current
681 edited.instance();
682 Error err = edited->load_from_file(EditorSettings::get_singleton()->get_feature_profiles_dir().plus_file(profile + ".profile"));
683 ERR_FAIL_COND_MSG(err != OK, "Error when loading EditorSettings from file '" + EditorSettings::get_singleton()->get_feature_profiles_dir().plus_file(profile + ".profile") + "'.");
684 }
685
686 updating_features = true;
687
688 TreeItem *root = class_list->create_item();
689
690 TreeItem *features = class_list->create_item(root);
691 features->set_text(0, TTR("Enabled Features:"));
692 for (int i = 0; i < EditorFeatureProfile::FEATURE_MAX; i++) {
693
694 TreeItem *feature = class_list->create_item(features);
695 feature->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
696 feature->set_text(0, TTRGET(EditorFeatureProfile::get_feature_name(EditorFeatureProfile::Feature(i))));
697 feature->set_selectable(0, true);
698 feature->set_editable(0, true);
699 feature->set_metadata(0, i);
700 if (!edited->is_feature_disabled(EditorFeatureProfile::Feature(i))) {
701 feature->set_checked(0, true);
702 }
703
704 if (i == feature_selected) {
705 feature->select(0);
706 }
707 }
708
709 TreeItem *classes = class_list->create_item(root);
710 classes->set_text(0, TTR("Enabled Classes:"));
711
712 _fill_classes_from(classes, "Node", class_selected);
713 _fill_classes_from(classes, "Resource", class_selected);
714
715 updating_features = false;
716
717 _class_list_item_selected();
718 }
719
_import_profiles(const Vector<String> & p_paths)720 void EditorFeatureProfileManager::_import_profiles(const Vector<String> &p_paths) {
721
722 //test it first
723 for (int i = 0; i < p_paths.size(); i++) {
724 Ref<EditorFeatureProfile> profile;
725 profile.instance();
726 Error err = profile->load_from_file(p_paths[i]);
727 String basefile = p_paths[i].get_file();
728 if (err != OK) {
729 EditorNode::get_singleton()->show_warning(vformat(TTR("File '%s' format is invalid, import aborted."), basefile));
730 return;
731 }
732
733 String dst_file = EditorSettings::get_singleton()->get_feature_profiles_dir().plus_file(basefile);
734
735 if (FileAccess::exists(dst_file)) {
736 EditorNode::get_singleton()->show_warning(vformat(TTR("Profile '%s' already exists. Remove it first before importing, import aborted."), basefile.get_basename()));
737 return;
738 }
739 }
740
741 //do it second
742 for (int i = 0; i < p_paths.size(); i++) {
743 Ref<EditorFeatureProfile> profile;
744 profile.instance();
745 Error err = profile->load_from_file(p_paths[i]);
746 ERR_CONTINUE(err != OK);
747 String basefile = p_paths[i].get_file();
748 String dst_file = EditorSettings::get_singleton()->get_feature_profiles_dir().plus_file(basefile);
749 profile->save_to_file(dst_file);
750 }
751
752 _update_profile_list();
753 }
754
_export_profile(const String & p_path)755 void EditorFeatureProfileManager::_export_profile(const String &p_path) {
756
757 ERR_FAIL_COND(edited.is_null());
758 Error err = edited->save_to_file(p_path);
759 if (err != OK) {
760 EditorNode::get_singleton()->show_warning(vformat(TTR("Error saving profile to path: '%s'."), p_path));
761 }
762 }
763
_save_and_update()764 void EditorFeatureProfileManager::_save_and_update() {
765
766 String edited_path = _get_selected_profile();
767 ERR_FAIL_COND(edited_path == String());
768 ERR_FAIL_COND(edited.is_null());
769
770 edited->save_to_file(EditorSettings::get_singleton()->get_feature_profiles_dir().plus_file(edited_path + ".profile"));
771
772 if (edited == current) {
773 update_timer->start();
774 }
775 }
776
_emit_current_profile_changed()777 void EditorFeatureProfileManager::_emit_current_profile_changed() {
778
779 emit_signal("current_feature_profile_changed");
780 }
781
notify_changed()782 void EditorFeatureProfileManager::notify_changed() {
783 _emit_current_profile_changed();
784 }
785
get_current_profile()786 Ref<EditorFeatureProfile> EditorFeatureProfileManager::get_current_profile() {
787 return current;
788 }
789
790 EditorFeatureProfileManager *EditorFeatureProfileManager::singleton = NULL;
791
_bind_methods()792 void EditorFeatureProfileManager::_bind_methods() {
793
794 ClassDB::bind_method("_update_selected_profile", &EditorFeatureProfileManager::_update_selected_profile);
795 ClassDB::bind_method("_profile_action", &EditorFeatureProfileManager::_profile_action);
796 ClassDB::bind_method("_create_new_profile", &EditorFeatureProfileManager::_create_new_profile);
797 ClassDB::bind_method("_profile_selected", &EditorFeatureProfileManager::_profile_selected);
798 ClassDB::bind_method("_erase_selected_profile", &EditorFeatureProfileManager::_erase_selected_profile);
799 ClassDB::bind_method("_import_profiles", &EditorFeatureProfileManager::_import_profiles);
800 ClassDB::bind_method("_export_profile", &EditorFeatureProfileManager::_export_profile);
801 ClassDB::bind_method("_class_list_item_selected", &EditorFeatureProfileManager::_class_list_item_selected);
802 ClassDB::bind_method("_class_list_item_edited", &EditorFeatureProfileManager::_class_list_item_edited);
803 ClassDB::bind_method("_property_item_edited", &EditorFeatureProfileManager::_property_item_edited);
804 ClassDB::bind_method("_emit_current_profile_changed", &EditorFeatureProfileManager::_emit_current_profile_changed);
805
806 ADD_SIGNAL(MethodInfo("current_feature_profile_changed"));
807 }
808
EditorFeatureProfileManager()809 EditorFeatureProfileManager::EditorFeatureProfileManager() {
810
811 VBoxContainer *main_vbc = memnew(VBoxContainer);
812 add_child(main_vbc);
813
814 HBoxContainer *name_hbc = memnew(HBoxContainer);
815 current_profile_name = memnew(LineEdit);
816 name_hbc->add_child(current_profile_name);
817 current_profile_name->set_editable(false);
818 current_profile_name->set_h_size_flags(SIZE_EXPAND_FILL);
819 profile_actions[PROFILE_CLEAR] = memnew(Button(TTR("Unset")));
820 name_hbc->add_child(profile_actions[PROFILE_CLEAR]);
821 profile_actions[PROFILE_CLEAR]->set_disabled(true);
822 profile_actions[PROFILE_CLEAR]->connect("pressed", this, "_profile_action", varray(PROFILE_CLEAR));
823
824 main_vbc->add_margin_child(TTR("Current Profile:"), name_hbc);
825
826 HBoxContainer *profiles_hbc = memnew(HBoxContainer);
827 profile_list = memnew(OptionButton);
828 profile_list->set_h_size_flags(SIZE_EXPAND_FILL);
829 profiles_hbc->add_child(profile_list);
830 profile_list->connect("item_selected", this, "_profile_selected");
831
832 profile_actions[PROFILE_SET] = memnew(Button(TTR("Make Current")));
833 profiles_hbc->add_child(profile_actions[PROFILE_SET]);
834 profile_actions[PROFILE_SET]->set_disabled(true);
835 profile_actions[PROFILE_SET]->connect("pressed", this, "_profile_action", varray(PROFILE_SET));
836
837 profile_actions[PROFILE_ERASE] = memnew(Button(TTR("Remove")));
838 profiles_hbc->add_child(profile_actions[PROFILE_ERASE]);
839 profile_actions[PROFILE_ERASE]->set_disabled(true);
840 profile_actions[PROFILE_ERASE]->connect("pressed", this, "_profile_action", varray(PROFILE_ERASE));
841
842 profiles_hbc->add_child(memnew(VSeparator));
843
844 profile_actions[PROFILE_NEW] = memnew(Button(TTR("New")));
845 profiles_hbc->add_child(profile_actions[PROFILE_NEW]);
846 profile_actions[PROFILE_NEW]->connect("pressed", this, "_profile_action", varray(PROFILE_NEW));
847
848 profiles_hbc->add_child(memnew(VSeparator));
849
850 profile_actions[PROFILE_IMPORT] = memnew(Button(TTR("Import")));
851 profiles_hbc->add_child(profile_actions[PROFILE_IMPORT]);
852 profile_actions[PROFILE_IMPORT]->connect("pressed", this, "_profile_action", varray(PROFILE_IMPORT));
853
854 profile_actions[PROFILE_EXPORT] = memnew(Button(TTR("Export")));
855 profiles_hbc->add_child(profile_actions[PROFILE_EXPORT]);
856 profile_actions[PROFILE_EXPORT]->set_disabled(true);
857 profile_actions[PROFILE_EXPORT]->connect("pressed", this, "_profile_action", varray(PROFILE_EXPORT));
858
859 main_vbc->add_margin_child(TTR("Available Profiles:"), profiles_hbc);
860
861 h_split = memnew(HSplitContainer);
862 h_split->set_v_size_flags(SIZE_EXPAND_FILL);
863 main_vbc->add_child(h_split);
864
865 VBoxContainer *class_list_vbc = memnew(VBoxContainer);
866 h_split->add_child(class_list_vbc);
867 class_list_vbc->set_h_size_flags(SIZE_EXPAND_FILL);
868
869 class_list = memnew(Tree);
870 class_list_vbc->add_margin_child(TTR("Enabled Classes:"), class_list, true);
871 class_list->set_hide_root(true);
872 class_list->set_edit_checkbox_cell_only_when_checkbox_is_pressed(true);
873 class_list->connect("cell_selected", this, "_class_list_item_selected");
874 class_list->connect("item_edited", this, "_class_list_item_edited", varray(), CONNECT_DEFERRED);
875
876 VBoxContainer *property_list_vbc = memnew(VBoxContainer);
877 h_split->add_child(property_list_vbc);
878 property_list_vbc->set_h_size_flags(SIZE_EXPAND_FILL);
879
880 property_list = memnew(Tree);
881 property_list_vbc->add_margin_child(TTR("Class Options"), property_list, true);
882 property_list->set_hide_root(true);
883 property_list->set_hide_folding(true);
884 property_list->set_edit_checkbox_cell_only_when_checkbox_is_pressed(true);
885 property_list->connect("item_edited", this, "_property_item_edited", varray(), CONNECT_DEFERRED);
886
887 new_profile_dialog = memnew(ConfirmationDialog);
888 new_profile_dialog->set_title(TTR("New profile name:"));
889 new_profile_name = memnew(LineEdit);
890 new_profile_dialog->add_child(new_profile_name);
891 new_profile_name->set_custom_minimum_size(Size2(300 * EDSCALE, 1));
892 add_child(new_profile_dialog);
893 new_profile_dialog->connect("confirmed", this, "_create_new_profile");
894 new_profile_dialog->register_text_enter(new_profile_name);
895 new_profile_dialog->get_ok()->set_text(TTR("Create"));
896
897 erase_profile_dialog = memnew(ConfirmationDialog);
898 add_child(erase_profile_dialog);
899 erase_profile_dialog->set_title(TTR("Erase Profile"));
900 erase_profile_dialog->connect("confirmed", this, "_erase_selected_profile");
901
902 import_profiles = memnew(EditorFileDialog);
903 add_child(import_profiles);
904 import_profiles->set_mode(EditorFileDialog::MODE_OPEN_FILES);
905 import_profiles->add_filter("*.profile; " + TTR("Godot Feature Profile"));
906 import_profiles->connect("files_selected", this, "_import_profiles");
907 import_profiles->set_title(TTR("Import Profile(s)"));
908 import_profiles->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
909
910 export_profile = memnew(EditorFileDialog);
911 add_child(export_profile);
912 export_profile->set_mode(EditorFileDialog::MODE_SAVE_FILE);
913 export_profile->add_filter("*.profile; " + TTR("Godot Feature Profile"));
914 export_profile->connect("file_selected", this, "_export_profile");
915 export_profile->set_title(TTR("Export Profile"));
916 export_profile->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
917
918 set_title(TTR("Manage Editor Feature Profiles"));
919 EDITOR_DEF("_default_feature_profile", "");
920
921 update_timer = memnew(Timer);
922 update_timer->set_wait_time(1); //wait a second before updating editor
923 add_child(update_timer);
924 update_timer->connect("timeout", this, "_emit_current_profile_changed");
925 update_timer->set_one_shot(true);
926
927 updating_features = false;
928
929 singleton = this;
930 }
931