1 /*************************************************************************/
2 /* file_dialog.cpp */
3 /*************************************************************************/
4 /* This file is part of: */
5 /* GODOT ENGINE */
6 /* https://godotengine.org */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
9 /* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
10 /* */
11 /* Permission is hereby granted, free of charge, to any person obtaining */
12 /* a copy of this software and associated documentation files (the */
13 /* "Software"), to deal in the Software without restriction, including */
14 /* without limitation the rights to use, copy, modify, merge, publish, */
15 /* distribute, sublicense, and/or sell copies of the Software, and to */
16 /* permit persons to whom the Software is furnished to do so, subject to */
17 /* the following conditions: */
18 /* */
19 /* The above copyright notice and this permission notice shall be */
20 /* included in all copies or substantial portions of the Software. */
21 /* */
22 /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23 /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24 /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
25 /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26 /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27 /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29 /*************************************************************************/
30
31 #include "file_dialog.h"
32
33 #include "core/os/keyboard.h"
34 #include "core/print_string.h"
35 #include "scene/gui/label.h"
36
37 FileDialog::GetIconFunc FileDialog::get_icon_func = NULL;
38 FileDialog::GetIconFunc FileDialog::get_large_icon_func = NULL;
39
40 FileDialog::RegisterFunc FileDialog::register_func = NULL;
41 FileDialog::RegisterFunc FileDialog::unregister_func = NULL;
42
get_vbox()43 VBoxContainer *FileDialog::get_vbox() {
44 return vbox;
45 }
46
_notification(int p_what)47 void FileDialog::_notification(int p_what) {
48
49 if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
50
51 if (p_what == NOTIFICATION_ENTER_TREE) {
52 dir_up->set_icon(get_icon("parent_folder"));
53 refresh->set_icon(get_icon("reload"));
54 show_hidden->set_icon(get_icon("toggle_hidden"));
55 }
56
57 Color font_color = get_color("font_color", "ToolButton");
58 Color font_color_hover = get_color("font_color_hover", "ToolButton");
59 Color font_color_pressed = get_color("font_color_pressed", "ToolButton");
60
61 dir_up->add_color_override("icon_color_normal", font_color);
62 dir_up->add_color_override("icon_color_hover", font_color_hover);
63 dir_up->add_color_override("icon_color_pressed", font_color_pressed);
64
65 refresh->add_color_override("icon_color_normal", font_color);
66 refresh->add_color_override("icon_color_hover", font_color_hover);
67 refresh->add_color_override("icon_color_pressed", font_color_pressed);
68
69 show_hidden->add_color_override("icon_color_normal", font_color);
70 show_hidden->add_color_override("icon_color_hover", font_color_hover);
71 show_hidden->add_color_override("icon_color_pressed", font_color_pressed);
72
73 } else if (p_what == NOTIFICATION_POPUP_HIDE) {
74
75 set_process_unhandled_input(false);
76 }
77 }
78
_unhandled_input(const Ref<InputEvent> & p_event)79 void FileDialog::_unhandled_input(const Ref<InputEvent> &p_event) {
80
81 Ref<InputEventKey> k = p_event;
82 if (k.is_valid() && is_window_modal_on_top()) {
83
84 if (k->is_pressed()) {
85
86 bool handled = true;
87
88 switch (k->get_scancode()) {
89
90 case KEY_H: {
91
92 if (k->get_command()) {
93 set_show_hidden_files(!show_hidden_files);
94 } else {
95 handled = false;
96 }
97
98 } break;
99 case KEY_F5: {
100
101 invalidate();
102 } break;
103 case KEY_BACKSPACE: {
104
105 _dir_entered("..");
106 } break;
107 default: {
108 handled = false;
109 }
110 }
111
112 if (handled)
113 accept_event();
114 }
115 }
116 }
117
set_enable_multiple_selection(bool p_enable)118 void FileDialog::set_enable_multiple_selection(bool p_enable) {
119
120 tree->set_select_mode(p_enable ? Tree::SELECT_MULTI : Tree::SELECT_SINGLE);
121 };
122
get_selected_files() const123 Vector<String> FileDialog::get_selected_files() const {
124
125 Vector<String> list;
126
127 TreeItem *item = tree->get_root();
128 while ((item = tree->get_next_selected(item))) {
129
130 list.push_back(dir_access->get_current_dir().plus_file(item->get_text(0)));
131 };
132
133 return list;
134 };
135
update_dir()136 void FileDialog::update_dir() {
137
138 dir->set_text(dir_access->get_current_dir_without_drive());
139
140 if (drives->is_visible()) {
141 drives->select(dir_access->get_current_drive());
142 }
143
144 // Deselect any item, to make "Select Current Folder" button text by default.
145 deselect_items();
146 }
147
_dir_entered(String p_dir)148 void FileDialog::_dir_entered(String p_dir) {
149
150 dir_access->change_dir(p_dir);
151 file->set_text("");
152 invalidate();
153 update_dir();
154 }
155
_file_entered(const String & p_file)156 void FileDialog::_file_entered(const String &p_file) {
157
158 _action_pressed();
159 }
160
_save_confirm_pressed()161 void FileDialog::_save_confirm_pressed() {
162 String f = dir_access->get_current_dir().plus_file(file->get_text());
163 emit_signal("file_selected", f);
164 hide();
165 }
166
_post_popup()167 void FileDialog::_post_popup() {
168
169 ConfirmationDialog::_post_popup();
170 if (invalidated) {
171 update_file_list();
172 invalidated = false;
173 }
174 if (mode == MODE_SAVE_FILE)
175 file->grab_focus();
176 else
177 tree->grab_focus();
178
179 set_process_unhandled_input(true);
180
181 // For open dir mode, deselect all items on file dialog open.
182 if (mode == MODE_OPEN_DIR) {
183 deselect_items();
184 file_box->set_visible(false);
185 } else {
186 file_box->set_visible(true);
187 }
188 }
189
_action_pressed()190 void FileDialog::_action_pressed() {
191
192 if (mode == MODE_OPEN_FILES) {
193
194 TreeItem *ti = tree->get_next_selected(NULL);
195 String fbase = dir_access->get_current_dir();
196
197 PoolVector<String> files;
198 while (ti) {
199
200 files.push_back(fbase.plus_file(ti->get_text(0)));
201 ti = tree->get_next_selected(ti);
202 }
203
204 if (files.size()) {
205 emit_signal("files_selected", files);
206 hide();
207 }
208
209 return;
210 }
211
212 String f = dir_access->get_current_dir().plus_file(file->get_text());
213
214 if ((mode == MODE_OPEN_ANY || mode == MODE_OPEN_FILE) && dir_access->file_exists(f)) {
215 emit_signal("file_selected", f);
216 hide();
217 } else if (mode == MODE_OPEN_ANY || mode == MODE_OPEN_DIR) {
218
219 String path = dir_access->get_current_dir();
220
221 path = path.replace("\\", "/");
222 TreeItem *item = tree->get_selected();
223 if (item) {
224 Dictionary d = item->get_metadata(0);
225 if (d["dir"] && d["name"] != "..") {
226 path = path.plus_file(d["name"]);
227 }
228 }
229
230 emit_signal("dir_selected", path);
231 hide();
232 }
233
234 if (mode == MODE_SAVE_FILE) {
235
236 bool valid = false;
237
238 if (filter->get_selected() == filter->get_item_count() - 1) {
239 valid = true; // match none
240 } else if (filters.size() > 1 && filter->get_selected() == 0) {
241 // match all filters
242 for (int i = 0; i < filters.size(); i++) {
243
244 String flt = filters[i].get_slice(";", 0);
245 for (int j = 0; j < flt.get_slice_count(","); j++) {
246
247 String str = flt.get_slice(",", j).strip_edges();
248 if (f.match(str)) {
249 valid = true;
250 break;
251 }
252 }
253 if (valid)
254 break;
255 }
256 } else {
257 int idx = filter->get_selected();
258 if (filters.size() > 1)
259 idx--;
260 if (idx >= 0 && idx < filters.size()) {
261
262 String flt = filters[idx].get_slice(";", 0);
263 int filterSliceCount = flt.get_slice_count(",");
264 for (int j = 0; j < filterSliceCount; j++) {
265
266 String str = (flt.get_slice(",", j).strip_edges());
267 if (f.match(str)) {
268 valid = true;
269 break;
270 }
271 }
272
273 if (!valid && filterSliceCount > 0) {
274 String str = (flt.get_slice(",", 0).strip_edges());
275 f += str.substr(1, str.length() - 1);
276 file->set_text(f.get_file());
277 valid = true;
278 }
279 } else {
280 valid = true;
281 }
282 }
283
284 if (!valid) {
285
286 exterr->popup_centered_minsize(Size2(250, 80));
287 return;
288 }
289
290 if (dir_access->file_exists(f)) {
291 confirm_save->set_text(RTR("File Exists, Overwrite?"));
292 confirm_save->popup_centered(Size2(200, 80));
293 } else {
294
295 emit_signal("file_selected", f);
296 hide();
297 }
298 }
299 }
300
_cancel_pressed()301 void FileDialog::_cancel_pressed() {
302
303 file->set_text("");
304 invalidate();
305 hide();
306 }
307
_is_open_should_be_disabled()308 bool FileDialog::_is_open_should_be_disabled() {
309
310 if (mode == MODE_OPEN_ANY || mode == MODE_SAVE_FILE)
311 return false;
312
313 TreeItem *ti = tree->get_next_selected(tree->get_root());
314 while (ti) {
315 TreeItem *prev_ti = ti;
316 ti = tree->get_next_selected(tree->get_root());
317 if (ti == prev_ti)
318 break;
319 }
320 // We have something that we can't select?
321 if (!ti)
322 return mode != MODE_OPEN_DIR; // In "Open folder" mode, having nothing selected picks the current folder.
323
324 Dictionary d = ti->get_metadata(0);
325
326 // Opening a file, but selected a folder? Forbidden.
327 return ((mode == MODE_OPEN_FILE || mode == MODE_OPEN_FILES) && d["dir"]) || // Flipped case, also forbidden.
328 (mode == MODE_OPEN_DIR && !d["dir"]);
329 }
330
_go_up()331 void FileDialog::_go_up() {
332
333 dir_access->change_dir("..");
334 update_file_list();
335 update_dir();
336 }
337
deselect_items()338 void FileDialog::deselect_items() {
339
340 // Clear currently selected items in file manager.
341 tree->deselect_all();
342
343 // And change get_ok title.
344 if (!tree->is_anything_selected()) {
345 get_ok()->set_disabled(_is_open_should_be_disabled());
346
347 switch (mode) {
348
349 case MODE_OPEN_FILE:
350 case MODE_OPEN_FILES:
351 get_ok()->set_text(RTR("Open"));
352 break;
353 case MODE_OPEN_DIR:
354 get_ok()->set_text(RTR("Select Current Folder"));
355 break;
356 case MODE_OPEN_ANY:
357 case MODE_SAVE_FILE:
358 // FIXME: Implement, or refactor to avoid duplication with set_mode
359 break;
360 }
361 }
362 }
363
_tree_multi_selected(Object * p_object,int p_cell,bool p_selected)364 void FileDialog::_tree_multi_selected(Object *p_object, int p_cell, bool p_selected) {
365 _tree_selected();
366 }
367
_tree_selected()368 void FileDialog::_tree_selected() {
369
370 TreeItem *ti = tree->get_selected();
371 if (!ti)
372 return;
373 Dictionary d = ti->get_metadata(0);
374
375 if (!d["dir"]) {
376
377 file->set_text(d["name"]);
378 } else if (mode == MODE_OPEN_DIR) {
379 get_ok()->set_text(RTR("Select This Folder"));
380 }
381
382 get_ok()->set_disabled(_is_open_should_be_disabled());
383 }
384
_tree_item_activated()385 void FileDialog::_tree_item_activated() {
386
387 TreeItem *ti = tree->get_selected();
388 if (!ti)
389 return;
390
391 Dictionary d = ti->get_metadata(0);
392
393 if (d["dir"]) {
394
395 dir_access->change_dir(d["name"]);
396 if (mode == MODE_OPEN_FILE || mode == MODE_OPEN_FILES || mode == MODE_OPEN_DIR || mode == MODE_OPEN_ANY)
397 file->set_text("");
398 call_deferred("_update_file_list");
399 call_deferred("_update_dir");
400 } else {
401
402 _action_pressed();
403 }
404 }
405
update_file_name()406 void FileDialog::update_file_name() {
407 int idx = filter->get_selected() - 1;
408 if ((idx == -1 && filter->get_item_count() == 2) || (filter->get_item_count() > 2 && idx >= 0 && idx < filter->get_item_count() - 2)) {
409 if (idx == -1) idx += 1;
410 String filter_str = filters[idx];
411 String file_str = file->get_text();
412 String base_name = file_str.get_basename();
413 file_str = base_name + "." + filter_str.strip_edges().to_lower();
414 file->set_text(file_str);
415 }
416 }
417
update_file_list()418 void FileDialog::update_file_list() {
419
420 tree->clear();
421
422 // Scroll back to the top after opening a directory
423 tree->get_vscroll_bar()->set_value(0);
424
425 dir_access->list_dir_begin();
426
427 TreeItem *root = tree->create_item();
428 Ref<Texture> folder = get_icon("folder");
429 Ref<Texture> file_icon = get_icon("file");
430 const Color folder_color = get_color("folder_icon_modulate");
431 const Color file_color = get_color("file_icon_modulate");
432 List<String> files;
433 List<String> dirs;
434
435 bool is_hidden;
436 String item;
437
438 while ((item = dir_access->get_next()) != "") {
439
440 if (item == "." || item == "..")
441 continue;
442
443 is_hidden = dir_access->current_is_hidden();
444
445 if (show_hidden_files || !is_hidden) {
446 if (!dir_access->current_is_dir())
447 files.push_back(item);
448 else
449 dirs.push_back(item);
450 }
451 }
452
453 dirs.sort_custom<NaturalNoCaseComparator>();
454 files.sort_custom<NaturalNoCaseComparator>();
455
456 while (!dirs.empty()) {
457 String &dir_name = dirs.front()->get();
458 TreeItem *ti = tree->create_item(root);
459 ti->set_text(0, dir_name);
460 ti->set_icon(0, folder);
461 ti->set_icon_modulate(0, folder_color);
462
463 Dictionary d;
464 d["name"] = dir_name;
465 d["dir"] = true;
466
467 ti->set_metadata(0, d);
468
469 dirs.pop_front();
470 }
471
472 List<String> patterns;
473 // build filter
474 if (filter->get_selected() == filter->get_item_count() - 1) {
475
476 // match all
477 } else if (filters.size() > 1 && filter->get_selected() == 0) {
478 // match all filters
479 for (int i = 0; i < filters.size(); i++) {
480
481 String f = filters[i].get_slice(";", 0);
482 for (int j = 0; j < f.get_slice_count(","); j++) {
483
484 patterns.push_back(f.get_slice(",", j).strip_edges());
485 }
486 }
487 } else {
488 int idx = filter->get_selected();
489 if (filters.size() > 1)
490 idx--;
491
492 if (idx >= 0 && idx < filters.size()) {
493
494 String f = filters[idx].get_slice(";", 0);
495 for (int j = 0; j < f.get_slice_count(","); j++) {
496
497 patterns.push_back(f.get_slice(",", j).strip_edges());
498 }
499 }
500 }
501
502 String base_dir = dir_access->get_current_dir();
503
504 while (!files.empty()) {
505
506 bool match = patterns.empty();
507 String match_str;
508
509 for (List<String>::Element *E = patterns.front(); E; E = E->next()) {
510
511 if (files.front()->get().matchn(E->get())) {
512 match_str = E->get();
513 match = true;
514 break;
515 }
516 }
517
518 if (match) {
519 TreeItem *ti = tree->create_item(root);
520 ti->set_text(0, files.front()->get());
521
522 if (get_icon_func) {
523
524 Ref<Texture> icon = get_icon_func(base_dir.plus_file(files.front()->get()));
525 ti->set_icon(0, icon);
526 } else {
527 ti->set_icon(0, file_icon);
528 }
529 ti->set_icon_modulate(0, file_color);
530
531 if (mode == MODE_OPEN_DIR) {
532 ti->set_custom_color(0, get_color("files_disabled"));
533 ti->set_selectable(0, false);
534 }
535 Dictionary d;
536 d["name"] = files.front()->get();
537 d["dir"] = false;
538 ti->set_metadata(0, d);
539
540 if (file->get_text() == files.front()->get() || match_str == files.front()->get())
541 ti->select(0);
542 }
543
544 files.pop_front();
545 }
546
547 if (tree->get_root() && tree->get_root()->get_children() && tree->get_selected() == NULL)
548 tree->get_root()->get_children()->select(0);
549 }
550
_filter_selected(int)551 void FileDialog::_filter_selected(int) {
552
553 update_file_name();
554 update_file_list();
555 }
556
update_filters()557 void FileDialog::update_filters() {
558
559 filter->clear();
560
561 if (filters.size() > 1) {
562 String all_filters;
563
564 const int max_filters = 5;
565
566 for (int i = 0; i < MIN(max_filters, filters.size()); i++) {
567 String flt = filters[i].get_slice(";", 0).strip_edges();
568 if (i > 0)
569 all_filters += ", ";
570 all_filters += flt;
571 }
572
573 if (max_filters < filters.size())
574 all_filters += ", ...";
575
576 filter->add_item(RTR("All Recognized") + " (" + all_filters + ")");
577 }
578 for (int i = 0; i < filters.size(); i++) {
579
580 String flt = filters[i].get_slice(";", 0).strip_edges();
581 String desc = filters[i].get_slice(";", 1).strip_edges();
582 if (desc.length())
583 filter->add_item(String(tr(desc)) + " (" + flt + ")");
584 else
585 filter->add_item("(" + flt + ")");
586 }
587
588 filter->add_item(RTR("All Files (*)"));
589 }
590
clear_filters()591 void FileDialog::clear_filters() {
592
593 filters.clear();
594 update_filters();
595 invalidate();
596 }
add_filter(const String & p_filter)597 void FileDialog::add_filter(const String &p_filter) {
598
599 filters.push_back(p_filter);
600 update_filters();
601 invalidate();
602 }
603
set_filters(const Vector<String> & p_filters)604 void FileDialog::set_filters(const Vector<String> &p_filters) {
605 filters = p_filters;
606 update_filters();
607 invalidate();
608 }
609
get_filters() const610 Vector<String> FileDialog::get_filters() const {
611 return filters;
612 }
613
get_current_dir() const614 String FileDialog::get_current_dir() const {
615
616 return dir->get_text();
617 }
get_current_file() const618 String FileDialog::get_current_file() const {
619
620 return file->get_text();
621 }
get_current_path() const622 String FileDialog::get_current_path() const {
623
624 return dir->get_text().plus_file(file->get_text());
625 }
set_current_dir(const String & p_dir)626 void FileDialog::set_current_dir(const String &p_dir) {
627
628 dir_access->change_dir(p_dir);
629 update_dir();
630 invalidate();
631 }
set_current_file(const String & p_file)632 void FileDialog::set_current_file(const String &p_file) {
633
634 file->set_text(p_file);
635 update_dir();
636 invalidate();
637 int lp = p_file.find_last(".");
638 if (lp != -1) {
639 file->select(0, lp);
640 if (file->is_inside_tree() && !get_tree()->is_node_being_edited(file))
641 file->grab_focus();
642 }
643 }
set_current_path(const String & p_path)644 void FileDialog::set_current_path(const String &p_path) {
645
646 if (!p_path.size())
647 return;
648 int pos = MAX(p_path.find_last("/"), p_path.find_last("\\"));
649 if (pos == -1) {
650
651 set_current_file(p_path);
652 } else {
653
654 String dir = p_path.substr(0, pos);
655 String file = p_path.substr(pos + 1, p_path.length());
656 set_current_dir(dir);
657 set_current_file(file);
658 }
659 }
660
set_mode_overrides_title(bool p_override)661 void FileDialog::set_mode_overrides_title(bool p_override) {
662 mode_overrides_title = p_override;
663 }
664
is_mode_overriding_title() const665 bool FileDialog::is_mode_overriding_title() const {
666 return mode_overrides_title;
667 }
668
set_mode(Mode p_mode)669 void FileDialog::set_mode(Mode p_mode) {
670
671 ERR_FAIL_INDEX((int)p_mode, 5);
672
673 mode = p_mode;
674 switch (mode) {
675
676 case MODE_OPEN_FILE:
677 get_ok()->set_text(RTR("Open"));
678 if (mode_overrides_title)
679 set_title(RTR("Open a File"));
680 makedir->hide();
681 break;
682 case MODE_OPEN_FILES:
683 get_ok()->set_text(RTR("Open"));
684 if (mode_overrides_title)
685 set_title(RTR("Open File(s)"));
686 makedir->hide();
687 break;
688 case MODE_OPEN_DIR:
689 get_ok()->set_text(RTR("Select Current Folder"));
690 if (mode_overrides_title)
691 set_title(RTR("Open a Directory"));
692 makedir->show();
693 break;
694 case MODE_OPEN_ANY:
695 get_ok()->set_text(RTR("Open"));
696 if (mode_overrides_title)
697 set_title(RTR("Open a File or Directory"));
698 makedir->show();
699 break;
700 case MODE_SAVE_FILE:
701 get_ok()->set_text(RTR("Save"));
702 if (mode_overrides_title)
703 set_title(RTR("Save a File"));
704 makedir->show();
705 break;
706 }
707
708 if (mode == MODE_OPEN_FILES) {
709 tree->set_select_mode(Tree::SELECT_MULTI);
710 } else {
711 tree->set_select_mode(Tree::SELECT_SINGLE);
712 }
713 }
714
get_mode() const715 FileDialog::Mode FileDialog::get_mode() const {
716
717 return mode;
718 }
719
set_access(Access p_access)720 void FileDialog::set_access(Access p_access) {
721
722 ERR_FAIL_INDEX(p_access, 3);
723 if (access == p_access)
724 return;
725 memdelete(dir_access);
726 switch (p_access) {
727 case ACCESS_FILESYSTEM: {
728
729 dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
730 } break;
731 case ACCESS_RESOURCES: {
732
733 dir_access = DirAccess::create(DirAccess::ACCESS_RESOURCES);
734 } break;
735 case ACCESS_USERDATA: {
736
737 dir_access = DirAccess::create(DirAccess::ACCESS_USERDATA);
738 } break;
739 }
740 access = p_access;
741 _update_drives();
742 invalidate();
743 update_filters();
744 update_dir();
745 }
746
invalidate()747 void FileDialog::invalidate() {
748
749 if (is_visible_in_tree()) {
750 update_file_list();
751 invalidated = false;
752 } else {
753 invalidated = true;
754 }
755 }
756
get_access() const757 FileDialog::Access FileDialog::get_access() const {
758
759 return access;
760 }
761
_make_dir_confirm()762 void FileDialog::_make_dir_confirm() {
763
764 Error err = dir_access->make_dir(makedirname->get_text());
765 if (err == OK) {
766 dir_access->change_dir(makedirname->get_text());
767 invalidate();
768 update_filters();
769 update_dir();
770 } else {
771 mkdirerr->popup_centered_minsize(Size2(250, 50));
772 }
773 makedirname->set_text(""); // reset label
774 }
775
_make_dir()776 void FileDialog::_make_dir() {
777
778 makedialog->popup_centered_minsize(Size2(250, 80));
779 makedirname->grab_focus();
780 }
781
_select_drive(int p_idx)782 void FileDialog::_select_drive(int p_idx) {
783
784 String d = drives->get_item_text(p_idx);
785 dir_access->change_dir(d);
786 file->set_text("");
787 invalidate();
788 update_dir();
789 }
790
_update_drives()791 void FileDialog::_update_drives() {
792
793 int dc = dir_access->get_drive_count();
794 if (dc == 0 || access != ACCESS_FILESYSTEM) {
795 drives->hide();
796 } else {
797 drives->clear();
798 Node *dp = drives->get_parent();
799 if (dp) {
800 dp->remove_child(drives);
801 }
802 dp = dir_access->drives_are_shortcuts() ? shortcuts_container : drives_container;
803 dp->add_child(drives);
804 drives->show();
805
806 for (int i = 0; i < dir_access->get_drive_count(); i++) {
807 drives->add_item(dir_access->get_drive(i));
808 }
809
810 drives->select(dir_access->get_current_drive());
811 }
812 }
813
814 bool FileDialog::default_show_hidden_files = false;
815
_bind_methods()816 void FileDialog::_bind_methods() {
817
818 ClassDB::bind_method(D_METHOD("_unhandled_input"), &FileDialog::_unhandled_input);
819
820 ClassDB::bind_method(D_METHOD("_tree_multi_selected"), &FileDialog::_tree_multi_selected);
821 ClassDB::bind_method(D_METHOD("_tree_selected"), &FileDialog::_tree_selected);
822 ClassDB::bind_method(D_METHOD("_tree_item_activated"), &FileDialog::_tree_item_activated);
823 ClassDB::bind_method(D_METHOD("_dir_entered"), &FileDialog::_dir_entered);
824 ClassDB::bind_method(D_METHOD("_file_entered"), &FileDialog::_file_entered);
825 ClassDB::bind_method(D_METHOD("_action_pressed"), &FileDialog::_action_pressed);
826 ClassDB::bind_method(D_METHOD("_cancel_pressed"), &FileDialog::_cancel_pressed);
827 ClassDB::bind_method(D_METHOD("_filter_selected"), &FileDialog::_filter_selected);
828 ClassDB::bind_method(D_METHOD("_save_confirm_pressed"), &FileDialog::_save_confirm_pressed);
829
830 ClassDB::bind_method(D_METHOD("clear_filters"), &FileDialog::clear_filters);
831 ClassDB::bind_method(D_METHOD("add_filter", "filter"), &FileDialog::add_filter);
832 ClassDB::bind_method(D_METHOD("set_filters", "filters"), &FileDialog::set_filters);
833 ClassDB::bind_method(D_METHOD("get_filters"), &FileDialog::get_filters);
834 ClassDB::bind_method(D_METHOD("get_current_dir"), &FileDialog::get_current_dir);
835 ClassDB::bind_method(D_METHOD("get_current_file"), &FileDialog::get_current_file);
836 ClassDB::bind_method(D_METHOD("get_current_path"), &FileDialog::get_current_path);
837 ClassDB::bind_method(D_METHOD("set_current_dir", "dir"), &FileDialog::set_current_dir);
838 ClassDB::bind_method(D_METHOD("set_current_file", "file"), &FileDialog::set_current_file);
839 ClassDB::bind_method(D_METHOD("set_current_path", "path"), &FileDialog::set_current_path);
840 ClassDB::bind_method(D_METHOD("set_mode_overrides_title", "override"), &FileDialog::set_mode_overrides_title);
841 ClassDB::bind_method(D_METHOD("is_mode_overriding_title"), &FileDialog::is_mode_overriding_title);
842 ClassDB::bind_method(D_METHOD("set_mode", "mode"), &FileDialog::set_mode);
843 ClassDB::bind_method(D_METHOD("get_mode"), &FileDialog::get_mode);
844 ClassDB::bind_method(D_METHOD("get_vbox"), &FileDialog::get_vbox);
845 ClassDB::bind_method(D_METHOD("get_line_edit"), &FileDialog::get_line_edit);
846 ClassDB::bind_method(D_METHOD("set_access", "access"), &FileDialog::set_access);
847 ClassDB::bind_method(D_METHOD("get_access"), &FileDialog::get_access);
848 ClassDB::bind_method(D_METHOD("set_show_hidden_files", "show"), &FileDialog::set_show_hidden_files);
849 ClassDB::bind_method(D_METHOD("is_showing_hidden_files"), &FileDialog::is_showing_hidden_files);
850 ClassDB::bind_method(D_METHOD("_select_drive"), &FileDialog::_select_drive);
851 ClassDB::bind_method(D_METHOD("_make_dir"), &FileDialog::_make_dir);
852 ClassDB::bind_method(D_METHOD("_make_dir_confirm"), &FileDialog::_make_dir_confirm);
853 ClassDB::bind_method(D_METHOD("_update_file_name"), &FileDialog::update_file_name);
854 ClassDB::bind_method(D_METHOD("_update_file_list"), &FileDialog::update_file_list);
855 ClassDB::bind_method(D_METHOD("_update_dir"), &FileDialog::update_dir);
856 ClassDB::bind_method(D_METHOD("_go_up"), &FileDialog::_go_up);
857 ClassDB::bind_method(D_METHOD("deselect_items"), &FileDialog::deselect_items);
858
859 ClassDB::bind_method(D_METHOD("invalidate"), &FileDialog::invalidate);
860
861 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "mode_overrides_title"), "set_mode_overrides_title", "is_mode_overriding_title");
862 ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Open File,Open Files,Open Folder,Open Any,Save"), "set_mode", "get_mode");
863 ADD_PROPERTY(PropertyInfo(Variant::INT, "access", PROPERTY_HINT_ENUM, "Resources,User data,File system"), "set_access", "get_access");
864 ADD_PROPERTY(PropertyInfo(Variant::POOL_STRING_ARRAY, "filters"), "set_filters", "get_filters");
865 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_hidden_files"), "set_show_hidden_files", "is_showing_hidden_files");
866 ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_dir"), "set_current_dir", "get_current_dir");
867 ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_file"), "set_current_file", "get_current_file");
868 ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_path"), "set_current_path", "get_current_path");
869
870 ADD_SIGNAL(MethodInfo("file_selected", PropertyInfo(Variant::STRING, "path")));
871 ADD_SIGNAL(MethodInfo("files_selected", PropertyInfo(Variant::POOL_STRING_ARRAY, "paths")));
872 ADD_SIGNAL(MethodInfo("dir_selected", PropertyInfo(Variant::STRING, "dir")));
873
874 BIND_ENUM_CONSTANT(MODE_OPEN_FILE);
875 BIND_ENUM_CONSTANT(MODE_OPEN_FILES);
876 BIND_ENUM_CONSTANT(MODE_OPEN_DIR);
877 BIND_ENUM_CONSTANT(MODE_OPEN_ANY);
878 BIND_ENUM_CONSTANT(MODE_SAVE_FILE);
879
880 BIND_ENUM_CONSTANT(ACCESS_RESOURCES);
881 BIND_ENUM_CONSTANT(ACCESS_USERDATA);
882 BIND_ENUM_CONSTANT(ACCESS_FILESYSTEM);
883 }
884
set_show_hidden_files(bool p_show)885 void FileDialog::set_show_hidden_files(bool p_show) {
886 show_hidden_files = p_show;
887 invalidate();
888 }
889
is_showing_hidden_files() const890 bool FileDialog::is_showing_hidden_files() const {
891 return show_hidden_files;
892 }
893
set_default_show_hidden_files(bool p_show)894 void FileDialog::set_default_show_hidden_files(bool p_show) {
895 default_show_hidden_files = p_show;
896 }
897
FileDialog()898 FileDialog::FileDialog() {
899
900 show_hidden_files = default_show_hidden_files;
901
902 mode_overrides_title = true;
903
904 VBoxContainer *vbc = memnew(VBoxContainer);
905 add_child(vbc);
906
907 mode = MODE_SAVE_FILE;
908 set_title(RTR("Save a File"));
909
910 HBoxContainer *hbc = memnew(HBoxContainer);
911
912 dir_up = memnew(ToolButton);
913 dir_up->set_tooltip(RTR("Go to parent folder."));
914 hbc->add_child(dir_up);
915 dir_up->connect("pressed", this, "_go_up");
916
917 hbc->add_child(memnew(Label(RTR("Path:"))));
918
919 drives_container = memnew(HBoxContainer);
920 hbc->add_child(drives_container);
921
922 drives = memnew(OptionButton);
923 drives->connect("item_selected", this, "_select_drive");
924 hbc->add_child(drives);
925
926 dir = memnew(LineEdit);
927 hbc->add_child(dir);
928 dir->set_h_size_flags(SIZE_EXPAND_FILL);
929
930 refresh = memnew(ToolButton);
931 refresh->set_tooltip(RTR("Refresh files."));
932 refresh->connect("pressed", this, "_update_file_list");
933 hbc->add_child(refresh);
934
935 show_hidden = memnew(ToolButton);
936 show_hidden->set_toggle_mode(true);
937 show_hidden->set_pressed(is_showing_hidden_files());
938 show_hidden->set_tooltip(RTR("Toggle the visibility of hidden files."));
939 show_hidden->connect("toggled", this, "set_show_hidden_files");
940 hbc->add_child(show_hidden);
941
942 shortcuts_container = memnew(HBoxContainer);
943 hbc->add_child(shortcuts_container);
944
945 makedir = memnew(Button);
946 makedir->set_text(RTR("Create Folder"));
947 makedir->connect("pressed", this, "_make_dir");
948 hbc->add_child(makedir);
949 vbc->add_child(hbc);
950
951 tree = memnew(Tree);
952 tree->set_hide_root(true);
953 vbc->add_margin_child(RTR("Directories & Files:"), tree, true);
954
955 file_box = memnew(HBoxContainer);
956 file_box->add_child(memnew(Label(RTR("File:"))));
957 file = memnew(LineEdit);
958 file->set_stretch_ratio(4);
959 file->set_h_size_flags(SIZE_EXPAND_FILL);
960 file_box->add_child(file);
961 filter = memnew(OptionButton);
962 filter->set_stretch_ratio(3);
963 filter->set_h_size_flags(SIZE_EXPAND_FILL);
964 filter->set_clip_text(true); // too many extensions overflows it
965 file_box->add_child(filter);
966 vbc->add_child(file_box);
967
968 dir_access = DirAccess::create(DirAccess::ACCESS_RESOURCES);
969 access = ACCESS_RESOURCES;
970 _update_drives();
971
972 connect("confirmed", this, "_action_pressed");
973 tree->connect("multi_selected", this, "_tree_multi_selected", varray(), CONNECT_DEFERRED);
974 tree->connect("cell_selected", this, "_tree_selected", varray(), CONNECT_DEFERRED);
975 tree->connect("item_activated", this, "_tree_item_activated", varray());
976 tree->connect("nothing_selected", this, "deselect_items");
977 dir->connect("text_entered", this, "_dir_entered");
978 file->connect("text_entered", this, "_file_entered");
979 filter->connect("item_selected", this, "_filter_selected");
980
981 confirm_save = memnew(ConfirmationDialog);
982 confirm_save->set_as_toplevel(true);
983 add_child(confirm_save);
984
985 confirm_save->connect("confirmed", this, "_save_confirm_pressed");
986
987 makedialog = memnew(ConfirmationDialog);
988 makedialog->set_title(RTR("Create Folder"));
989 VBoxContainer *makevb = memnew(VBoxContainer);
990 makedialog->add_child(makevb);
991
992 makedirname = memnew(LineEdit);
993 makevb->add_margin_child(RTR("Name:"), makedirname);
994 add_child(makedialog);
995 makedialog->register_text_enter(makedirname);
996 makedialog->connect("confirmed", this, "_make_dir_confirm");
997 mkdirerr = memnew(AcceptDialog);
998 mkdirerr->set_text(RTR("Could not create folder."));
999 add_child(mkdirerr);
1000
1001 exterr = memnew(AcceptDialog);
1002 exterr->set_text(RTR("Must use a valid extension."));
1003 add_child(exterr);
1004
1005 update_filters();
1006 update_dir();
1007
1008 set_hide_on_ok(false);
1009 vbox = vbc;
1010
1011 invalidated = true;
1012 if (register_func)
1013 register_func(this);
1014 }
1015
~FileDialog()1016 FileDialog::~FileDialog() {
1017
1018 if (unregister_func)
1019 unregister_func(this);
1020 memdelete(dir_access);
1021 }
1022
_bind_methods()1023 void LineEditFileChooser::_bind_methods() {
1024
1025 ClassDB::bind_method(D_METHOD("_browse"), &LineEditFileChooser::_browse);
1026 ClassDB::bind_method(D_METHOD("_chosen"), &LineEditFileChooser::_chosen);
1027 ClassDB::bind_method(D_METHOD("get_button"), &LineEditFileChooser::get_button);
1028 ClassDB::bind_method(D_METHOD("get_line_edit"), &LineEditFileChooser::get_line_edit);
1029 ClassDB::bind_method(D_METHOD("get_file_dialog"), &LineEditFileChooser::get_file_dialog);
1030 }
1031
_chosen(const String & p_text)1032 void LineEditFileChooser::_chosen(const String &p_text) {
1033
1034 line_edit->set_text(p_text);
1035 line_edit->emit_signal("text_entered", p_text);
1036 }
1037
_browse()1038 void LineEditFileChooser::_browse() {
1039
1040 dialog->popup_centered_ratio();
1041 }
1042
LineEditFileChooser()1043 LineEditFileChooser::LineEditFileChooser() {
1044
1045 line_edit = memnew(LineEdit);
1046 add_child(line_edit);
1047 line_edit->set_h_size_flags(SIZE_EXPAND_FILL);
1048 button = memnew(Button);
1049 button->set_text(" .. ");
1050 add_child(button);
1051 button->connect("pressed", this, "_browse");
1052 dialog = memnew(FileDialog);
1053 add_child(dialog);
1054 dialog->connect("file_selected", this, "_chosen");
1055 dialog->connect("dir_selected", this, "_chosen");
1056 dialog->connect("files_selected", this, "_chosen");
1057 }
1058