Skip to content

Commit

Permalink
Actions
Browse files Browse the repository at this point in the history
  • Loading branch information
qarmin committed Feb 11, 2024
1 parent 473375a commit dfb89b2
Show file tree
Hide file tree
Showing 11 changed files with 263 additions and 26 deletions.
6 changes: 6 additions & 0 deletions czkawka_core/src/empty_folder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
90 changes: 88 additions & 2 deletions krokiet/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,55 @@ 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,
CurrentTab::SimilarImages => 4,
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,
CurrentTab::SimilarImages => 3,
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,
Expand Down Expand Up @@ -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);
}
}
}
6 changes: 3 additions & 3 deletions krokiet/src/connect_delete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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| {
Expand Down
16 changes: 11 additions & 5 deletions krokiet/src/connect_scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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)
Expand All @@ -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));
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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)
}

Expand All @@ -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);
}
Expand Down
143 changes: 138 additions & 5 deletions krokiet/src/connect_select.rs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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);
});
Expand All @@ -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()
Expand All @@ -52,7 +68,63 @@ 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> {
Expand Down Expand Up @@ -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,
// },
// ]))
// }
}
2 changes: 1 addition & 1 deletion krokiet/ui/action_buttons.slint
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
Loading

0 comments on commit dfb89b2

Please sign in to comment.