Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/gui/editor_file_dialog.cpp
9898 views
1
/**************************************************************************/
2
/* editor_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 "editor_file_dialog.h"
32
33
#include "core/config/project_settings.h"
34
#include "core/os/keyboard.h"
35
#include "core/os/os.h"
36
#include "editor/docks/filesystem_dock.h"
37
#include "editor/editor_node.h"
38
#include "editor/file_system/dependency_editor.h"
39
#include "editor/file_system/editor_file_system.h"
40
#include "editor/inspector/editor_resource_preview.h"
41
#include "editor/settings/editor_settings.h"
42
#include "editor/themes/editor_scale.h"
43
#include "scene/gui/center_container.h"
44
#include "scene/gui/check_box.h"
45
#include "scene/gui/flow_container.h"
46
#include "scene/gui/grid_container.h"
47
#include "scene/gui/label.h"
48
#include "scene/gui/line_edit.h"
49
#include "scene/gui/option_button.h"
50
#include "scene/gui/separator.h"
51
#include "scene/gui/split_container.h"
52
#include "scene/gui/texture_rect.h"
53
#include "servers/display_server.h"
54
55
void EditorFileDialog::_native_popup() {
56
// Show native dialog directly.
57
String root;
58
if (access == ACCESS_RESOURCES) {
59
root = ProjectSettings::get_singleton()->get_resource_path();
60
} else if (access == ACCESS_USERDATA) {
61
root = OS::get_singleton()->get_user_data_dir();
62
}
63
64
// Attach native file dialog to first persistent parent window.
65
Window *w = (is_transient() || is_transient_to_focused()) ? get_parent_visible_window() : nullptr;
66
while (w && w->get_flag(FLAG_POPUP) && w->get_parent_visible_window()) {
67
w = w->get_parent_visible_window();
68
}
69
DisplayServer::WindowID wid = w ? w->get_window_id() : DisplayServer::INVALID_WINDOW_ID;
70
71
DisplayServer::get_singleton()->file_dialog_with_options_show(get_displayed_title(), ProjectSettings::get_singleton()->globalize_path(dir->get_text()), root, file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), processed_filters, _get_options(), callable_mp(this, &EditorFileDialog::_native_dialog_cb), wid);
72
}
73
74
void EditorFileDialog::popup(const Rect2i &p_rect) {
75
_update_option_controls();
76
77
bool use_native = DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE) && (bool(EDITOR_GET("interface/editor/use_native_file_dialogs")) || OS::get_singleton()->is_sandboxed());
78
if (!side_vbox && use_native) {
79
_native_popup();
80
} else {
81
// Show custom file dialog (full dialog or side menu only).
82
_update_side_menu_visibility(use_native);
83
ConfirmationDialog::popup(p_rect);
84
}
85
}
86
87
void EditorFileDialog::set_visible(bool p_visible) {
88
if (p_visible) {
89
bool use_native = DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE) && (bool(EDITOR_GET("interface/editor/use_native_file_dialogs")) || OS::get_singleton()->is_sandboxed());
90
_update_option_controls();
91
if (!side_vbox && use_native) {
92
_native_popup();
93
} else {
94
// Show custom file dialog (full dialog or side menu only).
95
_update_side_menu_visibility(use_native);
96
ConfirmationDialog::set_visible(p_visible);
97
}
98
} else {
99
ConfirmationDialog::set_visible(p_visible);
100
}
101
}
102
103
void EditorFileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter, const Dictionary &p_selected_options) {
104
if (!p_ok) {
105
file->set_text("");
106
emit_signal(SNAME("canceled"));
107
return;
108
}
109
110
if (p_files.is_empty()) {
111
return;
112
}
113
114
Vector<String> files = p_files;
115
if (access != ACCESS_FILESYSTEM) {
116
for (String &file_name : files) {
117
file_name = ProjectSettings::get_singleton()->localize_path(file_name);
118
}
119
}
120
selected_options = p_selected_options;
121
122
String f = files[0];
123
124
filter->select(p_filter);
125
dir->set_text(f.get_base_dir());
126
file->set_text(f.get_file());
127
_dir_submitted(f.get_base_dir());
128
129
if (mode == FILE_MODE_OPEN_FILES) {
130
emit_signal(SNAME("files_selected"), files);
131
} else {
132
if (mode == FILE_MODE_SAVE_FILE) {
133
bool valid = false;
134
135
if (p_filter == filter->get_item_count() - 1) {
136
valid = true; // Match none.
137
} else if (filters.size() > 1 && p_filter == 0) {
138
// Match all filters.
139
for (int i = 0; i < filters.size(); i++) {
140
String flt = filters[i].get_slicec(';', 0);
141
for (int j = 0; j < flt.get_slice_count(","); j++) {
142
String str = flt.get_slicec(',', j).strip_edges();
143
if (f.matchn(str)) {
144
valid = true;
145
break;
146
}
147
}
148
if (valid) {
149
break;
150
}
151
}
152
} else {
153
int idx = p_filter;
154
if (filters.size() > 1) {
155
idx--;
156
}
157
if (idx >= 0 && idx < filters.size()) {
158
String flt = filters[idx].get_slicec(';', 0);
159
int filter_slice_count = flt.get_slice_count(",");
160
for (int j = 0; j < filter_slice_count; j++) {
161
String str = flt.get_slicec(',', j).strip_edges();
162
if (f.matchn(str)) {
163
valid = true;
164
break;
165
}
166
}
167
168
if (!valid && filter_slice_count > 0) {
169
String str = flt.get_slicec(',', 0).strip_edges();
170
f += str.substr(1);
171
file->set_text(f.get_file());
172
valid = true;
173
}
174
} else {
175
valid = true;
176
}
177
}
178
179
// Add first extension of filter if no valid extension is found.
180
if (!valid) {
181
int idx = p_filter;
182
String flt = filters[idx].get_slicec(';', 0);
183
String ext = flt.get_slicec(',', 0).strip_edges().get_extension();
184
f += "." + ext;
185
}
186
emit_signal(SNAME("file_selected"), f);
187
} else if ((mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_FILE) && dir_access->file_exists(f)) {
188
emit_signal(SNAME("file_selected"), f);
189
} else if (mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_DIR) {
190
emit_signal(SNAME("dir_selected"), f);
191
}
192
}
193
}
194
195
void EditorFileDialog::popup_file_dialog() {
196
popup_centered_clamped(Size2(1050, 700) * EDSCALE, 0.8);
197
_focus_file_text();
198
}
199
200
void EditorFileDialog::_focus_file_text() {
201
int lp = file->get_text().rfind_char('.');
202
if (lp != -1) {
203
file->select(0, lp);
204
file->grab_focus();
205
}
206
}
207
208
VBoxContainer *EditorFileDialog::get_vbox() {
209
return vbox;
210
}
211
212
void EditorFileDialog::_update_theme_item_cache() {
213
ConfirmationDialog::_update_theme_item_cache();
214
215
theme_cache.parent_folder = get_editor_theme_icon(SNAME("ArrowUp"));
216
theme_cache.forward_folder = get_editor_theme_icon(SNAME("Forward"));
217
theme_cache.back_folder = get_editor_theme_icon(SNAME("Back"));
218
theme_cache.reload = get_editor_theme_icon(SNAME("Reload"));
219
theme_cache.toggle_hidden = get_editor_theme_icon(SNAME("GuiVisibilityVisible"));
220
theme_cache.toggle_filename_filter = get_editor_theme_icon(SNAME("FilenameFilter"));
221
theme_cache.favorite = get_editor_theme_icon(SNAME("Favorites"));
222
theme_cache.mode_thumbnails = get_editor_theme_icon(SNAME("FileThumbnail"));
223
theme_cache.mode_list = get_editor_theme_icon(SNAME("FileList"));
224
theme_cache.favorites_up = get_editor_theme_icon(SNAME("MoveUp"));
225
theme_cache.favorites_down = get_editor_theme_icon(SNAME("MoveDown"));
226
theme_cache.create_folder = get_editor_theme_icon(SNAME("FolderCreate"));
227
theme_cache.open_folder = get_editor_theme_icon(SNAME("FolderBrowse"));
228
229
theme_cache.filter_box = get_editor_theme_icon(SNAME("Search"));
230
theme_cache.file_sort_button = get_editor_theme_icon(SNAME("Sort"));
231
232
theme_cache.folder = get_editor_theme_icon(SNAME("Folder"));
233
theme_cache.folder_icon_color = get_theme_color(SNAME("folder_icon_color"), SNAME("FileDialog"));
234
235
theme_cache.action_copy = get_editor_theme_icon(SNAME("ActionCopy"));
236
theme_cache.action_delete = get_editor_theme_icon(SNAME("Remove"));
237
theme_cache.filesystem = get_editor_theme_icon(SNAME("Filesystem"));
238
239
theme_cache.folder_medium_thumbnail = get_editor_theme_icon(SNAME("FolderMediumThumb"));
240
theme_cache.file_medium_thumbnail = get_editor_theme_icon(SNAME("FileMediumThumb"));
241
theme_cache.folder_big_thumbnail = get_editor_theme_icon(SNAME("FolderBigThumb"));
242
theme_cache.file_big_thumbnail = get_editor_theme_icon(SNAME("FileBigThumb"));
243
244
theme_cache.progress[0] = get_editor_theme_icon("Progress1");
245
theme_cache.progress[1] = get_editor_theme_icon("Progress2");
246
theme_cache.progress[2] = get_editor_theme_icon("Progress3");
247
theme_cache.progress[3] = get_editor_theme_icon("Progress4");
248
theme_cache.progress[4] = get_editor_theme_icon("Progress5");
249
theme_cache.progress[5] = get_editor_theme_icon("Progress6");
250
theme_cache.progress[6] = get_editor_theme_icon("Progress7");
251
theme_cache.progress[7] = get_editor_theme_icon("Progress8");
252
}
253
254
void EditorFileDialog::_notification(int p_what) {
255
switch (p_what) {
256
case NOTIFICATION_POSTINITIALIZE: {
257
set_translation_domain(SNAME("godot.editor"));
258
} break;
259
260
case NOTIFICATION_TRANSLATION_CHANGED: {
261
update_filters();
262
[[fallthrough]];
263
}
264
265
case NOTIFICATION_THEME_CHANGED:
266
case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
267
_update_icons();
268
invalidate();
269
} break;
270
271
case NOTIFICATION_PROCESS: {
272
if (preview_waiting) {
273
preview_wheel_timeout -= get_process_delta_time();
274
if (preview_wheel_timeout <= 0) {
275
preview_wheel_index++;
276
if (preview_wheel_index >= 8) {
277
preview_wheel_index = 0;
278
}
279
280
Ref<Texture2D> frame = theme_cache.progress[preview_wheel_index];
281
preview->set_texture(frame);
282
preview_wheel_timeout = 0.1;
283
}
284
}
285
} break;
286
287
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
288
if (!EditorSettings::get_singleton()->check_changed_settings_in_group("filesystem/file_dialog")) {
289
break;
290
}
291
bool is_showing_hidden = EDITOR_GET("filesystem/file_dialog/show_hidden_files");
292
if (show_hidden_files != is_showing_hidden) {
293
set_show_hidden_files(is_showing_hidden);
294
}
295
set_display_mode((DisplayMode)EDITOR_GET("filesystem/file_dialog/display_mode").operator int());
296
297
// DO NOT CALL UPDATE FILE LIST HERE, ALL HUNDREDS OF HIDDEN DIALOGS WILL RESPOND, CALL INVALIDATE INSTEAD
298
invalidate();
299
} break;
300
301
case NOTIFICATION_VISIBILITY_CHANGED: {
302
if (!is_visible()) {
303
set_process_shortcut_input(false);
304
}
305
306
invalidate(); // For consistency with the standard FileDialog.
307
} break;
308
309
case NOTIFICATION_WM_WINDOW_FOCUS_IN: {
310
// Check if the current directory was removed externally (much less likely to happen while editor window is focused).
311
String previous_dir = get_current_dir();
312
while (!dir_access->dir_exists(get_current_dir())) {
313
_go_up();
314
315
// In case we can't go further up, use some fallback and break.
316
if (get_current_dir() == previous_dir) {
317
_dir_submitted(OS::get_singleton()->get_user_data_dir());
318
break;
319
}
320
}
321
} break;
322
}
323
}
324
325
void EditorFileDialog::shortcut_input(const Ref<InputEvent> &p_event) {
326
ERR_FAIL_COND(p_event.is_null());
327
328
Ref<InputEventKey> k = p_event;
329
330
if (k.is_valid()) {
331
if (k->is_pressed()) {
332
bool handled = false;
333
334
if (ED_IS_SHORTCUT("file_dialog/go_back", p_event)) {
335
_go_back();
336
handled = true;
337
}
338
if (ED_IS_SHORTCUT("file_dialog/go_forward", p_event)) {
339
_go_forward();
340
handled = true;
341
}
342
if (ED_IS_SHORTCUT("file_dialog/go_up", p_event)) {
343
_go_up();
344
handled = true;
345
}
346
if (ED_IS_SHORTCUT("file_dialog/refresh", p_event)) {
347
invalidate();
348
handled = true;
349
}
350
if (ED_IS_SHORTCUT("file_dialog/toggle_hidden_files", p_event)) {
351
set_show_hidden_files(!show_hidden_files);
352
handled = true;
353
}
354
if (ED_IS_SHORTCUT("file_dialog/toggle_favorite", p_event)) {
355
_favorite_pressed();
356
handled = true;
357
}
358
if (ED_IS_SHORTCUT("file_dialog/toggle_mode", p_event)) {
359
if (mode_thumbnails->is_pressed()) {
360
set_display_mode(DISPLAY_LIST);
361
} else {
362
set_display_mode(DISPLAY_THUMBNAILS);
363
}
364
handled = true;
365
}
366
if (ED_IS_SHORTCUT("file_dialog/create_folder", p_event)) {
367
_make_dir();
368
handled = true;
369
}
370
if (ED_IS_SHORTCUT("file_dialog/delete", p_event)) {
371
_delete_items();
372
handled = true;
373
}
374
if (ED_IS_SHORTCUT("file_dialog/focus_path", p_event)) {
375
dir->grab_focus();
376
dir->select_all();
377
handled = true;
378
}
379
if (ED_IS_SHORTCUT("file_dialog/focus_filter", p_event)) {
380
show_search_filter_button->set_pressed(!show_search_filter_button->is_pressed());
381
_focus_filter_box();
382
handled = true;
383
}
384
if (ED_IS_SHORTCUT("file_dialog/move_favorite_up", p_event)) {
385
_favorite_move_up();
386
handled = true;
387
}
388
if (ED_IS_SHORTCUT("file_dialog/move_favorite_down", p_event)) {
389
_favorite_move_down();
390
handled = true;
391
}
392
393
if (handled) {
394
set_input_as_handled();
395
}
396
}
397
}
398
}
399
400
void EditorFileDialog::set_enable_multiple_selection(bool p_enable) {
401
item_list->set_select_mode(p_enable ? ItemList::SELECT_MULTI : ItemList::SELECT_SINGLE);
402
}
403
404
Vector<String> EditorFileDialog::get_selected_files() const {
405
Vector<String> list;
406
for (int i = 0; i < item_list->get_item_count(); i++) {
407
if (item_list->is_selected(i)) {
408
list.push_back(item_list->get_item_text(i));
409
}
410
}
411
return list;
412
}
413
414
void EditorFileDialog::update_dir() {
415
full_dir = dir_access->get_current_dir();
416
if (drives->is_visible()) {
417
if (dir_access->get_current_dir().is_network_share_path()) {
418
_update_drives(false);
419
drives->add_item(RTR("Network"));
420
drives->set_item_disabled(-1, true);
421
drives->select(drives->get_item_count() - 1);
422
} else {
423
drives->select(dir_access->get_current_drive());
424
}
425
}
426
dir->set_text(dir_access->get_current_dir(false));
427
428
filter_box->clear();
429
430
// Disable "Open" button only when selecting file(s) mode.
431
get_ok_button()->set_disabled(_is_open_should_be_disabled());
432
switch (mode) {
433
case FILE_MODE_OPEN_FILE:
434
case FILE_MODE_OPEN_FILES:
435
file->set_text("");
436
set_ok_button_text(TTRC("Open"));
437
break;
438
case FILE_MODE_OPEN_ANY:
439
case FILE_MODE_OPEN_DIR:
440
file->set_text("");
441
set_ok_button_text(TTRC("Select Current Folder"));
442
break;
443
case FILE_MODE_SAVE_FILE:
444
// FIXME: Implement, or refactor to avoid duplication with set_mode
445
break;
446
}
447
}
448
449
void EditorFileDialog::_dir_submitted(const String &p_dir) {
450
String new_dir = p_dir;
451
#ifdef WINDOWS_ENABLED
452
if (drives->is_visible() && !new_dir.is_network_share_path() && new_dir.is_absolute_path() && new_dir.find(":/") == -1 && new_dir.find(":\\") == -1) {
453
// Non network path without X:/ prefix on Windows, add drive letter.
454
new_dir = drives->get_item_text(drives->get_selected()).path_join(new_dir);
455
}
456
#endif
457
dir_access->change_dir(new_dir);
458
invalidate();
459
update_dir();
460
_push_history();
461
}
462
463
void EditorFileDialog::_file_submitted(const String &p_file) {
464
_action_pressed();
465
}
466
467
void EditorFileDialog::_save_confirm_pressed() {
468
String f = dir_access->get_current_dir().path_join(file->get_text());
469
_save_to_recent();
470
hide();
471
emit_signal(SNAME("file_selected"), f);
472
}
473
474
void EditorFileDialog::_post_popup() {
475
ConfirmationDialog::_post_popup();
476
477
// Check if the current path doesn't exist and correct it.
478
String current = dir_access->get_current_dir();
479
while (!dir_access->dir_exists(current)) {
480
current = current.get_base_dir();
481
}
482
set_current_dir(current);
483
484
if (mode == FILE_MODE_SAVE_FILE) {
485
file->grab_focus();
486
} else {
487
item_list->grab_focus();
488
}
489
490
bool is_open_directory_mode = mode == FILE_MODE_OPEN_DIR;
491
PopupMenu *p = file_sort_button->get_popup();
492
p->set_item_disabled(2, is_open_directory_mode);
493
p->set_item_disabled(3, is_open_directory_mode);
494
p->set_item_disabled(4, is_open_directory_mode);
495
p->set_item_disabled(5, is_open_directory_mode);
496
if (is_open_directory_mode) {
497
file_box->set_visible(false);
498
} else {
499
file_box->set_visible(true);
500
}
501
502
if (!get_current_file().is_empty()) {
503
_request_single_thumbnail(get_current_dir().path_join(get_current_file()));
504
}
505
506
local_history.clear();
507
local_history_pos = -1;
508
_push_history();
509
510
set_process_shortcut_input(true);
511
}
512
513
void EditorFileDialog::_thumbnail_result(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, const Variant &p_udata) {
514
if (display_mode == DISPLAY_LIST || p_preview.is_null()) {
515
return;
516
}
517
518
for (int i = 0; i < item_list->get_item_count(); i++) {
519
Dictionary d = item_list->get_item_metadata(i);
520
String pname = d["path"];
521
if (pname == p_path) {
522
item_list->set_item_icon(i, p_preview);
523
item_list->set_item_tag_icon(i, Ref<Texture2D>());
524
}
525
}
526
}
527
528
void EditorFileDialog::_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, const Variant &p_udata) {
529
set_process(false);
530
preview_waiting = false;
531
532
if (p_preview.is_valid() && get_current_path() == p_path) {
533
preview->set_texture(p_preview);
534
if (display_mode == DISPLAY_THUMBNAILS) {
535
preview_vb->hide();
536
} else {
537
preview_vb->show();
538
}
539
540
} else {
541
preview_vb->hide();
542
preview->set_texture(Ref<Texture2D>());
543
}
544
}
545
546
void EditorFileDialog::_request_single_thumbnail(const String &p_path) {
547
if (!FileAccess::exists(p_path) || !previews_enabled) {
548
return;
549
}
550
551
set_process(true);
552
preview_waiting = true;
553
preview_wheel_timeout = 0;
554
EditorResourcePreview::get_singleton()->queue_resource_preview(p_path, this, "_thumbnail_done", p_path);
555
}
556
557
void EditorFileDialog::_action_pressed() {
558
// Accept side menu properties and show native dialog.
559
if (side_vbox && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE) && (bool(EDITOR_GET("interface/editor/use_native_file_dialogs")) || OS::get_singleton()->is_sandboxed())) {
560
hide();
561
_native_popup();
562
563
return;
564
}
565
566
// Accept selection in the custom dialog.
567
if (mode == FILE_MODE_OPEN_FILES) {
568
String fbase = dir_access->get_current_dir();
569
570
Vector<String> files;
571
for (int i = 0; i < item_list->get_item_count(); i++) {
572
if (item_list->is_selected(i)) {
573
files.push_back(fbase.path_join(item_list->get_item_text(i)));
574
}
575
}
576
577
if (files.size()) {
578
_save_to_recent();
579
hide();
580
emit_signal(SNAME("files_selected"), files);
581
}
582
583
return;
584
}
585
586
String file_text = file->get_text();
587
String f = file_text.is_absolute_path() ? file_text : dir_access->get_current_dir().path_join(file_text);
588
589
if ((mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_FILE) && (dir_access->file_exists(f) || dir_access->is_bundle(f))) {
590
_save_to_recent();
591
hide();
592
emit_signal(SNAME("file_selected"), f);
593
} else if (mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_DIR) {
594
String path = dir_access->get_current_dir();
595
596
path = path.replace_char('\\', '/');
597
598
for (int i = 0; i < item_list->get_item_count(); i++) {
599
if (item_list->is_selected(i)) {
600
Dictionary d = item_list->get_item_metadata(i);
601
if (d["dir"]) {
602
path = path.path_join(d["name"]);
603
604
break;
605
}
606
}
607
}
608
609
_save_to_recent();
610
hide();
611
emit_signal(SNAME("dir_selected"), path);
612
}
613
614
if (mode == FILE_MODE_SAVE_FILE) {
615
bool valid = false;
616
617
if (filter->get_selected() == filter->get_item_count() - 1) {
618
valid = true; // Match none.
619
} else if (filters.size() > 1 && filter->get_selected() == 0) {
620
// Match all filters.
621
for (int i = 0; i < filters.size(); i++) {
622
String flt = filters[i].get_slicec(';', 0);
623
for (int j = 0; j < flt.get_slice_count(","); j++) {
624
String str = flt.get_slicec(',', j).strip_edges();
625
if (f.matchn(str)) {
626
valid = true;
627
break;
628
}
629
}
630
if (valid) {
631
break;
632
}
633
}
634
} else {
635
int idx = filter->get_selected();
636
if (filters.size() > 1) {
637
idx--;
638
}
639
if (idx >= 0 && idx < filters.size()) {
640
String flt = filters[idx].get_slicec(';', 0);
641
int filter_slice_count = flt.get_slice_count(",");
642
for (int j = 0; j < filter_slice_count; j++) {
643
String str = (flt.get_slicec(',', j).strip_edges());
644
if (f.matchn(str)) {
645
valid = true;
646
break;
647
}
648
}
649
650
if (!valid && filter_slice_count > 0) {
651
String str = flt.get_slicec(',', 0).strip_edges();
652
f += str.substr(1);
653
_request_single_thumbnail(get_current_dir().path_join(f.get_file()));
654
file->set_text(f.get_file());
655
valid = true;
656
}
657
} else {
658
valid = true;
659
}
660
}
661
662
// First check we're not having an empty name.
663
String file_name = file_text.strip_edges().get_file();
664
if (file_name.is_empty()) {
665
error_dialog->set_text(TTRC("Cannot save file with an empty filename."));
666
error_dialog->popup_centered(Size2(250, 80) * EDSCALE);
667
return;
668
}
669
670
// Add first extension of filter if no valid extension is found.
671
if (!valid) {
672
int idx = filter->get_selected();
673
String flt = filters[idx].get_slicec(';', 0);
674
String ext = flt.get_slicec(',', 0).strip_edges().get_extension();
675
f += "." + ext;
676
}
677
678
if (file_name.begins_with(".")) { // Could still happen if typed manually.
679
error_dialog->set_text(TTRC("Cannot save file with a name starting with a dot."));
680
error_dialog->popup_centered(Size2(250, 80) * EDSCALE);
681
return;
682
}
683
684
if (dir_access->file_exists(f) && !disable_overwrite_warning) {
685
confirm_save->set_text(vformat(TTR("File \"%s\" already exists.\nDo you want to overwrite it?"), f));
686
confirm_save->popup_centered(Size2(250, 80) * EDSCALE);
687
} else {
688
_save_to_recent();
689
hide();
690
emit_signal(SNAME("file_selected"), f);
691
}
692
}
693
}
694
695
void EditorFileDialog::_cancel_pressed() {
696
file->set_text("");
697
invalidate();
698
hide();
699
}
700
701
void EditorFileDialog::_item_selected(int p_item) {
702
int current = p_item;
703
if (current < 0 || current >= item_list->get_item_count()) {
704
return;
705
}
706
707
Dictionary d = item_list->get_item_metadata(current);
708
709
if (!d["dir"]) {
710
file->set_text(d["name"]);
711
_request_single_thumbnail(get_current_dir().path_join(get_current_file()));
712
713
if (mode != FILE_MODE_SAVE_FILE) {
714
// FILE_MODE_OPEN_ANY can alternate this text depending on what's selected.
715
set_ok_button_text(TTRC("Open"));
716
}
717
} else if (mode == FILE_MODE_OPEN_DIR || mode == FILE_MODE_OPEN_ANY) {
718
file->set_text("");
719
set_ok_button_text(TTRC("Select This Folder"));
720
}
721
722
get_ok_button()->set_disabled(_is_open_should_be_disabled());
723
}
724
725
void EditorFileDialog::_multi_selected(int p_item, bool p_selected) {
726
int current = p_item;
727
if (current < 0 || current >= item_list->get_item_count()) {
728
return;
729
}
730
731
Dictionary d = item_list->get_item_metadata(current);
732
733
if (!d["dir"] && p_selected) {
734
file->set_text(d["name"]);
735
_request_single_thumbnail(get_current_dir().path_join(get_current_file()));
736
}
737
738
get_ok_button()->set_disabled(_is_open_should_be_disabled());
739
}
740
741
void EditorFileDialog::_items_clear_selection(const Vector2 &p_pos, MouseButton p_mouse_button_index) {
742
if (p_mouse_button_index != MouseButton::LEFT) {
743
return;
744
}
745
746
item_list->deselect_all();
747
748
// If nothing is selected, then block Open button.
749
switch (mode) {
750
case FILE_MODE_OPEN_FILE:
751
case FILE_MODE_OPEN_FILES:
752
set_ok_button_text(TTRC("Open"));
753
get_ok_button()->set_disabled(!item_list->is_anything_selected());
754
break;
755
756
case FILE_MODE_OPEN_ANY:
757
case FILE_MODE_OPEN_DIR:
758
file->set_text("");
759
get_ok_button()->set_disabled(false);
760
set_ok_button_text(TTRC("Select Current Folder"));
761
break;
762
763
case FILE_MODE_SAVE_FILE:
764
// FIXME: Implement, or refactor to avoid duplication with set_mode
765
break;
766
}
767
}
768
769
void EditorFileDialog::_push_history() {
770
local_history.resize(local_history_pos + 1);
771
String new_path = dir_access->get_current_dir();
772
if (local_history.is_empty() || new_path != local_history[local_history_pos]) {
773
local_history.push_back(new_path);
774
local_history_pos++;
775
dir_prev->set_disabled(local_history_pos == 0);
776
dir_next->set_disabled(true);
777
}
778
}
779
780
void EditorFileDialog::_item_dc_selected(int p_item) {
781
int current = p_item;
782
if (current < 0 || current >= item_list->get_item_count()) {
783
return;
784
}
785
786
Dictionary d = item_list->get_item_metadata(current);
787
788
if (d["dir"]) {
789
dir_access->change_dir(d["name"]);
790
callable_mp(this, &EditorFileDialog::update_file_list).call_deferred();
791
callable_mp(this, &EditorFileDialog::update_dir).call_deferred();
792
793
_push_history();
794
795
} else {
796
_action_pressed();
797
}
798
}
799
800
void EditorFileDialog::_item_list_item_rmb_clicked(int p_item, const Vector2 &p_pos, MouseButton p_mouse_button_index) {
801
if (p_mouse_button_index != MouseButton::RIGHT) {
802
return;
803
}
804
805
// Right click on specific file(s) or folder(s).
806
item_menu->clear();
807
item_menu->reset_size();
808
809
// Allow specific actions only on one item.
810
bool single_item_selected = item_list->get_selected_items().size() == 1;
811
812
// Disallow deleting the .import folder, Godot kills a cat if you do and it is possibly a senseless novice action.
813
bool allow_delete = true;
814
for (int i = 0; i < item_list->get_item_count(); i++) {
815
if (!item_list->is_selected(i)) {
816
continue;
817
}
818
Dictionary item_meta = item_list->get_item_metadata(i);
819
if (String(item_meta["path"]).begins_with(ProjectSettings::get_singleton()->get_project_data_path())) {
820
allow_delete = false;
821
break;
822
}
823
}
824
825
if (single_item_selected) {
826
item_menu->add_icon_item(theme_cache.action_copy, TTRC("Copy Path"), ITEM_MENU_COPY_PATH);
827
}
828
if (allow_delete) {
829
item_menu->add_icon_item(theme_cache.action_delete, TTRC("Delete"), ITEM_MENU_DELETE, Key::KEY_DELETE);
830
}
831
832
#if !defined(ANDROID_ENABLED) && !defined(WEB_ENABLED)
833
// Opening the system file manager is not supported on the Android and web editors.
834
if (single_item_selected) {
835
item_menu->add_separator();
836
Dictionary item_meta = item_list->get_item_metadata(p_item);
837
String item_text = item_meta["dir"] ? TTRC("Open in File Manager") : TTRC("Show in File Manager");
838
item_menu->add_icon_item(theme_cache.filesystem, item_text, ITEM_MENU_SHOW_IN_EXPLORER);
839
}
840
#endif
841
if (single_item_selected) {
842
Dictionary item_meta = item_list->get_item_metadata(p_item);
843
if (item_meta["bundle"]) {
844
item_menu->add_icon_item(theme_cache.open_folder, TTRC("Show Package Contents"), ITEM_MENU_SHOW_BUNDLE_CONTENT);
845
}
846
}
847
848
if (item_menu->get_item_count() > 0) {
849
item_menu->set_position(item_list->get_screen_position() + p_pos);
850
item_menu->reset_size();
851
item_menu->popup();
852
}
853
}
854
855
void EditorFileDialog::_item_list_empty_clicked(const Vector2 &p_pos, MouseButton p_mouse_button_index) {
856
if (p_mouse_button_index != MouseButton::RIGHT && p_mouse_button_index != MouseButton::LEFT) {
857
return;
858
}
859
860
// Left or right click on folder background. Deselect all files so that actions are applied on the current folder.
861
for (int i = 0; i < item_list->get_item_count(); i++) {
862
item_list->deselect(i);
863
}
864
865
if (p_mouse_button_index != MouseButton::RIGHT) {
866
return;
867
}
868
869
item_menu->clear();
870
item_menu->reset_size();
871
872
if (can_create_dir) {
873
item_menu->add_icon_item(theme_cache.folder, TTRC("New Folder..."), ITEM_MENU_NEW_FOLDER, KeyModifierMask::CMD_OR_CTRL | Key::N);
874
}
875
item_menu->add_icon_item(theme_cache.reload, TTRC("Refresh"), ITEM_MENU_REFRESH, Key::F5);
876
#if !defined(ANDROID_ENABLED) && !defined(WEB_ENABLED)
877
// Opening the system file manager is not supported on the Android and web editors.
878
item_menu->add_separator();
879
item_menu->add_icon_item(theme_cache.filesystem, TTRC("Open in File Manager"), ITEM_MENU_SHOW_IN_EXPLORER);
880
#endif
881
882
item_menu->set_position(item_list->get_screen_position() + p_pos);
883
item_menu->reset_size();
884
item_menu->popup();
885
}
886
887
void EditorFileDialog::_item_menu_id_pressed(int p_option) {
888
switch (p_option) {
889
case ITEM_MENU_COPY_PATH: {
890
Dictionary item_meta = item_list->get_item_metadata(item_list->get_current());
891
DisplayServer::get_singleton()->clipboard_set(item_meta["path"]);
892
} break;
893
894
case ITEM_MENU_DELETE: {
895
_delete_items();
896
} break;
897
898
case ITEM_MENU_REFRESH: {
899
invalidate();
900
} break;
901
902
case ITEM_MENU_NEW_FOLDER: {
903
_make_dir();
904
} break;
905
906
case ITEM_MENU_SHOW_IN_EXPLORER: {
907
String path;
908
int idx = item_list->get_current();
909
if (idx == -1 || !item_list->is_anything_selected()) {
910
// Folder background was clicked. Open this folder.
911
path = ProjectSettings::get_singleton()->globalize_path(dir_access->get_current_dir());
912
} else {
913
// Specific item was clicked. Open folders directly, or the folder containing a selected file.
914
Dictionary item_meta = item_list->get_item_metadata(idx);
915
path = ProjectSettings::get_singleton()->globalize_path(item_meta["path"]);
916
}
917
OS::get_singleton()->shell_show_in_file_manager(path, true);
918
} break;
919
920
case ITEM_MENU_SHOW_BUNDLE_CONTENT: {
921
String path;
922
int idx = item_list->get_current();
923
if (idx == -1 || !item_list->is_anything_selected()) {
924
return;
925
}
926
Dictionary item_meta = item_list->get_item_metadata(idx);
927
dir_access->change_dir(item_meta["path"]);
928
callable_mp(this, &EditorFileDialog::update_file_list).call_deferred();
929
callable_mp(this, &EditorFileDialog::update_dir).call_deferred();
930
931
_push_history();
932
} break;
933
}
934
}
935
936
bool EditorFileDialog::_is_open_should_be_disabled() {
937
if (mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_SAVE_FILE) {
938
return false;
939
}
940
941
Vector<int> items = item_list->get_selected_items();
942
if (items.is_empty()) {
943
return mode != FILE_MODE_OPEN_DIR; // In "Open folder" mode, having nothing selected picks the current folder.
944
}
945
946
for (int i = 0; i < items.size(); i++) {
947
Dictionary d = item_list->get_item_metadata(items.get(i));
948
949
if (((mode == FILE_MODE_OPEN_FILE || mode == FILE_MODE_OPEN_FILES) && d["dir"]) || (mode == FILE_MODE_OPEN_DIR && !d["dir"])) {
950
return true;
951
}
952
}
953
954
return false;
955
}
956
957
void EditorFileDialog::update_file_name() {
958
int idx = filter->get_selected() - 1;
959
if ((idx == -1 && filter->get_item_count() == 2) || (filter->get_item_count() > 2 && idx >= 0 && idx < filter->get_item_count() - 2)) {
960
if (idx == -1) {
961
idx += 1;
962
}
963
String filter_str = filters[idx];
964
String file_str = file->get_text();
965
String base_name = file_str.get_basename();
966
Vector<String> filter_substr = filter_str.split(";");
967
if (filter_substr.size() >= 2) {
968
file_str = base_name + "." + filter_substr[0].strip_edges().get_extension().to_lower();
969
} else {
970
file_str = base_name + "." + filter_str.strip_edges().get_extension().to_lower();
971
}
972
file->set_text(file_str);
973
}
974
}
975
976
// DO NOT USE THIS FUNCTION UNLESS NEEDED, CALL INVALIDATE() INSTEAD.
977
void EditorFileDialog::update_file_list() {
978
int thumbnail_size = EDITOR_GET("filesystem/file_dialog/thumbnail_size");
979
thumbnail_size *= EDSCALE;
980
Ref<Texture2D> folder_thumbnail;
981
Ref<Texture2D> file_thumbnail;
982
983
item_list->clear();
984
985
// Scroll back to the top after opening a directory
986
item_list->get_v_scroll_bar()->set_value(0);
987
988
if (display_mode == DISPLAY_THUMBNAILS) {
989
item_list->set_max_columns(0);
990
item_list->set_icon_mode(ItemList::ICON_MODE_TOP);
991
item_list->set_fixed_column_width(thumbnail_size * 3 / 2);
992
item_list->set_max_text_lines(2);
993
item_list->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
994
item_list->set_fixed_icon_size(Size2(thumbnail_size, thumbnail_size));
995
996
if (thumbnail_size < 64) {
997
folder_thumbnail = theme_cache.folder_medium_thumbnail;
998
file_thumbnail = theme_cache.file_medium_thumbnail;
999
} else {
1000
folder_thumbnail = theme_cache.folder_big_thumbnail;
1001
file_thumbnail = theme_cache.file_big_thumbnail;
1002
}
1003
1004
preview_vb->hide();
1005
1006
} else {
1007
item_list->set_icon_mode(ItemList::ICON_MODE_LEFT);
1008
item_list->set_max_columns(1);
1009
item_list->set_max_text_lines(1);
1010
item_list->set_fixed_column_width(0);
1011
item_list->set_fixed_icon_size(Size2());
1012
if (preview->get_texture().is_valid()) {
1013
preview_vb->show();
1014
}
1015
}
1016
1017
String cdir = dir_access->get_current_dir();
1018
1019
dir_access->list_dir_begin();
1020
1021
List<FileInfo> file_infos;
1022
List<String> dirs;
1023
1024
String item = dir_access->get_next();
1025
1026
while (!item.is_empty()) {
1027
if (item == "." || item == "..") {
1028
item = dir_access->get_next();
1029
continue;
1030
}
1031
1032
bool matches_search = true;
1033
if (search_string.length() > 0) {
1034
matches_search = item.find(search_string) != -1;
1035
}
1036
1037
FileInfo file_info;
1038
file_info.name = item;
1039
file_info.path = cdir.path_join(file_info.name);
1040
file_info.type = item.get_extension();
1041
file_info.modified_time = FileAccess::get_modified_time(file_info.path);
1042
1043
if (matches_search) {
1044
if (show_hidden_files) {
1045
if (!dir_access->current_is_dir()) {
1046
file_infos.push_back(file_info);
1047
} else {
1048
dirs.push_back(file_info.name);
1049
}
1050
} else if (!dir_access->current_is_hidden()) {
1051
String full_path = cdir == "res://" ? file_info.name : dir_access->get_current_dir() + "/" + file_info.name;
1052
if (dir_access->current_is_dir()) {
1053
if (Engine::get_singleton()->is_project_manager_hint() || !EditorFileSystem::_should_skip_directory(full_path)) {
1054
dirs.push_back(file_info.name);
1055
}
1056
} else {
1057
file_infos.push_back(file_info);
1058
}
1059
}
1060
}
1061
item = dir_access->get_next();
1062
}
1063
1064
dirs.sort_custom<FileNoCaseComparator>();
1065
bool reverse_directories = file_sort == FileSortOption::FILE_SORT_NAME_REVERSE;
1066
if (reverse_directories) {
1067
dirs.reverse();
1068
}
1069
sort_file_info_list(file_infos, file_sort);
1070
1071
List<String> patterns;
1072
// build filter
1073
if (filter->get_selected() == filter->get_item_count() - 1) {
1074
// match all
1075
} else if (filters.size() > 1 && filter->get_selected() == 0) {
1076
// match all filters
1077
for (int i = 0; i < filters.size(); i++) {
1078
String f = filters[i].get_slicec(';', 0);
1079
for (int j = 0; j < f.get_slice_count(","); j++) {
1080
patterns.push_back(f.get_slicec(',', j).strip_edges());
1081
}
1082
}
1083
} else {
1084
int idx = filter->get_selected();
1085
if (filters.size() > 1) {
1086
idx--;
1087
}
1088
1089
if (idx >= 0 && idx < filters.size()) {
1090
String f = filters[idx].get_slicec(';', 0);
1091
for (int j = 0; j < f.get_slice_count(","); j++) {
1092
patterns.push_back(f.get_slicec(',', j).strip_edges());
1093
}
1094
}
1095
}
1096
1097
while (!dirs.is_empty()) {
1098
const String &dir_name = dirs.front()->get();
1099
1100
bool bundle = dir_access->is_bundle(dir_name);
1101
bool found = true;
1102
if (bundle) {
1103
bool match = patterns.is_empty();
1104
for (const String &E : patterns) {
1105
if (dir_name.matchn(E)) {
1106
match = true;
1107
break;
1108
}
1109
}
1110
found = match;
1111
}
1112
1113
if (found) {
1114
item_list->add_item(dir_name);
1115
1116
if (display_mode == DISPLAY_THUMBNAILS) {
1117
item_list->set_item_icon(-1, folder_thumbnail);
1118
} else {
1119
item_list->set_item_icon(-1, theme_cache.folder);
1120
}
1121
1122
Dictionary d;
1123
d["name"] = dir_name;
1124
d["path"] = cdir.path_join(dir_name);
1125
d["dir"] = !bundle;
1126
d["bundle"] = bundle;
1127
1128
item_list->set_item_metadata(-1, d);
1129
item_list->set_item_icon_modulate(-1, FileSystemDock::get_dir_icon_color(String(d["path"]), theme_cache.folder_icon_color));
1130
}
1131
1132
dirs.pop_front();
1133
}
1134
1135
while (!file_infos.is_empty()) {
1136
bool match = patterns.is_empty();
1137
1138
FileInfo file_info = file_infos.front()->get();
1139
for (const String &E : patterns) {
1140
if (file_info.name.matchn(E)) {
1141
match = true;
1142
break;
1143
}
1144
}
1145
1146
if (match) {
1147
item_list->add_item(file_info.name);
1148
1149
if (get_icon_func) {
1150
Ref<Texture2D> icon = get_icon_func(file_info.path);
1151
if (display_mode == DISPLAY_THUMBNAILS) {
1152
Ref<Texture2D> thumbnail;
1153
if (get_thumbnail_func) {
1154
thumbnail = get_thumbnail_func(file_info.path);
1155
}
1156
if (thumbnail.is_null()) {
1157
thumbnail = file_thumbnail;
1158
}
1159
1160
item_list->set_item_icon(-1, thumbnail);
1161
item_list->set_item_tag_icon(-1, icon);
1162
} else {
1163
item_list->set_item_icon(-1, icon);
1164
}
1165
}
1166
1167
Dictionary d;
1168
d["name"] = file_info.name;
1169
d["dir"] = false;
1170
d["bundle"] = false;
1171
d["path"] = file_info.path;
1172
item_list->set_item_metadata(-1, d);
1173
1174
if (display_mode == DISPLAY_THUMBNAILS && previews_enabled) {
1175
EditorResourcePreview::get_singleton()->queue_resource_preview(file_info.path, this, "_thumbnail_result", file_info.path);
1176
}
1177
1178
if (file->get_text() == file_info.name) {
1179
item_list->set_current(item_list->get_item_count() - 1);
1180
}
1181
}
1182
1183
file_infos.pop_front();
1184
}
1185
1186
if (favorites->get_current() >= 0) {
1187
favorites->deselect(favorites->get_current());
1188
}
1189
1190
favorite->set_pressed(false);
1191
fav_up->set_disabled(true);
1192
fav_down->set_disabled(true);
1193
get_ok_button()->set_disabled(_is_open_should_be_disabled());
1194
for (int i = 0; i < favorites->get_item_count(); i++) {
1195
if (favorites->get_item_metadata(i) == cdir || favorites->get_item_metadata(i) == cdir + "/") {
1196
favorites->select(i);
1197
favorite->set_pressed(true);
1198
if (i > 0) {
1199
fav_up->set_disabled(false);
1200
}
1201
if (i < favorites->get_item_count() - 1) {
1202
fav_down->set_disabled(false);
1203
}
1204
break;
1205
}
1206
}
1207
}
1208
1209
void EditorFileDialog::_filter_selected(int) {
1210
update_file_name();
1211
update_file_list();
1212
}
1213
1214
void EditorFileDialog::_search_filter_selected() {
1215
Vector<int> items = item_list->get_selected_items();
1216
if (!items.is_empty()) {
1217
int index = items[0];
1218
file->set_text(item_list->get_item_text(index));
1219
file->emit_signal(SceneStringName(text_submitted), file->get_text());
1220
}
1221
}
1222
1223
void EditorFileDialog::update_filters() {
1224
filter->clear();
1225
processed_filters.clear();
1226
1227
if (filters.size() > 1) {
1228
String all_filters;
1229
String all_mime;
1230
String all_filters_full;
1231
String all_mime_full;
1232
1233
const int max_filters = 5;
1234
1235
// "All Recognized" display name.
1236
for (int i = 0; i < MIN(max_filters, filters.size()); i++) {
1237
String flt = filters[i].get_slicec(';', 0).strip_edges();
1238
if (!all_filters.is_empty() && !flt.is_empty()) {
1239
all_filters += ", ";
1240
}
1241
all_filters += flt;
1242
1243
String mime = filters[i].get_slicec(';', 2).strip_edges();
1244
if (!all_mime.is_empty() && !mime.is_empty()) {
1245
all_mime += ", ";
1246
}
1247
all_mime += mime;
1248
}
1249
1250
// "All Recognized" filter.
1251
for (int i = 0; i < filters.size(); i++) {
1252
String flt = filters[i].get_slicec(';', 0).strip_edges();
1253
if (!all_filters_full.is_empty() && !flt.is_empty()) {
1254
all_filters_full += ",";
1255
}
1256
all_filters_full += flt;
1257
1258
String mime = filters[i].get_slicec(';', 2).strip_edges();
1259
if (!all_mime_full.is_empty() && !mime.is_empty()) {
1260
all_mime_full += ",";
1261
}
1262
all_mime_full += mime;
1263
}
1264
1265
String native_all_name;
1266
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE_MIME)) {
1267
native_all_name += all_filters;
1268
}
1269
if (!native_all_name.is_empty()) {
1270
native_all_name += ", ";
1271
}
1272
native_all_name += all_mime;
1273
1274
if (max_filters < filters.size()) {
1275
all_filters += ", ...";
1276
native_all_name += ", ...";
1277
}
1278
1279
filter->add_item(atr(ETR("All Recognized")) + " (" + all_filters + ")");
1280
processed_filters.push_back(all_filters_full + ";" + atr(ETR("All Recognized")) + " (" + native_all_name + ")" + ";" + all_mime_full);
1281
}
1282
for (int i = 0; i < filters.size(); i++) {
1283
String flt = filters[i].get_slicec(';', 0).strip_edges();
1284
String desc = filters[i].get_slicec(';', 1).strip_edges();
1285
String mime = filters[i].get_slicec(';', 2).strip_edges();
1286
String native_name;
1287
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE_MIME)) {
1288
native_name += flt;
1289
}
1290
if (!native_name.is_empty() && !mime.is_empty()) {
1291
native_name += ", ";
1292
}
1293
native_name += mime;
1294
if (!desc.is_empty()) {
1295
filter->add_item(atr(desc) + " (" + flt + ")");
1296
processed_filters.push_back(flt + ";" + atr(desc) + " (" + native_name + ");" + mime);
1297
} else {
1298
filter->add_item("(" + flt + ")");
1299
processed_filters.push_back(flt + ";(" + native_name + ");" + mime);
1300
}
1301
}
1302
1303
String f = TTR("All Files") + " (*.*)";
1304
filter->add_item(f);
1305
processed_filters.push_back("*.*;" + f + ";application/octet-stream");
1306
}
1307
1308
void EditorFileDialog::clear_filters() {
1309
filters.clear();
1310
update_filters();
1311
invalidate();
1312
}
1313
1314
void EditorFileDialog::clear_search_filter() {
1315
set_search_filter("");
1316
update_search_filter_gui();
1317
invalidate();
1318
}
1319
1320
void EditorFileDialog::update_search_filter_gui() {
1321
filter_hb->set_visible(show_search_filter);
1322
if (!show_search_filter) {
1323
search_string.clear();
1324
}
1325
if (filter_box->get_text() == search_string) {
1326
return;
1327
}
1328
filter_box->set_text(search_string);
1329
}
1330
1331
void EditorFileDialog::add_filter(const String &p_filter, const String &p_description) {
1332
if (p_description.is_empty()) {
1333
filters.push_back(p_filter);
1334
} else {
1335
filters.push_back(vformat("%s ; %s", p_filter, p_description));
1336
}
1337
update_filters();
1338
invalidate();
1339
}
1340
1341
void EditorFileDialog::set_filters(const Vector<String> &p_filters) {
1342
if (filters == p_filters) {
1343
return;
1344
}
1345
filters = p_filters;
1346
update_filters();
1347
invalidate();
1348
}
1349
1350
void EditorFileDialog::set_search_filter(const String &p_search_filter) {
1351
if (search_string == p_search_filter) {
1352
return;
1353
}
1354
search_string = p_search_filter;
1355
update_search_filter_gui();
1356
emit_signal(SNAME("filename_filter_changed"), filter);
1357
invalidate();
1358
}
1359
1360
Vector<String> EditorFileDialog::get_filters() const {
1361
return filters;
1362
}
1363
1364
String EditorFileDialog::get_search_filter() const {
1365
return search_string;
1366
}
1367
1368
String EditorFileDialog::get_current_dir() const {
1369
return dir_access->get_current_dir();
1370
}
1371
1372
String EditorFileDialog::get_current_file() const {
1373
return file->get_text();
1374
}
1375
1376
String EditorFileDialog::get_current_path() const {
1377
return dir_access->get_current_dir().path_join(file->get_text());
1378
}
1379
1380
void EditorFileDialog::set_current_dir(const String &p_dir) {
1381
if (p_dir.is_relative_path()) {
1382
dir_access->change_dir(OS::get_singleton()->get_resource_dir());
1383
}
1384
dir_access->change_dir(p_dir);
1385
update_dir();
1386
invalidate();
1387
}
1388
1389
void EditorFileDialog::set_current_file(const String &p_file) {
1390
file->set_text(p_file);
1391
update_dir();
1392
invalidate();
1393
1394
if (is_visible()) {
1395
_focus_file_text();
1396
_request_single_thumbnail(get_current_dir().path_join(get_current_file()));
1397
}
1398
}
1399
1400
void EditorFileDialog::set_current_path(const String &p_path) {
1401
if (!p_path.size()) {
1402
return;
1403
}
1404
int pos = MAX(p_path.rfind_char('/'), p_path.rfind_char('\\'));
1405
if (pos == -1) {
1406
set_current_file(p_path);
1407
} else {
1408
String path_dir = p_path.substr(0, pos);
1409
String path_file = p_path.substr(pos + 1);
1410
set_current_dir(path_dir);
1411
set_current_file(path_file);
1412
}
1413
}
1414
1415
void EditorFileDialog::set_file_mode(FileMode p_mode) {
1416
mode = p_mode;
1417
switch (mode) {
1418
case FILE_MODE_OPEN_FILE:
1419
set_ok_button_text(TTRC("Open"));
1420
set_title(TTRC("Open a File"));
1421
can_create_dir = false;
1422
break;
1423
case FILE_MODE_OPEN_FILES:
1424
set_ok_button_text(TTRC("Open"));
1425
set_title(TTRC("Open File(s)"));
1426
can_create_dir = false;
1427
break;
1428
case FILE_MODE_OPEN_DIR:
1429
set_ok_button_text(TTRC("Open"));
1430
set_title(TTRC("Open a Directory"));
1431
can_create_dir = true;
1432
break;
1433
case FILE_MODE_OPEN_ANY:
1434
set_ok_button_text(TTRC("Open"));
1435
set_title(TTRC("Open a File or Directory"));
1436
can_create_dir = true;
1437
break;
1438
case FILE_MODE_SAVE_FILE:
1439
set_ok_button_text(TTRC("Save"));
1440
set_title(TTRC("Save a File"));
1441
can_create_dir = true;
1442
break;
1443
}
1444
1445
if (mode == FILE_MODE_OPEN_FILES) {
1446
item_list->set_select_mode(ItemList::SELECT_MULTI);
1447
} else {
1448
item_list->set_select_mode(ItemList::SELECT_SINGLE);
1449
}
1450
1451
makedir_sep->set_visible(can_create_dir);
1452
makedir->set_visible(can_create_dir);
1453
}
1454
1455
EditorFileDialog::FileMode EditorFileDialog::get_file_mode() const {
1456
return mode;
1457
}
1458
1459
void EditorFileDialog::set_access(Access p_access) {
1460
ERR_FAIL_INDEX(p_access, 3);
1461
if (access == p_access) {
1462
return;
1463
}
1464
switch (p_access) {
1465
case ACCESS_FILESYSTEM: {
1466
dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
1467
} break;
1468
case ACCESS_RESOURCES: {
1469
dir_access = DirAccess::create(DirAccess::ACCESS_RESOURCES);
1470
} break;
1471
case ACCESS_USERDATA: {
1472
dir_access = DirAccess::create(DirAccess::ACCESS_USERDATA);
1473
} break;
1474
}
1475
access = p_access;
1476
_update_drives();
1477
invalidate();
1478
update_filters();
1479
update_dir();
1480
}
1481
1482
void EditorFileDialog::invalidate() {
1483
if (!is_visible() || is_invalidating) {
1484
return;
1485
}
1486
1487
is_invalidating = true;
1488
callable_mp(this, &EditorFileDialog::_invalidate).call_deferred();
1489
}
1490
1491
void EditorFileDialog::_invalidate() {
1492
if (!is_invalidating) {
1493
return;
1494
}
1495
1496
update_file_list();
1497
_update_favorites();
1498
_update_recent();
1499
1500
is_invalidating = false;
1501
}
1502
1503
EditorFileDialog::Access EditorFileDialog::get_access() const {
1504
return access;
1505
}
1506
1507
void EditorFileDialog::_make_dir_confirm() {
1508
const String stripped_dirname = makedirname->get_text().strip_edges();
1509
1510
if (stripped_dirname.is_empty()) {
1511
error_dialog->set_text(TTRC("The path specified is invalid."));
1512
error_dialog->popup_centered(Size2(250, 50) * EDSCALE);
1513
makedirname->set_text(""); // Reset label.
1514
return;
1515
}
1516
1517
if (dir_access->dir_exists(stripped_dirname)) {
1518
error_dialog->set_text(TTRC("Could not create folder. File with that name already exists."));
1519
error_dialog->popup_centered(Size2(250, 50) * EDSCALE);
1520
makedirname->set_text(""); // Reset label.
1521
return;
1522
}
1523
1524
Error err = dir_access->make_dir(stripped_dirname);
1525
if (err == OK) {
1526
dir_access->change_dir(stripped_dirname);
1527
invalidate();
1528
update_filters();
1529
update_dir();
1530
_push_history();
1531
if (access != ACCESS_FILESYSTEM) {
1532
EditorFileSystem::get_singleton()->scan_changes(); //we created a dir, so rescan changes
1533
}
1534
} else {
1535
error_dialog->set_text(TTRC("Could not create folder."));
1536
error_dialog->popup_centered(Size2(250, 50) * EDSCALE);
1537
}
1538
makedirname->set_text(""); // reset label
1539
}
1540
1541
void EditorFileDialog::_make_dir() {
1542
makedialog->popup_centered(Size2(250, 80) * EDSCALE);
1543
makedirname->grab_focus();
1544
}
1545
1546
void EditorFileDialog::_focus_filter_box() {
1547
filter_box->grab_focus();
1548
filter_box->select_all();
1549
}
1550
1551
void EditorFileDialog::_filter_changed(const String &p_text) {
1552
search_string = p_text;
1553
invalidate();
1554
1555
if (item_list->get_selected_items().size() > 0) {
1556
item_list->deselect_all();
1557
if (item_list->get_item_count() > 0) {
1558
item_list->call_deferred("select", 0);
1559
}
1560
}
1561
}
1562
1563
void EditorFileDialog::_file_sort_popup(int p_id) {
1564
for (int i = 0; i != static_cast<int>(FileSortOption::FILE_SORT_MAX); i++) {
1565
file_sort_button->get_popup()->set_item_checked(i, (i == p_id));
1566
}
1567
file_sort = static_cast<FileSortOption>(p_id);
1568
invalidate();
1569
}
1570
1571
void EditorFileDialog::_delete_items() {
1572
// Collect the selected folders and files to delete and check them in the deletion dependency dialog.
1573
Vector<String> folders;
1574
Vector<String> files;
1575
for (int i = 0; i < item_list->get_item_count(); i++) {
1576
if (!item_list->is_selected(i)) {
1577
continue;
1578
}
1579
Dictionary item_meta = item_list->get_item_metadata(i);
1580
if (item_meta["dir"]) {
1581
folders.push_back(item_meta["path"]);
1582
} else {
1583
files.push_back(item_meta["path"]);
1584
}
1585
}
1586
if (folders.size() + files.size() > 0) {
1587
if (access == ACCESS_FILESYSTEM) {
1588
global_remove_dialog->popup_centered();
1589
} else {
1590
dep_remove_dialog->reset_size();
1591
dep_remove_dialog->show(folders, files);
1592
}
1593
}
1594
}
1595
1596
void EditorFileDialog::_delete_files_global() {
1597
// Delete files outside of the project directory without dependency checks.
1598
for (int i = 0; i < item_list->get_item_count(); i++) {
1599
if (!item_list->is_selected(i)) {
1600
continue;
1601
}
1602
Dictionary item_meta = item_list->get_item_metadata(i);
1603
// Only delete empty directories for safety.
1604
dir_access->remove(item_meta["path"]);
1605
}
1606
update_file_list();
1607
}
1608
1609
void EditorFileDialog::_select_drive(int p_idx) {
1610
String d = drives->get_item_text(p_idx);
1611
dir_access->change_dir(d);
1612
file->set_text("");
1613
invalidate();
1614
update_dir();
1615
_push_history();
1616
}
1617
1618
void EditorFileDialog::_update_drives(bool p_select) {
1619
int dc = dir_access->get_drive_count();
1620
if (dc == 0 || access != ACCESS_FILESYSTEM) {
1621
shortcuts_container->hide();
1622
drives_container->hide();
1623
drives->hide();
1624
} else {
1625
drives->clear();
1626
Node *dp = drives->get_parent();
1627
if (dp) {
1628
dp->remove_child(drives);
1629
}
1630
dp = dir_access->drives_are_shortcuts() ? shortcuts_container : drives_container;
1631
shortcuts_container->set_visible(dir_access->drives_are_shortcuts());
1632
drives_container->set_visible(!dir_access->drives_are_shortcuts());
1633
dp->add_child(drives);
1634
drives->show();
1635
1636
for (int i = 0; i < dir_access->get_drive_count(); i++) {
1637
String d = dir_access->get_drive(i);
1638
drives->add_item(dir_access->get_drive(i));
1639
}
1640
if (p_select) {
1641
drives->select(dir_access->get_current_drive());
1642
}
1643
}
1644
}
1645
1646
void EditorFileDialog::_update_icons() {
1647
// Update icons.
1648
1649
mode_thumbnails->set_button_icon(theme_cache.mode_thumbnails);
1650
mode_list->set_button_icon(theme_cache.mode_list);
1651
1652
if (is_layout_rtl()) {
1653
dir_prev->set_button_icon(theme_cache.forward_folder);
1654
dir_next->set_button_icon(theme_cache.back_folder);
1655
} else {
1656
dir_prev->set_button_icon(theme_cache.back_folder);
1657
dir_next->set_button_icon(theme_cache.forward_folder);
1658
}
1659
dir_up->set_button_icon(theme_cache.parent_folder);
1660
1661
refresh->set_button_icon(theme_cache.reload);
1662
favorite->set_button_icon(theme_cache.favorite);
1663
show_hidden->set_button_icon(theme_cache.toggle_hidden);
1664
makedir->set_button_icon(theme_cache.create_folder);
1665
1666
filter_box->set_right_icon(theme_cache.filter_box);
1667
file_sort_button->set_button_icon(theme_cache.file_sort_button);
1668
show_search_filter_button->set_button_icon(theme_cache.toggle_filename_filter);
1669
filter_box->set_clear_button_enabled(true);
1670
1671
fav_up->set_button_icon(theme_cache.favorites_up);
1672
fav_down->set_button_icon(theme_cache.favorites_down);
1673
}
1674
1675
void EditorFileDialog::_favorite_selected(int p_idx) {
1676
Error change_dir_result = dir_access->change_dir(favorites->get_item_metadata(p_idx));
1677
if (change_dir_result != OK) {
1678
error_dialog->set_text(TTRC("Favorited folder does not exist anymore and will be removed."));
1679
error_dialog->popup_centered(Size2(250, 50) * EDSCALE);
1680
1681
bool res = (access == ACCESS_RESOURCES);
1682
1683
Vector<String> favorited = EditorSettings::get_singleton()->get_favorites();
1684
String dir_to_remove = favorites->get_item_metadata(p_idx);
1685
1686
bool found = false;
1687
for (int i = 0; i < favorited.size(); i++) {
1688
bool cres = favorited[i].begins_with("res://");
1689
if (cres != res) {
1690
continue;
1691
}
1692
1693
if (favorited[i] == dir_to_remove) {
1694
found = true;
1695
break;
1696
}
1697
}
1698
1699
if (found) {
1700
favorited.erase(favorites->get_item_metadata(p_idx));
1701
favorites->remove_item(p_idx);
1702
EditorSettings::get_singleton()->set_favorites(favorited);
1703
}
1704
} else {
1705
update_dir();
1706
invalidate();
1707
_push_history();
1708
}
1709
}
1710
1711
void EditorFileDialog::_favorite_move_up() {
1712
int current = favorites->get_current();
1713
1714
if (current > 0 && current < favorites->get_item_count()) {
1715
Vector<String> favorited = EditorSettings::get_singleton()->get_favorites();
1716
1717
int a_idx = favorited.find(String(favorites->get_item_metadata(current - 1)));
1718
int b_idx = favorited.find(String(favorites->get_item_metadata(current)));
1719
1720
if (a_idx == -1 || b_idx == -1) {
1721
return;
1722
}
1723
SWAP(favorited.write[a_idx], favorited.write[b_idx]);
1724
1725
EditorSettings::get_singleton()->set_favorites(favorited);
1726
1727
_update_favorites();
1728
update_file_list();
1729
}
1730
}
1731
1732
void EditorFileDialog::_favorite_move_down() {
1733
int current = favorites->get_current();
1734
1735
if (current >= 0 && current < favorites->get_item_count() - 1) {
1736
Vector<String> favorited = EditorSettings::get_singleton()->get_favorites();
1737
1738
int a_idx = favorited.find(String(favorites->get_item_metadata(current + 1)));
1739
int b_idx = favorited.find(String(favorites->get_item_metadata(current)));
1740
1741
if (a_idx == -1 || b_idx == -1) {
1742
return;
1743
}
1744
SWAP(favorited.write[a_idx], favorited.write[b_idx]);
1745
1746
EditorSettings::get_singleton()->set_favorites(favorited);
1747
1748
_update_favorites();
1749
update_file_list();
1750
}
1751
}
1752
1753
void EditorFileDialog::_update_favorites() {
1754
bool access_resources = (access == ACCESS_RESOURCES);
1755
1756
String current = get_current_dir();
1757
favorites->clear();
1758
1759
favorite->set_pressed(false);
1760
1761
Vector<String> favorited = EditorSettings::get_singleton()->get_favorites();
1762
Vector<String> favorited_paths;
1763
Vector<String> favorited_names;
1764
1765
bool fav_changed = false;
1766
int current_favorite = -1;
1767
for (int i = 0; i < favorited.size(); i++) {
1768
String name = favorited[i];
1769
1770
if (access_resources != name.begins_with("res://")) {
1771
continue;
1772
}
1773
1774
if (!name.ends_with("/")) {
1775
continue;
1776
}
1777
1778
if (!dir_access->dir_exists(name)) {
1779
// Remove invalid directory from the list of Favorited directories.
1780
favorited.remove_at(i--);
1781
fav_changed = true;
1782
continue;
1783
}
1784
1785
// Compute favorite display text.
1786
if (access_resources && name == "res://") {
1787
if (name == current) {
1788
current_favorite = favorited_paths.size();
1789
}
1790
name = "/";
1791
favorited_paths.append(favorited[i]);
1792
favorited_names.append(name);
1793
} else {
1794
if (name == current || name == current + "/") {
1795
current_favorite = favorited_paths.size();
1796
}
1797
name = name.trim_suffix("/");
1798
name = name.get_file();
1799
favorited_paths.append(favorited[i]);
1800
favorited_names.append(name);
1801
}
1802
}
1803
1804
if (fav_changed) {
1805
EditorSettings::get_singleton()->set_favorites(favorited);
1806
}
1807
1808
EditorNode::disambiguate_filenames(favorited_paths, favorited_names);
1809
1810
for (int i = 0; i < favorited_paths.size(); i++) {
1811
favorites->add_item(favorited_names[i], theme_cache.folder);
1812
favorites->set_item_tooltip(-1, favorited_paths[i]);
1813
favorites->set_item_metadata(-1, favorited_paths[i]);
1814
favorites->set_item_icon_modulate(-1, FileSystemDock::get_dir_icon_color(favorited_paths[i], theme_cache.folder_icon_color));
1815
1816
if (i == current_favorite) {
1817
favorite->set_pressed(true);
1818
favorites->set_current(favorites->get_item_count() - 1);
1819
recent->deselect_all();
1820
}
1821
}
1822
1823
fav_up->set_disabled(current_favorite < 1);
1824
fav_down->set_disabled(current_favorite == -1 || favorited_paths.size() - 1 <= current_favorite);
1825
}
1826
1827
void EditorFileDialog::_favorite_pressed() {
1828
bool access_resources = (access == ACCESS_RESOURCES);
1829
1830
String cd = get_current_dir();
1831
if (!cd.ends_with("/")) {
1832
cd += "/";
1833
}
1834
1835
Vector<String> favorited = EditorSettings::get_singleton()->get_favorites();
1836
1837
bool found = false;
1838
for (const String &name : favorited) {
1839
if (access_resources != name.begins_with("res://")) {
1840
continue;
1841
}
1842
1843
if (name == cd) {
1844
found = true;
1845
break;
1846
}
1847
}
1848
1849
if (found) {
1850
favorited.erase(cd);
1851
} else {
1852
favorited.push_back(cd);
1853
}
1854
1855
EditorSettings::get_singleton()->set_favorites(favorited);
1856
1857
_update_favorites();
1858
}
1859
1860
void EditorFileDialog::_update_recent() {
1861
recent->clear();
1862
1863
bool access_resources = (access == ACCESS_RESOURCES);
1864
Vector<String> recentd = EditorSettings::get_singleton()->get_recent_dirs();
1865
Vector<String> recentd_paths;
1866
Vector<String> recentd_names;
1867
bool modified = false;
1868
1869
for (int i = 0; i < recentd.size(); i++) {
1870
String name = recentd[i];
1871
if (access_resources != name.begins_with("res://")) {
1872
continue;
1873
}
1874
1875
if (!dir_access->dir_exists(name)) {
1876
// Remove invalid directory from the list of Recent directories.
1877
recentd.remove_at(i--);
1878
modified = true;
1879
continue;
1880
}
1881
1882
// Compute recent directory display text.
1883
if (access_resources && name == "res://") {
1884
name = "/";
1885
} else {
1886
name = name.trim_suffix("/");
1887
name = name.get_file();
1888
}
1889
recentd_paths.append(recentd[i]);
1890
recentd_names.append(name);
1891
}
1892
1893
EditorNode::disambiguate_filenames(recentd_paths, recentd_names);
1894
1895
for (int i = 0; i < recentd_paths.size(); i++) {
1896
recent->add_item(recentd_names[i], theme_cache.folder);
1897
recent->set_item_tooltip(-1, recentd_paths[i]);
1898
recent->set_item_metadata(-1, recentd_paths[i]);
1899
recent->set_item_icon_modulate(-1, FileSystemDock::get_dir_icon_color(recentd_paths[i], theme_cache.folder_icon_color));
1900
}
1901
1902
if (modified) {
1903
EditorSettings::get_singleton()->set_recent_dirs(recentd);
1904
}
1905
}
1906
1907
void EditorFileDialog::_recent_selected(int p_idx) {
1908
Vector<String> recentd = EditorSettings::get_singleton()->get_recent_dirs();
1909
ERR_FAIL_INDEX(p_idx, recentd.size());
1910
1911
dir_access->change_dir(recent->get_item_metadata(p_idx));
1912
update_file_list();
1913
update_dir();
1914
_push_history();
1915
}
1916
1917
void EditorFileDialog::_go_up() {
1918
dir_access->change_dir(get_current_dir().trim_suffix("/").get_base_dir());
1919
update_file_list();
1920
update_dir();
1921
_push_history();
1922
}
1923
1924
void EditorFileDialog::_go_back() {
1925
if (local_history_pos <= 0) {
1926
return;
1927
}
1928
1929
local_history_pos--;
1930
dir_access->change_dir(local_history[local_history_pos]);
1931
update_file_list();
1932
update_dir();
1933
1934
dir_prev->set_disabled(local_history_pos == 0);
1935
dir_next->set_disabled(local_history_pos == local_history.size() - 1);
1936
}
1937
1938
void EditorFileDialog::_go_forward() {
1939
if (local_history_pos >= local_history.size() - 1) {
1940
return;
1941
}
1942
1943
local_history_pos++;
1944
dir_access->change_dir(local_history[local_history_pos]);
1945
update_file_list();
1946
update_dir();
1947
1948
dir_prev->set_disabled(local_history_pos == 0);
1949
dir_next->set_disabled(local_history_pos == local_history.size() - 1);
1950
}
1951
1952
void EditorFileDialog::set_display_mode(DisplayMode p_mode) {
1953
if (display_mode == p_mode) {
1954
return;
1955
}
1956
if (p_mode == DISPLAY_THUMBNAILS) {
1957
mode_list->set_pressed(false);
1958
mode_thumbnails->set_pressed(true);
1959
} else {
1960
mode_thumbnails->set_pressed(false);
1961
mode_list->set_pressed(true);
1962
}
1963
display_mode = p_mode;
1964
invalidate();
1965
}
1966
1967
EditorFileDialog::DisplayMode EditorFileDialog::get_display_mode() const {
1968
return display_mode;
1969
}
1970
1971
TypedArray<Dictionary> EditorFileDialog::_get_options() const {
1972
TypedArray<Dictionary> out;
1973
for (const EditorFileDialog::Option &opt : options) {
1974
Dictionary dict;
1975
dict["name"] = opt.name;
1976
dict["values"] = opt.values;
1977
dict["default"] = (int)selected_options.get(opt.name, opt.default_idx);
1978
out.push_back(dict);
1979
}
1980
return out;
1981
}
1982
1983
void EditorFileDialog::_option_changed_checkbox_toggled(bool p_pressed, const String &p_name) {
1984
if (selected_options.has(p_name)) {
1985
selected_options[p_name] = p_pressed;
1986
}
1987
}
1988
1989
void EditorFileDialog::_option_changed_item_selected(int p_idx, const String &p_name) {
1990
if (selected_options.has(p_name)) {
1991
selected_options[p_name] = p_idx;
1992
}
1993
}
1994
1995
void EditorFileDialog::_update_option_controls() {
1996
if (!options_dirty) {
1997
return;
1998
}
1999
options_dirty = false;
2000
2001
while (flow_checkbox_options->get_child_count() > 0) {
2002
Node *child = flow_checkbox_options->get_child(0);
2003
flow_checkbox_options->remove_child(child);
2004
child->queue_free();
2005
}
2006
while (grid_select_options->get_child_count() > 0) {
2007
Node *child = grid_select_options->get_child(0);
2008
grid_select_options->remove_child(child);
2009
child->queue_free();
2010
}
2011
2012
selected_options.clear();
2013
2014
for (const EditorFileDialog::Option &opt : options) {
2015
if (opt.values.is_empty()) {
2016
CheckBox *cb = memnew(CheckBox);
2017
cb->set_accessibility_name(opt.name);
2018
cb->set_pressed(opt.default_idx);
2019
cb->set_text(opt.name);
2020
flow_checkbox_options->add_child(cb);
2021
cb->connect(SceneStringName(toggled), callable_mp(this, &EditorFileDialog::_option_changed_checkbox_toggled).bind(opt.name));
2022
selected_options[opt.name] = (bool)opt.default_idx;
2023
} else {
2024
Label *lbl = memnew(Label);
2025
lbl->set_text(opt.name);
2026
grid_select_options->add_child(lbl);
2027
2028
OptionButton *ob = memnew(OptionButton);
2029
for (const String &val : opt.values) {
2030
ob->add_item(val);
2031
}
2032
ob->set_accessibility_name(opt.name);
2033
ob->select(opt.default_idx);
2034
grid_select_options->add_child(ob);
2035
ob->connect(SceneStringName(item_selected), callable_mp(this, &EditorFileDialog::_option_changed_item_selected).bind(opt.name));
2036
selected_options[opt.name] = opt.default_idx;
2037
}
2038
}
2039
}
2040
2041
Dictionary EditorFileDialog::get_selected_options() const {
2042
return selected_options;
2043
}
2044
2045
String EditorFileDialog::get_option_name(int p_option) const {
2046
ERR_FAIL_INDEX_V(p_option, options.size(), String());
2047
return options[p_option].name;
2048
}
2049
2050
Vector<String> EditorFileDialog::get_option_values(int p_option) const {
2051
ERR_FAIL_INDEX_V(p_option, options.size(), Vector<String>());
2052
return options[p_option].values;
2053
}
2054
2055
int EditorFileDialog::get_option_default(int p_option) const {
2056
ERR_FAIL_INDEX_V(p_option, options.size(), -1);
2057
return options[p_option].default_idx;
2058
}
2059
2060
void EditorFileDialog::set_option_name(int p_option, const String &p_name) {
2061
if (p_option < 0) {
2062
p_option += get_option_count();
2063
}
2064
ERR_FAIL_INDEX(p_option, options.size());
2065
options.write[p_option].name = p_name;
2066
options_dirty = true;
2067
if (is_visible()) {
2068
_update_option_controls();
2069
}
2070
}
2071
2072
void EditorFileDialog::set_option_values(int p_option, const Vector<String> &p_values) {
2073
if (p_option < 0) {
2074
p_option += get_option_count();
2075
}
2076
ERR_FAIL_INDEX(p_option, options.size());
2077
options.write[p_option].values = p_values;
2078
if (p_values.is_empty()) {
2079
options.write[p_option].default_idx = CLAMP(options[p_option].default_idx, 0, 1);
2080
} else {
2081
options.write[p_option].default_idx = CLAMP(options[p_option].default_idx, 0, options[p_option].values.size() - 1);
2082
}
2083
options_dirty = true;
2084
if (is_visible()) {
2085
_update_option_controls();
2086
}
2087
}
2088
2089
void EditorFileDialog::set_option_default(int p_option, int p_index) {
2090
if (p_option < 0) {
2091
p_option += get_option_count();
2092
}
2093
ERR_FAIL_INDEX(p_option, options.size());
2094
if (options[p_option].values.is_empty()) {
2095
options.write[p_option].default_idx = CLAMP(p_index, 0, 1);
2096
} else {
2097
options.write[p_option].default_idx = CLAMP(p_index, 0, options[p_option].values.size() - 1);
2098
}
2099
options_dirty = true;
2100
if (is_visible()) {
2101
_update_option_controls();
2102
}
2103
}
2104
2105
void EditorFileDialog::add_option(const String &p_name, const Vector<String> &p_values, int p_index) {
2106
Option opt;
2107
opt.name = p_name;
2108
opt.values = p_values;
2109
if (opt.values.is_empty()) {
2110
opt.default_idx = CLAMP(p_index, 0, 1);
2111
} else {
2112
opt.default_idx = CLAMP(p_index, 0, opt.values.size() - 1);
2113
}
2114
options.push_back(opt);
2115
options_dirty = true;
2116
if (is_visible()) {
2117
_update_option_controls();
2118
}
2119
}
2120
2121
void EditorFileDialog::set_option_count(int p_count) {
2122
ERR_FAIL_COND(p_count < 0);
2123
2124
if (options.size() == p_count) {
2125
return;
2126
}
2127
options.resize(p_count);
2128
2129
options_dirty = true;
2130
notify_property_list_changed();
2131
if (is_visible()) {
2132
_update_option_controls();
2133
}
2134
}
2135
2136
int EditorFileDialog::get_option_count() const {
2137
return options.size();
2138
}
2139
2140
void EditorFileDialog::_bind_methods() {
2141
ClassDB::bind_method(D_METHOD("_cancel_pressed"), &EditorFileDialog::_cancel_pressed);
2142
2143
ClassDB::bind_method(D_METHOD("clear_filters"), &EditorFileDialog::clear_filters);
2144
ClassDB::bind_method(D_METHOD("add_filter", "filter", "description"), &EditorFileDialog::add_filter, DEFVAL(""));
2145
ClassDB::bind_method(D_METHOD("set_filters", "filters"), &EditorFileDialog::set_filters);
2146
ClassDB::bind_method(D_METHOD("get_filters"), &EditorFileDialog::get_filters);
2147
ClassDB::bind_method(D_METHOD("get_option_name", "option"), &EditorFileDialog::get_option_name);
2148
ClassDB::bind_method(D_METHOD("get_option_values", "option"), &EditorFileDialog::get_option_values);
2149
ClassDB::bind_method(D_METHOD("get_option_default", "option"), &EditorFileDialog::get_option_default);
2150
ClassDB::bind_method(D_METHOD("set_option_name", "option", "name"), &EditorFileDialog::set_option_name);
2151
ClassDB::bind_method(D_METHOD("set_option_values", "option", "values"), &EditorFileDialog::set_option_values);
2152
ClassDB::bind_method(D_METHOD("set_option_default", "option", "default_value_index"), &EditorFileDialog::set_option_default);
2153
ClassDB::bind_method(D_METHOD("set_option_count", "count"), &EditorFileDialog::set_option_count);
2154
ClassDB::bind_method(D_METHOD("get_option_count"), &EditorFileDialog::get_option_count);
2155
ClassDB::bind_method(D_METHOD("add_option", "name", "values", "default_value_index"), &EditorFileDialog::add_option);
2156
ClassDB::bind_method(D_METHOD("get_selected_options"), &EditorFileDialog::get_selected_options);
2157
ClassDB::bind_method(D_METHOD("clear_filename_filter"), &EditorFileDialog::clear_search_filter);
2158
ClassDB::bind_method(D_METHOD("set_filename_filter", "filter"), &EditorFileDialog::set_search_filter);
2159
ClassDB::bind_method(D_METHOD("get_filename_filter"), &EditorFileDialog::get_search_filter);
2160
ClassDB::bind_method(D_METHOD("get_current_dir"), &EditorFileDialog::get_current_dir);
2161
ClassDB::bind_method(D_METHOD("get_current_file"), &EditorFileDialog::get_current_file);
2162
ClassDB::bind_method(D_METHOD("get_current_path"), &EditorFileDialog::get_current_path);
2163
ClassDB::bind_method(D_METHOD("set_current_dir", "dir"), &EditorFileDialog::set_current_dir);
2164
ClassDB::bind_method(D_METHOD("set_current_file", "file"), &EditorFileDialog::set_current_file);
2165
ClassDB::bind_method(D_METHOD("set_current_path", "path"), &EditorFileDialog::set_current_path);
2166
ClassDB::bind_method(D_METHOD("set_file_mode", "mode"), &EditorFileDialog::set_file_mode);
2167
ClassDB::bind_method(D_METHOD("get_file_mode"), &EditorFileDialog::get_file_mode);
2168
ClassDB::bind_method(D_METHOD("get_vbox"), &EditorFileDialog::get_vbox);
2169
ClassDB::bind_method(D_METHOD("get_line_edit"), &EditorFileDialog::get_line_edit);
2170
ClassDB::bind_method(D_METHOD("set_access", "access"), &EditorFileDialog::set_access);
2171
ClassDB::bind_method(D_METHOD("get_access"), &EditorFileDialog::get_access);
2172
ClassDB::bind_method(D_METHOD("set_show_hidden_files", "show"), &EditorFileDialog::set_show_hidden_files);
2173
ClassDB::bind_method(D_METHOD("is_showing_hidden_files"), &EditorFileDialog::is_showing_hidden_files);
2174
ClassDB::bind_method(D_METHOD("_thumbnail_done"), &EditorFileDialog::_thumbnail_done);
2175
ClassDB::bind_method(D_METHOD("set_display_mode", "mode"), &EditorFileDialog::set_display_mode);
2176
ClassDB::bind_method(D_METHOD("get_display_mode"), &EditorFileDialog::get_display_mode);
2177
ClassDB::bind_method(D_METHOD("_thumbnail_result"), &EditorFileDialog::_thumbnail_result);
2178
ClassDB::bind_method(D_METHOD("set_disable_overwrite_warning", "disable"), &EditorFileDialog::set_disable_overwrite_warning);
2179
ClassDB::bind_method(D_METHOD("is_overwrite_warning_disabled"), &EditorFileDialog::is_overwrite_warning_disabled);
2180
ClassDB::bind_method(D_METHOD("add_side_menu", "menu", "title"), &EditorFileDialog::add_side_menu, DEFVAL(""));
2181
ClassDB::bind_method(D_METHOD("popup_file_dialog"), &EditorFileDialog::popup_file_dialog);
2182
2183
ClassDB::bind_method(D_METHOD("invalidate"), &EditorFileDialog::invalidate);
2184
2185
ADD_SIGNAL(MethodInfo("file_selected", PropertyInfo(Variant::STRING, "path")));
2186
ADD_SIGNAL(MethodInfo("files_selected", PropertyInfo(Variant::PACKED_STRING_ARRAY, "paths")));
2187
ADD_SIGNAL(MethodInfo("dir_selected", PropertyInfo(Variant::STRING, "dir")));
2188
ADD_SIGNAL(MethodInfo("filename_filter_changed", PropertyInfo(Variant::STRING, "filter")));
2189
2190
ADD_PROPERTY(PropertyInfo(Variant::INT, "access", PROPERTY_HINT_ENUM, "Resources,User data,File system"), "set_access", "get_access");
2191
ADD_PROPERTY(PropertyInfo(Variant::INT, "display_mode", PROPERTY_HINT_ENUM, "Thumbnails,List"), "set_display_mode", "get_display_mode");
2192
ADD_PROPERTY(PropertyInfo(Variant::INT, "file_mode", PROPERTY_HINT_ENUM, "Open one,Open many,Open folder,Open any,Save"), "set_file_mode", "get_file_mode");
2193
ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_dir", PROPERTY_HINT_DIR, "", PROPERTY_USAGE_NONE), "set_current_dir", "get_current_dir");
2194
ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_file", PROPERTY_HINT_FILE_PATH, "*", PROPERTY_USAGE_NONE), "set_current_file", "get_current_file");
2195
ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_current_path", "get_current_path");
2196
ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "filters"), "set_filters", "get_filters");
2197
ADD_ARRAY_COUNT("Options", "option_count", "set_option_count", "get_option_count", "option_");
2198
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_hidden_files"), "set_show_hidden_files", "is_showing_hidden_files");
2199
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disable_overwrite_warning"), "set_disable_overwrite_warning", "is_overwrite_warning_disabled");
2200
2201
BIND_ENUM_CONSTANT(FILE_MODE_OPEN_FILE);
2202
BIND_ENUM_CONSTANT(FILE_MODE_OPEN_FILES);
2203
BIND_ENUM_CONSTANT(FILE_MODE_OPEN_DIR);
2204
BIND_ENUM_CONSTANT(FILE_MODE_OPEN_ANY);
2205
BIND_ENUM_CONSTANT(FILE_MODE_SAVE_FILE);
2206
2207
BIND_ENUM_CONSTANT(ACCESS_RESOURCES);
2208
BIND_ENUM_CONSTANT(ACCESS_USERDATA);
2209
BIND_ENUM_CONSTANT(ACCESS_FILESYSTEM);
2210
2211
BIND_ENUM_CONSTANT(DISPLAY_THUMBNAILS);
2212
BIND_ENUM_CONSTANT(DISPLAY_LIST);
2213
2214
Option defaults;
2215
2216
base_property_helper.set_prefix("option_");
2217
base_property_helper.set_array_length_getter(&EditorFileDialog::get_option_count);
2218
base_property_helper.register_property(PropertyInfo(Variant::STRING, "name"), defaults.name, &EditorFileDialog::set_option_name, &EditorFileDialog::get_option_name);
2219
base_property_helper.register_property(PropertyInfo(Variant::PACKED_STRING_ARRAY, "values"), defaults.values, &EditorFileDialog::set_option_values, &EditorFileDialog::get_option_values);
2220
base_property_helper.register_property(PropertyInfo(Variant::INT, "default"), defaults.default_idx, &EditorFileDialog::set_option_default, &EditorFileDialog::get_option_default);
2221
PropertyListHelper::register_base_helper(&base_property_helper);
2222
}
2223
2224
void EditorFileDialog::set_show_hidden_files(bool p_show) {
2225
if (p_show == show_hidden_files) {
2226
return;
2227
}
2228
2229
EditorSettings::get_singleton()->set("filesystem/file_dialog/show_hidden_files", p_show);
2230
show_hidden_files = p_show;
2231
show_hidden->set_pressed(p_show);
2232
invalidate();
2233
}
2234
2235
void EditorFileDialog::set_show_search_filter(bool p_show) {
2236
if (p_show == show_search_filter) {
2237
return;
2238
}
2239
if (p_show) {
2240
filter_box->grab_focus();
2241
} else {
2242
search_string.clear();
2243
filter_box->clear();
2244
if (filter_box->has_focus()) {
2245
item_list->call_deferred("grab_focus");
2246
}
2247
}
2248
show_search_filter = p_show;
2249
update_search_filter_gui();
2250
invalidate();
2251
}
2252
2253
bool EditorFileDialog::is_showing_hidden_files() const {
2254
return show_hidden_files;
2255
}
2256
2257
void EditorFileDialog::set_default_show_hidden_files(bool p_show) {
2258
default_show_hidden_files = p_show;
2259
}
2260
2261
void EditorFileDialog::set_default_display_mode(DisplayMode p_mode) {
2262
default_display_mode = p_mode;
2263
}
2264
2265
void EditorFileDialog::_save_to_recent() {
2266
String cur_dir = get_current_dir();
2267
Vector<String> recent_new = EditorSettings::get_singleton()->get_recent_dirs();
2268
2269
const int max = 20;
2270
int count = 0;
2271
bool res = cur_dir.begins_with("res://");
2272
2273
for (int i = 0; i < recent_new.size(); i++) {
2274
bool cres = recent_new[i].begins_with("res://");
2275
if (recent_new[i] == cur_dir || (res == cres && count > max)) {
2276
recent_new.remove_at(i);
2277
i--;
2278
} else {
2279
count++;
2280
}
2281
}
2282
2283
recent_new.insert(0, cur_dir);
2284
2285
EditorSettings::get_singleton()->set_recent_dirs(recent_new);
2286
}
2287
2288
void EditorFileDialog::set_disable_overwrite_warning(bool p_disable) {
2289
disable_overwrite_warning = p_disable;
2290
}
2291
2292
bool EditorFileDialog::is_overwrite_warning_disabled() const {
2293
return disable_overwrite_warning;
2294
}
2295
2296
void EditorFileDialog::set_previews_enabled(bool p_enabled) {
2297
previews_enabled = p_enabled;
2298
}
2299
2300
bool EditorFileDialog::are_previews_enabled() {
2301
return previews_enabled;
2302
}
2303
2304
void EditorFileDialog::add_side_menu(Control *p_menu, const String &p_title) {
2305
// HSplitContainer has 3 children at maximum capacity, 1 of them is the SplitContainerDragger.
2306
ERR_FAIL_COND_MSG(body_hsplit->get_child_count() > 2, "EditorFileDialog: Only one side menu can be added.");
2307
// Everything for the side menu goes inside of a VBoxContainer.
2308
side_vbox = memnew(VBoxContainer);
2309
side_vbox->set_h_size_flags(Control::SIZE_EXPAND_FILL);
2310
side_vbox->set_stretch_ratio(0.5);
2311
body_hsplit->add_child(side_vbox);
2312
// Add a Label to the VBoxContainer.
2313
if (!p_title.is_empty()) {
2314
Label *title_label = memnew(Label(p_title));
2315
title_label->set_theme_type_variation("HeaderSmall");
2316
side_vbox->add_child(title_label);
2317
}
2318
// Add the given menu to the VBoxContainer.
2319
p_menu->set_v_size_flags(Control::SIZE_EXPAND_FILL);
2320
side_vbox->add_child(p_menu);
2321
}
2322
2323
void EditorFileDialog::_update_side_menu_visibility(bool p_native_dlg) {
2324
if (p_native_dlg) {
2325
pathhb->set_visible(false);
2326
flow_checkbox_options->set_visible(false);
2327
grid_select_options->set_visible(false);
2328
list_hb->set_visible(false);
2329
} else {
2330
pathhb->set_visible(true);
2331
flow_checkbox_options->set_visible(true);
2332
grid_select_options->set_visible(true);
2333
list_hb->set_visible(true);
2334
}
2335
}
2336
2337
EditorFileDialog::EditorFileDialog() {
2338
show_hidden_files = default_show_hidden_files;
2339
display_mode = default_display_mode;
2340
2341
vbc = memnew(VBoxContainer);
2342
add_child(vbc);
2343
2344
set_title(TTRC("Save a File"));
2345
2346
ED_SHORTCUT("file_dialog/go_back", TTRC("Go Back"), KeyModifierMask::ALT | Key::LEFT);
2347
ED_SHORTCUT("file_dialog/go_forward", TTRC("Go Forward"), KeyModifierMask::ALT | Key::RIGHT);
2348
ED_SHORTCUT("file_dialog/go_up", TTRC("Go Up"), KeyModifierMask::ALT | Key::UP);
2349
ED_SHORTCUT("file_dialog/refresh", TTRC("Refresh"), Key::F5);
2350
ED_SHORTCUT("file_dialog/toggle_hidden_files", TTRC("Toggle Hidden Files"), KeyModifierMask::CTRL | Key::H);
2351
ED_SHORTCUT("file_dialog/toggle_favorite", TTRC("Toggle Favorite"), KeyModifierMask::ALT | Key::F);
2352
ED_SHORTCUT("file_dialog/toggle_mode", TTRC("Toggle Mode"), KeyModifierMask::ALT | Key::V);
2353
ED_SHORTCUT("file_dialog/create_folder", TTRC("Create Folder"), KeyModifierMask::CMD_OR_CTRL | Key::N);
2354
ED_SHORTCUT("file_dialog/delete", TTRC("Delete"), Key::KEY_DELETE);
2355
ED_SHORTCUT("file_dialog/focus_path", TTRC("Focus Path"), KeyModifierMask::CMD_OR_CTRL | Key::L);
2356
// Allow both Cmd + L and Cmd + Shift + G to match Safari's and Finder's shortcuts respectively.
2357
ED_SHORTCUT_OVERRIDE_ARRAY("file_dialog/focus_path", "macos",
2358
{ int32_t(KeyModifierMask::META | Key::L), int32_t(KeyModifierMask::META | KeyModifierMask::SHIFT | Key::G) });
2359
ED_SHORTCUT("file_dialog/focus_filter", TTRC("Focus Filter"), KeyModifierMask::CMD_OR_CTRL | Key::F);
2360
ED_SHORTCUT("file_dialog/move_favorite_up", TTRC("Move Favorite Up"), KeyModifierMask::CMD_OR_CTRL | Key::UP);
2361
ED_SHORTCUT("file_dialog/move_favorite_down", TTRC("Move Favorite Down"), KeyModifierMask::CMD_OR_CTRL | Key::DOWN);
2362
2363
ED_SHORTCUT_OVERRIDE("file_dialog/toggle_hidden_files", "macos", KeyModifierMask::META | KeyModifierMask::SHIFT | Key::PERIOD);
2364
ED_SHORTCUT_OVERRIDE("file_dialog/toggle_favorite", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::F);
2365
ED_SHORTCUT_OVERRIDE("file_dialog/toggle_mode", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::V);
2366
2367
pathhb = memnew(HBoxContainer);
2368
vbc->add_child(pathhb);
2369
2370
dir_prev = memnew(Button);
2371
dir_prev->set_theme_type_variation(SceneStringName(FlatButton));
2372
dir_prev->set_tooltip_text(TTRC("Go to previous folder."));
2373
dir_next = memnew(Button);
2374
dir_next->set_theme_type_variation(SceneStringName(FlatButton));
2375
dir_next->set_tooltip_text(TTRC("Go to next folder."));
2376
dir_up = memnew(Button);
2377
dir_up->set_theme_type_variation(SceneStringName(FlatButton));
2378
dir_up->set_tooltip_text(TTRC("Go to parent folder."));
2379
2380
pathhb->add_child(dir_prev);
2381
pathhb->add_child(dir_next);
2382
pathhb->add_child(dir_up);
2383
2384
dir_prev->connect(SceneStringName(pressed), callable_mp(this, &EditorFileDialog::_go_back));
2385
dir_next->connect(SceneStringName(pressed), callable_mp(this, &EditorFileDialog::_go_forward));
2386
dir_up->connect(SceneStringName(pressed), callable_mp(this, &EditorFileDialog::_go_up));
2387
2388
Label *l = memnew(Label(TTRC("Path:")));
2389
l->set_theme_type_variation("HeaderSmall");
2390
pathhb->add_child(l);
2391
2392
drives_container = memnew(HBoxContainer);
2393
pathhb->add_child(drives_container);
2394
2395
dir = memnew(LineEdit);
2396
dir->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE);
2397
dir->set_accessibility_name(TTRC("Path:"));
2398
pathhb->add_child(dir);
2399
2400
refresh = memnew(Button);
2401
refresh->set_theme_type_variation(SceneStringName(FlatButton));
2402
refresh->set_tooltip_text(TTRC("Refresh files."));
2403
refresh->connect(SceneStringName(pressed), callable_mp(this, &EditorFileDialog::update_file_list));
2404
pathhb->add_child(refresh);
2405
2406
favorite = memnew(Button);
2407
favorite->set_theme_type_variation(SceneStringName(FlatButton));
2408
favorite->set_toggle_mode(true);
2409
favorite->set_tooltip_text(TTRC("(Un)favorite current folder."));
2410
favorite->connect(SceneStringName(pressed), callable_mp(this, &EditorFileDialog::_favorite_pressed));
2411
pathhb->add_child(favorite);
2412
2413
shortcuts_container = memnew(HBoxContainer);
2414
pathhb->add_child(shortcuts_container);
2415
2416
drives = memnew(OptionButton);
2417
drives->connect(SceneStringName(item_selected), callable_mp(this, &EditorFileDialog::_select_drive));
2418
drives->set_accessibility_name(TTRC("Current Drive"));
2419
pathhb->add_child(drives);
2420
2421
makedir_sep = memnew(VSeparator);
2422
pathhb->add_child(makedir_sep);
2423
2424
makedir = memnew(Button);
2425
makedir->set_theme_type_variation(SceneStringName(FlatButton));
2426
makedir->set_tooltip_text(TTRC("Create a new folder."));
2427
makedir->connect(SceneStringName(pressed), callable_mp(this, &EditorFileDialog::_make_dir));
2428
pathhb->add_child(makedir);
2429
2430
dir->set_h_size_flags(Control::SIZE_EXPAND_FILL);
2431
2432
body_hsplit = memnew(HSplitContainer);
2433
body_hsplit->set_v_size_flags(Control::SIZE_EXPAND_FILL);
2434
vbc->add_child(body_hsplit);
2435
2436
flow_checkbox_options = memnew(HFlowContainer);
2437
flow_checkbox_options->set_h_size_flags(Control::SIZE_EXPAND_FILL);
2438
flow_checkbox_options->set_alignment(FlowContainer::ALIGNMENT_CENTER);
2439
vbc->add_child(flow_checkbox_options);
2440
2441
grid_select_options = memnew(GridContainer);
2442
grid_select_options->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
2443
grid_select_options->set_columns(2);
2444
vbc->add_child(grid_select_options);
2445
2446
list_hb = memnew(HSplitContainer);
2447
list_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
2448
body_hsplit->add_child(list_hb);
2449
2450
VSplitContainer *vsc = memnew(VSplitContainer);
2451
list_hb->add_child(vsc);
2452
2453
VBoxContainer *fav_vb = memnew(VBoxContainer);
2454
vsc->add_child(fav_vb);
2455
fav_vb->set_custom_minimum_size(Size2(150, 100) * EDSCALE);
2456
fav_vb->set_v_size_flags(Control::SIZE_EXPAND_FILL);
2457
HBoxContainer *fav_hb = memnew(HBoxContainer);
2458
fav_vb->add_child(fav_hb);
2459
2460
l = memnew(Label(TTRC("Favorites:")));
2461
l->set_theme_type_variation("HeaderSmall");
2462
fav_hb->add_child(l);
2463
2464
fav_hb->add_spacer();
2465
fav_up = memnew(Button);
2466
fav_up->set_theme_type_variation(SceneStringName(FlatButton));
2467
fav_up->set_accessibility_name(TTRC("Move Up"));
2468
fav_hb->add_child(fav_up);
2469
fav_up->connect(SceneStringName(pressed), callable_mp(this, &EditorFileDialog::_favorite_move_up));
2470
fav_down = memnew(Button);
2471
fav_down->set_theme_type_variation(SceneStringName(FlatButton));
2472
fav_down->set_accessibility_name(TTRC("Move Down"));
2473
fav_hb->add_child(fav_down);
2474
fav_down->connect(SceneStringName(pressed), callable_mp(this, &EditorFileDialog::_favorite_move_down));
2475
2476
favorites = memnew(ItemList);
2477
favorites->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
2478
fav_vb->add_child(favorites);
2479
favorites->set_v_size_flags(Control::SIZE_EXPAND_FILL);
2480
favorites->set_theme_type_variation("ItemListSecondary");
2481
favorites->set_accessibility_name(TTRC("Favorites:"));
2482
favorites->connect(SceneStringName(item_selected), callable_mp(this, &EditorFileDialog::_favorite_selected));
2483
2484
VBoxContainer *rec_vb = memnew(VBoxContainer);
2485
vsc->add_child(rec_vb);
2486
rec_vb->set_custom_minimum_size(Size2(150, 100) * EDSCALE);
2487
rec_vb->set_v_size_flags(Control::SIZE_EXPAND_FILL);
2488
recent = memnew(ItemList);
2489
recent->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
2490
recent->set_allow_reselect(true);
2491
recent->set_theme_type_variation("ItemListSecondary");
2492
recent->set_accessibility_name(TTRC("Recent:"));
2493
rec_vb->add_margin_child(TTRC("Recent:"), recent, true);
2494
recent->connect(SceneStringName(item_selected), callable_mp(this, &EditorFileDialog::_recent_selected));
2495
2496
VBoxContainer *item_vb = memnew(VBoxContainer);
2497
list_hb->add_child(item_vb);
2498
item_vb->set_custom_minimum_size(Size2(320, 0) * EDSCALE);
2499
2500
HBoxContainer *preview_hb = memnew(HBoxContainer);
2501
preview_hb->set_v_size_flags(Control::SIZE_EXPAND_FILL);
2502
item_vb->add_child(preview_hb);
2503
2504
VBoxContainer *list_vb = memnew(VBoxContainer);
2505
list_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
2506
2507
HBoxContainer *lower_hb = memnew(HBoxContainer);
2508
lower_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
2509
2510
l = memnew(Label(TTRC("Directories & Files:")));
2511
l->set_theme_type_variation("HeaderSmall");
2512
l->set_h_size_flags(Control::SIZE_EXPAND_FILL);
2513
2514
lower_hb->add_child(l);
2515
2516
show_hidden = memnew(Button);
2517
show_hidden->set_theme_type_variation(SceneStringName(FlatButton));
2518
show_hidden->set_toggle_mode(true);
2519
show_hidden->set_pressed(is_showing_hidden_files());
2520
show_hidden->set_tooltip_text(TTRC("Toggle the visibility of hidden files."));
2521
show_hidden->connect(SceneStringName(toggled), callable_mp(this, &EditorFileDialog::set_show_hidden_files));
2522
lower_hb->add_child(show_hidden);
2523
2524
lower_hb->add_child(memnew(VSeparator));
2525
2526
Ref<ButtonGroup> view_mode_group;
2527
view_mode_group.instantiate();
2528
2529
mode_thumbnails = memnew(Button);
2530
mode_thumbnails->set_theme_type_variation(SceneStringName(FlatButton));
2531
mode_thumbnails->connect(SceneStringName(pressed), callable_mp(this, &EditorFileDialog::set_display_mode).bind(DISPLAY_THUMBNAILS));
2532
mode_thumbnails->set_toggle_mode(true);
2533
mode_thumbnails->set_pressed(display_mode == DISPLAY_THUMBNAILS);
2534
mode_thumbnails->set_button_group(view_mode_group);
2535
mode_thumbnails->set_tooltip_text(TTRC("View items as a grid of thumbnails."));
2536
lower_hb->add_child(mode_thumbnails);
2537
2538
mode_list = memnew(Button);
2539
mode_list->set_theme_type_variation(SceneStringName(FlatButton));
2540
mode_list->connect(SceneStringName(pressed), callable_mp(this, &EditorFileDialog::set_display_mode).bind(DISPLAY_LIST));
2541
mode_list->set_toggle_mode(true);
2542
mode_list->set_pressed(display_mode == DISPLAY_LIST);
2543
mode_list->set_button_group(view_mode_group);
2544
mode_list->set_tooltip_text(TTRC("View items as a list."));
2545
lower_hb->add_child(mode_list);
2546
2547
lower_hb->add_child(memnew(VSeparator));
2548
2549
file_sort_button = memnew(MenuButton);
2550
file_sort_button->set_flat(false);
2551
file_sort_button->set_theme_type_variation("FlatMenuButton");
2552
file_sort_button->set_tooltip_text(TTRC("Sort files"));
2553
2554
show_search_filter_button = memnew(Button);
2555
show_search_filter_button->set_theme_type_variation(SceneStringName(FlatButton));
2556
show_search_filter_button->set_toggle_mode(true);
2557
show_search_filter_button->set_pressed(false);
2558
show_search_filter_button->set_tooltip_text(TTRC("Toggle the visibility of the filter for file names."));
2559
show_search_filter_button->connect(SceneStringName(toggled), callable_mp(this, &EditorFileDialog::set_show_search_filter));
2560
lower_hb->add_child(show_search_filter_button);
2561
2562
PopupMenu *p = file_sort_button->get_popup();
2563
p->connect(SceneStringName(id_pressed), callable_mp(this, &EditorFileDialog::_file_sort_popup));
2564
p->add_radio_check_item(TTRC("Sort by Name (Ascending)"), static_cast<int>(FileSortOption::FILE_SORT_NAME));
2565
p->add_radio_check_item(TTRC("Sort by Name (Descending)"), static_cast<int>(FileSortOption::FILE_SORT_NAME_REVERSE));
2566
p->add_radio_check_item(TTRC("Sort by Type (Ascending)"), static_cast<int>(FileSortOption::FILE_SORT_TYPE));
2567
p->add_radio_check_item(TTRC("Sort by Type (Descending)"), static_cast<int>(FileSortOption::FILE_SORT_TYPE_REVERSE));
2568
p->add_radio_check_item(TTRC("Sort by Last Modified"), static_cast<int>(FileSortOption::FILE_SORT_MODIFIED_TIME));
2569
p->add_radio_check_item(TTRC("Sort by First Modified"), static_cast<int>(FileSortOption::FILE_SORT_MODIFIED_TIME_REVERSE));
2570
p->set_item_checked(0, true);
2571
lower_hb->add_child(file_sort_button);
2572
2573
list_vb->add_child(lower_hb);
2574
preview_hb->add_child(list_vb);
2575
2576
// Item (files and folders) list with context menu.
2577
2578
item_list = memnew(ItemList);
2579
item_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
2580
item_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
2581
item_list->connect("item_clicked", callable_mp(this, &EditorFileDialog::_item_list_item_rmb_clicked));
2582
item_list->connect("empty_clicked", callable_mp(this, &EditorFileDialog::_item_list_empty_clicked));
2583
item_list->set_allow_rmb_select(true);
2584
item_list->set_accessibility_name(TTRC("Directories & Files:"));
2585
2586
list_vb->add_child(item_list);
2587
2588
item_menu = memnew(PopupMenu);
2589
item_menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorFileDialog::_item_menu_id_pressed));
2590
add_child(item_menu);
2591
2592
// Other stuff.
2593
2594
preview_vb = memnew(VBoxContainer);
2595
preview_hb->add_child(preview_vb);
2596
CenterContainer *prev_cc = memnew(CenterContainer);
2597
preview_vb->add_margin_child(TTRC("Preview:"), prev_cc);
2598
preview = memnew(TextureRect);
2599
prev_cc->add_child(preview);
2600
preview_vb->hide();
2601
2602
filter_hb = memnew(HBoxContainer);
2603
filter_hb->add_child(memnew(Label(RTR("Filter:"))));
2604
filter_box = memnew(LineEdit);
2605
filter_box->set_h_size_flags(Control::SIZE_EXPAND_FILL);
2606
filter_box->set_placeholder(TTRC("Filter"));
2607
filter_box->set_accessibility_name(TTRC("Filename Filter:"));
2608
filter_hb->add_child(filter_box);
2609
filter_hb->set_visible(false);
2610
item_vb->add_child(filter_hb);
2611
2612
file_box = memnew(HBoxContainer);
2613
2614
l = memnew(Label(TTRC("File:")));
2615
l->set_theme_type_variation("HeaderSmall");
2616
file_box->add_child(l);
2617
2618
file = memnew(LineEdit);
2619
file->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE);
2620
file->set_stretch_ratio(4);
2621
file->set_h_size_flags(Control::SIZE_EXPAND_FILL);
2622
file->set_accessibility_name(TTRC("File:"));
2623
file_box->add_child(file);
2624
filter = memnew(OptionButton);
2625
filter->set_stretch_ratio(3);
2626
filter->set_h_size_flags(Control::SIZE_EXPAND_FILL);
2627
filter->set_clip_text(true); // Too many extensions overflow it.
2628
filter->set_accessibility_name(TTRC("File Type Filter"));
2629
file_box->add_child(filter);
2630
file_box->set_h_size_flags(Control::SIZE_EXPAND_FILL);
2631
item_vb->add_child(file_box);
2632
2633
dir_access = DirAccess::create(DirAccess::ACCESS_RESOURCES);
2634
_update_drives();
2635
2636
connect(SceneStringName(confirmed), callable_mp(this, &EditorFileDialog::_action_pressed));
2637
item_list->connect(SceneStringName(item_selected), callable_mp(this, &EditorFileDialog::_item_selected), CONNECT_DEFERRED);
2638
item_list->connect("multi_selected", callable_mp(this, &EditorFileDialog::_multi_selected), CONNECT_DEFERRED);
2639
item_list->connect("item_activated", callable_mp(this, &EditorFileDialog::_item_dc_selected).bind());
2640
item_list->connect("empty_clicked", callable_mp(this, &EditorFileDialog::_items_clear_selection));
2641
dir->connect(SceneStringName(text_submitted), callable_mp(this, &EditorFileDialog::_dir_submitted));
2642
filter_box->connect(SceneStringName(text_changed), callable_mp(this, &EditorFileDialog::_filter_changed));
2643
filter_box->connect(SceneStringName(text_submitted), callable_mp(this, &EditorFileDialog::_search_filter_selected).unbind(1));
2644
file->connect(SceneStringName(text_submitted), callable_mp(this, &EditorFileDialog::_file_submitted));
2645
filter->connect(SceneStringName(item_selected), callable_mp(this, &EditorFileDialog::_filter_selected));
2646
2647
confirm_save = memnew(ConfirmationDialog);
2648
add_child(confirm_save);
2649
confirm_save->connect(SceneStringName(confirmed), callable_mp(this, &EditorFileDialog::_save_confirm_pressed));
2650
2651
dep_remove_dialog = memnew(DependencyRemoveDialog);
2652
add_child(dep_remove_dialog);
2653
2654
global_remove_dialog = memnew(ConfirmationDialog);
2655
global_remove_dialog->set_text(TTRC("Remove the selected files? For safety only files and empty directories can be deleted from here. (Cannot be undone.)\nDepending on your filesystem configuration, the files will either be moved to the system trash or deleted permanently."));
2656
global_remove_dialog->connect(SceneStringName(confirmed), callable_mp(this, &EditorFileDialog::_delete_files_global));
2657
add_child(global_remove_dialog);
2658
2659
makedialog = memnew(ConfirmationDialog);
2660
makedialog->set_title(TTRC("Create Folder"));
2661
VBoxContainer *makevb = memnew(VBoxContainer);
2662
makedialog->add_child(makevb);
2663
2664
makedirname = memnew(LineEdit);
2665
makedirname->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE);
2666
makedirname->set_accessibility_name(TTRC("Name:"));
2667
makevb->add_margin_child(TTRC("Name:"), makedirname);
2668
add_child(makedialog);
2669
makedialog->register_text_enter(makedirname);
2670
makedialog->connect(SceneStringName(confirmed), callable_mp(this, &EditorFileDialog::_make_dir_confirm));
2671
error_dialog = memnew(AcceptDialog);
2672
add_child(error_dialog);
2673
2674
update_filters();
2675
update_dir();
2676
2677
set_hide_on_ok(false);
2678
vbox = vbc;
2679
2680
if (register_func) {
2681
register_func(this);
2682
}
2683
2684
property_helper.setup_for_instance(base_property_helper, this);
2685
}
2686
2687
EditorFileDialog::~EditorFileDialog() {
2688
if (unregister_func) {
2689
unregister_func(this);
2690
}
2691
}
2692
2693