summaryrefslogtreecommitdiff
path: root/editor/export
diff options
context:
space:
mode:
authorAaron Franke <arnfranke@yahoo.com>2022-07-20 17:45:01 -0500
committerAaron Franke <arnfranke@yahoo.com>2022-07-26 08:28:29 -0500
commit006e5f28d58c516d2331a48842665289e2ba9fcf (patch)
tree98415950126adc7925e2071b9f35e50ed177035d /editor/export
parente53ae131787b3eb45560635b6d358d8fee4e088c (diff)
Move project export and export template manager into export folder
Diffstat (limited to 'editor/export')
-rw-r--r--editor/export/export_template_manager.cpp1000
-rw-r--r--editor/export/export_template_manager.h134
-rw-r--r--editor/export/project_export.cpp1290
-rw-r--r--editor/export/project_export.h180
4 files changed, 2604 insertions, 0 deletions
diff --git a/editor/export/export_template_manager.cpp b/editor/export/export_template_manager.cpp
new file mode 100644
index 0000000000..e02066e956
--- /dev/null
+++ b/editor/export/export_template_manager.cpp
@@ -0,0 +1,1000 @@
+/*************************************************************************/
+/* export_template_manager.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 "export_template_manager.h"
+
+#include "core/io/dir_access.h"
+#include "core/io/json.h"
+#include "core/io/zip_io.h"
+#include "core/version.h"
+#include "editor/editor_node.h"
+#include "editor/editor_paths.h"
+#include "editor/editor_scale.h"
+#include "editor/progress_dialog.h"
+#include "scene/gui/file_dialog.h"
+#include "scene/gui/tree.h"
+#include "scene/main/http_request.h"
+
+void ExportTemplateManager::_update_template_status() {
+ // Fetch installed templates from the file system.
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ const String &templates_dir = EditorSettings::get_singleton()->get_export_templates_dir();
+
+ Error err = da->change_dir(templates_dir);
+ ERR_FAIL_COND_MSG(err != OK, "Could not access templates directory at '" + templates_dir + "'.");
+
+ RBSet<String> templates;
+ da->list_dir_begin();
+ if (err == OK) {
+ String c = da->get_next();
+ while (!c.is_empty()) {
+ if (da->current_is_dir() && !c.begins_with(".")) {
+ templates.insert(c);
+ }
+ c = da->get_next();
+ }
+ }
+ da->list_dir_end();
+
+ // Update the state of the current version.
+ String current_version = VERSION_FULL_CONFIG;
+ current_value->set_text(current_version);
+
+ if (templates.has(current_version)) {
+ current_missing_label->hide();
+ current_installed_label->show();
+
+ current_installed_hb->show();
+ current_version_exists = true;
+ } else {
+ current_installed_label->hide();
+ current_missing_label->show();
+
+ current_installed_hb->hide();
+ current_version_exists = false;
+ }
+
+ if (is_downloading_templates) {
+ install_options_vb->hide();
+ download_progress_hb->show();
+ } else {
+ download_progress_hb->hide();
+ install_options_vb->show();
+
+ if (templates.has(current_version)) {
+ current_installed_path->set_text(templates_dir.plus_file(current_version));
+ }
+ }
+
+ // Update the list of other installed versions.
+ installed_table->clear();
+ TreeItem *installed_root = installed_table->create_item();
+
+ for (RBSet<String>::Element *E = templates.back(); E; E = E->prev()) {
+ String version_string = E->get();
+ if (version_string == current_version) {
+ continue;
+ }
+
+ TreeItem *ti = installed_table->create_item(installed_root);
+ ti->set_text(0, version_string);
+
+ ti->add_button(0, get_theme_icon(SNAME("Folder"), SNAME("EditorIcons")), OPEN_TEMPLATE_FOLDER, false, TTR("Open the folder containing these templates."));
+ ti->add_button(0, get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), UNINSTALL_TEMPLATE, false, TTR("Uninstall these templates."));
+ }
+}
+
+void ExportTemplateManager::_download_current() {
+ if (is_downloading_templates) {
+ return;
+ }
+ is_downloading_templates = true;
+
+ install_options_vb->hide();
+ download_progress_hb->show();
+
+ if (mirrors_available) {
+ String mirror_url = _get_selected_mirror();
+ if (mirror_url.is_empty()) {
+ _set_current_progress_status(TTR("There are no mirrors available."), true);
+ return;
+ }
+
+ _download_template(mirror_url, true);
+ } else if (!is_refreshing_mirrors) {
+ _set_current_progress_status(TTR("Retrieving the mirror list..."));
+ _refresh_mirrors();
+ }
+}
+
+void ExportTemplateManager::_download_template(const String &p_url, bool p_skip_check) {
+ if (!p_skip_check && is_downloading_templates) {
+ return;
+ }
+ is_downloading_templates = true;
+
+ install_options_vb->hide();
+ download_progress_hb->show();
+ _set_current_progress_status(TTR("Starting the download..."));
+
+ download_templates->set_download_file(EditorPaths::get_singleton()->get_cache_dir().plus_file("tmp_templates.tpz"));
+ download_templates->set_use_threads(true);
+
+ const String proxy_host = EDITOR_GET("network/http_proxy/host");
+ const int proxy_port = EDITOR_GET("network/http_proxy/port");
+ download_templates->set_http_proxy(proxy_host, proxy_port);
+ download_templates->set_https_proxy(proxy_host, proxy_port);
+
+ Error err = download_templates->request(p_url);
+ if (err != OK) {
+ _set_current_progress_status(TTR("Error requesting URL:") + " " + p_url, true);
+ return;
+ }
+
+ set_process(true);
+ _set_current_progress_status(TTR("Connecting to the mirror..."));
+}
+
+void ExportTemplateManager::_download_template_completed(int p_status, int p_code, const PackedStringArray &headers, const PackedByteArray &p_data) {
+ switch (p_status) {
+ case HTTPRequest::RESULT_CANT_RESOLVE: {
+ _set_current_progress_status(TTR("Can't resolve the requested address."), true);
+ } break;
+ case HTTPRequest::RESULT_BODY_SIZE_LIMIT_EXCEEDED:
+ case HTTPRequest::RESULT_CONNECTION_ERROR:
+ case HTTPRequest::RESULT_CHUNKED_BODY_SIZE_MISMATCH:
+ case HTTPRequest::RESULT_SSL_HANDSHAKE_ERROR:
+ case HTTPRequest::RESULT_CANT_CONNECT: {
+ _set_current_progress_status(TTR("Can't connect to the mirror."), true);
+ } break;
+ case HTTPRequest::RESULT_NO_RESPONSE: {
+ _set_current_progress_status(TTR("No response from the mirror."), true);
+ } break;
+ case HTTPRequest::RESULT_REQUEST_FAILED: {
+ _set_current_progress_status(TTR("Request failed."), true);
+ } break;
+ case HTTPRequest::RESULT_REDIRECT_LIMIT_REACHED: {
+ _set_current_progress_status(TTR("Request ended up in a redirect loop."), true);
+ } break;
+ default: {
+ if (p_code != 200) {
+ _set_current_progress_status(TTR("Request failed:") + " " + itos(p_code), true);
+ } else {
+ _set_current_progress_status(TTR("Download complete; extracting templates..."));
+ String path = download_templates->get_download_file();
+
+ is_downloading_templates = false;
+ bool ret = _install_file_selected(path, true);
+ if (ret) {
+ // Clean up downloaded file.
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ Error err = da->remove(path);
+ if (err != OK) {
+ EditorNode::get_singleton()->add_io_error(TTR("Cannot remove temporary file:") + "\n" + path + "\n");
+ }
+ } else {
+ EditorNode::get_singleton()->add_io_error(vformat(TTR("Templates installation failed.\nThe problematic templates archives can be found at '%s'."), path));
+ }
+ }
+ } break;
+ }
+
+ set_process(false);
+}
+
+void ExportTemplateManager::_cancel_template_download() {
+ if (!is_downloading_templates) {
+ return;
+ }
+
+ download_templates->cancel_request();
+ download_progress_hb->hide();
+ install_options_vb->show();
+ is_downloading_templates = false;
+}
+
+void ExportTemplateManager::_refresh_mirrors() {
+ if (is_refreshing_mirrors) {
+ return;
+ }
+ is_refreshing_mirrors = true;
+
+ String current_version = VERSION_FULL_CONFIG;
+ const String mirrors_metadata_url = "https://godotengine.org/mirrorlist/" + current_version + ".json";
+ request_mirrors->request(mirrors_metadata_url);
+}
+
+void ExportTemplateManager::_refresh_mirrors_completed(int p_status, int p_code, const PackedStringArray &headers, const PackedByteArray &p_data) {
+ if (p_status != HTTPRequest::RESULT_SUCCESS || p_code != 200) {
+ EditorNode::get_singleton()->show_warning(TTR("Error getting the list of mirrors."));
+ is_refreshing_mirrors = false;
+ if (is_downloading_templates) {
+ _cancel_template_download();
+ }
+ return;
+ }
+
+ String response_json;
+ {
+ const uint8_t *r = p_data.ptr();
+ response_json.parse_utf8((const char *)r, p_data.size());
+ }
+
+ JSON json;
+ Error err = json.parse(response_json);
+ if (err != OK) {
+ EditorNode::get_singleton()->show_warning(TTR("Error parsing JSON with the list of mirrors. Please report this issue!"));
+ is_refreshing_mirrors = false;
+ if (is_downloading_templates) {
+ _cancel_template_download();
+ }
+ return;
+ }
+
+ mirrors_list->clear();
+ mirrors_list->add_item(TTR("Best available mirror"), 0);
+
+ mirrors_available = false;
+
+ Dictionary data = json.get_data();
+ if (data.has("mirrors")) {
+ Array mirrors = data["mirrors"];
+
+ for (int i = 0; i < mirrors.size(); i++) {
+ Dictionary m = mirrors[i];
+ ERR_CONTINUE(!m.has("url") || !m.has("name"));
+
+ mirrors_list->add_item(m["name"]);
+ mirrors_list->set_item_metadata(i + 1, m["url"]);
+
+ mirrors_available = true;
+ }
+ }
+ if (!mirrors_available) {
+ EditorNode::get_singleton()->show_warning(TTR("No download links found for this version. Direct download is only available for official releases."));
+ if (is_downloading_templates) {
+ _cancel_template_download();
+ }
+ }
+
+ is_refreshing_mirrors = false;
+
+ if (is_downloading_templates) {
+ String mirror_url = _get_selected_mirror();
+ if (mirror_url.is_empty()) {
+ _set_current_progress_status(TTR("There are no mirrors available."), true);
+ return;
+ }
+
+ _download_template(mirror_url, true);
+ }
+}
+
+bool ExportTemplateManager::_humanize_http_status(HTTPRequest *p_request, String *r_status, int *r_downloaded_bytes, int *r_total_bytes) {
+ *r_status = "";
+ *r_downloaded_bytes = -1;
+ *r_total_bytes = -1;
+ bool success = true;
+
+ switch (p_request->get_http_client_status()) {
+ case HTTPClient::STATUS_DISCONNECTED:
+ *r_status = TTR("Disconnected");
+ success = false;
+ break;
+ case HTTPClient::STATUS_RESOLVING:
+ *r_status = TTR("Resolving");
+ break;
+ case HTTPClient::STATUS_CANT_RESOLVE:
+ *r_status = TTR("Can't Resolve");
+ success = false;
+ break;
+ case HTTPClient::STATUS_CONNECTING:
+ *r_status = TTR("Connecting...");
+ break;
+ case HTTPClient::STATUS_CANT_CONNECT:
+ *r_status = TTR("Can't Connect");
+ success = false;
+ break;
+ case HTTPClient::STATUS_CONNECTED:
+ *r_status = TTR("Connected");
+ break;
+ case HTTPClient::STATUS_REQUESTING:
+ *r_status = TTR("Requesting...");
+ break;
+ case HTTPClient::STATUS_BODY:
+ *r_status = TTR("Downloading");
+ *r_downloaded_bytes = p_request->get_downloaded_bytes();
+ *r_total_bytes = p_request->get_body_size();
+
+ if (p_request->get_body_size() > 0) {
+ *r_status += " " + String::humanize_size(p_request->get_downloaded_bytes()) + "/" + String::humanize_size(p_request->get_body_size());
+ } else {
+ *r_status += " " + String::humanize_size(p_request->get_downloaded_bytes());
+ }
+ break;
+ case HTTPClient::STATUS_CONNECTION_ERROR:
+ *r_status = TTR("Connection Error");
+ success = false;
+ break;
+ case HTTPClient::STATUS_SSL_HANDSHAKE_ERROR:
+ *r_status = TTR("SSL Handshake Error");
+ success = false;
+ break;
+ }
+
+ return success;
+}
+
+void ExportTemplateManager::_set_current_progress_status(const String &p_status, bool p_error) {
+ download_progress_bar->hide();
+ download_progress_label->set_text(p_status);
+
+ if (p_error) {
+ download_progress_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor")));
+ } else {
+ download_progress_label->add_theme_color_override("font_color", get_theme_color(SNAME("font_color"), SNAME("Label")));
+ }
+}
+
+void ExportTemplateManager::_set_current_progress_value(float p_value, const String &p_status) {
+ download_progress_bar->show();
+ download_progress_bar->set_value(p_value);
+ download_progress_label->set_text(p_status);
+}
+
+void ExportTemplateManager::_install_file() {
+ install_file_dialog->popup_file_dialog();
+}
+
+bool ExportTemplateManager::_install_file_selected(const String &p_file, bool p_skip_progress) {
+ Ref<FileAccess> io_fa;
+ zlib_filefunc_def io = zipio_create_io(&io_fa);
+
+ unzFile pkg = unzOpen2(p_file.utf8().get_data(), &io);
+ if (!pkg) {
+ EditorNode::get_singleton()->show_warning(TTR("Can't open the export templates file."));
+ return false;
+ }
+ int ret = unzGoToFirstFile(pkg);
+
+ // Count them and find version.
+ int fc = 0;
+ String version;
+ String contents_dir;
+
+ while (ret == UNZ_OK) {
+ unz_file_info info;
+ char fname[16384];
+ ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0);
+ if (ret != UNZ_OK) {
+ break;
+ }
+
+ String file = String::utf8(fname);
+ if (file.ends_with("version.txt")) {
+ Vector<uint8_t> data;
+ data.resize(info.uncompressed_size);
+
+ // Read.
+ unzOpenCurrentFile(pkg);
+ ret = unzReadCurrentFile(pkg, data.ptrw(), data.size());
+ ERR_BREAK_MSG(ret < 0, vformat("An error occurred while attempting to read from file: %s. This file will not be used.", file));
+ unzCloseCurrentFile(pkg);
+
+ String data_str;
+ data_str.parse_utf8((const char *)data.ptr(), data.size());
+ data_str = data_str.strip_edges();
+
+ // Version number should be of the form major.minor[.patch].status[.module_config]
+ // so it can in theory have 3 or more slices.
+ if (data_str.get_slice_count(".") < 3) {
+ EditorNode::get_singleton()->show_warning(vformat(TTR("Invalid version.txt format inside the export templates file: %s."), data_str));
+ unzClose(pkg);
+ return false;
+ }
+
+ version = data_str;
+ contents_dir = file.get_base_dir().trim_suffix("/").trim_suffix("\\");
+ }
+
+ if (file.get_file().size() != 0) {
+ fc++;
+ }
+
+ ret = unzGoToNextFile(pkg);
+ }
+
+ if (version.is_empty()) {
+ EditorNode::get_singleton()->show_warning(TTR("No version.txt found inside the export templates file."));
+ unzClose(pkg);
+ return false;
+ }
+
+ Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ String template_path = EditorSettings::get_singleton()->get_export_templates_dir().plus_file(version);
+ Error err = d->make_dir_recursive(template_path);
+ if (err != OK) {
+ EditorNode::get_singleton()->show_warning(TTR("Error creating path for extracting templates:") + "\n" + template_path);
+ unzClose(pkg);
+ return false;
+ }
+
+ EditorProgress *p = nullptr;
+ if (!p_skip_progress) {
+ p = memnew(EditorProgress("ltask", TTR("Extracting Export Templates"), fc));
+ }
+
+ fc = 0;
+ ret = unzGoToFirstFile(pkg);
+ while (ret == UNZ_OK) {
+ // Get filename.
+ unz_file_info info;
+ char fname[16384];
+ ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0);
+ if (ret != UNZ_OK) {
+ break;
+ }
+
+ String file_path(String::utf8(fname).simplify_path());
+
+ String file = file_path.get_file();
+
+ if (file.size() == 0) {
+ ret = unzGoToNextFile(pkg);
+ continue;
+ }
+
+ Vector<uint8_t> data;
+ data.resize(info.uncompressed_size);
+
+ // Read
+ unzOpenCurrentFile(pkg);
+ ret = unzReadCurrentFile(pkg, data.ptrw(), data.size());
+ ERR_BREAK_MSG(ret < 0, vformat("An error occurred while attempting to read from file: %s. This file will not be used.", file));
+ unzCloseCurrentFile(pkg);
+
+ String base_dir = file_path.get_base_dir().trim_suffix("/");
+
+ if (base_dir != contents_dir && base_dir.begins_with(contents_dir)) {
+ base_dir = base_dir.substr(contents_dir.length(), file_path.length()).trim_prefix("/");
+ file = base_dir.plus_file(file);
+
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ ERR_CONTINUE(da.is_null());
+
+ String output_dir = template_path.plus_file(base_dir);
+
+ if (!DirAccess::exists(output_dir)) {
+ Error mkdir_err = da->make_dir_recursive(output_dir);
+ ERR_CONTINUE(mkdir_err != OK);
+ }
+ }
+
+ if (p) {
+ p->step(TTR("Importing:") + " " + file, fc);
+ }
+
+ String to_write = template_path.plus_file(file);
+ Ref<FileAccess> f = FileAccess::open(to_write, FileAccess::WRITE);
+
+ if (f.is_null()) {
+ ret = unzGoToNextFile(pkg);
+ fc++;
+ ERR_CONTINUE_MSG(true, "Can't open file from path '" + String(to_write) + "'.");
+ }
+
+ f->store_buffer(data.ptr(), data.size());
+ f.unref(); // close file.
+#ifndef WINDOWS_ENABLED
+ FileAccess::set_unix_permissions(to_write, (info.external_fa >> 16) & 0x01FF);
+#endif
+
+ ret = unzGoToNextFile(pkg);
+ fc++;
+ }
+
+ if (p) {
+ memdelete(p);
+ }
+ unzClose(pkg);
+
+ _update_template_status();
+ return true;
+}
+
+void ExportTemplateManager::_uninstall_template(const String &p_version) {
+ uninstall_confirm->set_text(vformat(TTR("Remove templates for the version '%s'?"), p_version));
+ uninstall_confirm->popup_centered();
+ uninstall_version = p_version;
+}
+
+void ExportTemplateManager::_uninstall_template_confirmed() {
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ const String &templates_dir = EditorSettings::get_singleton()->get_export_templates_dir();
+
+ Error err = da->change_dir(templates_dir);
+ ERR_FAIL_COND_MSG(err != OK, "Could not access templates directory at '" + templates_dir + "'.");
+ err = da->change_dir(uninstall_version);
+ ERR_FAIL_COND_MSG(err != OK, "Could not access templates directory at '" + templates_dir.plus_file(uninstall_version) + "'.");
+
+ err = da->erase_contents_recursive();
+ ERR_FAIL_COND_MSG(err != OK, "Could not remove all templates in '" + templates_dir.plus_file(uninstall_version) + "'.");
+
+ da->change_dir("..");
+ err = da->remove(uninstall_version);
+ ERR_FAIL_COND_MSG(err != OK, "Could not remove templates directory at '" + templates_dir.plus_file(uninstall_version) + "'.");
+
+ _update_template_status();
+}
+
+String ExportTemplateManager::_get_selected_mirror() const {
+ if (mirrors_list->get_item_count() == 1) {
+ return "";
+ }
+
+ int selected = mirrors_list->get_selected_id();
+ if (selected == 0) {
+ // This is a special "best available" value; so pick the first available mirror from the rest of the list.
+ selected = 1;
+ }
+
+ return mirrors_list->get_item_metadata(selected);
+}
+
+void ExportTemplateManager::_mirror_options_button_cbk(int p_id) {
+ switch (p_id) {
+ case VISIT_WEB_MIRROR: {
+ String mirror_url = _get_selected_mirror();
+ if (mirror_url.is_empty()) {
+ EditorNode::get_singleton()->show_warning(TTR("There are no mirrors available."));
+ return;
+ }
+
+ OS::get_singleton()->shell_open(mirror_url);
+ } break;
+
+ case COPY_MIRROR_URL: {
+ String mirror_url = _get_selected_mirror();
+ if (mirror_url.is_empty()) {
+ EditorNode::get_singleton()->show_warning(TTR("There are no mirrors available."));
+ return;
+ }
+
+ DisplayServer::get_singleton()->clipboard_set(mirror_url);
+ } break;
+ }
+}
+
+void ExportTemplateManager::_installed_table_button_cbk(Object *p_item, int p_column, int p_id, MouseButton p_button) {
+ if (p_button != MouseButton::LEFT) {
+ return;
+ }
+ TreeItem *ti = Object::cast_to<TreeItem>(p_item);
+ if (!ti) {
+ return;
+ }
+
+ switch (p_id) {
+ case OPEN_TEMPLATE_FOLDER: {
+ String version_string = ti->get_text(0);
+ _open_template_folder(version_string);
+ } break;
+
+ case UNINSTALL_TEMPLATE: {
+ String version_string = ti->get_text(0);
+ _uninstall_template(version_string);
+ } break;
+ }
+}
+
+void ExportTemplateManager::_open_template_folder(const String &p_version) {
+ const String &templates_dir = EditorSettings::get_singleton()->get_export_templates_dir();
+ OS::get_singleton()->shell_open("file://" + templates_dir.plus_file(p_version));
+}
+
+void ExportTemplateManager::popup_manager() {
+ _update_template_status();
+ _refresh_mirrors();
+ popup_centered(Size2(720, 280) * EDSCALE);
+}
+
+void ExportTemplateManager::ok_pressed() {
+ if (!is_downloading_templates) {
+ hide();
+ return;
+ }
+
+ hide_dialog_accept->popup_centered();
+}
+
+void ExportTemplateManager::_hide_dialog() {
+ hide();
+}
+
+bool ExportTemplateManager::can_install_android_template() {
+ const String templates_dir = EditorSettings::get_singleton()->get_export_templates_dir().plus_file(VERSION_FULL_CONFIG);
+ return FileAccess::exists(templates_dir.plus_file("android_source.zip"));
+}
+
+Error ExportTemplateManager::install_android_template() {
+ const String &templates_path = EditorSettings::get_singleton()->get_export_templates_dir().plus_file(VERSION_FULL_CONFIG);
+ const String &source_zip = templates_path.plus_file("android_source.zip");
+ ERR_FAIL_COND_V(!FileAccess::exists(source_zip), ERR_CANT_OPEN);
+ return install_android_template_from_file(source_zip);
+}
+Error ExportTemplateManager::install_android_template_from_file(const String &p_file) {
+ // To support custom Android builds, we install the Java source code and buildsystem
+ // from android_source.zip to the project's res://android folder.
+
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ ERR_FAIL_COND_V(da.is_null(), ERR_CANT_CREATE);
+
+ // Make res://android dir (if it does not exist).
+ da->make_dir("android");
+ {
+ // Add version, to ensure building won't work if template and Godot version don't match.
+ Ref<FileAccess> f = FileAccess::open("res://android/.build_version", FileAccess::WRITE);
+ ERR_FAIL_COND_V(f.is_null(), ERR_CANT_CREATE);
+ f->store_line(VERSION_FULL_CONFIG);
+ }
+
+ // Create the android plugins directory.
+ Error err = da->make_dir_recursive("android/plugins");
+ ERR_FAIL_COND_V(err != OK, err);
+
+ err = da->make_dir_recursive("android/build");
+ ERR_FAIL_COND_V(err != OK, err);
+ {
+ // Add an empty .gdignore file to avoid scan.
+ Ref<FileAccess> f = FileAccess::open("res://android/build/.gdignore", FileAccess::WRITE);
+ ERR_FAIL_COND_V(f.is_null(), ERR_CANT_CREATE);
+ f->store_line("");
+ }
+
+ // Uncompress source template.
+
+ Ref<FileAccess> io_fa;
+ zlib_filefunc_def io = zipio_create_io(&io_fa);
+
+ unzFile pkg = unzOpen2(p_file.utf8().get_data(), &io);
+ ERR_FAIL_COND_V_MSG(!pkg, ERR_CANT_OPEN, "Android sources not in ZIP format.");
+
+ int ret = unzGoToFirstFile(pkg);
+ int total_files = 0;
+ // Count files to unzip.
+ while (ret == UNZ_OK) {
+ total_files++;
+ ret = unzGoToNextFile(pkg);
+ }
+ ret = unzGoToFirstFile(pkg);
+
+ ProgressDialog::get_singleton()->add_task("uncompress_src", TTR("Uncompressing Android Build Sources"), total_files);
+
+ HashSet<String> dirs_tested;
+ int idx = 0;
+ while (ret == UNZ_OK) {
+ // Get file path.
+ unz_file_info info;
+ char fpath[16384];
+ ret = unzGetCurrentFileInfo(pkg, &info, fpath, 16384, nullptr, 0, nullptr, 0);
+ if (ret != UNZ_OK) {
+ break;
+ }
+
+ String path = String::utf8(fpath);
+ String base_dir = path.get_base_dir();
+
+ if (!path.ends_with("/")) {
+ Vector<uint8_t> data;
+ data.resize(info.uncompressed_size);
+
+ // Read.
+ unzOpenCurrentFile(pkg);
+ unzReadCurrentFile(pkg, data.ptrw(), data.size());
+ unzCloseCurrentFile(pkg);
+
+ if (!dirs_tested.has(base_dir)) {
+ da->make_dir_recursive(String("android/build").plus_file(base_dir));
+ dirs_tested.insert(base_dir);
+ }
+
+ String to_write = String("res://android/build").plus_file(path);
+ Ref<FileAccess> f = FileAccess::open(to_write, FileAccess::WRITE);
+ if (f.is_valid()) {
+ f->store_buffer(data.ptr(), data.size());
+ f.unref(); // close file.
+#ifndef WINDOWS_ENABLED
+ FileAccess::set_unix_permissions(to_write, (info.external_fa >> 16) & 0x01FF);
+#endif
+ } else {
+ ERR_PRINT("Can't uncompress file: " + to_write);
+ }
+ }
+
+ ProgressDialog::get_singleton()->task_step("uncompress_src", path, idx);
+
+ idx++;
+ ret = unzGoToNextFile(pkg);
+ }
+
+ ProgressDialog::get_singleton()->end_task("uncompress_src");
+ unzClose(pkg);
+
+ return OK;
+}
+
+void ExportTemplateManager::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED: {
+ current_value->add_theme_font_override("font", get_theme_font(SNAME("main"), SNAME("EditorFonts")));
+ current_missing_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor")));
+ current_installed_label->add_theme_color_override("font_color", get_theme_color(SNAME("disabled_font_color"), SNAME("Editor")));
+
+ mirror_options_button->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons")));
+ } break;
+
+ case NOTIFICATION_VISIBILITY_CHANGED: {
+ if (!is_visible()) {
+ set_process(false);
+ } else if (is_visible() && is_downloading_templates) {
+ set_process(true);
+ }
+ } break;
+
+ case NOTIFICATION_PROCESS: {
+ update_countdown -= get_process_delta_time();
+ if (update_countdown > 0) {
+ return;
+ }
+ update_countdown = 0.5;
+
+ String status;
+ int downloaded_bytes;
+ int total_bytes;
+ bool success = _humanize_http_status(download_templates, &status, &downloaded_bytes, &total_bytes);
+
+ if (downloaded_bytes >= 0) {
+ if (total_bytes > 0) {
+ _set_current_progress_value(float(downloaded_bytes) / total_bytes, status);
+ } else {
+ _set_current_progress_value(0, status);
+ }
+ } else {
+ _set_current_progress_status(status);
+ }
+
+ if (!success) {
+ set_process(false);
+ }
+ } break;
+
+ case NOTIFICATION_WM_CLOSE_REQUEST: {
+ // This won't stop the window from closing, but will show the alert if the download is active.
+ ok_pressed();
+ } break;
+ }
+}
+
+void ExportTemplateManager::_bind_methods() {
+}
+
+ExportTemplateManager::ExportTemplateManager() {
+ set_title(TTR("Export Template Manager"));
+ set_hide_on_ok(false);
+ set_ok_button_text(TTR("Close"));
+
+ // Downloadable export templates are only available for stable and official alpha/beta/RC builds
+ // (which always have a number following their status, e.g. "alpha1").
+ // Therefore, don't display download-related features when using a development version
+ // (whose builds aren't numbered).
+ downloads_available =
+ String(VERSION_STATUS) != String("dev") &&
+ String(VERSION_STATUS) != String("alpha") &&
+ String(VERSION_STATUS) != String("beta") &&
+ String(VERSION_STATUS) != String("rc");
+
+ VBoxContainer *main_vb = memnew(VBoxContainer);
+ add_child(main_vb);
+
+ // Current version controls.
+ HBoxContainer *current_hb = memnew(HBoxContainer);
+ main_vb->add_child(current_hb);
+
+ Label *current_label = memnew(Label);
+ current_label->set_theme_type_variation("HeaderSmall");
+ current_label->set_text(TTR("Current Version:"));
+ current_hb->add_child(current_label);
+
+ current_value = memnew(Label);
+ current_hb->add_child(current_value);
+
+ // Current version statuses.
+ // Status: Current version is missing.
+ current_missing_label = memnew(Label);
+ current_missing_label->set_theme_type_variation("HeaderSmall");
+
+ current_missing_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ current_missing_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
+ current_missing_label->set_text(TTR("Export templates are missing. Download them or install from a file."));
+ current_hb->add_child(current_missing_label);
+
+ // Status: Current version is installed.
+ current_installed_label = memnew(Label);
+ current_installed_label->set_theme_type_variation("HeaderSmall");
+ current_installed_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ current_installed_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
+ current_installed_label->set_text(TTR("Export templates are installed and ready to be used."));
+ current_hb->add_child(current_installed_label);
+ current_installed_label->hide();
+
+ // Currently installed template.
+ current_installed_hb = memnew(HBoxContainer);
+ main_vb->add_child(current_installed_hb);
+
+ current_installed_path = memnew(LineEdit);
+ current_installed_path->set_editable(false);
+ current_installed_path->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ current_installed_hb->add_child(current_installed_path);
+
+ current_open_button = memnew(Button);
+ current_open_button->set_text(TTR("Open Folder"));
+ current_open_button->set_tooltip(TTR("Open the folder containing installed templates for the current version."));
+ current_installed_hb->add_child(current_open_button);
+ current_open_button->connect("pressed", callable_mp(this, &ExportTemplateManager::_open_template_folder), varray(VERSION_FULL_CONFIG));
+
+ current_uninstall_button = memnew(Button);
+ current_uninstall_button->set_text(TTR("Uninstall"));
+ current_uninstall_button->set_tooltip(TTR("Uninstall templates for the current version."));
+ current_installed_hb->add_child(current_uninstall_button);
+ current_uninstall_button->connect("pressed", callable_mp(this, &ExportTemplateManager::_uninstall_template), varray(VERSION_FULL_CONFIG));
+
+ main_vb->add_child(memnew(HSeparator));
+
+ // Download and install section.
+ HBoxContainer *install_templates_hb = memnew(HBoxContainer);
+ main_vb->add_child(install_templates_hb);
+
+ // Download and install buttons are available.
+ install_options_vb = memnew(VBoxContainer);
+ install_options_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ install_templates_hb->add_child(install_options_vb);
+
+ HBoxContainer *download_install_hb = memnew(HBoxContainer);
+ install_options_vb->add_child(download_install_hb);
+
+ Label *mirrors_label = memnew(Label);
+ mirrors_label->set_text(TTR("Download from:"));
+ download_install_hb->add_child(mirrors_label);
+
+ mirrors_list = memnew(OptionButton);
+ mirrors_list->set_custom_minimum_size(Size2(280, 0) * EDSCALE);
+ download_install_hb->add_child(mirrors_list);
+ mirrors_list->add_item(TTR("Best available mirror"), 0);
+
+ request_mirrors = memnew(HTTPRequest);
+ mirrors_list->add_child(request_mirrors);
+ request_mirrors->connect("request_completed", callable_mp(this, &ExportTemplateManager::_refresh_mirrors_completed));
+
+ mirror_options_button = memnew(MenuButton);
+ mirror_options_button->get_popup()->add_item(TTR("Open in Web Browser"), VISIT_WEB_MIRROR);
+ mirror_options_button->get_popup()->add_item(TTR("Copy Mirror URL"), COPY_MIRROR_URL);
+ download_install_hb->add_child(mirror_options_button);
+ mirror_options_button->get_popup()->connect("id_pressed", callable_mp(this, &ExportTemplateManager::_mirror_options_button_cbk));
+
+ download_install_hb->add_spacer();
+
+ Button *download_current_button = memnew(Button);
+ download_current_button->set_text(TTR("Download and Install"));
+ download_current_button->set_tooltip(TTR("Download and install templates for the current version from the best possible mirror."));
+ download_install_hb->add_child(download_current_button);
+ download_current_button->connect("pressed", callable_mp(this, &ExportTemplateManager::_download_current));
+
+ // Update downloads buttons to prevent unsupported downloads.
+ if (!downloads_available) {
+ download_current_button->set_disabled(true);
+ download_current_button->set_tooltip(TTR("Official export templates aren't available for development builds."));
+ }
+
+ HBoxContainer *install_file_hb = memnew(HBoxContainer);
+ install_file_hb->set_alignment(BoxContainer::ALIGNMENT_END);
+ install_options_vb->add_child(install_file_hb);
+
+ install_file_button = memnew(Button);
+ install_file_button->set_text(TTR("Install from File"));
+ install_file_button->set_tooltip(TTR("Install templates from a local file."));
+ install_file_hb->add_child(install_file_button);
+ install_file_button->connect("pressed", callable_mp(this, &ExportTemplateManager::_install_file));
+
+ // Templates are being downloaded; buttons unavailable.
+ download_progress_hb = memnew(HBoxContainer);
+ download_progress_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ install_templates_hb->add_child(download_progress_hb);
+ download_progress_hb->hide();
+
+ download_progress_bar = memnew(ProgressBar);
+ download_progress_bar->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ download_progress_bar->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
+ download_progress_bar->set_min(0);
+ download_progress_bar->set_max(1);
+ download_progress_bar->set_value(0);
+ download_progress_bar->set_step(0.01);
+ download_progress_hb->add_child(download_progress_bar);
+
+ download_progress_label = memnew(Label);
+ download_progress_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ download_progress_hb->add_child(download_progress_label);
+
+ Button *download_cancel_button = memnew(Button);
+ download_cancel_button->set_text(TTR("Cancel"));
+ download_cancel_button->set_tooltip(TTR("Cancel the download of the templates."));
+ download_progress_hb->add_child(download_cancel_button);
+ download_cancel_button->connect("pressed", callable_mp(this, &ExportTemplateManager::_cancel_template_download));
+
+ download_templates = memnew(HTTPRequest);
+ install_templates_hb->add_child(download_templates);
+ download_templates->connect("request_completed", callable_mp(this, &ExportTemplateManager::_download_template_completed));
+
+ main_vb->add_child(memnew(HSeparator));
+
+ // Other installed templates table.
+ HBoxContainer *installed_versions_hb = memnew(HBoxContainer);
+ main_vb->add_child(installed_versions_hb);
+ Label *installed_label = memnew(Label);
+ installed_label->set_theme_type_variation("HeaderSmall");
+ installed_label->set_text(TTR("Other Installed Versions:"));
+ installed_versions_hb->add_child(installed_label);
+
+ installed_table = memnew(Tree);
+ installed_table->set_hide_root(true);
+ installed_table->set_custom_minimum_size(Size2(0, 100) * EDSCALE);
+ installed_table->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ main_vb->add_child(installed_table);
+ installed_table->connect("button_clicked", callable_mp(this, &ExportTemplateManager::_installed_table_button_cbk));
+
+ // Dialogs.
+ uninstall_confirm = memnew(ConfirmationDialog);
+ uninstall_confirm->set_title(TTR("Uninstall Template"));
+ add_child(uninstall_confirm);
+ uninstall_confirm->connect("confirmed", callable_mp(this, &ExportTemplateManager::_uninstall_template_confirmed));
+
+ install_file_dialog = memnew(FileDialog);
+ install_file_dialog->set_title(TTR("Select Template File"));
+ install_file_dialog->set_access(FileDialog::ACCESS_FILESYSTEM);
+ install_file_dialog->set_file_mode(FileDialog::FILE_MODE_OPEN_FILE);
+ install_file_dialog->add_filter("*.tpz", TTR("Godot Export Templates"));
+ install_file_dialog->connect("file_selected", callable_mp(this, &ExportTemplateManager::_install_file_selected), varray(false));
+ add_child(install_file_dialog);
+
+ hide_dialog_accept = memnew(AcceptDialog);
+ hide_dialog_accept->set_text(TTR("The templates will continue to download.\nYou may experience a short editor freeze when they finish."));
+ add_child(hide_dialog_accept);
+ hide_dialog_accept->connect("confirmed", callable_mp(this, &ExportTemplateManager::_hide_dialog));
+}
diff --git a/editor/export/export_template_manager.h b/editor/export/export_template_manager.h
new file mode 100644
index 0000000000..f01da15832
--- /dev/null
+++ b/editor/export/export_template_manager.h
@@ -0,0 +1,134 @@
+/*************************************************************************/
+/* export_template_manager.h */
+/*************************************************************************/
+/* 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. */
+/*************************************************************************/
+
+#ifndef EXPORT_TEMPLATE_MANAGER_H
+#define EXPORT_TEMPLATE_MANAGER_H
+
+#include "scene/gui/dialogs.h"
+
+class ExportTemplateVersion;
+class FileDialog;
+class HTTPRequest;
+class MenuButton;
+class OptionButton;
+class ProgressBar;
+class Tree;
+
+class ExportTemplateManager : public AcceptDialog {
+ GDCLASS(ExportTemplateManager, AcceptDialog);
+
+ bool current_version_exists = false;
+ bool downloads_available = true;
+ bool mirrors_available = false;
+ bool is_refreshing_mirrors = false;
+ bool is_downloading_templates = false;
+ float update_countdown = 0;
+
+ Label *current_value = nullptr;
+ Label *current_missing_label = nullptr;
+ Label *current_installed_label = nullptr;
+
+ HBoxContainer *current_installed_hb = nullptr;
+ LineEdit *current_installed_path = nullptr;
+ Button *current_open_button = nullptr;
+ Button *current_uninstall_button = nullptr;
+
+ VBoxContainer *install_options_vb = nullptr;
+ OptionButton *mirrors_list = nullptr;
+
+ enum MirrorAction {
+ VISIT_WEB_MIRROR,
+ COPY_MIRROR_URL,
+ };
+
+ MenuButton *mirror_options_button = nullptr;
+ HBoxContainer *download_progress_hb = nullptr;
+ ProgressBar *download_progress_bar = nullptr;
+ Label *download_progress_label = nullptr;
+ HTTPRequest *download_templates = nullptr;
+ Button *install_file_button = nullptr;
+ HTTPRequest *request_mirrors = nullptr;
+
+ enum TemplatesAction {
+ OPEN_TEMPLATE_FOLDER,
+ UNINSTALL_TEMPLATE,
+ };
+
+ Tree *installed_table = nullptr;
+
+ ConfirmationDialog *uninstall_confirm = nullptr;
+ String uninstall_version;
+ FileDialog *install_file_dialog = nullptr;
+ AcceptDialog *hide_dialog_accept = nullptr;
+
+ void _update_template_status();
+
+ void _download_current();
+ void _download_template(const String &p_url, bool p_skip_check = false);
+ void _download_template_completed(int p_status, int p_code, const PackedStringArray &headers, const PackedByteArray &p_data);
+ void _cancel_template_download();
+ void _refresh_mirrors();
+ void _refresh_mirrors_completed(int p_status, int p_code, const PackedStringArray &headers, const PackedByteArray &p_data);
+
+ bool _humanize_http_status(HTTPRequest *p_request, String *r_status, int *r_downloaded_bytes, int *r_total_bytes);
+ void _set_current_progress_status(const String &p_status, bool p_error = false);
+ void _set_current_progress_value(float p_value, const String &p_status);
+
+ void _install_file();
+ bool _install_file_selected(const String &p_file, bool p_skip_progress = false);
+
+ void _uninstall_template(const String &p_version);
+ void _uninstall_template_confirmed();
+
+ String _get_selected_mirror() const;
+ void _mirror_options_button_cbk(int p_id);
+ void _installed_table_button_cbk(Object *p_item, int p_column, int p_id, MouseButton p_button);
+
+ void _open_template_folder(const String &p_version);
+
+ virtual void ok_pressed() override;
+ void _hide_dialog();
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ bool can_install_android_template();
+ Error install_android_template();
+
+ Error install_android_template_from_file(const String &p_file);
+
+ void popup_manager();
+
+ ExportTemplateManager();
+};
+
+#endif // EXPORT_TEMPLATE_MANAGER_H
diff --git a/editor/export/project_export.cpp b/editor/export/project_export.cpp
new file mode 100644
index 0000000000..a20f19efc8
--- /dev/null
+++ b/editor/export/project_export.cpp
@@ -0,0 +1,1290 @@
+/*************************************************************************/
+/* project_export.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 "project_export.h"
+
+#include "core/config/project_settings.h"
+#include "core/version.h"
+#include "editor/editor_file_dialog.h"
+#include "editor/editor_file_system.h"
+#include "editor/editor_node.h"
+#include "editor/editor_properties.h"
+#include "editor/editor_scale.h"
+#include "editor/export/editor_export.h"
+#include "scene/gui/link_button.h"
+#include "scene/gui/tree.h"
+
+void ProjectExportDialog::_theme_changed() {
+ duplicate_preset->set_icon(presets->get_theme_icon(SNAME("Duplicate"), SNAME("EditorIcons")));
+ delete_preset->set_icon(presets->get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")));
+}
+
+void ProjectExportDialog::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_VISIBILITY_CHANGED: {
+ if (!is_visible()) {
+ EditorSettings::get_singleton()->set_project_metadata("dialog_bounds", "export", Rect2(get_position(), get_size()));
+ }
+ } break;
+
+ case NOTIFICATION_READY: {
+ duplicate_preset->set_icon(presets->get_theme_icon(SNAME("Duplicate"), SNAME("EditorIcons")));
+ delete_preset->set_icon(presets->get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")));
+ connect("confirmed", callable_mp(this, &ProjectExportDialog::_export_pck_zip));
+ _update_export_all();
+ } break;
+
+ case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
+ parameters->set_property_name_style(EditorPropertyNameProcessor::get_settings_style());
+ } break;
+ }
+}
+
+void ProjectExportDialog::popup_export() {
+ add_preset->get_popup()->clear();
+ for (int i = 0; i < EditorExport::get_singleton()->get_export_platform_count(); i++) {
+ Ref<EditorExportPlatform> plat = EditorExport::get_singleton()->get_export_platform(i);
+
+ add_preset->get_popup()->add_icon_item(plat->get_logo(), plat->get_name());
+ }
+
+ _update_presets();
+ if (presets->get_current() >= 0) {
+ _update_current_preset(); // triggers rescan for templates if newly installed
+ }
+
+ // Restore valid window bounds or pop up at default size.
+ Rect2 saved_size = EditorSettings::get_singleton()->get_project_metadata("dialog_bounds", "export", Rect2());
+ if (saved_size != Rect2()) {
+ popup(saved_size);
+ } else {
+ popup_centered_clamped(Size2(900, 700) * EDSCALE, 0.8);
+ }
+}
+
+void ProjectExportDialog::_add_preset(int p_platform) {
+ Ref<EditorExportPreset> preset = EditorExport::get_singleton()->get_export_platform(p_platform)->create_preset();
+ ERR_FAIL_COND(!preset.is_valid());
+
+ String name = EditorExport::get_singleton()->get_export_platform(p_platform)->get_name();
+ bool make_runnable = true;
+ int attempt = 1;
+ while (true) {
+ bool valid = true;
+
+ for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) {
+ Ref<EditorExportPreset> p = EditorExport::get_singleton()->get_export_preset(i);
+ if (p->get_platform() == preset->get_platform() && p->is_runnable()) {
+ make_runnable = false;
+ }
+ if (p->get_name() == name) {
+ valid = false;
+ break;
+ }
+ }
+
+ if (valid) {
+ break;
+ }
+
+ attempt++;
+ name = EditorExport::get_singleton()->get_export_platform(p_platform)->get_name() + " " + itos(attempt);
+ }
+
+ preset->set_name(name);
+ if (make_runnable) {
+ preset->set_runnable(make_runnable);
+ }
+ EditorExport::get_singleton()->add_export_preset(preset);
+ _update_presets();
+ _edit_preset(EditorExport::get_singleton()->get_export_preset_count() - 1);
+}
+
+void ProjectExportDialog::_force_update_current_preset_parameters() {
+ // Force the parameters section to refresh its UI.
+ parameters->edit(nullptr);
+ _update_current_preset();
+}
+
+void ProjectExportDialog::_update_current_preset() {
+ _edit_preset(presets->get_current());
+}
+
+void ProjectExportDialog::_update_presets() {
+ updating = true;
+
+ Ref<EditorExportPreset> current;
+ if (presets->get_current() >= 0 && presets->get_current() < presets->get_item_count()) {
+ current = get_current_preset();
+ }
+
+ int current_idx = -1;
+ presets->clear();
+ for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) {
+ Ref<EditorExportPreset> preset = EditorExport::get_singleton()->get_export_preset(i);
+ if (preset == current) {
+ current_idx = i;
+ }
+
+ String name = preset->get_name();
+ if (preset->is_runnable()) {
+ name += " (" + TTR("Runnable") + ")";
+ }
+ preset->update_files_to_export();
+ presets->add_item(name, preset->get_platform()->get_logo());
+ }
+
+ if (current_idx != -1) {
+ presets->select(current_idx);
+ }
+
+ updating = false;
+}
+
+void ProjectExportDialog::_update_export_all() {
+ bool can_export = EditorExport::get_singleton()->get_export_preset_count() > 0;
+
+ for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) {
+ Ref<EditorExportPreset> preset = EditorExport::get_singleton()->get_export_preset(i);
+ bool needs_templates;
+ String error;
+ if (preset->get_export_path().is_empty() || !preset->get_platform()->can_export(preset, error, needs_templates)) {
+ can_export = false;
+ break;
+ }
+ }
+
+ export_all_button->set_disabled(!can_export);
+
+ if (can_export) {
+ export_all_button->set_tooltip(TTR("Export the project for all the presets defined."));
+ } else {
+ export_all_button->set_tooltip(TTR("All presets must have an export path defined for Export All to work."));
+ }
+}
+
+void ProjectExportDialog::_edit_preset(int p_index) {
+ if (p_index < 0 || p_index >= presets->get_item_count()) {
+ name->set_text("");
+ name->set_editable(false);
+ export_path->hide();
+ runnable->set_disabled(true);
+ parameters->edit(nullptr);
+ presets->deselect_all();
+ duplicate_preset->set_disabled(true);
+ delete_preset->set_disabled(true);
+ sections->hide();
+ export_error->hide();
+ export_templates_error->hide();
+ return;
+ }
+
+ Ref<EditorExportPreset> current = EditorExport::get_singleton()->get_export_preset(p_index);
+ ERR_FAIL_COND(current.is_null());
+
+ updating = true;
+
+ presets->select(p_index);
+ sections->show();
+
+ name->set_editable(true);
+ export_path->show();
+ duplicate_preset->set_disabled(false);
+ delete_preset->set_disabled(false);
+ name->set_text(current->get_name());
+
+ List<String> extension_list = current->get_platform()->get_binary_extensions(current);
+ Vector<String> extension_vector;
+ for (int i = 0; i < extension_list.size(); i++) {
+ extension_vector.push_back("*." + extension_list[i]);
+ }
+
+ export_path->setup(extension_vector, false, true);
+ export_path->update_property();
+ runnable->set_disabled(false);
+ runnable->set_pressed(current->is_runnable());
+ parameters->edit(current.ptr());
+
+ export_filter->select(current->get_export_filter());
+ include_filters->set_text(current->get_include_filter());
+ exclude_filters->set_text(current->get_exclude_filter());
+
+ _fill_resource_tree();
+
+ bool needs_templates;
+ String error;
+ if (!current->get_platform()->can_export(current, error, needs_templates)) {
+ if (!error.is_empty()) {
+ Vector<String> items = error.split("\n", false);
+ error = "";
+ for (int i = 0; i < items.size(); i++) {
+ if (i > 0) {
+ error += "\n";
+ }
+ error += " - " + items[i];
+ }
+
+ export_error->set_text(error);
+ export_error->show();
+ } else {
+ export_error->hide();
+ }
+ if (needs_templates) {
+ export_templates_error->show();
+ } else {
+ export_templates_error->hide();
+ }
+
+ export_warning->hide();
+ export_button->set_disabled(true);
+ get_ok_button()->set_disabled(true);
+ } else {
+ if (error != String()) {
+ Vector<String> items = error.split("\n", false);
+ error = "";
+ for (int i = 0; i < items.size(); i++) {
+ if (i > 0) {
+ error += "\n";
+ }
+ error += " - " + items[i];
+ }
+ export_warning->set_text(error);
+ export_warning->show();
+ } else {
+ export_warning->hide();
+ }
+
+ export_error->hide();
+ export_templates_error->hide();
+ export_button->set_disabled(false);
+ get_ok_button()->set_disabled(false);
+ }
+
+ custom_features->set_text(current->get_custom_features());
+ _update_feature_list();
+ _update_export_all();
+ child_controls_changed();
+
+ String enc_in_filters_str = current->get_enc_in_filter();
+ String enc_ex_filters_str = current->get_enc_ex_filter();
+ if (!updating_enc_filters) {
+ enc_in_filters->set_text(enc_in_filters_str);
+ enc_ex_filters->set_text(enc_ex_filters_str);
+ }
+
+ bool enc_pck_mode = current->get_enc_pck();
+ enc_pck->set_pressed(enc_pck_mode);
+
+ enc_directory->set_disabled(!enc_pck_mode);
+ enc_in_filters->set_editable(enc_pck_mode);
+ enc_ex_filters->set_editable(enc_pck_mode);
+ script_key->set_editable(enc_pck_mode);
+
+ bool enc_directory_mode = current->get_enc_directory();
+ enc_directory->set_pressed(enc_directory_mode);
+
+ int script_export_mode = current->get_script_export_mode();
+ script_mode->select(script_export_mode);
+
+ String key = current->get_script_encryption_key();
+ if (!updating_script_key) {
+ script_key->set_text(key);
+ }
+ if (enc_pck_mode) {
+ script_key->set_editable(true);
+
+ bool key_valid = _validate_script_encryption_key(key);
+ if (key_valid) {
+ script_key_error->hide();
+ } else {
+ script_key_error->show();
+ }
+ } else {
+ script_key->set_editable(false);
+ script_key_error->hide();
+ }
+
+ updating = false;
+}
+
+void ProjectExportDialog::_update_feature_list() {
+ Ref<EditorExportPreset> current = get_current_preset();
+ ERR_FAIL_COND(current.is_null());
+
+ RBSet<String> fset;
+ List<String> features;
+
+ current->get_platform()->get_platform_features(&features);
+ current->get_platform()->get_preset_features(current, &features);
+
+ String custom = current->get_custom_features();
+ Vector<String> custom_list = custom.split(",");
+ for (int i = 0; i < custom_list.size(); i++) {
+ String f = custom_list[i].strip_edges();
+ if (!f.is_empty()) {
+ features.push_back(f);
+ }
+ }
+
+ for (const String &E : features) {
+ fset.insert(E);
+ }
+
+ custom_feature_display->clear();
+ String text;
+ bool first = true;
+ for (const String &E : fset) {
+ if (!first) {
+ text += ", ";
+ } else {
+ first = false;
+ }
+ text += E;
+ }
+ custom_feature_display->add_text(text);
+}
+
+void ProjectExportDialog::_custom_features_changed(const String &p_text) {
+ if (updating) {
+ return;
+ }
+
+ Ref<EditorExportPreset> current = get_current_preset();
+ ERR_FAIL_COND(current.is_null());
+
+ current->set_custom_features(p_text);
+ _update_feature_list();
+}
+
+void ProjectExportDialog::_tab_changed(int) {
+ _update_feature_list();
+}
+
+void ProjectExportDialog::_update_parameters(const String &p_edited_property) {
+ _update_current_preset();
+}
+
+void ProjectExportDialog::_runnable_pressed() {
+ if (updating) {
+ return;
+ }
+
+ Ref<EditorExportPreset> current = get_current_preset();
+ ERR_FAIL_COND(current.is_null());
+
+ if (runnable->is_pressed()) {
+ for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) {
+ Ref<EditorExportPreset> p = EditorExport::get_singleton()->get_export_preset(i);
+ if (p->get_platform() == current->get_platform()) {
+ p->set_runnable(current == p);
+ }
+ }
+ } else {
+ current->set_runnable(false);
+ }
+
+ _update_presets();
+}
+
+void ProjectExportDialog::_name_changed(const String &p_string) {
+ if (updating) {
+ return;
+ }
+
+ Ref<EditorExportPreset> current = get_current_preset();
+ ERR_FAIL_COND(current.is_null());
+
+ current->set_name(p_string);
+ _update_presets();
+}
+
+void ProjectExportDialog::set_export_path(const String &p_value) {
+ Ref<EditorExportPreset> current = get_current_preset();
+ ERR_FAIL_COND(current.is_null());
+
+ current->set_export_path(p_value);
+}
+
+String ProjectExportDialog::get_export_path() {
+ Ref<EditorExportPreset> current = get_current_preset();
+ ERR_FAIL_COND_V(current.is_null(), String(""));
+
+ return current->get_export_path();
+}
+
+Ref<EditorExportPreset> ProjectExportDialog::get_current_preset() const {
+ return EditorExport::get_singleton()->get_export_preset(presets->get_current());
+}
+
+void ProjectExportDialog::_export_path_changed(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing) {
+ if (updating) {
+ return;
+ }
+
+ Ref<EditorExportPreset> current = get_current_preset();
+ ERR_FAIL_COND(current.is_null());
+
+ current->set_export_path(p_value);
+ _update_presets();
+ _update_export_all();
+}
+
+void ProjectExportDialog::_enc_filters_changed(const String &p_filters) {
+ if (updating) {
+ return;
+ }
+
+ Ref<EditorExportPreset> current = get_current_preset();
+ ERR_FAIL_COND(current.is_null());
+
+ current->set_enc_in_filter(enc_in_filters->get_text());
+ current->set_enc_ex_filter(enc_ex_filters->get_text());
+
+ updating_enc_filters = true;
+ _update_current_preset();
+ updating_enc_filters = false;
+}
+
+void ProjectExportDialog::_open_key_help_link() {
+ OS::get_singleton()->shell_open(vformat("%s/development/compiling/compiling_with_script_encryption_key.html", VERSION_DOCS_URL));
+}
+
+void ProjectExportDialog::_enc_pck_changed(bool p_pressed) {
+ if (updating) {
+ return;
+ }
+
+ Ref<EditorExportPreset> current = get_current_preset();
+ ERR_FAIL_COND(current.is_null());
+
+ current->set_enc_pck(p_pressed);
+ enc_directory->set_disabled(!p_pressed);
+ enc_in_filters->set_editable(p_pressed);
+ enc_ex_filters->set_editable(p_pressed);
+ script_key->set_editable(p_pressed);
+
+ _update_current_preset();
+}
+
+void ProjectExportDialog::_enc_directory_changed(bool p_pressed) {
+ if (updating) {
+ return;
+ }
+
+ Ref<EditorExportPreset> current = get_current_preset();
+ ERR_FAIL_COND(current.is_null());
+
+ current->set_enc_directory(p_pressed);
+
+ _update_current_preset();
+}
+
+void ProjectExportDialog::_script_export_mode_changed(int p_mode) {
+ if (updating) {
+ return;
+ }
+
+ Ref<EditorExportPreset> current = get_current_preset();
+ ERR_FAIL_COND(current.is_null());
+
+ current->set_script_export_mode(p_mode);
+
+ _update_current_preset();
+}
+
+void ProjectExportDialog::_script_encryption_key_changed(const String &p_key) {
+ if (updating) {
+ return;
+ }
+
+ Ref<EditorExportPreset> current = get_current_preset();
+ ERR_FAIL_COND(current.is_null());
+
+ current->set_script_encryption_key(p_key);
+
+ updating_script_key = true;
+ _update_current_preset();
+ updating_script_key = false;
+}
+
+bool ProjectExportDialog::_validate_script_encryption_key(const String &p_key) {
+ bool is_valid = false;
+
+ if (!p_key.is_empty() && p_key.is_valid_hex_number(false) && p_key.length() == 64) {
+ is_valid = true;
+ }
+ return is_valid;
+}
+
+void ProjectExportDialog::_duplicate_preset() {
+ Ref<EditorExportPreset> current = get_current_preset();
+ if (current.is_null()) {
+ return;
+ }
+
+ Ref<EditorExportPreset> preset = current->get_platform()->create_preset();
+ ERR_FAIL_COND(!preset.is_valid());
+
+ String name = current->get_name() + " (copy)";
+ bool make_runnable = true;
+ while (true) {
+ bool valid = true;
+
+ for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) {
+ Ref<EditorExportPreset> p = EditorExport::get_singleton()->get_export_preset(i);
+ if (p->get_platform() == preset->get_platform() && p->is_runnable()) {
+ make_runnable = false;
+ }
+ if (p->get_name() == name) {
+ valid = false;
+ break;
+ }
+ }
+
+ if (valid) {
+ break;
+ }
+
+ name += " (copy)";
+ }
+
+ preset->set_name(name);
+ if (make_runnable) {
+ preset->set_runnable(make_runnable);
+ }
+ preset->set_export_filter(current->get_export_filter());
+ preset->set_include_filter(current->get_include_filter());
+ preset->set_exclude_filter(current->get_exclude_filter());
+ preset->set_custom_features(current->get_custom_features());
+
+ for (const PropertyInfo &E : current->get_properties()) {
+ preset->set(E.name, current->get(E.name));
+ }
+
+ EditorExport::get_singleton()->add_export_preset(preset);
+ _update_presets();
+ _edit_preset(EditorExport::get_singleton()->get_export_preset_count() - 1);
+}
+
+void ProjectExportDialog::_delete_preset() {
+ Ref<EditorExportPreset> current = get_current_preset();
+ if (current.is_null()) {
+ return;
+ }
+
+ delete_confirm->set_text(vformat(TTR("Delete preset '%s'?"), current->get_name()));
+ delete_confirm->popup_centered();
+}
+
+void ProjectExportDialog::_delete_preset_confirm() {
+ int idx = presets->get_current();
+ _edit_preset(-1);
+ export_button->set_disabled(true);
+ get_ok_button()->set_disabled(true);
+ EditorExport::get_singleton()->remove_export_preset(idx);
+ _update_presets();
+
+ // The Export All button might become enabled (if all other presets have an export path defined),
+ // or it could be disabled (if there are no presets anymore).
+ _update_export_all();
+}
+
+Variant ProjectExportDialog::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
+ if (p_from == presets) {
+ int pos = presets->get_item_at_position(p_point, true);
+
+ if (pos >= 0) {
+ Dictionary d;
+ d["type"] = "export_preset";
+ d["preset"] = pos;
+
+ HBoxContainer *drag = memnew(HBoxContainer);
+ TextureRect *tr = memnew(TextureRect);
+ tr->set_texture(presets->get_item_icon(pos));
+ drag->add_child(tr);
+ Label *label = memnew(Label);
+ label->set_text(presets->get_item_text(pos));
+ drag->add_child(label);
+
+ presets->set_drag_preview(drag);
+
+ return d;
+ }
+ }
+
+ return Variant();
+}
+
+bool ProjectExportDialog::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
+ if (p_from == presets) {
+ Dictionary d = p_data;
+ if (!d.has("type") || String(d["type"]) != "export_preset") {
+ return false;
+ }
+
+ if (presets->get_item_at_position(p_point, true) < 0 && !presets->is_pos_at_end_of_items(p_point)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void ProjectExportDialog::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
+ if (p_from == presets) {
+ Dictionary d = p_data;
+ int from_pos = d["preset"];
+
+ int to_pos = -1;
+
+ if (presets->get_item_at_position(p_point, true) >= 0) {
+ to_pos = presets->get_item_at_position(p_point, true);
+ }
+
+ if (to_pos == -1 && !presets->is_pos_at_end_of_items(p_point)) {
+ return;
+ }
+
+ if (to_pos == from_pos) {
+ return;
+ } else if (to_pos > from_pos) {
+ to_pos--;
+ }
+
+ Ref<EditorExportPreset> preset = EditorExport::get_singleton()->get_export_preset(from_pos);
+ EditorExport::get_singleton()->remove_export_preset(from_pos);
+ EditorExport::get_singleton()->add_export_preset(preset, to_pos);
+
+ _update_presets();
+ if (to_pos >= 0) {
+ _edit_preset(to_pos);
+ } else {
+ _edit_preset(presets->get_item_count() - 1);
+ }
+ }
+}
+
+void ProjectExportDialog::_export_type_changed(int p_which) {
+ if (updating) {
+ return;
+ }
+
+ Ref<EditorExportPreset> current = get_current_preset();
+ if (current.is_null()) {
+ return;
+ }
+
+ current->set_export_filter(EditorExportPreset::ExportFilter(p_which));
+ updating = true;
+ _fill_resource_tree();
+ updating = false;
+}
+
+void ProjectExportDialog::_filter_changed(const String &p_filter) {
+ if (updating) {
+ return;
+ }
+
+ Ref<EditorExportPreset> current = get_current_preset();
+ if (current.is_null()) {
+ return;
+ }
+
+ current->set_include_filter(include_filters->get_text());
+ current->set_exclude_filter(exclude_filters->get_text());
+}
+
+void ProjectExportDialog::_fill_resource_tree() {
+ include_files->clear();
+ include_label->hide();
+ include_margin->hide();
+
+ Ref<EditorExportPreset> current = get_current_preset();
+ if (current.is_null()) {
+ return;
+ }
+
+ EditorExportPreset::ExportFilter f = current->get_export_filter();
+
+ if (f == EditorExportPreset::EXPORT_ALL_RESOURCES) {
+ return;
+ }
+
+ include_label->show();
+ include_margin->show();
+
+ TreeItem *root = include_files->create_item();
+
+ _fill_tree(EditorFileSystem::get_singleton()->get_filesystem(), root, current, f == EditorExportPreset::EXPORT_SELECTED_SCENES);
+}
+
+bool ProjectExportDialog::_fill_tree(EditorFileSystemDirectory *p_dir, TreeItem *p_item, Ref<EditorExportPreset> &current, bool p_only_scenes) {
+ p_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
+ p_item->set_icon(0, presets->get_theme_icon(SNAME("folder"), SNAME("FileDialog")));
+ p_item->set_text(0, p_dir->get_name() + "/");
+ p_item->set_editable(0, true);
+ p_item->set_metadata(0, p_dir->get_path());
+
+ bool used = false;
+ for (int i = 0; i < p_dir->get_subdir_count(); i++) {
+ TreeItem *subdir = include_files->create_item(p_item);
+ if (_fill_tree(p_dir->get_subdir(i), subdir, current, p_only_scenes)) {
+ used = true;
+ } else {
+ memdelete(subdir);
+ }
+ }
+
+ for (int i = 0; i < p_dir->get_file_count(); i++) {
+ String type = p_dir->get_file_type(i);
+ if (p_only_scenes && type != "PackedScene") {
+ continue;
+ }
+ if (type == "TextFile") {
+ continue;
+ }
+
+ TreeItem *file = include_files->create_item(p_item);
+ file->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
+ file->set_text(0, p_dir->get_file(i));
+
+ String path = p_dir->get_file_path(i);
+
+ file->set_icon(0, EditorNode::get_singleton()->get_class_icon(type));
+ file->set_editable(0, true);
+ file->set_checked(0, current->has_export_file(path));
+ file->set_metadata(0, path);
+ file->propagate_check(0);
+
+ used = true;
+ }
+ return used;
+}
+
+void ProjectExportDialog::_tree_changed() {
+ if (updating) {
+ return;
+ }
+
+ Ref<EditorExportPreset> current = get_current_preset();
+ if (current.is_null()) {
+ return;
+ }
+
+ TreeItem *item = include_files->get_edited();
+ if (!item) {
+ return;
+ }
+
+ item->propagate_check(0);
+}
+
+void ProjectExportDialog::_check_propagated_to_item(Object *p_obj, int column) {
+ Ref<EditorExportPreset> current = get_current_preset();
+ if (current.is_null()) {
+ return;
+ }
+ TreeItem *item = Object::cast_to<TreeItem>(p_obj);
+ String path = item->get_metadata(0);
+ if (item && !path.ends_with("/")) {
+ bool added = item->is_checked(0);
+ if (added) {
+ current->add_export_file(path);
+ } else {
+ current->remove_export_file(path);
+ }
+ }
+}
+
+void ProjectExportDialog::_export_pck_zip() {
+ Ref<EditorExportPreset> current = get_current_preset();
+ ERR_FAIL_COND(current.is_null());
+
+ String dir = current->get_export_path().get_base_dir();
+ export_pck_zip->set_current_dir(dir);
+
+ export_pck_zip->popup_file_dialog();
+}
+
+void ProjectExportDialog::_export_pck_zip_selected(const String &p_path) {
+ Ref<EditorExportPreset> current = get_current_preset();
+ ERR_FAIL_COND(current.is_null());
+ Ref<EditorExportPlatform> platform = current->get_platform();
+ ERR_FAIL_COND(platform.is_null());
+
+ if (p_path.ends_with(".zip")) {
+ platform->export_zip(current, export_pck_zip_debug->is_pressed(), p_path);
+ } else if (p_path.ends_with(".pck")) {
+ platform->export_pack(current, export_pck_zip_debug->is_pressed(), p_path);
+ }
+}
+
+void ProjectExportDialog::_open_export_template_manager() {
+ EditorNode::get_singleton()->open_export_template_manager();
+ hide();
+}
+
+void ProjectExportDialog::_validate_export_path(const String &p_path) {
+ // Disable export via OK button or Enter key if LineEdit has an empty filename
+ bool invalid_path = (p_path.get_file().get_basename().is_empty());
+
+ // Check if state change before needlessly messing with signals
+ if (invalid_path && export_project->get_ok_button()->is_disabled()) {
+ return;
+ }
+ if (!invalid_path && !export_project->get_ok_button()->is_disabled()) {
+ return;
+ }
+
+ if (invalid_path) {
+ export_project->get_ok_button()->set_disabled(true);
+ export_project->get_line_edit()->disconnect("text_submitted", Callable(export_project, "_file_submitted"));
+ } else {
+ export_project->get_ok_button()->set_disabled(false);
+ export_project->get_line_edit()->connect("text_submitted", Callable(export_project, "_file_submitted"));
+ }
+}
+
+void ProjectExportDialog::_export_project() {
+ Ref<EditorExportPreset> current = get_current_preset();
+ ERR_FAIL_COND(current.is_null());
+ Ref<EditorExportPlatform> platform = current->get_platform();
+ ERR_FAIL_COND(platform.is_null());
+
+ export_project->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
+ export_project->clear_filters();
+
+ List<String> extension_list = platform->get_binary_extensions(current);
+ for (int i = 0; i < extension_list.size(); i++) {
+ // TRANSLATORS: This is the name of a project export file format. %s will be replaced by the platform name.
+ export_project->add_filter("*." + extension_list[i], vformat(TTR("%s Export"), platform->get_name()));
+ }
+
+ if (!current->get_export_path().is_empty()) {
+ export_project->set_current_path(current->get_export_path());
+ } else {
+ if (extension_list.size() >= 1) {
+ export_project->set_current_file(default_filename + "." + extension_list[0]);
+ } else {
+ export_project->set_current_file(default_filename);
+ }
+ }
+
+ // Ensure that signal is connected if previous attempt left it disconnected
+ // with _validate_export_path.
+ // FIXME: This is a hack, we should instead change EditorFileDialog to allow
+ // disabling validation by the "text_submitted" signal.
+ if (!export_project->get_line_edit()->is_connected("text_submitted", Callable(export_project, "_file_submitted"))) {
+ export_project->get_ok_button()->set_disabled(false);
+ export_project->get_line_edit()->connect("text_submitted", Callable(export_project, "_file_submitted"));
+ }
+
+ export_project->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
+ export_project->popup_file_dialog();
+}
+
+void ProjectExportDialog::_export_project_to_path(const String &p_path) {
+ // Save this name for use in future exports (but drop the file extension)
+ default_filename = p_path.get_file().get_basename();
+ EditorSettings::get_singleton()->set_project_metadata("export_options", "default_filename", default_filename);
+
+ Ref<EditorExportPreset> current = get_current_preset();
+ ERR_FAIL_COND(current.is_null());
+ Ref<EditorExportPlatform> platform = current->get_platform();
+ ERR_FAIL_COND(platform.is_null());
+ current->set_export_path(p_path);
+
+ platform->clear_messages();
+ Error err = platform->export_project(current, export_debug->is_pressed(), p_path, 0);
+ result_dialog_log->clear();
+ if (err != ERR_SKIP) {
+ if (platform->fill_log_messages(result_dialog_log, err)) {
+ result_dialog->popup_centered_ratio(0.5);
+ }
+ }
+}
+
+void ProjectExportDialog::_export_all_dialog() {
+ export_all_dialog->show();
+ export_all_dialog->popup_centered(Size2(300, 80));
+}
+
+void ProjectExportDialog::_export_all_dialog_action(const String &p_str) {
+ export_all_dialog->hide();
+
+ _export_all(p_str != "release");
+}
+
+void ProjectExportDialog::_export_all(bool p_debug) {
+ String mode = p_debug ? TTR("Debug") : TTR("Release");
+ EditorProgress ep("exportall", TTR("Exporting All") + " " + mode, EditorExport::get_singleton()->get_export_preset_count(), true);
+
+ bool show_dialog = false;
+ result_dialog_log->clear();
+ for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) {
+ Ref<EditorExportPreset> preset = EditorExport::get_singleton()->get_export_preset(i);
+ ERR_FAIL_COND(preset.is_null());
+ Ref<EditorExportPlatform> platform = preset->get_platform();
+ ERR_FAIL_COND(platform.is_null());
+
+ ep.step(preset->get_name(), i);
+
+ platform->clear_messages();
+ Error err = platform->export_project(preset, p_debug, preset->get_export_path(), 0);
+ if (err == ERR_SKIP) {
+ return;
+ }
+ bool has_messages = platform->fill_log_messages(result_dialog_log, err);
+ show_dialog = show_dialog || has_messages;
+ }
+ if (show_dialog) {
+ result_dialog->popup_centered_ratio(0.5);
+ }
+}
+
+void ProjectExportDialog::_bind_methods() {
+ ClassDB::bind_method("_get_drag_data_fw", &ProjectExportDialog::get_drag_data_fw);
+ ClassDB::bind_method("_can_drop_data_fw", &ProjectExportDialog::can_drop_data_fw);
+ ClassDB::bind_method("_drop_data_fw", &ProjectExportDialog::drop_data_fw);
+ ClassDB::bind_method("_export_all", &ProjectExportDialog::_export_all);
+ ClassDB::bind_method("set_export_path", &ProjectExportDialog::set_export_path);
+ ClassDB::bind_method("get_export_path", &ProjectExportDialog::get_export_path);
+ ClassDB::bind_method("get_current_preset", &ProjectExportDialog::get_current_preset);
+
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "export_path"), "set_export_path", "get_export_path");
+}
+
+ProjectExportDialog::ProjectExportDialog() {
+ set_title(TTR("Export"));
+
+ VBoxContainer *main_vb = memnew(VBoxContainer);
+ main_vb->connect("theme_changed", callable_mp(this, &ProjectExportDialog::_theme_changed));
+ add_child(main_vb);
+ HSplitContainer *hbox = memnew(HSplitContainer);
+ main_vb->add_child(hbox);
+ hbox->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+
+ // Presets list.
+
+ VBoxContainer *preset_vb = memnew(VBoxContainer);
+ preset_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ hbox->add_child(preset_vb);
+
+ Label *l = memnew(Label(TTR("Presets")));
+ l->set_theme_type_variation("HeaderSmall");
+
+ HBoxContainer *preset_hb = memnew(HBoxContainer);
+ preset_hb->add_child(l);
+ preset_hb->add_spacer();
+ preset_vb->add_child(preset_hb);
+
+ add_preset = memnew(MenuButton);
+ add_preset->set_text(TTR("Add..."));
+ add_preset->get_popup()->connect("index_pressed", callable_mp(this, &ProjectExportDialog::_add_preset));
+ preset_hb->add_child(add_preset);
+ MarginContainer *mc = memnew(MarginContainer);
+ preset_vb->add_child(mc);
+ mc->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ presets = memnew(ItemList);
+#ifndef _MSC_VER
+#warning must reimplement drag forward
+#endif
+ //presets->set_drag_forwarding(this);
+ mc->add_child(presets);
+ presets->connect("item_selected", callable_mp(this, &ProjectExportDialog::_edit_preset));
+ duplicate_preset = memnew(Button);
+ duplicate_preset->set_tooltip(TTR("Duplicate"));
+ duplicate_preset->set_flat(true);
+ preset_hb->add_child(duplicate_preset);
+ duplicate_preset->connect("pressed", callable_mp(this, &ProjectExportDialog::_duplicate_preset));
+ delete_preset = memnew(Button);
+ delete_preset->set_tooltip(TTR("Delete"));
+ delete_preset->set_flat(true);
+ preset_hb->add_child(delete_preset);
+ delete_preset->connect("pressed", callable_mp(this, &ProjectExportDialog::_delete_preset));
+
+ // Preset settings.
+
+ VBoxContainer *settings_vb = memnew(VBoxContainer);
+ settings_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ hbox->add_child(settings_vb);
+
+ name = memnew(LineEdit);
+ settings_vb->add_margin_child(TTR("Name:"), name);
+ name->connect("text_changed", callable_mp(this, &ProjectExportDialog::_name_changed));
+ runnable = memnew(CheckButton);
+ runnable->set_text(TTR("Runnable"));
+ runnable->set_tooltip(TTR("If checked, the preset will be available for use in one-click deploy.\nOnly one preset per platform may be marked as runnable."));
+ runnable->connect("pressed", callable_mp(this, &ProjectExportDialog::_runnable_pressed));
+ settings_vb->add_child(runnable);
+
+ export_path = memnew(EditorPropertyPath);
+ settings_vb->add_child(export_path);
+ export_path->set_label(TTR("Export Path"));
+ export_path->set_object_and_property(this, "export_path");
+ export_path->set_save_mode();
+ export_path->connect("property_changed", callable_mp(this, &ProjectExportDialog::_export_path_changed));
+
+ // Subsections.
+
+ sections = memnew(TabContainer);
+ sections->set_use_hidden_tabs_for_min_size(true);
+ sections->set_theme_type_variation("TabContainerOdd");
+ settings_vb->add_child(sections);
+ sections->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+
+ // Main preset parameters.
+
+ parameters = memnew(EditorInspector);
+ sections->add_child(parameters);
+ parameters->set_name(TTR("Options"));
+ parameters->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ parameters->set_property_name_style(EditorPropertyNameProcessor::get_settings_style());
+ parameters->connect("property_edited", callable_mp(this, &ProjectExportDialog::_update_parameters));
+ EditorExport::get_singleton()->connect("export_presets_updated", callable_mp(this, &ProjectExportDialog::_force_update_current_preset_parameters));
+
+ // Resources export parameters.
+
+ VBoxContainer *resources_vb = memnew(VBoxContainer);
+ sections->add_child(resources_vb);
+ resources_vb->set_name(TTR("Resources"));
+
+ export_filter = memnew(OptionButton);
+ export_filter->add_item(TTR("Export all resources in the project"));
+ export_filter->add_item(TTR("Export selected scenes (and dependencies)"));
+ export_filter->add_item(TTR("Export selected resources (and dependencies)"));
+ export_filter->add_item(TTR("Export all resources in the project except resources checked below"));
+ resources_vb->add_margin_child(TTR("Export Mode:"), export_filter);
+ export_filter->connect("item_selected", callable_mp(this, &ProjectExportDialog::_export_type_changed));
+
+ include_label = memnew(Label);
+ include_label->set_text(TTR("Resources to export:"));
+ resources_vb->add_child(include_label);
+ include_margin = memnew(MarginContainer);
+ include_margin->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ resources_vb->add_child(include_margin);
+
+ include_files = memnew(Tree);
+ include_margin->add_child(include_files);
+ include_files->connect("item_edited", callable_mp(this, &ProjectExportDialog::_tree_changed));
+ include_files->connect("check_propagated_to_item", callable_mp(this, &ProjectExportDialog::_check_propagated_to_item));
+
+ include_filters = memnew(LineEdit);
+ resources_vb->add_margin_child(
+ TTR("Filters to export non-resource files/folders\n(comma-separated, e.g: *.json, *.txt, docs/*)"),
+ include_filters);
+ include_filters->connect("text_changed", callable_mp(this, &ProjectExportDialog::_filter_changed));
+
+ exclude_filters = memnew(LineEdit);
+ resources_vb->add_margin_child(
+ TTR("Filters to exclude files/folders from project\n(comma-separated, e.g: *.json, *.txt, docs/*)"),
+ exclude_filters);
+ exclude_filters->connect("text_changed", callable_mp(this, &ProjectExportDialog::_filter_changed));
+
+ script_mode = memnew(OptionButton);
+ resources_vb->add_margin_child(TTR("GDScript Export Mode:"), script_mode);
+ script_mode->add_item(TTR("Text"), (int)EditorExportPreset::MODE_SCRIPT_TEXT);
+ script_mode->add_item(TTR("Compiled Bytecode (Faster Loading)"), (int)EditorExportPreset::MODE_SCRIPT_COMPILED);
+ script_mode->connect("item_selected", callable_mp(this, &ProjectExportDialog::_script_export_mode_changed));
+
+ // Feature tags.
+
+ VBoxContainer *feature_vb = memnew(VBoxContainer);
+ feature_vb->set_name(TTR("Features"));
+ custom_features = memnew(LineEdit);
+ custom_features->connect("text_changed", callable_mp(this, &ProjectExportDialog::_custom_features_changed));
+ feature_vb->add_margin_child(TTR("Custom (comma-separated):"), custom_features);
+ custom_feature_display = memnew(RichTextLabel);
+ custom_feature_display->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ feature_vb->add_margin_child(TTR("Feature List:"), custom_feature_display, true);
+ sections->add_child(feature_vb);
+
+ // Script export parameters.
+
+ VBoxContainer *sec_vb = memnew(VBoxContainer);
+ sec_vb->set_name(TTR("Encryption"));
+
+ enc_pck = memnew(CheckButton);
+ enc_pck->connect("toggled", callable_mp(this, &ProjectExportDialog::_enc_pck_changed));
+ enc_pck->set_text(TTR("Encrypt Exported PCK"));
+ sec_vb->add_child(enc_pck);
+
+ enc_directory = memnew(CheckButton);
+ enc_directory->connect("toggled", callable_mp(this, &ProjectExportDialog::_enc_directory_changed));
+ enc_directory->set_text(TTR("Encrypt Index (File Names and Info)"));
+ sec_vb->add_child(enc_directory);
+
+ enc_in_filters = memnew(LineEdit);
+ enc_in_filters->connect("text_changed", callable_mp(this, &ProjectExportDialog::_enc_filters_changed));
+ sec_vb->add_margin_child(
+ TTR("Filters to include files/folders\n(comma-separated, e.g: *.tscn, *.tres, scenes/*)"),
+ enc_in_filters);
+
+ enc_ex_filters = memnew(LineEdit);
+ enc_ex_filters->connect("text_changed", callable_mp(this, &ProjectExportDialog::_enc_filters_changed));
+ sec_vb->add_margin_child(
+ TTR("Filters to exclude files/folders\n(comma-separated, e.g: *.ctex, *.import, music/*)"),
+ enc_ex_filters);
+
+ script_key = memnew(LineEdit);
+ script_key->connect("text_changed", callable_mp(this, &ProjectExportDialog::_script_encryption_key_changed));
+ script_key_error = memnew(Label);
+ script_key_error->set_text(String::utf8("• ") + TTR("Invalid Encryption Key (must be 64 hexadecimal characters long)"));
+ script_key_error->add_theme_color_override("font_color", EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("error_color"), SNAME("Editor")));
+ sec_vb->add_margin_child(TTR("Encryption Key (256-bits as hexadecimal):"), script_key);
+ sec_vb->add_child(script_key_error);
+ sections->add_child(sec_vb);
+
+ Label *sec_info = memnew(Label);
+ sec_info->set_text(TTR("Note: Encryption key needs to be stored in the binary,\nyou need to build the export templates from source."));
+ sec_vb->add_child(sec_info);
+
+ LinkButton *sec_more_info = memnew(LinkButton);
+ sec_more_info->set_text(TTR("More Info..."));
+ sec_more_info->connect("pressed", callable_mp(this, &ProjectExportDialog::_open_key_help_link));
+ sec_vb->add_child(sec_more_info);
+
+ sections->connect("tab_changed", callable_mp(this, &ProjectExportDialog::_tab_changed));
+
+ // Disable by default.
+ name->set_editable(false);
+ export_path->hide();
+ runnable->set_disabled(true);
+ duplicate_preset->set_disabled(true);
+ delete_preset->set_disabled(true);
+ script_key_error->hide();
+ sections->hide();
+ parameters->edit(nullptr);
+
+ // Deletion dialog.
+
+ delete_confirm = memnew(ConfirmationDialog);
+ add_child(delete_confirm);
+ delete_confirm->set_ok_button_text(TTR("Delete"));
+ delete_confirm->connect("confirmed", callable_mp(this, &ProjectExportDialog::_delete_preset_confirm));
+
+ // Export buttons, dialogs and errors.
+
+ set_cancel_button_text(TTR("Close"));
+ set_ok_button_text(TTR("Export PCK/ZIP..."));
+ export_button = add_button(TTR("Export Project..."), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "export");
+ export_button->connect("pressed", callable_mp(this, &ProjectExportDialog::_export_project));
+ // Disable initially before we select a valid preset
+ export_button->set_disabled(true);
+ get_ok_button()->set_disabled(true);
+
+ export_all_dialog = memnew(ConfirmationDialog);
+ add_child(export_all_dialog);
+ export_all_dialog->set_title(TTR("Export All"));
+ export_all_dialog->set_text(TTR("Choose an export mode:"));
+ export_all_dialog->get_ok_button()->hide();
+ export_all_dialog->add_button(TTR("Debug"), true, "debug");
+ export_all_dialog->add_button(TTR("Release"), true, "release");
+ export_all_dialog->connect("custom_action", callable_mp(this, &ProjectExportDialog::_export_all_dialog_action));
+
+ export_all_button = add_button(TTR("Export All..."), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "export");
+ export_all_button->connect("pressed", callable_mp(this, &ProjectExportDialog::_export_all_dialog));
+ export_all_button->set_disabled(true);
+
+ export_pck_zip = memnew(EditorFileDialog);
+ export_pck_zip->add_filter("*.zip", TTR("ZIP File"));
+ export_pck_zip->add_filter("*.pck", TTR("Godot Project Pack"));
+ export_pck_zip->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
+ export_pck_zip->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
+ add_child(export_pck_zip);
+ export_pck_zip->connect("file_selected", callable_mp(this, &ProjectExportDialog::_export_pck_zip_selected));
+
+ export_error = memnew(Label);
+ main_vb->add_child(export_error);
+ export_error->hide();
+ export_error->add_theme_color_override("font_color", EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("error_color"), SNAME("Editor")));
+
+ export_warning = memnew(Label);
+ main_vb->add_child(export_warning);
+ export_warning->hide();
+ export_warning->add_theme_color_override("font_color", EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("warning_color"), SNAME("Editor")));
+
+ export_templates_error = memnew(HBoxContainer);
+ main_vb->add_child(export_templates_error);
+ export_templates_error->hide();
+
+ Label *export_error2 = memnew(Label);
+ export_templates_error->add_child(export_error2);
+ export_error2->add_theme_color_override("font_color", EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("error_color"), SNAME("Editor")));
+ export_error2->set_text(String::utf8("• ") + TTR("Export templates for this platform are missing:") + " ");
+
+ result_dialog = memnew(AcceptDialog);
+ result_dialog->set_title(TTR("Project Export"));
+ result_dialog_log = memnew(RichTextLabel);
+ result_dialog_log->set_custom_minimum_size(Size2(300, 80) * EDSCALE);
+ result_dialog->add_child(result_dialog_log);
+
+ main_vb->add_child(result_dialog);
+ result_dialog->hide();
+
+ LinkButton *download_templates = memnew(LinkButton);
+ download_templates->set_text(TTR("Manage Export Templates"));
+ download_templates->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
+ export_templates_error->add_child(download_templates);
+ download_templates->connect("pressed", callable_mp(this, &ProjectExportDialog::_open_export_template_manager));
+
+ export_project = memnew(EditorFileDialog);
+ export_project->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
+ add_child(export_project);
+ export_project->connect("file_selected", callable_mp(this, &ProjectExportDialog::_export_project_to_path));
+ export_project->get_line_edit()->connect("text_changed", callable_mp(this, &ProjectExportDialog::_validate_export_path));
+
+ export_debug = memnew(CheckBox);
+ export_debug->set_text(TTR("Export With Debug"));
+ export_debug->set_pressed(true);
+ export_debug->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
+ export_project->get_vbox()->add_child(export_debug);
+
+ export_pck_zip_debug = memnew(CheckBox);
+ export_pck_zip_debug->set_text(TTR("Export With Debug"));
+ export_pck_zip_debug->set_pressed(true);
+ export_pck_zip_debug->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
+ export_pck_zip->get_vbox()->add_child(export_pck_zip_debug);
+
+ set_hide_on_ok(false);
+
+ default_filename = EditorSettings::get_singleton()->get_project_metadata("export_options", "default_filename", "");
+ // If no default set, use project name
+ if (default_filename.is_empty()) {
+ // If no project name defined, use a sane default
+ default_filename = ProjectSettings::get_singleton()->get("application/config/name");
+ if (default_filename.is_empty()) {
+ default_filename = "UnnamedProject";
+ }
+ }
+}
+
+ProjectExportDialog::~ProjectExportDialog() {
+}
diff --git a/editor/export/project_export.h b/editor/export/project_export.h
new file mode 100644
index 0000000000..96dd765a2c
--- /dev/null
+++ b/editor/export/project_export.h
@@ -0,0 +1,180 @@
+/*************************************************************************/
+/* project_export.h */
+/*************************************************************************/
+/* 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. */
+/*************************************************************************/
+
+#ifndef PROJECT_EXPORT_H
+#define PROJECT_EXPORT_H
+
+#include "scene/gui/dialogs.h"
+
+class CheckBox;
+class CheckButton;
+class EditorExportPreset;
+class EditorFileDialog;
+class EditorFileSystemDirectory;
+class EditorInspector;
+class EditorPropertyPath;
+class ItemList;
+class MenuButton;
+class OptionButton;
+class RichTextLabel;
+class TabContainer;
+class Tree;
+class TreeItem;
+
+class ProjectExportDialog : public ConfirmationDialog {
+ GDCLASS(ProjectExportDialog, ConfirmationDialog);
+
+private:
+ TabContainer *sections = nullptr;
+
+ MenuButton *add_preset = nullptr;
+ Button *duplicate_preset = nullptr;
+ Button *delete_preset = nullptr;
+ ItemList *presets = nullptr;
+
+ LineEdit *name = nullptr;
+ EditorPropertyPath *export_path = nullptr;
+ EditorInspector *parameters = nullptr;
+ CheckButton *runnable = nullptr;
+
+ Button *button_export = nullptr;
+ bool updating = false;
+
+ RichTextLabel *result_dialog_log = nullptr;
+ AcceptDialog *result_dialog = nullptr;
+ ConfirmationDialog *delete_confirm = nullptr;
+
+ OptionButton *export_filter = nullptr;
+ LineEdit *include_filters = nullptr;
+ LineEdit *exclude_filters = nullptr;
+ Tree *include_files = nullptr;
+
+ Label *include_label = nullptr;
+ MarginContainer *include_margin = nullptr;
+
+ Button *export_button = nullptr;
+ Button *export_all_button = nullptr;
+ AcceptDialog *export_all_dialog = nullptr;
+
+ LineEdit *custom_features = nullptr;
+ RichTextLabel *custom_feature_display = nullptr;
+
+ OptionButton *script_mode = nullptr;
+ LineEdit *script_key = nullptr;
+ Label *script_key_error = nullptr;
+
+ Label *export_error = nullptr;
+ Label *export_warning = nullptr;
+ HBoxContainer *export_templates_error = nullptr;
+
+ String default_filename;
+
+ void _runnable_pressed();
+ void _update_parameters(const String &p_edited_property);
+ void _name_changed(const String &p_string);
+ void _export_path_changed(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing);
+ void _add_preset(int p_platform);
+ void _edit_preset(int p_index);
+ void _duplicate_preset();
+ void _delete_preset();
+ void _delete_preset_confirm();
+ void _update_export_all();
+
+ void _force_update_current_preset_parameters();
+ void _update_current_preset();
+ void _update_presets();
+
+ void _export_type_changed(int p_which);
+ void _filter_changed(const String &p_filter);
+ void _fill_resource_tree();
+ bool _fill_tree(EditorFileSystemDirectory *p_dir, TreeItem *p_item, Ref<EditorExportPreset> &current, bool p_only_scenes);
+ void _tree_changed();
+ void _check_propagated_to_item(Object *p_obj, int column);
+
+ Variant get_drag_data_fw(const Point2 &p_point, Control *p_from);
+ bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
+ void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
+
+ EditorFileDialog *export_pck_zip = nullptr;
+ EditorFileDialog *export_project = nullptr;
+ CheckBox *export_debug = nullptr;
+ CheckBox *export_pck_zip_debug = nullptr;
+
+ CheckButton *enc_pck = nullptr;
+ CheckButton *enc_directory = nullptr;
+ LineEdit *enc_in_filters = nullptr;
+ LineEdit *enc_ex_filters = nullptr;
+
+ void _open_export_template_manager();
+
+ void _export_pck_zip();
+ void _export_pck_zip_selected(const String &p_path);
+
+ void _validate_export_path(const String &p_path);
+ void _export_project();
+ void _export_project_to_path(const String &p_path);
+ void _export_all_dialog();
+ void _export_all_dialog_action(const String &p_str);
+ void _export_all(bool p_debug);
+
+ void _update_feature_list();
+ void _custom_features_changed(const String &p_text);
+
+ bool updating_script_key = false;
+ bool updating_enc_filters = false;
+ void _enc_pck_changed(bool p_pressed);
+ void _enc_directory_changed(bool p_pressed);
+ void _enc_filters_changed(const String &p_text);
+ void _script_export_mode_changed(int p_mode);
+ void _script_encryption_key_changed(const String &p_key);
+ bool _validate_script_encryption_key(const String &p_key);
+
+ void _open_key_help_link();
+
+ void _tab_changed(int);
+
+protected:
+ void _theme_changed();
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ void popup_export();
+
+ void set_export_path(const String &p_value);
+ String get_export_path();
+
+ Ref<EditorExportPreset> get_current_preset() const;
+
+ ProjectExportDialog();
+ ~ProjectExportDialog();
+};
+
+#endif // PROJECT_EXPORT_H