/*************************************************************************/ /* script_create_dialog.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ /* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ /* "Software"), to deal in the Software without restriction, including */ /* without limitation the rights to use, copy, modify, merge, publish, */ /* distribute, sublicense, and/or sell copies of the Software, and to */ /* permit persons to whom the Software is furnished to do so, subject to */ /* the following conditions: */ /* */ /* The above copyright notice and this permission notice shall be */ /* included in all copies or substantial portions of the Software. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ #include "script_create_dialog.h" #include "core/config/project_settings.h" #include "core/io/file_access.h" #include "core/io/resource_saver.h" #include "core/string/string_builder.h" #include "editor/create_dialog.h" #include "editor/editor_file_dialog.h" #include "editor/editor_file_system.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" void ScriptCreateDialog::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: case NOTIFICATION_THEME_CHANGED: { for (int i = 0; i < ScriptServer::get_language_count(); i++) { Ref language_icon = get_theme_icon(ScriptServer::get_language(i)->get_type(), SNAME("EditorIcons")); if (language_icon.is_valid()) { language_menu->set_item_icon(i, language_icon); } } String last_language = EditorSettings::get_singleton()->get_project_metadata("script_setup", "last_selected_language", ""); if (!last_language.is_empty()) { for (int i = 0; i < language_menu->get_item_count(); i++) { if (language_menu->get_item_text(i) == last_language) { language_menu->select(i); current_language = i; break; } } } else { language_menu->select(default_language); } if (EditorSettings::get_singleton()->has_meta("script_setup/use_script_templates")) { is_using_templates = bool(EditorSettings::get_singleton()->get_meta("script_setup/use_script_templates")); use_templates->set_pressed(is_using_templates); } path_button->set_icon(get_theme_icon(SNAME("Folder"), SNAME("EditorIcons"))); parent_browse_button->set_icon(get_theme_icon(SNAME("Folder"), SNAME("EditorIcons"))); parent_search_button->set_icon(get_theme_icon(SNAME("ClassList"), SNAME("EditorIcons"))); status_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); } break; } } void ScriptCreateDialog::_path_hbox_sorted() { if (is_visible()) { int filename_start_pos = initial_bp.rfind("/") + 1; int filename_end_pos = initial_bp.length(); if (!is_built_in) { file_path->select(filename_start_pos, filename_end_pos); } // First set cursor to the end of line to scroll LineEdit view // to the right and then set the actual cursor position. file_path->set_caret_column(file_path->get_text().length()); file_path->set_caret_column(filename_start_pos); file_path->grab_focus(); } } bool ScriptCreateDialog::_can_be_built_in() { return (supports_built_in && built_in_enabled); } void ScriptCreateDialog::config(const String &p_base_name, const String &p_base_path, bool p_built_in_enabled, bool p_load_enabled) { class_name->set_text(""); class_name->deselect(); parent_name->set_text(p_base_name); parent_name->deselect(); if (!p_base_path.is_empty()) { initial_bp = p_base_path.get_basename(); file_path->set_text(initial_bp + "." + ScriptServer::get_language(language_menu->get_selected())->get_extension()); current_language = language_menu->get_selected(); } else { initial_bp = ""; file_path->set_text(""); } file_path->deselect(); built_in_enabled = p_built_in_enabled; load_enabled = p_load_enabled; _language_changed(current_language); _class_name_changed(""); _path_changed(file_path->get_text()); } void ScriptCreateDialog::set_inheritance_base_type(const String &p_base) { base_type = p_base; } bool ScriptCreateDialog::_validate_parent(const String &p_string) { if (p_string.length() == 0) { return false; } if (can_inherit_from_file && p_string.is_quoted()) { String p = p_string.substr(1, p_string.length() - 2); if (_validate_path(p, true) == "") { return true; } } return ClassDB::class_exists(p_string) || ScriptServer::is_global_class(p_string); } bool ScriptCreateDialog::_validate_class(const String &p_string) { if (p_string.length() == 0) { return false; } for (int i = 0; i < p_string.length(); i++) { if (i == 0) { // Cannot start with a number. if (p_string[0] >= '0' && p_string[0] <= '9') { return false; } } bool valid_char = is_ascii_identifier_char(p_string[i]) || p_string[i] == '.'; if (!valid_char) { return false; } } return true; } String ScriptCreateDialog::_validate_path(const String &p_path, bool p_file_must_exist) { String p = p_path.strip_edges(); if (p.is_empty()) { return TTR("Path is empty."); } if (p.get_file().get_basename().is_empty()) { return TTR("Filename is empty."); } if (!p.get_file().get_basename().is_valid_filename()) { return TTR("Filename is invalid."); } p = ProjectSettings::get_singleton()->localize_path(p); if (!p.begins_with("res://")) { return TTR("Path is not local."); } DirAccess *d = DirAccess::create(DirAccess::ACCESS_RESOURCES); if (d->change_dir(p.get_base_dir()) != OK) { memdelete(d); return TTR("Base path is invalid."); } memdelete(d); // Check if file exists. DirAccess *f = DirAccess::create(DirAccess::ACCESS_RESOURCES); if (f->dir_exists(p)) { memdelete(f); return TTR("A directory with the same name exists."); } else if (p_file_must_exist && !f->file_exists(p)) { memdelete(f); return TTR("File does not exist."); } memdelete(f); // Check file extension. String extension = p.get_extension(); List extensions; // Get all possible extensions for script. for (int l = 0; l < language_menu->get_item_count(); l++) { ScriptServer::get_language(l)->get_recognized_extensions(&extensions); } bool found = false; bool match = false; int index = 0; for (const String &E : extensions) { if (E.nocasecmp_to(extension) == 0) { found = true; if (E == ScriptServer::get_language(language_menu->get_selected())->get_extension()) { match = true; } break; } index++; } if (!found) { return TTR("Invalid extension."); } if (!match) { return TTR("Extension doesn't match chosen language."); } // Let ScriptLanguage do custom validation. String path_error = ScriptServer::get_language(language_menu->get_selected())->validate_path(p); if (!path_error.is_empty()) { return path_error; } // All checks passed. return ""; } String ScriptCreateDialog::_get_class_name() const { if (has_named_classes) { return class_name->get_text(); } else { return ProjectSettings::get_singleton()->localize_path(file_path->get_text()).get_file().get_basename(); } } void ScriptCreateDialog::_class_name_changed(const String &p_name) { is_class_name_valid = _validate_class(class_name->get_text()); _update_dialog(); } void ScriptCreateDialog::_parent_name_changed(const String &p_parent) { is_parent_name_valid = _validate_parent(parent_name->get_text()); _update_dialog(); } void ScriptCreateDialog::_template_changed(int p_template) { const ScriptLanguage::ScriptTemplate &sinfo = _get_current_template(); // Update last used dictionaries if (is_using_templates && !parent_name->get_text().begins_with("\"res:")) { if (sinfo.origin == ScriptLanguage::TemplateLocation::TEMPLATE_PROJECT) { // Save the last used template for this node into the project dictionary. Dictionary dic_templates_project = EditorSettings::get_singleton()->get_project_metadata("script_setup", "templates_dictionary", Dictionary()); dic_templates_project[parent_name->get_text()] = sinfo.get_hash(); EditorSettings::get_singleton()->set_project_metadata("script_setup", "templates_dictionary", dic_templates_project); } else { // Save template into to editor dictionary (not a project template). Dictionary dic_templates; if (EditorSettings::get_singleton()->has_meta("script_setup/templates_dictionary")) { dic_templates = (Dictionary)EditorSettings::get_singleton()->get_meta("script_setup/templates_dictionary"); } dic_templates[parent_name->get_text()] = sinfo.get_hash(); EditorSettings::get_singleton()->set_meta("script_setup/templates_dictionary", dic_templates); // Remove template from project dictionary as we last used an editor level template. Dictionary dic_templates_project = EditorSettings::get_singleton()->get_project_metadata("script_setup", "templates_dictionary", Dictionary()); if (dic_templates_project.has(parent_name->get_text())) { dic_templates_project.erase(parent_name->get_text()); EditorSettings::get_singleton()->set_project_metadata("script_setup", "templates_dictionary", dic_templates_project); } } } // Update template label information. String template_info = String::utf8("• "); template_info += TTR("Template:"); template_info += " " + sinfo.name; if (!sinfo.description.is_empty()) { template_info += " - " + sinfo.description; } template_info_label->set_text(template_info); template_info_label->add_theme_color_override("font_color", get_theme_color(SNAME("success_color"), SNAME("Editor"))); } void ScriptCreateDialog::ok_pressed() { if (is_new_script_created) { _create_new(); } else { _load_exist(); } EditorSettings::get_singleton()->save(); is_new_script_created = true; _update_dialog(); } void ScriptCreateDialog::_create_new() { String cname_param = _get_class_name(); Ref