Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/docks/groups_editor.cpp
20832 views
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/docks/scene_tree_dock.h"
34
#include "editor/editor_node.h"
35
#include "editor/editor_string_names.h"
36
#include "editor/editor_undo_redo_manager.h"
37
#include "editor/gui/editor_validation_panel.h"
38
#include "editor/settings/editor_settings.h"
39
#include "editor/settings/project_settings_editor.h"
40
#include "editor/themes/editor_scale.h"
41
#include "scene/gui/box_container.h"
42
#include "scene/gui/check_button.h"
43
#include "scene/gui/grid_container.h"
44
#include "scene/gui/label.h"
45
#include "scene/resources/packed_scene.h"
46
47
static bool can_edit(Node *p_node, const String &p_group) {
48
Node *n = p_node;
49
bool can_edit = true;
50
while (n) {
51
Ref<SceneState> ss = (n == EditorNode::get_singleton()->get_edited_scene()) ? n->get_scene_inherited_state() : n->get_scene_instance_state();
52
if (ss.is_valid()) {
53
int path = ss->find_node_by_path(n->get_path_to(p_node));
54
if (path != -1) {
55
if (ss->is_node_in_group(path, p_group)) {
56
can_edit = false;
57
break;
58
}
59
}
60
}
61
n = n->get_owner();
62
}
63
return can_edit;
64
}
65
66
struct _GroupInfoComparator {
67
bool operator()(const Node::GroupInfo &p_a, const Node::GroupInfo &p_b) const {
68
return p_a.name.operator String() < p_b.name.operator String();
69
}
70
};
71
72
void GroupsEditor::_add_scene_group(const String &p_name) {
73
scene_groups[p_name] = true;
74
}
75
76
void GroupsEditor::_remove_scene_group(const String &p_name) {
77
scene_groups.erase(p_name);
78
ProjectSettingsEditor::get_singleton()->get_group_settings()->remove_node_references(scene_root_node, p_name);
79
}
80
81
void GroupsEditor::_rename_scene_group(const String &p_old_name, const String &p_new_name) {
82
scene_groups[p_new_name] = scene_groups[p_old_name];
83
scene_groups.erase(p_old_name);
84
ProjectSettingsEditor::get_singleton()->get_group_settings()->rename_node_references(scene_root_node, p_old_name, p_new_name);
85
}
86
87
void GroupsEditor::_set_group_checked(const String &p_name, bool p_checked) {
88
TreeItem *ti = tree->get_item_with_text(p_name);
89
if (!ti) {
90
return;
91
}
92
93
ti->set_checked(0, p_checked);
94
}
95
96
void GroupsEditor::_add_to_group(const StringName &p_name, bool p_persist, const Array &p_nodes) {
97
for (const Variant &v : p_nodes) {
98
Node *node = Object::cast_to<Node>(v.get_validated_object());
99
if (node) {
100
node->add_to_group(p_name, p_persist);
101
}
102
}
103
}
104
105
void GroupsEditor::_remove_from_group(const StringName &p_name, const Array &p_nodes) {
106
for (const Variant &v : p_nodes) {
107
Node *node = Object::cast_to<Node>(v.get_validated_object());
108
if (node) {
109
node->remove_from_group(p_name);
110
}
111
}
112
}
113
114
void GroupsEditor::_get_group_mask(const StringName &p_name, Array &r_nodes, bool p_invert) {
115
for (Node *p_node : selection) {
116
if (p_invert != p_node->is_in_group(p_name)) {
117
r_nodes.push_back(p_node);
118
}
119
}
120
}
121
122
bool GroupsEditor::_can_edit(const StringName &p_group) {
123
for (Node *p_node : selection) {
124
if (!can_edit(p_node, p_group)) {
125
return false;
126
}
127
}
128
return true;
129
}
130
131
bool GroupsEditor::_has_group(const String &p_name) {
132
return global_groups.has(p_name) || scene_groups.has(p_name);
133
}
134
135
void GroupsEditor::_modify_group(Object *p_item, int p_column, int p_id, MouseButton p_mouse_button) {
136
if (p_mouse_button != MouseButton::LEFT) {
137
return;
138
}
139
140
if (selection.is_empty()) {
141
return;
142
}
143
144
TreeItem *ti = Object::cast_to<TreeItem>(p_item);
145
if (!ti) {
146
return;
147
}
148
149
if (p_id == COPY_GROUP) {
150
DisplayServer::get_singleton()->clipboard_set(ti->get_text(p_column));
151
}
152
}
153
154
void GroupsEditor::_load_scene_groups(Node *p_node) {
155
List<Node::GroupInfo> groups;
156
p_node->get_groups(&groups);
157
158
for (const GroupInfo &gi : groups) {
159
if (!gi.persistent) {
160
continue;
161
}
162
163
if (global_groups.has(gi.name)) {
164
continue;
165
}
166
167
bool is_editable = can_edit(p_node, gi.name);
168
if (scene_groups.has(gi.name)) {
169
scene_groups[gi.name] = scene_groups[gi.name] && is_editable;
170
} else {
171
scene_groups[gi.name] = is_editable;
172
}
173
}
174
175
for (int i = 0; i < p_node->get_child_count(); i++) {
176
_load_scene_groups(p_node->get_child(i));
177
}
178
}
179
180
void GroupsEditor::_update_groups() {
181
if (!is_visible_in_tree()) {
182
groups_dirty = true;
183
return;
184
}
185
186
if (updating_groups) {
187
return;
188
}
189
190
updating_groups = true;
191
192
global_groups = ProjectSettings::get_singleton()->get_global_groups_list();
193
194
_load_scene_groups(scene_root_node);
195
196
for (HashMap<StringName, bool>::Iterator E = scene_groups.begin(); E;) {
197
HashMap<StringName, bool>::Iterator next = E;
198
++next;
199
200
if (global_groups.has(E->key)) {
201
scene_groups.erase(E->key);
202
}
203
E = next;
204
}
205
206
updating_groups = false;
207
}
208
209
void GroupsEditor::_update_tree() {
210
if (!is_visible_in_tree()) {
211
groups_dirty = true;
212
return;
213
}
214
215
if (selection.is_empty()) {
216
return;
217
}
218
219
if (updating_tree) {
220
return;
221
}
222
223
updating_tree = true;
224
225
tree->clear();
226
227
List<Node::GroupInfo> groups;
228
for (Node *p_node : selection) {
229
p_node->get_groups(&groups);
230
}
231
groups.sort_custom<_GroupInfoComparator>();
232
233
List<StringName> current_groups;
234
for (const Node::GroupInfo &gi : groups) {
235
current_groups.push_back(gi.name);
236
}
237
238
TreeItem *root = tree->create_item();
239
240
TreeItem *local_root = tree->create_item(root);
241
local_root->set_text(0, TTR("Scene Groups"));
242
local_root->set_icon(0, get_editor_theme_icon(SNAME("PackedScene")));
243
local_root->set_custom_bg_color(0, get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor)));
244
local_root->set_custom_stylebox(0, get_theme_stylebox(SNAME("prop_subsection_stylebox"), EditorStringName(Editor)));
245
local_root->set_selectable(0, false);
246
247
List<StringName> scene_keys;
248
for (const KeyValue<StringName, bool> &E : scene_groups) {
249
scene_keys.push_back(E.key);
250
}
251
scene_keys.sort_custom<NoCaseComparator>();
252
253
for (const StringName &E : scene_keys) {
254
if (!filter->get_text().is_subsequence_ofn(E)) {
255
continue;
256
}
257
258
TreeItem *item = tree->create_item(local_root);
259
item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
260
item->set_editable(0, _can_edit(E));
261
item->set_checked(0, current_groups.find(E) != nullptr);
262
item->set_text(0, E);
263
item->set_meta("__local", true);
264
item->set_meta("__name", E);
265
item->set_meta("__description", "");
266
if (!scene_groups[E]) {
267
item->add_button(0, get_editor_theme_icon(SNAME("Lock")), -1, true, TTR("This group belongs to another scene and can't be edited."));
268
}
269
item->add_button(0, get_editor_theme_icon(SNAME("ActionCopy")), COPY_GROUP, false, TTR("Copy group name to clipboard."));
270
}
271
272
List<StringName> keys;
273
for (const KeyValue<StringName, String> &E : global_groups) {
274
keys.push_back(E.key);
275
}
276
keys.sort_custom<NoCaseComparator>();
277
278
TreeItem *global_root = tree->create_item(root);
279
global_root->set_text(0, TTR("Global Groups"));
280
global_root->set_icon(0, get_editor_theme_icon(SNAME("Environment")));
281
global_root->set_custom_bg_color(0, get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor)));
282
global_root->set_custom_stylebox(0, get_theme_stylebox(SNAME("prop_subsection_stylebox"), EditorStringName(Editor)));
283
global_root->set_selectable(0, false);
284
285
for (const StringName &E : keys) {
286
if (!filter->get_text().is_subsequence_ofn(E)) {
287
continue;
288
}
289
290
TreeItem *item = tree->create_item(global_root);
291
item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
292
item->set_editable(0, _can_edit(E));
293
item->set_checked(0, current_groups.find(E) != nullptr);
294
item->set_text(0, E);
295
item->set_meta("__local", false);
296
item->set_meta("__name", E);
297
item->set_meta("__description", global_groups[E]);
298
if (!global_groups[E].is_empty()) {
299
item->set_tooltip_text(0, vformat("%s\n\n%s", E, global_groups[E]));
300
}
301
item->add_button(0, get_editor_theme_icon(SNAME("ActionCopy")), COPY_GROUP, false, TTR("Copy group name to clipboard."));
302
}
303
304
updating_tree = false;
305
}
306
307
void GroupsEditor::_queue_update_groups_and_tree() {
308
if (update_groups_and_tree_queued) {
309
return;
310
}
311
update_groups_and_tree_queued = true;
312
callable_mp(this, &GroupsEditor::_update_groups_and_tree).call_deferred();
313
}
314
315
void GroupsEditor::_update_groups_and_tree() {
316
update_groups_and_tree_queued = false;
317
// The scene_root_node could be unset before we actually run this code because this is queued with call_deferred().
318
// In that case NOTIFICATION_VISIBILITY_CHANGED will call this function again soon.
319
if (!scene_root_node) {
320
return;
321
}
322
_update_groups();
323
_update_tree();
324
}
325
326
void GroupsEditor::_update_scene_groups(const ObjectID &p_id) {
327
HashMap<ObjectID, HashMap<StringName, bool>>::Iterator I = scene_groups_cache.find(p_id);
328
if (I) {
329
scene_groups = I->value;
330
scene_groups_cache.remove(I);
331
} else {
332
scene_groups = HashMap<StringName, bool>();
333
}
334
}
335
336
void GroupsEditor::_cache_scene_groups(const ObjectID &p_id) {
337
const int edited_scene_count = EditorNode::get_editor_data().get_edited_scene_count();
338
for (int i = 0; i < edited_scene_count; i++) {
339
Node *edited_scene_root = EditorNode::get_editor_data().get_edited_scene_root(i);
340
if (edited_scene_root && p_id == edited_scene_root->get_instance_id()) {
341
scene_groups_cache[p_id] = scene_groups_for_caching;
342
break;
343
}
344
}
345
}
346
347
void GroupsEditor::set_selection(const Vector<Node *> &p_nodes) {
348
if (p_nodes.is_empty()) {
349
holder->hide();
350
select_a_node->show();
351
selection.clear();
352
return;
353
}
354
355
selection = p_nodes;
356
357
holder->show();
358
select_a_node->hide();
359
360
if (scene_tree->get_edited_scene_root() != scene_root_node) {
361
scene_root_node = scene_tree->get_edited_scene_root();
362
_update_scene_groups(scene_root_node->get_instance_id());
363
_update_groups();
364
}
365
366
_update_tree();
367
}
368
369
void GroupsEditor::_item_edited() {
370
TreeItem *ti = tree->get_edited();
371
if (!ti) {
372
return;
373
}
374
375
String name = ti->get_text(0);
376
377
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
378
if (ti->is_checked(0)) {
379
undo_redo->create_action(TTR("Add to Group"));
380
381
Array nodes;
382
_get_group_mask(name, nodes, true);
383
undo_redo->add_do_method(this, "_add_to_group", name, true, nodes);
384
undo_redo->add_undo_method(this, "_remove_from_group", name, nodes);
385
386
undo_redo->add_do_method(this, "_set_group_checked", name, true);
387
undo_redo->add_undo_method(this, "_set_group_checked", name, false);
388
389
// To force redraw of scene tree.
390
undo_redo->add_do_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree");
391
undo_redo->add_undo_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree");
392
393
undo_redo->commit_action();
394
395
} else {
396
undo_redo->create_action(TTR("Remove from Group"));
397
398
Array nodes;
399
_get_group_mask(name, nodes, false);
400
undo_redo->add_do_method(this, "_remove_from_group", name, nodes);
401
undo_redo->add_undo_method(this, "_add_to_group", name, true, nodes);
402
403
undo_redo->add_do_method(this, "_set_group_checked", name, false);
404
undo_redo->add_undo_method(this, "_set_group_checked", name, true);
405
406
// To force redraw of scene tree.
407
undo_redo->add_do_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree");
408
undo_redo->add_undo_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree");
409
410
undo_redo->commit_action();
411
}
412
}
413
414
void GroupsEditor::_notification(int p_what) {
415
switch (p_what) {
416
case NOTIFICATION_READY: {
417
get_tree()->connect("node_added", callable_mp(this, &GroupsEditor::_load_scene_groups));
418
get_tree()->connect("node_removed", callable_mp(this, &GroupsEditor::_node_removed));
419
} break;
420
case NOTIFICATION_THEME_CHANGED: {
421
filter->set_right_icon(get_editor_theme_icon("Search"));
422
add->set_button_icon(get_editor_theme_icon("Add"));
423
_update_tree();
424
} break;
425
case NOTIFICATION_VISIBILITY_CHANGED: {
426
if (groups_dirty && is_visible_in_tree()) {
427
groups_dirty = false;
428
_update_groups_and_tree();
429
}
430
} break;
431
}
432
}
433
434
void GroupsEditor::_menu_id_pressed(int p_id) {
435
TreeItem *ti = tree->get_selected();
436
if (!ti) {
437
return;
438
}
439
440
bool is_local = ti->get_meta("__local");
441
String group_name = ti->get_meta("__name");
442
443
switch (p_id) {
444
case DELETE_GROUP: {
445
if (!is_local || scene_groups[group_name]) {
446
_show_remove_group_dialog();
447
}
448
} break;
449
case RENAME_GROUP: {
450
if (!is_local || scene_groups[group_name]) {
451
_show_rename_group_dialog();
452
}
453
} break;
454
case CONVERT_GROUP: {
455
String description = ti->get_meta("__description");
456
String property_name = GLOBAL_GROUP_PREFIX + group_name;
457
458
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
459
if (is_local) {
460
undo_redo->create_action(TTR("Convert to Global Group"));
461
462
undo_redo->add_do_property(ProjectSettings::get_singleton(), property_name, "");
463
undo_redo->add_undo_property(ProjectSettings::get_singleton(), property_name, Variant());
464
465
undo_redo->add_do_method(ProjectSettings::get_singleton(), "save");
466
undo_redo->add_undo_method(ProjectSettings::get_singleton(), "save");
467
468
undo_redo->add_undo_method(this, "_add_scene_group", group_name);
469
470
undo_redo->add_do_method(this, "_update_groups_and_tree");
471
undo_redo->add_undo_method(this, "_update_groups_and_tree");
472
473
undo_redo->commit_action();
474
} else {
475
undo_redo->create_action(TTR("Convert to Scene Group"));
476
477
undo_redo->add_do_property(ProjectSettings::get_singleton(), property_name, Variant());
478
undo_redo->add_undo_property(ProjectSettings::get_singleton(), property_name, description);
479
480
undo_redo->add_do_method(ProjectSettings::get_singleton(), "save");
481
undo_redo->add_undo_method(ProjectSettings::get_singleton(), "save");
482
483
undo_redo->add_do_method(this, "_add_scene_group", group_name);
484
485
undo_redo->add_do_method(this, "_update_groups_and_tree");
486
undo_redo->add_undo_method(this, "_update_groups_and_tree");
487
488
undo_redo->commit_action();
489
}
490
} break;
491
}
492
}
493
494
void GroupsEditor::_item_mouse_selected(const Vector2 &p_pos, MouseButton p_mouse_button) {
495
TreeItem *ti = tree->get_selected();
496
if (!ti) {
497
return;
498
}
499
500
if (p_mouse_button == MouseButton::LEFT) {
501
callable_mp(this, &GroupsEditor::_item_edited).call_deferred();
502
} else if (p_mouse_button == MouseButton::RIGHT) {
503
// Restore the previous state after clicking RMB.
504
if (ti->is_editable(0)) {
505
ti->set_checked(0, !ti->is_checked(0));
506
}
507
508
menu->clear();
509
if (ti->get_meta("__local")) {
510
menu->add_icon_item(get_editor_theme_icon(SNAME("Environment")), TTR("Convert to Global Group"), CONVERT_GROUP);
511
} else {
512
menu->add_icon_item(get_editor_theme_icon(SNAME("PackedScene")), TTR("Convert to Scene Group"), CONVERT_GROUP);
513
}
514
515
String group_name = ti->get_meta("__name");
516
if (global_groups.has(group_name) || scene_groups[group_name]) {
517
menu->add_separator();
518
menu->add_icon_shortcut(get_editor_theme_icon(SNAME("Rename")), ED_GET_SHORTCUT("groups_editor/rename"), RENAME_GROUP);
519
menu->add_icon_shortcut(get_editor_theme_icon(SNAME("Remove")), ED_GET_SHORTCUT("groups_editor/delete"), DELETE_GROUP);
520
}
521
522
menu->set_position(tree->get_screen_position() + p_pos);
523
menu->reset_size();
524
menu->popup();
525
}
526
}
527
528
void GroupsEditor::_confirm_add() {
529
String name = add_group_name->get_text().strip_edges();
530
531
String description = add_group_description->get_text();
532
533
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
534
undo_redo->create_action(TTR("Add to Group"));
535
536
Array nodes;
537
_get_group_mask(name, nodes, true);
538
undo_redo->add_do_method(this, "_add_to_group", name, true, nodes);
539
undo_redo->add_undo_method(this, "_remove_from_group", name, nodes);
540
541
bool is_local = !global_group_button->is_pressed();
542
if (is_local) {
543
undo_redo->add_do_method(this, "_add_scene_group", name);
544
undo_redo->add_undo_method(this, "_remove_scene_group", name);
545
} else {
546
String property_name = GLOBAL_GROUP_PREFIX + name;
547
548
undo_redo->add_do_property(ProjectSettings::get_singleton(), property_name, description);
549
undo_redo->add_undo_property(ProjectSettings::get_singleton(), property_name, Variant());
550
551
undo_redo->add_do_method(ProjectSettings::get_singleton(), "save");
552
undo_redo->add_undo_method(ProjectSettings::get_singleton(), "save");
553
554
undo_redo->add_do_method(this, "_update_groups");
555
undo_redo->add_undo_method(this, "_update_groups");
556
}
557
558
undo_redo->add_do_method(this, "_update_tree");
559
undo_redo->add_undo_method(this, "_update_tree");
560
561
// To force redraw of scene tree.
562
undo_redo->add_do_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree");
563
undo_redo->add_undo_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree");
564
565
undo_redo->commit_action();
566
tree->grab_focus(true);
567
}
568
569
void GroupsEditor::_confirm_rename() {
570
TreeItem *ti = tree->get_selected();
571
if (!ti) {
572
return;
573
}
574
575
String old_name = ti->get_meta("__name");
576
String new_name = rename_group->get_text().strip_edges();
577
578
if (old_name == new_name) {
579
return;
580
}
581
582
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
583
undo_redo->create_action(TTR("Rename Group"));
584
585
if (!global_groups.has(old_name)) {
586
undo_redo->add_do_method(this, "_rename_scene_group", old_name, new_name);
587
undo_redo->add_undo_method(this, "_rename_scene_group", new_name, old_name);
588
} else {
589
String property_new_name = GLOBAL_GROUP_PREFIX + new_name;
590
String property_old_name = GLOBAL_GROUP_PREFIX + old_name;
591
592
String description = ti->get_meta("__description");
593
594
undo_redo->add_do_property(ProjectSettings::get_singleton(), property_new_name, description);
595
undo_redo->add_undo_property(ProjectSettings::get_singleton(), property_new_name, Variant());
596
597
undo_redo->add_do_property(ProjectSettings::get_singleton(), property_old_name, Variant());
598
undo_redo->add_undo_property(ProjectSettings::get_singleton(), property_old_name, description);
599
600
if (rename_check_box->is_pressed()) {
601
undo_redo->add_do_method(ProjectSettingsEditor::get_singleton()->get_group_settings(), "rename_references", old_name, new_name);
602
undo_redo->add_undo_method(ProjectSettingsEditor::get_singleton()->get_group_settings(), "rename_references", new_name, old_name);
603
}
604
605
undo_redo->add_do_method(ProjectSettings::get_singleton(), "save");
606
undo_redo->add_undo_method(ProjectSettings::get_singleton(), "save");
607
608
undo_redo->add_do_method(this, "_update_groups");
609
undo_redo->add_undo_method(this, "_update_groups");
610
}
611
612
undo_redo->add_do_method(this, "_update_tree");
613
undo_redo->add_undo_method(this, "_update_tree");
614
615
undo_redo->commit_action();
616
617
tree->grab_focus(true);
618
}
619
620
void GroupsEditor::_confirm_delete() {
621
TreeItem *ti = tree->get_selected();
622
if (!ti) {
623
return;
624
}
625
626
String name = ti->get_meta("__name");
627
bool is_local = ti->get_meta("__local");
628
629
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
630
undo_redo->create_action(TTR("Remove Group"));
631
632
if (is_local) {
633
undo_redo->add_do_method(this, "_remove_scene_group", name);
634
undo_redo->add_undo_method(this, "_add_scene_group", name);
635
} else {
636
String property_name = GLOBAL_GROUP_PREFIX + name;
637
String description = ti->get_meta("__description");
638
639
undo_redo->add_do_property(ProjectSettings::get_singleton(), property_name, Variant());
640
undo_redo->add_undo_property(ProjectSettings::get_singleton(), property_name, description);
641
642
if (remove_check_box->is_pressed()) {
643
undo_redo->add_do_method(ProjectSettingsEditor::get_singleton()->get_group_settings(), "remove_references", name);
644
}
645
646
undo_redo->add_do_method(ProjectSettings::get_singleton(), "save");
647
undo_redo->add_undo_method(ProjectSettings::get_singleton(), "save");
648
649
undo_redo->add_do_method(this, "_update_groups");
650
undo_redo->add_undo_method(this, "_update_groups");
651
}
652
653
undo_redo->add_do_method(this, "_update_tree");
654
undo_redo->add_undo_method(this, "_update_tree");
655
656
undo_redo->commit_action();
657
tree->grab_focus(true);
658
}
659
660
void GroupsEditor::_show_add_group_dialog() {
661
if (!add_group_dialog) {
662
add_group_dialog = memnew(ConfirmationDialog);
663
add_group_dialog->set_title(TTR("Create New Group"));
664
add_group_dialog->connect(SceneStringName(confirmed), callable_mp(this, &GroupsEditor::_confirm_add));
665
666
VBoxContainer *vbc = memnew(VBoxContainer);
667
add_group_dialog->add_child(vbc);
668
669
GridContainer *gc = memnew(GridContainer);
670
gc->set_columns(2);
671
vbc->add_child(gc);
672
673
Label *label_name = memnew(Label(TTR("Name:")));
674
label_name->set_h_size_flags(SIZE_SHRINK_BEGIN);
675
gc->add_child(label_name);
676
677
HBoxContainer *hbc = memnew(HBoxContainer);
678
hbc->set_h_size_flags(SIZE_EXPAND_FILL);
679
gc->add_child(hbc);
680
681
add_group_name = memnew(LineEdit);
682
add_group_name->set_custom_minimum_size(Size2(200 * EDSCALE, 0));
683
add_group_name->set_h_size_flags(SIZE_EXPAND_FILL);
684
add_group_name->set_accessibility_name(TTRC("Name:"));
685
hbc->add_child(add_group_name);
686
687
global_group_button = memnew(CheckButton);
688
global_group_button->set_text(TTR("Global"));
689
hbc->add_child(global_group_button);
690
691
Label *label_description = memnew(Label(TTR("Description:")));
692
label_name->set_h_size_flags(SIZE_SHRINK_BEGIN);
693
gc->add_child(label_description);
694
695
add_group_description = memnew(LineEdit);
696
add_group_description->set_h_size_flags(SIZE_EXPAND_FILL);
697
add_group_description->set_editable(false);
698
add_group_description->set_accessibility_name(TTRC("Description:"));
699
gc->add_child(add_group_description);
700
701
global_group_button->connect(SceneStringName(toggled), callable_mp(add_group_description, &LineEdit::set_editable));
702
703
add_group_dialog->register_text_enter(add_group_name);
704
add_group_dialog->register_text_enter(add_group_description);
705
706
add_validation_panel = memnew(EditorValidationPanel);
707
add_validation_panel->add_line(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Group name is valid."));
708
add_validation_panel->set_update_callback(callable_mp(this, &GroupsEditor::_check_add));
709
add_validation_panel->set_accept_button(add_group_dialog->get_ok_button());
710
711
add_group_name->connect(SceneStringName(text_changed), callable_mp(add_validation_panel, &EditorValidationPanel::update).unbind(1));
712
713
vbc->add_child(add_validation_panel);
714
715
add_child(add_group_dialog);
716
}
717
add_group_name->clear();
718
add_group_description->clear();
719
720
global_group_button->set_pressed(false);
721
722
add_validation_panel->update();
723
724
add_group_dialog->popup_centered();
725
add_group_name->grab_focus();
726
}
727
728
void GroupsEditor::_show_rename_group_dialog() {
729
if (!rename_group_dialog) {
730
rename_group_dialog = memnew(ConfirmationDialog);
731
rename_group_dialog->set_title(TTR("Rename Group"));
732
rename_group_dialog->connect(SceneStringName(confirmed), callable_mp(this, &GroupsEditor::_confirm_rename));
733
734
VBoxContainer *vbc = memnew(VBoxContainer);
735
rename_group_dialog->add_child(vbc);
736
737
HBoxContainer *hbc = memnew(HBoxContainer);
738
hbc->add_child(memnew(Label(TTR("Name:"))));
739
740
rename_group = memnew(LineEdit);
741
rename_group->set_custom_minimum_size(Size2(300 * EDSCALE, 1));
742
hbc->add_child(rename_group);
743
vbc->add_child(hbc);
744
745
rename_group_dialog->register_text_enter(rename_group);
746
747
rename_validation_panel = memnew(EditorValidationPanel);
748
rename_validation_panel->add_line(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Group name is valid."));
749
rename_validation_panel->set_update_callback(callable_mp(this, &GroupsEditor::_check_rename));
750
rename_validation_panel->set_accept_button(rename_group_dialog->get_ok_button());
751
752
rename_group->connect(SceneStringName(text_changed), callable_mp(rename_validation_panel, &EditorValidationPanel::update).unbind(1));
753
754
vbc->add_child(rename_validation_panel);
755
756
rename_check_box = memnew(CheckBox);
757
rename_check_box->set_text(TTR("Rename references in all scenes"));
758
vbc->add_child(rename_check_box);
759
760
add_child(rename_group_dialog);
761
}
762
763
TreeItem *ti = tree->get_selected();
764
if (!ti) {
765
return;
766
}
767
768
bool is_global = !ti->get_meta("__local");
769
rename_check_box->set_visible(is_global);
770
rename_check_box->set_pressed(false);
771
772
String name = ti->get_meta("__name");
773
774
rename_group->set_text(name);
775
rename_group_dialog->set_meta("__name", name);
776
777
rename_validation_panel->update();
778
779
rename_group_dialog->reset_size();
780
rename_group_dialog->popup_centered();
781
rename_group->select_all();
782
rename_group->grab_focus();
783
}
784
785
void GroupsEditor::_show_remove_group_dialog() {
786
if (!remove_group_dialog) {
787
remove_group_dialog = memnew(ConfirmationDialog);
788
remove_group_dialog->connect(SceneStringName(confirmed), callable_mp(this, &GroupsEditor::_confirm_delete));
789
790
VBoxContainer *vbox = memnew(VBoxContainer);
791
remove_label = memnew(Label);
792
remove_label->set_focus_mode(FOCUS_ACCESSIBILITY);
793
vbox->add_child(remove_label);
794
795
remove_check_box = memnew(CheckBox);
796
remove_check_box->set_text(TTR("Delete references from all scenes"));
797
vbox->add_child(remove_check_box);
798
799
remove_group_dialog->add_child(vbox);
800
801
add_child(remove_group_dialog);
802
}
803
804
TreeItem *ti = tree->get_selected();
805
if (!ti) {
806
return;
807
}
808
809
bool is_global = !ti->get_meta("__local");
810
remove_check_box->set_visible(is_global);
811
remove_check_box->set_pressed(false);
812
remove_label->set_text(vformat(TTR("Delete group \"%s\" and all its references?"), ti->get_text(0)));
813
814
remove_group_dialog->reset_size();
815
remove_group_dialog->popup_centered();
816
}
817
818
void GroupsEditor::_check_add() {
819
String group_name = add_group_name->get_text().strip_edges();
820
_validate_name(group_name, add_validation_panel);
821
}
822
823
void GroupsEditor::_check_rename() {
824
String group_name = rename_group->get_text().strip_edges();
825
String old_name = rename_group_dialog->get_meta("__name");
826
827
if (group_name == old_name) {
828
return;
829
}
830
_validate_name(group_name, rename_validation_panel);
831
}
832
833
void GroupsEditor::_validate_name(const String &p_name, EditorValidationPanel *p_validation_panel) {
834
if (p_name.is_empty()) {
835
p_validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Group can't be empty."), EditorValidationPanel::MSG_ERROR);
836
} else if (_has_group(p_name)) {
837
p_validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Group already exists."), EditorValidationPanel::MSG_ERROR);
838
}
839
}
840
841
void GroupsEditor::_groups_gui_input(Ref<InputEvent> p_event) {
842
Ref<InputEventKey> key = p_event;
843
if (key.is_valid() && key->is_pressed() && !key->is_echo()) {
844
if (ED_IS_SHORTCUT("groups_editor/delete", p_event)) {
845
_menu_id_pressed(DELETE_GROUP);
846
} else if (ED_IS_SHORTCUT("groups_editor/rename", p_event)) {
847
_menu_id_pressed(RENAME_GROUP);
848
} else if (ED_IS_SHORTCUT("editor/open_search", p_event)) {
849
filter->grab_focus();
850
filter->select_all();
851
} else {
852
return;
853
}
854
855
accept_event();
856
}
857
}
858
859
void GroupsEditor::_bind_methods() {
860
ClassDB::bind_method("_update_tree", &GroupsEditor::_update_tree);
861
ClassDB::bind_method("_update_groups", &GroupsEditor::_update_groups);
862
ClassDB::bind_method("_update_groups_and_tree", &GroupsEditor::_update_groups_and_tree);
863
864
ClassDB::bind_method("_add_scene_group", &GroupsEditor::_add_scene_group);
865
ClassDB::bind_method("_rename_scene_group", &GroupsEditor::_rename_scene_group);
866
ClassDB::bind_method("_remove_scene_group", &GroupsEditor::_remove_scene_group);
867
ClassDB::bind_method("_set_group_checked", &GroupsEditor::_set_group_checked);
868
869
ClassDB::bind_method("_add_to_group", &GroupsEditor::_add_to_group);
870
ClassDB::bind_method("_remove_from_group", &GroupsEditor::_remove_from_group);
871
}
872
873
void GroupsEditor::_node_removed(Node *p_node) {
874
if (scene_root_node == p_node) {
875
scene_groups_for_caching = scene_groups;
876
callable_mp(this, &GroupsEditor::_cache_scene_groups).call_deferred(p_node->get_instance_id());
877
scene_root_node = nullptr;
878
}
879
880
if (scene_root_node && scene_root_node == p_node->get_owner()) {
881
_queue_update_groups_and_tree();
882
}
883
}
884
885
GroupsEditor::GroupsEditor() {
886
scene_tree = SceneTree::get_singleton();
887
888
ED_SHORTCUT("groups_editor/delete", TTRC("Delete"), Key::KEY_DELETE);
889
ED_SHORTCUT("groups_editor/rename", TTRC("Rename"), Key::F2);
890
ED_SHORTCUT_OVERRIDE("groups_editor/rename", "macos", Key::ENTER);
891
892
holder = memnew(VBoxContainer);
893
holder->set_v_size_flags(SIZE_EXPAND_FILL);
894
holder->hide();
895
add_child(holder);
896
897
HBoxContainer *hbc = memnew(HBoxContainer);
898
holder->add_child(hbc);
899
900
add = memnew(Button);
901
add->set_theme_type_variation("FlatMenuButton");
902
add->set_tooltip_text(TTR("Add a new group."));
903
add->connect(SceneStringName(pressed), callable_mp(this, &GroupsEditor::_show_add_group_dialog));
904
hbc->add_child(add);
905
906
filter = memnew(LineEdit);
907
filter->set_clear_button_enabled(true);
908
filter->set_placeholder(TTR("Filter Groups"));
909
filter->set_accessibility_name(TTRC("Filter Groups"));
910
filter->set_h_size_flags(SIZE_EXPAND_FILL);
911
filter->connect(SceneStringName(text_changed), callable_mp(this, &GroupsEditor::_update_tree).unbind(1));
912
hbc->add_child(filter);
913
914
MarginContainer *mc = memnew(MarginContainer);
915
mc->set_theme_type_variation("NoBorderHorizontalBottom");
916
mc->set_v_size_flags(SIZE_EXPAND_FILL);
917
holder->add_child(mc);
918
919
tree = memnew(Tree);
920
tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
921
tree->set_hide_root(true);
922
tree->set_allow_rmb_select(true);
923
tree->set_select_mode(Tree::SelectMode::SELECT_SINGLE);
924
tree->set_scroll_hint_mode(Tree::SCROLL_HINT_MODE_TOP);
925
mc->add_child(tree);
926
tree->connect("button_clicked", callable_mp(this, &GroupsEditor::_modify_group));
927
tree->connect("item_mouse_selected", callable_mp(this, &GroupsEditor::_item_mouse_selected));
928
tree->connect(SceneStringName(gui_input), callable_mp(this, &GroupsEditor::_groups_gui_input));
929
930
menu = memnew(PopupMenu);
931
menu->connect(SceneStringName(id_pressed), callable_mp(this, &GroupsEditor::_menu_id_pressed));
932
tree->add_child(menu);
933
934
select_a_node = memnew(Label);
935
select_a_node->set_focus_mode(FOCUS_ACCESSIBILITY);
936
select_a_node->set_text(TTRC("Select one or more nodes to edit their groups."));
937
select_a_node->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
938
select_a_node->set_v_size_flags(SIZE_EXPAND_FILL);
939
select_a_node->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
940
select_a_node->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
941
select_a_node->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);
942
add_child(select_a_node);
943
944
ProjectSettingsEditor::get_singleton()->get_group_settings()->connect("group_changed", callable_mp(this, &GroupsEditor::_update_groups_and_tree));
945
}
946
947