editor_feature_profile.cpp 32 KB

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