groups_editor.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940
  1. /**************************************************************************/
  2. /* groups_editor.cpp */
  3. /**************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /**************************************************************************/
  8. /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
  9. /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /**************************************************************************/
  30. #include "groups_editor.h"
  31. #include "editor/docks/scene_tree_dock.h"
  32. #include "editor/editor_node.h"
  33. #include "editor/editor_string_names.h"
  34. #include "editor/editor_undo_redo_manager.h"
  35. #include "editor/gui/editor_validation_panel.h"
  36. #include "editor/settings/editor_settings.h"
  37. #include "editor/settings/project_settings_editor.h"
  38. #include "editor/themes/editor_scale.h"
  39. #include "scene/gui/box_container.h"
  40. #include "scene/gui/check_button.h"
  41. #include "scene/gui/grid_container.h"
  42. #include "scene/gui/label.h"
  43. #include "scene/resources/packed_scene.h"
  44. static bool can_edit(Node *p_node, const String &p_group) {
  45. Node *n = p_node;
  46. bool can_edit = true;
  47. while (n) {
  48. Ref<SceneState> ss = (n == EditorNode::get_singleton()->get_edited_scene()) ? n->get_scene_inherited_state() : n->get_scene_instance_state();
  49. if (ss.is_valid()) {
  50. int path = ss->find_node_by_path(n->get_path_to(p_node));
  51. if (path != -1) {
  52. if (ss->is_node_in_group(path, p_group)) {
  53. can_edit = false;
  54. break;
  55. }
  56. }
  57. }
  58. n = n->get_owner();
  59. }
  60. return can_edit;
  61. }
  62. struct _GroupInfoComparator {
  63. bool operator()(const Node::GroupInfo &p_a, const Node::GroupInfo &p_b) const {
  64. return p_a.name.operator String() < p_b.name.operator String();
  65. }
  66. };
  67. void GroupsEditor::_add_scene_group(const String &p_name) {
  68. scene_groups[p_name] = true;
  69. }
  70. void GroupsEditor::_remove_scene_group(const String &p_name) {
  71. scene_groups.erase(p_name);
  72. ProjectSettingsEditor::get_singleton()->get_group_settings()->remove_node_references(scene_root_node, p_name);
  73. }
  74. void GroupsEditor::_rename_scene_group(const String &p_old_name, const String &p_new_name) {
  75. scene_groups[p_new_name] = scene_groups[p_old_name];
  76. scene_groups.erase(p_old_name);
  77. ProjectSettingsEditor::get_singleton()->get_group_settings()->rename_node_references(scene_root_node, p_old_name, p_new_name);
  78. }
  79. void GroupsEditor::_set_group_checked(const String &p_name, bool p_checked) {
  80. TreeItem *ti = tree->get_item_with_text(p_name);
  81. if (!ti) {
  82. return;
  83. }
  84. ti->set_checked(0, p_checked);
  85. }
  86. void GroupsEditor::_add_to_group(const StringName &p_name, bool p_persist, const Array &p_nodes) {
  87. for (const Variant &v : p_nodes) {
  88. Node *node = Object::cast_to<Node>(v.get_validated_object());
  89. if (node) {
  90. node->add_to_group(p_name, p_persist);
  91. }
  92. }
  93. }
  94. void GroupsEditor::_remove_from_group(const StringName &p_name, const Array &p_nodes) {
  95. for (const Variant &v : p_nodes) {
  96. Node *node = Object::cast_to<Node>(v.get_validated_object());
  97. if (node) {
  98. node->remove_from_group(p_name);
  99. }
  100. }
  101. }
  102. void GroupsEditor::_get_group_mask(const StringName &p_name, Array &r_nodes, bool p_invert) {
  103. for (Node *p_node : selection) {
  104. if (p_invert != p_node->is_in_group(p_name)) {
  105. r_nodes.push_back(p_node);
  106. }
  107. }
  108. }
  109. bool GroupsEditor::_can_edit(const StringName &p_group) {
  110. for (Node *p_node : selection) {
  111. if (!can_edit(p_node, p_group)) {
  112. return false;
  113. }
  114. }
  115. return true;
  116. }
  117. bool GroupsEditor::_has_group(const String &p_name) {
  118. return global_groups.has(p_name) || scene_groups.has(p_name);
  119. }
  120. void GroupsEditor::_modify_group(Object *p_item, int p_column, int p_id, MouseButton p_mouse_button) {
  121. if (p_mouse_button != MouseButton::LEFT) {
  122. return;
  123. }
  124. if (selection.is_empty()) {
  125. return;
  126. }
  127. TreeItem *ti = Object::cast_to<TreeItem>(p_item);
  128. if (!ti) {
  129. return;
  130. }
  131. if (p_id == COPY_GROUP) {
  132. DisplayServer::get_singleton()->clipboard_set(ti->get_text(p_column));
  133. }
  134. }
  135. void GroupsEditor::_load_scene_groups(Node *p_node) {
  136. List<Node::GroupInfo> groups;
  137. p_node->get_groups(&groups);
  138. for (const GroupInfo &gi : groups) {
  139. if (!gi.persistent) {
  140. continue;
  141. }
  142. if (global_groups.has(gi.name)) {
  143. continue;
  144. }
  145. bool is_editable = can_edit(p_node, gi.name);
  146. if (scene_groups.has(gi.name)) {
  147. scene_groups[gi.name] = scene_groups[gi.name] && is_editable;
  148. } else {
  149. scene_groups[gi.name] = is_editable;
  150. }
  151. }
  152. for (int i = 0; i < p_node->get_child_count(); i++) {
  153. _load_scene_groups(p_node->get_child(i));
  154. }
  155. }
  156. void GroupsEditor::_update_groups() {
  157. if (!is_visible_in_tree()) {
  158. groups_dirty = true;
  159. return;
  160. }
  161. if (updating_groups) {
  162. return;
  163. }
  164. updating_groups = true;
  165. global_groups = ProjectSettings::get_singleton()->get_global_groups_list();
  166. _load_scene_groups(scene_root_node);
  167. for (HashMap<StringName, bool>::Iterator E = scene_groups.begin(); E;) {
  168. HashMap<StringName, bool>::Iterator next = E;
  169. ++next;
  170. if (global_groups.has(E->key)) {
  171. scene_groups.erase(E->key);
  172. }
  173. E = next;
  174. }
  175. updating_groups = false;
  176. }
  177. void GroupsEditor::_update_tree() {
  178. if (!is_visible_in_tree()) {
  179. groups_dirty = true;
  180. return;
  181. }
  182. if (selection.is_empty()) {
  183. return;
  184. }
  185. if (updating_tree) {
  186. return;
  187. }
  188. updating_tree = true;
  189. tree->clear();
  190. List<Node::GroupInfo> groups;
  191. for (Node *p_node : selection) {
  192. p_node->get_groups(&groups);
  193. }
  194. groups.sort_custom<_GroupInfoComparator>();
  195. List<StringName> current_groups;
  196. for (const Node::GroupInfo &gi : groups) {
  197. current_groups.push_back(gi.name);
  198. }
  199. TreeItem *root = tree->create_item();
  200. TreeItem *local_root = tree->create_item(root);
  201. local_root->set_text(0, TTR("Scene Groups"));
  202. local_root->set_icon(0, get_editor_theme_icon(SNAME("PackedScene")));
  203. local_root->set_custom_bg_color(0, get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor)));
  204. local_root->set_custom_stylebox(0, get_theme_stylebox(SNAME("prop_subsection_stylebox"), EditorStringName(Editor)));
  205. local_root->set_selectable(0, false);
  206. List<StringName> scene_keys;
  207. for (const KeyValue<StringName, bool> &E : scene_groups) {
  208. scene_keys.push_back(E.key);
  209. }
  210. scene_keys.sort_custom<NoCaseComparator>();
  211. for (const StringName &E : scene_keys) {
  212. if (!filter->get_text().is_subsequence_ofn(E)) {
  213. continue;
  214. }
  215. TreeItem *item = tree->create_item(local_root);
  216. item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
  217. item->set_editable(0, _can_edit(E));
  218. item->set_checked(0, current_groups.find(E) != nullptr);
  219. item->set_text(0, E);
  220. item->set_meta("__local", true);
  221. item->set_meta("__name", E);
  222. item->set_meta("__description", "");
  223. if (!scene_groups[E]) {
  224. item->add_button(0, get_editor_theme_icon(SNAME("Lock")), -1, true, TTR("This group belongs to another scene and can't be edited."));
  225. }
  226. item->add_button(0, get_editor_theme_icon(SNAME("ActionCopy")), COPY_GROUP, false, TTR("Copy group name to clipboard."));
  227. }
  228. List<StringName> keys;
  229. for (const KeyValue<StringName, String> &E : global_groups) {
  230. keys.push_back(E.key);
  231. }
  232. keys.sort_custom<NoCaseComparator>();
  233. TreeItem *global_root = tree->create_item(root);
  234. global_root->set_text(0, TTR("Global Groups"));
  235. global_root->set_icon(0, get_editor_theme_icon(SNAME("Environment")));
  236. global_root->set_custom_bg_color(0, get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor)));
  237. global_root->set_custom_stylebox(0, get_theme_stylebox(SNAME("prop_subsection_stylebox"), EditorStringName(Editor)));
  238. global_root->set_selectable(0, false);
  239. for (const StringName &E : keys) {
  240. if (!filter->get_text().is_subsequence_ofn(E)) {
  241. continue;
  242. }
  243. TreeItem *item = tree->create_item(global_root);
  244. item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
  245. item->set_editable(0, _can_edit(E));
  246. item->set_checked(0, current_groups.find(E) != nullptr);
  247. item->set_text(0, E);
  248. item->set_meta("__local", false);
  249. item->set_meta("__name", E);
  250. item->set_meta("__description", global_groups[E]);
  251. if (!global_groups[E].is_empty()) {
  252. item->set_tooltip_text(0, vformat("%s\n\n%s", E, global_groups[E]));
  253. }
  254. item->add_button(0, get_editor_theme_icon(SNAME("ActionCopy")), COPY_GROUP, false, TTR("Copy group name to clipboard."));
  255. }
  256. updating_tree = false;
  257. }
  258. void GroupsEditor::_queue_update_groups_and_tree() {
  259. if (update_groups_and_tree_queued) {
  260. return;
  261. }
  262. update_groups_and_tree_queued = true;
  263. callable_mp(this, &GroupsEditor::_update_groups_and_tree).call_deferred();
  264. }
  265. void GroupsEditor::_update_groups_and_tree() {
  266. update_groups_and_tree_queued = false;
  267. // The scene_root_node could be unset before we actually run this code because this is queued with call_deferred().
  268. // In that case NOTIFICATION_VISIBILITY_CHANGED will call this function again soon.
  269. if (!scene_root_node) {
  270. return;
  271. }
  272. _update_groups();
  273. _update_tree();
  274. }
  275. void GroupsEditor::_update_scene_groups(const ObjectID &p_id) {
  276. HashMap<ObjectID, HashMap<StringName, bool>>::Iterator I = scene_groups_cache.find(p_id);
  277. if (I) {
  278. scene_groups = I->value;
  279. scene_groups_cache.remove(I);
  280. } else {
  281. scene_groups = HashMap<StringName, bool>();
  282. }
  283. }
  284. void GroupsEditor::_cache_scene_groups(const ObjectID &p_id) {
  285. const int edited_scene_count = EditorNode::get_editor_data().get_edited_scene_count();
  286. for (int i = 0; i < edited_scene_count; i++) {
  287. Node *edited_scene_root = EditorNode::get_editor_data().get_edited_scene_root(i);
  288. if (edited_scene_root && p_id == edited_scene_root->get_instance_id()) {
  289. scene_groups_cache[p_id] = scene_groups_for_caching;
  290. break;
  291. }
  292. }
  293. }
  294. void GroupsEditor::set_selection(const Vector<Node *> &p_nodes) {
  295. if (p_nodes.is_empty()) {
  296. holder->hide();
  297. select_a_node->show();
  298. selection.clear();
  299. return;
  300. }
  301. selection = p_nodes;
  302. holder->show();
  303. select_a_node->hide();
  304. if (scene_tree->get_edited_scene_root() != scene_root_node) {
  305. scene_root_node = scene_tree->get_edited_scene_root();
  306. _update_scene_groups(scene_root_node->get_instance_id());
  307. _update_groups();
  308. }
  309. _update_tree();
  310. }
  311. void GroupsEditor::_item_edited() {
  312. TreeItem *ti = tree->get_edited();
  313. if (!ti) {
  314. return;
  315. }
  316. String name = ti->get_text(0);
  317. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  318. if (ti->is_checked(0)) {
  319. undo_redo->create_action(TTR("Add to Group"));
  320. Array nodes;
  321. _get_group_mask(name, nodes, true);
  322. undo_redo->add_do_method(this, "_add_to_group", name, true, nodes);
  323. undo_redo->add_undo_method(this, "_remove_from_group", name, nodes);
  324. undo_redo->add_do_method(this, "_set_group_checked", name, true);
  325. undo_redo->add_undo_method(this, "_set_group_checked", name, false);
  326. // To force redraw of scene tree.
  327. undo_redo->add_do_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree");
  328. undo_redo->add_undo_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree");
  329. undo_redo->commit_action();
  330. } else {
  331. undo_redo->create_action(TTR("Remove from Group"));
  332. Array nodes;
  333. _get_group_mask(name, nodes, false);
  334. undo_redo->add_do_method(this, "_remove_from_group", name, nodes);
  335. undo_redo->add_undo_method(this, "_add_to_group", name, true, nodes);
  336. undo_redo->add_do_method(this, "_set_group_checked", name, false);
  337. undo_redo->add_undo_method(this, "_set_group_checked", name, true);
  338. // To force redraw of scene tree.
  339. undo_redo->add_do_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree");
  340. undo_redo->add_undo_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree");
  341. undo_redo->commit_action();
  342. }
  343. }
  344. void GroupsEditor::_notification(int p_what) {
  345. switch (p_what) {
  346. case NOTIFICATION_READY: {
  347. get_tree()->connect("node_added", callable_mp(this, &GroupsEditor::_load_scene_groups));
  348. get_tree()->connect("node_removed", callable_mp(this, &GroupsEditor::_node_removed));
  349. } break;
  350. case NOTIFICATION_THEME_CHANGED: {
  351. filter->set_right_icon(get_editor_theme_icon("Search"));
  352. add->set_button_icon(get_editor_theme_icon("Add"));
  353. _update_tree();
  354. } break;
  355. case NOTIFICATION_VISIBILITY_CHANGED: {
  356. if (groups_dirty && is_visible_in_tree()) {
  357. groups_dirty = false;
  358. _update_groups_and_tree();
  359. }
  360. } break;
  361. }
  362. }
  363. void GroupsEditor::_menu_id_pressed(int p_id) {
  364. TreeItem *ti = tree->get_selected();
  365. if (!ti) {
  366. return;
  367. }
  368. bool is_local = ti->get_meta("__local");
  369. String group_name = ti->get_meta("__name");
  370. switch (p_id) {
  371. case DELETE_GROUP: {
  372. if (!is_local || scene_groups[group_name]) {
  373. _show_remove_group_dialog();
  374. }
  375. } break;
  376. case RENAME_GROUP: {
  377. if (!is_local || scene_groups[group_name]) {
  378. _show_rename_group_dialog();
  379. }
  380. } break;
  381. case CONVERT_GROUP: {
  382. String description = ti->get_meta("__description");
  383. String property_name = GLOBAL_GROUP_PREFIX + group_name;
  384. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  385. if (is_local) {
  386. undo_redo->create_action(TTR("Convert to Global Group"));
  387. undo_redo->add_do_property(ProjectSettings::get_singleton(), property_name, "");
  388. undo_redo->add_undo_property(ProjectSettings::get_singleton(), property_name, Variant());
  389. undo_redo->add_do_method(ProjectSettings::get_singleton(), "save");
  390. undo_redo->add_undo_method(ProjectSettings::get_singleton(), "save");
  391. undo_redo->add_undo_method(this, "_add_scene_group", group_name);
  392. undo_redo->add_do_method(this, "_update_groups_and_tree");
  393. undo_redo->add_undo_method(this, "_update_groups_and_tree");
  394. undo_redo->commit_action();
  395. } else {
  396. undo_redo->create_action(TTR("Convert to Scene Group"));
  397. undo_redo->add_do_property(ProjectSettings::get_singleton(), property_name, Variant());
  398. undo_redo->add_undo_property(ProjectSettings::get_singleton(), property_name, description);
  399. undo_redo->add_do_method(ProjectSettings::get_singleton(), "save");
  400. undo_redo->add_undo_method(ProjectSettings::get_singleton(), "save");
  401. undo_redo->add_do_method(this, "_add_scene_group", group_name);
  402. undo_redo->add_do_method(this, "_update_groups_and_tree");
  403. undo_redo->add_undo_method(this, "_update_groups_and_tree");
  404. undo_redo->commit_action();
  405. }
  406. } break;
  407. }
  408. }
  409. void GroupsEditor::_item_mouse_selected(const Vector2 &p_pos, MouseButton p_mouse_button) {
  410. TreeItem *ti = tree->get_selected();
  411. if (!ti) {
  412. return;
  413. }
  414. if (p_mouse_button == MouseButton::LEFT) {
  415. callable_mp(this, &GroupsEditor::_item_edited).call_deferred();
  416. } else if (p_mouse_button == MouseButton::RIGHT) {
  417. // Restore the previous state after clicking RMB.
  418. if (ti->is_editable(0)) {
  419. ti->set_checked(0, !ti->is_checked(0));
  420. }
  421. menu->clear();
  422. if (ti->get_meta("__local")) {
  423. menu->add_icon_item(get_editor_theme_icon(SNAME("Environment")), TTR("Convert to Global Group"), CONVERT_GROUP);
  424. } else {
  425. menu->add_icon_item(get_editor_theme_icon(SNAME("PackedScene")), TTR("Convert to Scene Group"), CONVERT_GROUP);
  426. }
  427. String group_name = ti->get_meta("__name");
  428. if (global_groups.has(group_name) || scene_groups[group_name]) {
  429. menu->add_separator();
  430. menu->add_icon_shortcut(get_editor_theme_icon(SNAME("Rename")), ED_GET_SHORTCUT("groups_editor/rename"), RENAME_GROUP);
  431. menu->add_icon_shortcut(get_editor_theme_icon(SNAME("Remove")), ED_GET_SHORTCUT("groups_editor/delete"), DELETE_GROUP);
  432. }
  433. menu->set_position(tree->get_screen_position() + p_pos);
  434. menu->reset_size();
  435. menu->popup();
  436. }
  437. }
  438. void GroupsEditor::_confirm_add() {
  439. String name = add_group_name->get_text().strip_edges();
  440. String description = add_group_description->get_text();
  441. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  442. undo_redo->create_action(TTR("Add to Group"));
  443. Array nodes;
  444. _get_group_mask(name, nodes, true);
  445. undo_redo->add_do_method(this, "_add_to_group", name, true, nodes);
  446. undo_redo->add_undo_method(this, "_remove_from_group", name, nodes);
  447. bool is_local = !global_group_button->is_pressed();
  448. if (is_local) {
  449. undo_redo->add_do_method(this, "_add_scene_group", name);
  450. undo_redo->add_undo_method(this, "_remove_scene_group", name);
  451. } else {
  452. String property_name = GLOBAL_GROUP_PREFIX + name;
  453. undo_redo->add_do_property(ProjectSettings::get_singleton(), property_name, description);
  454. undo_redo->add_undo_property(ProjectSettings::get_singleton(), property_name, Variant());
  455. undo_redo->add_do_method(ProjectSettings::get_singleton(), "save");
  456. undo_redo->add_undo_method(ProjectSettings::get_singleton(), "save");
  457. undo_redo->add_do_method(this, "_update_groups");
  458. undo_redo->add_undo_method(this, "_update_groups");
  459. }
  460. undo_redo->add_do_method(this, "_update_tree");
  461. undo_redo->add_undo_method(this, "_update_tree");
  462. // To force redraw of scene tree.
  463. undo_redo->add_do_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree");
  464. undo_redo->add_undo_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree");
  465. undo_redo->commit_action();
  466. tree->grab_focus(true);
  467. }
  468. void GroupsEditor::_confirm_rename() {
  469. TreeItem *ti = tree->get_selected();
  470. if (!ti) {
  471. return;
  472. }
  473. String old_name = ti->get_meta("__name");
  474. String new_name = rename_group->get_text().strip_edges();
  475. if (old_name == new_name) {
  476. return;
  477. }
  478. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  479. undo_redo->create_action(TTR("Rename Group"));
  480. if (!global_groups.has(old_name)) {
  481. undo_redo->add_do_method(this, "_rename_scene_group", old_name, new_name);
  482. undo_redo->add_undo_method(this, "_rename_scene_group", new_name, old_name);
  483. } else {
  484. String property_new_name = GLOBAL_GROUP_PREFIX + new_name;
  485. String property_old_name = GLOBAL_GROUP_PREFIX + old_name;
  486. String description = ti->get_meta("__description");
  487. undo_redo->add_do_property(ProjectSettings::get_singleton(), property_new_name, description);
  488. undo_redo->add_undo_property(ProjectSettings::get_singleton(), property_new_name, Variant());
  489. undo_redo->add_do_property(ProjectSettings::get_singleton(), property_old_name, Variant());
  490. undo_redo->add_undo_property(ProjectSettings::get_singleton(), property_old_name, description);
  491. if (rename_check_box->is_pressed()) {
  492. undo_redo->add_do_method(ProjectSettingsEditor::get_singleton()->get_group_settings(), "rename_references", old_name, new_name);
  493. undo_redo->add_undo_method(ProjectSettingsEditor::get_singleton()->get_group_settings(), "rename_references", new_name, old_name);
  494. }
  495. undo_redo->add_do_method(ProjectSettings::get_singleton(), "save");
  496. undo_redo->add_undo_method(ProjectSettings::get_singleton(), "save");
  497. undo_redo->add_do_method(this, "_update_groups");
  498. undo_redo->add_undo_method(this, "_update_groups");
  499. }
  500. undo_redo->add_do_method(this, "_update_tree");
  501. undo_redo->add_undo_method(this, "_update_tree");
  502. undo_redo->commit_action();
  503. tree->grab_focus(true);
  504. }
  505. void GroupsEditor::_confirm_delete() {
  506. TreeItem *ti = tree->get_selected();
  507. if (!ti) {
  508. return;
  509. }
  510. String name = ti->get_meta("__name");
  511. bool is_local = ti->get_meta("__local");
  512. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  513. undo_redo->create_action(TTR("Remove Group"));
  514. if (is_local) {
  515. undo_redo->add_do_method(this, "_remove_scene_group", name);
  516. undo_redo->add_undo_method(this, "_add_scene_group", name);
  517. } else {
  518. String property_name = GLOBAL_GROUP_PREFIX + name;
  519. String description = ti->get_meta("__description");
  520. undo_redo->add_do_property(ProjectSettings::get_singleton(), property_name, Variant());
  521. undo_redo->add_undo_property(ProjectSettings::get_singleton(), property_name, description);
  522. if (remove_check_box->is_pressed()) {
  523. undo_redo->add_do_method(ProjectSettingsEditor::get_singleton()->get_group_settings(), "remove_references", name);
  524. }
  525. undo_redo->add_do_method(ProjectSettings::get_singleton(), "save");
  526. undo_redo->add_undo_method(ProjectSettings::get_singleton(), "save");
  527. undo_redo->add_do_method(this, "_update_groups");
  528. undo_redo->add_undo_method(this, "_update_groups");
  529. }
  530. undo_redo->add_do_method(this, "_update_tree");
  531. undo_redo->add_undo_method(this, "_update_tree");
  532. undo_redo->commit_action();
  533. tree->grab_focus(true);
  534. }
  535. void GroupsEditor::_show_add_group_dialog() {
  536. if (!add_group_dialog) {
  537. add_group_dialog = memnew(ConfirmationDialog);
  538. add_group_dialog->set_title(TTR("Create New Group"));
  539. add_group_dialog->connect(SceneStringName(confirmed), callable_mp(this, &GroupsEditor::_confirm_add));
  540. VBoxContainer *vbc = memnew(VBoxContainer);
  541. add_group_dialog->add_child(vbc);
  542. GridContainer *gc = memnew(GridContainer);
  543. gc->set_columns(2);
  544. vbc->add_child(gc);
  545. Label *label_name = memnew(Label(TTR("Name:")));
  546. label_name->set_h_size_flags(SIZE_SHRINK_BEGIN);
  547. gc->add_child(label_name);
  548. HBoxContainer *hbc = memnew(HBoxContainer);
  549. hbc->set_h_size_flags(SIZE_EXPAND_FILL);
  550. gc->add_child(hbc);
  551. add_group_name = memnew(LineEdit);
  552. add_group_name->set_custom_minimum_size(Size2(200 * EDSCALE, 0));
  553. add_group_name->set_h_size_flags(SIZE_EXPAND_FILL);
  554. add_group_name->set_accessibility_name(TTRC("Name:"));
  555. hbc->add_child(add_group_name);
  556. global_group_button = memnew(CheckButton);
  557. global_group_button->set_text(TTR("Global"));
  558. hbc->add_child(global_group_button);
  559. Label *label_description = memnew(Label(TTR("Description:")));
  560. label_name->set_h_size_flags(SIZE_SHRINK_BEGIN);
  561. gc->add_child(label_description);
  562. add_group_description = memnew(LineEdit);
  563. add_group_description->set_h_size_flags(SIZE_EXPAND_FILL);
  564. add_group_description->set_editable(false);
  565. add_group_description->set_accessibility_name(TTRC("Description:"));
  566. gc->add_child(add_group_description);
  567. global_group_button->connect(SceneStringName(toggled), callable_mp(add_group_description, &LineEdit::set_editable));
  568. add_group_dialog->register_text_enter(add_group_name);
  569. add_group_dialog->register_text_enter(add_group_description);
  570. add_validation_panel = memnew(EditorValidationPanel);
  571. add_validation_panel->add_line(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Group name is valid."));
  572. add_validation_panel->set_update_callback(callable_mp(this, &GroupsEditor::_check_add));
  573. add_validation_panel->set_accept_button(add_group_dialog->get_ok_button());
  574. add_group_name->connect(SceneStringName(text_changed), callable_mp(add_validation_panel, &EditorValidationPanel::update).unbind(1));
  575. vbc->add_child(add_validation_panel);
  576. add_child(add_group_dialog);
  577. }
  578. add_group_name->clear();
  579. add_group_description->clear();
  580. global_group_button->set_pressed(false);
  581. add_validation_panel->update();
  582. add_group_dialog->popup_centered();
  583. add_group_name->grab_focus();
  584. }
  585. void GroupsEditor::_show_rename_group_dialog() {
  586. if (!rename_group_dialog) {
  587. rename_group_dialog = memnew(ConfirmationDialog);
  588. rename_group_dialog->set_title(TTR("Rename Group"));
  589. rename_group_dialog->connect(SceneStringName(confirmed), callable_mp(this, &GroupsEditor::_confirm_rename));
  590. VBoxContainer *vbc = memnew(VBoxContainer);
  591. rename_group_dialog->add_child(vbc);
  592. HBoxContainer *hbc = memnew(HBoxContainer);
  593. hbc->add_child(memnew(Label(TTR("Name:"))));
  594. rename_group = memnew(LineEdit);
  595. rename_group->set_custom_minimum_size(Size2(300 * EDSCALE, 1));
  596. hbc->add_child(rename_group);
  597. vbc->add_child(hbc);
  598. rename_group_dialog->register_text_enter(rename_group);
  599. rename_validation_panel = memnew(EditorValidationPanel);
  600. rename_validation_panel->add_line(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Group name is valid."));
  601. rename_validation_panel->set_update_callback(callable_mp(this, &GroupsEditor::_check_rename));
  602. rename_validation_panel->set_accept_button(rename_group_dialog->get_ok_button());
  603. rename_group->connect(SceneStringName(text_changed), callable_mp(rename_validation_panel, &EditorValidationPanel::update).unbind(1));
  604. vbc->add_child(rename_validation_panel);
  605. rename_check_box = memnew(CheckBox);
  606. rename_check_box->set_text(TTR("Rename references in all scenes"));
  607. vbc->add_child(rename_check_box);
  608. add_child(rename_group_dialog);
  609. }
  610. TreeItem *ti = tree->get_selected();
  611. if (!ti) {
  612. return;
  613. }
  614. bool is_global = !ti->get_meta("__local");
  615. rename_check_box->set_visible(is_global);
  616. rename_check_box->set_pressed(false);
  617. String name = ti->get_meta("__name");
  618. rename_group->set_text(name);
  619. rename_group_dialog->set_meta("__name", name);
  620. rename_validation_panel->update();
  621. rename_group_dialog->reset_size();
  622. rename_group_dialog->popup_centered();
  623. rename_group->select_all();
  624. rename_group->grab_focus();
  625. }
  626. void GroupsEditor::_show_remove_group_dialog() {
  627. if (!remove_group_dialog) {
  628. remove_group_dialog = memnew(ConfirmationDialog);
  629. remove_group_dialog->connect(SceneStringName(confirmed), callable_mp(this, &GroupsEditor::_confirm_delete));
  630. VBoxContainer *vbox = memnew(VBoxContainer);
  631. remove_label = memnew(Label);
  632. remove_label->set_focus_mode(FOCUS_ACCESSIBILITY);
  633. vbox->add_child(remove_label);
  634. remove_check_box = memnew(CheckBox);
  635. remove_check_box->set_text(TTR("Delete references from all scenes"));
  636. vbox->add_child(remove_check_box);
  637. remove_group_dialog->add_child(vbox);
  638. add_child(remove_group_dialog);
  639. }
  640. TreeItem *ti = tree->get_selected();
  641. if (!ti) {
  642. return;
  643. }
  644. bool is_global = !ti->get_meta("__local");
  645. remove_check_box->set_visible(is_global);
  646. remove_check_box->set_pressed(false);
  647. remove_label->set_text(vformat(TTR("Delete group \"%s\" and all its references?"), ti->get_text(0)));
  648. remove_group_dialog->reset_size();
  649. remove_group_dialog->popup_centered();
  650. }
  651. void GroupsEditor::_check_add() {
  652. String group_name = add_group_name->get_text().strip_edges();
  653. _validate_name(group_name, add_validation_panel);
  654. }
  655. void GroupsEditor::_check_rename() {
  656. String group_name = rename_group->get_text().strip_edges();
  657. String old_name = rename_group_dialog->get_meta("__name");
  658. if (group_name == old_name) {
  659. return;
  660. }
  661. _validate_name(group_name, rename_validation_panel);
  662. }
  663. void GroupsEditor::_validate_name(const String &p_name, EditorValidationPanel *p_validation_panel) {
  664. if (p_name.is_empty()) {
  665. p_validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Group can't be empty."), EditorValidationPanel::MSG_ERROR);
  666. } else if (_has_group(p_name)) {
  667. p_validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Group already exists."), EditorValidationPanel::MSG_ERROR);
  668. }
  669. }
  670. void GroupsEditor::_groups_gui_input(Ref<InputEvent> p_event) {
  671. Ref<InputEventKey> key = p_event;
  672. if (key.is_valid() && key->is_pressed() && !key->is_echo()) {
  673. if (ED_IS_SHORTCUT("groups_editor/delete", p_event)) {
  674. _menu_id_pressed(DELETE_GROUP);
  675. } else if (ED_IS_SHORTCUT("groups_editor/rename", p_event)) {
  676. _menu_id_pressed(RENAME_GROUP);
  677. } else if (ED_IS_SHORTCUT("editor/open_search", p_event)) {
  678. filter->grab_focus();
  679. filter->select_all();
  680. } else {
  681. return;
  682. }
  683. accept_event();
  684. }
  685. }
  686. void GroupsEditor::_bind_methods() {
  687. ClassDB::bind_method("_update_tree", &GroupsEditor::_update_tree);
  688. ClassDB::bind_method("_update_groups", &GroupsEditor::_update_groups);
  689. ClassDB::bind_method("_update_groups_and_tree", &GroupsEditor::_update_groups_and_tree);
  690. ClassDB::bind_method("_add_scene_group", &GroupsEditor::_add_scene_group);
  691. ClassDB::bind_method("_rename_scene_group", &GroupsEditor::_rename_scene_group);
  692. ClassDB::bind_method("_remove_scene_group", &GroupsEditor::_remove_scene_group);
  693. ClassDB::bind_method("_set_group_checked", &GroupsEditor::_set_group_checked);
  694. ClassDB::bind_method("_add_to_group", &GroupsEditor::_add_to_group);
  695. ClassDB::bind_method("_remove_from_group", &GroupsEditor::_remove_from_group);
  696. }
  697. void GroupsEditor::_node_removed(Node *p_node) {
  698. if (scene_root_node == p_node) {
  699. scene_groups_for_caching = scene_groups;
  700. callable_mp(this, &GroupsEditor::_cache_scene_groups).call_deferred(p_node->get_instance_id());
  701. scene_root_node = nullptr;
  702. }
  703. if (scene_root_node && scene_root_node == p_node->get_owner()) {
  704. _queue_update_groups_and_tree();
  705. }
  706. }
  707. GroupsEditor::GroupsEditor() {
  708. scene_tree = SceneTree::get_singleton();
  709. ED_SHORTCUT("groups_editor/delete", TTRC("Delete"), Key::KEY_DELETE);
  710. ED_SHORTCUT("groups_editor/rename", TTRC("Rename"), Key::F2);
  711. ED_SHORTCUT_OVERRIDE("groups_editor/rename", "macos", Key::ENTER);
  712. holder = memnew(VBoxContainer);
  713. holder->set_v_size_flags(SIZE_EXPAND_FILL);
  714. holder->hide();
  715. add_child(holder);
  716. HBoxContainer *hbc = memnew(HBoxContainer);
  717. holder->add_child(hbc);
  718. add = memnew(Button);
  719. add->set_theme_type_variation("FlatMenuButton");
  720. add->set_tooltip_text(TTR("Add a new group."));
  721. add->connect(SceneStringName(pressed), callable_mp(this, &GroupsEditor::_show_add_group_dialog));
  722. hbc->add_child(add);
  723. filter = memnew(LineEdit);
  724. filter->set_clear_button_enabled(true);
  725. filter->set_placeholder(TTR("Filter Groups"));
  726. filter->set_accessibility_name(TTRC("Filter Groups"));
  727. filter->set_h_size_flags(SIZE_EXPAND_FILL);
  728. filter->connect(SceneStringName(text_changed), callable_mp(this, &GroupsEditor::_update_tree).unbind(1));
  729. hbc->add_child(filter);
  730. tree = memnew(Tree);
  731. tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
  732. tree->set_hide_root(true);
  733. tree->set_v_size_flags(SIZE_EXPAND_FILL);
  734. tree->set_allow_rmb_select(true);
  735. tree->set_select_mode(Tree::SelectMode::SELECT_SINGLE);
  736. tree->connect("button_clicked", callable_mp(this, &GroupsEditor::_modify_group));
  737. tree->connect("item_mouse_selected", callable_mp(this, &GroupsEditor::_item_mouse_selected));
  738. tree->connect(SceneStringName(gui_input), callable_mp(this, &GroupsEditor::_groups_gui_input));
  739. holder->add_child(tree);
  740. menu = memnew(PopupMenu);
  741. menu->connect(SceneStringName(id_pressed), callable_mp(this, &GroupsEditor::_menu_id_pressed));
  742. tree->add_child(menu);
  743. select_a_node = memnew(Label);
  744. select_a_node->set_focus_mode(FOCUS_ACCESSIBILITY);
  745. select_a_node->set_text(TTRC("Select one or more nodes to edit their groups."));
  746. select_a_node->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
  747. select_a_node->set_v_size_flags(SIZE_EXPAND_FILL);
  748. select_a_node->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
  749. select_a_node->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
  750. select_a_node->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);
  751. add_child(select_a_node);
  752. ProjectSettingsEditor::get_singleton()->get_group_settings()->connect("group_changed", callable_mp(this, &GroupsEditor::_update_groups_and_tree));
  753. }