Fix #714: find duplicates enhanced selection logic
authorColin Clark <colin.clark@cclark.uk>
Thu, 23 Apr 2020 09:40:19 +0000 (10:40 +0100)
committerColin Clark <colin.clark@cclark.uk>
Thu, 23 Apr 2020 09:40:19 +0000 (10:40 +0100)
https://github.com/BestImageViewer/geeqie/issues/714

Sort by click on column header
"Show all" option
Revised layout
Key "0" de-select all

doc/docbook/GuideImageSearchFindingDuplicates.xml
doc/docbook/GuideReferenceKeyboardShortcuts.xml
src/dupe.c
src/dupe.h

index 29606d0..29ba7e2 100644 (file)
           </para>\r
         </listitem>\r
       </varlistentry>\r
+      <varlistentry>\r
+        <term>\r
+          <guilabel>Show all</guilabel>\r
+        </term>\r
+        <listitem>\r
+          <para>\r
+            Do not compare. Show all images.\r
+          </para>\r
+        </listitem>\r
+      </varlistentry>\r
     </variablelist>\r
   </section>\r
   <section id="Resultslist">\r
     <title>Results list</title>\r
     <para>Files that match with the selected comparison method will appear in the list. Matching files are grouped in alternating color.</para>\r
-    <para>The order of the result list can not be changed, files will appear in the order of the search. When comparing by image content similarity, the matching groups will be sorted by order of rank starting with the files that are most similar.</para>\r
+    <para>The order of the result list can be changed by clicking on the column header. This will re-order the images within each set. When comparing by image content similarity, the matching sets will be sorted by order of rank starting with the files that are most similar.</para>\r
     <para>\r
       A\r
       <emphasis role="strong">context menu</emphasis>\r
     </para>\r
     <para>\r
       Groups in the results list may be selected by using the keyboard. Refer to the <emphasis>Find Duplicates Window</emphasis> section of\r
-       <link linkend="FindDuplicatesWindow" >Keyboard Shortcuts</link>\r
+       <link linkend="DuplicatesKeyboardShortcuts" >Keyboard Shortcuts</link>\r
       .\r
     </para>\r
     <para>\r
index 8a89e16..3a1aecf 100644 (file)
           </row>\r
         </thead>\r
         <tbody>\r
+          <row>\r
+            <entry>\r
+              <keycap>0</keycap>\r
+            </entry>\r
+            <entry />\r
+            <entry>Select none.</entry>\r
+          </row>\r
           <row>\r
             <entry>\r
               <keycap>1</keycap>\r
index f74206e..173bda9 100644 (file)
@@ -70,6 +70,7 @@ enum {
        DUPE_COLUMN_DIMENSIONS,
        DUPE_COLUMN_PATH,
        DUPE_COLUMN_COLOR,
+       DUPE_COLUMN_SET,
        DUPE_COLUMN_COUNT       /* total columns */
 };
 
@@ -524,6 +525,7 @@ static void dupe_listview_add(DupeWindow *dw, DupeItem *parent, DupeItem *child)
                        {
                        gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, DUPE_COLUMN_COLOR, &color_set, -1);
                        color_set = !color_set;
+                       dw->set_count++;
                        }
                else
                        {
@@ -574,6 +576,7 @@ static void dupe_listview_add(DupeWindow *dw, DupeItem *parent, DupeItem *child)
                                DUPE_COLUMN_DIMENSIONS, text[DUPE_COLUMN_DIMENSIONS],
                                DUPE_COLUMN_PATH, text[DUPE_COLUMN_PATH],
                                DUPE_COLUMN_COLOR, color_set,
+                               DUPE_COLUMN_SET, dw->set_count,
                                -1);
 
        g_free(text[DUPE_COLUMN_RANK]);
@@ -737,6 +740,8 @@ static void dupe_listview_select_dupes(DupeWindow *dw, DupeSelectType parents)
        GtkTreeSelection *selection;
        GtkTreeIter iter;
        gboolean valid;
