initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
This commit is contained in:
6
editor/file_system/SCsub
Normal file
6
editor/file_system/SCsub
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
|
||||
env.add_source_files(env.editor_sources, "*.cpp")
|
917
editor/file_system/dependency_editor.cpp
Normal file
917
editor/file_system/dependency_editor.cpp
Normal file
@@ -0,0 +1,917 @@
|
||||
/**************************************************************************/
|
||||
/* dependency_editor.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "dependency_editor.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/io/resource_loader.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/file_system/editor_file_system.h"
|
||||
#include "editor/gui/editor_file_dialog.h"
|
||||
#include "editor/settings/editor_settings.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/margin_container.h"
|
||||
#include "scene/gui/popup_menu.h"
|
||||
|
||||
void DependencyEditor::_searched(const String &p_path) {
|
||||
HashMap<String, String> dep_rename;
|
||||
dep_rename[replacing] = p_path;
|
||||
|
||||
ResourceLoader::rename_dependencies(editing, dep_rename);
|
||||
|
||||
_update_list();
|
||||
_update_file();
|
||||
}
|
||||
|
||||
void DependencyEditor::_load_pressed(Object *p_item, int p_cell, int p_button, MouseButton p_mouse_button) {
|
||||
if (p_mouse_button != MouseButton::LEFT) {
|
||||
return;
|
||||
}
|
||||
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);
|
||||
}
|
||||
search->popup_file_dialog();
|
||||
}
|
||||
|
||||
void DependencyEditor::_fix_and_find(EditorFileSystemDirectory *efsd, HashMap<String, HashMap<String, String>> &candidates) {
|
||||
for (int i = 0; i < efsd->get_subdir_count(); i++) {
|
||||
_fix_and_find(efsd->get_subdir(i), candidates);
|
||||
}
|
||||
|
||||
for (int i = 0; i < efsd->get_file_count(); i++) {
|
||||
String file = efsd->get_file(i);
|
||||
if (!candidates.has(file)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String path = efsd->get_file_path(i);
|
||||
|
||||
for (KeyValue<String, String> &E : candidates[file]) {
|
||||
if (E.value.is_empty()) {
|
||||
E.value = path;
|
||||
continue;
|
||||
}
|
||||
|
||||
//must match the best, using subdirs
|
||||
String existing = E.value.replace_first("res://", "");
|
||||
String current = path.replace_first("res://", "");
|
||||
String lost = E.key.replace_first("res://", "");
|
||||
|
||||
Vector<String> existingv = existing.split("/");
|
||||
existingv.reverse();
|
||||
Vector<String> currentv = current.split("/");
|
||||
currentv.reverse();
|
||||
Vector<String> lostv = lost.split("/");
|
||||
lostv.reverse();
|
||||
|
||||
int existing_score = 0;
|
||||
int current_score = 0;
|
||||
|
||||
for (int j = 0; j < lostv.size(); j++) {
|
||||
if (j < existingv.size() && lostv[j] == existingv[j]) {
|
||||
existing_score++;
|
||||
}
|
||||
if (j < currentv.size() && lostv[j] == currentv[j]) {
|
||||
current_score++;
|
||||
}
|
||||
}
|
||||
|
||||
if (current_score > existing_score) {
|
||||
//if it was the same, could track distance to new path but..
|
||||
|
||||
E.value = path; //replace by more accurate
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DependencyEditor::_fix_all() {
|
||||
if (!EditorFileSystem::get_singleton()->get_filesystem()) {
|
||||
return;
|
||||
}
|
||||
|
||||
HashMap<String, HashMap<String, String>> candidates;
|
||||
|
||||
for (const String &E : missing) {
|
||||
String base = E.get_file();
|
||||
if (!candidates.has(base)) {
|
||||
candidates[base] = HashMap<String, String>();
|
||||
}
|
||||
|
||||
candidates[base][E] = "";
|
||||
}
|
||||
|
||||
_fix_and_find(EditorFileSystem::get_singleton()->get_filesystem(), candidates);
|
||||
|
||||
HashMap<String, String> remaps;
|
||||
|
||||
for (KeyValue<String, HashMap<String, String>> &E : candidates) {
|
||||
for (const KeyValue<String, String> &F : E.value) {
|
||||
if (!F.value.is_empty()) {
|
||||
remaps[F.key] = F.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (remaps.size()) {
|
||||
ResourceLoader::rename_dependencies(editing, remaps);
|
||||
|
||||
_update_list();
|
||||
_update_file();
|
||||
}
|
||||
}
|
||||
|
||||
void DependencyEditor::_update_file() {
|
||||
EditorFileSystem::get_singleton()->update_file(editing);
|
||||
}
|
||||
|
||||
void DependencyEditor::_update_list() {
|
||||
List<String> deps;
|
||||
ResourceLoader::get_dependencies(editing, &deps, true);
|
||||
|
||||
tree->clear();
|
||||
missing.clear();
|
||||
|
||||
TreeItem *root = tree->create_item();
|
||||
|
||||
Ref<Texture2D> folder = tree->get_theme_icon(SNAME("folder"), SNAME("FileDialog"));
|
||||
|
||||
bool broken = false;
|
||||
|
||||
for (const String &n : deps) {
|
||||
TreeItem *item = tree->create_item(root);
|
||||
String path;
|
||||
String type;
|
||||
|
||||
if (n.contains("::")) {
|
||||
path = n.get_slice("::", 0);
|
||||
type = n.get_slice("::", 1);
|
||||
} 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)) {
|
||||
item->set_custom_color(1, Color(1, 0.4, 0.3));
|
||||
missing.push_back(path);
|
||||
broken = true;
|
||||
}
|
||||
|
||||
item->add_button(1, folder, 0);
|
||||
}
|
||||
|
||||
fixdeps->set_disabled(!broken);
|
||||
}
|
||||
|
||||
void DependencyEditor::edit(const String &p_path) {
|
||||
editing = p_path;
|
||||
set_title(TTR("Dependencies For:") + " " + p_path.get_file());
|
||||
|
||||
_update_list();
|
||||
popup_centered_ratio(0.4);
|
||||
|
||||
if (EditorNode::get_singleton()->is_scene_open(p_path)) {
|
||||
EditorNode::get_singleton()->show_warning(vformat(TTR("Scene '%s' is currently being edited.\nChanges will only take effect when reloaded."), p_path.get_file()));
|
||||
} else if (ResourceCache::has(p_path)) {
|
||||
EditorNode::get_singleton()->show_warning(vformat(TTR("Resource '%s' is in use.\nChanges will only take effect when reloaded."), p_path.get_file()));
|
||||
}
|
||||
}
|
||||
|
||||
DependencyEditor::DependencyEditor() {
|
||||
VBoxContainer *vb = memnew(VBoxContainer);
|
||||
vb->set_name(TTR("Dependencies"));
|
||||
add_child(vb);
|
||||
|
||||
tree = memnew(Tree);
|
||||
tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
tree->set_columns(2);
|
||||
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));
|
||||
|
||||
HBoxContainer *hbc = memnew(HBoxContainer);
|
||||
Label *label = memnew(Label(TTR("Dependencies:")));
|
||||
label->set_theme_type_variation("HeaderSmall");
|
||||
|
||||
hbc->add_child(label);
|
||||
hbc->add_spacer();
|
||||
fixdeps = memnew(Button(TTR("Fix Broken")));
|
||||
hbc->add_child(fixdeps);
|
||||
fixdeps->connect(SceneStringName(pressed), callable_mp(this, &DependencyEditor::_fix_all));
|
||||
|
||||
vb->add_child(hbc);
|
||||
|
||||
MarginContainer *mc = memnew(MarginContainer);
|
||||
mc->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
|
||||
mc->add_child(tree);
|
||||
vb->add_child(mc);
|
||||
|
||||
set_title(TTR("Dependency Editor"));
|
||||
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);
|
||||
}
|
||||
|
||||
/////////////////////////////////////
|
||||
void DependencyEditorOwners::_list_rmb_clicked(int p_item, const Vector2 &p_pos, MouseButton p_mouse_button_index) {
|
||||
if (p_mouse_button_index != MouseButton::RIGHT) {
|
||||
return;
|
||||
}
|
||||
|
||||
file_options->clear();
|
||||
file_options->reset_size();
|
||||
if (p_item >= 0) {
|
||||
PackedInt32Array selected_items = owners->get_selected_items();
|
||||
bool only_scenes_selected = true;
|
||||
|
||||
for (int i = 0; i < selected_items.size(); i++) {
|
||||
int item_idx = selected_items[i];
|
||||
if (ResourceLoader::get_resource_type(owners->get_item_text(item_idx)) != "PackedScene") {
|
||||
only_scenes_selected = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (only_scenes_selected) {
|
||||
file_options->add_icon_item(get_editor_theme_icon(SNAME("Load")), TTRN("Open Scene", "Open Scenes", selected_items.size()), FILE_MENU_OPEN);
|
||||
} else if (selected_items.size() == 1) {
|
||||
file_options->add_icon_item(get_editor_theme_icon(SNAME("Load")), TTR("Open"), FILE_MENU_OPEN);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
file_options->set_position(owners->get_screen_position() + p_pos);
|
||||
file_options->reset_size();
|
||||
file_options->popup();
|
||||
}
|
||||
|
||||
void DependencyEditorOwners::_select_file(int p_idx) {
|
||||
String fpath = owners->get_item_text(p_idx);
|
||||
EditorNode::get_singleton()->load_scene_or_resource(fpath);
|
||||
|
||||
hide();
|
||||
emit_signal(SceneStringName(confirmed));
|
||||
}
|
||||
|
||||
void DependencyEditorOwners::_empty_clicked(const Vector2 &p_pos, MouseButton p_mouse_button_index) {
|
||||
if (p_mouse_button_index != MouseButton::LEFT) {
|
||||
return;
|
||||
}
|
||||
|
||||
owners->deselect_all();
|
||||
}
|
||||
|
||||
void DependencyEditorOwners::_file_option(int p_option) {
|
||||
switch (p_option) {
|
||||
case FILE_MENU_OPEN: {
|
||||
PackedInt32Array selected_items = owners->get_selected_items();
|
||||
for (int i = 0; i < selected_items.size(); i++) {
|
||||
int item_idx = selected_items[i];
|
||||
if (item_idx < 0 || item_idx >= owners->get_item_count()) {
|
||||
break;
|
||||
}
|
||||
_select_file(item_idx);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void DependencyEditorOwners::_fill_owners(EditorFileSystemDirectory *efsd) {
|
||||
if (!efsd) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < efsd->get_subdir_count(); i++) {
|
||||
_fill_owners(efsd->get_subdir(i));
|
||||
}
|
||||
|
||||
for (int i = 0; i < efsd->get_file_count(); i++) {
|
||||
Vector<String> deps = efsd->get_file_deps(i);
|
||||
bool found = false;
|
||||
for (int j = 0; j < deps.size(); j++) {
|
||||
if (deps[j] == editing) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(efsd->get_file_type(i));
|
||||
|
||||
owners->add_item(efsd->get_file_path(i), icon);
|
||||
}
|
||||
}
|
||||
|
||||
void DependencyEditorOwners::show(const String &p_path) {
|
||||
editing = p_path;
|
||||
owners->clear();
|
||||
_fill_owners(EditorFileSystem::get_singleton()->get_filesystem());
|
||||
popup_centered_ratio(0.3);
|
||||
|
||||
set_title(vformat(TTR("Owners of: %s (Total: %d)"), p_path.get_file(), owners->get_item_count()));
|
||||
}
|
||||
|
||||
DependencyEditorOwners::DependencyEditorOwners() {
|
||||
file_options = memnew(PopupMenu);
|
||||
add_child(file_options);
|
||||
file_options->connect(SceneStringName(id_pressed), callable_mp(this, &DependencyEditorOwners::_file_option));
|
||||
|
||||
owners = memnew(ItemList);
|
||||
owners->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
owners->set_select_mode(ItemList::SELECT_MULTI);
|
||||
owners->connect("item_clicked", callable_mp(this, &DependencyEditorOwners::_list_rmb_clicked));
|
||||
owners->connect("item_activated", callable_mp(this, &DependencyEditorOwners::_select_file));
|
||||
owners->connect("empty_clicked", callable_mp(this, &DependencyEditorOwners::_empty_clicked));
|
||||
owners->set_allow_rmb_select(true);
|
||||
add_child(owners);
|
||||
}
|
||||
|
||||
///////////////////////
|
||||
|
||||
void DependencyRemoveDialog::_find_files_in_removed_folder(EditorFileSystemDirectory *efsd, const String &p_folder) {
|
||||
if (!efsd) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < efsd->get_subdir_count(); ++i) {
|
||||
_find_files_in_removed_folder(efsd->get_subdir(i), p_folder);
|
||||
}
|
||||
for (int i = 0; i < efsd->get_file_count(); i++) {
|
||||
String file = efsd->get_file_path(i);
|
||||
ERR_FAIL_COND(all_remove_files.has(file)); //We are deleting a directory which is contained in a directory we are deleting...
|
||||
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.
|
||||
}
|
||||
}
|
||||
|
||||
void DependencyRemoveDialog::_find_all_removed_dependencies(EditorFileSystemDirectory *efsd, Vector<RemovedDependency> &p_removed) {
|
||||
if (!efsd) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < efsd->get_subdir_count(); i++) {
|
||||
_find_all_removed_dependencies(efsd->get_subdir(i), p_removed);
|
||||
}
|
||||
|
||||
for (int i = 0; i < efsd->get_file_count(); i++) {
|
||||
const String path = efsd->get_file_path(i);
|
||||
|
||||
//It doesn't matter if a file we are about to delete will have some of its dependencies removed too
|
||||
if (all_remove_files.has(path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector<String> all_deps = efsd->get_file_deps(i);
|
||||
for (int j = 0; j < all_deps.size(); ++j) {
|
||||
if (all_remove_files.has(all_deps[j])) {
|
||||
RemovedDependency dep;
|
||||
dep.file = path;
|
||||
dep.file_type = efsd->get_file_type(i);
|
||||
dep.dependency = all_deps[j];
|
||||
dep.dependency_folder = all_remove_files[all_deps[j]];
|
||||
p_removed.push_back(dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DependencyRemoveDialog::_find_localization_remaps_of_removed_files(Vector<RemovedDependency> &p_removed) {
|
||||
for (KeyValue<String, String> &files : all_remove_files) {
|
||||
const String &path = files.key;
|
||||
|
||||
// Look for dependencies in the translation remaps.
|
||||
if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/translation_remaps")) {
|
||||
Dictionary remaps = GLOBAL_GET("internationalization/locale/translation_remaps");
|
||||
|
||||
if (remaps.has(path)) {
|
||||
RemovedDependency dep;
|
||||
dep.file = TTR("Localization remap");
|
||||
dep.file_type = "";
|
||||
dep.dependency = path;
|
||||
dep.dependency_folder = files.value;
|
||||
p_removed.push_back(dep);
|
||||
}
|
||||
|
||||
for (const KeyValue<Variant, Variant> &remap_kv : remaps) {
|
||||
PackedStringArray remapped_files = remap_kv.value;
|
||||
for (const String &remapped_file : remapped_files) {
|
||||
int splitter_pos = remapped_file.rfind_char(':');
|
||||
String res_path = remapped_file.substr(0, splitter_pos);
|
||||
if (res_path == path) {
|
||||
String locale_name = remapped_file.substr(splitter_pos + 1);
|
||||
|
||||
RemovedDependency dep;
|
||||
dep.file = vformat(TTR("Localization remap for path '%s' and locale '%s'."), remap_kv.key, locale_name);
|
||||
dep.file_type = "";
|
||||
dep.dependency = path;
|
||||
dep.dependency_folder = files.value;
|
||||
p_removed.push_back(dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DependencyRemoveDialog::_build_removed_dependency_tree(const Vector<RemovedDependency> &p_removed) {
|
||||
owners->clear();
|
||||
owners->create_item(); // root
|
||||
|
||||
HashMap<String, TreeItem *> tree_items;
|
||||
for (int i = 0; i < p_removed.size(); i++) {
|
||||
RemovedDependency rd = p_removed[i];
|
||||
|
||||
//Ensure that the dependency is already in the tree
|
||||
if (!tree_items.has(rd.dependency)) {
|
||||
if (rd.dependency_folder.length() > 0) {
|
||||
//Ensure the ancestor folder is already in the tree
|
||||
if (!tree_items.has(rd.dependency_folder)) {
|
||||
TreeItem *folder_item = owners->create_item(owners->get_root());
|
||||
folder_item->set_text(0, rd.dependency_folder);
|
||||
folder_item->set_icon(0, owners->get_editor_theme_icon(SNAME("Folder")));
|
||||
tree_items[rd.dependency_folder] = folder_item;
|
||||
}
|
||||
TreeItem *dependency_item = owners->create_item(tree_items[rd.dependency_folder]);
|
||||
dependency_item->set_text(0, rd.dependency);
|
||||
dependency_item->set_icon(0, owners->get_editor_theme_icon(SNAME("Warning")));
|
||||
tree_items[rd.dependency] = dependency_item;
|
||||
} else {
|
||||
TreeItem *dependency_item = owners->create_item(owners->get_root());
|
||||
dependency_item->set_text(0, rd.dependency);
|
||||
dependency_item->set_icon(0, owners->get_editor_theme_icon(SNAME("Warning")));
|
||||
tree_items[rd.dependency] = dependency_item;
|
||||
}
|
||||
}
|
||||
|
||||
//List this file under this dependency
|
||||
Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(rd.file_type);
|
||||
TreeItem *file_item = owners->create_item(tree_items[rd.dependency]);
|
||||
file_item->set_text(0, rd.file);
|
||||
file_item->set_icon(0, icon);
|
||||
}
|
||||
}
|
||||
|
||||
void DependencyRemoveDialog::_show_files_to_delete_list() {
|
||||
files_to_delete_list->clear();
|
||||
|
||||
for (const String &s : dirs_to_delete) {
|
||||
String t = s.trim_prefix("res://");
|
||||
files_to_delete_list->add_item(t, Ref<Texture2D>(), false);
|
||||
}
|
||||
|
||||
for (const String &s : files_to_delete) {
|
||||
String t = s.trim_prefix("res://");
|
||||
files_to_delete_list->add_item(t, Ref<Texture2D>(), false);
|
||||
}
|
||||
}
|
||||
|
||||
void DependencyRemoveDialog::show(const Vector<String> &p_folders, const Vector<String> &p_files) {
|
||||
all_remove_files.clear();
|
||||
dirs_to_delete.clear();
|
||||
files_to_delete.clear();
|
||||
owners->clear();
|
||||
|
||||
for (int i = 0; i < p_folders.size(); ++i) {
|
||||
String folder = p_folders[i].ends_with("/") ? p_folders[i] : (p_folders[i] + "/");
|
||||
_find_files_in_removed_folder(EditorFileSystem::get_singleton()->get_filesystem_path(folder), folder);
|
||||
dirs_to_delete.push_back(folder);
|
||||
}
|
||||
for (int i = 0; i < p_files.size(); ++i) {
|
||||
all_remove_files[p_files[i]] = String();
|
||||
files_to_delete.push_back(p_files[i]);
|
||||
}
|
||||
|
||||
_show_files_to_delete_list();
|
||||
|
||||
Vector<RemovedDependency> removed_deps;
|
||||
_find_all_removed_dependencies(EditorFileSystem::get_singleton()->get_filesystem(), removed_deps);
|
||||
_find_localization_remaps_of_removed_files(removed_deps);
|
||||
removed_deps.sort();
|
||||
if (removed_deps.is_empty()) {
|
||||
vb_owners->hide();
|
||||
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."));
|
||||
reset_size();
|
||||
popup_centered();
|
||||
} else {
|
||||
_build_removed_dependency_tree(removed_deps);
|
||||
vb_owners->show();
|
||||
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."));
|
||||
popup_centered(Size2(500, 350));
|
||||
}
|
||||
|
||||
EditorFileSystem::get_singleton()->scan_changes();
|
||||
}
|
||||
|
||||
void DependencyRemoveDialog::ok_pressed() {
|
||||
for (const KeyValue<String, String> &E : all_remove_files) {
|
||||
String file = E.key;
|
||||
|
||||
if (ResourceCache::has(file)) {
|
||||
Ref<Resource> res = ResourceCache::get_ref(file);
|
||||
emit_signal(SNAME("resource_removed"), res);
|
||||
res->set_path("");
|
||||
}
|
||||
}
|
||||
|
||||
HashMap<String, StringName> setting_path_map;
|
||||
for (const StringName &setting : path_project_settings) {
|
||||
const String path = ResourceUID::ensure_path(GLOBAL_GET(setting));
|
||||
setting_path_map[path] = setting;
|
||||
}
|
||||
|
||||
bool project_settings_modified = false;
|
||||
for (const String &file : files_to_delete) {
|
||||
// If the file we are deleting for e.g. the main scene, default environment,
|
||||
// or audio bus layout, we must clear its definition in Project Settings.
|
||||
const StringName *setting_name = setting_path_map.getptr(file);
|
||||
if (setting_name) {
|
||||
ProjectSettings::get_singleton()->set(*setting_name, "");
|
||||
}
|
||||
|
||||
const String path = OS::get_singleton()->get_resource_dir() + file.replace_first("res://", "/");
|
||||
print_verbose("Moving to trash: " + path);
|
||||
Error err = OS::get_singleton()->move_to_trash(path);
|
||||
if (err != OK) {
|
||||
EditorNode::get_singleton()->add_io_error(TTR("Cannot remove:") + "\n" + file + "\n");
|
||||
} else {
|
||||
emit_signal(SNAME("file_removed"), file);
|
||||
}
|
||||
}
|
||||
if (project_settings_modified) {
|
||||
ProjectSettings::get_singleton()->save();
|
||||
}
|
||||
|
||||
if (dirs_to_delete.is_empty()) {
|
||||
// If we only deleted files we should only need to tell the file system about the files we touched.
|
||||
for (int i = 0; i < files_to_delete.size(); ++i) {
|
||||
EditorFileSystem::get_singleton()->update_file(files_to_delete[i]);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < dirs_to_delete.size(); ++i) {
|
||||
String path = OS::get_singleton()->get_resource_dir() + dirs_to_delete[i].replace_first("res://", "/");
|
||||
print_verbose("Moving to trash: " + path);
|
||||
Error err = OS::get_singleton()->move_to_trash(path);
|
||||
if (err != OK) {
|
||||
EditorNode::get_singleton()->add_io_error(TTR("Cannot remove:") + "\n" + dirs_to_delete[i] + "\n");
|
||||
} else {
|
||||
emit_signal(SNAME("folder_removed"), dirs_to_delete[i]);
|
||||
}
|
||||
}
|
||||
|
||||
EditorFileSystem::get_singleton()->scan_changes();
|
||||
}
|
||||
|
||||
// If some files/dirs would be deleted, favorite dirs need to be updated
|
||||
Vector<String> previous_favorites = EditorSettings::get_singleton()->get_favorites();
|
||||
Vector<String> new_favorites;
|
||||
|
||||
for (int i = 0; i < previous_favorites.size(); ++i) {
|
||||
if (previous_favorites[i].ends_with("/")) {
|
||||
if (!dirs_to_delete.has(previous_favorites[i])) {
|
||||
new_favorites.push_back(previous_favorites[i]);
|
||||
}
|
||||
} else {
|
||||
if (!files_to_delete.has(previous_favorites[i])) {
|
||||
new_favorites.push_back(previous_favorites[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (new_favorites.size() < previous_favorites.size()) {
|
||||
EditorSettings::get_singleton()->set_favorites(new_favorites);
|
||||
}
|
||||
}
|
||||
|
||||
void DependencyRemoveDialog::_bind_methods() {
|
||||
ADD_SIGNAL(MethodInfo("resource_removed", PropertyInfo(Variant::OBJECT, "obj")));
|
||||
ADD_SIGNAL(MethodInfo("file_removed", PropertyInfo(Variant::STRING, "file")));
|
||||
ADD_SIGNAL(MethodInfo("folder_removed", PropertyInfo(Variant::STRING, "folder")));
|
||||
}
|
||||
|
||||
DependencyRemoveDialog::DependencyRemoveDialog() {
|
||||
set_ok_button_text(TTR("Remove"));
|
||||
|
||||
VBoxContainer *vb = memnew(VBoxContainer);
|
||||
vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
add_child(vb);
|
||||
|
||||
text = memnew(Label);
|
||||
text->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
|
||||
vb->add_child(text);
|
||||
|
||||
Label *files_to_delete_label = memnew(Label);
|
||||
files_to_delete_label->set_theme_type_variation("HeaderSmall");
|
||||
files_to_delete_label->set_text(TTR("Files to be deleted:"));
|
||||
vb->add_child(files_to_delete_label);
|
||||
|
||||
files_to_delete_list = memnew(ItemList);
|
||||
files_to_delete_list->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
files_to_delete_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
files_to_delete_list->set_custom_minimum_size(Size2(0, 94) * EDSCALE);
|
||||
files_to_delete_list->set_accessibility_name(TTRC("Files to be deleted:"));
|
||||
vb->add_child(files_to_delete_list);
|
||||
|
||||
vb_owners = memnew(VBoxContainer);
|
||||
vb_owners->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
vb_owners->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
vb->add_child(vb_owners);
|
||||
|
||||
Label *owners_label = memnew(Label);
|
||||
owners_label->set_theme_type_variation("HeaderSmall");
|
||||
owners_label->set_text(TTR("Dependencies of files to be deleted:"));
|
||||
vb_owners->add_child(owners_label);
|
||||
|
||||
owners = memnew(Tree);
|
||||
owners->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
owners->set_hide_root(true);
|
||||
owners->set_custom_minimum_size(Size2(0, 94) * EDSCALE);
|
||||
owners->set_accessibility_name(TTRC("Dependencies"));
|
||||
vb_owners->add_child(owners);
|
||||
owners->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
|
||||
List<PropertyInfo> property_list;
|
||||
ProjectSettings::get_singleton()->get_property_list(&property_list);
|
||||
for (const PropertyInfo &pi : property_list) {
|
||||
if (pi.type == Variant::STRING && pi.hint == PROPERTY_HINT_FILE) {
|
||||
path_project_settings.push_back(pi.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////
|
||||
|
||||
void DependencyErrorDialog::show(const String &p_for_file, const Vector<String> &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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
popup_centered();
|
||||
}
|
||||
|
||||
void DependencyErrorDialog::ok_pressed() {
|
||||
EditorNode::get_singleton()->load_scene_or_resource(for_file, true);
|
||||
}
|
||||
|
||||
void DependencyErrorDialog::custom_action(const String &) {
|
||||
EditorNode::get_singleton()->fix_dependencies(for_file);
|
||||
}
|
||||
|
||||
DependencyErrorDialog::DependencyErrorDialog() {
|
||||
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_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
|
||||
set_min_size(Size2(500, 220) * EDSCALE);
|
||||
set_ok_button_text(TTR("Open Anyway"));
|
||||
set_cancel_button_text(TTR("Close"));
|
||||
|
||||
text = memnew(Label);
|
||||
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!"));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
void OrphanResourcesDialog::ok_pressed() {
|
||||
paths.clear();
|
||||
|
||||
_find_to_delete(files->get_root(), paths);
|
||||
if (paths.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete_confirm->set_text(vformat(TTR("Permanently delete %d item(s)? (No undo!)"), paths.size()));
|
||||
delete_confirm->popup_centered();
|
||||
}
|
||||
|
||||
bool OrphanResourcesDialog::_fill_owners(EditorFileSystemDirectory *efsd, HashMap<String, int> &refs, TreeItem *p_parent) {
|
||||
if (!efsd) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool has_children = false;
|
||||
|
||||
for (int i = 0; i < efsd->get_subdir_count(); i++) {
|
||||
TreeItem *dir_item = nullptr;
|
||||
if (p_parent) {
|
||||
dir_item = files->create_item(p_parent);
|
||||
dir_item->set_text(0, efsd->get_subdir(i)->get_name());
|
||||
dir_item->set_icon(0, files->get_theme_icon(SNAME("folder"), SNAME("FileDialog")));
|
||||
}
|
||||
bool children = _fill_owners(efsd->get_subdir(i), refs, dir_item);
|
||||
|
||||
if (p_parent) {
|
||||
if (!children) {
|
||||
memdelete(dir_item);
|
||||
} else {
|
||||
has_children = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < efsd->get_file_count(); i++) {
|
||||
if (!p_parent) {
|
||||
Vector<String> deps = efsd->get_file_deps(i);
|
||||
for (int j = 0; j < deps.size(); j++) {
|
||||
if (!refs.has(deps[j])) {
|
||||
refs[deps[j]] = 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String path = efsd->get_file_path(i);
|
||||
if (!refs.has(path)) {
|
||||
TreeItem *ti = files->create_item(p_parent);
|
||||
ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
|
||||
ti->set_text(0, efsd->get_file(i));
|
||||
ti->set_editable(0, true);
|
||||
|
||||
String type = efsd->get_file_type(i);
|
||||
|
||||
Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(type);
|
||||
ti->set_icon(0, icon);
|
||||
int ds = efsd->get_file_deps(i).size();
|
||||
ti->set_text(1, itos(ds));
|
||||
if (ds) {
|
||||
ti->add_button(1, files->get_editor_theme_icon(SNAME("GuiVisibilityVisible")), -1, false, TTR("Show Dependencies"));
|
||||
}
|
||||
ti->set_metadata(0, path);
|
||||
has_children = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return has_children;
|
||||
}
|
||||
|
||||
void OrphanResourcesDialog::refresh() {
|
||||
HashMap<String, int> refs;
|
||||
_fill_owners(EditorFileSystem::get_singleton()->get_filesystem(), refs, nullptr);
|
||||
files->clear();
|
||||
TreeItem *root = files->create_item();
|
||||
_fill_owners(EditorFileSystem::get_singleton()->get_filesystem(), refs, root);
|
||||
}
|
||||
|
||||
void OrphanResourcesDialog::show() {
|
||||
refresh();
|
||||
popup_centered_ratio(0.4);
|
||||
}
|
||||
|
||||
void OrphanResourcesDialog::_find_to_delete(TreeItem *p_item, List<String> &r_paths) {
|
||||
while (p_item) {
|
||||
if (p_item->get_cell_mode(0) == TreeItem::CELL_MODE_CHECK && p_item->is_checked(0)) {
|
||||
r_paths.push_back(p_item->get_metadata(0));
|
||||
}
|
||||
|
||||
if (p_item->get_first_child()) {
|
||||
_find_to_delete(p_item->get_first_child(), r_paths);
|
||||
}
|
||||
|
||||
p_item = p_item->get_next();
|
||||
}
|
||||
}
|
||||
|
||||
void OrphanResourcesDialog::_delete_confirm() {
|
||||
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
|
||||
for (const String &E : paths) {
|
||||
da->remove(E);
|
||||
EditorFileSystem::get_singleton()->update_file(E);
|
||||
}
|
||||
refresh();
|
||||
}
|
||||
|
||||
void OrphanResourcesDialog::_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button) {
|
||||
if (p_button != MouseButton::LEFT) {
|
||||
return;
|
||||
}
|
||||
TreeItem *ti = Object::cast_to<TreeItem>(p_item);
|
||||
|
||||
String path = ti->get_metadata(0);
|
||||
dep_edit->edit(path);
|
||||
}
|
||||
|
||||
OrphanResourcesDialog::OrphanResourcesDialog() {
|
||||
set_title(TTR("Orphan Resource Explorer"));
|
||||
delete_confirm = memnew(ConfirmationDialog);
|
||||
set_ok_button_text(TTR("Delete"));
|
||||
add_child(delete_confirm);
|
||||
dep_edit = memnew(DependencyEditor);
|
||||
add_child(dep_edit);
|
||||
delete_confirm->connect(SceneStringName(confirmed), callable_mp(this, &OrphanResourcesDialog::_delete_confirm));
|
||||
set_hide_on_ok(false);
|
||||
|
||||
VBoxContainer *vbc = memnew(VBoxContainer);
|
||||
add_child(vbc);
|
||||
|
||||
files = memnew(Tree);
|
||||
files->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
files->set_columns(2);
|
||||
files->set_column_titles_visible(true);
|
||||
files->set_column_custom_minimum_width(1, 100 * EDSCALE);
|
||||
files->set_column_expand(0, true);
|
||||
files->set_column_clip_content(0, true);
|
||||
files->set_column_expand(1, false);
|
||||
files->set_column_clip_content(1, true);
|
||||
files->set_column_title(0, TTR("Resource"));
|
||||
files->set_column_title(1, TTR("Owns"));
|
||||
files->set_hide_root(true);
|
||||
vbc->add_margin_child(TTR("Resources Without Explicit Ownership:"), files, true);
|
||||
files->connect("button_clicked", callable_mp(this, &OrphanResourcesDialog::_button_pressed));
|
||||
}
|
172
editor/file_system/dependency_editor.h
Normal file
172
editor/file_system/dependency_editor.h
Normal file
@@ -0,0 +1,172 @@
|
||||
/**************************************************************************/
|
||||
/* dependency_editor.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#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 DependencyEditor : public AcceptDialog {
|
||||
GDCLASS(DependencyEditor, AcceptDialog);
|
||||
|
||||
Tree *tree = nullptr;
|
||||
Button *fixdeps = nullptr;
|
||||
|
||||
EditorFileDialog *search = nullptr;
|
||||
|
||||
String replacing;
|
||||
String editing;
|
||||
List<String> missing;
|
||||
|
||||
void _fix_and_find(EditorFileSystemDirectory *efsd, HashMap<String, HashMap<String, String>> &candidates);
|
||||
|
||||
void _searched(const String &p_path);
|
||||
void _load_pressed(Object *p_item, int p_cell, int p_button, MouseButton p_mouse_button);
|
||||
void _fix_all();
|
||||
void _update_list();
|
||||
|
||||
void _update_file();
|
||||
|
||||
public:
|
||||
void edit(const String &p_path);
|
||||
DependencyEditor();
|
||||
};
|
||||
|
||||
class DependencyEditorOwners : public AcceptDialog {
|
||||
GDCLASS(DependencyEditorOwners, AcceptDialog);
|
||||
|
||||
ItemList *owners = nullptr;
|
||||
PopupMenu *file_options = nullptr;
|
||||
String editing;
|
||||
|
||||
void _fill_owners(EditorFileSystemDirectory *efsd);
|
||||
|
||||
void _list_rmb_clicked(int p_item, const Vector2 &p_pos, MouseButton p_mouse_button_index);
|
||||
void _select_file(int p_idx);
|
||||
void _empty_clicked(const Vector2 &p_pos, MouseButton p_mouse_button_index);
|
||||
void _file_option(int p_option);
|
||||
|
||||
private:
|
||||
enum FileMenu {
|
||||
FILE_MENU_OPEN,
|
||||
};
|
||||
|
||||
public:
|
||||
void show(const String &p_path);
|
||||
DependencyEditorOwners();
|
||||
};
|
||||
|
||||
class DependencyRemoveDialog : public ConfirmationDialog {
|
||||
GDCLASS(DependencyRemoveDialog, ConfirmationDialog);
|
||||
|
||||
Label *text = nullptr;
|
||||
Tree *owners = nullptr;
|
||||
VBoxContainer *vb_owners = nullptr;
|
||||
ItemList *files_to_delete_list = nullptr;
|
||||
|
||||
HashMap<String, String> all_remove_files;
|
||||
Vector<String> dirs_to_delete;
|
||||
Vector<String> files_to_delete;
|
||||
|
||||
struct RemovedDependency {
|
||||
String file;
|
||||
String file_type;
|
||||
String dependency;
|
||||
String dependency_folder;
|
||||
|
||||
bool operator<(const RemovedDependency &p_other) const {
|
||||
if (dependency_folder.is_empty() != p_other.dependency_folder.is_empty()) {
|
||||
return p_other.dependency_folder.is_empty();
|
||||
} else {
|
||||
return dependency < p_other.dependency;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
LocalVector<StringName> path_project_settings;
|
||||
|
||||
void _find_files_in_removed_folder(EditorFileSystemDirectory *efsd, const String &p_folder);
|
||||
void _find_all_removed_dependencies(EditorFileSystemDirectory *efsd, Vector<RemovedDependency> &p_removed);
|
||||
void _find_localization_remaps_of_removed_files(Vector<RemovedDependency> &p_removed);
|
||||
void _build_removed_dependency_tree(const Vector<RemovedDependency> &p_removed);
|
||||
void _show_files_to_delete_list();
|
||||
|
||||
void ok_pressed() override;
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void show(const Vector<String> &p_folders, const Vector<String> &p_files);
|
||||
DependencyRemoveDialog();
|
||||
};
|
||||
|
||||
class DependencyErrorDialog : public ConfirmationDialog {
|
||||
GDCLASS(DependencyErrorDialog, ConfirmationDialog);
|
||||
|
||||
private:
|
||||
String for_file;
|
||||
Mode mode;
|
||||
Button *fdep = nullptr;
|
||||
Label *text = nullptr;
|
||||
Tree *files = nullptr;
|
||||
void ok_pressed() override;
|
||||
void custom_action(const String &) override;
|
||||
|
||||
public:
|
||||
void show(const String &p_for_file, const Vector<String> &report);
|
||||
DependencyErrorDialog();
|
||||
};
|
||||
|
||||
class OrphanResourcesDialog : public ConfirmationDialog {
|
||||
GDCLASS(OrphanResourcesDialog, ConfirmationDialog);
|
||||
|
||||
DependencyEditor *dep_edit = nullptr;
|
||||
Tree *files = nullptr;
|
||||
ConfirmationDialog *delete_confirm = nullptr;
|
||||
void ok_pressed() override;
|
||||
|
||||
bool _fill_owners(EditorFileSystemDirectory *efsd, HashMap<String, int> &refs, TreeItem *p_parent);
|
||||
|
||||
List<String> paths;
|
||||
void _find_to_delete(TreeItem *p_item, List<String> &r_paths);
|
||||
void _delete_confirm();
|
||||
void _button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button);
|
||||
|
||||
void refresh();
|
||||
|
||||
public:
|
||||
void show();
|
||||
OrphanResourcesDialog();
|
||||
};
|
3773
editor/file_system/editor_file_system.cpp
Normal file
3773
editor/file_system/editor_file_system.cpp
Normal file
File diff suppressed because it is too large
Load Diff
421
editor/file_system/editor_file_system.h
Normal file
421
editor/file_system/editor_file_system.h
Normal file
@@ -0,0 +1,421 @@
|
||||
/**************************************************************************/
|
||||
/* editor_file_system.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/dir_access.h"
|
||||
#include "core/io/resource_importer.h"
|
||||
#include "core/io/resource_loader.h"
|
||||
#include "core/os/thread.h"
|
||||
#include "core/os/thread_safe.h"
|
||||
#include "core/templates/hash_set.h"
|
||||
#include "core/templates/safe_refcount.h"
|
||||
#include "scene/main/node.h"
|
||||
|
||||
class FileAccess;
|
||||
|
||||
struct EditorProgressBG;
|
||||
class EditorFileSystemDirectory : public Object {
|
||||
GDCLASS(EditorFileSystemDirectory, Object);
|
||||
|
||||
String name;
|
||||
uint64_t modified_time;
|
||||
bool verified = false; //used for checking changes
|
||||
|
||||
EditorFileSystemDirectory *parent = nullptr;
|
||||
Vector<EditorFileSystemDirectory *> subdirs;
|
||||
|
||||
struct FileInfo {
|
||||
String file;
|
||||
StringName type;
|
||||
StringName resource_script_class; // If any resource has script with a global class name, its found here.
|
||||
ResourceUID::ID uid = ResourceUID::INVALID_ID;
|
||||
uint64_t modified_time = 0;
|
||||
uint64_t import_modified_time = 0;
|
||||
String import_md5;
|
||||
Vector<String> import_dest_paths;
|
||||
bool import_valid = false;
|
||||
String import_group_file;
|
||||
Vector<String> deps;
|
||||
bool verified = false; //used for checking changes
|
||||
// This is for script resources only.
|
||||
struct ScriptClassInfo {
|
||||
String name;
|
||||
String extends;
|
||||
String icon_path;
|
||||
bool is_abstract = false;
|
||||
bool is_tool = false;
|
||||
};
|
||||
ScriptClassInfo class_info;
|
||||
};
|
||||
|
||||
Vector<FileInfo *> files;
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
friend class EditorFileSystem;
|
||||
|
||||
public:
|
||||
String get_name();
|
||||
String get_path() const;
|
||||
|
||||
int get_subdir_count() const;
|
||||
EditorFileSystemDirectory *get_subdir(int p_idx);
|
||||
int get_file_count() const;
|
||||
String get_file(int p_idx) const;
|
||||
String get_file_path(int p_idx) const;
|
||||
StringName get_file_type(int p_idx) const;
|
||||
StringName get_file_resource_script_class(int p_idx) const;
|
||||
Vector<String> get_file_deps(int p_idx) const;
|
||||
bool get_file_import_is_valid(int p_idx) const;
|
||||
uint64_t get_file_modified_time(int p_idx) const;
|
||||
uint64_t get_file_import_modified_time(int p_idx) const;
|
||||
String get_file_script_class_name(int p_idx) const; //used for scripts
|
||||
String get_file_script_class_extends(int p_idx) const; //used for scripts
|
||||
String get_file_script_class_icon_path(int p_idx) const; //used for scripts
|
||||
String get_file_icon_path(int p_idx) const; //used for FileSystemDock
|
||||
|
||||
EditorFileSystemDirectory *get_parent();
|
||||
|
||||
int find_file_index(const String &p_file) const;
|
||||
int find_dir_index(const String &p_dir) const;
|
||||
|
||||
void force_update();
|
||||
|
||||
EditorFileSystemDirectory();
|
||||
~EditorFileSystemDirectory();
|
||||
};
|
||||
|
||||
class EditorFileSystemImportFormatSupportQuery : public RefCounted {
|
||||
GDCLASS(EditorFileSystemImportFormatSupportQuery, RefCounted);
|
||||
|
||||
protected:
|
||||
GDVIRTUAL0RC_REQUIRED(bool, _is_active)
|
||||
GDVIRTUAL0RC_REQUIRED(Vector<String>, _get_file_extensions)
|
||||
GDVIRTUAL0RC_REQUIRED(bool, _query)
|
||||
static void _bind_methods() {
|
||||
GDVIRTUAL_BIND(_is_active);
|
||||
GDVIRTUAL_BIND(_get_file_extensions);
|
||||
GDVIRTUAL_BIND(_query);
|
||||
}
|
||||
|
||||
public:
|
||||
virtual bool is_active() const {
|
||||
bool ret = false;
|
||||
GDVIRTUAL_CALL(_is_active, ret);
|
||||
return ret;
|
||||
}
|
||||
virtual Vector<String> get_file_extensions() const {
|
||||
Vector<String> ret;
|
||||
GDVIRTUAL_CALL(_get_file_extensions, ret);
|
||||
return ret;
|
||||
}
|
||||
virtual bool query() {
|
||||
bool ret = false;
|
||||
GDVIRTUAL_CALL(_query, ret);
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
class EditorFileSystem : public Node {
|
||||
GDCLASS(EditorFileSystem, Node);
|
||||
|
||||
_THREAD_SAFE_CLASS_
|
||||
|
||||
struct ItemAction {
|
||||
enum Action {
|
||||
ACTION_NONE,
|
||||
ACTION_DIR_ADD,
|
||||
ACTION_DIR_REMOVE,
|
||||
ACTION_FILE_ADD,
|
||||
ACTION_FILE_REMOVE,
|
||||
ACTION_FILE_TEST_REIMPORT,
|
||||
ACTION_FILE_RELOAD
|
||||
};
|
||||
|
||||
Action action = ACTION_NONE;
|
||||
EditorFileSystemDirectory *dir = nullptr;
|
||||
String file;
|
||||
EditorFileSystemDirectory *new_dir = nullptr;
|
||||
EditorFileSystemDirectory::FileInfo *new_file = nullptr;
|
||||
};
|
||||
|
||||
struct ScannedDirectory {
|
||||
String name;
|
||||
String full_path;
|
||||
Vector<ScannedDirectory *> subdirs;
|
||||
List<String> files;
|
||||
|
||||
~ScannedDirectory();
|
||||
};
|
||||
|
||||
bool use_threads = false;
|
||||
Thread thread;
|
||||
static void _thread_func(void *_userdata);
|
||||
|
||||
EditorFileSystemDirectory *new_filesystem = nullptr;
|
||||
static ScannedDirectory *first_scan_root_dir;
|
||||
|
||||
bool filesystem_changed_queued = false;
|
||||
bool scanning = false;
|
||||
bool importing = false;
|
||||
bool first_scan = true;
|
||||
bool scan_changes_pending = false;
|
||||
float scan_total;
|
||||
String filesystem_settings_version_for_import;
|
||||
bool revalidate_import_files = false;
|
||||
static int nb_files_total;
|
||||
|
||||
void _notify_filesystem_changed();
|
||||
void _scan_filesystem();
|
||||
void _first_scan_filesystem();
|
||||
void _first_scan_process_scripts(const ScannedDirectory *p_scan_dir, List<String> &p_gdextension_extensions, HashSet<String> &p_existing_class_names, HashSet<String> &p_extensions);
|
||||
|
||||
static void _scan_for_uid_directory(const ScannedDirectory *p_scan_dir, const HashSet<String> &p_import_extensions);
|
||||
|
||||
static void _load_first_scan_root_dir();
|
||||
|
||||
HashSet<String> late_update_files;
|
||||
|
||||
void _save_late_updated_files();
|
||||
|
||||
EditorFileSystemDirectory *filesystem = nullptr;
|
||||
|
||||
static EditorFileSystem *singleton;
|
||||
|
||||
using ScriptClassInfo = EditorFileSystemDirectory::FileInfo::ScriptClassInfo;
|
||||
|
||||
/* Used for reading the filesystem cache file */
|
||||
struct FileCache {
|
||||
StringName type;
|
||||
String resource_script_class;
|
||||
ResourceUID::ID uid = ResourceUID::INVALID_ID;
|
||||
uint64_t modification_time = 0;
|
||||
uint64_t import_modification_time = 0;
|
||||
String import_md5;
|
||||
Vector<String> import_dest_paths;
|
||||
Vector<String> deps;
|
||||
bool import_valid = false;
|
||||
String import_group_file;
|
||||
ScriptClassInfo class_info;
|
||||
};
|
||||
|
||||
HashMap<String, FileCache> file_cache;
|
||||
HashSet<String> dep_update_list;
|
||||
|
||||
struct ScanProgress {
|
||||
float hi = 0;
|
||||
int current = 0;
|
||||
EditorProgressBG *progress = nullptr;
|
||||
void increment();
|
||||
};
|
||||
|
||||
struct DirectoryComparator {
|
||||
bool operator()(const EditorFileSystemDirectory *p_a, const EditorFileSystemDirectory *p_b) const {
|
||||
return p_a->name.filenocasecmp_to(p_b->name) < 0;
|
||||
}
|
||||
};
|
||||
|
||||
void _save_filesystem_cache();
|
||||
void _save_filesystem_cache(EditorFileSystemDirectory *p_dir, Ref<FileAccess> p_file);
|
||||
|
||||
bool _find_file(const String &p_file, EditorFileSystemDirectory **r_d, int &r_file_pos) const;
|
||||
|
||||
void _scan_fs_changes(EditorFileSystemDirectory *p_dir, ScanProgress &p_progress, bool p_recursive = true);
|
||||
|
||||
void _delete_internal_files(const String &p_file);
|
||||
int _insert_actions_delete_files_directory(EditorFileSystemDirectory *p_dir);
|
||||
|
||||
HashSet<String> textfile_extensions;
|
||||
HashSet<String> other_file_extensions;
|
||||
HashSet<String> valid_extensions;
|
||||
HashSet<String> import_extensions;
|
||||
|
||||
static int _scan_new_dir(ScannedDirectory *p_dir, Ref<DirAccess> &da);
|
||||
void _process_file_system(const ScannedDirectory *p_scan_dir, EditorFileSystemDirectory *p_dir, ScanProgress &p_progress, HashSet<String> *p_processed_files);
|
||||
|
||||
Thread thread_sources;
|
||||
bool scanning_changes = false;
|
||||
SafeFlag scanning_changes_done;
|
||||
|
||||
static void _thread_func_sources(void *_userdata);
|
||||
|
||||
List<String> sources_changed;
|
||||
List<ItemAction> scan_actions;
|
||||
|
||||
bool _update_scan_actions();
|
||||
|
||||
void _update_extensions();
|
||||
|
||||
Error _reimport_file(const String &p_file, const HashMap<StringName, Variant> &p_custom_options = HashMap<StringName, Variant>(), const String &p_custom_importer = String(), Variant *generator_parameters = nullptr, bool p_update_file_system = true);
|
||||
Error _reimport_group(const String &p_group_file, const Vector<String> &p_files);
|
||||
|
||||
bool _test_for_reimport(const String &p_path, const String &p_expected_import_md5);
|
||||
bool _is_test_for_reimport_needed(const String &p_path, uint64_t p_last_modification_time, uint64_t p_modification_time, uint64_t p_last_import_modification_time, uint64_t p_import_modification_time, const Vector<String> &p_import_dest_paths);
|
||||
bool _can_import_file(const String &p_path);
|
||||
Vector<String> _get_import_dest_paths(const String &p_path);
|
||||
|
||||
bool reimport_on_missing_imported_files;
|
||||
|
||||
Vector<String> _get_dependencies(const String &p_path);
|
||||
|
||||
struct ImportFile {
|
||||
String path;
|
||||
String importer;
|
||||
bool threaded = false;
|
||||
int order = 0;
|
||||
bool operator<(const ImportFile &p_if) const {
|
||||
return order == p_if.order ? (importer < p_if.importer) : (order < p_if.order);
|
||||
}
|
||||
};
|
||||
|
||||
struct ScriptClassInfoUpdate : public ScriptClassInfo {
|
||||
StringName type;
|
||||
ScriptClassInfoUpdate() = default;
|
||||
explicit ScriptClassInfoUpdate(const ScriptClassInfo &p_info) :
|
||||
ScriptClassInfo(p_info) {}
|
||||
static ScriptClassInfoUpdate from_file_info(const EditorFileSystemDirectory::FileInfo *p_fi) {
|
||||
ScriptClassInfoUpdate update;
|
||||
update.type = p_fi->type;
|
||||
update.name = p_fi->class_info.name;
|
||||
update.extends = p_fi->class_info.extends;
|
||||
update.icon_path = p_fi->class_info.icon_path;
|
||||
update.is_abstract = p_fi->class_info.is_abstract;
|
||||
update.is_tool = p_fi->class_info.is_tool;
|
||||
return update;
|
||||
}
|
||||
};
|
||||
|
||||
Mutex update_script_mutex;
|
||||
HashMap<String, ScriptClassInfoUpdate> update_script_paths;
|
||||
HashSet<String> update_script_paths_documentation;
|
||||
void _queue_update_script_class(const String &p_path, const ScriptClassInfoUpdate &p_script_update);
|
||||
void _update_script_classes();
|
||||
void _update_script_documentation();
|
||||
void _process_update_pending();
|
||||
void _process_removed_files(const HashSet<String> &p_processed_files);
|
||||
bool _should_reload_script(const String &p_path);
|
||||
|
||||
Mutex update_scene_mutex;
|
||||
HashSet<String> update_scene_paths;
|
||||
void _queue_update_scene_groups(const String &p_path);
|
||||
void _update_scene_groups();
|
||||
void _update_pending_scene_groups();
|
||||
void _get_all_scenes(EditorFileSystemDirectory *p_dir, HashSet<String> &r_list);
|
||||
|
||||
ScriptClassInfo _get_global_script_class(const String &p_type, const String &p_path) const;
|
||||
|
||||
static Error _resource_import(const String &p_path);
|
||||
static Ref<Resource> _load_resource_on_startup(ResourceFormatImporter *p_importer, const String &p_path, Error *r_error, bool p_use_sub_threads, float *r_progress, ResourceFormatLoader::CacheMode p_cache_mode);
|
||||
|
||||
bool using_fat32_or_exfat; // Workaround for projects in FAT32 or exFAT filesystem (pendrives, most of the time)
|
||||
|
||||
void _find_group_files(EditorFileSystemDirectory *efd, HashMap<String, Vector<String>> &group_files, HashSet<String> &groups_to_reimport);
|
||||
|
||||
void _move_group_files(EditorFileSystemDirectory *efd, const String &p_group_file, const String &p_new_location);
|
||||
|
||||
HashSet<String> group_file_cache;
|
||||
HashMap<String, String> file_icon_cache;
|
||||
|
||||
bool refresh_queued = false;
|
||||
HashSet<ObjectID> folders_to_sort;
|
||||
|
||||
Error _copy_file(const String &p_from, const String &p_to);
|
||||
bool _copy_directory(const String &p_from, const String &p_to, HashMap<String, String> *p_files);
|
||||
void _queue_refresh_filesystem();
|
||||
void _refresh_filesystem();
|
||||
|
||||
struct ImportThreadData {
|
||||
const ImportFile *reimport_files;
|
||||
int reimport_from;
|
||||
Semaphore *imported_sem = nullptr;
|
||||
};
|
||||
|
||||
void _reimport_thread(uint32_t p_index, ImportThreadData *p_import_data);
|
||||
|
||||
static ResourceUID::ID _resource_saver_get_resource_id_for_path(const String &p_path, bool p_generate);
|
||||
|
||||
bool _scan_extensions();
|
||||
bool _scan_import_support(const Vector<String> &reimports);
|
||||
|
||||
Vector<Ref<EditorFileSystemImportFormatSupportQuery>> import_support_queries;
|
||||
|
||||
void _update_file_icon_path(EditorFileSystemDirectory::FileInfo *file_info);
|
||||
void _update_files_icon_path(EditorFileSystemDirectory *edp = nullptr);
|
||||
bool _remove_invalid_global_class_names(const HashSet<String> &p_existing_class_names);
|
||||
String _get_file_by_class_name(EditorFileSystemDirectory *p_dir, const String &p_class_name, EditorFileSystemDirectory::FileInfo *&r_file_info);
|
||||
|
||||
void _register_global_class_script(const String &p_search_path, const String &p_target_path, const ScriptClassInfoUpdate &p_script_update);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
static EditorFileSystem *get_singleton() { return singleton; }
|
||||
|
||||
EditorFileSystemDirectory *get_filesystem();
|
||||
bool is_scanning() const;
|
||||
bool is_importing() const { return importing; }
|
||||
bool doing_first_scan() const { return first_scan; }
|
||||
float get_scanning_progress() const;
|
||||
void scan();
|
||||
void scan_changes();
|
||||
void update_file(const String &p_file);
|
||||
void update_files(const Vector<String> &p_script_paths);
|
||||
HashSet<String> get_valid_extensions() const;
|
||||
void register_global_class_script(const String &p_search_path, const String &p_target_path);
|
||||
|
||||
EditorFileSystemDirectory *get_filesystem_path(const String &p_path);
|
||||
String get_file_type(const String &p_file) const;
|
||||
EditorFileSystemDirectory *find_file(const String &p_file, int *r_index) const;
|
||||
ResourceUID::ID get_file_uid(const String &p_path) const;
|
||||
|
||||
void reimport_files(const Vector<String> &p_files);
|
||||
Error reimport_append(const String &p_file, const HashMap<StringName, Variant> &p_custom_options, const String &p_custom_importer, Variant p_generator_parameters);
|
||||
|
||||
void reimport_file_with_custom_parameters(const String &p_file, const String &p_importer, const HashMap<StringName, Variant> &p_custom_params);
|
||||
|
||||
bool is_group_file(const String &p_path) const;
|
||||
void move_group_file(const String &p_path, const String &p_new_path);
|
||||
|
||||
Error make_dir_recursive(const String &p_path, const String &p_base_path = String());
|
||||
Error copy_file(const String &p_from, const String &p_to);
|
||||
Error copy_directory(const String &p_from, const String &p_to);
|
||||
|
||||
static bool _should_skip_directory(const String &p_path);
|
||||
|
||||
static void scan_for_uid();
|
||||
|
||||
void add_import_format_support_query(Ref<EditorFileSystemImportFormatSupportQuery> p_query);
|
||||
void remove_import_format_support_query(Ref<EditorFileSystemImportFormatSupportQuery> p_query);
|
||||
EditorFileSystem();
|
||||
~EditorFileSystem();
|
||||
};
|
290
editor/file_system/editor_paths.cpp
Normal file
290
editor/file_system/editor_paths.cpp
Normal file
@@ -0,0 +1,290 @@
|
||||
/**************************************************************************/
|
||||
/* editor_paths.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "editor_paths.h"
|
||||
|
||||
#include "core/config/engine.h"
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/io/dir_access.h"
|
||||
#include "core/os/os.h"
|
||||
#include "main/main.h"
|
||||
|
||||
EditorPaths *EditorPaths::singleton = nullptr;
|
||||
|
||||
bool EditorPaths::are_paths_valid() const {
|
||||
return paths_valid;
|
||||
}
|
||||
|
||||
String EditorPaths::get_data_dir() const {
|
||||
return data_dir;
|
||||
}
|
||||
|
||||
String EditorPaths::get_config_dir() const {
|
||||
return config_dir;
|
||||
}
|
||||
|
||||
String EditorPaths::get_cache_dir() const {
|
||||
return cache_dir;
|
||||
}
|
||||
|
||||
String EditorPaths::get_temp_dir() const {
|
||||
return temp_dir;
|
||||
}
|
||||
|
||||
String EditorPaths::get_project_data_dir() const {
|
||||
return project_data_dir;
|
||||
}
|
||||
|
||||
bool EditorPaths::is_self_contained() const {
|
||||
return self_contained;
|
||||
}
|
||||
|
||||
String EditorPaths::get_self_contained_file() const {
|
||||
return self_contained_file;
|
||||
}
|
||||
|
||||
String EditorPaths::get_export_templates_dir() const {
|
||||
return get_data_dir().path_join(export_templates_folder);
|
||||
}
|
||||
|
||||
String EditorPaths::get_debug_keystore_path() const {
|
||||
#ifdef ANDROID_ENABLED
|
||||
return "assets://keystores/debug.keystore";
|
||||
#else
|
||||
return get_data_dir().path_join("keystores/debug.keystore");
|
||||
#endif
|
||||
}
|
||||
|
||||
String EditorPaths::get_project_settings_dir() const {
|
||||
return get_project_data_dir().path_join("editor");
|
||||
}
|
||||
|
||||
String EditorPaths::get_text_editor_themes_dir() const {
|
||||
return get_config_dir().path_join(text_editor_themes_folder);
|
||||
}
|
||||
|
||||
String EditorPaths::get_script_templates_dir() const {
|
||||
return get_config_dir().path_join(script_templates_folder);
|
||||
}
|
||||
|
||||
String EditorPaths::get_project_script_templates_dir() const {
|
||||
return GLOBAL_GET("editor/script/templates_search_path");
|
||||
}
|
||||
|
||||
String EditorPaths::get_feature_profiles_dir() const {
|
||||
return get_config_dir().path_join(feature_profiles_folder);
|
||||
}
|
||||
|
||||
void EditorPaths::create() {
|
||||
memnew(EditorPaths);
|
||||
}
|
||||
|
||||
void EditorPaths::free() {
|
||||
ERR_FAIL_NULL(singleton);
|
||||
memdelete(singleton);
|
||||
singleton = nullptr;
|
||||
}
|
||||
|
||||
void EditorPaths::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("get_data_dir"), &EditorPaths::get_data_dir);
|
||||
ClassDB::bind_method(D_METHOD("get_config_dir"), &EditorPaths::get_config_dir);
|
||||
ClassDB::bind_method(D_METHOD("get_cache_dir"), &EditorPaths::get_cache_dir);
|
||||
ClassDB::bind_method(D_METHOD("is_self_contained"), &EditorPaths::is_self_contained);
|
||||
ClassDB::bind_method(D_METHOD("get_self_contained_file"), &EditorPaths::get_self_contained_file);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_project_settings_dir"), &EditorPaths::get_project_settings_dir);
|
||||
}
|
||||
|
||||
EditorPaths::EditorPaths() {
|
||||
ERR_FAIL_COND(singleton != nullptr);
|
||||
singleton = this;
|
||||
|
||||
project_data_dir = ProjectSettings::get_singleton()->get_project_data_path();
|
||||
|
||||
// Self-contained mode if a `._sc_` or `_sc_` file is present in executable dir.
|
||||
String exe_path = OS::get_singleton()->get_executable_path().get_base_dir();
|
||||
Ref<DirAccess> d = DirAccess::create_for_path(exe_path);
|
||||
if (d->file_exists(exe_path + "/._sc_")) {
|
||||
self_contained = true;
|
||||
self_contained_file = exe_path + "/._sc_";
|
||||
} else if (d->file_exists(exe_path + "/_sc_")) {
|
||||
self_contained = true;
|
||||
self_contained_file = exe_path + "/_sc_";
|
||||
}
|
||||
|
||||
// On macOS, look outside .app bundle, since .app bundle is read-only.
|
||||
// Note: This will not work if Gatekeeper path randomization is active.
|
||||
if (OS::get_singleton()->has_feature("macos") && exe_path.ends_with("MacOS") && exe_path.path_join("..").simplify_path().ends_with("Contents")) {
|
||||
exe_path = exe_path.path_join("../../..").simplify_path();
|
||||
d = DirAccess::create_for_path(exe_path);
|
||||
if (d->file_exists(exe_path + "/._sc_")) {
|
||||
self_contained = true;
|
||||
self_contained_file = exe_path + "/._sc_";
|
||||
} else if (d->file_exists(exe_path + "/_sc_")) {
|
||||
self_contained = true;
|
||||
self_contained_file = exe_path + "/_sc_";
|
||||
}
|
||||
}
|
||||
|
||||
String data_path;
|
||||
String config_path;
|
||||
String cache_path;
|
||||
|
||||
if (self_contained) {
|
||||
// editor is self contained, all in same folder
|
||||
data_path = exe_path;
|
||||
data_dir = data_path.path_join("editor_data");
|
||||
config_path = exe_path;
|
||||
config_dir = data_dir;
|
||||
cache_path = exe_path;
|
||||
cache_dir = data_dir.path_join("cache");
|
||||
temp_dir = data_dir.path_join("temp");
|
||||
} else {
|
||||
// Typically XDG_DATA_HOME or %APPDATA%.
|
||||
data_path = OS::get_singleton()->get_data_path();
|
||||
data_dir = data_path.path_join(OS::get_singleton()->get_godot_dir_name());
|
||||
// Can be different from data_path e.g. on Linux or macOS.
|
||||
config_path = OS::get_singleton()->get_config_path();
|
||||
config_dir = config_path.path_join(OS::get_singleton()->get_godot_dir_name());
|
||||
// Can be different from above paths, otherwise a subfolder of data_dir.
|
||||
cache_path = OS::get_singleton()->get_cache_path();
|
||||
if (cache_path == data_path) {
|
||||
cache_dir = data_dir.path_join("cache");
|
||||
} else {
|
||||
cache_dir = cache_path.path_join(OS::get_singleton()->get_godot_dir_name());
|
||||
}
|
||||
temp_dir = OS::get_singleton()->get_temp_path();
|
||||
}
|
||||
|
||||
paths_valid = (!data_path.is_empty() && !config_path.is_empty() && !cache_path.is_empty());
|
||||
ERR_FAIL_COND_MSG(!paths_valid, "Editor data, config, or cache paths are invalid.");
|
||||
|
||||
// Validate or create each dir and its relevant subdirectories.
|
||||
|
||||
Ref<DirAccess> dir = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
|
||||
|
||||
// Data dir.
|
||||
{
|
||||
if (dir->change_dir(data_dir) != OK) {
|
||||
dir->make_dir_recursive(data_dir);
|
||||
if (dir->change_dir(data_dir) != OK) {
|
||||
ERR_PRINT("Could not create editor data directory: " + data_dir);
|
||||
paths_valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dir->dir_exists(export_templates_folder)) {
|
||||
dir->make_dir(export_templates_folder);
|
||||
}
|
||||
}
|
||||
|
||||
// Config dir.
|
||||
{
|
||||
if (dir->change_dir(config_dir) != OK) {
|
||||
dir->make_dir_recursive(config_dir);
|
||||
if (dir->change_dir(config_dir) != OK) {
|
||||
ERR_PRINT("Could not create editor config directory: " + config_dir);
|
||||
paths_valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dir->dir_exists(text_editor_themes_folder)) {
|
||||
dir->make_dir(text_editor_themes_folder);
|
||||
}
|
||||
if (!dir->dir_exists(script_templates_folder)) {
|
||||
dir->make_dir(script_templates_folder);
|
||||
}
|
||||
if (!dir->dir_exists(feature_profiles_folder)) {
|
||||
dir->make_dir(feature_profiles_folder);
|
||||
}
|
||||
}
|
||||
|
||||
// Cache dir.
|
||||
{
|
||||
if (dir->change_dir(cache_dir) != OK) {
|
||||
dir->make_dir_recursive(cache_dir);
|
||||
if (dir->change_dir(cache_dir) != OK) {
|
||||
ERR_PRINT("Could not create editor cache directory: " + cache_dir);
|
||||
paths_valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Temporary dir.
|
||||
{
|
||||
if (dir->change_dir(temp_dir) != OK) {
|
||||
dir->make_dir_recursive(temp_dir);
|
||||
if (dir->change_dir(temp_dir) != OK) {
|
||||
ERR_PRINT("Could not create editor temporary directory: " + temp_dir);
|
||||
paths_valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate or create project-specific editor data dir,
|
||||
// including shader cache subdir.
|
||||
if (Engine::get_singleton()->is_project_manager_hint() || (Main::is_cmdline_tool() && !ProjectSettings::get_singleton()->is_project_loaded())) {
|
||||
// Nothing to create, use shared editor data dir for shader cache.
|
||||
Engine::get_singleton()->set_shader_cache_path(data_dir);
|
||||
} else {
|
||||
Ref<DirAccess> dir_res = DirAccess::create(DirAccess::ACCESS_RESOURCES);
|
||||
if (dir_res->change_dir(project_data_dir) != OK) {
|
||||
dir_res->make_dir_recursive(project_data_dir);
|
||||
if (dir_res->change_dir(project_data_dir) != OK) {
|
||||
ERR_PRINT("Could not create project data directory (" + project_data_dir + ") in: " + dir_res->get_current_dir());
|
||||
paths_valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the project data directory `.gdignore` file exists.
|
||||
String project_data_gdignore_file_path = project_data_dir.path_join(".gdignore");
|
||||
if (!FileAccess::exists(project_data_gdignore_file_path)) {
|
||||
// Add an empty .gdignore file to avoid scan.
|
||||
Ref<FileAccess> f = FileAccess::open(project_data_gdignore_file_path, FileAccess::WRITE);
|
||||
if (f.is_valid()) {
|
||||
f->store_line("");
|
||||
} else {
|
||||
ERR_PRINT("Failed to create file " + project_data_gdignore_file_path.quote() + ".");
|
||||
}
|
||||
}
|
||||
|
||||
Engine::get_singleton()->set_shader_cache_path(project_data_dir);
|
||||
|
||||
// Editor metadata dir.
|
||||
if (!dir_res->dir_exists("editor")) {
|
||||
dir_res->make_dir("editor");
|
||||
}
|
||||
// Imported assets dir.
|
||||
String imported_files_path = ProjectSettings::get_singleton()->get_imported_files_path();
|
||||
if (!dir_res->dir_exists(imported_files_path)) {
|
||||
dir_res->make_dir(imported_files_path);
|
||||
}
|
||||
}
|
||||
}
|
85
editor/file_system/editor_paths.h
Normal file
85
editor/file_system/editor_paths.h
Normal file
@@ -0,0 +1,85 @@
|
||||
/**************************************************************************/
|
||||
/* editor_paths.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/object/class_db.h"
|
||||
#include "core/object/object.h"
|
||||
#include "core/string/ustring.h"
|
||||
|
||||
class EditorPaths : public Object {
|
||||
GDCLASS(EditorPaths, Object)
|
||||
|
||||
bool paths_valid = false; // If any of the paths can't be created, this is false.
|
||||
String data_dir; // Editor data (templates, shader cache, etc.).
|
||||
String config_dir; // Editor config (settings, profiles, themes, etc.).
|
||||
String cache_dir; // Editor cache (thumbnails, tmp generated files).
|
||||
String temp_dir; // Editor temporary directory.
|
||||
String project_data_dir; // Project-specific data (metadata, shader cache, etc.).
|
||||
bool self_contained = false; // Self-contained means everything goes to `editor_data` dir.
|
||||
String self_contained_file; // Self-contained file with configuration.
|
||||
String export_templates_folder = "export_templates";
|
||||
String text_editor_themes_folder = "text_editor_themes";
|
||||
String script_templates_folder = "script_templates";
|
||||
String feature_profiles_folder = "feature_profiles";
|
||||
|
||||
static EditorPaths *singleton;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
bool are_paths_valid() const;
|
||||
|
||||
String get_data_dir() const;
|
||||
String get_config_dir() const;
|
||||
String get_cache_dir() const;
|
||||
String get_temp_dir() const;
|
||||
String get_project_data_dir() const;
|
||||
String get_export_templates_dir() const;
|
||||
String get_debug_keystore_path() const;
|
||||
String get_project_settings_dir() const;
|
||||
String get_text_editor_themes_dir() const;
|
||||
String get_script_templates_dir() const;
|
||||
String get_project_script_templates_dir() const;
|
||||
String get_feature_profiles_dir() const;
|
||||
|
||||
bool is_self_contained() const;
|
||||
String get_self_contained_file() const;
|
||||
|
||||
static EditorPaths *get_singleton() {
|
||||
return singleton;
|
||||
}
|
||||
|
||||
static void create();
|
||||
static void free();
|
||||
|
||||
EditorPaths();
|
||||
};
|
61
editor/file_system/file_info.cpp
Normal file
61
editor/file_system/file_info.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
/**************************************************************************/
|
||||
/* file_info.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "file_info.h"
|
||||
|
||||
void sort_file_info_list(List<FileInfo> &r_file_list, FileSortOption p_file_sort_option) {
|
||||
// Sort the file list if needed.
|
||||
switch (p_file_sort_option) {
|
||||
case FileSortOption::FILE_SORT_TYPE:
|
||||
r_file_list.sort_custom<FileInfoTypeComparator>();
|
||||
break;
|
||||
case FileSortOption::FILE_SORT_TYPE_REVERSE:
|
||||
r_file_list.sort_custom<FileInfoTypeComparator>();
|
||||
r_file_list.reverse();
|
||||
break;
|
||||
case FileSortOption::FILE_SORT_MODIFIED_TIME:
|
||||
r_file_list.sort_custom<FileInfoModifiedTimeComparator>();
|
||||
break;
|
||||
case FileSortOption::FILE_SORT_MODIFIED_TIME_REVERSE:
|
||||
r_file_list.sort_custom<FileInfoModifiedTimeComparator>();
|
||||
r_file_list.reverse();
|
||||
break;
|
||||
case FileSortOption::FILE_SORT_NAME_REVERSE:
|
||||
r_file_list.sort();
|
||||
r_file_list.reverse();
|
||||
break;
|
||||
case FileSortOption::FILE_SORT_NAME:
|
||||
r_file_list.sort();
|
||||
break;
|
||||
default:
|
||||
ERR_FAIL_MSG("Invalid file sort option.");
|
||||
break;
|
||||
}
|
||||
}
|
72
editor/file_system/file_info.h
Normal file
72
editor/file_system/file_info.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/**************************************************************************/
|
||||
/* file_info.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/string/string_name.h"
|
||||
#include "core/templates/list.h"
|
||||
|
||||
enum class FileSortOption {
|
||||
FILE_SORT_NAME = 0,
|
||||
FILE_SORT_NAME_REVERSE = 1,
|
||||
FILE_SORT_TYPE = 2,
|
||||
FILE_SORT_TYPE_REVERSE = 3,
|
||||
FILE_SORT_MODIFIED_TIME = 4,
|
||||
FILE_SORT_MODIFIED_TIME_REVERSE = 5,
|
||||
FILE_SORT_MAX = 6,
|
||||
};
|
||||
|
||||
struct FileInfo {
|
||||
String name;
|
||||
String path;
|
||||
String icon_path;
|
||||
StringName type;
|
||||
Vector<String> sources;
|
||||
bool import_broken = false;
|
||||
uint64_t modified_time = 0;
|
||||
|
||||
bool operator<(const FileInfo &p_fi) const {
|
||||
return FileNoCaseComparator()(name, p_fi.name);
|
||||
}
|
||||
};
|
||||
|
||||
struct FileInfoTypeComparator {
|
||||
bool operator()(const FileInfo &p_a, const FileInfo &p_b) const {
|
||||
return FileNoCaseComparator()(p_a.name.get_extension() + p_a.type + p_a.name.get_basename(), p_b.name.get_extension() + p_b.type + p_b.name.get_basename());
|
||||
}
|
||||
};
|
||||
|
||||
struct FileInfoModifiedTimeComparator {
|
||||
bool operator()(const FileInfo &p_a, const FileInfo &p_b) const {
|
||||
return p_a.modified_time > p_b.modified_time;
|
||||
}
|
||||
};
|
||||
|
||||
void sort_file_info_list(List<FileInfo> &r_file_list, FileSortOption p_file_sort_option);
|
Reference in New Issue
Block a user