From e2a96dde4850abfe8d7c1ee32bcf32f0191586ea Mon Sep 17 00:00:00 2001 From: kobewi Date: Thu, 10 Aug 2023 08:12:10 +0200 Subject: [PATCH] Unify make dir and duplicate dialogs --- editor/directory_create_dialog.cpp | 96 +++++++++++++++++++----------- editor/directory_create_dialog.h | 18 ++++-- editor/filesystem_dock.cpp | 90 +++++++++++----------------- editor/filesystem_dock.h | 5 +- editor/gui/editor_dir_dialog.cpp | 8 ++- editor/gui/editor_dir_dialog.h | 2 +- 6 files changed, 117 insertions(+), 102 deletions(-) diff --git a/editor/directory_create_dialog.cpp b/editor/directory_create_dialog.cpp index 46baa2c6e1f..ee03d5a7f69 100644 --- a/editor/directory_create_dialog.cpp +++ b/editor/directory_create_dialog.cpp @@ -39,33 +39,48 @@ #include "scene/gui/label.h" #include "scene/gui/line_edit.h" -static String sanitize_input(const String &p_path) { +String DirectoryCreateDialog::_sanitize_input(const String &p_path) const { String path = p_path.strip_edges(); - if (path.ends_with("/")) { - path = path.left(path.length() - 1); + if (mode == MODE_DIRECTORY) { + path = path.trim_suffix("/"); } return path; } String DirectoryCreateDialog::_validate_path(const String &p_path) const { if (p_path.is_empty()) { - return TTR("Folder name cannot be empty."); + return TTR("Name cannot be empty."); + } + if (mode == MODE_FILE && p_path.ends_with("/")) { + return TTR("File name can't end with /."); } - if (p_path.contains("\\") || p_path.contains(":") || p_path.contains("*") || - p_path.contains("|") || p_path.contains(">")) { - return TTR("Folder name contains invalid characters."); - } + const PackedStringArray splits = p_path.split("/"); + for (int i = 0; i < splits.size(); i++) { + const String &part = splits[i]; + bool is_file = mode == MODE_FILE && i == splits.size() - 1; - for (const String &part : p_path.split("/")) { if (part.is_empty()) { - return TTR("Folder name cannot be empty."); + if (is_file) { + return TTR("File name cannot be empty."); + } else { + return TTR("Folder name cannot be empty."); + } } - if (part.ends_with(" ") || part[0] == ' ') { - return TTR("Folder name cannot begin or end with a space."); + if (part.contains("\\") || part.contains(":") || part.contains("*") || + part.contains("|") || part.contains(">") || part.ends_with(".") || part.ends_with(" ")) { + if (is_file) { + return TTR("File name contains invalid characters."); + } else { + return TTR("Folder name contains invalid characters."); + } } if (part[0] == '.') { - return TTR("Folder name cannot begin with a dot."); + if (is_file) { + return TTR("File name begins with a dot."); + } else { + return TTR("Folder name begins with a dot."); + } } } @@ -82,12 +97,18 @@ String DirectoryCreateDialog::_validate_path(const String &p_path) const { } void DirectoryCreateDialog::_on_dir_path_changed() { - const String path = sanitize_input(dir_path->get_text()); + const String path = _sanitize_input(dir_path->get_text()); const String error = _validate_path(path); if (error.is_empty()) { if (path.contains("/")) { - validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Using slashes in folder names will create subfolders recursively."), EditorValidationPanel::MSG_OK); + if (mode == MODE_DIRECTORY) { + validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Using slashes in folder names will create subfolders recursively."), EditorValidationPanel::MSG_OK); + } else { + validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Using slashes in path will create the file in subfolder, creating new subfolders if necessary."), EditorValidationPanel::MSG_OK); + } + } else if (mode == MODE_FILE) { + validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("File name is valid."), EditorValidationPanel::MSG_OK); } } else { validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, error, EditorValidationPanel::MSG_ERROR); @@ -95,18 +116,13 @@ void DirectoryCreateDialog::_on_dir_path_changed() { } void DirectoryCreateDialog::ok_pressed() { - const String path = sanitize_input(dir_path->get_text()); + const String path = _sanitize_input(dir_path->get_text()); // The OK button should be disabled if the path is invalid, but just in case. const String error = _validate_path(path); ERR_FAIL_COND_MSG(!error.is_empty(), error); - Error err = EditorFileSystem::get_singleton()->make_dir_recursive(path, base_dir); - if (err == OK) { - emit_signal(SNAME("dir_created"), base_dir.path_join(path)); - } else { - EditorNode::get_singleton()->show_warning(TTR("Could not create folder.")); - } + accept_callback.call(base_dir.path_join(path)); hide(); } @@ -115,28 +131,40 @@ void DirectoryCreateDialog::_post_popup() { dir_path->grab_focus(); } -void DirectoryCreateDialog::config(const String &p_base_dir) { +void DirectoryCreateDialog::config(const String &p_base_dir, const Callable &p_accept_callback, int p_mode, const String &p_title, const String &p_default_name) { + set_title(p_title); base_dir = p_base_dir; - label->set_text(vformat(TTR("Create new folder in %s:"), base_dir)); - dir_path->set_text("new folder"); - dir_path->select_all(); - validation_panel->update(); -} + base_path_label->set_text(vformat(TTR("Base path: %s"), base_dir)); + accept_callback = p_accept_callback; + mode = p_mode; -void DirectoryCreateDialog::_bind_methods() { - ADD_SIGNAL(MethodInfo("dir_created", PropertyInfo(Variant::STRING, "path"))); + dir_path->set_text(p_default_name); + validation_panel->update(); + + if (p_mode == MODE_FILE) { + int extension_pos = p_default_name.rfind("."); + if (extension_pos > -1) { + dir_path->select(0, extension_pos); + return; + } + } + dir_path->select_all(); } DirectoryCreateDialog::DirectoryCreateDialog() { - set_title(TTR("Create Folder")); set_min_size(Size2i(480, 0) * EDSCALE); VBoxContainer *vb = memnew(VBoxContainer); add_child(vb); - label = memnew(Label); - label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_WORD_ELLIPSIS); - vb->add_child(label); + base_path_label = memnew(Label); + base_path_label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_WORD_ELLIPSIS); + vb->add_child(base_path_label); + + Label *name_label = memnew(Label); + name_label->set_text(TTR("Name:")); + name_label->set_theme_type_variation("HeaderSmall"); + vb->add_child(name_label); dir_path = memnew(LineEdit); vb->add_child(dir_path); diff --git a/editor/directory_create_dialog.h b/editor/directory_create_dialog.h index 82e2e98ae53..2292a6e5c9e 100644 --- a/editor/directory_create_dialog.h +++ b/editor/directory_create_dialog.h @@ -40,23 +40,31 @@ class LineEdit; class DirectoryCreateDialog : public ConfirmationDialog { GDCLASS(DirectoryCreateDialog, ConfirmationDialog); - String base_dir; +public: + enum Mode { + MODE_FILE, + MODE_DIRECTORY, + }; - Label *label = nullptr; +private: + String base_dir; + Callable accept_callback; + int mode = MODE_FILE; + + Label *base_path_label = nullptr; LineEdit *dir_path = nullptr; EditorValidationPanel *validation_panel = nullptr; + String _sanitize_input(const String &p_input) const; String _validate_path(const String &p_path) const; void _on_dir_path_changed(); protected: - static void _bind_methods(); - virtual void ok_pressed() override; virtual void _post_popup() override; public: - void config(const String &p_base_dir); + void config(const String &p_base_dir, const Callable &p_accept_callback, int p_mode, const String &p_title, const String &p_default_name = ""); DirectoryCreateDialog(); }; diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index c9cc42d68d4..53982b37b9f 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -1865,35 +1865,18 @@ void FileSystemDock::_rename_operation_confirm() { _rescan(); } -void FileSystemDock::_duplicate_operation_confirm() { - String new_name = duplicate_dialog_text->get_text().strip_edges(); - if (new_name.length() == 0) { - EditorNode::get_singleton()->show_warning(TTR("No name provided.")); - return; - } else if (new_name.contains("/") || new_name.contains("\\") || new_name.contains(":")) { - EditorNode::get_singleton()->show_warning(TTR("Name contains invalid characters.")); - return; - } else if (new_name[0] == '.') { - EditorNode::get_singleton()->show_warning(TTR("Name begins with a dot.")); - return; - } - - String base_dir = to_duplicate.path.get_base_dir(); - // get_base_dir() returns "some/path" if the original path was "some/path/", so work it around. - if (to_duplicate.path.ends_with("/")) { - base_dir = base_dir.get_base_dir(); - } - - String new_path = base_dir.path_join(new_name); - - // Present a more user friendly warning for name conflict +void FileSystemDock::_duplicate_operation_confirm(const String &p_path) { + String base_dir = p_path.trim_suffix("/").get_base_dir(); Ref da = DirAccess::create(DirAccess::ACCESS_RESOURCES); - if (da->file_exists(new_path) || da->dir_exists(new_path)) { - EditorNode::get_singleton()->show_warning(TTR("A file or folder with this name already exists.")); - return; - } + if (!da->dir_exists(base_dir)) { + Error err = da->make_dir_recursive(base_dir); - _try_duplicate_item(to_duplicate, new_path); + if (err != OK) { + EditorNode::get_singleton()->show_warning(vformat(TTR("Could not create base directory: %s"), error_names[err])); + return; + } + } + _try_duplicate_item(to_duplicate, p_path); // Rescan everything. print_verbose("FileSystem: calling rescan."); @@ -2531,24 +2514,22 @@ void FileSystemDock::_file_option(int p_option, const Vector &p_selected } break; case FILE_DUPLICATE: { - // Duplicate the selected files. - for (int i = 0; i < p_selected.size(); i++) { - to_duplicate.path = p_selected[i]; - to_duplicate.is_file = !to_duplicate.path.ends_with("/"); - if (to_duplicate.is_file) { - 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.rfind(".")); - } else { - String name = to_duplicate.path.substr(0, to_duplicate.path.length() - 1).get_file(); - duplicate_dialog->set_title(TTR("Duplicating folder:") + " " + name); - duplicate_dialog_text->set_text(name); - duplicate_dialog_text->select(0, name.length()); - } - duplicate_dialog->popup_centered(Size2(250, 80) * EDSCALE); - duplicate_dialog_text->grab_focus(); + if (p_selected.size() != 1) { + return; } + + to_duplicate.path = p_selected[0]; + to_duplicate.is_file = !to_duplicate.path.ends_with("/"); + if (to_duplicate.is_file) { + String name = to_duplicate.path.get_file(); + make_dir_dialog->config(to_duplicate.path.get_base_dir(), callable_mp(this, &FileSystemDock::_duplicate_operation_confirm), + DirectoryCreateDialog::MODE_FILE, TTR("Duplicating file:") + " " + name, name); + } else { + String name = to_duplicate.path.trim_suffix("/").get_file(); + make_dir_dialog->config(to_duplicate.path.trim_suffix("/").get_base_dir(), callable_mp(this, &FileSystemDock::_duplicate_operation_confirm), + DirectoryCreateDialog::MODE_DIRECTORY, TTR("Duplicating folder:") + " " + name, name); + } + make_dir_dialog->popup_centered(); } break; case FILE_INFO: { @@ -2563,7 +2544,8 @@ void FileSystemDock::_file_option(int p_option, const Vector &p_selected if (!directory.ends_with("/")) { directory = directory.get_base_dir(); } - make_dir_dialog->config(directory); + make_dir_dialog->config(directory, callable_mp(this, &FileSystemDock::create_directory).bind(directory), + DirectoryCreateDialog::MODE_DIRECTORY, TTR("Create Folder"), "new folder"); make_dir_dialog->popup_centered(); } break; @@ -2793,6 +2775,13 @@ void FileSystemDock::focus_on_filter() { } } +void FileSystemDock::create_directory(const String &p_path, const String &p_base_dir) { + Error err = EditorFileSystem::get_singleton()->make_dir_recursive(p_path, p_base_dir); + if (err != OK) { + EditorNode::get_singleton()->show_warning(vformat(TTR("Could not create folder: %s"), error_names[err])); + } +} + ScriptCreateDialog *FileSystemDock::get_script_create_dialog() const { return make_script_dialog; } @@ -4282,17 +4271,6 @@ FileSystemDock::FileSystemDock() { overwrite_dialog_footer = memnew(Label); overwrite_dialog_vb->add_child(overwrite_dialog_footer); - duplicate_dialog = memnew(ConfirmationDialog); - VBoxContainer *duplicate_dialog_vb = memnew(VBoxContainer); - duplicate_dialog->add_child(duplicate_dialog_vb); - - duplicate_dialog_text = memnew(LineEdit); - duplicate_dialog_vb->add_margin_child(TTR("Name:"), duplicate_dialog_text); - duplicate_dialog->set_ok_button_text(TTR("Duplicate")); - add_child(duplicate_dialog); - duplicate_dialog->register_text_enter(duplicate_dialog_text); - duplicate_dialog->connect(SceneStringName(confirmed), callable_mp(this, &FileSystemDock::_duplicate_operation_confirm)); - make_dir_dialog = memnew(DirectoryCreateDialog); add_child(make_dir_dialog); diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index ee68f44d4cd..819abbd3894 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -183,8 +183,6 @@ private: DependencyRemoveDialog *remove_dialog = nullptr; EditorDirDialog *move_dialog = nullptr; - ConfirmationDialog *duplicate_dialog = nullptr; - LineEdit *duplicate_dialog_text = nullptr; DirectoryCreateDialog *make_dir_dialog = nullptr; ConfirmationDialog *overwrite_dialog = nullptr; @@ -291,7 +289,7 @@ private: void _resource_created(); void _make_scene_confirm(); void _rename_operation_confirm(); - void _duplicate_operation_confirm(); + void _duplicate_operation_confirm(const String &p_path); void _overwrite_dialog_action(bool p_overwrite); void _convert_dialog_action(); Vector _check_existing(); @@ -384,6 +382,7 @@ public: void navigate_to_path(const String &p_path); void focus_on_path(); void focus_on_filter(); + void create_directory(const String &p_path, const String &p_base_dir); ScriptCreateDialog *get_script_create_dialog() const; diff --git a/editor/gui/editor_dir_dialog.cpp b/editor/gui/editor_dir_dialog.cpp index 23568a3c2a3..481c8ed016b 100644 --- a/editor/gui/editor_dir_dialog.cpp +++ b/editor/gui/editor_dir_dialog.cpp @@ -175,11 +175,14 @@ void EditorDirDialog::ok_pressed() { void EditorDirDialog::_make_dir() { TreeItem *ti = tree->get_selected(); ERR_FAIL_NULL(ti); - makedialog->config(ti->get_metadata(0)); + const String &directory = ti->get_metadata(0); + makedialog->config(directory, callable_mp(this, &EditorDirDialog::_make_dir_confirm).bind(directory), DirectoryCreateDialog::MODE_DIRECTORY, "new folder"); makedialog->popup_centered(); } -void EditorDirDialog::_make_dir_confirm(const String &p_path) { +void EditorDirDialog::_make_dir_confirm(const String &p_path, const String &p_base_dir) { + FileSystemDock::get_singleton()->create_directory(p_path, p_base_dir); + // Multiple level of directories can be created at once. String base_dir = p_path.get_base_dir(); while (true) { @@ -228,5 +231,4 @@ EditorDirDialog::EditorDirDialog() { makedialog = memnew(DirectoryCreateDialog); add_child(makedialog); - makedialog->connect("dir_created", callable_mp(this, &EditorDirDialog::_make_dir_confirm)); } diff --git a/editor/gui/editor_dir_dialog.h b/editor/gui/editor_dir_dialog.h index b10cc8dd9f5..491eb617493 100644 --- a/editor/gui/editor_dir_dialog.h +++ b/editor/gui/editor_dir_dialog.h @@ -56,7 +56,7 @@ class EditorDirDialog : public ConfirmationDialog { void _update_dir(const Color &p_default_folder_color, const Dictionary &p_assigned_folder_colors, const HashMap &p_folder_colors, bool p_is_dark_theme, TreeItem *p_item, EditorFileSystemDirectory *p_dir, const String &p_select_path = String()); void _make_dir(); - void _make_dir_confirm(const String &p_path); + void _make_dir_confirm(const String &p_path, const String &p_base_dir); void _copy_pressed(); void ok_pressed() override;