+       gint set_count = 0;
+       gint set_count_last = -1;
 
        selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dw->listview));
        gtk_tree_selection_unselect_all(selection);
@@ -747,10 +752,21 @@ static void dupe_listview_select_dupes(DupeWindow *dw, DupeSelectType parents)
                {
                DupeItem *di;
 
-               gtk_tree_model_get(store, &iter, DUPE_COLUMN_POINTER, &di, -1);
-               if ((dupe_match_find_parent(dw, di) == di) == (parents == DUPE_SELECT_GROUP1))
+               gtk_tree_model_get(store, &iter, DUPE_COLUMN_POINTER, &di, DUPE_COLUMN_SET, &set_count, -1);
+               if (set_count != set_count_last)
                        {
-                       gtk_tree_selection_select_iter(selection, &iter);
+                       set_count_last = set_count;
+                       if (parents == DUPE_SELECT_GROUP1)
+                               {
+                               gtk_tree_selection_select_iter(selection, &iter);
+                               }
+                       }
+               else
+                       {
+                       if (parents == DUPE_SELECT_GROUP2)
+                               {
+                               gtk_tree_selection_select_iter(selection, &iter);
+                               }
                        }
                valid = gtk_tree_model_iter_next(store, &iter);
                }
@@ -1168,6 +1184,10 @@ static gboolean dupe_match(DupeItem *a, DupeItem *b, DupeMatchType mask, gdouble
 
        if (a->fd->path == b->fd->path) return FALSE;
 
+       if (mask & DUPE_MATCH_ALL)
+               {
+               return TRUE;
+               }
        if (mask & DUPE_MATCH_PATH)
                {
                if (utf8_compare(a->fd->path, b->fd->path, TRUE) != 0) return FALSE;
@@ -1412,6 +1432,13 @@ static void dupe_check_stop(DupeWindow *dw)
        dw->img_loader = NULL;
 }
 
+static void dupe_check_stop_cb(GtkWidget *widget, gpointer data)
+{
+       DupeWindow *dw = data;
+
+       dupe_check_stop(dw);
+}
+
 static void dupe_loader_done_cb(ImageLoader *il, gpointer data)
 {
        DupeWindow *dw = data;
@@ -1803,6 +1830,8 @@ static void dupe_files_add(DupeWindow *dw, CollectionData *collection, CollectIn
 
        if (!di) return;
 
+       dupe_item_read_cache(di);
+
        /* Ensure images in the lists have unique FileDatas */
        GList *work;
        DupeItem *di_list;
@@ -2046,6 +2075,7 @@ static void dupe_window_recompare(DupeWindow *dw)
 
        dupe_match_reset_list(dw->list);
        dupe_match_reset_list(dw->second_list);
+       dw->set_count = 0;
 
        dupe_check_start(dw);
 }
@@ -2749,6 +2779,8 @@ enum {
        DUPE_MENU_COLUMN_MASK
 };
 
+static void dupe_listview_show_rank(GtkWidget *listview, gboolean rank);
+
 static void dupe_menu_type_cb(GtkWidget *combo, gpointer data)
 {
        DupeWindow *dw = data;
@@ -2761,6 +2793,14 @@ static void dupe_menu_type_cb(GtkWidget *combo, gpointer data)
 
        options->duplicates_match = dw->match_mask;
 
+       if (dw->match_mask & (DUPE_MATCH_SIM_HIGH | DUPE_MATCH_SIM_MED | DUPE_MATCH_SIM_LOW | DUPE_MATCH_SIM_CUSTOM))
+               {
+               dupe_listview_show_rank(dw->listview, TRUE);
+               }
+       else
+               {
+               dupe_listview_show_rank(dw->listview, FALSE);
+               }
        dupe_window_recompare(dw);
 }
 
@@ -2800,6 +2840,7 @@ static void dupe_menu_setup(DupeWindow *dw)
        dupe_menu_add_item(store, _("Similarity"), DUPE_MATCH_SIM_MED, dw);
        dupe_menu_add_item(store, _("Similarity (low)"), DUPE_MATCH_SIM_LOW, dw);
        dupe_menu_add_item(store, _("Similarity (custom)"), DUPE_MATCH_SIM_CUSTOM, dw);
+       dupe_menu_add_item(store, _("Show all"), DUPE_MATCH_ALL, dw);
 
        g_signal_connect(G_OBJECT(dw->combo), "changed",
                         G_CALLBACK(dupe_menu_type_cb), dw);
@@ -2867,6 +2908,7 @@ static void dupe_listview_add_column(DupeWindow *dw, GtkWidget *listview, gint n
        column = gtk_tree_view_column_new();
        gtk_tree_view_column_set_title(column, title);
        gtk_tree_view_column_set_min_width(column, 4);
+       gtk_tree_view_column_set_sort_column_id(column, n);
 
        if (n != DUPE_COLUMN_RANK &&
            n != DUPE_COLUMN_THUMB)
@@ -2913,6 +2955,7 @@ static void dupe_listview_set_height(GtkWidget *listview, gboolean thumb)
        if (!column) return;
 
        gtk_tree_view_column_set_fixed_width(column, (thumb) ? options->thumbnails.max_width : 4);
+       gtk_tree_view_column_set_visible(column, thumb);
 
        list = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(column));
        if (!list) return;
@@ -2923,6 +2966,15 @@ static void dupe_listview_set_height(GtkWidget *listview, gboolean thumb)
        gtk_tree_view_columns_autosize(GTK_TREE_VIEW(listview));
 }
 
+static void dupe_listview_show_rank(GtkWidget *listview, gboolean rank)
+{
+       GtkTreeViewColumn *column;
+
+       column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), DUPE_COLUMN_RANK - 1);
+       if (!column) return;
+
+       gtk_tree_view_column_set_visible(column, rank);
+}
 
 /*
  *-------------------------------------------------------------------
@@ -3167,6 +3219,10 @@ static gboolean dupe_window_keypress_cb(GtkWidget *widget, GdkEventKey *event, g
                                        dupe_window_collection_from_selection(dw);
                                        }
                                break;
+                       case '0':
+                               options->duplicates_select_type = DUPE_SELECT_NONE;
+                               dupe_listview_select_dupes(dw, DUPE_SELECT_NONE);
+                               break;
                        case '1':
                                options->duplicates_select_type = DUPE_SELECT_GROUP1;
                                dupe_listview_select_dupes(dw, DUPE_SELECT_GROUP1);
@@ -3224,6 +3280,7 @@ void dupe_window_clear(DupeWindow *dw)
 
        dupe_list_free(dw->list);
        dw->list = NULL;
+       dw->set_count = 0;
 
        dupe_match_reset_list(dw->second_list);
 
@@ -3265,6 +3322,15 @@ void dupe_window_close(DupeWindow *dw)
        g_free(dw);
 }
 
+static gint dupe_window_close_cb(GtkWidget *widget, gpointer data)
+{
+       DupeWindow *dw = data;
+
+       dupe_window_close(dw);
+
+       return TRUE;
+}
+
 static gint dupe_window_delete(GtkWidget *widget, GdkEvent *event, gpointer data)
 {
        DupeWindow *dw = data;
@@ -3273,14 +3339,131 @@ static gint dupe_window_delete(GtkWidget *widget, GdkEvent *event, gpointer data
        return TRUE;
 }
 
+static void dupe_help_cb(GtkAction *action, gpointer data)
+{
+       help_window_show("GuideImageSearchFindingDuplicates.html");
+}
+
+static gint default_sort_cb(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer data)
+{
+       return 0;
+}
+
+static gint column_sort_cb(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer data)
+{
+       GtkTreeSortable *sortable = data;
+       gint ret = 0;
+       gchar *rank_str_a, *rank_str_b;
+       gint rank_int_a;
+       gint rank_int_b;
+       gint group_a;
+       gint group_b;
+       gint sort_column_id;
+       GtkSortType sort_order;
+       DupeItem *di_a;
+       DupeItem *di_b;
+
+       gtk_tree_sortable_get_sort_column_id(sortable, &sort_column_id, &sort_order);
+
+       gtk_tree_model_get(model, a, DUPE_COLUMN_RANK, &rank_str_a, DUPE_COLUMN_SET, &group_a, DUPE_COLUMN_POINTER, &di_a, -1);
+
+       gtk_tree_model_get(model, b, DUPE_COLUMN_RANK, &rank_str_b, DUPE_COLUMN_SET, &group_b, DUPE_COLUMN_POINTER, &di_b, -1);
+
+       if (group_a == group_b)
+               {
+               switch (sort_column_id)
+                       {
+                       case DUPE_COLUMN_NAME:
+                               ret = utf8_compare(di_a->fd->name, di_b->fd->name, TRUE);
+                               break;
+                       case DUPE_COLUMN_SIZE:
+                               if (di_a->fd->size == di_b->fd->size)
+                                       {
+                                       ret = 0;
+                                       }
+                               else
+                                       {
+                                       ret = (di_a->fd->size > di_b->fd->size) ? 1 : -1;
+                                       }
+                               break;
+                       case DUPE_COLUMN_DATE:
+                               if (di_a->fd->date == di_b->fd->date)
+                                       {
+                                       ret = 0;
+                                       }
+                               else
+                                       {
+                                       ret = (di_a->fd->date > di_b->fd->date) ? 1 : -1;
+                                       }
+                               break;
+                       case DUPE_COLUMN_DIMENSIONS:
+                               if ((di_a->width == di_b->width) && (di_a->height == di_b->height))
+                                       {
+                                       ret = 0;
+                                       }
+                               else
+                                       {
+                                       ret = ((di_a->width * di_a->height) > (di_b->width * di_b->height)) ? 1 : -1;
+                                       }
+                               break;
+                       case DUPE_COLUMN_RANK:
+                               rank_int_a = atoi(rank_str_a);
+                               rank_int_b = atoi(rank_str_b);
+                               if (rank_int_a == 0) rank_int_a = 101;
+                               if (rank_int_b == 0) rank_int_b = 101;
+
+                               if (rank_int_a == rank_int_b)
+                                       {
+                                       ret = 0;
+                                       }
+                               else
+                                       {
+                                       ret = (rank_int_a > rank_int_b) ? 1 : -1;
+                                       }
+                               break;
+                       case DUPE_COLUMN_PATH:
+                               ret = utf8_compare(di_a->fd->path, di_b->fd->path, TRUE);
+                               break;
+                       }
+               }
+       else if (group_a < group_b)
+               {
+               ret = (sort_order == GTK_SORT_ASCENDING) ? 1 : -1;
+               }
+       else
+               {
+               ret = (sort_order == GTK_SORT_ASCENDING) ? -1 : 1;
+               }
+
+       return ret;
+}
+
+static gboolean dupe_window_recompare_cb(gpointer data)
+{
+       DupeWindow *dw = data;
+
+       dupe_window_recompare_cb(dw);
+}
+
+static void column_clicked_cb(GtkWidget *widget,  gpointer data)
+{
+       DupeWindow *dw = data;
+
+       options->duplicates_match = DUPE_SELECT_NONE;
+       dupe_listview_select_dupes(dw, DUPE_SELECT_NONE);
+       dupe_window_recompare_cb, dw;
+}
+
 /* collection and files can be NULL */
 DupeWindow *dupe_window_new()
 {
        DupeWindow *dw;
        GtkWidget *vbox;
+       GtkWidget *hbox;
        GtkWidget *scrolled;
        GtkWidget *frame;
        GtkWidget *status_box;
+       GtkWidget *button_box;
        GtkWidget *label;
        GtkWidget *button;
        GtkListStore *store;
@@ -3347,24 +3530,41 @@ DupeWindow *dupe_window_new()
        gtk_table_attach_defaults(GTK_TABLE(dw->table), scrolled, 0, 2, 0, 1);
        gtk_widget_show(scrolled);
 
-       store = gtk_list_store_new(9, G_TYPE_POINTER, G_TYPE_STRING, GDK_TYPE_PIXBUF,
-                                  G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
-                                  G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
+       store = gtk_list_store_new(DUPE_COLUMN_COUNT, G_TYPE_POINTER, G_TYPE_STRING, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_INT, G_TYPE_INT);
        dw->listview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
        g_object_unref(store);
 
+       dw->sortable = GTK_TREE_SORTABLE(store);
+
+       gtk_tree_sortable_set_sort_func(dw->sortable, DUPE_COLUMN_RANK, column_sort_cb, dw->sortable, NULL);
+       gtk_tree_sortable_set_sort_func(dw->sortable, DUPE_COLUMN_SET, default_sort_cb, dw->sortable, NULL);
+       gtk_tree_sortable_set_sort_func(dw->sortable, DUPE_COLUMN_THUMB, default_sort_cb, dw->sortable, NULL);
+       gtk_tree_sortable_set_sort_func(dw->sortable, DUPE_COLUMN_NAME, column_sort_cb, dw->sortable, NULL);
+       gtk_tree_sortable_set_sort_func(dw->sortable, DUPE_COLUMN_SIZE, column_sort_cb, dw->sortable, NULL);
+       gtk_tree_sortable_set_sort_func(dw->sortable, DUPE_COLUMN_DATE, column_sort_cb, dw->sortable, NULL);
+       gtk_tree_sortable_set_sort_func(dw->sortable, DUPE_COLUMN_DIMENSIONS, column_sort_cb, dw->sortable, NULL);
+       gtk_tree_sortable_set_sort_func(dw->sortable, DUPE_COLUMN_PATH, column_sort_cb, dw->sortable, NULL);
+
        selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dw->listview));
        gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
        gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(dw->listview), TRUE);
        gtk_tree_view_set_enable_search(GTK_TREE_VIEW(dw->listview), FALSE);
 
