1
0
Fork 0
mirror of synced 2024-05-17 19:03:08 +12:00
This commit is contained in:
Rafał Mikrut 2024-02-11 10:25:14 +01:00
parent 473375ad5f
commit dfb89b2f5e
11 changed files with 263 additions and 26 deletions

View file

@ -26,6 +26,12 @@ pub struct FolderEntry {
pub modified_date: u64, pub modified_date: u64,
} }
impl FolderEntry {
pub fn get_modified_date(&self) -> u64 {
self.modified_date
}
}
pub struct EmptyFolder { pub struct EmptyFolder {
common_data: CommonToolData, common_data: CommonToolData,
information: Info, information: Info,

View file

@ -4,7 +4,7 @@ use crate::{CurrentTab, ExcludedDirectoriesModel, IncludedDirectoriesModel, Main
use slint::{ModelRc, SharedString, VecModel}; use slint::{ModelRc, SharedString, VecModel};
// Remember to match updated this according to ui/main_lists.slint and connect_scan.rs files // Remember to match updated this according to ui/main_lists.slint and connect_scan.rs files
pub fn get_path_idx(active_tab: CurrentTab) -> usize { pub fn get_str_path_idx(active_tab: CurrentTab) -> usize {
match active_tab { match active_tab {
CurrentTab::EmptyFolders => 1, CurrentTab::EmptyFolders => 1,
CurrentTab::EmptyFiles => 1, CurrentTab::EmptyFiles => 1,
@ -12,7 +12,7 @@ pub fn get_path_idx(active_tab: CurrentTab) -> usize {
CurrentTab::Settings => panic!("Button should be disabled"), CurrentTab::Settings => panic!("Button should be disabled"),
} }
} }
pub fn get_name_idx(active_tab: CurrentTab) -> usize { pub fn get_str_name_idx(active_tab: CurrentTab) -> usize {
match active_tab { match active_tab {
CurrentTab::EmptyFolders => 0, CurrentTab::EmptyFolders => 0,
CurrentTab::EmptyFiles => 0, CurrentTab::EmptyFiles => 0,
@ -20,6 +20,39 @@ pub fn get_name_idx(active_tab: CurrentTab) -> usize {
CurrentTab::Settings => panic!("Button should be disabled"), CurrentTab::Settings => panic!("Button should be disabled"),
} }
} }
pub fn get_int_modification_date_idx(active_tab: CurrentTab) -> usize {
match active_tab {
CurrentTab::EmptyFiles => 0,
CurrentTab::SimilarImages => 0,
CurrentTab::EmptyFolders => 0,
CurrentTab::Settings => panic!("Button should be disabled"),
}
}
pub fn get_int_size_idx(active_tab: CurrentTab) -> usize {
match active_tab {
CurrentTab::EmptyFiles => 2,
CurrentTab::SimilarImages => 2,
CurrentTab::Settings => panic!("Button should be disabled"),
CurrentTab::EmptyFolders => panic!("Unable to get size from this tab"),
}
}
pub fn get_width_idx(active_tab: CurrentTab) -> usize {
match active_tab {
CurrentTab::SimilarImages => 4,
CurrentTab::Settings => panic!("Button should be disabled"),
_ => panic!("Unable to get height from this tab"),
}
}
pub fn get_height_idx(active_tab: CurrentTab) -> usize {
match active_tab {
CurrentTab::SimilarImages => 5,
CurrentTab::Settings => panic!("Button should be disabled"),
_ => panic!("Unable to get height from this tab"),
}
}
pub fn get_is_header_mode(active_tab: CurrentTab) -> bool { pub fn get_is_header_mode(active_tab: CurrentTab) -> bool {
match active_tab { match active_tab {
CurrentTab::EmptyFolders | CurrentTab::EmptyFiles => false, CurrentTab::EmptyFolders | CurrentTab::EmptyFiles => false,
@ -99,3 +132,56 @@ pub fn create_excluded_directories_model_from_pathbuf(items: &[PathBuf]) -> Mode
pub fn create_vec_model_from_vec_string(items: Vec<String>) -> VecModel<SharedString> { pub fn create_vec_model_from_vec_string(items: Vec<String>) -> VecModel<SharedString> {
VecModel::from(items.into_iter().map(SharedString::from).collect::<Vec<_>>()) VecModel::from(items.into_iter().map(SharedString::from).collect::<Vec<_>>())
} }
// Workaround for https://github.com/slint-ui/slint/discussions/4596
// Currently there is no way to save u64 in slint, so we need to split it into two i32
pub fn split_u64_into_i32s(value: u64) -> (i32, i32) {
let part1: i32 = (value >> 32) as i32;
let part2: i32 = value as i32;
(part1, part2)
}
pub fn connect_i32_into_u64(part1: i32, part2: i32) -> u64 {
((part1 as u64) << 32) | (part2 as u64 & 0xFFFFFFFF)
}
#[cfg(test)]
mod test {
use crate::common::split_u64_into_i32s;
#[test]
fn test_split_u64_into_i32s_small() {
let value = 1;
let (part1, part2) = split_u64_into_i32s(value);
assert_eq!(part1, 0);
assert_eq!(part2, 1);
}
#[test]
fn test_split_u64_into_i32s_big() {
let value = u64::MAX;
let (part1, part2) = split_u64_into_i32s(value);
assert_eq!(part1, -1);
assert_eq!(part2, -1);
}
#[test]
fn test_connect_i32_into_u64_small() {
let part1 = 0;
let part2 = 1;
let value = super::connect_i32_into_u64(part1, part2);
assert_eq!(value, 1);
}
#[test]
fn test_connect_i32_into_u64_big() {
let part1 = -1;
let part2 = -1;
let value = super::connect_i32_into_u64(part1, part2);
assert_eq!(value, u64::MAX);
}
#[test]
fn test_connect_split_zero() {
for start_value in [0, 1, 10, u32::MAX as u64, i32::MAX as u64, u64::MAX] {
let (part1, part2) = split_u64_into_i32s(start_value);
let end_value = super::connect_i32_into_u64(part1, part2);
assert_eq!(start_value, end_value);
}
}
}

View file

@ -4,7 +4,7 @@ use std::path::MAIN_SEPARATOR;
use czkawka_core::common::remove_folder_if_contains_only_empty_folders; use czkawka_core::common::remove_folder_if_contains_only_empty_folders;
use crate::common::{get_is_header_mode, get_name_idx, get_path_idx, get_tool_model, set_tool_model}; use crate::common::{get_is_header_mode, get_str_name_idx, get_str_path_idx, get_tool_model, set_tool_model};
use crate::{Callabler, CurrentTab, GuiState, MainListModel, MainWindow}; use crate::{Callabler, CurrentTab, GuiState, MainListModel, MainWindow};
pub fn connect_delete_button(app: &MainWindow) { pub fn connect_delete_button(app: &MainWindow) {
@ -44,8 +44,8 @@ fn handle_delete_items(items: &ModelRc<MainListModel>, active_tab: CurrentTab) -
// and at the end should be send signal to main thread to update model // and at the end should be send signal to main thread to update model
// TODO handle also situations where cannot delete file/folder // TODO handle also situations where cannot delete file/folder
fn remove_selected_items(items: Vec<MainListModel>, active_tab: CurrentTab) { fn remove_selected_items(items: Vec<MainListModel>, active_tab: CurrentTab) {
let path_idx = get_path_idx(active_tab); let path_idx = get_str_path_idx(active_tab);
let name_idx = get_name_idx(active_tab); let name_idx = get_str_name_idx(active_tab);
let items_to_remove = items let items_to_remove = items
.iter() .iter()
.map(|item| { .map(|item| {

View file

@ -16,6 +16,7 @@ use czkawka_core::empty_folder::{EmptyFolder, FolderEntry};
use czkawka_core::similar_images; use czkawka_core::similar_images;
use czkawka_core::similar_images::{ImagesEntry, SimilarImages}; use czkawka_core::similar_images::{ImagesEntry, SimilarImages};
use crate::common::split_u64_into_i32s;
use crate::settings::{collect_settings, SettingsCustom, ALLOWED_HASH_TYPE_VALUES, ALLOWED_RESIZE_ALGORITHM_VALUES}; use crate::settings::{collect_settings, SettingsCustom, ALLOWED_HASH_TYPE_VALUES, ALLOWED_RESIZE_ALGORITHM_VALUES};
use crate::{CurrentTab, GuiState, MainListModel, MainWindow, ProgressToSend}; use crate::{CurrentTab, GuiState, MainListModel, MainWindow, ProgressToSend};
@ -50,7 +51,6 @@ pub fn connect_scan_button(app: &MainWindow, progress_sender: Sender<ProgressDat
}); });
} }
// TODO handle referenced folders
fn scan_similar_images(a: Weak<MainWindow>, progress_sender: Sender<ProgressData>, stop_receiver: Receiver<()>, custom_settings: SettingsCustom) { fn scan_similar_images(a: Weak<MainWindow>, progress_sender: Sender<ProgressData>, stop_receiver: Receiver<()>, custom_settings: SettingsCustom) {
thread::Builder::new() thread::Builder::new()
.stack_size(DEFAULT_THREAD_SIZE) .stack_size(DEFAULT_THREAD_SIZE)
@ -70,6 +70,7 @@ fn scan_similar_images(a: Weak<MainWindow>, progress_sender: Sender<ProgressData
.expect("Hash type not found") .expect("Hash type not found")
.2; .2;
finder.set_hash_alg(hash_type); finder.set_hash_alg(hash_type);
dbg!(&custom_settings.similar_images_sub_ignore_same_size);
finder.set_exclude_images_with_same_size(custom_settings.similar_images_sub_ignore_same_size); finder.set_exclude_images_with_same_size(custom_settings.similar_images_sub_ignore_same_size);
finder.set_similarity(custom_settings.similar_images_sub_similarity as u32); finder.set_similarity(custom_settings.similar_images_sub_similarity as u32);
finder.find_similar_images(Some(&stop_receiver), Some(&progress_sender)); finder.find_similar_images(Some(&stop_receiver), Some(&progress_sender));
@ -144,7 +145,9 @@ fn prepare_data_model_similar_images(fe: &ImagesEntry, hash_size: u8) -> (ModelR
directory.into(), directory.into(),
NaiveDateTime::from_timestamp_opt(fe.get_modified_date() as i64, 0).unwrap().to_string().into(), NaiveDateTime::from_timestamp_opt(fe.get_modified_date() as i64, 0).unwrap().to_string().into(),
]); ]);
let data_model_int = VecModel::from_slice(&[fe.width as i32, fe.height as i32]); let modification_split = split_u64_into_i32s(fe.get_modified_date());
let size_split = split_u64_into_i32s(fe.size);
let data_model_int = VecModel::from_slice(&[modification_split.0, modification_split.1, size_split.0, size_split.1, fe.width as i32, fe.height as i32]);
(data_model_str, data_model_int) (data_model_str, data_model_int)
} }
@ -187,7 +190,9 @@ fn prepare_data_model_empty_files(fe: &FileEntry) -> (ModelRc<SharedString>, Mod
directory.into(), directory.into(),
NaiveDateTime::from_timestamp_opt(fe.get_modified_date() as i64, 0).unwrap().to_string().into(), NaiveDateTime::from_timestamp_opt(fe.get_modified_date() as i64, 0).unwrap().to_string().into(),
]); ]);
let data_model_int = VecModel::from_slice(&[]); let modification_split = split_u64_into_i32s(fe.get_modified_date());
let size_split = split_u64_into_i32s(fe.size);
let data_model_int = VecModel::from_slice(&[modification_split.0, modification_split.1, size_split.0, size_split.1]);
(data_model_str, data_model_int) (data_model_str, data_model_int)
} }
@ -230,7 +235,8 @@ fn prepare_data_model_empty_folders(fe: &FolderEntry) -> (ModelRc<SharedString>,
directory.into(), directory.into(),
NaiveDateTime::from_timestamp_opt(fe.modified_date as i64, 0).unwrap().to_string().into(), NaiveDateTime::from_timestamp_opt(fe.modified_date as i64, 0).unwrap().to_string().into(),
]); ]);
let data_model_int = VecModel::from_slice(&[]); let modification_split = split_u64_into_i32s(fe.get_modified_date());
let data_model_int = VecModel::from_slice(&[modification_split.0, modification_split.1]);
(data_model_str, data_model_int) (data_model_str, data_model_int)
} }
@ -241,7 +247,7 @@ fn insert_data_to_model(items: &Rc<VecModel<MainListModel>>, data_model_str: Mod
header_row, header_row,
selected_row: false, selected_row: false,
val_str: ModelRc::new(data_model_str), val_str: ModelRc::new(data_model_str),
val_int: ModelRc::new(data_model_int), // TODO fill val_int: ModelRc::new(data_model_int),
}; };
items.push(main); items.push(main);
} }

