1
0
Fork 0
mirror of synced 2024-05-03 12:03:22 +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,
}
impl FolderEntry {
pub fn get_modified_date(&self) -> u64 {
self.modified_date
}
}
pub struct EmptyFolder {
common_data: CommonToolData,
information: Info,

View file

@ -4,7 +4,7 @@ use crate::{CurrentTab, ExcludedDirectoriesModel, IncludedDirectoriesModel, Main
use slint::{ModelRc, SharedString, VecModel};
// 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 {
CurrentTab::EmptyFolders => 1,
CurrentTab::EmptyFiles => 1,
@ -12,7 +12,7 @@ pub fn get_path_idx(active_tab: CurrentTab) -> usize {
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 {
CurrentTab::EmptyFolders => 0,
CurrentTab::EmptyFiles => 0,
@ -20,6 +20,39 @@ pub fn get_name_idx(active_tab: CurrentTab) -> usize {
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 {
match active_tab {
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> {
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 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};
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
// TODO handle also situations where cannot delete file/folder
fn remove_selected_items(items: Vec<MainListModel>, active_tab: CurrentTab) {
let path_idx = get_path_idx(active_tab);
let name_idx = get_name_idx(active_tab);
let path_idx = get_str_path_idx(active_tab);
let name_idx = get_str_name_idx(active_tab);
let items_to_remove = items
.iter()
.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::{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::{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) {
thread::Builder::new()
.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")
.2;
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_similarity(custom_settings.similar_images_sub_similarity as u32);
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(),
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)
}
@ -187,7 +190,9 @@ fn prepare_data_model_empty_files(fe: &FileEntry) -> (ModelRc<SharedString>, Mod
directory.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)
}
@ -230,7 +235,8 @@ fn prepare_data_model_empty_folders(fe: &FolderEntry) -> (ModelRc<SharedString>,
directory.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)
}
@ -241,7 +247,7 @@ fn insert_data_to_model(items: &Rc<VecModel<MainListModel>>, data_model_str: Mod
header_row,
selected_row: false,
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);
}

View file

@ -1,6 +1,6 @@
use crate::common::{get_tool_model, set_tool_model};
use crate::SelectModel;
use crate::common::{connect_i32_into_u64, get_int_size_idx, get_is_header_mode, get_tool_model, set_tool_model};
use crate::{Callabler, GuiState, MainListModel, MainWindow, SelectMode};
use crate::{CurrentTab, SelectModel};
use slint::{ComponentHandle, Model, ModelRc, VecModel};
// 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::UnselectAll => deselect_all(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);
});
@ -31,10 +34,23 @@ pub fn connect_showing_proper_select_buttons(app: &MainWindow) {
}
fn set_select_buttons(app: &MainWindow) {
// let active_tab = app.global::<GuiState>().get_active_tab();
let base_buttons = vec![SelectMode::SelectAll, SelectMode::UnselectAll, SelectMode::InvertSelection];
let active_tab = app.global::<GuiState>().get_active_tab();
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
.into_iter()
@ -52,9 +68,65 @@ fn translate_select_mode(select_mode: SelectMode) -> String {
SelectMode::SelectAll => "Select all".into(),
SelectMode::UnselectAll => "Unselect all".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> {
let mut old_data = model.iter().collect::<Vec<_>>();
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))
}
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;
text: "Select";
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 {
SelectAll,
UnselectAll,
InvertSelection
InvertSelection,
SelectTheBiggestSize,
SelectTheBiggestResolution,
SelectTheSmallestSize,
SelectTheSmallestResolution,
SelectNewest,
SelectOldest,
}
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 <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) => {
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();
}
}
@ -151,8 +151,8 @@ export component MainWindow inherits Window {
property <length> x_offset: 0;
property <length> y_offset: 0;
x: parent.x + x_offset;
y: parent.y + y_offset;
x: parent.x + x_offset - self.item_width / 2.0;
y: parent.y + y_offset - self.all_items_height - 5px;
height: root.height;
width: root.width;

View file

@ -17,7 +17,7 @@ export component PopupSelectResults inherits Rectangle {
callback show_popup();
property <[SelectModel]> model: GuiState.select_results_list;
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;
popup_window := PopupWindow {

View file

@ -69,21 +69,21 @@ export component ToolSettings {
ComboBoxWrapper {
text: "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 {
text: "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 {
text: "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 {
text: "Ignore same size";
checked: Settings.similar_images_sub_ignore_same_size;
checked <=> Settings.similar_images_sub_ignore_same_size;
}
SliderWrapper {
text: "Max difference";