Browse Source

Merge pull request #112187 from timothyqiu/deps-manual-ii

Allow fixing indirect missing dependencies manually
Thaddeus Crews 1 month ago
parent
commit
019889d1da

+ 1 - 0
editor/docks/filesystem_dock.cpp

@@ -43,6 +43,7 @@
 #include "editor/editor_node.h"
 #include "editor/editor_string_names.h"
 #include "editor/editor_undo_redo_manager.h"
+#include "editor/file_system/dependency_editor.h"
 #include "editor/gui/create_dialog.h"
 #include "editor/gui/directory_create_dialog.h"
 #include "editor/gui/editor_dir_dialog.h"

+ 4 - 1
editor/docks/filesystem_dock.h

@@ -39,14 +39,17 @@
 #include "scene/gui/box_container.h"
 #include "scene/gui/control.h"
 #include "scene/gui/dialogs.h"
+#include "scene/gui/item_list.h"
 #include "scene/gui/menu_button.h"
 #include "scene/gui/split_container.h"
 #include "scene/gui/tree.h"
 
 class CreateDialog;
+class DependencyEditor;
+class DependencyEditorOwners;
+class DependencyRemoveDialog;
 class EditorDirDialog;
 class HBoxContainer;
-class ItemList;
 class LineEdit;
 class ProgressBar;
 class SceneCreateDialog;

+ 6 - 20
editor/editor_node.cpp

@@ -1576,13 +1576,9 @@ Error EditorNode::load_resource(const String &p_resource, bool p_ignore_broken_d
 	}
 	ERR_FAIL_COND_V(res.is_null(), ERR_CANT_OPEN);
 
-	if (!p_ignore_broken_deps && dependency_errors.has(p_resource)) {
-		Vector<String> errors;
-		for (const String &E : dependency_errors[p_resource]) {
-			errors.push_back(E);
-		}
-		dependency_error->show(p_resource, errors);
-		dependency_errors.erase(p_resource);
+	if (!p_ignore_broken_deps && !dependency_errors.is_empty()) {
+		dependency_error->show(p_resource, dependency_errors);
+		dependency_errors.clear();
 
 		return ERR_FILE_MISSING_DEPENDENCIES;
 	}
@@ -4558,10 +4554,6 @@ bool EditorNode::is_multi_window_enabled() const {
 	return !SceneTree::get_singleton()->get_root()->is_embedding_subwindows() && !EDITOR_GET("interface/editor/single_window_mode") && EDITOR_GET("interface/multi_window/enable");
 }
 
-void EditorNode::fix_dependencies(const String &p_for_file) {
-	dependency_fixer->edit(p_for_file);
-}
-
 int EditorNode::new_scene() {
 	int idx = editor_data.add_edited_scene(-1);
 	_set_current_scene(idx); // Before trying to remove an empty scene, set the current tab index to the newly added tab index.
@@ -4636,13 +4628,10 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b
 	Error err;
 	Ref<PackedScene> sdata = ResourceLoader::load(lpath, "", ResourceFormatLoader::CACHE_MODE_REPLACE, &err);
 
-	if (!p_ignore_broken_deps && dependency_errors.has(lpath)) {
+	if (!p_ignore_broken_deps && !dependency_errors.is_empty()) {
 		current_menu_option = -1;
-		Vector<String> errors;
-		for (const String &E : dependency_errors[lpath]) {
-			errors.push_back(E);
-		}
-		dependency_error->show(lpath, errors);
+		dependency_error->show(lpath, dependency_errors);
+		dependency_errors.clear();
 
 		if (prev != -1 && prev != idx) {
 			_set_current_scene(prev);
@@ -8318,9 +8307,6 @@ EditorNode::EditorNode() {
 	dependency_error = memnew(DependencyErrorDialog);
 	gui_base->add_child(dependency_error);
 
-	dependency_fixer = memnew(DependencyEditor);
-	gui_base->add_child(dependency_fixer);
-
 	editor_settings_dialog = memnew(EditorSettingsDialog);
 	gui_base->add_child(editor_settings_dialog);
 

+ 0 - 3
editor/editor_node.h

@@ -65,7 +65,6 @@ class Window;
 class AudioStreamImportSettingsDialog;
 class AudioStreamPreviewGenerator;
 class BackgroundProgress;
-class DependencyEditor;
 class DependencyErrorDialog;
 class DockSplitContainer;
 class DynamicFontImportSettingsDialog;
@@ -421,7 +420,6 @@ private:
 
 	DependencyErrorDialog *dependency_error = nullptr;
 	HashMap<String, HashSet<String>> dependency_errors;
-	DependencyEditor *dependency_fixer = nullptr;
 	OrphanResourcesDialog *orphan_resources = nullptr;
 	ConfirmationDialog *open_imported = nullptr;
 	Button *new_inherited_button = nullptr;
@@ -851,7 +849,6 @@ public:
 	String get_preview_locale() const;
 	void set_preview_locale(const String &p_locale);
 
-	void fix_dependencies(const String &p_for_file);
 	int new_scene();
 	Error load_scene(const String &p_scene, bool p_ignore_broken_deps = false, bool p_set_inherited = false, bool p_force_open_imported = false, bool p_silent_change_tab = false);
 	Error load_resource(const String &p_resource, bool p_ignore_broken_deps = false);

+ 186 - 75
editor/file_system/dependency_editor.cpp

@@ -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,46 +216,26 @@ 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;
-			}
+			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(stored_path);
+			broken = true;
 		}
 
-		String name = path.get_file();
-
+		const String type = dep.contains("::") ? dep.get_slice("::", 1) : "Resource";
 		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)) {
-			item->set_custom_color(1, Color(1, 0.4, 0.3));
-			missing.push_back(path);
-			broken = true;
-		}
 
 		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()));
 