View file

@ -1,6 +1,6 @@
use crate::common::{get_tool_model, set_tool_model}; use crate::common::{connect_i32_into_u64, get_int_size_idx, get_is_header_mode, get_tool_model, set_tool_model};
use crate::SelectModel;
use crate::{Callabler, GuiState, MainListModel, MainWindow, SelectMode}; use crate::{Callabler, GuiState, MainListModel, MainWindow, SelectMode};
use crate::{CurrentTab, SelectModel};
use slint::{ComponentHandle, Model, ModelRc, VecModel}; use slint::{ComponentHandle, Model, ModelRc, VecModel};
// TODO optimize this, not sure if it is possible to not copy entire model to just select item // TODO optimize this, not sure if it is possible to not copy entire model to just select item
@ -16,6 +16,9 @@ pub fn connect_select(app: &MainWindow) {
SelectMode::SelectAll => select_all(current_model), SelectMode::SelectAll => select_all(current_model),
SelectMode::UnselectAll => deselect_all(current_model), SelectMode::UnselectAll => deselect_all(current_model),
SelectMode::InvertSelection => invert_selection(current_model), SelectMode::InvertSelection => invert_selection(current_model),
SelectMode::SelectTheBiggestSize => select_the_biggest_size(current_model, active_tab),
SelectMode::SelectTheSmallestSize => select_the_small_size(current_model, active_tab),
_ => unimplemented!(),
}; };
set_tool_model(&app, active_tab, new_model); set_tool_model(&app, active_tab, new_model);
}); });
@ -31,10 +34,23 @@ pub fn connect_showing_proper_select_buttons(app: &MainWindow) {
} }
fn set_select_buttons(app: &MainWindow) { fn set_select_buttons(app: &MainWindow) {
// let active_tab = app.global::<GuiState>().get_active_tab(); let active_tab = app.global::<GuiState>().get_active_tab();
let base_buttons = vec![SelectMode::SelectAll, SelectMode::UnselectAll, SelectMode::InvertSelection]; let mut base_buttons = vec![SelectMode::SelectAll, SelectMode::UnselectAll, SelectMode::InvertSelection];
// TODO Here needs to be put logic to set custom buttons depending on tab let additional_buttons = match active_tab {
CurrentTab::SimilarImages => vec![
SelectMode::SelectOldest,
SelectMode::SelectNewest,
SelectMode::SelectTheSmallestSize,
SelectMode::SelectTheBiggestSize,
SelectMode::SelectTheSmallestResolution,
SelectMode::SelectTheBiggestResolution,
],
_ => vec![],
};
base_buttons.extend(additional_buttons);
base_buttons.reverse();
let new_select_model = base_buttons let new_select_model = base_buttons
.into_iter() .into_iter()
@ -52,9 +68,65 @@ fn translate_select_mode(select_mode: SelectMode) -> String {
SelectMode::SelectAll => "Select all".into(), SelectMode::SelectAll => "Select all".into(),
SelectMode::UnselectAll => "Unselect all".into(), SelectMode::UnselectAll => "Unselect all".into(),
SelectMode::InvertSelection => "Invert selection".into(), SelectMode::InvertSelection => "Invert selection".into(),
SelectMode::SelectTheBiggestSize => "Select the biggest size".into(),
SelectMode::SelectTheBiggestResolution => "Select the biggest resolution".into(),
SelectMode::SelectTheSmallestSize => "Select the smallest size".into(),
SelectMode::SelectTheSmallestResolution => "Select the smallest resolution".into(),
SelectMode::SelectNewest => "Select newest".into(),
SelectMode::SelectOldest => "Select oldest".into(),
} }
} }
fn select_the_biggest_size(model: ModelRc<MainListModel>, active_tab: CurrentTab) -> ModelRc<MainListModel> {
let is_header_mode = get_is_header_mode(active_tab);
assert!(is_header_mode); // non header modes not really have reasont to use this function
let mut old_data = model.iter().collect::<Vec<_>>();
let headers_idx = find_header_idx_and_deselect_all(&mut old_data);
let size_idx = get_int_size_idx(active_tab);
for i in 0..(headers_idx.len() - 1) {
let mut max_size = 0;
let mut max_size_idx = 0;
for j in (headers_idx[i] + 1)..headers_idx[i + 1] {
let int_data = old_data[j].val_int.iter().collect::<Vec<_>>();
let size = connect_i32_into_u64(int_data[size_idx], int_data[size_idx + 1]);
if size > max_size {
max_size = size;
max_size_idx = j;
}
}
old_data[max_size_idx].checked = true;
}
ModelRc::new(VecModel::from(old_data))
}
fn select_the_small_size(model: ModelRc<MainListModel>, active_tab: CurrentTab) -> ModelRc<MainListModel> {
let is_header_mode = get_is_header_mode(active_tab);
assert!(is_header_mode); // non header modes not really have reasont to use this function
let mut old_data = model.iter().collect::<Vec<_>>();
let headers_idx = find_header_idx_and_deselect_all(&mut old_data);
let size_idx = get_int_size_idx(active_tab);
for i in 0..(headers_idx.len() - 1) {
let mut min_size = u64::MAX;
let mut min_size_idx = 0;
for j in (headers_idx[i] + 1)..headers_idx[i + 1] {
let int_data = old_data[j].val_int.iter().collect::<Vec<_>>();
let size = connect_i32_into_u64(int_data[size_idx], int_data[size_idx + 1]);
if size < min_size {
min_size = size;
min_size_idx = j;
}
}
old_data[min_size_idx].checked = true;
}
ModelRc::new(VecModel::from(old_data))
}
fn select_all(model: ModelRc<MainListModel>) -> ModelRc<MainListModel> { fn select_all(model: ModelRc<MainListModel>) -> ModelRc<MainListModel> {
let mut old_data = model.iter().collect::<Vec<_>>(); let mut old_data = model.iter().collect::<Vec<_>>();
old_data.iter_mut().for_each(|x| { old_data.iter_mut().for_each(|x| {
@ -80,3 +152,64 @@ fn invert_selection(model: ModelRc<MainListModel>) -> ModelRc<MainListModel> {
}); });
ModelRc::new(VecModel::from(old_data)) ModelRc::new(VecModel::from(old_data))
} }
fn find_header_idx_and_deselect_all(old_data: &mut Vec<MainListModel>) -> Vec<usize> {
let mut header_idx = old_data
.iter()
.enumerate()
.filter_map(|(idx, m)| if m.header_row { Some(idx) } else { None })
.collect::<Vec<_>>();
header_idx.push(old_data.len());
old_data.iter_mut().for_each(|x| {
if !x.header_row {
x.checked = false;
}
});
header_idx
}
#[cfg(test)]
mod test {
use crate::{MainListModel, SelectMode};
use slint::ModelRc;
// #[test]
// pub fn test_select_all() {
// let model = ModelRc::new(VecModel::from(vec![SelectModel {
// name: "test".into(),
// data: SelectMode::SelectAll,
// }]));
// let new_model = select_all(model);
// let new_data = new_model.iter().collect::<Vec<_>>();
// assert_eq!(new_data[0].checked, true);
// }
//
// fn prepare_simple_model() -> ModelRc<MainListModel> {
// ModelRc::new(VecModel::from(vec![
// MainListModel {
// header_row: false,
// checked: false,
// selected_row: false,
// val_str: [],
// val_int: [0, 0, 0, 0, 0, 0],
// },
// MainListModel {
// header_row: false,
// checked: true,
// text: "test".into(),
// size: 0,
// resolution: (0, 0),
// date: 0,
// },
// MainListModel {
// header_row: false,
// checked: false,
// text: "test".into(),
// size: 0,
// resolution: (0, 0),
// date: 0,
// },
// ]))
// }
}

View file

@ -72,7 +72,7 @@ export component ActionButtons inherits HorizontalLayout {
enabled: !scanning && lists_enabled; enabled: !scanning && lists_enabled;
text: "Select"; text: "Select";
clicked => { clicked => {
show_select_popup(self.x - self.width / 2, self.y + parent.y); show_select_popup(self.x + self.width / 2, self.y + parent.y);
} }
} }

