export_template_manager.cpp 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171
  1. /**************************************************************************/
  2. /* export_template_manager.cpp */
  3. /**************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /**************************************************************************/
  8. /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
  9. /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /**************************************************************************/
  30. #include "export_template_manager.h"
  31. #include "core/io/dir_access.h"
  32. #include "core/io/json.h"
  33. #include "core/io/zip_io.h"
  34. #include "core/version.h"
  35. #include "editor/editor_node.h"
  36. #include "editor/editor_string_names.h"
  37. #include "editor/export/editor_export_preset.h"
  38. #include "editor/file_system/editor_file_system.h"
  39. #include "editor/file_system/editor_paths.h"
  40. #include "editor/gui/progress_dialog.h"
  41. #include "editor/settings/editor_settings.h"
  42. #include "editor/themes/editor_scale.h"
  43. #include "scene/gui/file_dialog.h"
  44. #include "scene/gui/line_edit.h"
  45. #include "scene/gui/link_button.h"
  46. #include "scene/gui/menu_button.h"
  47. #include "scene/gui/option_button.h"
  48. #include "scene/gui/separator.h"
  49. #include "scene/gui/tree.h"
  50. #include "scene/main/http_request.h"
  51. enum DownloadsAvailability {
  52. DOWNLOADS_AVAILABLE,
  53. DOWNLOADS_NOT_AVAILABLE_IN_OFFLINE_MODE,
  54. DOWNLOADS_NOT_AVAILABLE_FOR_DEV_BUILDS,
  55. DOWNLOADS_NOT_AVAILABLE_FOR_DOUBLE_BUILDS,
  56. };
  57. static DownloadsAvailability _get_downloads_availability() {
  58. const int network_mode = EDITOR_GET("network/connection/network_mode");
  59. // Downloadable export templates are only available for stable and official alpha/beta/RC builds
  60. // (which always have a number following their status, e.g. "alpha1").
  61. // Therefore, don't display download-related features when using a development version
  62. // (whose builds aren't numbered).
  63. if (String(GODOT_VERSION_STATUS) == String("dev") ||
  64. String(GODOT_VERSION_STATUS) == String("alpha") ||
  65. String(GODOT_VERSION_STATUS) == String("beta") ||
  66. String(GODOT_VERSION_STATUS) == String("rc")) {
  67. return DOWNLOADS_NOT_AVAILABLE_FOR_DEV_BUILDS;
  68. }
  69. #ifdef REAL_T_IS_DOUBLE
  70. return DOWNLOADS_NOT_AVAILABLE_FOR_DOUBLE_BUILDS;
  71. #endif
  72. if (network_mode == EditorSettings::NETWORK_OFFLINE) {
  73. return DOWNLOADS_NOT_AVAILABLE_IN_OFFLINE_MODE;
  74. }
  75. return DOWNLOADS_AVAILABLE;
  76. }
  77. void ExportTemplateManager::_update_template_status() {
  78. // Fetch installed templates from the file system.
  79. Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
  80. const String &templates_dir = EditorPaths::get_singleton()->get_export_templates_dir();
  81. Error err = da->change_dir(templates_dir);
  82. ERR_FAIL_COND_MSG(err != OK, "Could not access templates directory at '" + templates_dir + "'.");
  83. RBSet<String> templates;
  84. da->list_dir_begin();
  85. if (err == OK) {
  86. String c = da->get_next();
  87. while (!c.is_empty()) {
  88. if (da->current_is_dir() && !c.begins_with(".")) {
  89. templates.insert(c);
  90. }
  91. c = da->get_next();
  92. }
  93. }
  94. da->list_dir_end();
  95. // Update the state of the current version.
  96. String current_version = GODOT_VERSION_FULL_CONFIG;
  97. current_value->set_text(current_version);
  98. if (templates.has(current_version)) {
  99. current_missing_label->hide();
  100. current_installed_label->show();
  101. current_installed_hb->show();
  102. current_version_exists = true;
  103. } else {
  104. current_installed_label->hide();
  105. current_missing_label->show();
  106. current_installed_hb->hide();
  107. current_version_exists = false;
  108. }
  109. if (is_downloading_templates) {
  110. install_options_vb->hide();
  111. download_progress_hb->show();
  112. } else {
  113. download_progress_hb->hide();
  114. install_options_vb->show();
  115. if (templates.has(current_version)) {
  116. current_installed_path->set_text(templates_dir.path_join(current_version));
  117. }
  118. }
  119. // Update the list of other installed versions.
  120. installed_table->clear();
  121. TreeItem *installed_root = installed_table->create_item();
  122. for (RBSet<String>::Element *E = templates.back(); E; E = E->prev()) {
  123. String version_string = E->get();
  124. if (version_string == current_version) {
  125. continue;
  126. }
  127. TreeItem *ti = installed_table->create_item(installed_root);
  128. ti->set_text(0, version_string);
  129. #ifndef ANDROID_ENABLED
  130. ti->add_button(0, get_editor_theme_icon(SNAME("Folder")), OPEN_TEMPLATE_FOLDER, false, TTR("Open the folder containing these templates."));
  131. #endif
  132. ti->add_button(0, get_editor_theme_icon(SNAME("Remove")), UNINSTALL_TEMPLATE, false, TTR("Uninstall these templates."));
  133. }
  134. }
  135. void ExportTemplateManager::_download_current() {
  136. if (is_downloading_templates) {
  137. return;
  138. }
  139. is_downloading_templates = true;
  140. install_options_vb->hide();
  141. download_progress_hb->show();
  142. if (mirrors_available) {
  143. String mirror_url = _get_selected_mirror();
  144. if (mirror_url.is_empty()) {
  145. _set_current_progress_status(TTR("There are no mirrors available."), true);
  146. return;
  147. }
  148. _download_template(mirror_url, true);
  149. } else if (!is_refreshing_mirrors) {
  150. _set_current_progress_status(TTR("Retrieving the mirror list..."));
  151. _refresh_mirrors();
  152. }
  153. }
  154. void ExportTemplateManager::_download_template(const String &p_url, bool p_skip_check) {
  155. if (!p_skip_check && is_downloading_templates) {
  156. return;
  157. }
  158. is_downloading_templates = true;
  159. install_options_vb->hide();
  160. download_progress_hb->show();
  161. download_progress_bar->show();
  162. download_progress_bar->set_indeterminate(true);
  163. _set_current_progress_status(TTR("Starting the download..."));
  164. download_templates->set_download_file(EditorPaths::get_singleton()->get_cache_dir().path_join("tmp_templates.tpz"));
  165. download_templates->set_use_threads(true);
  166. const String proxy_host = EDITOR_GET("network/http_proxy/host");
  167. const int proxy_port = EDITOR_GET("network/http_proxy/port");
  168. download_templates->set_http_proxy(proxy_host, proxy_port);
  169. download_templates->set_https_proxy(proxy_host, proxy_port);
  170. Error err = download_templates->request(p_url);
  171. if (err != OK) {
  172. _set_current_progress_status(TTR("Error requesting URL:") + " " + p_url, true);
  173. download_progress_hb->hide();
  174. return;
  175. }
  176. set_process(true);
  177. _set_current_progress_status(TTR("Connecting to the mirror..."));
  178. }
  179. void ExportTemplateManager::_download_template_completed(int p_status, int p_code, const PackedStringArray &headers, const PackedByteArray &p_data) {
  180. switch (p_status) {
  181. case HTTPRequest::RESULT_CANT_RESOLVE: {
  182. _set_current_progress_status(TTR("Can't resolve the requested address."), true);
  183. } break;
  184. case HTTPRequest::RESULT_BODY_SIZE_LIMIT_EXCEEDED:
  185. case HTTPRequest::RESULT_CONNECTION_ERROR:
  186. case HTTPRequest::RESULT_CHUNKED_BODY_SIZE_MISMATCH:
  187. case HTTPRequest::RESULT_TLS_HANDSHAKE_ERROR:
  188. case HTTPRequest::RESULT_CANT_CONNECT: {
  189. _set_current_progress_status(TTR("Can't connect to the mirror."), true);
  190. } break;
  191. case HTTPRequest::RESULT_NO_RESPONSE: {
  192. _set_current_progress_status(TTR("No response from the mirror."), true);
  193. } break;
  194. case HTTPRequest::RESULT_REQUEST_FAILED: {
  195. _set_current_progress_status(TTR("Request failed."), true);
  196. } break;
  197. case HTTPRequest::RESULT_REDIRECT_LIMIT_REACHED: {
  198. _set_current_progress_status(TTR("Request ended up in a redirect loop."), true);
  199. } break;
  200. default: {
  201. if (p_code != 200) {
  202. _set_current_progress_status(TTR("Request failed:") + " " + itos(p_code), true);
  203. } else {
  204. _set_current_progress_status(TTR("Download complete; extracting templates..."));
  205. String path = download_templates->get_download_file();
  206. is_downloading_templates = false;
  207. bool ret = _install_file_selected(path, true);
  208. if (ret) {
  209. // Clean up downloaded file.
  210. Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
  211. Error err = da->remove(path);
  212. if (err != OK) {
  213. EditorNode::get_singleton()->add_io_error(TTR("Cannot remove temporary file:") + "\n" + path + "\n");
  214. }
  215. } else {
  216. EditorNode::get_singleton()->add_io_error(vformat(TTR("Templates installation failed.\nThe problematic templates archives can be found at '%s'."), path));
  217. }
  218. }
  219. } break;
  220. }
  221. set_process(false);
  222. }
  223. void ExportTemplateManager::_cancel_template_download() {
  224. if (!is_downloading_templates) {
  225. return;
  226. }
  227. download_templates->cancel_request();
  228. download_progress_hb->hide();
  229. install_options_vb->show();
  230. is_downloading_templates = false;
  231. }
  232. void ExportTemplateManager::_refresh_mirrors() {
  233. if (is_refreshing_mirrors) {
  234. return;
  235. }
  236. is_refreshing_mirrors = true;
  237. String current_version = GODOT_VERSION_FULL_CONFIG;
  238. const String mirrors_metadata_url = "https://godotengine.org/mirrorlist/" + current_version + ".json";
  239. request_mirrors->request(mirrors_metadata_url);
  240. }
  241. void ExportTemplateManager::_refresh_mirrors_completed(int p_status, int p_code, const PackedStringArray &headers, const PackedByteArray &p_data) {
  242. if (p_status != HTTPRequest::RESULT_SUCCESS || p_code != 200) {
  243. EditorNode::get_singleton()->show_warning(TTR("Error getting the list of mirrors."));
  244. is_refreshing_mirrors = false;
  245. if (is_downloading_templates) {
  246. _cancel_template_download();
  247. }
  248. return;
  249. }
  250. String response_json = String::utf8((const char *)p_data.ptr(), p_data.size());
  251. JSON json;
  252. Error err = json.parse(response_json);
  253. if (err != OK) {
  254. EditorNode::get_singleton()->show_warning(TTR("Error parsing JSON with the list of mirrors. Please report this issue!"));
  255. is_refreshing_mirrors = false;
  256. if (is_downloading_templates) {
  257. _cancel_template_download();
  258. }
  259. return;
  260. }
  261. mirrors_list->clear();
  262. mirrors_list->add_item(TTR("Best available mirror"), 0);
  263. mirrors_available = false;
  264. Dictionary mirror_data = json.get_data();
  265. if (mirror_data.has("mirrors")) {
  266. Array mirrors = mirror_data["mirrors"];
  267. for (int i = 0; i < mirrors.size(); i++) {
  268. Dictionary m = mirrors[i];
  269. ERR_CONTINUE(!m.has("url") || !m.has("name"));
  270. mirrors_list->add_item(m["name"]);
  271. mirrors_list->set_item_metadata(i + 1, m["url"]);
  272. mirrors_available = true;
  273. }
  274. }
  275. if (!mirrors_available) {
  276. EditorNode::get_singleton()->show_warning(TTR("No download links found for this version. Direct download is only available for official releases."));
  277. if (is_downloading_templates) {
  278. _cancel_template_download();
  279. }
  280. }
  281. is_refreshing_mirrors = false;
  282. if (is_downloading_templates) {
  283. String mirror_url = _get_selected_mirror();
  284. if (mirror_url.is_empty()) {
  285. _set_current_progress_status(TTR("There are no mirrors available."), true);
  286. return;
  287. }
  288. _download_template(mirror_url, true);
  289. }
  290. }
  291. void ExportTemplateManager::_force_online_mode() {
  292. EditorSettings::get_singleton()->set_setting("network/connection/network_mode", EditorSettings::NETWORK_ONLINE);
  293. EditorSettings::get_singleton()->notify_changes();
  294. EditorSettings::get_singleton()->save();
  295. popup_manager();
  296. }
  297. bool ExportTemplateManager::_humanize_http_status(HTTPRequest *p_request, String *r_status, int *r_downloaded_bytes, int *r_total_bytes) {
  298. *r_status = "";
  299. *r_downloaded_bytes = -1;
  300. *r_total_bytes = -1;
  301. bool success = true;
  302. switch (p_request->get_http_client_status()) {
  303. case HTTPClient::STATUS_DISCONNECTED:
  304. *r_status = TTR("Disconnected");
  305. success = false;
  306. break;
  307. case HTTPClient::STATUS_RESOLVING:
  308. *r_status = TTR("Resolving");
  309. break;
  310. case HTTPClient::STATUS_CANT_RESOLVE:
  311. *r_status = TTR("Can't Resolve");
  312. success = false;
  313. break;
  314. case HTTPClient::STATUS_CONNECTING:
  315. *r_status = TTR("Connecting...");
  316. break;
  317. case HTTPClient::STATUS_CANT_CONNECT:
  318. *r_status = TTR("Can't Connect");
  319. success = false;
  320. break;
  321. case HTTPClient::STATUS_CONNECTED:
  322. *r_status = TTR("Connected");
  323. break;
  324. case HTTPClient::STATUS_REQUESTING:
  325. *r_status = TTR("Requesting...");
  326. break;
  327. case HTTPClient::STATUS_BODY:
  328. *r_status = TTR("Downloading");
  329. *r_downloaded_bytes = p_request->get_downloaded_bytes();
  330. *r_total_bytes = p_request->get_body_size();
  331. if (p_request->get_body_size() > 0) {
  332. *r_status += " " + String::humanize_size(p_request->get_downloaded_bytes()) + "/" + String::humanize_size(p_request->get_body_size());
  333. } else {
  334. *r_status += " " + String::humanize_size(p_request->get_downloaded_bytes());
  335. }
  336. break;
  337. case HTTPClient::STATUS_CONNECTION_ERROR:
  338. *r_status = TTR("Connection Error");
  339. success = false;
  340. break;
  341. case HTTPClient::STATUS_TLS_HANDSHAKE_ERROR:
  342. *r_status = TTR("TLS Handshake Error");
  343. success = false;
  344. break;
  345. }
  346. return success;
  347. }
  348. void ExportTemplateManager::_set_current_progress_status(const String &p_status, bool p_error) {
  349. download_progress_label->set_text(p_status);
  350. if (p_error) {
  351. download_progress_bar->hide();
  352. download_progress_label->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
  353. } else {
  354. download_progress_label->add_theme_color_override(SceneStringName(font_color), get_theme_color(SceneStringName(font_color), SNAME("Label")));
  355. }
  356. }
  357. void ExportTemplateManager::_set_current_progress_value(float p_value, const String &p_status) {
  358. download_progress_bar->show();
  359. download_progress_bar->set_indeterminate(false);
  360. download_progress_bar->set_value(p_value);
  361. download_progress_label->set_text(p_status);
  362. }
  363. void ExportTemplateManager::_install_file() {
  364. install_file_dialog->popup_file_dialog();
  365. }
  366. bool ExportTemplateManager::_install_file_selected(const String &p_file, bool p_skip_progress) {
  367. Ref<FileAccess> io_fa;
  368. zlib_filefunc_def io = zipio_create_io(&io_fa);
  369. unzFile pkg = unzOpen2(p_file.utf8().get_data(), &io);
  370. if (!pkg) {
  371. EditorNode::get_singleton()->show_warning(TTR("Can't open the export templates file."));
  372. return false;
  373. }
  374. int ret = unzGoToFirstFile(pkg);
  375. // Count them and find version.
  376. int fc = 0;
  377. String version;
  378. String contents_dir;
  379. while (ret == UNZ_OK) {
  380. unz_file_info info;
  381. char fname[16384];
  382. ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0);
  383. if (ret != UNZ_OK) {
  384. break;
  385. }
  386. String file = String::utf8(fname);
  387. // Skip the __MACOSX directory created by macOS's built-in file zipper.
  388. if (file.begins_with("__MACOSX")) {
  389. ret = unzGoToNextFile(pkg);
  390. continue;
  391. }
  392. if (file.ends_with("version.txt")) {
  393. Vector<uint8_t> uncomp_data;
  394. uncomp_data.resize(info.uncompressed_size);
  395. // Read.
  396. unzOpenCurrentFile(pkg);
  397. ret = unzReadCurrentFile(pkg, uncomp_data.ptrw(), uncomp_data.size());
  398. ERR_BREAK_MSG(ret < 0, vformat("An error occurred while attempting to read from file: %s. This file will not be used.", file));
  399. unzCloseCurrentFile(pkg);
  400. String data_str = String::utf8((const char *)uncomp_data.ptr(), uncomp_data.size());
  401. data_str = data_str.strip_edges();
  402. // Version number should be of the form major.minor[.patch].status[.module_config]
  403. // so it can in theory have 3 or more slices.
  404. if (data_str.get_slice_count(".") < 3) {
  405. EditorNode::get_singleton()->show_warning(vformat(TTR("Invalid version.txt format inside the export templates file: %s."), data_str));
  406. unzClose(pkg);
  407. return false;
  408. }
  409. version = data_str;
  410. contents_dir = file.get_base_dir().trim_suffix("/").trim_suffix("\\");
  411. }
  412. if (file.get_file().size() != 0) {
  413. fc++;
  414. }
  415. ret = unzGoToNextFile(pkg);
  416. }
  417. if (version.is_empty()) {
  418. EditorNode::get_singleton()->show_warning(TTR("No version.txt found inside the export templates file."));
  419. unzClose(pkg);
  420. return false;
  421. }
  422. Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
  423. String template_path = EditorPaths::get_singleton()->get_export_templates_dir().path_join(version);
  424. Error err = d->make_dir_recursive(template_path);
  425. if (err != OK) {
  426. EditorNode::get_singleton()->show_warning(TTR("Error creating path for extracting templates:") + "\n" + template_path);
  427. unzClose(pkg);
  428. return false;
  429. }
  430. EditorProgress *p = nullptr;
  431. if (!p_skip_progress) {
  432. p = memnew(EditorProgress("ltask", TTR("Extracting Export Templates"), fc));
  433. }
  434. fc = 0;
  435. ret = unzGoToFirstFile(pkg);
  436. while (ret == UNZ_OK) {
  437. // Get filename.
  438. unz_file_info info;
  439. char fname[16384];
  440. ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0);
  441. if (ret != UNZ_OK) {
  442. break;
  443. }
  444. if (String::utf8(fname).ends_with("/")) {
  445. // File is a directory, ignore it.
  446. // Directories will be created when extracting each file.
  447. ret = unzGoToNextFile(pkg);
  448. continue;
  449. }
  450. String file_path(String::utf8(fname).simplify_path());
  451. String file = file_path.get_file();
  452. // Skip the __MACOSX directory created by macOS's built-in file zipper.
  453. if (file.is_empty() || file.begins_with("__MACOSX")) {
  454. ret = unzGoToNextFile(pkg);
  455. continue;
  456. }
  457. Vector<uint8_t> uncomp_data;
  458. uncomp_data.resize(info.uncompressed_size);
  459. // Read
  460. unzOpenCurrentFile(pkg);
  461. ret = unzReadCurrentFile(pkg, uncomp_data.ptrw(), uncomp_data.size());
  462. ERR_BREAK_MSG(ret < 0, vformat("An error occurred while attempting to read from file: %s. This file will not be used.", file));
  463. unzCloseCurrentFile(pkg);
  464. String base_dir = file_path.get_base_dir().trim_suffix("/");
  465. if (base_dir != contents_dir && base_dir.begins_with(contents_dir)) {
  466. base_dir = base_dir.substr(contents_dir.length(), file_path.length()).trim_prefix("/");
  467. file = base_dir.path_join(file);
  468. Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
  469. ERR_CONTINUE(da.is_null());
  470. String output_dir = template_path.path_join(base_dir);
  471. if (!DirAccess::exists(output_dir)) {
  472. Error mkdir_err = da->make_dir_recursive(output_dir);
  473. ERR_CONTINUE(mkdir_err != OK);
  474. }
  475. }
  476. if (p) {
  477. p->step(TTR("Importing:") + " " + file, fc);
  478. }
  479. String to_write = template_path.path_join(file);
  480. Ref<FileAccess> f = FileAccess::open(to_write, FileAccess::WRITE);
  481. if (f.is_null()) {
  482. ret = unzGoToNextFile(pkg);
  483. fc++;
  484. ERR_CONTINUE_MSG(true, "Can't open file from path '" + String(to_write) + "'.");
  485. }
  486. f->store_buffer(uncomp_data.ptr(), uncomp_data.size());
  487. f.unref(); // close file.
  488. #ifndef WINDOWS_ENABLED
  489. FileAccess::set_unix_permissions(to_write, (info.external_fa >> 16) & 0x01FF);
  490. #endif
  491. ret = unzGoToNextFile(pkg);
  492. fc++;
  493. }
  494. if (p) {
  495. memdelete(p);
  496. }
  497. unzClose(pkg);
  498. _update_template_status();
  499. EditorSettings::get_singleton()->set("_export_template_download_directory", p_file.get_base_dir());
  500. return true;
  501. }
  502. void ExportTemplateManager::_uninstall_template(const String &p_version) {
  503. uninstall_confirm->set_text(vformat(TTR("Remove templates for the version '%s'?"), p_version));
  504. uninstall_confirm->popup_centered();
  505. uninstall_version = p_version;
  506. }
  507. void ExportTemplateManager::_uninstall_template_confirmed() {
  508. Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
  509. const String &templates_dir = EditorPaths::get_singleton()->get_export_templates_dir();
  510. Error err = da->change_dir(templates_dir);
  511. ERR_FAIL_COND_MSG(err != OK, "Could not access templates directory at '" + templates_dir + "'.");
  512. err = da->change_dir(uninstall_version);
  513. ERR_FAIL_COND_MSG(err != OK, "Could not access templates directory at '" + templates_dir.path_join(uninstall_version) + "'.");
  514. err = da->erase_contents_recursive();
  515. ERR_FAIL_COND_MSG(err != OK, "Could not remove all templates in '" + templates_dir.path_join(uninstall_version) + "'.");
  516. da->change_dir("..");
  517. err = da->remove(uninstall_version);
  518. ERR_FAIL_COND_MSG(err != OK, "Could not remove templates directory at '" + templates_dir.path_join(uninstall_version) + "'.");
  519. _update_template_status();
  520. }
  521. String ExportTemplateManager::_get_selected_mirror() const {
  522. if (mirrors_list->get_item_count() == 1) {
  523. return "";
  524. }
  525. int selected = mirrors_list->get_selected_id();
  526. if (selected == 0) {
  527. // This is a special "best available" value; so pick the first available mirror from the rest of the list.
  528. selected = 1;
  529. }
  530. return mirrors_list->get_item_metadata(selected);
  531. }
  532. void ExportTemplateManager::_mirror_options_button_cbk(int p_id) {
  533. switch (p_id) {
  534. case VISIT_WEB_MIRROR: {
  535. String mirror_url = _get_selected_mirror();
  536. if (mirror_url.is_empty()) {
  537. EditorNode::get_singleton()->show_warning(TTR("There are no mirrors available."));
  538. return;
  539. }
  540. OS::get_singleton()->shell_open(mirror_url);
  541. } break;
  542. case COPY_MIRROR_URL: {
  543. String mirror_url = _get_selected_mirror();
  544. if (mirror_url.is_empty()) {
  545. EditorNode::get_singleton()->show_warning(TTR("There are no mirrors available."));
  546. return;
  547. }
  548. DisplayServer::get_singleton()->clipboard_set(mirror_url);
  549. } break;
  550. }
  551. }
  552. void ExportTemplateManager::_installed_table_button_cbk(Object *p_item, int p_column, int p_id, MouseButton p_button) {
  553. if (p_button != MouseButton::LEFT) {
  554. return;
  555. }
  556. TreeItem *ti = Object::cast_to<TreeItem>(p_item);
  557. if (!ti) {
  558. return;
  559. }
  560. switch (p_id) {
  561. case OPEN_TEMPLATE_FOLDER: {
  562. String version_string = ti->get_text(0);
  563. _open_template_folder(version_string);
  564. } break;
  565. case UNINSTALL_TEMPLATE: {
  566. String version_string = ti->get_text(0);
  567. _uninstall_template(version_string);
  568. } break;
  569. }
  570. }
  571. void ExportTemplateManager::_open_template_folder(const String &p_version) {
  572. const String &templates_dir = EditorPaths::get_singleton()->get_export_templates_dir();
  573. OS::get_singleton()->shell_show_in_file_manager(templates_dir.path_join(p_version), true);
  574. }
  575. void ExportTemplateManager::popup_manager() {
  576. _update_template_status();
  577. switch (_get_downloads_availability()) {
  578. case DOWNLOADS_AVAILABLE: {
  579. current_missing_label->set_text(TTR("Export templates are missing. Download them or install from a file."));
  580. mirrors_list->clear();
  581. mirrors_list->add_item(TTR("Best available mirror"), 0);
  582. mirrors_list->set_disabled(false);
  583. mirrors_list->set_tooltip_text("");
  584. mirror_options_button->set_disabled(false);
  585. download_current_button->set_disabled(false);
  586. download_current_button->set_tooltip_text("");
  587. if (!is_downloading_templates) {
  588. _refresh_mirrors();
  589. }
  590. enable_online_hb->hide();
  591. } break;
  592. case DOWNLOADS_NOT_AVAILABLE_IN_OFFLINE_MODE: {
  593. current_missing_label->set_text(TTR("Export templates are missing. Install them from a file."));
  594. mirrors_list->clear();
  595. mirrors_list->add_item(TTR("Not available in offline mode"), 0);
  596. mirrors_list->set_disabled(true);
  597. mirrors_list->set_tooltip_text(TTR("Template downloading is disabled in offline mode."));
  598. mirror_options_button->set_disabled(true);
  599. download_current_button->set_disabled(true);
  600. download_current_button->set_tooltip_text(TTR("Template downloading is disabled in offline mode."));
  601. enable_online_hb->show();
  602. } break;
  603. case DOWNLOADS_NOT_AVAILABLE_FOR_DEV_BUILDS: {
  604. current_missing_label->set_text(TTR("Export templates are missing. Install them from a file."));
  605. mirrors_list->clear();
  606. mirrors_list->add_item(TTR("No templates for development builds"), 0);
  607. mirrors_list->set_disabled(true);
  608. mirrors_list->set_tooltip_text(TTR("Official export templates aren't available for development builds."));
  609. mirror_options_button->set_disabled(true);
  610. download_current_button->set_disabled(true);
  611. download_current_button->set_tooltip_text(TTR("Official export templates aren't available for development builds."));
  612. enable_online_hb->hide();
  613. } break;
  614. case DOWNLOADS_NOT_AVAILABLE_FOR_DOUBLE_BUILDS: {
  615. current_missing_label->set_text(TTR("Export templates are missing. Install them from a file."));
  616. mirrors_list->clear();
  617. mirrors_list->add_item(TTR("No templates for double-precision builds"), 0);
  618. mirrors_list->set_disabled(true);
  619. mirrors_list->set_tooltip_text(TTR("Official export templates aren't available for double-precision builds."));
  620. mirror_options_button->set_disabled(true);
  621. download_current_button->set_disabled(true);
  622. download_current_button->set_tooltip_text(TTR("Official export templates aren't available for double-precision builds."));
  623. }
  624. }
  625. popup_centered(Size2(720, 280) * EDSCALE);
  626. }
  627. void ExportTemplateManager::ok_pressed() {
  628. if (!is_downloading_templates) {
  629. hide();
  630. return;
  631. }
  632. hide_dialog_accept->popup_centered();
  633. }
  634. void ExportTemplateManager::_hide_dialog() {
  635. hide();
  636. }
  637. String ExportTemplateManager::get_android_build_directory(const Ref<EditorExportPreset> &p_preset) {
  638. if (p_preset.is_valid()) {
  639. String gradle_build_dir = p_preset->get("gradle_build/gradle_build_directory");
  640. if (!gradle_build_dir.is_empty()) {
  641. return gradle_build_dir.path_join("build");
  642. }
  643. }
  644. return "res://android/build";
  645. }
  646. String ExportTemplateManager::get_android_source_zip(const Ref<EditorExportPreset> &p_preset) {
  647. if (p_preset.is_valid()) {
  648. String android_source_zip = p_preset->get("gradle_build/android_source_template");
  649. if (!android_source_zip.is_empty()) {
  650. return android_source_zip;
  651. }
  652. }
  653. const String templates_dir = EditorPaths::get_singleton()->get_export_templates_dir().path_join(GODOT_VERSION_FULL_CONFIG);
  654. return templates_dir.path_join("android_source.zip");
  655. }
  656. String ExportTemplateManager::get_android_template_identifier(const Ref<EditorExportPreset> &p_preset) {
  657. // The template identifier is the Godot version for the default template, and the full path plus md5 hash for custom templates.
  658. if (p_preset.is_valid()) {
  659. String android_source_zip = p_preset->get("gradle_build/android_source_template");
  660. if (!android_source_zip.is_empty()) {
  661. return android_source_zip + String(" [") + FileAccess::get_md5(android_source_zip) + String("]");
  662. }
  663. }
  664. return GODOT_VERSION_FULL_CONFIG;
  665. }
  666. bool ExportTemplateManager::is_android_template_installed(const Ref<EditorExportPreset> &p_preset) {
  667. return DirAccess::exists(get_android_build_directory(p_preset));
  668. }
  669. bool ExportTemplateManager::can_install_android_template(const Ref<EditorExportPreset> &p_preset) {
  670. return FileAccess::exists(get_android_source_zip(p_preset));
  671. }
  672. Error ExportTemplateManager::install_android_template(const Ref<EditorExportPreset> &p_preset) {
  673. const String source_zip = get_android_source_zip(p_preset);
  674. ERR_FAIL_COND_V(!FileAccess::exists(source_zip), ERR_CANT_OPEN);
  675. return install_android_template_from_file(source_zip, p_preset);
  676. }
  677. Error ExportTemplateManager::install_android_template_from_file(const String &p_file, const Ref<EditorExportPreset> &p_preset) {
  678. // To support custom Android builds, we install the Java source code and buildsystem
  679. // from android_source.zip to the project's res://android folder.
  680. Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
  681. ERR_FAIL_COND_V(da.is_null(), ERR_CANT_CREATE);
  682. String build_dir = get_android_build_directory(p_preset);
  683. String parent_dir = build_dir.get_base_dir();
  684. // Make parent of the build dir (if it does not exist).
  685. da->make_dir_recursive(parent_dir);
  686. {
  687. // Add identifier, to ensure building won't work if the current template doesn't match.
  688. Ref<FileAccess> f = FileAccess::open(parent_dir.path_join(".build_version"), FileAccess::WRITE);
  689. ERR_FAIL_COND_V(f.is_null(), ERR_CANT_CREATE);
  690. f->store_line(get_android_template_identifier(p_preset));
  691. }
  692. // Create the android build directory.
  693. Error err = da->make_dir_recursive(build_dir);
  694. ERR_FAIL_COND_V(err != OK, err);
  695. {
  696. // Add an empty .gdignore file to avoid scan.
  697. Ref<FileAccess> f = FileAccess::open(build_dir.path_join(".gdignore"), FileAccess::WRITE);
  698. ERR_FAIL_COND_V(f.is_null(), ERR_CANT_CREATE);
  699. f->store_line("");
  700. }
  701. // Uncompress source template.
  702. Ref<FileAccess> io_fa;
  703. zlib_filefunc_def io = zipio_create_io(&io_fa);
  704. unzFile pkg = unzOpen2(p_file.utf8().get_data(), &io);
  705. ERR_FAIL_NULL_V_MSG(pkg, ERR_CANT_OPEN, "Android sources not in ZIP format.");
  706. int ret = unzGoToFirstFile(pkg);
  707. int total_files = 0;
  708. // Count files to unzip.
  709. while (ret == UNZ_OK) {
  710. total_files++;
  711. ret = unzGoToNextFile(pkg);
  712. }
  713. ret = unzGoToFirstFile(pkg);
  714. ProgressDialog::get_singleton()->add_task("uncompress_src", TTR("Uncompressing Android Build Sources"), total_files);
  715. HashSet<String> dirs_tested;
  716. int idx = 0;
  717. while (ret == UNZ_OK) {
  718. // Get file path.
  719. unz_file_info info;
  720. char fpath[16384];
  721. ret = unzGetCurrentFileInfo(pkg, &info, fpath, 16384, nullptr, 0, nullptr, 0);
  722. if (ret != UNZ_OK) {
  723. break;
  724. }
  725. String path = String::utf8(fpath);
  726. String base_dir = path.get_base_dir();
  727. if (!path.ends_with("/")) {
  728. Vector<uint8_t> uncomp_data;
  729. uncomp_data.resize(info.uncompressed_size);
  730. // Read.
  731. unzOpenCurrentFile(pkg);
  732. unzReadCurrentFile(pkg, uncomp_data.ptrw(), uncomp_data.size());
  733. unzCloseCurrentFile(pkg);
  734. if (!dirs_tested.has(base_dir)) {
  735. da->make_dir_recursive(build_dir.path_join(base_dir));
  736. dirs_tested.insert(base_dir);
  737. }
  738. String to_write = build_dir.path_join(path);
  739. Ref<FileAccess> f = FileAccess::open(to_write, FileAccess::WRITE);
  740. if (f.is_valid()) {
  741. f->store_buffer(uncomp_data.ptr(), uncomp_data.size());
  742. f.unref(); // close file.
  743. #ifndef WINDOWS_ENABLED
  744. FileAccess::set_unix_permissions(to_write, (info.external_fa >> 16) & 0x01FF);
  745. #endif
  746. } else {
  747. ERR_PRINT("Can't uncompress file: " + to_write);
  748. }
  749. }
  750. ProgressDialog::get_singleton()->task_step("uncompress_src", path, idx);
  751. idx++;
  752. ret = unzGoToNextFile(pkg);
  753. }
  754. ProgressDialog::get_singleton()->end_task("uncompress_src");
  755. unzClose(pkg);
  756. EditorFileSystem::get_singleton()->scan_changes();
  757. return OK;
  758. }
  759. void ExportTemplateManager::_notification(int p_what) {
  760. switch (p_what) {
  761. case NOTIFICATION_THEME_CHANGED: {
  762. current_value->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("main"), EditorStringName(EditorFonts)));
  763. current_missing_label->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
  764. current_installed_label->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor)));
  765. mirror_options_button->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
  766. } break;
  767. case NOTIFICATION_VISIBILITY_CHANGED: {
  768. if (!is_visible()) {
  769. set_process(false);
  770. } else if (is_visible() && is_downloading_templates) {
  771. set_process(true);
  772. }
  773. } break;
  774. case NOTIFICATION_PROCESS: {
  775. update_countdown -= get_process_delta_time();
  776. if (update_countdown > 0) {
  777. return;
  778. }
  779. update_countdown = 0.5;
  780. String status;
  781. int downloaded_bytes;
  782. int total_bytes;
  783. bool success = _humanize_http_status(download_templates, &status, &downloaded_bytes, &total_bytes);
  784. if (downloaded_bytes >= 0) {
  785. if (total_bytes > 0) {
  786. _set_current_progress_value(float(downloaded_bytes) / total_bytes, status);
  787. } else {
  788. _set_current_progress_value(0, status);
  789. }
  790. } else {
  791. _set_current_progress_status(status);
  792. }
  793. if (!success) {
  794. set_process(false);
  795. }
  796. } break;
  797. case NOTIFICATION_WM_CLOSE_REQUEST: {
  798. // This won't stop the window from closing, but will show the alert if the download is active.
  799. ok_pressed();
  800. } break;
  801. }
  802. }
  803. ExportTemplateManager::ExportTemplateManager() {
  804. set_title(TTR("Export Template Manager"));
  805. set_hide_on_ok(false);
  806. set_ok_button_text(TTR("Close"));
  807. VBoxContainer *main_vb = memnew(VBoxContainer);
  808. add_child(main_vb);
  809. // Current version controls.
  810. HBoxContainer *current_hb = memnew(HBoxContainer);
  811. main_vb->add_child(current_hb);
  812. Label *current_label = memnew(Label);
  813. current_label->set_theme_type_variation("HeaderSmall");
  814. current_label->set_text(TTR("Current Version:"));
  815. current_hb->add_child(current_label);
  816. current_value = memnew(Label);
  817. current_value->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
  818. current_hb->add_child(current_value);
  819. // Current version statuses.
  820. // Status: Current version is missing.
  821. current_missing_label = memnew(Label);
  822. current_missing_label->set_theme_type_variation("HeaderSmall");
  823. current_missing_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
  824. current_missing_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
  825. current_hb->add_child(current_missing_label);
  826. // Status: Current version is installed.
  827. current_installed_label = memnew(Label);
  828. current_installed_label->set_theme_type_variation("HeaderSmall");
  829. current_installed_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
  830. current_installed_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
  831. current_installed_label->set_text(TTR("Export templates are installed and ready to be used."));
  832. current_hb->add_child(current_installed_label);
  833. current_installed_label->hide();
  834. // Currently installed template.
  835. current_installed_hb = memnew(HBoxContainer);
  836. main_vb->add_child(current_installed_hb);
  837. current_installed_path = memnew(LineEdit);
  838. current_installed_path->set_editable(false);
  839. current_installed_path->set_h_size_flags(Control::SIZE_EXPAND_FILL);
  840. current_installed_path->set_accessibility_name(TTRC("Installed Path"));
  841. current_installed_hb->add_child(current_installed_path);
  842. #ifndef ANDROID_ENABLED
  843. Button *current_open_button = memnew(Button);
  844. current_open_button->set_text(TTR("Open Folder"));
  845. current_open_button->set_tooltip_text(TTR("Open the folder containing installed templates for the current version."));
  846. current_installed_hb->add_child(current_open_button);
  847. current_open_button->connect(SceneStringName(pressed), callable_mp(this, &ExportTemplateManager::_open_template_folder).bind(GODOT_VERSION_FULL_CONFIG));
  848. #endif
  849. current_uninstall_button = memnew(Button);
  850. current_uninstall_button->set_text(TTR("Uninstall"));
  851. current_uninstall_button->set_tooltip_text(TTR("Uninstall templates for the current version."));
  852. current_installed_hb->add_child(current_uninstall_button);
  853. current_uninstall_button->connect(SceneStringName(pressed), callable_mp(this, &ExportTemplateManager::_uninstall_template).bind(GODOT_VERSION_FULL_CONFIG));
  854. main_vb->add_child(memnew(HSeparator));
  855. // Download and install section.
  856. HBoxContainer *install_templates_hb = memnew(HBoxContainer);
  857. main_vb->add_child(install_templates_hb);
  858. // Download and install buttons are available.
  859. install_options_vb = memnew(VBoxContainer);
  860. install_options_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
  861. install_templates_hb->add_child(install_options_vb);
  862. HBoxContainer *download_install_hb = memnew(HBoxContainer);
  863. install_options_vb->add_child(download_install_hb);
  864. Label *mirrors_label = memnew(Label);
  865. mirrors_label->set_text(TTR("Download from:"));
  866. download_install_hb->add_child(mirrors_label);
  867. mirrors_list = memnew(OptionButton);
  868. mirrors_list->set_accessibility_name(TTRC("Mirror"));
  869. mirrors_list->set_custom_minimum_size(Size2(280, 0) * EDSCALE);
  870. download_install_hb->add_child(mirrors_list);
  871. request_mirrors = memnew(HTTPRequest);
  872. mirrors_list->add_child(request_mirrors);
  873. request_mirrors->connect("request_completed", callable_mp(this, &ExportTemplateManager::_refresh_mirrors_completed));
  874. mirror_options_button = memnew(MenuButton);
  875. mirror_options_button->set_accessibility_name(TTRC("Mirror Options"));
  876. mirror_options_button->get_popup()->add_item(TTR("Open in Web Browser"), VISIT_WEB_MIRROR);
  877. mirror_options_button->get_popup()->add_item(TTR("Copy Mirror URL"), COPY_MIRROR_URL);
  878. download_install_hb->add_child(mirror_options_button);
  879. mirror_options_button->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &ExportTemplateManager::_mirror_options_button_cbk));
  880. download_install_hb->add_spacer();
  881. download_current_button = memnew(Button);
  882. download_current_button->set_text(TTR("Download and Install"));
  883. download_current_button->set_tooltip_text(TTR("Download and install templates for the current version from the best possible mirror."));
  884. download_install_hb->add_child(download_current_button);
  885. download_current_button->connect(SceneStringName(pressed), callable_mp(this, &ExportTemplateManager::_download_current));
  886. HBoxContainer *install_file_hb = memnew(HBoxContainer);
  887. install_file_hb->set_alignment(BoxContainer::ALIGNMENT_END);
  888. install_options_vb->add_child(install_file_hb);
  889. install_file_button = memnew(Button);
  890. install_file_button->set_text(TTR("Install from File"));
  891. install_file_button->set_tooltip_text(TTR("Install templates from a local file."));
  892. install_file_hb->add_child(install_file_button);
  893. install_file_button->connect(SceneStringName(pressed), callable_mp(this, &ExportTemplateManager::_install_file));
  894. enable_online_hb = memnew(HBoxContainer);
  895. install_options_vb->add_child(enable_online_hb);
  896. Label *enable_online_label = memnew(Label);
  897. enable_online_label->set_text(TTR("Online mode is needed to download the templates."));
  898. enable_online_hb->add_child(enable_online_label);
  899. LinkButton *enable_online_button = memnew(LinkButton);
  900. enable_online_button->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
  901. enable_online_button->set_text(TTR("Go Online"));
  902. enable_online_hb->add_child(enable_online_button);
  903. enable_online_button->connect(SceneStringName(pressed), callable_mp(this, &ExportTemplateManager::_force_online_mode));
  904. // Templates are being downloaded; buttons unavailable.
  905. download_progress_hb = memnew(HBoxContainer);
  906. download_progress_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
  907. install_templates_hb->add_child(download_progress_hb);
  908. download_progress_hb->hide();
  909. download_progress_bar = memnew(ProgressBar);
  910. download_progress_bar->set_h_size_flags(Control::SIZE_EXPAND_FILL);
  911. download_progress_bar->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
  912. download_progress_bar->set_min(0);
  913. download_progress_bar->set_max(1);
  914. download_progress_bar->set_value(0);
  915. download_progress_bar->set_step(0.01);
  916. download_progress_bar->set_editor_preview_indeterminate(true);
  917. download_progress_hb->add_child(download_progress_bar);
  918. download_progress_label = memnew(Label);
  919. download_progress_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
  920. download_progress_hb->add_child(download_progress_label);
  921. Button *download_cancel_button = memnew(Button);
  922. download_cancel_button->set_text(TTR("Cancel"));
  923. download_cancel_button->set_tooltip_text(TTR("Cancel the download of the templates."));
  924. download_progress_hb->add_child(download_cancel_button);
  925. download_cancel_button->connect(SceneStringName(pressed), callable_mp(this, &ExportTemplateManager::_cancel_template_download));
  926. download_templates = memnew(HTTPRequest);
  927. install_templates_hb->add_child(download_templates);
  928. download_templates->connect("request_completed", callable_mp(this, &ExportTemplateManager::_download_template_completed));
  929. main_vb->add_child(memnew(HSeparator));
  930. // Other installed templates table.
  931. HBoxContainer *installed_versions_hb = memnew(HBoxContainer);
  932. main_vb->add_child(installed_versions_hb);
  933. Label *installed_label = memnew(Label);
  934. installed_label->set_theme_type_variation("HeaderSmall");
  935. installed_label->set_text(TTR("Other Installed Versions:"));
  936. installed_versions_hb->add_child(installed_label);
  937. installed_table = memnew(Tree);
  938. installed_table->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
  939. installed_table->set_hide_root(true);
  940. installed_table->set_custom_minimum_size(Size2(0, 100) * EDSCALE);
  941. installed_table->set_v_size_flags(Control::SIZE_EXPAND_FILL);
  942. main_vb->add_child(installed_table);
  943. installed_table->connect("button_clicked", callable_mp(this, &ExportTemplateManager::_installed_table_button_cbk));
  944. // Dialogs.
  945. uninstall_confirm = memnew(ConfirmationDialog);
  946. uninstall_confirm->set_title(TTR("Uninstall Template"));
  947. add_child(uninstall_confirm);
  948. uninstall_confirm->connect(SceneStringName(confirmed), callable_mp(this, &ExportTemplateManager::_uninstall_template_confirmed));
  949. install_file_dialog = memnew(FileDialog);
  950. install_file_dialog->set_title(TTR("Select Template File"));
  951. install_file_dialog->set_access(FileDialog::ACCESS_FILESYSTEM);
  952. install_file_dialog->set_file_mode(FileDialog::FILE_MODE_OPEN_FILE);
  953. install_file_dialog->set_current_dir(EDITOR_DEF("_export_template_download_directory", ""));
  954. install_file_dialog->add_filter("*.tpz", TTR("Godot Export Templates"));
  955. install_file_dialog->connect("file_selected", callable_mp(this, &ExportTemplateManager::_install_file_selected).bind(false));
  956. add_child(install_file_dialog);
  957. hide_dialog_accept = memnew(AcceptDialog);
  958. hide_dialog_accept->set_text(TTR("The templates will continue to download.\nYou may experience a short editor freeze when they finish."));
  959. add_child(hide_dialog_accept);
  960. hide_dialog_accept->connect(SceneStringName(confirmed), callable_mp(this, &ExportTemplateManager::_hide_dialog));
  961. }