From 7f6468c284976099c4178690f5eeb4cb9e2a4ec8 Mon Sep 17 00:00:00 2001 From: Frodo45127 Date: Wed, 14 Feb 2024 14:57:23 +0100 Subject: [PATCH] Implemented multifolder support for "Add from Folder". --- locale/English_en.ftl | 3 + rpfm_ui/src/packfile_contents_ui/slots.rs | 70 ++++++++++++++++++++--- rpfm_ui/src/settings_ui/backend.rs | 1 + rpfm_ui/src/settings_ui/mod.rs | 12 ++++ rpfm_ui/src/settings_ui/tips.rs | 3 + 5 files changed, 82 insertions(+), 7 deletions(-) diff --git a/locale/English_en.ftl b/locale/English_en.ftl index 839a115ce..fed20a446 100644 --- a/locale/English_en.ftl +++ b/locale/English_en.ftl @@ -1568,3 +1568,6 @@ update_anim_ids_instructions =

This allows you to update the Anim Ids of all starting_id = Starting ID: offset = Offset: instructions = Instructions + +enable_multifolder_filepicker = Enable Multi-Folder FilePicker +settings_enable_multifolder_filepicker = This replace the System File-Picker that appears when using "Add from Folder" with a generic Qt FilePicker that allows importing multiple folders at once. diff --git a/rpfm_ui/src/packfile_contents_ui/slots.rs b/rpfm_ui/src/packfile_contents_ui/slots.rs index 391da8e67..1a2cf1909 100644 --- a/rpfm_ui/src/packfile_contents_ui/slots.rs +++ b/rpfm_ui/src/packfile_contents_ui/slots.rs @@ -12,7 +12,8 @@ Module with all the code related to the main `PackFileContentsSlots`. !*/ -use qt_widgets::{QFileDialog, q_file_dialog::FileMode}; +use qt_widgets::{QFileDialog, q_file_dialog::{FileMode, Option as FileDialogOption}}; +use qt_widgets::QListView; use qt_widgets::SlotOfQPoint; use qt_widgets::QTreeView; @@ -20,9 +21,10 @@ use qt_gui::QCursor; use qt_gui::QGuiApplication; use qt_core::QBox; -use qt_core::{SlotNoArgs, SlotOfBool, SlotOfQModelIndexInt, SlotOfQString}; +use qt_core::QFlags; use qt_core::QPtr; use qt_core::QString; +use qt_core::{SlotNoArgs, SlotOfBool, SlotOfQModelIndexInt, SlotOfQString}; use std::collections::HashSet; use std::fs::DirBuilder; @@ -662,7 +664,22 @@ impl PackFileContentsSlots { app_ui.main_window(), &qtr("context_menu_add_folders"), ); - file_dialog.set_file_mode(FileMode::Directory); + + file_dialog.set_file_mode(FileMode::DirectoryOnly); + + // Wonky workaround to allow multiple folder selection. + if setting_bool("enable_multifolder_filepicker") { + file_dialog.set_options(QFlags::from(FileDialogOption::DontUseNativeDialog.to_int() | file_dialog.options().to_int())); + + if let Ok(list_view) = file_dialog.find_child::("listView") { + list_view.set_selection_mode(qt_widgets::q_abstract_item_view::SelectionMode::MultiSelection); + } + + if let Ok(tree_view) = file_dialog.find_child::("treeView") { + tree_view.set_selection_mode(qt_widgets::q_abstract_item_view::SelectionMode::MultiSelection); + } + } + match UI_STATE.get_operational_mode() { // If we have a "MyMod" selected... @@ -691,11 +708,31 @@ impl PackFileContentsSlots { // Get the Paths of the folders we want to add. let mut folder_paths: Vec = vec![]; let paths_qt = file_dialog.selected_files(); - for index in 0..paths_qt.size() { folder_paths.push(PathBuf::from(paths_qt.at(index).to_std_string())); } + for index in 0..paths_qt.size() { + folder_paths.push(PathBuf::from(paths_qt.at(index).to_std_string())); + } + + // Make sure all folders are part of the same subfolder. The multifolder selector can accidentally add folders with different base paths, + // and we need to avoid that. + if let Some(base_path) = folder_paths.get(0) { + let mut base_path = base_path.to_path_buf(); + base_path.pop(); + + for folder_path in &folder_paths { + let mut second_path = folder_path.to_path_buf(); + second_path.pop(); + + if base_path != second_path { + return show_dialog(app_ui.main_window(), format!("Error: adding multiple folders from different parent folders is not supported."), false); + } + } + } // Get the Paths of the files inside the folders we want to add. let mut paths: Vec = vec![]; - for path in &folder_paths { paths.append(&mut files_from_subdir(path, true).unwrap()); } + for path in &folder_paths { + paths.append(&mut files_from_subdir(path, true).unwrap()); + } // Check to ensure we actually have a path, as you may try to add empty folders. if let Some(path) = paths.get(0) { @@ -712,8 +749,10 @@ impl PackFileContentsSlots { // Otherwise, they are added like normal files. else if let Some(selection) = pack_file_contents_ui.packfile_contents_tree_view.get_path_from_selection().get(0) { + let destination_paths = (0..folder_paths.len()).map(|_| ContainerPath::Folder(selection.to_string())).collect::>(); + app_ui.toggle_main_window(false); - PackFileContentsUI::add_files(&app_ui, &pack_file_contents_ui, &folder_paths, &[ContainerPath::Folder(selection.to_string())], None); + PackFileContentsUI::add_files(&app_ui, &pack_file_contents_ui, &folder_paths, &destination_paths, None); app_ui.toggle_main_window(true); } } @@ -739,11 +778,28 @@ impl PackFileContentsSlots { folder_paths.push(PathBuf::from(paths_qt.at(index).to_std_string())); } + // Make sure all folders are part of the same subfolder. The multifolder selector can accidentally add folders with different base paths, + // and we need to avoid that. + if let Some(base_path) = folder_paths.get(0) { + let mut base_path = base_path.to_path_buf(); + base_path.pop(); + + for folder_path in &folder_paths { + let mut second_path = folder_path.to_path_buf(); + second_path.pop(); + + if base_path != second_path { + return show_dialog(app_ui.main_window(), format!("Error: adding multiple folders from different parent folders is not supported."), false); + } + } + } + // Get the Paths of the files inside the folders we want to add. if let Some(selection) = pack_file_contents_ui.packfile_contents_tree_view.get_path_from_selection().get(0) { + let destination_paths = (0..folder_paths.len()).map(|_| ContainerPath::Folder(selection.to_string())).collect::>(); app_ui.toggle_main_window(false); - PackFileContentsUI::add_files(&app_ui, &pack_file_contents_ui, &folder_paths, &[ContainerPath::Folder(selection.to_string())], None); + PackFileContentsUI::add_files(&app_ui, &pack_file_contents_ui, &folder_paths, &destination_paths, None); app_ui.toggle_main_window(true); } } diff --git a/rpfm_ui/src/settings_ui/backend.rs b/rpfm_ui/src/settings_ui/backend.rs index 16f4df4ea..231077c3e 100644 --- a/rpfm_ui/src/settings_ui/backend.rs +++ b/rpfm_ui/src/settings_ui/backend.rs @@ -133,6 +133,7 @@ pub unsafe fn init_settings(main_window: &QPtr) { set_setting_if_new_bool(&q_settings, "delete_empty_folders_on_delete", true); set_setting_if_new_bool(&q_settings, "autosave_folder_size_warning_triggered", false); set_setting_if_new_bool(&q_settings, "ignore_game_files_in_ak", false); + set_setting_if_new_bool(&q_settings, "enable_multifolder_filepicker", false); // Table Settings. set_setting_if_new_bool(&q_settings, "adjust_columns_to_content", true); diff --git a/rpfm_ui/src/settings_ui/mod.rs b/rpfm_ui/src/settings_ui/mod.rs index d461c959e..af5a7501b 100644 --- a/rpfm_ui/src/settings_ui/mod.rs +++ b/rpfm_ui/src/settings_ui/mod.rs @@ -107,6 +107,7 @@ pub struct SettingsUI { include_base_folder_on_add_from_folder_label: QBox, delete_empty_folders_on_delete_label: QBox, ignore_game_files_in_ak_label: QBox, + enable_multifolder_filepicker_label: QBox, general_language_combobox: QBox, extra_global_default_game_combobox: QBox, @@ -130,6 +131,7 @@ pub struct SettingsUI { include_base_folder_on_add_from_folder_checkbox: QBox, delete_empty_folders_on_delete_checkbox: QBox, ignore_game_files_in_ak_checkbox: QBox, + enable_multifolder_filepicker_checkbox: QBox, font_data: Rc>, @@ -427,6 +429,9 @@ impl SettingsUI { let ignore_game_files_in_ak_label = QLabel::from_q_string_q_widget(&qtr("ignore_game_files_in_ak"), &general_frame); let ignore_game_files_in_ak_checkbox = QCheckBox::from_q_widget(&general_frame); + let enable_multifolder_filepicker_label = QLabel::from_q_string_q_widget(&qtr("enable_multifolder_filepicker"), &general_frame); + let enable_multifolder_filepicker_checkbox = QCheckBox::from_q_widget(&general_frame); + // Adding to the grid. general_grid.add_widget_5a(&general_language_label, 0, 0, 1, 1); general_grid.add_widget_5a(&general_language_combobox, 0, 1, 1, 1); @@ -488,6 +493,9 @@ impl SettingsUI { general_grid.add_widget_5a(&ignore_game_files_in_ak_label, 20, 0, 1, 1); general_grid.add_widget_5a(&ignore_game_files_in_ak_checkbox, 20, 1, 1, 1); + general_grid.add_widget_5a(&enable_multifolder_filepicker_label, 21, 0, 1, 1); + general_grid.add_widget_5a(&enable_multifolder_filepicker_checkbox, 21, 1, 1, 1); + settings_grid.add_widget_5a(&general_frame, 2, 0, 2, 1); //-----------------------------------------------// @@ -766,6 +774,7 @@ impl SettingsUI { include_base_folder_on_add_from_folder_label, delete_empty_folders_on_delete_label, ignore_game_files_in_ak_label, + enable_multifolder_filepicker_label, general_language_combobox, extra_global_default_game_combobox, @@ -789,6 +798,7 @@ impl SettingsUI { include_base_folder_on_add_from_folder_checkbox, delete_empty_folders_on_delete_checkbox, ignore_game_files_in_ak_checkbox, + enable_multifolder_filepicker_checkbox, font_data: Rc::new(RefCell::new((String::new(), -1))), @@ -931,6 +941,7 @@ impl SettingsUI { self.include_base_folder_on_add_from_folder_checkbox.set_checked(setting_bool_from_q_setting(&q_settings, "include_base_folder_on_add_from_folder")); self.delete_empty_folders_on_delete_checkbox.set_checked(setting_bool_from_q_setting(&q_settings, "delete_empty_folders_on_delete")); self.ignore_game_files_in_ak_checkbox.set_checked(setting_bool_from_q_setting(&q_settings, "ignore_game_files_in_ak")); + self.enable_multifolder_filepicker_checkbox.set_checked(setting_bool_from_q_setting(&q_settings, "enable_multifolder_filepicker")); // Load the Table Stuff. self.ui_table_adjust_columns_to_content_checkbox.set_checked(setting_bool_from_q_setting(&q_settings, "adjust_columns_to_content")); @@ -1046,6 +1057,7 @@ impl SettingsUI { set_setting_bool_to_q_setting(&q_settings, "include_base_folder_on_add_from_folder", self.include_base_folder_on_add_from_folder_checkbox.is_checked()); set_setting_bool_to_q_setting(&q_settings, "delete_empty_folders_on_delete", self.delete_empty_folders_on_delete_checkbox.is_checked()); set_setting_bool_to_q_setting(&q_settings, "ignore_game_files_in_ak", self.ignore_game_files_in_ak_checkbox.is_checked()); + set_setting_bool_to_q_setting(&q_settings, "enable_multifolder_filepicker", self.enable_multifolder_filepicker_checkbox.is_checked()); // Get the Table Settings. set_setting_bool_to_q_setting(&q_settings, "adjust_columns_to_content", self.ui_table_adjust_columns_to_content_checkbox.is_checked()); diff --git a/rpfm_ui/src/settings_ui/tips.rs b/rpfm_ui/src/settings_ui/tips.rs index 60cb4ec18..d59f3e355 100644 --- a/rpfm_ui/src/settings_ui/tips.rs +++ b/rpfm_ui/src/settings_ui/tips.rs @@ -37,6 +37,7 @@ pub unsafe fn set_tips(settings_ui: &Rc) { let include_base_folder_on_add_from_folder = qtr("settings_include_base_folder_on_add_from_folder"); let delete_empty_folders_on_delete = qtr("settings_delete_empty_folders_on_delete"); let ignore_game_files_in_ak = qtr("settings_ignore_game_files_in_ak"); + let enable_multifolder_filepicker = qtr("settings_enable_multifolder_filepicker"); settings_ui.ui_global_use_dark_theme_label.set_tool_tip(&ui_global_use_dark_theme_tip); settings_ui.ui_global_use_dark_theme_checkbox.set_tool_tip(&ui_global_use_dark_theme_tip); @@ -60,6 +61,8 @@ pub unsafe fn set_tips(settings_ui: &Rc) { settings_ui.delete_empty_folders_on_delete_checkbox.set_tool_tip(&delete_empty_folders_on_delete); settings_ui.ignore_game_files_in_ak_label.set_tool_tip(&ignore_game_files_in_ak); settings_ui.ignore_game_files_in_ak_checkbox.set_tool_tip(&ignore_game_files_in_ak); + settings_ui.enable_multifolder_filepicker_label.set_tool_tip(&enable_multifolder_filepicker); + settings_ui.enable_multifolder_filepicker_checkbox.set_tool_tip(&enable_multifolder_filepicker); //-----------------------------------------------// // `Extra` tips.