diff --git a/czkawka_gui/czkawka.glade b/czkawka_gui/czkawka.glade index 3f4d81d..7ba8d28 100644 --- a/czkawka_gui/czkawka.glade +++ b/czkawka_gui/czkawka.glade @@ -2036,7 +2036,7 @@ Author: Rafał Mikrut True False - network-transmit-receive + edit-select-all False @@ -2165,6 +2165,55 @@ Author: Rafał Mikrut 2 + + + True + True + True + + + True + False + + + True + False + 2 + + + True + False + network-transmit-receive + + + False + True + 0 + + + + + True + False + Symlink + + + False + True + 1 + + + + + + + + + False + True + 3 + + False diff --git a/czkawka_gui/src/connect_button_symlink.rs b/czkawka_gui/src/connect_button_symlink.rs new file mode 100644 index 0000000..28e1061 --- /dev/null +++ b/czkawka_gui/src/connect_button_symlink.rs @@ -0,0 +1,216 @@ +extern crate gtk; +use crate::gui_data::GuiData; +use crate::help_functions::*; +use gtk::prelude::*; +use gtk::{TreeIter, TreePath}; +use std::fs; + +pub fn connect_button_symlink(gui_data: &GuiData) { + let gui_data = gui_data.clone(); + + let buttons_symlink = gui_data.buttons_symlink.clone(); + let notebook_main = gui_data.notebook_main.clone(); + let notebook_main_children_names = gui_data.notebook_main_children_names.clone(); + + let scrolled_window_duplicate_finder = gui_data.scrolled_window_duplicate_finder.clone(); + let scrolled_window_similar_images_finder = gui_data.scrolled_window_similar_images_finder.clone(); + let scrolled_window_same_music_finder = gui_data.scrolled_window_same_music_finder.clone(); + + buttons_symlink.connect_clicked(move |_| match notebook_main_children_names.get(notebook_main.get_current_page().unwrap() as usize).unwrap().as_str() { + "notebook_main_duplicate_finder_label" => { + symlink(scrolled_window_duplicate_finder.clone(), ColumnsDuplicates::Name as i32, ColumnsDuplicates::Path as i32, ColumnsDuplicates::Color as i32, &gui_data); + } + "notebook_main_same_music_finder" => { + symlink(scrolled_window_same_music_finder.clone(), ColumnsSameMusic::Name as i32, ColumnsSameMusic::Path as i32, ColumnsSameMusic::Color as i32, &gui_data); + } + "notebook_main_similar_images_finder_label" => { + symlink( + scrolled_window_similar_images_finder.clone(), + ColumnsSimilarImages::Name as i32, + ColumnsSimilarImages::Path as i32, + ColumnsSimilarImages::Color as i32, + &gui_data, + ); + } + e => panic!("Not existent {}", e), + }); +} +fn symlink(scrolled_window: gtk::ScrolledWindow, column_file_name: i32, column_path: i32, column_color: i32, gui_data: &GuiData) { + let text_view_errors = gui_data.text_view_errors.clone(); + reset_text_view(&text_view_errors); + + let tree_view = get_tree_view(&scrolled_window); + let list_store = get_list_store(&scrolled_window); + let selection = tree_view.get_selection(); + + let (selection_rows, tree_model) = selection.get_selected_rows(); + if selection_rows.is_empty() { + return; + } + + struct SymlinkData { + original_data: String, + files_to_symlink: Vec, + } + let mut vec_tree_path_to_remove: Vec = Vec::new(); // List of symlinked files without its root + let mut vec_symlink_data: Vec = Vec::new(); + + let current_iter: TreeIter = tree_model.get_iter_first().unwrap(); // Symlink button should be only visible when more than 1 element is visible, otherwise it needs to be fixed + let mut current_symlink_data: Option = None; + let mut current_selected_index = 0; + loop { + if tree_model.get_value(¤t_iter, column_color).get::().unwrap().unwrap() == HEADER_ROW_COLOR { + if let Some(current_symlink_data) = current_symlink_data { + if !current_symlink_data.files_to_symlink.is_empty() { + vec_symlink_data.push(current_symlink_data); + } + } + + current_symlink_data = None; + if !tree_model.iter_next(¤t_iter) { + panic!("HEADER, shouldn't be a last item."); + } + continue; + } + + if tree_model.get_path(¤t_iter).unwrap() == selection_rows[current_selected_index] { + let file_name = tree_model.get_value(¤t_iter, column_file_name).get::().unwrap().unwrap(); + let path = tree_model.get_value(¤t_iter, column_path).get::().unwrap().unwrap(); + let full_file_path = format!("{}/{}", path, file_name); + + if current_symlink_data.is_some() { + vec_tree_path_to_remove.push(tree_model.get_path(¤t_iter).unwrap()); + let mut temp_data = current_symlink_data.unwrap(); + temp_data.files_to_symlink.push(full_file_path); + current_symlink_data = Some(temp_data); + } else { + current_symlink_data = Some(SymlinkData { + original_data: full_file_path, + files_to_symlink: vec![], + }); + } + + if current_selected_index != selection_rows.len() - 1 { + current_selected_index += 1; + } else { + if let Some(current_symlink_data) = current_symlink_data { + if !current_symlink_data.files_to_symlink.is_empty() { + vec_symlink_data.push(current_symlink_data); + } + } + break; // There is no more selected items, so we just end checking + } + } + + if !tree_model.iter_next(¤t_iter) { + if let Some(current_symlink_data) = current_symlink_data { + if !current_symlink_data.files_to_symlink.is_empty() { + vec_symlink_data.push(current_symlink_data); + } + } + + break; + } + } + for symlink_data in vec_symlink_data { + for file_to_symlink in symlink_data.files_to_symlink { + match fs::remove_file(&file_to_symlink) { + Ok(_) => (), + Err(_) => { + add_text_to_text_view(&text_view_errors, format!("Failed to remove file {} when creating symlink.", file_to_symlink).as_str()); + continue; + } + }; + + #[cfg(target_family = "unix")] + { + match std::os::unix::fs::symlink(&symlink_data.original_data, &file_to_symlink) { + Ok(_) => (), + Err(_) => { + add_text_to_text_view(&text_view_errors, format!("Failed to remove file {} when creating symlink.", file_to_symlink).as_str()); + continue; + } + }; + } + // TODO Add this, because for now it not working () + // #[cfg(target_family = "windows")] + // { + // match std::os::windows::fs::symlink(&symlink_data.original_data, &file_to_symlink) { + // Ok(_) => (), + // Err(_) => { + // add_text_to_text_view(&text_view_errors, format!("Failed to remove file {} when creating symlink.", file_to_symlink).as_str()); + // continue; + // } + // }; + // } + } + println!(); + } + for tree_path in vec_tree_path_to_remove.iter().rev() { + list_store.remove(&tree_model.get_iter(tree_path).unwrap()); + } + + // Remove only child from header + if let Some(first_iter) = list_store.get_iter_first() { + let mut vec_tree_path_to_delete: Vec = Vec::new(); + let mut current_iter = first_iter; + if tree_model.get_value(¤t_iter, column_color).get::().unwrap().unwrap() != HEADER_ROW_COLOR { + panic!(); // First element should be header + }; + + let mut next_iter; + let mut next_next_iter; + 'main: loop { + if tree_model.get_value(¤t_iter, column_color).get::().unwrap().unwrap() != HEADER_ROW_COLOR { + panic!(); // First element should be header + }; + + next_iter = current_iter.clone(); + if !list_store.iter_next(&next_iter) { + // There is only single header left (H1 -> END) -> (NOTHING) + vec_tree_path_to_delete.push(list_store.get_path(¤t_iter).unwrap()); + break 'main; + } + + if tree_model.get_value(&next_iter, column_color).get::().unwrap().unwrap() == HEADER_ROW_COLOR { + // There are two headers each others(we remove just first) -> (H1 -> H2) -> (H2) + vec_tree_path_to_delete.push(list_store.get_path(¤t_iter).unwrap()); + current_iter = next_iter.clone(); + continue 'main; + } + + next_next_iter = next_iter.clone(); + if !list_store.iter_next(&next_next_iter) { + // There is only one child of header left, so we remove it with header (H1 -> C1 -> END) -> (NOTHING) + vec_tree_path_to_delete.push(list_store.get_path(¤t_iter).unwrap()); + vec_tree_path_to_delete.push(list_store.get_path(&next_iter).unwrap()); + break 'main; + } + + if tree_model.get_value(&next_next_iter, column_color).get::().unwrap().unwrap() == HEADER_ROW_COLOR { + // One child between two headers, we can remove them (H1 -> C1 -> H2) -> (H2) + vec_tree_path_to_delete.push(list_store.get_path(¤t_iter).unwrap()); + vec_tree_path_to_delete.push(list_store.get_path(&next_iter).unwrap()); + current_iter = next_next_iter.clone(); + continue 'main; + } + + loop { + // (H1 -> C1 -> C2 -> Cn -> END) -> (NO CHANGE, BECAUSE IS GOOD) + if !list_store.iter_next(&next_next_iter) { + break 'main; + } + // Move to next header + if tree_model.get_value(&next_next_iter, column_color).get::().unwrap().unwrap() == HEADER_ROW_COLOR { + current_iter = next_next_iter.clone(); + continue 'main; + } + } + } + for tree_path in vec_tree_path_to_delete.iter().rev() { + list_store.remove(&list_store.get_iter(&tree_path).unwrap()); + } + } + + selection.unselect_all(); +} diff --git a/czkawka_gui/src/connect_compute_results.rs b/czkawka_gui/src/connect_compute_results.rs index b3f01b5..e5c8223 100644 --- a/czkawka_gui/src/connect_compute_results.rs +++ b/czkawka_gui/src/connect_compute_results.rs @@ -187,10 +187,12 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< *shared_buttons.borrow_mut().get_mut("duplicate").unwrap().get_mut("save").unwrap() = true; *shared_buttons.borrow_mut().get_mut("duplicate").unwrap().get_mut("delete").unwrap() = true; *shared_buttons.borrow_mut().get_mut("duplicate").unwrap().get_mut("select").unwrap() = true; + *shared_buttons.borrow_mut().get_mut("duplicate").unwrap().get_mut("symlink").unwrap() = true; } else { *shared_buttons.borrow_mut().get_mut("duplicate").unwrap().get_mut("save").unwrap() = false; *shared_buttons.borrow_mut().get_mut("duplicate").unwrap().get_mut("delete").unwrap() = false; *shared_buttons.borrow_mut().get_mut("duplicate").unwrap().get_mut("select").unwrap() = false; + *shared_buttons.borrow_mut().get_mut("duplicate").unwrap().get_mut("symlink").unwrap() = false; } set_buttons(&mut *shared_buttons.borrow_mut().get_mut("duplicate").unwrap(), &buttons_array, &buttons_names); } @@ -442,10 +444,12 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< *shared_buttons.borrow_mut().get_mut("similar_images").unwrap().get_mut("save").unwrap() = true; *shared_buttons.borrow_mut().get_mut("similar_images").unwrap().get_mut("delete").unwrap() = true; *shared_buttons.borrow_mut().get_mut("similar_images").unwrap().get_mut("select").unwrap() = true; + *shared_buttons.borrow_mut().get_mut("similar_images").unwrap().get_mut("symlink").unwrap() = true; } else { *shared_buttons.borrow_mut().get_mut("similar_images").unwrap().get_mut("save").unwrap() = false; *shared_buttons.borrow_mut().get_mut("similar_images").unwrap().get_mut("delete").unwrap() = false; *shared_buttons.borrow_mut().get_mut("similar_images").unwrap().get_mut("select").unwrap() = false; + *shared_buttons.borrow_mut().get_mut("similar_images").unwrap().get_mut("symlink").unwrap() = false; } set_buttons(&mut *shared_buttons.borrow_mut().get_mut("similar_images").unwrap(), &buttons_array, &buttons_names); } @@ -590,10 +594,12 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< *shared_buttons.borrow_mut().get_mut("same_music").unwrap().get_mut("save").unwrap() = true; *shared_buttons.borrow_mut().get_mut("same_music").unwrap().get_mut("delete").unwrap() = true; *shared_buttons.borrow_mut().get_mut("same_music").unwrap().get_mut("select").unwrap() = true; + *shared_buttons.borrow_mut().get_mut("same_music").unwrap().get_mut("symlink").unwrap() = true; } else { *shared_buttons.borrow_mut().get_mut("same_music").unwrap().get_mut("save").unwrap() = false; *shared_buttons.borrow_mut().get_mut("same_music").unwrap().get_mut("delete").unwrap() = false; *shared_buttons.borrow_mut().get_mut("same_music").unwrap().get_mut("select").unwrap() = false; + *shared_buttons.borrow_mut().get_mut("same_music").unwrap().get_mut("symlink").unwrap() = false; } set_buttons(&mut *shared_buttons.borrow_mut().get_mut("same_music").unwrap(), &buttons_array, &buttons_names); } diff --git a/czkawka_gui/src/gui_data.rs b/czkawka_gui/src/gui_data.rs index abb8c7e..9a9a695 100644 --- a/czkawka_gui/src/gui_data.rs +++ b/czkawka_gui/src/gui_data.rs @@ -26,7 +26,7 @@ pub struct GuiData { // States pub main_notebooks_labels: [String; 8], pub upper_notebooks_labels: [String; 5], - pub buttons_labels: [String; 4], + pub buttons_labels: [String; 5], // Buttons state pub shared_buttons: Rc>>>, @@ -56,9 +56,10 @@ pub struct GuiData { pub buttons_select: gtk::Button, pub buttons_delete: gtk::Button, pub buttons_save: gtk::Button, + pub buttons_symlink: gtk::Button, pub buttons_show_errors: gtk::Button, - pub buttons_names: [String; 4], - pub buttons_array: [Button; 4], + pub buttons_names: [String; 5], + pub buttons_array: [Button; 5], pub buttons_add_included_directory: gtk::Button, pub buttons_remove_included_directory: gtk::Button, pub buttons_add_excluded_directory: gtk::Button, @@ -198,7 +199,7 @@ impl GuiData { "allowed_extensions".to_string(), "settings".to_string(), ]; - let buttons_labels = ["search".to_string(), "select".to_string(), "delete".to_string(), "save".to_string()]; + let buttons_labels = ["search".to_string(), "select".to_string(), "delete".to_string(), "save".to_string(), "symlink".to_string()]; // Buttons State - to remember existence of different buttons on pages @@ -229,7 +230,6 @@ impl GuiData { shared_upper_notebooks.borrow_mut().insert(i.to_string(), temp_hashmap); } // Some upper notebook tabs are disabled - *shared_upper_notebooks.borrow_mut().get_mut("empty_file").unwrap().get_mut("allowed_extensions").unwrap() = false; *shared_upper_notebooks.borrow_mut().get_mut("temporary_file").unwrap().get_mut("allowed_extensions").unwrap() = false; // State of search results @@ -258,11 +258,12 @@ impl GuiData { let buttons_select: gtk::Button = builder.get_object("buttons_select").unwrap(); let buttons_delete: gtk::Button = builder.get_object("buttons_delete").unwrap(); let buttons_save: gtk::Button = builder.get_object("buttons_save").unwrap(); + let buttons_symlink: gtk::Button = builder.get_object("buttons_symlink").unwrap(); let buttons_show_errors: gtk::Button = builder.get_object("buttons_show_errors").unwrap(); - let buttons_names = ["search".to_string(), "select".to_string(), "delete".to_string(), "save".to_string()]; - let buttons_array = [buttons_search.clone(), buttons_select.clone(), buttons_delete.clone(), buttons_save.clone()]; + let buttons_names = ["search".to_string(), "select".to_string(), "delete".to_string(), "save".to_string(), "symlink".to_string()]; + let buttons_array = [buttons_search.clone(), buttons_select.clone(), buttons_delete.clone(), buttons_save.clone(), buttons_symlink.clone()]; let buttons_add_included_directory: gtk::Button = builder.get_object("buttons_add_included_directory").unwrap(); let buttons_remove_included_directory: gtk::Button = builder.get_object("buttons_remove_included_directory").unwrap(); @@ -405,6 +406,7 @@ impl GuiData { buttons_select, buttons_delete, buttons_save, + buttons_symlink, buttons_show_errors, buttons_names, buttons_array, diff --git a/czkawka_gui/src/help_functions.rs b/czkawka_gui/src/help_functions.rs index 4f261b5..3825df3 100644 --- a/czkawka_gui/src/help_functions.rs +++ b/czkawka_gui/src/help_functions.rs @@ -209,7 +209,13 @@ pub fn select_function_similar_images(_tree_selection: >k::TreeSelection, tree pub fn set_buttons(hashmap: &mut HashMap, buttons_array: &[gtk::Button], button_names: &[String]) { for (index, button) in buttons_array.iter().enumerate() { if *hashmap.get_mut(button_names[index].as_str()).unwrap() { - button.show(); + if cfg!(target_family = "windows") { + if button_names[index] != "symlink" { + button.show(); + } + } else { + button.show(); + } } else { button.hide(); } diff --git a/czkawka_gui/src/initialize_gui.rs b/czkawka_gui/src/initialize_gui.rs index ae0a9bf..5aacd1b 100644 --- a/czkawka_gui/src/initialize_gui.rs +++ b/czkawka_gui/src/initialize_gui.rs @@ -18,6 +18,7 @@ pub fn initialize_gui(gui_data: &GuiData) { let buttons_save = gui_data.buttons_save.clone(); let buttons_delete = gui_data.buttons_delete.clone(); let buttons_select = gui_data.buttons_select.clone(); + let buttons_symlink = gui_data.buttons_symlink.clone(); let scrolled_window_duplicate_finder = gui_data.scrolled_window_duplicate_finder.clone(); let scrolled_window_main_empty_folder_finder = gui_data.scrolled_window_main_empty_folder_finder.clone(); let scrolled_window_main_empty_files_finder = gui_data.scrolled_window_main_empty_files_finder.clone(); @@ -36,6 +37,7 @@ pub fn initialize_gui(gui_data: &GuiData) { buttons_save.hide(); buttons_delete.hide(); buttons_select.hide(); + buttons_symlink.hide(); // Set Main Scrolled Window Treeviews { diff --git a/czkawka_gui/src/main.rs b/czkawka_gui/src/main.rs index 9afd498..d08495f 100644 --- a/czkawka_gui/src/main.rs +++ b/czkawka_gui/src/main.rs @@ -6,6 +6,7 @@ mod connect_button_save; mod connect_button_search; mod connect_button_select; mod connect_button_stop; +mod connect_button_symlink; mod connect_compute_results; mod connect_hide_text_view_errors; mod connect_notebook_tabs; @@ -28,6 +29,7 @@ use crate::connect_button_save::*; use crate::connect_button_search::*; use crate::connect_button_select::*; use crate::connect_button_stop::*; +use crate::connect_button_symlink::*; use crate::connect_compute_results::*; use crate::connect_hide_text_view_errors::*; use crate::connect_notebook_tabs::*; @@ -95,6 +97,7 @@ fn main() { ); connect_button_select(&gui_data); connect_button_stop(&gui_data); + connect_button_symlink(&gui_data); connect_notebook_tabs(&gui_data); connect_selection_of_directories(&gui_data); connect_popovers(&gui_data); diff --git a/czkawka_gui/src/saving_loading.rs b/czkawka_gui/src/saving_loading.rs index ed8e95e..b9534c2 100644 --- a/czkawka_gui/src/saving_loading.rs +++ b/czkawka_gui/src/saving_loading.rs @@ -229,18 +229,14 @@ pub fn load_configuration(gui_data: &GuiData, manual_execution: bool) { current_type = TypeOfLoadedData::None; add_text_to_text_view( &text_view_errors, - format!( - "Found invalid header in line {} \"\"\"{}\"\"\" when loading file {:?} (save file may be from different Czkawka version)", - line_number, line, config_file - ) - .as_str(), + format!("Found invalid header in line {} \"{}\" when loading file {:?} (save file may be from different Czkawka version)", line_number, line, config_file).as_str(), ); } else { match current_type { TypeOfLoadedData::None => { add_text_to_text_view( &text_view_errors, - format!("Found orphan data in line {} \"\"\"{}\"\"\" when loading file {:?} (save file may be from different Czkawka version)", line_number, line, config_file).as_str(), + format!("Found orphan data in line {} \"{}\" when loading file {:?} (save file may be from different Czkawka version)", line_number, line, config_file).as_str(), ); } TypeOfLoadedData::IncludedDirectories => { @@ -264,7 +260,7 @@ pub fn load_configuration(gui_data: &GuiData, manual_execution: bool) { } else { add_text_to_text_view( &text_view_errors, - format!("Found invalid data in line {} \"\"\"{}\"\"\" isn't proper value(0/1/true/false) when loading file {:?}", line_number, line, config_file).as_str(), + format!("Found invalid data in line {} \"{}\" isn't proper value(0/1/true/false) when loading file {:?}", line_number, line, config_file).as_str(), ); } } @@ -277,7 +273,7 @@ pub fn load_configuration(gui_data: &GuiData, manual_execution: bool) { } else { add_text_to_text_view( &text_view_errors, - format!("Found invalid data in line {} \"\"\"{}\"\"\" isn't proper value(0/1/true/false) when loading file {:?}", line_number, line, config_file).as_str(), + format!("Found invalid data in line {} \"{}\" isn't proper value(0/1/true/false) when loading file {:?}", line_number, line, config_file).as_str(), ); } } @@ -290,7 +286,7 @@ pub fn load_configuration(gui_data: &GuiData, manual_execution: bool) { } else { add_text_to_text_view( &text_view_errors, - format!("Found invalid data in line {} \"\"\"{}\"\"\" isn't proper value(0/1/true/false) when loading file {:?}", line_number, line, config_file).as_str(), + format!("Found invalid data in line {} \"{}\" isn't proper value(0/1/true/false) when loading file {:?}", line_number, line, config_file).as_str(), ); } } @@ -303,7 +299,7 @@ pub fn load_configuration(gui_data: &GuiData, manual_execution: bool) { } else { add_text_to_text_view( &text_view_errors, - format!("Found invalid data in line {} \"\"\"{}\"\"\" isn't proper value(0/1/true/false) when loading file {:?}", line_number, line, config_file).as_str(), + format!("Found invalid data in line {} \"{}\" isn't proper value(0/1/true/false) when loading file {:?}", line_number, line, config_file).as_str(), ); } } @@ -316,7 +312,7 @@ pub fn load_configuration(gui_data: &GuiData, manual_execution: bool) { } else { add_text_to_text_view( &text_view_errors, - format!("Found invalid data in line {} \"\"\"{}\"\"\" isn't proper value(0/1/true/false) when loading file {:?}", line_number, line, config_file).as_str(), + format!("Found invalid data in line {} \"{}\" isn't proper value(0/1/true/false) when loading file {:?}", line_number, line, config_file).as_str(), ); } } @@ -367,6 +363,8 @@ pub fn load_configuration(gui_data: &GuiData, manual_execution: bool) { gui_data.check_button_settings_show_text_view.set_active(bottom_text_panel); if !bottom_text_panel { gui_data.scrolled_window_errors.hide(); + } else { + gui_data.scrolled_window_errors.show(); } } else { gui_data.check_button_settings_load_at_start.set_active(false);