-       dupe_listview_add_column(dw, dw->listview, DUPE_COLUMN_RANK, "", FALSE, TRUE);
-       dupe_listview_add_column(dw, dw->listview, DUPE_COLUMN_THUMB, "", TRUE, FALSE);
+       dupe_listview_add_column(dw, dw->listview, DUPE_COLUMN_RANK, _("Rank"), FALSE, TRUE);
+       dupe_listview_add_column(dw, dw->listview, DUPE_COLUMN_THUMB, _("Thumb"), TRUE, FALSE);
        dupe_listview_add_column(dw, dw->listview, DUPE_COLUMN_NAME, _("Name"), FALSE, FALSE);
        dupe_listview_add_column(dw, dw->listview, DUPE_COLUMN_SIZE, _("Size"), FALSE, TRUE);
        dupe_listview_add_column(dw, dw->listview, DUPE_COLUMN_DATE, _("Date"), FALSE, TRUE);
        dupe_listview_add_column(dw, dw->listview, DUPE_COLUMN_DIMENSIONS, _("Dimensions"), FALSE, FALSE);
        dupe_listview_add_column(dw, dw->listview, DUPE_COLUMN_PATH, _("Path"), FALSE, FALSE);
+       dupe_listview_add_column(dw, dw->listview, DUPE_COLUMN_SET, _("Set"), FALSE, FALSE);
+
+       g_signal_connect(gtk_tree_view_get_column(GTK_TREE_VIEW(dw->listview), DUPE_COLUMN_RANK - 1), "clicked", (GCallback)column_clicked_cb, dw);
+       g_signal_connect(gtk_tree_view_get_column(GTK_TREE_VIEW(dw->listview), DUPE_COLUMN_NAME - 1), "clicked", (GCallback)column_clicked_cb, dw);
+       g_signal_connect(gtk_tree_view_get_column(GTK_TREE_VIEW(dw->listview), DUPE_COLUMN_SIZE - 1), "clicked", (GCallback)column_clicked_cb, dw);
+       g_signal_connect(gtk_tree_view_get_column(GTK_TREE_VIEW(dw->listview), DUPE_COLUMN_DATE - 1), "clicked", (GCallback)column_clicked_cb, dw);
+       g_signal_connect(gtk_tree_view_get_column(GTK_TREE_VIEW(dw->listview), DUPE_COLUMN_DIMENSIONS - 1), "clicked", (GCallback)column_clicked_cb, dw);
+       g_signal_connect(gtk_tree_view_get_column(GTK_TREE_VIEW(dw->listview), DUPE_COLUMN_PATH - 1), "clicked", (GCallback)column_clicked_cb, dw);
 
        gtk_container_add(GTK_CONTAINER(scrolled), dw->listview);
        gtk_widget_show(dw->listview);
