1 | /**************************************************************************/ |
2 | /* file_dialog.cpp */ |
3 | /**************************************************************************/ |
4 | /* This file is part of: */ |
5 | /* GODOT ENGINE */ |
6 | /* https://godotengine.org */ |
7 | /**************************************************************************/ |
8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
10 | /* */ |
11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
12 | /* a copy of this software and associated documentation files (the */ |
13 | /* "Software"), to deal in the Software without restriction, including */ |
14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
17 | /* the following conditions: */ |
18 | /* */ |
19 | /* The above copyright notice and this permission notice shall be */ |
20 | /* included in all copies or substantial portions of the Software. */ |
21 | /* */ |
22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
29 | /**************************************************************************/ |
30 | |
31 | #include "file_dialog.h" |
32 | |
33 | #include "core/os/keyboard.h" |
34 | #include "core/string/print_string.h" |
35 | #include "scene/gui/label.h" |
36 | #include "scene/theme/theme_db.h" |
37 | |
38 | FileDialog::GetIconFunc FileDialog::get_icon_func = nullptr; |
39 | |
40 | FileDialog::RegisterFunc FileDialog::register_func = nullptr; |
41 | FileDialog::RegisterFunc FileDialog::unregister_func = nullptr; |
42 | |
43 | void FileDialog::() { |
44 | popup_centered_clamped(Size2i(700, 500), 0.8f); |
45 | _focus_file_text(); |
46 | } |
47 | |
48 | void FileDialog::_focus_file_text() { |
49 | int lp = file->get_text().rfind("." ); |
50 | if (lp != -1) { |
51 | file->select(0, lp); |
52 | if (file->is_inside_tree() && !get_tree()->is_node_being_edited(file)) { |
53 | file->grab_focus(); |
54 | } |
55 | } |
56 | } |
57 | |
58 | void FileDialog::(const Rect2i &p_rect) { |
59 | if (access == ACCESS_FILESYSTEM && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) { |
60 | DisplayServer::get_singleton()->file_dialog_show(get_title(), dir->get_text(), file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), filters, callable_mp(this, &FileDialog::_native_dialog_cb)); |
61 | } else { |
62 | ConfirmationDialog::popup(p_rect); |
63 | } |
64 | } |
65 | |
66 | void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files) { |
67 | if (p_ok) { |
68 | if (p_files.size() > 0) { |
69 | String f = p_files[0]; |
70 | if (mode == FILE_MODE_OPEN_FILES) { |
71 | emit_signal(SNAME("files_selected" ), p_files); |
72 | } else { |
73 | if (mode == FILE_MODE_SAVE_FILE) { |
74 | emit_signal(SNAME("file_selected" ), f); |
75 | } else if ((mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_FILE) && dir_access->file_exists(f)) { |
76 | emit_signal(SNAME("file_selected" ), f); |
77 | } else if (mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_DIR) { |
78 | emit_signal(SNAME("dir_selected" ), f); |
79 | } |
80 | } |
81 | file->set_text(f); |
82 | dir->set_text(f.get_base_dir()); |
83 | } |
84 | } else { |
85 | file->set_text("" ); |
86 | emit_signal(SNAME("canceled" )); |
87 | } |
88 | } |
89 | |
90 | VBoxContainer *FileDialog::get_vbox() { |
91 | return vbox; |
92 | } |
93 | |
94 | void FileDialog::_validate_property(PropertyInfo &p_property) const { |
95 | if (p_property.name == "dialog_text" ) { |
96 | // File dialogs have a custom layout, and dialog nodes can't have both a text and a layout. |
97 | p_property.usage = PROPERTY_USAGE_NONE; |
98 | } |
99 | } |
100 | |
101 | void FileDialog::_notification(int p_what) { |
102 | switch (p_what) { |
103 | case NOTIFICATION_VISIBILITY_CHANGED: { |
104 | if (!is_visible()) { |
105 | set_process_shortcut_input(false); |
106 | } |
107 | |
108 | invalidate(); // Put it here to preview in the editor. |
109 | } break; |
110 | |
111 | case NOTIFICATION_THEME_CHANGED: { |
112 | dir_up->set_icon(theme_cache.parent_folder); |
113 | if (vbox->is_layout_rtl()) { |
114 | dir_prev->set_icon(theme_cache.forward_folder); |
115 | dir_next->set_icon(theme_cache.back_folder); |
116 | } else { |
117 | dir_prev->set_icon(theme_cache.back_folder); |
118 | dir_next->set_icon(theme_cache.forward_folder); |
119 | } |
120 | refresh->set_icon(theme_cache.reload); |
121 | show_hidden->set_icon(theme_cache.toggle_hidden); |
122 | |
123 | dir_up->add_theme_color_override("icon_normal_color" , theme_cache.icon_normal_color); |
124 | dir_up->add_theme_color_override("icon_hover_color" , theme_cache.icon_hover_color); |
125 | dir_up->add_theme_color_override("icon_focus_color" , theme_cache.icon_focus_color); |
126 | dir_up->add_theme_color_override("icon_pressed_color" , theme_cache.icon_pressed_color); |
127 | |
128 | dir_prev->add_theme_color_override("icon_color_normal" , theme_cache.icon_normal_color); |
129 | dir_prev->add_theme_color_override("icon_color_hover" , theme_cache.icon_hover_color); |
130 | dir_prev->add_theme_color_override("icon_focus_color" , theme_cache.icon_focus_color); |
131 | dir_prev->add_theme_color_override("icon_color_pressed" , theme_cache.icon_pressed_color); |
132 | |
133 | dir_next->add_theme_color_override("icon_color_normal" , theme_cache.icon_normal_color); |
134 | dir_next->add_theme_color_override("icon_color_hover" , theme_cache.icon_hover_color); |
135 | dir_next->add_theme_color_override("icon_focus_color" , theme_cache.icon_focus_color); |
136 | dir_next->add_theme_color_override("icon_color_pressed" , theme_cache.icon_pressed_color); |
137 | |
138 | refresh->add_theme_color_override("icon_normal_color" , theme_cache.icon_normal_color); |
139 | refresh->add_theme_color_override("icon_hover_color" , theme_cache.icon_hover_color); |
140 | refresh->add_theme_color_override("icon_focus_color" , theme_cache.icon_focus_color); |
141 | refresh->add_theme_color_override("icon_pressed_color" , theme_cache.icon_pressed_color); |
142 | |
143 | show_hidden->add_theme_color_override("icon_normal_color" , theme_cache.icon_normal_color); |
144 | show_hidden->add_theme_color_override("icon_hover_color" , theme_cache.icon_hover_color); |
145 | show_hidden->add_theme_color_override("icon_focus_color" , theme_cache.icon_focus_color); |
146 | show_hidden->add_theme_color_override("icon_pressed_color" , theme_cache.icon_pressed_color); |
147 | |
148 | invalidate(); |
149 | } break; |
150 | |
151 | case NOTIFICATION_TRANSLATION_CHANGED: { |
152 | update_filters(); |
153 | } break; |
154 | } |
155 | } |
156 | |
157 | void FileDialog::shortcut_input(const Ref<InputEvent> &p_event) { |
158 | ERR_FAIL_COND(p_event.is_null()); |
159 | |
160 | Ref<InputEventKey> k = p_event; |
161 | if (k.is_valid() && has_focus()) { |
162 | if (k->is_pressed()) { |
163 | bool handled = true; |
164 | |
165 | switch (k->get_keycode()) { |
166 | case Key::H: { |
167 | if (k->is_command_or_control_pressed()) { |
168 | set_show_hidden_files(!show_hidden_files); |
169 | } else { |
170 | handled = false; |
171 | } |
172 | |
173 | } break; |
174 | case Key::F5: { |
175 | invalidate(); |
176 | } break; |
177 | case Key::BACKSPACE: { |
178 | _dir_submitted(".." ); |
179 | } break; |
180 | default: { |
181 | handled = false; |
182 | } |
183 | } |
184 | |
185 | if (handled) { |
186 | set_input_as_handled(); |
187 | } |
188 | } |
189 | } |
190 | } |
191 | |
192 | void FileDialog::set_enable_multiple_selection(bool p_enable) { |
193 | tree->set_select_mode(p_enable ? Tree::SELECT_MULTI : Tree::SELECT_SINGLE); |
194 | } |
195 | |
196 | Vector<String> FileDialog::get_selected_files() const { |
197 | Vector<String> list; |
198 | |
199 | TreeItem *item = tree->get_root(); |
200 | item = tree->get_next_selected(item); |
201 | while (item) { |
202 | list.push_back(dir_access->get_current_dir().path_join(item->get_text(0))); |
203 | item = tree->get_next_selected(item); |
204 | } |
205 | |
206 | return list; |
207 | } |
208 | |
209 | void FileDialog::update_dir() { |
210 | if (root_prefix.is_empty()) { |
211 | dir->set_text(dir_access->get_current_dir(false)); |
212 | } else { |
213 | dir->set_text(dir_access->get_current_dir(false).trim_prefix(root_prefix).trim_prefix("/" )); |
214 | } |
215 | |
216 | if (drives->is_visible()) { |
217 | if (dir_access->get_current_dir().is_network_share_path()) { |
218 | _update_drives(false); |
219 | drives->add_item(RTR("Network" )); |
220 | drives->set_item_disabled(-1, true); |
221 | drives->select(drives->get_item_count() - 1); |
222 | } else { |
223 | drives->select(dir_access->get_current_drive()); |
224 | } |
225 | } |
226 | |
227 | // Deselect any item, to make "Select Current Folder" button text by default. |
228 | deselect_all(); |
229 | } |
230 | |
231 | void FileDialog::_dir_submitted(String p_dir) { |
232 | _change_dir(root_prefix.path_join(p_dir)); |
233 | file->set_text("" ); |
234 | _push_history(); |
235 | } |
236 | |
237 | void FileDialog::_file_submitted(const String &p_file) { |
238 | _action_pressed(); |
239 | } |
240 | |
241 | void FileDialog::_save_confirm_pressed() { |
242 | String f = dir_access->get_current_dir().path_join(file->get_text()); |
243 | emit_signal(SNAME("file_selected" ), f); |
244 | hide(); |
245 | } |
246 | |
247 | void FileDialog::() { |
248 | ConfirmationDialog::_post_popup(); |
249 | if (mode == FILE_MODE_SAVE_FILE) { |
250 | file->grab_focus(); |
251 | } else { |
252 | tree->grab_focus(); |
253 | } |
254 | |
255 | set_process_shortcut_input(true); |
256 | |
257 | // For open dir mode, deselect all items on file dialog open. |
258 | if (mode == FILE_MODE_OPEN_DIR) { |
259 | deselect_all(); |
260 | file_box->set_visible(false); |
261 | } else { |
262 | file_box->set_visible(true); |
263 | } |
264 | |
265 | local_history.clear(); |
266 | local_history_pos = -1; |
267 | _push_history(); |
268 | } |
269 | |
270 | void FileDialog::_push_history() { |
271 | local_history.resize(local_history_pos + 1); |
272 | String new_path = dir_access->get_current_dir(); |
273 | if (local_history.size() == 0 || new_path != local_history[local_history_pos]) { |
274 | local_history.push_back(new_path); |
275 | local_history_pos++; |
276 | dir_prev->set_disabled(local_history_pos == 0); |
277 | dir_next->set_disabled(true); |
278 | } |
279 | } |
280 | |
281 | void FileDialog::_action_pressed() { |
282 | if (mode == FILE_MODE_OPEN_FILES) { |
283 | TreeItem *ti = tree->get_next_selected(nullptr); |
284 | String fbase = dir_access->get_current_dir(); |
285 | |
286 | Vector<String> files; |
287 | while (ti) { |
288 | files.push_back(fbase.path_join(ti->get_text(0))); |
289 | ti = tree->get_next_selected(ti); |
290 | } |
291 | |
292 | if (files.size()) { |
293 | emit_signal(SNAME("files_selected" ), files); |
294 | hide(); |
295 | } |
296 | |
297 | return; |
298 | } |
299 | |
300 | String file_text = file->get_text(); |
301 | String f = file_text.is_absolute_path() ? file_text : dir_access->get_current_dir().path_join(file_text); |
302 | |
303 | if ((mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_FILE) && dir_access->file_exists(f)) { |
304 | emit_signal(SNAME("file_selected" ), f); |
305 | hide(); |
306 | } else if (mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_DIR) { |
307 | String path = dir_access->get_current_dir(); |
308 | |
309 | path = path.replace("\\" , "/" ); |
310 | TreeItem *item = tree->get_selected(); |
311 | if (item) { |
312 | Dictionary d = item->get_metadata(0); |
313 | if (d["dir" ] && d["name" ] != ".." ) { |
314 | path = path.path_join(d["name" ]); |
315 | } |
316 | } |
317 | |
318 | emit_signal(SNAME("dir_selected" ), path); |
319 | hide(); |
320 | } |
321 | |
322 | if (mode == FILE_MODE_SAVE_FILE) { |
323 | bool valid = false; |
324 | |
325 | if (filter->get_selected() == filter->get_item_count() - 1) { |
326 | valid = true; // match none |
327 | } else if (filters.size() > 1 && filter->get_selected() == 0) { |
328 | // match all filters |
329 | for (int i = 0; i < filters.size(); i++) { |
330 | String flt = filters[i].get_slice(";" , 0); |
331 | for (int j = 0; j < flt.get_slice_count("," ); j++) { |
332 | String str = flt.get_slice("," , j).strip_edges(); |
333 | if (f.match(str)) { |
334 | valid = true; |
335 | break; |
336 | } |
337 | } |
338 | if (valid) { |
339 | break; |
340 | } |
341 | } |
342 | } else { |
343 | int idx = filter->get_selected(); |
344 | if (filters.size() > 1) { |
345 | idx--; |
346 | } |
347 | if (idx >= 0 && idx < filters.size()) { |
348 | String flt = filters[idx].get_slice(";" , 0); |
349 | int filterSliceCount = flt.get_slice_count("," ); |
350 | for (int j = 0; j < filterSliceCount; j++) { |
351 | String str = (flt.get_slice("," , j).strip_edges()); |
352 | if (f.match(str)) { |
353 | valid = true; |
354 | break; |
355 | } |
356 | } |
357 | |
358 | if (!valid && filterSliceCount > 0) { |
359 | String str = (flt.get_slice("," , 0).strip_edges()); |
360 | f += str.substr(1, str.length() - 1); |
361 | file->set_text(f.get_file()); |
362 | valid = true; |
363 | } |
364 | } else { |
365 | valid = true; |
366 | } |
367 | } |
368 | |
369 | String file_name = file_text.strip_edges().get_file(); |
370 | if (!valid || file_name.is_empty()) { |
371 | exterr->popup_centered(Size2(250, 80)); |
372 | return; |
373 | } |
374 | |
375 | if (dir_access->file_exists(f)) { |
376 | confirm_save->set_text(vformat(RTR("File \"%s\" already exists.\nDo you want to overwrite it?" ), f)); |
377 | confirm_save->popup_centered(Size2(250, 80)); |
378 | } else { |
379 | emit_signal(SNAME("file_selected" ), f); |
380 | hide(); |
381 | } |
382 | } |
383 | } |
384 | |
385 | void FileDialog::_cancel_pressed() { |
386 | file->set_text("" ); |
387 | invalidate(); |
388 | hide(); |
389 | } |
390 | |
391 | bool FileDialog::_is_open_should_be_disabled() { |
392 | if (mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_SAVE_FILE) { |
393 | return false; |
394 | } |
395 | |
396 | TreeItem *ti = tree->get_next_selected(tree->get_root()); |
397 | while (ti) { |
398 | TreeItem *prev_ti = ti; |
399 | ti = tree->get_next_selected(tree->get_root()); |
400 | if (ti == prev_ti) { |
401 | break; |
402 | } |
403 | } |
404 | // We have something that we can't select? |
405 | if (!ti) { |
406 | return mode != FILE_MODE_OPEN_DIR; // In "Open folder" mode, having nothing selected picks the current folder. |
407 | } |
408 | |
409 | Dictionary d = ti->get_metadata(0); |
410 | |
411 | // Opening a file, but selected a folder? Forbidden. |
412 | return ((mode == FILE_MODE_OPEN_FILE || mode == FILE_MODE_OPEN_FILES) && d["dir" ]) || // Flipped case, also forbidden. |
413 | (mode == FILE_MODE_OPEN_DIR && !d["dir" ]); |
414 | } |
415 | |
416 | void FileDialog::_go_up() { |
417 | _change_dir(".." ); |
418 | _push_history(); |
419 | } |
420 | |
421 | void FileDialog::_go_back() { |
422 | if (local_history_pos <= 0) { |
423 | return; |
424 | } |
425 | |
426 | local_history_pos--; |
427 | _change_dir(local_history[local_history_pos]); |
428 | |
429 | dir_prev->set_disabled(local_history_pos == 0); |
430 | dir_next->set_disabled(local_history_pos == local_history.size() - 1); |
431 | } |
432 | |
433 | void FileDialog::_go_forward() { |
434 | if (local_history_pos >= local_history.size() - 1) { |
435 | return; |
436 | } |
437 | |
438 | local_history_pos++; |
439 | _change_dir(local_history[local_history_pos]); |
440 | |
441 | dir_prev->set_disabled(local_history_pos == 0); |
442 | dir_next->set_disabled(local_history_pos == local_history.size() - 1); |
443 | } |
444 | |
445 | void FileDialog::deselect_all() { |
446 | // Clear currently selected items in file manager. |
447 | tree->deselect_all(); |
448 | |
449 | // And change get_ok title. |
450 | if (!tree->is_anything_selected()) { |
451 | get_ok_button()->set_disabled(_is_open_should_be_disabled()); |
452 | |
453 | switch (mode) { |
454 | case FILE_MODE_OPEN_FILE: |
455 | case FILE_MODE_OPEN_FILES: |
456 | set_ok_button_text(RTR("Open" )); |
457 | break; |
458 | case FILE_MODE_OPEN_DIR: |
459 | set_ok_button_text(RTR("Select Current Folder" )); |
460 | break; |
461 | case FILE_MODE_OPEN_ANY: |
462 | case FILE_MODE_SAVE_FILE: |
463 | // FIXME: Implement, or refactor to avoid duplication with set_mode |
464 | break; |
465 | } |
466 | } |
467 | } |
468 | |
469 | void FileDialog::_tree_multi_selected(Object *p_object, int p_cell, bool p_selected) { |
470 | _tree_selected(); |
471 | } |
472 | |
473 | void FileDialog::_tree_selected() { |
474 | TreeItem *ti = tree->get_selected(); |
475 | if (!ti) { |
476 | return; |
477 | } |
478 | Dictionary d = ti->get_metadata(0); |
479 | |
480 | if (!d["dir" ]) { |
481 | file->set_text(d["name" ]); |
482 | } else if (mode == FILE_MODE_OPEN_DIR) { |
483 | set_ok_button_text(RTR("Select This Folder" )); |
484 | } |
485 | |
486 | get_ok_button()->set_disabled(_is_open_should_be_disabled()); |
487 | } |
488 | |
489 | void FileDialog::_tree_item_activated() { |
490 | TreeItem *ti = tree->get_selected(); |
491 | if (!ti) { |
492 | return; |
493 | } |
494 | |
495 | Dictionary d = ti->get_metadata(0); |
496 | |
497 | if (d["dir" ]) { |
498 | _change_dir(d["name" ]); |
499 | if (mode == FILE_MODE_OPEN_FILE || mode == FILE_MODE_OPEN_FILES || mode == FILE_MODE_OPEN_DIR || mode == FILE_MODE_OPEN_ANY) { |
500 | file->set_text("" ); |
501 | } |
502 | _push_history(); |
503 | } else { |
504 | _action_pressed(); |
505 | } |
506 | } |
507 | |
508 | void FileDialog::update_file_name() { |
509 | int idx = filter->get_selected() - 1; |
510 | if ((idx == -1 && filter->get_item_count() == 2) || (filter->get_item_count() > 2 && idx >= 0 && idx < filter->get_item_count() - 2)) { |
511 | if (idx == -1) { |
512 | idx += 1; |
513 | } |
514 | String filter_str = filters[idx]; |
515 | String file_str = file->get_text(); |
516 | String base_name = file_str.get_basename(); |
517 | Vector<String> filter_substr = filter_str.split(";" ); |
518 | if (filter_substr.size() >= 2) { |
519 | file_str = base_name + "." + filter_substr[0].strip_edges().get_extension().to_lower(); |
520 | } else { |
521 | file_str = base_name + "." + filter_str.strip_edges().get_extension().to_lower(); |
522 | } |
523 | file->set_text(file_str); |
524 | } |
525 | } |
526 | |
527 | void FileDialog::update_file_list() { |
528 | tree->clear(); |
529 | |
530 | // Scroll back to the top after opening a directory |
531 | tree->get_vscroll_bar()->set_value(0); |
532 | |
533 | dir_access->list_dir_begin(); |
534 | |
535 | if (dir_access->is_readable(dir_access->get_current_dir().utf8().get_data())) { |
536 | message->hide(); |
537 | } else { |
538 | message->set_text(RTR("You don't have permission to access contents of this folder." )); |
539 | message->show(); |
540 | } |
541 | |
542 | TreeItem *root = tree->create_item(); |
543 | List<String> files; |
544 | List<String> dirs; |
545 | |
546 | bool is_hidden; |
547 | String item = dir_access->get_next(); |
548 | |
549 | while (!item.is_empty()) { |
550 | if (item == "." || item == ".." ) { |
551 | item = dir_access->get_next(); |
552 | continue; |
553 | } |
554 | |
555 | is_hidden = dir_access->current_is_hidden(); |
556 | |
557 | if (show_hidden_files || !is_hidden) { |
558 | if (!dir_access->current_is_dir()) { |
559 | files.push_back(item); |
560 | } else { |
561 | dirs.push_back(item); |
562 | } |
563 | } |
564 | item = dir_access->get_next(); |
565 | } |
566 | |
567 | dirs.sort_custom<NaturalNoCaseComparator>(); |
568 | files.sort_custom<NaturalNoCaseComparator>(); |
569 | |
570 | while (!dirs.is_empty()) { |
571 | String &dir_name = dirs.front()->get(); |
572 | TreeItem *ti = tree->create_item(root); |
573 | ti->set_text(0, dir_name); |
574 | ti->set_icon(0, theme_cache.folder); |
575 | ti->set_icon_modulate(0, theme_cache.folder_icon_color); |
576 | |
577 | Dictionary d; |
578 | d["name" ] = dir_name; |
579 | d["dir" ] = true; |
580 | |
581 | ti->set_metadata(0, d); |
582 | |
583 | dirs.pop_front(); |
584 | } |
585 | |
586 | List<String> patterns; |
587 | // build filter |
588 | if (filter->get_selected() == filter->get_item_count() - 1) { |
589 | // match all |
590 | } else if (filters.size() > 1 && filter->get_selected() == 0) { |
591 | // match all filters |
592 | for (int i = 0; i < filters.size(); i++) { |
593 | String f = filters[i].get_slice(";" , 0); |
594 | for (int j = 0; j < f.get_slice_count("," ); j++) { |
595 | patterns.push_back(f.get_slice("," , j).strip_edges()); |
596 | } |
597 | } |
598 | } else { |
599 | int idx = filter->get_selected(); |
600 | if (filters.size() > 1) { |
601 | idx--; |
602 | } |
603 | |
604 | if (idx >= 0 && idx < filters.size()) { |
605 | String f = filters[idx].get_slice(";" , 0); |
606 | for (int j = 0; j < f.get_slice_count("," ); j++) { |
607 | patterns.push_back(f.get_slice("," , j).strip_edges()); |
608 | } |
609 | } |
610 | } |
611 | |
612 | String base_dir = dir_access->get_current_dir(); |
613 | |
614 | while (!files.is_empty()) { |
615 | bool match = patterns.is_empty(); |
616 | String match_str; |
617 | |
618 | for (const String &E : patterns) { |
619 | if (files.front()->get().matchn(E)) { |
620 | match_str = E; |
621 | match = true; |
622 | break; |
623 | } |
624 | } |
625 | |
626 | if (match) { |
627 | TreeItem *ti = tree->create_item(root); |
628 | ti->set_text(0, files.front()->get()); |
629 | |
630 | if (get_icon_func) { |
631 | Ref<Texture2D> icon = get_icon_func(base_dir.path_join(files.front()->get())); |
632 | ti->set_icon(0, icon); |
633 | } else { |
634 | ti->set_icon(0, theme_cache.file); |
635 | } |
636 | ti->set_icon_modulate(0, theme_cache.file_icon_color); |
637 | |
638 | if (mode == FILE_MODE_OPEN_DIR) { |
639 | ti->set_custom_color(0, theme_cache.file_disabled_color); |
640 | ti->set_selectable(0, false); |
641 | } |
642 | Dictionary d; |
643 | d["name" ] = files.front()->get(); |
644 | d["dir" ] = false; |
645 | ti->set_metadata(0, d); |
646 | |
647 | if (file->get_text() == files.front()->get() || match_str == files.front()->get()) { |
648 | ti->select(0); |
649 | } |
650 | } |
651 | |
652 | files.pop_front(); |
653 | } |
654 | |
655 | if (mode != FILE_MODE_SAVE_FILE) { |
656 | // Select the first file from list if nothing is selected. |
657 | if (tree->get_root() && tree->get_root()->get_first_child() && tree->get_selected() == nullptr) { |
658 | tree->get_root()->get_first_child()->select(0); |
659 | } |
660 | } |
661 | } |
662 | |
663 | void FileDialog::_filter_selected(int) { |
664 | update_file_name(); |
665 | update_file_list(); |
666 | } |
667 | |
668 | void FileDialog::update_filters() { |
669 | filter->clear(); |
670 | |
671 | if (filters.size() > 1) { |
672 | String all_filters; |
673 | |
674 | const int max_filters = 5; |
675 | |
676 | for (int i = 0; i < MIN(max_filters, filters.size()); i++) { |
677 | String flt = filters[i].get_slice(";" , 0).strip_edges(); |
678 | if (i > 0) { |
679 | all_filters += ", " ; |
680 | } |
681 | all_filters += flt; |
682 | } |
683 | |
684 | if (max_filters < filters.size()) { |
685 | all_filters += ", ..." ; |
686 | } |
687 | |
688 | filter->add_item(RTR("All Recognized" ) + " (" + all_filters + ")" ); |
689 | } |
690 | for (int i = 0; i < filters.size(); i++) { |
691 | String flt = filters[i].get_slice(";" , 0).strip_edges(); |
692 | String desc = filters[i].get_slice(";" , 1).strip_edges(); |
693 | if (desc.length()) { |
694 | filter->add_item(String(tr(desc)) + " (" + flt + ")" ); |
695 | } else { |
696 | filter->add_item("(" + flt + ")" ); |
697 | } |
698 | } |
699 | |
700 | filter->add_item(RTR("All Files" ) + " (*)" ); |
701 | } |
702 | |
703 | void FileDialog::clear_filters() { |
704 | filters.clear(); |
705 | update_filters(); |
706 | invalidate(); |
707 | } |
708 | |
709 | void FileDialog::add_filter(const String &p_filter, const String &p_description) { |
710 | ERR_FAIL_COND_MSG(p_filter.begins_with("." ), "Filter must be \"filename.extension\", can't start with dot." ); |
711 | if (p_description.is_empty()) { |
712 | filters.push_back(p_filter); |
713 | } else { |
714 | filters.push_back(vformat("%s ; %s" , p_filter, p_description)); |
715 | } |
716 | update_filters(); |
717 | invalidate(); |
718 | } |
719 | |
720 | void FileDialog::set_filters(const Vector<String> &p_filters) { |
721 | if (filters == p_filters) { |
722 | return; |
723 | } |
724 | filters = p_filters; |
725 | update_filters(); |
726 | invalidate(); |
727 | } |
728 | |
729 | Vector<String> FileDialog::get_filters() const { |
730 | return filters; |
731 | } |
732 | |
733 | String FileDialog::get_current_dir() const { |
734 | return dir->get_text(); |
735 | } |
736 | |
737 | String FileDialog::get_current_file() const { |
738 | return file->get_text(); |
739 | } |
740 | |
741 | String FileDialog::get_current_path() const { |
742 | return dir->get_text().path_join(file->get_text()); |
743 | } |
744 | |
745 | void FileDialog::set_current_dir(const String &p_dir) { |
746 | _change_dir(p_dir); |
747 | |
748 | _push_history(); |
749 | } |
750 | |
751 | void FileDialog::set_current_file(const String &p_file) { |
752 | if (file->get_text() == p_file) { |
753 | return; |
754 | } |
755 | file->set_text(p_file); |
756 | update_dir(); |
757 | invalidate(); |
758 | _focus_file_text(); |
759 | } |
760 | |
761 | void FileDialog::set_current_path(const String &p_path) { |
762 | if (!p_path.size()) { |
763 | return; |
764 | } |
765 | int pos = MAX(p_path.rfind("/" ), p_path.rfind("\\" )); |
766 | if (pos == -1) { |
767 | set_current_file(p_path); |
768 | } else { |
769 | String path_dir = p_path.substr(0, pos); |
770 | String path_file = p_path.substr(pos + 1, p_path.length()); |
771 | set_current_dir(path_dir); |
772 | set_current_file(path_file); |
773 | } |
774 | } |
775 | |
776 | void FileDialog::set_root_subfolder(const String &p_root) { |
777 | root_subfolder = p_root; |
778 | ERR_FAIL_COND_MSG(!dir_access->dir_exists(p_root), "root_subfolder must be an existing sub-directory." ); |
779 | |
780 | local_history.clear(); |
781 | local_history_pos = -1; |
782 | |
783 | dir_access->change_dir(root_subfolder); |
784 | if (root_subfolder.is_empty()) { |
785 | root_prefix = "" ; |
786 | } else { |
787 | root_prefix = dir_access->get_current_dir(); |
788 | } |
789 | invalidate(); |
790 | update_dir(); |
791 | } |
792 | |
793 | String FileDialog::get_root_subfolder() const { |
794 | return root_subfolder; |
795 | } |
796 | |
797 | void FileDialog::set_mode_overrides_title(bool p_override) { |
798 | mode_overrides_title = p_override; |
799 | } |
800 | |
801 | bool FileDialog::is_mode_overriding_title() const { |
802 | return mode_overrides_title; |
803 | } |
804 | |
805 | void FileDialog::set_file_mode(FileMode p_mode) { |
806 | ERR_FAIL_INDEX((int)p_mode, 5); |
807 | if (mode == p_mode) { |
808 | return; |
809 | } |
810 | mode = p_mode; |
811 | switch (mode) { |
812 | case FILE_MODE_OPEN_FILE: |
813 | set_ok_button_text(RTR("Open" )); |
814 | if (mode_overrides_title) { |
815 | set_title(TTRC("Open a File" )); |
816 | } |
817 | makedir->hide(); |
818 | break; |
819 | case FILE_MODE_OPEN_FILES: |
820 | set_ok_button_text(RTR("Open" )); |
821 | if (mode_overrides_title) { |
822 | set_title(TTRC("Open File(s)" )); |
823 | } |
824 | makedir->hide(); |
825 | break; |
826 | case FILE_MODE_OPEN_DIR: |
827 | set_ok_button_text(RTR("Select Current Folder" )); |
828 | if (mode_overrides_title) { |
829 | set_title(TTRC("Open a Directory" )); |
830 | } |
831 | makedir->show(); |
832 | break; |
833 | case FILE_MODE_OPEN_ANY: |
834 | set_ok_button_text(RTR("Open" )); |
835 | if (mode_overrides_title) { |
836 | set_title(TTRC("Open a File or Directory" )); |
837 | } |
838 | makedir->show(); |
839 | break; |
840 | case FILE_MODE_SAVE_FILE: |
841 | set_ok_button_text(RTR("Save" )); |
842 | if (mode_overrides_title) { |
843 | set_title(TTRC("Save a File" )); |
844 | } |
845 | makedir->show(); |
846 | break; |
847 | } |
848 | |
849 | if (mode == FILE_MODE_OPEN_FILES) { |
850 | tree->set_select_mode(Tree::SELECT_MULTI); |
851 | } else { |
852 | tree->set_select_mode(Tree::SELECT_SINGLE); |
853 | } |
854 | |
855 | get_ok_button()->set_disabled(_is_open_should_be_disabled()); |
856 | } |
857 | |
858 | FileDialog::FileMode FileDialog::get_file_mode() const { |
859 | return mode; |
860 | } |
861 | |
862 | void FileDialog::set_access(Access p_access) { |
863 | ERR_FAIL_INDEX(p_access, 3); |
864 | if (access == p_access) { |
865 | return; |
866 | } |
867 | switch (p_access) { |
868 | case ACCESS_FILESYSTEM: { |
869 | dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); |
870 | } break; |
871 | case ACCESS_RESOURCES: { |
872 | dir_access = DirAccess::create(DirAccess::ACCESS_RESOURCES); |
873 | } break; |
874 | case ACCESS_USERDATA: { |
875 | dir_access = DirAccess::create(DirAccess::ACCESS_USERDATA); |
876 | } break; |
877 | } |
878 | access = p_access; |
879 | root_prefix = "" ; |
880 | root_subfolder = "" ; |
881 | _update_drives(); |
882 | invalidate(); |
883 | update_filters(); |
884 | update_dir(); |
885 | } |
886 | |
887 | void FileDialog::invalidate() { |
888 | if (!is_visible() || is_invalidating) { |
889 | return; |
890 | } |
891 | |
892 | is_invalidating = true; |
893 | callable_mp(this, &FileDialog::_invalidate).call_deferred(); |
894 | } |
895 | |
896 | void FileDialog::_invalidate() { |
897 | if (!is_invalidating) { |
898 | return; |
899 | } |
900 | |
901 | update_file_list(); |
902 | |
903 | is_invalidating = false; |
904 | } |
905 | |
906 | FileDialog::Access FileDialog::get_access() const { |
907 | return access; |
908 | } |
909 | |
910 | void FileDialog::_make_dir_confirm() { |
911 | Error err = dir_access->make_dir(makedirname->get_text().strip_edges()); |
912 | if (err == OK) { |
913 | _change_dir(makedirname->get_text().strip_edges()); |
914 | update_filters(); |
915 | _push_history(); |
916 | } else { |
917 | mkdirerr->popup_centered(Size2(250, 50)); |
918 | } |
919 | makedirname->set_text("" ); // reset label |
920 | } |
921 | |
922 | void FileDialog::_make_dir() { |
923 | makedialog->popup_centered(Size2(250, 80)); |
924 | makedirname->grab_focus(); |
925 | } |
926 | |
927 | void FileDialog::_select_drive(int p_idx) { |
928 | String d = drives->get_item_text(p_idx); |
929 | _change_dir(d); |
930 | file->set_text("" ); |
931 | _push_history(); |
932 | } |
933 | |
934 | void FileDialog::_change_dir(const String &p_new_dir) { |
935 | if (root_prefix.is_empty()) { |
936 | dir_access->change_dir(p_new_dir); |
937 | } else { |
938 | String old_dir = dir_access->get_current_dir(); |
939 | dir_access->change_dir(p_new_dir); |
940 | if (!dir_access->get_current_dir(false).begins_with(root_prefix)) { |
941 | dir_access->change_dir(old_dir); |
942 | return; |
943 | } |
944 | } |
945 | |
946 | invalidate(); |
947 | update_dir(); |
948 | } |
949 | |
950 | void FileDialog::_update_drives(bool p_select) { |
951 | int dc = dir_access->get_drive_count(); |
952 | if (dc == 0 || access != ACCESS_FILESYSTEM) { |
953 | drives->hide(); |
954 | } else { |
955 | drives->clear(); |
956 | Node *dp = drives->get_parent(); |
957 | if (dp) { |
958 | dp->remove_child(drives); |
959 | } |
960 | dp = dir_access->drives_are_shortcuts() ? shortcuts_container : drives_container; |
961 | dp->add_child(drives); |
962 | drives->show(); |
963 | |
964 | for (int i = 0; i < dir_access->get_drive_count(); i++) { |
965 | drives->add_item(dir_access->get_drive(i)); |
966 | } |
967 | |
968 | if (p_select) { |
969 | drives->select(dir_access->get_current_drive()); |
970 | } |
971 | } |
972 | } |
973 | |
974 | bool FileDialog::default_show_hidden_files = false; |
975 | |
976 | void FileDialog::_bind_methods() { |
977 | ClassDB::bind_method(D_METHOD("_cancel_pressed" ), &FileDialog::_cancel_pressed); |
978 | |
979 | ClassDB::bind_method(D_METHOD("clear_filters" ), &FileDialog::clear_filters); |
980 | ClassDB::bind_method(D_METHOD("add_filter" , "filter" , "description" ), &FileDialog::add_filter, DEFVAL("" )); |
981 | ClassDB::bind_method(D_METHOD("set_filters" , "filters" ), &FileDialog::set_filters); |
982 | ClassDB::bind_method(D_METHOD("get_filters" ), &FileDialog::get_filters); |
983 | ClassDB::bind_method(D_METHOD("get_current_dir" ), &FileDialog::get_current_dir); |
984 | ClassDB::bind_method(D_METHOD("get_current_file" ), &FileDialog::get_current_file); |
985 | ClassDB::bind_method(D_METHOD("get_current_path" ), &FileDialog::get_current_path); |
986 | ClassDB::bind_method(D_METHOD("set_current_dir" , "dir" ), &FileDialog::set_current_dir); |
987 | ClassDB::bind_method(D_METHOD("set_current_file" , "file" ), &FileDialog::set_current_file); |
988 | ClassDB::bind_method(D_METHOD("set_current_path" , "path" ), &FileDialog::set_current_path); |
989 | ClassDB::bind_method(D_METHOD("set_mode_overrides_title" , "override" ), &FileDialog::set_mode_overrides_title); |
990 | ClassDB::bind_method(D_METHOD("is_mode_overriding_title" ), &FileDialog::is_mode_overriding_title); |
991 | ClassDB::bind_method(D_METHOD("set_file_mode" , "mode" ), &FileDialog::set_file_mode); |
992 | ClassDB::bind_method(D_METHOD("get_file_mode" ), &FileDialog::get_file_mode); |
993 | ClassDB::bind_method(D_METHOD("get_vbox" ), &FileDialog::get_vbox); |
994 | ClassDB::bind_method(D_METHOD("get_line_edit" ), &FileDialog::get_line_edit); |
995 | ClassDB::bind_method(D_METHOD("set_access" , "access" ), &FileDialog::set_access); |
996 | ClassDB::bind_method(D_METHOD("get_access" ), &FileDialog::get_access); |
997 | ClassDB::bind_method(D_METHOD("set_root_subfolder" , "dir" ), &FileDialog::set_root_subfolder); |
998 | ClassDB::bind_method(D_METHOD("get_root_subfolder" ), &FileDialog::get_root_subfolder); |
999 | ClassDB::bind_method(D_METHOD("set_show_hidden_files" , "show" ), &FileDialog::set_show_hidden_files); |
1000 | ClassDB::bind_method(D_METHOD("is_showing_hidden_files" ), &FileDialog::is_showing_hidden_files); |
1001 | ClassDB::bind_method(D_METHOD("set_use_native_dialog" , "native" ), &FileDialog::set_use_native_dialog); |
1002 | ClassDB::bind_method(D_METHOD("get_use_native_dialog" ), &FileDialog::get_use_native_dialog); |
1003 | ClassDB::bind_method(D_METHOD("deselect_all" ), &FileDialog::deselect_all); |
1004 | |
1005 | ClassDB::bind_method(D_METHOD("invalidate" ), &FileDialog::invalidate); |
1006 | |
1007 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "mode_overrides_title" ), "set_mode_overrides_title" , "is_mode_overriding_title" ); |
1008 | ADD_PROPERTY(PropertyInfo(Variant::INT, "file_mode" , PROPERTY_HINT_ENUM, "Open File,Open Files,Open Folder,Open Any,Save" ), "set_file_mode" , "get_file_mode" ); |
1009 | ADD_PROPERTY(PropertyInfo(Variant::INT, "access" , PROPERTY_HINT_ENUM, "Resources,User Data,File System" ), "set_access" , "get_access" ); |
1010 | ADD_PROPERTY(PropertyInfo(Variant::STRING, "root_subfolder" ), "set_root_subfolder" , "get_root_subfolder" ); |
1011 | ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "filters" ), "set_filters" , "get_filters" ); |
1012 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_hidden_files" ), "set_show_hidden_files" , "is_showing_hidden_files" ); |
1013 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_native_dialog" ), "set_use_native_dialog" , "get_use_native_dialog" ); |
1014 | ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_dir" , PROPERTY_HINT_DIR, "" , PROPERTY_USAGE_NONE), "set_current_dir" , "get_current_dir" ); |
1015 | ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_file" , PROPERTY_HINT_FILE, "*" , PROPERTY_USAGE_NONE), "set_current_file" , "get_current_file" ); |
1016 | ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_path" , PROPERTY_HINT_NONE, "" , PROPERTY_USAGE_NONE), "set_current_path" , "get_current_path" ); |
1017 | |
1018 | ADD_SIGNAL(MethodInfo("file_selected" , PropertyInfo(Variant::STRING, "path" ))); |
1019 | ADD_SIGNAL(MethodInfo("files_selected" , PropertyInfo(Variant::PACKED_STRING_ARRAY, "paths" ))); |
1020 | ADD_SIGNAL(MethodInfo("dir_selected" , PropertyInfo(Variant::STRING, "dir" ))); |
1021 | |
1022 | BIND_ENUM_CONSTANT(FILE_MODE_OPEN_FILE); |
1023 | BIND_ENUM_CONSTANT(FILE_MODE_OPEN_FILES); |
1024 | BIND_ENUM_CONSTANT(FILE_MODE_OPEN_DIR); |
1025 | BIND_ENUM_CONSTANT(FILE_MODE_OPEN_ANY); |
1026 | BIND_ENUM_CONSTANT(FILE_MODE_SAVE_FILE); |
1027 | |
1028 | BIND_ENUM_CONSTANT(ACCESS_RESOURCES); |
1029 | BIND_ENUM_CONSTANT(ACCESS_USERDATA); |
1030 | BIND_ENUM_CONSTANT(ACCESS_FILESYSTEM); |
1031 | |
1032 | BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, parent_folder); |
1033 | BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, forward_folder); |
1034 | BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, back_folder); |
1035 | BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, reload); |
1036 | BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, toggle_hidden); |
1037 | BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, folder); |
1038 | BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, file); |
1039 | |
1040 | BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, FileDialog, folder_icon_color); |
1041 | BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, FileDialog, file_icon_color); |
1042 | BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, FileDialog, file_disabled_color); |
1043 | |
1044 | // TODO: Define own colors? |
1045 | BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, FileDialog, icon_normal_color, "font_color" , "Button" ); |
1046 | BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, FileDialog, icon_hover_color, "font_hover_color" , "Button" ); |
1047 | BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, FileDialog, icon_focus_color, "font_focus_color" , "Button" ); |
1048 | BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, FileDialog, icon_pressed_color, "font_pressed_color" , "Button" ); |
1049 | } |
1050 | |
1051 | void FileDialog::set_show_hidden_files(bool p_show) { |
1052 | if (show_hidden_files == p_show) { |
1053 | return; |
1054 | } |
1055 | show_hidden_files = p_show; |
1056 | invalidate(); |
1057 | } |
1058 | |
1059 | bool FileDialog::is_showing_hidden_files() const { |
1060 | return show_hidden_files; |
1061 | } |
1062 | |
1063 | void FileDialog::set_default_show_hidden_files(bool p_show) { |
1064 | default_show_hidden_files = p_show; |
1065 | } |
1066 | |
1067 | void FileDialog::set_use_native_dialog(bool p_native) { |
1068 | use_native_dialog = p_native; |
1069 | } |
1070 | |
1071 | bool FileDialog::get_use_native_dialog() const { |
1072 | return use_native_dialog; |
1073 | } |
1074 | |
1075 | FileDialog::FileDialog() { |
1076 | show_hidden_files = default_show_hidden_files; |
1077 | |
1078 | vbox = memnew(VBoxContainer); |
1079 | add_child(vbox, false, INTERNAL_MODE_FRONT); |
1080 | |
1081 | mode = FILE_MODE_SAVE_FILE; |
1082 | set_title(TTRC("Save a File" )); |
1083 | |
1084 | HBoxContainer *hbc = memnew(HBoxContainer); |
1085 | |
1086 | dir_prev = memnew(Button); |
1087 | dir_prev->set_flat(true); |
1088 | dir_prev->set_tooltip_text(RTR("Go to previous folder." )); |
1089 | dir_next = memnew(Button); |
1090 | dir_next->set_flat(true); |
1091 | dir_next->set_tooltip_text(RTR("Go to next folder." )); |
1092 | dir_up = memnew(Button); |
1093 | dir_up->set_flat(true); |
1094 | dir_up->set_tooltip_text(RTR("Go to parent folder." )); |
1095 | hbc->add_child(dir_prev); |
1096 | hbc->add_child(dir_next); |
1097 | hbc->add_child(dir_up); |
1098 | dir_prev->connect("pressed" , callable_mp(this, &FileDialog::_go_back)); |
1099 | dir_next->connect("pressed" , callable_mp(this, &FileDialog::_go_forward)); |
1100 | dir_up->connect("pressed" , callable_mp(this, &FileDialog::_go_up)); |
1101 | |
1102 | hbc->add_child(memnew(Label(RTR("Path:" )))); |
1103 | |
1104 | drives_container = memnew(HBoxContainer); |
1105 | hbc->add_child(drives_container); |
1106 | |
1107 | drives = memnew(OptionButton); |
1108 | drives->connect("item_selected" , callable_mp(this, &FileDialog::_select_drive)); |
1109 | hbc->add_child(drives); |
1110 | |
1111 | dir = memnew(LineEdit); |
1112 | dir->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE); |
1113 | hbc->add_child(dir); |
1114 | dir->set_h_size_flags(Control::SIZE_EXPAND_FILL); |
1115 | |
1116 | refresh = memnew(Button); |
1117 | refresh->set_flat(true); |
1118 | refresh->set_tooltip_text(RTR("Refresh files." )); |
1119 | refresh->connect("pressed" , callable_mp(this, &FileDialog::update_file_list)); |
1120 | hbc->add_child(refresh); |
1121 | |
1122 | show_hidden = memnew(Button); |
1123 | show_hidden->set_flat(true); |
1124 | show_hidden->set_toggle_mode(true); |
1125 | show_hidden->set_pressed(is_showing_hidden_files()); |
1126 | show_hidden->set_tooltip_text(RTR("Toggle the visibility of hidden files." )); |
1127 | show_hidden->connect("toggled" , callable_mp(this, &FileDialog::set_show_hidden_files)); |
1128 | hbc->add_child(show_hidden); |
1129 | |
1130 | shortcuts_container = memnew(HBoxContainer); |
1131 | hbc->add_child(shortcuts_container); |
1132 | |
1133 | makedir = memnew(Button); |
1134 | makedir->set_text(RTR("Create Folder" )); |
1135 | makedir->connect("pressed" , callable_mp(this, &FileDialog::_make_dir)); |
1136 | hbc->add_child(makedir); |
1137 | vbox->add_child(hbc); |
1138 | |
1139 | tree = memnew(Tree); |
1140 | tree->set_hide_root(true); |
1141 | vbox->add_margin_child(RTR("Directories & Files:" ), tree, true); |
1142 | |
1143 | message = memnew(Label); |
1144 | message->hide(); |
1145 | message->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); |
1146 | message->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); |
1147 | message->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER); |
1148 | tree->add_child(message); |
1149 | |
1150 | file_box = memnew(HBoxContainer); |
1151 | file_box->add_child(memnew(Label(RTR("File:" )))); |
1152 | file = memnew(LineEdit); |
1153 | file->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE); |
1154 | file->set_stretch_ratio(4); |
1155 | file->set_h_size_flags(Control::SIZE_EXPAND_FILL); |
1156 | file_box->add_child(file); |
1157 | filter = memnew(OptionButton); |
1158 | filter->set_stretch_ratio(3); |
1159 | filter->set_h_size_flags(Control::SIZE_EXPAND_FILL); |
1160 | filter->set_clip_text(true); // too many extensions overflows it |
1161 | file_box->add_child(filter); |
1162 | vbox->add_child(file_box); |
1163 | |
1164 | dir_access = DirAccess::create(DirAccess::ACCESS_RESOURCES); |
1165 | _update_drives(); |
1166 | |
1167 | connect("confirmed" , callable_mp(this, &FileDialog::_action_pressed)); |
1168 | tree->connect("multi_selected" , callable_mp(this, &FileDialog::_tree_multi_selected), CONNECT_DEFERRED); |
1169 | tree->connect("cell_selected" , callable_mp(this, &FileDialog::_tree_selected), CONNECT_DEFERRED); |
1170 | tree->connect("item_activated" , callable_mp(this, &FileDialog::_tree_item_activated)); |
1171 | tree->connect("nothing_selected" , callable_mp(this, &FileDialog::deselect_all)); |
1172 | dir->connect("text_submitted" , callable_mp(this, &FileDialog::_dir_submitted)); |
1173 | file->connect("text_submitted" , callable_mp(this, &FileDialog::_file_submitted)); |
1174 | filter->connect("item_selected" , callable_mp(this, &FileDialog::_filter_selected)); |
1175 | |
1176 | confirm_save = memnew(ConfirmationDialog); |
1177 | add_child(confirm_save, false, INTERNAL_MODE_FRONT); |
1178 | |
1179 | confirm_save->connect("confirmed" , callable_mp(this, &FileDialog::_save_confirm_pressed)); |
1180 | |
1181 | makedialog = memnew(ConfirmationDialog); |
1182 | makedialog->set_title(RTR("Create Folder" )); |
1183 | VBoxContainer *makevb = memnew(VBoxContainer); |
1184 | makedialog->add_child(makevb); |
1185 | |
1186 | makedirname = memnew(LineEdit); |
1187 | makedirname->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE); |
1188 | makevb->add_margin_child(RTR("Name:" ), makedirname); |
1189 | add_child(makedialog, false, INTERNAL_MODE_FRONT); |
1190 | makedialog->register_text_enter(makedirname); |
1191 | makedialog->connect("confirmed" , callable_mp(this, &FileDialog::_make_dir_confirm)); |
1192 | mkdirerr = memnew(AcceptDialog); |
1193 | mkdirerr->set_text(RTR("Could not create folder." )); |
1194 | add_child(mkdirerr, false, INTERNAL_MODE_FRONT); |
1195 | |
1196 | exterr = memnew(AcceptDialog); |
1197 | exterr->set_text(RTR("Invalid extension, or empty filename." )); |
1198 | add_child(exterr, false, INTERNAL_MODE_FRONT); |
1199 | |
1200 | update_filters(); |
1201 | update_dir(); |
1202 | |
1203 | set_hide_on_ok(false); |
1204 | |
1205 | if (register_func) { |
1206 | register_func(this); |
1207 | } |
1208 | } |
1209 | |
1210 | FileDialog::~FileDialog() { |
1211 | if (unregister_func) { |
1212 | unregister_func(this); |
1213 | } |
1214 | } |
1215 | |