Browse Source

Show how many files will be deleted/moved (#514)

* Print proper character(\) in Windows when printing files

* Show how many files will be moved/deleted.
Don't show dialog when 0 files is selected
pull/515/head
Rafał Mikrut 7 months ago committed by GitHub
parent
commit
878550446d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      Changelog.md
  2. 68
      czkawka_gui/src/connect_button_delete.rs
  3. 2
      czkawka_gui/src/connect_button_hardlink.rs
  4. 8
      czkawka_gui/src/connect_button_move.rs
  5. 7
      czkawka_gui/src/connect_popovers.rs
  6. 61
      czkawka_gui/src/help_functions.rs
  7. 2
      czkawka_gui/src/initialize_gui.rs
  8. 6
      czkawka_gui/src/opening_selecting_records.rs
  9. 3
      i18n/en/czkawka_gui.ftl
  10. 2
      i18n/pl/czkawka_gui.ftl
  11. 29
      instructions/Translations.md

9
Changelog.md

@ -1,10 +1,12 @@
## Version 4.0.0 - ?
- Multithread support for collecting files to check(2/3x on 4 thread processor) - [#502](https://github.com/qarmin/czkawka/pull/502), [#504](https://github.com/qarmin/czkawka/pull/504)
- Add Polish and Italian translation - [#469](https://github.com/qarmin/czkawka/pull/469), [#508](https://github.com/qarmin/czkawka/pull/508)
- Add support for finding similar videos - [#460](https://github.com/qarmin/czkawka/pull/460)
- GUI code refactoring(could fix some bugs) - [#462](https://github.com/qarmin/czkawka/pull/462)
- Fixed crash when trying to hard/symlink 0 files - [#462](https://github.com/qarmin/czkawka/pull/462)
- GTK 4 compatibility improvements for future change of toolkit - [#467](https://github.com/qarmin/czkawka/pull/467), [#468](https://github.com/qarmin/czkawka/pull/468), [#473](https://github.com/qarmin/czkawka/pull/473), [#474](https://github.com/qarmin/czkawka/pull/474)
- GTK 4 compatibility improvements for future change of toolkit - [#467](https://github.com/qarmin/czkawka/pull/467), [#468](https://github.com/qarmin/czkawka/pull/468), [#473](https://github.com/qarmin/czkawka/pull/473), [#474](https://github.com/qarmin/czkawka/pull/474), [#503](https://github.com/qarmin/czkawka/pull/503), [#505](https://github.com/qarmin/czkawka/pull/505)
- Change minimal supported OS to Ubuntu 20.04(needed by GTK) - [#468](https://github.com/qarmin/czkawka/pull/468)
- Increased performance when using image previews - [#468](https://github.com/qarmin/czkawka/pull/468)
- Increased performance by avoiding creating unnecessary image previews - [#468](https://github.com/qarmin/czkawka/pull/468)
- Improved performance due caching hash of broken/not supported images/videos = [#471](https://github.com/qarmin/czkawka/pull/471)
- Option to not remove cache from non existent files(e.g. from unplugged pendrive) - [#472](https://github.com/qarmin/czkawka/pull/472)
- Add multiple tooltips with helpful messages - [#472](https://github.com/qarmin/czkawka/pull/472)
@ -13,6 +15,9 @@
- Remove support for finding zeroed files - [#461](https://github.com/qarmin/czkawka/pull/461)
- Remove HashMB mode - [#476](https://github.com/qarmin/czkawka/pull/476)
- Approximate comparison of music - [#483](https://github.com/qarmin/czkawka/pull/483)
- Enable column sorting for simple treeview - [#487](https://github.com/qarmin/czkawka/pull/487)
- Allow to hide upper panel - [#491](https://github.com/qarmin/czkawka/pull/491)
- Make UI take less space - [#500](https://github.com/qarmin/czkawka/pull/500)
## Version 3.3.1 - 22.11.2021r
- Fix crash when moving buttons [#457](https://github.com/qarmin/czkawka/pull/457)

68
czkawka_gui/src/connect_button_delete.rs

@ -41,31 +41,31 @@ pub async fn delete_things(gui_data: GuiData) {
let preview_path = gui_data.preview_path.clone();
let text_view_errors = gui_data.text_view_errors.clone();
if !check_if_can_delete_files(&check_button_settings_confirm_deletion, &window_main).await {
return;
}
let nb_number = notebook_main.current_page().unwrap();
let tree_view = &main_tree_views[nb_number as usize];
let nb_object = &NOTEBOOKS_INFOS[nb_number as usize];
let (number_of_selected_items, number_of_selected_groups) = check_how_much_elements_is_selected(tree_view, nb_object.column_color, nb_object.column_selection);
// Nothing is selected
if number_of_selected_items == 0 {
return;
}
if !check_if_can_delete_files(&check_button_settings_confirm_deletion, &window_main, number_of_selected_items, number_of_selected_groups).await {
return;
}
if let Some(column_color) = nb_object.column_color {
if !check_button_settings_confirm_group_deletion.is_active() || !check_if_deleting_all_files_in_group(tree_view, column_color, nb_object.column_selection, &window_main, &check_button_settings_confirm_group_deletion).await {
tree_remove(
&tree_view.clone(),
nb_object.column_name,
nb_object.column_path,
column_color,
nb_object.column_selection,
&check_button_settings_use_trash,
&text_view_errors,
);
tree_remove(tree_view, nb_object.column_name, nb_object.column_path, column_color, nb_object.column_selection, &check_button_settings_use_trash, &text_view_errors);
}
} else {
if nb_number == NotebookMainEnum::EmptyDirectories as u32 {
empty_folder_remover(&tree_view.clone(), nb_object.column_name, nb_object.column_path, nb_object.column_selection, &check_button_settings_use_trash, &text_view_errors);
empty_folder_remover(tree_view, nb_object.column_name, nb_object.column_path, nb_object.column_selection, &check_button_settings_use_trash, &text_view_errors);
} else {
basic_remove(&tree_view.clone(), nb_object.column_name, nb_object.column_path, nb_object.column_selection, &check_button_settings_use_trash, &text_view_errors);
basic_remove(tree_view, nb_object.column_name, nb_object.column_path, nb_object.column_selection, &check_button_settings_use_trash, &text_view_errors);
}
}
@ -82,9 +82,9 @@ pub async fn delete_things(gui_data: GuiData) {
}
}
pub async fn check_if_can_delete_files(check_button_settings_confirm_deletion: &gtk::CheckButton, window_main: &gtk::Window) -> bool {
pub async fn check_if_can_delete_files(check_button_settings_confirm_deletion: &gtk::CheckButton, window_main: &gtk::Window, number_of_selected_items: u64, number_of_selected_groups: u64) -> bool {
if check_button_settings_confirm_deletion.is_active() {
let (confirmation_dialog_delete, check_button) = create_dialog_ask_for_deletion(window_main);
let (confirmation_dialog_delete, check_button) = create_dialog_ask_for_deletion(window_main, number_of_selected_items, number_of_selected_groups);
let response_type = confirmation_dialog_delete.run_future().await;
if response_type == gtk::ResponseType::Ok {
@ -102,12 +102,19 @@ pub async fn check_if_can_delete_files(check_button_settings_confirm_deletion: &
true
}
fn create_dialog_ask_for_deletion(window_main: &gtk::Window) -> (Dialog, CheckButton) {
fn create_dialog_ask_for_deletion(window_main: &gtk::Window, number_of_selected_items: u64, number_of_selected_groups: u64) -> (Dialog, CheckButton) {
let dialog = gtk::Dialog::builder().title(&fl!("delete_title_dialog")).transient_for(window_main).modal(true).build();
let button_ok = dialog.add_button(&fl!("general_ok_button"), ResponseType::Ok);
dialog.add_button(&fl!("general_close_button"), ResponseType::Cancel);
let label: gtk::Label = gtk::Label::new(Some(&fl!("delete_question_label")));
let label2: gtk::Label = match number_of_selected_groups {
0 => gtk::Label::new(Some(&fl!("delete_items_label", generate_translation_hashmap(vec![("items", number_of_selected_items.to_string())])))),
_ => gtk::Label::new(Some(&fl!(
"delete_items_groups_label",
generate_translation_hashmap(vec![("items", number_of_selected_items.to_string()), ("groups", number_of_selected_groups.to_string())])
))),
};
let check_button: gtk::CheckButton = gtk::CheckButton::with_label(&fl!("dialogs_ask_next_time"));
check_button.set_active(true);
check_button.set_halign(Align::Center);
@ -116,7 +123,10 @@ fn create_dialog_ask_for_deletion(window_main: &gtk::Window) -> (Dialog, CheckBu
let internal_box = get_dialog_box_child(&dialog);
internal_box.add(&label);
internal_box.add(&label2);
internal_box.add(&check_button);
internal_box.set_margin(5);
check_button.set_margin_top(5);
dialog.show_all();
(dialog, check_button)
@ -227,7 +237,7 @@ pub fn empty_folder_remover(tree_view: &gtk::TreeView, column_file_name: i32, co
// We must check if folder is really empty or contains only other empty folders
let mut error_happened = false;
let mut folders_to_check: Vec<String> = vec![format!("{}/{}", path, name)];
let mut folders_to_check: Vec<String> = vec![get_full_name_from_path_name(&path, &name)];
let mut current_folder: String;
let mut next_folder: String;
'dir: while !folders_to_check.is_empty() {
@ -275,14 +285,14 @@ pub fn empty_folder_remover(tree_view: &gtk::TreeView, column_file_name: i32, co
if !error_happened {
if !use_trash {
match fs::remove_dir_all(format!("{}/{}", path, name)) {
match fs::remove_dir_all(get_full_name_from_path_name(&path, &name)) {
Ok(_) => {
model.remove(&iter);
}
Err(_inspected) => error_happened = true,
}
} else {
match trash::delete(format!("{}/{}", path, name)) {
match trash::delete(get_full_name_from_path_name(&path, &name)) {
Ok(_) => {
model.remove(&iter);
}
@ -291,7 +301,7 @@ pub fn empty_folder_remover(tree_view: &gtk::TreeView, column_file_name: i32, co
}
}
if error_happened {
messages += &fl!("delete_folder_failed", generate_translation_hashmap(vec![("dir", format!("{}/{}", path, name))]));
messages += &fl!("delete_folder_failed", generate_translation_hashmap(vec![("dir", get_full_name_from_path_name(&path, &name))]));
messages += "\n";
}
}
@ -332,23 +342,23 @@ pub fn basic_remove(tree_view: &gtk::TreeView, column_file_name: i32, column_pat
let path = model.value(&iter, column_path).get::<String>().unwrap();
if !use_trash {
match fs::remove_file(format!("{}/{}", path, name)) {
match fs::remove_file(get_full_name_from_path_name(&path, &name)) {
Ok(_) => {
model.remove(&iter);
}
Err(e) => {
messages += fl!("delete_file_failed", generate_translation_hashmap(vec![("name", format!("{}/{}", path, name)), ("reason", e.to_string())])).as_str();
messages += fl!("delete_file_failed", generate_translation_hashmap(vec![("name", get_full_name_from_path_name(&path, &name)), ("reason", e.to_string())])).as_str();
messages += "\n";
}
}
} else {
match trash::delete(format!("{}/{}", path, name)) {
match trash::delete(get_full_name_from_path_name(&path, &name)) {
Ok(_) => {
model.remove(&iter);
}
Err(e) => {
messages += fl!("delete_file_failed", generate_translation_hashmap(vec![("name", format!("{}/{}", path, name)), ("reason", e.to_string())])).as_str();
messages += fl!("delete_file_failed", generate_translation_hashmap(vec![("name", get_full_name_from_path_name(&path, &name)), ("reason", e.to_string())])).as_str();
messages += "\n";
}
}
@ -410,12 +420,12 @@ pub fn tree_remove(tree_view: &gtk::TreeView, column_file_name: i32, column_path
vec_file_name.dedup();
for file_name in vec_file_name {
if !use_trash {
if let Err(e) = fs::remove_file(format!("{}/{}", path.clone(), file_name.clone())) {
messages += fl!("delete_file_failed", generate_translation_hashmap(vec![("name", format!("{}/{}", path, file_name)), ("reason", e.to_string())])).as_str();
if let Err(e) = fs::remove_file(get_full_name_from_path_name(&path, &file_name)) {
messages += fl!("delete_file_failed", generate_translation_hashmap(vec![("name", get_full_name_from_path_name(&path, &file_name)), ("reason", e.to_string())])).as_str();
messages += "\n";
}
} else if let Err(e) = trash::delete(format!("{}/{}", path.clone(), file_name.clone())) {
messages += fl!("delete_file_failed", generate_translation_hashmap(vec![("name", format!("{}/{}", path, file_name)), ("reason", e.to_string())])).as_str();
} else if let Err(e) = trash::delete(get_full_name_from_path_name(&path, &file_name)) {
messages += fl!("delete_file_failed", generate_translation_hashmap(vec![("name", get_full_name_from_path_name(&path, &file_name)), ("reason", e.to_string())])).as_str();
messages += "\n";
}

2
czkawka_gui/src/connect_button_hardlink.rs

@ -140,7 +140,7 @@ pub fn hardlink_symlink(tree_view: &gtk::TreeView, column_file_name: i32, column
if model.path(&current_iter).unwrap() == selected_rows[current_selected_index] {
let file_name = model.value(&current_iter, column_file_name).get::<String>().unwrap();
let path = model.value(&current_iter, column_path).get::<String>().unwrap();
let full_file_path = format!("{}/{}", path, file_name);
let full_file_path = get_full_name_from_path_name(&path, &file_name);
if current_symhardlink_data.is_some() {
vec_tree_path_to_remove.push(model.path(&current_iter).unwrap());

8
czkawka_gui/src/connect_button_move.rs

@ -31,6 +31,12 @@ pub fn connect_button_move(gui_data: &GuiData) {
let tree_view = &main_tree_views[nb_number as usize];
let nb_object = &NOTEBOOKS_INFOS[nb_number as usize];
let (number_of_selected_items, _number_of_selected_groups) = check_how_much_elements_is_selected(tree_view, nb_object.column_color, nb_object.column_selection);
// Nothing is selected
if number_of_selected_items == 0 {
return;
}
move_things(
tree_view,
nb_object.column_name,
@ -174,7 +180,7 @@ fn move_files_common(selected_rows: &[TreePath], model: &gtk::ListStore, column_
let file_name = model.value(&iter, column_file_name).get::<String>().unwrap();
let path = model.value(&iter, column_path).get::<String>().unwrap();
let thing = format!("{}/{}", path, file_name);
let thing = get_full_name_from_path_name(&path, &file_name);
let destination_file = destination_folder.join(file_name);
if Path::new(&thing).is_dir() {
if let Err(e) = fs_extra::dir::move_dir(&thing, &destination_file, &fs_extra::dir::CopyOptions::new()) {

7
czkawka_gui/src/connect_popovers.rs

@ -401,11 +401,8 @@ fn popover_custom_select_unselect(popover: &gtk::Popover, window_main: &Window,
let is_selected = model.value(&iter, column_button_selection as i32).get::<bool>().unwrap();
let path = model.value(&iter, column_path).get::<String>().unwrap();
let name = model.value(&iter, column_file_name).get::<String>().unwrap();
#[cfg(not(target_family = "windows"))]
let character = "/";
#[cfg(target_family = "windows")]
let character = "\\";
let path_and_name = format!("{}{}{}", path, character, name);
let path_and_name = get_full_name_from_path_name(&path, &name);
let mut need_to_change_thing: bool = false;

61
czkawka_gui/src/help_functions.rs

@ -2,7 +2,7 @@ use std::collections::HashMap;
use std::path::{Path, PathBuf};
use gtk::prelude::*;
use gtk::{ListStore, TextView, Widget};
use gtk::{ListStore, TextView, TreeView, Widget};
use czkawka_core::big_file::BigFile;
use czkawka_core::broken_files::BrokenFiles;
@ -19,6 +19,11 @@ use czkawka_core::{fl, invalid_symlinks};
use crate::notebook_enums::{NotebookMainEnum, NUMBER_OF_NOTEBOOK_MAIN_TABS};
#[cfg(not(target_family = "windows"))]
pub const CHARACTER: char = '/';
#[cfg(target_family = "windows")]
pub const CHARACTER: char = '\\';
pub const KEY_DELETE: u32 = 119;
pub const KEY_ENTER: u32 = 36;
pub const KEY_SPACE: u32 = 65;
@ -470,6 +475,14 @@ pub fn get_notebook_object_from_tree_view(tree_view: &gtk::TreeView) -> &Noteboo
&NOTEBOOKS_INFOS[nb_enum as usize]
}
pub fn get_full_name_from_path_name(path: &str, name: &str) -> String {
let mut string = String::with_capacity(path.len() + name.len() + 1);
string.push_str(path);
string.push(CHARACTER);
string.push_str(name);
string
}
// After e.g. deleting files, header may become orphan or have one child, so should be deleted in this case
pub fn clean_invalid_headers(model: &gtk::ListStore, column_color: i32) {
// Remove only child from header
@ -541,6 +554,52 @@ pub fn clean_invalid_headers(model: &gtk::ListStore, column_color: i32) {
}
}
}
pub fn check_how_much_elements_is_selected(tree_view: &TreeView, column_color: Option<i32>, column_selection: i32) -> (u64, u64) {
let mut number_of_selected_items: u64 = 0;
let mut number_of_selected_groups: u64 = 0;
let model = get_list_store(tree_view);
let mut is_item_currently_selected_in_group: bool = false;
// First iter
if let Some(iter) = model.iter_first() {
if let Some(column_color) = column_color {
assert_eq!(model.value(&iter, column_color).get::<String>().unwrap(), HEADER_ROW_COLOR); // First element should be header
loop {
if !model.iter_next(&iter) {
break;
}
if model.value(&iter, column_color).get::<String>().unwrap() == HEADER_ROW_COLOR {
is_item_currently_selected_in_group = false;
} else {
if model.value(&iter, column_selection).get::<bool>().unwrap() {
number_of_selected_items += 1;
if !is_item_currently_selected_in_group {
number_of_selected_groups += 1;
}
is_item_currently_selected_in_group = true;
}
}
}
} else {
loop {
if !model.iter_next(&iter) {
break;
}
if model.value(&iter, column_selection).get::<bool>().unwrap() {
number_of_selected_items += 1;
}
}
}
}
(number_of_selected_items, number_of_selected_groups)
}
pub fn get_custom_label_from_button_with_image(button: &gtk::Bin) -> gtk::Label {
let internal_box = button.child().unwrap().downcast::<gtk::Box>().unwrap();

2
czkawka_gui/src/initialize_gui.rs

@ -673,7 +673,7 @@ fn show_preview(tree_view: &TreeView, text_view_errors: &TextView, check_button_
let path = tree_model.value(&tree_model.iter(&tree_path).unwrap(), column_path).get::<String>().unwrap();
let name = tree_model.value(&tree_model.iter(&tree_path).unwrap(), column_name).get::<String>().unwrap();
let file_name = format!("{}/{}", path, name);
let file_name = get_full_name_from_path_name(&path, &name);
let file_name = file_name.as_str();
if let Some(extension) = Path::new(file_name).extension() {

6
czkawka_gui/src/opening_selecting_records.rs

@ -80,9 +80,7 @@ fn common_open_function(tree_view: &gtk::TreeView, column_name: i32, column_path
let end_path = match opening_mode {
OpenMode::OnlyPath => path,
OpenMode::PathAndName => {
format!("{}/{}", path, name)
}
OpenMode::PathAndName => get_full_name_from_path_name(&path, &name),
};
open::that_in_background(&end_path);
@ -96,11 +94,9 @@ fn common_open_function(tree_view: &gtk::TreeView, column_name: i32, column_path
fn handle_tree_keypress(tree_view: &gtk::TreeView, key_code: u32, name_column: i32, path_column: i32, mark_column: i32) {
match key_code {
KEY_ENTER => {
// Enter
common_open_function(tree_view, name_column, path_column, OpenMode::PathAndName);
}
KEY_SPACE => {
// Space
common_mark_function(tree_view, mark_column);
}
_ => {}

3
i18n/en/czkawka_gui.ftl

@ -424,6 +424,9 @@ delete_all_files_in_group_label1 = In some groups there are selected all records
delete_all_files_in_group_label2 = Are you sure that you want to delete them?
delete_folder_failed = Failed to remove folder {$dir} because folder doesn't exists, you don't have permissions or isn't empty.
delete_items_label = { $items } files will be removed.
delete_items_groups_label = { $items } files from { $groups } groups will be removed.
hardlink_failed = Failed to hardlink
hard_sym_invalid_selection_title_dialog = Invalid selection with some groups
hard_sym_invalid_selection_label_1 = In some groups there is only 1 record selected and it will be ignored.

2
i18n/pl/czkawka_gui.ftl

@ -367,6 +367,8 @@ delete_all_files_in_group_title = Potwierdzenie usunięcia wszystkich plików w
delete_all_files_in_group_label1 = W niektórych grupach zaznaczono wszystkie rekordy.
delete_all_files_in_group_label2 = Czy na pewno je usunąć?
delete_folder_failed = Nie udało się usunąć folderu { $name } ponieważ nie istnieje, uprawnienia nie są wystarczające lub nie jest pusty.
delete_items_label = { $items } plików zostanie usuniętych.
delete_items_groups_label = { $items } plików z { $groups } grup będzie usuniętych.
hardlink_failed = Nie udało się utworzyć twardego dowiązania
hard_sym_invalid_selection_title_dialog = Niepoprawne zaznaczenie w niektórych grupach
hard_sym_invalid_selection_label_1 = W niektórych grupach zaznaczono tylko 1 rekord który zostanie zignorowany.

29
instructions/Translations.md

@ -63,4 +63,31 @@ Next new record must be added to array.
combo_box_text: "Polski (Polish)",
short_text: "pl",
},
```
```
# Validating translation offline
When trying to translate objects offline, due renames, adding and removing elements, may happen that translations will contain outdated entries.
To help find such keywords, special python script can be used.
To be able to use it, be sure that you are directly inside main `czkawka` folder.
Next, be sure that your language is available in array/list and also in i18n folder and then run python script `python3 misc/translation_test.py`.
Then results should be visible in console:
```
Checking pl language
Missing keyword - duplicate_mode_name_combo_box
Missing keyword - duplicate_mode_size_combo_box
Missing keyword - duplicate_mode_hash_combo_box
Missing keyword - settings_language_label_tooltip
Missing keyword - settings_language_label
Unused keyword - duplicate_mode_name_checkbox
Unused keyword - duplicate_mode_size_checkbox
Unused keyword - duplicate_mode_hash_checkbox
Unused keyword - duplicate_mode_name_checkbox_tooltip
Unused keyword - duplicate_mode_size_checkbox_tooltip
Unused keyword - duplicate_mode_hash_checkbox_tooltip
```
`Missing keyword` means that some keywords exists in base translations and texts needs to be translated.
`Unused keyword` means that keyword is no longer used. It can be renamed or entirely removed from file.
When script will not print anything except "Checking language", then this means that translation file have exactly same keys as base one.
Loading…
Cancel
Save