Skip to content

Commit

Permalink
Unify make dir and duplicate dialogs
Browse files Browse the repository at this point in the history
  • Loading branch information
KoBeWi authored and jss2a98aj committed Dec 10, 2024
1 parent cad4324 commit a7e0615
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 100 deletions.
94 changes: 61 additions & 33 deletions editor/directory_create_dialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 (p_path.contains("\\") || p_path.contains(":") || p_path.contains("*") ||
p_path.contains("|") || p_path.contains(">")) {
return TTR("Folder name contains invalid characters.");
if (mode == MODE_FILE && p_path.ends_with("/")) {
return TTR("File name can't end with /.");
}

for (const String &part : p_path.split("/")) {
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;

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.");
}
}
}

Expand All @@ -82,31 +97,32 @@ 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);
}
}

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();
}

Expand All @@ -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();
base_path_label->set_text(vformat(TTR("Base path: %s"), base_dir));
accept_callback = p_accept_callback;
mode = p_mode;

dir_path->set_text(p_default_name);
validation_panel->update();
}

void DirectoryCreateDialog::_bind_methods() {
ADD_SIGNAL(MethodInfo("dir_created", PropertyInfo(Variant::STRING, "path")));
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);
Expand Down
16 changes: 12 additions & 4 deletions editor/directory_create_dialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,23 +40,31 @@ class LineEdit;
class DirectoryCreateDialog : public ConfirmationDialog {
GDCLASS(DirectoryCreateDialog, ConfirmationDialog);

public:
enum Mode {
MODE_FILE,
MODE_DIRECTORY,
};

private:
String base_dir;
Callable accept_callback;
int mode = MODE_FILE;

Label *label = nullptr;
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();
};
Expand Down
90 changes: 34 additions & 56 deletions editor/filesystem_dock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1863,35 +1863,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<DirAccess> 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.");
Expand Down Expand Up @@ -2471,24 +2454,22 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &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: {
Expand All @@ -2503,7 +2484,8 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &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;

Expand Down Expand Up @@ -2706,6 +2688,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;
}
Expand Down Expand Up @@ -4139,17 +4128,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);

Expand Down
5 changes: 2 additions & 3 deletions editor/filesystem_dock.h
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,6 @@ class FileSystemDock : public VBoxContainer {
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;
Expand Down Expand Up @@ -293,7 +291,7 @@ class FileSystemDock : public VBoxContainer {
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);
Vector<String> _check_existing();
void _move_operation_confirm(const String &p_to_path, bool p_copy = false, Overwrite p_overwrite = OVERWRITE_UNDECIDED);
Expand Down Expand Up @@ -403,6 +401,7 @@ class FileSystemDock : public VBoxContainer {
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;

Expand Down
8 changes: 5 additions & 3 deletions editor/gui/editor_dir_dialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,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) {
Expand Down Expand Up @@ -230,5 +233,4 @@ EditorDirDialog::EditorDirDialog() {

makedialog = memnew(DirectoryCreateDialog);
add_child(makedialog);
makedialog->connect("dir_created", callable_mp(this, &EditorDirDialog::_make_dir_confirm));
}
2 changes: 1 addition & 1 deletion editor/gui/editor_dir_dialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Color> &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;
Expand Down

0 comments on commit a7e0615

Please sign in to comment.