Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/file_system/dependency_editor.cpp
20871 views
1
/**************************************************************************/
2
/* dependency_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 "dependency_editor.h"
32
33
#include "core/config/project_settings.h"
34
#include "core/io/file_access.h"
35
#include "core/io/resource_loader.h"
36
#include "editor/editor_node.h"
37
#include "editor/editor_string_names.h"
38
#include "editor/file_system/editor_file_system.h"
39
#include "editor/gui/editor_file_dialog.h"
40
#include "editor/gui/editor_quick_open_dialog.h"
41
#include "editor/settings/editor_settings.h"
42
#include "editor/themes/editor_scale.h"
43
#include "scene/gui/box_container.h"
44
#include "scene/gui/item_list.h"
45
#include "scene/gui/line_edit.h"
46
#include "scene/gui/margin_container.h"
47
#include "scene/gui/menu_button.h"
48
#include "scene/gui/popup_menu.h"
49
#include "scene/gui/tree.h"
50
51
static void _setup_search_file_dialog(EditorFileDialog *p_dialog, const String &p_file, const String &p_type) {
52
p_dialog->set_title(vformat(TTR("Search Replacement For: %s"), p_file.get_file()));
53
54
// Set directory to closest existing directory.
55
p_dialog->set_current_dir(p_file.get_base_dir());
56
57
p_dialog->clear_filters();
58
List<String> ext;
59
ResourceLoader::get_recognized_extensions_for_type(p_type, &ext);
60
for (const String &E : ext) {
61
p_dialog->add_filter("*." + E);
62
}
63
}
64
65
struct DependencyEditorSortByType {
66
bool operator()(const String &p_a, const String &p_b) const {
67
const String a_type = p_a.contains("::") ? p_a.get_slice("::", 1) : "Resource";
68
const String b_type = p_b.contains("::") ? p_b.get_slice("::", 1) : "Resource";
69
const String a_path = p_a.contains("::") ? p_a.get_slice("::", 2) : p_a;
70
const String b_path = p_b.contains("::") ? p_b.get_slice("::", 2) : p_b;
71
return a_type == b_type ? a_path < b_path : a_type < b_type;
72
}
73
};
74
75
struct DependencyEditorSortByPath {
76
bool operator()(const String &p_a, const String &p_b) const {
77
const String a_path = p_a.contains("::") ? p_a.get_slice("::", 2) : p_a;
78
const String b_path = p_b.contains("::") ? p_b.get_slice("::", 2) : p_b;
79
return a_path < b_path;
80
}
81
};
82
83
struct DependencyEditorSortByFile {
84
bool operator()(const String &p_a, const String &p_b) const {
85
const String a_path = p_a.contains("::") ? p_a.get_slice("::", 2) : p_a;
86
const String b_path = p_b.contains("::") ? p_b.get_slice("::", 2) : p_b;
87
const String a_file = a_path.get_file();
88
const String b_file = b_path.get_file();
89
return a_file == b_file ? a_path < b_path : a_file < b_file;
90
}
91
};
92
93
void DependencyEditor::_searched(const String &p_path) {
94
HashMap<String, String> dep_rename;
95
dep_rename[replacing] = p_path;
96
97
ResourceLoader::rename_dependencies(editing, dep_rename);
98
99
_update_list();
100
_update_file();
101
}
102
103
void DependencyEditor::_load_pressed(Object *p_item, int p_cell, int p_button, MouseButton p_mouse_button) {
104
if (p_mouse_button != MouseButton::LEFT) {
105
return;
106
}
107
TreeItem *ti = Object::cast_to<TreeItem>(p_item);
108
replacing = ti->get_text((int)Column::PATH);
109
110
_setup_search_file_dialog(search, replacing, ti->get_metadata(0));
111
search->popup_file_dialog();
112
}
113
114
void DependencyEditor::_fix_and_find(EditorFileSystemDirectory *efsd, HashMap<String, HashMap<String, String>> &candidates) {
115
for (int i = 0; i < efsd->get_subdir_count(); i++) {
116
_fix_and_find(efsd->get_subdir(i), candidates);
117
}
118
119
for (int i = 0; i < efsd->get_file_count(); i++) {
120
String file = efsd->get_file(i);
121
if (!candidates.has(file)) {
122
continue;
123
}
124
125
String path = efsd->get_file_path(i);
126
127
for (KeyValue<String, String> &E : candidates[file]) {
128
if (E.value.is_empty()) {
129
E.value = path;
130
continue;
131
}
132
133
//must match the best, using subdirs
134
String existing = E.value.replace_first("res://", "");
135
String current = path.replace_first("res://", "");
136
String lost = E.key.replace_first("res://", "");
137
138
Vector<String> existingv = existing.split("/");
139
existingv.reverse();
140
Vector<String> currentv = current.split("/");
141
currentv.reverse();
142
Vector<String> lostv = lost.split("/");
143
lostv.reverse();
144
145
int existing_score = 0;
146
int current_score = 0;
147
148
for (int j = 0; j < lostv.size(); j++) {
149
if (j < existingv.size() && lostv[j] == existingv[j]) {
150
existing_score++;
151
}
152
if (j < currentv.size() && lostv[j] == currentv[j]) {
153
current_score++;
154
}
155
}
156
157
if (current_score > existing_score) {
158
//if it was the same, could track distance to new path but..
159
160
E.value = path; //replace by more accurate
161
}
162
}
163
}
164
}
165
166
void DependencyEditor::_fix_all() {
167
if (!EditorFileSystem::get_singleton()->get_filesystem()) {
168
return;
169
}
170
171
HashMap<String, HashMap<String, String>> candidates;
172
173
for (const String &E : missing) {
174
String base = E.get_file();
175
if (!candidates.has(base)) {
176
candidates[base] = HashMap<String, String>();
177
}
178
179
candidates[base][E] = "";
180
}
181
182
_fix_and_find(EditorFileSystem::get_singleton()->get_filesystem(), candidates);
183
184
HashMap<String, String> remaps;
185
186
for (KeyValue<String, HashMap<String, String>> &E : candidates) {
187
for (const KeyValue<String, String> &F : E.value) {
188
if (!F.value.is_empty()) {
189
remaps[F.key] = F.value;
190
}
191
}
192
}
193
194
if (remaps.size()) {
195
ResourceLoader::rename_dependencies(editing, remaps);
196
197
_update_list();
198
_update_file();
199
}
200
}
201
202
void DependencyEditor::_update_file() {
203
EditorFileSystem::get_singleton()->update_file(editing);
204
}
205
206
void DependencyEditor::_notification(int p_what) {
207
if (p_what == NOTIFICATION_THEME_CHANGED) {
208
warning_label->add_theme_color_override(SceneStringName(font_color), get_theme_color("warning_color", EditorStringName(Editor)));
209
filter->set_right_icon(get_editor_theme_icon(SNAME("Search")));
210
menu_sort->set_button_icon(get_editor_theme_icon(SNAME("Sort")));
211
}
212
}
213
214
static String _get_resolved_dep_path(const String &p_dep) {
215
if (p_dep.get_slice_count("::") < 3) {
216
return p_dep.get_slice("::", 0); // No UID, just return the path.
217
}
218
219
const String uid_text = p_dep.get_slice("::", 0);
220
ResourceUID::ID uid = ResourceUID::get_singleton()->text_to_id(uid_text);
221
222
// Dependency is in UID format, obtain proper path.
223
if (uid != ResourceUID::INVALID_ID && ResourceUID::get_singleton()->has_id(uid)) {
224
return ResourceUID::get_singleton()->get_id_path(uid);
225
}
226
227
// UID fallback path.
228
return p_dep.get_slice("::", 2);
229
}
230
231
static String _get_stored_dep_path(const String &p_dep) {
232
if (p_dep.get_slice_count("::") > 2) {
233
return p_dep.get_slice("::", 2);
234
}
235
return p_dep.get_slice("::", 0);
236
}
237
238
List<String> DependencyEditor::_filter_deps(const List<String> &p_deps) {
239
const String filter_text = filter->get_text();
240
241
if (filter_text.is_empty()) {
242
return p_deps;
243
}
244
245
List<String> filtered;
246
247
for (const String &item : p_deps) {
248
const String path = item.contains("::") ? item.get_slice("::", 2) : item;
249
250
if (path.containsn(filter_text)) {
251
filtered.push_back(item);
252
}
253
}
254
255
return filtered;
256
}
257
258
void DependencyEditor::_update_list() {
259
List<String> deps;
260
ResourceLoader::get_dependencies(editing, &deps, true);
261
deps = _filter_deps(deps);
262
263
switch (sort_by) {
264
case DependencyEditorSortBy::TYPE:
265
deps.sort_custom<DependencyEditorSortByType>();
266
break;
267
case DependencyEditorSortBy::TYPE_REVERSE:
268
deps.sort_custom<DependencyEditorSortByType>();
269
deps.reverse();
270
break;
271
case DependencyEditorSortBy::PATH:
272
deps.sort_custom<DependencyEditorSortByPath>();
273
break;
274
case DependencyEditorSortBy::PATH_REVERSE:
275
deps.sort_custom<DependencyEditorSortByPath>();
276
deps.reverse();
277
break;
278
case DependencyEditorSortBy::NAME:
279
deps.sort_custom<DependencyEditorSortByFile>();
280
break;
281
case DependencyEditorSortBy::NAME_REVERSE:
282
deps.sort_custom<DependencyEditorSortByFile>();
283
deps.reverse();
284
break;
285
default:
286
break;
287
}
288
289
tree->clear();
290
missing.clear();
291
292
TreeItem *root = tree->create_item();
293
294
Ref<Texture2D> folder = tree->get_theme_icon(SNAME("folder"), SNAME("FileDialog"));
295
296
bool broken = false;
297
298
for (const String &dep : deps) {
299
TreeItem *item = tree->create_item(root);
300
301
const String path = _get_resolved_dep_path(dep);
302
if (FileAccess::exists(path)) {
303
item->set_text((int)Column::NAME, path.get_file());
304
item->set_text((int)Column::PATH, path);
305
} else {
306
const String &stored_path = _get_stored_dep_path(dep);
307
item->set_text((int)Column::NAME, stored_path.get_file());
308
item->set_text((int)Column::PATH, stored_path);
309
item->set_custom_color((int)Column::PATH, Color(1, 0.4, 0.3));
310
missing.push_back(stored_path);
311
broken = true;
312
}
313
314
const String type = dep.contains("::") ? dep.get_slice("::", 1) : "Resource";
315
String name = path.get_file();
316
Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(type);
317
item->set_icon((int)Column::TYPE, icon);
318
item->set_metadata(0, type);
319
item->set_text((int)Column::TYPE, type);
320
item->set_text((int)Column::NAME, name);
321
item->set_text((int)Column::PATH, path);
322
item->add_button((int)Column::PATH, folder, 0);
323
}
324
325
fixdeps->set_disabled(!broken);
326
}
327
328
void DependencyEditor::edit(const String &p_path) {
329
editing = p_path;
330
set_title(TTR("Dependencies For:") + " " + p_path.get_file());
331
332
filter->set_text("");
333
334
_update_menu_sort();
335
_update_list();
336
337
if (EditorNode::get_singleton()->is_scene_open(p_path)) {
338
warning_label->show();
339
warning_label->set_text(vformat(TTR("Scene \"%s\" is currently being edited. Changes will only take effect when reloaded."), p_path.get_file()));
340
} else if (ResourceCache::has(p_path)) {
341
warning_label->show();
342
warning_label->set_text(vformat(TTR("Resource \"%s\" is in use. Changes will only take effect when reloaded."), p_path.get_file()));
343
} else {
344
warning_label->hide();
345
}
346
popup_centered_ratio(0.4);
347
}
348
349
void DependencyEditor::_sort_option_selected(int p_id) {
350
sort_by = (DependencyEditorSortBy)p_id;
351
_update_menu_sort();
352
_update_list();
353
}
354
355
void DependencyEditor::_update_menu_sort() {
356
for (int i = 0; i != (int)DependencyEditorSortBy::MAX; i++) {
357
menu_sort->get_popup()->set_item_checked(i, (i == (int)sort_by));
358
}
359
}
360
361
DependencyEditor::DependencyEditor() {
362
VBoxContainer *vb = memnew(VBoxContainer);
363
add_child(vb);
364
365
tree = memnew(Tree);
366
tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
367
tree->set_theme_type_variation("TreeTable");
368
tree->set_hide_folding(true);
369
tree->set_columns((int)Column::MAX);
370
tree->set_column_titles_visible(true);
371
tree->set_column_title((int)Column::TYPE, TTRC("Type"));
372
tree->set_column_clip_content((int)Column::TYPE, true);
373
tree->set_column_expand_ratio((int)Column::TYPE, 2);
374
tree->set_column_title((int)Column::NAME, TTRC("Name"));
375
tree->set_column_clip_content((int)Column::NAME, true);
376
tree->set_column_expand_ratio((int)Column::NAME, 3);
377
tree->set_column_title((int)Column::PATH, TTRC("Path"));
378
tree->set_column_clip_content((int)Column::PATH, true);
379
tree->set_column_expand_ratio((int)Column::PATH, 5);
380
tree->set_hide_root(true);
381
tree->connect("button_clicked", callable_mp(this, &DependencyEditor::_load_pressed));
382
383
HBoxContainer *hbc = memnew(HBoxContainer);
384
Label *label = memnew(Label(TTR("Dependencies:")));
385
label->set_theme_type_variation("HeaderSmall");
386
387
hbc->add_child(label);
388
hbc->add_spacer();
389
fixdeps = memnew(Button(TTR("Fix Broken")));
390
hbc->add_child(fixdeps);
391
fixdeps->connect(SceneStringName(pressed), callable_mp(this, &DependencyEditor::_fix_all));
392
393
vb->add_child(hbc);
394
395
HBoxContainer *hbc_filter = memnew(HBoxContainer);
396
vb->add_child(hbc_filter);
397
filter = memnew(LineEdit);
398
filter->set_accessibility_name(TTRC("Filter Dependencies"));
399
filter->set_placeholder(TTRC("Filter Dependencies"));
400
filter->set_clear_button_enabled(true);
401
filter->set_h_size_flags(Control::SIZE_EXPAND_FILL);
402
filter->connect(SceneStringName(text_changed), callable_mp(this, &DependencyEditor::_update_list).unbind(1));
403
hbc_filter->add_child(filter);
404
405
menu_sort = memnew(MenuButton);
406
menu_sort->set_flat(false);
407
menu_sort->set_theme_type_variation("FlatMenuButton");
408
menu_sort->set_tooltip_text(TTRC("Sort Dependencies"));
409
menu_sort->set_accessibility_name(TTRC("Sort Dependencies"));
410
411
PopupMenu *popup_sort = menu_sort->get_popup();
412
popup_sort->connect(SceneStringName(id_pressed), callable_mp(this, &DependencyEditor::_sort_option_selected));
413
popup_sort->add_radio_check_item(TTRC("Sort by Type (Ascending)"), (int)DependencyEditorSortBy::TYPE);
414
popup_sort->add_radio_check_item(TTRC("Sort by Type (Descending)"), (int)DependencyEditorSortBy::TYPE_REVERSE);
415
popup_sort->add_radio_check_item(TTRC("Sort by Name (Ascending)"), (int)DependencyEditorSortBy::NAME);
416
popup_sort->add_radio_check_item(TTRC("Sort by Name (Descending)"), (int)DependencyEditorSortBy::NAME_REVERSE);
417
popup_sort->add_radio_check_item(TTRC("Sort by Path (Ascending)"), (int)DependencyEditorSortBy::PATH);
418
popup_sort->add_radio_check_item(TTRC("Sort by Path (Descending)"), (int)DependencyEditorSortBy::PATH_REVERSE);
419
popup_sort->set_item_checked((int)sort_by, true);
420
421
hbc_filter->add_child(menu_sort);
422
423
MarginContainer *mc = memnew(MarginContainer);
424
mc->set_v_size_flags(Control::SIZE_EXPAND_FILL);
425
426
mc->add_child(tree);
427
vb->add_child(mc);
428
429
warning_label = memnew(Label);
430
warning_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD);
431
vb->add_child(warning_label);
432
433
set_title(TTR("Dependency Editor"));
434
search = memnew(EditorFileDialog);
435
search->connect("file_selected", callable_mp(this, &DependencyEditor::_searched));
436
search->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
437
add_child(search);
438
}
439
440
/////////////////////////////////////
441
void DependencyEditorOwners::_list_rmb_clicked(int p_item, const Vector2 &p_pos, MouseButton p_mouse_button_index) {
442
if (p_mouse_button_index != MouseButton::RIGHT) {
443
return;
444
}
445
446
file_options->clear();
447
file_options->reset_size();
448
if (p_item >= 0) {
449
PackedInt32Array selected_items = owners->get_selected_items();
450
bool only_scenes_selected = true;
451
452
for (int i = 0; i < selected_items.size(); i++) {
453
int item_idx = selected_items[i];
454
if (ResourceLoader::get_resource_type(owners->get_item_text(item_idx)) != "PackedScene") {
455
only_scenes_selected = false;
456
break;
457
}
458
}
459
460
if (only_scenes_selected) {
461
file_options->add_icon_item(get_editor_theme_icon(SNAME("Load")), TTRN("Open Scene", "Open Scenes", selected_items.size()), FILE_MENU_OPEN);
462
} else if (selected_items.size() == 1) {
463
file_options->add_icon_item(get_editor_theme_icon(SNAME("Load")), TTR("Open"), FILE_MENU_OPEN);
464
} else {
465
return;
466
}
467
}
468
469
file_options->set_position(owners->get_screen_position() + p_pos);
470
file_options->reset_size();
471
file_options->popup();
472
}
473
474
void DependencyEditorOwners::_select_file(int p_idx) {
475
String fpath = owners->get_item_text(p_idx);
476
EditorNode::get_singleton()->load_scene_or_resource(fpath);
477
478
hide();
479
emit_signal(SceneStringName(confirmed));
480
}
481
482
void DependencyEditorOwners::_empty_clicked(const Vector2 &p_pos, MouseButton p_mouse_button_index) {
483
if (p_mouse_button_index != MouseButton::LEFT) {
484
return;
485
}
486
487
owners->deselect_all();
488
}
489
490
void DependencyEditorOwners::_file_option(int p_option) {
491
switch (p_option) {
492
case FILE_MENU_OPEN: {
493
PackedInt32Array selected_items = owners->get_selected_items();
494
for (int i = 0; i < selected_items.size(); i++) {
495
int item_idx = selected_items[i];
496
if (item_idx < 0 || item_idx >= owners->get_item_count()) {
497
break;
498
}
499
_select_file(item_idx);
500
}
501
} break;
502
}
503
}
504
505
void DependencyEditorOwners::_fill_owners(EditorFileSystemDirectory *efsd) {
506
if (!efsd) {
507
return;
508
}
509
510
for (int i = 0; i < efsd->get_subdir_count(); i++) {
511
_fill_owners(efsd->get_subdir(i));
512
}
513
514
for (int i = 0; i < efsd->get_file_count(); i++) {
515
Vector<String> deps = efsd->get_file_deps(i);
516
bool found = false;
517
for (int j = 0; j < deps.size(); j++) {
518
if (deps[j] == editing) {
519
found = true;
520
break;
521
}
522
}
523
if (!found) {
524
continue;
525
}
526
527
Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(efsd->get_file_type(i));
528
529
owners->add_item(efsd->get_file_path(i), icon);
530
}
531
}
532
533
void DependencyEditorOwners::show(const String &p_path) {
534
editing = p_path;
535
owners->clear();
536
_fill_owners(EditorFileSystem::get_singleton()->get_filesystem());
537
538
int count = owners->get_item_count();
539
if (count > 0) {
540
empty->hide();
541
owners_count->set_text(vformat(TTR("Owners of: %s (Total: %d)"), p_path.get_file(), count));
542
owners_count->show();
543
owners_mc->show();
544
} else {
545
owners_count->hide();
546
owners_mc->hide();
547
empty->set_text(vformat(TTR("No owners found for: %s"), p_path.get_file()));
548
empty->show();
549
}
550
551
popup_centered_ratio(0.3);
552
}
553
554
DependencyEditorOwners::DependencyEditorOwners() {
555
file_options = memnew(PopupMenu);
556
add_child(file_options);
557
file_options->connect(SceneStringName(id_pressed), callable_mp(this, &DependencyEditorOwners::_file_option));
558
559
VBoxContainer *vbox = memnew(VBoxContainer);
560
add_child(vbox);
561
562
owners_count = memnew(Label);
563
owners_count->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
564
owners_count->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);
565
owners_count->set_custom_minimum_size(Size2(200 * EDSCALE, 0));
566
vbox->add_child(owners_count);
567
568
empty = memnew(Label);
569
empty->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
570
empty->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
571
empty->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
572
empty->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);
573
empty->set_custom_minimum_size(Size2(200 * EDSCALE, 0));
574
empty->set_v_size_flags(Control::SIZE_EXPAND_FILL);
575
empty->hide();
576
vbox->add_child(empty);
577
578
owners_mc = memnew(MarginContainer);
579
owners_mc->set_v_size_flags(Control::SIZE_EXPAND_FILL);
580
owners_mc->set_theme_type_variation("NoBorderHorizontalWindow");
581
vbox->add_child(owners_mc);
582
583
owners = memnew(ItemList);
584
owners->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
585
owners->set_select_mode(ItemList::SELECT_MULTI);
586
owners->set_scroll_hint_mode(ItemList::SCROLL_HINT_MODE_BOTH);
587
owners->connect("item_clicked", callable_mp(this, &DependencyEditorOwners::_list_rmb_clicked));
588
owners->connect("item_activated", callable_mp(this, &DependencyEditorOwners::_select_file));
589
owners->connect("empty_clicked", callable_mp(this, &DependencyEditorOwners::_empty_clicked));
590
owners->set_allow_rmb_select(true);
591
owners_mc->add_child(owners);
592
593
set_title(TTRC("Owners List"));
594
}
595
596
///////////////////////
597
598
void DependencyRemoveDialog::_find_files_in_removed_folder(EditorFileSystemDirectory *efsd, const String &p_folder) {
599
if (!efsd) {
600
return;
601
}
602
603
for (int i = 0; i < efsd->get_subdir_count(); ++i) {
604
_find_files_in_removed_folder(efsd->get_subdir(i), p_folder);
605
}
606
for (int i = 0; i < efsd->get_file_count(); i++) {
607
String file = efsd->get_file_path(i);
608
ERR_FAIL_COND(all_remove_files.has(file)); //We are deleting a directory which is contained in a directory we are deleting...
609
all_remove_files[file] = p_folder; //Point the file to the ancestor directory we are deleting so we know what to parent it under in the tree.
610
}
611
}
612
613
void DependencyRemoveDialog::_find_all_removed_dependencies(EditorFileSystemDirectory *efsd, Vector<RemovedDependency> &p_removed) {
614
if (!efsd) {
615
return;
616
}
617
618
for (int i = 0; i < efsd->get_subdir_count(); i++) {
619
_find_all_removed_dependencies(efsd->get_subdir(i), p_removed);
620
}
621
622
for (int i = 0; i < efsd->get_file_count(); i++) {
623
const String path = efsd->get_file_path(i);
624
625
//It doesn't matter if a file we are about to delete will have some of its dependencies removed too
626
if (all_remove_files.has(path)) {
627
continue;
628
}
629
630
Vector<String> all_deps = efsd->get_file_deps(i);
631
for (int j = 0; j < all_deps.size(); ++j) {
632
if (all_remove_files.has(all_deps[j])) {
633
RemovedDependency dep;
634
dep.file = path;
635
dep.file_type = efsd->get_file_type(i);
636
dep.dependency = all_deps[j];
637
dep.dependency_folder = all_remove_files[all_deps[j]];
638
p_removed.push_back(dep);
639
}
640
}
641
}
642
}
643
644
void DependencyRemoveDialog::_find_localization_remaps_of_removed_files(Vector<RemovedDependency> &p_removed) {
645
for (KeyValue<String, String> &files : all_remove_files) {
646
const String &path = files.key;
647
648
// Look for dependencies in the translation remaps.
649
if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/translation_remaps")) {
650
Dictionary remaps = GLOBAL_GET("internationalization/locale/translation_remaps");
651
652
if (remaps.has(path)) {
653
RemovedDependency dep;
654
dep.file = TTR("Localization remap");
655
dep.file_type = "";
656
dep.dependency = path;
657
dep.dependency_folder = files.value;
658
p_removed.push_back(dep);
659
}
660
661
for (const KeyValue<Variant, Variant> &remap_kv : remaps) {
662
PackedStringArray remapped_files = remap_kv.value;
663
for (const String &remapped_file : remapped_files) {
664
int splitter_pos = remapped_file.rfind_char(':');
665
String res_path = remapped_file.substr(0, splitter_pos);
666
if (res_path == path) {
667
String locale_name = remapped_file.substr(splitter_pos + 1);
668
669
RemovedDependency dep;
670
dep.file = vformat(TTR("Localization remap for path '%s' and locale '%s'."), remap_kv.key, locale_name);
671
dep.file_type = "";
672
dep.dependency = path;
673
dep.dependency_folder = files.value;
674
p_removed.push_back(dep);
675
}
676
}
677
}
678
}
679
}
680
}
681
682
void DependencyRemoveDialog::_build_removed_dependency_tree(const Vector<RemovedDependency> &p_removed) {
683
owners->clear();
684
owners->create_item(); // root
685
686
HashMap<String, TreeItem *> tree_items;
687
for (int i = 0; i < p_removed.size(); i++) {
688
RemovedDependency rd = p_removed[i];
689
690
//Ensure that the dependency is already in the tree
691
if (!tree_items.has(rd.dependency)) {
692
if (rd.dependency_folder.length() > 0) {
693
//Ensure the ancestor folder is already in the tree
694
if (!tree_items.has(rd.dependency_folder)) {
695
TreeItem *folder_item = owners->create_item(owners->get_root());
696
folder_item->set_text(0, rd.dependency_folder);
697
folder_item->set_icon(0, owners->get_editor_theme_icon(SNAME("Folder")));
698
tree_items[rd.dependency_folder] = folder_item;
699
}
700
TreeItem *dependency_item = owners->create_item(tree_items[rd.dependency_folder]);
701
dependency_item->set_text(0, rd.dependency);
702
dependency_item->set_icon(0, owners->get_editor_theme_icon(SNAME("Warning")));
703
tree_items[rd.dependency] = dependency_item;
704
} else {
705
TreeItem *dependency_item = owners->create_item(owners->get_root());
706
dependency_item->set_text(0, rd.dependency);
707
dependency_item->set_icon(0, owners->get_editor_theme_icon(SNAME("Warning")));
708
tree_items[rd.dependency] = dependency_item;
709
}
710
}
711
712
//List this file under this dependency
713
Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(rd.file_type);
714
TreeItem *file_item = owners->create_item(tree_items[rd.dependency]);
715
file_item->set_text(0, rd.file);
716
file_item->set_icon(0, icon);
717
}
718
}
719
720
void DependencyRemoveDialog::_show_files_to_delete_list() {
721
files_to_delete_list->clear();
722
723
for (const String &s : dirs_to_delete) {
724
String t = s.trim_prefix("res://");
725
files_to_delete_list->add_item(t, Ref<Texture2D>(), false);
726
}
727
728
for (const String &s : files_to_delete) {
729
String t = s.trim_prefix("res://");
730
files_to_delete_list->add_item(t, Ref<Texture2D>(), false);
731
}
732
}
733
734
void DependencyRemoveDialog::show(const Vector<String> &p_folders, const Vector<String> &p_files) {
735
all_remove_files.clear();
736
dirs_to_delete.clear();
737
files_to_delete.clear();
738
owners->clear();
739
740
for (int i = 0; i < p_folders.size(); ++i) {
741
String folder = p_folders[i].ends_with("/") ? p_folders[i] : (p_folders[i] + "/");
742
_find_files_in_removed_folder(EditorFileSystem::get_singleton()->get_filesystem_path(folder), folder);
743
dirs_to_delete.push_back(folder);
744
}
745
for (int i = 0; i < p_files.size(); ++i) {
746
all_remove_files[p_files[i]] = String();
747
files_to_delete.push_back(p_files[i]);
748
}
749
750
_show_files_to_delete_list();
751
752
Vector<RemovedDependency> removed_deps;
753
_find_all_removed_dependencies(EditorFileSystem::get_singleton()->get_filesystem(), removed_deps);
754
_find_localization_remaps_of_removed_files(removed_deps);
755
removed_deps.sort();
756
if (removed_deps.is_empty()) {
757
vb_owners->hide();
758
text->set_text(TTR("Remove the selected files from the project? (Cannot be undone.)\nDepending on your filesystem configuration, the files will either be moved to the system trash or deleted permanently."));
759
reset_size();
760
popup_centered();
761
} else {
762
_build_removed_dependency_tree(removed_deps);
763
vb_owners->show();
764
text->set_text(TTR("The files being removed are required by other resources in order for them to work.\nRemove them anyway? (Cannot be undone.)\nDepending on your filesystem configuration, the files will either be moved to the system trash or deleted permanently."));
765
popup_centered(Size2(500, 350));
766
}
767
768
EditorFileSystem::get_singleton()->scan_changes();
769
}
770
771
void DependencyRemoveDialog::ok_pressed() {
772
HashMap<String, StringName> setting_path_map;
773
for (const StringName &setting : path_project_settings) {
774
const String path = ResourceUID::ensure_path(GLOBAL_GET(setting));
775
setting_path_map[path] = setting;
776
}
777
778
bool project_settings_modified = false;
779
780
for (const KeyValue<String, String> &E : all_remove_files) {
781
String file = E.key;
782
783
if (ResourceCache::has(file)) {
784
Ref<Resource> res = ResourceCache::get_ref(file);
785
emit_signal(SNAME("resource_removed"), res);
786
res->set_path("");
787
}
788
789
// If the file we are deleting for e.g. the main scene, default environment,
790
// or audio bus layout, we must clear its definition in Project Settings.
791
const StringName *setting_name = setting_path_map.getptr(file);
792
if (setting_name) {
793
ProjectSettings::get_singleton()->set(*setting_name, "");
794
project_settings_modified = true;
795
}
796
}
797
798
for (const String &file : files_to_delete) {
799
const String path = OS::get_singleton()->get_resource_dir() + file.replace_first("res://", "/");
800
print_verbose("Moving to trash: " + path);
801
Error err = OS::get_singleton()->move_to_trash(path);
802
if (err != OK) {
803
EditorNode::get_singleton()->add_io_error(TTR("Cannot remove:") + "\n" + file + "\n");
804
} else {
805
emit_signal(SNAME("file_removed"), file);
806
}
807
}
808
if (project_settings_modified) {
809
ProjectSettings::get_singleton()->save();
810
}
811
812
if (dirs_to_delete.is_empty()) {
813
// If we only deleted files we should only need to tell the file system about the files we touched.
814
for (int i = 0; i < files_to_delete.size(); ++i) {
815
EditorFileSystem::get_singleton()->update_file(files_to_delete[i]);
816
}
817
} else {
818
for (int i = 0; i < dirs_to_delete.size(); ++i) {
819
String path = OS::get_singleton()->get_resource_dir() + dirs_to_delete[i].replace_first("res://", "/");
820
print_verbose("Moving to trash: " + path);
821
Error err = OS::get_singleton()->move_to_trash(path);
822
if (err != OK) {
823
EditorNode::get_singleton()->add_io_error(TTR("Cannot remove:") + "\n" + dirs_to_delete[i] + "\n");
824
} else {
825
emit_signal(SNAME("folder_removed"), dirs_to_delete[i]);
826
}
827
}
828
829
EditorFileSystem::get_singleton()->scan_changes();
830
}
831
832
// If some files/dirs would be deleted, favorite dirs need to be updated
833
Vector<String> previous_favorites = EditorSettings::get_singleton()->get_favorites();
834
Vector<String> new_favorites;
835
836
for (int i = 0; i < previous_favorites.size(); ++i) {
837
if (previous_favorites[i].ends_with("/")) {
838
if (!dirs_to_delete.has(previous_favorites[i])) {
839
new_favorites.push_back(previous_favorites[i]);
840
}
841
} else {
842
if (!files_to_delete.has(previous_favorites[i])) {
843
new_favorites.push_back(previous_favorites[i]);
844
}
845
}
846
}
847
848
if (new_favorites.size() < previous_favorites.size()) {
849
EditorSettings::get_singleton()->set_favorites(new_favorites);
850
}
851
}
852
853
void DependencyRemoveDialog::_bind_methods() {
854
ADD_SIGNAL(MethodInfo("resource_removed", PropertyInfo(Variant::OBJECT, "obj")));
855
ADD_SIGNAL(MethodInfo("file_removed", PropertyInfo(Variant::STRING, "file")));
856
ADD_SIGNAL(MethodInfo("folder_removed", PropertyInfo(Variant::STRING, "folder")));
857
}
858
859
DependencyRemoveDialog::DependencyRemoveDialog() {
860
set_ok_button_text(TTR("Remove"));
861
862
VBoxContainer *vb = memnew(VBoxContainer);
863
vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
864
add_child(vb);
865
866
text = memnew(Label);
867
text->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
868
vb->add_child(text);
869
870
Label *files_to_delete_label = memnew(Label);
871
files_to_delete_label->set_theme_type_variation("HeaderSmall");
872
files_to_delete_label->set_text(TTR("Files to be deleted:"));
873
vb->add_child(files_to_delete_label);
874
875
MarginContainer *mc = memnew(MarginContainer);
876
mc->set_theme_type_variation("NoBorderHorizontalWindow");
877
mc->set_v_size_flags(Control::SIZE_EXPAND_FILL);
878
vb->add_child(mc);
879
880
files_to_delete_list = memnew(ItemList);
881
files_to_delete_list->set_scroll_hint_mode(ItemList::SCROLL_HINT_MODE_BOTH);
882
files_to_delete_list->set_custom_minimum_size(Size2(0, 94) * EDSCALE);
883
files_to_delete_list->set_accessibility_name(TTRC("Files to be deleted:"));
884
mc->add_child(files_to_delete_list);
885
886
vb_owners = memnew(VBoxContainer);
887
vb_owners->set_h_size_flags(Control::SIZE_EXPAND_FILL);
888
vb_owners->set_v_size_flags(Control::SIZE_EXPAND_FILL);
889
vb->add_child(vb_owners);
890
891
Label *owners_label = memnew(Label);
892
owners_label->set_theme_type_variation("HeaderSmall");
893
owners_label->set_text(TTR("Dependencies of files to be deleted:"));
894
vb_owners->add_child(owners_label);
895
896
mc = memnew(MarginContainer);
897
mc->set_theme_type_variation("NoBorderHorizontalWindow");
898
mc->set_v_size_flags(Control::SIZE_EXPAND_FILL);
899
vb_owners->add_child(mc);
900
901
owners = memnew(Tree);
902
owners->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
903
owners->set_scroll_hint_mode(Tree::SCROLL_HINT_MODE_BOTH);
904
owners->set_hide_root(true);
905
owners->set_custom_minimum_size(Size2(0, 94) * EDSCALE);
906
owners->set_accessibility_name(TTRC("Dependencies"));
907
mc->add_child(owners);
908
owners->set_v_size_flags(Control::SIZE_EXPAND_FILL);
909
910
List<PropertyInfo> property_list;
911
ProjectSettings::get_singleton()->get_property_list(&property_list);
912
for (const PropertyInfo &pi : property_list) {
913
if (pi.type == Variant::STRING && pi.hint == PROPERTY_HINT_FILE) {
914
path_project_settings.push_back(pi.name);
915
}
916
}
917
}
918
919
//////////////
920
enum {
921
BUTTON_ID_SEARCH,
922
BUTTON_ID_OPEN_DEPS_EDITOR,
923
};
924
925
void DependencyErrorDialog::show(const String &p_for_file, const HashMap<String, HashSet<String>> &p_report) {
926
for_file = p_for_file;
927
928
// TRANSLATORS: The placeholder is a filename.
929
set_title(vformat(TTR("Error loading: %s"), p_for_file.get_file()));
930
931
HashMap<String, HashSet<String>> missing_to_owners;
932
for (const KeyValue<String, HashSet<String>> &E : p_report) {
933
for (const String &missing : E.value) {
934
missing_to_owners[missing].insert(E.key);
935
}
936
}
937
938
files->clear();
939
TreeItem *root = files->create_item(nullptr);
940
Ref<Texture2D> folder_icon = get_theme_icon(SNAME("folder"), SNAME("FileDialog"));
941
942
for (const KeyValue<String, HashSet<String>> &E : missing_to_owners) {
943
const String &missing_path = E.key.get_slice("::", 0);
944
const String &missing_type = E.key.get_slice("::", 1);
945
946
TreeItem *missing_ti = root->create_child();
947
missing_ti->set_text(0, missing_path);
948
missing_ti->set_metadata(0, E.key);
949
missing_ti->set_auto_translate_mode(0, AUTO_TRANSLATE_MODE_DISABLED);
950
missing_ti->set_icon(0, EditorNode::get_singleton()->get_class_icon(missing_type));
951
missing_ti->set_icon(1, get_editor_theme_icon(icon_name_fail));
952
missing_ti->add_button(1, folder_icon, BUTTON_ID_SEARCH, false, TTRC("Search"));
953
missing_ti->set_collapsed(true);
954
955
for (const String &owner_path : E.value) {
956
TreeItem *owner_ti = missing_ti->create_child();
957
// TRANSLATORS: The placeholder is a file path.
958
owner_ti->set_text(0, vformat(TTR("Referenced by %s"), owner_path));
959
owner_ti->set_metadata(0, owner_path);
960
owner_ti->set_auto_translate_mode(0, AUTO_TRANSLATE_MODE_DISABLED);
961
owner_ti->add_button(1, files->get_editor_theme_icon(SNAME("Edit")), BUTTON_ID_OPEN_DEPS_EDITOR, false, TTRC("Fix Dependencies"));
962
}
963
}
964
965
set_ok_button_text(TTRC("Open Anyway"));
966
popup_centered();
967
}
968
969
void DependencyErrorDialog::ok_pressed() {
970
EditorNode::get_singleton()->load_scene_or_resource(for_file, !errors_fixed);
971
}
972
973
void DependencyErrorDialog::_on_files_button_clicked(TreeItem *p_item, int p_column, int p_id, MouseButton p_button) {
974
switch (p_id) {
975
case BUTTON_ID_SEARCH: {
976
const String &meta = p_item->get_metadata(0);
977
const String &missing_path = meta.get_slice("::", 0);
978
const String &missing_type = meta.get_slice("::", 1);
979
if (replacement_file_dialog == nullptr) {
980
replacement_file_dialog = memnew(EditorFileDialog);
981
replacement_file_dialog->connect("file_selected", callable_mp(this, &DependencyErrorDialog::_on_replacement_file_selected));
982
replacement_file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
983
add_child(replacement_file_dialog);
984
}
985
replacing_item = p_item;
986
_setup_search_file_dialog(replacement_file_dialog, missing_path, missing_type);
987
replacement_file_dialog->popup_file_dialog();
988
} break;
989
990
case BUTTON_ID_OPEN_DEPS_EDITOR: {
991
const String &owner_path = p_item->get_metadata(0);
992
if (deps_editor == nullptr) {
993
deps_editor = memnew(DependencyEditor);
994
deps_editor->connect(SceneStringName(visibility_changed), callable_mp(this, &DependencyErrorDialog::_check_for_resolved));
995
add_child(deps_editor);
996
}
997
deps_editor->edit(owner_path);
998
} break;
999
}
1000
}
1001
1002
void DependencyErrorDialog::_on_replacement_file_selected(const String &p_path) {
1003
const String &missing_path = String(replacing_item->get_metadata(0)).get_slice("::", 0);
1004
1005
for (TreeItem *owner_ti = replacing_item->get_first_child(); owner_ti; owner_ti = owner_ti->get_next()) {
1006
const String &owner_path = owner_ti->get_metadata(0);
1007
ResourceLoader::rename_dependencies(owner_path, { { missing_path, p_path } });
1008
}
1009
1010
_check_for_resolved();
1011
}
1012
1013
void DependencyErrorDialog::_check_for_resolved() {
1014
if (deps_editor && deps_editor->is_visible()) {
1015
return; // Only update when the dialog is closed.
1016
}
1017
1018
errors_fixed = true;
1019
HashMap<String, LocalVector<String>> owner_deps;
1020
1021
TreeItem *root = files->get_root();
1022
for (TreeItem *missing_ti = root->get_first_child(); missing_ti; missing_ti = missing_ti->get_next()) {
1023
bool all_owners_fixed = true;
1024
1025
for (TreeItem *owner_ti = missing_ti->get_first_child(); owner_ti; owner_ti = owner_ti->get_next()) {
1026
const String &owner_path = owner_ti->get_metadata(0);
1027
1028
if (!owner_deps.has(owner_path)) {
1029
List<String> deps;
1030
ResourceLoader::get_dependencies(owner_path, &deps);
1031
1032
LocalVector<String> &stored_paths = owner_deps[owner_path];
1033
for (const String &dep : deps) {
1034
if (!errors_fixed && !FileAccess::exists(_get_resolved_dep_path(dep))) {
1035
errors_fixed = false;
1036
}
1037
stored_paths.push_back(_get_stored_dep_path(dep));
1038
}
1039
}
1040
const LocalVector<String> &stored_paths = owner_deps[owner_path];
1041
const String &missing_path = String(missing_ti->get_metadata(0)).get_slice("::", 0);
1042
1043
if (stored_paths.has(missing_path)) {
1044
all_owners_fixed = false;
1045
break;
1046
}
1047
}
1048
1049
missing_ti->set_icon(1, get_editor_theme_icon(all_owners_fixed ? icon_name_check : icon_name_fail));
1050
}
1051
1052
set_ok_button_text(errors_fixed ? TTRC("Open") : TTRC("Open Anyway"));
1053
}
1054
1055
DependencyErrorDialog::DependencyErrorDialog() {
1056
icon_name_fail = StringName("ImportFail");
1057
icon_name_check = StringName("ImportCheck");
1058
1059
VBoxContainer *vb = memnew(VBoxContainer);
1060
add_child(vb);
1061
1062
files = memnew(Tree);
1063
files->set_hide_root(true);
1064
files->set_select_mode(Tree::SELECT_ROW);
1065
files->set_columns(2);
1066
files->set_column_expand(1, false);
1067
files->set_v_size_flags(Control::SIZE_EXPAND_FILL);
1068
files->connect("button_clicked", callable_mp(this, &DependencyErrorDialog::_on_files_button_clicked));
1069
vb->add_margin_child(TTRC("Load failed due to missing dependencies:"), files, true);
1070
1071
set_min_size(Size2(500, 320) * EDSCALE);
1072
set_cancel_button_text(TTRC("Close"));
1073
1074
Label *text = memnew(Label(TTRC("Which action should be taken?")));
1075
text->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
1076
vb->add_child(text);
1077
}
1078
1079
//////////////////////////////////////////////////////////////////////
1080
1081
void OrphanResourcesDialog::ok_pressed() {
1082
paths.clear();
1083
1084
_find_to_delete(files->get_root(), paths);
1085
if (paths.is_empty()) {
1086
return;
1087
}
1088
1089
delete_confirm->set_text(vformat(TTR("Permanently delete %d item(s)? (No undo!)"), paths.size()));
1090
delete_confirm->popup_centered();
1091
}
1092
1093
bool OrphanResourcesDialog::_fill_owners(EditorFileSystemDirectory *efsd, HashMap<String, int> &refs, TreeItem *p_parent) {
1094
if (!efsd) {
1095
return false;
1096
}
1097
1098
bool has_children = false;
1099
1100
for (int i = 0; i < efsd->get_subdir_count(); i++) {
1101
TreeItem *dir_item = nullptr;
1102
if (p_parent) {
1103
dir_item = files->create_item(p_parent);
1104
dir_item->set_text(0, efsd->get_subdir(i)->get_name());
1105
dir_item->set_icon(0, files->get_theme_icon(SNAME("folder"), SNAME("FileDialog")));
1106
}
1107
bool children = _fill_owners(efsd->get_subdir(i), refs, dir_item);
1108
1109
if (p_parent) {
1110
if (!children) {
1111
memdelete(dir_item);
1112
} else {
1113
has_children = true;
1114
}
1115
}
1116
}
1117
1118
for (int i = 0; i < efsd->get_file_count(); i++) {
1119
if (!p_parent) {
1120
Vector<String> deps = efsd->get_file_deps(i);
1121
for (int j = 0; j < deps.size(); j++) {
1122
if (!refs.has(deps[j])) {
1123
refs[deps[j]] = 1;
1124
}
1125
}
1126
} else {
1127
String path = efsd->get_file_path(i);
1128
if (!refs.has(path)) {
1129
TreeItem *ti = files->create_item(p_parent);
1130
ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
1131
ti->set_text(0, efsd->get_file(i));
1132
ti->set_editable(0, true);
1133
1134
String type = efsd->get_file_type(i);
1135
1136
Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(type);
1137
ti->set_icon(0, icon);
1138
int ds = efsd->get_file_deps(i).size();
1139
ti->set_text(1, itos(ds));
1140
if (ds) {
1141
ti->add_button(1, files->get_editor_theme_icon(SNAME("GuiVisibilityVisible")), -1, false, TTR("Show Dependencies"));
1142
}
1143
ti->set_metadata(0, path);
1144
has_children = true;
1145
}
1146
}
1147
}
1148
1149
return has_children;
1150
}
1151
1152
void OrphanResourcesDialog::refresh() {
1153
HashMap<String, int> refs;
1154
_fill_owners(EditorFileSystem::get_singleton()->get_filesystem(), refs, nullptr);
1155
files->clear();
1156
TreeItem *root = files->create_item();
1157
_fill_owners(EditorFileSystem::get_singleton()->get_filesystem(), refs, root);
1158
}
1159
1160
void OrphanResourcesDialog::show() {
1161
refresh();
1162
popup_centered_ratio(0.4);
1163
}
1164
1165
void OrphanResourcesDialog::_find_to_delete(TreeItem *p_item, List<String> &r_paths) {
1166
while (p_item) {
1167
if (p_item->get_cell_mode(0) == TreeItem::CELL_MODE_CHECK && p_item->is_checked(0)) {
1168
r_paths.push_back(p_item->get_metadata(0));
1169
}
1170
1171
if (p_item->get_first_child()) {
1172
_find_to_delete(p_item->get_first_child(), r_paths);
1173
}
1174
1175
p_item = p_item->get_next();
1176
}
1177
}
1178
1179
void OrphanResourcesDialog::_delete_confirm() {
1180
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
1181
for (const String &E : paths) {
1182
da->remove(E);
1183
EditorFileSystem::get_singleton()->update_file(E);
1184
}
1185
refresh();
1186
}
1187
1188
void OrphanResourcesDialog::_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button) {
1189
if (p_button != MouseButton::LEFT) {
1190
return;
1191
}
1192
TreeItem *ti = Object::cast_to<TreeItem>(p_item);
1193
1194
String path = ti->get_metadata(0);
1195
dep_edit->edit(path);
1196
}
1197
1198
OrphanResourcesDialog::OrphanResourcesDialog() {
1199
set_title(TTR("Orphan Resource Explorer"));
1200
delete_confirm = memnew(ConfirmationDialog);
1201
set_ok_button_text(TTR("Delete"));
1202
add_child(delete_confirm);
1203
dep_edit = memnew(DependencyEditor);
1204
add_child(dep_edit);
1205
delete_confirm->connect(SceneStringName(confirmed), callable_mp(this, &OrphanResourcesDialog::_delete_confirm));
1206
set_hide_on_ok(false);
1207
1208
VBoxContainer *vbc = memnew(VBoxContainer);
1209
add_child(vbc);
1210
1211
files = memnew(Tree);
1212
files->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
1213
files->set_theme_type_variation("TreeTable");
1214
files->set_columns(2);
1215
files->set_column_titles_visible(true);
1216
files->set_column_custom_minimum_width(1, 100 * EDSCALE);
1217
files->set_column_expand(0, true);
1218
files->set_column_clip_content(0, true);
1219
files->set_column_expand(1, false);
1220
files->set_column_clip_content(1, true);
1221
files->set_column_title(0, TTR("Resource"));
1222
files->set_column_title(1, TTR("Owns"));
1223
files->set_hide_root(true);
1224
files->set_scroll_hint_mode(Tree::SCROLL_HINT_MODE_BOTTOM);
1225
files->connect("button_clicked", callable_mp(this, &OrphanResourcesDialog::_button_pressed));
1226
1227
MarginContainer *mc = vbc->add_margin_child(TTRC("Resources Without Explicit Ownership:"), files, true);
1228
mc->set_theme_type_variation("NoBorderHorizontalWindow");
1229
mc->set_v_size_flags(Control::SIZE_EXPAND_FILL);
1230
}
1231
1232