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