summaryrefslogtreecommitdiffstats
path: root/editor/filesystem_dock.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'editor/filesystem_dock.cpp')
-rw-r--r--editor/filesystem_dock.cpp247
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);