@@ -3407,15 +3607,30 @@ DupeWindow *dupe_window_new()
 
        pref_line(dw->second_vbox, GTK_ORIENTATION_HORIZONTAL);
 
-       status_box = pref_box_new(vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
+       status_box = gtk_hbox_new(FALSE, 0);
+       gtk_box_pack_start(GTK_BOX(vbox), status_box, FALSE, FALSE, 0);
+       gtk_widget_show(status_box);
 
-       label = gtk_label_new(_("Compare by:"));
-       gtk_box_pack_start(GTK_BOX(status_box), label, FALSE, FALSE, PREF_PAD_SPACE);
-       gtk_widget_show(label);
+       frame = gtk_frame_new(NULL);
+       DEBUG_NAME(frame);
+       gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
+       gtk_box_pack_start(GTK_BOX(status_box), frame, TRUE, TRUE, 0);
+       gtk_widget_show(frame);
 
-       dupe_menu_setup(dw);
-       gtk_box_pack_start(GTK_BOX(status_box), dw->combo, FALSE, FALSE, 0);
-       gtk_widget_show(dw->combo);
+       dw->status_label = gtk_label_new("");
+       gtk_container_add(GTK_CONTAINER(frame), dw->status_label);
+       gtk_widget_show(dw->status_label);
+
+       dw->extra_label = gtk_progress_bar_new();
+       gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(dw->extra_label), 0.0);
+#if GTK_CHECK_VERSION(3,0,0)
+       gtk_progress_bar_set_text(GTK_PROGRESS_BAR(dw->extra_label), "");
+       gtk_progress_bar_set_show_text(GTK_PROGRESS_BAR(dw->extra_label), TRUE);
+#endif
+       gtk_box_pack_start(GTK_BOX(status_box), dw->extra_label, FALSE, FALSE, PREF_PAD_SPACE);
+       gtk_widget_show(dw->extra_label);
+
+       status_box = pref_box_new(vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
 
        dw->button_thumbs = gtk_check_button_new_with_label(_("Thumbnails"));
        dw->show_thumbs = options->duplicates_thumbnails;
@@ -3425,6 +3640,31 @@ DupeWindow *dupe_window_new()
        gtk_box_pack_start(GTK_BOX(status_box), dw->button_thumbs, FALSE, FALSE, PREF_PAD_SPACE);
        gtk_widget_show(dw->button_thumbs);
 
+       label = gtk_label_new(_("Compare by:"));
+       gtk_box_pack_start(GTK_BOX(status_box), label, FALSE, FALSE, PREF_PAD_SPACE);
+       gtk_widget_show(label);
+
+       dupe_menu_setup(dw);
+       gtk_box_pack_start(GTK_BOX(status_box), dw->combo, FALSE, FALSE, 0);
+       gtk_widget_show(dw->combo);
+
+       label = gtk_label_new(_("Custom Threshold"));
+       gtk_box_pack_start(GTK_BOX(status_box), label, FALSE, FALSE, PREF_PAD_SPACE);
+       gtk_widget_show(label);
+       dw->custom_threshold = gtk_spin_button_new_with_range(1, 100, 1);
+       gtk_widget_set_tooltip_text(GTK_WIDGET(dw->custom_threshold), "Custom similarity threshold");
+       gtk_spin_button_set_value(GTK_SPIN_BUTTON(dw->custom_threshold), options->duplicates_similarity_threshold);
+       g_signal_connect(G_OBJECT(dw->custom_threshold), "value_changed", G_CALLBACK(dupe_window_custom_threshold_cb), dw);
+       gtk_box_pack_start(GTK_BOX(status_box), dw->custom_threshold, FALSE, FALSE, PREF_PAD_SPACE);
+       gtk_widget_show(dw->custom_threshold);
+
+       button = gtk_check_button_new_with_label(_("Sort"));
+       gtk_widget_set_tooltip_text(GTK_WIDGET(button), "Sort by group totals");
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), options->sort_totals);
+       g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(dupe_sort_totals_toggle_cb), dw);
+       gtk_box_pack_start(GTK_BOX(status_box), button, FALSE, FALSE, PREF_PAD_SPACE);
+       gtk_widget_show(button);
+
        dw->button_rotation_invariant = gtk_check_button_new_with_label(_("Ignore Rotation"));
        gtk_widget_set_tooltip_text(GTK_WIDGET(dw->button_rotation_invariant), "Ignore image orientation");
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dw->button_rotation_invariant), options->rot_invariant_sim);
@@ -3437,51 +3677,34 @@ DupeWindow *dupe_window_new()
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), dw->second_set);
        g_signal_connect(G_OBJECT(button), "toggled",
                         G_CALLBACK(dupe_second_set_toggle_cb), dw);
