Browse Source

Merge pull request #13724 from RayKoopa/editor_file_dialog_context_menu

Add context menu to editor file dialogs
Rémi Verschelde 7 years ago
parent
commit
9e2fad7e64

+ 1 - 0
editor/dependency_editor.h

@@ -35,6 +35,7 @@
 #include "scene/gui/tab_container.h"
 #include "scene/gui/tab_container.h"
 #include "scene/gui/tree.h"
 #include "scene/gui/tree.h"
 
 
+class EditorFileDialog;
 class EditorFileSystemDirectory;
 class EditorFileSystemDirectory;
 class EditorNode;
 class EditorNode;
 
 

+ 162 - 15
editor/editor_file_dialog.cpp

@@ -28,12 +28,13 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /*************************************************************************/
 /*************************************************************************/
 #include "editor_file_dialog.h"
 #include "editor_file_dialog.h"
-
+#include "dependency_editor.h"
 #include "editor_resource_preview.h"
 #include "editor_resource_preview.h"
 #include "editor_scale.h"
 #include "editor_scale.h"
 #include "editor_settings.h"
 #include "editor_settings.h"
 #include "os/file_access.h"
 #include "os/file_access.h"
 #include "os/keyboard.h"
 #include "os/keyboard.h"
+#include "os/os.h"
 #include "print_string.h"
 #include "print_string.h"
 #include "scene/gui/center_container.h"
 #include "scene/gui/center_container.h"
 #include "scene/gui/label.h"
 #include "scene/gui/label.h"
@@ -159,6 +160,10 @@ void EditorFileDialog::_unhandled_input(const Ref<InputEvent> &p_event) {
 				_make_dir();
 				_make_dir();
 				handled = true;
 				handled = true;
 			}
 			}
+			if (ED_IS_SHORTCUT("file_dialog/delete", p_event)) {
+				_delete_items();
+				handled = true;
+			}
 			if (ED_IS_SHORTCUT("file_dialog/focus_path", p_event)) {
 			if (ED_IS_SHORTCUT("file_dialog/focus_path", p_event)) {
 				dir->grab_focus();
 				dir->grab_focus();
 				handled = true;
 				handled = true;
@@ -512,6 +517,106 @@ void EditorFileDialog::_item_dc_selected(int p_item) {
 	}
 	}
 }
 }
 
 