View file

@ -44,7 +44,13 @@ export struct ExcludedDirectoriesModel {
export enum SelectMode { export enum SelectMode {
SelectAll, SelectAll,
UnselectAll, UnselectAll,
InvertSelection InvertSelection,
SelectTheBiggestSize,
SelectTheBiggestResolution,
SelectTheSmallestSize,
SelectTheSmallestResolution,
SelectNewest,
SelectOldest,
} }
export struct SelectModel { export struct SelectModel {

View file

@ -19,5 +19,5 @@ export global GuiState {
in-out property <bool> available_subsettings: active_tab == CurrentTab.SimilarImages; in-out property <bool> available_subsettings: active_tab == CurrentTab.SimilarImages;
in-out property <CurrentTab> active_tab: CurrentTab.EmptyFiles; in-out property <CurrentTab> active_tab: CurrentTab.EmptyFiles;
in-out property <[SelectModel]> select_results_list: [{data: SelectMode.SelectAll, name: "Select All"}, {data: SelectMode.UnselectAll, name: "Deselect All"}]; in-out property <[SelectModel]> select_results_list: [{data: SelectMode.SelectAll, name: "Select All"}, {data: SelectMode.UnselectAll, name: "Deselect All"}, {data: SelectMode.SelectTheSmallestResolution, name: "Select the smallest resolution"}];
} }

View file

@ -119,7 +119,7 @@ export component MainWindow inherits Window {
} }
show_select_popup(x_offset, y_offset) => { show_select_popup(x_offset, y_offset) => {
select_popup_window.x_offset = x_offset; select_popup_window.x_offset = x_offset;
select_popup_window.y_offset = y_offset - select-popup-window.all_items_height - 5px; select_popup_window.y_offset = y_offset;
select_popup_window.show_popup(); select_popup_window.show_popup();
} }
} }
@ -151,8 +151,8 @@ export component MainWindow inherits Window {
property <length> x_offset: 0; property <length> x_offset: 0;
property <length> y_offset: 0; property <length> y_offset: 0;
x: parent.x + x_offset; x: parent.x + x_offset - self.item_width / 2.0;
y: parent.y + y_offset; y: parent.y + y_offset - self.all_items_height - 5px;
height: root.height; height: root.height;
width: root.width; width: root.width;

View file

@ -17,7 +17,7 @@ export component PopupSelectResults inherits Rectangle {
callback show_popup(); callback show_popup();
property <[SelectModel]> model: GuiState.select_results_list; property <[SelectModel]> model: GuiState.select_results_list;
property <length> item_height: 30px; property <length> item_height: 30px;
property <length> item_width: 140px; out property <length> item_width: 200px;
out property <length> all_items_height: item_height * model.length; out property <length> all_items_height: item_height * model.length;
popup_window := PopupWindow { popup_window := PopupWindow {

View file

@ -69,21 +69,21 @@ export component ToolSettings {
ComboBoxWrapper { ComboBoxWrapper {
text: "Hash size"; text: "Hash size";
model: Settings.similar_images_sub_available_hash_size; model: Settings.similar_images_sub_available_hash_size;
current_index: Settings.similar_images_sub_hash_size_index; current_index <=> Settings.similar_images_sub_hash_size_index;
} }
ComboBoxWrapper { ComboBoxWrapper {
text: "Resize Algorithm"; text: "Resize Algorithm";
model: Settings.similar_images_sub_available_resize_algorithm; model: Settings.similar_images_sub_available_resize_algorithm;
current_index: Settings.similar_images_sub_resize_algorithm_index; current_index <=> Settings.similar_images_sub_resize_algorithm_index;
} }
ComboBoxWrapper { ComboBoxWrapper {
text: "Hash type"; text: "Hash type";
model: Settings.similar_images_sub_available_hash_type; model: Settings.similar_images_sub_available_hash_type;
current_index: Settings.similar_images_sub_hash_type_index; current_index <=> Settings.similar_images_sub_hash_type_index;
} }
CheckBoxWrapper { CheckBoxWrapper {
text: "Ignore same size"; text: "Ignore same size";
checked: Settings.similar_images_sub_ignore_same_size; checked <=> Settings.similar_images_sub_ignore_same_size;
} }
SliderWrapper { SliderWrapper {
text: "Max difference"; text: "Max difference";