1 /*************************************************************************/
2 /*  project_export.cpp                                                   */
3 /*************************************************************************/
4 /*                       This file is part of:                           */
5 /*                           GODOT ENGINE                                */
6 /*                      https://godotengine.org                          */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
9 /* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
10 /*                                                                       */
11 /* Permission is hereby granted, free of charge, to any person obtaining */
12 /* a copy of this software and associated documentation files (the       */
13 /* "Software"), to deal in the Software without restriction, including   */
14 /* without limitation the rights to use, copy, modify, merge, publish,   */
15 /* distribute, sublicense, and/or sell copies of the Software, and to    */
16 /* permit persons to whom the Software is furnished to do so, subject to */
17 /* the following conditions:                                             */
18 /*                                                                       */
19 /* The above copyright notice and this permission notice shall be        */
20 /* included in all copies or substantial portions of the Software.       */
21 /*                                                                       */
22 /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
23 /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
24 /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
25 /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
26 /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
27 /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
28 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
29 /*************************************************************************/
30 
31 #include "project_export.h"
32 
33 #include "core/compressed_translation.h"
34 #include "core/io/image_loader.h"
35 #include "core/io/resource_loader.h"
36 #include "core/io/resource_saver.h"
37 #include "core/os/dir_access.h"
38 #include "core/os/file_access.h"
39 #include "core/os/os.h"
40 #include "core/project_settings.h"
41 #include "editor_data.h"
42 #include "editor_node.h"
43 #include "editor_scale.h"
44 #include "editor_settings.h"
45 #include "scene/gui/box_container.h"
46 #include "scene/gui/margin_container.h"
47 #include "scene/gui/scroll_container.h"
48 #include "scene/gui/tab_container.h"
49 
_notification(int p_what)50 void ProjectExportDialog::_notification(int p_what) {
51 
52 	switch (p_what) {
53 		case NOTIFICATION_READY: {
54 			duplicate_preset->set_icon(get_icon("Duplicate", "EditorIcons"));
55 			delete_preset->set_icon(get_icon("Remove", "EditorIcons"));
56 			connect("confirmed", this, "_export_pck_zip");
57 			custom_feature_display->get_parent_control()->add_style_override("panel", get_stylebox("bg", "Tree"));
58 		} break;
59 		case NOTIFICATION_POPUP_HIDE: {
60 			EditorSettings::get_singleton()->set_project_metadata("dialog_bounds", "export", get_rect());
61 		} break;
62 		case NOTIFICATION_THEME_CHANGED: {
63 			duplicate_preset->set_icon(get_icon("Duplicate", "EditorIcons"));
64 			delete_preset->set_icon(get_icon("Remove", "EditorIcons"));
65 			Control *panel = custom_feature_display->get_parent_control();
66 			if (panel)
67 				panel->add_style_override("panel", get_stylebox("bg", "Tree"));
68 		} break;
69 	}
70 }
71 
popup_export()72 void ProjectExportDialog::popup_export() {
73 
74 	add_preset->get_popup()->clear();
75 	for (int i = 0; i < EditorExport::get_singleton()->get_export_platform_count(); i++) {
76 
77 		Ref<EditorExportPlatform> plat = EditorExport::get_singleton()->get_export_platform(i);
78 
79 		add_preset->get_popup()->add_icon_item(plat->get_logo(), plat->get_name());
80 	}
81 
82 	_update_presets();
83 	if (presets->get_current() >= 0) {
84 		_update_current_preset(); // triggers rescan for templates if newly installed
85 	}
86 
87 	// Restore valid window bounds or pop up at default size.
88 	Rect2 saved_size = EditorSettings::get_singleton()->get_project_metadata("dialog_bounds", "export", Rect2());
89 	if (saved_size != Rect2()) {
90 		popup(saved_size);
91 	} else {
92 		popup_centered_clamped(Size2(900, 700) * EDSCALE, 0.8);
93 	}
94 }
95 
_add_preset(int p_platform)96 void ProjectExportDialog::_add_preset(int p_platform) {
97 
98 	Ref<EditorExportPreset> preset = EditorExport::get_singleton()->get_export_platform(p_platform)->create_preset();
99 	ERR_FAIL_COND(!preset.is_valid());
100 
101 	String name = EditorExport::get_singleton()->get_export_platform(p_platform)->get_name();
102 	bool make_runnable = true;
103 	int attempt = 1;
104 	while (true) {
105 
106 		bool valid = true;
107 
108 		for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) {
109 			Ref<EditorExportPreset> p = EditorExport::get_singleton()->get_export_preset(i);
110 			if (p->get_platform() == preset->get_platform() && p->is_runnable()) {
111 				make_runnable = false;
112 			}
113 			if (p->get_name() == name) {
114 				valid = false;
115 				break;
116 			}
117 		}
118 
119 		if (valid)
120 			break;
121 
122 		attempt++;
123 		name = EditorExport::get_singleton()->get_export_platform(p_platform)->get_name() + " " + itos(attempt);
124 	}
125 
126 	preset->set_name(name);
127 	if (make_runnable)
128 		preset->set_runnable(make_runnable);
129 	EditorExport::get_singleton()->add_export_preset(preset);
130 	_update_presets();
131 	_edit_preset(EditorExport::get_singleton()->get_export_preset_count() - 1);
132 }
133 
_force_update_current_preset_parameters()134 void ProjectExportDialog::_force_update_current_preset_parameters() {
135 	// Force the parameters section to refresh its UI.
136 	parameters->edit(nullptr);
137 	_update_current_preset();
138 }
139 
_update_current_preset()140 void ProjectExportDialog::_update_current_preset() {
141 
142 	_edit_preset(presets->get_current());
143 }
144 
_update_presets()145 void ProjectExportDialog::_update_presets() {
146 
147 	updating = true;
148 
149 	Ref<EditorExportPreset> current;
150 	if (presets->get_current() >= 0 && presets->get_current() < presets->get_item_count())
151 		current = get_current_preset();
152 
153 	int current_idx = -1;
154 	presets->clear();
155 	for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) {
156 		Ref<EditorExportPreset> preset = EditorExport::get_singleton()->get_export_preset(i);
157 		if (preset == current) {
158 			current_idx = i;
159 		}
160 
161 		String name = preset->get_name();
162 		if (preset->is_runnable())
163 			name += " (" + TTR("Runnable") + ")";
164 		preset->update_files_to_export();
165 		presets->add_item(name, preset->get_platform()->get_logo());
166 	}
167 
168 	if (current_idx != -1) {
169 		presets->select(current_idx);
170 	}
171 
172 	updating = false;
173 }
174 
_update_export_all()175 void ProjectExportDialog::_update_export_all() {
176 
177 	bool can_export = EditorExport::get_singleton()->get_export_preset_count() > 0;
178 
179 	for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) {
180 		Ref<EditorExportPreset> preset = EditorExport::get_singleton()->get_export_preset(i);
181 		bool needs_templates;
182 		String error;
183 		if (preset->get_export_path() == "" || !preset->get_platform()->can_export(preset, error, needs_templates)) {
184 			can_export = false;
185 			break;
186 		}
187 	}
188 
189 	if (can_export) {
190 		export_all_button->set_disabled(false);
191 	} else {
192 		export_all_button->set_disabled(true);
193 	}
194 }
195 
_edit_preset(int p_index)196 void ProjectExportDialog::_edit_preset(int p_index) {
197 
198 	if (p_index < 0 || p_index >= presets->get_item_count()) {
199 		name->set_text("");
200 		name->set_editable(false);
201 		export_path->hide();
202 		runnable->set_disabled(true);
203 		parameters->edit(NULL);
204 		presets->unselect_all();
205 		duplicate_preset->set_disabled(true);
206 		delete_preset->set_disabled(true);
207 		sections->hide();
208 		patches->clear();
209 		export_error->hide();
210 		export_templates_error->hide();
211 		return;
212 	}
213 
214 	Ref<EditorExportPreset> current = EditorExport::get_singleton()->get_export_preset(p_index);
215 	ERR_FAIL_COND(current.is_null());
216 
217 	updating = true;
218 
219 	presets->select(p_index);
220 	sections->show();
221 
222 	name->set_editable(true);
223 	export_path->show();
224 	duplicate_preset->set_disabled(false);
225 	delete_preset->set_disabled(false);
226 	name->set_text(current->get_name());
227 
228 	List<String> extension_list = current->get_platform()->get_binary_extensions(current);
229 	Vector<String> extension_vector;
230 	for (int i = 0; i < extension_list.size(); i++) {
231 		extension_vector.push_back("*." + extension_list[i]);
232 	}
233 
234 	export_path->setup(extension_vector, false, true);
235 	export_path->update_property();
236 	runnable->set_disabled(false);
237 	runnable->set_pressed(current->is_runnable());
238 	parameters->edit(current.ptr());
239 
240 	export_filter->select(current->get_export_filter());
241 	include_filters->set_text(current->get_include_filter());
242 	exclude_filters->set_text(current->get_exclude_filter());
243 
244 	patches->clear();
245 	TreeItem *patch_root = patches->create_item();
246 	Vector<String> patchlist = current->get_patches();
247 	for (int i = 0; i < patchlist.size(); i++) {
248 		TreeItem *patch = patches->create_item(patch_root);
249 		patch->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
250 		String file = patchlist[i].get_file();
251 		patch->set_editable(0, true);
252 		patch->set_text(0, file.get_file().replace("*", ""));
253 		if (file.ends_with("*"))
254 			patch->set_checked(0, true);
255 		patch->set_tooltip(0, patchlist[i]);
256 		patch->set_metadata(0, i);
257 		patch->add_button(0, get_icon("Remove", "EditorIcons"), 0);
258 		patch->add_button(0, get_icon("folder", "FileDialog"), 1);
259 	}
260 
261 	TreeItem *patch_add = patches->create_item(patch_root);
262 	patch_add->set_metadata(0, patchlist.size());
263 	if (patchlist.size() == 0)
264 		patch_add->set_text(0, TTR("Add initial export..."));
265 	else
266 		patch_add->set_text(0, TTR("Add previous patches..."));
267 
268 	patch_add->add_button(0, get_icon("folder", "FileDialog"), 1);
269 
270 	_fill_resource_tree();
271 
272 	bool needs_templates;
273 	String error;
274 	if (!current->get_platform()->can_export(current, error, needs_templates)) {
275 
276 		if (error != String()) {
277 
278 			Vector<String> items = error.split("\n", false);
279 			error = "";
280 			for (int i = 0; i < items.size(); i++) {
281 				if (i > 0)
282 					error += "\n";
283 				error += " - " + items[i];
284 			}
285 
286 			export_error->set_text(error);
287 			export_error->show();
288 		} else {
289 			export_error->hide();
290 		}
291 		if (needs_templates)
292 			export_templates_error->show();
293 		else
294 			export_templates_error->hide();
295 
296 		export_button->set_disabled(true);
297 		get_ok()->set_disabled(true);
298 
299 	} else {
300 		export_error->hide();
301 		export_templates_error->hide();
302 		export_button->set_disabled(false);
303 		get_ok()->set_disabled(false);
304 	}
305 
306 	custom_features->set_text(current->get_custom_features());
307 	_update_feature_list();
308 	_update_export_all();
309 	minimum_size_changed();
310 
311 	int script_export_mode = current->get_script_export_mode();
312 	script_mode->select(script_export_mode);
313 
314 	String key = current->get_script_encryption_key();
315 	if (!updating_script_key) {
316 		script_key->set_text(key);
317 	}
318 	if (script_export_mode == EditorExportPreset::MODE_SCRIPT_ENCRYPTED) {
319 		script_key->set_editable(true);
320 
321 		bool key_valid = _validate_script_encryption_key(key);
322 		if (key_valid) {
323 			script_key_error->hide();
324 		} else {
325 			script_key_error->show();
326 		}
327 	} else {
328 		script_key->set_editable(false);
329 		script_key_error->hide();
330 	}
331 
332 	updating = false;
333 }
334 
_update_feature_list()335 void ProjectExportDialog::_update_feature_list() {
336 
337 	Ref<EditorExportPreset> current = get_current_preset();
338 	ERR_FAIL_COND(current.is_null());
339 
340 	Set<String> fset;
341 	List<String> features;
342 
343 	current->get_platform()->get_platform_features(&features);
344 	current->get_platform()->get_preset_features(current, &features);
345 
346 	String custom = current->get_custom_features();
347 	Vector<String> custom_list = custom.split(",");
348 	for (int i = 0; i < custom_list.size(); i++) {
349 		String f = custom_list[i].strip_edges();
350 		if (f != String()) {
351 			features.push_back(f);
352 		}
353 	}
354 
355 	for (List<String>::Element *E = features.front(); E; E = E->next()) {
356 		fset.insert(E->get());
357 	}
358 
359 	custom_feature_display->clear();
360 	for (Set<String>::Element *E = fset.front(); E; E = E->next()) {
361 		String f = E->get();
362 		if (E->next()) {
363 			f += ", ";
364 		}
365 		custom_feature_display->add_text(f);
366 	}
367 }
368 
_custom_features_changed(const String & p_text)369 void ProjectExportDialog::_custom_features_changed(const String &p_text) {
370 
371 	if (updating)
372 		return;
373 
374 	Ref<EditorExportPreset> current = get_current_preset();
375 	ERR_FAIL_COND(current.is_null());
376 
377 	current->set_custom_features(p_text);
378 	_update_feature_list();
379 }
380 
_tab_changed(int)381 void ProjectExportDialog::_tab_changed(int) {
382 	_update_feature_list();
383 }
384 
_patch_button_pressed(Object * p_item,int p_column,int p_id)385 void ProjectExportDialog::_patch_button_pressed(Object *p_item, int p_column, int p_id) {
386 
387 	TreeItem *ti = (TreeItem *)p_item;
388 
389 	patch_index = ti->get_metadata(0);
390 
391 	Ref<EditorExportPreset> current = get_current_preset();
392 	ERR_FAIL_COND(current.is_null());
393 
394 	if (p_id == 0) {
395 		Vector<String> patches = current->get_patches();
396 		ERR_FAIL_INDEX(patch_index, patches.size());
397 		patch_erase->set_text(vformat(TTR("Delete patch '%s' from list?"), patches[patch_index].get_file()));
398 		patch_erase->popup_centered_minsize();
399 	} else {
400 		patch_dialog->popup_centered_ratio();
401 	}
402 }
403 
_patch_edited()404 void ProjectExportDialog::_patch_edited() {
405 
406 	TreeItem *item = patches->get_edited();
407 	if (!item)
408 		return;
409 	int index = item->get_metadata(0);
410 
411 	Ref<EditorExportPreset> current = get_current_preset();
412 	ERR_FAIL_COND(current.is_null());
413 
414 	Vector<String> patches = current->get_patches();
415 
416 	ERR_FAIL_INDEX(index, patches.size());
417 
418 	String patch = patches[index].replace("*", "");
419 
420 	if (item->is_checked(0)) {
421 		patch += "*";
422 	}
423 
424 	current->set_patch(index, patch);
425 }
426 
_patch_selected(const String & p_path)427 void ProjectExportDialog::_patch_selected(const String &p_path) {
428 
429 	Ref<EditorExportPreset> current = get_current_preset();
430 	ERR_FAIL_COND(current.is_null());
431 
432 	Vector<String> patches = current->get_patches();
433 
434 	if (patch_index >= patches.size()) {
435 
436 		current->add_patch(ProjectSettings::get_singleton()->get_resource_path().path_to(p_path) + "*");
437 	} else {
438 		String enabled = patches[patch_index].ends_with("*") ? String("*") : String();
439 		current->set_patch(patch_index, ProjectSettings::get_singleton()->get_resource_path().path_to(p_path) + enabled);
440 	}
441 
442 	_update_current_preset();
443 }
444 
_patch_deleted()445 void ProjectExportDialog::_patch_deleted() {
446 
447 	Ref<EditorExportPreset> current = get_current_preset();
448 	ERR_FAIL_COND(current.is_null());
449 
450 	Vector<String> patches = current->get_patches();
451 	if (patch_index < patches.size()) {
452 
453 		current->remove_patch(patch_index);
454 		_update_current_preset();
455 	}
456 }
457 
_update_parameters(const String & p_edited_property)458 void ProjectExportDialog::_update_parameters(const String &p_edited_property) {
459 
460 	_update_current_preset();
461 }
462 
_runnable_pressed()463 void ProjectExportDialog::_runnable_pressed() {
464 
465 	if (updating)
466 		return;
467 
468 	Ref<EditorExportPreset> current = get_current_preset();
469 	ERR_FAIL_COND(current.is_null());
470 
471 	if (runnable->is_pressed()) {
472 
473 		for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) {
474 			Ref<EditorExportPreset> p = EditorExport::get_singleton()->get_export_preset(i);
475 			if (p->get_platform() == current->get_platform()) {
476 				p->set_runnable(current == p);
477 			}
478 		}
479 	} else {
480 
481 		current->set_runnable(false);
482 	}
483 
484 	_update_presets();
485 }
486 
_name_changed(const String & p_string)487 void ProjectExportDialog::_name_changed(const String &p_string) {
488 
489 	if (updating)
490 		return;
491 
492 	Ref<EditorExportPreset> current = get_current_preset();
493 	ERR_FAIL_COND(current.is_null());
494 
495 	current->set_name(p_string);
496 	_update_presets();
497 }
498 
set_export_path(const String & p_value)499 void ProjectExportDialog::set_export_path(const String &p_value) {
500 	Ref<EditorExportPreset> current = get_current_preset();
501 	ERR_FAIL_COND(current.is_null());
502 
503 	current->set_export_path(p_value);
504 }
505 
get_export_path()506 String ProjectExportDialog::get_export_path() {
507 	Ref<EditorExportPreset> current = get_current_preset();
508 	ERR_FAIL_COND_V(current.is_null(), String(""));
509 
510 	return current->get_export_path();
511 }
512 
get_current_preset() const513 Ref<EditorExportPreset> ProjectExportDialog::get_current_preset() const {
514 
515 	return EditorExport::get_singleton()->get_export_preset(presets->get_current());
516 }
517 
_export_path_changed(const StringName & p_property,const Variant & p_value,const String & p_field,bool p_changing)518 void ProjectExportDialog::_export_path_changed(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing) {
519 
520 	if (updating)
521 		return;
522 
523 	Ref<EditorExportPreset> current = get_current_preset();
524 	ERR_FAIL_COND(current.is_null());
525 
526 	current->set_export_path(p_value);
527 	_update_presets();
528 }
529 
_script_export_mode_changed(int p_mode)530 void ProjectExportDialog::_script_export_mode_changed(int p_mode) {
531 
532 	if (updating)
533 		return;
534 
535 	Ref<EditorExportPreset> current = get_current_preset();
536 	ERR_FAIL_COND(current.is_null());
537 
538 	current->set_script_export_mode(p_mode);
539 
540 	_update_current_preset();
541 }
542 
_script_encryption_key_changed(const String & p_key)543 void ProjectExportDialog::_script_encryption_key_changed(const String &p_key) {
544 
545 	if (updating)
546 		return;
547 
548 	Ref<EditorExportPreset> current = get_current_preset();
549 	ERR_FAIL_COND(current.is_null());
550 
551 	current->set_script_encryption_key(p_key);
552 
553 	updating_script_key = true;
554 	_update_current_preset();
555 	updating_script_key = false;
556 }
557 
_validate_script_encryption_key(const String & p_key)558 bool ProjectExportDialog::_validate_script_encryption_key(const String &p_key) {
559 
560 	bool is_valid = false;
561 
562 	if (!p_key.empty() && p_key.is_valid_hex_number(false) && p_key.length() == 64) {
563 		is_valid = true;
564 	}
565 	return is_valid;
566 }
567 
_duplicate_preset()568 void ProjectExportDialog::_duplicate_preset() {
569 
570 	Ref<EditorExportPreset> current = get_current_preset();
571 	if (current.is_null())
572 		return;
573 
574 	Ref<EditorExportPreset> preset = current->get_platform()->create_preset();
575 	ERR_FAIL_COND(!preset.is_valid());
576 
577 	String name = current->get_name() + " (copy)";
578 	bool make_runnable = true;
579 	while (true) {
580 
581 		bool valid = true;
582 
583 		for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) {
584 			Ref<EditorExportPreset> p = EditorExport::get_singleton()->get_export_preset(i);
585 			if (p->get_platform() == preset->get_platform() && p->is_runnable()) {
586 				make_runnable = false;
587 			}
588 			if (p->get_name() == name) {
589 				valid = false;
590 				break;
591 			}
592 		}
593 
594 		if (valid)
595 			break;
596 
597 		name += " (copy)";
598 	}
599 
600 	preset->set_name(name);
601 	if (make_runnable)
602 		preset->set_runnable(make_runnable);
603 	preset->set_export_filter(current->get_export_filter());
604 	preset->set_include_filter(current->get_include_filter());
605 	preset->set_exclude_filter(current->get_exclude_filter());
606 	Vector<String> list = current->get_patches();
607 	for (int i = 0; i < list.size(); i++) {
608 		preset->add_patch(list[i]);
609 	}
610 	preset->set_custom_features(current->get_custom_features());
611 
612 	for (const List<PropertyInfo>::Element *E = current->get_properties().front(); E; E = E->next()) {
613 		preset->set(E->get().name, current->get(E->get().name));
614 	}
615 
616 	EditorExport::get_singleton()->add_export_preset(preset);
617 	_update_presets();
618 	_edit_preset(EditorExport::get_singleton()->get_export_preset_count() - 1);
619 }
620 
_delete_preset()621 void ProjectExportDialog::_delete_preset() {
622 
623 	Ref<EditorExportPreset> current = get_current_preset();
624 	if (current.is_null())
625 		return;
626 
627 	delete_confirm->set_text(vformat(TTR("Delete preset '%s'?"), current->get_name()));
628 	delete_confirm->popup_centered_minsize();
629 }
630 
_delete_preset_confirm()631 void ProjectExportDialog::_delete_preset_confirm() {
632 
633 	int idx = presets->get_current();
634 	_edit_preset(-1);
635 	export_button->set_disabled(true);
636 	get_ok()->set_disabled(true);
637 	EditorExport::get_singleton()->remove_export_preset(idx);
638 	_update_presets();
639 }
640 
get_drag_data_fw(const Point2 & p_point,Control * p_from)641 Variant ProjectExportDialog::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
642 
643 	if (p_from == presets) {
644 		int pos = presets->get_item_at_position(p_point, true);
645 
646 		if (pos >= 0) {
647 			Dictionary d;
648 			d["type"] = "export_preset";
649 			d["preset"] = pos;
650 
651 			HBoxContainer *drag = memnew(HBoxContainer);
652 			TextureRect *tr = memnew(TextureRect);
653 			tr->set_texture(presets->get_item_icon(pos));
654 			drag->add_child(tr);
655 			Label *label = memnew(Label);
656 			label->set_text(presets->get_item_text(pos));
657 			drag->add_child(label);
658 
659 			set_drag_preview(drag);
660 
661 			return d;
662 		}
663 	} else if (p_from == patches) {
664 
665 		TreeItem *item = patches->get_item_at_position(p_point);
666 
667 		if (item && item->get_cell_mode(0) == TreeItem::CELL_MODE_CHECK) {
668 
669 			int metadata = item->get_metadata(0);
670 			Dictionary d;
671 			d["type"] = "export_patch";
672 			d["patch"] = metadata;
673 
674 			Label *label = memnew(Label);
675 			label->set_text(item->get_text(0));
676 			set_drag_preview(label);
677 
678 			return d;
679 		}
680 	}
681 
682 	return Variant();
683 }
684 
can_drop_data_fw(const Point2 & p_point,const Variant & p_data,Control * p_from) const685 bool ProjectExportDialog::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
686 
687 	if (p_from == presets) {
688 		Dictionary d = p_data;
689 		if (!d.has("type") || String(d["type"]) != "export_preset")
690 			return false;
691 
692 		if (presets->get_item_at_position(p_point, true) < 0 && !presets->is_pos_at_end_of_items(p_point))
693 			return false;
694 	} else if (p_from == patches) {
695 
696 		Dictionary d = p_data;
697 		if (!d.has("type") || String(d["type"]) != "export_patch")
698 			return false;
699 
700 		patches->set_drop_mode_flags(Tree::DROP_MODE_ON_ITEM);
701 
702 		TreeItem *item = patches->get_item_at_position(p_point);
703 
704 		if (!item) {
705 
706 			return false;
707 		}
708 	}
709 
710 	return true;
711 }
712 
drop_data_fw(const Point2 & p_point,const Variant & p_data,Control * p_from)713 void ProjectExportDialog::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
714 
715 	if (p_from == presets) {
716 		Dictionary d = p_data;
717 		int from_pos = d["preset"];
718 
719 		int to_pos = -1;
720 
721 		if (presets->get_item_at_position(p_point, true) >= 0) {
722 			to_pos = presets->get_item_at_position(p_point, true);
723 		}
724 
725 		if (to_pos == -1 && !presets->is_pos_at_end_of_items(p_point))
726 			return;
727 
728 		if (to_pos == from_pos)
729 			return;
730 		else if (to_pos > from_pos) {
731 			to_pos--;
732 		}
733 
734 		Ref<EditorExportPreset> preset = EditorExport::get_singleton()->get_export_preset(from_pos);
735 		EditorExport::get_singleton()->remove_export_preset(from_pos);
736 		EditorExport::get_singleton()->add_export_preset(preset, to_pos);
737 
738 		_update_presets();
739 		if (to_pos >= 0)
740 			_edit_preset(to_pos);
741 		else
742 			_edit_preset(presets->get_item_count() - 1);
743 	} else if (p_from == patches) {
744 
745 		Dictionary d = p_data;
746 		if (!d.has("type") || String(d["type"]) != "export_patch")
747 			return;
748 
749 		int from_pos = d["patch"];
750 
751 		TreeItem *item = patches->get_item_at_position(p_point);
752 		if (!item)
753 			return;
754 
755 		int to_pos = item->get_cell_mode(0) == TreeItem::CELL_MODE_CHECK ? int(item->get_metadata(0)) : -1;
756 
757 		if (to_pos == from_pos)
758 			return;
759 		else if (to_pos > from_pos) {
760 			to_pos--;
761 		}
762 
763 		Ref<EditorExportPreset> preset = get_current_preset();
764 		String patch = preset->get_patch(from_pos);
765 		preset->remove_patch(from_pos);
766 		preset->add_patch(patch, to_pos);
767 
768 		_update_current_preset();
769 	}
770 }
771 
_export_type_changed(int p_which)772 void ProjectExportDialog::_export_type_changed(int p_which) {
773 
774 	if (updating)
775 		return;
776 
777 	Ref<EditorExportPreset> current = get_current_preset();
778 	if (current.is_null())
779 		return;
780 
781 	current->set_export_filter(EditorExportPreset::ExportFilter(p_which));
782 	updating = true;
783 	_fill_resource_tree();
784 	updating = false;
785 }
786 
_filter_changed(const String & p_filter)787 void ProjectExportDialog::_filter_changed(const String &p_filter) {
788 
789 	if (updating)
790 		return;
791 
792 	Ref<EditorExportPreset> current = get_current_preset();
793 	if (current.is_null())
794 		return;
795 
796 	current->set_include_filter(include_filters->get_text());
797 	current->set_exclude_filter(exclude_filters->get_text());
798 }
799 
_fill_resource_tree()800 void ProjectExportDialog::_fill_resource_tree() {
801 
802 	include_files->clear();
803 	include_label->hide();
804 	include_margin->hide();
805 
806 	Ref<EditorExportPreset> current = get_current_preset();
807 	if (current.is_null())
808 		return;
809 
810 	EditorExportPreset::ExportFilter f = current->get_export_filter();
811 
812 	if (f == EditorExportPreset::EXPORT_ALL_RESOURCES) {
813 		return;
814 	}
815 
816 	include_label->show();
817 	include_margin->show();
818 
819 	TreeItem *root = include_files->create_item();
820 
821 	_fill_tree(EditorFileSystem::get_singleton()->get_filesystem(), root, current, f == EditorExportPreset::EXPORT_SELECTED_SCENES);
822 }
823 
_fill_tree(EditorFileSystemDirectory * p_dir,TreeItem * p_item,Ref<EditorExportPreset> & current,bool p_only_scenes)824 bool ProjectExportDialog::_fill_tree(EditorFileSystemDirectory *p_dir, TreeItem *p_item, Ref<EditorExportPreset> &current, bool p_only_scenes) {
825 
826 	p_item->set_icon(0, get_icon("folder", "FileDialog"));
827 	p_item->set_text(0, p_dir->get_name() + "/");
828 
829 	bool used = false;
830 	for (int i = 0; i < p_dir->get_subdir_count(); i++) {
831 
832 		TreeItem *subdir = include_files->create_item(p_item);
833 		if (_fill_tree(p_dir->get_subdir(i), subdir, current, p_only_scenes)) {
834 			used = true;
835 		} else {
836 			memdelete(subdir);
837 		}
838 	}
839 
840 	for (int i = 0; i < p_dir->get_file_count(); i++) {
841 
842 		String type = p_dir->get_file_type(i);
843 		if (p_only_scenes && type != "PackedScene")
844 			continue;
845 
846 		TreeItem *file = include_files->create_item(p_item);
847 		file->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
848 		file->set_text(0, p_dir->get_file(i));
849 
850 		String path = p_dir->get_file_path(i);
851 
852 		file->set_icon(0, EditorNode::get_singleton()->get_class_icon(type));
853 		file->set_editable(0, true);
854 		file->set_checked(0, current->has_export_file(path));
855 		file->set_metadata(0, path);
856 
857 		used = true;
858 	}
859 
860 	return used;
861 }
862 
_tree_changed()863 void ProjectExportDialog::_tree_changed() {
864 
865 	if (updating)
866 		return;
867 
868 	Ref<EditorExportPreset> current = get_current_preset();
869 	if (current.is_null())
870 		return;
871 
872 	TreeItem *item = include_files->get_edited();
873 	if (!item)
874 		return;
875 
876 	String path = item->get_metadata(0);
877 	bool added = item->is_checked(0);
878 
879 	if (added) {
880 		current->add_export_file(path);
881 	} else {
882 		current->remove_export_file(path);
883 	}
884 }
885 
_export_pck_zip()886 void ProjectExportDialog::_export_pck_zip() {
887 
888 	export_pck_zip->popup_centered_ratio();
889 }
890 
_export_pck_zip_selected(const String & p_path)891 void ProjectExportDialog::_export_pck_zip_selected(const String &p_path) {
892 
893 	Ref<EditorExportPreset> current = get_current_preset();
894 	ERR_FAIL_COND(current.is_null());
895 	Ref<EditorExportPlatform> platform = current->get_platform();
896 	ERR_FAIL_COND(platform.is_null());
897 
898 	if (p_path.ends_with(".zip")) {
899 		platform->export_zip(current, export_pck_zip_debug->is_pressed(), p_path);
900 	} else if (p_path.ends_with(".pck")) {
901 		platform->export_pack(current, export_pck_zip_debug->is_pressed(), p_path);
902 	}
903 }
904 
_open_export_template_manager()905 void ProjectExportDialog::_open_export_template_manager() {
906 
907 	EditorNode::get_singleton()->open_export_template_manager();
908 	hide();
909 }
910 
_validate_export_path(const String & p_path)911 void ProjectExportDialog::_validate_export_path(const String &p_path) {
912 	// Disable export via OK button or Enter key if LineEdit has an empty filename
913 	bool invalid_path = (p_path.get_file().get_basename() == "");
914 
915 	// Check if state change before needlessly messing with signals
916 	if (invalid_path && export_project->get_ok()->is_disabled())
917 		return;
918 	if (!invalid_path && !export_project->get_ok()->is_disabled())
919 		return;
920 
921 	if (invalid_path) {
922 		export_project->get_ok()->set_disabled(true);
923 		export_project->get_line_edit()->disconnect("text_entered", export_project, "_file_entered");
924 	} else {
925 		export_project->get_ok()->set_disabled(false);
926 		export_project->get_line_edit()->connect("text_entered", export_project, "_file_entered");
927 	}
928 }
929 
_export_project()930 void ProjectExportDialog::_export_project() {
931 
932 	Ref<EditorExportPreset> current = get_current_preset();
933 	ERR_FAIL_COND(current.is_null());
934 	Ref<EditorExportPlatform> platform = current->get_platform();
935 	ERR_FAIL_COND(platform.is_null());
936 
937 	export_project->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
938 	export_project->clear_filters();
939 
940 	List<String> extension_list = platform->get_binary_extensions(current);
941 	for (int i = 0; i < extension_list.size(); i++) {
942 		export_project->add_filter("*." + extension_list[i] + " ; " + platform->get_name() + " Export");
943 	}
944 
945 	if (current->get_export_path() != "") {
946 		export_project->set_current_path(current->get_export_path());
947 	} else {
948 		if (extension_list.size() >= 1) {
949 			export_project->set_current_file(default_filename + "." + extension_list[0]);
950 		} else {
951 			export_project->set_current_file(default_filename);
952 		}
953 	}
954 
955 	// Ensure that signal is connected if previous attempt left it disconnected with _validate_export_path
956 	if (!export_project->get_line_edit()->is_connected("text_entered", export_project, "_file_entered")) {
957 		export_project->get_ok()->set_disabled(false);
958 		export_project->get_line_edit()->connect("text_entered", export_project, "_file_entered");
959 	}
960 
961 	export_project->set_mode(EditorFileDialog::MODE_SAVE_FILE);
962 	export_project->popup_centered_ratio();
963 }
964 
_export_project_to_path(const String & p_path)965 void ProjectExportDialog::_export_project_to_path(const String &p_path) {
966 	// Save this name for use in future exports (but drop the file extension)
967 	default_filename = p_path.get_file().get_basename();
968 	EditorSettings::get_singleton()->set_project_metadata("export_options", "default_filename", default_filename);
969 
970 	Ref<EditorExportPreset> current = get_current_preset();
971 	ERR_FAIL_COND(current.is_null());
972 	Ref<EditorExportPlatform> platform = current->get_platform();
973 	ERR_FAIL_COND(platform.is_null());
974 	current->set_export_path(p_path);
975 
976 	Error err = platform->export_project(current, export_debug->is_pressed(), p_path, 0);
977 	if (err != OK && err != ERR_SKIP) {
978 		if (err == ERR_FILE_NOT_FOUND) {
979 			error_dialog->set_text(vformat(TTR("Failed to export the project for platform '%s'.\nExport templates seem to be missing or invalid."), platform->get_name()));
980 		} else { // Assume misconfiguration. FIXME: Improve error handling and preset config validation.
981 			error_dialog->set_text(vformat(TTR("Failed to export the project for platform '%s'.\nThis might be due to a configuration issue in the export preset or your export settings."), platform->get_name()));
982 		}
983 
984 		ERR_PRINTS(vformat("Failed to export the project for platform '%s'.", platform->get_name()));
985 		error_dialog->show();
986 		error_dialog->popup_centered_minsize(Size2(300, 80));
987 	}
988 }
989 
_export_all_dialog()990 void ProjectExportDialog::_export_all_dialog() {
991 
992 	export_all_dialog->show();
993 	export_all_dialog->popup_centered_minsize(Size2(300, 80));
994 }
995 
_export_all_dialog_action(const String & p_str)996 void ProjectExportDialog::_export_all_dialog_action(const String &p_str) {
997 
998 	export_all_dialog->hide();
999 
1000 	_export_all(p_str != "release");
1001 }
1002 
_export_all(bool p_debug)1003 void ProjectExportDialog::_export_all(bool p_debug) {
1004 
1005 	String mode = p_debug ? TTR("Debug") : TTR("Release");
1006 	EditorProgress ep("exportall", TTR("Exporting All") + " " + mode, EditorExport::get_singleton()->get_export_preset_count(), true);
1007 
1008 	for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) {
1009 		Ref<EditorExportPreset> preset = EditorExport::get_singleton()->get_export_preset(i);
1010 		ERR_FAIL_COND(preset.is_null());
1011 		Ref<EditorExportPlatform> platform = preset->get_platform();
1012 		ERR_FAIL_COND(platform.is_null());
1013 
1014 		ep.step(preset->get_name(), i);
1015 
1016 		Error err = platform->export_project(preset, p_debug, preset->get_export_path(), 0);
1017 		if (err != OK && err != ERR_SKIP) {
1018 			if (err == ERR_FILE_BAD_PATH) {
1019 				error_dialog->set_text(TTR("The given export path doesn't exist:") + "\n" + preset->get_export_path().get_base_dir());
1020 			} else {
1021 				error_dialog->set_text(TTR("Export templates for this platform are missing/corrupted:") + " " + platform->get_name());
1022 			}
1023 			error_dialog->show();
1024 			error_dialog->popup_centered_minsize(Size2(300, 80));
1025 			ERR_PRINT("Failed to export project");
1026 		}
1027 	}
1028 }
1029 
_bind_methods()1030 void ProjectExportDialog::_bind_methods() {
1031 
1032 	ClassDB::bind_method("_add_preset", &ProjectExportDialog::_add_preset);
1033 	ClassDB::bind_method("_edit_preset", &ProjectExportDialog::_edit_preset);
1034 	ClassDB::bind_method("_update_parameters", &ProjectExportDialog::_update_parameters);
1035 	ClassDB::bind_method("_runnable_pressed", &ProjectExportDialog::_runnable_pressed);
1036 	ClassDB::bind_method("_name_changed", &ProjectExportDialog::_name_changed);
1037 	ClassDB::bind_method("_duplicate_preset", &ProjectExportDialog::_duplicate_preset);
1038 	ClassDB::bind_method("_delete_preset", &ProjectExportDialog::_delete_preset);
1039 	ClassDB::bind_method("_delete_preset_confirm", &ProjectExportDialog::_delete_preset_confirm);
1040 	ClassDB::bind_method("get_drag_data_fw", &ProjectExportDialog::get_drag_data_fw);
1041 	ClassDB::bind_method("can_drop_data_fw", &ProjectExportDialog::can_drop_data_fw);
1042 	ClassDB::bind_method("drop_data_fw", &ProjectExportDialog::drop_data_fw);
1043 	ClassDB::bind_method("_export_type_changed", &ProjectExportDialog::_export_type_changed);
1044 	ClassDB::bind_method("_filter_changed", &ProjectExportDialog::_filter_changed);
1045 	ClassDB::bind_method("_tree_changed", &ProjectExportDialog::_tree_changed);
1046 	ClassDB::bind_method("_patch_button_pressed", &ProjectExportDialog::_patch_button_pressed);
1047 	ClassDB::bind_method("_patch_selected", &ProjectExportDialog::_patch_selected);
1048 	ClassDB::bind_method("_patch_deleted", &ProjectExportDialog::_patch_deleted);
1049 	ClassDB::bind_method("_patch_edited", &ProjectExportDialog::_patch_edited);
1050 	ClassDB::bind_method("_export_pck_zip", &ProjectExportDialog::_export_pck_zip);
1051 	ClassDB::bind_method("_export_pck_zip_selected", &ProjectExportDialog::_export_pck_zip_selected);
1052 	ClassDB::bind_method("_open_export_template_manager", &ProjectExportDialog::_open_export_template_manager);
1053 	ClassDB::bind_method("_validate_export_path", &ProjectExportDialog::_validate_export_path);
1054 	ClassDB::bind_method("_export_path_changed", &ProjectExportDialog::_export_path_changed);
1055 	ClassDB::bind_method("_script_export_mode_changed", &ProjectExportDialog::_script_export_mode_changed);
1056 	ClassDB::bind_method("_script_encryption_key_changed", &ProjectExportDialog::_script_encryption_key_changed);
1057 	ClassDB::bind_method("_export_project", &ProjectExportDialog::_export_project);
1058 	ClassDB::bind_method("_export_project_to_path", &ProjectExportDialog::_export_project_to_path);
1059 	ClassDB::bind_method("_export_all", &ProjectExportDialog::_export_all);
1060 	ClassDB::bind_method("_export_all_dialog", &ProjectExportDialog::_export_all_dialog);
1061 	ClassDB::bind_method("_export_all_dialog_action", &ProjectExportDialog::_export_all_dialog_action);
1062 	ClassDB::bind_method("_custom_features_changed", &ProjectExportDialog::_custom_features_changed);
1063 	ClassDB::bind_method("_tab_changed", &ProjectExportDialog::_tab_changed);
1064 	ClassDB::bind_method("set_export_path", &ProjectExportDialog::set_export_path);
1065 	ClassDB::bind_method("get_export_path", &ProjectExportDialog::get_export_path);
1066 	ClassDB::bind_method("get_current_preset", &ProjectExportDialog::get_current_preset);
1067 	ClassDB::bind_method("_force_update_current_preset_parameters", &ProjectExportDialog::_force_update_current_preset_parameters);
1068 
1069 	ADD_PROPERTY(PropertyInfo(Variant::STRING, "export_path"), "set_export_path", "get_export_path");
1070 }
1071 
ProjectExportDialog()1072 ProjectExportDialog::ProjectExportDialog() {
1073 
1074 	set_title(TTR("Export"));
1075 	set_resizable(true);
1076 
1077 	VBoxContainer *main_vb = memnew(VBoxContainer);
1078 	add_child(main_vb);
1079 	HSplitContainer *hbox = memnew(HSplitContainer);
1080 	main_vb->add_child(hbox);
1081 	hbox->set_v_size_flags(SIZE_EXPAND_FILL);
1082 
1083 	// Presets list.
1084 
1085 	VBoxContainer *preset_vb = memnew(VBoxContainer);
1086 	preset_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1087 	hbox->add_child(preset_vb);
1088 
1089 	HBoxContainer *preset_hb = memnew(HBoxContainer);
1090 	preset_hb->add_child(memnew(Label(TTR("Presets"))));
1091 	preset_hb->add_spacer();
1092 	preset_vb->add_child(preset_hb);
1093 
1094 	add_preset = memnew(MenuButton);
1095 	add_preset->set_text(TTR("Add..."));
1096 	add_preset->get_popup()->connect("index_pressed", this, "_add_preset");
1097 	preset_hb->add_child(add_preset);
1098 	MarginContainer *mc = memnew(MarginContainer);
1099 	preset_vb->add_child(mc);
1100 	mc->set_v_size_flags(SIZE_EXPAND_FILL);
1101 	presets = memnew(ItemList);
1102 	presets->set_drag_forwarding(this);
1103 	mc->add_child(presets);
1104 	presets->connect("item_selected", this, "_edit_preset");
1105 	duplicate_preset = memnew(ToolButton);
1106 	preset_hb->add_child(duplicate_preset);
1107 	duplicate_preset->connect("pressed", this, "_duplicate_preset");
1108 	delete_preset = memnew(ToolButton);
1109 	preset_hb->add_child(delete_preset);
1110 	delete_preset->connect("pressed", this, "_delete_preset");
1111 
1112 	// Preset settings.
1113 
1114 	VBoxContainer *settings_vb = memnew(VBoxContainer);
1115 	settings_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1116 	hbox->add_child(settings_vb);
1117 
1118 	name = memnew(LineEdit);
1119 	settings_vb->add_margin_child(TTR("Name:"), name);
1120 	name->connect("text_changed", this, "_name_changed");
1121 	runnable = memnew(CheckButton);
1122 	runnable->set_text(TTR("Runnable"));
1123 	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."));
1124 	runnable->connect("pressed", this, "_runnable_pressed");
1125 	settings_vb->add_child(runnable);
1126 
1127 	export_path = memnew(EditorPropertyPath);
1128 	settings_vb->add_child(export_path);
1129 	export_path->set_label(TTR("Export Path"));
1130 	export_path->set_object_and_property(this, "export_path");
1131 	export_path->set_save_mode();
1132 	export_path->connect("property_changed", this, "_export_path_changed");
1133 
1134 	// Subsections.
1135 
1136 	sections = memnew(TabContainer);
1137 	sections->set_tab_align(TabContainer::ALIGN_LEFT);
1138 	sections->set_use_hidden_tabs_for_min_size(true);
1139 	settings_vb->add_child(sections);
1140 	sections->set_v_size_flags(SIZE_EXPAND_FILL);
1141 
1142 	// Main preset parameters.
1143 
1144 	parameters = memnew(EditorInspector);
1145 	sections->add_child(parameters);
1146 	parameters->set_name(TTR("Options"));
1147 	parameters->set_v_size_flags(SIZE_EXPAND_FILL);
1148 	parameters->connect("property_edited", this, "_update_parameters");
1149 	EditorExport::get_singleton()->connect("export_presets_updated", this, "_force_update_current_preset_parameters");
1150 
1151 	// Resources export parameters.
1152 
1153 	VBoxContainer *resources_vb = memnew(VBoxContainer);
1154 	sections->add_child(resources_vb);
1155 	resources_vb->set_name(TTR("Resources"));
1156 
1157 	export_filter = memnew(OptionButton);
1158 	export_filter->add_item(TTR("Export all resources in the project"));
1159 	export_filter->add_item(TTR("Export selected scenes (and dependencies)"));
1160 	export_filter->add_item(TTR("Export selected resources (and dependencies)"));
1161 	resources_vb->add_margin_child(TTR("Export Mode:"), export_filter);
1162 	export_filter->connect("item_selected", this, "_export_type_changed");
1163 
1164 	include_label = memnew(Label);
1165 	include_label->set_text(TTR("Resources to export:"));
1166 	resources_vb->add_child(include_label);
1167 	include_margin = memnew(MarginContainer);
1168 	include_margin->set_v_size_flags(SIZE_EXPAND_FILL);
1169 	resources_vb->add_child(include_margin);
1170 
1171 	include_files = memnew(Tree);
1172 	include_margin->add_child(include_files);
1173 	include_files->connect("item_edited", this, "_tree_changed");
1174 
1175 	include_filters = memnew(LineEdit);
1176 	resources_vb->add_margin_child(
1177 			TTR("Filters to export non-resource files/folders\n(comma-separated, e.g: *.json, *.txt, docs/*)"),
1178 			include_filters);
1179 	include_filters->connect("text_changed", this, "_filter_changed");
1180 
1181 	exclude_filters = memnew(LineEdit);
1182 	resources_vb->add_margin_child(
1183 			TTR("Filters to exclude files/folders from project\n(comma-separated, e.g: *.json, *.txt, docs/*)"),
1184 			exclude_filters);
1185 	exclude_filters->connect("text_changed", this, "_filter_changed");
1186 
1187 	// Patch packages.
1188 
1189 	VBoxContainer *patch_vb = memnew(VBoxContainer);
1190 	sections->add_child(patch_vb);
1191 	patch_vb->set_name(TTR("Patches"));
1192 
1193 	// FIXME: Patching support doesn't seem properly implemented yet, so we hide it.
1194 	// The rest of the code is still kept for now, in the hope that it will be made
1195 	// functional and reactivated.
1196 	patch_vb->hide();
1197 
1198 	patches = memnew(Tree);
1199 	patch_vb->add_child(patches);
1200 	patches->set_v_size_flags(SIZE_EXPAND_FILL);
1201 	patches->set_hide_root(true);
1202 	patches->connect("button_pressed", this, "_patch_button_pressed");
1203 	patches->connect("item_edited", this, "_patch_edited");
1204 	patches->set_drag_forwarding(this);
1205 	patches->set_edit_checkbox_cell_only_when_checkbox_is_pressed(true);
1206 
1207 	HBoxContainer *patches_hb = memnew(HBoxContainer);
1208 	patch_vb->add_child(patches_hb);
1209 	patches_hb->add_spacer();
1210 	patch_export = memnew(Button);
1211 	patch_export->set_text(TTR("Make Patch"));
1212 	patches_hb->add_child(patch_export);
1213 	patches_hb->add_spacer();
1214 
1215 	patch_dialog = memnew(EditorFileDialog);
1216 	patch_dialog->add_filter("*.pck ; " + TTR("Pack File"));
1217 	patch_dialog->set_mode(EditorFileDialog::MODE_OPEN_FILE);
1218 	patch_dialog->connect("file_selected", this, "_patch_selected");
1219 	add_child(patch_dialog);
1220 
1221 	patch_erase = memnew(ConfirmationDialog);
1222 	patch_erase->get_ok()->set_text(TTR("Delete"));
1223 	patch_erase->connect("confirmed", this, "_patch_deleted");
1224 	add_child(patch_erase);
1225 
1226 	// Feature tags.
1227 
1228 	VBoxContainer *feature_vb = memnew(VBoxContainer);
1229 	feature_vb->set_name(TTR("Features"));
1230 	custom_features = memnew(LineEdit);
1231 	custom_features->connect("text_changed", this, "_custom_features_changed");
1232 	feature_vb->add_margin_child(TTR("Custom (comma-separated):"), custom_features);
1233 	custom_feature_display = memnew(RichTextLabel);
1234 	custom_feature_display->set_v_size_flags(Control::SIZE_EXPAND_FILL);
1235 	feature_vb->add_margin_child(TTR("Feature List:"), custom_feature_display, true);
1236 	sections->add_child(feature_vb);
1237 
1238 	// Script export parameters.
1239 
1240 	updating_script_key = false;
1241 
1242 	VBoxContainer *script_vb = memnew(VBoxContainer);
1243 	script_vb->set_name(TTR("Script"));
1244 	script_mode = memnew(OptionButton);
1245 	script_vb->add_margin_child(TTR("Script Export Mode:"), script_mode);
1246 	script_mode->add_item(TTR("Text"), (int)EditorExportPreset::MODE_SCRIPT_TEXT);
1247 	script_mode->add_item(TTR("Compiled"), (int)EditorExportPreset::MODE_SCRIPT_COMPILED);
1248 	script_mode->add_item(TTR("Encrypted (Provide Key Below)"), (int)EditorExportPreset::MODE_SCRIPT_ENCRYPTED);
1249 	script_mode->connect("item_selected", this, "_script_export_mode_changed");
1250 	script_key = memnew(LineEdit);
1251 	script_key->connect("text_changed", this, "_script_encryption_key_changed");
1252 	script_key_error = memnew(Label);
1253 	script_key_error->set_text("- " + TTR("Invalid Encryption Key (must be 64 characters long)"));
1254 	script_key_error->add_color_override("font_color", EditorNode::get_singleton()->get_gui_base()->get_color("error_color", "Editor"));
1255 	script_vb->add_margin_child(TTR("Script Encryption Key (256-bits as hex):"), script_key);
1256 	script_vb->add_child(script_key_error);
1257 	sections->add_child(script_vb);
1258 
1259 	sections->connect("tab_changed", this, "_tab_changed");
1260 
1261 	// Disable by default.
1262 	name->set_editable(false);
1263 	export_path->hide();
1264 	runnable->set_disabled(true);
1265 	duplicate_preset->set_disabled(true);
1266 	delete_preset->set_disabled(true);
1267 	script_key_error->hide();
1268 	sections->hide();
1269 	parameters->edit(NULL);
1270 
1271 	// Deletion dialog.
1272 
1273 	delete_confirm = memnew(ConfirmationDialog);
1274 	add_child(delete_confirm);
1275 	delete_confirm->get_ok()->set_text(TTR("Delete"));
1276 	delete_confirm->connect("confirmed", this, "_delete_preset_confirm");
1277 
1278 	// Export buttons, dialogs and errors.
1279 
1280 	updating = false;
1281 
1282 	get_cancel()->set_text(TTR("Close"));
1283 	get_ok()->set_text(TTR("Export PCK/Zip"));
1284 	export_button = add_button(TTR("Export Project"), !OS::get_singleton()->get_swap_ok_cancel(), "export");
1285 	export_button->connect("pressed", this, "_export_project");
1286 	// Disable initially before we select a valid preset
1287 	export_button->set_disabled(true);
1288 	get_ok()->set_disabled(true);
1289 
1290 	export_all_dialog = memnew(ConfirmationDialog);
1291 	add_child(export_all_dialog);
1292 	export_all_dialog->set_title("Export All");
1293 	export_all_dialog->set_text(TTR("Export mode?"));
1294 	export_all_dialog->get_ok()->hide();
1295 	export_all_dialog->add_button(TTR("Debug"), true, "debug");
1296 	export_all_dialog->add_button(TTR("Release"), true, "release");
1297 	export_all_dialog->connect("custom_action", this, "_export_all_dialog_action");
1298 
1299 	export_all_button = add_button(TTR("Export All"), !OS::get_singleton()->get_swap_ok_cancel(), "export");
1300 	export_all_button->connect("pressed", this, "_export_all_dialog");
1301 	export_all_button->set_disabled(true);
1302 
1303 	export_pck_zip = memnew(EditorFileDialog);
1304 	export_pck_zip->add_filter("*.zip ; " + TTR("ZIP File"));
1305 	export_pck_zip->add_filter("*.pck ; " + TTR("Godot Game Pack"));
1306 	export_pck_zip->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
1307 	export_pck_zip->set_mode(EditorFileDialog::MODE_SAVE_FILE);
1308 	add_child(export_pck_zip);
1309 	export_pck_zip->connect("file_selected", this, "_export_pck_zip_selected");
1310 
1311 	export_error = memnew(Label);
1312 	main_vb->add_child(export_error);
1313 	export_error->hide();
1314 	export_error->add_color_override("font_color", EditorNode::get_singleton()->get_gui_base()->get_color("error_color", "Editor"));
1315 
1316 	export_templates_error = memnew(HBoxContainer);
1317 	main_vb->add_child(export_templates_error);
1318 	export_templates_error->hide();
1319 
1320 	Label *export_error2 = memnew(Label);
1321 	export_templates_error->add_child(export_error2);
1322 	export_error2->add_color_override("font_color", EditorNode::get_singleton()->get_gui_base()->get_color("error_color", "Editor"));
1323 	export_error2->set_text(" - " + TTR("Export templates for this platform are missing:") + " ");
1324 
1325 	error_dialog = memnew(AcceptDialog);
1326 	error_dialog->set_title("Error");
1327 	error_dialog->set_text(TTR("Export templates for this platform are missing/corrupted:") + " ");
1328 	main_vb->add_child(error_dialog);
1329 	error_dialog->hide();
1330 
1331 	LinkButton *download_templates = memnew(LinkButton);
1332 	download_templates->set_text(TTR("Manage Export Templates"));
1333 	download_templates->set_v_size_flags(SIZE_SHRINK_CENTER);
1334 	export_templates_error->add_child(download_templates);
1335 	download_templates->connect("pressed", this, "_open_export_template_manager");
1336 
1337 	export_project = memnew(EditorFileDialog);
1338 	export_project->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
1339 	add_child(export_project);
1340 	export_project->connect("file_selected", this, "_export_project_to_path");
1341 	export_project->get_line_edit()->connect("text_changed", this, "_validate_export_path");
1342 
1343 	export_debug = memnew(CheckBox);
1344 	export_debug->set_text(TTR("Export With Debug"));
1345 	export_debug->set_pressed(true);
1346 	export_project->get_vbox()->add_child(export_debug);
1347 
1348 	export_pck_zip_debug = memnew(CheckBox);
1349 	export_pck_zip_debug->set_text(TTR("Export With Debug"));
1350 	export_pck_zip_debug->set_pressed(true);
1351 	export_pck_zip->get_vbox()->add_child(export_pck_zip_debug);
1352 
1353 	set_hide_on_ok(false);
1354 
1355 	editor_icons = "EditorIcons";
1356 
1357 	default_filename = EditorSettings::get_singleton()->get_project_metadata("export_options", "default_filename", "");
1358 	// If no default set, use project name
1359 	if (default_filename == "") {
1360 		// If no project name defined, use a sane default
1361 		default_filename = ProjectSettings::get_singleton()->get("application/config/name");
1362 		if (default_filename == "") {
1363 			default_filename = "UnnamedProject";
1364 		}
1365 	}
1366 
1367 	_update_export_all();
1368 }
1369 
~ProjectExportDialog()1370 ProjectExportDialog::~ProjectExportDialog() {
1371 }
1372