From 40f981dcd34c9d664b8546014dc4229fc1dc96a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Mikrut?= <41945903+qarmin@users.noreply.github.com> Date: Mon, 14 Dec 2020 13:07:35 +0100 Subject: [PATCH] Add custom selection/unselection (#117) --- czkawka_core/src/common.rs | 17 +- czkawka_gui/czkawka.glade | 119 +++++++- czkawka_gui/src/connect_popovers.rs | 416 ++++++++++++++++++++++++++++ czkawka_gui/src/gui_data.rs | 12 + 4 files changed, 550 insertions(+), 14 deletions(-) diff --git a/czkawka_core/src/common.rs b/czkawka_core/src/common.rs index fe2469e..bd61bb4 100644 --- a/czkawka_core/src/common.rs +++ b/czkawka_core/src/common.rs @@ -52,13 +52,13 @@ impl Common { /// Function to check if directory match expression pub fn regex_check(expression: &str, directory: impl AsRef) -> bool { - if !expression.contains('*') { - #[cfg(debug_assertions)] - { - println!("Invalid expression ERROR: Expression should have *"); - } - return false; - } + // if !expression.contains('*') { + // #[cfg(debug_assertions)] + // { + // println!("Invalid expression Warning: Expression should have *,"); + // } + // //return false; + // } let temp_splits: Vec<&str> = expression.split('*').collect(); let mut splits: Vec<&str> = Vec::new(); @@ -139,6 +139,8 @@ mod test { assert!(Common::regex_check("*home/*", "/home/")); assert!(Common::regex_check("*.git*", "/home/.git")); assert!(Common::regex_check("*/home/rafal*rafal*rafal*rafal*", "/home/rafal/rafalrafalrafal")); + assert!(Common::regex_check("AAA", "AAA")); + assert!(Common::regex_check("AAA*", "AAABDGG/QQPW*")); assert!(!Common::regex_check("*home", "/home/")); assert!(!Common::regex_check("*home", "/homefasfasfasfasf/")); assert!(!Common::regex_check("*home", "/homefasfasfasfasf")); @@ -149,7 +151,6 @@ mod test { assert!(!Common::regex_check("*home/*koc", "/koc/home/")); assert!(!Common::regex_check("*home/", "/home")); assert!(!Common::regex_check("*TTT", "/GGG")); - assert!(!Common::regex_check("AAA", "AAA")); } #[test] fn test_windows_path() { diff --git a/czkawka_gui/czkawka.glade b/czkawka_gui/czkawka.glade index 994ed9d..6a0d308 100644 --- a/czkawka_gui/czkawka.glade +++ b/czkawka_gui/czkawka.glade @@ -198,7 +198,7 @@ Author: Rafał Mikrut False True - 1 + 0 @@ -208,6 +208,17 @@ Author: Rafał Mikrut True True + + False + True + 1 + + + + + True + False + False True @@ -227,6 +238,17 @@ Author: Rafał Mikrut 3 + + + True + False + + + False + True + 4 + + Select all except oldest @@ -237,7 +259,7 @@ Author: Rafał Mikrut False True - 4 + 5 @@ -250,7 +272,7 @@ Author: Rafał Mikrut False True - 5 + 6 @@ -263,7 +285,7 @@ Author: Rafał Mikrut False True - 6 + 7 @@ -276,7 +298,44 @@ Author: Rafał Mikrut False True - 7 + 8 + + + + + True + False + + + False + True + 9 + + + + + Select custom + True + True + True + + + False + True + 10 + + + + + Unselect custom + True + True + True + + + False + True + 11 @@ -299,7 +358,7 @@ Author: Rafał Mikrut False True - 1 + 0 @@ -309,6 +368,17 @@ Author: Rafał Mikrut True True + + False + True + 1 + + + + + True + False + False True @@ -328,6 +398,43 @@ Author: Rafał Mikrut 3 + + + True + False + + + False + True + 4 + + + + + Select custom + True + True + True + + + False + True + 5 + + + + + Unselect custom + True + True + True + + + False + True + 6 + + diff --git a/czkawka_gui/src/connect_popovers.rs b/czkawka_gui/src/connect_popovers.rs index 81dcfee..c738c9a 100644 --- a/czkawka_gui/src/connect_popovers.rs +++ b/czkawka_gui/src/connect_popovers.rs @@ -1,6 +1,7 @@ extern crate gtk; use crate::gui_data::GuiData; use crate::help_functions::*; +use czkawka_core::common::Common; use gtk::prelude::*; use gtk::TreeIter; @@ -291,6 +292,241 @@ fn popover_one_newest(popover: >k::Popover, scrolled_window: >k::ScrolledWin popover.popdown(); } +fn popover_select_custom(popover: >k::Popover, gui_data: &GuiData, scrolled_window: >k::ScrolledWindow, column_color: Option, column_file_name: i32, column_path: i32) { + popover.popdown(); + + let wildcard: String; + enum WildcardType { + Path, + Name, + PathName, + }; + let wildcard_type: WildcardType; + + // Accept Dialog + { + let window_main = gui_data.window_main.clone(); + let confirmation_dialog_delete = gtk::Dialog::with_buttons(Some("Select custom"), Some(&window_main), gtk::DialogFlags::MODAL, &[("Ok", gtk::ResponseType::Ok), ("Close", gtk::ResponseType::Cancel)]); + let label: gtk::Label = gtk::Label::new(Some("Usage: */folder-nr*/* or name-version-*.txt")); + + let radio_path = gtk::RadioButton::with_label("Path"); + let radio_name = gtk::RadioButton::with_label_from_widget(&radio_path, "Name"); + let radio_name_path = gtk::RadioButton::with_label_from_widget(&radio_path, "Path + Name"); + + let entry_path = gtk::Entry::new(); + let entry_name = gtk::Entry::new(); + let entry_name_path = gtk::Entry::new(); + + label.set_margin_bottom(5); + label.set_margin_end(5); + label.set_margin_start(5); + + // TODO Label should have const width, and rest should fill entry, but for now is 50%-50% + let grid = gtk::Grid::new(); + grid.set_row_homogeneous(true); + grid.set_column_homogeneous(true); + + grid.attach(&label, 0, 0, 2, 1); + + grid.attach(&radio_path, 0, 1, 1, 1); + grid.attach(&radio_name, 0, 2, 1, 1); + grid.attach(&radio_name_path, 0, 3, 1, 1); + + grid.attach(&entry_path, 1, 1, 1, 1); + grid.attach(&entry_name, 1, 2, 1, 1); + grid.attach(&entry_name_path, 1, 3, 1, 1); + + for widgets in confirmation_dialog_delete.get_children() { + // By default GtkBox is child of dialog, so we can easily add other things to it + widgets.downcast::().unwrap().add(&grid); + } + + confirmation_dialog_delete.show_all(); + + let response_type = confirmation_dialog_delete.run(); + if response_type == gtk::ResponseType::Ok { + if radio_path.get_active() { + wildcard_type = WildcardType::Path; + wildcard = entry_path.get_text().to_string(); + } else if radio_name.get_active() { + wildcard_type = WildcardType::Name; + wildcard = entry_name.get_text().to_string(); + } else if radio_name_path.get_active() { + wildcard_type = WildcardType::PathName; + wildcard = entry_name_path.get_text().to_string(); + } else { + panic!("Non handled option in select wildcard"); + } + } else { + confirmation_dialog_delete.close(); + return; + } + confirmation_dialog_delete.close(); + } + if !wildcard.is_empty() { + let wildcard = wildcard.trim(); + + let tree_view = get_tree_view(&scrolled_window); + let selection = tree_view.get_selection(); + let tree_model = tree_view.get_model().unwrap(); + + let tree_iter = tree_model.get_iter_first().unwrap(); // Never should be available button where there is no available records + + loop { + if let Some(column_color) = column_color { + let color = tree_model.get_value(&tree_iter, column_color).get::().unwrap().unwrap(); + if color == HEADER_ROW_COLOR { + if !tree_model.iter_next(&tree_iter) { + break; + } + continue; + } + } + + let path = tree_model.get_value(&tree_iter, column_path).get::().unwrap().unwrap(); + let name = tree_model.get_value(&tree_iter, column_file_name).get::().unwrap().unwrap(); + match wildcard_type { + WildcardType::Path => { + if Common::regex_check(wildcard, path) { + selection.select_iter(&tree_iter); + } + } + WildcardType::Name => { + if Common::regex_check(wildcard, name) { + selection.select_iter(&tree_iter); + } + } + WildcardType::PathName => { + if Common::regex_check(wildcard, format!("{}/{}", path, name)) { + selection.select_iter(&tree_iter); + } + } + } + + if !tree_model.iter_next(&tree_iter) { + break; + } + } + } +} +fn popover_unselect_custom(popover: >k::Popover, gui_data: &GuiData, scrolled_window: >k::ScrolledWindow, column_color: Option, column_file_name: i32, column_path: i32) { + popover.popdown(); + + let wildcard: String; + enum WildcardType { + Path, + Name, + PathName, + }; + let wildcard_type: WildcardType; + + // Accept Dialog + { + let window_main = gui_data.window_main.clone(); + let confirmation_dialog_delete = gtk::Dialog::with_buttons(Some("Unselect custom"), Some(&window_main), gtk::DialogFlags::MODAL, &[("Ok", gtk::ResponseType::Ok), ("Close", gtk::ResponseType::Cancel)]); + let label: gtk::Label = gtk::Label::new(Some("Usage: */folder-nr*/* or name-version-*.txt")); + + let radio_path = gtk::RadioButton::with_label("Path"); + let radio_name = gtk::RadioButton::with_label_from_widget(&radio_path, "Name"); + let radio_name_path = gtk::RadioButton::with_label_from_widget(&radio_path, "Path + Name"); + + let entry_path = gtk::Entry::new(); + let entry_name = gtk::Entry::new(); + let entry_name_path = gtk::Entry::new(); + + label.set_margin_bottom(5); + label.set_margin_end(5); + label.set_margin_start(5); + + // TODO Label should have const width, and rest should fill entry, but for now is 50%-50% + let grid = gtk::Grid::new(); + grid.set_row_homogeneous(true); + grid.set_column_homogeneous(true); + + grid.attach(&label, 0, 0, 2, 1); + + grid.attach(&radio_path, 0, 1, 1, 1); + grid.attach(&radio_name, 0, 2, 1, 1); + grid.attach(&radio_name_path, 0, 3, 1, 1); + + grid.attach(&entry_path, 1, 1, 1, 1); + grid.attach(&entry_name, 1, 2, 1, 1); + grid.attach(&entry_name_path, 1, 3, 1, 1); + + for widgets in confirmation_dialog_delete.get_children() { + // By default GtkBox is child of dialog, so we can easily add other things to it + widgets.downcast::().unwrap().add(&grid); + } + + confirmation_dialog_delete.show_all(); + + let response_type = confirmation_dialog_delete.run(); + if response_type == gtk::ResponseType::Ok { + if radio_path.get_active() { + wildcard_type = WildcardType::Path; + wildcard = entry_path.get_text().to_string(); + } else if radio_name.get_active() { + wildcard_type = WildcardType::Name; + wildcard = entry_name.get_text().to_string(); + } else if radio_name_path.get_active() { + wildcard_type = WildcardType::PathName; + wildcard = entry_name_path.get_text().to_string(); + } else { + panic!("Non handled option in unselect wildcard"); + } + } else { + confirmation_dialog_delete.close(); + return; + } + confirmation_dialog_delete.close(); + } + if !wildcard.is_empty() { + let wildcard = wildcard.trim(); + + let tree_view = get_tree_view(&scrolled_window); + let selection = tree_view.get_selection(); + let tree_model = tree_view.get_model().unwrap(); + + let tree_iter = tree_model.get_iter_first().unwrap(); // Never should be available button where there is no available records + + loop { + if let Some(column_color) = column_color { + let color = tree_model.get_value(&tree_iter, column_color).get::().unwrap().unwrap(); + if color == HEADER_ROW_COLOR { + if !tree_model.iter_next(&tree_iter) { + break; + } + continue; + } + } + + let path = tree_model.get_value(&tree_iter, column_path).get::().unwrap().unwrap(); + let name = tree_model.get_value(&tree_iter, column_file_name).get::().unwrap().unwrap(); + match wildcard_type { + WildcardType::Path => { + if Common::regex_check(wildcard, path) { + selection.unselect_iter(&tree_iter); + } + } + WildcardType::Name => { + if Common::regex_check(wildcard, name) { + selection.unselect_iter(&tree_iter); + } + } + WildcardType::PathName => { + if Common::regex_check(wildcard, format!("{}/{}", path, name)) { + selection.unselect_iter(&tree_iter); + } + } + } + + if !tree_model.iter_next(&tree_iter) { + break; + } + } + } +} + pub fn connect_popovers(gui_data: &GuiData) { connect_select_all(&gui_data); connect_unselect_all(&gui_data); @@ -300,6 +536,9 @@ pub fn connect_popovers(gui_data: &GuiData) { connect_all_except_newest(&gui_data); connect_one_oldest(&gui_data); connect_one_newest(&gui_data); + + connect_select_custom(&gui_data); + connect_unselect_custom(&gui_data); } pub fn connect_select_all(gui_data: &GuiData) { let notebook_main_children_names = gui_data.notebook_main_children_names.clone(); @@ -612,3 +851,180 @@ pub fn connect_one_oldest(gui_data: &GuiData) { e => panic!("Not existent {}", e), }); } + +pub fn connect_select_custom(gui_data: &GuiData) { + let notebook_main_children_names = gui_data.notebook_main_children_names.clone(); + let notebook_main = gui_data.notebook_main.clone(); + + let gui_data = gui_data.clone(); + let second_gui_data = gui_data.clone(); + let scrolled_window_main_empty_folder_finder = gui_data.scrolled_window_main_empty_folder_finder.clone(); + let scrolled_window_big_files_finder = gui_data.scrolled_window_big_files_finder.clone(); + let scrolled_window_main_empty_files_finder = gui_data.scrolled_window_main_empty_files_finder.clone(); + let scrolled_window_main_temporary_files_finder = gui_data.scrolled_window_main_temporary_files_finder.clone(); + let scrolled_window_similar_images_finder = gui_data.scrolled_window_similar_images_finder.clone(); + let scrolled_window_zeroed_files_finder = gui_data.scrolled_window_zeroed_files_finder.clone(); + let scrolled_window_same_music_finder = gui_data.scrolled_window_same_music_finder.clone(); + let scrolled_window_duplicate_finder = gui_data.scrolled_window_duplicate_finder.clone(); + let popover_select_duplicate = gui_data.popover_select_duplicate.clone(); + let popover_select_simple_list = gui_data.popover_select_simple_list.clone(); + let buttons_popover_simple_list_select_custom = gui_data.buttons_popover_simple_list_select_custom.clone(); + let buttons_popover_duplicate_select_custom = gui_data.buttons_popover_duplicate_select_custom.clone(); + buttons_popover_duplicate_select_custom.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" => { + popover_select_custom( + &popover_select_duplicate, + &gui_data, + &scrolled_window_duplicate_finder, + Some(ColumnsDuplicates::Color as i32), + ColumnsDuplicates::Name as i32, + ColumnsDuplicates::Path as i32, + ); + } + "notebook_main_same_music_finder" => { + popover_select_custom( + &popover_select_duplicate, + &gui_data, + &scrolled_window_same_music_finder, + Some(ColumnsSameMusic::Color as i32), + ColumnsSameMusic::Name as i32, + ColumnsSameMusic::Path as i32, + ); + } + "notebook_main_similar_images_finder_label" => { + popover_select_custom( + &popover_select_duplicate, + &gui_data, + &scrolled_window_similar_images_finder, + Some(ColumnsSimilarImages::Color as i32), + ColumnsSimilarImages::Name as i32, + ColumnsSimilarImages::Path as i32, + ); + } + e => panic!("Not existent {}", e), + }); + + let gui_data = second_gui_data; + let notebook_main_children_names = gui_data.notebook_main_children_names.clone(); + let notebook_main = gui_data.notebook_main.clone(); + buttons_popover_simple_list_select_custom.connect_clicked(move |_| match notebook_main_children_names.get(notebook_main.get_current_page().unwrap() as usize).unwrap().as_str() { + "scrolled_window_main_empty_folder_finder" => { + popover_select_custom( + &popover_select_simple_list, + &gui_data, + &scrolled_window_main_empty_folder_finder, + None, + ColumnsEmptyFolders::Name as i32, + ColumnsEmptyFolders::Path as i32, + ); + } + "scrolled_window_main_empty_files_finder" => { + popover_select_custom(&popover_select_simple_list, &gui_data, &scrolled_window_main_empty_files_finder, None, ColumnsEmptyFiles::Name as i32, ColumnsEmptyFiles::Path as i32); + } + "scrolled_window_main_temporary_files_finder" => { + popover_select_custom( + &popover_select_simple_list, + &gui_data, + &scrolled_window_main_temporary_files_finder, + None, + ColumnsTemporaryFiles::Name as i32, + ColumnsTemporaryFiles::Path as i32, + ); + } + "notebook_main_zeroed_files_finder" => { + popover_select_custom(&popover_select_simple_list, &gui_data, &scrolled_window_zeroed_files_finder, None, ColumnsZeroedFiles::Name as i32, ColumnsZeroedFiles::Path as i32); + } + "notebook_big_main_file_finder" => { + popover_select_custom(&popover_select_simple_list, &gui_data, &scrolled_window_big_files_finder, None, ColumnsBigFiles::Name as i32, ColumnsBigFiles::Path as i32); + } + e => panic!("Not existent {}", e), + }); +} +pub fn connect_unselect_custom(gui_data: &GuiData) { + let notebook_main_children_names = gui_data.notebook_main_children_names.clone(); + let notebook_main = gui_data.notebook_main.clone(); + + let gui_data = gui_data.clone(); + let second_gui_data = gui_data.clone(); + let scrolled_window_main_empty_folder_finder = gui_data.scrolled_window_main_empty_folder_finder.clone(); + let scrolled_window_big_files_finder = gui_data.scrolled_window_big_files_finder.clone(); + let scrolled_window_main_empty_files_finder = gui_data.scrolled_window_main_empty_files_finder.clone(); + let scrolled_window_main_temporary_files_finder = gui_data.scrolled_window_main_temporary_files_finder.clone(); + let scrolled_window_similar_images_finder = gui_data.scrolled_window_similar_images_finder.clone(); + let scrolled_window_zeroed_files_finder = gui_data.scrolled_window_zeroed_files_finder.clone(); + let scrolled_window_same_music_finder = gui_data.scrolled_window_same_music_finder.clone(); + let scrolled_window_duplicate_finder = gui_data.scrolled_window_duplicate_finder.clone(); + let popover_select_duplicate = gui_data.popover_select_duplicate.clone(); + let popover_select_simple_list = gui_data.popover_select_simple_list.clone(); + let buttons_popover_simple_list_unselect_custom = gui_data.buttons_popover_simple_list_unselect_custom.clone(); + let buttons_popover_duplicate_unselect_custom = gui_data.buttons_popover_duplicate_unselect_custom.clone(); + buttons_popover_duplicate_unselect_custom.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" => { + popover_unselect_custom( + &popover_select_duplicate, + &gui_data, + &scrolled_window_duplicate_finder, + Some(ColumnsDuplicates::Color as i32), + ColumnsDuplicates::Name as i32, + ColumnsDuplicates::Path as i32, + ); + } + "notebook_main_same_music_finder" => { + popover_unselect_custom( + &popover_select_duplicate, + &gui_data, + &scrolled_window_same_music_finder, + Some(ColumnsSameMusic::Color as i32), + ColumnsSameMusic::Name as i32, + ColumnsSameMusic::Path as i32, + ); + } + "notebook_main_similar_images_finder_label" => { + popover_unselect_custom( + &popover_select_duplicate, + &gui_data, + &scrolled_window_similar_images_finder, + Some(ColumnsSimilarImages::Color as i32), + ColumnsSimilarImages::Name as i32, + ColumnsSimilarImages::Path as i32, + ); + } + e => panic!("Not existent {}", e), + }); + + let gui_data = second_gui_data; + let notebook_main_children_names = gui_data.notebook_main_children_names.clone(); + let notebook_main = gui_data.notebook_main.clone(); + buttons_popover_simple_list_unselect_custom.connect_clicked(move |_| match notebook_main_children_names.get(notebook_main.get_current_page().unwrap() as usize).unwrap().as_str() { + "scrolled_window_main_empty_folder_finder" => { + popover_unselect_custom( + &popover_select_simple_list, + &gui_data, + &scrolled_window_main_empty_folder_finder, + None, + ColumnsEmptyFolders::Name as i32, + ColumnsEmptyFolders::Path as i32, + ); + } + "scrolled_window_main_empty_files_finder" => { + popover_unselect_custom(&popover_select_simple_list, &gui_data, &scrolled_window_main_empty_files_finder, None, ColumnsEmptyFiles::Name as i32, ColumnsEmptyFiles::Path as i32); + } + "scrolled_window_main_temporary_files_finder" => { + popover_unselect_custom( + &popover_select_simple_list, + &gui_data, + &scrolled_window_main_temporary_files_finder, + None, + ColumnsTemporaryFiles::Name as i32, + ColumnsTemporaryFiles::Path as i32, + ); + } + "notebook_main_zeroed_files_finder" => { + popover_unselect_custom(&popover_select_simple_list, &gui_data, &scrolled_window_zeroed_files_finder, None, ColumnsZeroedFiles::Name as i32, ColumnsZeroedFiles::Path as i32); + } + "notebook_big_main_file_finder" => { + popover_unselect_custom(&popover_select_simple_list, &gui_data, &scrolled_window_big_files_finder, None, ColumnsBigFiles::Name as i32, ColumnsBigFiles::Path as i32); + } + e => panic!("Not existent {}", e), + }); +} diff --git a/czkawka_gui/src/gui_data.rs b/czkawka_gui/src/gui_data.rs index ebd5322..e21c177 100644 --- a/czkawka_gui/src/gui_data.rs +++ b/czkawka_gui/src/gui_data.rs @@ -73,10 +73,14 @@ pub struct GuiData { pub buttons_popover_duplicate_select_all_except_newest: gtk::Button, pub buttons_popover_duplicate_select_one_oldest: gtk::Button, pub buttons_popover_duplicate_select_one_newest: gtk::Button, + pub buttons_popover_duplicate_select_custom: gtk::Button, + pub buttons_popover_duplicate_unselect_custom: gtk::Button, pub buttons_popover_simple_list_select_all: gtk::Button, pub buttons_popover_simple_list_unselect_all: gtk::Button, pub buttons_popover_simple_list_reverse: gtk::Button, + pub buttons_popover_simple_list_select_custom: gtk::Button, + pub buttons_popover_simple_list_unselect_custom: gtk::Button, //// Popovers pub popover_select_duplicate: gtk::Popover, @@ -269,10 +273,14 @@ impl GuiData { let buttons_popover_duplicate_select_all_except_newest: gtk::Button = builder.get_object("buttons_popover_duplicate_select_all_except_newest").unwrap(); let buttons_popover_duplicate_select_one_oldest: gtk::Button = builder.get_object("buttons_popover_duplicate_select_one_oldest").unwrap(); let buttons_popover_duplicate_select_one_newest: gtk::Button = builder.get_object("buttons_popover_duplicate_select_one_newest").unwrap(); + let buttons_popover_duplicate_select_custom: gtk::Button = builder.get_object("buttons_popover_duplicate_select_custom").unwrap(); + let buttons_popover_duplicate_unselect_custom: gtk::Button = builder.get_object("buttons_popover_duplicate_unselect_custom").unwrap(); let buttons_popover_simple_list_select_all: gtk::Button = builder.get_object("buttons_popover_simple_list_select_all").unwrap(); let buttons_popover_simple_list_unselect_all: gtk::Button = builder.get_object("buttons_popover_simple_list_unselect_all").unwrap(); let buttons_popover_simple_list_reverse: gtk::Button = builder.get_object("buttons_popover_simple_list_reverse").unwrap(); + let buttons_popover_simple_list_select_custom: gtk::Button = builder.get_object("buttons_popover_simple_list_select_custom").unwrap(); + let buttons_popover_simple_list_unselect_custom: gtk::Button = builder.get_object("buttons_popover_simple_list_unselect_custom").unwrap(); //// Popovers let popover_select_duplicate: gtk::Popover = builder.get_object("popover_select_duplicate").unwrap(); @@ -402,9 +410,13 @@ impl GuiData { buttons_popover_duplicate_select_all_except_newest, buttons_popover_duplicate_select_one_oldest, buttons_popover_duplicate_select_one_newest, + buttons_popover_duplicate_select_custom, + buttons_popover_duplicate_unselect_custom, buttons_popover_simple_list_select_all, buttons_popover_simple_list_unselect_all, buttons_popover_simple_list_reverse, + buttons_popover_simple_list_select_custom, + buttons_popover_simple_list_unselect_custom, popover_select_duplicate, popover_select_simple_list, check_button_recursive,