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
31#include "groups_editor.h"
32
33#include "editor/editor_node.h"
34#include "editor/editor_scale.h"
35#include "editor/editor_string_names.h"
36#include "editor/editor_undo_redo_manager.h"
37#include "editor/gui/scene_tree_editor.h"
38#include "editor/scene_tree_dock.h"
39#include "scene/gui/button.h"
40#include "scene/gui/label.h"
41#include "scene/gui/line_edit.h"
42#include "scene/gui/tree.h"
43#include "scene/resources/packed_scene.h"
44
45static bool can_edit(Node *p_node, String p_group) {
46 Node *n = p_node;
47 bool can_edit = true;
48 while (n) {
49 Ref<SceneState> ss = (n == EditorNode::get_singleton()->get_edited_scene()) ? n->get_scene_inherited_state() : n->get_scene_instance_state();
50 if (ss.is_valid()) {
51 int path = ss->find_node_by_path(n->get_path_to(p_node));
52 if (path != -1) {
53 if (ss->is_node_in_group(path, p_group)) {
54 can_edit = false;
55 }
56 }
57 }
58 n = n->get_owner();
59 }
60 return can_edit;
61}
62
63void GroupDialog::_group_selected() {
64 nodes_to_add->clear();
65 add_node_root = nodes_to_add->create_item();
66
67 nodes_to_remove->clear();
68 remove_node_root = nodes_to_remove->create_item();
69
70 if (!groups->is_anything_selected()) {
71 group_empty->hide();
72 return;
73 }
74
75 selected_group = groups->get_selected()->get_text(0);
76 _load_nodes(scene_tree->get_edited_scene_root());
77
78 group_empty->set_visible(!remove_node_root->get_first_child());
79}
80
81void GroupDialog::_load_nodes(Node *p_current) {
82 String item_name = p_current->get_name();
83 if (p_current != scene_tree->get_edited_scene_root()) {
84 item_name = String(p_current->get_parent()->get_name()) + "/" + item_name;
85 }
86
87 bool keep = true;
88 Node *root = scene_tree->get_edited_scene_root();
89 Node *owner = p_current->get_owner();
90 if (owner != root && p_current != root && !owner && !root->is_editable_instance(owner)) {
91 keep = false;
92 }
93
94 TreeItem *node = nullptr;
95 NodePath path = scene_tree->get_edited_scene_root()->get_path_to(p_current);
96 if (keep && p_current->is_in_group(selected_group)) {
97 if (remove_filter->get_text().is_subsequence_ofn(String(p_current->get_name()))) {
98 node = nodes_to_remove->create_item(remove_node_root);
99 keep = true;
100 } else {
101 keep = false;
102 }
103 } else if (keep && add_filter->get_text().is_subsequence_ofn(String(p_current->get_name()))) {
104 node = nodes_to_add->create_item(add_node_root);
105 keep = true;
106 } else {
107 keep = false;
108 }
109
110 if (keep) {
111 node->set_text(0, item_name);
112 node->set_metadata(0, path);
113 node->set_tooltip_text(0, path);
114
115 Ref<Texture2D> icon = EditorNode::get_singleton()->get_object_icon(p_current, "Node");
116 node->set_icon(0, icon);
117
118 if (!can_edit(p_current, selected_group)) {
119 node->set_selectable(0, false);
120 node->set_custom_color(0, groups->get_theme_color(SNAME("disabled_font_color"), EditorStringName(Editor)));
121 }
122 }
123
124 for (int i = 0; i < p_current->get_child_count(); i++) {
125 _load_nodes(p_current->get_child(i));
126 }
127}
128
129void GroupDialog::_add_pressed() {
130 TreeItem *selected = nodes_to_add->get_next_selected(nullptr);
131
132 if (!selected) {
133 return;
134 }
135
136 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
137 undo_redo->create_action(TTR("Add to Group"));
138
139 while (selected) {
140 Node *node = scene_tree->get_edited_scene_root()->get_node(selected->get_metadata(0));
141 undo_redo->add_do_method(node, "add_to_group", selected_group, true);
142 undo_redo->add_undo_method(node, "remove_from_group", selected_group);
143
144 selected = nodes_to_add->get_next_selected(selected);
145 }
146
147 undo_redo->add_do_method(this, "_group_selected");
148 undo_redo->add_undo_method(this, "_group_selected");
149 undo_redo->add_do_method(this, "emit_signal", "group_edited");
150 undo_redo->add_undo_method(this, "emit_signal", "group_edited");
151
152 // To force redraw of scene tree.
153 undo_redo->add_do_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree");
154 undo_redo->add_undo_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree");
155
156 undo_redo->commit_action();
157}
158
159void GroupDialog::_removed_pressed() {
160 TreeItem *selected = nodes_to_remove->get_next_selected(nullptr);
161
162 if (!selected) {
163 return;
164 }
165
166 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
167 undo_redo->create_action(TTR("Remove from Group"));
168
169 while (selected) {
170 Node *node = scene_tree->get_edited_scene_root()->get_node(selected->get_metadata(0));
171 undo_redo->add_do_method(node, "remove_from_group", selected_group);
172 undo_redo->add_undo_method(node, "add_to_group", selected_group, true);
173
174 selected = nodes_to_add->get_next_selected(selected);
175 }
176
177 undo_redo->add_do_method(this, "_group_selected");
178 undo_redo->add_undo_method(this, "_group_selected");
179 undo_redo->add_do_method(this, "emit_signal", "group_edited");
180 undo_redo->add_undo_method(this, "emit_signal", "group_edited");
181
182 // To force redraw of scene tree.
183 undo_redo->add_do_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree");
184 undo_redo->add_undo_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree");
185
186 undo_redo->commit_action();
187}
188
189void GroupDialog::_remove_filter_changed(const String &p_filter) {
190 _group_selected();
191}
192
193void GroupDialog::_add_filter_changed(const String &p_filter) {
194 _group_selected();
195}
196
197void GroupDialog::_add_group_pressed(const String &p_name) {
198 _add_group(add_group_text->get_text());
199 add_group_text->clear();
200}
201
202void GroupDialog::_add_group(String p_name) {
203 if (!is_visible()) {
204 return; // No need to edit the dialog if it's not being used.
205 }
206
207 String name = p_name.strip_edges();
208 if (name.is_empty() || groups->get_item_with_text(name)) {
209 return;
210 }
211
212 TreeItem *new_group = groups->create_item(groups_root);
213 new_group->set_text(0, name);
214 new_group->add_button(0, groups->get_editor_theme_icon(SNAME("Remove")), DELETE_GROUP);
215 new_group->add_button(0, groups->get_editor_theme_icon(SNAME("ActionCopy")), COPY_GROUP);
216 new_group->set_editable(0, true);
217 new_group->select(0);
218 groups->ensure_cursor_is_visible();
219}
220
221void GroupDialog::_add_group_text_changed(const String &p_new_text) {
222 add_group_button->set_disabled(p_new_text.strip_edges().is_empty());
223}
224
225void GroupDialog::_group_renamed() {
226 TreeItem *renamed_group = groups->get_selected();
227 if (!renamed_group) {
228 return;
229 }
230
231 const String name = renamed_group->get_text(0).strip_edges();
232 if (name == selected_group) {
233 return;
234 }
235
236 if (name.is_empty()) {
237 renamed_group->set_text(0, selected_group);
238 error->set_text(TTR("Invalid group name."));
239 error->popup_centered();
240 return;
241 }
242
243 for (TreeItem *E = groups_root->get_first_child(); E; E = E->get_next()) {
244 if (E != renamed_group && E->get_text(0) == name) {
245 renamed_group->set_text(0, selected_group);
246 error->set_text(TTR("Group name already exists."));
247 error->popup_centered();
248 return;
249 }
250 }
251
252 renamed_group->set_text(0, name); // Spaces trimmed.
253
254 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
255 undo_redo->create_action(TTR("Rename Group"));
256
257 List<Node *> nodes;
258 scene_tree->get_nodes_in_group(selected_group, &nodes);
259 bool removed_all = true;
260 for (Node *node : nodes) {
261 if (can_edit(node, selected_group)) {
262 undo_redo->add_do_method(node, "remove_from_group", selected_group);
263 undo_redo->add_undo_method(node, "remove_from_group", name);
264 undo_redo->add_do_method(node, "add_to_group", name, true);
265 undo_redo->add_undo_method(node, "add_to_group", selected_group, true);
266 } else {
267 removed_all = false;
268 }
269 }
270
271 if (!removed_all) {
272 undo_redo->add_do_method(this, "_add_group", selected_group);
273 undo_redo->add_undo_method(this, "_delete_group_item", selected_group);
274 }
275
276 undo_redo->add_do_method(this, "_rename_group_item", selected_group, name);
277 undo_redo->add_undo_method(this, "_rename_group_item", name, selected_group);
278 undo_redo->add_do_method(this, "_group_selected");
279 undo_redo->add_undo_method(this, "_group_selected");
280 undo_redo->add_do_method(this, "emit_signal", "group_edited");
281 undo_redo->add_undo_method(this, "emit_signal", "group_edited");
282
283 undo_redo->commit_action();
284}
285
286void GroupDialog::_rename_group_item(const String &p_old_name, const String &p_new_name) {
287 if (!is_visible()) {
288 return; // No need to edit the dialog if it's not being used.
289 }
290
291 selected_group = p_new_name;
292
293 for (TreeItem *E = groups_root->get_first_child(); E; E = E->get_next()) {
294 if (E->get_text(0) == p_old_name) {
295 E->set_text(0, p_new_name);
296 return;
297 }
298 }
299}
300
301void GroupDialog::_load_groups(Node *p_current) {
302 List<Node::GroupInfo> gi;
303 p_current->get_groups(&gi);
304
305 for (const Node::GroupInfo &E : gi) {
306 if (!E.persistent) {
307 continue;
308 }
309 _add_group(E.name);
310 }
311
312 for (int i = 0; i < p_current->get_child_count(); i++) {
313 _load_groups(p_current->get_child(i));
314 }
315}
316
317void GroupDialog::_modify_group_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button) {
318 if (p_button != MouseButton::LEFT) {
319 return;
320 }
321
322 TreeItem *ti = Object::cast_to<TreeItem>(p_item);
323 if (!ti) {
324 return;
325 }
326
327 switch (p_id) {
328 case DELETE_GROUP: {
329 String name = ti->get_text(0);
330
331 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
332 undo_redo->create_action(TTR("Delete Group"));
333
334 List<Node *> nodes;
335 scene_tree->get_nodes_in_group(name, &nodes);
336 bool removed_all = true;
337 for (Node *E : nodes) {
338 if (can_edit(E, name)) {
339 undo_redo->add_do_method(E, "remove_from_group", name);
340 undo_redo->add_undo_method(E, "add_to_group", name, true);
341 } else {
342 removed_all = false;
343 }
344 }
345
346 if (removed_all) {
347 undo_redo->add_do_method(this, "_delete_group_item", name);
348 undo_redo->add_undo_method(this, "_add_group", name);
349 }
350
351 undo_redo->add_do_method(this, "_group_selected");
352 undo_redo->add_undo_method(this, "_group_selected");
353 undo_redo->add_do_method(this, "emit_signal", "group_edited");
354 undo_redo->add_undo_method(this, "emit_signal", "group_edited");
355
356 // To force redraw of scene tree.
357 undo_redo->add_do_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree");
358 undo_redo->add_undo_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree");
359
360 undo_redo->commit_action();
361 } break;
362 case COPY_GROUP: {
363 DisplayServer::get_singleton()->clipboard_set(ti->get_text(p_column));
364 } break;
365 }
366}
367
368void GroupDialog::_delete_group_item(const String &p_name) {
369 if (!is_visible()) {
370 return; // No need to edit the dialog if it's not being used.
371 }
372
373 if (selected_group == p_name) {
374 add_filter->clear();
375 remove_filter->clear();
376 nodes_to_remove->clear();
377 nodes_to_add->clear();
378 groups->deselect_all();
379 selected_group = "";
380 }
381
382 for (TreeItem *E = groups_root->get_first_child(); E; E = E->get_next()) {
383 if (E->get_text(0) == p_name) {
384 groups_root->remove_child(E);
385 return;
386 }
387 }
388}
389
390void GroupDialog::_notification(int p_what) {
391 switch (p_what) {
392 case NOTIFICATION_TRANSLATION_CHANGED:
393 case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
394 case NOTIFICATION_ENTER_TREE:
395 case NOTIFICATION_THEME_CHANGED: {
396 if (is_layout_rtl()) {
397 add_button->set_icon(groups->get_editor_theme_icon(SNAME("Back")));
398 remove_button->set_icon(groups->get_editor_theme_icon(SNAME("Forward")));
399 } else {
400 add_button->set_icon(groups->get_editor_theme_icon(SNAME("Forward")));
401 remove_button->set_icon(groups->get_editor_theme_icon(SNAME("Back")));
402 }
403
404 add_filter->set_right_icon(groups->get_editor_theme_icon(SNAME("Search")));
405 add_filter->set_clear_button_enabled(true);
406 remove_filter->set_right_icon(groups->get_editor_theme_icon(SNAME("Search")));
407 remove_filter->set_clear_button_enabled(true);
408 } break;
409 }
410}
411
412void GroupDialog::edit() {
413 popup_centered();
414
415 groups->clear();
416 groups_root = groups->create_item();
417
418 nodes_to_add->clear();
419 nodes_to_remove->clear();
420
421 add_group_text->clear();
422 add_filter->clear();
423 remove_filter->clear();
424
425 _load_groups(scene_tree->get_edited_scene_root());
426}
427
428void GroupDialog::_bind_methods() {
429 ClassDB::bind_method("_delete_group_item", &GroupDialog::_delete_group_item);
430
431 ClassDB::bind_method("_add_group", &GroupDialog::_add_group);
432
433 ClassDB::bind_method("_rename_group_item", &GroupDialog::_rename_group_item);
434
435 ClassDB::bind_method("_group_selected", &GroupDialog::_group_selected);
436
437 ADD_SIGNAL(MethodInfo("group_edited"));
438}
439
440GroupDialog::GroupDialog() {
441 set_min_size(Size2(600, 400) * EDSCALE);
442
443 scene_tree = SceneTree::get_singleton();
444
445 VBoxContainer *vbc = memnew(VBoxContainer);
446 add_child(vbc);
447 vbc->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_KEEP_SIZE, 8 * EDSCALE);
448
449 HBoxContainer *hbc = memnew(HBoxContainer);
450 vbc->add_child(hbc);
451 hbc->set_v_size_flags(Control::SIZE_EXPAND_FILL);
452
453 VBoxContainer *vbc_left = memnew(VBoxContainer);
454 hbc->add_child(vbc_left);
455 vbc_left->set_h_size_flags(Control::SIZE_EXPAND_FILL);
456
457 Label *group_title = memnew(Label);
458 group_title->set_theme_type_variation("HeaderSmall");
459
460 group_title->set_text(TTR("Groups"));
461 vbc_left->add_child(group_title);
462
463 groups = memnew(Tree);
464 vbc_left->add_child(groups);
465 groups->set_hide_root(true);
466 groups->set_select_mode(Tree::SELECT_SINGLE);
467 groups->set_allow_reselect(true);
468 groups->set_allow_rmb_select(true);
469 groups->set_v_size_flags(Control::SIZE_EXPAND_FILL);
470 groups->add_theme_constant_override("draw_guides", 1);
471 groups->connect("item_selected", callable_mp(this, &GroupDialog::_group_selected));
472 groups->connect("button_clicked", callable_mp(this, &GroupDialog::_modify_group_pressed));
473 groups->connect("item_edited", callable_mp(this, &GroupDialog::_group_renamed));
474
475 HBoxContainer *chbc = memnew(HBoxContainer);
476 vbc_left->add_child(chbc);
477 chbc->set_h_size_flags(Control::SIZE_EXPAND_FILL);
478
479 add_group_text = memnew(LineEdit);
480 chbc->add_child(add_group_text);
481 add_group_text->set_h_size_flags(Control::SIZE_EXPAND_FILL);
482 add_group_text->connect("text_submitted", callable_mp(this, &GroupDialog::_add_group_pressed));
483 add_group_text->connect("text_changed", callable_mp(this, &GroupDialog::_add_group_text_changed));
484
485 add_group_button = memnew(Button);
486 add_group_button->set_text(TTR("Add"));
487 chbc->add_child(add_group_button);
488 add_group_button->connect("pressed", callable_mp(this, &GroupDialog::_add_group_pressed).bind(String()));
489
490 VBoxContainer *vbc_add = memnew(VBoxContainer);
491 hbc->add_child(vbc_add);
492 vbc_add->set_h_size_flags(Control::SIZE_EXPAND_FILL);
493
494 Label *out_of_group_title = memnew(Label);
495 out_of_group_title->set_theme_type_variation("HeaderSmall");
496
497 out_of_group_title->set_text(TTR("Nodes Not in Group"));
498 vbc_add->add_child(out_of_group_title);
499
500 nodes_to_add = memnew(Tree);
501 vbc_add->add_child(nodes_to_add);
502 nodes_to_add->set_hide_root(true);
503 nodes_to_add->set_hide_folding(true);
504 nodes_to_add->set_select_mode(Tree::SELECT_MULTI);
505 nodes_to_add->set_v_size_flags(Control::SIZE_EXPAND_FILL);
506 nodes_to_add->add_theme_constant_override("draw_guides", 1);
507
508 HBoxContainer *add_filter_hbc = memnew(HBoxContainer);
509 add_filter_hbc->add_theme_constant_override("separate", 0);
510 vbc_add->add_child(add_filter_hbc);
511
512 add_filter = memnew(LineEdit);
513 add_filter->set_h_size_flags(Control::SIZE_EXPAND_FILL);
514 add_filter->set_placeholder(TTR("Filter Nodes"));
515 add_filter_hbc->add_child(add_filter);
516 add_filter->connect("text_changed", callable_mp(this, &GroupDialog::_add_filter_changed));
517
518 VBoxContainer *vbc_buttons = memnew(VBoxContainer);
519 hbc->add_child(vbc_buttons);
520 vbc_buttons->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
521 vbc_buttons->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
522
523 add_button = memnew(Button);
524 add_button->set_flat(true);
525 add_button->set_text(TTR("Add"));
526 add_button->connect("pressed", callable_mp(this, &GroupDialog::_add_pressed));
527
528 vbc_buttons->add_child(add_button);
529 vbc_buttons->add_spacer();
530 vbc_buttons->add_spacer();
531 vbc_buttons->add_spacer();
532
533 remove_button = memnew(Button);
534 remove_button->set_flat(true);
535 remove_button->set_text(TTR("Remove"));
536 remove_button->connect("pressed", callable_mp(this, &GroupDialog::_removed_pressed));
537
538 vbc_buttons->add_child(remove_button);
539
540 VBoxContainer *vbc_remove = memnew(VBoxContainer);
541 hbc->add_child(vbc_remove);
542 vbc_remove->set_h_size_flags(Control::SIZE_EXPAND_FILL);
543
544 Label *in_group_title = memnew(Label);
545 in_group_title->set_theme_type_variation("HeaderSmall");
546
547 in_group_title->set_text(TTR("Nodes in Group"));
548 vbc_remove->add_child(in_group_title);
549
550 nodes_to_remove = memnew(Tree);
551 vbc_remove->add_child(nodes_to_remove);
552 nodes_to_remove->set_v_size_flags(Control::SIZE_EXPAND_FILL);
553 nodes_to_remove->set_hide_root(true);
554 nodes_to_remove->set_hide_folding(true);
555 nodes_to_remove->set_select_mode(Tree::SELECT_MULTI);
556 nodes_to_remove->add_theme_constant_override("draw_guides", 1);
557
558 HBoxContainer *remove_filter_hbc = memnew(HBoxContainer);
559 remove_filter_hbc->add_theme_constant_override("separate", 0);
560 vbc_remove->add_child(remove_filter_hbc);
561
562 remove_filter = memnew(LineEdit);
563 remove_filter->set_h_size_flags(Control::SIZE_EXPAND_FILL);
564 remove_filter->set_placeholder(TTR("Filter Nodes"));
565 remove_filter_hbc->add_child(remove_filter);
566 remove_filter->connect("text_changed", callable_mp(this, &GroupDialog::_remove_filter_changed));
567
568 group_empty = memnew(Label());
569 group_empty->set_theme_type_variation("HeaderSmall");
570
571 group_empty->set_text(TTR("Empty groups will be automatically removed."));
572 group_empty->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
573 group_empty->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
574 group_empty->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);
575 group_empty->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
576 nodes_to_remove->add_child(group_empty);
577 group_empty->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_KEEP_SIZE, 8 * EDSCALE);
578
579 set_title(TTR("Group Editor"));
580
581 error = memnew(AcceptDialog);
582 add_child(error);
583 error->set_ok_button_text(TTR("Close"));
584
585 _add_group_text_changed("");
586}
587
588////////////////////////////////////////////////////////////////////////////////
589
590void GroupsEditor::_add_group(const String &p_group) {
591 if (!node) {
592 return;
593 }
594 const String name = group_name->get_text().strip_edges();
595
596 group_name->clear();
597 if (node->is_in_group(name)) {
598 error->set_text(TTR("Group name already exists."));
599 error->popup_centered();
600 return;
601 }
602
603 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
604 undo_redo->create_action(TTR("Add to Group"));
605
606 undo_redo->add_do_method(node, "add_to_group", name, true);
607 undo_redo->add_undo_method(node, "remove_from_group", name);
608 undo_redo->add_do_method(this, "update_tree");
609 undo_redo->add_undo_method(this, "update_tree");
610
611 // To force redraw of scene tree.
612 undo_redo->add_do_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree");
613 undo_redo->add_undo_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree");
614
615 undo_redo->commit_action();
616}
617
618void GroupsEditor::_group_selected() {
619 if (!tree->is_anything_selected()) {
620 return;
621 }
622 selected_group = tree->get_selected()->get_text(0);
623}
624
625void GroupsEditor::_group_renamed() {
626 if (!node || !can_edit(node, selected_group)) {
627 return;
628 }
629
630 TreeItem *ti = tree->get_selected();
631 if (!ti) {
632 return;
633 }
634
635 const String name = ti->get_text(0).strip_edges();
636 if (name == selected_group) {
637 return;
638 }
639
640 if (name.is_empty()) {
641 ti->set_text(0, selected_group);
642 error->set_text(TTR("Invalid group name."));
643 error->popup_centered();
644 return;
645 }
646
647 for (TreeItem *E = groups_root->get_first_child(); E; E = E->get_next()) {
648 if (E != ti && E->get_text(0) == name) {
649 ti->set_text(0, selected_group);
650 error->set_text(TTR("Group name already exists."));
651 error->popup_centered();
652 return;
653 }
654 }
655
656 ti->set_text(0, name); // Spaces trimmed.
657
658 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
659 undo_redo->create_action(TTR("Rename Group"));
660
661 undo_redo->add_do_method(node, "remove_from_group", selected_group);
662 undo_redo->add_undo_method(node, "remove_from_group", name);
663 undo_redo->add_do_method(node, "add_to_group", name, true);
664 undo_redo->add_undo_method(node, "add_to_group", selected_group, true);
665
666 undo_redo->add_do_method(this, "_group_selected");
667 undo_redo->add_undo_method(this, "_group_selected");
668 undo_redo->add_do_method(this, "update_tree");
669 undo_redo->add_undo_method(this, "update_tree");
670
671 // To force redraw of scene tree.
672 undo_redo->add_do_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree");
673 undo_redo->add_undo_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree");
674
675 undo_redo->commit_action();
676}
677
678void GroupsEditor::_modify_group(Object *p_item, int p_column, int p_id, MouseButton p_button) {
679 if (p_button != MouseButton::LEFT) {
680 return;
681 }
682
683 if (!node) {
684 return;
685 }
686
687 TreeItem *ti = Object::cast_to<TreeItem>(p_item);
688 if (!ti) {
689 return;
690 }
691 switch (p_id) {
692 case DELETE_GROUP: {
693 const String name = ti->get_text(0);
694 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
695 undo_redo->create_action(TTR("Remove from Group"));
696
697 undo_redo->add_do_method(node, "remove_from_group", name);
698 undo_redo->add_undo_method(node, "add_to_group", name, true);
699 undo_redo->add_do_method(this, "update_tree");
700 undo_redo->add_undo_method(this, "update_tree");
701
702 // To force redraw of scene tree.
703 undo_redo->add_do_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree");
704 undo_redo->add_undo_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree");
705
706 undo_redo->commit_action();
707 } break;
708 case COPY_GROUP: {
709 DisplayServer::get_singleton()->clipboard_set(ti->get_text(p_column));
710 } break;
711 }
712}
713
714void GroupsEditor::_group_name_changed(const String &p_new_text) {
715 add->set_disabled(p_new_text.strip_edges().is_empty());
716}
717
718struct _GroupInfoComparator {
719 bool operator()(const Node::GroupInfo &p_a, const Node::GroupInfo &p_b) const {
720 return p_a.name.operator String() < p_b.name.operator String();
721 }
722};
723
724void GroupsEditor::update_tree() {
725 tree->clear();
726
727 if (!node) {
728 return;
729 }
730
731 List<Node::GroupInfo> groups;
732 node->get_groups(&groups);
733 groups.sort_custom<_GroupInfoComparator>();
734
735 TreeItem *root = tree->create_item();
736 groups_root = root;
737
738 for (const GroupInfo &gi : groups) {
739 if (!gi.persistent) {
740 continue;
741 }
742
743 Node *n = node;
744 bool can_be_deleted = true;
745
746 while (n) {
747 Ref<SceneState> ss = (n == EditorNode::get_singleton()->get_edited_scene()) ? n->get_scene_inherited_state() : n->get_scene_instance_state();
748
749 if (ss.is_valid()) {
750 int path = ss->find_node_by_path(n->get_path_to(node));
751 if (path != -1) {
752 if (ss->is_node_in_group(path, gi.name)) {
753 can_be_deleted = false;
754 }
755 }
756 }
757
758 n = n->get_owner();
759 }
760
761 TreeItem *item = tree->create_item(root);
762 item->set_text(0, gi.name);
763 item->set_editable(0, true);
764 if (can_be_deleted) {
765 item->add_button(0, get_editor_theme_icon(SNAME("Remove")), DELETE_GROUP);
766 item->add_button(0, get_editor_theme_icon(SNAME("ActionCopy")), COPY_GROUP);
767 } else {
768 item->set_selectable(0, false);
769 }
770 }
771}
772
773void GroupsEditor::set_current(Node *p_node) {
774 node = p_node;
775 update_tree();
776}
777
778void GroupsEditor::_show_group_dialog() {
779 group_dialog->edit();
780}
781
782void GroupsEditor::_bind_methods() {
783 ClassDB::bind_method("update_tree", &GroupsEditor::update_tree);
784 ClassDB::bind_method("_group_selected", &GroupsEditor::_group_selected);
785}
786
787GroupsEditor::GroupsEditor() {
788 node = nullptr;
789
790 VBoxContainer *vbc = this;
791
792 group_dialog = memnew(GroupDialog);
793
794 add_child(group_dialog);
795 group_dialog->connect("group_edited", callable_mp(this, &GroupsEditor::update_tree));
796
797 Button *group_dialog_button = memnew(Button);
798 group_dialog_button->set_text(TTR("Manage Groups"));
799 vbc->add_child(group_dialog_button);
800 group_dialog_button->connect("pressed", callable_mp(this, &GroupsEditor::_show_group_dialog));
801
802 HBoxContainer *hbc = memnew(HBoxContainer);
803 vbc->add_child(hbc);
804
805 group_name = memnew(LineEdit);
806 group_name->set_h_size_flags(Control::SIZE_EXPAND_FILL);
807 hbc->add_child(group_name);
808 group_name->connect("text_submitted", callable_mp(this, &GroupsEditor::_add_group));
809 group_name->connect("text_changed", callable_mp(this, &GroupsEditor::_group_name_changed));
810
811 add = memnew(Button);
812 add->set_text(TTR("Add"));
813 hbc->add_child(add);
814 add->connect("pressed", callable_mp(this, &GroupsEditor::_add_group).bind(String()));
815
816 tree = memnew(Tree);
817 vbc->add_child(tree);
818 tree->set_hide_root(true);
819 tree->set_allow_reselect(true);
820 tree->set_allow_rmb_select(true);
821 tree->set_v_size_flags(Control::SIZE_EXPAND_FILL);
822 tree->connect("item_selected", callable_mp(this, &GroupsEditor::_group_selected));
823 tree->connect("button_clicked", callable_mp(this, &GroupsEditor::_modify_group));
824 tree->connect("item_edited", callable_mp(this, &GroupsEditor::_group_renamed));
825 tree->add_theme_constant_override("draw_guides", 1);
826 add_theme_constant_override("separation", 3 * EDSCALE);
827
828 error = memnew(AcceptDialog);
829 add_child(error);
830 error->get_ok_button()->set_text(TTR("Close"));
831
832 _group_name_changed("");
833}
834
835GroupsEditor::~GroupsEditor() {
836}
837