diff options
Diffstat (limited to 'editor/script_create_dialog.cpp')
| -rw-r--r-- | editor/script_create_dialog.cpp | 579 | 
1 files changed, 363 insertions, 216 deletions
diff --git a/editor/script_create_dialog.cpp b/editor/script_create_dialog.cpp index 0e96a1d247..20c6aafc7f 100644 --- a/editor/script_create_dialog.cpp +++ b/editor/script_create_dialog.cpp @@ -5,8 +5,8 @@  /*                           GODOT ENGINE                                */  /*                      https://godotengine.org                          */  /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur.                 */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md).   */ +/* 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       */ @@ -33,7 +33,6 @@  #include "core/config/project_settings.h"  #include "core/io/file_access.h"  #include "core/io/resource_saver.h" -#include "core/object/script_language.h"  #include "core/string/string_builder.h"  #include "editor/create_dialog.h"  #include "editor/editor_node.h" @@ -45,17 +44,16 @@ void ScriptCreateDialog::_notification(int p_what) {  		case NOTIFICATION_ENTER_TREE:  		case NOTIFICATION_THEME_CHANGED: {  			for (int i = 0; i < ScriptServer::get_language_count(); i++) { -				String lang = ScriptServer::get_language(i)->get_type(); -				Ref<Texture2D> lang_icon = get_theme_icon(lang, SNAME("EditorIcons")); -				if (lang_icon.is_valid()) { -					language_menu->set_item_icon(i, lang_icon); +				Ref<Texture2D> 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_lang = EditorSettings::get_singleton()->get_project_metadata("script_setup", "last_selected_language", ""); -			if (!last_lang.is_empty()) { +			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_lang) { +					if (language_menu->get_item_text(i) == last_language) {  						language_menu->select(i);  						current_language = i;  						break; @@ -64,6 +62,10 @@ void ScriptCreateDialog::_notification(int p_what) {  			} 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"))); @@ -114,7 +116,7 @@ void ScriptCreateDialog::config(const String &p_base_name, const String &p_base_  	built_in_enabled = p_built_in_enabled;  	load_enabled = p_load_enabled; -	_lang_changed(current_language); +	_language_changed(current_language);  	_class_name_changed("");  	_path_changed(file_path->get_text());  } @@ -145,8 +147,9 @@ bool ScriptCreateDialog::_validate_class(const String &p_string) {  	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; // no start with number plz +				return false;  			}  		} @@ -170,6 +173,10 @@ String ScriptCreateDialog::_validate_path(const String &p_path, bool p_file_must  		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."); @@ -178,11 +185,11 @@ String ScriptCreateDialog::_validate_path(const String &p_path, bool p_file_must  	DirAccess *d = DirAccess::create(DirAccess::ACCESS_RESOURCES);  	if (d->change_dir(p.get_base_dir()) != OK) {  		memdelete(d); -		return TTR("Invalid base path."); +		return TTR("Base path is invalid.");  	}  	memdelete(d); -	/* Does file already exist */ +	// Check if file exists.  	DirAccess *f = DirAccess::create(DirAccess::ACCESS_RESOURCES);  	if (f->dir_exists(p)) {  		memdelete(f); @@ -193,11 +200,11 @@ String ScriptCreateDialog::_validate_path(const String &p_path, bool p_file_must  	}  	memdelete(f); -	/* Check file extension */ +	// Check file extension.  	String extension = p.get_extension();  	List<String> extensions; -	// get all possible extensions for script +	// 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);  	} @@ -207,8 +214,6 @@ String ScriptCreateDialog::_validate_path(const String &p_path, bool p_file_must  	int index = 0;  	for (const String &E : extensions) {  		if (E.nocasecmp_to(extension) == 0) { -			//FIXME (?) - changing language this way doesn't update controls, needs rework -			//language_menu->select(index); // change Language option by extension  			found = true;  			if (E == ScriptServer::get_language(language_menu->get_selected())->get_extension()) {  				match = true; @@ -222,16 +227,16 @@ String ScriptCreateDialog::_validate_path(const String &p_path, bool p_file_must  		return TTR("Invalid extension.");  	}  	if (!match) { -		return TTR("Wrong extension chosen."); +		return TTR("Extension doesn't match chosen language.");  	} -	/* Let ScriptLanguage do custom validation */ +	// 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 */ +	// All checks passed.  	return "";  } @@ -244,40 +249,49 @@ String ScriptCreateDialog::_get_class_name() const {  }  void ScriptCreateDialog::_class_name_changed(const String &p_name) { -	if (_validate_class(class_name->get_text())) { -		is_class_name_valid = true; -	} else { -		is_class_name_valid = false; -	} +	is_class_name_valid = _validate_class(class_name->get_text());  	_update_dialog();  }  void ScriptCreateDialog::_parent_name_changed(const String &p_parent) { -	if (_validate_parent(parent_name->get_text())) { -		is_parent_name_valid = true; -	} else { -		is_parent_name_valid = false; -	} +	is_parent_name_valid = _validate_parent(parent_name->get_text());  	_update_dialog();  }  void ScriptCreateDialog::_template_changed(int p_template) { -	String selected_template = p_template == 0 ? "" : template_menu->get_item_text(p_template); -	EditorSettings::get_singleton()->set_project_metadata("script_setup", "last_selected_template", selected_template); -	if (p_template == 0) { -		//default -		script_template = ""; -		return; -	} -	int selected_id = template_menu->get_selected_id(); - -	for (int i = 0; i < template_list.size(); i++) { -		const ScriptTemplateInfo &sinfo = template_list[i]; -		if (sinfo.id == selected_id) { -			script_template = sinfo.dir.plus_file(sinfo.name + "." + sinfo.extension); -			break; +	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() { @@ -287,6 +301,7 @@ void ScriptCreateDialog::ok_pressed() {  		_load_exist();  	} +	EditorSettings::get_singleton()->save();  	is_new_script_created = true;  	_update_dialog();  } @@ -295,18 +310,10 @@ void ScriptCreateDialog::_create_new() {  	String cname_param = _get_class_name();  	Ref<Script> scr; -	if (!script_template.is_empty()) { -		scr = ResourceLoader::load(script_template); -		if (scr.is_null()) { -			alert->set_text(vformat(TTR("Error loading template '%s'"), script_template)); -			alert->popup_centered(); -			return; -		} -		scr = scr->duplicate(); -		ScriptServer::get_language(language_menu->get_selected())->make_template(cname_param, parent_name->get_text(), scr); -	} else { -		scr = ScriptServer::get_language(language_menu->get_selected())->get_template(cname_param, parent_name->get_text()); -	} + +	const ScriptLanguage::ScriptTemplate sinfo = _get_current_template(); + +	scr = ScriptServer::get_language(language_menu->get_selected())->make_template(sinfo.content, cname_param, parent_name->get_text());  	if (has_named_classes) {  		String cname = class_name->get_text(); @@ -345,8 +352,20 @@ void ScriptCreateDialog::_load_exist() {  	hide();  } -void ScriptCreateDialog::_lang_changed(int l) { -	ScriptLanguage *language = ScriptServer::get_language(l); +Vector<String> ScriptCreateDialog::get_hierarchy(String p_object) const { +	Vector<String> hierachy; +	hierachy.append(p_object); + +	String parent_class = ClassDB::get_parent_class(p_object); +	while (parent_class.is_valid_identifier()) { +		hierachy.append(parent_class); +		parent_class = ClassDB::get_parent_class(parent_class); +	} +	return hierachy; +} + +void ScriptCreateDialog::_language_changed(int l) { +	language = ScriptServer::get_language(l);  	has_named_classes = language->has_named_classes();  	can_inherit_from_file = language->can_inherit_from_file(); @@ -364,13 +383,13 @@ void ScriptCreateDialog::_lang_changed(int l) {  		}  		if (extension.length() == 0) { -			// add extension if none +			// Add extension if none.  			path += selected_ext;  			_path_changed(path);  		} else { -			// change extension by selected language +			// Change extension by selected language.  			List<String> extensions; -			// get all possible extensions for script +			// Get all possible extensions for script.  			for (int m = 0; m < language_menu->get_item_count(); m++) {  				ScriptServer::get_language(m)->get_recognized_extensions(&extensions);  			} @@ -389,123 +408,12 @@ void ScriptCreateDialog::_lang_changed(int l) {  	}  	file_path->set_text(path); -	bool use_templates = language->is_using_templates(); -	template_menu->set_disabled(!use_templates); -	template_menu->clear(); - -	if (use_templates) { -		_update_script_templates(language->get_extension()); - -		String last_lang = EditorSettings::get_singleton()->get_project_metadata("script_setup", "last_selected_language", ""); -		String last_template = EditorSettings::get_singleton()->get_project_metadata("script_setup", "last_selected_template", ""); - -		template_menu->add_item(TTR("Default")); - -		ScriptTemplateInfo *templates = template_list.ptrw(); - -		Vector<String> origin_names; -		origin_names.push_back(TTR("Project")); -		origin_names.push_back(TTR("Editor")); -		int cur_origin = -1; - -		// Populate script template items previously sorted and now grouped by origin -		for (int i = 0; i < template_list.size(); i++) { -			if (int(templates[i].origin) != cur_origin) { -				template_menu->add_separator(); - -				String origin_name = origin_names[templates[i].origin]; - -				int last_index = template_menu->get_item_count() - 1; -				template_menu->set_item_text(last_index, origin_name); - -				cur_origin = templates[i].origin; -			} -			String item_name = templates[i].name.capitalize(); -			template_menu->add_item(item_name); - -			int new_id = template_menu->get_item_count() - 1; -			templates[i].id = new_id; -		} -		// Disable overridden -		for (const KeyValue<String, Vector<int>> &E : template_overrides) { -			const Vector<int> &overrides = E.value; - -			if (overrides.size() == 1) { -				continue; // doesn't override anything -			} -			const ScriptTemplateInfo &extended = template_list[overrides[0]]; - -			StringBuilder override_info; -			override_info += TTR("Overrides"); -			override_info += ": "; - -			for (int i = 1; i < overrides.size(); i++) { -				const ScriptTemplateInfo &overridden = template_list[overrides[i]]; - -				int disable_index = template_menu->get_item_index(overridden.id); -				template_menu->set_item_disabled(disable_index, true); - -				override_info += origin_names[overridden.origin]; -				if (i < overrides.size() - 1) { -					override_info += ", "; -				} -			} -			template_menu->set_item_icon(extended.id, get_theme_icon(SNAME("Override"), SNAME("EditorIcons"))); -			template_menu->get_popup()->set_item_tooltip(extended.id, override_info.as_string()); -		} -		// Reselect last selected template -		for (int i = 0; i < template_menu->get_item_count(); i++) { -			const String &ti = template_menu->get_item_text(i); -			if (language_menu->get_item_text(language_menu->get_selected()) == last_lang && last_template == ti) { -				template_menu->select(i); -				break; -			} -		} -	} else { -		template_menu->add_item(TTR("N/A")); -		script_template = ""; -	} - -	_template_changed(template_menu->get_selected());  	EditorSettings::get_singleton()->set_project_metadata("script_setup", "last_selected_language", language_menu->get_item_text(language_menu->get_selected()));  	_parent_name_changed(parent_name->get_text());  	_update_dialog();  } -void ScriptCreateDialog::_update_script_templates(const String &p_extension) { -	template_list.clear(); -	template_overrides.clear(); - -	Vector<String> dirs; - -	// Ordered from local to global for correct override mechanism -	dirs.push_back(EditorSettings::get_singleton()->get_project_script_templates_dir()); -	dirs.push_back(EditorSettings::get_singleton()->get_script_templates_dir()); - -	for (int i = 0; i < dirs.size(); i++) { -		Vector<String> list = EditorSettings::get_singleton()->get_script_templates(p_extension, dirs[i]); - -		for (int j = 0; j < list.size(); j++) { -			ScriptTemplateInfo sinfo; -			sinfo.origin = ScriptOrigin(i); -			sinfo.dir = dirs[i]; -			sinfo.name = list[j]; -			sinfo.extension = p_extension; -			template_list.push_back(sinfo); - -			if (!template_overrides.has(sinfo.name)) { -				Vector<int> overrides; -				overrides.push_back(template_list.size() - 1); // first one -				template_overrides.insert(sinfo.name, overrides); -			} else { -				Vector<int> &overrides = template_overrides[sinfo.name]; -				overrides.push_back(template_list.size() - 1); -			} -		} -	} -} -  void ScriptCreateDialog::_built_in_pressed() {  	if (internal->is_pressed()) {  		is_built_in = true; @@ -517,6 +425,12 @@ void ScriptCreateDialog::_built_in_pressed() {  	_update_dialog();  } +void ScriptCreateDialog::_use_template_pressed() { +	is_using_templates = use_templates->is_pressed(); +	EditorSettings::get_singleton()->set_meta("script_setup/use_script_templates", is_using_templates); +	_update_dialog(); +} +  void ScriptCreateDialog::_browse_path(bool browse_parent, bool p_save) {  	is_browsing_parent = browse_parent; @@ -545,16 +459,16 @@ void ScriptCreateDialog::_browse_path(bool browse_parent, bool p_save) {  }  void ScriptCreateDialog::_file_selected(const String &p_file) { -	String p = ProjectSettings::get_singleton()->localize_path(p_file); +	String path = ProjectSettings::get_singleton()->localize_path(p_file);  	if (is_browsing_parent) { -		parent_name->set_text("\"" + p + "\""); +		parent_name->set_text("\"" + path + "\"");  		_parent_name_changed(parent_name->get_text());  	} else { -		file_path->set_text(p); -		_path_changed(p); +		file_path->set_text(path); +		_path_changed(path); -		String filename = p.get_file().get_basename(); -		int select_start = p.rfind(filename); +		String filename = path.get_file().get_basename(); +		int select_start = path.rfind(filename);  		file_path->select(select_start, select_start + filename.length());  		file_path->set_caret_column(select_start + filename.length());  		file_path->grab_focus(); @@ -588,7 +502,7 @@ void ScriptCreateDialog::_path_changed(const String &p_path) {  		return;  	} -	/* Does file already exist */ +	// Check if file exists.  	DirAccess *f = DirAccess::create(DirAccess::ACCESS_RESOURCES);  	String p = ProjectSettings::get_singleton()->localize_path(p_path.strip_edges());  	if (f->file_exists(p)) { @@ -623,9 +537,98 @@ void ScriptCreateDialog::_msg_path_valid(bool valid, const String &p_msg) {  	}  } -void ScriptCreateDialog::_update_dialog() { -	/* "Add Script Dialog" GUI logic and script checks. */ +void ScriptCreateDialog::_update_template_menu() { +	bool is_language_using_templates = language->is_using_templates(); +	template_menu->set_disabled(false); +	template_menu->clear(); +	template_list.clear(); +	if (is_language_using_templates) { +		// Get the lastest templates used for each type of node from project settings then global settings. +		Dictionary last_local_templates = EditorSettings::get_singleton()->get_project_metadata("script_setup", "templates_dictionary", Dictionary()); +		Dictionary last_global_templates; +		if (EditorSettings::get_singleton()->has_meta("script_setup/templates_dictionary")) { +			last_global_templates = (Dictionary)EditorSettings::get_singleton()->get_meta("script_setup/templates_dictionary"); +		} +		String inherits_base_type = parent_name->get_text(); + +		// If it inherits from a script, select Object instead. +		if (inherits_base_type[0] == '"') { +			inherits_base_type = "Object"; +		} + +		// Get all ancestor node for selected base node. +		// There templates will also fit the base node. +		Vector<String> hierarchy = get_hierarchy(inherits_base_type); +		int last_used_template = -1; +		int preselected_template = -1; +		int previous_ancestor_level = -1; + +		// Templates can be stored in tree different locations. +		Vector<ScriptLanguage::TemplateLocation> template_locations; +		template_locations.append(ScriptLanguage::TEMPLATE_PROJECT); +		template_locations.append(ScriptLanguage::TEMPLATE_EDITOR); +		template_locations.append(ScriptLanguage::TEMPLATE_BUILT_IN); + +		for (const ScriptLanguage::TemplateLocation &template_location : template_locations) { +			String display_name = _get_script_origin_label(template_location); +			bool separator = false; +			int ancestor_level = 0; +			for (const String ¤t_node : hierarchy) { +				Vector<ScriptLanguage::ScriptTemplate> templates_found; +				if (template_location == ScriptLanguage::TEMPLATE_BUILT_IN) { +					templates_found = language->get_built_in_templates(current_node); +				} else { +					String template_directory; +					if (template_location == ScriptLanguage::TEMPLATE_PROJECT) { +						template_directory = EditorSettings::get_singleton()->get_project_script_templates_dir(); +					} else { +						template_directory = EditorSettings::get_singleton()->get_script_templates_dir(); +					} +					templates_found = _get_user_templates(language, current_node, template_directory, template_location); +				} +				if (!templates_found.is_empty()) { +					if (!separator) { +						template_menu->add_separator(); +						template_menu->set_item_text(template_menu->get_item_count() - 1, display_name); +						separator = true; +					} +					for (ScriptLanguage::ScriptTemplate &t : templates_found) { +						template_menu->add_item(t.inherit + ": " + t.name); +						int id = template_menu->get_item_count() - 1; +						// Check if this template should be preselected if node isn't in the last used dictionary. +						if (ancestor_level < previous_ancestor_level || previous_ancestor_level == -1) { +							previous_ancestor_level = ancestor_level; +							preselected_template = id; +						} +						// Check for last used template for this node in project settings then in global settings. +						if (last_local_templates.has(parent_name->get_text()) && t.get_hash() == String(last_local_templates[parent_name->get_text()])) { +							last_used_template = id; +						} else if (last_used_template == -1 && last_global_templates.has(parent_name->get_text()) && t.get_hash() == String(last_global_templates[parent_name->get_text()])) { +							last_used_template = id; +						} +						t.id = id; +						template_list.push_back(t); +						String icon = has_theme_icon(t.inherit, SNAME("EditorIcons")) ? t.inherit : "Object"; +						template_menu->set_item_icon(id, get_theme_icon(icon, SNAME("EditorIcons"))); +					} +				} +				ancestor_level++; +			} +		} + +		if (last_used_template != -1) { +			template_menu->select(last_used_template); +		} else if (preselected_template != -1) { +			template_menu->select(preselected_template); +		} +	} +	_template_changed(template_menu->get_selected()); +} + +void ScriptCreateDialog::_update_dialog() { +	// "Add Script Dialog" GUI logic and script checks. +	_update_template_menu();  	bool script_ok = true;  	// Is script path/name valid (order from top to bottom)? @@ -697,41 +700,51 @@ void ScriptCreateDialog::_update_dialog() {  	// This warning isn't relevant if the script is built-in.  	script_name_warning_label->set_visible(!is_built_in && _get_class_name() == parent_name->get_text()); -	if (is_built_in) { -		get_ok_button()->set_text(TTR("Create")); -		parent_name->set_editable(true); -		parent_search_button->set_disabled(false); -		parent_browse_button->set_disabled(!can_inherit_from_file); -		_msg_path_valid(true, TTR("Built-in script (into scene file).")); -	} else if (is_new_script_created) { -		// New script created. - -		get_ok_button()->set_text(TTR("Create")); -		parent_name->set_editable(true); -		parent_search_button->set_disabled(false); -		parent_browse_button->set_disabled(!can_inherit_from_file); -		if (is_path_valid) { +	bool is_new_file = is_built_in || is_new_script_created; + +	parent_name->set_editable(is_new_file); +	parent_search_button->set_disabled(!is_new_file); +	parent_browse_button->set_disabled(!is_new_file || !can_inherit_from_file); +	template_inactive_message = ""; +	String button_text = is_new_file ? TTR("Create") : TTR("Load"); +	get_ok_button()->set_text(button_text); + +	if (is_new_file) { +		if (is_built_in) { +			_msg_path_valid(true, TTR("Built-in script (into scene file).")); +		} +		if (is_new_script_created && is_path_valid) {  			_msg_path_valid(true, TTR("Will create a new script file."));  		} -	} else if (load_enabled) { -		// Script loaded. - -		get_ok_button()->set_text(TTR("Load")); -		parent_name->set_editable(false); -		parent_search_button->set_disabled(true); -		parent_browse_button->set_disabled(true); -		if (is_path_valid) { -			_msg_path_valid(true, TTR("Will load an existing script file.")); +	} else { +		if (load_enabled) { +			template_inactive_message = TTR("Using existing script file."); +			if (is_path_valid) { +				_msg_path_valid(true, TTR("Will load an existing script file.")); +			} +		} else { +			template_inactive_message = TTR("Using existing script file."); +			_msg_path_valid(false, TTR("Script file already exists.")); +			script_ok = false; +		} +	} + +	// Show templates list if needed. +	if (is_using_templates) { +		// Check if at least one suitable template has been found. +		if (template_menu->get_item_count() == 0 && template_inactive_message.is_empty()) { +			template_inactive_message = TTR("No suitable template.");  		}  	} else { -		get_ok_button()->set_text(TTR("Create")); -		parent_name->set_editable(true); -		parent_search_button->set_disabled(false); -		parent_browse_button->set_disabled(!can_inherit_from_file); -		_msg_path_valid(false, TTR("Script file already exists.")); +		template_inactive_message = TTR("Empty"); +	} -		script_ok = false; +	if (!template_inactive_message.is_empty()) { +		template_menu->set_disabled(true); +		template_menu->clear(); +		template_menu->add_item(template_inactive_message);  	} +	template_info_label->set_visible(!template_menu->is_disabled());  	get_ok_button()->set_disabled(!script_ok); @@ -745,6 +758,122 @@ void ScriptCreateDialog::_update_dialog() {  	}  } +ScriptLanguage::ScriptTemplate ScriptCreateDialog::_get_current_template() const { +	int selected_id = template_menu->get_selected_id(); +	for (const ScriptLanguage::ScriptTemplate &t : template_list) { +		if (is_using_templates) { +			if (t.id == selected_id) { +				return t; +			} +		} else { +			// Using empty built-in template if templates are disabled. +			if (t.origin == ScriptLanguage::TemplateLocation::TEMPLATE_BUILT_IN && t.name == "Empty") { +				return t; +			} +		} +	} +	return ScriptLanguage::ScriptTemplate(); +} + +Vector<ScriptLanguage::ScriptTemplate> ScriptCreateDialog::_get_user_templates(const ScriptLanguage *language, const StringName &p_object, const String &p_dir, const ScriptLanguage::TemplateLocation &p_origin) const { +	Vector<ScriptLanguage::ScriptTemplate> user_templates; +	String extension = language->get_extension(); + +	String dir_path = p_dir.plus_file(p_object); + +	DirAccess *d = DirAccess::open(dir_path); +	if (d) { +		d->list_dir_begin(); +		String file = d->get_next(); +		while (file != String()) { +			if (file.get_extension() == extension) { +				user_templates.append(_parse_template(language, dir_path, file, p_origin, p_object)); +			} +			file = d->get_next(); +		} +		d->list_dir_end(); +		memdelete(d); +	} +	return user_templates; +} + +ScriptLanguage::ScriptTemplate ScriptCreateDialog::_parse_template(const ScriptLanguage *language, const String &p_path, const String &p_filename, const ScriptLanguage::TemplateLocation &p_origin, const String &p_inherits) const { +	ScriptLanguage::ScriptTemplate script_template = ScriptLanguage::ScriptTemplate(); +	script_template.origin = p_origin; +	script_template.inherit = p_inherits; +	String space_indent = "    "; +	// Get meta delimiter +	String meta_delimiter = String(); +	List<String> comment_delimiters; +	language->get_comment_delimiters(&comment_delimiters); +	for (const String &script_delimiter : comment_delimiters) { +		if (script_delimiter.find(" ") == -1) { +			meta_delimiter = script_delimiter; +			break; +		} +	} +	String meta_prefix = meta_delimiter + " meta-"; + +	// Parse file for meta-information and script content +	Error err; +	FileAccess *file = FileAccess::open(p_path.plus_file(p_filename), FileAccess::READ, &err); +	if (!err) { +		while (!file->eof_reached()) { +			String line = file->get_line(); +			if (line.begins_with(meta_prefix)) { +				// Store meta information +				line = line.substr(meta_prefix.length(), -1); +				if (line.begins_with("name")) { +					script_template.name = line.substr(5, -1).strip_edges(); +				} +				if (line.begins_with("description")) { +					script_template.description = line.substr(12, -1).strip_edges(); +				} +				if (line.begins_with("space-indent")) { +					String indent_value = line.substr(17, -1).strip_edges(); +					if (indent_value.is_valid_int()) { +						space_indent = ""; +						for (int i = 0; i < indent_value.to_int(); i++) { +							space_indent += " "; +						} +					} else { +						WARN_PRINT(vformat("Template meta-use_space_indent need to be a valid integer value. Found %s.", indent_value)); +					} +				} +			} else { +				// Store script +				if (space_indent != "") { +					line = line.replace(space_indent, "_TS_"); +				} +				script_template.content += line.replace("\t", "_TS_") + "\n"; +			} +		} +		file->close(); +		memdelete(file); +	} + +	script_template.content = script_template.content.lstrip("\n"); + +	// Get name from file name if no name in meta information +	if (script_template.name == String()) { +		script_template.name = p_filename.get_basename().replace("_", " ").capitalize(); +	} + +	return script_template; +} + +String ScriptCreateDialog::_get_script_origin_label(const ScriptLanguage::TemplateLocation &p_origin) const { +	switch (p_origin) { +		case ScriptLanguage::TEMPLATE_BUILT_IN: +			return TTR("Built-in"); +		case ScriptLanguage::TEMPLATE_EDITOR: +			return TTR("Editor"); +		case ScriptLanguage::TEMPLATE_PROJECT: +			return TTR("Project"); +	} +	return ""; +} +  void ScriptCreateDialog::_bind_methods() {  	ClassDB::bind_method(D_METHOD("config", "inherits", "path", "built_in_enabled", "load_enabled"), &ScriptCreateDialog::config, DEFVAL(true), DEFVAL(true)); @@ -757,7 +886,7 @@ ScriptCreateDialog::ScriptCreateDialog() {  	GridContainer *gc = memnew(GridContainer);  	gc->set_columns(2); -	/* Error Messages Field */ +	/* Information Messages Field */  	VBoxContainer *vb = memnew(VBoxContainer); @@ -782,6 +911,10 @@ ScriptCreateDialog::ScriptCreateDialog() {  	script_name_warning_label->set_autowrap_mode(Label::AUTOWRAP_WORD_SMART);  	script_name_warning_label->hide(); +	template_info_label = memnew(Label); +	vb->add_child(template_info_label); +	template_info_label->set_autowrap_mode(Label::AUTOWRAP_WORD_SMART); +  	status_panel = memnew(PanelContainer);  	status_panel->set_h_size_flags(Control::SIZE_FILL);  	status_panel->set_v_size_flags(Control::SIZE_EXPAND_FILL); @@ -801,7 +934,7 @@ ScriptCreateDialog::ScriptCreateDialog() {  	/* Language */  	language_menu = memnew(OptionButton); -	language_menu->set_custom_minimum_size(Size2(250, 0) * EDSCALE); +	language_menu->set_custom_minimum_size(Size2(350, 0) * EDSCALE);  	language_menu->set_h_size_flags(Control::SIZE_EXPAND_FILL);  	gc->add_child(memnew(Label(TTR("Language:"))));  	gc->add_child(language_menu); @@ -819,7 +952,7 @@ ScriptCreateDialog::ScriptCreateDialog() {  	}  	current_language = default_language; -	language_menu->connect("item_selected", callable_mp(this, &ScriptCreateDialog::_lang_changed)); +	language_menu->connect("item_selected", callable_mp(this, &ScriptCreateDialog::_language_changed));  	/* Inherits */ @@ -851,10 +984,24 @@ ScriptCreateDialog::ScriptCreateDialog() {  	/* Templates */ -	template_menu = memnew(OptionButton); +	is_using_templates = true;  	gc->add_child(memnew(Label(TTR("Template:")))); -	gc->add_child(template_menu); +	HBoxContainer *template_hb = memnew(HBoxContainer); +	template_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + +	use_templates = memnew(CheckBox); +	use_templates->set_pressed(is_using_templates); +	use_templates->connect("pressed", callable_mp(this, &ScriptCreateDialog::_use_template_pressed)); +	template_hb->add_child(use_templates); + +	template_inactive_message = ""; + +	template_menu = memnew(OptionButton); +	template_menu->set_h_size_flags(Control::SIZE_EXPAND_FILL);  	template_menu->connect("item_selected", callable_mp(this, &ScriptCreateDialog::_template_changed)); +	template_hb->add_child(template_menu); + +	gc->add_child(template_hb);  	/* Built-in Script */  |