From 4941d5f534f19b3ce218eb5283bf392431177ff1 Mon Sep 17 00:00:00 2001 From: kobewi Date: Sat, 25 Mar 2023 20:58:37 +0100 Subject: [PATCH] Improve file move and copy operations --- editor/editor_dir_dialog.cpp | 35 ++++++-- editor/editor_dir_dialog.h | 12 ++- editor/filesystem_dock.cpp | 160 ++++++++++++++++++++++------------- editor/filesystem_dock.h | 13 ++- 4 files changed, 146 insertions(+), 74 deletions(-) diff --git a/editor/editor_dir_dialog.cpp b/editor/editor_dir_dialog.cpp index e8e25bcf9685..9da592d6398f 100644 --- a/editor/editor_dir_dialog.cpp +++ b/editor/editor_dir_dialog.cpp @@ -30,11 +30,14 @@ #include "editor_dir_dialog.h" +#include "core/io/dir_access.h" #include "core/os/keyboard.h" #include "core/os/os.h" #include "editor/editor_file_system.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" +#include "scene/gui/check_box.h" +#include "scene/gui/tree.h" #include "servers/display_server.h" void EditorDirDialog::_update_dir(TreeItem *p_item, EditorFileSystemDirectory *p_dir, const String &p_select_path) { @@ -56,8 +59,6 @@ void EditorDirDialog::_update_dir(TreeItem *p_item, EditorFileSystemDirectory *p p_item->set_text(0, p_dir->get_name()); } - //this should be handled by EditorFileSystem already - //bool show_hidden = EDITOR_GET("filesystem/file_dialog/show_hidden_files"); updating = false; for (int i = 0; i < p_dir->get_subdir_count(); i++) { TreeItem *ti = tree->create_item(p_item); @@ -78,6 +79,10 @@ void EditorDirDialog::reload(const String &p_path) { must_reload = false; } +bool EditorDirDialog::is_copy_pressed() const { + return copy->is_pressed(); +} + void EditorDirDialog::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { @@ -107,6 +112,14 @@ void EditorDirDialog::_notification(int p_what) { } } +void EditorDirDialog::_copy_toggled(bool p_pressed) { + if (p_pressed) { + set_ok_button_text(TTR("Copy")); + } else { + set_ok_button_text(TTR("Move")); + } +} + void EditorDirDialog::_item_collapsed(Object *p_item) { TreeItem *item = Object::cast_to(p_item); @@ -172,8 +185,7 @@ void EditorDirDialog::_make_dir_confirm() { mkdirerr->popup_centered(Size2(250, 80) * EDSCALE); } else { opened_paths.insert(dir); - //reload(dir.path_join(makedirname->get_text())); - EditorFileSystem::get_singleton()->scan_changes(); //we created a dir, so rescan changes + EditorFileSystem::get_singleton()->scan_changes(); // We created a dir, so rescan changes. } makedirname->set_text(""); // reset label } @@ -186,11 +198,19 @@ EditorDirDialog::EditorDirDialog() { set_title(TTR("Choose a Directory")); set_hide_on_ok(false); - tree = memnew(Tree); - add_child(tree); + VBoxContainer *vb = memnew(VBoxContainer); + add_child(vb); + tree = memnew(Tree); + vb->add_child(tree); + tree->set_v_size_flags(Control::SIZE_EXPAND_FILL); tree->connect("item_activated", callable_mp(this, &EditorDirDialog::_item_activated)); + copy = memnew(CheckBox); + vb->add_child(copy); + copy->set_text(TTR("Copy File(s)")); + copy->connect("toggled", callable_mp(this, &EditorDirDialog::_copy_toggled)); + makedir = add_button(TTR("Create Folder"), DisplayServer::get_singleton()->get_swap_cancel_ok(), "makedir"); makedir->connect("pressed", callable_mp(this, &EditorDirDialog::_make_dir)); @@ -200,7 +220,6 @@ EditorDirDialog::EditorDirDialog() { VBoxContainer *makevb = memnew(VBoxContainer); makedialog->add_child(makevb); - //makedialog->set_child_rect(makevb); makedirname = memnew(LineEdit); makevb->add_margin_child(TTR("Name:"), makedirname); @@ -211,5 +230,5 @@ EditorDirDialog::EditorDirDialog() { mkdirerr->set_text(TTR("Could not create folder.")); add_child(mkdirerr); - set_ok_button_text(TTR("Choose")); + set_ok_button_text(TTR("Move")); } diff --git a/editor/editor_dir_dialog.h b/editor/editor_dir_dialog.h index e8c5f6274f09..9f2b48c164b7 100644 --- a/editor/editor_dir_dialog.h +++ b/editor/editor_dir_dialog.h @@ -31,10 +31,12 @@ #ifndef EDITOR_DIR_DIALOG_H #define EDITOR_DIR_DIALOG_H -#include "core/io/dir_access.h" -#include "editor/editor_file_system.h" #include "scene/gui/dialogs.h" -#include "scene/gui/tree.h" + +class CheckBox; +class EditorFileSystemDirectory; +class Tree; +class TreeItem; class EditorDirDialog : public ConfirmationDialog { GDCLASS(EditorDirDialog, ConfirmationDialog); @@ -48,7 +50,9 @@ class EditorDirDialog : public ConfirmationDialog { Tree *tree = nullptr; bool updating = false; + CheckBox *copy = nullptr; + void _copy_toggled(bool p_pressed); void _item_collapsed(Object *p_item); void _item_activated(); void _update_dir(TreeItem *p_item, EditorFileSystemDirectory *p_dir, const String &p_select_path = String()); @@ -66,6 +70,8 @@ class EditorDirDialog : public ConfirmationDialog { public: void reload(const String &p_path = ""); + bool is_copy_pressed() const; + EditorDirDialog(); }; diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index c8bb5a5fc3de..e96f902b6453 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -1454,6 +1454,29 @@ void FileSystemDock::_update_project_settings_after_move(const HashMapsave(); } +String FileSystemDock::_get_unique_name(const FileOrFolder &p_entry, const String &p_at_path) { + String new_path; + String new_path_base; + + if (p_entry.is_file) { + new_path = p_at_path.path_join(p_entry.path.get_file()); + new_path_base = new_path.get_basename() + " (%d)." + new_path.get_extension(); + } else { + PackedStringArray path_split = p_entry.path.split("/"); + new_path = p_at_path.path_join(path_split[path_split.size() - 2]); + new_path_base = new_path + " (%d)"; + } + + int exist_counter = 1; + Ref 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); + } + + return new_path; +} + void FileSystemDock::_update_favorites_list_after_move(const HashMap &p_files_renames, const HashMap &p_folders_renames) const { Vector favorites_list = EditorSettings::get_singleton()->get_favorites(); Vector new_favorites; @@ -1658,8 +1681,9 @@ void FileSystemDock::_duplicate_operation_confirm() { _rescan(); } -void FileSystemDock::_move_with_overwrite() { - _move_operation_confirm(to_move_path, true); +void FileSystemDock::_overwrite_dialog_action(bool p_overwrite) { + overwrite_dialog->hide(); + _move_operation_confirm(to_move_path, to_move_or_copy, p_overwrite ? OVERWRITE_REPLACE : OVERWRITE_RENAME); } Vector FileSystemDock::_check_existing() { @@ -1681,14 +1705,21 @@ Vector FileSystemDock::_check_existing() { return conflicting_items; } -void FileSystemDock::_move_operation_confirm(const String &p_to_path, bool p_overwrite) { - if (!p_overwrite) { +void FileSystemDock::_move_dialog_confirm(const String &p_path) { + _move_operation_confirm(p_path, move_dialog->is_copy_pressed()); +} + +void FileSystemDock::_move_operation_confirm(const String &p_to_path, bool p_copy, Overwrite p_overwrite) { + if (p_overwrite == OVERWRITE_UNDECIDED) { to_move_path = p_to_path; + to_move_or_copy = p_copy; + Vector 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?"), + p_copy ? TTR("The following files or folders conflict with items in the target location '%s':\n\n%s\n\nDo you wish to overwrite them or rename the copied files?") + : TTR("The following files or folders conflict with items in the target location '%s':\n\n%s\n\nDo you wish to overwrite them or rename the moved files?"), to_move_path, String("\n").join(conflicting_items))); overwrite_dialog->popup_centered(); @@ -1696,43 +1727,71 @@ void FileSystemDock::_move_operation_confirm(const String &p_to_path, bool p_ove } } - // Check groups. + Vector new_paths; + new_paths.resize(to_move.size()); for (int i = 0; i < to_move.size(); i++) { - if (to_move[i].is_file && EditorFileSystem::get_singleton()->is_group_file(to_move[i].path)) { - EditorFileSystem::get_singleton()->move_group_file(to_move[i].path, p_to_path.path_join(to_move[i].path.get_file())); + if (p_overwrite == OVERWRITE_RENAME) { + new_paths.write[i] = _get_unique_name(to_move[i], p_to_path); + } else { + new_paths.write[i] = p_to_path.path_join(to_move[i].path.get_file()); } } - HashMap file_renames; - HashMap folder_renames; - bool is_moved = false; - for (int i = 0; i < to_move.size(); i++) { - String old_path = to_move[i].path.ends_with("/") ? to_move[i].path.substr(0, to_move[i].path.length() - 1) : to_move[i].path; - String new_path = p_to_path.path_join(old_path.get_file()); - if (old_path != new_path) { - _try_move_item(to_move[i], new_path, file_renames, folder_renames); - is_moved = true; + if (p_copy) { + bool is_copied = false; + for (int i = 0; i < to_move.size(); i++) { + String old_path = to_move[i].path.ends_with("/") ? to_move[i].path.substr(0, to_move[i].path.length() - 1) : to_move[i].path; + const String &new_path = new_paths[i]; + + if (old_path != new_path) { + _try_duplicate_item(to_move[i], new_paths[i]); + is_copied = true; + } } - } - if (is_moved) { - int current_tab = EditorNode::get_singleton()->get_current_tab(); - _save_scenes_after_move(file_renames); // Save scenes before updating. - _update_dependencies_after_move(file_renames); - _update_resource_paths_after_move(file_renames); - _update_project_settings_after_move(file_renames); - _update_favorites_list_after_move(file_renames, folder_renames); + if (is_copied) { + _rescan(); + } + } else { + // Check groups. + for (int i = 0; i < to_move.size(); i++) { + if (to_move[i].is_file && EditorFileSystem::get_singleton()->is_group_file(to_move[i].path)) { + EditorFileSystem::get_singleton()->move_group_file(to_move[i].path, new_paths[i]); + } + } - EditorNode::get_singleton()->set_current_tab(current_tab); + bool is_moved = false; + HashMap file_renames; + HashMap folder_renames; - print_verbose("FileSystem: calling rescan."); - _rescan(); + for (int i = 0; i < to_move.size(); i++) { + String old_path = to_move[i].path.ends_with("/") ? to_move[i].path.substr(0, to_move[i].path.length() - 1) : to_move[i].path; + const String &new_path = new_paths[i]; + if (old_path != new_path) { + _try_move_item(to_move[i], new_path, file_renames, folder_renames); + is_moved = true; + } + } - print_verbose("FileSystem: saving moved scenes."); - _save_scenes_after_move(file_renames); + if (is_moved) { + int current_tab = EditorNode::get_singleton()->get_current_tab(); + _save_scenes_after_move(file_renames); // Save scenes before updating. + _update_dependencies_after_move(file_renames); + _update_resource_paths_after_move(file_renames); + _update_project_settings_after_move(file_renames); + _update_favorites_list_after_move(file_renames, folder_renames); - path = p_to_path; - current_path->set_text(path); + EditorNode::get_singleton()->set_current_tab(current_tab); + + print_verbose("FileSystem: calling rescan."); + _rescan(); + + print_verbose("FileSystem: saving moved scenes."); + _save_scenes_after_move(file_renames); + + path = p_to_path; + current_path->set_text(path); + } } } @@ -1950,7 +2009,7 @@ void FileSystemDock::_file_option(int p_option, const Vector &p_selected } break; case FILE_MOVE: { - // Move the files to a given location. + // Move or copy the files to a given location. to_move.clear(); Vector collapsed_paths = _remove_self_included_paths(p_selected); for (int i = collapsed_paths.size() - 1; i >= 0; i--) { @@ -1960,7 +2019,7 @@ void FileSystemDock::_file_option(int p_option, const Vector &p_selected } } if (to_move.size() > 0) { - move_dialog->popup_centered_ratio(); + move_dialog->popup_centered_ratio(0.4); } } break; @@ -2425,28 +2484,7 @@ void FileSystemDock::drop_data_fw(const Point2 &p_point, const Variant &p_data, } if (!to_move.is_empty()) { if (Input::get_singleton()->is_key_pressed(Key::CTRL)) { - 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.path_join(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.path_join(path_split[path_split.size() - 2]); - new_path_base = new_path + " (%d)"; - } - - int exist_counter = 1; - Ref 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(); + _move_operation_confirm(to_dir, true); } else { _move_operation_confirm(to_dir); } @@ -2637,7 +2675,7 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, Vector 1 || p_paths[0] != "res://") { - p_popup->add_icon_item(get_theme_icon(SNAME("MoveUp"), SNAME("EditorIcons")), TTR("Move To..."), FILE_MOVE); + p_popup->add_icon_item(get_theme_icon(SNAME("MoveUp"), SNAME("EditorIcons")), TTR("Move/Duplicate To..."), FILE_MOVE); p_popup->add_icon_shortcut(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), ED_GET_SHORTCUT("filesystem_dock/delete"), FILE_REMOVE); } @@ -3278,9 +3316,8 @@ FileSystemDock::FileSystemDock() { add_child(remove_dialog); move_dialog = memnew(EditorDirDialog); - move_dialog->set_ok_button_text(TTR("Move")); add_child(move_dialog); - move_dialog->connect("dir_selected", callable_mp(this, &FileSystemDock::_move_operation_confirm).bind(false)); + move_dialog->connect("dir_selected", callable_mp(this, &FileSystemDock::_move_dialog_confirm)); rename_dialog = memnew(ConfirmationDialog); VBoxContainer *rename_dialog_vb = memnew(VBoxContainer); @@ -3294,9 +3331,10 @@ FileSystemDock::FileSystemDock() { rename_dialog->connect("confirmed", callable_mp(this, &FileSystemDock::_rename_operation_confirm)); overwrite_dialog = memnew(ConfirmationDialog); - overwrite_dialog->set_ok_button_text(TTR("Overwrite")); add_child(overwrite_dialog); - overwrite_dialog->connect("confirmed", callable_mp(this, &FileSystemDock::_move_with_overwrite)); + overwrite_dialog->set_ok_button_text(TTR("Overwrite")); + overwrite_dialog->add_button(TTR("Keep Both"), true)->connect("pressed", callable_mp(this, &FileSystemDock::_overwrite_dialog_action).bind(false)); + overwrite_dialog->connect("confirmed", callable_mp(this, &FileSystemDock::_overwrite_dialog_action).bind(true)); duplicate_dialog = memnew(ConfirmationDialog); VBoxContainer *duplicate_dialog_vb = memnew(VBoxContainer); diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index 9060f5c0a4d9..35ca3eaccb02 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -104,6 +104,12 @@ class FileSystemDock : public VBoxContainer { FILE_NEW_SCENE, }; + enum Overwrite { + OVERWRITE_UNDECIDED, + OVERWRITE_REPLACE, + OVERWRITE_RENAME, + }; + FileSortOption file_sort = FILE_SORT_NAME; VBoxContainer *scanning_vb = nullptr; @@ -173,6 +179,7 @@ class FileSystemDock : public VBoxContainer { FileOrFolder to_duplicate; Vector to_move; String to_move_path; + bool to_move_or_copy = false; Vector history; int history_pos; @@ -227,6 +234,7 @@ class FileSystemDock : public VBoxContainer { void _save_scenes_after_move(const HashMap &p_renames) const; void _update_favorites_list_after_move(const HashMap &p_files_renames, const HashMap &p_folders_renames) const; void _update_project_settings_after_move(const HashMap &p_renames) const; + String _get_unique_name(const FileOrFolder &p_entry, const String &p_at_path); void _resource_removed(const Ref &p_resource); void _file_removed(String p_file); @@ -237,9 +245,10 @@ class FileSystemDock : public VBoxContainer { void _make_scene_confirm(); void _rename_operation_confirm(); void _duplicate_operation_confirm(); - void _move_with_overwrite(); + void _overwrite_dialog_action(bool p_overwrite); Vector _check_existing(); - void _move_operation_confirm(const String &p_to_path, bool p_overwrite = false); + void _move_dialog_confirm(const String &p_path); + void _move_operation_confirm(const String &p_to_path, bool p_copy = false, Overwrite p_overwrite = OVERWRITE_UNDECIDED); void _tree_rmb_option(int p_option); void _file_list_rmb_option(int p_option);