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