From 071caeef20e39d9148e03812371b55efb78503df Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rafa=C5=82=20Mikrut?= <mikrutrafal54@gmail.com>
Date: Thu, 15 Oct 2020 08:54:41 +0200
Subject: [PATCH] Add similar images support for GUI

---
 Changelog.md                      |   7 +-
 Instruction.md                    |   2 +-
 README.md                         |   1 +
 czkawka_core/src/similar_files.rs |   7 +
 czkawka_gui/czkawka.glade         |  78 +++++++++++
 czkawka_gui/src/help_functions.rs |  68 +++++++++-
 czkawka_gui/src/main.rs           | 208 +++++++++++++++++++++++++++---
 7 files changed, 347 insertions(+), 24 deletions(-)

diff --git a/Changelog.md b/Changelog.md
index 58ba89c1f..5831202a7 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,5 +1,9 @@
 ## Version 1.1.1 - 
-- Add test suite to PR
+- Replace String with PathBuf for paths [#59](https://github.com/qarmin/czkawka/pull/59)
+- Add test suite to PR [#65](https://github.com/qarmin/czkawka/pull/65)
+- Support for finding similar images to CLI [#66](https://github.com/qarmin/czkawka/pull/66)
+- Fix grammar-related errors and Ponglish expressions [#62](https://github.com/qarmin/czkawka/pull/62), [#63](https://github.com/qarmin/czkawka/pull/63)
+- Don't delete by default files in duplicate finder in CLI - [23f203](https://github.com/qarmin/czkawka/commit/23f203a061e254275c95ca23ca4f1a78bd941f02)
 
 
 
@@ -19,6 +23,7 @@
 - Fixed crash with invalid file modification date [#33](https://github.com/qarmin/czkawka/issues/33)
 - Upper tabs can hide and show when this is necessary [#38](https://github.com/qarmin/czkawka/pull/38)
 - Fixed crash when file/folder name have non Unicode character [#44](https://github.com/qarmin/czkawka/issues/44)
+- Added support for finding similar pictures in GUI [#69](https://github.com/qarmin/czkawka/issues/69)
 
 ## Version 1.0.0 - 02.10.2020r
 - Added confirmation button to delete button
diff --git a/Instruction.md b/Instruction.md
index 8034722e5..500235eab 100644
--- a/Instruction.md
+++ b/Instruction.md
@@ -1,5 +1,5 @@
 # Instruction
-
+TODO
 ## GUI
 
 
diff --git a/README.md b/README.md
index f3bc04b73..26dab8d01 100644
--- a/README.md
+++ b/README.md
@@ -126,6 +126,7 @@ Differences should be more visible when using slower CPU or faster disk.
 | Empty folders | X | X |
 | Temporary files | X | X |
 | Big files | X |   |
+| Similar images | X |   |
 | Installed packages |  | X |
 | Invalid names |   | X |
 | Names conflict |   | X |
diff --git a/czkawka_core/src/similar_files.rs b/czkawka_core/src/similar_files.rs
index 6e4557e4b..127277be4 100644
--- a/czkawka_core/src/similar_files.rs
+++ b/czkawka_core/src/similar_files.rs
@@ -95,6 +95,10 @@ impl SimilarImages {
         &self.text_messages
     }
 
+    pub const fn get_similar_images(&self) -> &Vec<StructSimilar> {
+        &self.similar_vectors
+    }
+
     pub const fn get_information(&self) -> &Info {
         &self.information
     }
@@ -259,6 +263,9 @@ impl SimilarImages {
 
         let mut new_vector: Vec<StructSimilar> = Vec::new();
         for (string_hash, vec_file_entry) in &self.image_hashes {
+            if rx.is_some() && rx.unwrap().try_recv().is_ok() {
+                return false;
+            }
             let vector_with_found_similar_hashes = self.bktree.find(string_hash.as_str(), 3).collect::<Vec<_>>();
             if vector_with_found_similar_hashes.len() == 1 && vec_file_entry.len() == 1 {
                 // Exists only 1 unique picture, so there is no need to use it
diff --git a/czkawka_gui/czkawka.glade b/czkawka_gui/czkawka.glade
index 49d94e6b2..83d6c7798 100644
--- a/czkawka_gui/czkawka.glade
+++ b/czkawka_gui/czkawka.glade
@@ -970,6 +970,84 @@ Author: RafaƂ Mikrut
                 <property name="tab_fill">False</property>
               </packing>
             </child>
+            <child>
+              <object class="GtkBox" id="notebook_main_similar_images_finder_label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkBox">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="spacing">8</property>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="label" translatable="yes">Minimal file size(in bytes)</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkEntry" id="entry_similar_images_minimal_size">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="max_length">15</property>
+                        <property name="text" translatable="yes">16384</property>
+                        <property name="caps_lock_warning">False</property>
+                        <property name="input_purpose">number</property>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+                <child>
+                  <object class="GtkScrolledWindow" id="scrolled_window_similar_images_finder">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="shadow_type">in</property>
+                    <child>
+                      <placeholder/>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="position">5</property>
+              </packing>
+            </child>
+            <child type="tab">
+              <object class="GtkLabel">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">Similar Images</property>
+              </object>
+              <packing>
+                <property name="position">5</property>
+                <property name="tab_fill">False</property>
+              </packing>
+            </child>
           </object>
           <packing>
             <property name="expand">True</property>
diff --git a/czkawka_gui/src/help_functions.rs b/czkawka_gui/src/help_functions.rs
index a81b9b591..90734ccff 100644
--- a/czkawka_gui/src/help_functions.rs
+++ b/czkawka_gui/src/help_functions.rs
@@ -1,4 +1,5 @@
 use czkawka_core::common_messages::Messages;
+use czkawka_core::similar_files::Similarity;
 use gtk::prelude::*;
 use gtk::TreeViewColumn;
 use std::collections::HashMap;
@@ -39,6 +40,12 @@ pub enum ColumnsTemporaryFiles {
     Path,
     Modification,
 }
+pub enum ColumnsSimilarImages {
+    Similarity = 0,
+    Name,
+    Path,
+    Modification,
+}
 
 pub const TEXT_COLOR: &str = "#ffffff";
 pub const MAIN_ROW_COLOR: &str = "#343434";
@@ -216,6 +223,46 @@ pub fn create_tree_view_empty_files(tree_view: &mut gtk::TreeView) {
     tree_view.set_vexpand(true);
 }
 
+pub fn create_tree_view_similar_images(tree_view: &mut gtk::TreeView) {
+    let renderer = gtk::CellRendererText::new();
+    let name_column: gtk::TreeViewColumn = TreeViewColumn::new();
+    name_column.pack_start(&renderer, true);
+    name_column.set_title("Similarity");
+    name_column.set_resizable(true);
+    name_column.set_min_width(50);
+    name_column.add_attribute(&renderer, "text", ColumnsSimilarImages::Similarity as i32);
+
+    tree_view.append_column(&name_column);
+    let renderer = gtk::CellRendererText::new();
+    let name_column: gtk::TreeViewColumn = TreeViewColumn::new();
+    name_column.pack_start(&renderer, true);
+    name_column.set_title("File Name");
+    name_column.set_resizable(true);
+    name_column.set_min_width(50);
+    name_column.add_attribute(&renderer, "text", ColumnsSimilarImages::Name as i32);
+    tree_view.append_column(&name_column);
+
+    let renderer = gtk::CellRendererText::new();
+    let path_column: gtk::TreeViewColumn = TreeViewColumn::new();
+    path_column.pack_start(&renderer, true);
+    path_column.set_title("Path");
+    path_column.set_resizable(true);
+    path_column.set_min_width(100);
+    path_column.add_attribute(&renderer, "text", ColumnsSimilarImages::Path as i32);
+    tree_view.append_column(&path_column);
+
+    let renderer = gtk::CellRendererText::new();
+    let modification_date_column: gtk::TreeViewColumn = TreeViewColumn::new();
+    modification_date_column.pack_start(&renderer, true);
+    modification_date_column.set_title("Modification Date");
+    modification_date_column.set_resizable(true);
+    modification_date_column.set_min_width(100);
+    modification_date_column.add_attribute(&renderer, "text", ColumnsSimilarImages::Modification as i32);
+    tree_view.append_column(&modification_date_column);
+
+    tree_view.set_vexpand(true);
+}
+
 pub fn create_tree_view_directories(tree_view: &mut gtk::TreeView) {
     let renderer = gtk::CellRendererText::new();
     let name_column: gtk::TreeViewColumn = TreeViewColumn::new();
@@ -285,7 +332,7 @@ pub fn print_text_messages_to_text_view(text_messages: &Messages, text_view: &gt
     text_view.get_buffer().unwrap().set_text(messages.as_str());
 }
 
-pub fn select_function_3column(_tree_selection: &gtk::TreeSelection, tree_model: &gtk::TreeModel, tree_path: &gtk::TreePath, _is_path_currently_selected: bool) -> bool {
+pub fn select_function_duplicates(_tree_selection: &gtk::TreeSelection, tree_model: &gtk::TreeModel, tree_path: &gtk::TreePath, _is_path_currently_selected: bool) -> bool {
     // let name = tree_model.get_value(&tree_model.get_iter(tree_path).unwrap(),ColumnsDuplicates::Name as i32).get::<String>().unwrap().unwrap();
     // let path = tree_model.get_value(&tree_model.get_iter(tree_path).unwrap(), ColumnsDuplicates::Path as i32).get::<String>().unwrap().unwrap();
     // let modification = tree_model.get_value(&tree_model.get_iter(tree_path).unwrap(),ColumnsDuplicates::Modification as i32).get::<String>().unwrap().unwrap();
@@ -297,6 +344,15 @@ pub fn select_function_3column(_tree_selection: &gtk::TreeSelection, tree_model:
 
     true
 }
+pub fn select_function_similar_images(_tree_selection: &gtk::TreeSelection, tree_model: &gtk::TreeModel, tree_path: &gtk::TreePath, _is_path_currently_selected: bool) -> bool {
+    let path = tree_model.get_value(&tree_model.get_iter(tree_path).unwrap(), ColumnsSimilarImages::Path as i32).get::<String>().unwrap().unwrap();
+
+    if path.trim() == "" {
+        return false;
+    }
+
+    true
+}
 
 pub fn set_buttons(hashmap: &mut HashMap<String, bool>, buttons_array: &[gtk::Button], button_names: &[&str]) {
     for (index, button) in buttons_array.iter().enumerate() {
@@ -322,3 +378,13 @@ pub fn hide_all_buttons_except(except_name: &str, buttons_array: &[gtk::Button],
         }
     }
 }
+
+pub fn get_text_from_similarity(similarity: &Similarity) -> &str {
+    match similarity {
+        Similarity::None => "Original",
+        Similarity::Small => "Small",
+        Similarity::Medium => "Medium",
+        Similarity::High => "High",
+        Similarity::VeryHigh => "Very High",
+    }
+}
diff --git a/czkawka_gui/src/main.rs b/czkawka_gui/src/main.rs
index a756b881e..785cdad38 100644
--- a/czkawka_gui/src/main.rs
+++ b/czkawka_gui/src/main.rs
@@ -12,6 +12,7 @@ use czkawka_core::common_traits::SaveResults;
 use czkawka_core::duplicate::CheckingMethod;
 use czkawka_core::empty_files::EmptyFiles;
 use czkawka_core::empty_folder::EmptyFolder;
+use czkawka_core::similar_files::SimilarImages;
 use czkawka_core::temporary::Temporary;
 use duplicate::DuplicateFinder;
 use gtk::prelude::*;
@@ -61,7 +62,7 @@ fn main() {
 
     ////////////////////////////////////////////////////////////////////////////////////////////////
     //// States
-    let main_notebooks_labels = ["duplicate", "empty_folder", "empty_file", "temporary_file", "big_file"];
+    let main_notebooks_labels = ["duplicate", "empty_folder", "empty_file", "temporary_file", "big_file", "similar_images"];
     let upper_notebooks_labels = [/*"general",*/ "included_directories", "excluded_directories", "excluded_items", "allowed_extensions"];
     let buttons_labels = ["search", "stop", "resume", "pause", "select", "delete", "save"];
 
@@ -104,6 +105,7 @@ fn main() {
     let shared_empty_files_state: Rc<RefCell<_>> = Rc::new(RefCell::new(EmptyFiles::new()));
     let shared_temporary_files_state: Rc<RefCell<_>> = Rc::new(RefCell::new(Temporary::new()));
     let shared_big_files_state: Rc<RefCell<_>> = Rc::new(RefCell::new(BigFile::new()));
+    let shared_similar_images_state: Rc<RefCell<_>> = Rc::new(RefCell::new(SimilarImages::new()));
 
     // State of confirmation dialogs
     let shared_confirmation_dialog_delete_dialog_showing_state: Rc<RefCell<_>> = Rc::new(RefCell::new(true));
@@ -111,6 +113,7 @@ fn main() {
     ////////////////////////////////////////////////////////////////////////////////////////////////
 
     //// GUI Entry
+    let entry_similar_images_minimal_size: gtk::Entry = builder.get_object("entry_similar_images_minimal_size").unwrap();
     let entry_duplicate_minimal_size: gtk::Entry = builder.get_object("entry_duplicate_minimal_size").unwrap();
     let entry_allowed_extensions: gtk::Entry = builder.get_object("entry_allowed_extensions").unwrap();
     let entry_excluded_items: gtk::Entry = builder.get_object("entry_excluded_items").unwrap();
@@ -182,13 +185,13 @@ fn main() {
     let text_view_errors: gtk::TextView = builder.get_object("text_view_errors").unwrap();
 
     //// Scrolled windows
-
     // Main notebook
     let scrolled_window_duplicate_finder: gtk::ScrolledWindow = builder.get_object("scrolled_window_duplicate_finder").unwrap();
     let scrolled_window_main_empty_folder_finder: gtk::ScrolledWindow = builder.get_object("scrolled_window_main_empty_folder_finder").unwrap();
     let scrolled_window_main_empty_files_finder: gtk::ScrolledWindow = builder.get_object("scrolled_window_main_empty_files_finder").unwrap();
     let scrolled_window_main_temporary_files_finder: gtk::ScrolledWindow = builder.get_object("scrolled_window_main_temporary_files_finder").unwrap();
     let scrolled_window_big_files_finder: gtk::ScrolledWindow = builder.get_object("scrolled_window_big_files_finder").unwrap();
+    let scrolled_window_similar_images_finder: gtk::ScrolledWindow = builder.get_object("scrolled_window_similar_images_finder").unwrap();
 
     // Upper notebook
     let scrolled_window_included_directories: gtk::ScrolledWindow = builder.get_object("scrolled_window_included_directories").unwrap();
@@ -202,6 +205,7 @@ fn main() {
         EmptyFiles(EmptyFiles),
         BigFiles(BigFile),
         Temporary(Temporary),
+        SimilarImages(SimilarImages),
     }
 
     // Used for getting data from thread
@@ -240,7 +244,7 @@ fn main() {
                 let mut tree_view: gtk::TreeView = TreeView::with_model(&list_store);
 
                 tree_view.get_selection().set_mode(SelectionMode::Multiple);
-                tree_view.get_selection().set_select_function(Some(Box::new(select_function_3column)));
+                tree_view.get_selection().set_select_function(Some(Box::new(select_function_duplicates)));
 
                 create_tree_view_duplicates(&mut tree_view);
 
@@ -303,6 +307,22 @@ fn main() {
                 scrolled_window_big_files_finder.add(&tree_view);
                 scrolled_window_big_files_finder.show_all();
             }
+            // Similar Images
+            {
+                // TODO create maybe open button to support opening multiple files at once
+                let col_types: [glib::types::Type; 4] = [glib::types::Type::String, glib::types::Type::String, glib::types::Type::String, glib::types::Type::String];
+                let list_store: gtk::ListStore = gtk::ListStore::new(&col_types);
+
+                let mut tree_view: gtk::TreeView = TreeView::with_model(&list_store);
+
+                tree_view.get_selection().set_mode(SelectionMode::Multiple);
+                tree_view.get_selection().set_select_function(Some(Box::new(select_function_similar_images)));
+
+                create_tree_view_similar_images(&mut tree_view);
+
+                scrolled_window_similar_images_finder.add(&tree_view);
+                scrolled_window_similar_images_finder.show_all();
+            }
         }
 
         // Set Included Directory
@@ -398,6 +418,7 @@ fn main() {
                     "scrolled_window_main_empty_files_finder" => page = "empty_file",
                     "scrolled_window_main_temporary_files_finder" => page = "temporary_file",
                     "notebook_big_main_file_finder" => page = "big_file",
+                    "notebook_main_similar_images_finder_label" => page = "similar_images",
                     e => {
                         panic!("Not existent page {}", e);
                     }
@@ -431,11 +452,6 @@ fn main() {
 
         // Main buttons
         {
-            assert!(notebook_main_children_names.contains(&"notebook_main_duplicate_finder_label".to_string()));
-            assert!(notebook_main_children_names.contains(&"scrolled_window_main_empty_folder_finder".to_string()));
-            assert!(notebook_main_children_names.contains(&"scrolled_window_main_empty_files_finder".to_string()));
-            assert!(notebook_main_children_names.contains(&"scrolled_window_main_temporary_files_finder".to_string()));
-            assert!(notebook_main_children_names.contains(&"notebook_big_main_file_finder".to_string()));
             // Search button
             {
                 let entry_info = entry_info.clone();
@@ -457,7 +473,7 @@ fn main() {
                     // Disable main notebook from any iteraction until search will end
                     notebook_main.set_sensitive(false);
 
-                    entry_info.set_text("Searching data, please wait...");
+                    entry_info.set_text("Searching data, it may take a while please wait...");
 
                     match notebook_main_children_names.get(notebook_main.get_current_page().unwrap() as usize).unwrap().as_str() {
                         "notebook_main_duplicate_finder_label" => {
@@ -562,6 +578,29 @@ fn main() {
                                 let _ = sender.send(Message::BigFiles(bf));
                             });
                         }
+
+                        "notebook_main_similar_images_finder_label" => {
+                            let sender = sender.clone();
+                            let receiver_stop = rx.clone();
+
+                            let minimal_file_size = match entry_similar_images_minimal_size.get_text().as_str().parse::<u64>() {
+                                Ok(t) => t,
+                                Err(_) => 1024 * 16, // By default
+                            };
+
+                            // Find similar images
+                            thread::spawn(move || {
+                                let mut sf = SimilarImages::new();
+
+                                sf.set_included_directory(included_directories);
+                                sf.set_excluded_directory(excluded_directories);
+                                sf.set_recursive_search(recursive_search);
+                                sf.set_excluded_items(excluded_items);
+                                sf.set_minimal_file_size(minimal_file_size);
+                                sf.find_similar_images(Option::from(&receiver_stop));
+                                let _ = sender.send(Message::SimilarImages(sf));
+                            });
+                        }
                         e => panic!("Not existent {}", e),
                     }
                 });
@@ -577,6 +616,7 @@ fn main() {
                 let scrolled_window_big_files_finder = scrolled_window_big_files_finder.clone();
                 let scrolled_window_main_empty_files_finder = scrolled_window_main_empty_files_finder.clone();
                 let scrolled_window_main_temporary_files_finder = scrolled_window_main_temporary_files_finder.clone();
+                let scrolled_window_similar_images_finder = scrolled_window_similar_images_finder.clone();
 
                 buttons_delete.connect_clicked(move |_| {
                     if *shared_confirmation_dialog_delete_dialog_showing_state.borrow_mut() {
@@ -799,6 +839,39 @@ fn main() {
                             text_view_errors.get_buffer().unwrap().set_text(messages.as_str());
                             selection.unselect_all();
                         }
+                        "notebook_main_similar_images_finder_label" => {
+                            let tree_view = scrolled_window_similar_images_finder.get_children().get(0).unwrap().clone().downcast::<gtk::TreeView>().unwrap();
+                            let selection = tree_view.get_selection();
+
+                            let (selection_rows, tree_model) = selection.get_selected_rows();
+                            let list_store = tree_model.clone().downcast::<gtk::ListStore>().unwrap();
+
+                            // let new_tree_model = TreeModel::new(); // TODO - maybe create new model when inserting a new data, because this seems to be not optimal when using thousands of rows
+
+                            let mut messages: String = "".to_string();
+
+                            // Must be deleted from end to start, because when deleting entries, TreePath(and also TreeIter) will points to invalid data
+                            for tree_path in selection_rows.iter().rev() {
+                                let name = tree_model.get_value(&tree_model.get_iter(tree_path).unwrap(), ColumnsBigFiles::Name as i32).get::<String>().unwrap().unwrap();
+                                let path = tree_model.get_value(&tree_model.get_iter(tree_path).unwrap(), ColumnsBigFiles::Path as i32).get::<String>().unwrap().unwrap();
+
+                                match fs::remove_file(format!("{}/{}", path, name)) {
+                                    Ok(_) => {
+                                        list_store.remove(&list_store.get_iter(tree_path).unwrap());
+                                    }
+                                    Err(_) => {
+                                        messages += format!(
+                                            "Failed to remove file {}/{}. It is possible that you already deleted it, because similar images shows all possible file doesn't exists or you don't have permissions.\n",
+                                            path, name
+                                        )
+                                        .as_str()
+                                    }
+                                }
+                            }
+
+                            text_view_errors.get_buffer().unwrap().set_text(messages.as_str());
+                            selection.unselect_all();
+                        }
                         e => panic!("Not existent {}", e),
                     }
                 });
@@ -815,18 +888,6 @@ fn main() {
                         popover_select.set_relative_to(Some(&buttons_select));
                         popover_select.popup();
                     }
-                    "scrolled_window_main_empty_folder_finder" => {
-                        // Do nothing
-                    }
-                    "scrolled_window_main_empty_files_finder" => {
-                        // Do nothing
-                    }
-                    "scrolled_window_main_temporary_files_finder" => {
-                        // Do nothing
-                    }
-                    "notebook_big_main_file_finder" => {
-                        // Do nothing
-                    }
                     e => panic!("Not existent {}", e),
                 });
             }
@@ -840,6 +901,7 @@ fn main() {
                 let shared_big_files_state = shared_big_files_state.clone();
                 let shared_temporary_files_state = shared_temporary_files_state.clone();
                 let shared_empty_files_state = shared_empty_files_state.clone();
+                let shared_similar_images_state = shared_similar_images_state.clone();
                 let notebook_main = notebook_main.clone();
                 buttons_save_clone.connect_clicked(move |_| match notebook_main_children_names.get(notebook_main.get_current_page().unwrap() as usize).unwrap().as_str() {
                     "notebook_main_duplicate_finder_label" => {
@@ -907,6 +969,19 @@ fn main() {
                             *shared_buttons.borrow_mut().get_mut("big_file").unwrap().get_mut("save").unwrap() = false;
                         }
                     }
+                    "notebook_main_similar_images_finder_label" => {
+                        let file_name = "results_similar_images.txt";
+
+                        let mut df = shared_similar_images_state.borrow_mut();
+                        df.save_results_to_file(file_name);
+
+                        entry_info.set_text(format!("Saved results to file {}", file_name).as_str());
+                        // Set state
+                        {
+                            buttons_save.hide();
+                            *shared_buttons.borrow_mut().get_mut("similar_images").unwrap().get_mut("save").unwrap() = false;
+                        }
+                    }
                     e => panic!("Not existent {}", e),
                 });
             }
@@ -1745,6 +1820,97 @@ fn main() {
                     }
                 }
             }
+            Message::SimilarImages(sf) => {
+                if sf.get_stopped_search() {
+                    entry_info.set_text("Searching for duplicated was stopped by user");
+
+                    //Also clear list
+                    scrolled_window_duplicate_finder
+                        .get_children()
+                        .get(0)
+                        .unwrap()
+                        .clone()
+                        .downcast::<gtk::TreeView>()
+                        .unwrap()
+                        .get_model()
+                        .unwrap()
+                        .downcast::<gtk::ListStore>()
+                        .unwrap()
+                        .clear();
+                } else {
+                    // let information = sf.get_information();
+                    let text_messages = sf.get_text_messages();
+
+                    let base_images_size = sf.get_similar_images().len();
+
+                    entry_info.set_text(format!("Found similar pictures for {} images.", base_images_size).as_str());
+
+                    // Create GUI
+                    {
+                        let list_store = scrolled_window_similar_images_finder
+                            .get_children()
+                            .get(0)
+                            .unwrap()
+                            .clone()
+                            .downcast::<gtk::TreeView>()
+                            .unwrap()
+                            .get_model()
+                            .unwrap()
+                            .downcast::<gtk::ListStore>()
+                            .unwrap();
+                        list_store.clear();
+
+                        let col_indices = [0, 1, 2, 3];
+
+                        let vec_struct_similar = sf.get_similar_images();
+
+                        for (index, struct_similar) in vec_struct_similar.iter().enumerate() {
+                            // Empty at the beginning
+                            if index != 0 {
+                                let values: [&dyn ToValue; 4] = [&"".to_string(), &"".to_string(), &"".to_string(), &"".to_string()];
+                                list_store.set(&list_store.append(), &col_indices, &values);
+                            }
+                            // Header
+                            let (directory, file) = split_path(&struct_similar.base_image.path);
+                            let values: [&dyn ToValue; 4] = [
+                                &(get_text_from_similarity(&struct_similar.base_image.similarity).to_string()),
+                                &file,
+                                &directory,
+                                &(NaiveDateTime::from_timestamp(struct_similar.base_image.modified_date as i64, 0).to_string()),
+                            ];
+                            list_store.set(&list_store.append(), &col_indices, &values);
+
+                            // Meat
+                            for similar_images in &struct_similar.similar_images {
+                                let (directory, file) = split_path(&similar_images.path);
+                                let values: [&dyn ToValue; 4] = [
+                                    &(get_text_from_similarity(&similar_images.similarity).to_string()),
+                                    &file,
+                                    &directory,
+                                    &(NaiveDateTime::from_timestamp(similar_images.modified_date as i64, 0).to_string()),
+                                ];
+                                list_store.set(&list_store.append(), &col_indices, &values);
+                            }
+                        }
+
+                        print_text_messages_to_text_view(text_messages, &text_view_errors);
+                    }
+
+                    // Set state
+                    {
+                        *shared_similar_images_state.borrow_mut() = sf;
+
+                        if base_images_size > 0 {
+                            *shared_buttons.borrow_mut().get_mut("similar_images").unwrap().get_mut("save").unwrap() = true;
+                            *shared_buttons.borrow_mut().get_mut("similar_images").unwrap().get_mut("delete").unwrap() = true;
+                        } else {
+                            *shared_buttons.borrow_mut().get_mut("similar_images").unwrap().get_mut("save").unwrap() = false;
+                            *shared_buttons.borrow_mut().get_mut("similar_images").unwrap().get_mut("delete").unwrap() = false;
+                        }
+                        set_buttons(&mut *shared_buttons.borrow_mut().get_mut("similar_images").unwrap(), &buttons_array, &buttons_names);
+                    }
+                }
+            }
         }
         // Returning false here would close the receiver and have senders fail
         glib::Continue(true)