From e2d31d4f980c9de8d26e7a8342768576b1c29d93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Mikrut?= <41945903+qarmin@users.noreply.github.com> Date: Sun, 5 Dec 2021 18:35:41 +0100 Subject: [PATCH] Add confirmation and warning dialog to hard/symlinks (#489) --- czkawka_gui/src/connect_button_delete.rs | 4 +- czkawka_gui/src/connect_button_hardlink.rs | 225 ++++++++++++++---- czkawka_gui/src/connect_button_move.rs | 1 + czkawka_gui/src/connect_popovers.rs | 2 +- .../src/connect_selection_of_directories.rs | 4 +- czkawka_gui/src/gui_settings.rs | 4 + czkawka_gui/ui/settings.glade | 23 +- 7 files changed, 203 insertions(+), 60 deletions(-) diff --git a/czkawka_gui/src/connect_button_delete.rs b/czkawka_gui/src/connect_button_delete.rs index e6ed908..8399cfa 100644 --- a/czkawka_gui/src/connect_button_delete.rs +++ b/czkawka_gui/src/connect_button_delete.rs @@ -101,7 +101,7 @@ pub async fn check_if_can_delete_files(check_button_settings_confirm_deletion: & } fn create_dialog_ask_for_deletion(window_main: >k::Window) -> (Dialog, CheckButton) { - let dialog = gtk::Dialog::builder().title("Delete confirmation").transient_for(window_main).build(); + let dialog = gtk::Dialog::builder().title("Delete confirmation").transient_for(window_main).modal(true).build(); let button_ok = dialog.add_button("Ok", ResponseType::Ok); dialog.add_button("Close", ResponseType::Cancel); @@ -121,7 +121,7 @@ fn create_dialog_ask_for_deletion(window_main: >k::Window) -> (Dialog, CheckBu } fn create_dialog_group_deletion(window_main: >k::Window) -> (Dialog, CheckButton) { - let dialog = gtk::Dialog::builder().title("Confirmation of deleting all files in group").transient_for(window_main).build(); + let dialog = gtk::Dialog::builder().title("Confirmation of deleting all files in group").transient_for(window_main).modal(true).build(); let button_ok = dialog.add_button("Ok", ResponseType::Ok); dialog.add_button("Close", ResponseType::Cancel); diff --git a/czkawka_gui/src/connect_button_hardlink.rs b/czkawka_gui/src/connect_button_hardlink.rs index 9344b40..324a73f 100644 --- a/czkawka_gui/src/connect_button_hardlink.rs +++ b/czkawka_gui/src/connect_button_hardlink.rs @@ -2,7 +2,7 @@ use std::fs; use std::path::PathBuf; use gtk::prelude::*; -use gtk::{TextView, TreeIter, TreePath}; +use gtk::{Align, CheckButton, Dialog, ResponseType, TextView, TreeIter, TreePath}; use czkawka_core::duplicate::make_hard_link; @@ -11,8 +11,27 @@ use crate::help_functions::*; use crate::notebook_enums::*; pub fn connect_button_hardlink_symlink(gui_data: &GuiData) { - let buttons_hardlink = gui_data.bottom_buttons.buttons_hardlink.clone(); + { + let buttons_hardlink = gui_data.bottom_buttons.buttons_hardlink.clone(); + let gui_data = gui_data.clone(); + + buttons_hardlink.connect_clicked(move |_| { + glib::MainContext::default().spawn_local(sym_hard_link_things(gui_data.clone(), false)); + }); + } + { + let buttons_symlink = gui_data.bottom_buttons.buttons_symlink.clone(); + + let gui_data = gui_data.clone(); + + buttons_symlink.connect_clicked(move |_| { + glib::MainContext::default().spawn_local(sym_hard_link_things(gui_data.clone(), false)); + }); + } +} + +pub async fn sym_hard_link_things(gui_data: GuiData, hardlinking: bool) { let notebook_main = gui_data.main_notebook.notebook_main.clone(); let main_tree_views = gui_data.main_notebook.get_main_tree_views(); @@ -21,60 +40,40 @@ pub fn connect_button_hardlink_symlink(gui_data: &GuiData) { let text_view_errors = gui_data.text_view_errors.clone(); let preview_path = gui_data.preview_path.clone(); + let window_main = gui_data.window_main.clone(); - buttons_hardlink.connect_clicked(move |_| { - let nb_number = notebook_main.current_page().unwrap(); - let tree_view = &main_tree_views[nb_number as usize]; - let nb_object = &NOTEBOOKS_INFOS[nb_number as usize]; + let nb_number = notebook_main.current_page().unwrap(); + let tree_view = &main_tree_views[nb_number as usize]; + let nb_object = &NOTEBOOKS_INFOS[nb_number as usize]; - let column_color = nb_object.column_color.expect("Hardinkning can be only used for tree views with grouped results"); - hardlink_symlink(tree_view, nb_object.column_name, nb_object.column_path, column_color, nb_object.column_selection, true, &text_view_errors); + let column_color = nb_object.column_color.expect("Symlinking can be only used for tree views with grouped results"); - match &nb_object.notebook_type { - NotebookMainEnum::SimilarImages | NotebookMainEnum::Duplicate => { - if nb_object.notebook_type == NotebookMainEnum::SimilarImages { - image_preview_similar_images.hide(); - } else { - image_preview_duplicates.hide(); - } - *preview_path.borrow_mut() = "".to_string(); + let check_button_settings_confirm_link = gui_data.settings.check_button_settings_confirm_link.clone(); + + if !check_if_anything_is_selected_async(tree_view, column_color, nb_object.column_selection).await { + return; + } + + if !check_if_can_link_files(&check_button_settings_confirm_link, &window_main).await { + return; + } + if !check_if_changing_one_item_in_group_and_continue(tree_view, column_color, nb_object.column_selection, &window_main).await { + return; + } + + hardlink_symlink(tree_view, nb_object.column_name, nb_object.column_path, column_color, nb_object.column_selection, hardlinking, &text_view_errors); + + match &nb_object.notebook_type { + NotebookMainEnum::SimilarImages | NotebookMainEnum::Duplicate => { + if nb_object.notebook_type == NotebookMainEnum::SimilarImages { + image_preview_similar_images.hide(); + } else { + image_preview_duplicates.hide(); } - _ => {} + *preview_path.borrow_mut() = "".to_string(); } - }); - - let buttons_symlink = gui_data.bottom_buttons.buttons_symlink.clone(); - - let notebook_main = gui_data.main_notebook.notebook_main.clone(); - let main_tree_views = gui_data.main_notebook.get_main_tree_views(); - - let image_preview_similar_images = gui_data.main_notebook.image_preview_similar_images.clone(); - let image_preview_duplicates = gui_data.main_notebook.image_preview_duplicates.clone(); - - let text_view_errors = gui_data.text_view_errors.clone(); - - let preview_path = gui_data.preview_path.clone(); - - buttons_symlink.connect_clicked(move |_| { - let nb_number = notebook_main.current_page().unwrap(); - let tree_view = &main_tree_views[nb_number as usize]; - let nb_object = &NOTEBOOKS_INFOS[nb_number as usize]; - - let column_color = nb_object.column_color.expect("Symlinking can be only used for tree views with grouped results"); - hardlink_symlink(tree_view, nb_object.column_name, nb_object.column_path, column_color, nb_object.column_selection, false, &text_view_errors); - - match &nb_object.notebook_type { - NotebookMainEnum::SimilarImages | NotebookMainEnum::Duplicate => { - if nb_object.notebook_type == NotebookMainEnum::SimilarImages { - image_preview_similar_images.hide(); - } else { - image_preview_duplicates.hide(); - } - *preview_path.borrow_mut() = "".to_string(); - } - _ => {} - } - }); + _ => {} + } } pub fn hardlink_symlink(tree_view: >k::TreeView, column_file_name: i32, column_path: i32, column_color: i32, column_selection: i32, hardlinking: bool, text_view_errors: &TextView) { @@ -213,3 +212,127 @@ pub fn hardlink_symlink(tree_view: >k::TreeView, column_file_name: i32, column clean_invalid_headers(&model, column_color); } + +fn create_dialog_non_group(window_main: >k::Window) -> Dialog { + let dialog = gtk::Dialog::builder().title("Invalid selection with some groups").transient_for(window_main).modal(true).build(); + let button_ok = dialog.add_button("Ok", ResponseType::Ok); + dialog.add_button("Close", ResponseType::Cancel); + + let label: gtk::Label = gtk::Label::new(Some("In some groups there is only 1 record selected and it will be ignored.")); + let label2: gtk::Label = gtk::Label::new(Some("To be able to hard/sym link this files, at least 2 results in group needs to be selected.")); + let label3: gtk::Label = gtk::Label::new(Some("First in group is recognized as original and is not changed but second and later are modified.")); + + button_ok.grab_focus(); + + let internal_box = get_dialog_box_child(&dialog); + internal_box.add(&label); + internal_box.add(&label2); + internal_box.add(&label3); + + dialog.show_all(); + dialog +} + +pub async fn check_if_changing_one_item_in_group_and_continue(tree_view: >k::TreeView, column_color: i32, column_selection: i32, window_main: >k::Window) -> bool { + let model = get_list_store(tree_view); + + let mut selected_values_in_group = 0; + + if let Some(iter) = model.iter_first() { + assert_eq!(model.value(&iter, column_color).get::().unwrap(), HEADER_ROW_COLOR); // First element should be header + + loop { + if !model.iter_next(&iter) { + break; + } + + if model.value(&iter, column_color).get::().unwrap() == HEADER_ROW_COLOR { + if selected_values_in_group == 1 { + break; + } + selected_values_in_group = 0; + } else { + if model.value(&iter, column_selection).get::().unwrap() { + selected_values_in_group += 1; + } + } + } + } else { + return false; // No available records + } + + if selected_values_in_group == 1 { + let confirmation_dialog = create_dialog_non_group(window_main); + + let response_type = confirmation_dialog.run_future().await; + if response_type != gtk::ResponseType::Ok { + confirmation_dialog.hide(); + confirmation_dialog.close(); + return false; + } + confirmation_dialog.hide(); + confirmation_dialog.close(); + } + + true +} + +pub async fn check_if_anything_is_selected_async(tree_view: >k::TreeView, column_color: i32, column_selection: i32) -> bool { + let model = get_list_store(tree_view); + + if let Some(iter) = model.iter_first() { + assert_eq!(model.value(&iter, column_color).get::().unwrap(), HEADER_ROW_COLOR); // First element should be header + + loop { + if !model.iter_next(&iter) { + break; + } + + if model.value(&iter, column_color).get::().unwrap() == MAIN_ROW_COLOR && model.value(&iter, column_selection).get::().unwrap() { + return true; + } + } + } + + false +} + +pub async fn check_if_can_link_files(check_button_settings_confirm_link: >k::CheckButton, window_main: >k::Window) -> bool { + if check_button_settings_confirm_link.is_active() { + let (confirmation_dialog_link, check_button) = create_dialog_ask_for_linking(window_main); + + let response_type = confirmation_dialog_link.run_future().await; + if response_type == gtk::ResponseType::Ok { + if !check_button.is_active() { + check_button_settings_confirm_link.set_active(false); + } + confirmation_dialog_link.hide(); + confirmation_dialog_link.close(); + } else { + confirmation_dialog_link.hide(); + confirmation_dialog_link.close(); + return false; + }; + } + true +} + +fn create_dialog_ask_for_linking(window_main: >k::Window) -> (Dialog, CheckButton) { + let dialog = gtk::Dialog::builder().title("Link confirmation").transient_for(window_main).modal(true).build(); + let button_ok = dialog.add_button("Ok", ResponseType::Ok); + dialog.add_button("Close", ResponseType::Cancel); + + let label: gtk::Label = gtk::Label::new(Some("Are you sure that you want to link this files?")); + let check_button: gtk::CheckButton = gtk::CheckButton::with_label("Ask next time"); + check_button.set_active(true); + check_button.set_halign(Align::Center); + + button_ok.grab_focus(); + + let internal_box = get_dialog_box_child(&dialog); + internal_box.add(&label); + internal_box.add(&check_button); + + dialog.show_all(); + (dialog, check_button) +} diff --git a/czkawka_gui/src/connect_button_move.rs b/czkawka_gui/src/connect_button_move.rs index 34a91d2..0ebd904 100644 --- a/czkawka_gui/src/connect_button_move.rs +++ b/czkawka_gui/src/connect_button_move.rs @@ -61,6 +61,7 @@ fn move_things(tree_view: >k::TreeView, column_file_name: i32, column_path: i3 .title("Choose folder to which you want to move duplicated files") .action(gtk::FileChooserAction::SelectFolder) .transient_for(window_main) + .modal(true) .build(); chooser.add_button("Ok", ResponseType::Ok); chooser.add_button("Close", ResponseType::Cancel); diff --git a/czkawka_gui/src/connect_popovers.rs b/czkawka_gui/src/connect_popovers.rs index d4828f9..c70f896 100644 --- a/czkawka_gui/src/connect_popovers.rs +++ b/czkawka_gui/src/connect_popovers.rs @@ -224,7 +224,7 @@ fn popover_custom_select_unselect(popover: >k::Popover, window_main: &Window, // Dialog for select/unselect items { - let dialog = gtk::Dialog::builder().title(window_title).transient_for(window_main).build(); + let dialog = gtk::Dialog::builder().title(window_title).transient_for(window_main).modal(true).build(); dialog.add_button("Ok", ResponseType::Ok); dialog.add_button("Close", ResponseType::Cancel); diff --git a/czkawka_gui/src/connect_selection_of_directories.rs b/czkawka_gui/src/connect_selection_of_directories.rs index 360f661..6834792 100644 --- a/czkawka_gui/src/connect_selection_of_directories.rs +++ b/czkawka_gui/src/connect_selection_of_directories.rs @@ -79,7 +79,7 @@ pub fn connect_selection_of_directories(gui_data: &GuiData) { fn add_chosen_directories(window_main: &Window, tree_view: &TreeView, excluded_items: bool) { let folders_to = if excluded_items { "Folders to exclude" } else { "Folders to include" }; - let chooser = gtk::FileChooserDialog::builder().title(folders_to).action(gtk::FileChooserAction::SelectFolder).transient_for(window_main).build(); + let chooser = gtk::FileChooserDialog::builder().title(folders_to).action(gtk::FileChooserAction::SelectFolder).transient_for(window_main).modal(true).build(); chooser.add_button("Ok", ResponseType::Ok); chooser.add_button("Close", ResponseType::Cancel); @@ -103,7 +103,7 @@ fn add_chosen_directories(window_main: &Window, tree_view: &TreeView, excluded_i } fn add_manually_directories(window_main: &Window, tree_view: &TreeView) { - let dialog = gtk::Dialog::builder().title("Add directory manually").transient_for(window_main).build(); + let dialog = gtk::Dialog::builder().title("Add directory manually").transient_for(window_main).modal(true).build(); dialog.add_button("Ok", ResponseType::Ok); dialog.add_button("Close", ResponseType::Cancel); diff --git a/czkawka_gui/src/gui_settings.rs b/czkawka_gui/src/gui_settings.rs index aa2f691..6a3b463 100644 --- a/czkawka_gui/src/gui_settings.rs +++ b/czkawka_gui/src/gui_settings.rs @@ -9,6 +9,7 @@ pub struct GuiSettings { pub check_button_settings_save_at_exit: gtk::CheckButton, pub check_button_settings_load_at_start: gtk::CheckButton, pub check_button_settings_confirm_deletion: gtk::CheckButton, + pub check_button_settings_confirm_link: gtk::CheckButton, pub check_button_settings_confirm_group_deletion: gtk::CheckButton, pub check_button_settings_show_text_view: gtk::CheckButton, pub check_button_settings_use_cache: gtk::CheckButton, @@ -54,6 +55,7 @@ impl GuiSettings { let check_button_settings_save_at_exit: gtk::CheckButton = builder.object("check_button_settings_save_at_exit").unwrap(); let check_button_settings_load_at_start: gtk::CheckButton = builder.object("check_button_settings_load_at_start").unwrap(); let check_button_settings_confirm_deletion: gtk::CheckButton = builder.object("check_button_settings_confirm_deletion").unwrap(); + let check_button_settings_confirm_link: gtk::CheckButton = builder.object("check_button_settings_confirm_link").unwrap(); let check_button_settings_confirm_group_deletion: gtk::CheckButton = builder.object("check_button_settings_confirm_group_deletion").unwrap(); let check_button_settings_show_text_view: gtk::CheckButton = builder.object("check_button_settings_show_text_view").unwrap(); let check_button_settings_use_cache: gtk::CheckButton = builder.object("check_button_settings_use_cache").unwrap(); @@ -62,6 +64,7 @@ impl GuiSettings { check_button_settings_save_at_exit.set_tooltip_text(Some("Saves configuration to file when closing app.")); check_button_settings_load_at_start.set_tooltip_text(Some("Loading at start configuration from file.\n\nNot selecting this option will load default settings.")); check_button_settings_confirm_deletion.set_tooltip_text(Some("Shows confirmation dialog when clicking at delete button.")); + check_button_settings_confirm_link.set_tooltip_text(Some("Shows confirmation dialog when clicking at hard/symlink button.")); check_button_settings_confirm_group_deletion.set_tooltip_text(Some("Shows dialog when trying to remove all records from group.")); check_button_settings_show_text_view.set_tooltip_text(Some("Shows error panel at bottom.")); check_button_settings_use_cache.set_tooltip_text(Some("Option to which allows to not use cache feature.")); @@ -127,6 +130,7 @@ impl GuiSettings { check_button_settings_save_at_exit, check_button_settings_load_at_start, check_button_settings_confirm_deletion, + check_button_settings_confirm_link, check_button_settings_confirm_group_deletion, check_button_settings_show_text_view, check_button_settings_use_cache, diff --git a/czkawka_gui/ui/settings.glade b/czkawka_gui/ui/settings.glade index 2246d20..a4bfec9 100644 --- a/czkawka_gui/ui/settings.glade +++ b/czkawka_gui/ui/settings.glade @@ -147,6 +147,21 @@ Author: Rafał Mikrut 2 + + + Show confirm dialog when hard/symlinks any files + True + True + False + True + True + + + False + True + 3 + + Show confirm dialog when deleting all files in group @@ -159,7 +174,7 @@ Author: Rafał Mikrut False True - 3 + 4 @@ -174,7 +189,7 @@ Author: Rafał Mikrut False False - 4 + 5 @@ -189,7 +204,7 @@ Author: Rafał Mikrut False True - 5 + 6 @@ -204,7 +219,7 @@ Author: Rafał Mikrut False True - 6 + 7