Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/scene/gui/file_dialog.cpp
9903 views
1
/**************************************************************************/
2
/* file_dialog.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 "file_dialog.h"
32
33
#include "core/config/project_settings.h"
34
#include "core/io/dir_access.h"
35
#include "core/os/keyboard.h"
36
#include "scene/gui/box_container.h"
37
#include "scene/gui/check_box.h"
38
#include "scene/gui/flow_container.h"
39
#include "scene/gui/grid_container.h"
40
#include "scene/gui/item_list.h"
41
#include "scene/gui/label.h"
42
#include "scene/gui/line_edit.h"
43
#include "scene/gui/menu_button.h"
44
#include "scene/gui/option_button.h"
45
#include "scene/gui/separator.h"
46
#include "scene/gui/split_container.h"
47
#include "scene/theme/theme_db.h"
48
49
void FileDialog::popup_file_dialog() {
50
popup_centered_clamped(Size2i(700, 500), 0.8f);
51
_focus_file_text();
52
}
53
54
void FileDialog::_focus_file_text() {
55
int lp = filename_edit->get_text().rfind_char('.');
56
if (lp != -1) {
57
filename_edit->select(0, lp);
58
if (filename_edit->is_inside_tree() && !is_part_of_edited_scene()) {
59
filename_edit->grab_focus();
60
}
61
}
62
}
63
64
void FileDialog::_native_popup() {
65
// Show native dialog directly.
66
String root;
67
if (!root_prefix.is_empty()) {
68
root = ProjectSettings::get_singleton()->globalize_path(root_prefix);
69
} else if (access == ACCESS_RESOURCES) {
70
root = ProjectSettings::get_singleton()->get_resource_path();
71
} else if (access == ACCESS_USERDATA) {
72
root = OS::get_singleton()->get_user_data_dir();
73
}
74
75
// Attach native file dialog to first persistent parent window.
76
Window *w = (is_transient() || is_transient_to_focused()) ? get_parent_visible_window() : nullptr;
77
while (w && w->get_flag(FLAG_POPUP) && w->get_parent_visible_window()) {
78
w = w->get_parent_visible_window();
79
}
80
DisplayServer::WindowID wid = w ? w->get_window_id() : DisplayServer::INVALID_WINDOW_ID;
81
82
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE_EXTRA)) {
83
DisplayServer::get_singleton()->file_dialog_with_options_show(get_displayed_title(), ProjectSettings::get_singleton()->globalize_path(full_dir), root, filename_edit->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), processed_filters, _get_options(), callable_mp(this, &FileDialog::_native_dialog_cb_with_options), wid);
84
} else {
85
DisplayServer::get_singleton()->file_dialog_show(get_displayed_title(), ProjectSettings::get_singleton()->globalize_path(full_dir), filename_edit->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), processed_filters, callable_mp(this, &FileDialog::_native_dialog_cb), wid);
86
}
87
}
88
89
bool FileDialog::_can_use_native_popup() {
90
if (access == ACCESS_RESOURCES || access == ACCESS_USERDATA || options.size() > 0) {
91
return DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE_EXTRA);
92
}
93
return DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE);
94
}
95
96
void FileDialog::popup(const Rect2i &p_rect) {
97
_update_option_controls();
98
99
#ifdef TOOLS_ENABLED
100
if (is_part_of_edited_scene()) {
101
ConfirmationDialog::popup(p_rect);
102
return;
103
}
104
#endif
105
106
if (_can_use_native_popup() && (use_native_dialog || OS::get_singleton()->is_sandboxed())) {
107
_native_popup();
108
} else {
109
ConfirmationDialog::popup(p_rect);
110
}
111
}
112
113
void FileDialog::set_visible(bool p_visible) {
114
if (p_visible) {
115
_update_option_controls();
116
}
117
118
#ifdef TOOLS_ENABLED
119
if (is_part_of_edited_scene()) {
120
ConfirmationDialog::set_visible(p_visible);
121
return;
122
}
123
#endif
124
125
if (_can_use_native_popup() && (use_native_dialog || OS::get_singleton()->is_sandboxed())) {
126
if (p_visible) {
127
_native_popup();
128
}
129
} else {
130
ConfirmationDialog::set_visible(p_visible);
131
}
132
}
133
134
void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter) {
135
_native_dialog_cb_with_options(p_ok, p_files, p_filter, Dictionary());
136
}
137
138
void FileDialog::_native_dialog_cb_with_options(bool p_ok, const Vector<String> &p_files, int p_filter, const Dictionary &p_selected_options) {
139
if (!p_ok) {
140
filename_edit->set_text("");
141
emit_signal(SNAME("canceled"));
142
return;
143
}
144
145
if (p_files.is_empty()) {
146
return;
147
}
148
149
Vector<String> files = p_files;
150
if (access != ACCESS_FILESYSTEM) {
151
for (String &file_name : files) {
152
file_name = ProjectSettings::get_singleton()->localize_path(file_name);
153
}
154
}
155
selected_options = p_selected_options;
156
157
String f = files[0];
158
filter->select(p_filter);
159
directory_edit->set_text(f.get_base_dir());
160
filename_edit->set_text(f.get_file());
161
_change_dir(f.get_base_dir());
162
163
if (mode == FILE_MODE_OPEN_FILES) {
164
emit_signal(SNAME("files_selected"), files);
165
} else {
166
if (mode == FILE_MODE_SAVE_FILE) {
167
bool valid = false;
168
169
if (p_filter == filter->get_item_count() - 1) {
170
valid = true; // Match none.
171
} else if (filters.size() > 1 && p_filter == 0) {
172
// Match all filters.
173
for (int i = 0; i < filters.size(); i++) {
174
String flt = filters[i].get_slicec(';', 0);
175
for (int j = 0; j < flt.get_slice_count(","); j++) {
176
String str = flt.get_slicec(',', j).strip_edges();
177
if (f.matchn(str)) {
178
valid = true;
179
break;
180
}
181
}
182
if (valid) {
183
break;
184
}
185
}
186
} else {
187
int idx = p_filter;
188
if (filters.size() > 1) {
189
idx--;
190
}
191
if (idx >= 0 && idx < filters.size()) {
192
String flt = filters[idx].get_slicec(';', 0);
193
int filter_slice_count = flt.get_slice_count(",");
194
for (int j = 0; j < filter_slice_count; j++) {
195
String str = flt.get_slicec(',', j).strip_edges();
196
if (f.matchn(str)) {
197
valid = true;
198
break;
199
}
200
}
201
202
if (!valid && filter_slice_count > 0) {
203
String str = flt.get_slicec(',', 0).strip_edges();
204
f += str.substr(1);
205
filename_edit->set_text(f.get_file());
206
valid = true;
207
}
208
} else {
209
valid = true;
210
}
211
}
212
213
// Add first extension of filter if no valid extension is found.
214
if (!valid) {
215
int idx = p_filter;
216
String flt = filters[idx].get_slicec(';', 0);
217
String ext = flt.get_slicec(',', 0).strip_edges().get_extension();
218
f += "." + ext;
219
}
220
emit_signal(SNAME("file_selected"), f);
221
} else if ((mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_FILE) && dir_access->file_exists(f)) {
222
emit_signal(SNAME("file_selected"), f);
223
} else if (mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_DIR) {
224
emit_signal(SNAME("dir_selected"), f);
225
}
226
}
227
}
228
229
void FileDialog::_validate_property(PropertyInfo &p_property) const {
230
if (p_property.name == "dialog_text") {
231
// File dialogs have a custom layout, and dialog nodes can't have both a text and a layout.
232
p_property.usage = PROPERTY_USAGE_NONE;
233
}
234
}
235
236
void FileDialog::_notification(int p_what) {
237
switch (p_what) {
238
case NOTIFICATION_READY: {
239
#ifdef TOOLS_ENABLED
240
if (is_part_of_edited_scene()) {
241
return;
242
}
243
#endif
244
245
// Replace the built-in dialog with the native one if it started visible.
246
if (is_visible() && _can_use_native_popup() && (use_native_dialog || OS::get_singleton()->is_sandboxed())) {
247
ConfirmationDialog::set_visible(false);
248
_native_popup();
249
}
250
} break;
251
252
case NOTIFICATION_VISIBILITY_CHANGED: {
253
if (is_visible()) {
254
_update_favorite_list();
255
_update_recent_list();
256
invalidate(); // Put it here to preview in the editor.
257
} else {
258
set_process_shortcut_input(false);
259
}
260
} break;
261
262
case NOTIFICATION_THEME_CHANGED: {
263
favorite_list->set_custom_minimum_size(Vector2(128 * get_theme_default_base_scale(), 0));
264
recent_list->set_custom_minimum_size(Vector2(128 * get_theme_default_base_scale(), 0));
265
266
if (main_vbox->is_layout_rtl()) {
267
_setup_button(dir_prev, theme_cache.forward_folder);
268
_setup_button(dir_next, theme_cache.back_folder);
269
} else {
270
_setup_button(dir_prev, theme_cache.back_folder);
271
_setup_button(dir_next, theme_cache.forward_folder);
272
}
273
_setup_button(dir_up, theme_cache.parent_folder);
274
_setup_button(refresh_button, theme_cache.reload);
275
_setup_button(favorite_button, theme_cache.favorite);
276
_setup_button(make_dir_button, theme_cache.create_folder);
277
_setup_button(show_hidden, theme_cache.toggle_hidden);
278
_setup_button(thumbnail_mode_button, theme_cache.thumbnail_mode);
279
_setup_button(list_mode_button, theme_cache.list_mode);
280
_setup_button(show_filename_filter_button, theme_cache.toggle_filename_filter);
281
_setup_button(file_sort_button, theme_cache.sort);
282
_setup_button(fav_up_button, theme_cache.favorite_up);
283
_setup_button(fav_down_button, theme_cache.favorite_down);
284
invalidate();
285
} break;
286
287
case NOTIFICATION_TRANSLATION_CHANGED: {
288
update_filters();
289
} break;
290
}
291
}
292
293
void FileDialog::shortcut_input(const Ref<InputEvent> &p_event) {
294
ERR_FAIL_COND(p_event.is_null());
295
296
Ref<InputEventKey> k = p_event;
297
if (k.is_valid() && has_focus()) {
298
if (k->is_pressed()) {
299
bool handled = true;
300
301
switch (k->get_keycode()) {
302
case Key::H: {
303
if (k->is_command_or_control_pressed()) {
304
set_show_hidden_files(!show_hidden_files);
305
} else {
306
handled = false;
307
}
308
309
} break;
310
case Key::F: {
311
if (k->is_command_or_control_pressed()) {
312
show_filename_filter_button->set_pressed(!show_filename_filter_button->is_pressed());
313
} else {
314
handled = false;
315
}
316
317
} break;
318
case Key::F5: {
319
invalidate();
320
} break;
321
case Key::BACKSPACE: {
322
_dir_submitted("..");
323
} break;
324
#ifdef MACOS_ENABLED
325
// Cmd + Shift + G (matches Finder's "Go To" shortcut).
326
case Key::G: {
327
if (k->is_command_or_control_pressed() && k->is_shift_pressed()) {
328
directory_edit->grab_focus();
329
directory_edit->select_all();
330
} else {
331
handled = false;
332
}
333
} break;
334
#endif
335
// Ctrl + L (matches most Windows/Linux file managers' "focus on path bar" shortcut,
336
// plus macOS Safari's "focus on address bar" shortcut).
337
case Key::L: {
338
if (k->is_command_or_control_pressed()) {
339
directory_edit->grab_focus();
340
directory_edit->select_all();
341
} else {
342
handled = false;
343
}
344
} break;
345
default: {
346
handled = false;
347
}
348
}
349
350
if (handled) {
351
set_input_as_handled();
352
}
353
}
354
}
355
}
356
357
void FileDialog::set_enable_multiple_selection(bool p_enable) {
358
file_list->set_select_mode(p_enable ? ItemList::SELECT_MULTI : ItemList::SELECT_SINGLE);
359
}
360
361
Vector<String> FileDialog::get_selected_files() const {
362
const String current_dir = dir_access->get_current_dir();
363
Vector<String> list;
364
for (int idx : file_list->get_selected_items()) {
365
list.push_back(current_dir.path_join(file_list->get_item_text(idx)));
366
}
367
return list;
368
}
369
370
void FileDialog::update_dir() {
371
full_dir = dir_access->get_current_dir();
372
if (root_prefix.is_empty()) {
373
directory_edit->set_text(dir_access->get_current_dir(false));
374
} else {
375
directory_edit->set_text(dir_access->get_current_dir(false).trim_prefix(root_prefix).trim_prefix("/"));
376
}
377
378
if (drives->is_visible()) {
379
if (dir_access->get_current_dir().is_network_share_path()) {
380
_update_drives(false);
381
drives->add_item(ETR("Network"));
382
drives->set_item_disabled(-1, true);
383
drives->select(drives->get_item_count() - 1);
384
} else {
385
drives->select(dir_access->get_current_drive());
386
}
387
}
388
389
// Deselect any item, to make "Select Current Folder" button text by default.
390
deselect_all();
391
}
392
393
void FileDialog::_dir_submitted(String p_dir) {
394
String new_dir = p_dir;
395
#ifdef WINDOWS_ENABLED
396
if (root_prefix.is_empty() && drives->is_visible() && !new_dir.is_network_share_path() && new_dir.is_absolute_path() && new_dir.find(":/") == -1 && new_dir.find(":\\") == -1) {
397
// Non network path without X:/ prefix on Windows, add drive letter.
398
new_dir = drives->get_item_text(drives->get_selected()).path_join(new_dir);
399
}
400
#endif
401
if (!root_prefix.is_empty()) {
402
new_dir = root_prefix.path_join(new_dir);
403
}
404
_change_dir(new_dir);
405
filename_edit->set_text("");
406
_push_history();
407
}
408
409
void FileDialog::_file_submitted(const String &p_file) {
410
_action_pressed();
411
}
412
413
void FileDialog::_save_confirm_pressed() {
414
_save_to_recent();
415
416
String f = dir_access->get_current_dir().path_join(filename_edit->get_text());
417
emit_signal(SNAME("file_selected"), f);
418
hide();
419
}
420
421
void FileDialog::_post_popup() {
422
ConfirmationDialog::_post_popup();
423
if (mode == FILE_MODE_SAVE_FILE) {
424
filename_edit->grab_focus();
425
} else {
426
file_list->grab_focus();
427
}
428
429
set_process_shortcut_input(true);
430
431
// For open dir mode, deselect all items on file dialog open.
432
if (mode == FILE_MODE_OPEN_DIR) {
433
deselect_all();
434
file_box->set_visible(false);
435
} else {
436
file_box->set_visible(true);
437
}
438
439
local_history.clear();
440
local_history_pos = -1;
441
_push_history();
442
}
443
444
void FileDialog::_push_history() {
445
local_history.resize(local_history_pos + 1);
446
String new_path = dir_access->get_current_dir();
447
if (local_history.is_empty() || new_path != local_history[local_history_pos]) {
448
local_history.push_back(new_path);
449
local_history_pos++;
450
dir_prev->set_disabled(local_history_pos == 0);
451
dir_next->set_disabled(true);
452
}
453
}
454
455
void FileDialog::_action_pressed() {
456
if (mode == FILE_MODE_OPEN_FILES) {
457
const Vector<String> files = get_selected_files();
458
if (!files.is_empty()) {
459
_save_to_recent();
460
emit_signal(SNAME("files_selected"), files);
461
hide();
462
}
463
return;
464
}
465
466
String file_text = filename_edit->get_text();
467
String f = file_text.is_absolute_path() ? file_text : dir_access->get_current_dir().path_join(file_text);
468
469
if ((mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_FILE) && (dir_access->file_exists(f) || dir_access->is_bundle(f))) {
470
_save_to_recent();
471
emit_signal(SNAME("file_selected"), f);
472
hide();
473
} else if (mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_DIR) {
474
String path = dir_access->get_current_dir();
475
476
path = path.replace_char('\\', '/');
477
int selected = _get_selected_file_idx();
478
if (selected > -1) {
479
Dictionary d = file_list->get_item_metadata(selected);
480
if (d["dir"] && d["name"] != "..") {
481
path = path.path_join(d["name"]);
482
}
483
}
484
485
_save_to_recent();
486
emit_signal(SNAME("dir_selected"), path);
487
hide();
488
}
489
490
if (mode == FILE_MODE_SAVE_FILE) {
491
bool valid = false;
492
493
if (filter->get_selected() == filter->get_item_count() - 1) {
494
valid = true; // Match none.
495
} else if (filters.size() > 1 && filter->get_selected() == 0) {
496
// Match all filters.
497
for (int i = 0; i < filters.size(); i++) {
498
String flt = filters[i].get_slicec(';', 0);
499
for (int j = 0; j < flt.get_slice_count(","); j++) {
500
String str = flt.get_slicec(',', j).strip_edges();
501
if (f.matchn(str)) {
502
valid = true;
503
break;
504
}
505
}
506
if (valid) {
507
break;
508
}
509
}
510
} else {
511
int idx = filter->get_selected();
512
if (filters.size() > 1) {
513
idx--;
514
}
515
if (idx >= 0 && idx < filters.size()) {
516
String flt = filters[idx].get_slicec(';', 0);
517
int filter_slice_count = flt.get_slice_count(",");
518
for (int j = 0; j < filter_slice_count; j++) {
519
String str = (flt.get_slicec(',', j).strip_edges());
520
if (f.matchn(str)) {
521
valid = true;
522
break;
523
}
524
}
525
526
if (!valid && filter_slice_count > 0) {
527
String str = flt.get_slicec(',', 0).strip_edges();
528
f += str.substr(1);
529
filename_edit->set_text(f.get_file());
530
valid = true;
531
}
532
} else {
533
valid = true;
534
}
535
}
536
537
String file_name = file_text.strip_edges().get_file();
538
if (!valid || file_name.is_empty()) {
539
exterr->popup_centered(Size2(250, 80));
540
return;
541
}
542
543
if (dir_access->file_exists(f) || dir_access->is_bundle(f)) {
544
confirm_save->set_text(vformat(atr(ETR("File \"%s\" already exists.\nDo you want to overwrite it?")), f));
545
confirm_save->popup_centered(Size2(250, 80));
546
} else {
547
_save_to_recent();
548
emit_signal(SNAME("file_selected"), f);
549
hide();
550
}
551
}
552
}
553
554
void FileDialog::_cancel_pressed() {
555
filename_edit->set_text("");
556
invalidate();
557
hide();
558
}
559
560
bool FileDialog::_is_open_should_be_disabled() {
561
if (mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_SAVE_FILE) {
562
return false;
563
}
564
565
Vector<int> items = file_list->get_selected_items();
566
if (items.is_empty()) {
567
return mode != FILE_MODE_OPEN_DIR; // In "Open folder" mode, having nothing selected picks the current folder.
568
}
569
570
for (const int idx : items) {
571
Dictionary d = file_list->get_item_metadata(idx);
572
573
if (((mode == FILE_MODE_OPEN_FILE || mode == FILE_MODE_OPEN_FILES) && d["dir"]) || (mode == FILE_MODE_OPEN_DIR && !d["dir"])) {
574
return true;
575
}
576
}
577
return false;
578
}
579
580
void FileDialog::_go_up() {
581
_change_dir("..");
582
_push_history();
583
}
584
585
void FileDialog::_go_back() {
586
if (local_history_pos <= 0) {
587
return;
588
}
589
590
local_history_pos--;
591
_change_dir(local_history[local_history_pos]);
592
593
dir_prev->set_disabled(local_history_pos == 0);
594
dir_next->set_disabled(local_history_pos == local_history.size() - 1);
595
}
596
597
void FileDialog::_go_forward() {
598
if (local_history_pos >= local_history.size() - 1) {
599
return;
600
}
601
602
local_history_pos++;
603
_change_dir(local_history[local_history_pos]);
604
605
dir_prev->set_disabled(local_history_pos == 0);
606
dir_next->set_disabled(local_history_pos == local_history.size() - 1);
607
}
608
609
void FileDialog::deselect_all() {
610
// Clear currently selected items in file manager.
611
file_list->deselect_all();
612
613
// And change get_ok title.
614
get_ok_button()->set_disabled(_is_open_should_be_disabled());
615
616
switch (mode) {
617
case FILE_MODE_OPEN_FILE:
618
case FILE_MODE_OPEN_FILES:
619
set_default_ok_text(ETR("Open"));
620
break;
621
case FILE_MODE_OPEN_DIR:
622
set_default_ok_text(ETR("Select Current Folder"));
623
break;
624
case FILE_MODE_OPEN_ANY:
625
set_default_ok_text(ETR("Open"));
626
break;
627
case FILE_MODE_SAVE_FILE:
628
set_default_ok_text(ETR("Save"));
629
break;
630
}
631
}
632
633
int FileDialog::_get_selected_file_idx() {
634
const PackedInt32Array selected = file_list->get_selected_items();
635
return selected.is_empty() ? -1 : selected[0];
636
}
637
638
void FileDialog::_file_list_multi_selected(int p_item, bool p_selected) {
639
if (p_selected) {
640
_file_list_selected(p_item);
641
} else {
642
get_ok_button()->set_disabled(_is_open_should_be_disabled());
643
}
644
}
645
646
void FileDialog::_file_list_selected(int p_item) {
647
Dictionary d = file_list->get_item_metadata(p_item);
648
649
if (!d["dir"]) {
650
filename_edit->set_text(d["name"]);
651
if (mode == FILE_MODE_SAVE_FILE) {
652
set_default_ok_text(ETR("Save"));
653
} else {
654
set_default_ok_text(ETR("Open"));
655
}
656
} else if (mode == FILE_MODE_OPEN_DIR || mode == FILE_MODE_OPEN_ANY || !dir_access->file_exists(filename_edit->get_text())) {
657
filename_edit->set_text("");
658
if (mode == FILE_MODE_OPEN_DIR || mode == FILE_MODE_OPEN_ANY) {
659
set_default_ok_text(ETR("Select This Folder"));
660
}
661
}
662
663
get_ok_button()->set_disabled(_is_open_should_be_disabled());
664
}
665
666
void FileDialog::_file_list_item_activated(int p_item) {
667
Dictionary d = file_list->get_item_metadata(p_item);
668
669
if (d["dir"]) {
670
_change_dir(d["name"]);
671
if (mode == FILE_MODE_OPEN_FILE || mode == FILE_MODE_OPEN_FILES || mode == FILE_MODE_OPEN_DIR || mode == FILE_MODE_OPEN_ANY) {
672
filename_edit->set_text("");
673
}
674
_push_history();
675
} else {
676
_action_pressed();
677
}
678
}
679
680
void FileDialog::update_file_name() {
681
int idx = filter->get_selected() - 1;
682
if ((idx == -1 && filter->get_item_count() == 2) || (filter->get_item_count() > 2 && idx >= 0 && idx < filter->get_item_count() - 2)) {
683
if (idx == -1) {
684
idx += 1;
685
}
686
String filter_str = filters[idx];
687
String file_str = filename_edit->get_text();
688
String base_name = file_str.get_basename();
689
Vector<String> filter_substr = filter_str.split(";");
690
if (filter_substr.size() >= 2) {
691
file_str = base_name + "." + filter_substr[0].strip_edges().get_extension().to_lower();
692
} else {
693
file_str = base_name + "." + filter_str.strip_edges().get_extension().to_lower();
694
}
695
filename_edit->set_text(file_str);
696
}
697
}
698
699
void FileDialog::_item_menu_id_pressed(int p_option) {
700
switch (p_option) {
701
case ITEM_MENU_SHOW_IN_EXPLORER: {
702
String path;
703
int selected = _get_selected_file_idx();
704
if (selected > -1) {
705
Dictionary d = file_list->get_item_metadata(selected);
706
path = ProjectSettings::get_singleton()->globalize_path(dir_access->get_current_dir().path_join(d["name"]));
707
} else {
708
path = ProjectSettings::get_singleton()->globalize_path(dir_access->get_current_dir());
709
}
710
711
OS::get_singleton()->shell_show_in_file_manager(path, true);
712
} break;
713
714
case ITEM_MENU_SHOW_BUNDLE_CONTENT: {
715
int selected = _get_selected_file_idx();
716
if (selected == -1) {
717
return;
718
}
719
Dictionary d = file_list->get_item_metadata(selected);
720
_change_dir(d["name"]);
721
if (mode == FILE_MODE_OPEN_FILE || mode == FILE_MODE_OPEN_FILES || mode == FILE_MODE_OPEN_DIR || mode == FILE_MODE_OPEN_ANY) {
722
filename_edit->set_text("");
723
}
724
_push_history();
725
} break;
726
}
727
}
728
729
void FileDialog::_empty_clicked(const Vector2 &p_pos, MouseButton p_button) {
730
if (p_button == MouseButton::RIGHT) {
731
item_menu->clear();
732
#if !defined(ANDROID_ENABLED) && !defined(WEB_ENABLED)
733
// Opening the system file manager is not supported on the Android and web editors.
734
item_menu->add_item(ETR("Open in File Manager"), ITEM_MENU_SHOW_IN_EXPLORER);
735
736
item_menu->set_position(file_list->get_screen_position() + p_pos);
737
item_menu->reset_size();
738
item_menu->popup();
739
#endif
740
} else if (p_button == MouseButton::LEFT) {
741
deselect_all();
742
}
743
}
744
745
void FileDialog::_item_clicked(int p_item, const Vector2 &p_pos, MouseButton p_button) {
746
if (p_button == MouseButton::RIGHT) {
747
item_menu->clear();
748
#if !defined(ANDROID_ENABLED) && !defined(WEB_ENABLED)
749
// Opening the system file manager is not supported on the Android and web editors.
750
Dictionary d = file_list->get_item_metadata(p_item);
751
if (d["bundle"]) {
752
item_menu->add_item(ETR("Show Package Contents"), ITEM_MENU_SHOW_BUNDLE_CONTENT);
753
}
754
item_menu->add_item(ETR("Open in File Manager"), ITEM_MENU_SHOW_IN_EXPLORER);
755
756
item_menu->set_position(file_list->get_screen_position() + p_pos);
757
item_menu->reset_size();
758
item_menu->popup();
759
#endif
760
}
761
}
762
763
void FileDialog::update_file_list() {
764
file_list->clear();
765
766
// Scroll back to the top after opening a directory
767
file_list->get_v_scroll_bar()->set_value(0);
768
769
if (display_mode == DISPLAY_THUMBNAILS) {
770
file_list->set_max_columns(0);
771
file_list->set_icon_mode(ItemList::ICON_MODE_TOP);
772
file_list->set_fixed_column_width(theme_cache.thumbnail_size * 3 / 2);
773
file_list->set_max_text_lines(2);
774
file_list->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
775
file_list->set_fixed_icon_size(Size2(theme_cache.thumbnail_size, theme_cache.thumbnail_size));
776
} else {
777
file_list->set_icon_mode(ItemList::ICON_MODE_LEFT);
778
file_list->set_max_columns(1);
779
file_list->set_max_text_lines(1);
780
file_list->set_fixed_column_width(0);
781
file_list->set_fixed_icon_size(Size2());
782
}
783
784
dir_access->list_dir_begin();
785
if (dir_access->is_readable(dir_access->get_current_dir().utf8().get_data())) {
786
message->hide();
787
} else {
788
message->set_text(ETR("You don't have permission to access contents of this folder."));
789
message->show();
790
}
791
792
LocalVector<String> files;
793
LocalVector<String> dirs;
794
795
bool is_hidden;
796
String item = dir_access->get_next();
797
798
while (!item.is_empty()) {
799
if (item == "." || item == "..") {
800
item = dir_access->get_next();
801
continue;
802
}
803
804
is_hidden = dir_access->current_is_hidden();
805
806
if (show_hidden_files || !is_hidden) {
807
if (!dir_access->current_is_dir()) {
808
files.push_back(item);
809
} else {
810
dirs.push_back(item);
811
}
812
}
813
item = dir_access->get_next();
814
}
815
816
String filename_filter_lower = file_name_filter.to_lower();
817
818
List<String> patterns;
819
// Build filter.
820
if (filter->get_selected() == filter->get_item_count() - 1) {
821
// Match all.
822
} else if (filters.size() > 1 && filter->get_selected() == 0) {
823
// Match all filters.
824
for (int i = 0; i < filters.size(); i++) {
825
String f = filters[i].get_slicec(';', 0);
826
for (int j = 0; j < f.get_slice_count(","); j++) {
827
patterns.push_back(f.get_slicec(',', j).strip_edges());
828
}
829
}
830
} else {
831
int idx = filter->get_selected();
832
if (filters.size() > 1) {
833
idx--;
834
}
835
836
if (idx >= 0 && idx < filters.size()) {
837
String f = filters[idx].get_slicec(';', 0);
838
for (int j = 0; j < f.get_slice_count(","); j++) {
839
patterns.push_back(f.get_slicec(',', j).strip_edges());
840
}
841
}
842
}
843
844
LocalVector<DirInfo> filtered_dirs;
845
filtered_dirs.reserve(dirs.size());
846
const String base_dir = dir_access->get_current_dir();
847
848
for (const String &dir_name : dirs) {
849
bool bundle = dir_access->is_bundle(dir_name);
850
bool found = true;
851
if (bundle) {
852
bool match = patterns.is_empty();
853
for (const String &E : patterns) {
854
if (dir_name.matchn(E)) {
855
match = true;
856
break;
857
}
858
}
859
found = match;
860
}
861
862
if (found && (filename_filter_lower.is_empty() || dir_name.to_lower().contains(filename_filter_lower))) {
863
DirInfo di;
864
di.name = dir_name;
865
di.bundle = bundle;
866
if (file_sort == FileSortOption::MODIFIED_TIME || file_sort == FileSortOption::MODIFIED_TIME_REVERSE) {
867
di.modified_time = FileAccess::get_modified_time(base_dir.path_join(dir_name));
868
}
869
filtered_dirs.push_back(di);
870
}
871
}
872
873
if (file_sort == FileSortOption::MODIFIED_TIME || file_sort == FileSortOption::MODIFIED_TIME_REVERSE) {
874
filtered_dirs.sort_custom<DirInfo::TimeComparator>();
875
} else {
876
filtered_dirs.sort_custom<DirInfo::NameComparator>();
877
}
878
879
if (file_sort == FileSortOption::NAME_REVERSE || file_sort == FileSortOption::MODIFIED_TIME_REVERSE) {
880
filtered_dirs.reverse();
881
}
882
883
for (const DirInfo &info : filtered_dirs) {
884
if (display_mode == DISPLAY_THUMBNAILS) {
885
file_list->add_item(info.name, theme_cache.folder_thumbnail);
886
} else {
887
file_list->add_item(info.name, theme_cache.folder);
888
}
889
file_list->set_item_icon_modulate(-1, theme_cache.folder_icon_color);
890
891
Dictionary d;
892
d["name"] = info.name;
893
d["dir"] = !info.bundle;
894
d["bundle"] = info.bundle;
895
file_list->set_item_metadata(-1, d);
896
}
897
898
LocalVector<FileInfo> filtered_files;
899
filtered_files.reserve(files.size());
900
901
for (const String &filename : files) {
902
bool match = patterns.is_empty();
903
String match_str;
904
905
for (const String &E : patterns) {
906
if (filename.matchn(E)) {
907
match_str = E;
908
match = true;
909
break;
910
}
911
}
912
913
if (match && (filename_filter_lower.is_empty() || filename.to_lower().contains(filename_filter_lower))) {
914
FileInfo fi;
915
fi.name = filename;
916
fi.match_string = match_str;
917
918
// Only assign sorting fields when needed.
919
if (file_sort == FileSortOption::TYPE || file_sort == FileSortOption::TYPE_REVERSE) {
920
fi.type_sort = filename.get_extension() + filename.get_basename();
921
} else if (file_sort == FileSortOption::MODIFIED_TIME || file_sort == FileSortOption::MODIFIED_TIME_REVERSE) {
922
fi.modified_time = FileAccess::get_modified_time(base_dir.path_join(filename));
923
}
924
filtered_files.push_back(fi);
925
}
926
}
927
928
switch (file_sort) {
929
case FileSortOption::NAME:
930
case FileSortOption::NAME_REVERSE:
931
filtered_files.sort_custom<FileInfo::NameComparator>();
932
break;
933
case FileSortOption::TYPE:
934
case FileSortOption::TYPE_REVERSE:
935
filtered_files.sort_custom<FileInfo::TypeComparator>();
936
break;
937
case FileSortOption::MODIFIED_TIME:
938
case FileSortOption::MODIFIED_TIME_REVERSE:
939
filtered_files.sort_custom<FileInfo::TimeComparator>();
940
break;
941
default:
942
ERR_PRINT(vformat("Invalid FileDialog sort option: %d", int(file_sort)));
943
}
944
945
if (file_sort == FileSortOption::NAME_REVERSE || file_sort == FileSortOption::TYPE_REVERSE || file_sort == FileSortOption::MODIFIED_TIME_REVERSE) {
946
filtered_files.reverse();
947
}
948
949
for (const FileInfo &info : filtered_files) {
950
file_list->add_item(info.name);
951
if (get_icon_func) {
952
Ref<Texture2D> icon = get_icon_func(base_dir.path_join(info.name));
953
file_list->set_item_icon(-1, icon);
954
} else if (display_mode == DISPLAY_THUMBNAILS) {
955
file_list->set_item_icon(-1, theme_cache.file_thumbnail);
956
} else {
957
file_list->set_item_icon(-1, theme_cache.file);
958
}
959
file_list->set_item_icon_modulate(-1, theme_cache.file_icon_color);
960
961
if (mode == FILE_MODE_OPEN_DIR) {
962
file_list->set_item_disabled(-1, true);
963
}
964
Dictionary d;
965
d["name"] = info.name;
966
d["dir"] = false;
967
d["bundle"] = false;
968
file_list->set_item_metadata(-1, d);
969
970
if (filename_edit->get_text() == info.name || info.match_string == info.name) {
971
file_list->select(file_list->get_item_count() - 1);
972
}
973
}
974
975
if (mode != FILE_MODE_SAVE_FILE && mode != FILE_MODE_OPEN_DIR) {
976
// Select the first file from list if nothing is selected.
977
int selected = _get_selected_file_idx();
978
if (selected == -1) {
979
_file_list_select_first();
980
}
981
}
982
983
favorite_list->deselect_all();
984
favorite_button->set_pressed(false);
985
986
const int fav_count = favorite_list->get_item_count();
987
for (int i = 0; i < fav_count; i++) {
988
const String fav_dir = favorite_list->get_item_metadata(i);
989
if (fav_dir != base_dir && fav_dir != base_dir + "/") {
990
continue;
991
}
992
favorite_list->select(i);
993
favorite_button->set_pressed(true);
994
break;
995
}
996
_update_fav_buttons();
997
}
998
999
void FileDialog::_filter_selected(int) {
1000
update_file_name();
1001
update_file_list();
1002
}
1003
1004
void FileDialog::_filename_filter_changed() {
1005
update_filename_filter();
1006
update_file_list();
1007
callable_mp(this, &FileDialog::_file_list_select_first).call_deferred();
1008
}
1009
1010
void FileDialog::_file_list_select_first() {
1011
if (file_list->get_item_count() > 0) {
1012
file_list->select(0);
1013
_file_list_selected(0);
1014
}
1015
}
1016
1017
void FileDialog::_filename_filter_selected() {
1018
int selected = _get_selected_file_idx();
1019
if (selected > -1) {
1020
filename_edit->set_text(file_list->get_item_text(selected));
1021
filename_edit->emit_signal(SceneStringName(text_submitted), filename_edit->get_text());
1022
}
1023
}
1024
1025
void FileDialog::update_filters() {
1026
filter->clear();
1027
processed_filters.clear();
1028
1029
if (filters.size() > 1) {
1030
String all_filters;
1031
String all_mime;
1032
String all_filters_full;
1033
String all_mime_full;
1034
1035
const int max_filters = 5;
1036
1037
// "All Recognized" display name.
1038
for (int i = 0; i < MIN(max_filters, filters.size()); i++) {
1039
String flt = filters[i].get_slicec(';', 0).strip_edges();
1040
if (!all_filters.is_empty() && !flt.is_empty()) {
1041
all_filters += ", ";
1042
}
1043
all_filters += flt;
1044
1045
String mime = filters[i].get_slicec(';', 2).strip_edges();
1046
if (!all_mime.is_empty() && !mime.is_empty()) {
1047
all_mime += ", ";
1048
}
1049
all_mime += mime;
1050
}
1051
1052
// "All Recognized" filter.
1053
for (int i = 0; i < filters.size(); i++) {
1054
String flt = filters[i].get_slicec(';', 0).strip_edges();
1055
if (!all_filters_full.is_empty() && !flt.is_empty()) {
1056
all_filters_full += ",";
1057
}
1058
all_filters_full += flt;
1059
1060
String mime = filters[i].get_slicec(';', 2).strip_edges();
1061
if (!all_mime_full.is_empty() && !mime.is_empty()) {
1062
all_mime_full += ",";
1063
}
1064
all_mime_full += mime;
1065
}
1066
1067
String native_all_name;
1068
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE_MIME)) {
1069
native_all_name += all_filters;
1070
}
1071
if (!native_all_name.is_empty()) {
1072
native_all_name += ", ";
1073
}
1074
native_all_name += all_mime;
1075
1076
if (max_filters < filters.size()) {
1077
all_filters += ", ...";
1078
native_all_name += ", ...";
1079
}
1080
1081
filter->add_item(atr(ETR("All Recognized")) + " (" + all_filters + ")");
1082
processed_filters.push_back(all_filters_full + ";" + atr(ETR("All Recognized")) + " (" + native_all_name + ")" + ";" + all_mime_full);
1083
}
1084
for (int i = 0; i < filters.size(); i++) {
1085
String flt = filters[i].get_slicec(';', 0).strip_edges();
1086
String desc = filters[i].get_slicec(';', 1).strip_edges();
1087
String mime = filters[i].get_slicec(';', 2).strip_edges();
1088
String native_name;
1089
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE_MIME)) {
1090
native_name += flt;
1091
}
1092
if (!native_name.is_empty() && !mime.is_empty()) {
1093
native_name += ", ";
1094
}
1095
native_name += mime;
1096
if (!desc.is_empty()) {
1097
filter->add_item(atr(desc) + " (" + flt + ")");
1098
processed_filters.push_back(flt + ";" + atr(desc) + " (" + native_name + ");" + mime);
1099
} else {
1100
filter->add_item("(" + flt + ")");
1101
processed_filters.push_back(flt + ";(" + native_name + ");" + mime);
1102
}
1103
}
1104
1105
String f = atr(ETR("All Files")) + " (*.*)";
1106
filter->add_item(f);
1107
processed_filters.push_back("*.*;" + f + ";application/octet-stream");
1108
}
1109
1110
void FileDialog::update_customization() {
1111
_update_make_dir_visible();
1112
show_hidden->set_visible(customization_flags[CUSTOMIZATION_HIDDEN_FILES]);
1113
layout_container->set_visible(customization_flags[CUSTOMIZATION_LAYOUT]);
1114
layout_separator->set_visible(customization_flags[CUSTOMIZATION_FILE_FILTER] || customization_flags[CUSTOMIZATION_FILE_SORT]);
1115
show_filename_filter_button->set_visible(customization_flags[CUSTOMIZATION_FILE_FILTER]);
1116
file_sort_button->set_visible(customization_flags[CUSTOMIZATION_FILE_SORT]);
1117
show_hidden_separator->set_visible(customization_flags[CUSTOMIZATION_HIDDEN_FILES] && (customization_flags[CUSTOMIZATION_LAYOUT] || customization_flags[CUSTOMIZATION_FILE_FILTER] || customization_flags[CUSTOMIZATION_FILE_SORT]));
1118
favorite_button->set_visible(customization_flags[CUSTOMIZATION_FAVORITES]);
1119
favorite_vbox->set_visible(customization_flags[CUSTOMIZATION_FAVORITES]);
1120
recent_vbox->set_visible(customization_flags[CUSTOMIZATION_RECENT]);
1121
}
1122
1123
void FileDialog::clear_filename_filter() {
1124
set_filename_filter("");
1125
update_filename_filter_gui();
1126
invalidate();
1127
}
1128
1129
void FileDialog::update_filename_filter_gui() {
1130
filename_filter_box->set_visible(show_filename_filter);
1131
if (!show_filename_filter) {
1132
file_name_filter.clear();
1133
}
1134
if (filename_filter->get_text() == file_name_filter) {
1135
return;
1136
}
1137
filename_filter->set_text(file_name_filter);
1138
}
1139
1140
void FileDialog::update_filename_filter() {
1141
if (filename_filter->get_text() == file_name_filter) {
1142
return;
1143
}
1144
set_filename_filter(filename_filter->get_text());
1145
}
1146
1147
void FileDialog::clear_filters() {
1148
filters.clear();
1149
update_filters();
1150
invalidate();
1151
}
1152
1153
void FileDialog::add_filter(const String &p_filter, const String &p_description) {
1154
ERR_FAIL_COND_MSG(p_filter.begins_with("."), "Filter must be \"filename.extension\", can't start with dot.");
1155
if (p_description.is_empty()) {
1156
filters.push_back(p_filter);
1157
} else {
1158
filters.push_back(vformat("%s ; %s", p_filter, p_description));
1159
}
1160
update_filters();
1161
invalidate();
1162
}
1163
1164
void FileDialog::set_filters(const Vector<String> &p_filters) {
1165
if (filters == p_filters) {
1166
return;
1167
}
1168
filters = p_filters;
1169
update_filters();
1170
invalidate();
1171
}
1172
1173
void FileDialog::set_filename_filter(const String &p_filename_filter) {
1174
if (file_name_filter == p_filename_filter) {
1175
return;
1176
}
1177
file_name_filter = p_filename_filter;
1178
update_filename_filter_gui();
1179
emit_signal(SNAME("filename_filter_changed"), filter);
1180
invalidate();
1181
}
1182
1183
Vector<String> FileDialog::get_filters() const {
1184
return filters;
1185
}
1186
1187
String FileDialog::get_filename_filter() const {
1188
return file_name_filter;
1189
}
1190
1191
String FileDialog::get_current_dir() const {
1192
return full_dir;
1193
}
1194
1195
String FileDialog::get_current_file() const {
1196
return filename_edit->get_text();
1197
}
1198
1199
String FileDialog::get_current_path() const {
1200
return full_dir.path_join(filename_edit->get_text());
1201
}
1202
1203
void FileDialog::set_current_dir(const String &p_dir) {
1204
_change_dir(p_dir);
1205
1206
_push_history();
1207
}
1208
1209
void FileDialog::set_current_file(const String &p_file) {
1210
if (filename_edit->get_text() == p_file) {
1211
return;
1212
}
1213
filename_edit->set_text(p_file);
1214
update_dir();
1215
invalidate();
1216
_focus_file_text();
1217
}
1218
1219
void FileDialog::set_current_path(const String &p_path) {
1220
if (!p_path.size()) {
1221
return;
1222
}
1223
int pos = MAX(p_path.rfind_char('/'), p_path.rfind_char('\\'));
1224
if (pos == -1) {
1225
set_current_file(p_path);
1226
} else {
1227
String path_dir = p_path.substr(0, pos);
1228
String path_file = p_path.substr(pos + 1);
1229
set_current_dir(path_dir);
1230
set_current_file(path_file);
1231
}
1232
}
1233
1234
void FileDialog::set_root_subfolder(const String &p_root) {
1235
root_subfolder = p_root;
1236
ERR_FAIL_COND_MSG(!dir_access->dir_exists(p_root), "root_subfolder must be an existing sub-directory.");
1237
1238
local_history.clear();
1239
local_history_pos = -1;
1240
1241
dir_access->change_dir(root_subfolder);
1242
if (root_subfolder.is_empty()) {
1243
root_prefix = "";
1244
} else {
1245
root_prefix = dir_access->get_current_dir();
1246
}
1247
invalidate();
1248
update_dir();
1249
}
1250
1251
String FileDialog::get_root_subfolder() const {
1252
return root_subfolder;
1253
}
1254
1255
void FileDialog::set_mode_overrides_title(bool p_override) {
1256
mode_overrides_title = p_override;
1257
}
1258
1259
bool FileDialog::is_mode_overriding_title() const {
1260
return mode_overrides_title;
1261
}
1262
1263
void FileDialog::set_file_mode(FileMode p_mode) {
1264
ERR_FAIL_INDEX((int)p_mode, 5);
1265
if (mode == p_mode) {
1266
return;
1267
}
1268
mode = p_mode;
1269
switch (mode) {
1270
case FILE_MODE_OPEN_FILE:
1271
set_default_ok_text(ETR("Open"));
1272
if (mode_overrides_title) {
1273
set_title(ETR("Open a File"));
1274
}
1275
break;
1276
case FILE_MODE_OPEN_FILES:
1277
set_default_ok_text(ETR("Open"));
1278
if (mode_overrides_title) {
1279
set_title(ETR("Open File(s)"));
1280
}
1281
break;
1282
case FILE_MODE_OPEN_DIR:
1283
set_default_ok_text(ETR("Select Current Folder"));
1284
if (mode_overrides_title) {
1285
set_title(ETR("Open a Directory"));
1286
}
1287
break;
1288
case FILE_MODE_OPEN_ANY:
1289
set_default_ok_text(ETR("Open"));
1290
if (mode_overrides_title) {
1291
set_title(ETR("Open a File or Directory"));
1292
}
1293
make_dir_button->show();
1294
break;
1295
case FILE_MODE_SAVE_FILE:
1296
set_default_ok_text(ETR("Save"));
1297
if (mode_overrides_title) {
1298
set_title(ETR("Save a File"));
1299
}
1300
break;
1301
}
1302
_update_make_dir_visible();
1303
1304
if (mode == FILE_MODE_OPEN_FILES) {
1305
file_list->set_select_mode(ItemList::SELECT_MULTI);
1306
} else {
1307
file_list->set_select_mode(ItemList::SELECT_SINGLE);
1308
}
1309
1310
get_ok_button()->set_disabled(_is_open_should_be_disabled());
1311
}
1312
1313
FileDialog::FileMode FileDialog::get_file_mode() const {
1314
return mode;
1315
}
1316
1317
void FileDialog::set_display_mode(DisplayMode p_mode) {
1318
ERR_FAIL_INDEX((int)p_mode, 2);
1319
if (display_mode == p_mode) {
1320
return;
1321
}
1322
display_mode = p_mode;
1323
1324
if (p_mode == DISPLAY_THUMBNAILS) {
1325
thumbnail_mode_button->set_pressed(true);
1326
list_mode_button->set_pressed(false);
1327
} else {
1328
thumbnail_mode_button->set_pressed(false);
1329
list_mode_button->set_pressed(true);
1330
}
1331
invalidate();
1332
}
1333
1334
FileDialog::DisplayMode FileDialog::get_display_mode() const {
1335
return display_mode;
1336
}
1337
1338
void FileDialog::set_customization_flag_enabled(Customization p_flag, bool p_enabled) {
1339
ERR_FAIL_INDEX(p_flag, CUSTOMIZATION_MAX);
1340
if (customization_flags[p_flag] == p_enabled) {
1341
return;
1342
}
1343
customization_flags[p_flag] = p_enabled;
1344
update_customization();
1345
}
1346
1347
bool FileDialog::is_customization_flag_enabled(Customization p_flag) const {
1348
ERR_FAIL_INDEX_V(p_flag, CUSTOMIZATION_MAX, false);
1349
return customization_flags[p_flag];
1350
}
1351
1352
void FileDialog::set_access(Access p_access) {
1353
ERR_FAIL_INDEX(p_access, 3);
1354
if (access == p_access) {
1355
return;
1356
}
1357
access = p_access;
1358
root_prefix = "";
1359
root_subfolder = "";
1360
1361
switch (p_access) {
1362
case ACCESS_FILESYSTEM: {
1363
dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
1364
#ifdef ANDROID_ENABLED
1365
set_current_dir(OS::get_singleton()->get_system_dir(OS::SYSTEM_DIR_DESKTOP));
1366
#endif
1367
} break;
1368
case ACCESS_RESOURCES: {
1369
dir_access = DirAccess::create(DirAccess::ACCESS_RESOURCES);
1370
} break;
1371
case ACCESS_USERDATA: {
1372
dir_access = DirAccess::create(DirAccess::ACCESS_USERDATA);
1373
} break;
1374
}
1375
_update_drives();
1376
invalidate();
1377
update_filters();
1378
update_dir();
1379
_update_favorite_list();
1380
_update_recent_list();
1381
}
1382
1383
void FileDialog::invalidate() {
1384
if (!is_visible() || is_invalidating) {
1385
return;
1386
}
1387
1388
is_invalidating = true;
1389
callable_mp(this, &FileDialog::_invalidate).call_deferred();
1390
}
1391
1392
void FileDialog::_invalidate() {
1393
if (!is_invalidating) {
1394
return;
1395
}
1396
1397
update_file_list();
1398
1399
is_invalidating = false;
1400
}
1401
1402
void FileDialog::_setup_button(Button *p_button, const Ref<Texture2D> &p_icon) {
1403
p_button->set_button_icon(p_icon);
1404
1405
p_button->begin_bulk_theme_override();
1406
p_button->add_theme_color_override(SNAME("icon_normal_color"), theme_cache.icon_normal_color);
1407
p_button->add_theme_color_override(SNAME("icon_hover_color"), theme_cache.icon_hover_color);
1408
p_button->add_theme_color_override(SNAME("icon_focus_color"), theme_cache.icon_focus_color);
1409
p_button->add_theme_color_override(SNAME("icon_pressed_color"), theme_cache.icon_pressed_color);
1410
p_button->end_bulk_theme_override();
1411
}
1412
1413
void FileDialog::_update_make_dir_visible() {
1414
make_dir_container->set_visible(customization_flags[CUSTOMIZATION_CREATE_FOLDER] && mode != FILE_MODE_OPEN_FILE && mode != FILE_MODE_OPEN_FILES);
1415
}
1416
1417
FileDialog::Access FileDialog::get_access() const {
1418
return access;
1419
}
1420
1421
void FileDialog::_make_dir_confirm() {
1422
Error err = dir_access->make_dir(new_dir_name->get_text().strip_edges());
1423
if (err == OK) {
1424
_change_dir(new_dir_name->get_text().strip_edges());
1425
update_filters();
1426
_push_history();
1427
} else {
1428
mkdirerr->popup_centered(Size2(250, 50));
1429
}
1430
new_dir_name->set_text(""); // reset label
1431
}
1432
1433
void FileDialog::_make_dir() {
1434
make_dir_dialog->popup_centered(Size2(250, 80));
1435
new_dir_name->grab_focus();
1436
}
1437
1438
void FileDialog::_select_drive(int p_idx) {
1439
String d = drives->get_item_text(p_idx);
1440
_change_dir(d);
1441
filename_edit->set_text("");
1442
_push_history();
1443
}
1444
1445
void FileDialog::_change_dir(const String &p_new_dir) {
1446
if (root_prefix.is_empty()) {
1447
dir_access->change_dir(p_new_dir);
1448
} else {
1449
String old_dir = dir_access->get_current_dir();
1450
dir_access->change_dir(p_new_dir);
1451
if (!dir_access->get_current_dir(false).begins_with(root_prefix)) {
1452
dir_access->change_dir(old_dir);
1453
return;
1454
}
1455
}
1456
1457
invalidate();
1458
update_dir();
1459
}
1460
1461
void FileDialog::_update_drives(bool p_select) {
1462
int dc = dir_access->get_drive_count();
1463
if (dc == 0 || access != ACCESS_FILESYSTEM) {
1464
drives->hide();
1465
} else {
1466
drives->clear();
1467
Node *dp = drives->get_parent();
1468
if (dp) {
1469
dp->remove_child(drives);
1470
}
1471
dp = dir_access->drives_are_shortcuts() ? shortcuts_container : drives_container;
1472
dp->add_child(drives);
1473
drives->show();
1474
1475
for (int i = 0; i < dir_access->get_drive_count(); i++) {
1476
drives->add_item(dir_access->get_drive(i));
1477
}
1478
1479
if (p_select) {
1480
drives->select(dir_access->get_current_drive());
1481
}
1482
}
1483
}
1484
1485
void FileDialog::_sort_option_selected(int p_option) {
1486
for (int i = 0; i < int(FileSortOption::MAX); i++) {
1487
file_sort_button->get_popup()->set_item_checked(i, (i == p_option));
1488
}
1489
file_sort = FileSortOption(p_option);
1490
invalidate();
1491
}
1492
1493
void FileDialog::_favorite_selected(int p_item) {
1494
ERR_FAIL_UNSIGNED_INDEX((uint32_t)p_item, global_favorites.size());
1495
_change_dir(favorite_list->get_item_metadata(p_item));
1496
_push_history();
1497
}
1498
1499
void FileDialog::_favorite_pressed() {
1500
String directory = get_current_dir();
1501
if (!directory.ends_with("/")) {
1502
directory += "/";
1503
}
1504
1505
bool found = false;
1506
for (const String &name : global_favorites) {
1507
if (!_path_matches_access(name)) {
1508
continue;
1509
}
1510
1511
if (name == directory) {
1512
found = true;
1513
break;
1514
}
1515
}
1516
1517
if (found) {
1518
global_favorites.erase(directory);
1519
} else {
1520
global_favorites.push_back(directory);
1521
}
1522
_update_favorite_list();
1523
}
1524
1525
void FileDialog::_favorite_move_up() {
1526
int current = favorite_list->get_current();
1527
if (current <= 0) {
1528
return;
1529
}
1530
1531
int a_idx = global_favorites.find(favorite_list->get_item_metadata(current - 1));
1532
int b_idx = global_favorites.find(favorite_list->get_item_metadata(current));
1533
1534
if (a_idx == -1 || b_idx == -1) {
1535
return;
1536
}
1537
SWAP(global_favorites[a_idx], global_favorites[b_idx]);
1538
_update_favorite_list();
1539
}
1540
1541
void FileDialog::_favorite_move_down() {
1542
int current = favorite_list->get_current();
1543
if (current == -1 || current >= favorite_list->get_item_count() - 1) {
1544
return;
1545
}
1546
1547
int a_idx = global_favorites.find(favorite_list->get_item_metadata(current));
1548
int b_idx = global_favorites.find(favorite_list->get_item_metadata(current + 1));
1549
1550
if (a_idx == -1 || b_idx == -1) {
1551
return;
1552
}
1553
SWAP(global_favorites[a_idx], global_favorites[b_idx]);
1554
_update_favorite_list();
1555
}
1556
1557
void FileDialog::_update_favorite_list() {
1558
const String current = get_current_dir();
1559
1560
favorite_list->clear();
1561
favorite_button->set_pressed(false);
1562
1563
Vector<String> favorited_paths;
1564
Vector<String> favorited_names;
1565
1566
int current_favorite = -1;
1567
for (uint32_t i = 0; i < global_favorites.size(); i++) {
1568
String name = global_favorites[i];
1569
if (!_path_matches_access(name)) {
1570
continue;
1571
}
1572
1573
if (!name.ends_with("/") || !name.begins_with(root_prefix)) {
1574
continue;
1575
}
1576
1577
if (!dir_access->dir_exists(name)) {
1578
// Remove invalid directory from the list of favorited directories.
1579
global_favorites.remove_at(i);
1580
i--;
1581
continue;
1582
}
1583
1584
if (name == current) {
1585
current_favorite = favorited_names.size();
1586
}
1587
favorited_paths.append(name);
1588
1589
// Compute favorite display text.
1590
if (name == "res://" || name == "user://") {
1591
name = "/";
1592
} else {
1593
if (current_favorite == -1 && name == current + "/") {
1594
current_favorite = favorited_names.size();
1595
}
1596
name = name.trim_suffix("/");
1597
name = name.get_file();
1598
}
1599
favorited_names.append(name);
1600
}
1601
1602
// EditorNode::disambiguate_filenames(favorited_paths, favorited_names); // TODO Needs a non-editor method.
1603
1604
const int favorites_size = favorited_paths.size();
1605
for (int i = 0; i < favorites_size; i++) {
1606
favorite_list->add_item(favorited_names[i], theme_cache.folder);
1607
favorite_list->set_item_tooltip(-1, favorited_paths[i]);
1608
favorite_list->set_item_metadata(-1, favorited_paths[i]);
1609
1610
if (i == current_favorite) {
1611
favorite_button->set_pressed(true);
1612
favorite_list->set_current(favorite_list->get_item_count() - 1);
1613
recent_list->deselect_all();
1614
}
1615
}
1616
_update_fav_buttons();
1617
}
1618
1619
void FileDialog::_update_fav_buttons() {
1620
const int current = favorite_list->get_current();
1621
fav_up_button->set_disabled(current < 1);
1622
fav_down_button->set_disabled(current == -1 || current >= favorite_list->get_item_count() - 1);
1623
}
1624
1625
void FileDialog::_recent_selected(int p_item) {
1626
ERR_FAIL_UNSIGNED_INDEX((uint32_t)p_item, global_recents.size());
1627
_change_dir(recent_list->get_item_metadata(p_item));
1628
_push_history();
1629
}
1630
1631
void FileDialog::_save_to_recent() {
1632
String directory = get_current_dir();
1633
if (!directory.ends_with("/")) {
1634
directory += "/";
1635
}
1636
1637
int count = 0;
1638
for (uint32_t i = 0; i < global_recents.size(); i++) {
1639
const String &dir = global_recents[i];
1640
if (!_path_matches_access(dir)) {
1641
continue;
1642
}
1643
1644
if (dir == directory || count > MAX_RECENTS) {
1645
global_recents.remove_at(i);
1646
i--;
1647
} else {
1648
count++;
1649
}
1650
}
1651
global_recents.insert(0, directory);
1652
1653
_update_recent_list();
1654
}
1655
1656
void FileDialog::_update_recent_list() {
1657
recent_list->clear();
1658
1659
Vector<String> recent_dir_paths;
1660
Vector<String> recent_dir_names;
1661
1662
for (uint32_t i = 0; i < global_recents.size(); i++) {
1663
String name = global_recents[i];
1664
if (!_path_matches_access(name)) {
1665
continue;
1666
}
1667
1668
if (!name.begins_with(root_prefix)) {
1669
continue;
1670
}
1671
1672
if (!dir_access->dir_exists(name)) {
1673
// Remove invalid directory from the list of recent directories.
1674
global_recents.remove_at(i);
1675
i--;
1676
continue;
1677
}
1678
recent_dir_paths.append(name);
1679
1680
// Compute recent directory display text.
1681
if (name == "res://" || name == "user://") {
1682
name = "/";
1683
} else {
1684
name = name.trim_suffix("/").get_file();
1685
}
1686
recent_dir_names.append(name);
1687
}
1688
1689
// EditorNode::disambiguate_filenames(recent_dir_paths, recent_dir_names); // TODO Needs a non-editor method.
1690
1691
const int recent_size = recent_dir_paths.size();
1692
for (int i = 0; i < recent_size; i++) {
1693
recent_list->add_item(recent_dir_names[i], theme_cache.folder);
1694
recent_list->set_item_tooltip(-1, recent_dir_paths[i]);
1695
recent_list->set_item_metadata(-1, recent_dir_paths[i]);
1696
}
1697
}
1698
1699
bool FileDialog::_path_matches_access(const String &p_path) const {
1700
bool is_res = p_path.begins_with("res://");
1701
bool is_user = p_path.begins_with("user://");
1702
if (access == ACCESS_RESOURCES) {
1703
return is_res;
1704
} else if (access == ACCESS_USERDATA) {
1705
return is_user;
1706
}
1707
return !is_res && !is_user;
1708
}
1709
1710
TypedArray<Dictionary> FileDialog::_get_options() const {
1711
TypedArray<Dictionary> out;
1712
for (const FileDialog::Option &opt : options) {
1713
Dictionary dict;
1714
dict["name"] = opt.name;
1715
dict["values"] = opt.values;
1716
dict["default"] = (int)selected_options.get(opt.name, opt.default_idx);
1717
out.push_back(dict);
1718
}
1719
return out;
1720
}
1721
1722
void FileDialog::_option_changed_checkbox_toggled(bool p_pressed, const String &p_name) {
1723
if (selected_options.has(p_name)) {
1724
selected_options[p_name] = p_pressed;
1725
}
1726
}
1727
1728
void FileDialog::_option_changed_item_selected(int p_idx, const String &p_name) {
1729
if (selected_options.has(p_name)) {
1730
selected_options[p_name] = p_idx;
1731
}
1732
}
1733
1734
void FileDialog::_update_option_controls() {
1735
if (!options_dirty) {
1736
return;
1737
}
1738
options_dirty = false;
1739
1740
while (flow_checkbox_options->get_child_count() > 0) {
1741
Node *child = flow_checkbox_options->get_child(0);
1742
flow_checkbox_options->remove_child(child);
1743
child->queue_free();
1744
}
1745
while (grid_select_options->get_child_count() > 0) {
1746
Node *child = grid_select_options->get_child(0);
1747
grid_select_options->remove_child(child);
1748
child->queue_free();
1749
}
1750
selected_options.clear();
1751
1752
for (const FileDialog::Option &opt : options) {
1753
if (opt.values.is_empty()) {
1754
CheckBox *cb = memnew(CheckBox);
1755
cb->set_text(opt.name);
1756
cb->set_accessibility_name(opt.name);
1757
cb->set_pressed(opt.default_idx);
1758
flow_checkbox_options->add_child(cb);
1759
cb->connect(SceneStringName(toggled), callable_mp(this, &FileDialog::_option_changed_checkbox_toggled).bind(opt.name));
1760
selected_options[opt.name] = (bool)opt.default_idx;
1761
} else {
1762
Label *lbl = memnew(Label);
1763
lbl->set_text(opt.name);
1764
grid_select_options->add_child(lbl);
1765
1766
OptionButton *ob = memnew(OptionButton);
1767
for (const String &val : opt.values) {
1768
ob->add_item(val);
1769
}
1770
ob->set_accessibility_name(opt.name);
1771
ob->select(opt.default_idx);
1772
grid_select_options->add_child(ob);
1773
ob->connect(SceneStringName(item_selected), callable_mp(this, &FileDialog::_option_changed_item_selected).bind(opt.name));
1774
selected_options[opt.name] = opt.default_idx;
1775
}
1776
}
1777
}
1778
1779
Dictionary FileDialog::get_selected_options() const {
1780
return selected_options;
1781
}
1782
1783
String FileDialog::get_option_name(int p_option) const {
1784
ERR_FAIL_INDEX_V(p_option, options.size(), String());
1785
return options[p_option].name;
1786
}
1787
1788
Vector<String> FileDialog::get_option_values(int p_option) const {
1789
ERR_FAIL_INDEX_V(p_option, options.size(), Vector<String>());
1790
return options[p_option].values;
1791
}
1792
1793
int FileDialog::get_option_default(int p_option) const {
1794
ERR_FAIL_INDEX_V(p_option, options.size(), -1);
1795
return options[p_option].default_idx;
1796
}
1797
1798
void FileDialog::set_option_name(int p_option, const String &p_name) {
1799
if (p_option < 0) {
1800
p_option += get_option_count();
1801
}
1802
ERR_FAIL_INDEX(p_option, options.size());
1803
options.write[p_option].name = p_name;
1804
options_dirty = true;
1805
if (is_visible()) {
1806
_update_option_controls();
1807
}
1808
}
1809
1810
void FileDialog::set_option_values(int p_option, const Vector<String> &p_values) {
1811
if (p_option < 0) {
1812
p_option += get_option_count();
1813
}
1814
ERR_FAIL_INDEX(p_option, options.size());
1815
options.write[p_option].values = p_values;
1816
if (p_values.is_empty()) {
1817
options.write[p_option].default_idx = CLAMP(options[p_option].default_idx, 0, 1);
1818
} else {
1819
options.write[p_option].default_idx = CLAMP(options[p_option].default_idx, 0, options[p_option].values.size() - 1);
1820
}
1821
options_dirty = true;
1822
if (is_visible()) {
1823
_update_option_controls();
1824
}
1825
}
1826
1827
void FileDialog::set_option_default(int p_option, int p_index) {
1828
if (p_option < 0) {
1829
p_option += get_option_count();
1830
}
1831
ERR_FAIL_INDEX(p_option, options.size());
1832
if (options[p_option].values.is_empty()) {
1833
options.write[p_option].default_idx = CLAMP(p_index, 0, 1);
1834
} else {
1835
options.write[p_option].default_idx = CLAMP(p_index, 0, options[p_option].values.size() - 1);
1836
}
1837
options_dirty = true;
1838
if (is_visible()) {
1839
_update_option_controls();
1840
}
1841
}
1842
1843
void FileDialog::add_option(const String &p_name, const Vector<String> &p_values, int p_index) {
1844
Option opt;
1845
opt.name = p_name;
1846
opt.values = p_values;
1847
if (opt.values.is_empty()) {
1848
opt.default_idx = CLAMP(p_index, 0, 1);
1849
} else {
1850
opt.default_idx = CLAMP(p_index, 0, opt.values.size() - 1);
1851
}
1852
options.push_back(opt);
1853
options_dirty = true;
1854
if (is_visible()) {
1855
_update_option_controls();
1856
}
1857
}
1858
1859
void FileDialog::set_option_count(int p_count) {
1860
ERR_FAIL_COND(p_count < 0);
1861
1862
if (options.size() == p_count) {
1863
return;
1864
}
1865
options.resize(p_count);
1866
1867
options_dirty = true;
1868
notify_property_list_changed();
1869
if (is_visible()) {
1870
_update_option_controls();
1871
}
1872
}
1873
1874
int FileDialog::get_option_count() const {
1875
return options.size();
1876
}
1877
1878
void FileDialog::_bind_methods() {
1879
ClassDB::bind_method(D_METHOD("_cancel_pressed"), &FileDialog::_cancel_pressed);
1880
1881
ClassDB::bind_method(D_METHOD("clear_filters"), &FileDialog::clear_filters);
1882
ClassDB::bind_method(D_METHOD("add_filter", "filter", "description"), &FileDialog::add_filter, DEFVAL(""));
1883
ClassDB::bind_method(D_METHOD("set_filters", "filters"), &FileDialog::set_filters);
1884
ClassDB::bind_method(D_METHOD("get_filters"), &FileDialog::get_filters);
1885
ClassDB::bind_method(D_METHOD("clear_filename_filter"), &FileDialog::clear_filename_filter);
1886
ClassDB::bind_method(D_METHOD("set_filename_filter", "filter"), &FileDialog::set_filename_filter);
1887
ClassDB::bind_method(D_METHOD("get_filename_filter"), &FileDialog::get_filename_filter);
1888
ClassDB::bind_method(D_METHOD("get_option_name", "option"), &FileDialog::get_option_name);
1889
ClassDB::bind_method(D_METHOD("get_option_values", "option"), &FileDialog::get_option_values);
1890
ClassDB::bind_method(D_METHOD("get_option_default", "option"), &FileDialog::get_option_default);
1891
ClassDB::bind_method(D_METHOD("set_option_name", "option", "name"), &FileDialog::set_option_name);
1892
ClassDB::bind_method(D_METHOD("set_option_values", "option", "values"), &FileDialog::set_option_values);
1893
ClassDB::bind_method(D_METHOD("set_option_default", "option", "default_value_index"), &FileDialog::set_option_default);
1894
ClassDB::bind_method(D_METHOD("set_option_count", "count"), &FileDialog::set_option_count);
1895
ClassDB::bind_method(D_METHOD("get_option_count"), &FileDialog::get_option_count);
1896
ClassDB::bind_method(D_METHOD("add_option", "name", "values", "default_value_index"), &FileDialog::add_option);
1897
ClassDB::bind_method(D_METHOD("get_selected_options"), &FileDialog::get_selected_options);
1898
ClassDB::bind_method(D_METHOD("get_current_dir"), &FileDialog::get_current_dir);
1899
ClassDB::bind_method(D_METHOD("get_current_file"), &FileDialog::get_current_file);
1900
ClassDB::bind_method(D_METHOD("get_current_path"), &FileDialog::get_current_path);
1901
ClassDB::bind_method(D_METHOD("set_current_dir", "dir"), &FileDialog::set_current_dir);
1902
ClassDB::bind_method(D_METHOD("set_current_file", "file"), &FileDialog::set_current_file);
1903
ClassDB::bind_method(D_METHOD("set_current_path", "path"), &FileDialog::set_current_path);
1904
ClassDB::bind_method(D_METHOD("set_mode_overrides_title", "override"), &FileDialog::set_mode_overrides_title);
1905
ClassDB::bind_method(D_METHOD("is_mode_overriding_title"), &FileDialog::is_mode_overriding_title);
1906
ClassDB::bind_method(D_METHOD("set_file_mode", "mode"), &FileDialog::set_file_mode);
1907
ClassDB::bind_method(D_METHOD("get_file_mode"), &FileDialog::get_file_mode);
1908
ClassDB::bind_method(D_METHOD("set_display_mode", "mode"), &FileDialog::set_display_mode);
1909
ClassDB::bind_method(D_METHOD("get_display_mode"), &FileDialog::get_display_mode);
1910
ClassDB::bind_method(D_METHOD("get_vbox"), &FileDialog::get_vbox);
1911
ClassDB::bind_method(D_METHOD("get_line_edit"), &FileDialog::get_line_edit);
1912
ClassDB::bind_method(D_METHOD("set_access", "access"), &FileDialog::set_access);
1913
ClassDB::bind_method(D_METHOD("get_access"), &FileDialog::get_access);
1914
ClassDB::bind_method(D_METHOD("set_root_subfolder", "dir"), &FileDialog::set_root_subfolder);
1915
ClassDB::bind_method(D_METHOD("get_root_subfolder"), &FileDialog::get_root_subfolder);
1916
ClassDB::bind_method(D_METHOD("set_show_hidden_files", "show"), &FileDialog::set_show_hidden_files);
1917
ClassDB::bind_method(D_METHOD("is_showing_hidden_files"), &FileDialog::is_showing_hidden_files);
1918
ClassDB::bind_method(D_METHOD("set_use_native_dialog", "native"), &FileDialog::set_use_native_dialog);
1919
ClassDB::bind_method(D_METHOD("get_use_native_dialog"), &FileDialog::get_use_native_dialog);
1920
ClassDB::bind_method(D_METHOD("set_customization_flag_enabled", "flag", "enabled"), &FileDialog::set_customization_flag_enabled);
1921
ClassDB::bind_method(D_METHOD("is_customization_flag_enabled", "flag"), &FileDialog::is_customization_flag_enabled);
1922
ClassDB::bind_method(D_METHOD("deselect_all"), &FileDialog::deselect_all);
1923
1924
ClassDB::bind_method(D_METHOD("invalidate"), &FileDialog::invalidate);
1925
1926
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "mode_overrides_title"), "set_mode_overrides_title", "is_mode_overriding_title");
1927
ADD_PROPERTY(PropertyInfo(Variant::INT, "file_mode", PROPERTY_HINT_ENUM, "Open File,Open Files,Open Folder,Open Any,Save"), "set_file_mode", "get_file_mode");
1928
ADD_PROPERTY(PropertyInfo(Variant::INT, "display_mode", PROPERTY_HINT_ENUM, "Thumbnails,List"), "set_display_mode", "get_display_mode");
1929
ADD_PROPERTY(PropertyInfo(Variant::INT, "access", PROPERTY_HINT_ENUM, "Resources,User Data,File System"), "set_access", "get_access");
1930
ADD_PROPERTY(PropertyInfo(Variant::STRING, "root_subfolder"), "set_root_subfolder", "get_root_subfolder");
1931
ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "filters"), "set_filters", "get_filters");
1932
ADD_PROPERTY(PropertyInfo(Variant::STRING, "filename_filter"), "set_filename_filter", "get_filename_filter");
1933
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_hidden_files"), "set_show_hidden_files", "is_showing_hidden_files");
1934
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_native_dialog"), "set_use_native_dialog", "get_use_native_dialog");
1935
1936
ADD_ARRAY_COUNT("Options", "option_count", "set_option_count", "get_option_count", "option_");
1937
1938
ADD_GROUP("Customization", "");
1939
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "hidden_files_toggle_enabled"), "set_customization_flag_enabled", "is_customization_flag_enabled", CUSTOMIZATION_HIDDEN_FILES);
1940
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "file_filter_toggle_enabled"), "set_customization_flag_enabled", "is_customization_flag_enabled", CUSTOMIZATION_FILE_FILTER);
1941
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "file_sort_options_enabled"), "set_customization_flag_enabled", "is_customization_flag_enabled", CUSTOMIZATION_FILE_SORT);
1942
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "folder_creation_enabled"), "set_customization_flag_enabled", "is_customization_flag_enabled", CUSTOMIZATION_CREATE_FOLDER);
1943
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "favorites_enabled"), "set_customization_flag_enabled", "is_customization_flag_enabled", CUSTOMIZATION_FAVORITES);
1944
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "recent_list_enabled"), "set_customization_flag_enabled", "is_customization_flag_enabled", CUSTOMIZATION_RECENT);
1945
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "layout_toggle_enabled"), "set_customization_flag_enabled", "is_customization_flag_enabled", CUSTOMIZATION_LAYOUT);
1946
1947
ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_dir", PROPERTY_HINT_DIR, "", PROPERTY_USAGE_NONE), "set_current_dir", "get_current_dir");
1948
ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_file", PROPERTY_HINT_FILE_PATH, "*", PROPERTY_USAGE_NONE), "set_current_file", "get_current_file");
1949
ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_current_path", "get_current_path");
1950
1951
ADD_SIGNAL(MethodInfo("file_selected", PropertyInfo(Variant::STRING, "path")));
1952
ADD_SIGNAL(MethodInfo("files_selected", PropertyInfo(Variant::PACKED_STRING_ARRAY, "paths")));
1953
ADD_SIGNAL(MethodInfo("dir_selected", PropertyInfo(Variant::STRING, "dir")));
1954
ADD_SIGNAL(MethodInfo("filename_filter_changed", PropertyInfo(Variant::STRING, "filter")));
1955
1956
BIND_ENUM_CONSTANT(FILE_MODE_OPEN_FILE);
1957
BIND_ENUM_CONSTANT(FILE_MODE_OPEN_FILES);
1958
BIND_ENUM_CONSTANT(FILE_MODE_OPEN_DIR);
1959
BIND_ENUM_CONSTANT(FILE_MODE_OPEN_ANY);
1960
BIND_ENUM_CONSTANT(FILE_MODE_SAVE_FILE);
1961
1962
BIND_ENUM_CONSTANT(ACCESS_RESOURCES);
1963
BIND_ENUM_CONSTANT(ACCESS_USERDATA);
1964
BIND_ENUM_CONSTANT(ACCESS_FILESYSTEM);
1965
1966
BIND_ENUM_CONSTANT(DISPLAY_THUMBNAILS);
1967
BIND_ENUM_CONSTANT(DISPLAY_LIST);
1968
1969
BIND_ENUM_CONSTANT(CUSTOMIZATION_HIDDEN_FILES);
1970
BIND_ENUM_CONSTANT(CUSTOMIZATION_CREATE_FOLDER);
1971
BIND_ENUM_CONSTANT(CUSTOMIZATION_FILE_FILTER);
1972
BIND_ENUM_CONSTANT(CUSTOMIZATION_FILE_SORT);
1973
BIND_ENUM_CONSTANT(CUSTOMIZATION_FAVORITES);
1974
BIND_ENUM_CONSTANT(CUSTOMIZATION_RECENT);
1975
BIND_ENUM_CONSTANT(CUSTOMIZATION_LAYOUT);
1976
1977
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, FileDialog, thumbnail_size);
1978
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, parent_folder);
1979
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, forward_folder);
1980
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, back_folder);
1981
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, reload);
1982
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, favorite);
1983
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, toggle_hidden);
1984
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, folder);
1985
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, toggle_filename_filter);
1986
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, file);
1987
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, create_folder);
1988
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, sort);
1989
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, favorite_up);
1990
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, favorite_down);
1991
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, thumbnail_mode);
1992
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, list_mode);
1993
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, file_thumbnail);
1994
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, folder_thumbnail);
1995
1996
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, FileDialog, folder_icon_color);
1997
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, FileDialog, file_icon_color);
1998
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, FileDialog, file_disabled_color);
1999
2000
// TODO: Define own colors?
2001
BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, FileDialog, icon_normal_color, "font_color", "Button");
2002
BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, FileDialog, icon_hover_color, "font_hover_color", "Button");
2003
BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, FileDialog, icon_focus_color, "font_focus_color", "Button");
2004
BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, FileDialog, icon_pressed_color, "font_pressed_color", "Button");
2005
2006
Option defaults;
2007
2008
base_property_helper.set_prefix("option_");
2009
base_property_helper.set_array_length_getter(&FileDialog::get_option_count);
2010
base_property_helper.register_property(PropertyInfo(Variant::STRING, "name"), defaults.name, &FileDialog::set_option_name, &FileDialog::get_option_name);
2011
base_property_helper.register_property(PropertyInfo(Variant::PACKED_STRING_ARRAY, "values"), defaults.values, &FileDialog::set_option_values, &FileDialog::get_option_values);
2012
base_property_helper.register_property(PropertyInfo(Variant::INT, "default"), defaults.default_idx, &FileDialog::set_option_default, &FileDialog::get_option_default);
2013
PropertyListHelper::register_base_helper(&base_property_helper);
2014
2015
ADD_CLASS_DEPENDENCY("Button");
2016
ADD_CLASS_DEPENDENCY("ConfirmationDialog");
2017
ADD_CLASS_DEPENDENCY("LineEdit");
2018
ADD_CLASS_DEPENDENCY("OptionButton");
2019
ADD_CLASS_DEPENDENCY("Tree");
2020
}
2021
2022
void FileDialog::set_show_hidden_files(bool p_show) {
2023
if (show_hidden_files == p_show) {
2024
return;
2025
}
2026
show_hidden_files = p_show;
2027
invalidate();
2028
}
2029
2030
void FileDialog::set_show_filename_filter(bool p_show) {
2031
if (p_show == show_filename_filter) {
2032
return;
2033
}
2034
if (p_show) {
2035
filename_filter->grab_focus();
2036
} else {
2037
if (filename_filter->has_focus()) {
2038
callable_mp((Control *)file_list, &Control::grab_focus).call_deferred();
2039
}
2040
}
2041
show_filename_filter = p_show;
2042
update_filename_filter_gui();
2043
invalidate();
2044
}
2045
2046
bool FileDialog::get_show_filename_filter() const {
2047
return show_filename_filter;
2048
}
2049
2050
bool FileDialog::is_showing_hidden_files() const {
2051
return show_hidden_files;
2052
}
2053
2054
void FileDialog::set_default_show_hidden_files(bool p_show) {
2055
default_show_hidden_files = p_show;
2056
}
2057
2058
void FileDialog::set_use_native_dialog(bool p_native) {
2059
use_native_dialog = p_native;
2060
2061
#ifdef TOOLS_ENABLED
2062
if (is_part_of_edited_scene()) {
2063
return;
2064
}
2065
#endif
2066
2067
// Replace the built-in dialog with the native one if it's currently visible.
2068
if (is_inside_tree() && is_visible() && _can_use_native_popup() && (use_native_dialog || OS::get_singleton()->is_sandboxed())) {
2069
ConfirmationDialog::set_visible(false);
2070
_native_popup();
2071
}
2072
}
2073
2074
bool FileDialog::get_use_native_dialog() const {
2075
return use_native_dialog;
2076
}
2077
2078
FileDialog::FileDialog() {
2079
set_title(ETR("Save a File"));
2080
set_hide_on_ok(false);
2081
set_size(Size2(640, 360));
2082
set_default_ok_text(ETR("Save")); // Default mode text.
2083
2084
for (int i = 0; i < CUSTOMIZATION_MAX; i++) {
2085
customization_flags[i] = true;
2086
}
2087
2088
show_hidden_files = default_show_hidden_files;
2089
dir_access = DirAccess::create(DirAccess::ACCESS_RESOURCES);
2090
2091
main_vbox = memnew(VBoxContainer);
2092
add_child(main_vbox, false, INTERNAL_MODE_FRONT);
2093
2094
HBoxContainer *top_toolbar = memnew(HBoxContainer);
2095
main_vbox->add_child(top_toolbar);
2096
2097
dir_prev = memnew(Button);
2098
dir_prev->set_theme_type_variation(SceneStringName(FlatButton));
2099
dir_prev->set_tooltip_text(ETR("Go to previous folder."));
2100
top_toolbar->add_child(dir_prev);
2101
dir_prev->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_go_back));
2102
2103
dir_next = memnew(Button);
2104
dir_next->set_theme_type_variation(SceneStringName(FlatButton));
2105
dir_next->set_tooltip_text(ETR("Go to next folder."));
2106
top_toolbar->add_child(dir_next);
2107
dir_next->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_go_forward));
2108
2109
dir_up = memnew(Button);
2110
dir_up->set_theme_type_variation(SceneStringName(FlatButton));
2111
dir_up->set_tooltip_text(ETR("Go to parent folder."));
2112
top_toolbar->add_child(dir_up);
2113
dir_up->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_go_up));
2114
2115
{
2116
Label *label = memnew(Label(ETR("Path:")));
2117
top_toolbar->add_child(label);
2118
}
2119
2120
drives_container = memnew(HBoxContainer);
2121
top_toolbar->add_child(drives_container);
2122
2123
drives = memnew(OptionButton);
2124
drives->connect(SceneStringName(item_selected), callable_mp(this, &FileDialog::_select_drive));
2125
drives->set_accessibility_name(ETR("Drive"));
2126
top_toolbar->add_child(drives);
2127
2128
directory_edit = memnew(LineEdit);
2129
directory_edit->set_accessibility_name(ETR("Path:"));
2130
directory_edit->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE);
2131
directory_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL);
2132
top_toolbar->add_child(directory_edit);
2133
directory_edit->connect(SceneStringName(text_submitted), callable_mp(this, &FileDialog::_dir_submitted));
2134
2135
shortcuts_container = memnew(HBoxContainer);
2136
top_toolbar->add_child(shortcuts_container);
2137
2138
refresh_button = memnew(Button);
2139
refresh_button->set_theme_type_variation(SceneStringName(FlatButton));
2140
refresh_button->set_tooltip_text(ETR("Refresh files."));
2141
top_toolbar->add_child(refresh_button);
2142
refresh_button->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::update_file_list));
2143
2144
favorite_button = memnew(Button);
2145
favorite_button->set_theme_type_variation(SceneStringName(FlatButton));
2146
favorite_button->set_tooltip_text(ETR("(Un)favorite current folder."));
2147
top_toolbar->add_child(favorite_button);
2148
favorite_button->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_favorite_pressed));
2149
2150
make_dir_container = memnew(HBoxContainer);
2151
top_toolbar->add_child(make_dir_container);
2152
2153
make_dir_container->add_child(memnew(VSeparator));
2154
2155
make_dir_button = memnew(Button);
2156
make_dir_button->set_theme_type_variation(SceneStringName(FlatButton));
2157
make_dir_button->set_tooltip_text(ETR("Create a new folder."));
2158
make_dir_container->add_child(make_dir_button);
2159
make_dir_button->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_make_dir));
2160
2161
HSplitContainer *main_split = memnew(HSplitContainer);
2162
main_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);
2163
main_vbox->add_child(main_split);
2164
2165
{
2166
VSplitContainer *fav_split = memnew(VSplitContainer);
2167
main_split->add_child(fav_split);
2168
2169
favorite_vbox = memnew(VBoxContainer);
2170
favorite_vbox->set_v_size_flags(Control::SIZE_EXPAND_FILL);
2171
fav_split->add_child(favorite_vbox);
2172
2173
HBoxContainer *fav_hbox = memnew(HBoxContainer);
2174
favorite_vbox->add_child(fav_hbox);
2175
2176
{
2177
Label *label = memnew(Label(ETR("Favorites:")));
2178
label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
2179
fav_hbox->add_child(label);
2180
}
2181
2182
fav_up_button = memnew(Button);
2183
fav_up_button->set_theme_type_variation(SceneStringName(FlatButton));
2184
fav_hbox->add_child(fav_up_button);
2185
fav_up_button->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_favorite_move_up));
2186
2187
fav_down_button = memnew(Button);
2188
fav_down_button->set_theme_type_variation(SceneStringName(FlatButton));
2189
fav_hbox->add_child(fav_down_button);
2190
fav_down_button->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_favorite_move_down));
2191
2192
favorite_list = memnew(ItemList);
2193
favorite_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
2194
favorite_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
2195
favorite_list->set_accessibility_name(ETR("Favorites:"));
2196
favorite_vbox->add_child(favorite_list);
2197
favorite_list->connect(SceneStringName(item_selected), callable_mp(this, &FileDialog::_favorite_selected));
2198
2199
recent_vbox = memnew(VBoxContainer);
2200
recent_vbox->set_v_size_flags(Control::SIZE_EXPAND_FILL);
2201
fav_split->add_child(recent_vbox);
2202
2203
{
2204
Label *label = memnew(Label(ETR("Recent:")));
2205
recent_vbox->add_child(label);
2206
}
2207
2208
recent_list = memnew(ItemList);
2209
recent_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
2210
recent_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
2211
recent_list->set_accessibility_name(ETR("Recent:"));
2212
recent_vbox->add_child(recent_list);
2213
recent_list->connect(SceneStringName(item_selected), callable_mp(this, &FileDialog::_recent_selected));
2214
}
2215
2216
VBoxContainer *file_vbox = memnew(VBoxContainer);
2217
main_split->add_child(file_vbox);
2218
2219
HBoxContainer *lower_toolbar = memnew(HBoxContainer);
2220
file_vbox->add_child(lower_toolbar);
2221
2222
{
2223
Label *label = memnew(Label(ETR("Directories & Files:")));
2224
label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
2225
label->set_theme_type_variation("HeaderSmall");
2226
lower_toolbar->add_child(label);
2227
}
2228
2229
show_hidden = memnew(Button);
2230
show_hidden->set_theme_type_variation(SceneStringName(FlatButton));
2231
show_hidden->set_toggle_mode(true);
2232
show_hidden->set_pressed(is_showing_hidden_files());
2233
show_hidden->set_tooltip_text(ETR("Toggle the visibility of hidden files."));
2234
lower_toolbar->add_child(show_hidden);
2235
show_hidden->connect(SceneStringName(toggled), callable_mp(this, &FileDialog::set_show_hidden_files));
2236
2237
show_hidden_separator = memnew(VSeparator);
2238
lower_toolbar->add_child(show_hidden_separator);
2239
2240
layout_container = memnew(HBoxContainer);
2241
lower_toolbar->add_child(layout_container);
2242
2243
Ref<ButtonGroup> view_mode_group;
2244
view_mode_group.instantiate();
2245
2246
thumbnail_mode_button = memnew(Button);
2247
thumbnail_mode_button->set_toggle_mode(true);
2248
thumbnail_mode_button->set_pressed(true);
2249
thumbnail_mode_button->set_button_group(view_mode_group);
2250
thumbnail_mode_button->set_theme_type_variation(SceneStringName(FlatButton));
2251
thumbnail_mode_button->set_tooltip_text(ETR("View items as a grid of thumbnails."));
2252
layout_container->add_child(thumbnail_mode_button);
2253
thumbnail_mode_button->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::set_display_mode).bind(DISPLAY_THUMBNAILS));
2254
2255
list_mode_button = memnew(Button);
2256
list_mode_button->set_toggle_mode(true);
2257
list_mode_button->set_button_group(view_mode_group);
2258
list_mode_button->set_theme_type_variation(SceneStringName(FlatButton));
2259
list_mode_button->set_tooltip_text(ETR("View items as a list."));
2260
layout_container->add_child(list_mode_button);
2261
list_mode_button->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::set_display_mode).bind(DISPLAY_LIST));
2262
2263
layout_separator = memnew(VSeparator);
2264
layout_container->add_child(layout_separator);
2265
2266
show_filename_filter_button = memnew(Button);
2267
show_filename_filter_button->set_theme_type_variation(SceneStringName(FlatButton));
2268
show_filename_filter_button->set_toggle_mode(true);
2269
show_filename_filter_button->set_pressed(false);
2270
show_filename_filter_button->set_tooltip_text(ETR("Toggle the visibility of the filter for file names."));
2271
lower_toolbar->add_child(show_filename_filter_button);
2272
show_filename_filter_button->connect(SceneStringName(toggled), callable_mp(this, &FileDialog::set_show_filename_filter));
2273
2274
file_sort_button = memnew(MenuButton);
2275
file_sort_button->set_flat(false);
2276
file_sort_button->set_theme_type_variation("FlatMenuButton");
2277
file_sort_button->set_tooltip_text(ETR("Sort files"));
2278
2279
PopupMenu *sort_menu = file_sort_button->get_popup();
2280
sort_menu->add_radio_check_item(ETR("Sort by Name (Ascending)"), int(FileSortOption::NAME));
2281
sort_menu->add_radio_check_item(ETR("Sort by Name (Descending)"), int(FileSortOption::NAME_REVERSE));
2282
sort_menu->add_radio_check_item(ETR("Sort by Type (Ascending)"), int(FileSortOption::TYPE));
2283
sort_menu->add_radio_check_item(ETR("Sort by Type (Descending)"), int(FileSortOption::TYPE_REVERSE));
2284
sort_menu->add_radio_check_item(ETR("Sort by Modified Time (Newest First)"), int(FileSortOption::MODIFIED_TIME));
2285
sort_menu->add_radio_check_item(ETR("Sort by Modified Time (Oldest First)"), int(FileSortOption::MODIFIED_TIME_REVERSE));
2286
sort_menu->set_item_checked(0, true);
2287
lower_toolbar->add_child(file_sort_button);
2288
sort_menu->connect(SceneStringName(id_pressed), callable_mp(this, &FileDialog::_sort_option_selected));
2289
2290
file_list = memnew(ItemList);
2291
file_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
2292
file_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
2293
file_list->set_accessibility_name(ETR("Directories & Files:"));
2294
file_list->set_allow_rmb_select(true);
2295
file_vbox->add_child(file_list);
2296
file_list->connect("multi_selected", callable_mp(this, &FileDialog::_file_list_multi_selected));
2297
file_list->connect("item_selected", callable_mp(this, &FileDialog::_file_list_selected));
2298
file_list->connect("item_activated", callable_mp(this, &FileDialog::_file_list_item_activated));
2299
file_list->connect("item_clicked", callable_mp(this, &FileDialog::_item_clicked));
2300
file_list->connect("empty_clicked", callable_mp(this, &FileDialog::_empty_clicked));
2301
2302
message = memnew(Label);
2303
message->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
2304
message->hide();
2305
message->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
2306
message->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
2307
message->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
2308
file_list->add_child(message);
2309
2310
filename_filter_box = memnew(HBoxContainer);
2311
filename_filter_box->set_visible(false);
2312
file_vbox->add_child(filename_filter_box);
2313
2314
{
2315
Label *label = memnew(Label(ETR("Filter:")));
2316
filename_filter_box->add_child(label);
2317
}
2318
2319
filename_filter = memnew(LineEdit);
2320
filename_filter->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE);
2321
filename_filter->set_stretch_ratio(4);
2322
filename_filter->set_h_size_flags(Control::SIZE_EXPAND_FILL);
2323
filename_filter->set_clear_button_enabled(true);
2324
filename_filter->set_accessibility_name(ETR("Filename Filter:"));
2325
filename_filter_box->add_child(filename_filter);
2326
filename_filter->connect(SceneStringName(text_changed), callable_mp(this, &FileDialog::_filename_filter_changed).unbind(1));
2327
filename_filter->connect(SceneStringName(text_submitted), callable_mp(this, &FileDialog::_filename_filter_selected).unbind(1));
2328
2329
file_box = memnew(HBoxContainer);
2330
file_vbox->add_child(file_box);
2331
2332
{
2333
Label *label = memnew(Label(ETR("File:")));
2334
file_box->add_child(label);
2335
}
2336
2337
filename_edit = memnew(LineEdit);
2338
filename_edit->set_accessibility_name(ETR("File:"));
2339
filename_edit->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE);
2340
filename_edit->set_stretch_ratio(4);
2341
filename_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL);
2342
file_box->add_child(filename_edit);
2343
filename_edit->connect(SceneStringName(text_submitted), callable_mp(this, &FileDialog::_file_submitted));
2344
2345
filter = memnew(OptionButton);
2346
filter->set_stretch_ratio(3);
2347
filter->set_h_size_flags(Control::SIZE_EXPAND_FILL);
2348
filter->set_clip_text(true); // Too many extensions overflows it.
2349
file_box->add_child(filter);
2350
filter->connect(SceneStringName(item_selected), callable_mp(this, &FileDialog::_filter_selected));
2351
2352
flow_checkbox_options = memnew(HFlowContainer);
2353
flow_checkbox_options->set_h_size_flags(Control::SIZE_EXPAND_FILL);
2354
flow_checkbox_options->set_alignment(FlowContainer::ALIGNMENT_CENTER);
2355
main_vbox->add_child(flow_checkbox_options);
2356
2357
grid_select_options = memnew(GridContainer);
2358
grid_select_options->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
2359
grid_select_options->set_columns(2);
2360
main_vbox->add_child(grid_select_options);
2361
2362
confirm_save = memnew(ConfirmationDialog);
2363
add_child(confirm_save, false, INTERNAL_MODE_FRONT);
2364
confirm_save->connect(SceneStringName(confirmed), callable_mp(this, &FileDialog::_save_confirm_pressed));
2365
2366
make_dir_dialog = memnew(ConfirmationDialog);
2367
make_dir_dialog->set_title(ETR("Create Folder"));
2368
add_child(make_dir_dialog, false, INTERNAL_MODE_FRONT);
2369
make_dir_dialog->connect(SceneStringName(confirmed), callable_mp(this, &FileDialog::_make_dir_confirm));
2370
2371
VBoxContainer *makevb = memnew(VBoxContainer);
2372
make_dir_dialog->add_child(makevb);
2373
2374
new_dir_name = memnew(LineEdit);
2375
new_dir_name->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE);
2376
makevb->add_margin_child(ETR("Name:"), new_dir_name);
2377
make_dir_dialog->register_text_enter(new_dir_name);
2378
2379
mkdirerr = memnew(AcceptDialog);
2380
mkdirerr->set_text(ETR("Could not create folder."));
2381
add_child(mkdirerr, false, INTERNAL_MODE_FRONT);
2382
2383
exterr = memnew(AcceptDialog);
2384
exterr->set_text(ETR("Invalid extension, or empty filename."));
2385
add_child(exterr, false, INTERNAL_MODE_FRONT);
2386
2387
item_menu = memnew(PopupMenu);
2388
item_menu->connect(SceneStringName(id_pressed), callable_mp(this, &FileDialog::_item_menu_id_pressed));
2389
add_child(item_menu, false, INTERNAL_MODE_FRONT);
2390
2391
connect(SceneStringName(confirmed), callable_mp(this, &FileDialog::_action_pressed));
2392
2393
_update_drives();
2394
update_filters();
2395
update_filename_filter_gui();
2396
update_dir();
2397
2398
if (register_func) {
2399
register_func(this);
2400
}
2401
2402
property_helper.setup_for_instance(base_property_helper, this);
2403
}
2404
2405
FileDialog::~FileDialog() {
2406
if (unregister_func) {
2407
unregister_func(this);
2408
}
2409
}
2410
2411