+void EditorFileDialog::_item_list_item_rmb_selected(int p_item, const Vector2 &p_pos) {
+
+	// Right click on specific file(s) or folder(s).
+	item_menu->clear();
+	item_menu->set_size(Size2(1, 1));
+
+	// Allow specific actions only on one item.
+	bool single_item_selected = item_list->get_selected_items().size() == 1;
+
+	// Disallow deleting the .import folder, Godot kills a cat if you do and it is possibly a senseless novice action.
+	bool allow_delete = true;
+	for (int i = 0; i < item_list->get_item_count(); i++) {
+		if (!item_list->is_selected(i)) {
+			continue;
+		}
+		Dictionary item_meta = item_list->get_item_metadata(i);
+		if (item_meta["path"] == "res://.import") {
+			allow_delete = false;
+			break;
+		}
+	}
+
+	if (single_item_selected) {
+		item_menu->add_icon_item(get_icon("CopyNodePath", "EditorIcons"), TTR("Copy Path"), ITEM_MENU_COPY_PATH);
+	}
+	if (allow_delete) {
+		item_menu->add_icon_item(get_icon("Remove", "EditorIcons"), TTR("Delete"), ITEM_MENU_DELETE, KEY_DELETE);
+	}
+	if (single_item_selected) {
+		item_menu->add_separator();
+		item_menu->add_icon_item(get_icon("Filesystem", "EditorIcons"), TTR("Show In File Manager"), ITEM_MENU_SHOW_IN_EXPLORER);
+	}
+
+	if (item_menu->get_item_count() > 0) {
+		item_menu->set_position(item_list->get_global_position() + p_pos);
+		item_menu->popup();
+	}
+}
+
+void EditorFileDialog::_item_list_rmb_clicked(const Vector2 &p_pos) {
+
+	// Right click on folder background. Deselect all files so that actions are applied on the current folder.
+	for (int i = 0; i < item_list->get_item_count(); i++) {
+		item_list->unselect(i);
+	}
+
+	item_menu->clear();
+	item_menu->set_size(Size2(1, 1));
+
+	if (can_create_dir) {
+		item_menu->add_icon_item(get_icon("folder", "FileDialog"), TTR("New Folder.."), ITEM_MENU_NEW_FOLDER, KEY_MASK_CMD | KEY_N);
+	}
+	item_menu->add_icon_item(get_icon("Reload", "EditorIcons"), TTR("Refresh"), ITEM_MENU_REFRESH, KEY_F5);
+	item_menu->add_separator();
+	item_menu->add_icon_item(get_icon("Filesystem", "EditorIcons"), TTR("Show In File Manager"), ITEM_MENU_SHOW_IN_EXPLORER);
+
+	item_menu->set_position(item_list->get_global_position() + p_pos);
+	item_menu->popup();
+}
+
+void EditorFileDialog::_item_menu_id_pressed(int p_option) {
+
+	switch (p_option) {
+
+		case ITEM_MENU_COPY_PATH: {
+			Dictionary item_meta = item_list->get_item_metadata(item_list->get_current());
+			OS::get_singleton()->set_clipboard(item_meta["path"]);
+		} break;
+
+		case ITEM_MENU_DELETE: {
+			_delete_items();
+		} break;
+
+		case ITEM_MENU_REFRESH: {
+			invalidate();
+		} break;
+
+		case ITEM_MENU_NEW_FOLDER: {
+			_make_dir();
+		} break;
+
+		case ITEM_MENU_SHOW_IN_EXPLORER: {
+			String path;
+			int idx = item_list->get_current();
+			if (idx == -1 || item_list->get_selected_items().size() == 0) {
+				// Folder background was clicked. Open this folder.
+				path = ProjectSettings::get_singleton()->globalize_path(dir_access->get_current_dir());
+			} else {
+				// Specific item was clicked. Open folders directly, or the folder containing a selected file.
+				Dictionary item_meta = item_list->get_item_metadata(idx);
+				path = ProjectSettings::get_singleton()->globalize_path(item_meta["path"]);
+				if (!item_meta["dir"]) {
+					path = path.get_base_dir();
+				}
+			}
+			OS::get_singleton()->shell_open(String("file://") + path);
+		} break;
+	}
+}
+
 bool EditorFileDialog::_is_open_should_be_disabled() {
 bool EditorFileDialog::_is_open_should_be_disabled() {
 
 
 	if (mode == MODE_OPEN_ANY || mode == MODE_SAVE_FILE)
 	if (mode == MODE_OPEN_ANY || mode == MODE_SAVE_FILE)
@@ -617,7 +722,7 @@ void EditorFileDialog::update_file_list() {
 
 
 		Dictionary d;
 		Dictionary d;
 		d["name"] = dir_name;
 		d["name"] = dir_name;
-		d["path"] = String();
+		d["path"] = cdir.plus_file(dir_name);
 		d["dir"] = true;
 		d["dir"] = true;
 
 
 		item_list->set_item_metadata(item_list->get_item_count() - 1, d);
 		item_list->set_item_metadata(item_list->get_item_count() - 1, d);
@@ -657,8 +762,6 @@ void EditorFileDialog::update_file_list() {
 		}
 		}
 	}
 	}
 
 
