1
0
Fork 0
mirror of synced 2024-05-17 19:03:08 +12:00
This commit is contained in:
Rafał Mikrut 2023-10-28 20:08:14 +02:00
parent 230ac9d6c7
commit b8c5ffa713
8 changed files with 140 additions and 126 deletions

View file

@ -1,3 +1 @@
use slint::{ModelRc, SharedString};
pub type ModelType = (bool, bool, bool, ModelRc<SharedString>);

View file

@ -2,7 +2,7 @@ use std::borrow::Borrow;
use slint::{ComponentHandle, Model, ModelRc, VecModel};
use crate::common::ModelType;
use crate::MainListModel;
use crate::{CurrentTab, MainWindow};
pub fn connect_delete_button(app: &MainWindow) {
@ -33,32 +33,32 @@ fn handle_delete_empty_folders(app: &MainWindow) {
}
// TODO delete in parallel items, consider to add progress bar
fn remove_selected_items(items: Vec<ModelType>) {
fn remove_selected_items(items: Vec<MainListModel>) {
dbg!(format!("Items to remove {}", items.len()));
items.into_iter().for_each(|(_checked, _header_row, _selected_row, _data)| {});
items.into_iter().for_each(|_item| {});
}
fn deselect_all_items(items: &mut [ModelType]) {
items.iter_mut().for_each(|(_checked, _header_row, selected_row, _data)| {
*selected_row = false;
fn deselect_all_items(items: &mut [MainListModel]) {
items.iter_mut().for_each(|item| {
item.selected_row = false;
});
}
fn filter_out_checked_items(items: &ModelRc<ModelType>, have_header: bool) -> (Vec<ModelType>, Vec<ModelType>) {
fn filter_out_checked_items(items: &ModelRc<MainListModel>, have_header: bool) -> (Vec<MainListModel>, Vec<MainListModel>) {
if cfg!(debug_assertions) {
check_if_header_is_checked(items);
check_if_header_is_selected_but_should_not_be(items, have_header);
}
let (entries_to_delete, mut entries_left): (Vec<_>, Vec<_>) = items.iter().partition(|(checked, _header_row, _selected_row, _data)| *checked);
let (entries_to_delete, mut entries_left): (Vec<_>, Vec<_>) = items.iter().partition(|item| item.checked);
if have_header && !entries_left.is_empty() {
// First row must be header
assert!(entries_left[0].1);
assert!(entries_left[0].header_row);
if entries_left.len() == 3 {
// First row is header, so if second or third is also header, then there is no enough items to fill model
if entries_left[1].1 || entries_left[2].1 {
if entries_left[1].header_row || entries_left[2].header_row {
entries_left = Vec::new();
}
} else if entries_left.len() < 3 {
@ -66,9 +66,9 @@ fn filter_out_checked_items(items: &ModelRc<ModelType>, have_header: bool) -> (V
entries_left = Vec::new();
} else {
let mut last_header = 0;
let mut new_items: Vec<ModelType> = Vec::new();
let mut new_items: Vec<MainListModel> = Vec::new();
for i in 1..entries_left.len() {
if entries_left[i].1 {
if entries_left[i].header_row {
if i - last_header > 2 {
new_items.extend(entries_left[last_header..i].iter().cloned());
}
@ -89,22 +89,20 @@ fn filter_out_checked_items(items: &ModelRc<ModelType>, have_header: bool) -> (V
// Function to verify if really headers are not checked
// Checked header is big bug
#[cfg(debug_assertions)]
fn check_if_header_is_checked(items: &ModelRc<ModelType>) {
for i in items.iter() {
let (checked, header_row, _selected_row, _data) = i;
if header_row {
assert!(!checked);
fn check_if_header_is_checked(items: &ModelRc<MainListModel>) {
for item in items.iter() {
if item.header_row {
assert!(!item.checked);
}
}
}
// In some modes header should not be visible, but if are, then it is a bug
#[cfg(debug_assertions)]
fn check_if_header_is_selected_but_should_not_be(items: &ModelRc<ModelType>, can_have_header: bool) {
fn check_if_header_is_selected_but_should_not_be(items: &ModelRc<MainListModel>, can_have_header: bool) {
if !can_have_header {
for i in items.iter() {
let (_checked, header_row, _selected_row, _data) = i;
assert!(!header_row);
for item in items.iter() {
assert!(!item.header_row);
}
}
}
@ -113,13 +111,13 @@ fn check_if_header_is_selected_but_should_not_be(items: &ModelRc<ModelType>, can
mod tests {
use slint::{Model, ModelRc, SharedString, VecModel};
use crate::common::ModelType;
use crate::common::MainListModel;
use crate::connect_delete::filter_out_checked_items;
#[test]
fn test_filter_out_checked_items_empty() {
let vec_items = Vec::new();
let items: ModelRc<ModelType> = ModelRc::new(VecModel::from(vec_items));
let items: ModelRc<MainListModel> = ModelRc::new(VecModel::from(vec_items));
let (to_delete, left) = filter_out_checked_items(&items, false);
assert!(to_delete.is_empty());
assert!(left.is_empty());
@ -130,7 +128,7 @@ mod tests {
#[test]
fn test_filter_out_checked_items_one_element_valid_normal() {
let vec_items = vec![(false, false, false, ModelRc::new(VecModel::default()))];
let items: ModelRc<ModelType> = ModelRc::new(VecModel::from(vec_items));
let items: ModelRc<MainListModel> = ModelRc::new(VecModel::from(vec_items));
let (to_delete, left) = filter_out_checked_items(&items, false);
assert!(to_delete.is_empty());
assert_eq!(left.len(), items.iter().count());
@ -139,7 +137,7 @@ mod tests {
#[test]
fn test_filter_out_checked_items_one_element_valid_header() {
let vec_items = vec![(false, true, false, ModelRc::new(VecModel::default()))];
let items: ModelRc<ModelType> = ModelRc::new(VecModel::from(vec_items));
let items: ModelRc<MainListModel> = ModelRc::new(VecModel::from(vec_items));
let (to_delete, left) = filter_out_checked_items(&items, true);
assert!(to_delete.is_empty());
assert!(left.is_empty());
@ -149,82 +147,79 @@ mod tests {
#[should_panic]
fn test_filter_out_checked_items_one_element_invalid_normal() {
let vec_items = vec![(false, true, false, ModelRc::new(VecModel::default()))];
let items: ModelRc<ModelType> = ModelRc::new(VecModel::from(vec_items));
let items: ModelRc<MainListModel> = ModelRc::new(VecModel::from(vec_items));
filter_out_checked_items(&items, false);
}
#[test]
#[should_panic]
fn test_filter_out_checked_items_one_element_invalid_header() {
let vec_items = vec![(false, false, false, ModelRc::new(VecModel::default()))];
let items: ModelRc<ModelType> = ModelRc::new(VecModel::from(vec_items));
let items: ModelRc<MainListModel> = ModelRc::new(VecModel::from(vec_items));
filter_out_checked_items(&items, true);
}
//
// #[test]
// fn test_filter_out_checked_items_multiple_element_valid_normal() {
// let vec_items = vec![
// (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("1")]))),
// (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("2")]))),
// (true, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("3")]))),
// (true, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("4")]))),
// (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("5")]))),
// ];
// let items: ModelRc<MainListModel> = ModelRc::new(VecModel::from(vec_items));
// let (to_delete, left) = filter_out_checked_items(&items, false);
// let to_delete_data = get_single_data_from_model(&to_delete);
// let left_data = get_single_data_from_model(&left);
//
// assert_eq!(to_delete_data, vec!["3", "4"]);
// assert_eq!(left_data, vec!["1", "2", "5"]);
// }
//
// #[test]
// fn test_filter_out_checked_items_multiple_element_valid_header() {
// let vec_items = vec![
// (false, true, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("1")]))),
// (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("2")]))),
// (true, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("3")]))),
// (false, true, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("4")]))),
// (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("5")]))),
// (false, true, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("6")]))),
// (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("7")]))),
// (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("8")]))),
// ];
// let items: ModelRc<MainListModel> = ModelRc::new(VecModel::from(vec_items));
// let (to_delete, left) = filter_out_checked_items(&items, true);
// let to_delete_data = get_single_data_from_model(&to_delete);
// let left_data = get_single_data_from_model(&left);
//
// assert_eq!(to_delete_data, vec!["3"]);
// assert_eq!(left_data, vec!["6", "7", "8"]);
// }
//
// #[test]
// fn test_filter_out_checked_items_multiple2_element_valid_header() {
// let vec_items = vec![
// (false, true, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("1")]))),
// (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("2")]))),
// (true, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("3")]))),
// (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("4")]))),
// (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("5")]))),
// (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("6")]))),
// (false, true, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("7")]))),
// (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("8")]))),
// ];
// let items: ModelRc<MainListModel> = ModelRc::new(VecModel::from(vec_items));
// let (to_delete, left) = filter_out_checked_items(&items, true);
// let to_delete_data = get_single_data_from_model(&to_delete);
// let left_data = get_single_data_from_model(&left);
//
// assert_eq!(to_delete_data, vec!["3"]);
// assert_eq!(left_data, vec!["1", "2", "4", "5", "6"]);
// }
#[test]
fn test_filter_out_checked_items_multiple_element_valid_normal() {
let vec_items = vec![
(false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("1")]))),
(false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("2")]))),
(true, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("3")]))),
(true, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("4")]))),
(false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("5")]))),
];
let items: ModelRc<ModelType> = ModelRc::new(VecModel::from(vec_items));
let (to_delete, left) = filter_out_checked_items(&items, false);
let to_delete_data = get_single_data_from_model(&to_delete);
let left_data = get_single_data_from_model(&left);
assert_eq!(to_delete_data, vec!["3", "4"]);
assert_eq!(left_data, vec!["1", "2", "5"]);
}
#[test]
fn test_filter_out_checked_items_multiple_element_valid_header() {
let vec_items = vec![
(false, true, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("1")]))),
(false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("2")]))),
(true, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("3")]))),
(false, true, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("4")]))),
(false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("5")]))),
(false, true, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("6")]))),
(false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("7")]))),
(false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("8")]))),
];
let items: ModelRc<ModelType> = ModelRc::new(VecModel::from(vec_items));
let (to_delete, left) = filter_out_checked_items(&items, true);
let to_delete_data = get_single_data_from_model(&to_delete);
let left_data = get_single_data_from_model(&left);
assert_eq!(to_delete_data, vec!["3"]);
assert_eq!(left_data, vec!["6", "7", "8"]);
}
#[test]
fn test_filter_out_checked_items_multiple2_element_valid_header() {
let vec_items = vec![
(false, true, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("1")]))),
(false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("2")]))),
(true, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("3")]))),
(false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("4")]))),
(false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("5")]))),
(false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("6")]))),
(false, true, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("7")]))),
(false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("8")]))),
];
let items: ModelRc<ModelType> = ModelRc::new(VecModel::from(vec_items));
let (to_delete, left) = filter_out_checked_items(&items, true);
let to_delete_data = get_single_data_from_model(&to_delete);
let left_data = get_single_data_from_model(&left);
assert_eq!(to_delete_data, vec!["3"]);
assert_eq!(left_data, vec!["1", "2", "4", "5", "6"]);
}
fn get_single_data_from_model(model: &[ModelType]) -> Vec<String> {
let mut d = model
.iter()
.map(|(_checked, _header_row, _selected_row, data)| data.iter().next().unwrap().to_string())
.collect::<Vec<_>>();
fn get_single_data_from_model(model: &[MainListModel]) -> Vec<String> {
let mut d = model.iter().map(|item| item.data.iter().next().unwrap().to_string()).collect::<Vec<_>>();
d.sort();
d
}

View file

@ -1,3 +1,4 @@
use crate::MainListModel;
use crate::{split_path, CurrentTab, MainWindow, ProgressToSend};
use chrono::NaiveDateTime;
use crossbeam_channel::{Receiver, Sender};
@ -58,7 +59,13 @@ fn scan_empty_folders(a: Weak<MainWindow>, progress_sender: Sender<ProgressData>
SharedString::from(NaiveDateTime::from_timestamp_opt(folder_map[&path].modified_date as i64, 0).unwrap().to_string()),
]);
items.push((false, false, false, ModelRc::new(data_model)));
let main = MainListModel {
checked: false,
header_row: false,
selected_row: false,
val: ModelRc::new(data_model),
};
items.push(main);
}
app.set_empty_folder_model(items.into());
app.invoke_scan_ended();

