editor_feature_profile.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920
  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. #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("Import Dock"),
  42. TTRC("Node Dock"),
  43. TTRC("FileSystem and Import Docks")
  44. };
  45. const char *EditorFeatureProfile::feature_identifiers[FEATURE_MAX] = {
  46. "3d",
  47. "script",
  48. "asset_lib",
  49. "scene_tree",
  50. "import_dock",
  51. "node_dock",
  52. "filesystem_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].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_IMPORT_DOCK);
  229. BIND_ENUM_CONSTANT(FEATURE_NODE_DOCK);
  230. BIND_ENUM_CONSTANT(FEATURE_FILESYSTEM_DOCK);
  231. BIND_ENUM_CONSTANT(FEATURE_MAX);
  232. }
  233. EditorFeatureProfile::EditorFeatureProfile() {
  234. for (int i = 0; i < FEATURE_MAX; i++) {
  235. features_disabled[i] = false;
  236. }
  237. }
  238. //////////////////////////
  239. void EditorFeatureProfileManager::_notification(int p_what) {
  240. if (p_what == NOTIFICATION_READY) {
  241. current_profile = EDITOR_GET("_default_feature_profile");
  242. if (current_profile != String()) {
  243. current.instance();
  244. Error err = current->load_from_file(EditorSettings::get_singleton()->get_feature_profiles_dir().plus_file(current_profile + ".profile"));
  245. if (err != OK) {
  246. ERR_PRINT("Error loading default feature profile: " + current_profile);
  247. current_profile = String();
  248. current.unref();
  249. }
  250. }
  251. _update_profile_list(current_profile);
  252. }
  253. }
  254. String EditorFeatureProfileManager::_get_selected_profile() {
  255. int idx = profile_list->get_selected();
  256. if (idx < 0) {
  257. return String();
  258. }
  259. return profile_list->get_item_metadata(idx);
  260. }
  261. void EditorFeatureProfileManager::_update_profile_list(const String &p_select_profile) {
  262. String selected_profile;
  263. if (p_select_profile == String()) { //default, keep
  264. if (profile_list->get_selected() >= 0) {
  265. selected_profile = profile_list->get_item_metadata(profile_list->get_selected());
  266. if (!FileAccess::exists(EditorSettings::get_singleton()->get_feature_profiles_dir().plus_file(selected_profile + ".profile"))) {
  267. selected_profile = String(); //does not exist
  268. }
  269. }
  270. } else {
  271. selected_profile = p_select_profile;
  272. }
  273. Vector<String> profiles;
  274. DirAccessRef d = DirAccess::open(EditorSettings::get_singleton()->get_feature_profiles_dir());
  275. ERR_FAIL_COND_MSG(!d, "Cannot open directory '" + EditorSettings::get_singleton()->get_feature_profiles_dir() + "'.");
  276. d->list_dir_begin();
  277. while (true) {
  278. String f = d->get_next();
  279. if (f == String()) {
  280. break;
  281. }
  282. if (!d->current_is_dir()) {
  283. int last_pos = f.find_last(".profile");
  284. if (last_pos != -1) {
  285. profiles.push_back(f.substr(0, last_pos));
  286. }
  287. }
  288. }
  289. profiles.sort();
  290. profile_list->clear();
  291. for (int i = 0; i < profiles.size(); i++) {
  292. String name = profiles[i];
  293. if (i == 0 && selected_profile == String()) {
  294. selected_profile = name;
  295. }
  296. if (name == current_profile) {
  297. name += " (current)";
  298. }
  299. profile_list->add_item(name);
  300. int index = profile_list->get_item_count() - 1;
  301. profile_list->set_item_metadata(index, profiles[i]);
  302. if (profiles[i] == selected_profile) {
  303. profile_list->select(index);
  304. }
  305. }
  306. profile_actions[PROFILE_CLEAR]->set_disabled(current_profile == String());
  307. profile_actions[PROFILE_ERASE]->set_disabled(selected_profile == String());
  308. profile_actions[PROFILE_EXPORT]->set_disabled(selected_profile == String());
  309. profile_actions[PROFILE_SET]->set_disabled(selected_profile == String());
  310. current_profile_name->set_text(current_profile);
  311. _update_selected_profile();
  312. }
  313. void EditorFeatureProfileManager::_profile_action(int p_action) {
  314. switch (p_action) {
  315. case PROFILE_CLEAR: {
  316. EditorSettings::get_singleton()->set("_default_feature_profile", "");
  317. EditorSettings::get_singleton()->save();
  318. current_profile = "";
  319. current.unref();
  320. _update_profile_list();
  321. _emit_current_profile_changed();
  322. } break;
  323. case PROFILE_SET: {
  324. String selected = _get_selected_profile();
  325. ERR_FAIL_COND(selected == String());
  326. if (selected == current_profile) {
  327. return; // Nothing to do here.
  328. }
  329. EditorSettings::get_singleton()->set("_default_feature_profile", selected);
  330. EditorSettings::get_singleton()->save();
  331. current_profile = selected;
  332. current = edited;
  333. _update_profile_list();
  334. _emit_current_profile_changed();
  335. } break;
  336. case PROFILE_IMPORT: {
  337. import_profiles->popup_centered_ratio();
  338. } break;
  339. case PROFILE_EXPORT: {
  340. export_profile->popup_centered_ratio();
  341. export_profile->set_current_file(_get_selected_profile() + ".profile");
  342. } break;
  343. case PROFILE_NEW: {
  344. new_profile_dialog->popup_centered();
  345. new_profile_name->clear();
  346. new_profile_name->grab_focus();
  347. } break;
  348. case PROFILE_ERASE: {
  349. String selected = _get_selected_profile();
  350. ERR_FAIL_COND(selected == String());
  351. erase_profile_dialog->set_text(vformat(TTR("Erase profile '%s'? (no undo)"), selected));
  352. erase_profile_dialog->popup_centered();
  353. } break;
  354. }
  355. }
  356. void EditorFeatureProfileManager::_erase_selected_profile() {
  357. String selected = _get_selected_profile();
  358. ERR_FAIL_COND(selected == String());
  359. DirAccessRef da = DirAccess::open(EditorSettings::get_singleton()->get_feature_profiles_dir());
  360. ERR_FAIL_COND_MSG(!da, "Cannot open directory '" + EditorSettings::get_singleton()->get_feature_profiles_dir() + "'.");
  361. da->remove(selected + ".profile");
  362. if (selected == current_profile) {
  363. _profile_action(PROFILE_CLEAR);
  364. } else {
  365. _update_profile_list();
  366. }
  367. }
  368. void EditorFeatureProfileManager::_create_new_profile() {
  369. String name = new_profile_name->get_text().strip_edges();
  370. if (!name.is_valid_filename() || name.find(".") != -1) {
  371. EditorNode::get_singleton()->show_warning(TTR("Profile must be a valid filename and must not contain '.'"));
  372. return;
  373. }
  374. String file = EditorSettings::get_singleton()->get_feature_profiles_dir().plus_file(name + ".profile");
  375. if (FileAccess::exists(file)) {
  376. EditorNode::get_singleton()->show_warning(TTR("Profile with this name already exists."));
  377. return;
  378. }
  379. Ref<EditorFeatureProfile> new_profile;
  380. new_profile.instance();
  381. new_profile->save_to_file(file);
  382. _update_profile_list(name);
  383. }
  384. void EditorFeatureProfileManager::_profile_selected(int p_what) {
  385. _update_selected_profile();
  386. }
  387. void EditorFeatureProfileManager::_fill_classes_from(TreeItem *p_parent, const String &p_class, const String &p_selected) {
  388. TreeItem *class_item = class_list->create_item(p_parent);
  389. class_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
  390. class_item->set_icon(0, EditorNode::get_singleton()->get_class_icon(p_class, "Node"));
  391. String text = p_class;
  392. bool disabled = edited->is_class_disabled(p_class);
  393. bool disabled_editor = edited->is_class_editor_disabled(p_class);
  394. bool disabled_properties = edited->has_class_properties_disabled(p_class);
  395. if (disabled) {
  396. class_item->set_custom_color(0, class_list->get_theme_color("disabled_font_color", "Editor"));
  397. } else if (disabled_editor && disabled_properties) {
  398. text += " " + TTR("(Editor Disabled, Properties Disabled)");
  399. } else if (disabled_properties) {
  400. text += " " + TTR("(Properties Disabled)");
  401. } else if (disabled_editor) {
  402. text += " " + TTR("(Editor Disabled)");
  403. }
  404. class_item->set_text(0, text);
  405. class_item->set_editable(0, true);
  406. class_item->set_selectable(0, true);
  407. class_item->set_metadata(0, p_class);
  408. if (p_class == p_selected) {
  409. class_item->select(0);
  410. }
  411. if (disabled) {
  412. //class disabled, do nothing else (do not show further)
  413. return;
  414. }
  415. class_item->set_checked(0, true); // if its not disabled, its checked
  416. List<StringName> child_classes;
  417. ClassDB::get_direct_inheriters_from_class(p_class, &child_classes);
  418. child_classes.sort_custom<StringName::AlphCompare>();
  419. for (List<StringName>::Element *E = child_classes.front(); E; E = E->next()) {
  420. String name = E->get();
  421. if (name.begins_with("Editor") || ClassDB::get_api_type(name) != ClassDB::API_CORE) {
  422. continue;
  423. }
  424. _fill_classes_from(class_item, name, p_selected);
  425. }
  426. }
  427. void EditorFeatureProfileManager::_class_list_item_selected() {
  428. if (updating_features)
  429. return;
  430. property_list->clear();
  431. TreeItem *item = class_list->get_selected();
  432. if (!item) {
  433. return;
  434. }
  435. Variant md = item->get_metadata(0);
  436. if (md.get_type() != Variant::STRING && md.get_type() != Variant::STRING_NAME) {
  437. return;
  438. }
  439. String class_name = md;
  440. if (edited->is_class_disabled(class_name)) {
  441. return;
  442. }
  443. updating_features = true;
  444. TreeItem *root = property_list->create_item();
  445. TreeItem *options = property_list->create_item(root);
  446. options->set_text(0, TTR("Class Options:"));
  447. {
  448. TreeItem *option = property_list->create_item(options);
  449. option->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
  450. option->set_editable(0, true);
  451. option->set_selectable(0, true);
  452. option->set_checked(0, !edited->is_class_editor_disabled(class_name));
  453. option->set_text(0, TTR("Enable Contextual Editor"));
  454. option->set_metadata(0, CLASS_OPTION_DISABLE_EDITOR);
  455. }
  456. TreeItem *properties = property_list->create_item(root);
  457. properties->set_text(0, TTR("Enabled Properties:"));
  458. List<PropertyInfo> props;
  459. ClassDB::get_property_list(class_name, &props, true);
  460. for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
  461. String name = E->get().name;
  462. if (!(E->get().usage & PROPERTY_USAGE_EDITOR))
  463. continue;
  464. TreeItem *property = property_list->create_item(properties);
  465. property->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
  466. property->set_editable(0, true);
  467. property->set_selectable(0, true);
  468. property->set_checked(0, !edited->is_class_property_disabled(class_name, name));
  469. property->set_text(0, name.capitalize());
  470. property->set_metadata(0, name);
  471. String icon_type = Variant::get_type_name(E->get().type);
  472. property->set_icon(0, EditorNode::get_singleton()->get_class_icon(icon_type));
  473. }
  474. updating_features = false;
  475. }
  476. void EditorFeatureProfileManager::_class_list_item_edited() {
  477. if (updating_features)
  478. return;
  479. TreeItem *item = class_list->get_edited();
  480. if (!item) {
  481. return;
  482. }
  483. bool checked = item->is_checked(0);
  484. Variant md = item->get_metadata(0);
  485. if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) {
  486. String class_selected = md;
  487. edited->set_disable_class(class_selected, !checked);
  488. _save_and_update();
  489. _update_selected_profile();
  490. } else if (md.get_type() == Variant::INT) {
  491. int feature_selected = md;
  492. edited->set_disable_feature(EditorFeatureProfile::Feature(feature_selected), !checked);
  493. _save_and_update();
  494. }
  495. }
  496. void EditorFeatureProfileManager::_property_item_edited() {
  497. if (updating_features)
  498. return;
  499. TreeItem *class_item = class_list->get_selected();
  500. if (!class_item) {
  501. return;
  502. }
  503. Variant md = class_item->get_metadata(0);
  504. if (md.get_type() != Variant::STRING && md.get_type() != Variant::STRING_NAME) {
  505. return;
  506. }
  507. String class_name = md;
  508. TreeItem *item = property_list->get_edited();
  509. if (!item) {
  510. return;
  511. }
  512. bool checked = item->is_checked(0);
  513. md = item->get_metadata(0);
  514. if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) {
  515. String property_selected = md;
  516. edited->set_disable_class_property(class_name, property_selected, !checked);
  517. _save_and_update();
  518. _update_selected_profile();
  519. } else if (md.get_type() == Variant::INT) {
  520. int feature_selected = md;
  521. switch (feature_selected) {
  522. case CLASS_OPTION_DISABLE_EDITOR: {
  523. edited->set_disable_class_editor(class_name, !checked);
  524. _save_and_update();
  525. _update_selected_profile();
  526. } break;
  527. }
  528. }
  529. }
  530. void EditorFeatureProfileManager::_update_selected_profile() {
  531. String class_selected;
  532. int feature_selected = -1;
  533. if (class_list->get_selected()) {
  534. Variant md = class_list->get_selected()->get_metadata(0);
  535. if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) {
  536. class_selected = md;
  537. } else if (md.get_type() == Variant::INT) {
  538. feature_selected = md;
  539. }
  540. }
  541. class_list->clear();
  542. String profile = _get_selected_profile();
  543. if (profile == String()) { //nothing selected, nothing edited
  544. property_list->clear();
  545. edited.unref();
  546. return;
  547. }
  548. if (profile == current_profile) {
  549. edited = current; //reuse current profile (which is what editor uses)
  550. ERR_FAIL_COND(current.is_null()); //nothing selected, current should never be null
  551. } else {
  552. //reload edited, if different from current
  553. edited.instance();
  554. Error err = edited->load_from_file(EditorSettings::get_singleton()->get_feature_profiles_dir().plus_file(profile + ".profile"));
  555. ERR_FAIL_COND_MSG(err != OK, "Error when loading EditorSettings from file '" + EditorSettings::get_singleton()->get_feature_profiles_dir().plus_file(profile + ".profile") + "'.");
  556. }
  557. updating_features = true;
  558. TreeItem *root = class_list->create_item();
  559. TreeItem *features = class_list->create_item(root);
  560. features->set_text(0, TTR("Enabled Features:"));
  561. for (int i = 0; i < EditorFeatureProfile::FEATURE_MAX; i++) {
  562. TreeItem *feature = class_list->create_item(features);
  563. feature->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
  564. feature->set_text(0, TTRGET(EditorFeatureProfile::get_feature_name(EditorFeatureProfile::Feature(i))));
  565. feature->set_selectable(0, true);
  566. feature->set_editable(0, true);
  567. feature->set_metadata(0, i);
  568. if (!edited->is_feature_disabled(EditorFeatureProfile::Feature(i))) {
  569. feature->set_checked(0, true);
  570. }
  571. if (i == feature_selected) {
  572. feature->select(0);
  573. }
  574. }
  575. TreeItem *classes = class_list->create_item(root);
  576. classes->set_text(0, TTR("Enabled Classes:"));
  577. _fill_classes_from(classes, "Node", class_selected);
  578. _fill_classes_from(classes, "Resource", class_selected);
  579. updating_features = false;
  580. _class_list_item_selected();
  581. }
  582. void EditorFeatureProfileManager::_import_profiles(const Vector<String> &p_paths) {
  583. //test it first
  584. for (int i = 0; i < p_paths.size(); i++) {
  585. Ref<EditorFeatureProfile> profile;
  586. profile.instance();
  587. Error err = profile->load_from_file(p_paths[i]);
  588. String basefile = p_paths[i].get_file();
  589. if (err != OK) {
  590. EditorNode::get_singleton()->show_warning(vformat(TTR("File '%s' format is invalid, import aborted."), basefile));
  591. return;
  592. }
  593. String dst_file = EditorSettings::get_singleton()->get_feature_profiles_dir().plus_file(basefile);
  594. if (FileAccess::exists(dst_file)) {
  595. EditorNode::get_singleton()->show_warning(vformat(TTR("Profile '%s' already exists. Remove it first before importing, import aborted."), basefile.get_basename()));
  596. return;
  597. }
  598. }
  599. //do it second
  600. for (int i = 0; i < p_paths.size(); i++) {
  601. Ref<EditorFeatureProfile> profile;
  602. profile.instance();
  603. Error err = profile->load_from_file(p_paths[i]);
  604. ERR_CONTINUE(err != OK);
  605. String basefile = p_paths[i].get_file();
  606. String dst_file = EditorSettings::get_singleton()->get_feature_profiles_dir().plus_file(basefile);
  607. profile->save_to_file(dst_file);
  608. }
  609. _update_profile_list();
  610. }
  611. void EditorFeatureProfileManager::_export_profile(const String &p_path) {
  612. ERR_FAIL_COND(edited.is_null());
  613. Error err = edited->save_to_file(p_path);
  614. if (err != OK) {
  615. EditorNode::get_singleton()->show_warning(vformat(TTR("Error saving profile to path: '%s'."), p_path));
  616. }
  617. }
  618. void EditorFeatureProfileManager::_save_and_update() {
  619. String edited_path = _get_selected_profile();
  620. ERR_FAIL_COND(edited_path == String());
  621. ERR_FAIL_COND(edited.is_null());
  622. edited->save_to_file(EditorSettings::get_singleton()->get_feature_profiles_dir().plus_file(edited_path + ".profile"));
  623. if (edited == current) {
  624. update_timer->start();
  625. }
  626. }
  627. void EditorFeatureProfileManager::_emit_current_profile_changed() {
  628. emit_signal("current_feature_profile_changed");
  629. }
  630. void EditorFeatureProfileManager::notify_changed() {
  631. _emit_current_profile_changed();
  632. }
  633. Ref<EditorFeatureProfile> EditorFeatureProfileManager::get_current_profile() {
  634. return current;
  635. }
  636. EditorFeatureProfileManager *EditorFeatureProfileManager::singleton = nullptr;
  637. void EditorFeatureProfileManager::_bind_methods() {
  638. ClassDB::bind_method("_update_selected_profile", &EditorFeatureProfileManager::_update_selected_profile);
  639. ADD_SIGNAL(MethodInfo("current_feature_profile_changed"));
  640. }
  641. EditorFeatureProfileManager::EditorFeatureProfileManager() {
  642. VBoxContainer *main_vbc = memnew(VBoxContainer);
  643. add_child(main_vbc);
  644. HBoxContainer *name_hbc = memnew(HBoxContainer);
  645. current_profile_name = memnew(LineEdit);
  646. name_hbc->add_child(current_profile_name);
  647. current_profile_name->set_editable(false);
  648. current_profile_name->set_h_size_flags(Control::SIZE_EXPAND_FILL);
  649. profile_actions[PROFILE_CLEAR] = memnew(Button(TTR("Unset")));
  650. name_hbc->add_child(profile_actions[PROFILE_CLEAR]);
  651. profile_actions[PROFILE_CLEAR]->set_disabled(true);
  652. profile_actions[PROFILE_CLEAR]->connect("pressed", callable_mp(this, &EditorFeatureProfileManager::_profile_action), varray(PROFILE_CLEAR));
  653. main_vbc->add_margin_child(TTR("Current Profile:"), name_hbc);
  654. HBoxContainer *profiles_hbc = memnew(HBoxContainer);
  655. profile_list = memnew(OptionButton);
  656. profile_list->set_h_size_flags(Control::SIZE_EXPAND_FILL);
  657. profiles_hbc->add_child(profile_list);
  658. profile_list->connect("item_selected", callable_mp(this, &EditorFeatureProfileManager::_profile_selected));
  659. profile_actions[PROFILE_SET] = memnew(Button(TTR("Make Current")));
  660. profiles_hbc->add_child(profile_actions[PROFILE_SET]);
  661. profile_actions[PROFILE_SET]->set_disabled(true);
  662. profile_actions[PROFILE_SET]->connect("pressed", callable_mp(this, &EditorFeatureProfileManager::_profile_action), varray(PROFILE_SET));
  663. profile_actions[PROFILE_ERASE] = memnew(Button(TTR("Remove")));
  664. profiles_hbc->add_child(profile_actions[PROFILE_ERASE]);
  665. profile_actions[PROFILE_ERASE]->set_disabled(true);
  666. profile_actions[PROFILE_ERASE]->connect("pressed", callable_mp(this, &EditorFeatureProfileManager::_profile_action), varray(PROFILE_ERASE));
  667. profiles_hbc->add_child(memnew(VSeparator));
  668. profile_actions[PROFILE_NEW] = memnew(Button(TTR("New")));
  669. profiles_hbc->add_child(profile_actions[PROFILE_NEW]);
  670. profile_actions[PROFILE_NEW]->connect("pressed", callable_mp(this, &EditorFeatureProfileManager::_profile_action), varray(PROFILE_NEW));
  671. profiles_hbc->add_child(memnew(VSeparator));
  672. profile_actions[PROFILE_IMPORT] = memnew(Button(TTR("Import")));
  673. profiles_hbc->add_child(profile_actions[PROFILE_IMPORT]);
  674. profile_actions[PROFILE_IMPORT]->connect("pressed", callable_mp(this, &EditorFeatureProfileManager::_profile_action), varray(PROFILE_IMPORT));
  675. profile_actions[PROFILE_EXPORT] = memnew(Button(TTR("Export")));
  676. profiles_hbc->add_child(profile_actions[PROFILE_EXPORT]);
  677. profile_actions[PROFILE_EXPORT]->set_disabled(true);
  678. profile_actions[PROFILE_EXPORT]->connect("pressed", callable_mp(this, &EditorFeatureProfileManager::_profile_action), varray(PROFILE_EXPORT));
  679. main_vbc->add_margin_child(TTR("Available Profiles:"), profiles_hbc);
  680. h_split = memnew(HSplitContainer);
  681. h_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);
  682. main_vbc->add_child(h_split);
  683. VBoxContainer *class_list_vbc = memnew(VBoxContainer);
  684. h_split->add_child(class_list_vbc);
  685. class_list_vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL);
  686. class_list = memnew(Tree);
  687. class_list_vbc->add_margin_child(TTR("Enabled Classes:"), class_list, true);
  688. class_list->set_hide_root(true);
  689. class_list->set_edit_checkbox_cell_only_when_checkbox_is_pressed(true);
  690. class_list->connect("cell_selected", callable_mp(this, &EditorFeatureProfileManager::_class_list_item_selected));
  691. class_list->connect("item_edited", callable_mp(this, &EditorFeatureProfileManager::_class_list_item_edited), varray(), CONNECT_DEFERRED);
  692. VBoxContainer *property_list_vbc = memnew(VBoxContainer);
  693. h_split->add_child(property_list_vbc);
  694. property_list_vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL);
  695. property_list = memnew(Tree);
  696. property_list_vbc->add_margin_child(TTR("Class Options"), property_list, true);
  697. property_list->set_hide_root(true);
  698. property_list->set_hide_folding(true);
  699. property_list->set_edit_checkbox_cell_only_when_checkbox_is_pressed(true);
  700. property_list->connect("item_edited", callable_mp(this, &EditorFeatureProfileManager::_property_item_edited), varray(), CONNECT_DEFERRED);
  701. new_profile_dialog = memnew(ConfirmationDialog);
  702. new_profile_dialog->set_title(TTR("New profile name:"));
  703. new_profile_name = memnew(LineEdit);
  704. new_profile_dialog->add_child(new_profile_name);
  705. new_profile_name->set_custom_minimum_size(Size2(300 * EDSCALE, 1));
  706. add_child(new_profile_dialog);
  707. new_profile_dialog->connect("confirmed", callable_mp(this, &EditorFeatureProfileManager::_create_new_profile));
  708. new_profile_dialog->register_text_enter(new_profile_name);
  709. new_profile_dialog->get_ok()->set_text(TTR("Create"));
  710. erase_profile_dialog = memnew(ConfirmationDialog);
  711. add_child(erase_profile_dialog);
  712. erase_profile_dialog->set_title(TTR("Erase Profile"));
  713. erase_profile_dialog->connect("confirmed", callable_mp(this, &EditorFeatureProfileManager::_erase_selected_profile));
  714. import_profiles = memnew(EditorFileDialog);
  715. add_child(import_profiles);
  716. import_profiles->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES);
  717. import_profiles->add_filter("*.profile; " + TTR("Godot Feature Profile"));
  718. import_profiles->connect("files_selected", callable_mp(this, &EditorFeatureProfileManager::_import_profiles));
  719. import_profiles->set_title(TTR("Import Profile(s)"));
  720. import_profiles->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
  721. export_profile = memnew(EditorFileDialog);
  722. add_child(export_profile);
  723. export_profile->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
  724. export_profile->add_filter("*.profile; " + TTR("Godot Feature Profile"));
  725. export_profile->connect("file_selected", callable_mp(this, &EditorFeatureProfileManager::_export_profile));
  726. export_profile->set_title(TTR("Export Profile"));
  727. export_profile->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
  728. set_title(TTR("Manage Editor Feature Profiles"));
  729. EDITOR_DEF("_default_feature_profile", "");
  730. update_timer = memnew(Timer);
  731. update_timer->set_wait_time(1); //wait a second before updating editor
  732. add_child(update_timer);
  733. update_timer->connect("timeout", callable_mp(this, &EditorFeatureProfileManager::_emit_current_profile_changed));
  734. update_timer->set_one_shot(true);
  735. updating_features = false;
  736. singleton = this;
  737. }