diff options
Diffstat (limited to 'editor/filesystem_dock.cpp')
-rw-r--r-- | editor/filesystem_dock.cpp | 417 |
1 files changed, 318 insertions, 99 deletions
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 4fdc3dc080..ab5fd30998 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,12 +30,13 @@ #include "filesystem_dock.h" +#include "core/config/project_settings.h" #include "core/io/resource_loader.h" #include "core/os/dir_access.h" #include "core/os/file_access.h" #include "core/os/keyboard.h" #include "core/os/os.h" -#include "core/project_settings.h" +#include "core/templates/list.h" #include "editor_feature_profile.h" #include "editor_node.h" #include "editor_resource_preview.h" @@ -46,13 +47,12 @@ #include "scene/resources/packed_scene.h" #include "servers/display_server.h" -Ref<Texture2D> FileSystemDock::_get_tree_item_icon(EditorFileSystemDirectory *p_dir, int p_idx) { +Ref<Texture2D> FileSystemDock::_get_tree_item_icon(bool p_is_valid, String p_file_type) { Ref<Texture2D> file_icon; - if (!p_dir->get_file_import_is_valid(p_idx)) { + if (!p_is_valid) { file_icon = get_theme_icon("ImportFail", "EditorIcons"); } else { - String file_type = p_dir->get_file_type(p_idx); - file_icon = (has_theme_icon(file_type, "EditorIcons")) ? get_theme_icon(file_type, "EditorIcons") : get_theme_icon("File", "EditorIcons"); + file_icon = (has_theme_icon(p_file_type, "EditorIcons")) ? get_theme_icon(p_file_type, "EditorIcons") : get_theme_icon("File", "EditorIcons"); } return file_icon; } @@ -68,6 +68,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory } subdirectory_item->set_text(0, dname); + subdirectory_item->set_structured_text_bidi_override(0, STRUCTURED_TEXT_FILE); subdirectory_item->set_icon(0, get_theme_icon("Folder", "EditorIcons")); subdirectory_item->set_icon_modulate(0, get_theme_color("folder_icon_modulate", "FileDialog")); subdirectory_item->set_selectable(0, true); @@ -75,6 +76,9 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory subdirectory_item->set_metadata(0, lpath); if (!p_select_in_favorites && (path == lpath || ((display_mode == DISPLAY_MODE_SPLIT) && path.get_base_dir() == lpath))) { subdirectory_item->select(0); + // Keep select an item when re-created a tree + // To prevent crashing when nothing is selected. + subdirectory_item->set_as_cursor(0); } if (p_unfold_path && path.begins_with(lpath) && path != lpath) { @@ -87,22 +91,27 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory } // Create items for all subdirectories. - for (int i = 0; i < p_dir->get_subdir_count(); i++) { + bool reversed = file_sort == FILE_SORT_NAME_REVERSE; + for (int i = reversed ? p_dir->get_subdir_count() - 1 : 0; + reversed ? i >= 0 : i < p_dir->get_subdir_count(); + reversed ? i-- : i++) { parent_should_expand = (_create_tree(subdirectory_item, p_dir->get_subdir(i), uncollapsed_paths, p_select_in_favorites, p_unfold_path) || parent_should_expand); } // Create all items for the files in the subdirectory. if (display_mode == DISPLAY_MODE_TREE_ONLY) { String main_scene = ProjectSettings::get_singleton()->get("application/run/main_scene"); + + // Build the list of the files to display. + List<FileInfo> file_list; for (int i = 0; i < p_dir->get_file_count(); i++) { String file_type = p_dir->get_file_type(i); - if (_is_file_type_disabled_by_feature_profile(file_type)) { // If type is disabled, file won't be displayed. continue; } - String file_name = p_dir->get_file(i); + String file_name = p_dir->get_file(i); if (searched_string.length() > 0) { if (file_name.to_lower().find(searched_string) < 0) { // The searched string is not in the file name, we skip it. @@ -113,10 +122,27 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory } } + FileInfo fi; + fi.name = p_dir->get_file(i); + fi.type = p_dir->get_file_type(i); + fi.import_broken = !p_dir->get_file_import_is_valid(i); + fi.modified_time = p_dir->get_file_modified_time(i); + + file_list.push_back(fi); + } + + // Sort the file list if needed. + _sort_file_info_list(file_list); + + // Build the tree. + for (List<FileInfo>::Element *E = file_list.front(); E; E = E->next()) { + FileInfo fi = E->get(); + TreeItem *file_item = tree->create_item(subdirectory_item); - file_item->set_text(0, file_name); - file_item->set_icon(0, _get_tree_item_icon(p_dir, i)); - String file_metadata = lpath.plus_file(file_name); + file_item->set_text(0, fi.name); + file_item->set_structured_text_bidi_override(0, STRUCTURED_TEXT_FILE); + file_item->set_icon(0, _get_tree_item_icon(!fi.import_broken, fi.type)); + String file_metadata = lpath.plus_file(fi.name); file_item->set_metadata(0, file_metadata); if (!p_select_in_favorites && path == file_metadata) { file_item->select(0); @@ -220,7 +246,7 @@ void FileSystemDock::_update_tree(const Vector<String> &p_uncollapsed_paths, boo int index; EditorFileSystemDirectory *dir = EditorFileSystem::get_singleton()->find_file(fave, &index); if (dir) { - icon = _get_tree_item_icon(dir, index); + icon = _get_tree_item_icon(dir->get_file_import_is_valid(index), dir->get_file_type(index)); } else { icon = get_theme_icon("File", "EditorIcons"); } @@ -273,9 +299,9 @@ void FileSystemDock::_update_display_mode(bool p_force) { tree->show(); tree->set_v_size_flags(SIZE_EXPAND_FILL); if (display_mode == DISPLAY_MODE_TREE_ONLY) { - tree_search_box->show(); + toolbar2_hbc->show(); } else { - tree_search_box->hide(); + toolbar2_hbc->hide(); } _update_tree(_compute_uncollapsed_paths()); @@ -286,7 +312,7 @@ void FileSystemDock::_update_display_mode(bool p_force) { tree->show(); tree->set_v_size_flags(SIZE_EXPAND_FILL); tree->ensure_cursor_is_visible(); - tree_search_box->hide(); + toolbar2_hbc->hide(); _update_tree(_compute_uncollapsed_paths()); file_list_vb->show(); @@ -299,6 +325,8 @@ void FileSystemDock::_update_display_mode(bool p_force) { void FileSystemDock::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_TRANSLATION_CHANGED: + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_ENTER_TREE: { if (initialized) { return; @@ -318,13 +346,22 @@ void FileSystemDock::_notification(int p_what) { files->connect("item_activated", callable_mp(this, &FileSystemDock::_file_list_activate_file)); button_hist_next->connect("pressed", callable_mp(this, &FileSystemDock::_fw_history)); button_hist_prev->connect("pressed", callable_mp(this, &FileSystemDock::_bw_history)); + tree_search_box->set_right_icon(get_theme_icon("Search", ei)); tree_search_box->set_clear_button_enabled(true); + tree_button_sort->set_icon(get_theme_icon("Sort", ei)); + file_list_search_box->set_right_icon(get_theme_icon("Search", ei)); file_list_search_box->set_clear_button_enabled(true); + file_list_button_sort->set_icon(get_theme_icon("Sort", ei)); - button_hist_next->set_icon(get_theme_icon("Forward", ei)); - button_hist_prev->set_icon(get_theme_icon("Back", ei)); + if (is_layout_rtl()) { + button_hist_next->set_icon(get_theme_icon("Back", ei)); + button_hist_prev->set_icon(get_theme_icon("Forward", ei)); + } else { + button_hist_next->set_icon(get_theme_icon("Forward", ei)); + button_hist_prev->set_icon(get_theme_icon("Back", ei)); + } file_list_popup->connect("id_pressed", callable_mp(this, &FileSystemDock::_file_list_rmb_option)); tree_popup->connect("id_pressed", callable_mp(this, &FileSystemDock::_tree_rmb_option)); @@ -377,8 +414,13 @@ void FileSystemDock::_notification(int p_what) { String ei = "EditorIcons"; button_reload->set_icon(get_theme_icon("Reload", ei)); button_toggle_display_mode->set_icon(get_theme_icon("Panels2", ei)); - button_hist_next->set_icon(get_theme_icon("Forward", ei)); - button_hist_prev->set_icon(get_theme_icon("Back", ei)); + if (is_layout_rtl()) { + button_hist_next->set_icon(get_theme_icon("Back", ei)); + button_hist_prev->set_icon(get_theme_icon("Forward", ei)); + } else { + button_hist_next->set_icon(get_theme_icon("Forward", ei)); + button_hist_prev->set_icon(get_theme_icon("Back", ei)); + } if (file_list_display_mode == FILE_LIST_DISPLAY_LIST) { button_file_list_display_mode->set_icon(get_theme_icon("FileThumbnail", "EditorIcons")); } else { @@ -387,8 +429,11 @@ void FileSystemDock::_notification(int p_what) { tree_search_box->set_right_icon(get_theme_icon("Search", ei)); tree_search_box->set_clear_button_enabled(true); + tree_button_sort->set_icon(get_theme_icon("Sort", ei)); + file_list_search_box->set_right_icon(get_theme_icon("Search", ei)); file_list_search_box->set_clear_button_enabled(true); + file_list_button_sort->set_icon(get_theme_icon("Sort", ei)); // Update always show folders. bool new_always_show_folders = bool(EditorSettings::get_singleton()->get("docks/filesystem/always_show_folders")); @@ -489,7 +534,7 @@ void FileSystemDock::_navigate_to_path(const String &p_path, bool p_select_in_fa } String file_name = p_path.get_file(); - if (!file_name.empty()) { + if (!file_name.is_empty()) { for (int i = 0; i < files->get_item_count(); i++) { if (files->get_item_text(i) == file_name) { files->select(i, true); @@ -589,6 +634,7 @@ void FileSystemDock::_search(EditorFileSystemDirectory *p_path, List<FileInfo> * fi.type = p_path->get_file_type(i); fi.path = p_path->get_file_path(i); fi.import_broken = !p_path->get_file_import_is_valid(i); + fi.modified_time = p_path->get_file_modified_time(i); if (_is_file_type_disabled_by_feature_profile(fi.type)) { // This type is disabled, will not appear here. @@ -603,6 +649,54 @@ void FileSystemDock::_search(EditorFileSystemDirectory *p_path, List<FileInfo> * } } +struct FileSystemDock::FileInfoTypeComparator { + bool operator()(const FileInfo &p_a, const FileInfo &p_b) const { + // Uses the extension, then the icon name to distinguish file types. + String icon_path_a = ""; + String icon_path_b = ""; + Ref<Texture2D> icon_a = EditorNode::get_singleton()->get_class_icon(p_a.type); + if (icon_a.is_valid()) { + icon_path_a = icon_a->get_name(); + } + Ref<Texture2D> icon_b = EditorNode::get_singleton()->get_class_icon(p_b.type); + if (icon_b.is_valid()) { + icon_path_b = icon_b->get_name(); + } + return NaturalNoCaseComparator()(p_a.name.get_extension() + icon_path_a + p_a.name.get_basename(), p_b.name.get_extension() + icon_path_b + p_b.name.get_basename()); + } +}; + +struct FileSystemDock::FileInfoModifiedTimeComparator { + bool operator()(const FileInfo &p_a, const FileInfo &p_b) const { + return p_a.modified_time > p_b.modified_time; + } +}; + +void FileSystemDock::_sort_file_info_list(List<FileSystemDock::FileInfo> &r_file_list) { + // Sort the file list if needed. + switch (file_sort) { + case FILE_SORT_TYPE: + r_file_list.sort_custom<FileInfoTypeComparator>(); + break; + case FILE_SORT_TYPE_REVERSE: + r_file_list.sort_custom<FileInfoTypeComparator>(); + r_file_list.invert(); + break; + case FILE_SORT_MODIFIED_TIME: + r_file_list.sort_custom<FileInfoModifiedTimeComparator>(); + break; + case FILE_SORT_MODIFIED_TIME_REVERSE: + r_file_list.sort_custom<FileInfoModifiedTimeComparator>(); + r_file_list.invert(); + break; + case FILE_SORT_NAME_REVERSE: + r_file_list.invert(); + break; + default: // FILE_SORT_NAME + break; + } +} + void FileSystemDock::_update_file_list(bool p_keep_selection) { // Register the previously selected items. Set<String> cselection; @@ -660,7 +754,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { const Color folder_color = get_theme_color("folder_icon_modulate", "FileDialog"); // Build the FileInfo list. - List<FileInfo> filelist; + List<FileInfo> file_list; if (path == "Favorites") { // Display the favorites. Vector<String> favorites = EditorSettings::get_singleton()->get_favorites(); @@ -692,13 +786,15 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { if (efd) { fi.type = efd->get_file_type(index); fi.import_broken = !efd->get_file_import_is_valid(index); + fi.modified_time = efd->get_file_modified_time(index); } else { fi.type = ""; fi.import_broken = true; + fi.modified_time = 0; } if (searched_string.length() == 0 || fi.name.to_lower().find(searched_string) >= 0) { - filelist.push_back(fi); + file_list.push_back(fi); } } } @@ -719,7 +815,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { if (searched_string.length() > 0) { // Display the search results. - _search(EditorFileSystem::get_singleton()->get_filesystem(), &filelist, 128); + _search(EditorFileSystem::get_singleton()->get_filesystem(), &file_list, 128); } else { if (display_mode == DISPLAY_MODE_TREE_ONLY || always_show_folders) { // Display folders in the list. @@ -736,7 +832,10 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { files->set_item_icon_modulate(files->get_item_count() - 1, folder_color); } - for (int i = 0; i < efd->get_subdir_count(); i++) { + bool reversed = file_sort == FILE_SORT_NAME_REVERSE; + for (int i = reversed ? efd->get_subdir_count() - 1 : 0; + reversed ? i >= 0 : i < efd->get_subdir_count(); + reversed ? i-- : i++) { String dname = efd->get_subdir(i)->get_name(); files->add_item(dname, folder_icon, true); @@ -756,17 +855,21 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { fi.path = directory.plus_file(fi.name); fi.type = efd->get_file_type(i); fi.import_broken = !efd->get_file_import_is_valid(i); + fi.modified_time = efd->get_file_modified_time(i); - filelist.push_back(fi); + file_list.push_back(fi); } } - filelist.sort(); + file_list.sort(); } + // Sort the file list if needed. + _sort_file_info_list(file_list); + // Fills the ItemList control node from the FileInfos. String main_scene = ProjectSettings::get_singleton()->get("application/run/main_scene"); String oi = "Object"; - for (List<FileInfo>::Element *E = filelist.front(); E; E = E->next()) { + for (List<FileInfo>::Element *E = file_list.front(); E; E = E->next()) { FileInfo *finfo = &(E->get()); String fname = finfo->name; String fpath = finfo->path; @@ -1307,10 +1410,28 @@ void FileSystemDock::_make_scene_confirm() { void FileSystemDock::_file_removed(String p_file) { emit_signal("file_removed", p_file); + + // Find the closest parent directory available, in case multiple items were deleted along the same path. + path = p_file.get_base_dir(); + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES); + while (!da->dir_exists(path)) { + path = path.get_base_dir(); + } + + current_path->set_text(path); } void FileSystemDock::_folder_removed(String p_folder) { emit_signal("folder_removed", p_folder); + + // Find the closest parent directory available, in case multiple items were deleted along the same path. + path = p_folder.get_base_dir(); + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES); + while (!da->dir_exists(path)) { + path = path.get_base_dir(); + } + + current_path->set_text(path); } void FileSystemDock::_rename_operation_confirm() { @@ -1365,6 +1486,9 @@ void FileSystemDock::_rename_operation_confirm() { print_verbose("FileSystem: saving moved scenes."); _save_scenes_after_move(file_renames); + + path = new_path; + current_path->set_text(path); } void FileSystemDock::_duplicate_operation_confirm() { @@ -1405,7 +1529,8 @@ void FileSystemDock::_move_with_overwrite() { _move_operation_confirm(to_move_path, true); } -bool FileSystemDock::_check_existing() { +Vector<String> FileSystemDock::_check_existing() { + Vector<String> conflicting_items; String &p_to_path = to_move_path; for (int i = 0; i < to_move.size(); i++) { String ol_pth = to_move[i].path.ends_with("/") ? to_move[i].path.substr(0, to_move[i].path.length() - 1) : to_move[i].path; @@ -1415,21 +1540,24 @@ bool FileSystemDock::_check_existing() { String old_path = (p_item.is_file || p_item.path.ends_with("/")) ? p_item.path : (p_item.path + "/"); String new_path = (p_item.is_file || p_new_path.ends_with("/")) ? p_new_path : (p_new_path + "/"); - if (p_item.is_file && FileAccess::exists(new_path)) { - return false; - } else if (!p_item.is_file && DirAccess::exists(new_path)) { - return false; + if ((p_item.is_file && FileAccess::exists(new_path)) || + (!p_item.is_file && DirAccess::exists(new_path))) { + conflicting_items.push_back(old_path); } } - return true; + return conflicting_items; } void FileSystemDock::_move_operation_confirm(const String &p_to_path, bool p_overwrite) { if (!p_overwrite) { to_move_path = p_to_path; - bool can_move = _check_existing(); - if (!can_move) { + Vector<String> conflicting_items = _check_existing(); + if (!conflicting_items.is_empty()) { // Ask to do something. + overwrite_dialog->set_text(vformat( + TTR("The following files or folders conflict with items in the target location '%s':\n\n%s\n\nDo you wish to overwrite them?"), + to_move_path, + String("\n").join(conflicting_items))); overwrite_dialog->popup_centered(); return; } @@ -1469,6 +1597,9 @@ void FileSystemDock::_move_operation_confirm(const String &p_to_path, bool p_ove print_verbose("FileSystem: saving moved scenes."); _save_scenes_after_move(file_renames); + + path = p_to_path; + current_path->set_text(path); } } @@ -1617,7 +1748,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected paths.push_back(fpath); } } - if (!paths.empty()) { + if (!paths.is_empty()) { emit_signal("instance", paths); } } break; @@ -1649,7 +1780,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected case FILE_DEPENDENCIES: { // Checkout the file dependencies. - if (!p_selected.empty()) { + if (!p_selected.is_empty()) { String fpath = p_selected[0]; deps_editor->edit(fpath); } @@ -1657,7 +1788,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected case FILE_OWNERS: { // Checkout the file owners. - if (!p_selected.empty()) { + if (!p_selected.is_empty()) { String fpath = p_selected[0]; owners_editor->show(fpath); } @@ -1680,7 +1811,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected case FILE_RENAME: { // Rename the active file. - if (!p_selected.empty()) { + if (!p_selected.is_empty()) { to_rename.path = p_selected[0]; if (to_rename.path != "res://") { to_rename.is_file = !to_rename.path.ends_with("/"); @@ -1688,7 +1819,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected String name = to_rename.path.get_file(); rename_dialog->set_title(TTR("Renaming file:") + " " + name); rename_dialog_text->set_text(name); - rename_dialog_text->select(0, name.find_last(".")); + rename_dialog_text->select(0, name.rfind(".")); } else { String name = to_rename.path.substr(0, to_rename.path.length() - 1).get_file(); rename_dialog->set_title(TTR("Renaming folder:") + " " + name); @@ -1732,7 +1863,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected String name = to_duplicate.path.get_file(); duplicate_dialog->set_title(TTR("Duplicating file:") + " " + name); duplicate_dialog_text->set_text(name); - duplicate_dialog_text->select(0, name.find_last(".")); + duplicate_dialog_text->select(0, name.rfind(".")); } else { String name = to_duplicate.path.substr(0, to_duplicate.path.length() - 1).get_file(); duplicate_dialog->set_title(TTR("Duplicating folder:") + " " + name); @@ -1781,7 +1912,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } break; case FILE_COPY_PATH: { - if (!p_selected.empty()) { + if (!p_selected.is_empty()) { String fpath = p_selected[0]; DisplayServer::get_singleton()->clipboard_set(fpath); } @@ -1794,7 +1925,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } void FileSystemDock::_resource_created() { - Object *c = new_resource_dialog->instance_selected(); + Variant c = new_resource_dialog->instance_selected(); ERR_FAIL_COND(!c); Resource *r = Object::cast_to<Resource>(c); @@ -1808,17 +1939,14 @@ void FileSystemDock::_resource_created() { memdelete(node); } - REF res(r); - editor->push_item(c); - - RES current_res = RES(r); + editor->push_item(r); String fpath = path; if (!fpath.ends_with("/")) { fpath = fpath.get_base_dir(); } - editor->save_resource_as(current_res, fpath); + editor->save_resource_as(RES(r), fpath); } void FileSystemDock::_search_changed(const String &p_text, const Control *p_from) { @@ -1909,7 +2037,7 @@ Variant FileSystemDock::get_drag_data_fw(const Point2 &p_point, Control *p_from) all_not_favorites = true; } - if (paths.empty()) { + if (paths.is_empty()) { return Variant(); } @@ -1957,7 +2085,7 @@ bool FileSystemDock::can_drop_data_fw(const Point2 &p_point, const Variant &p_da String to_dir; bool favorite; _get_drag_target_folder(to_dir, favorite, p_point, p_from); - return !to_dir.empty(); + return !to_dir.is_empty(); } if (drag_data.has("type") && (String(drag_data["type"]) == "files" || String(drag_data["type"]) == "files_and_dirs")) { @@ -1970,7 +2098,7 @@ bool FileSystemDock::can_drop_data_fw(const Point2 &p_point, const Variant &p_da return true; } - if (to_dir.empty()) { + if (to_dir.is_empty()) { return false; } @@ -2065,7 +2193,7 @@ void FileSystemDock::drop_data_fw(const Point2 &p_point, const Variant &p_data, String to_dir; bool favorite; _get_drag_target_folder(to_dir, favorite, p_point, p_from); - if (res.is_valid() && !to_dir.empty()) { + if (res.is_valid() && !to_dir.is_empty()) { EditorNode::get_singleton()->push_item(res.ptr()); EditorNode::get_singleton()->save_resource_as(res, to_dir); } @@ -2076,7 +2204,7 @@ void FileSystemDock::drop_data_fw(const Point2 &p_point, const Variant &p_data, String to_dir; bool favorite; _get_drag_target_folder(to_dir, favorite, p_point, p_from); - if (!to_dir.empty()) { + if (!to_dir.is_empty()) { Vector<String> fnames = drag_data["files"]; to_move.clear(); for (int i = 0; i < fnames.size(); i++) { @@ -2084,8 +2212,33 @@ void FileSystemDock::drop_data_fw(const Point2 &p_point, const Variant &p_data, to_move.push_back(FileOrFolder(fnames[i], !fnames[i].ends_with("/"))); } } - if (!to_move.empty()) { - _move_operation_confirm(to_dir); + if (!to_move.is_empty()) { + if (Input::get_singleton()->is_key_pressed(KEY_CONTROL)) { + for (int i = 0; i < to_move.size(); i++) { + String new_path; + String new_path_base; + + if (to_move[i].is_file) { + new_path = to_dir.plus_file(to_move[i].path.get_file()); + new_path_base = new_path.get_basename() + " (%d)." + new_path.get_extension(); + } else { + PackedStringArray path_split = to_move[i].path.split("/"); + new_path = to_dir.plus_file(path_split[path_split.size() - 2]); + new_path_base = new_path + " (%d)"; + } + + int exist_counter = 1; + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES); + while (da->file_exists(new_path) || da->dir_exists(new_path)) { + exist_counter++; + new_path = vformat(new_path_base, exist_counter); + } + _try_duplicate_item(to_move[i], new_path); + } + _rescan(); + } else { + _move_operation_confirm(to_dir); + } } } else if (favorite) { // Add the files from favorites. @@ -2162,7 +2315,7 @@ void FileSystemDock::_get_drag_target_folder(String &target, bool &target_favori void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, Vector<String> p_paths, bool p_display_path_dependent_options) { // Add options for files and folders. - ERR_FAIL_COND_MSG(p_paths.empty(), "Path cannot be empty."); + ERR_FAIL_COND_MSG(p_paths.is_empty(), "Path cannot be empty."); Vector<String> filenames; Vector<String> foldernames; @@ -2243,16 +2396,16 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, Vector<Str } if (p_paths.size() == 1) { - p_popup->add_icon_item(get_theme_icon("ActionCopy", "EditorIcons"), TTR("Copy Path"), FILE_COPY_PATH); + p_popup->add_icon_shortcut(get_theme_icon("ActionCopy", "EditorIcons"), ED_GET_SHORTCUT("filesystem_dock/copy_path"), FILE_COPY_PATH); if (p_paths[0] != "res://") { - p_popup->add_icon_item(get_theme_icon("Rename", "EditorIcons"), TTR("Rename..."), FILE_RENAME); - p_popup->add_icon_item(get_theme_icon("Duplicate", "EditorIcons"), TTR("Duplicate..."), FILE_DUPLICATE); + p_popup->add_icon_shortcut(get_theme_icon("Rename", "EditorIcons"), ED_GET_SHORTCUT("filesystem_dock/rename"), FILE_RENAME); + p_popup->add_icon_shortcut(get_theme_icon("Duplicate", "EditorIcons"), ED_GET_SHORTCUT("filesystem_dock/duplicate"), FILE_DUPLICATE); } } if (p_paths.size() > 1 || p_paths[0] != "res://") { p_popup->add_icon_item(get_theme_icon("MoveUp", "EditorIcons"), TTR("Move To..."), FILE_MOVE); - p_popup->add_icon_item(get_theme_icon("Remove", "EditorIcons"), TTR("Delete"), FILE_REMOVE); + p_popup->add_icon_shortcut(get_theme_icon("Remove", "EditorIcons"), ED_GET_SHORTCUT("filesystem_dock/delete"), FILE_REMOVE); } if (p_paths.size() == 1) { @@ -2275,6 +2428,7 @@ void FileSystemDock::_tree_rmb_select(const Vector2 &p_pos) { // Right click is pressed in the tree. Vector<String> paths = _tree_get_selected(false); + tree_popup->clear(); if (paths.size() == 1) { if (paths[0].ends_with("/")) { tree_popup->add_icon_item(get_theme_icon("GuiTreeArrowDown", "EditorIcons"), TTR("Expand All"), FOLDER_EXPAND_ALL); @@ -2284,11 +2438,10 @@ void FileSystemDock::_tree_rmb_select(const Vector2 &p_pos) { } // Popup. - if (!paths.empty()) { - tree_popup->clear(); + if (!paths.is_empty()) { tree_popup->set_size(Size2(1, 1)); _file_and_folders_fill_popup(tree_popup, paths); - tree_popup->set_position(tree->get_global_position() + p_pos); + tree_popup->set_position(tree->get_screen_position() + p_pos); tree_popup->popup(); } } @@ -2318,14 +2471,14 @@ void FileSystemDock::_file_list_rmb_select(int p_item, const Vector2 &p_pos) { continue; } if (files->get_item_text(p_item) == "..") { - files->unselect(i); + files->deselect(i); continue; } paths.push_back(files->get_item_metadata(i)); } // Popup. - if (!paths.empty()) { + if (!paths.is_empty()) { file_list_popup->clear(); file_list_popup->set_size(Size2(1, 1)); _file_and_folders_fill_popup(file_list_popup, paths, searched_string.length() == 0); @@ -2386,7 +2539,11 @@ void FileSystemDock::_tree_gui_input(Ref<InputEvent> p_event) { _tree_rmb_option(FILE_REMOVE); } else if (ED_IS_SHORTCUT("filesystem_dock/rename", p_event)) { _tree_rmb_option(FILE_RENAME); + } else { + return; } + + accept_event(); } } @@ -2401,15 +2558,39 @@ void FileSystemDock::_file_list_gui_input(Ref<InputEvent> p_event) { _file_list_rmb_option(FILE_REMOVE); } else if (ED_IS_SHORTCUT("filesystem_dock/rename", p_event)) { _file_list_rmb_option(FILE_RENAME); + } else { + return; } + + accept_event(); } } -void FileSystemDock::_update_import_dock() { - if (!import_dock_needs_update) { +void FileSystemDock::_get_imported_files(const String &p_path, Vector<String> &files) const { + if (!p_path.ends_with("/")) { + if (FileAccess::exists(p_path + ".import")) { + files.push_back(p_path); + } return; } + DirAccess *da = DirAccess::open(p_path); + da->list_dir_begin(); + String n = da->get_next(); + while (n != String()) { + if (n != "." && n != ".." && !n.ends_with(".import")) { + String npath = p_path + n + (da->current_is_dir() ? "/" : ""); + _get_imported_files(npath, files); + } + n = da->get_next(); + } + da->list_dir_end(); +} + +void FileSystemDock::_update_import_dock() { + if (!import_dock_needs_update) + return; + // List selected. Vector<String> selected; if (display_mode == DISPLAY_MODE_TREE_ONLY) { @@ -2419,29 +2600,24 @@ void FileSystemDock::_update_import_dock() { } else { // Use the file list. for (int i = 0; i < files->get_item_count(); i++) { - if (!files->is_selected(i)) { + if (!files->is_selected(i)) continue; - } selected.push_back(files->get_item_metadata(i)); } } + // Expand directory selection + Vector<String> efiles; + for (int i = 0; i < selected.size(); i++) { + _get_imported_files(selected[i], efiles); + } + // Check import. Vector<String> imports; String import_type; - for (int i = 0; i < selected.size(); i++) { - String fpath = selected[i]; - - if (fpath.ends_with("/")) { - imports.clear(); - break; - } - - if (!FileAccess::exists(fpath + ".import")) { - imports.clear(); - break; - } + for (int i = 0; i < efiles.size(); i++) { + String fpath = efiles[i]; Ref<ConfigFile> cf; cf.instance(); Error err = cf->load(fpath + ".import"); @@ -2476,6 +2652,39 @@ void FileSystemDock::_feature_profile_changed() { _update_display_mode(true); } +void FileSystemDock::set_file_sort(FileSortOption p_file_sort) { + for (int i = 0; i != FILE_SORT_MAX; i++) { + tree_button_sort->get_popup()->set_item_checked(i, (i == (int)p_file_sort)); + file_list_button_sort->get_popup()->set_item_checked(i, (i == (int)p_file_sort)); + } + file_sort = p_file_sort; + + // Update everything needed. + _update_tree(_compute_uncollapsed_paths()); + _update_file_list(true); +} + +void FileSystemDock::_file_sort_popup(int p_id) { + set_file_sort((FileSortOption)p_id); +} + +MenuButton *FileSystemDock::_create_file_menu_button() { + MenuButton *button = memnew(MenuButton); + button->set_flat(true); + button->set_tooltip(TTR("Sort files")); + + PopupMenu *p = button->get_popup(); + p->connect("id_pressed", callable_mp(this, &FileSystemDock::_file_sort_popup)); + p->add_radio_check_item("Sort by Name (Ascending)", FILE_SORT_NAME); + p->add_radio_check_item("Sort by Name (Descending)", FILE_SORT_NAME_REVERSE); + p->add_radio_check_item("Sort by Type (Ascending)", FILE_SORT_TYPE); + p->add_radio_check_item("Sort by Type (Descending)", FILE_SORT_TYPE_REVERSE); + p->add_radio_check_item("Sort by Last Modified", FILE_SORT_MODIFIED_TIME); + p->add_radio_check_item("Sort by First Modified", FILE_SORT_MODIFIED_TIME_REVERSE); + p->set_item_checked(file_sort, true); + return button; +} + void FileSystemDock::_bind_methods() { ClassDB::bind_method(D_METHOD("_update_tree"), &FileSystemDock::_update_tree); @@ -2483,10 +2692,10 @@ void FileSystemDock::_bind_methods() { ClassDB::bind_method(D_METHOD("_tree_thumbnail_done"), &FileSystemDock::_tree_thumbnail_done); ClassDB::bind_method(D_METHOD("_select_file"), &FileSystemDock::_select_file); - ClassDB::bind_method(D_METHOD("get_drag_data_fw"), &FileSystemDock::get_drag_data_fw); - ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &FileSystemDock::can_drop_data_fw); - ClassDB::bind_method(D_METHOD("drop_data_fw"), &FileSystemDock::drop_data_fw); - ClassDB::bind_method(D_METHOD("navigate_to_path"), &FileSystemDock::navigate_to_path); + ClassDB::bind_method(D_METHOD("get_drag_data_fw", "position", "from"), &FileSystemDock::get_drag_data_fw); + ClassDB::bind_method(D_METHOD("can_drop_data_fw", "position", "data", "from"), &FileSystemDock::can_drop_data_fw); + ClassDB::bind_method(D_METHOD("drop_data_fw", "position", "data", "from"), &FileSystemDock::drop_data_fw); + ClassDB::bind_method(D_METHOD("navigate_to_path", "path"), &FileSystemDock::navigate_to_path); ClassDB::bind_method(D_METHOD("_update_import_dock"), &FileSystemDock::_update_import_dock); @@ -2506,10 +2715,11 @@ FileSystemDock::FileSystemDock(EditorNode *p_editor) { editor = p_editor; path = "res://"; - ED_SHORTCUT("filesystem_dock/copy_path", TTR("Copy Path"), KEY_MASK_CMD | KEY_C); + // `KEY_MASK_CMD | KEY_C` conflicts with other editor shortcuts. + ED_SHORTCUT("filesystem_dock/copy_path", TTR("Copy Path"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_C); ED_SHORTCUT("filesystem_dock/duplicate", TTR("Duplicate..."), KEY_MASK_CMD | KEY_D); - ED_SHORTCUT("filesystem_dock/delete", TTR("Delete"), KEY_DELETE); - ED_SHORTCUT("filesystem_dock/rename", TTR("Rename")); + ED_SHORTCUT("filesystem_dock/delete", TTR("Move to Trash"), KEY_DELETE); + ED_SHORTCUT("filesystem_dock/rename", TTR("Rename..."), KEY_F2); VBoxContainer *top_vbc = memnew(VBoxContainer); add_child(top_vbc); @@ -2518,19 +2728,22 @@ FileSystemDock::FileSystemDock(EditorNode *p_editor) { toolbar_hbc->add_theme_constant_override("separation", 0); top_vbc->add_child(toolbar_hbc); - button_hist_prev = memnew(ToolButton); + button_hist_prev = memnew(Button); + button_hist_prev->set_flat(true); button_hist_prev->set_disabled(true); button_hist_prev->set_focus_mode(FOCUS_NONE); button_hist_prev->set_tooltip(TTR("Previous Folder/File")); toolbar_hbc->add_child(button_hist_prev); - button_hist_next = memnew(ToolButton); + button_hist_next = memnew(Button); + button_hist_next->set_flat(true); button_hist_next->set_disabled(true); button_hist_next->set_focus_mode(FOCUS_NONE); button_hist_next->set_tooltip(TTR("Next Folder/File")); toolbar_hbc->add_child(button_hist_next); current_path = memnew(LineEdit); + current_path->set_structured_text_bidi_override(Control::STRUCTURED_TEXT_FILE); current_path->set_h_size_flags(SIZE_EXPAND_FILL); _set_current_path_text(path); toolbar_hbc->add_child(current_path); @@ -2551,7 +2764,7 @@ FileSystemDock::FileSystemDock(EditorNode *p_editor) { button_toggle_display_mode->set_tooltip(TTR("Toggle Split Mode")); toolbar_hbc->add_child(button_toggle_display_mode); - HBoxContainer *toolbar2_hbc = memnew(HBoxContainer); + toolbar2_hbc = memnew(HBoxContainer); toolbar2_hbc->add_theme_constant_override("separation", 0); top_vbc->add_child(toolbar2_hbc); @@ -2561,6 +2774,9 @@ FileSystemDock::FileSystemDock(EditorNode *p_editor) { tree_search_box->connect("text_changed", callable_mp(this, &FileSystemDock::_search_changed), varray(tree_search_box)); toolbar2_hbc->add_child(tree_search_box); + tree_button_sort = _create_file_menu_button(); + toolbar2_hbc->add_child(tree_button_sort); + file_list_popup = memnew(PopupMenu); add_child(file_list_popup); @@ -2602,7 +2818,11 @@ FileSystemDock::FileSystemDock(EditorNode *p_editor) { file_list_search_box->connect("text_changed", callable_mp(this, &FileSystemDock::_search_changed), varray(file_list_search_box)); path_hb->add_child(file_list_search_box); - button_file_list_display_mode = memnew(ToolButton); + file_list_button_sort = _create_file_menu_button(); + path_hb->add_child(file_list_button_sort); + + button_file_list_display_mode = memnew(Button); + button_file_list_display_mode->set_flat(true); path_hb->add_child(button_file_list_display_mode); files = memnew(ItemList); @@ -2641,7 +2861,7 @@ FileSystemDock::FileSystemDock(EditorNode *p_editor) { add_child(remove_dialog); move_dialog = memnew(EditorDirDialog); - move_dialog->get_ok()->set_text(TTR("Move")); + move_dialog->get_ok_button()->set_text(TTR("Move")); add_child(move_dialog); move_dialog->connect("dir_selected", callable_mp(this, &FileSystemDock::_move_operation_confirm), make_binds(false)); @@ -2651,14 +2871,13 @@ FileSystemDock::FileSystemDock(EditorNode *p_editor) { rename_dialog_text = memnew(LineEdit); rename_dialog_vb->add_margin_child(TTR("Name:"), rename_dialog_text); - rename_dialog->get_ok()->set_text(TTR("Rename")); + rename_dialog->get_ok_button()->set_text(TTR("Rename")); add_child(rename_dialog); rename_dialog->register_text_enter(rename_dialog_text); rename_dialog->connect("confirmed", callable_mp(this, &FileSystemDock::_rename_operation_confirm)); overwrite_dialog = memnew(ConfirmationDialog); - overwrite_dialog->set_text(TTR("There is already file or folder with the same name in this location.")); - overwrite_dialog->get_ok()->set_text(TTR("Overwrite")); + overwrite_dialog->get_ok_button()->set_text(TTR("Overwrite")); add_child(overwrite_dialog); overwrite_dialog->connect("confirmed", callable_mp(this, &FileSystemDock::_move_with_overwrite)); @@ -2668,7 +2887,7 @@ FileSystemDock::FileSystemDock(EditorNode *p_editor) { duplicate_dialog_text = memnew(LineEdit); duplicate_dialog_vb->add_margin_child(TTR("Name:"), duplicate_dialog_text); - duplicate_dialog->get_ok()->set_text(TTR("Duplicate")); + duplicate_dialog->get_ok_button()->set_text(TTR("Duplicate")); add_child(duplicate_dialog); duplicate_dialog->register_text_enter(duplicate_dialog_text); duplicate_dialog->connect("confirmed", callable_mp(this, &FileSystemDock::_duplicate_operation_confirm)); |