1 | /**************************************************************************/ |
2 | /* project_export.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 | |
31 | #include "project_export.h" |
32 | |
33 | #include "core/config/project_settings.h" |
34 | #include "core/version.h" |
35 | #include "editor/editor_file_system.h" |
36 | #include "editor/editor_node.h" |
37 | #include "editor/editor_properties.h" |
38 | #include "editor/editor_scale.h" |
39 | #include "editor/editor_settings.h" |
40 | #include "editor/editor_string_names.h" |
41 | #include "editor/export/editor_export.h" |
42 | #include "editor/gui/editor_file_dialog.h" |
43 | #include "editor/import/resource_importer_texture_settings.h" |
44 | #include "scene/gui/check_box.h" |
45 | #include "scene/gui/check_button.h" |
46 | #include "scene/gui/item_list.h" |
47 | #include "scene/gui/link_button.h" |
48 | #include "scene/gui/menu_button.h" |
49 | #include "scene/gui/option_button.h" |
50 | #include "scene/gui/popup_menu.h" |
51 | #include "scene/gui/split_container.h" |
52 | #include "scene/gui/tab_container.h" |
53 | #include "scene/gui/texture_rect.h" |
54 | #include "scene/gui/tree.h" |
55 | |
56 | void ProjectExportTextureFormatError::_on_fix_texture_format_pressed() { |
57 | ProjectSettings::get_singleton()->set_setting(setting_identifier, true); |
58 | ProjectSettings::get_singleton()->save(); |
59 | EditorFileSystem::get_singleton()->scan_changes(); |
60 | emit_signal("texture_format_enabled" ); |
61 | } |
62 | |
63 | void ProjectExportTextureFormatError::_bind_methods() { |
64 | ADD_SIGNAL(MethodInfo("texture_format_enabled" )); |
65 | } |
66 | |
67 | void ProjectExportTextureFormatError::_notification(int p_what) { |
68 | switch (p_what) { |
69 | case NOTIFICATION_THEME_CHANGED: { |
70 | texture_format_error_label->add_theme_color_override("font_color" , get_theme_color(SNAME("error_color" ), EditorStringName(Editor))); |
71 | } break; |
72 | } |
73 | } |
74 | |
75 | void ProjectExportTextureFormatError::show_for_texture_format(const String &p_friendly_name, const String &p_setting_identifier) { |
76 | texture_format_error_label->set_text(vformat(TTR("Target platform requires '%s' texture compression. Enable 'Import %s' to fix." ), p_friendly_name, p_friendly_name.replace("/" , " " ))); |
77 | setting_identifier = p_setting_identifier; |
78 | show(); |
79 | } |
80 | |
81 | ProjectExportTextureFormatError::ProjectExportTextureFormatError() { |
82 | // Set up the label. |
83 | texture_format_error_label = memnew(Label); |
84 | add_child(texture_format_error_label); |
85 | // Set up the fix button. |
86 | fix_texture_format_button = memnew(LinkButton); |
87 | fix_texture_format_button->set_v_size_flags(Control::SIZE_SHRINK_CENTER); |
88 | fix_texture_format_button->set_text(TTR("Fix Import" )); |
89 | add_child(fix_texture_format_button); |
90 | fix_texture_format_button->connect("pressed" , callable_mp(this, &ProjectExportTextureFormatError::_on_fix_texture_format_pressed)); |
91 | } |
92 | |
93 | void ProjectExportDialog::_theme_changed() { |
94 | duplicate_preset->set_icon(presets->get_editor_theme_icon(SNAME("Duplicate" ))); |
95 | delete_preset->set_icon(presets->get_editor_theme_icon(SNAME("Remove" ))); |
96 | } |
97 | |
98 | void ProjectExportDialog::_notification(int p_what) { |
99 | switch (p_what) { |
100 | case NOTIFICATION_VISIBILITY_CHANGED: { |
101 | if (!is_visible()) { |
102 | EditorSettings::get_singleton()->set_project_metadata("dialog_bounds" , "export" , Rect2(get_position(), get_size())); |
103 | } |
104 | } break; |
105 | |
106 | case NOTIFICATION_READY: { |
107 | duplicate_preset->set_icon(presets->get_editor_theme_icon(SNAME("Duplicate" ))); |
108 | delete_preset->set_icon(presets->get_editor_theme_icon(SNAME("Remove" ))); |
109 | connect("confirmed" , callable_mp(this, &ProjectExportDialog::_export_pck_zip)); |
110 | _update_export_all(); |
111 | } break; |
112 | } |
113 | } |
114 | |
115 | void ProjectExportDialog::() { |
116 | add_preset->get_popup()->clear(); |
117 | for (int i = 0; i < EditorExport::get_singleton()->get_export_platform_count(); i++) { |
118 | Ref<EditorExportPlatform> plat = EditorExport::get_singleton()->get_export_platform(i); |
119 | |
120 | add_preset->get_popup()->add_icon_item(plat->get_logo(), plat->get_name()); |
121 | } |
122 | |
123 | _update_presets(); |
124 | if (presets->get_current() >= 0) { |
125 | _update_current_preset(); // triggers rescan for templates if newly installed |
126 | } |
127 | |
128 | // Restore valid window bounds or pop up at default size. |
129 | Rect2 saved_size = EditorSettings::get_singleton()->get_project_metadata("dialog_bounds" , "export" , Rect2()); |
130 | if (saved_size != Rect2()) { |
131 | popup(saved_size); |
132 | } else { |
133 | popup_centered_clamped(Size2(900, 700) * EDSCALE, 0.8); |
134 | } |
135 | } |
136 | |
137 | void ProjectExportDialog::_add_preset(int p_platform) { |
138 | Ref<EditorExportPreset> preset = EditorExport::get_singleton()->get_export_platform(p_platform)->create_preset(); |
139 | ERR_FAIL_COND(!preset.is_valid()); |
140 | |
141 | String preset_name = EditorExport::get_singleton()->get_export_platform(p_platform)->get_name(); |
142 | bool make_runnable = true; |
143 | int attempt = 1; |
144 | while (true) { |
145 | bool valid = true; |
146 | |
147 | for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) { |
148 | Ref<EditorExportPreset> p = EditorExport::get_singleton()->get_export_preset(i); |
149 | if (p->get_platform() == preset->get_platform() && p->is_runnable()) { |
150 | make_runnable = false; |
151 | } |
152 | if (p->get_name() == preset_name) { |
153 | valid = false; |
154 | break; |
155 | } |
156 | } |
157 | |
158 | if (valid) { |
159 | break; |
160 | } |
161 | |
162 | attempt++; |
163 | preset_name = EditorExport::get_singleton()->get_export_platform(p_platform)->get_name() + " " + itos(attempt); |
164 | } |
165 | |
166 | preset->set_name(preset_name); |
167 | if (make_runnable) { |
168 | preset->set_runnable(make_runnable); |
169 | } |
170 | EditorExport::get_singleton()->add_export_preset(preset); |
171 | _update_presets(); |
172 | _edit_preset(EditorExport::get_singleton()->get_export_preset_count() - 1); |
173 | } |
174 | |
175 | void ProjectExportDialog::_force_update_current_preset_parameters() { |
176 | // Force the parameters section to refresh its UI. |
177 | parameters->edit(nullptr); |
178 | _update_current_preset(); |
179 | } |
180 | |
181 | void ProjectExportDialog::_update_current_preset() { |
182 | _edit_preset(presets->get_current()); |
183 | } |
184 | |
185 | void ProjectExportDialog::_update_presets() { |
186 | updating = true; |
187 | |
188 | Ref<EditorExportPreset> current; |
189 | if (presets->get_current() >= 0 && presets->get_current() < presets->get_item_count()) { |
190 | current = get_current_preset(); |
191 | } |
192 | |
193 | int current_idx = -1; |
194 | presets->clear(); |
195 | for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) { |
196 | Ref<EditorExportPreset> preset = EditorExport::get_singleton()->get_export_preset(i); |
197 | if (preset == current) { |
198 | current_idx = i; |
199 | } |
200 | |
201 | String preset_name = preset->get_name(); |
202 | if (preset->is_runnable()) { |
203 | preset_name += " (" + TTR("Runnable" ) + ")" ; |
204 | } |
205 | preset->update_files(); |
206 | presets->add_item(preset_name, preset->get_platform()->get_logo()); |
207 | } |
208 | |
209 | if (current_idx != -1) { |
210 | presets->select(current_idx); |
211 | } |
212 | |
213 | updating = false; |
214 | } |
215 | |
216 | void ProjectExportDialog::_update_export_all() { |
217 | bool can_export = EditorExport::get_singleton()->get_export_preset_count() > 0; |
218 | |
219 | for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) { |
220 | Ref<EditorExportPreset> preset = EditorExport::get_singleton()->get_export_preset(i); |
221 | bool needs_templates; |
222 | String error; |
223 | if (preset->get_export_path().is_empty() || !preset->get_platform()->can_export(preset, error, needs_templates)) { |
224 | can_export = false; |
225 | break; |
226 | } |
227 | } |
228 | |
229 | export_all_button->set_disabled(!can_export); |
230 | |
231 | if (can_export) { |
232 | export_all_button->set_tooltip_text(TTR("Export the project for all the presets defined." )); |
233 | } else { |
234 | export_all_button->set_tooltip_text(TTR("All presets must have an export path defined for Export All to work." )); |
235 | } |
236 | } |
237 | |
238 | void ProjectExportDialog::_edit_preset(int p_index) { |
239 | if (p_index < 0 || p_index >= presets->get_item_count()) { |
240 | name->set_text("" ); |
241 | name->set_editable(false); |
242 | export_path->hide(); |
243 | runnable->set_disabled(true); |
244 | parameters->edit(nullptr); |
245 | presets->deselect_all(); |
246 | duplicate_preset->set_disabled(true); |
247 | delete_preset->set_disabled(true); |
248 | sections->hide(); |
249 | export_error->hide(); |
250 | export_templates_error->hide(); |
251 | return; |
252 | } |
253 | |
254 | Ref<EditorExportPreset> current = EditorExport::get_singleton()->get_export_preset(p_index); |
255 | ERR_FAIL_COND(current.is_null()); |
256 | |
257 | updating = true; |
258 | |
259 | presets->select(p_index); |
260 | sections->show(); |
261 | |
262 | name->set_editable(true); |
263 | export_path->show(); |
264 | duplicate_preset->set_disabled(false); |
265 | delete_preset->set_disabled(false); |
266 | get_ok_button()->set_disabled(false); |
267 | name->set_text(current->get_name()); |
268 | |
269 | List<String> extension_list = current->get_platform()->get_binary_extensions(current); |
270 | Vector<String> extension_vector; |
271 | for (int i = 0; i < extension_list.size(); i++) { |
272 | extension_vector.push_back("*." + extension_list[i]); |
273 | } |
274 | |
275 | export_path->setup(extension_vector, false, true); |
276 | export_path->update_property(); |
277 | runnable->set_disabled(false); |
278 | runnable->set_pressed(current->is_runnable()); |
279 | parameters->set_object_class(current->get_platform()->get_class_name()); |
280 | parameters->edit(current.ptr()); |
281 | |
282 | export_filter->select(current->get_export_filter()); |
283 | include_filters->set_text(current->get_include_filter()); |
284 | include_label->set_text(current->get_export_filter() == EditorExportPreset::EXCLUDE_SELECTED_RESOURCES ? TTR("Resources to exclude:" ) : TTR("Resources to export:" )); |
285 | exclude_filters->set_text(current->get_exclude_filter()); |
286 | server_strip_message->set_visible(current->get_export_filter() == EditorExportPreset::EXPORT_CUSTOMIZED); |
287 | |
288 | _fill_resource_tree(); |
289 | |
290 | bool needs_templates; |
291 | String error; |
292 | if (!current->get_platform()->can_export(current, error, needs_templates)) { |
293 | if (!error.is_empty()) { |
294 | Vector<String> items = error.split("\n" , false); |
295 | error = "" ; |
296 | for (int i = 0; i < items.size(); i++) { |
297 | if (i > 0) { |
298 | error += "\n" ; |
299 | } |
300 | error += " - " + items[i]; |
301 | } |
302 | |
303 | export_error->set_text(error); |
304 | export_error->show(); |
305 | } else { |
306 | export_error->hide(); |
307 | } |
308 | if (needs_templates) { |
309 | export_templates_error->show(); |
310 | } else { |
311 | export_templates_error->hide(); |
312 | } |
313 | |
314 | export_warning->hide(); |
315 | export_button->set_disabled(true); |
316 | } else { |
317 | if (error != String()) { |
318 | Vector<String> items = error.split("\n" , false); |
319 | error = "" ; |
320 | for (int i = 0; i < items.size(); i++) { |
321 | if (i > 0) { |
322 | error += "\n" ; |
323 | } |
324 | error += " - " + items[i]; |
325 | } |
326 | export_warning->set_text(error); |
327 | export_warning->show(); |
328 | } else { |
329 | export_warning->hide(); |
330 | } |
331 | |
332 | export_error->hide(); |
333 | export_templates_error->hide(); |
334 | export_button->set_disabled(false); |
335 | } |
336 | |
337 | custom_features->set_text(current->get_custom_features()); |
338 | _update_feature_list(); |
339 | _update_export_all(); |
340 | child_controls_changed(); |
341 | |
342 | if ((feature_set.has("s3tc" ) || feature_set.has("bptc" )) && !ResourceImporterTextureSettings::should_import_s3tc_bptc()) { |
343 | export_texture_format_error->show_for_texture_format("S3TC/BPTC" , "rendering/textures/vram_compression/import_s3tc_bptc" ); |
344 | } else if ((feature_set.has("etc2" ) || feature_set.has("astc" )) && !ResourceImporterTextureSettings::should_import_etc2_astc()) { |
345 | export_texture_format_error->show_for_texture_format("ETC2/ASTC" , "rendering/textures/vram_compression/import_etc2_astc" ); |
346 | } else { |
347 | export_texture_format_error->hide(); |
348 | } |
349 | |
350 | String enc_in_filters_str = current->get_enc_in_filter(); |
351 | String enc_ex_filters_str = current->get_enc_ex_filter(); |
352 | if (!updating_enc_filters) { |
353 | enc_in_filters->set_text(enc_in_filters_str); |
354 | enc_ex_filters->set_text(enc_ex_filters_str); |
355 | } |
356 | |
357 | bool enc_pck_mode = current->get_enc_pck(); |
358 | enc_pck->set_pressed(enc_pck_mode); |
359 | |
360 | enc_directory->set_disabled(!enc_pck_mode); |
361 | enc_in_filters->set_editable(enc_pck_mode); |
362 | enc_ex_filters->set_editable(enc_pck_mode); |
363 | script_key->set_editable(enc_pck_mode); |
364 | |
365 | bool enc_directory_mode = current->get_enc_directory(); |
366 | enc_directory->set_pressed(enc_directory_mode); |
367 | |
368 | String key = current->get_script_encryption_key(); |
369 | if (!updating_script_key) { |
370 | script_key->set_text(key); |
371 | } |
372 | if (enc_pck_mode) { |
373 | script_key->set_editable(true); |
374 | |
375 | bool key_valid = _validate_script_encryption_key(key); |
376 | if (key_valid) { |
377 | script_key_error->hide(); |
378 | } else { |
379 | script_key_error->show(); |
380 | } |
381 | } else { |
382 | script_key->set_editable(false); |
383 | script_key_error->hide(); |
384 | } |
385 | |
386 | updating = false; |
387 | } |
388 | |
389 | void ProjectExportDialog::_update_feature_list() { |
390 | Ref<EditorExportPreset> current = get_current_preset(); |
391 | ERR_FAIL_COND(current.is_null()); |
392 | |
393 | List<String> features_list; |
394 | |
395 | current->get_platform()->get_platform_features(&features_list); |
396 | current->get_platform()->get_preset_features(current, &features_list); |
397 | |
398 | String custom = current->get_custom_features(); |
399 | Vector<String> custom_list = custom.split("," ); |
400 | for (int i = 0; i < custom_list.size(); i++) { |
401 | String f = custom_list[i].strip_edges(); |
402 | if (!f.is_empty()) { |
403 | features_list.push_back(f); |
404 | } |
405 | } |
406 | |
407 | feature_set.clear(); |
408 | for (const String &E : features_list) { |
409 | feature_set.insert(E); |
410 | } |
411 | |
412 | custom_feature_display->clear(); |
413 | String text; |
414 | bool first = true; |
415 | for (const String &E : feature_set) { |
416 | if (!first) { |
417 | text += ", " ; |
418 | } else { |
419 | first = false; |
420 | } |
421 | text += E; |
422 | } |
423 | custom_feature_display->add_text(text); |
424 | } |
425 | |
426 | void ProjectExportDialog::_custom_features_changed(const String &p_text) { |
427 | if (updating) { |
428 | return; |
429 | } |
430 | |
431 | Ref<EditorExportPreset> current = get_current_preset(); |
432 | ERR_FAIL_COND(current.is_null()); |
433 | |
434 | current->set_custom_features(p_text); |
435 | _update_feature_list(); |
436 | } |
437 | |
438 | void ProjectExportDialog::_tab_changed(int) { |
439 | _update_feature_list(); |
440 | } |
441 | |
442 | void ProjectExportDialog::_update_parameters(const String &p_edited_property) { |
443 | _update_current_preset(); |
444 | } |
445 | |
446 | void ProjectExportDialog::_runnable_pressed() { |
447 | if (updating) { |
448 | return; |
449 | } |
450 | |
451 | Ref<EditorExportPreset> current = get_current_preset(); |
452 | ERR_FAIL_COND(current.is_null()); |
453 | |
454 | if (runnable->is_pressed()) { |
455 | for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) { |
456 | Ref<EditorExportPreset> p = EditorExport::get_singleton()->get_export_preset(i); |
457 | if (p->get_platform() == current->get_platform()) { |
458 | p->set_runnable(current == p); |
459 | } |
460 | } |
461 | } else { |
462 | current->set_runnable(false); |
463 | } |
464 | |
465 | _update_presets(); |
466 | } |
467 | |
468 | void ProjectExportDialog::_name_changed(const String &p_string) { |
469 | if (updating) { |
470 | return; |
471 | } |
472 | |
473 | Ref<EditorExportPreset> current = get_current_preset(); |
474 | ERR_FAIL_COND(current.is_null()); |
475 | |
476 | current->set_name(p_string); |
477 | _update_presets(); |
478 | } |
479 | |
480 | void ProjectExportDialog::set_export_path(const String &p_value) { |
481 | Ref<EditorExportPreset> current = get_current_preset(); |
482 | ERR_FAIL_COND(current.is_null()); |
483 | |
484 | current->set_export_path(p_value); |
485 | } |
486 | |
487 | String ProjectExportDialog::get_export_path() { |
488 | Ref<EditorExportPreset> current = get_current_preset(); |
489 | ERR_FAIL_COND_V(current.is_null(), String("" )); |
490 | |
491 | return current->get_export_path(); |
492 | } |
493 | |
494 | Ref<EditorExportPreset> ProjectExportDialog::get_current_preset() const { |
495 | return EditorExport::get_singleton()->get_export_preset(presets->get_current()); |
496 | } |
497 | |
498 | void ProjectExportDialog::_export_path_changed(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing) { |
499 | if (updating) { |
500 | return; |
501 | } |
502 | |
503 | Ref<EditorExportPreset> current = get_current_preset(); |
504 | ERR_FAIL_COND(current.is_null()); |
505 | |
506 | current->set_export_path(p_value); |
507 | _update_presets(); |
508 | _update_export_all(); |
509 | } |
510 | |
511 | void ProjectExportDialog::_enc_filters_changed(const String &p_filters) { |
512 | if (updating) { |
513 | return; |
514 | } |
515 | |
516 | Ref<EditorExportPreset> current = get_current_preset(); |
517 | ERR_FAIL_COND(current.is_null()); |
518 | |
519 | current->set_enc_in_filter(enc_in_filters->get_text()); |
520 | current->set_enc_ex_filter(enc_ex_filters->get_text()); |
521 | |
522 | updating_enc_filters = true; |
523 | _update_current_preset(); |
524 | updating_enc_filters = false; |
525 | } |
526 | |
527 | void ProjectExportDialog::_open_key_help_link() { |
528 | OS::get_singleton()->shell_open(vformat("%s/contributing/development/compiling/compiling_with_script_encryption_key.html" , VERSION_DOCS_URL)); |
529 | } |
530 | |
531 | void ProjectExportDialog::_enc_pck_changed(bool p_pressed) { |
532 | if (updating) { |
533 | return; |
534 | } |
535 | |
536 | Ref<EditorExportPreset> current = get_current_preset(); |
537 | ERR_FAIL_COND(current.is_null()); |
538 | |
539 | current->set_enc_pck(p_pressed); |
540 | enc_directory->set_disabled(!p_pressed); |
541 | enc_in_filters->set_editable(p_pressed); |
542 | enc_ex_filters->set_editable(p_pressed); |
543 | script_key->set_editable(p_pressed); |
544 | |
545 | _update_current_preset(); |
546 | } |
547 | |
548 | void ProjectExportDialog::_enc_directory_changed(bool p_pressed) { |
549 | if (updating) { |
550 | return; |
551 | } |
552 | |
553 | Ref<EditorExportPreset> current = get_current_preset(); |
554 | ERR_FAIL_COND(current.is_null()); |
555 | |
556 | current->set_enc_directory(p_pressed); |
557 | |
558 | _update_current_preset(); |
559 | } |
560 | |
561 | void ProjectExportDialog::_script_encryption_key_changed(const String &p_key) { |
562 | if (updating) { |
563 | return; |
564 | } |
565 | |
566 | Ref<EditorExportPreset> current = get_current_preset(); |
567 | ERR_FAIL_COND(current.is_null()); |
568 | |
569 | current->set_script_encryption_key(p_key); |
570 | |
571 | updating_script_key = true; |
572 | _update_current_preset(); |
573 | updating_script_key = false; |
574 | } |
575 | |
576 | bool ProjectExportDialog::_validate_script_encryption_key(const String &p_key) { |
577 | bool is_valid = false; |
578 | |
579 | if (!p_key.is_empty() && p_key.is_valid_hex_number(false) && p_key.length() == 64) { |
580 | is_valid = true; |
581 | } |
582 | return is_valid; |
583 | } |
584 | |
585 | void ProjectExportDialog::_duplicate_preset() { |
586 | Ref<EditorExportPreset> current = get_current_preset(); |
587 | if (current.is_null()) { |
588 | return; |
589 | } |
590 | |
591 | Ref<EditorExportPreset> preset = current->get_platform()->create_preset(); |
592 | ERR_FAIL_COND(!preset.is_valid()); |
593 | |
594 | String preset_name = current->get_name() + " (copy)" ; |
595 | bool make_runnable = true; |
596 | while (true) { |
597 | bool valid = true; |
598 | |
599 | for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) { |
600 | Ref<EditorExportPreset> p = EditorExport::get_singleton()->get_export_preset(i); |
601 | if (p->get_platform() == preset->get_platform() && p->is_runnable()) { |
602 | make_runnable = false; |
603 | } |
604 | if (p->get_name() == preset_name) { |
605 | valid = false; |
606 | break; |
607 | } |
608 | } |
609 | |
610 | if (valid) { |
611 | break; |
612 | } |
613 | |
614 | preset_name += " (copy)" ; |
615 | } |
616 | |
617 | preset->set_name(preset_name); |
618 | if (make_runnable) { |
619 | preset->set_runnable(make_runnable); |
620 | } |
621 | preset->set_dedicated_server(current->is_dedicated_server()); |
622 | preset->set_export_filter(current->get_export_filter()); |
623 | preset->set_include_filter(current->get_include_filter()); |
624 | preset->set_exclude_filter(current->get_exclude_filter()); |
625 | preset->set_custom_features(current->get_custom_features()); |
626 | |
627 | for (const KeyValue<StringName, Variant> &E : current->get_values()) { |
628 | preset->set(E.key, E.value); |
629 | } |
630 | |
631 | EditorExport::get_singleton()->add_export_preset(preset); |
632 | _update_presets(); |
633 | _edit_preset(EditorExport::get_singleton()->get_export_preset_count() - 1); |
634 | } |
635 | |
636 | void ProjectExportDialog::_delete_preset() { |
637 | Ref<EditorExportPreset> current = get_current_preset(); |
638 | if (current.is_null()) { |
639 | return; |
640 | } |
641 | |
642 | delete_confirm->set_text(vformat(TTR("Delete preset '%s'?" ), current->get_name())); |
643 | delete_confirm->popup_centered(); |
644 | } |
645 | |
646 | void ProjectExportDialog::_delete_preset_confirm() { |
647 | int idx = presets->get_current(); |
648 | _edit_preset(-1); |
649 | export_button->set_disabled(true); |
650 | get_ok_button()->set_disabled(true); |
651 | EditorExport::get_singleton()->remove_export_preset(idx); |
652 | _update_presets(); |
653 | |
654 | // The Export All button might become enabled (if all other presets have an export path defined), |
655 | // or it could be disabled (if there are no presets anymore). |
656 | _update_export_all(); |
657 | } |
658 | |
659 | Variant ProjectExportDialog::get_drag_data_fw(const Point2 &p_point, Control *p_from) { |
660 | if (p_from == presets) { |
661 | int pos = presets->get_item_at_position(p_point, true); |
662 | |
663 | if (pos >= 0) { |
664 | Dictionary d; |
665 | d["type" ] = "export_preset" ; |
666 | d["preset" ] = pos; |
667 | |
668 | HBoxContainer *drag = memnew(HBoxContainer); |
669 | TextureRect *tr = memnew(TextureRect); |
670 | tr->set_texture(presets->get_item_icon(pos)); |
671 | drag->add_child(tr); |
672 | Label *label = memnew(Label); |
673 | label->set_text(presets->get_item_text(pos)); |
674 | drag->add_child(label); |
675 | |
676 | presets->set_drag_preview(drag); |
677 | |
678 | return d; |
679 | } |
680 | } |
681 | |
682 | return Variant(); |
683 | } |
684 | |
685 | bool ProjectExportDialog::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { |
686 | if (p_from == presets) { |
687 | Dictionary d = p_data; |
688 | if (!d.has("type" ) || String(d["type" ]) != "export_preset" ) { |
689 | return false; |
690 | } |
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 | } |
695 | } |
696 | |
697 | return true; |
698 | } |
699 | |
700 | void ProjectExportDialog::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { |
701 | if (p_from == presets) { |
702 | Dictionary d = p_data; |
703 | int from_pos = d["preset" ]; |
704 | |
705 | int to_pos = -1; |
706 | |
707 | if (presets->get_item_at_position(p_point, true) >= 0) { |
708 | to_pos = presets->get_item_at_position(p_point, true); |
709 | } |
710 | |
711 | if (to_pos == -1 && !presets->is_pos_at_end_of_items(p_point)) { |
712 | return; |
713 | } |
714 | |
715 | if (to_pos == from_pos) { |
716 | return; |
717 | } else if (to_pos > from_pos) { |
718 | to_pos--; |
719 | } |
720 | |
721 | Ref<EditorExportPreset> preset = EditorExport::get_singleton()->get_export_preset(from_pos); |
722 | EditorExport::get_singleton()->remove_export_preset(from_pos); |
723 | EditorExport::get_singleton()->add_export_preset(preset, to_pos); |
724 | |
725 | _update_presets(); |
726 | if (to_pos >= 0) { |
727 | _edit_preset(to_pos); |
728 | } else { |
729 | _edit_preset(presets->get_item_count() - 1); |
730 | } |
731 | } |
732 | } |
733 | |
734 | void ProjectExportDialog::_export_type_changed(int p_which) { |
735 | if (updating) { |
736 | return; |
737 | } |
738 | |
739 | Ref<EditorExportPreset> current = get_current_preset(); |
740 | if (current.is_null()) { |
741 | return; |
742 | } |
743 | |
744 | EditorExportPreset::ExportFilter filter_type = (EditorExportPreset::ExportFilter)p_which; |
745 | current->set_export_filter(filter_type); |
746 | current->set_dedicated_server(filter_type == EditorExportPreset::EXPORT_CUSTOMIZED); |
747 | server_strip_message->set_visible(filter_type == EditorExportPreset::EXPORT_CUSTOMIZED); |
748 | |
749 | // Default to stripping everything when first switching to server build. |
750 | if (filter_type == EditorExportPreset::EXPORT_CUSTOMIZED && current->get_customized_files_count() == 0) { |
751 | current->set_file_export_mode("res://" , EditorExportPreset::MODE_FILE_STRIP); |
752 | } |
753 | include_label->set_text(current->get_export_filter() == EditorExportPreset::EXCLUDE_SELECTED_RESOURCES ? TTR("Resources to exclude:" ) : TTR("Resources to export:" )); |
754 | |
755 | updating = true; |
756 | _fill_resource_tree(); |
757 | updating = false; |
758 | } |
759 | |
760 | void ProjectExportDialog::_filter_changed(const String &p_filter) { |
761 | if (updating) { |
762 | return; |
763 | } |
764 | |
765 | Ref<EditorExportPreset> current = get_current_preset(); |
766 | if (current.is_null()) { |
767 | return; |
768 | } |
769 | |
770 | current->set_include_filter(include_filters->get_text()); |
771 | current->set_exclude_filter(exclude_filters->get_text()); |
772 | } |
773 | |
774 | void ProjectExportDialog::_fill_resource_tree() { |
775 | include_files->clear(); |
776 | include_label->hide(); |
777 | include_margin->hide(); |
778 | |
779 | Ref<EditorExportPreset> current = get_current_preset(); |
780 | if (current.is_null()) { |
781 | return; |
782 | } |
783 | |
784 | EditorExportPreset::ExportFilter f = current->get_export_filter(); |
785 | |
786 | if (f == EditorExportPreset::EXPORT_ALL_RESOURCES) { |
787 | return; |
788 | } |
789 | |
790 | TreeItem *root = include_files->create_item(); |
791 | |
792 | if (f == EditorExportPreset::EXPORT_CUSTOMIZED) { |
793 | include_files->set_columns(2); |
794 | include_files->set_column_expand(1, false); |
795 | include_files->set_column_custom_minimum_width(1, 250 * EDSCALE); |
796 | } else { |
797 | include_files->set_columns(1); |
798 | } |
799 | |
800 | include_label->show(); |
801 | include_margin->show(); |
802 | |
803 | _fill_tree(EditorFileSystem::get_singleton()->get_filesystem(), root, current, f); |
804 | |
805 | if (f == EditorExportPreset::EXPORT_CUSTOMIZED) { |
806 | _propagate_file_export_mode(include_files->get_root(), EditorExportPreset::MODE_FILE_NOT_CUSTOMIZED); |
807 | } |
808 | } |
809 | |
810 | void ProjectExportDialog::_setup_item_for_file_mode(TreeItem *p_item, EditorExportPreset::FileExportMode p_mode) { |
811 | if (p_mode == EditorExportPreset::MODE_FILE_NOT_CUSTOMIZED) { |
812 | p_item->set_checked(0, false); |
813 | p_item->set_cell_mode(1, TreeItem::CELL_MODE_STRING); |
814 | p_item->set_editable(1, false); |
815 | p_item->set_selectable(1, false); |
816 | p_item->set_custom_color(1, get_theme_color(SNAME("disabled_font_color" ), EditorStringName(Editor))); |
817 | } else { |
818 | p_item->set_checked(0, true); |
819 | p_item->set_cell_mode(1, TreeItem::CELL_MODE_CUSTOM); |
820 | p_item->set_editable(1, true); |
821 | p_item->set_selectable(1, true); |
822 | p_item->clear_custom_color(1); |
823 | } |
824 | p_item->set_metadata(1, p_mode); |
825 | } |
826 | |
827 | bool ProjectExportDialog::_fill_tree(EditorFileSystemDirectory *p_dir, TreeItem *p_item, Ref<EditorExportPreset> ¤t, EditorExportPreset::ExportFilter p_export_filter) { |
828 | p_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); |
829 | p_item->set_icon(0, presets->get_theme_icon(SNAME("folder" ), SNAME("FileDialog" ))); |
830 | p_item->set_text(0, p_dir->get_name() + "/" ); |
831 | p_item->set_editable(0, true); |
832 | p_item->set_metadata(0, p_dir->get_path()); |
833 | |
834 | if (p_export_filter == EditorExportPreset::EXPORT_CUSTOMIZED) { |
835 | _setup_item_for_file_mode(p_item, current->get_file_export_mode(p_dir->get_path())); |
836 | } |
837 | |
838 | bool used = false; |
839 | for (int i = 0; i < p_dir->get_subdir_count(); i++) { |
840 | TreeItem *subdir = include_files->create_item(p_item); |
841 | if (_fill_tree(p_dir->get_subdir(i), subdir, current, p_export_filter)) { |
842 | used = true; |
843 | } else { |
844 | memdelete(subdir); |
845 | } |
846 | } |
847 | |
848 | for (int i = 0; i < p_dir->get_file_count(); i++) { |
849 | String type = p_dir->get_file_type(i); |
850 | if (p_export_filter == EditorExportPreset::EXPORT_SELECTED_SCENES && type != "PackedScene" ) { |
851 | continue; |
852 | } |
853 | if (type == "TextFile" ) { |
854 | continue; |
855 | } |
856 | |
857 | TreeItem *file = include_files->create_item(p_item); |
858 | file->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); |
859 | file->set_text(0, p_dir->get_file(i)); |
860 | |
861 | String path = p_dir->get_file_path(i); |
862 | |
863 | file->set_icon(0, EditorNode::get_singleton()->get_class_icon(type)); |
864 | file->set_editable(0, true); |
865 | file->set_metadata(0, path); |
866 | |
867 | if (p_export_filter == EditorExportPreset::EXPORT_CUSTOMIZED) { |
868 | _setup_item_for_file_mode(file, current->get_file_export_mode(path)); |
869 | } else { |
870 | file->set_checked(0, current->has_export_file(path)); |
871 | file->propagate_check(0); |
872 | } |
873 | |
874 | used = true; |
875 | } |
876 | return used; |
877 | } |
878 | |
879 | void ProjectExportDialog::_propagate_file_export_mode(TreeItem *p_item, EditorExportPreset::FileExportMode p_inherited_export_mode) { |
880 | EditorExportPreset::FileExportMode file_export_mode = (EditorExportPreset::FileExportMode)(int)p_item->get_metadata(1); |
881 | bool is_inherited = false; |
882 | if (file_export_mode == EditorExportPreset::MODE_FILE_NOT_CUSTOMIZED) { |
883 | file_export_mode = p_inherited_export_mode; |
884 | is_inherited = true; |
885 | } |
886 | |
887 | if (file_export_mode == EditorExportPreset::MODE_FILE_NOT_CUSTOMIZED) { |
888 | p_item->set_text(1, "" ); |
889 | } else { |
890 | String text = file_mode_popup->get_item_text(file_mode_popup->get_item_index(file_export_mode)); |
891 | if (is_inherited) { |
892 | text += " " + TTR("(Inherited)" ); |
893 | } |
894 | p_item->set_text(1, text); |
895 | } |
896 | |
897 | for (int i = 0; i < p_item->get_child_count(); i++) { |
898 | _propagate_file_export_mode(p_item->get_child(i), file_export_mode); |
899 | } |
900 | } |
901 | |
902 | void ProjectExportDialog::_tree_changed() { |
903 | if (updating) { |
904 | return; |
905 | } |
906 | |
907 | Ref<EditorExportPreset> current = get_current_preset(); |
908 | if (current.is_null()) { |
909 | return; |
910 | } |
911 | |
912 | TreeItem *item = include_files->get_edited(); |
913 | if (!item) { |
914 | return; |
915 | } |
916 | |
917 | if (current->get_export_filter() == EditorExportPreset::EXPORT_CUSTOMIZED) { |
918 | EditorExportPreset::FileExportMode file_mode = EditorExportPreset::MODE_FILE_NOT_CUSTOMIZED; |
919 | String path = item->get_metadata(0); |
920 | |
921 | if (item->is_checked(0)) { |
922 | file_mode = current->get_file_export_mode(path, EditorExportPreset::MODE_FILE_STRIP); |
923 | } |
924 | |
925 | current->set_file_export_mode(path, file_mode); |
926 | _setup_item_for_file_mode(item, file_mode); |
927 | _propagate_file_export_mode(include_files->get_root(), EditorExportPreset::MODE_FILE_NOT_CUSTOMIZED); |
928 | } else { |
929 | item->propagate_check(0); |
930 | } |
931 | } |
932 | |
933 | void ProjectExportDialog::_check_propagated_to_item(Object *p_obj, int column) { |
934 | Ref<EditorExportPreset> current = get_current_preset(); |
935 | if (current.is_null()) { |
936 | return; |
937 | } |
938 | TreeItem *item = Object::cast_to<TreeItem>(p_obj); |
939 | String path = item->get_metadata(0); |
940 | if (item && !path.ends_with("/" )) { |
941 | bool added = item->is_checked(0); |
942 | if (added) { |
943 | current->add_export_file(path); |
944 | } else { |
945 | current->remove_export_file(path); |
946 | } |
947 | } |
948 | } |
949 | |
950 | void ProjectExportDialog::(bool p_arrow_clicked) { |
951 | Rect2 bounds = include_files->get_custom_popup_rect(); |
952 | bounds.position += get_global_canvas_transform().get_origin(); |
953 | bounds.size *= get_global_canvas_transform().get_scale(); |
954 | if (!is_embedding_subwindows()) { |
955 | bounds.position += get_position(); |
956 | } |
957 | file_mode_popup->popup(bounds); |
958 | } |
959 | |
960 | void ProjectExportDialog::_set_file_export_mode(int p_id) { |
961 | Ref<EditorExportPreset> current = get_current_preset(); |
962 | if (current.is_null()) { |
963 | return; |
964 | } |
965 | |
966 | TreeItem *item = include_files->get_edited(); |
967 | String path = item->get_metadata(0); |
968 | |
969 | EditorExportPreset::FileExportMode file_export_mode = (EditorExportPreset::FileExportMode)p_id; |
970 | current->set_file_export_mode(path, file_export_mode); |
971 | item->set_metadata(1, file_export_mode); |
972 | _propagate_file_export_mode(include_files->get_root(), EditorExportPreset::MODE_FILE_NOT_CUSTOMIZED); |
973 | } |
974 | |
975 | void ProjectExportDialog::_export_pck_zip() { |
976 | Ref<EditorExportPreset> current = get_current_preset(); |
977 | ERR_FAIL_COND(current.is_null()); |
978 | |
979 | String dir = current->get_export_path().get_base_dir(); |
980 | export_pck_zip->set_current_dir(dir); |
981 | |
982 | export_pck_zip->popup_file_dialog(); |
983 | } |
984 | |
985 | void ProjectExportDialog::_export_pck_zip_selected(const String &p_path) { |
986 | Ref<EditorExportPreset> current = get_current_preset(); |
987 | ERR_FAIL_COND(current.is_null()); |
988 | Ref<EditorExportPlatform> platform = current->get_platform(); |
989 | ERR_FAIL_COND(platform.is_null()); |
990 | |
991 | if (p_path.ends_with(".zip" )) { |
992 | platform->export_zip(current, export_pck_zip_debug->is_pressed(), p_path); |
993 | } else if (p_path.ends_with(".pck" )) { |
994 | platform->export_pack(current, export_pck_zip_debug->is_pressed(), p_path); |
995 | } |
996 | } |
997 | |
998 | void ProjectExportDialog::_open_export_template_manager() { |
999 | hide(); |
1000 | EditorNode::get_singleton()->open_export_template_manager(); |
1001 | } |
1002 | |
1003 | void ProjectExportDialog::_validate_export_path(const String &p_path) { |
1004 | // Disable export via OK button or Enter key if LineEdit has an empty filename |
1005 | bool invalid_path = (p_path.get_file().get_basename().is_empty()); |
1006 | |
1007 | // Check if state change before needlessly messing with signals |
1008 | if (invalid_path && export_project->get_ok_button()->is_disabled()) { |
1009 | return; |
1010 | } |
1011 | if (!invalid_path && !export_project->get_ok_button()->is_disabled()) { |
1012 | return; |
1013 | } |
1014 | |
1015 | if (invalid_path) { |
1016 | export_project->get_ok_button()->set_disabled(true); |
1017 | export_project->get_line_edit()->disconnect("text_submitted" , callable_mp(export_project, &EditorFileDialog::_file_submitted)); |
1018 | } else { |
1019 | export_project->get_ok_button()->set_disabled(false); |
1020 | export_project->get_line_edit()->connect("text_submitted" , callable_mp(export_project, &EditorFileDialog::_file_submitted)); |
1021 | } |
1022 | } |
1023 | |
1024 | void ProjectExportDialog::_export_project() { |
1025 | Ref<EditorExportPreset> current = get_current_preset(); |
1026 | ERR_FAIL_COND(current.is_null()); |
1027 | Ref<EditorExportPlatform> platform = current->get_platform(); |
1028 | ERR_FAIL_COND(platform.is_null()); |
1029 | |
1030 | export_project->set_access(EditorFileDialog::ACCESS_FILESYSTEM); |
1031 | export_project->clear_filters(); |
1032 | |
1033 | List<String> extension_list = platform->get_binary_extensions(current); |
1034 | for (int i = 0; i < extension_list.size(); i++) { |
1035 | // TRANSLATORS: This is the name of a project export file format. %s will be replaced by the platform name. |
1036 | export_project->add_filter("*." + extension_list[i], vformat(TTR("%s Export" ), platform->get_name())); |
1037 | } |
1038 | |
1039 | if (!current->get_export_path().is_empty()) { |
1040 | export_project->set_current_path(current->get_export_path()); |
1041 | } else { |
1042 | if (extension_list.size() >= 1) { |
1043 | export_project->set_current_file(default_filename + "." + extension_list[0]); |
1044 | } else { |
1045 | export_project->set_current_file(default_filename); |
1046 | } |
1047 | } |
1048 | |
1049 | // Ensure that signal is connected if previous attempt left it disconnected |
1050 | // with _validate_export_path. |
1051 | // FIXME: This is a hack, we should instead change EditorFileDialog to allow |
1052 | // disabling validation by the "text_submitted" signal. |
1053 | if (!export_project->get_line_edit()->is_connected("text_submitted" , callable_mp(export_project, &EditorFileDialog::_file_submitted))) { |
1054 | export_project->get_ok_button()->set_disabled(false); |
1055 | export_project->get_line_edit()->connect("text_submitted" , callable_mp(export_project, &EditorFileDialog::_file_submitted)); |
1056 | } |
1057 | |
1058 | export_project->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); |
1059 | export_project->popup_file_dialog(); |
1060 | } |
1061 | |
1062 | void ProjectExportDialog::_export_project_to_path(const String &p_path) { |
1063 | // Save this name for use in future exports (but drop the file extension) |
1064 | default_filename = p_path.get_file().get_basename(); |
1065 | EditorSettings::get_singleton()->set_project_metadata("export_options" , "default_filename" , default_filename); |
1066 | |
1067 | Ref<EditorExportPreset> current = get_current_preset(); |
1068 | ERR_FAIL_COND(current.is_null()); |
1069 | Ref<EditorExportPlatform> platform = current->get_platform(); |
1070 | ERR_FAIL_COND(platform.is_null()); |
1071 | current->set_export_path(p_path); |
1072 | |
1073 | platform->clear_messages(); |
1074 | Error err = platform->export_project(current, export_debug->is_pressed(), current->get_export_path(), 0); |
1075 | result_dialog_log->clear(); |
1076 | if (err != ERR_SKIP) { |
1077 | if (platform->fill_log_messages(result_dialog_log, err)) { |
1078 | result_dialog->popup_centered_ratio(0.5); |
1079 | } |
1080 | } |
1081 | } |
1082 | |
1083 | void ProjectExportDialog::_export_all_dialog() { |
1084 | #ifndef ANDROID_ENABLED |
1085 | export_all_dialog->show(); |
1086 | export_all_dialog->popup_centered(Size2(300, 80)); |
1087 | #endif |
1088 | } |
1089 | |
1090 | void ProjectExportDialog::_export_all_dialog_action(const String &p_str) { |
1091 | export_all_dialog->hide(); |
1092 | |
1093 | _export_all(p_str != "release" ); |
1094 | } |
1095 | |
1096 | void ProjectExportDialog::_export_all(bool p_debug) { |
1097 | String export_target = p_debug ? TTR("Debug" ) : TTR("Release" ); |
1098 | EditorProgress ep("exportall" , TTR("Exporting All" ) + " " + export_target, EditorExport::get_singleton()->get_export_preset_count(), true); |
1099 | |
1100 | bool show_dialog = false; |
1101 | result_dialog_log->clear(); |
1102 | for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) { |
1103 | Ref<EditorExportPreset> preset = EditorExport::get_singleton()->get_export_preset(i); |
1104 | ERR_FAIL_COND(preset.is_null()); |
1105 | Ref<EditorExportPlatform> platform = preset->get_platform(); |
1106 | ERR_FAIL_COND(platform.is_null()); |
1107 | |
1108 | ep.step(preset->get_name(), i); |
1109 | |
1110 | platform->clear_messages(); |
1111 | Error err = platform->export_project(preset, p_debug, preset->get_export_path(), 0); |
1112 | if (err == ERR_SKIP) { |
1113 | return; |
1114 | } |
1115 | bool has_messages = platform->fill_log_messages(result_dialog_log, err); |
1116 | show_dialog = show_dialog || has_messages; |
1117 | } |
1118 | if (show_dialog) { |
1119 | result_dialog->popup_centered_ratio(0.5); |
1120 | } |
1121 | } |
1122 | |
1123 | void ProjectExportDialog::_bind_methods() { |
1124 | ClassDB::bind_method("_export_all" , &ProjectExportDialog::_export_all); |
1125 | ClassDB::bind_method("set_export_path" , &ProjectExportDialog::set_export_path); |
1126 | ClassDB::bind_method("get_export_path" , &ProjectExportDialog::get_export_path); |
1127 | ClassDB::bind_method("get_current_preset" , &ProjectExportDialog::get_current_preset); |
1128 | |
1129 | ADD_PROPERTY(PropertyInfo(Variant::STRING, "export_path" ), "set_export_path" , "get_export_path" ); |
1130 | } |
1131 | |
1132 | ProjectExportDialog::ProjectExportDialog() { |
1133 | set_title(TTR("Export" )); |
1134 | set_clamp_to_embedder(true); |
1135 | |
1136 | VBoxContainer *main_vb = memnew(VBoxContainer); |
1137 | main_vb->connect("theme_changed" , callable_mp(this, &ProjectExportDialog::_theme_changed)); |
1138 | add_child(main_vb); |
1139 | HSplitContainer *hbox = memnew(HSplitContainer); |
1140 | main_vb->add_child(hbox); |
1141 | hbox->set_v_size_flags(Control::SIZE_EXPAND_FILL); |
1142 | |
1143 | // Presets list. |
1144 | |
1145 | VBoxContainer *preset_vb = memnew(VBoxContainer); |
1146 | preset_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); |
1147 | hbox->add_child(preset_vb); |
1148 | |
1149 | Label *l = memnew(Label(TTR("Presets" ))); |
1150 | l->set_theme_type_variation("HeaderSmall" ); |
1151 | |
1152 | HBoxContainer *preset_hb = memnew(HBoxContainer); |
1153 | preset_hb->add_child(l); |
1154 | preset_hb->add_spacer(); |
1155 | preset_vb->add_child(preset_hb); |
1156 | |
1157 | add_preset = memnew(MenuButton); |
1158 | add_preset->set_text(TTR("Add..." )); |
1159 | add_preset->get_popup()->connect("index_pressed" , callable_mp(this, &ProjectExportDialog::_add_preset)); |
1160 | preset_hb->add_child(add_preset); |
1161 | MarginContainer *mc = memnew(MarginContainer); |
1162 | preset_vb->add_child(mc); |
1163 | mc->set_v_size_flags(Control::SIZE_EXPAND_FILL); |
1164 | presets = memnew(ItemList); |
1165 | SET_DRAG_FORWARDING_GCD(presets, ProjectExportDialog); |
1166 | mc->add_child(presets); |
1167 | presets->connect("item_selected" , callable_mp(this, &ProjectExportDialog::_edit_preset)); |
1168 | duplicate_preset = memnew(Button); |
1169 | duplicate_preset->set_tooltip_text(TTR("Duplicate" )); |
1170 | duplicate_preset->set_flat(true); |
1171 | preset_hb->add_child(duplicate_preset); |
1172 | duplicate_preset->connect("pressed" , callable_mp(this, &ProjectExportDialog::_duplicate_preset)); |
1173 | delete_preset = memnew(Button); |
1174 | delete_preset->set_tooltip_text(TTR("Delete" )); |
1175 | delete_preset->set_flat(true); |
1176 | preset_hb->add_child(delete_preset); |
1177 | delete_preset->connect("pressed" , callable_mp(this, &ProjectExportDialog::_delete_preset)); |
1178 | |
1179 | // Preset settings. |
1180 | |
1181 | VBoxContainer *settings_vb = memnew(VBoxContainer); |
1182 | settings_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); |
1183 | hbox->add_child(settings_vb); |
1184 | |
1185 | name = memnew(LineEdit); |
1186 | settings_vb->add_margin_child(TTR("Name:" ), name); |
1187 | name->connect("text_changed" , callable_mp(this, &ProjectExportDialog::_name_changed)); |
1188 | runnable = memnew(CheckButton); |
1189 | runnable->set_text(TTR("Runnable" )); |
1190 | runnable->set_tooltip_text(TTR("If checked, the preset will be available for use in one-click deploy.\nOnly one preset per platform may be marked as runnable." )); |
1191 | runnable->connect("pressed" , callable_mp(this, &ProjectExportDialog::_runnable_pressed)); |
1192 | settings_vb->add_child(runnable); |
1193 | |
1194 | export_path = memnew(EditorPropertyPath); |
1195 | settings_vb->add_child(export_path); |
1196 | export_path->set_label(TTR("Export Path" )); |
1197 | export_path->set_object_and_property(this, "export_path" ); |
1198 | export_path->set_save_mode(); |
1199 | export_path->connect("property_changed" , callable_mp(this, &ProjectExportDialog::_export_path_changed)); |
1200 | |
1201 | // Subsections. |
1202 | |
1203 | sections = memnew(TabContainer); |
1204 | sections->set_use_hidden_tabs_for_min_size(true); |
1205 | sections->set_theme_type_variation("TabContainerOdd" ); |
1206 | settings_vb->add_child(sections); |
1207 | sections->set_v_size_flags(Control::SIZE_EXPAND_FILL); |
1208 | |
1209 | // Main preset parameters. |
1210 | |
1211 | parameters = memnew(EditorInspector); |
1212 | sections->add_child(parameters); |
1213 | parameters->set_name(TTR("Options" )); |
1214 | parameters->set_v_size_flags(Control::SIZE_EXPAND_FILL); |
1215 | parameters->set_use_doc_hints(true); |
1216 | parameters->connect("property_edited" , callable_mp(this, &ProjectExportDialog::_update_parameters)); |
1217 | EditorExport::get_singleton()->connect("export_presets_updated" , callable_mp(this, &ProjectExportDialog::_force_update_current_preset_parameters)); |
1218 | |
1219 | // Resources export parameters. |
1220 | |
1221 | VBoxContainer *resources_vb = memnew(VBoxContainer); |
1222 | sections->add_child(resources_vb); |
1223 | resources_vb->set_name(TTR("Resources" )); |
1224 | |
1225 | export_filter = memnew(OptionButton); |
1226 | export_filter->add_item(TTR("Export all resources in the project" )); |
1227 | export_filter->add_item(TTR("Export selected scenes (and dependencies)" )); |
1228 | export_filter->add_item(TTR("Export selected resources (and dependencies)" )); |
1229 | export_filter->add_item(TTR("Export all resources in the project except resources checked below" )); |
1230 | export_filter->add_item(TTR("Export as dedicated server" )); |
1231 | resources_vb->add_margin_child(TTR("Export Mode:" ), export_filter); |
1232 | export_filter->connect("item_selected" , callable_mp(this, &ProjectExportDialog::_export_type_changed)); |
1233 | |
1234 | include_label = memnew(Label); |
1235 | include_label->set_text(TTR("Resources to export:" )); |
1236 | resources_vb->add_child(include_label); |
1237 | include_margin = memnew(MarginContainer); |
1238 | include_margin->set_v_size_flags(Control::SIZE_EXPAND_FILL); |
1239 | resources_vb->add_child(include_margin); |
1240 | |
1241 | include_files = memnew(Tree); |
1242 | include_margin->add_child(include_files); |
1243 | include_files->connect("item_edited" , callable_mp(this, &ProjectExportDialog::_tree_changed)); |
1244 | include_files->connect("check_propagated_to_item" , callable_mp(this, &ProjectExportDialog::_check_propagated_to_item)); |
1245 | include_files->connect("custom_popup_edited" , callable_mp(this, &ProjectExportDialog::_tree_popup_edited)); |
1246 | |
1247 | server_strip_message = memnew(Label); |
1248 | server_strip_message->set_visible(false); |
1249 | server_strip_message->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART); |
1250 | resources_vb->add_child(server_strip_message); |
1251 | |
1252 | { |
1253 | List<StringName> resource_names; |
1254 | ClassDB::get_inheriters_from_class("Resource" , &resource_names); |
1255 | |
1256 | PackedStringArray strippable; |
1257 | for (StringName resource_name : resource_names) { |
1258 | if (ClassDB::has_method(resource_name, "create_placeholder" , true)) { |
1259 | strippable.push_back(resource_name); |
1260 | } |
1261 | } |
1262 | strippable.sort(); |
1263 | |
1264 | String message = TTR("\"Strip Visuals\" will replace the following resources with placeholders:" ) + " " ; |
1265 | message += String(", " ).join(strippable); |
1266 | server_strip_message->set_text(message); |
1267 | } |
1268 | |
1269 | file_mode_popup = memnew(PopupMenu); |
1270 | add_child(file_mode_popup); |
1271 | file_mode_popup->add_item(TTR("Strip Visuals" ), EditorExportPreset::MODE_FILE_STRIP); |
1272 | file_mode_popup->add_item(TTR("Keep" ), EditorExportPreset::MODE_FILE_KEEP); |
1273 | file_mode_popup->add_item(TTR("Remove" ), EditorExportPreset::MODE_FILE_REMOVE); |
1274 | file_mode_popup->connect("id_pressed" , callable_mp(this, &ProjectExportDialog::_set_file_export_mode)); |
1275 | |
1276 | include_filters = memnew(LineEdit); |
1277 | resources_vb->add_margin_child( |
1278 | TTR("Filters to export non-resource files/folders\n(comma-separated, e.g: *.json, *.txt, docs/*)" ), |
1279 | include_filters); |
1280 | include_filters->connect("text_changed" , callable_mp(this, &ProjectExportDialog::_filter_changed)); |
1281 | |
1282 | exclude_filters = memnew(LineEdit); |
1283 | resources_vb->add_margin_child( |
1284 | TTR("Filters to exclude files/folders from project\n(comma-separated, e.g: *.json, *.txt, docs/*)" ), |
1285 | exclude_filters); |
1286 | exclude_filters->connect("text_changed" , callable_mp(this, &ProjectExportDialog::_filter_changed)); |
1287 | |
1288 | // Feature tags. |
1289 | |
1290 | VBoxContainer *feature_vb = memnew(VBoxContainer); |
1291 | feature_vb->set_name(TTR("Features" )); |
1292 | custom_features = memnew(LineEdit); |
1293 | custom_features->connect("text_changed" , callable_mp(this, &ProjectExportDialog::_custom_features_changed)); |
1294 | feature_vb->add_margin_child(TTR("Custom (comma-separated):" ), custom_features); |
1295 | custom_feature_display = memnew(RichTextLabel); |
1296 | custom_feature_display->set_v_size_flags(Control::SIZE_EXPAND_FILL); |
1297 | feature_vb->add_margin_child(TTR("Feature List:" ), custom_feature_display, true); |
1298 | sections->add_child(feature_vb); |
1299 | |
1300 | // Script export parameters. |
1301 | |
1302 | VBoxContainer *sec_vb = memnew(VBoxContainer); |
1303 | sec_vb->set_name(TTR("Encryption" )); |
1304 | |
1305 | enc_pck = memnew(CheckButton); |
1306 | enc_pck->connect("toggled" , callable_mp(this, &ProjectExportDialog::_enc_pck_changed)); |
1307 | enc_pck->set_text(TTR("Encrypt Exported PCK" )); |
1308 | sec_vb->add_child(enc_pck); |
1309 | |
1310 | enc_directory = memnew(CheckButton); |
1311 | enc_directory->connect("toggled" , callable_mp(this, &ProjectExportDialog::_enc_directory_changed)); |
1312 | enc_directory->set_text(TTR("Encrypt Index (File Names and Info)" )); |
1313 | sec_vb->add_child(enc_directory); |
1314 | |
1315 | enc_in_filters = memnew(LineEdit); |
1316 | enc_in_filters->connect("text_changed" , callable_mp(this, &ProjectExportDialog::_enc_filters_changed)); |
1317 | sec_vb->add_margin_child( |
1318 | TTR("Filters to include files/folders\n(comma-separated, e.g: *.tscn, *.tres, scenes/*)" ), |
1319 | enc_in_filters); |
1320 | |
1321 | enc_ex_filters = memnew(LineEdit); |
1322 | enc_ex_filters->connect("text_changed" , callable_mp(this, &ProjectExportDialog::_enc_filters_changed)); |
1323 | sec_vb->add_margin_child( |
1324 | TTR("Filters to exclude files/folders\n(comma-separated, e.g: *.ctex, *.import, music/*)" ), |
1325 | enc_ex_filters); |
1326 | |
1327 | script_key = memnew(LineEdit); |
1328 | script_key->connect("text_changed" , callable_mp(this, &ProjectExportDialog::_script_encryption_key_changed)); |
1329 | script_key_error = memnew(Label); |
1330 | script_key_error->set_text(String::utf8("• " ) + TTR("Invalid Encryption Key (must be 64 hexadecimal characters long)" )); |
1331 | script_key_error->add_theme_color_override("font_color" , EditorNode::get_singleton()->get_editor_theme()->get_color(SNAME("error_color" ), EditorStringName(Editor))); |
1332 | sec_vb->add_margin_child(TTR("Encryption Key (256-bits as hexadecimal):" ), script_key); |
1333 | sec_vb->add_child(script_key_error); |
1334 | sections->add_child(sec_vb); |
1335 | |
1336 | Label *sec_info = memnew(Label); |
1337 | sec_info->set_text(TTR("Note: Encryption key needs to be stored in the binary,\nyou need to build the export templates from source." )); |
1338 | sec_vb->add_child(sec_info); |
1339 | |
1340 | LinkButton *sec_more_info = memnew(LinkButton); |
1341 | sec_more_info->set_text(TTR("More Info..." )); |
1342 | sec_more_info->connect("pressed" , callable_mp(this, &ProjectExportDialog::_open_key_help_link)); |
1343 | sec_vb->add_child(sec_more_info); |
1344 | |
1345 | sections->connect("tab_changed" , callable_mp(this, &ProjectExportDialog::_tab_changed)); |
1346 | |
1347 | // Disable by default. |
1348 | name->set_editable(false); |
1349 | export_path->hide(); |
1350 | runnable->set_disabled(true); |
1351 | duplicate_preset->set_disabled(true); |
1352 | delete_preset->set_disabled(true); |
1353 | script_key_error->hide(); |
1354 | sections->hide(); |
1355 | parameters->edit(nullptr); |
1356 | |
1357 | // Deletion dialog. |
1358 | |
1359 | delete_confirm = memnew(ConfirmationDialog); |
1360 | add_child(delete_confirm); |
1361 | delete_confirm->set_ok_button_text(TTR("Delete" )); |
1362 | delete_confirm->connect("confirmed" , callable_mp(this, &ProjectExportDialog::_delete_preset_confirm)); |
1363 | |
1364 | // Export buttons, dialogs and errors. |
1365 | |
1366 | set_cancel_button_text(TTR("Close" )); |
1367 | set_ok_button_text(TTR("Export PCK/ZIP..." )); |
1368 | get_ok_button()->set_disabled(true); |
1369 | #ifdef ANDROID_ENABLED |
1370 | export_button = memnew(Button); |
1371 | export_button->hide(); |
1372 | #else |
1373 | export_button = add_button(TTR("Export Project..." ), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "export" ); |
1374 | #endif |
1375 | export_button->connect("pressed" , callable_mp(this, &ProjectExportDialog::_export_project)); |
1376 | // Disable initially before we select a valid preset |
1377 | export_button->set_disabled(true); |
1378 | |
1379 | export_all_dialog = memnew(ConfirmationDialog); |
1380 | add_child(export_all_dialog); |
1381 | export_all_dialog->set_title(TTR("Export All" )); |
1382 | export_all_dialog->set_text(TTR("Choose an export mode:" )); |
1383 | export_all_dialog->get_ok_button()->hide(); |
1384 | export_all_dialog->add_button(TTR("Debug" ), true, "debug" ); |
1385 | export_all_dialog->add_button(TTR("Release" ), true, "release" ); |
1386 | export_all_dialog->connect("custom_action" , callable_mp(this, &ProjectExportDialog::_export_all_dialog_action)); |
1387 | #ifdef ANDROID_ENABLED |
1388 | export_all_dialog->hide(); |
1389 | |
1390 | export_all_button = memnew(Button); |
1391 | export_all_button->hide(); |
1392 | #else |
1393 | export_all_button = add_button(TTR("Export All..." ), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "export" ); |
1394 | #endif |
1395 | export_all_button->connect("pressed" , callable_mp(this, &ProjectExportDialog::_export_all_dialog)); |
1396 | export_all_button->set_disabled(true); |
1397 | |
1398 | export_pck_zip = memnew(EditorFileDialog); |
1399 | export_pck_zip->add_filter("*.zip" , TTR("ZIP File" )); |
1400 | export_pck_zip->add_filter("*.pck" , TTR("Godot Project Pack" )); |
1401 | export_pck_zip->set_access(EditorFileDialog::ACCESS_FILESYSTEM); |
1402 | export_pck_zip->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); |
1403 | add_child(export_pck_zip); |
1404 | export_pck_zip->connect("file_selected" , callable_mp(this, &ProjectExportDialog::_export_pck_zip_selected)); |
1405 | |
1406 | // Export warnings and errors bottom section. |
1407 | |
1408 | export_texture_format_error = memnew(ProjectExportTextureFormatError); |
1409 | main_vb->add_child(export_texture_format_error); |
1410 | export_texture_format_error->hide(); |
1411 | export_texture_format_error->connect("texture_format_enabled" , callable_mp(this, &ProjectExportDialog::_update_current_preset)); |
1412 | |
1413 | export_error = memnew(Label); |
1414 | main_vb->add_child(export_error); |
1415 | export_error->hide(); |
1416 | export_error->add_theme_color_override("font_color" , EditorNode::get_singleton()->get_editor_theme()->get_color(SNAME("error_color" ), EditorStringName(Editor))); |
1417 | |
1418 | export_warning = memnew(Label); |
1419 | main_vb->add_child(export_warning); |
1420 | export_warning->hide(); |
1421 | export_warning->add_theme_color_override("font_color" , EditorNode::get_singleton()->get_editor_theme()->get_color(SNAME("warning_color" ), EditorStringName(Editor))); |
1422 | |
1423 | export_templates_error = memnew(HBoxContainer); |
1424 | main_vb->add_child(export_templates_error); |
1425 | export_templates_error->hide(); |
1426 | |
1427 | Label *export_error2 = memnew(Label); |
1428 | export_templates_error->add_child(export_error2); |
1429 | export_error2->add_theme_color_override("font_color" , EditorNode::get_singleton()->get_editor_theme()->get_color(SNAME("error_color" ), EditorStringName(Editor))); |
1430 | export_error2->set_text(String::utf8("• " ) + TTR("Export templates for this platform are missing:" ) + " " ); |
1431 | |
1432 | result_dialog = memnew(AcceptDialog); |
1433 | result_dialog->set_title(TTR("Project Export" )); |
1434 | result_dialog_log = memnew(RichTextLabel); |
1435 | result_dialog_log->set_custom_minimum_size(Size2(300, 80) * EDSCALE); |
1436 | result_dialog->add_child(result_dialog_log); |
1437 | |
1438 | main_vb->add_child(result_dialog); |
1439 | result_dialog->hide(); |
1440 | |
1441 | LinkButton *download_templates = memnew(LinkButton); |
1442 | download_templates->set_text(TTR("Manage Export Templates" )); |
1443 | download_templates->set_v_size_flags(Control::SIZE_SHRINK_CENTER); |
1444 | export_templates_error->add_child(download_templates); |
1445 | download_templates->connect("pressed" , callable_mp(this, &ProjectExportDialog::_open_export_template_manager)); |
1446 | |
1447 | // Export project file dialog. |
1448 | |
1449 | export_project = memnew(EditorFileDialog); |
1450 | export_project->set_access(EditorFileDialog::ACCESS_FILESYSTEM); |
1451 | add_child(export_project); |
1452 | export_project->connect("file_selected" , callable_mp(this, &ProjectExportDialog::_export_project_to_path)); |
1453 | export_project->get_line_edit()->connect("text_changed" , callable_mp(this, &ProjectExportDialog::_validate_export_path)); |
1454 | |
1455 | export_debug = memnew(CheckBox); |
1456 | export_debug->set_text(TTR("Export With Debug" )); |
1457 | export_debug->set_pressed(true); |
1458 | export_debug->set_h_size_flags(Control::SIZE_SHRINK_CENTER); |
1459 | export_project->get_vbox()->add_child(export_debug); |
1460 | |
1461 | export_pck_zip_debug = memnew(CheckBox); |
1462 | export_pck_zip_debug->set_text(TTR("Export With Debug" )); |
1463 | export_pck_zip_debug->set_pressed(true); |
1464 | export_pck_zip_debug->set_h_size_flags(Control::SIZE_SHRINK_CENTER); |
1465 | export_pck_zip->get_vbox()->add_child(export_pck_zip_debug); |
1466 | |
1467 | set_hide_on_ok(false); |
1468 | |
1469 | default_filename = EditorSettings::get_singleton()->get_project_metadata("export_options" , "default_filename" , "" ); |
1470 | // If no default set, use project name |
1471 | if (default_filename.is_empty()) { |
1472 | // If no project name defined, use a sane default |
1473 | default_filename = GLOBAL_GET("application/config/name" ); |
1474 | if (default_filename.is_empty()) { |
1475 | default_filename = "UnnamedProject" ; |
1476 | } |
1477 | } |
1478 | } |
1479 | |
1480 | ProjectExportDialog::~ProjectExportDialog() { |
1481 | } |
1482 | |