-       gtk_box_pack_end(GTK_BOX(status_box), button, FALSE, FALSE, PREF_PAD_SPACE);
+       gtk_box_pack_start(GTK_BOX(status_box), button, FALSE, FALSE, PREF_PAD_SPACE);
        gtk_widget_show(button);
 
-       status_box = gtk_hbox_new(FALSE, 0);
-       gtk_box_pack_start(GTK_BOX(vbox), status_box, FALSE, FALSE, 0);
-       gtk_widget_show(status_box);
-
-       frame = gtk_frame_new(NULL);
-       DEBUG_NAME(frame);
-       gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
-       gtk_box_pack_start(GTK_BOX(status_box), frame, TRUE, TRUE, 0);
-       gtk_widget_show(frame);
+       button_box = gtk_hbox_new(FALSE, 0);
+       gtk_box_pack_start(GTK_BOX(vbox), button_box, FALSE, FALSE, 0);
+       gtk_widget_show(button_box);
 
-       dw->status_label = gtk_label_new("");
-       gtk_container_add(GTK_CONTAINER(frame), dw->status_label);
-       gtk_widget_show(dw->status_label);
+       hbox = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
+       gtk_button_box_set_layout(GTK_BUTTON_BOX(hbox), GTK_BUTTONBOX_END);
+       gtk_box_set_spacing(GTK_BOX(hbox), PREF_PAD_SPACE);
+       gtk_box_pack_end(GTK_BOX(button_box), hbox, FALSE, FALSE, 0);
+       gtk_widget_show(hbox);
 
