1
0
Fork 0
mirror of synced 2024-05-11 07:53:42 +12:00

Add better custom selecting (#478)

This commit is contained in:
Rafał Mikrut 2021-12-02 13:31:10 +01:00 committed by GitHub
parent fce8ba8ddf
commit bb428171cb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 261 additions and 87 deletions

20
Cargo.lock generated
View file

@ -14,6 +14,15 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "alsa"
version = "0.5.0"
@ -405,9 +414,9 @@ dependencies = [
[[package]]
name = "crc32fast"
version = "1.2.2"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3825b1e8580894917dc4468cb634a1b4e9745fddc854edad72d9c04644c0319f"
checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836"
dependencies = [
"cfg-if",
]
@ -507,6 +516,7 @@ dependencies = [
"image",
"img_hash",
"open",
"regex",
"trash",
"winapi",
]
@ -1521,9 +1531,9 @@ checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
[[package]]
name = "open"
version = "2.0.1"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b46b233de7d83bc167fe43ae2dda3b5b84e80e09cceba581e4decb958a4896bf"
checksum = "176ee4b630d174d2da8241336763bb459281dddc0f4d87f72c3b1efc9a6109b7"
dependencies = [
"pathdiff",
"winapi",
@ -1796,6 +1806,8 @@ version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]

View file

@ -45,13 +45,9 @@ impl Common {
/// Function to check if directory match expression
pub fn regex_check(expression: &str, directory: impl AsRef<Path>) -> bool {
// if !expression.contains('*') {
// #[cfg(debug_assertions)]
// {
// println!("Invalid expression Warning: Expression should have *,");
// }
// //return false;
// }
if expression == "*" {
return true;
}
let temp_splits: Vec<&str> = expression.split('*').collect();
let mut splits: Vec<&str> = Vec::new();

View file

@ -31,6 +31,9 @@ open = "2.0.1"
# To get image preview
image = "0.23.14"
# To be able to use custom select
regex = "1.5.4"
# To get image_hash types
img_hash = "3.2.0"

View file

@ -1,5 +1,6 @@
use gtk::prelude::*;
use gtk::{ResponseType, TreeIter, Window};
use regex::Regex;
use czkawka_core::common::Common;
@ -216,12 +217,6 @@ fn popover_one_oldest_newest(popover: &gtk::Popover, tree_view: &gtk::TreeView,
fn popover_custom_select_unselect(popover: &gtk::Popover, window_main: &Window, tree_view: &gtk::TreeView, column_color: Option<i32>, column_file_name: i32, column_path: i32, column_button_selection: u32, select_things: bool) {
popover.popdown();
enum WildcardType {
Path,
Name,
PathName,
}
let window_title = match select_things {
false => "Unselect Custom",
true => "Select Custom",
@ -233,112 +228,232 @@ fn popover_custom_select_unselect(popover: &gtk::Popover, window_main: &Window,
dialog.add_button("Ok", ResponseType::Ok);
dialog.add_button("Close", ResponseType::Cancel);
let label: gtk::Label = gtk::Label::new(Some("Usage: */folder-nr*/* or name-version-*.txt"));
let check_button_path = gtk::CheckButton::builder().label("Path").build();
let check_button_name = gtk::CheckButton::builder().label("Name").build();
let check_button_rust_regex = gtk::CheckButton::builder().label("Regex Path + Name").build();
let radio_path = gtk::RadioButton::builder().label("Path").build();
let radio_name_path = gtk::RadioButton::builder().label("Path + Name").build();
radio_name_path.join_group(Some(&radio_path));
let radio_name = gtk::RadioButton::builder().label("Name").build();
radio_name.join_group(Some(&radio_path)); // TODO, not sure why this not exists for builder, but should
let check_button_select_not_all_results = gtk::CheckButton::builder().label("Don't select all records in group").build();
check_button_select_not_all_results.set_active(true);
let entry_path = gtk::Entry::new();
let entry_name = gtk::Entry::new();
let entry_name_path = gtk::Entry::new();
let entry_rust_regex = gtk::Entry::new();
entry_rust_regex.set_sensitive(false); // By default check button regex is disabled
label.set_margin_bottom(5);
label.set_margin_end(5);
label.set_margin_start(5);
let label_regex_valid = gtk::Label::new(None);
// 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);
// Tooltips
{
let tooltip_path = "Allows to select records by its path.\n\nExample usage:\n/home/pimpek/rzecz.txt can be found with /home/pim*";
let tooltip_name = "Allows to select records by file names.\n\nExample usage:\n/usr/ping/pong.txt can be found with *ong*";
let tooltip_regex = "Allows to select records by specified Regex.\n\nWith this mode, searched text is Path with Name\n\nExample usage:\n/usr/bin/ziemniak.txt can be found with /ziem[a-z]+\n\nThis use default Rust regex implementation, so you can read more about it in https://docs.rs/regex.";
let tooltip_group_button = "Prevents from selecting all records in group.\n\n This is enabled by default, because in most of situations user don't want to delete both original and duplicates files, but want to leave at least one file.\n\nWarning: This setting don't work if already user selected all results in group manually.";
grid.attach(&label, 0, 0, 2, 1);
check_button_path.set_tooltip_text(Some(tooltip_path));
entry_path.set_tooltip_text(Some(tooltip_path));
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);
check_button_name.set_tooltip_text(Some(tooltip_name));
entry_name.set_tooltip_text(Some(tooltip_name));
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);
check_button_rust_regex.set_tooltip_text(Some(tooltip_regex));
entry_rust_regex.set_tooltip_text(Some(tooltip_regex));
let box_widget = get_dialog_box_child(&dialog);
box_widget.add(&grid);
check_button_select_not_all_results.set_tooltip_text(Some(tooltip_group_button));
}
{
let label_regex_valid = label_regex_valid.clone();
entry_rust_regex.connect_changed(move |entry_rust_regex| {
let message;
let text_to_check = entry_rust_regex.text().to_string();
if text_to_check.is_empty() {
message = "";
} else {
match Regex::new(&text_to_check) {
Ok(_) => message = "Regex is valid",
Err(_) => message = "Regex is invalid",
}
}
dialog.show_all();
// TODO add red and green color to text
// let attributes_list = AttrList::new();
// let p_a = PangoAttribute::init();
// let attribute = PangoAttrFontDesc { attr };
// attributes_list.insert(attribute);
// label_regex_valid.set_attributes(Some(&attributes_list));
label_regex_valid.set_text(message);
});
}
// Disable other modes when Rust Regex is enabled
{
let check_button_path = check_button_path.clone();
let check_button_name = check_button_name.clone();
let check_button_rust_regex = check_button_rust_regex.clone();
let entry_path = entry_path.clone();
let entry_name = entry_name.clone();
let entry_rust_regex = entry_rust_regex.clone();
check_button_rust_regex.connect_toggled(move |check_button_rust_regex| {
if check_button_rust_regex.is_active() {
check_button_path.set_sensitive(false);
check_button_name.set_sensitive(false);
entry_path.set_sensitive(false);
entry_name.set_sensitive(false);
entry_rust_regex.set_sensitive(true);
} else {
check_button_path.set_sensitive(true);
check_button_name.set_sensitive(true);
entry_path.set_sensitive(true);
entry_name.set_sensitive(true);
entry_rust_regex.set_sensitive(false);
}
});
}
// Configure look of things
{
// 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(&check_button_name, 0, 1, 1, 1);
grid.attach(&check_button_path, 0, 2, 1, 1);
grid.attach(&check_button_rust_regex, 0, 3, 1, 1);
grid.attach(&entry_name, 1, 1, 1, 1);
grid.attach(&entry_path, 1, 2, 1, 1);
grid.attach(&entry_rust_regex, 1, 3, 1, 1);
grid.attach(&label_regex_valid, 0, 4, 2, 1);
if select_things {
grid.attach(&check_button_select_not_all_results, 0, 5, 2, 1);
}
let box_widget = get_dialog_box_child(&dialog);
box_widget.add(&grid);
dialog.show_all();
}
let tree_view = tree_view.clone();
dialog.connect_response(move |confirmation_dialog_select_unselect, response_type| {
let wildcard_type: WildcardType;
let wildcard: String;
let name_widcard = entry_name.text().trim().to_string();
let path_widcard = entry_path.text().trim().to_string();
let regex_widcard = entry_rust_regex.text().trim().to_string();
#[cfg(target_family = "windows")]
let name_widcard = name_widcard.replace("/", "\\");
#[cfg(target_family = "windows")]
let path_widcard = name_widcard.replace("/", "\\");
if response_type == gtk::ResponseType::Ok {
if radio_path.is_active() {
wildcard_type = WildcardType::Path;
wildcard = entry_path.text().to_string();
} else if radio_name.is_active() {
wildcard_type = WildcardType::Name;
wildcard = entry_name.text().to_string();
} else if radio_name_path.is_active() {
wildcard_type = WildcardType::PathName;
wildcard = entry_name_path.text().to_string();
} else {
panic!("Non handled option in wildcard");
}
let check_path = check_button_path.is_active();
let check_name = check_button_name.is_active();
let check_regex = check_button_rust_regex.is_active();
if !wildcard.is_empty() {
let wildcard = wildcard.trim();
let check_all_selected = check_button_select_not_all_results.is_active();
#[cfg(target_family = "windows")]
let wildcard = wildcard.replace("/", "\\");
#[cfg(target_family = "windows")]
let wildcard = wildcard.as_str();
if check_button_path.is_active() || check_button_name.is_active() || check_button_rust_regex.is_active() {
let compiled_regex = match check_regex {
true => match Regex::new(&regex_widcard) {
Ok(t) => t,
Err(_) => {
eprintln!("What? Regex should compile properly.");
confirmation_dialog_select_unselect.close();
return;
}
},
false => Regex::new("").unwrap(),
};
let model = get_list_store(&tree_view);
let iter = model.iter_first().unwrap(); // Never should be available button where there is no available records
let mut number_of_all_things = 0;
let mut number_of_already_selected_things = 0;
let mut vec_of_iters: Vec<TreeIter> = Vec::new();
loop {
if let Some(column_color) = column_color {
let color = model.value(&iter, column_color).get::<String>().unwrap();
if color == HEADER_ROW_COLOR {
if select_things {
if check_all_selected && (number_of_all_things - number_of_already_selected_things == vec_of_iters.len()) {
vec_of_iters.pop();
}
for iter in vec_of_iters {
model.set_value(&iter, column_button_selection, &true.to_value());
}
} else {
for iter in vec_of_iters {
model.set_value(&iter, column_button_selection, &false.to_value());
}
}
if !model.iter_next(&iter) {
break;
}
number_of_all_things = 0;
number_of_already_selected_things = 0;
vec_of_iters = Vec::new();
continue;
}
}
let is_selected = model.value(&iter, column_button_selection as i32).get::<bool>().unwrap();
let path = model.value(&iter, column_path).get::<String>().unwrap();
let name = model.value(&iter, column_file_name).get::<String>().unwrap();
match wildcard_type {
WildcardType::Path => {
if Common::regex_check(wildcard, path) {
model.set_value(&iter, column_button_selection, &select_things.to_value());
}
#[cfg(not(target_family = "windows"))]
let character = "/";
#[cfg(target_family = "windows")]
let character = "\\";
let path_and_name = format!("{}{}{}", path, character, name);
let mut need_to_change_thing: bool = false;
number_of_all_things += 1;
if check_regex && compiled_regex.find(&path_and_name).is_some() {
need_to_change_thing = true;
} else {
if check_name && Common::regex_check(&name_widcard, &name) {
need_to_change_thing = true;
}
WildcardType::Name => {
if Common::regex_check(wildcard, name) {
model.set_value(&iter, column_button_selection, &select_things.to_value());
}
if check_path && Common::regex_check(&path_widcard, &path) {
need_to_change_thing = true;
}
WildcardType::PathName => {
if Common::regex_check(wildcard, format!("{}/{}", path, name)) {
model.set_value(&iter, column_button_selection, &select_things.to_value());
}
if need_to_change_thing {
if select_things {
if is_selected {
number_of_already_selected_things += 1;
} else {
vec_of_iters.push(iter.clone());
}
} else {
vec_of_iters.push(iter.clone());
}
}
if !model.iter_next(&iter) {
if select_things {
if check_all_selected && (number_of_all_things - number_of_already_selected_things == vec_of_iters.len()) {
vec_of_iters.pop();
}
for iter in vec_of_iters {
model.set_value(&iter, column_button_selection, &true.to_value());
}
} else {
for iter in vec_of_iters {
model.set_value(&iter, column_button_selection, &false.to_value());
}
}
break;
}
}
}
} else {
confirmation_dialog_select_unselect.close();
return;
}
confirmation_dialog_select_unselect.close();
});
@ -609,8 +724,8 @@ pub fn connect_popovers(gui_data: &GuiData) {
popover_all_except_biggest_smallest(
&popover_select,
tree_view,
nb_object.column_color.expect("AEBI can't be used without headers"),
nb_object.column_size_as_bytes.expect("AEBI needs size as bytes column"),
nb_object.column_color.expect("AEB can't be used without headers"),
nb_object.column_size_as_bytes.expect("AEB needs size as bytes column"),
nb_object.column_dimensions,
nb_object.column_selection as u32,
true,
@ -629,8 +744,8 @@ pub fn connect_popovers(gui_data: &GuiData) {
popover_all_except_biggest_smallest(
&popover_select,
tree_view,
nb_object.column_color.expect("AESI can't be used without headers"),
nb_object.column_size_as_bytes.expect("AESI needs size as bytes column"),
nb_object.column_color.expect("AES can't be used without headers"),
nb_object.column_size_as_bytes.expect("AES needs size as bytes column"),
nb_object.column_dimensions,
nb_object.column_selection as u32,
false,

View file

@ -27,7 +27,7 @@ Author: Rafał Mikrut
-->
<interface>
<requires lib="gtk+" version="3.22"/>
<requires lib="gtk+" version="3.24"/>
<!-- interface-license-type mit -->
<!-- interface-name Czkawka -->
<!-- interface-description Czkawka is simple and fast app to find duplicates, empty folders, similar images etc. -->

View file

@ -27,7 +27,7 @@ Author: Rafał Mikrut
-->
<interface>
<requires lib="gtk+" version="3.22"/>
<requires lib="gtk+" version="3.24"/>
<!-- interface-license-type mit -->
<!-- interface-name Czkawka -->
<!-- interface-description Czkawka is simple and fast app to find duplicates, empty folders, similar images etc. -->
@ -229,6 +229,7 @@ Author: Rafał Mikrut
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="margin-end">5</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
</object>
@ -479,6 +480,8 @@ Author: Rafał Mikrut
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel">
@ -585,6 +588,8 @@ Author: Rafał Mikrut
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel">
@ -657,6 +662,9 @@ Author: Rafał Mikrut
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-top">2</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
@ -725,6 +733,8 @@ Author: Rafał Mikrut
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
@ -793,6 +803,9 @@ Author: Rafał Mikrut
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-top">2</property>
<property name="spacing">8</property>
<child>
<object class="GtkLabel">
@ -871,6 +884,7 @@ Author: Rafał Mikrut
<object class="GtkScrolledWindow" id="scrolled_window_duplicate_finder">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="margin-end">5</property>
<property name="shadow-type">in</property>
<child>
<placeholder/>
@ -948,6 +962,9 @@ Author: Rafał Mikrut
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-top">2</property>
<property name="spacing">8</property>
<child>
<object class="GtkLabel">
@ -1075,6 +1092,9 @@ Author: Rafał Mikrut
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-top">2</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
@ -1177,6 +1197,8 @@ Author: Rafał Mikrut
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
@ -1279,6 +1301,8 @@ Author: Rafał Mikrut
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
@ -1349,6 +1373,8 @@ Author: Rafał Mikrut
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="spacing">8</property>
<child>
<object class="GtkLabel">
@ -1427,6 +1453,9 @@ Author: Rafał Mikrut
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-bottom">2</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
@ -1489,6 +1518,7 @@ Author: Rafał Mikrut
<object class="GtkScrolledWindow" id="scrolled_window_similar_images_finder">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="margin-end">5</property>
<property name="shadow-type">in</property>
<child>
<placeholder/>
@ -1546,6 +1576,9 @@ Author: Rafał Mikrut
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-top">2</property>
<property name="spacing">8</property>
<child>
<object class="GtkLabel">
@ -1624,6 +1657,9 @@ Author: Rafał Mikrut
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-bottom">2</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
@ -1722,6 +1758,9 @@ Author: Rafał Mikrut
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-top">2</property>
<property name="spacing">8</property>
<child>
<object class="GtkLabel">
@ -1800,6 +1839,9 @@ Author: Rafał Mikrut
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-bottom">2</property>
<property name="spacing">8</property>
<child>
<object class="GtkCheckButton" id="check_button_music_title">
@ -1884,6 +1926,7 @@ Author: Rafał Mikrut
<object class="GtkScrolledWindow" id="scrolled_window_same_music_finder">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="margin-end">5</property>
<property name="shadow-type">in</property>
<child>
<placeholder/>

View file

@ -27,7 +27,7 @@ Author: Rafał Mikrut
-->
<interface>
<requires lib="gtk+" version="3.22"/>
<requires lib="gtk+" version="3.24"/>
<!-- interface-license-type mit -->
<!-- interface-name Czkawka -->
<!-- interface-description Czkawka is simple and fast app to find duplicates, empty folders, similar images etc. -->

View file

@ -27,7 +27,7 @@ Author: Rafał Mikrut
-->
<interface>
<requires lib="gtk+" version="3.22"/>
<requires lib="gtk+" version="3.24"/>
<!-- interface-license-type mit -->
<!-- interface-name Czkawka -->
<!-- interface-description Czkawka is simple and fast app to find duplicates, empty folders, similar images etc. -->

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2
<!-- Generated with glade 3.39.0
The MIT License (MIT)
@ -27,7 +27,7 @@ Author: Rafał Mikrut
-->
<interface>
<requires lib="gtk+" version="3.22"/>
<requires lib="gtk+" version="3.24"/>
<!-- interface-license-type mit -->
<!-- interface-name Czkawka -->
<!-- interface-description Czkawka is simple and fast app to find duplicates, empty folders, similar images etc. -->
@ -50,6 +50,10 @@ Author: Rafał Mikrut
<object class="GtkGrid" id="grid_progress_stages">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="valign">center</property>
<property name="margin-start">2</property>
<property name="margin-end">2</property>
<property name="margin-top">2</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
@ -121,6 +125,7 @@ Author: Rafał Mikrut
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="halign">end</property>
<property name="margin-end">2</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>

View file

@ -27,7 +27,7 @@ Author: Rafał Mikrut
-->
<interface>
<requires lib="gtk+" version="3.22"/>
<requires lib="gtk+" version="3.24"/>
<!-- interface-license-type mit -->
<!-- interface-name Czkawka -->
<!-- interface-description Czkawka is simple and fast app to find duplicates, empty folders, similar images etc. -->