-		Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(type);
+	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);
+		}
+	}
 
-		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::_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::custom_action(const String &) {
-	EditorNode::get_singleton()->fix_dependencies(for_file);
+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!"));
 }
 
 //////////////////////////////////////////////////////////////////////

+ 22 - 9
editor/file_system/dependency_editor.h

@@ -30,13 +30,15 @@
 
 #pragma once
 
-#include "scene/gui/box_container.h"
 #include "scene/gui/dialogs.h"
-#include "scene/gui/item_list.h"
-#include "scene/gui/tree.h"
 
 class EditorFileDialog;
 class EditorFileSystemDirectory;
+class ItemList;
+class PopupMenu;
+class Tree;
+class TreeItem;
+class VBoxContainer;
 
 class DependencyEditor : public AcceptDialog {
 	GDCLASS(DependencyEditor, AcceptDialog);
@@ -139,17 +141,28 @@ public:
 class DependencyErrorDialog : public ConfirmationDialog {
 	GDCLASS(DependencyErrorDialog, ConfirmationDialog);
 
-private:
+	StringName icon_name_fail;
+	StringName icon_name_check;
+
 	String for_file;
-	Mode mode;
-	Button *fdep = nullptr;
-	Label *text = nullptr;
+
+	TreeItem *replacing_item = nullptr;
+	bool errors_fixed = false;
+
 	Tree *files = nullptr;
+
+	EditorFileDialog *replacement_file_dialog = nullptr;
+	DependencyEditor *deps_editor = nullptr;
+
 	void ok_pressed() override;
-	void custom_action(const String &) override;
+
+	void _on_files_button_clicked(TreeItem *p_item, int p_column, int p_id, MouseButton p_button);
+	void _on_replacement_file_selected(const String &p_path);
+	void _check_for_resolved();
 
 public:
-	void show(const String &p_for_file, const Vector<String> &report);
+	void show(const String &p_for_file, const HashMap<String, HashSet<String>> &p_report);
+
 	DependencyErrorDialog();
 };