-       button = gtk_check_button_new_with_label(_("Sort"));
-       gtk_widget_set_tooltip_text(GTK_WIDGET(button), "Sort by group totals");
-       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), options->sort_totals);
-       g_signal_connect(G_OBJECT(button), "toggled",
-                        G_CALLBACK(dupe_sort_totals_toggle_cb), dw);
-       gtk_box_pack_start(GTK_BOX(status_box), button, FALSE, FALSE, PREF_PAD_SPACE);
+       button = pref_button_new(NULL, GTK_STOCK_HELP, NULL, FALSE, G_CALLBACK(dupe_help_cb), NULL);
+       gtk_container_add(GTK_CONTAINER(hbox), button);
+       gtk_widget_set_can_default(button, TRUE);
        gtk_widget_show(button);
 
-       label = gtk_label_new(_("Custom Threshold"));
-       gtk_box_pack_start(GTK_BOX(status_box), label, FALSE, FALSE, PREF_PAD_SPACE);
-       gtk_widget_show(label);
-       dw->custom_threshold = gtk_spin_button_new_with_range(1, 100, 1);
-       gtk_widget_set_tooltip_text(GTK_WIDGET(dw->custom_threshold), "Custom similarity threshold");
-       gtk_spin_button_set_value(GTK_SPIN_BUTTON(dw->custom_threshold), options->duplicates_similarity_threshold);
-       g_signal_connect(G_OBJECT(dw->custom_threshold), "value_changed",
-                                                                                                       G_CALLBACK(dupe_window_custom_threshold_cb), dw);
-       gtk_box_pack_start(GTK_BOX(status_box), dw->custom_threshold, FALSE, FALSE, PREF_PAD_SPACE);
-       gtk_widget_show(dw->custom_threshold);
-
-       dw->extra_label = gtk_progress_bar_new();
-       gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(dw->extra_label), 0.0);
-#if GTK_CHECK_VERSION(3,0,0)
-       gtk_progress_bar_set_text(GTK_PROGRESS_BAR(dw->extra_label), "");
-       gtk_progress_bar_set_show_text(GTK_PROGRESS_BAR(dw->extra_label), TRUE);
-#endif
-       gtk_box_pack_end(GTK_BOX(status_box), dw->extra_label, FALSE, FALSE, 0);
-       gtk_widget_show(dw->extra_label);
+       button = pref_button_new(NULL, GTK_STOCK_STOP, NULL, FALSE, G_CALLBACK(dupe_check_stop_cb), dw);
+       gtk_container_add(GTK_CONTAINER(hbox), button);
+       gtk_widget_set_can_default(button, TRUE);
+       gtk_widget_show(button);
 
