1 /*************************************************************************/
2 /*  script_create_dialog.cpp                                             */
3 /*************************************************************************/
4 /*                       This file is part of:                           */
5 /*                           GODOT ENGINE                                */
6 /*                      https://godotengine.org                          */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur.                 */
9 /* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md)    */
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 #include "script_create_dialog.h"
31 #include "editor_file_system.h"
32 #include "globals.h"
33 #include "io/resource_saver.h"
34 #include "os/file_access.h"
35 #include "script_language.h"
36 
config(const String & p_base_name,const String & p_base_path)37 void ScriptCreateDialog::config(const String &p_base_name, const String &p_base_path) {
38 
39 	class_name->set_text("");
40 	parent_name->set_text(p_base_name);
41 	if (p_base_path != "") {
42 		initial_bp = p_base_path.basename();
43 		file_path->set_text(initial_bp + "." + ScriptServer::get_language(language_menu->get_selected())->get_extension());
44 	} else {
45 		initial_bp = "";
46 		file_path->set_text("");
47 	}
48 	_class_name_changed("");
49 	_path_changed(file_path->get_text());
50 }
51 
_validate(const String & p_string)52 bool ScriptCreateDialog::_validate(const String &p_string) {
53 
54 	if (p_string.length() == 0)
55 		return false;
56 
57 	for (int i = 0; i < p_string.length(); i++) {
58 
59 		if (i == 0) {
60 			if (p_string[0] >= '0' && p_string[0] <= '9')
61 				return false; // no start with number plz
62 		}
63 
64 		bool valid_char = (p_string[i] >= '0' && p_string[i] <= '9') || (p_string[i] >= 'a' && p_string[i] <= 'z') || (p_string[i] >= 'A' && p_string[i] <= 'Z') || p_string[i] == '_';
65 
66 		if (!valid_char)
67 			return false;
68 	}
69 
70 	return true;
71 }
72 
_class_name_changed(const String & p_name)73 void ScriptCreateDialog::_class_name_changed(const String &p_name) {
74 
75 	if (!_validate(parent_name->get_text())) {
76 		error_label->set_text(TTR("Invalid parent class name"));
77 		error_label->add_color_override("font_color", Color(1, 0.4, 0.0, 0.8));
78 	} else if (class_name->is_editable()) {
79 		if (class_name->get_text() == "") {
80 			error_label->set_text(TTR("Valid chars:") + " a-z A-Z 0-9 _");
81 			error_label->add_color_override("font_color", Color(1, 1, 1, 0.6));
82 		} else if (!_validate(class_name->get_text())) {
83 			error_label->set_text(TTR("Invalid class name"));
84 			error_label->add_color_override("font_color", Color(1, 0.2, 0.2, 0.8));
85 		} else {
86 			error_label->set_text(TTR("Valid name"));
87 			error_label->add_color_override("font_color", Color(0, 1.0, 0.8, 0.8));
88 		}
89 	} else {
90 
91 		error_label->set_text(TTR("N/A"));
92 		error_label->add_color_override("font_color", Color(0, 1.0, 0.8, 0.8));
93 	}
94 }
95 
ok_pressed()96 void ScriptCreateDialog::ok_pressed() {
97 
98 	if (create_new) {
99 		_create_new();
100 	} else {
101 		_load_exist();
102 	}
103 
104 	create_new = true;
105 	_update_controls();
106 }
107 
_create_new()108 void ScriptCreateDialog::_create_new() {
109 
110 	if (class_name->is_editable() && !_validate(class_name->get_text())) {
111 
112 		alert->set_text(TTR("Class name is invalid!"));
113 		alert->popup_centered_minsize();
114 		return;
115 	}
116 	if (!_validate(parent_name->get_text())) {
117 		alert->set_text(TTR("Parent class name is invalid!"));
118 		alert->popup_centered_minsize();
119 		return;
120 	}
121 
122 	String cname;
123 	if (class_name->is_editable())
124 		cname = class_name->get_text();
125 
126 	String text;
127 	if (template_select == 0) {
128 		text = ScriptServer::get_language(language_menu->get_selected())->get_template(cname, parent_name->get_text());
129 	} else if (template_select == 1) {
130 		text = ScriptServer::get_language(language_menu->get_selected())->get_empty_template(cname, parent_name->get_text());
131 	} else if (template_select == 2) {
132 		text = ScriptServer::get_language(language_menu->get_selected())->get_nocomment_template(cname, parent_name->get_text());
133 	} else {
134 		text = ScriptServer::get_language(language_menu->get_selected())->get_template(cname, parent_name->get_text());
135 	}
136 
137 	Script *script = ScriptServer::get_language(language_menu->get_selected())->create_script();
138 	script->set_source_code(text);
139 	if (cname != "")
140 		script->set_name(cname);
141 
142 	Ref<Script> scr(script);
143 
144 	if (!internal->is_pressed()) {
145 
146 		String lpath = Globals::get_singleton()->localize_path(file_path->get_text());
147 		script->set_path(lpath);
148 		if (!path_valid) {
149 
150 			alert->set_text(TTR("Invalid path!"));
151 			alert->popup_centered_minsize();
152 			return;
153 		}
154 		Error err = ResourceSaver::save(lpath, scr, ResourceSaver::FLAG_CHANGE_PATH);
155 		if (err != OK) {
156 
157 			alert->set_text(TTR("Could not create script in filesystem."));
158 			alert->popup_centered_minsize();
159 			return;
160 		}
161 	}
162 
163 	hide();
164 	emit_signal("script_created", scr);
165 }
166 
_load_exist()167 void ScriptCreateDialog::_load_exist() {
168 
169 	String path = file_path->get_text();
170 	RES p_script = ResourceLoader::load(path, "Script");
171 	if (p_script.is_null()) {
172 		alert->get_ok()->set_text(TTR("Ugh"));
173 		alert->set_text(vformat(TTR("Error loading script from %s"), path));
174 		alert->popup_centered_minsize();
175 		return;
176 	}
177 
178 	hide();
179 	emit_signal("script_created", p_script.get_ref_ptr());
180 }
181 
_lang_changed(int l)182 void ScriptCreateDialog::_lang_changed(int l) {
183 
184 	l = language_menu->get_selected();
185 	if (ScriptServer::get_language(l)->has_named_classes()) {
186 		class_name->set_editable(true);
187 	} else {
188 		class_name->set_editable(false);
189 	}
190 
191 	String selected_ext = "." + ScriptServer::get_language(l)->get_extension();
192 	String path = file_path->get_text();
193 	String extension = "";
194 	if (path.find(".") >= 0) {
195 		extension = path.extension();
196 	}
197 
198 	if (extension.length() == 0) {
199 		// add extension if none
200 		path += selected_ext;
201 		_path_changed(path);
202 	} else {
203 		// change extension by selected language
204 		List<String> extensions;
205 		// get all possible extensions for script
206 		for (int l = 0; l < language_menu->get_item_count(); l++) {
207 			ScriptServer::get_language(l)->get_recognized_extensions(&extensions);
208 		}
209 
210 		for (List<String>::Element *E = extensions.front(); E; E = E->next()) {
211 			if (E->get().nocasecmp_to(extension) == 0) {
212 				path = path.basename() + selected_ext;
213 				_path_changed(path);
214 				break;
215 			}
216 		}
217 	}
218 	file_path->set_text(path);
219 	_class_name_changed(class_name->get_text());
220 }
221 
_template_changed(int p_template)222 void ScriptCreateDialog::_template_changed(int p_template) {
223 
224 	template_select = p_template;
225 }
226 
_built_in_pressed()227 void ScriptCreateDialog::_built_in_pressed() {
228 
229 	if (internal->is_pressed()) {
230 		path_vb->hide();
231 	} else {
232 		path_vb->show();
233 	}
234 }
235 
_browse_path()236 void ScriptCreateDialog::_browse_path() {
237 
238 	file_browse->set_mode(EditorFileDialog::MODE_SAVE_FILE);
239 	file_browse->set_disable_overwrite_warning(true);
240 	file_browse->clear_filters();
241 	List<String> extensions;
242 
243 	int lang = language_menu->get_selected();
244 	ScriptServer::get_language(lang)->get_recognized_extensions(&extensions);
245 
246 	for (List<String>::Element *E = extensions.front(); E; E = E->next()) {
247 		file_browse->add_filter("*." + E->get());
248 	}
249 
250 	file_browse->set_current_path(file_path->get_text());
251 	file_browse->popup_centered_ratio();
252 }
253 
_file_selected(const String & p_file)254 void ScriptCreateDialog::_file_selected(const String &p_file) {
255 
256 	String p = Globals::get_singleton()->localize_path(p_file);
257 	file_path->set_text(p);
258 	_path_changed(p);
259 }
260 
_path_changed(const String & p_path)261 void ScriptCreateDialog::_path_changed(const String &p_path) {
262 
263 	path_valid = false;
264 	String p = p_path;
265 
266 	if (p == "") {
267 
268 		path_error_label->set_text(TTR("Path is empty"));
269 		path_error_label->add_color_override("font_color", Color(1, 0.4, 0.0, 0.8));
270 		return;
271 	}
272 
273 	p = Globals::get_singleton()->localize_path(p);
274 	if (!p.begins_with("res://")) {
275 
276 		path_error_label->set_text(TTR("Path is not local"));
277 		path_error_label->add_color_override("font_color", Color(1, 0.4, 0.0, 0.8));
278 		return;
279 	}
280 
281 	if (p.find("/") || p.find("\\")) {
282 		DirAccess *d = DirAccess::create(DirAccess::ACCESS_RESOURCES);
283 
284 		if (d->change_dir(p.get_base_dir()) != OK) {
285 
286 			path_error_label->set_text(TTR("Invalid base path"));
287 			path_error_label->add_color_override("font_color", Color(1, 0.4, 0.0, 0.8));
288 			memdelete(d);
289 			return;
290 		}
291 		memdelete(d);
292 	}
293 
294 	FileAccess *f = FileAccess::create(FileAccess::ACCESS_RESOURCES);
295 
296 	create_new = !f->file_exists(p);
297 	memdelete(f);
298 
299 	String extension = p.extension();
300 	List<String> extensions;
301 
302 	// get all possible extensions for script
303 	for (int l = 0; l < language_menu->get_item_count(); l++) {
304 		ScriptServer::get_language(l)->get_recognized_extensions(&extensions);
305 	}
306 
307 	bool found = false;
308 	int index = 0;
309 	for (List<String>::Element *E = extensions.front(); E; E = E->next()) {
310 		if (E->get().nocasecmp_to(extension) == 0) {
311 			language_menu->select(index); // change Language option by extension
312 			found = true;
313 			break;
314 		}
315 		index++;
316 	}
317 
318 	if (!found) {
319 		path_error_label->set_text(TTR("Invalid extension"));
320 		path_error_label->add_color_override("font_color", Color(1, 0.4, 0.0, 0.8));
321 		return;
322 	}
323 
324 	_update_controls();
325 
326 	path_error_label->add_color_override("font_color", Color(0, 1.0, 0.8, 0.8));
327 
328 	path_valid = true;
329 }
330 
_update_controls()331 void ScriptCreateDialog::_update_controls() {
332 
333 	if (create_new) {
334 		path_error_label->set_text(TTR("Create new script"));
335 		get_ok()->set_text(TTR("Create"));
336 	} else {
337 		path_error_label->set_text(TTR("Load existing script"));
338 		get_ok()->set_text(TTR("Load"));
339 	}
340 	parent_name->set_editable(create_new);
341 	internal->set_disabled(!create_new);
342 }
343 
_bind_methods()344 void ScriptCreateDialog::_bind_methods() {
345 
346 	ObjectTypeDB::bind_method("_class_name_changed", &ScriptCreateDialog::_class_name_changed);
347 	ObjectTypeDB::bind_method("_lang_changed", &ScriptCreateDialog::_lang_changed);
348 	ObjectTypeDB::bind_method("_template_changed", &ScriptCreateDialog::_template_changed);
349 	ObjectTypeDB::bind_method("_built_in_pressed", &ScriptCreateDialog::_built_in_pressed);
350 	ObjectTypeDB::bind_method("_browse_path", &ScriptCreateDialog::_browse_path);
351 	ObjectTypeDB::bind_method("_file_selected", &ScriptCreateDialog::_file_selected);
352 	ObjectTypeDB::bind_method("_path_changed", &ScriptCreateDialog::_path_changed);
353 	ADD_SIGNAL(MethodInfo("script_created", PropertyInfo(Variant::OBJECT, "script", PROPERTY_HINT_RESOURCE_TYPE, "Script")));
354 }
355 
ScriptCreateDialog()356 ScriptCreateDialog::ScriptCreateDialog() {
357 
358 	/* SNAP DIALOG */
359 
360 	VBoxContainer *vb = memnew(VBoxContainer);
361 	add_child(vb);
362 	set_child_rect(vb);
363 
364 	class_name = memnew(LineEdit);
365 	VBoxContainer *vb2 = memnew(VBoxContainer);
366 	vb2->add_child(class_name);
367 	class_name->connect("text_changed", this, "_class_name_changed");
368 	error_label = memnew(Label);
369 	error_label->set_text("valid chars: a-z A-Z 0-9 _");
370 	error_label->set_align(Label::ALIGN_CENTER);
371 	vb2->add_child(error_label);
372 	vb->add_margin_child(TTR("Class Name:"), vb2);
373 
374 	parent_name = memnew(LineEdit);
375 	vb->add_margin_child(TTR("Inherits:"), parent_name);
376 	parent_name->connect("text_changed", this, "_class_name_changed");
377 
378 	language_menu = memnew(OptionButton);
379 	vb->add_margin_child(TTR("Language"), language_menu);
380 
381 	for (int i = 0; i < ScriptServer::get_language_count(); i++) {
382 
383 		language_menu->add_item(ScriptServer::get_language(i)->get_name());
384 	}
385 
386 	language_menu->select(0);
387 	language_menu->connect("item_selected", this, "_lang_changed");
388 
389 	template_menu = memnew(OptionButton);
390 	vb->add_margin_child(TTR("Template"), template_menu);
391 
392 	template_menu->add_item(TTR("Default"));
393 	template_menu->add_item(TTR("Empty GD File"));
394 	template_menu->add_item(TTR("No Comment GD File"));
395 
396 	template_menu->select(0);
397 	template_menu->connect("item_selected", this, "_template_changed");
398 	//parent_name->set_text();
399 
400 	vb2 = memnew(VBoxContainer);
401 	path_vb = memnew(VBoxContainer);
402 	vb2->add_child(path_vb);
403 
404 	HBoxContainer *hbc = memnew(HBoxContainer);
405 	file_path = memnew(LineEdit);
406 	file_path->connect("text_changed", this, "_path_changed");
407 	hbc->add_child(file_path);
408 	file_path->set_h_size_flags(SIZE_EXPAND_FILL);
409 	Button *b = memnew(Button);
410 	b->set_text(" .. ");
411 	b->connect("pressed", this, "_browse_path");
412 	hbc->add_child(b);
413 	path_vb->add_child(hbc);
414 	path_error_label = memnew(Label);
415 	path_vb->add_child(path_error_label);
416 	path_error_label->set_text(TTR("Error!"));
417 	path_error_label->set_align(Label::ALIGN_CENTER);
418 
419 	internal = memnew(CheckButton);
420 	internal->set_text(TTR("Built-In Script"));
421 	vb2->add_child(internal);
422 	internal->connect("pressed", this, "_built_in_pressed");
423 
424 	vb->add_margin_child(TTR("Path:"), vb2);
425 
426 	set_size(Size2(200, 150));
427 	set_hide_on_ok(false);
428 	set_title(TTR("Attach Node Script"));
429 
430 	file_browse = memnew(EditorFileDialog);
431 	file_browse->connect("file_selected", this, "_file_selected");
432 	add_child(file_browse);
433 	get_ok()->set_text(TTR("Create"));
434 	alert = memnew(AcceptDialog);
435 	add_child(alert);
436 	_lang_changed(0);
437 
438 	create_new = true;
439 }
440