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/io/file_access.h"
35#include "core/os/keyboard.h"
36#include "core/os/os.h"
37#include "editor/dependency_editor.h"
38#include "editor/editor_file_system.h"
39#include "editor/editor_node.h"
40#include "editor/editor_resource_preview.h"
41#include "editor/editor_scale.h"
42#include "editor/editor_settings.h"
43#include "scene/gui/center_container.h"
44#include "scene/gui/label.h"
45#include "scene/gui/margin_container.h"
46#include "scene/gui/option_button.h"
47#include "scene/gui/separator.h"
48#include "scene/gui/split_container.h"
49#include "scene/gui/texture_rect.h"
50#include "servers/display_server.h"
51
52EditorFileDialog::GetIconFunc EditorFileDialog::get_icon_func = nullptr;
53EditorFileDialog::GetIconFunc EditorFileDialog::get_thumbnail_func = nullptr;
54
55EditorFileDialog::RegisterFunc EditorFileDialog::register_func = nullptr;
56EditorFileDialog::RegisterFunc EditorFileDialog::unregister_func = nullptr;
57
58void EditorFileDialog::popup_file_dialog() {
59 popup_centered_clamped(Size2(1050, 700) * EDSCALE, 0.8);
60 _focus_file_text();
61}
62
63void EditorFileDialog::_focus_file_text() {
64 int lp = file->get_text().rfind(".");
65 if (lp != -1) {
66 file->select(0, lp);
67 file->grab_focus();
68 }
69}
70
71VBoxContainer *EditorFileDialog::get_vbox() {
72 return vbox;
73}
74
75void EditorFileDialog::_update_theme_item_cache() {
76 ConfirmationDialog::_update_theme_item_cache();
77
78 theme_cache.parent_folder = get_editor_theme_icon(SNAME("ArrowUp"));
79 theme_cache.forward_folder = get_editor_theme_icon(SNAME("Forward"));
80 theme_cache.back_folder = get_editor_theme_icon(SNAME("Back"));
81 theme_cache.reload = get_editor_theme_icon(SNAME("Reload"));
82 theme_cache.toggle_hidden = get_editor_theme_icon(SNAME("GuiVisibilityVisible"));
83 theme_cache.favorite = get_editor_theme_icon(SNAME("Favorites"));
84 theme_cache.mode_thumbnails = get_editor_theme_icon(SNAME("FileThumbnail"));
85 theme_cache.mode_list = get_editor_theme_icon(SNAME("FileList"));
86 theme_cache.favorites_up = get_editor_theme_icon(SNAME("MoveUp"));
87 theme_cache.favorites_down = get_editor_theme_icon(SNAME("MoveDown"));
88
89 theme_cache.folder = get_editor_theme_icon(SNAME("Folder"));
90 theme_cache.folder_icon_color = get_theme_color(SNAME("folder_icon_color"), SNAME("FileDialog"));
91
92 theme_cache.action_copy = get_editor_theme_icon(SNAME("ActionCopy"));
93 theme_cache.action_delete = get_editor_theme_icon(SNAME("Remove"));
94 theme_cache.filesystem = get_editor_theme_icon(SNAME("Filesystem"));
95
96 theme_cache.folder_medium_thumbnail = get_editor_theme_icon(SNAME("FolderMediumThumb"));
97 theme_cache.file_medium_thumbnail = get_editor_theme_icon(SNAME("FileMediumThumb"));
98 theme_cache.folder_big_thumbnail = get_editor_theme_icon(SNAME("FolderBigThumb"));
99 theme_cache.file_big_thumbnail = get_editor_theme_icon(SNAME("FileBigThumb"));
100
101 theme_cache.progress[0] = get_editor_theme_icon("Progress1");
102 theme_cache.progress[1] = get_editor_theme_icon("Progress2");
103 theme_cache.progress[2] = get_editor_theme_icon("Progress3");
104 theme_cache.progress[3] = get_editor_theme_icon("Progress4");
105 theme_cache.progress[4] = get_editor_theme_icon("Progress5");
106 theme_cache.progress[5] = get_editor_theme_icon("Progress6");
107 theme_cache.progress[6] = get_editor_theme_icon("Progress7");
108 theme_cache.progress[7] = get_editor_theme_icon("Progress8");
109}
110
111void EditorFileDialog::_notification(int p_what) {
112 switch (p_what) {
113 case NOTIFICATION_THEME_CHANGED:
114 case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
115 case NOTIFICATION_TRANSLATION_CHANGED: {
116 _update_icons();
117 invalidate();
118 } break;
119
120 case NOTIFICATION_PROCESS: {
121 if (preview_waiting) {
122 preview_wheel_timeout -= get_process_delta_time();
123 if (preview_wheel_timeout <= 0) {
124 preview_wheel_index++;
125 if (preview_wheel_index >= 8) {
126 preview_wheel_index = 0;
127 }
128
129 Ref<Texture2D> frame = theme_cache.progress[preview_wheel_index];
130 preview->set_texture(frame);
131 preview_wheel_timeout = 0.1;
132 }
133 }
134 } break;
135
136 case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
137 bool is_showing_hidden = EDITOR_GET("filesystem/file_dialog/show_hidden_files");
138 if (show_hidden_files != is_showing_hidden) {
139 set_show_hidden_files(is_showing_hidden);
140 }
141 set_display_mode((DisplayMode)EDITOR_GET("filesystem/file_dialog/display_mode").operator int());
142
143 // DO NOT CALL UPDATE FILE LIST HERE, ALL HUNDREDS OF HIDDEN DIALOGS WILL RESPOND, CALL INVALIDATE INSTEAD
144 invalidate();
145 } break;
146
147 case NOTIFICATION_VISIBILITY_CHANGED: {
148 if (!is_visible()) {
149 set_process_shortcut_input(false);
150 }
151
152 invalidate(); // For consistency with the standard FileDialog.
153 } break;
154
155 case NOTIFICATION_WM_WINDOW_FOCUS_IN: {
156 // Check if the current directory was removed externally (much less likely to happen while editor window is focused).
157 String previous_dir = get_current_dir();
158 while (!dir_access->dir_exists(get_current_dir())) {
159 _go_up();
160
161 // In case we can't go further up, use some fallback and break.
162 if (get_current_dir() == previous_dir) {
163 _dir_submitted(OS::get_singleton()->get_user_data_dir());
164 break;
165 }
166 }
167 } break;
168 }
169}
170
171void EditorFileDialog::shortcut_input(const Ref<InputEvent> &p_event) {
172 ERR_FAIL_COND(p_event.is_null());
173
174 Ref<InputEventKey> k = p_event;
175
176 if (k.is_valid()) {
177 if (k->is_pressed()) {
178 bool handled = false;
179
180 if (ED_IS_SHORTCUT("file_dialog/go_back", p_event)) {
181 _go_back();
182 handled = true;
183 }
184 if (ED_IS_SHORTCUT("file_dialog/go_forward", p_event)) {
185 _go_forward();
186 handled = true;
187 }
188 if (ED_IS_SHORTCUT("file_dialog/go_up", p_event)) {
189 _go_up();
190 handled = true;
191 }
192 if (ED_IS_SHORTCUT("file_dialog/refresh", p_event)) {
193 invalidate();
194 handled = true;
195 }
196 if (ED_IS_SHORTCUT("file_dialog/toggle_hidden_files", p_event)) {
197 set_show_hidden_files(!show_hidden_files);
198 handled = true;
199 }
200 if (ED_IS_SHORTCUT("file_dialog/toggle_favorite", p_event)) {
201 _favorite_pressed();
202 handled = true;
203 }
204 if (ED_IS_SHORTCUT("file_dialog/toggle_mode", p_event)) {
205 if (mode_thumbnails->is_pressed()) {
206 set_display_mode(DISPLAY_LIST);
207 } else {
208 set_display_mode(DISPLAY_THUMBNAILS);
209 }
210 handled = true;
211 }
212 if (ED_IS_SHORTCUT("file_dialog/create_folder", p_event)) {
213 _make_dir();
214 handled = true;
215 }
216 if (ED_IS_SHORTCUT("file_dialog/delete", p_event)) {
217 _delete_items();
218 handled = true;
219 }
220 if (ED_IS_SHORTCUT("file_dialog/focus_path", p_event)) {
221 dir->grab_focus();
222 handled = true;
223 }
224 if (ED_IS_SHORTCUT("file_dialog/move_favorite_up", p_event)) {
225 _favorite_move_up();
226 handled = true;
227 }
228 if (ED_IS_SHORTCUT("file_dialog/move_favorite_down", p_event)) {
229 _favorite_move_down();
230 handled = true;
231 }
232
233 if (handled) {
234 set_input_as_handled();
235 }
236 }
237 }
238}
239
240void EditorFileDialog::set_enable_multiple_selection(bool p_enable) {
241 item_list->set_select_mode(p_enable ? ItemList::SELECT_MULTI : ItemList::SELECT_SINGLE);
242};
243
244Vector<String> EditorFileDialog::get_selected_files() const {
245 Vector<String> list;
246 for (int i = 0; i < item_list->get_item_count(); i++) {
247 if (item_list->is_selected(i)) {
248 list.push_back(item_list->get_item_text(i));
249 }
250 }
251 return list;
252};
253
254void EditorFileDialog::update_dir() {
255 if (drives->is_visible()) {
256 if (dir_access->get_current_dir().is_network_share_path()) {
257 _update_drives(false);
258 drives->add_item(RTR("Network"));
259 drives->set_item_disabled(-1, true);
260 drives->select(drives->get_item_count() - 1);
261 } else {
262 drives->select(dir_access->get_current_drive());
263 }
264 }
265 dir->set_text(dir_access->get_current_dir(false));
266
267 // Disable "Open" button only when selecting file(s) mode.
268 get_ok_button()->set_disabled(_is_open_should_be_disabled());
269 switch (mode) {
270 case FILE_MODE_OPEN_FILE:
271 case FILE_MODE_OPEN_FILES:
272 file->set_text("");
273 set_ok_button_text(TTR("Open"));
274 break;
275 case FILE_MODE_OPEN_ANY:
276 case FILE_MODE_OPEN_DIR:
277 file->set_text("");
278 set_ok_button_text(TTR("Select Current Folder"));
279 break;
280 case FILE_MODE_SAVE_FILE:
281 // FIXME: Implement, or refactor to avoid duplication with set_mode
282 break;
283 }
284}
285
286void EditorFileDialog::_dir_submitted(String p_dir) {
287 dir_access->change_dir(p_dir);
288 invalidate();
289 update_dir();
290 _push_history();
291}
292
293void EditorFileDialog::_file_submitted(const String &p_file) {
294 _action_pressed();
295}
296
297void EditorFileDialog::_save_confirm_pressed() {
298 String f = dir_access->get_current_dir().path_join(file->get_text());
299 _save_to_recent();
300 hide();
301 emit_signal(SNAME("file_selected"), f);
302}
303
304void EditorFileDialog::_post_popup() {
305 ConfirmationDialog::_post_popup();
306
307 // Check if the current path doesn't exist and correct it.
308 String current = dir_access->get_current_dir();
309 while (!dir_access->dir_exists(current)) {
310 current = current.get_base_dir();
311 }
312 set_current_dir(current);
313
314 if (mode == FILE_MODE_SAVE_FILE) {
315 file->grab_focus();
316 } else {
317 item_list->grab_focus();
318 }
319
320 if (mode == FILE_MODE_OPEN_DIR) {
321 file_box->set_visible(false);
322 } else {
323 file_box->set_visible(true);
324 }
325
326 if (!get_current_file().is_empty()) {
327 _request_single_thumbnail(get_current_dir().path_join(get_current_file()));
328 }
329
330 local_history.clear();
331 local_history_pos = -1;
332 _push_history();
333
334 set_process_shortcut_input(true);
335}
336
337void EditorFileDialog::_thumbnail_result(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, const Variant &p_udata) {
338 if (display_mode == DISPLAY_LIST || p_preview.is_null()) {
339 return;
340 }
341
342 for (int i = 0; i < item_list->get_item_count(); i++) {
343 Dictionary d = item_list->get_item_metadata(i);
344 String pname = d["path"];
345 if (pname == p_path) {
346 item_list->set_item_icon(i, p_preview);
347 item_list->set_item_tag_icon(i, Ref<Texture2D>());
348 }
349 }
350}
351
352void EditorFileDialog::_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, const Variant &p_udata) {
353 set_process(false);
354 preview_waiting = false;
355
356 if (p_preview.is_valid() && get_current_path() == p_path) {
357 preview->set_texture(p_preview);
358 if (display_mode == DISPLAY_THUMBNAILS) {
359 preview_vb->hide();
360 } else {
361 preview_vb->show();
362 }
363
364 } else {
365 preview_vb->hide();
366 preview->set_texture(Ref<Texture2D>());
367 }
368}
369
370void EditorFileDialog::_request_single_thumbnail(const String &p_path) {
371 if (!FileAccess::exists(p_path) || !previews_enabled) {
372 return;
373 }
374
375 set_process(true);
376 preview_waiting = true;
377 preview_wheel_timeout = 0;
378 EditorResourcePreview::get_singleton()->queue_resource_preview(p_path, this, "_thumbnail_done", p_path);
379}
380
381void EditorFileDialog::_action_pressed() {
382 if (mode == FILE_MODE_OPEN_FILES) {
383 String fbase = dir_access->get_current_dir();
384
385 Vector<String> files;
386 for (int i = 0; i < item_list->get_item_count(); i++) {
387 if (item_list->is_selected(i)) {
388 files.push_back(fbase.path_join(item_list->get_item_text(i)));
389 }
390 }
391
392 if (files.size()) {
393 _save_to_recent();
394 hide();
395 emit_signal(SNAME("files_selected"), files);
396 }
397
398 return;
399 }
400
401 String file_text = file->get_text();
402 String f = file_text.is_absolute_path() ? file_text : dir_access->get_current_dir().path_join(file_text);
403
404 if ((mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_FILE) && dir_access->file_exists(f)) {
405 _save_to_recent();
406 hide();
407 emit_signal(SNAME("file_selected"), f);
408 } else if (mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_DIR) {
409 String path = dir_access->get_current_dir();
410
411 path = path.replace("\\", "/");
412
413 for (int i = 0; i < item_list->get_item_count(); i++) {
414 if (item_list->is_selected(i)) {
415 Dictionary d = item_list->get_item_metadata(i);
416 if (d["dir"]) {
417 path = path.path_join(d["name"]);
418
419 break;
420 }
421 }
422 }
423
424 _save_to_recent();
425 hide();
426 emit_signal(SNAME("dir_selected"), path);
427 }
428
429 if (mode == FILE_MODE_SAVE_FILE) {
430 bool valid = false;
431
432 if (filter->get_selected() == filter->get_item_count() - 1) {
433 valid = true; // match none
434 } else if (filters.size() > 1 && filter->get_selected() == 0) {
435 // match all filters
436 for (int i = 0; i < filters.size(); i++) {
437 String flt = filters[i].get_slice(";", 0);
438 for (int j = 0; j < flt.get_slice_count(","); j++) {
439 String str = flt.get_slice(",", j).strip_edges();
440 if (f.match(str)) {
441 valid = true;
442 break;
443 }
444 }
445 if (valid) {
446 break;
447 }
448 }
449 } else {
450 int idx = filter->get_selected();
451 if (filters.size() > 1) {
452 idx--;
453 }
454 if (idx >= 0 && idx < filters.size()) {
455 String flt = filters[idx].get_slice(";", 0);
456 int filterSliceCount = flt.get_slice_count(",");
457 for (int j = 0; j < filterSliceCount; j++) {
458 String str = (flt.get_slice(",", j).strip_edges());
459 if (f.match(str)) {
460 valid = true;
461 break;
462 }
463 }
464
465 if (!valid && filterSliceCount > 0) {
466 String str = (flt.get_slice(",", 0).strip_edges());
467 f += str.substr(1, str.length() - 1);
468 _request_single_thumbnail(get_current_dir().path_join(f.get_file()));
469 file->set_text(f.get_file());
470 valid = true;
471 }
472 } else {
473 valid = true;
474 }
475 }
476
477 // First check we're not having an empty name.
478 String file_name = file_text.strip_edges().get_file();
479 if (file_name.is_empty()) {
480 error_dialog->set_text(TTR("Cannot save file with an empty filename."));
481 error_dialog->popup_centered(Size2(250, 80) * EDSCALE);
482 return;
483 }
484
485 // Add first extension of filter if no valid extension is found.
486 if (!valid) {
487 int idx = filter->get_selected();
488 String flt = filters[idx].get_slice(";", 0);
489 String ext = flt.get_slice(",", 0).strip_edges().get_extension();
490 f += "." + ext;
491 }
492
493 if (file_name.begins_with(".")) { // Could still happen if typed manually.
494 error_dialog->set_text(TTR("Cannot save file with a name starting with a dot."));
495 error_dialog->popup_centered(Size2(250, 80) * EDSCALE);
496 return;
497 }
498
499 if (dir_access->file_exists(f) && !disable_overwrite_warning) {
500 confirm_save->set_text(vformat(TTR("File \"%s\" already exists.\nDo you want to overwrite it?"), f));
501 confirm_save->popup_centered(Size2(250, 80) * EDSCALE);
502 } else {
503 _save_to_recent();
504 hide();
505 emit_signal(SNAME("file_selected"), f);
506 }
507 }
508}
509
510void EditorFileDialog::_cancel_pressed() {
511 file->set_text("");
512 invalidate();
513 hide();
514}
515
516void EditorFileDialog::_item_selected(int p_item) {
517 int current = p_item;
518 if (current < 0 || current >= item_list->get_item_count()) {
519 return;
520 }
521
522 Dictionary d = item_list->get_item_metadata(current);
523
524 if (!d["dir"]) {
525 file->set_text(d["name"]);
526 _request_single_thumbnail(get_current_dir().path_join(get_current_file()));
527
528 // FILE_MODE_OPEN_ANY can alternate this text depending on what's selected.
529 set_ok_button_text(TTR("Open"));
530 } else if (mode == FILE_MODE_OPEN_DIR || mode == FILE_MODE_OPEN_ANY) {
531 file->set_text("");
532 set_ok_button_text(TTR("Select This Folder"));
533 }
534
535 get_ok_button()->set_disabled(_is_open_should_be_disabled());
536}
537
538void EditorFileDialog::_multi_selected(int p_item, bool p_selected) {
539 int current = p_item;
540 if (current < 0 || current >= item_list->get_item_count()) {
541 return;
542 }
543
544 Dictionary d = item_list->get_item_metadata(current);
545
546 if (!d["dir"] && p_selected) {
547 file->set_text(d["name"]);
548 _request_single_thumbnail(get_current_dir().path_join(get_current_file()));
549 }
550
551 get_ok_button()->set_disabled(_is_open_should_be_disabled());
552}
553
554void EditorFileDialog::_items_clear_selection(const Vector2 &p_pos, MouseButton p_mouse_button_index) {
555 if (p_mouse_button_index != MouseButton::LEFT) {
556 return;
557 }
558
559 item_list->deselect_all();
560
561 // If nothing is selected, then block Open button.
562 switch (mode) {
563 case FILE_MODE_OPEN_FILE:
564 case FILE_MODE_OPEN_FILES:
565 set_ok_button_text(TTR("Open"));
566 get_ok_button()->set_disabled(!item_list->is_anything_selected());
567 break;
568
569 case FILE_MODE_OPEN_ANY:
570 case FILE_MODE_OPEN_DIR:
571 file->set_text("");
572 get_ok_button()->set_disabled(false);
573 set_ok_button_text(TTR("Select Current Folder"));
574 break;
575
576 case FILE_MODE_SAVE_FILE:
577 // FIXME: Implement, or refactor to avoid duplication with set_mode
578 break;
579 }
580}
581
582void EditorFileDialog::_push_history() {
583 local_history.resize(local_history_pos + 1);
584 String new_path = dir_access->get_current_dir();
585 if (local_history.size() == 0 || new_path != local_history[local_history_pos]) {
586 local_history.push_back(new_path);
587 local_history_pos++;
588 dir_prev->set_disabled(local_history_pos == 0);
589 dir_next->set_disabled(true);
590 }
591}
592
593void EditorFileDialog::_item_dc_selected(int p_item) {
594 int current = p_item;
595 if (current < 0 || current >= item_list->get_item_count()) {
596 return;
597 }
598
599 Dictionary d = item_list->get_item_metadata(current);
600
601 if (d["dir"]) {
602 dir_access->change_dir(d["name"]);
603 callable_mp(this, &EditorFileDialog::update_file_list).call_deferred();
604 callable_mp(this, &EditorFileDialog::update_dir).call_deferred();
605
606 _push_history();
607
608 } else {
609 _action_pressed();
610 }
611}
612
613void EditorFileDialog::_item_list_item_rmb_clicked(int p_item, const Vector2 &p_pos, MouseButton p_mouse_button_index) {
614 if (p_mouse_button_index != MouseButton::RIGHT) {
615 return;
616 }
617
618 // Right click on specific file(s) or folder(s).
619 item_menu->clear();
620 item_menu->reset_size();
621
622 // Allow specific actions only on one item.
623 bool single_item_selected = item_list->get_selected_items().size() == 1;
624
625 // Disallow deleting the .import folder, Godot kills a cat if you do and it is possibly a senseless novice action.
626 bool allow_delete = true;
627 for (int i = 0; i < item_list->get_item_count(); i++) {
628 if (!item_list->is_selected(i)) {
629 continue;
630 }
631 Dictionary item_meta = item_list->get_item_metadata(i);
632 if (String(item_meta["path"]).begins_with(ProjectSettings::get_singleton()->get_project_data_path())) {
633 allow_delete = false;
634 break;
635 }
636 }
637
638 if (single_item_selected) {
639 item_menu->add_icon_item(theme_cache.action_copy, TTR("Copy Path"), ITEM_MENU_COPY_PATH);
640 }
641 if (allow_delete) {
642 item_menu->add_icon_item(theme_cache.action_delete, TTR("Delete"), ITEM_MENU_DELETE, Key::KEY_DELETE);
643 }
644
645#if !defined(ANDROID_ENABLED) && !defined(WEB_ENABLED)
646 // Opening the system file manager is not supported on the Android and web editors.
647 if (single_item_selected) {
648 item_menu->add_separator();
649 Dictionary item_meta = item_list->get_item_metadata(p_item);
650 String item_text = item_meta["dir"] ? TTR("Open in File Manager") : TTR("Show in File Manager");
651 item_menu->add_icon_item(theme_cache.filesystem, item_text, ITEM_MENU_SHOW_IN_EXPLORER);
652 }
653#endif
654
655 if (item_menu->get_item_count() > 0) {
656 item_menu->set_position(item_list->get_screen_position() + p_pos);
657 item_menu->reset_size();
658 item_menu->popup();
659 }
660}
661
662void EditorFileDialog::_item_list_empty_clicked(const Vector2 &p_pos, MouseButton p_mouse_button_index) {
663 if (p_mouse_button_index != MouseButton::RIGHT && p_mouse_button_index != MouseButton::LEFT) {
664 return;
665 }
666
667 // Left or right click on folder background. Deselect all files so that actions are applied on the current folder.
668 for (int i = 0; i < item_list->get_item_count(); i++) {
669 item_list->deselect(i);
670 }
671
672 if (p_mouse_button_index != MouseButton::RIGHT) {
673 return;
674 }
675
676 item_menu->clear();
677 item_menu->reset_size();
678
679 if (can_create_dir) {
680 item_menu->add_icon_item(theme_cache.folder, TTR("New Folder..."), ITEM_MENU_NEW_FOLDER, KeyModifierMask::CMD_OR_CTRL | Key::N);
681 }
682 item_menu->add_icon_item(theme_cache.reload, TTR("Refresh"), ITEM_MENU_REFRESH, Key::F5);
683#if !defined(ANDROID_ENABLED) && !defined(WEB_ENABLED)
684 // Opening the system file manager is not supported on the Android and web editors.
685 item_menu->add_separator();
686 item_menu->add_icon_item(theme_cache.filesystem, TTR("Open in File Manager"), ITEM_MENU_SHOW_IN_EXPLORER);
687#endif
688
689 item_menu->set_position(item_list->get_screen_position() + p_pos);
690 item_menu->reset_size();
691 item_menu->popup();
692}
693
694void EditorFileDialog::_item_menu_id_pressed(int p_option) {
695 switch (p_option) {
696 case ITEM_MENU_COPY_PATH: {
697 Dictionary item_meta = item_list->get_item_metadata(item_list->get_current());
698 DisplayServer::get_singleton()->clipboard_set(item_meta["path"]);
699 } break;
700
701 case ITEM_MENU_DELETE: {
702 _delete_items();
703 } break;
704
705 case ITEM_MENU_REFRESH: {
706 invalidate();
707 } break;
708
709 case ITEM_MENU_NEW_FOLDER: {
710 _make_dir();
711 } break;
712
713 case ITEM_MENU_SHOW_IN_EXPLORER: {
714 String path;
715 int idx = item_list->get_current();
716 if (idx == -1 || item_list->get_selected_items().size() == 0) {
717 // Folder background was clicked. Open this folder.
718 path = ProjectSettings::get_singleton()->globalize_path(dir_access->get_current_dir());
719 } else {
720 // Specific item was clicked. Open folders directly, or the folder containing a selected file.
721 Dictionary item_meta = item_list->get_item_metadata(idx);
722 path = ProjectSettings::get_singleton()->globalize_path(item_meta["path"]);
723 }
724 OS::get_singleton()->shell_show_in_file_manager(path, true);
725 } break;
726 }
727}
728
729bool EditorFileDialog::_is_open_should_be_disabled() {
730 if (mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_SAVE_FILE) {
731 return false;
732 }
733
734 Vector<int> items = item_list->get_selected_items();
735 if (items.size() == 0) {
736 return mode != FILE_MODE_OPEN_DIR; // In "Open folder" mode, having nothing selected picks the current folder.
737 }
738
739 for (int i = 0; i < items.size(); i++) {
740 Dictionary d = item_list->get_item_metadata(items.get(i));
741
742 if (((mode == FILE_MODE_OPEN_FILE || mode == FILE_MODE_OPEN_FILES) && d["dir"]) || (mode == FILE_MODE_OPEN_DIR && !d["dir"])) {
743 return true;
744 }
745 }
746
747 return false;
748}
749
750void EditorFileDialog::update_file_name() {
751 int idx = filter->get_selected() - 1;
752 if ((idx == -1 && filter->get_item_count() == 2) || (filter->get_item_count() > 2 && idx >= 0 && idx < filter->get_item_count() - 2)) {
753 if (idx == -1) {
754 idx += 1;
755 }
756 String filter_str = filters[idx];
757 String file_str = file->get_text();
758 String base_name = file_str.get_basename();
759 Vector<String> filter_substr = filter_str.split(";");
760 if (filter_substr.size() >= 2) {
761 file_str = base_name + "." + filter_substr[0].strip_edges().get_extension().to_lower();
762 } else {
763 file_str = base_name + "." + filter_str.strip_edges().get_extension().to_lower();
764 }
765 file->set_text(file_str);
766 }
767}
768
769// DO NOT USE THIS FUNCTION UNLESS NEEDED, CALL INVALIDATE() INSTEAD.
770void EditorFileDialog::update_file_list() {
771 int thumbnail_size = EDITOR_GET("filesystem/file_dialog/thumbnail_size");
772 thumbnail_size *= EDSCALE;
773 Ref<Texture2D> folder_thumbnail;
774 Ref<Texture2D> file_thumbnail;
775
776 item_list->clear();
777
778 // Scroll back to the top after opening a directory
779 item_list->get_v_scroll_bar()->set_value(0);
780
781 if (display_mode == DISPLAY_THUMBNAILS) {
782 item_list->set_max_columns(0);
783 item_list->set_icon_mode(ItemList::ICON_MODE_TOP);
784 item_list->set_fixed_column_width(thumbnail_size * 3 / 2);
785 item_list->set_max_text_lines(2);
786 item_list->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
787 item_list->set_fixed_icon_size(Size2(thumbnail_size, thumbnail_size));
788
789 if (thumbnail_size < 64) {
790 folder_thumbnail = theme_cache.folder_medium_thumbnail;
791 file_thumbnail = theme_cache.file_medium_thumbnail;
792 } else {
793 folder_thumbnail = theme_cache.folder_big_thumbnail;
794 file_thumbnail = theme_cache.file_big_thumbnail;
795 }
796
797 preview_vb->hide();
798
799 } else {
800 item_list->set_icon_mode(ItemList::ICON_MODE_LEFT);
801 item_list->set_max_columns(1);
802 item_list->set_max_text_lines(1);
803 item_list->set_fixed_column_width(0);
804 item_list->set_fixed_icon_size(Size2());
805 if (preview->get_texture().is_valid()) {
806 preview_vb->show();
807 }
808 }
809
810 String cdir = dir_access->get_current_dir();
811
812 dir_access->list_dir_begin();
813
814 List<String> files;
815 List<String> dirs;
816
817 String item = dir_access->get_next();
818
819 while (!item.is_empty()) {
820 if (item == "." || item == "..") {
821 item = dir_access->get_next();
822 continue;
823 }
824
825 if (show_hidden_files) {
826 if (!dir_access->current_is_dir()) {
827 files.push_back(item);
828 } else {
829 dirs.push_back(item);
830 }
831 } else if (!dir_access->current_is_hidden()) {
832 String full_path = cdir == "res://" ? item : dir_access->get_current_dir() + "/" + item;
833 if (dir_access->current_is_dir() && (Engine::get_singleton()->is_project_manager_hint() || !EditorFileSystem::_should_skip_directory(full_path))) {
834 dirs.push_back(item);
835 } else {
836 files.push_back(item);
837 }
838 }
839 item = dir_access->get_next();
840 }
841
842 dirs.sort_custom<NaturalNoCaseComparator>();
843 files.sort_custom<NaturalNoCaseComparator>();
844
845 while (!dirs.is_empty()) {
846 const String &dir_name = dirs.front()->get();
847
848 item_list->add_item(dir_name);
849
850 if (display_mode == DISPLAY_THUMBNAILS) {
851 item_list->set_item_icon(-1, folder_thumbnail);
852 } else {
853 item_list->set_item_icon(-1, theme_cache.folder);
854 }
855
856 Dictionary d;
857 d["name"] = dir_name;
858 d["path"] = cdir.path_join(dir_name);
859 d["dir"] = true;
860
861 item_list->set_item_metadata(-1, d);
862 item_list->set_item_icon_modulate(-1, theme_cache.folder_icon_color);
863
864 dirs.pop_front();
865 }
866
867 List<String> patterns;
868 // build filter
869 if (filter->get_selected() == filter->get_item_count() - 1) {
870 // match all
871 } else if (filters.size() > 1 && filter->get_selected() == 0) {
872 // match all filters
873 for (int i = 0; i < filters.size(); i++) {
874 String f = filters[i].get_slice(";", 0);
875 for (int j = 0; j < f.get_slice_count(","); j++) {
876 patterns.push_back(f.get_slice(",", j).strip_edges());
877 }
878 }
879 } else {
880 int idx = filter->get_selected();
881 if (filters.size() > 1) {
882 idx--;
883 }
884
885 if (idx >= 0 && idx < filters.size()) {
886 String f = filters[idx].get_slice(";", 0);
887 for (int j = 0; j < f.get_slice_count(","); j++) {
888 patterns.push_back(f.get_slice(",", j).strip_edges());
889 }
890 }
891 }
892
893 while (!files.is_empty()) {
894 bool match = patterns.is_empty();
895
896 for (const String &E : patterns) {
897 if (files.front()->get().matchn(E)) {
898 match = true;
899 break;
900 }
901 }
902
903 if (match) {
904 item_list->add_item(files.front()->get());
905
906 if (get_icon_func) {
907 Ref<Texture2D> icon = get_icon_func(cdir.path_join(files.front()->get()));
908 if (display_mode == DISPLAY_THUMBNAILS) {
909 Ref<Texture2D> thumbnail;
910 if (get_thumbnail_func) {
911 thumbnail = get_thumbnail_func(cdir.path_join(files.front()->get()));
912 }
913 if (thumbnail.is_null()) {
914 thumbnail = file_thumbnail;
915 }
916
917 item_list->set_item_icon(-1, thumbnail);
918 item_list->set_item_tag_icon(-1, icon);
919 } else {
920 item_list->set_item_icon(-1, icon);
921 }
922 }
923
924 Dictionary d;
925 d["name"] = files.front()->get();
926 d["dir"] = false;
927 String fullpath = cdir.path_join(files.front()->get());
928 d["path"] = fullpath;
929 item_list->set_item_metadata(-1, d);
930
931 if (display_mode == DISPLAY_THUMBNAILS && previews_enabled) {
932 EditorResourcePreview::get_singleton()->queue_resource_preview(fullpath, this, "_thumbnail_result", fullpath);
933 }
934
935 if (file->get_text() == files.front()->get()) {
936 item_list->set_current(item_list->get_item_count() - 1);
937 }
938 }
939
940 files.pop_front();
941 }
942
943 if (favorites->get_current() >= 0) {
944 favorites->deselect(favorites->get_current());
945 }
946
947 favorite->set_pressed(false);
948 fav_up->set_disabled(true);
949 fav_down->set_disabled(true);
950 get_ok_button()->set_disabled(_is_open_should_be_disabled());
951 for (int i = 0; i < favorites->get_item_count(); i++) {
952 if (favorites->get_item_metadata(i) == cdir || favorites->get_item_metadata(i) == cdir + "/") {
953 favorites->select(i);
954 favorite->set_pressed(true);
955 if (i > 0) {
956 fav_up->set_disabled(false);
957 }
958 if (i < favorites->get_item_count() - 1) {
959 fav_down->set_disabled(false);
960 }
961 break;
962 }
963 }
964}
965
966void EditorFileDialog::_filter_selected(int) {
967 update_file_name();
968 update_file_list();
969}
970
971void EditorFileDialog::update_filters() {
972 filter->clear();
973
974 if (filters.size() > 1) {
975 String all_filters;
976
977 const int max_filters = 5;
978
979 for (int i = 0; i < MIN(max_filters, filters.size()); i++) {
980 String flt = filters[i].get_slice(";", 0).strip_edges();
981 if (i > 0) {
982 all_filters += ", ";
983 }
984 all_filters += flt;
985 }
986
987 if (max_filters < filters.size()) {
988 all_filters += ", ...";
989 }
990
991 filter->add_item(TTR("All Recognized") + " (" + all_filters + ")");
992 }
993 for (int i = 0; i < filters.size(); i++) {
994 String flt = filters[i].get_slice(";", 0).strip_edges();
995 String desc = filters[i].get_slice(";", 1).strip_edges();
996 if (desc.length()) {
997 filter->add_item(desc + " (" + flt + ")");
998 } else {
999 filter->add_item("(" + flt + ")");
1000 }
1001 }
1002
1003 filter->add_item(TTR("All Files (*)"));
1004}
1005
1006void EditorFileDialog::clear_filters() {
1007 filters.clear();
1008 update_filters();
1009 invalidate();
1010}
1011
1012void EditorFileDialog::add_filter(const String &p_filter, const String &p_description) {
1013 if (p_description.is_empty()) {
1014 filters.push_back(p_filter);
1015 } else {
1016 filters.push_back(vformat("%s ; %s", p_filter, p_description));
1017 }
1018 update_filters();
1019 invalidate();
1020}
1021
1022void EditorFileDialog::set_filters(const Vector<String> &p_filters) {
1023 if (filters == p_filters) {
1024 return;
1025 }
1026 filters = p_filters;
1027 update_filters();
1028 invalidate();
1029}
1030
1031Vector<String> EditorFileDialog::get_filters() const {
1032 return filters;
1033}
1034
1035String EditorFileDialog::get_current_dir() const {
1036 return dir_access->get_current_dir();
1037}
1038
1039String EditorFileDialog::get_current_file() const {
1040 return file->get_text();
1041}
1042
1043String EditorFileDialog::get_current_path() const {
1044 return dir_access->get_current_dir().path_join(file->get_text());
1045}
1046
1047void EditorFileDialog::set_current_dir(const String &p_dir) {
1048 if (p_dir.is_relative_path()) {
1049 dir_access->change_dir(OS::get_singleton()->get_resource_dir());
1050 }
1051 dir_access->change_dir(p_dir);
1052 update_dir();
1053 invalidate();
1054}
1055
1056void EditorFileDialog::set_current_file(const String &p_file) {
1057 file->set_text(p_file);
1058 update_dir();
1059 invalidate();
1060 _focus_file_text();
1061
1062 if (is_visible()) {
1063 _request_single_thumbnail(get_current_dir().path_join(get_current_file()));
1064 }
1065}
1066
1067void EditorFileDialog::set_current_path(const String &p_path) {
1068 if (!p_path.size()) {
1069 return;
1070 }
1071 int pos = MAX(p_path.rfind("/"), p_path.rfind("\\"));
1072 if (pos == -1) {
1073 set_current_file(p_path);
1074 } else {
1075 String path_dir = p_path.substr(0, pos);
1076 String path_file = p_path.substr(pos + 1, p_path.length());
1077 set_current_dir(path_dir);
1078 set_current_file(path_file);
1079 }
1080}
1081
1082void EditorFileDialog::set_file_mode(FileMode p_mode) {
1083 mode = p_mode;
1084 switch (mode) {
1085 case FILE_MODE_OPEN_FILE:
1086 set_ok_button_text(TTR("Open"));
1087 set_title(TTR("Open a File"));
1088 can_create_dir = false;
1089 break;
1090 case FILE_MODE_OPEN_FILES:
1091 set_ok_button_text(TTR("Open"));
1092 set_title(TTR("Open File(s)"));
1093 can_create_dir = false;
1094 break;
1095 case FILE_MODE_OPEN_DIR:
1096 set_ok_button_text(TTR("Open"));
1097 set_title(TTR("Open a Directory"));
1098 can_create_dir = true;
1099 break;
1100 case FILE_MODE_OPEN_ANY:
1101 set_ok_button_text(TTR("Open"));
1102 set_title(TTR("Open a File or Directory"));
1103 can_create_dir = true;
1104 break;
1105 case FILE_MODE_SAVE_FILE:
1106 set_ok_button_text(TTR("Save"));
1107 set_title(TTR("Save a File"));
1108 can_create_dir = true;
1109 break;
1110 }
1111
1112 if (mode == FILE_MODE_OPEN_FILES) {
1113 item_list->set_select_mode(ItemList::SELECT_MULTI);
1114 } else {
1115 item_list->set_select_mode(ItemList::SELECT_SINGLE);
1116 }
1117
1118 if (can_create_dir) {
1119 makedir->show();
1120 } else {
1121 makedir->hide();
1122 }
1123}
1124
1125EditorFileDialog::FileMode EditorFileDialog::get_file_mode() const {
1126 return mode;
1127}
1128
1129void EditorFileDialog::set_access(Access p_access) {
1130 ERR_FAIL_INDEX(p_access, 3);
1131 if (access == p_access) {
1132 return;
1133 }
1134 switch (p_access) {
1135 case ACCESS_FILESYSTEM: {
1136 dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
1137 } break;
1138 case ACCESS_RESOURCES: {
1139 dir_access = DirAccess::create(DirAccess::ACCESS_RESOURCES);
1140 } break;
1141 case ACCESS_USERDATA: {
1142 dir_access = DirAccess::create(DirAccess::ACCESS_USERDATA);
1143 } break;
1144 }
1145 access = p_access;
1146 _update_drives();
1147 invalidate();
1148 update_filters();
1149 update_dir();
1150}
1151
1152void EditorFileDialog::invalidate() {
1153 if (!is_visible() || is_invalidating) {
1154 return;
1155 }
1156
1157 is_invalidating = true;
1158 callable_mp(this, &EditorFileDialog::_invalidate).call_deferred();
1159}
1160
1161void EditorFileDialog::_invalidate() {
1162 if (!is_invalidating) {
1163 return;
1164 }
1165
1166 update_file_list();
1167 _update_favorites();
1168 _update_recent();
1169
1170 is_invalidating = false;
1171}
1172
1173EditorFileDialog::Access EditorFileDialog::get_access() const {
1174 return access;
1175}
1176
1177void EditorFileDialog::_make_dir_confirm() {
1178 const String stripped_dirname = makedirname->get_text().strip_edges();
1179
1180 if (dir_access->dir_exists(stripped_dirname)) {
1181 error_dialog->set_text(TTR("Could not create folder. File with that name already exists."));
1182 error_dialog->popup_centered(Size2(250, 50) * EDSCALE);
1183 makedirname->set_text(""); // Reset label.
1184 return;
1185 }
1186
1187 Error err = dir_access->make_dir(stripped_dirname);
1188 if (err == OK) {
1189 dir_access->change_dir(stripped_dirname);
1190 invalidate();
1191 update_filters();
1192 update_dir();
1193 _push_history();
1194 if (access != ACCESS_FILESYSTEM) {
1195 EditorFileSystem::get_singleton()->scan_changes(); //we created a dir, so rescan changes
1196 }
1197 } else {
1198 error_dialog->set_text(TTR("Could not create folder."));
1199 error_dialog->popup_centered(Size2(250, 50) * EDSCALE);
1200 }
1201 makedirname->set_text(""); // reset label
1202}
1203
1204void EditorFileDialog::_make_dir() {
1205 makedialog->popup_centered(Size2(250, 80) * EDSCALE);
1206 makedirname->grab_focus();
1207}
1208
1209void EditorFileDialog::_delete_items() {
1210 // Collect the selected folders and files to delete and check them in the deletion dependency dialog.
1211 Vector<String> folders;
1212 Vector<String> files;
1213 for (int i = 0; i < item_list->get_item_count(); i++) {
1214 if (!item_list->is_selected(i)) {
1215 continue;
1216 }
1217 Dictionary item_meta = item_list->get_item_metadata(i);
1218 if (item_meta["dir"]) {
1219 folders.push_back(item_meta["path"]);
1220 } else {
1221 files.push_back(item_meta["path"]);
1222 }
1223 }
1224 if (folders.size() + files.size() > 0) {
1225 if (access == ACCESS_FILESYSTEM) {
1226 global_remove_dialog->popup_centered();
1227 } else {
1228 dep_remove_dialog->reset_size();
1229 dep_remove_dialog->show(folders, files);
1230 }
1231 }
1232}
1233
1234void EditorFileDialog::_delete_files_global() {
1235 // Delete files outside of the project directory without dependency checks.
1236 for (int i = 0; i < item_list->get_item_count(); i++) {
1237 if (!item_list->is_selected(i)) {
1238 continue;
1239 }
1240 Dictionary item_meta = item_list->get_item_metadata(i);
1241 // Only delete empty directories for safety.
1242 dir_access->remove(item_meta["path"]);
1243 }
1244 update_file_list();
1245}
1246
1247void EditorFileDialog::_select_drive(int p_idx) {
1248 String d = drives->get_item_text(p_idx);
1249 dir_access->change_dir(d);
1250 file->set_text("");
1251 invalidate();
1252 update_dir();
1253 _push_history();
1254}
1255
1256void EditorFileDialog::_update_drives(bool p_select) {
1257 int dc = dir_access->get_drive_count();
1258 if (dc == 0 || access != ACCESS_FILESYSTEM) {
1259 drives->hide();
1260 } else {
1261 drives->clear();
1262 Node *dp = drives->get_parent();
1263 if (dp) {
1264 dp->remove_child(drives);
1265 }
1266 dp = dir_access->drives_are_shortcuts() ? shortcuts_container : drives_container;
1267 dp->add_child(drives);
1268 drives->show();
1269
1270 for (int i = 0; i < dir_access->get_drive_count(); i++) {
1271 String d = dir_access->get_drive(i);
1272 drives->add_item(dir_access->get_drive(i));
1273 }
1274 if (p_select) {
1275 drives->select(dir_access->get_current_drive());
1276 }
1277 }
1278}
1279
1280void EditorFileDialog::_update_icons() {
1281 // Update icons.
1282
1283 mode_thumbnails->set_icon(theme_cache.mode_thumbnails);
1284 mode_list->set_icon(theme_cache.mode_list);
1285
1286 if (is_layout_rtl()) {
1287 dir_prev->set_icon(theme_cache.forward_folder);
1288 dir_next->set_icon(theme_cache.back_folder);
1289 } else {
1290 dir_prev->set_icon(theme_cache.back_folder);
1291 dir_next->set_icon(theme_cache.forward_folder);
1292 }
1293 dir_up->set_icon(theme_cache.parent_folder);
1294
1295 refresh->set_icon(theme_cache.reload);
1296 favorite->set_icon(theme_cache.favorite);
1297 show_hidden->set_icon(theme_cache.toggle_hidden);
1298
1299 fav_up->set_icon(theme_cache.favorites_up);
1300 fav_down->set_icon(theme_cache.favorites_down);
1301}
1302
1303void EditorFileDialog::_favorite_selected(int p_idx) {
1304 Error change_dir_result = dir_access->change_dir(favorites->get_item_metadata(p_idx));
1305 if (change_dir_result != OK) {
1306 error_dialog->set_text(TTR("Favorited folder does not exist anymore and will be removed."));
1307 error_dialog->popup_centered(Size2(250, 50) * EDSCALE);
1308
1309 bool res = (access == ACCESS_RESOURCES);
1310
1311 Vector<String> favorited = EditorSettings::get_singleton()->get_favorites();
1312 String dir_to_remove = favorites->get_item_metadata(p_idx);
1313
1314 bool found = false;
1315 for (int i = 0; i < favorited.size(); i++) {
1316 bool cres = favorited[i].begins_with("res://");
1317 if (cres != res) {
1318 continue;
1319 }
1320
1321 if (favorited[i] == dir_to_remove) {
1322 found = true;
1323 break;
1324 }
1325 }
1326
1327 if (found) {
1328 favorited.erase(favorites->get_item_metadata(p_idx));
1329 favorites->remove_item(p_idx);
1330 EditorSettings::get_singleton()->set_favorites(favorited);
1331 }
1332 } else {
1333 update_dir();
1334 invalidate();
1335 _push_history();
1336 }
1337}
1338
1339void EditorFileDialog::_favorite_move_up() {
1340 int current = favorites->get_current();
1341
1342 if (current > 0 && current < favorites->get_item_count()) {
1343 Vector<String> favorited = EditorSettings::get_singleton()->get_favorites();
1344
1345 int a_idx = favorited.find(String(favorites->get_item_metadata(current - 1)));
1346 int b_idx = favorited.find(String(favorites->get_item_metadata(current)));
1347
1348 if (a_idx == -1 || b_idx == -1) {
1349 return;
1350 }
1351 SWAP(favorited.write[a_idx], favorited.write[b_idx]);
1352
1353 EditorSettings::get_singleton()->set_favorites(favorited);
1354
1355 _update_favorites();
1356 update_file_list();
1357 }
1358}
1359
1360void EditorFileDialog::_favorite_move_down() {
1361 int current = favorites->get_current();
1362
1363 if (current >= 0 && current < favorites->get_item_count() - 1) {
1364 Vector<String> favorited = EditorSettings::get_singleton()->get_favorites();
1365
1366 int a_idx = favorited.find(String(favorites->get_item_metadata(current + 1)));
1367 int b_idx = favorited.find(String(favorites->get_item_metadata(current)));
1368
1369 if (a_idx == -1 || b_idx == -1) {
1370 return;
1371 }
1372 SWAP(favorited.write[a_idx], favorited.write[b_idx]);
1373
1374 EditorSettings::get_singleton()->set_favorites(favorited);
1375
1376 _update_favorites();
1377 update_file_list();
1378 }
1379}
1380
1381void EditorFileDialog::_update_favorites() {
1382 bool res = (access == ACCESS_RESOURCES);
1383
1384 String current = get_current_dir();
1385 favorites->clear();
1386
1387 favorite->set_pressed(false);
1388
1389 Vector<String> favorited = EditorSettings::get_singleton()->get_favorites();
1390 Vector<String> favorited_paths;
1391 Vector<String> favorited_names;
1392
1393 bool fav_changed = false;
1394 int current_favorite = -1;
1395 for (int i = 0; i < favorited.size(); i++) {
1396 bool cres = favorited[i].begins_with("res://");
1397 if (cres != res) {
1398 continue;
1399 }
1400
1401 if (!dir_access->dir_exists(favorited[i])) {
1402 // Remove invalid directory from the list of Favorited directories.
1403 favorited.remove_at(i--);
1404 fav_changed = true;
1405 continue;
1406 }
1407
1408 // Compute favorite display text.
1409 String name = favorited[i];
1410 if (res && name == "res://") {
1411 if (name == current) {
1412 current_favorite = favorited_paths.size();
1413 }
1414 name = "/";
1415 favorited_paths.append(favorited[i]);
1416 favorited_names.append(name);
1417 } else if (name.ends_with("/")) {
1418 if (name == current || name == current + "/") {
1419 current_favorite = favorited_paths.size();
1420 }
1421 name = name.substr(0, name.length() - 1);
1422 name = name.get_file();
1423 favorited_paths.append(favorited[i]);
1424 favorited_names.append(name);
1425 } else {
1426 // Ignore favorited files.
1427 }
1428 }
1429
1430 if (fav_changed) {
1431 EditorSettings::get_singleton()->set_favorites(favorited);
1432 }
1433
1434 EditorNode::disambiguate_filenames(favorited_paths, favorited_names);
1435
1436 for (int i = 0; i < favorited_paths.size(); i++) {
1437 favorites->add_item(favorited_names[i], theme_cache.folder);
1438 favorites->set_item_metadata(-1, favorited_paths[i]);
1439 favorites->set_item_icon_modulate(-1, theme_cache.folder_icon_color);
1440
1441 if (i == current_favorite) {
1442 favorite->set_pressed(true);
1443 favorites->set_current(favorites->get_item_count() - 1);
1444 recent->deselect_all();
1445 }
1446 }
1447}
1448
1449void EditorFileDialog::_favorite_pressed() {
1450 bool res = (access == ACCESS_RESOURCES);
1451
1452 String cd = get_current_dir();
1453 if (!cd.ends_with("/")) {
1454 cd += "/";
1455 }
1456
1457 Vector<String> favorited = EditorSettings::get_singleton()->get_favorites();
1458
1459 bool found = false;
1460 for (int i = 0; i < favorited.size(); i++) {
1461 bool cres = favorited[i].begins_with("res://");
1462 if (cres != res) {
1463 continue;
1464 }
1465
1466 if (favorited[i] == cd) {
1467 found = true;
1468 break;
1469 }
1470 }
1471
1472 if (found) {
1473 favorited.erase(cd);
1474 } else {
1475 favorited.push_back(cd);
1476 }
1477
1478 EditorSettings::get_singleton()->set_favorites(favorited);
1479
1480 _update_favorites();
1481}
1482
1483void EditorFileDialog::_update_recent() {
1484 recent->clear();
1485
1486 bool res = (access == ACCESS_RESOURCES);
1487 Vector<String> recentd = EditorSettings::get_singleton()->get_recent_dirs();
1488 Vector<String> recentd_paths;
1489 Vector<String> recentd_names;
1490
1491 for (int i = 0; i < recentd.size(); i++) {
1492 bool cres = recentd[i].begins_with("res://");
1493 if (cres != res) {
1494 continue;
1495 }
1496
1497 if (!dir_access->dir_exists(recentd[i])) {
1498 // Remove invalid directory from the list of Recent directories.
1499 recentd.remove_at(i--);
1500 continue;
1501 }
1502
1503 // Compute recent directory display text.
1504 String name = recentd[i];
1505 if (res && name == "res://") {
1506 name = "/";
1507 } else {
1508 if (name.ends_with("/")) {
1509 name = name.substr(0, name.length() - 1);
1510 }
1511 name = name.get_file();
1512 }
1513 recentd_paths.append(recentd[i]);
1514 recentd_names.append(name);
1515 }
1516
1517 EditorNode::disambiguate_filenames(recentd_paths, recentd_names);
1518
1519 for (int i = 0; i < recentd_paths.size(); i++) {
1520 recent->add_item(recentd_names[i], theme_cache.folder);
1521 recent->set_item_metadata(-1, recentd_paths[i]);
1522 recent->set_item_icon_modulate(-1, theme_cache.folder_icon_color);
1523 }
1524 EditorSettings::get_singleton()->set_recent_dirs(recentd);
1525}
1526
1527void EditorFileDialog::_recent_selected(int p_idx) {
1528 Vector<String> recentd = EditorSettings::get_singleton()->get_recent_dirs();
1529 ERR_FAIL_INDEX(p_idx, recentd.size());
1530
1531 dir_access->change_dir(recent->get_item_metadata(p_idx));
1532 update_file_list();
1533 update_dir();
1534 _push_history();
1535}
1536
1537void EditorFileDialog::_go_up() {
1538 dir_access->change_dir(get_current_dir().trim_suffix("/").get_base_dir());
1539 update_file_list();
1540 update_dir();
1541 _push_history();
1542}
1543
1544void EditorFileDialog::_go_back() {
1545 if (local_history_pos <= 0) {
1546 return;
1547 }
1548
1549 local_history_pos--;
1550 dir_access->change_dir(local_history[local_history_pos]);
1551 update_file_list();
1552 update_dir();
1553
1554 dir_prev->set_disabled(local_history_pos == 0);
1555 dir_next->set_disabled(local_history_pos == local_history.size() - 1);
1556}
1557
1558void EditorFileDialog::_go_forward() {
1559 if (local_history_pos >= local_history.size() - 1) {
1560 return;
1561 }
1562
1563 local_history_pos++;
1564 dir_access->change_dir(local_history[local_history_pos]);
1565 update_file_list();
1566 update_dir();
1567
1568 dir_prev->set_disabled(local_history_pos == 0);
1569 dir_next->set_disabled(local_history_pos == local_history.size() - 1);
1570}
1571
1572bool EditorFileDialog::default_show_hidden_files = false;
1573
1574EditorFileDialog::DisplayMode EditorFileDialog::default_display_mode = DISPLAY_THUMBNAILS;
1575
1576void EditorFileDialog::set_display_mode(DisplayMode p_mode) {
1577 if (display_mode == p_mode) {
1578 return;
1579 }
1580 if (p_mode == DISPLAY_THUMBNAILS) {
1581 mode_list->set_pressed(false);
1582 mode_thumbnails->set_pressed(true);
1583 } else {
1584 mode_thumbnails->set_pressed(false);
1585 mode_list->set_pressed(true);
1586 }
1587 display_mode = p_mode;
1588 invalidate();
1589}
1590
1591EditorFileDialog::DisplayMode EditorFileDialog::get_display_mode() const {
1592 return display_mode;
1593}
1594
1595void EditorFileDialog::_bind_methods() {
1596 ClassDB::bind_method(D_METHOD("_cancel_pressed"), &EditorFileDialog::_cancel_pressed);
1597
1598 ClassDB::bind_method(D_METHOD("clear_filters"), &EditorFileDialog::clear_filters);
1599 ClassDB::bind_method(D_METHOD("add_filter", "filter", "description"), &EditorFileDialog::add_filter, DEFVAL(""));
1600 ClassDB::bind_method(D_METHOD("set_filters", "filters"), &EditorFileDialog::set_filters);
1601 ClassDB::bind_method(D_METHOD("get_filters"), &EditorFileDialog::get_filters);
1602 ClassDB::bind_method(D_METHOD("get_current_dir"), &EditorFileDialog::get_current_dir);
1603 ClassDB::bind_method(D_METHOD("get_current_file"), &EditorFileDialog::get_current_file);
1604 ClassDB::bind_method(D_METHOD("get_current_path"), &EditorFileDialog::get_current_path);
1605 ClassDB::bind_method(D_METHOD("set_current_dir", "dir"), &EditorFileDialog::set_current_dir);
1606 ClassDB::bind_method(D_METHOD("set_current_file", "file"), &EditorFileDialog::set_current_file);
1607 ClassDB::bind_method(D_METHOD("set_current_path", "path"), &EditorFileDialog::set_current_path);
1608 ClassDB::bind_method(D_METHOD("set_file_mode", "mode"), &EditorFileDialog::set_file_mode);
1609 ClassDB::bind_method(D_METHOD("get_file_mode"), &EditorFileDialog::get_file_mode);
1610 ClassDB::bind_method(D_METHOD("get_vbox"), &EditorFileDialog::get_vbox);
1611 ClassDB::bind_method(D_METHOD("get_line_edit"), &EditorFileDialog::get_line_edit);
1612 ClassDB::bind_method(D_METHOD("set_access", "access"), &EditorFileDialog::set_access);
1613 ClassDB::bind_method(D_METHOD("get_access"), &EditorFileDialog::get_access);
1614 ClassDB::bind_method(D_METHOD("set_show_hidden_files", "show"), &EditorFileDialog::set_show_hidden_files);
1615 ClassDB::bind_method(D_METHOD("is_showing_hidden_files"), &EditorFileDialog::is_showing_hidden_files);
1616 ClassDB::bind_method(D_METHOD("_thumbnail_done"), &EditorFileDialog::_thumbnail_done);
1617 ClassDB::bind_method(D_METHOD("set_display_mode", "mode"), &EditorFileDialog::set_display_mode);
1618 ClassDB::bind_method(D_METHOD("get_display_mode"), &EditorFileDialog::get_display_mode);
1619 ClassDB::bind_method(D_METHOD("_thumbnail_result"), &EditorFileDialog::_thumbnail_result);
1620 ClassDB::bind_method(D_METHOD("set_disable_overwrite_warning", "disable"), &EditorFileDialog::set_disable_overwrite_warning);
1621 ClassDB::bind_method(D_METHOD("is_overwrite_warning_disabled"), &EditorFileDialog::is_overwrite_warning_disabled);
1622
1623 ClassDB::bind_method(D_METHOD("invalidate"), &EditorFileDialog::invalidate);
1624
1625 ADD_SIGNAL(MethodInfo("file_selected", PropertyInfo(Variant::STRING, "path")));
1626 ADD_SIGNAL(MethodInfo("files_selected", PropertyInfo(Variant::PACKED_STRING_ARRAY, "paths")));
1627 ADD_SIGNAL(MethodInfo("dir_selected", PropertyInfo(Variant::STRING, "dir")));
1628
1629 ADD_PROPERTY(PropertyInfo(Variant::INT, "access", PROPERTY_HINT_ENUM, "Resources,User data,File system"), "set_access", "get_access");
1630 ADD_PROPERTY(PropertyInfo(Variant::INT, "display_mode", PROPERTY_HINT_ENUM, "Thumbnails,List"), "set_display_mode", "get_display_mode");
1631 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");
1632 ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_dir", PROPERTY_HINT_DIR, "", PROPERTY_USAGE_NONE), "set_current_dir", "get_current_dir");
1633 ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_file", PROPERTY_HINT_FILE, "*", PROPERTY_USAGE_NONE), "set_current_file", "get_current_file");
1634 ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_current_path", "get_current_path");
1635 ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "filters"), "set_filters", "get_filters");
1636 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_hidden_files"), "set_show_hidden_files", "is_showing_hidden_files");
1637 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disable_overwrite_warning"), "set_disable_overwrite_warning", "is_overwrite_warning_disabled");
1638
1639 BIND_ENUM_CONSTANT(FILE_MODE_OPEN_FILE);
1640 BIND_ENUM_CONSTANT(FILE_MODE_OPEN_FILES);
1641 BIND_ENUM_CONSTANT(FILE_MODE_OPEN_DIR);
1642 BIND_ENUM_CONSTANT(FILE_MODE_OPEN_ANY);
1643 BIND_ENUM_CONSTANT(FILE_MODE_SAVE_FILE);
1644
1645 BIND_ENUM_CONSTANT(ACCESS_RESOURCES);
1646 BIND_ENUM_CONSTANT(ACCESS_USERDATA);
1647 BIND_ENUM_CONSTANT(ACCESS_FILESYSTEM);
1648
1649 BIND_ENUM_CONSTANT(DISPLAY_THUMBNAILS);
1650 BIND_ENUM_CONSTANT(DISPLAY_LIST);
1651}
1652
1653void EditorFileDialog::set_show_hidden_files(bool p_show) {
1654 if (p_show == show_hidden_files) {
1655 return;
1656 }
1657
1658 EditorSettings::get_singleton()->set("filesystem/file_dialog/show_hidden_files", p_show);
1659 show_hidden_files = p_show;
1660 show_hidden->set_pressed(p_show);
1661 invalidate();
1662}
1663
1664bool EditorFileDialog::is_showing_hidden_files() const {
1665 return show_hidden_files;
1666}
1667
1668void EditorFileDialog::set_default_show_hidden_files(bool p_show) {
1669 default_show_hidden_files = p_show;
1670}
1671
1672void EditorFileDialog::set_default_display_mode(DisplayMode p_mode) {
1673 default_display_mode = p_mode;
1674}
1675
1676void EditorFileDialog::_save_to_recent() {
1677 String cur_dir = get_current_dir();
1678 Vector<String> recent_new = EditorSettings::get_singleton()->get_recent_dirs();
1679
1680 const int max = 20;
1681 int count = 0;
1682 bool res = cur_dir.begins_with("res://");
1683
1684 for (int i = 0; i < recent_new.size(); i++) {
1685 bool cres = recent_new[i].begins_with("res://");
1686 if (recent_new[i] == cur_dir || (res == cres && count > max)) {
1687 recent_new.remove_at(i);
1688 i--;
1689 } else {
1690 count++;
1691 }
1692 }
1693
1694 recent_new.insert(0, cur_dir);
1695
1696 EditorSettings::get_singleton()->set_recent_dirs(recent_new);
1697}
1698
1699void EditorFileDialog::set_disable_overwrite_warning(bool p_disable) {
1700 disable_overwrite_warning = p_disable;
1701}
1702
1703bool EditorFileDialog::is_overwrite_warning_disabled() const {
1704 return disable_overwrite_warning;
1705}
1706
1707void EditorFileDialog::set_previews_enabled(bool p_enabled) {
1708 previews_enabled = p_enabled;
1709}
1710
1711bool EditorFileDialog::are_previews_enabled() {
1712 return previews_enabled;
1713}
1714
1715EditorFileDialog::EditorFileDialog() {
1716 show_hidden_files = default_show_hidden_files;
1717 display_mode = default_display_mode;
1718 VBoxContainer *vbc = memnew(VBoxContainer);
1719 add_child(vbc);
1720
1721 set_title(TTR("Save a File"));
1722
1723 ED_SHORTCUT("file_dialog/go_back", TTR("Go Back"), KeyModifierMask::ALT | Key::LEFT);
1724 ED_SHORTCUT("file_dialog/go_forward", TTR("Go Forward"), KeyModifierMask::ALT | Key::RIGHT);
1725 ED_SHORTCUT("file_dialog/go_up", TTR("Go Up"), KeyModifierMask::ALT | Key::UP);
1726 ED_SHORTCUT("file_dialog/refresh", TTR("Refresh"), Key::F5);
1727 ED_SHORTCUT("file_dialog/toggle_hidden_files", TTR("Toggle Hidden Files"), KeyModifierMask::CMD_OR_CTRL | Key::H);
1728 ED_SHORTCUT("file_dialog/toggle_favorite", TTR("Toggle Favorite"), KeyModifierMask::ALT | Key::F);
1729 ED_SHORTCUT("file_dialog/toggle_mode", TTR("Toggle Mode"), KeyModifierMask::ALT | Key::V);
1730 ED_SHORTCUT("file_dialog/create_folder", TTR("Create Folder"), KeyModifierMask::CMD_OR_CTRL | Key::N);
1731 ED_SHORTCUT("file_dialog/delete", TTR("Delete"), Key::KEY_DELETE);
1732 ED_SHORTCUT("file_dialog/focus_path", TTR("Focus Path"), KeyModifierMask::CMD_OR_CTRL | Key::D);
1733 ED_SHORTCUT("file_dialog/move_favorite_up", TTR("Move Favorite Up"), KeyModifierMask::CMD_OR_CTRL | Key::UP);
1734 ED_SHORTCUT("file_dialog/move_favorite_down", TTR("Move Favorite Down"), KeyModifierMask::CMD_OR_CTRL | Key::DOWN);
1735
1736 if (EditorSettings::get_singleton()) {
1737 ED_SHORTCUT_OVERRIDE("file_dialog/toggle_favorite", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::F);
1738 ED_SHORTCUT_OVERRIDE("file_dialog/toggle_mode", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::V);
1739 }
1740
1741 HBoxContainer *pathhb = memnew(HBoxContainer);
1742
1743 dir_prev = memnew(Button);
1744 dir_prev->set_flat(true);
1745 dir_prev->set_tooltip_text(TTR("Go to previous folder."));
1746 dir_next = memnew(Button);
1747 dir_next->set_flat(true);
1748 dir_next->set_tooltip_text(TTR("Go to next folder."));
1749 dir_up = memnew(Button);
1750 dir_up->set_flat(true);
1751 dir_up->set_tooltip_text(TTR("Go to parent folder."));
1752
1753 pathhb->add_child(dir_prev);
1754 pathhb->add_child(dir_next);
1755 pathhb->add_child(dir_up);
1756
1757 dir_prev->connect("pressed", callable_mp(this, &EditorFileDialog::_go_back));
1758 dir_next->connect("pressed", callable_mp(this, &EditorFileDialog::_go_forward));
1759 dir_up->connect("pressed", callable_mp(this, &EditorFileDialog::_go_up));
1760
1761 Label *l = memnew(Label(TTR("Path:")));
1762 l->set_theme_type_variation("HeaderSmall");
1763 pathhb->add_child(l);
1764
1765 drives_container = memnew(HBoxContainer);
1766 pathhb->add_child(drives_container);
1767
1768 dir = memnew(LineEdit);
1769 dir->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE);
1770 pathhb->add_child(dir);
1771 dir->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1772
1773 refresh = memnew(Button);
1774 refresh->set_flat(true);
1775 refresh->set_tooltip_text(TTR("Refresh files."));
1776 refresh->connect("pressed", callable_mp(this, &EditorFileDialog::update_file_list));
1777 pathhb->add_child(refresh);
1778
1779 favorite = memnew(Button);
1780 favorite->set_flat(true);
1781 favorite->set_toggle_mode(true);
1782 favorite->set_tooltip_text(TTR("(Un)favorite current folder."));
1783 favorite->connect("pressed", callable_mp(this, &EditorFileDialog::_favorite_pressed));
1784 pathhb->add_child(favorite);
1785
1786 show_hidden = memnew(Button);
1787 show_hidden->set_flat(true);
1788 show_hidden->set_toggle_mode(true);
1789 show_hidden->set_pressed(is_showing_hidden_files());
1790 show_hidden->set_tooltip_text(TTR("Toggle the visibility of hidden files."));
1791 show_hidden->connect("toggled", callable_mp(this, &EditorFileDialog::set_show_hidden_files));
1792 pathhb->add_child(show_hidden);
1793
1794 pathhb->add_child(memnew(VSeparator));
1795
1796 Ref<ButtonGroup> view_mode_group;
1797 view_mode_group.instantiate();
1798
1799 mode_thumbnails = memnew(Button);
1800 mode_thumbnails->set_flat(true);
1801 mode_thumbnails->connect("pressed", callable_mp(this, &EditorFileDialog::set_display_mode).bind(DISPLAY_THUMBNAILS));
1802 mode_thumbnails->set_toggle_mode(true);
1803 mode_thumbnails->set_pressed(display_mode == DISPLAY_THUMBNAILS);
1804 mode_thumbnails->set_button_group(view_mode_group);
1805 mode_thumbnails->set_tooltip_text(TTR("View items as a grid of thumbnails."));
1806 pathhb->add_child(mode_thumbnails);
1807
1808 mode_list = memnew(Button);
1809 mode_list->set_flat(true);
1810 mode_list->connect("pressed", callable_mp(this, &EditorFileDialog::set_display_mode).bind(DISPLAY_LIST));
1811 mode_list->set_toggle_mode(true);
1812 mode_list->set_pressed(display_mode == DISPLAY_LIST);
1813 mode_list->set_button_group(view_mode_group);
1814 mode_list->set_tooltip_text(TTR("View items as a list."));
1815 pathhb->add_child(mode_list);
1816
1817 shortcuts_container = memnew(HBoxContainer);
1818 pathhb->add_child(shortcuts_container);
1819
1820 drives = memnew(OptionButton);
1821 drives->connect("item_selected", callable_mp(this, &EditorFileDialog::_select_drive));
1822 pathhb->add_child(drives);
1823
1824 makedir = memnew(Button);
1825 makedir->set_text(TTR("Create Folder"));
1826 makedir->connect("pressed", callable_mp(this, &EditorFileDialog::_make_dir));
1827 pathhb->add_child(makedir);
1828
1829 list_hb = memnew(HSplitContainer);
1830
1831 vbc->add_child(pathhb);
1832 vbc->add_child(list_hb);
1833 list_hb->set_v_size_flags(Control::SIZE_EXPAND_FILL);
1834
1835 VSplitContainer *vsc = memnew(VSplitContainer);
1836 list_hb->add_child(vsc);
1837
1838 VBoxContainer *fav_vb = memnew(VBoxContainer);
1839 vsc->add_child(fav_vb);
1840 fav_vb->set_custom_minimum_size(Size2(150, 100) * EDSCALE);
1841 fav_vb->set_v_size_flags(Control::SIZE_EXPAND_FILL);
1842 HBoxContainer *fav_hb = memnew(HBoxContainer);
1843 fav_vb->add_child(fav_hb);
1844
1845 l = memnew(Label(TTR("Favorites:")));
1846 l->set_theme_type_variation("HeaderSmall");
1847 fav_hb->add_child(l);
1848
1849 fav_hb->add_spacer();
1850 fav_up = memnew(Button);
1851 fav_up->set_flat(true);
1852 fav_hb->add_child(fav_up);
1853 fav_up->connect("pressed", callable_mp(this, &EditorFileDialog::_favorite_move_up));
1854 fav_down = memnew(Button);
1855 fav_down->set_flat(true);
1856 fav_hb->add_child(fav_down);
1857 fav_down->connect("pressed", callable_mp(this, &EditorFileDialog::_favorite_move_down));
1858
1859 favorites = memnew(ItemList);
1860 fav_vb->add_child(favorites);
1861 favorites->set_v_size_flags(Control::SIZE_EXPAND_FILL);
1862 favorites->connect("item_selected", callable_mp(this, &EditorFileDialog::_favorite_selected));
1863
1864 VBoxContainer *rec_vb = memnew(VBoxContainer);
1865 vsc->add_child(rec_vb);
1866 rec_vb->set_custom_minimum_size(Size2(150, 100) * EDSCALE);
1867 rec_vb->set_v_size_flags(Control::SIZE_EXPAND_FILL);
1868 recent = memnew(ItemList);
1869 recent->set_allow_reselect(true);
1870 rec_vb->add_margin_child(TTR("Recent:"), recent, true);
1871 recent->connect("item_selected", callable_mp(this, &EditorFileDialog::_recent_selected));
1872
1873 VBoxContainer *item_vb = memnew(VBoxContainer);
1874 list_hb->add_child(item_vb);
1875 item_vb->set_custom_minimum_size(Size2(320, 0) * EDSCALE);
1876
1877 HBoxContainer *preview_hb = memnew(HBoxContainer);
1878 preview_hb->set_v_size_flags(Control::SIZE_EXPAND_FILL);
1879 item_vb->add_child(preview_hb);
1880
1881 VBoxContainer *list_vb = memnew(VBoxContainer);
1882 list_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1883
1884 l = memnew(Label(TTR("Directories & Files:")));
1885 l->set_theme_type_variation("HeaderSmall");
1886 list_vb->add_child(l);
1887 preview_hb->add_child(list_vb);
1888
1889 // Item (files and folders) list with context menu.
1890
1891 item_list = memnew(ItemList);
1892 item_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
1893 item_list->connect("item_clicked", callable_mp(this, &EditorFileDialog::_item_list_item_rmb_clicked));
1894 item_list->connect("empty_clicked", callable_mp(this, &EditorFileDialog::_item_list_empty_clicked));
1895 item_list->set_allow_rmb_select(true);
1896
1897 list_vb->add_child(item_list);
1898
1899 item_menu = memnew(PopupMenu);
1900 item_menu->connect("id_pressed", callable_mp(this, &EditorFileDialog::_item_menu_id_pressed));
1901 add_child(item_menu);
1902
1903 // Other stuff.
1904
1905 preview_vb = memnew(VBoxContainer);
1906 preview_hb->add_child(preview_vb);
1907 CenterContainer *prev_cc = memnew(CenterContainer);
1908 preview_vb->add_margin_child(TTR("Preview:"), prev_cc);
1909 preview = memnew(TextureRect);
1910 prev_cc->add_child(preview);
1911 preview_vb->hide();
1912
1913 file_box = memnew(HBoxContainer);
1914
1915 l = memnew(Label(TTR("File:")));
1916 l->set_theme_type_variation("HeaderSmall");
1917 file_box->add_child(l);
1918
1919 file = memnew(LineEdit);
1920 file->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE);
1921 file->set_stretch_ratio(4);
1922 file->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1923 file_box->add_child(file);
1924 filter = memnew(OptionButton);
1925 filter->set_stretch_ratio(3);
1926 filter->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1927 filter->set_clip_text(true); // Too many extensions overflow it.
1928 file_box->add_child(filter);
1929 file_box->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1930 item_vb->add_child(file_box);
1931
1932 dir_access = DirAccess::create(DirAccess::ACCESS_RESOURCES);
1933 _update_drives();
1934
1935 connect("confirmed", callable_mp(this, &EditorFileDialog::_action_pressed));
1936 item_list->connect("item_selected", callable_mp(this, &EditorFileDialog::_item_selected), CONNECT_DEFERRED);
1937 item_list->connect("multi_selected", callable_mp(this, &EditorFileDialog::_multi_selected), CONNECT_DEFERRED);
1938 item_list->connect("item_activated", callable_mp(this, &EditorFileDialog::_item_dc_selected).bind());
1939 item_list->connect("empty_clicked", callable_mp(this, &EditorFileDialog::_items_clear_selection));
1940 dir->connect("text_submitted", callable_mp(this, &EditorFileDialog::_dir_submitted));
1941 file->connect("text_submitted", callable_mp(this, &EditorFileDialog::_file_submitted));
1942 filter->connect("item_selected", callable_mp(this, &EditorFileDialog::_filter_selected));
1943
1944 confirm_save = memnew(ConfirmationDialog);
1945 add_child(confirm_save);
1946 confirm_save->connect("confirmed", callable_mp(this, &EditorFileDialog::_save_confirm_pressed));
1947
1948 dep_remove_dialog = memnew(DependencyRemoveDialog);
1949 add_child(dep_remove_dialog);
1950
1951 global_remove_dialog = memnew(ConfirmationDialog);
1952 global_remove_dialog->set_text(TTR("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."));
1953 global_remove_dialog->connect("confirmed", callable_mp(this, &EditorFileDialog::_delete_files_global));
1954 add_child(global_remove_dialog);
1955
1956 makedialog = memnew(ConfirmationDialog);
1957 makedialog->set_title(TTR("Create Folder"));
1958 VBoxContainer *makevb = memnew(VBoxContainer);
1959 makedialog->add_child(makevb);
1960
1961 makedirname = memnew(LineEdit);
1962 makedirname->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE);
1963 makevb->add_margin_child(TTR("Name:"), makedirname);
1964 add_child(makedialog);
1965 makedialog->register_text_enter(makedirname);
1966 makedialog->connect("confirmed", callable_mp(this, &EditorFileDialog::_make_dir_confirm));
1967 error_dialog = memnew(AcceptDialog);
1968 add_child(error_dialog);
1969
1970 update_filters();
1971 update_dir();
1972
1973 set_hide_on_ok(false);
1974 vbox = vbc;
1975
1976 if (register_func) {
1977 register_func(this);
1978 }
1979}
1980
1981EditorFileDialog::~EditorFileDialog() {
1982 if (unregister_func) {
1983 unregister_func(this);
1984 }
1985}
1986