diff --git a/editor/docks/filesystem_dock.cpp b/editor/docks/filesystem_dock.cpp index ccb9af4b525..22838c4dd8f 100644 --- a/editor/docks/filesystem_dock.cpp +++ b/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" diff --git a/editor/docks/filesystem_dock.h b/editor/docks/filesystem_dock.h index 6f8994359ed..83cffa241fd 100644 --- a/editor/docks/filesystem_dock.h +++ b/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; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 5be7e8c3200..f9f57f28a91 100644 --- a/editor/editor_node.cpp +++ b/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 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 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 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); diff --git a/editor/editor_node.h b/editor/editor_node.h index 3c811cb9e7f..a5b011293b7 100644 --- a/editor/editor_node.h +++ b/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> 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); diff --git a/editor/file_system/dependency_editor.cpp b/editor/file_system/dependency_editor.cpp index 5c92c739fbf..5661eccb523 100644 --- a/editor/file_system/dependency_editor.cpp +++ b/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 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 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(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 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 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 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 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 &report) { +void DependencyErrorDialog::show(const String &p_for_file, const HashMap> &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> missing_to_owners; + for (const KeyValue> &E : p_report) { + for (const String &missing : E.value) { + missing_to_owners[missing].insert(E.key); } - - Ref 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 folder_icon = get_theme_icon(SNAME("folder"), SNAME("FileDialog")); + + for (const KeyValue> &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> 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 deps; + ResourceLoader::get_dependencies(owner_path, &deps); + + LocalVector &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 &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!")); } ////////////////////////////////////////////////////////////////////// diff --git a/editor/file_system/dependency_editor.h b/editor/file_system/dependency_editor.h index 4f9b30bbb9f..0ac5b3e9cdd 100644 --- a/editor/file_system/dependency_editor.h +++ b/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 &report); + void show(const String &p_for_file, const HashMap> &p_report); + DependencyErrorDialog(); };