-	String base_dir = dir_access->get_current_dir();
-
 	while (!files.empty()) {
 	while (!files.empty()) {
 
 
 		bool match = patterns.empty();
 		bool match = patterns.empty();
@@ -679,7 +782,7 @@ void EditorFileDialog::update_file_list() {
 
 
 			if (get_icon_func) {
 			if (get_icon_func) {
 
 
-				Ref<Texture> icon = get_icon_func(base_dir.plus_file(files.front()->get()));
+				Ref<Texture> icon = get_icon_func(cdir.plus_file(files.front()->get()));
 				//ti->set_icon(0,icon);
 				//ti->set_icon(0,icon);
 				if (display_mode == DISPLAY_THUMBNAILS) {
 				if (display_mode == DISPLAY_THUMBNAILS) {
 
 
@@ -698,12 +801,11 @@ void EditorFileDialog::update_file_list() {
 			Dictionary d;
 			Dictionary d;
 			d["name"] = files.front()->get();
 			d["name"] = files.front()->get();
 			d["dir"] = false;
 			d["dir"] = false;
-			String fullpath = base_dir.plus_file(files.front()->get());
-
+			String fullpath = cdir.plus_file(files.front()->get());
 			if (display_mode == DISPLAY_THUMBNAILS) {
 			if (display_mode == DISPLAY_THUMBNAILS) {
 				EditorResourcePreview::get_singleton()->queue_resource_preview(fullpath, this, "_thumbnail_result", fullpath);
 				EditorResourcePreview::get_singleton()->queue_resource_preview(fullpath, this, "_thumbnail_result", fullpath);
 			}
 			}
-			d["path"] = base_dir.plus_file(files.front()->get());
+			d["path"] = fullpath;
 			//ti->set_metadata(0,d);
 			//ti->set_metadata(0,d);
 			item_list->set_item_metadata(item_list->get_item_count() - 1, d);
 			item_list->set_item_metadata(item_list->get_item_count() - 1, d);
 
 
@@ -723,7 +825,7 @@ void EditorFileDialog::update_file_list() {
 	fav_down->set_disabled(true);
 	fav_down->set_disabled(true);
 	get_ok()->set_disabled(_is_open_should_be_disabled());
 	get_ok()->set_disabled(_is_open_should_be_disabled());
 	for (int i = 0; i < favorites->get_item_count(); i++) {
 	for (int i = 0; i < favorites->get_item_count(); i++) {
-		if (favorites->get_item_metadata(i) == base_dir) {
+		if (favorites->get_item_metadata(i) == cdir) {
 			favorites->select(i);
 			favorites->select(i);
 			favorite->set_pressed(true);
 			favorite->set_pressed(true);
 			if (i > 0) {
 			if (i > 0) {
@@ -854,27 +956,27 @@ void EditorFileDialog::set_mode(Mode p_mode) {
 		case MODE_OPEN_FILE:
 		case MODE_OPEN_FILE:
 			get_ok()->set_text(TTR("Open"));
 			get_ok()->set_text(TTR("Open"));
 			set_title(TTR("Open a File"));
 			set_title(TTR("Open a File"));
-			makedir->hide();
+			can_create_dir = false;
 			break;
 			break;
 		case MODE_OPEN_FILES:
 		case MODE_OPEN_FILES:
 			get_ok()->set_text(TTR("Open"));
 			get_ok()->set_text(TTR("Open"));
 			set_title(TTR("Open File(s)"));
 			set_title(TTR("Open File(s)"));
-			makedir->hide();
+			can_create_dir = false;
 			break;
 			break;
 		case MODE_OPEN_DIR:
 		case MODE_OPEN_DIR:
 			get_ok()->set_text(TTR("Open"));
 			get_ok()->set_text(TTR("Open"));
 			set_title(TTR("Open a Directory"));
 			set_title(TTR("Open a Directory"));
-			makedir->show();
+			can_create_dir = true;
 			break;
 			break;
 		case MODE_OPEN_ANY:
 		case MODE_OPEN_ANY:
 			get_ok()->set_text(TTR("Open"));
 			get_ok()->set_text(TTR("Open"));
 			set_title(TTR("Open a File or Directory"));
 			set_title(TTR("Open a File or Directory"));
-			makedir->show();
+			can_create_dir = true;
 			break;
 			break;
 		case MODE_SAVE_FILE:
 		case MODE_SAVE_FILE:
 			get_ok()->set_text(TTR("Save"));
 			get_ok()->set_text(TTR("Save"));
 			set_title(TTR("Save a File"));
 			set_title(TTR("Save a File"));
-			makedir->show();
+			can_create_dir = true;
 			break;
 			break;
 	}
 	}
 
 
@@ -883,6 +985,12 @@ void EditorFileDialog::set_mode(Mode p_mode) {
 	} else {
 	} else {
 		item_list->set_select_mode(ItemList::SELECT_SINGLE);
 		item_list->set_select_mode(ItemList::SELECT_SINGLE);
 	}
 	}
+
+	if (can_create_dir) {
+		makedir->show();
+	} else {
+		makedir->hide();
+	}
 }
 }
 
 
 EditorFileDialog::Mode EditorFileDialog::get_mode() const {
 EditorFileDialog::Mode EditorFileDialog::get_mode() const {
@@ -954,6 +1062,28 @@ void EditorFileDialog::_make_dir() {
 	makedirname->grab_focus();
 	makedirname->grab_focus();
 }
 }
 
 
+void EditorFileDialog::_delete_items() {
+
+	// Collect the selected folders and files to delete and check them in the deletion dependency dialog.
+	Vector<String> folders;
+	Vector<String> files;
+	for (int i = 0; i < item_list->get_item_count(); i++) {
+		if (!item_list->is_selected(i)) {
+			continue;
+		}
+		Dictionary item_meta = item_list->get_item_metadata(i);
+		if (item_meta["dir"]) {
+			folders.push_back(item_meta["path"]);
+		} else {
+			files.push_back(item_meta["path"]);
+		}
+	}
+	if (folders.size() + files.size() > 0) {
+		remove_dialog->set_size(Size2(1, 1));
+		remove_dialog->show(folders, files);
+	}
+}
+
 void EditorFileDialog::_select_drive(int p_idx) {
 void EditorFileDialog::_select_drive(int p_idx) {
 
 
 	String d = drives->get_item_text(p_idx);
 	String d = drives->get_item_text(p_idx);
@@ -1181,6 +1311,9 @@ void EditorFileDialog::_bind_methods() {
 
 
 	ClassDB::bind_method(D_METHOD("_item_selected"), &EditorFileDialog::_item_selected);
 	ClassDB::bind_method(D_METHOD("_item_selected"), &EditorFileDialog::_item_selected);
 	ClassDB::bind_method(D_METHOD("_items_clear_selection"), &EditorFileDialog::_items_clear_selection);
 	ClassDB::bind_method(D_METHOD("_items_clear_selection"), &EditorFileDialog::_items_clear_selection);
+	ClassDB::bind_method(D_METHOD("_item_list_item_rmb_selected"), &EditorFileDialog::_item_list_item_rmb_selected);
+	ClassDB::bind_method(D_METHOD("_item_list_rmb_clicked"), &EditorFileDialog::_item_list_rmb_clicked);
+	ClassDB::bind_method(D_METHOD("_item_menu_id_pressed"), &EditorFileDialog::_item_menu_id_pressed);
 	ClassDB::bind_method(D_METHOD("_item_db_selected"), &EditorFileDialog::_item_dc_selected);
 	ClassDB::bind_method(D_METHOD("_item_db_selected"), &EditorFileDialog::_item_dc_selected);
 	ClassDB::bind_method(D_METHOD("_dir_entered"), &EditorFileDialog::_dir_entered);
 	ClassDB::bind_method(D_METHOD("_dir_entered"), &EditorFileDialog::_dir_entered);
 	ClassDB::bind_method(D_METHOD("_file_entered"), &EditorFileDialog::_file_entered);
 	ClassDB::bind_method(D_METHOD("_file_entered"), &EditorFileDialog::_file_entered);
@@ -1326,6 +1459,7 @@ EditorFileDialog::EditorFileDialog() {
 	ED_SHORTCUT("file_dialog/toggle_favorite", TTR("Toggle Favorite"), KEY_MASK_ALT | KEY_F);
 	ED_SHORTCUT("file_dialog/toggle_favorite", TTR("Toggle Favorite"), KEY_MASK_ALT | KEY_F);
 	ED_SHORTCUT("file_dialog/toggle_mode", TTR("Toggle Mode"), KEY_MASK_ALT | KEY_V);
 	ED_SHORTCUT("file_dialog/toggle_mode", TTR("Toggle Mode"), KEY_MASK_ALT | KEY_V);
 	ED_SHORTCUT("file_dialog/create_folder", TTR("Create Folder"), KEY_MASK_CMD | KEY_N);
 	ED_SHORTCUT("file_dialog/create_folder", TTR("Create Folder"), KEY_MASK_CMD | KEY_N);
+	ED_SHORTCUT("file_dialog/delete", TTR("Delete"), KEY_DELETE);
 	ED_SHORTCUT("file_dialog/focus_path", TTR("Focus Path"), KEY_MASK_CMD | KEY_D);
 	ED_SHORTCUT("file_dialog/focus_path", TTR("Focus Path"), KEY_MASK_CMD | KEY_D);
 	ED_SHORTCUT("file_dialog/move_favorite_up", TTR("Move Favorite Up"), KEY_MASK_CMD | KEY_UP);
 	ED_SHORTCUT("file_dialog/move_favorite_up", TTR("Move Favorite Up"), KEY_MASK_CMD | KEY_UP);
 	ED_SHORTCUT("file_dialog/move_favorite_down", TTR("Move Favorite Down"), KEY_MASK_CMD | KEY_DOWN);
 	ED_SHORTCUT("file_dialog/move_favorite_down", TTR("Move Favorite Down"), KEY_MASK_CMD | KEY_DOWN);
@@ -1432,10 +1566,21 @@ EditorFileDialog::EditorFileDialog() {
 	list_vb->add_child(memnew(Label(TTR("Directories & Files:"))));
 	list_vb->add_child(memnew(Label(TTR("Directories & Files:"))));
 	preview_hb->add_child(list_vb);
 	preview_hb->add_child(list_vb);
 
 
+	// Item (files and folders) list with context menu
+
 	item_list = memnew(ItemList);
 	item_list = memnew(ItemList);
 	item_list->set_v_size_flags(SIZE_EXPAND_FILL);
 	item_list->set_v_size_flags(SIZE_EXPAND_FILL);
+	item_list->connect("item_rmb_selected", this, "_item_list_item_rmb_selected");
+	item_list->connect("rmb_clicked", this, "_item_list_rmb_clicked");
+	item_list->set_allow_rmb_select(true);
 	list_vb->add_child(item_list);
 	list_vb->add_child(item_list);
 
 
+	item_menu = memnew(PopupMenu);
+	item_menu->connect("id_pressed", this, "_item_menu_id_pressed");
+	add_child(item_menu);
+
+	// Other stuff
+
 	preview_vb = memnew(VBoxContainer);
 	preview_vb = memnew(VBoxContainer);
 	preview_hb->add_child(preview_vb);
 	preview_hb->add_child(preview_vb);
 	CenterContainer *prev_cc = memnew(CenterContainer);
 	CenterContainer *prev_cc = memnew(CenterContainer);
@@ -1474,9 +1619,11 @@ EditorFileDialog::EditorFileDialog() {
 	confirm_save = memnew(ConfirmationDialog);
 	confirm_save = memnew(ConfirmationDialog);
 	confirm_save->set_as_toplevel(true);
 	confirm_save->set_as_toplevel(true);
 	add_child(confirm_save);
 	add_child(confirm_save);
-
 	confirm_save->connect("confirmed", this, "_save_confirm_pressed");
 	confirm_save->connect("confirmed", this, "_save_confirm_pressed");
 
 
+	remove_dialog = memnew(DependencyRemoveDialog);
+	add_child(remove_dialog);
+
 	makedialog = memnew(ConfirmationDialog);
 	makedialog = memnew(ConfirmationDialog);
 	makedialog->set_title(TTR("Create Folder"));
 	makedialog->set_title(TTR("Create Folder"));
 	VBoxContainer *makevb = memnew(VBoxContainer);
 	VBoxContainer *makevb = memnew(VBoxContainer);

+ 20 - 0
editor/editor_file_dialog.h

@@ -39,6 +39,9 @@
 #include "scene/gui/split_container.h"
 #include "scene/gui/split_container.h"
 #include "scene/gui/texture_rect.h"
 #include "scene/gui/texture_rect.h"
 #include "scene/gui/tool_button.h"
 #include "scene/gui/tool_button.h"
+
+class DependencyRemoveDialog;
+
 /**
 /**
 	@author Juan Linietsky <[email protected]>
 	@author Juan Linietsky <[email protected]>
 */
 */
@@ -75,6 +78,14 @@ public:
 	static RegisterFunc unregister_func;
 	static RegisterFunc unregister_func;
 
 
 private:
 private:
+	enum ItemMenu {
+		ITEM_MENU_COPY_PATH,
+		ITEM_MENU_DELETE,
+		ITEM_MENU_REFRESH,
+		ITEM_MENU_NEW_FOLDER,
+		ITEM_MENU_SHOW_IN_EXPLORER
+	};
+
 	ConfirmationDialog *makedialog;
 	ConfirmationDialog *makedialog;
 	LineEdit *makedirname;
 	LineEdit *makedirname;
 
 
@@ -83,6 +94,7 @@ private:
 	//Button *action;
 	//Button *action;
 	VBoxContainer *vbox;
 	VBoxContainer *vbox;
 	Mode mode;
 	Mode mode;
+	bool can_create_dir;
 	LineEdit *dir;
 	LineEdit *dir;
 
 
 	ToolButton *dir_prev;
 	ToolButton *dir_prev;
@@ -91,6 +103,7 @@ private:
 
 
 	OptionButton *drives;
 	OptionButton *drives;
 	ItemList *item_list;
 	ItemList *item_list;
+	PopupMenu *item_menu;
 	TextureRect *preview;
 	TextureRect *preview;
 	VBoxContainer *preview_vb;
 	VBoxContainer *preview_vb;
 	HSplitContainer *list_hb;
 	HSplitContainer *list_hb;
@@ -100,6 +113,7 @@ private:
 	OptionButton *filter;
 	OptionButton *filter;
 	DirAccess *dir_access;
 	DirAccess *dir_access;
 	ConfirmationDialog *confirm_save;
 	ConfirmationDialog *confirm_save;
+	DependencyRemoveDialog *remove_dialog;
 	ToolButton *mode_thumbnails;
 	ToolButton *mode_thumbnails;
 	ToolButton *mode_list;
 	ToolButton *mode_list;
 
 
@@ -146,6 +160,10 @@ private:
 	void _items_clear_selection();
 	void _items_clear_selection();
 	void _item_dc_selected(int p_item);
 	void _item_dc_selected(int p_item);
 
 
+	void _item_list_item_rmb_selected(int p_item, const Vector2 &p_pos);
+	void _item_list_rmb_clicked(const Vector2 &p_pos);
+	void _item_menu_id_pressed(int p_option);
+
 	void _select_drive(int p_idx);
 	void _select_drive(int p_idx);
 	void _dir_entered(String p_dir);
 	void _dir_entered(String p_dir);
 	void _file_entered(const String &p_file);
 	void _file_entered(const String &p_file);
@@ -156,6 +174,8 @@ private:
 	void _make_dir();
 	void _make_dir();
 	void _make_dir_confirm();
 	void _make_dir_confirm();
 
 
+	void _delete_items();
+
 	void _update_drives();
 	void _update_drives();
 
 
 	void _go_up();
 	void _go_up();

+ 4 - 2
editor/editor_file_system.cpp

@@ -1244,8 +1244,10 @@ void EditorFileSystem::update_file(const String &p_file) {
 	if (!FileAccess::exists(p_file)) {
 	if (!FileAccess::exists(p_file)) {
 		//was removed
 		//was removed
 		_delete_internal_files(p_file);
 		_delete_internal_files(p_file);
-		memdelete(fs->files[cpos]);
-		fs->files.remove(cpos);
+		if (cpos != -1) { // Might've never been part of the editor file system (*.* files deleted in Open dialog).
+			memdelete(fs->files[cpos]);
+			fs->files.remove(cpos);
+		}
 		call_deferred("emit_signal", "filesystem_changed"); //update later
 		call_deferred("emit_signal", "filesystem_changed"); //update later
 		return;
 		return;
 	}
 	}