1/**************************************************************************/
2/* directory_create_dialog.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "directory_create_dialog.h"
32
33#include "core/io/dir_access.h"
34#include "editor/editor_node.h"
35#include "editor/editor_scale.h"
36#include "editor/gui/editor_validation_panel.h"
37#include "scene/gui/box_container.h"
38#include "scene/gui/label.h"
39#include "scene/gui/line_edit.h"
40
41static String sanitize_input(const String &p_path) {
42 String path = p_path.strip_edges();
43 if (path.ends_with("/")) {
44 path = path.left(path.length() - 1);
45 }
46 return path;
47}
48
49String DirectoryCreateDialog::_validate_path(const String &p_path) const {
50 if (p_path.is_empty()) {
51 return TTR("Folder name cannot be empty.");
52 }
53
54 if (p_path.contains("\\") || p_path.contains(":") || p_path.contains("*") ||
55 p_path.contains("|") || p_path.contains(">")) {
56 return TTR("Folder name contains invalid characters.");
57 }
58
59 for (const String &part : p_path.split("/")) {
60 if (part.is_empty()) {
61 return TTR("Folder name cannot be empty.");
62 }
63 if (part.ends_with(" ") || part[0] == ' ') {
64 return TTR("Folder name cannot begin or end with a space.");
65 }
66 if (part[0] == '.') {
67 return TTR("Folder name cannot begin with a dot.");
68 }
69 }
70
71 Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
72 da->change_dir(base_dir);
73 if (da->file_exists(p_path)) {
74 return TTR("File with that name already exists.");
75 }
76 if (da->dir_exists(p_path)) {
77 return TTR("Folder with that name already exists.");
78 }
79
80 return String();
81}
82
83void DirectoryCreateDialog::_on_dir_path_changed() {
84 const String path = sanitize_input(dir_path->get_text());
85 const String error = _validate_path(path);
86
87 if (error.is_empty()) {
88 if (path.contains("/")) {
89 validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Using slashes in folder names will create subfolders recursively."), EditorValidationPanel::MSG_OK);
90 }
91 } else {
92 validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, error, EditorValidationPanel::MSG_ERROR);
93 }
94}
95
96void DirectoryCreateDialog::ok_pressed() {
97 const String path = sanitize_input(dir_path->get_text());
98
99 // The OK button should be disabled if the path is invalid, but just in case.
100 const String error = _validate_path(path);
101 ERR_FAIL_COND_MSG(!error.is_empty(), error);
102
103 Error err;
104 Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
105
106 err = da->change_dir(base_dir);
107 ERR_FAIL_COND_MSG(err != OK, "Cannot open directory '" + base_dir + "'.");
108
109 print_verbose("Making folder " + path + " in " + base_dir);
110 err = da->make_dir_recursive(path);
111
112 if (err == OK) {
113 emit_signal(SNAME("dir_created"));
114 } else {
115 EditorNode::get_singleton()->show_warning(TTR("Could not create folder."));
116 }
117 hide();
118}
119
120void DirectoryCreateDialog::_post_popup() {
121 ConfirmationDialog::_post_popup();
122 dir_path->grab_focus();
123}
124
125void DirectoryCreateDialog::config(const String &p_base_dir) {
126 base_dir = p_base_dir;
127 label->set_text(vformat(TTR("Create new folder in %s:"), base_dir));
128 dir_path->set_text("new folder");
129 dir_path->select_all();
130 validation_panel->update();
131}
132
133void DirectoryCreateDialog::_bind_methods() {
134 ADD_SIGNAL(MethodInfo("dir_created"));
135}
136
137DirectoryCreateDialog::DirectoryCreateDialog() {
138 set_title(TTR("Create Folder"));
139 set_min_size(Size2i(480, 0) * EDSCALE);
140
141 VBoxContainer *vb = memnew(VBoxContainer);
142 add_child(vb);
143
144 label = memnew(Label);
145 label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_WORD_ELLIPSIS);
146 vb->add_child(label);
147
148 dir_path = memnew(LineEdit);
149 vb->add_child(dir_path);
150 register_text_enter(dir_path);
151
152 Control *spacing = memnew(Control);
153 spacing->set_custom_minimum_size(Size2(0, 10 * EDSCALE));
154 vb->add_child(spacing);
155
156 validation_panel = memnew(EditorValidationPanel);
157 vb->add_child(validation_panel);
158 validation_panel->add_line(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Folder name is valid."));
159 validation_panel->set_update_callback(callable_mp(this, &DirectoryCreateDialog::_on_dir_path_changed));
160 validation_panel->set_accept_button(get_ok_button());
161
162 dir_path->connect("text_changed", callable_mp(validation_panel, &EditorValidationPanel::update).unbind(1));
163}
164