godot/tools/editor/dependency_editor.cpp
Juan Linietsky 118eed485e ObjectTypeDB was renamed to ClassDB. Types are meant to be more generic to Variant.
All usages of "type" to refer to classes were renamed to "class"
ClassDB has been exposed to GDScript.
OBJ_TYPE() macro is now GDCLASS()
2017-01-02 23:03:46 -03:00

725 lines
18 KiB
C++

/*************************************************************************/
/* dependency_editor.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* http://www.godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2017 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 "os/file_access.h"
#include "scene/gui/margin_container.h"
#include "io/resource_loader.h"
#include "editor_node.h"
void DependencyEditor::_notification(int p_what){
}
void DependencyEditor::_searched(const String& p_path) {
Map<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){
TreeItem *ti=p_item->cast_to<TreeItem>();
String fname = ti->get_text(0);
replacing = ti->get_text(1);
search->set_title(TTR("Search Replacement For:")+" "+replacing.get_file());
search->clear_filters();
List<String> ext;
ResourceLoader::get_recognized_extensions_for_type(ti->get_metadata(0),&ext);
for (List<String>::Element *E=ext.front();E;E=E->next()) {
search->add_filter("*"+E->get());
}
search->popup_centered_ratio();
}
void DependencyEditor::_fix_and_find(EditorFileSystemDirectory *efsd, Map<String,Map<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(Map<String,String>::Element *E=candidates[file].front();E;E=E->next()) {
if (E->get()==String()) {
E->get()=path;
continue;
}
//must match the best, using subdirs
String existing=E->get().replace_first("res://","");
String current=path.replace_first("res://","");
String lost=E->key().replace_first("res://","");
Vector<String> existingv=existing.split("/");
existingv.invert();
Vector<String> currentv=current.split("/");
currentv.invert();
Vector<String> lostv=lost.split("/");
lostv.invert();
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->get()=path; //replace by more accurate
}
}
}
}
void DependencyEditor::_fix_all(){
if (!EditorFileSystem::get_singleton()->get_filesystem())
return;
Map<String,Map<String,String> > candidates;
for (List<String>::Element *E=missing.front();E;E=E->next()) {
String base = E->get().get_file();
if (!candidates.has(base)) {
candidates[base]=Map<String,String>();
}
candidates[base][E->get()]="";
}
_fix_and_find(EditorFileSystem::get_singleton()->get_filesystem(),candidates);
Map<String,String> remaps;
for (Map<String,Map<String,String> >::Element *E=candidates.front();E;E=E->next()) {
for (Map<String,String>::Element *F=E->get().front();F;F=F->next()) {
if (F->get()!=String()) {
remaps[F->key()]=F->get();
}
}
}
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<Texture> folder = get_icon("folder","FileDialog");
bool broken=false;
for(List<String>::Element *E=deps.front();E;E=E->next()) {
TreeItem *item = tree->create_item(root);
String n = E->get();
String path;
String type;
if (n.find("::")!=-1) {
path = n.get_slice("::",0);
type = n.get_slice("::",1);
} else {
path=n;
type="Resource";
}
String name = path.get_file();
Ref<Texture> icon;
if (has_icon(type,"EditorIcons")) {
icon=get_icon(type,"EditorIcons");
} else {
icon=get_icon("Object","EditorIcons");
}
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();
if (EditorNode::get_singleton()->is_scene_open(p_path)) {
EditorNode::get_singleton()->show_warning(vformat(TTR("Scene '%s' is currently being edited.\nChanges will not take effect unless 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 take effect when reloaded."),p_path.get_file()));
}
}
void DependencyEditor::_bind_methods() {
ClassDB::bind_method(_MD("_searched"),&DependencyEditor::_searched);
ClassDB::bind_method(_MD("_load_pressed"),&DependencyEditor::_load_pressed);
ClassDB::bind_method(_MD("_fix_all"),&DependencyEditor::_fix_all);
}
DependencyEditor::DependencyEditor() {
VBoxContainer *vb = memnew( VBoxContainer );
vb->set_name(TTR("Dependencies"));
add_child(vb);
set_child_rect(vb);
tree = memnew( Tree );
tree->set_columns(2);
tree->set_column_titles_visible(true);
tree->set_column_title(0,TTR("Resource"));
tree->set_column_title(1,TTR("Path"));
tree->set_hide_root(true);
tree->connect("button_pressed",this,"_load_pressed");
HBoxContainer *hbc = memnew( HBoxContainer );
Label *label = memnew( Label(TTR("Dependencies:")));
hbc->add_child(label);
hbc->add_spacer();
fixdeps = memnew( Button(TTR("Fix Broken")));
hbc->add_child(fixdeps);
fixdeps->connect("pressed",this,"_fix_all");
vb->add_child(hbc);
MarginContainer *mc = memnew( MarginContainer );
mc->set_v_size_flags(SIZE_EXPAND_FILL);
mc->add_child(tree);
vb->add_child(mc);
set_title(TTR("Dependency Editor"));
search = memnew( EditorFileDialog );
search->connect("file_selected",this,"_searched");
search->set_mode(EditorFileDialog::MODE_OPEN_FILE);
search->set_title(TTR("Search Replacement Resource:"));
add_child(search);
}
/////////////////////////////////////
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);
//print_line(":::"+efsd->get_file_path(i));
bool found=false;
for(int j=0;j<deps.size();j++) {
//print_line("\t"+deps[j]+" vs "+editing);
if (deps[j]==editing) {
//print_line("found");
found=true;
break;
}
}
if (!found)
continue;
Ref<Texture> icon;
String type=efsd->get_file_type(i);
if (!has_icon(type,"EditorIcons")) {
icon=get_icon("Object","EditorIcons");
} else {
icon=get_icon(type,"EditorIcons");
}
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();
set_title(TTR("Owners Of:")+" "+p_path.get_file());
}
DependencyEditorOwners::DependencyEditorOwners() {
owners = memnew( ItemList );
add_child(owners);
set_child_rect(owners);
}
///////////////////////
void DependencyRemoveDialog::_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);
//print_line(":::"+efsd->get_file_path(i));
Set<String> met;
for(int j=0;j<deps.size();j++) {
if (files.has(deps[j])) {
met.insert(deps[j]);
}
}
if (!met.size())
continue;
exist=true;
Ref<Texture> icon;
String type=efsd->get_file_type(i);
if (!has_icon(type,"EditorIcons")) {
icon=get_icon("Object","EditorIcons");
} else {
icon=get_icon(type,"EditorIcons");
}
for(Set<String>::Element *E=met.front();E;E=E->next()) {
String which = E->get();
if (!files[which]) {
TreeItem *ti=owners->create_item(owners->get_root());
ti->set_text(0,which.get_file());
files[which]=ti;
}
TreeItem *ti=owners->create_item(files[which]);
ti->set_text(0,efsd->get_file_path(i));
ti->set_icon(0,icon);
}
}
}
void DependencyRemoveDialog::show(const Vector<String> &to_erase) {
exist=false;
owners->clear();
files.clear();
owners->create_item(); // root
for(int i=0;i<to_erase.size();i++) {
files[to_erase[i]]=NULL;
}
_fill_owners(EditorFileSystem::get_singleton()->get_filesystem());
if (exist) {
owners->show();
text->set_text(TTR("The files being removed are required by other resources in order for them to work.\nRemove them anyway? (no undo)"));
popup_centered_minsize(Size2(500,220));
} else {
owners->hide();
text->set_text(TTR("Remove selected files from the project? (no undo)"));
popup_centered_minsize(Size2(400,100));
}
}
void DependencyRemoveDialog::ok_pressed() {
DirAccess *da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
for (Map<String,TreeItem*>::Element *E=files.front();E;E=E->next()) {
if (ResourceCache::has(E->key())) {
Resource *res = ResourceCache::get(E->key());
res->set_path(""); //clear reference to path
}
da->remove(E->key());
EditorFileSystem::get_singleton()->update_file(E->key());
}
memdelete(da);
}
DependencyRemoveDialog::DependencyRemoveDialog() {
VBoxContainer *vb = memnew( VBoxContainer );
add_child(vb);
set_child_rect(vb);
text = memnew( Label );
vb->add_child(text);
owners = memnew( Tree );
owners->set_hide_root(true);
vb->add_child(owners);
owners->set_v_size_flags(SIZE_EXPAND_FILL);
get_ok()->set_text(TTR("Remove"));
}
//////////////
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(NULL);
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<Texture> icon;
if (!has_icon(type,"EditorIcons")) {
icon=get_icon("Object","EditorIcons");
} else {
icon=get_icon(type,"EditorIcons");
}
TreeItem *ti=files->create_item(root);
ti->set_text(0,dep);
ti->set_icon(0,icon);
}
popup_centered_minsize(Size2(500,220));
}
void DependencyErrorDialog::ok_pressed() {
EditorNode::get_singleton()->load_scene(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);
set_child_rect(vb);
files = memnew( Tree );
files->set_hide_root(true);
vb->add_margin_child(TTR("Scene failed to load due to missing dependencies:"),files,true);
files->set_v_size_flags(SIZE_EXPAND_FILL);
get_ok()->set_text(TTR("Open Anyway"));
text = memnew( Label );
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.empty())
return;
delete_confirm->set_text(vformat(TTR("Permanently delete %d item(s)? (No undo!)"),paths.size()));
delete_confirm->popup_centered_minsize();
}
bool OrphanResourcesDialog::_fill_owners(EditorFileSystemDirectory *efsd,HashMap<String,int>& refs,TreeItem* p_parent){
if (!efsd)
return false;
bool has_childs=false;
for(int i=0;i<efsd->get_subdir_count();i++) {
TreeItem *dir_item=NULL;
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,get_icon("folder","FileDialog"));
}
bool children = _fill_owners(efsd->get_subdir(i),refs,dir_item);
if (p_parent) {
if (!children) {
memdelete(dir_item);
} else {
has_childs=true;
}
}
}
for(int i=0;i<efsd->get_file_count();i++) {
if (!p_parent) {
Vector<String> deps = efsd->get_file_deps(i);
//print_line(":::"+efsd->get_file_path(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<Texture> icon;
if (has_icon(type,"EditorIcons")) {
icon=get_icon(type,"EditorIcons");
} else {
icon=get_icon("Object","EditorIcons");
}
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,get_icon("Visible","EditorIcons"));
}
ti->set_metadata(0,path);
has_childs=true;
}
}
}
return has_childs;
}
void OrphanResourcesDialog::refresh() {
HashMap<String,int> refs;
_fill_owners(EditorFileSystem::get_singleton()->get_filesystem(),refs,NULL);
files->clear();
TreeItem *root=files->create_item();
_fill_owners(EditorFileSystem::get_singleton()->get_filesystem(),refs,root);
}
void OrphanResourcesDialog::show(){
refresh();
popup_centered_ratio();
}
void OrphanResourcesDialog::_find_to_delete(TreeItem* p_item,List<String>& paths) {
while(p_item) {
if (p_item->get_cell_mode(0)==TreeItem::CELL_MODE_CHECK && p_item->is_checked(0)) {
paths.push_back(p_item->get_metadata(0));
}
if (p_item->get_children()) {
_find_to_delete(p_item->get_children(),paths);
}
p_item=p_item->get_next();
}
}
void OrphanResourcesDialog::_delete_confirm() {
DirAccess *da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
for (List<String>::Element *E=paths.front();E;E=E->next()) {
da->remove(E->get());
EditorFileSystem::get_singleton()->update_file(E->get());
}
memdelete(da);
refresh();
}
void OrphanResourcesDialog::_button_pressed(Object *p_item,int p_column, int p_id) {
TreeItem *ti=p_item->cast_to<TreeItem>();
String path = ti->get_metadata(0);
dep_edit->edit(path);
}
void OrphanResourcesDialog::_bind_methods() {
ClassDB::bind_method(_MD("_delete_confirm"),&OrphanResourcesDialog::_delete_confirm);
ClassDB::bind_method(_MD("_button_pressed"),&OrphanResourcesDialog::_button_pressed);
}
OrphanResourcesDialog::OrphanResourcesDialog(){
VBoxContainer *vbc = memnew( VBoxContainer );
add_child(vbc);
set_child_rect(vbc);
files = memnew( Tree );
files->set_columns(2);
files->set_column_titles_visible(true);
files->set_column_min_width(1,100);
files->set_column_expand(0,true);
files->set_column_expand(1,false);
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);
set_title(TTR("Orphan Resource Explorer"));
delete_confirm = memnew( ConfirmationDialog );
delete_confirm->set_text(TTR("Delete selected files?"));
get_ok()->set_text(TTR("Delete"));
add_child(delete_confirm);
dep_edit = memnew( DependencyEditor );
add_child(dep_edit);
files->connect("button_pressed",this,"_button_pressed");
delete_confirm->connect("confirmed",this,"_delete_confirm");
set_hide_on_ok(false);
}