+       button = pref_button_new(NULL, GTK_STOCK_CLOSE, NULL, FALSE, G_CALLBACK(dupe_window_close_cb), dw);
+       gtk_container_add(GTK_CONTAINER(hbox), button);
+       gtk_widget_set_can_default(button, TRUE);
+       gtk_widget_grab_default(button);
+       gtk_widget_show(button);
        dupe_dnd_init(dw);
 
        /* order is important here, dnd_init should be seeing mouse
@@ -3498,6 +3721,9 @@ DupeWindow *dupe_window_new()
 
        gtk_widget_show(dw->window);
 
+       dupe_listview_set_height(dw->listview, dw->show_thumbs);
+       g_signal_emit_by_name(G_OBJECT(dw->combo), "changed");
+
        dupe_window_update_count(dw, TRUE);
        dupe_window_update_progress(dw, NULL, 0.0, FALSE);
 
index 3e97055..c96d60e 100644 (file)
@@ -38,7 +38,8 @@ typedef enum
        DUPE_MATCH_SIM_MED  = 1 << 7,
        DUPE_MATCH_SIM_LOW  = 1 << 8,
        DUPE_MATCH_SIM_CUSTOM = 1 << 9,
-       DUPE_MATCH_NAME_CI = 1 << 10    /* same as name, but case insensitive */
+       DUPE_MATCH_NAME_CI = 1 << 10,   /* same as name, but case insensitive */
+       DUPE_MATCH_ALL = 1 << 11
 } DupeMatchType;
 
 typedef enum
@@ -114,6 +115,9 @@ struct _DupeWindow
 
        ImageLoader *img_loader;
 
+       GtkTreeSortable *sortable;
+       gint set_count;
+
        /* second set comparison stuff */
 
        gboolean second_set;            /* second set enabled ? */