diff options
Diffstat (limited to 'editor/filesystem_dock.cpp')
-rw-r--r-- | editor/filesystem_dock.cpp | 247 |
1 files changed, 205 insertions, 42 deletions
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 0aa8ef66e7..9e11ffa242 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -47,7 +47,7 @@ #include "editor/editor_string_names.h" #include "editor/gui/editor_dir_dialog.h" #include "editor/gui/editor_scene_tabs.h" -#include "editor/import/resource_importer_scene.h" +#include "editor/import/scene_import_settings.h" #include "editor/import_dock.h" #include "editor/plugins/editor_resource_tooltip_plugins.h" #include "editor/scene_create_dialog.h" @@ -88,6 +88,7 @@ void FileSystemList::_line_editor_submit(String p_text) { bool FileSystemList::edit_selected() { ERR_FAIL_COND_V_MSG(!is_anything_selected(), false, "No item selected."); int s = get_current(); + ERR_FAIL_COND_V_MSG(s < 0, false, "No current item selected."); ensure_current_is_visible(); Rect2 rect; @@ -469,8 +470,6 @@ void FileSystemDock::_update_display_mode(bool p_force) { case DISPLAY_MODE_HSPLIT: case DISPLAY_MODE_VSPLIT: const bool is_vertical = display_mode == DISPLAY_MODE_VSPLIT; - const int split_offset = split_box->get_split_offset(); - is_vertical ? split_box_offset_h = split_offset : split_box_offset_v = split_offset; split_box->set_vertical(is_vertical); const int actual_offset = is_vertical ? split_box_offset_v : split_box_offset_h; @@ -755,6 +754,14 @@ void FileSystemDock::_navigate_to_path(const String &p_path, bool p_select_in_fa void FileSystemDock::navigate_to_path(const String &p_path) { file_list_search_box->clear(); _navigate_to_path(p_path); + + // Ensure that the FileSystem dock is visible. + if (get_window() == get_tree()->get_root()) { + TabContainer *tab_container = (TabContainer *)get_parent_control(); + tab_container->set_current_tab(tab_container->get_tab_idx_from_control((Control *)this)); + } else { + get_window()->grab_focus(); + } } void FileSystemDock::_file_list_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, const Variant &p_udata) { @@ -907,12 +914,13 @@ void FileSystemDock::_sort_file_info_list(List<FileSystemDock::FileInfo> &r_file } void FileSystemDock::_update_file_list(bool p_keep_selection) { - // Register the previously selected items. - HashSet<String> cselection; + // Register the previously current and selected items. + HashSet<String> previous_selection; + HashSet<int> valid_selection; if (p_keep_selection) { for (int i = 0; i < files->get_item_count(); i++) { if (files->is_selected(i)) { - cselection.insert(files->get_item_text(i)); + previous_selection.insert(files->get_item_text(i)); } } } @@ -1068,8 +1076,9 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { Color this_folder_color = has_custom_color ? folder_colors[assigned_folder_colors[dpath]] : inherited_folder_color; files->set_item_icon_modulate(-1, editor_is_dark_theme ? this_folder_color : this_folder_color * 1.75); - if (cselection.has(dname)) { + if (previous_selection.has(dname)) { files->select(files->get_item_count() - 1, false); + valid_selection.insert(files->get_item_count() - 1); } } } @@ -1142,8 +1151,9 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { } // Select the items. - if (cselection.has(fname)) { + if (previous_selection.has(fname)) { files->select(item_index, false); + valid_selection.insert(item_index); } if (!p_keep_selection && !file.is_empty() && fname == file) { @@ -1159,6 +1169,11 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { } files->set_item_tooltip(item_index, tooltip); } + + // If we only have any selected items retained, we need to update the current idx. + if (!valid_selection.is_empty()) { + files->set_current(*valid_selection.begin()); + } } void FileSystemDock::_select_file(const String &p_path, bool p_select_in_favorites) { @@ -1183,32 +1198,12 @@ void FileSystemDock::_select_file(const String &p_path, bool p_select_in_favorit String resource_type = ResourceLoader::get_resource_type(fpath); - if (resource_type == "PackedScene") { - bool is_imported = false; - - { - List<String> importer_exts; - ResourceImporterScene::get_scene_singleton()->get_recognized_extensions(&importer_exts); - String extension = fpath.get_extension(); - for (const String &E : importer_exts) { - if (extension.nocasecmp_to(E) == 0) { - is_imported = true; - break; - } - } - } - - if (is_imported) { - ResourceImporterScene::get_scene_singleton()->show_advanced_options(fpath); - } else { - EditorNode::get_singleton()->open_request(fpath); - } - } else if (resource_type == "AnimationLibrary") { + if (resource_type == "PackedScene" || resource_type == "AnimationLibrary") { bool is_imported = false; { List<String> importer_exts; - ResourceImporterScene::get_animation_singleton()->get_recognized_extensions(&importer_exts); + ResourceImporterScene::get_scene_importer_extensions(&importer_exts); String extension = fpath.get_extension(); for (const String &E : importer_exts) { if (extension.nocasecmp_to(E) == 0) { @@ -1219,7 +1214,7 @@ void FileSystemDock::_select_file(const String &p_path, bool p_select_in_favorit } if (is_imported) { - ResourceImporterScene::get_animation_singleton()->show_advanced_options(fpath); + SceneImportSettingsDialog::get_singleton()->open_settings(p_path, resource_type == "AnimationLibrary"); } else { EditorNode::get_singleton()->open_request(fpath); } @@ -1299,6 +1294,13 @@ void FileSystemDock::_fs_changed() { _update_file_list(true); } + if (!select_after_scan.is_empty()) { + _navigate_to_path(select_after_scan); + select_after_scan.clear(); + import_dock_needs_update = true; + _update_import_dock(); + } + set_process(false); } @@ -1478,8 +1480,6 @@ void FileSystemDock::_try_duplicate_item(const FileOrFolder &p_item, const Strin EditorNode::get_singleton()->add_io_error(TTR("Cannot move a folder into itself.") + "\n" + old_path + "\n"); return; } - const_cast<FileSystemDock *>(this)->current_path = new_path; - Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES); if (p_item.is_file) { @@ -1586,6 +1586,9 @@ void FileSystemDock::_update_dependencies_after_move(const HashMap<String, Strin // The following code assumes that the following holds: // 1) EditorFileSystem contains the old paths/folder structure from before the rename/move. // 2) ResourceLoader can use the new paths without needing to call rescan. + + // The currently edited scene should be reloaded first, so get it's path (GH-82652). + const String &edited_scene_path = EditorNode::get_editor_data().get_scene_path(EditorNode::get_editor_data().get_edited_scene()); List<String> scenes_to_reload; for (const String &E : p_file_owners) { // Because we haven't called a rescan yet the found remap might still be an old path itself. @@ -1595,7 +1598,11 @@ void FileSystemDock::_update_dependencies_after_move(const HashMap<String, Strin const Error err = ResourceLoader::rename_dependencies(file, p_renames); if (err == OK) { if (ResourceLoader::get_resource_type(file) == "PackedScene") { - scenes_to_reload.push_back(file); + if (file == edited_scene_path) { + scenes_to_reload.push_front(file); + } else { + scenes_to_reload.push_back(file); + } } } else { EditorNode::get_singleton()->add_io_error(TTR("Unable to update dependencies for:") + "\n" + E + "\n"); @@ -1874,10 +1881,6 @@ Vector<String> FileSystemDock::_check_existing() { return conflicting_items; } -void FileSystemDock::_move_dialog_confirm(const String &p_path) { - _move_operation_confirm(p_path, move_dialog->is_copy_pressed()); -} - void FileSystemDock::_move_operation_confirm(const String &p_to_path, bool p_copy, Overwrite p_overwrite) { if (p_overwrite == OVERWRITE_UNDECIDED) { to_move_path = p_to_path; @@ -1912,6 +1915,7 @@ void FileSystemDock::_move_operation_confirm(const String &p_to_path, bool p_cop for (int i = 0; i < to_move.size(); i++) { if (to_move[i].path != new_paths[i]) { _try_duplicate_item(to_move[i], new_paths[i]); + select_after_scan = new_paths[i]; is_copied = true; } } @@ -2125,6 +2129,135 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } } break; + case FILE_OPEN_IN_TERMINAL: { + String fpath = current_path; + if (current_path == "Favorites") { + fpath = p_selected[0]; + } + + Vector<String> terminal_emulators; + const String terminal_emulator_setting = EDITOR_GET("filesystem/external_programs/terminal_emulator"); + if (terminal_emulator_setting.is_empty()) { + // Figure out a default terminal emulator to use. +#if defined(WINDOWS_ENABLED) + // Default to PowerShell as done by Windows 10 and later. + terminal_emulators.push_back("powershell"); +#elif defined(MACOS_ENABLED) + terminal_emulators.push_back("/System/Applications/Utilities/Terminal.app"); +#elif defined(LINUXBSD_ENABLED) + // Try terminal emulators that ship with common Linux distributions first. + terminal_emulators.push_back("gnome-terminal"); + terminal_emulators.push_back("konsole"); + terminal_emulators.push_back("xfce4-terminal"); + terminal_emulators.push_back("lxterminal"); + terminal_emulators.push_back("kitty"); + terminal_emulators.push_back("alacritty"); + terminal_emulators.push_back("urxvt"); + terminal_emulators.push_back("xterm"); +#endif + } else { + // Use the user-specified terminal. + terminal_emulators.push_back(terminal_emulator_setting); + } + + String arguments = EDITOR_GET("filesystem/external_programs/terminal_emulator_flags"); + if (arguments.is_empty()) { + // NOTE: This default value is ignored further below if the terminal executable is `powershell` or `cmd`, + // due to these terminals requiring nonstandard syntax to start in a specified folder. + arguments = "{directory}"; + } + +#ifdef LINUXBSD_ENABLED + String chosen_terminal_emulator; + for (const String &terminal_emulator : terminal_emulators) { + List<String> test_args; // Required for `execute()`, as it doesn't accept `Vector<String>`. + test_args.push_back("-v"); + test_args.push_back(terminal_emulator); + // Silence command name being printed when found. (stderr is already silenced by `OS::execute()` by default.) + // FIXME: This doesn't appear to silence stdout. + test_args.push_back(">"); + test_args.push_back("/dev/null"); + int exit_code = 0; + const Error err = OS::get_singleton()->execute("command", test_args, nullptr, &exit_code); + if (err == OK && exit_code == EXIT_SUCCESS) { + chosen_terminal_emulator = terminal_emulator; + break; + } else if (err == ERR_CANT_FORK) { + ERR_PRINT_ED(vformat(TTR("Couldn't run external program to check for terminal emulator presence: command -v %s"), terminal_emulator)); + } + } +#else + // On Windows and macOS, the first (and only) terminal emulator in the list is always available. + String chosen_terminal_emulator = terminal_emulators[0]; +#endif + + List<String> terminal_emulator_args; // Required for `execute()`, as it doesn't accept `Vector<String>`. +#ifdef LINUXBSD_ENABLED + // Prepend default arguments based on the terminal emulator name. + // Use `String.ends_with()` so that installations in non-default paths + // or `/usr/local/bin` are detected correctly. + if (chosen_terminal_emulator.ends_with("konsole")) { + terminal_emulator_args.push_back("--workdir"); + } +#endif + + bool append_default_args = true; + +#ifdef WINDOWS_ENABLED + // Prepend default arguments based on the terminal emulator name. + // Use `String.get_basename().to_lower()` to handle Windows' case-insensitive paths + // with optional file extensions for executables in `PATH`. + if (chosen_terminal_emulator.get_basename().to_lower() == "powershell") { + terminal_emulator_args.push_back("-noexit"); + terminal_emulator_args.push_back("-command"); + terminal_emulator_args.push_back("cd '{directory}'"); + append_default_args = false; + } else if (chosen_terminal_emulator.get_basename().to_lower() == "cmd") { + terminal_emulator_args.push_back("/K"); + terminal_emulator_args.push_back("cd /d {directory}"); + append_default_args = false; + } +#endif + + Vector<String> arguments_array = arguments.split(" "); + for (const String &argument : arguments_array) { + if (!append_default_args && argument == "{directory}") { + // Prevent appending a `{directory}` placeholder twice when using powershell or cmd. + // This allows users to enter the path to cmd or PowerShell in the custom terminal emulator path, + // and make it work without having to enter custom arguments. + continue; + } + terminal_emulator_args.push_back(argument); + } + + const bool is_directory = fpath.ends_with("/"); + for (int i = 0; i < terminal_emulator_args.size(); i++) { + if (is_directory) { + terminal_emulator_args[i] = terminal_emulator_args[i].replace("{directory}", ProjectSettings::get_singleton()->globalize_path(fpath)); + } else { + terminal_emulator_args[i] = terminal_emulator_args[i].replace("{directory}", ProjectSettings::get_singleton()->globalize_path(fpath).get_base_dir()); + } + } + + if (OS::get_singleton()->is_stdout_verbose()) { + // Print full command line to help with troubleshooting. + String command_string = chosen_terminal_emulator; + for (const String &arg : terminal_emulator_args) { + command_string += " " + arg; + } + print_line("Opening terminal emulator:", command_string); + } + + const Error err = OS::get_singleton()->create_process(chosen_terminal_emulator, terminal_emulator_args, nullptr, true); + if (err != OK) { + String args_string; + for (int i = 0; i < terminal_emulator_args.size(); i++) { + args_string += terminal_emulator_args[i]; + } + ERR_PRINT_ED(vformat(TTR("Couldn't run external terminal program (error code %d): %s %s\nCheck `filesystem/external_programs/terminal_emulator` and `filesystem/external_programs/terminal_emulator_flags` in the Editor Settings."), err, chosen_terminal_emulator, args_string)); + } + } break; + case FILE_OPEN: { // Open folders. TreeItem *selected = tree->get_root(); @@ -2224,6 +2357,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } } if (to_move.size() > 0) { + move_dialog->config(p_selected); move_dialog->popup_centered_ratio(0.4); } } break; @@ -2449,6 +2583,14 @@ void FileSystemDock::_change_split_mode() { emit_signal(SNAME("display_mode_changed")); } +void FileSystemDock::_split_dragged(int p_offset) { + if (split_box->is_vertical()) { + split_box_offset_v = p_offset; + } else { + split_box_offset_h = p_offset; + } +} + void FileSystemDock::fix_dependencies(const String &p_for_file) { deps_editor->edit(p_for_file); } @@ -3045,12 +3187,16 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, Vector<Str // Opening the system file manager is not supported on the Android and web editors. const bool is_directory = fpath.ends_with("/"); - const String item_text = is_directory ? TTR("Open in File Manager") : TTR("Show in File Manager"); + p_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Filesystem")), ED_GET_SHORTCUT("filesystem_dock/show_in_explorer"), FILE_SHOW_IN_EXPLORER); - p_popup->set_item_text(p_popup->get_item_index(FILE_SHOW_IN_EXPLORER), item_text); + p_popup->set_item_text(p_popup->get_item_index(FILE_SHOW_IN_EXPLORER), is_directory ? TTR("Open in File Manager") : TTR("Show in File Manager")); + if (!is_directory) { p_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("ExternalLink")), ED_GET_SHORTCUT("filesystem_dock/open_in_external_program"), FILE_OPEN_EXTERNAL); } + + p_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Terminal")), ED_GET_SHORTCUT("filesystem_dock/open_in_terminal"), FILE_OPEN_IN_TERMINAL); + p_popup->set_item_text(p_popup->get_item_index(FILE_OPEN_IN_TERMINAL), is_directory ? TTR("Open in Terminal") : TTR("Open Containing Folder in Terminal")); #endif current_path = fpath; @@ -3095,6 +3241,7 @@ void FileSystemDock::_tree_empty_click(const Vector2 &p_pos, MouseButton p_butto // Opening the system file manager is not supported on the Android and web editors. tree_popup->add_separator(); tree_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Filesystem")), ED_GET_SHORTCUT("filesystem_dock/show_in_explorer"), FILE_SHOW_IN_EXPLORER); + tree_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Terminal")), ED_GET_SHORTCUT("filesystem_dock/open_in_terminal"), FILE_OPEN_IN_TERMINAL); #endif tree_popup->set_position(tree->get_screen_position() + p_pos); @@ -3273,6 +3420,8 @@ void FileSystemDock::_tree_gui_input(Ref<InputEvent> p_event) { _tree_rmb_option(FILE_SHOW_IN_EXPLORER); } else if (ED_IS_SHORTCUT("filesystem_dock/open_in_external_program", p_event)) { _tree_rmb_option(FILE_OPEN_EXTERNAL); + } else if (ED_IS_SHORTCUT("filesystem_dock/open_in_terminal", p_event)) { + _tree_rmb_option(FILE_OPEN_IN_TERMINAL); } else if (ED_IS_SHORTCUT("editor/open_search", p_event)) { focus_on_filter(); } else { @@ -3333,6 +3482,8 @@ void FileSystemDock::_file_list_gui_input(Ref<InputEvent> p_event) { _file_list_rmb_option(FILE_RENAME); } else if (ED_IS_SHORTCUT("filesystem_dock/show_in_explorer", p_event)) { _file_list_rmb_option(FILE_SHOW_IN_EXPLORER); + } else if (ED_IS_SHORTCUT("filesystem_dock/open_in_terminal", p_event)) { + _file_list_rmb_option(FILE_OPEN_IN_TERMINAL); } else if (ED_IS_SHORTCUT("editor/open_search", p_event)) { focus_on_filter(); } else { @@ -3468,6 +3619,14 @@ void FileSystemDock::_file_sort_popup(int p_id) { set_file_sort((FileSortOption)p_id); } +const HashMap<String, Color> &FileSystemDock::get_folder_colors() const { + return folder_colors; +} + +Dictionary FileSystemDock::get_assigned_folder_colors() const { + return assigned_folder_colors; +} + MenuButton *FileSystemDock::_create_file_menu_button() { MenuButton *button = memnew(MenuButton); button->set_flat(true); @@ -3529,6 +3688,7 @@ FileSystemDock::FileSystemDock() { // Opening the system file manager or opening in an external program is not supported on the Android and web editors. ED_SHORTCUT("filesystem_dock/show_in_explorer", TTR("Open in File Manager")); ED_SHORTCUT("filesystem_dock/open_in_external_program", TTR("Open in External Program")); + ED_SHORTCUT("filesystem_dock/open_in_terminal", TTR("Open in Terminal")); #endif // Properly translating color names would require a separate HashMap, so for simplicity they are provided as comments. @@ -3610,6 +3770,8 @@ FileSystemDock::FileSystemDock() { split_box = memnew(SplitContainer); split_box->set_v_size_flags(SIZE_EXPAND_FILL); + split_box->connect("dragged", callable_mp(this, &FileSystemDock::_split_dragged)); + split_box_offset_h = 240 * EDSCALE; add_child(split_box); tree = memnew(FileSystemTree); @@ -3690,7 +3852,8 @@ FileSystemDock::FileSystemDock() { move_dialog = memnew(EditorDirDialog); add_child(move_dialog); - move_dialog->connect("dir_selected", callable_mp(this, &FileSystemDock::_move_dialog_confirm)); + move_dialog->connect("move_pressed", callable_mp(this, &FileSystemDock::_move_operation_confirm).bind(false, OVERWRITE_UNDECIDED)); + move_dialog->connect("copy_pressed", callable_mp(this, &FileSystemDock::_move_operation_confirm).bind(true, OVERWRITE_UNDECIDED)); overwrite_dialog = memnew(ConfirmationDialog); add_child(overwrite_dialog); @@ -3727,7 +3890,7 @@ FileSystemDock::FileSystemDock() { make_dir_dialog = memnew(DirectoryCreateDialog); add_child(make_dir_dialog); - make_dir_dialog->connect("dir_created", callable_mp(this, &FileSystemDock::_rescan)); + make_dir_dialog->connect("dir_created", callable_mp(this, &FileSystemDock::_rescan).unbind(1)); make_scene_dialog = memnew(SceneCreateDialog); add_child(make_scene_dialog); |