View file

@ -15,7 +15,6 @@ use crate::connect_delete::connect_delete_button;
use crate::connect_open::connect_open_items;
use crate::connect_scan::connect_scan_button;
use crate::common::ModelType;
use crate::connect_progress_receiver::connect_progress_gathering;
use crate::connect_stop::connect_stop_button;
use czkawka_core::common_dir_traversal::ProgressData;
@ -43,7 +42,7 @@ fn main() {
// TODO remove this after trying
pub fn to_remove_debug(app: &MainWindow) {
let row_data: Rc<VecModel<ModelType>> = Rc::new(VecModel::default());
let row_data: Rc<VecModel<MainListModel>> = Rc::new(VecModel::default());
for r in 0..100_000 {
let items = VecModel::default();
@ -53,7 +52,15 @@ pub fn to_remove_debug(app: &MainWindow) {
let is_header = r % 3 == 0;
let is_checked = (r % 2 == 0) && !is_header;
row_data.push((is_checked, is_header, false, ModelRc::new(items)));
let item = MainListModel {
checked: is_checked,
header_row: is_header,
selected_row: false,
val: ModelRc::new(items),
};
row_data.push(item);
}
app.set_empty_folder_model(row_data.into());
}

View file

@ -18,6 +18,6 @@ export struct ProgressToSend {
export struct MainListModel {
checked: bool,
header_row: bool,
selected: bool,
data: [string]
selected_row: bool,
val: [string]
}

View file

@ -9,8 +9,8 @@ export component MainList {
in-out property <CurrentTab> active-tab;
in-out property <[MainListModel]> empty_folder_model;
in-out property <[{checked: bool, header_row: bool, selected_row: bool, val:[string]}]> empty_files_model;
in-out property <[{checked: bool, header_row: bool, selected_row: bool, val:[string]}]> similar_images_model;
in-out property <[MainListModel]> empty_files_model;
in-out property <[MainListModel]> similar_images_model;
// TODO - using root.active-tab in visible property will not clear model
if root.active-tab == CurrentTab.EmptyFolders: SelectableTableView {
@ -24,26 +24,28 @@ export component MainList {
fileNameIdx: 1;
item_opened(item) => {item_opened(item)}
}
// if root.active-tab == CurrentTab.EmptyFiles: SelectableTableView {
// min-width: 200px;
if root.active-tab == CurrentTab.EmptyFiles: SelectableTableView {
min-width: 200px;
// columns: ["Selection", "Folder Name", "Path"];
// last-column: "Modification Date";
// column-sizes: [30px, 100px, 100px, 100px];
// values <=> empty-files-model;
// parentPathIdx: 2;
// fileNameIdx: 1;
// item_opened(item) => {item_opened(item)}
// }
// if root.active-tab == CurrentTab.SimilarImages: SelectableTableView {
// min-width: 200px;
columns: ["Selection", "Folder Name", "Path"];
last-column: "Modification Date";
column-sizes: [30px, 100px, 100px, 100px];
values <=> empty-files-model;
parentPathIdx: 2;
fileNameIdx: 1;
item_opened(item) => {item_opened(item)}
}
// columns: ["Selection", "Folder Name", "Path"];
// last-column: "Modification Date";
// column-sizes: [30px, 100px, 100px, 100px];
// values <=> similar-images-model;
// parentPathIdx: 2;
// fileNameIdx: 1;
// item_opened(item) => {item_opened(item)}
// }
if root.active-tab == CurrentTab.SimilarImages: SelectableTableView {
min-width: 200px;
columns: ["Selection", "Folder Name", "Path"];
last-column: "Modification Date";
column-sizes: [30px, 100px, 100px, 100px];
values <=> similar-images-model;
parentPathIdx: 2;
fileNameIdx: 1;
item_opened(item) => {item_opened(item)}
}
}

View file

@ -5,6 +5,8 @@ import {MainList} from "main_lists.slint";
import {CurrentTab, ProgressToSend} from "common.slint";
import { ActionButtons } from "action_buttons.slint";
import { Progress } from "progress.slint";
import {MainListModel} from "common.slint";
export component MainWindow inherits Window {
callback deleted;
@ -28,7 +30,7 @@ export component MainWindow inherits Window {
};
in-out property <CurrentTab> active-tab: CurrentTab.EmptyFolders;
in-out property <[{checked: bool, header_row: bool, selected_row: bool, val:[string]}]> empty_folder_model: [
in-out property <[MainListModel]> empty_folder_model: [
{checked: false, selected_row: false, header_row: true, val: ["kropkarz", "/Xd1", "24.10.2023"]} ,
{checked: false, selected_row: false, header_row: false, val: ["witasphere", "/Xd1/Imagerren2", "25.11.1991"]} ,
{checked: false, selected_row: false, header_row: false, val: ["witasphere", "/Xd1/Imagerren2", "25.11.1991"]} ,
@ -57,8 +59,8 @@ export component MainWindow inherits Window {
{checked: false, selected_row: false, header_row: false, val: ["witasphere", "/Xd1/Imagerren2", "25.11.1991"]} ,
{checked: true, selected_row: false, header_row: false, val: ["lokkaler", "/Xd1/Vide2", "01.23.1911"]}
];
in-out property <[{checked: bool, header_row: bool, selected_row: bool, val:[string]}]> empty_files_model: [];
in-out property <[{checked: bool, header_row: bool, selected_row: bool, val:[string]}]> similar_images_model: [];
in-out property <[MainListModel]> empty_files_model: [];
in-out property <[MainListModel]> similar_images_model: [];
title: root.active-tab == CurrentTab.EmptyFiles ? "EmptyFiles" : (root.active-tab == CurrentTab.EmptyFolders ? "EmptyFolders" : "Similar Images");

View file

@ -1,13 +1,15 @@
import { Button, VerticalBox , HorizontalBox, TabWidget, ListView, StandardListView, StandardTableView, CheckBox} from "std-widgets.slint";
import {TypeOfOpenedItem} from "common.slint";
import {ColorPalette} from "color_palette.slint";
import {MainListModel} from "common.slint";
export component SelectableTableView inherits Rectangle {
callback item_opened(string);
in property <[string]> columns;
in property <string> last_column;
in-out property <[{checked: bool, header_row: bool, selected_row: bool, val:[string]}]> values;
in-out property <[MainListModel]> values;
in-out property <[length]> column_sizes;
private property <[length]> real_sizes: [0px,0px,0px,0px,0px,0px,0px,0px,0px,0px,0px,0px,0px,0px,0px,0px,0px,0px];
@ -74,6 +76,7 @@ export component SelectableTableView inherits Rectangle {
forward-focus: focus-item;
for r[idx] in root.values : Rectangle {
border-radius: 20px;
forward-focus: focus-item;
height: 20px;
background: r.header-row ? ColorPalette.list_view_normal_header_color : (touch-area.has-hover ? (r.selected_row ? ColorPalette.list-view-normal-selected-header : ColorPalette.list_view_normal_color) : (r.selected_row ? ColorPalette.list-view-normal-selected-header: ColorPalette.list_view_normal_color));