1
0
mirror of https://github.com/godotengine/godot.git synced 2025-11-14 13:41:12 +00:00

Allow fixing indirect missing dependencies manually

This commit is contained in:
Haoyu Qiu
2025-06-16 10:56:13 +08:00
parent 07f4c06601
commit f0d5073037
6 changed files with 222 additions and 111 deletions

View File

@@ -37,10 +37,28 @@
#include "editor/editor_string_names.h"
#include "editor/file_system/editor_file_system.h"
#include "editor/gui/editor_file_dialog.h"
#include "editor/gui/editor_quick_open_dialog.h"
#include "editor/settings/editor_settings.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/box_container.h"
#include "scene/gui/item_list.h"
#include "scene/gui/margin_container.h"
#include "scene/gui/popup_menu.h"
#include "scene/gui/tree.h"
static void _setup_search_file_dialog(EditorFileDialog *p_dialog, const String &p_file, const String &p_type) {
p_dialog->set_title(vformat(TTR("Search Replacement For: %s"), p_file.get_file()));
// Set directory to closest existing directory.
p_dialog->set_current_dir(p_file.get_base_dir());
p_dialog->clear_filters();
List<String> ext;
ResourceLoader::get_recognized_extensions_for_type(p_type, &ext);
for (const String &E : ext) {
p_dialog->add_filter("*." + E);
}
}
void DependencyEditor::_searched(const String &p_path) {
HashMap<String, String> dep_rename;
@@ -59,17 +77,7 @@ void DependencyEditor::_load_pressed(Object *p_item, int p_cell, int p_button, M
TreeItem *ti = Object::cast_to<TreeItem>(p_item);
replacing = ti->get_text(1);
search->set_title(TTR("Search Replacement For:") + " " + replacing.get_file());
// Set directory to closest existing directory.
search->set_current_dir(replacing.get_base_dir());
search->clear_filters();
List<String> ext;
ResourceLoader::get_recognized_extensions_for_type(ti->get_metadata(0), &ext);
for (const String &E : ext) {
search->add_filter("*." + E);
}
_setup_search_file_dialog(search, replacing, ti->get_metadata(0));
search->popup_file_dialog();
}
@@ -171,6 +179,30 @@ void DependencyEditor::_notification(int p_what) {
}
}
static String _get_resolved_dep_path(const String &p_dep) {
if (p_dep.get_slice_count("::") < 3) {
return p_dep.get_slice("::", 0); // No UID, just return the path.
}
const String uid_text = p_dep.get_slice("::", 0);
ResourceUID::ID uid = ResourceUID::get_singleton()->text_to_id(uid_text);
// Dependency is in UID format, obtain proper path.
if (uid != ResourceUID::INVALID_ID && ResourceUID::get_singleton()->has_id(uid)) {
return ResourceUID::get_singleton()->get_id_path(uid);
}
// UID fallback path.
return p_dep.get_slice("::", 2);
}
static String _get_stored_dep_path(const String &p_dep) {
if (p_dep.get_slice_count("::") > 2) {
return p_dep.get_slice("::", 2);
}
return p_dep.get_slice("::", 0);
}
void DependencyEditor::_update_list() {
List<String> deps;
ResourceLoader::get_dependencies(editing, &deps, true);
@@ -184,47 +216,27 @@ void DependencyEditor::_update_list() {
bool broken = false;
for (const String &n : deps) {
for (const String &dep : deps) {
TreeItem *item = tree->create_item(root);
String path;
String type;
if (n.contains("::")) {
path = n.get_slice("::", 0);
type = n.get_slice("::", 1);
const String path = _get_resolved_dep_path(dep);
if (FileAccess::exists(path)) {
item->set_text(0, path.get_file());
item->set_text(1, path);
} else {
path = n;
type = "Resource";
}
ResourceUID::ID uid = ResourceUID::get_singleton()->text_to_id(path);
if (uid != ResourceUID::INVALID_ID) {
// Dependency is in uid format, obtain proper path.
if (ResourceUID::get_singleton()->has_id(uid)) {
path = ResourceUID::get_singleton()->get_id_path(uid);
} else if (n.get_slice_count("::") >= 3) {
// If uid can't be found, try to use fallback path.
path = n.get_slice("::", 2);
} else {
ERR_PRINT("Invalid dependency UID and fallback path.");
continue;
}
}
String name = path.get_file();
Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(type);
item->set_text(0, name);
item->set_icon(0, icon);
item->set_metadata(0, type);
item->set_text(1, path);
if (!FileAccess::exists(path)) {
const String &stored_path = _get_stored_dep_path(dep);
item->set_text(0, stored_path.get_file());
item->set_text(1, stored_path);
item->set_custom_color(1, Color(1, 0.4, 0.3));
missing.push_back(path);
missing.push_back(stored_path);
broken = true;
}
const String type = dep.contains("::") ? dep.get_slice("::", 1) : "Resource";
Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(type);
item->set_icon(0, icon);
item->set_metadata(0, type);
item->add_button(1, folder, 0);
}
@@ -260,10 +272,8 @@ DependencyEditor::DependencyEditor() {
tree->set_column_titles_visible(true);
tree->set_column_title(0, TTR("Resource"));
tree->set_column_clip_content(0, true);
tree->set_column_expand_ratio(0, 2);
tree->set_column_title(1, TTR("Path"));
tree->set_column_clip_content(1, true);
tree->set_column_expand_ratio(1, 1);
tree->set_hide_root(true);
tree->connect("button_clicked", callable_mp(this, &DependencyEditor::_load_pressed));
@@ -293,7 +303,6 @@ DependencyEditor::DependencyEditor() {
search = memnew(EditorFileDialog);
search->connect("file_selected", callable_mp(this, &DependencyEditor::_searched));
search->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
search->set_title(TTR("Search Replacement Resource:"));
add_child(search);
}
@@ -728,61 +737,163 @@ DependencyRemoveDialog::DependencyRemoveDialog() {
}
//////////////
enum {
BUTTON_ID_SEARCH,
BUTTON_ID_OPEN_DEPS_EDITOR,
};
void DependencyErrorDialog::show(const String &p_for_file, const Vector<String> &report) {
void DependencyErrorDialog::show(const String &p_for_file, const HashMap<String, HashSet<String>> &p_report) {
for_file = p_for_file;
set_title(TTR("Error loading:") + " " + p_for_file.get_file());
files->clear();
TreeItem *root = files->create_item(nullptr);
for (int i = 0; i < report.size(); i++) {
String dep;
String type = "Object";
dep = report[i].get_slice("::", 0);
if (report[i].get_slice_count("::") > 0) {
type = report[i].get_slice("::", 1);
// TRANSLATORS: The placeholder is a filename.
set_title(vformat(TTR("Error loading: %s"), p_for_file.get_file()));
HashMap<String, HashSet<String>> missing_to_owners;
for (const KeyValue<String, HashSet<String>> &E : p_report) {
for (const String &missing : E.value) {
missing_to_owners[missing].insert(E.key);
}
Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(type);
TreeItem *ti = files->create_item(root);
ti->set_text(0, dep);
ti->set_icon(0, icon);
}
files->clear();
TreeItem *root = files->create_item(nullptr);
Ref<Texture2D> folder_icon = get_theme_icon(SNAME("folder"), SNAME("FileDialog"));
for (const KeyValue<String, HashSet<String>> &E : missing_to_owners) {
const String &missing_path = E.key.get_slice("::", 0);
const String &missing_type = E.key.get_slice("::", 1);
TreeItem *missing_ti = root->create_child();
missing_ti->set_text(0, missing_path);
missing_ti->set_metadata(0, E.key);
missing_ti->set_auto_translate_mode(0, AUTO_TRANSLATE_MODE_DISABLED);
missing_ti->set_icon(0, EditorNode::get_singleton()->get_class_icon(missing_type));
missing_ti->set_icon(1, get_editor_theme_icon(icon_name_fail));
missing_ti->add_button(1, folder_icon, BUTTON_ID_SEARCH, false, TTRC("Search"));
missing_ti->set_collapsed(true);
for (const String &owner_path : E.value) {
TreeItem *owner_ti = missing_ti->create_child();
// TRANSLATORS: The placeholder is a file path.
owner_ti->set_text(0, vformat(TTR("Referenced by %s"), owner_path));
owner_ti->set_metadata(0, owner_path);
owner_ti->set_auto_translate_mode(0, AUTO_TRANSLATE_MODE_DISABLED);
owner_ti->add_button(1, files->get_editor_theme_icon(SNAME("Edit")), BUTTON_ID_OPEN_DEPS_EDITOR, false, TTRC("Fix Dependencies"));
}
}
set_ok_button_text(TTRC("Open Anyway"));
popup_centered();
}
void DependencyErrorDialog::ok_pressed() {
EditorNode::get_singleton()->load_scene_or_resource(for_file, true);
EditorNode::get_singleton()->load_scene_or_resource(for_file, !errors_fixed);
}
void DependencyErrorDialog::custom_action(const String &) {
EditorNode::get_singleton()->fix_dependencies(for_file);
void DependencyErrorDialog::_on_files_button_clicked(TreeItem *p_item, int p_column, int p_id, MouseButton p_button) {
switch (p_id) {
case BUTTON_ID_SEARCH: {
const String &meta = p_item->get_metadata(0);
const String &missing_path = meta.get_slice("::", 0);
const String &missing_type = meta.get_slice("::", 1);
if (replacement_file_dialog == nullptr) {
replacement_file_dialog = memnew(EditorFileDialog);
replacement_file_dialog->connect("file_selected", callable_mp(this, &DependencyErrorDialog::_on_replacement_file_selected));
replacement_file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
add_child(replacement_file_dialog);
}
replacing_item = p_item;
_setup_search_file_dialog(replacement_file_dialog, missing_path, missing_type);
replacement_file_dialog->popup_file_dialog();
} break;
case BUTTON_ID_OPEN_DEPS_EDITOR: {
const String &owner_path = p_item->get_metadata(0);
if (deps_editor == nullptr) {
deps_editor = memnew(DependencyEditor);
deps_editor->connect(SceneStringName(visibility_changed), callable_mp(this, &DependencyErrorDialog::_check_for_resolved));
add_child(deps_editor);
}
deps_editor->edit(owner_path);
} break;
}
}
void DependencyErrorDialog::_on_replacement_file_selected(const String &p_path) {
const String &missing_path = String(replacing_item->get_metadata(0)).get_slice("::", 0);
for (TreeItem *owner_ti = replacing_item->get_first_child(); owner_ti; owner_ti = owner_ti->get_next()) {
const String &owner_path = owner_ti->get_metadata(0);
ResourceLoader::rename_dependencies(owner_path, { { missing_path, p_path } });
}
_check_for_resolved();
}
void DependencyErrorDialog::_check_for_resolved() {
if (deps_editor && deps_editor->is_visible()) {
return; // Only update when the dialog is closed.
}
errors_fixed = true;
HashMap<String, LocalVector<String>> owner_deps;
TreeItem *root = files->get_root();
for (TreeItem *missing_ti = root->get_first_child(); missing_ti; missing_ti = missing_ti->get_next()) {
bool all_owners_fixed = true;
for (TreeItem *owner_ti = missing_ti->get_first_child(); owner_ti; owner_ti = owner_ti->get_next()) {
const String &owner_path = owner_ti->get_metadata(0);
if (!owner_deps.has(owner_path)) {
List<String> deps;
ResourceLoader::get_dependencies(owner_path, &deps);
LocalVector<String> &stored_paths = owner_deps[owner_path];
for (const String &dep : deps) {
if (!errors_fixed && !FileAccess::exists(_get_resolved_dep_path(dep))) {
errors_fixed = false;
}
stored_paths.push_back(_get_stored_dep_path(dep));
}
}
const LocalVector<String> &stored_paths = owner_deps[owner_path];
const String &missing_path = String(missing_ti->get_metadata(0)).get_slice("::", 0);
if (stored_paths.has(missing_path)) {
all_owners_fixed = false;
break;
}
}
missing_ti->set_icon(1, get_editor_theme_icon(all_owners_fixed ? icon_name_check : icon_name_fail));
}
set_ok_button_text(errors_fixed ? TTRC("Open") : TTRC("Open Anyway"));
}
DependencyErrorDialog::DependencyErrorDialog() {
icon_name_fail = StringName("ImportFail");
icon_name_check = StringName("ImportCheck");
VBoxContainer *vb = memnew(VBoxContainer);
add_child(vb);
files = memnew(Tree);
files->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
files->set_hide_root(true);
vb->add_margin_child(TTR("Load failed due to missing dependencies:"), files, true);
files->set_select_mode(Tree::SELECT_ROW);
files->set_columns(2);
files->set_column_expand(1, false);
files->set_v_size_flags(Control::SIZE_EXPAND_FILL);
files->connect("button_clicked", callable_mp(this, &DependencyErrorDialog::_on_files_button_clicked));
vb->add_margin_child(TTRC("Load failed due to missing dependencies:"), files, true);
set_min_size(Size2(500, 220) * EDSCALE);
set_ok_button_text(TTR("Open Anyway"));
set_cancel_button_text(TTR("Close"));
set_min_size(Size2(500, 320) * EDSCALE);
set_cancel_button_text(TTRC("Close"));
text = memnew(Label);
Label *text = memnew(Label(TTRC("Which action should be taken?")));
text->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
vb->add_child(text);
text->set_text(TTR("Which action should be taken?"));
fdep = add_button(TTR("Fix Dependencies"), true, "fixdeps");
set_title(TTR("Errors loading!"));
}
//////////////////////////////////////////////////////////////////////