Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/file_system/dependency_editor.cpp
9896 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/file_system/editor_file_system.h"
38
#include "editor/gui/editor_file_dialog.h"
39
#include "editor/settings/editor_settings.h"
40
#include "editor/themes/editor_scale.h"
41
#include "scene/gui/margin_container.h"
42
#include "scene/gui/popup_menu.h"
43
44
void DependencyEditor::_searched(const String &p_path) {
45
HashMap<String, String> dep_rename;
46
dep_rename[replacing] = p_path;
47
48
ResourceLoader::rename_dependencies(editing, dep_rename);
49
50
_update_list();
51
_update_file();
52
}
53
54
void DependencyEditor::_load_pressed(Object *p_item, int p_cell, int p_button, MouseButton p_mouse_button) {
55
if (p_mouse_button != MouseButton::LEFT) {
56
return;
57
}
58
TreeItem *ti = Object::cast_to<TreeItem>(p_item);
59
replacing = ti->get_text(1);
60
61
search->set_title(TTR("Search Replacement For:") + " " + replacing.get_file());
62
63
// Set directory to closest existing directory.
64
search->set_current_dir(replacing.get_base_dir());
65
66
search->clear_filters();
67
List<String> ext;
68
ResourceLoader::get_recognized_extensions_for_type(ti->get_metadata(0), &ext);
69
for (const String &E : ext) {
70
search->add_filter("*." + E);
71
}
72
search->popup_file_dialog();
73
}
74
75
void DependencyEditor::_fix_and_find(EditorFileSystemDirectory *efsd, HashMap<String, HashMap<String, String>> &candidates) {
76
for (int i = 0; i < efsd->get_subdir_count(); i++) {
77
_fix_and_find(efsd->get_subdir(i), candidates);
78
}
79
80
for (int i = 0; i < efsd->get_file_count(); i++) {
81
String file = efsd->get_file(i);
82
if (!candidates.has(file)) {
83
continue;
84
}
85
86
String path = efsd->get_file_path(i);
87
88
for (KeyValue<String, String> &E : candidates[file]) {
89
if (E.value.is_empty()) {
90
E.value = path;
91
continue;
92
}
93
94
//must match the best, using subdirs
95
String existing = E.value.replace_first("res://", "");
96
String current = path.replace_first("res://", "");
97
String lost = E.key.replace_first("res://", "");
98
99
Vector<String> existingv = existing.split("/");
100
existingv.reverse();
101
Vector<String> currentv = current.split("/");
102
currentv.reverse();
103
Vector<String> lostv = lost.split("/");
104
lostv.reverse();
105
106
int existing_score = 0;
107
int current_score = 0;
108
109
for (int j = 0; j < lostv.size(); j++) {
110
if (j < existingv.size() && lostv[j] == existingv[j]) {
111
existing_score++;
112
}
113
if (j < currentv.size() && lostv[j] == currentv[j]) {
114
current_score++;
115
}
116
}
117
118
if (current_score > existing_score) {
119
//if it was the same, could track distance to new path but..
120
121
E.value = path; //replace by more accurate
122
}
123
}
124
}
125
}
126
127
void DependencyEditor::_fix_all() {
128
if (!EditorFileSystem::get_singleton()->get_filesystem()) {
129
return;
130
}
131
132
HashMap<String, HashMap<String, String>> candidates;
133
134
for (const String &E : missing) {
135
String base = E.get_file();
136
if (!candidates.has(base)) {
137
candidates[base] = HashMap<String, String>();
138
}
139
140
candidates[base][E] = "";
141
}
142
143
_fix_and_find(EditorFileSystem::get_singleton()->get_filesystem(), candidates);
144
145
HashMap<String, String> remaps;
146
147
for (KeyValue<String, HashMap<String, String>> &E : candidates) {
148
for (const KeyValue<String, String> &F : E.value) {
149
if (!F.value.is_empty()) {
150
remaps[F.key] = F.value;
151
}
152
}
153
}
154
155
if (remaps.size()) {
156
ResourceLoader::rename_dependencies(editing, remaps);
157
158
_update_list();
159
_update_file();
160
}
161
}
162
163
void DependencyEditor::_update_file() {
164
EditorFileSystem::get_singleton()->update_file(editing);
165
}
166
167
void DependencyEditor::_update_list() {
168
List<String> deps;
169
ResourceLoader::get_dependencies(editing, &deps, true);
170
171
tree->clear();
172
missing.clear();
173
174
TreeItem *root = tree->create_item();
175
176
Ref<Texture2D> folder = tree->get_theme_icon(SNAME("folder"), SNAME("FileDialog"));
177
178
bool broken = false;
179
180
for (const String &n : deps) {
181
TreeItem *item = tree->create_item(root);
182
String path;
183
String type;
184
185
if (n.contains("::")) {
186
path = n.get_slice("::", 0);
187
type = n.get_slice("::", 1);
188
} else {
189
path = n;
190
type = "Resource";
191
}
192
193
ResourceUID::ID uid = ResourceUID::get_singleton()->text_to_id(path);
194
if (uid != ResourceUID::INVALID_ID) {
195
// Dependency is in uid format, obtain proper path.
196
if (ResourceUID::get_singleton()->has_id(uid)) {
197
path = ResourceUID::get_singleton()->get_id_path(uid);
198
} else if (n.get_slice_count("::") >= 3) {
199
// If uid can't be found, try to use fallback path.
200
path = n.get_slice("::", 2);
201
} else {
202
ERR_PRINT("Invalid dependency UID and fallback path.");
203
continue;
204
}
205
}
206
207
String name = path.get_file();
208
209
Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(type);
210
item->set_text(0, name);
211
item->set_icon(0, icon);
212
item->set_metadata(0, type);
213
item->set_text(1, path);
214
215
if (!FileAccess::exists(path)) {
216
item->set_custom_color(1, Color(1, 0.4, 0.3));
217
missing.push_back(path);
218
broken = true;
219
}
220
221
item->add_button(1, folder, 0);
222
}
223
224
fixdeps->set_disabled(!broken);
225
}
226
227
void DependencyEditor::edit(const String &p_path) {
228
editing = p_path;
229
set_title(TTR("Dependencies For:") + " " + p_path.get_file());
230
231
_update_list();
232
popup_centered_ratio(0.4);
233
234
if (EditorNode::get_singleton()->is_scene_open(p_path)) {
235
EditorNode::get_singleton()->show_warning(vformat(TTR("Scene '%s' is currently being edited.\nChanges will only take effect when reloaded."), p_path.get_file()));
236
} else if (ResourceCache::has(p_path)) {
237
EditorNode::get_singleton()->show_warning(vformat(TTR("Resource '%s' is in use.\nChanges will only take effect when reloaded."), p_path.get_file()));
238
}
239
}
240
241
DependencyEditor::DependencyEditor() {
242
VBoxContainer *vb = memnew(VBoxContainer);
243
vb->set_name(TTR("Dependencies"));
244
add_child(vb);
245
246
tree = memnew(Tree);
247
tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
248
tree->set_columns(2);
249
tree->set_column_titles_visible(true);
250
tree->set_column_title(0, TTR("Resource"));
251
tree->set_column_clip_content(0, true);
252
tree->set_column_expand_ratio(0, 2);
253
tree->set_column_title(1, TTR("Path"));
254
tree->set_column_clip_content(1, true);
255
tree->set_column_expand_ratio(1, 1);
256
tree->set_hide_root(true);
257
tree->connect("button_clicked", callable_mp(this, &DependencyEditor::_load_pressed));
258
259
HBoxContainer *hbc = memnew(HBoxContainer);
260
Label *label = memnew(Label(TTR("Dependencies:")));
261
label->set_theme_type_variation("HeaderSmall");
262
263
hbc->add_child(label);
264
hbc->add_spacer();
265
fixdeps = memnew(Button(TTR("Fix Broken")));
266
hbc->add_child(fixdeps);
267
fixdeps->connect(SceneStringName(pressed), callable_mp(this, &DependencyEditor::_fix_all));
268
269
vb->add_child(hbc);
270
271
MarginContainer *mc = memnew(MarginContainer);
272
mc->set_v_size_flags(Control::SIZE_EXPAND_FILL);
273
274
mc->add_child(tree);
275
vb->add_child(mc);
276
277
set_title(TTR("Dependency Editor"));
278
search = memnew(EditorFileDialog);
279
search->connect("file_selected", callable_mp(this, &DependencyEditor::_searched));
280
search->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
281
search->set_title(TTR("Search Replacement Resource:"));
282
add_child(search);
283
}
284
285
/////////////////////////////////////
286
void DependencyEditorOwners::_list_rmb_clicked(int p_item, const Vector2 &p_pos, MouseButton p_mouse_button_index) {
287
if (p_mouse_button_index != MouseButton::RIGHT) {
288
return;
289
}
290
291
file_options->clear();
292
file_options->reset_size();
293
if (p_item >= 0) {
294
PackedInt32Array selected_items = owners->get_selected_items();
295
bool only_scenes_selected = true;
296
297
for (int i = 0; i < selected_items.size(); i++) {
298
int item_idx = selected_items[i];
299
if (ResourceLoader::get_resource_type(owners->get_item_text(item_idx)) != "PackedScene") {
300
only_scenes_selected = false;
301
break;
302
}
303
}
304
305
if (only_scenes_selected) {
306
file_options->add_icon_item(get_editor_theme_icon(SNAME("Load")), TTRN("Open Scene", "Open Scenes", selected_items.size()), FILE_MENU_OPEN);
307
} else if (selected_items.size() == 1) {
308
file_options->add_icon_item(get_editor_theme_icon(SNAME("Load")), TTR("Open"), FILE_MENU_OPEN);
309
} else {
310
return;
311
}
312
}
313
314
file_options->set_position(owners->get_screen_position() + p_pos);
315
file_options->reset_size();
316
file_options->popup();
317
}
318
319
void DependencyEditorOwners::_select_file(int p_idx) {
320
String fpath = owners->get_item_text(p_idx);
321
EditorNode::get_singleton()->load_scene_or_resource(fpath);
322
323
hide();
324
emit_signal(SceneStringName(confirmed));
325
}
326
327
void DependencyEditorOwners::_empty_clicked(const Vector2 &p_pos, MouseButton p_mouse_button_index) {
328
if (p_mouse_button_index != MouseButton::LEFT) {
329
return;
330
}
331
332
owners->deselect_all();
333
}
334
335
void DependencyEditorOwners::_file_option(int p_option) {
336
switch (p_option) {
337
case FILE_MENU_OPEN: {
338
PackedInt32Array selected_items = owners->get_selected_items();
339
for (int i = 0; i < selected_items.size(); i++) {
340
int item_idx = selected_items[i];
341
if (item_idx < 0 || item_idx >= owners->get_item_count()) {
342
break;
343
}
344
_select_file(item_idx);
345
}
346
} break;
347
}
348
}
349
350
void DependencyEditorOwners::_fill_owners(EditorFileSystemDirectory *efsd) {
351
if (!efsd) {
352
return;
353
}
354
355
for (int i = 0; i < efsd->get_subdir_count(); i++) {
356
_fill_owners(efsd->get_subdir(i));
357
}
358
359
for (int i = 0; i < efsd->get_file_count(); i++) {
360
Vector<String> deps = efsd->get_file_deps(i);
361
bool found = false;
362
for (int j = 0; j < deps.size(); j++) {
363
if (deps[j] == editing) {
364
found = true;
365
break;
366
}
367
}
368
if (!found) {
369
continue;
370
}
371
372
Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(efsd->get_file_type(i));
373
374
owners->add_item(efsd->get_file_path(i), icon);
375
}
376
}
377
378
void DependencyEditorOwners::show(const String &p_path) {
379
editing = p_path;
380
owners->clear();
381
_fill_owners(EditorFileSystem::get_singleton()->get_filesystem());
382
popup_centered_ratio(0.3);
383
384
set_title(vformat(TTR("Owners of: %s (Total: %d)"), p_path.get_file(), owners->get_item_count()));
385
}
386
387
DependencyEditorOwners::DependencyEditorOwners() {
388
file_options = memnew(PopupMenu);
389
add_child(file_options);
390
file_options->connect(SceneStringName(id_pressed), callable_mp(this, &DependencyEditorOwners::_file_option));
391
392
owners = memnew(ItemList);
393
owners->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
394
owners->set_select_mode(ItemList::SELECT_MULTI);
395
owners->connect("item_clicked", callable_mp(this, &DependencyEditorOwners::_list_rmb_clicked));
396
owners->connect("item_activated", callable_mp(this, &DependencyEditorOwners::_select_file));
397
owners->connect("empty_clicked", callable_mp(this, &DependencyEditorOwners::_empty_clicked));
398
owners->set_allow_rmb_select(true);
399
add_child(owners);
400
}
401
402
///////////////////////
403
404
void DependencyRemoveDialog::_find_files_in_removed_folder(EditorFileSystemDirectory *efsd, const String &p_folder) {
405
if (!efsd) {
406
return;
407
}
408
409
for (int i = 0; i < efsd->get_subdir_count(); ++i) {
410
_find_files_in_removed_folder(efsd->get_subdir(i), p_folder);
411
}
412
for (int i = 0; i < efsd->get_file_count(); i++) {
413
String file = efsd->get_file_path(i);
414
ERR_FAIL_COND(all_remove_files.has(file)); //We are deleting a directory which is contained in a directory we are deleting...
415
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.
416
}
417
}
418
419
void DependencyRemoveDialog::_find_all_removed_dependencies(EditorFileSystemDirectory *efsd, Vector<RemovedDependency> &p_removed) {
420
if (!efsd) {
421
return;
422
}
423
424
for (int i = 0; i < efsd->get_subdir_count(); i++) {
425
_find_all_removed_dependencies(efsd->get_subdir(i), p_removed);
426
}
427
428
for (int i = 0; i < efsd->get_file_count(); i++) {
429
const String path = efsd->get_file_path(i);
430
431
//It doesn't matter if a file we are about to delete will have some of its dependencies removed too
432
if (all_remove_files.has(path)) {
433
continue;
434
}
435
436
Vector<String> all_deps = efsd->get_file_deps(i);
437
for (int j = 0; j < all_deps.size(); ++j) {
438
if (all_remove_files.has(all_deps[j])) {
439
RemovedDependency dep;
440
dep.file = path;
441
dep.file_type = efsd->get_file_type(i);
442
dep.dependency = all_deps[j];
443
dep.dependency_folder = all_remove_files[all_deps[j]];
444
p_removed.push_back(dep);
445
}
446
}
447
}
448
}
449
450
void DependencyRemoveDialog::_find_localization_remaps_of_removed_files(Vector<RemovedDependency> &p_removed) {
451
for (KeyValue<String, String> &files : all_remove_files) {
452
const String &path = files.key;
453
454
// Look for dependencies in the translation remaps.
455
if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/translation_remaps")) {
456
Dictionary remaps = GLOBAL_GET("internationalization/locale/translation_remaps");
457
458
if (remaps.has(path)) {
459
RemovedDependency dep;
460
dep.file = TTR("Localization remap");
461
dep.file_type = "";
462
dep.dependency = path;
463
dep.dependency_folder = files.value;
464
p_removed.push_back(dep);
465
}
466
467
for (const KeyValue<Variant, Variant> &remap_kv : remaps) {
468
PackedStringArray remapped_files = remap_kv.value;
469
for (const String &remapped_file : remapped_files) {
470
int splitter_pos = remapped_file.rfind_char(':');
471
String res_path = remapped_file.substr(0, splitter_pos);
472
if (res_path == path) {
473
String locale_name = remapped_file.substr(splitter_pos + 1);
474
475
RemovedDependency dep;
476
dep.file = vformat(TTR("Localization remap for path '%s' and locale '%s'."), remap_kv.key, locale_name);
477
dep.file_type = "";
478
dep.dependency = path;
479
dep.dependency_folder = files.value;
480
p_removed.push_back(dep);
481
}
482
}
483
}
484
}
485
}
486
}
487
488
void DependencyRemoveDialog::_build_removed_dependency_tree(const Vector<RemovedDependency> &p_removed) {
489
owners->clear();
490
owners->create_item(); // root
491
492
HashMap<String, TreeItem *> tree_items;
493
for (int i = 0; i < p_removed.size(); i++) {
494
RemovedDependency rd = p_removed[i];
495
496
//Ensure that the dependency is already in the tree
497
if (!tree_items.has(rd.dependency)) {
498
if (rd.dependency_folder.length() > 0) {
499
//Ensure the ancestor folder is already in the tree
500
if (!tree_items.has(rd.dependency_folder)) {
501
TreeItem *folder_item = owners->create_item(owners->get_root());
502
folder_item->set_text(0, rd.dependency_folder);
503
folder_item->set_icon(0, owners->get_editor_theme_icon(SNAME("Folder")));
504
tree_items[rd.dependency_folder] = folder_item;
505
}
506
TreeItem *dependency_item = owners->create_item(tree_items[rd.dependency_folder]);
507
dependency_item->set_text(0, rd.dependency);
508
dependency_item->set_icon(0, owners->get_editor_theme_icon(SNAME("Warning")));
509
tree_items[rd.dependency] = dependency_item;
510
} else {
511
TreeItem *dependency_item = owners->create_item(owners->get_root());
512
dependency_item->set_text(0, rd.dependency);
513
dependency_item->set_icon(0, owners->get_editor_theme_icon(SNAME("Warning")));
514
tree_items[rd.dependency] = dependency_item;
515
}
516
}
517
518
//List this file under this dependency
519
Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(rd.file_type);
520
TreeItem *file_item = owners->create_item(tree_items[rd.dependency]);
521
file_item->set_text(0, rd.file);
522
file_item->set_icon(0, icon);
523
}
524
}
525
526
void DependencyRemoveDialog::_show_files_to_delete_list() {
527
files_to_delete_list->clear();
528
529
for (const String &s : dirs_to_delete) {
530
String t = s.trim_prefix("res://");
531
files_to_delete_list->add_item(t, Ref<Texture2D>(), false);
532
}
533
534
for (const String &s : files_to_delete) {
535
String t = s.trim_prefix("res://");
536
files_to_delete_list->add_item(t, Ref<Texture2D>(), false);
537
}
538
}
539
540
void DependencyRemoveDialog::show(const Vector<String> &p_folders, const Vector<String> &p_files) {
541
all_remove_files.clear();
542
dirs_to_delete.clear();
543
files_to_delete.clear();
544
owners->clear();
545
546
for (int i = 0; i < p_folders.size(); ++i) {
547
String folder = p_folders[i].ends_with("/") ? p_folders[i] : (p_folders[i] + "/");
548
_find_files_in_removed_folder(EditorFileSystem::get_singleton()->get_filesystem_path(folder), folder);
549
dirs_to_delete.push_back(folder);
550
}
551
for (int i = 0; i < p_files.size(); ++i) {
552
all_remove_files[p_files[i]] = String();
553
files_to_delete.push_back(p_files[i]);
554
}
555
556
_show_files_to_delete_list();
557
558
Vector<RemovedDependency> removed_deps;
559
_find_all_removed_dependencies(EditorFileSystem::get_singleton()->get_filesystem(), removed_deps);
560
_find_localization_remaps_of_removed_files(removed_deps);
561
removed_deps.sort();
562
if (removed_deps.is_empty()) {
563
vb_owners->hide();
564
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."));
565
reset_size();
566
popup_centered();
567
} else {
568
_build_removed_dependency_tree(removed_deps);
569
vb_owners->show();
570
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."));
571
popup_centered(Size2(500, 350));
572
}
573
574
EditorFileSystem::get_singleton()->scan_changes();
575
}
576
577
void DependencyRemoveDialog::ok_pressed() {
578
for (const KeyValue<String, String> &E : all_remove_files) {
579
String file = E.key;
580
581
if (ResourceCache::has(file)) {
582
Ref<Resource> res = ResourceCache::get_ref(file);
583
emit_signal(SNAME("resource_removed"), res);
584
res->set_path("");
585
}
586
}
587
588
HashMap<String, StringName> setting_path_map;
589
for (const StringName &setting : path_project_settings) {
590
const String path = ResourceUID::ensure_path(GLOBAL_GET(setting));
591
setting_path_map[path] = setting;
592
}
593
594
bool project_settings_modified = false;
595
for (const String &file : files_to_delete) {
596
// If the file we are deleting for e.g. the main scene, default environment,
597
// or audio bus layout, we must clear its definition in Project Settings.
598
const StringName *setting_name = setting_path_map.getptr(file);
599
if (setting_name) {
600
ProjectSettings::get_singleton()->set(*setting_name, "");
601
}
602
603
const String path = OS::get_singleton()->get_resource_dir() + file.replace_first("res://", "/");
604
print_verbose("Moving to trash: " + path);
605
Error err = OS::get_singleton()->move_to_trash(path);
606
if (err != OK) {
607
EditorNode::get_singleton()->add_io_error(TTR("Cannot remove:") + "\n" + file + "\n");
608
} else {
609
emit_signal(SNAME("file_removed"), file);
610
}
611
}
612
if (project_settings_modified) {
613
ProjectSettings::get_singleton()->save();
614
}
615
616
if (dirs_to_delete.is_empty()) {
617
// If we only deleted files we should only need to tell the file system about the files we touched.
618
for (int i = 0; i < files_to_delete.size(); ++i) {
619
EditorFileSystem::get_singleton()->update_file(files_to_delete[i]);
620
}
621
} else {
622
for (int i = 0; i < dirs_to_delete.size(); ++i) {
623
String path = OS::get_singleton()->get_resource_dir() + dirs_to_delete[i].replace_first("res://", "/");
624
print_verbose("Moving to trash: " + path);
625
Error err = OS::get_singleton()->move_to_trash(path);
626
if (err != OK) {
627
EditorNode::get_singleton()->add_io_error(TTR("Cannot remove:") + "\n" + dirs_to_delete[i] + "\n");
628
} else {
629
emit_signal(SNAME("folder_removed"), dirs_to_delete[i]);
630
}
631
}
632
633
EditorFileSystem::get_singleton()->scan_changes();
634
}
635
636
// If some files/dirs would be deleted, favorite dirs need to be updated
637
Vector<String> previous_favorites = EditorSettings::get_singleton()->get_favorites();
638
Vector<String> new_favorites;
639
640
for (int i = 0; i < previous_favorites.size(); ++i) {
641
if (previous_favorites[i].ends_with("/")) {
642
if (!dirs_to_delete.has(previous_favorites[i])) {
643
new_favorites.push_back(previous_favorites[i]);
644
}
645
} else {
646
if (!files_to_delete.has(previous_favorites[i])) {
647
new_favorites.push_back(previous_favorites[i]);
648
}
649
}
650
}
651
652
if (new_favorites.size() < previous_favorites.size()) {
653
EditorSettings::get_singleton()->set_favorites(new_favorites);
654
}
655
}
656
657
void DependencyRemoveDialog::_bind_methods() {
658
ADD_SIGNAL(MethodInfo("resource_removed", PropertyInfo(Variant::OBJECT, "obj")));
659
ADD_SIGNAL(MethodInfo("file_removed", PropertyInfo(Variant::STRING, "file")));
660
ADD_SIGNAL(MethodInfo("folder_removed", PropertyInfo(Variant::STRING, "folder")));
661
}
662
663
DependencyRemoveDialog::DependencyRemoveDialog() {
664
set_ok_button_text(TTR("Remove"));
665
666
VBoxContainer *vb = memnew(VBoxContainer);
667
vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
668
add_child(vb);
669
670
text = memnew(Label);
671
text->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
672
vb->add_child(text);
673
674
Label *files_to_delete_label = memnew(Label);
675
files_to_delete_label->set_theme_type_variation("HeaderSmall");
676
files_to_delete_label->set_text(TTR("Files to be deleted:"));
677
vb->add_child(files_to_delete_label);
678
679
files_to_delete_list = memnew(ItemList);
680
files_to_delete_list->set_h_size_flags(Control::SIZE_EXPAND_FILL);
681
files_to_delete_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
682
files_to_delete_list->set_custom_minimum_size(Size2(0, 94) * EDSCALE);
683
files_to_delete_list->set_accessibility_name(TTRC("Files to be deleted:"));
684
vb->add_child(files_to_delete_list);
685
686
vb_owners = memnew(VBoxContainer);
687
vb_owners->set_h_size_flags(Control::SIZE_EXPAND_FILL);
688
vb_owners->set_v_size_flags(Control::SIZE_EXPAND_FILL);
689
vb->add_child(vb_owners);
690
691
Label *owners_label = memnew(Label);
692
owners_label->set_theme_type_variation("HeaderSmall");
693
owners_label->set_text(TTR("Dependencies of files to be deleted:"));
694
vb_owners->add_child(owners_label);
695
696
owners = memnew(Tree);
697
owners->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
698
owners->set_hide_root(true);
699
owners->set_custom_minimum_size(Size2(0, 94) * EDSCALE);
700
owners->set_accessibility_name(TTRC("Dependencies"));
701
vb_owners->add_child(owners);
702
owners->set_v_size_flags(Control::SIZE_EXPAND_FILL);
703
704
List<PropertyInfo> property_list;
705
ProjectSettings::get_singleton()->get_property_list(&property_list);
706
for (const PropertyInfo &pi : property_list) {
707
if (pi.type == Variant::STRING && pi.hint == PROPERTY_HINT_FILE) {
708
path_project_settings.push_back(pi.name);
709
}
710
}
711
}
712
713
//////////////
714
715
void DependencyErrorDialog::show(const String &p_for_file, const Vector<String> &report) {
716
for_file = p_for_file;
717
set_title(TTR("Error loading:") + " " + p_for_file.get_file());
718
files->clear();
719
720
TreeItem *root = files->create_item(nullptr);
721
for (int i = 0; i < report.size(); i++) {
722
String dep;
723
String type = "Object";
724
dep = report[i].get_slice("::", 0);
725
if (report[i].get_slice_count("::") > 0) {
726
type = report[i].get_slice("::", 1);
727
}
728
729
Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(type);
730
731
TreeItem *ti = files->create_item(root);
732
ti->set_text(0, dep);
733
ti->set_icon(0, icon);
734
}
735
736
popup_centered();
737
}
738
739
void DependencyErrorDialog::ok_pressed() {
740
EditorNode::get_singleton()->load_scene_or_resource(for_file, true);
741
}
742
743
void DependencyErrorDialog::custom_action(const String &) {
744
EditorNode::get_singleton()->fix_dependencies(for_file);
745
}
746
747
DependencyErrorDialog::DependencyErrorDialog() {
748
VBoxContainer *vb = memnew(VBoxContainer);
749
add_child(vb);
750
751
files = memnew(Tree);
752
files->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
753
files->set_hide_root(true);
754
vb->add_margin_child(TTR("Load failed due to missing dependencies:"), files, true);
755
files->set_v_size_flags(Control::SIZE_EXPAND_FILL);
756
757
set_min_size(Size2(500, 220) * EDSCALE);
758
set_ok_button_text(TTR("Open Anyway"));
759
set_cancel_button_text(TTR("Close"));
760
761
text = memnew(Label);
762
text->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
763
vb->add_child(text);
764
text->set_text(TTR("Which action should be taken?"));
765
766
fdep = add_button(TTR("Fix Dependencies"), true, "fixdeps");
767
768
set_title(TTR("Errors loading!"));
769
}
770
771
//////////////////////////////////////////////////////////////////////
772
773
void OrphanResourcesDialog::ok_pressed() {
774
paths.clear();
775
776
_find_to_delete(files->get_root(), paths);
777
if (paths.is_empty()) {
778
return;
779
}
780
781
delete_confirm->set_text(vformat(TTR("Permanently delete %d item(s)? (No undo!)"), paths.size()));
782
delete_confirm->popup_centered();
783
}
784
785
bool OrphanResourcesDialog::_fill_owners(EditorFileSystemDirectory *efsd, HashMap<String, int> &refs, TreeItem *p_parent) {
786
if (!efsd) {
787
return false;
788
}
789
790
bool has_children = false;
791
792
for (int i = 0; i < efsd->get_subdir_count(); i++) {
793
TreeItem *dir_item = nullptr;
794
if (p_parent) {
795
dir_item = files->create_item(p_parent);
796
dir_item->set_text(0, efsd->get_subdir(i)->get_name());
797
dir_item->set_icon(0, files->get_theme_icon(SNAME("folder"), SNAME("FileDialog")));
798
}
799
bool children = _fill_owners(efsd->get_subdir(i), refs, dir_item);
800
801
if (p_parent) {
802
if (!children) {
803
memdelete(dir_item);
804
} else {
805
has_children = true;
806
}
807
}
808
}
809
810
for (int i = 0; i < efsd->get_file_count(); i++) {
811
if (!p_parent) {
812
Vector<String> deps = efsd->get_file_deps(i);
813
for (int j = 0; j < deps.size(); j++) {
814
if (!refs.has(deps[j])) {
815
refs[deps[j]] = 1;
816
}
817
}
818
} else {
819
String path = efsd->get_file_path(i);
820
if (!refs.has(path)) {
821
TreeItem *ti = files->create_item(p_parent);
822
ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
823
ti->set_text(0, efsd->get_file(i));
824
ti->set_editable(0, true);
825
826
String type = efsd->get_file_type(i);
827
828
Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(type);
829
ti->set_icon(0, icon);
830
int ds = efsd->get_file_deps(i).size();
831
ti->set_text(1, itos(ds));
832
if (ds) {
833
ti->add_button(1, files->get_editor_theme_icon(SNAME("GuiVisibilityVisible")), -1, false, TTR("Show Dependencies"));
834
}
835
ti->set_metadata(0, path);
836
has_children = true;
837
}
838
}
839
}
840
841
return has_children;
842
}
843
844
void OrphanResourcesDialog::refresh() {
845
HashMap<String, int> refs;
846
_fill_owners(EditorFileSystem::get_singleton()->get_filesystem(), refs, nullptr);
847
files->clear();
848
TreeItem *root = files->create_item();
849
_fill_owners(EditorFileSystem::get_singleton()->get_filesystem(), refs, root);
850
}
851
852
void OrphanResourcesDialog::show() {
853
refresh();
854
popup_centered_ratio(0.4);
855
}
856
857
void OrphanResourcesDialog::_find_to_delete(TreeItem *p_item, List<String> &r_paths) {
858
while (p_item) {
859
if (p_item->get_cell_mode(0) == TreeItem::CELL_MODE_CHECK && p_item->is_checked(0)) {
860
r_paths.push_back(p_item->get_metadata(0));
861
}
862
863
if (p_item->get_first_child()) {
864
_find_to_delete(p_item->get_first_child(), r_paths);
865
}
866
867
p_item = p_item->get_next();
868
}
869
}
870
871
void OrphanResourcesDialog::_delete_confirm() {
872
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
873
for (const String &E : paths) {
874
da->remove(E);
875
EditorFileSystem::get_singleton()->update_file(E);
876
}
877
refresh();
878
}
879
880
void OrphanResourcesDialog::_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button) {
881
if (p_button != MouseButton::LEFT) {
882
return;
883
}
884
TreeItem *ti = Object::cast_to<TreeItem>(p_item);
885
886
String path = ti->get_metadata(0);
887
dep_edit->edit(path);
888
}
889
890
OrphanResourcesDialog::OrphanResourcesDialog() {
891
set_title(TTR("Orphan Resource Explorer"));
892
delete_confirm = memnew(ConfirmationDialog);
893
set_ok_button_text(TTR("Delete"));
894
add_child(delete_confirm);
895
dep_edit = memnew(DependencyEditor);
896
add_child(dep_edit);
897
delete_confirm->connect(SceneStringName(confirmed), callable_mp(this, &OrphanResourcesDialog::_delete_confirm));
898
set_hide_on_ok(false);
899
900
VBoxContainer *vbc = memnew(VBoxContainer);
901
add_child(vbc);
902
903
files = memnew(Tree);
904
files->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
905
files->set_columns(2);
906
files->set_column_titles_visible(true);
907
files->set_column_custom_minimum_width(1, 100 * EDSCALE);
908
files->set_column_expand(0, true);
909
files->set_column_clip_content(0, true);
910
files->set_column_expand(1, false);
911
files->set_column_clip_content(1, true);
912
files->set_column_title(0, TTR("Resource"));
913
files->set_column_title(1, TTR("Owns"));
914
files->set_hide_root(true);
915
vbc->add_margin_child(TTR("Resources Without Explicit Ownership:"), files, true);
916
files->connect("button_clicked", callable_mp(this, &OrphanResourcesDialog::_button_pressed));
917
}
918
919