mirror of https://github.com/qarmin/czkawka
Excluded extensions and krokiet new features (#1184)
* AVC * Import split * Default thread size * Hen * Allowed extensions * Perf * Connect * Excluded * Zmiany * Optimization * 4.10 * At once * Included * Chang * VD * VD * Hashes * Wersja * SD * Up * Up * 2024 * Dup * Slint files * Added select * Selections * Fix * LTO * Actions * Added popup delete * AB * V4 * Release * LTO * Basic moving * Commonsy * Moving probably works * Popup movepull/1209/head
parent
b63c631e14
commit
378fa1fd6e
File diff suppressed because it is too large
Load Diff
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
@ -0,0 +1,122 @@
|
||||
use crate::common::{get_is_header_mode, get_tool_model, set_tool_model};
|
||||
use crate::model_operations::{collect_path_name_from_model, deselect_all_items, filter_out_checked_items};
|
||||
use crate::{Callabler, CurrentTab, GuiState, MainListModel, MainWindow};
|
||||
|
||||
use czkawka_core::common_messages::Messages;
|
||||
use rayon::prelude::*;
|
||||
use rfd::FileDialog;
|
||||
use slint::{ComponentHandle, ModelRc, VecModel};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{fs, path};
|
||||
|
||||
pub fn connect_move(app: &MainWindow) {
|
||||
let a = app.as_weak();
|
||||
app.on_folders_move_choose_requested(move || {
|
||||
let app = a.upgrade().unwrap();
|
||||
|
||||
let file_dialog = FileDialog::new();
|
||||
let Some(folder) = file_dialog.pick_folder() else {
|
||||
return;
|
||||
};
|
||||
let folder_str = folder.to_string_lossy().to_string();
|
||||
|
||||
app.invoke_show_move_folders_dialog(folder_str.into());
|
||||
});
|
||||
|
||||
let a = app.as_weak();
|
||||
app.global::<Callabler>().on_move_items(move |preserve_structure, copy_mode, output_folder| {
|
||||
let app = a.upgrade().unwrap();
|
||||
let active_tab = app.global::<GuiState>().get_active_tab();
|
||||
let current_model = get_tool_model(&app, active_tab);
|
||||
|
||||
let (errors, new_model) = move_operation(¤t_model, preserve_structure, copy_mode, &output_folder, active_tab);
|
||||
if let Some(new_model) = new_model {
|
||||
set_tool_model(&app, active_tab, new_model);
|
||||
}
|
||||
app.global::<GuiState>().set_info_text(Messages::new_from_errors(errors).create_messages_text().into());
|
||||
});
|
||||
}
|
||||
|
||||
fn move_operation(
|
||||
items: &ModelRc<MainListModel>,
|
||||
preserve_structure: bool,
|
||||
copy_mode: bool,
|
||||
output_folder: &str,
|
||||
active_tab: CurrentTab,
|
||||
) -> (Vec<String>, Option<ModelRc<MainListModel>>) {
|
||||
let (entries_to_move, mut entries_left) = filter_out_checked_items(items, get_is_header_mode(active_tab));
|
||||
|
||||
if !entries_to_move.is_empty() {
|
||||
let vec_items_to_move = collect_path_name_from_model(&entries_to_move, active_tab);
|
||||
let errors = move_selected_items(vec_items_to_move, preserve_structure, copy_mode, output_folder);
|
||||
deselect_all_items(&mut entries_left);
|
||||
|
||||
let r = ModelRc::new(VecModel::from(entries_left));
|
||||
return (errors, Some(r));
|
||||
}
|
||||
(vec![], None)
|
||||
}
|
||||
|
||||
fn move_selected_items(items_to_move: Vec<(String, String)>, preserve_structure: bool, copy_mode: bool, output_folder: &str) -> Vec<String> {
|
||||
if let Err(err) = fs::create_dir_all(output_folder) {
|
||||
return vec![format!("Error while creating folder: {err}")];
|
||||
}
|
||||
|
||||
if copy_mode {
|
||||
items_to_move
|
||||
.into_par_iter()
|
||||
.filter_map(|(path, name)| {
|
||||
let (input_file, output_file) = collect_path_and_create_folders(&path, &name, output_folder, preserve_structure);
|
||||
|
||||
if let Err(e) = fs::copy(&input_file, &output_file) {
|
||||
return Some(format!("Error while copying file {input_file:?} to {output_file:?}, reason {e}"));
|
||||
}
|
||||
None
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
items_to_move
|
||||
.into_par_iter()
|
||||
.filter_map(|(path, name)| {
|
||||
let (input_file, output_file) = collect_path_and_create_folders(&path, &name, output_folder, preserve_structure);
|
||||
|
||||
if output_file.exists() {
|
||||
return Some(format!("File {output_file:?} already exists"));
|
||||
}
|
||||
|
||||
// Try to rename file, may fail due various reasons
|
||||
if fs::rename(&input_file, &output_file).is_ok() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// It is possible that this failed, because file is on different partition, so
|
||||
// we need to copy file and then remove old
|
||||
if let Err(e) = fs::copy(&input_file, &output_file) {
|
||||
return Some(format!(
|
||||
"Error while copying file {input_file:?} to {output_file:?}(moving into different partition), reason {e}"
|
||||
));
|
||||
}
|
||||
if let Err(e) = fs::remove_file(&input_file) {
|
||||
return Some(format!("Error while removing file {input_file:?}(after copying into different partition), reason {e}"));
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
// Create input/output paths, and create output folder
|
||||
fn collect_path_and_create_folders(input_path: &str, input_file: &str, output_path: &str, preserve_structure: bool) -> (PathBuf, PathBuf) {
|
||||
let mut input_full_path = PathBuf::from(input_path);
|
||||
input_full_path.push(input_file);
|
||||
|
||||
let mut output_full_path = PathBuf::from(output_path);
|
||||
if preserve_structure {
|
||||
output_full_path.extend(Path::new(input_path).components().filter(|c| matches!(c, path::Component::Normal(_))));
|
||||
};
|
||||
let _ = fs::create_dir_all(&output_full_path);
|
||||
output_full_path.push(input_file);
|
||||
|
||||
(input_full_path, output_full_path)
|
||||
}
|
@ -0,0 +1,216 @@
|
||||
use crate::common::{
|
||||
connect_i32_into_u64, get_int_height_idx, get_int_modification_date_idx, get_int_size_idx, get_int_width_idx, get_is_header_mode, get_tool_model, set_tool_model,
|
||||
};
|
||||
use crate::{Callabler, CurrentTab, GuiState, MainListModel, MainWindow, SelectMode, 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
|
||||
// https://github.com/slint-ui/slint/discussions/4595
|
||||
pub fn connect_select(app: &MainWindow) {
|
||||
let a = app.as_weak();
|
||||
app.global::<Callabler>().on_select_items(move |select_mode| {
|
||||
let app = a.upgrade().unwrap();
|
||||
let active_tab = app.global::<GuiState>().get_active_tab();
|
||||
let current_model = get_tool_model(&app, active_tab);
|
||||
|
||||
let new_model = match select_mode {
|
||||
SelectMode::SelectAll => select_all(current_model),
|
||||
SelectMode::UnselectAll => deselect_all(current_model),
|
||||
SelectMode::InvertSelection => invert_selection(current_model),
|
||||
SelectMode::SelectTheBiggestSize => select_by_size_date(current_model, active_tab, true, true),
|
||||
SelectMode::SelectTheSmallestSize => select_by_size_date(current_model, active_tab, false, true),
|
||||
SelectMode::SelectTheBiggestResolution => select_by_resolution(current_model, active_tab, true),
|
||||
SelectMode::SelectTheSmallestResolution => select_by_resolution(current_model, active_tab, false),
|
||||
SelectMode::SelectNewest => select_by_size_date(current_model, active_tab, true, false),
|
||||
SelectMode::SelectOldest => select_by_size_date(current_model, active_tab, false, false),
|
||||
};
|
||||
set_tool_model(&app, active_tab, new_model);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn connect_showing_proper_select_buttons(app: &MainWindow) {
|
||||
set_select_buttons(app);
|
||||
let a = app.as_weak();
|
||||
app.global::<Callabler>().on_tab_changed(move || {
|
||||
let app = a.upgrade().unwrap();
|
||||
set_select_buttons(&app);
|
||||
});
|
||||
}
|
||||
|
||||
fn set_select_buttons(app: &MainWindow) {
|
||||
let active_tab = app.global::<GuiState>().get_active_tab();
|
||||
let mut base_buttons = vec![SelectMode::SelectAll, SelectMode::UnselectAll, SelectMode::InvertSelection];
|
||||
|
||||
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()
|
||||
.map(|e| SelectModel {
|
||||
name: translate_select_mode(e).into(),
|
||||
data: e,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
app.global::<GuiState>().set_select_results_list(ModelRc::new(VecModel::from(new_select_model)));
|
||||
}
|
||||
|
||||
fn translate_select_mode(select_mode: SelectMode) -> String {
|
||||
match select_mode {
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO, when model will be able to contain i64 instead two i32, this function could be merged with select_by_size_date
|
||||
fn select_by_resolution(model: ModelRc<MainListModel>, active_tab: CurrentTab, biggest: bool) -> ModelRc<MainListModel> {
|
||||
let is_header_mode = get_is_header_mode(active_tab);
|
||||
assert!(is_header_mode); // non header modes not really have reason 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 width_idx = get_int_width_idx(active_tab);
|
||||
let height_idx = get_int_height_idx(active_tab);
|
||||
|
||||
if biggest {
|
||||
for i in 0..(headers_idx.len() - 1) {
|
||||
let mut max_item = 0;
|
||||
let mut max_item_idx = 0;
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
for j in (headers_idx[i] + 1)..headers_idx[i + 1] {
|
||||
let int_data = old_data[j].val_int.iter().collect::<Vec<_>>();
|
||||
let item = int_data[width_idx] * int_data[height_idx];
|
||||
if item > max_item {
|
||||
max_item = item;
|
||||
max_item_idx = j;
|
||||
}
|
||||
}
|
||||
old_data[max_item_idx].checked = true;
|
||||
}
|
||||
} else {
|
||||
for i in 0..(headers_idx.len() - 1) {
|
||||
let mut min_item = u64::MAX;
|
||||
let mut min_item_idx = 0;
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
for j in (headers_idx[i] + 1)..headers_idx[i + 1] {
|
||||
let int_data = old_data[j].val_int.iter().collect::<Vec<_>>();
|
||||
let item = (int_data[width_idx] * int_data[height_idx]) as u64;
|
||||
if item < min_item {
|
||||
min_item = item;
|
||||
min_item_idx = j;
|
||||
}
|
||||
}
|
||||
old_data[min_item_idx].checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
ModelRc::new(VecModel::from(old_data))
|
||||
}
|
||||
|
||||
fn select_by_size_date(model: ModelRc<MainListModel>, active_tab: CurrentTab, biggest_newest: bool, size: bool) -> ModelRc<MainListModel> {
|
||||
let is_header_mode = get_is_header_mode(active_tab);
|
||||
assert!(is_header_mode); // non header modes not really have reason 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 item_idx = if size {
|
||||
get_int_size_idx(active_tab)
|
||||
} else {
|
||||
get_int_modification_date_idx(active_tab)
|
||||
};
|
||||
|
||||
if biggest_newest {
|
||||
for i in 0..(headers_idx.len() - 1) {
|
||||
let mut max_item = 0;
|
||||
let mut max_item_idx = 0;
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
for j in (headers_idx[i] + 1)..headers_idx[i + 1] {
|
||||
let int_data = old_data[j].val_int.iter().collect::<Vec<_>>();
|
||||
let item = connect_i32_into_u64(int_data[item_idx], int_data[item_idx + 1]);
|
||||
if item > max_item {
|
||||
max_item = item;
|
||||
max_item_idx = j;
|
||||
}
|
||||
}
|
||||
old_data[max_item_idx].checked = true;
|
||||
}
|
||||
} else {
|
||||
for i in 0..(headers_idx.len() - 1) {
|
||||
let mut min_item = u64::MAX;
|
||||
let mut min_item_idx = 0;
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
for j in (headers_idx[i] + 1)..headers_idx[i + 1] {
|
||||
let int_data = old_data[j].val_int.iter().collect::<Vec<_>>();
|
||||
let item = connect_i32_into_u64(int_data[item_idx], int_data[item_idx + 1]);
|
||||
if item < min_item {
|
||||
min_item = item;
|
||||
min_item_idx = j;
|
||||
}
|
||||
}
|
||||
old_data[min_item_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<_>>();
|
||||
for x in &mut old_data {
|
||||
if !x.header_row {
|
||||
x.checked = true;
|
||||
}
|
||||
}
|
||||
ModelRc::new(VecModel::from(old_data))
|
||||
}
|
||||
|
||||
fn deselect_all(model: ModelRc<MainListModel>) -> ModelRc<MainListModel> {
|
||||
let mut old_data = model.iter().collect::<Vec<_>>();
|
||||
old_data.iter_mut().for_each(|x| x.checked = false);
|
||||
ModelRc::new(VecModel::from(old_data))
|
||||
}
|
||||
|
||||
fn invert_selection(model: ModelRc<MainListModel>) -> ModelRc<MainListModel> {
|
||||
let mut old_data = model.iter().collect::<Vec<_>>();
|
||||
for x in &mut old_data {
|
||||
if !x.header_row {
|
||||
x.checked = !x.checked;
|
||||
}
|
||||
}
|
||||
ModelRc::new(VecModel::from(old_data))
|
||||
}
|
||||
|
||||
fn find_header_idx_and_deselect_all(old_data: &mut [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());
|
||||
|
||||
for x in old_data.iter_mut() {
|
||||
if !x.header_row {
|
||||
x.checked = false;
|
||||
}
|
||||
}
|
||||
header_idx
|
||||
}
|
@ -0,0 +1,238 @@
|
||||
use crate::common::{get_str_name_idx, get_str_path_idx};
|
||||
use crate::{CurrentTab, MainListModel};
|
||||
use slint::{Model, ModelRc};
|
||||
use std::path::MAIN_SEPARATOR;
|
||||
|
||||
pub fn deselect_all_items(items: &mut [MainListModel]) {
|
||||
for item in items {
|
||||
item.selected_row = false;
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn select_all_items(items: &mut [MainListModel]) {
|
||||
for item in items {
|
||||
item.selected_row = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn collect_full_path_from_model(items: &[MainListModel], active_tab: CurrentTab) -> Vec<String> {
|
||||
let path_idx = get_str_path_idx(active_tab);
|
||||
let name_idx = get_str_name_idx(active_tab);
|
||||
items
|
||||
.iter()
|
||||
.map(|item| {
|
||||
let path = item.val_str.iter().nth(path_idx).unwrap();
|
||||
let name = item.val_str.iter().nth(name_idx).unwrap();
|
||||
format!("{}{}{}", path, MAIN_SEPARATOR, name)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
pub fn collect_path_name_from_model(items: &[MainListModel], active_tab: CurrentTab) -> Vec<(String, String)> {
|
||||
let path_idx = get_str_path_idx(active_tab);
|
||||
let name_idx = get_str_name_idx(active_tab);
|
||||
items
|
||||
.iter()
|
||||
.map(|item| {
|
||||
dbg!(item.val_str.iter().nth(path_idx).unwrap().to_string());
|
||||
dbg!(item.val_str.iter().nth(name_idx).unwrap().to_string());
|
||||
(
|
||||
item.val_str.iter().nth(path_idx).unwrap().to_string(),
|
||||
item.val_str.iter().nth(name_idx).unwrap().to_string(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
pub 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(|item| item.checked);
|
||||
|
||||
// When have header, we must also throw out orphaned items - this needs to be
|
||||
if have_header && !entries_left.is_empty() {
|
||||
// First row must be header
|
||||
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].header_row || entries_left[2].header_row {
|
||||
entries_left = Vec::new();
|
||||
}
|
||||
} else if entries_left.len() < 3 {
|
||||
// Not have enough items to fill model
|
||||
entries_left = Vec::new();
|
||||
} else {
|
||||
let mut last_header = 0;
|
||||
let mut new_items: Vec<MainListModel> = Vec::new();
|
||||
for i in 1..entries_left.len() {
|
||||
if entries_left[i].header_row {
|
||||
if i - last_header > 2 {
|
||||
new_items.extend(entries_left[last_header..i].iter().cloned());
|
||||
}
|
||||
last_header = i;
|
||||
}
|
||||
}
|
||||
if entries_left.len() - last_header > 2 {
|
||||
new_items.extend(entries_left[last_header..].iter().cloned());
|
||||
}
|
||||
|
||||
entries_left = new_items;
|
||||
}
|
||||
}
|
||||
|
||||
(entries_to_delete, entries_left)
|
||||
}
|
||||
|
||||
// Function to verify if really headers are not checked
|
||||
// Checked header is big bug
|
||||
fn check_if_header_is_checked(items: &ModelRc<MainListModel>) {
|
||||
if cfg!(debug_assertions) {
|
||||
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
|
||||
fn check_if_header_is_selected_but_should_not_be(items: &ModelRc<MainListModel>, can_have_header: bool) {
|
||||
if cfg!(debug_assertions) {
|
||||
if !can_have_header {
|
||||
for item in items.iter() {
|
||||
assert!(!item.header_row);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use slint::{Model, ModelRc, SharedString, VecModel};
|
||||
|
||||
use crate::model_operations::filter_out_checked_items;
|
||||
use crate::MainListModel;
|
||||
|
||||
#[test]
|
||||
fn test_filter_out_checked_items_empty() {
|
||||
let items: ModelRc<MainListModel> = create_new_model(vec![]);
|
||||
|
||||
let (to_delete, left) = filter_out_checked_items(&items, false);
|
||||
assert!(to_delete.is_empty());
|
||||
assert!(left.is_empty());
|
||||
let (to_delete, left) = filter_out_checked_items(&items, true);
|
||||
assert!(to_delete.is_empty());
|
||||
assert!(left.is_empty());
|
||||
}
|
||||
#[test]
|
||||
fn test_filter_out_checked_items_one_element_valid_normal() {
|
||||
let items = create_new_model(vec![(false, false, false, vec![])]);
|
||||
let (to_delete, left) = filter_out_checked_items(&items, false);
|
||||
assert!(to_delete.is_empty());
|
||||
assert_eq!(left.len(), items.iter().count());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filter_out_checked_items_one_element_valid_header() {
|
||||
let items = create_new_model(vec![(false, true, false, vec![])]);
|
||||
let (to_delete, left) = filter_out_checked_items(&items, true);
|
||||
assert!(to_delete.is_empty());
|
||||
assert!(left.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_filter_out_checked_items_one_element_invalid_normal() {
|
||||
let items = create_new_model(vec![(false, true, false, vec![])]);
|
||||
filter_out_checked_items(&items, false);
|
||||
}
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_filter_out_checked_items_one_element_invalid_header() {
|
||||
let items = create_new_model(vec![(false, false, false, vec![])]);
|
||||
filter_out_checked_items(&items, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filter_out_checked_items_multiple_element_valid_normal() {
|
||||
let items = create_new_model(vec![
|
||||
(false, false, false, vec!["1"]),
|
||||
(false, false, false, vec!["2"]),
|
||||
(true, false, false, vec!["3"]),
|
||||
(true, false, false, vec!["4"]),
|
||||
(false, false, false, vec!["5"]),
|
||||
]);
|
||||
let (to_delete, left) = filter_out_checked_items(&items, false);
|
||||
let to_delete_data = get_single_data_str_from_model(&to_delete);
|
||||
let left_data = get_single_data_str_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 items = create_new_model(vec![
|
||||
(false, true, false, vec!["1"]),
|
||||
(false, false, false, vec!["2"]),
|
||||
(true, false, false, vec!["3"]),
|
||||
(false, true, false, vec!["4"]),
|
||||
(false, false, false, vec!["5"]),
|
||||
(false, true, false, vec!["6"]),
|
||||
(false, false, false, vec!["7"]),
|
||||
(false, false, false, vec!["8"]),
|
||||
]);
|
||||
let (to_delete, left) = filter_out_checked_items(&items, true);
|
||||
let to_delete_data = get_single_data_str_from_model(&to_delete);
|
||||
let left_data = get_single_data_str_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 items = create_new_model(vec![
|
||||
(false, true, false, vec!["1"]),
|
||||
(false, false, false, vec!["2"]),
|
||||
(true, false, false, vec!["3"]),
|
||||
(false, false, false, vec!["4"]),
|
||||
(false, false, false, vec!["5"]),
|
||||
(false, false, false, vec!["6"]),
|
||||
(false, true, false, vec!["7"]),
|
||||
(false, false, false, vec!["8"]),
|
||||
]);
|
||||
let (to_delete, left) = filter_out_checked_items(&items, true);
|
||||
let to_delete_data = get_single_data_str_from_model(&to_delete);
|
||||
let left_data = get_single_data_str_from_model(&left);
|
||||
|
||||
assert_eq!(to_delete_data, vec!["3"]);
|
||||
assert_eq!(left_data, vec!["1", "2", "4", "5", "6"]);
|
||||
}
|
||||
|
||||
fn get_single_data_str_from_model(model: &[MainListModel]) -> Vec<String> {
|
||||
let mut d = model.iter().map(|item| item.val_str.iter().next().unwrap().to_string()).collect::<Vec<_>>();
|
||||
d.sort();
|
||||
d
|
||||
}
|
||||
|
||||
fn create_new_model(items: Vec<(bool, bool, bool, Vec<&'static str>)>) -> ModelRc<MainListModel> {
|
||||
let model = VecModel::default();
|
||||
for item in items {
|
||||
let all_items: Vec<SharedString> = item.3.iter().map(|item| (*item).into()).collect::<Vec<_>>();
|
||||
let all_items = VecModel::from(all_items);
|
||||
model.push(MainListModel {
|
||||
checked: item.0,
|
||||
header_row: item.1,
|
||||
selected_row: item.2,
|
||||
val_str: ModelRc::new(all_items),
|
||||
val_int: ModelRc::new(VecModel::default()),
|
||||
});
|
||||
}
|
||||
ModelRc::new(model)
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
import { Button, VerticalBox ,TextEdit, HorizontalBox, TabWidget, ListView, StandardListView, StandardTableView, CheckBox, LineEdit} from "std-widgets.slint";
|
||||
import {SelectableTableView} from "selectable_tree_view.slint";
|
||||
import {LeftSidePanel} from "left_side_panel.slint";
|
||||
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, SelectMode, SelectModel} from "common.slint";
|
||||
import {Settings} from "settings.slint";
|
||||
import {Callabler} from "callabler.slint";
|
||||
import { BottomPanel } from "bottom_panel.slint";
|
||||
import {ColorPalette} from "color_palette.slint";
|
||||
import {GuiState} from "gui_state.slint";
|
||||
import { Preview } from "preview.slint";
|
||||
|
||||
export component PopupDelete inherits Rectangle {
|
||||
out property <length> popup_width: 350px;
|
||||
out property <length> popup_height: 150px;
|
||||
callback show_popup();
|
||||
|
||||
popup_window := PopupWindow {
|
||||
width: popup_width;
|
||||
height: popup_height;
|
||||
|
||||
close-on-click: true;
|
||||
Rectangle {
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
border-radius: 5px;
|
||||
background: ColorPalette.popup_background;
|
||||
VerticalLayout {
|
||||
Text {
|
||||
vertical-stretch: 0.0;
|
||||
text: "Delete items";
|
||||
vertical-alignment: center;
|
||||
horizontal-alignment: center;
|
||||
font-size: 13px;
|
||||
padding: 10px;
|
||||
}
|
||||
Text {
|
||||
vertical-stretch: 1.0;
|
||||
text: "Are you sure you want to delete the selected items?";
|
||||
vertical-alignment: center;
|
||||
horizontal-alignment: center;
|
||||
font-size: 13px;
|
||||
padding: 10px;
|
||||
}
|
||||
HorizontalLayout {
|
||||
Button {
|
||||
text: "Yes";
|
||||
clicked => {
|
||||
popup_window.close();
|
||||
Callabler.delete_selected_items();
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
|
||||
}
|
||||
Button {
|
||||
text: "No";
|
||||
clicked => {
|
||||
popup_window.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
show_popup() => {
|
||||
popup_window.show();
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
import { Button, VerticalBox ,TextEdit, HorizontalBox, TabWidget, ListView, StandardListView, StandardTableView, CheckBox, LineEdit} from "std-widgets.slint";
|
||||
import {SelectableTableView} from "selectable_tree_view.slint";
|
||||
import {LeftSidePanel} from "left_side_panel.slint";
|
||||
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, SelectMode, SelectModel} from "common.slint";
|
||||
import {Settings} from "settings.slint";
|
||||
import {Callabler} from "callabler.slint";
|
||||
import { BottomPanel } from "bottom_panel.slint";
|
||||
import {ColorPalette} from "color_palette.slint";
|
||||
import {GuiState} from "gui_state.slint";
|
||||
import { Preview } from "preview.slint";
|
||||
|
||||
export component PopupMoveFolders inherits Rectangle {
|
||||
out property <length> popup_width: 500px;
|
||||
out property <length> popup_height: 150px;
|
||||
in-out property <string> folder_name: "";
|
||||
callback show_popup();
|
||||
|
||||
popup_window := PopupWindow {
|
||||
width: popup_width;
|
||||
height: popup_height;
|
||||
|
||||
close-on-click: false;
|
||||
Rectangle {
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
border-radius: 5px;
|
||||
background: ColorPalette.popup_background;
|
||||
VerticalLayout {
|
||||
Text {
|
||||
vertical-stretch: 0.0;
|
||||
min-height: 30px;
|
||||
text: "Moving files";
|
||||
vertical-alignment: top;
|
||||
horizontal-alignment: center;
|
||||
font-size: 13px;
|
||||
}
|
||||
Text {
|
||||
vertical-stretch: 1.0;
|
||||
text: "Moving entries to folder\n" + folder_name + "\nAre you want to continue?";
|
||||
vertical-alignment: center;
|
||||
horizontal-alignment: center;
|
||||
font-size: 13px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
HorizontalLayout {
|
||||
alignment: center;
|
||||
copy_checkbox := CheckBox {
|
||||
text: "Copy files instead of moving";
|
||||
}
|
||||
}
|
||||
HorizontalLayout {
|
||||
alignment: center;
|
||||
preserve_folder_checkbox := CheckBox {
|
||||
text: "Preserve folder structure";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
Button {
|
||||
text: "Yes";
|
||||
clicked => {
|
||||
popup_window.close();
|
||||
Callabler.move_items(preserve_folder_checkbox.checked, copy_checkbox.checked, folder_name);
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
|
||||
}
|
||||
Button {
|
||||
text: "No";
|
||||
clicked => {
|
||||
popup_window.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init => {
|
||||
show_popup();
|
||||
}
|
||||
|
||||
show_popup() => {
|
||||
popup_window.show();
|
||||
}
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
import { Button, VerticalBox ,TextEdit, HorizontalBox, TabWidget, ListView, StandardListView, StandardTableView, CheckBox, LineEdit} from "std-widgets.slint";
|
||||
import {SelectableTableView} from "selectable_tree_view.slint";
|
||||
import {LeftSidePanel} from "left_side_panel.slint";
|
||||
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";
|
||||
import {Settings} from "settings.slint";
|
||||
import {Callabler} from "callabler.slint";
|
||||
import { BottomPanel } from "bottom_panel.slint";
|
||||
import {ColorPalette} from "color_palette.slint";
|
||||
import {GuiState} from "gui_state.slint";
|
||||
import { Preview } from "preview.slint";
|
||||
|
||||
export component PopupSelect inherits Rectangle {
|
||||
callback show_popup();
|
||||
|
||||
popup_window := PopupWindow {
|
||||
width: root.width;
|
||||
height: root.height;
|
||||
|
||||
property <bool> included_directories;
|
||||
private property <string> text_data;
|
||||
close-on-click: false;
|
||||
HorizontalLayout {
|
||||
alignment: LayoutAlignment.center;
|
||||
VerticalLayout {
|
||||
alignment: LayoutAlignment.center;
|
||||
Rectangle {
|
||||
clip: true;
|
||||
width: root.width - 20px;
|
||||
height: root.height - 20px;
|
||||
border-radius: 20px;
|
||||
background: ColorPalette.popup_background;
|
||||
VerticalLayout {
|
||||
Text {
|
||||
text: "Please add directories one per line";
|
||||
horizontal-alignment: TextHorizontalAlignment.center;
|
||||
}
|
||||
|
||||
TextEdit {
|
||||
vertical-stretch: 1.0;
|
||||
text <=> text-data;
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
min-height: 20px;
|
||||
Button {
|
||||
enabled: text-data != "";
|
||||
text: "OK";
|
||||
clicked => {
|
||||
Callabler.added_manual_directories(GuiState.choosing_include_directories, text_data);
|
||||
popup_window.close();
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Cancel";
|
||||
clicked => {
|
||||
popup_window.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
show_popup() => {
|
||||
popup_window.show();
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
import { Button, VerticalBox ,TextEdit, HorizontalBox, TabWidget, ListView, StandardListView, StandardTableView, CheckBox, LineEdit} from "std-widgets.slint";
|
||||
import {SelectableTableView} from "selectable_tree_view.slint";
|
||||
import {LeftSidePanel} from "left_side_panel.slint";
|
||||
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, SelectMode, SelectModel} from "common.slint";
|
||||
import {Settings} from "settings.slint";
|
||||
import {Callabler} from "callabler.slint";
|
||||
import { BottomPanel } from "bottom_panel.slint";
|
||||
import {ColorPalette} from "color_palette.slint";
|
||||
import {GuiState} from "gui_state.slint";
|
||||
import { Preview } from "preview.slint";
|
||||
|
||||
export component PopupSelectResults inherits Rectangle {
|
||||
callback show_popup();
|
||||
property <[SelectModel]> model: GuiState.select_results_list;
|
||||
property <length> item_height: 30px;
|
||||
out property <length> item_width: 200px;
|
||||
out property <length> all_items_height: item_height * model.length;
|
||||
|
||||
popup_window := PopupWindow {
|
||||
width: item_width;
|
||||
height: all_items_height;
|
||||
|
||||
close-on-click: true;
|
||||
Rectangle {
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
border-radius: 5px;
|
||||
background: ColorPalette.popup_background;
|
||||
VerticalLayout {
|
||||
for i in model: Button {
|
||||
text: i.name;
|
||||
height: item_height;
|
||||
width: item_width;
|
||||
|
||||
clicked => {
|
||||
Callabler.select_items(i.data);
|
||||
popup_window.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
show_popup() => {
|
||||
popup_window.show();
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue