[gthumb] search: added ability to search in multiple folders



commit 699b0b040740d2c47ba67b9f8d397dbfbf51585a
Author: Paolo Bacchilega <paobac src gnome org>
Date:   Sun Oct 20 09:24:30 2019 +0200

    search: added ability to search in multiple folders

 extensions/catalogs/dlg-catalog-properties.c   |   2 +-
 extensions/catalogs/gth-organize-task.c        |   6 +-
 extensions/search/actions.c                    |   5 +-
 extensions/search/callbacks.c                  |  13 +-
 extensions/search/data/ui/search-editor.ui     | 106 +++++------
 extensions/search/gth-search-editor.c          | 194 ++++++++++++++------
 extensions/search/gth-search-source-selector.c | 240 +++++++++++++++++++++++++
 extensions/search/gth-search-source-selector.h |  66 +++++++
 extensions/search/gth-search-source.c          | 198 ++++++++++++++++++++
 extensions/search/gth-search-source.h          |  59 ++++++
 extensions/search/gth-search-task.c            | 135 ++++++++------
 extensions/search/gth-search.c                 |  98 +++++-----
 extensions/search/gth-search.h                 |  12 +-
 extensions/search/meson.build                  |   2 +
 14 files changed, 906 insertions(+), 230 deletions(-)
---
diff --git a/extensions/catalogs/dlg-catalog-properties.c b/extensions/catalogs/dlg-catalog-properties.c
index f7d6be70..1622b1d2 100644
--- a/extensions/catalogs/dlg-catalog-properties.c
+++ b/extensions/catalogs/dlg-catalog-properties.c
@@ -200,7 +200,7 @@ dlg_catalog_properties (GthBrowser  *browser,
        data->dialog = g_object_new (GTK_TYPE_DIALOG,
                                     "title", _("Properties"),
                                     "transient-for", GTK_WINDOW (browser),
-                                    "modal", TRUE,
+                                    "modal", FALSE,
                                     "destroy-with-parent", FALSE,
                                     "use-header-bar", _gtk_settings_get_dialogs_use_header (),
                                     NULL);
diff --git a/extensions/catalogs/gth-organize-task.c b/extensions/catalogs/gth-organize-task.c
index 071e642a..d91595dc 100644
--- a/extensions/catalogs/gth-organize-task.c
+++ b/extensions/catalogs/gth-organize-task.c
@@ -280,8 +280,7 @@ add_catalog_for_date (GthOrganizeTask *self,
                        catalog_file = gth_catalog_get_file_for_date (date_time, ".search");
 
                        catalog = (GthCatalog *) gth_search_new ();
-                       gth_search_set_folder (GTH_SEARCH (catalog), self->priv->folder);
-                       gth_search_set_recursive (GTH_SEARCH (catalog), self->priv->recursive);
+                       gth_search_set_source (GTH_SEARCH (catalog), self->priv->folder, 
self->priv->recursive);
 
                        date_test = gth_main_get_registered_object (GTH_TYPE_TEST, (self->priv->group_policy 
== GTH_GROUP_POLICY_MODIFIED_DATE) ? "file::mtime" : "Embedded::Photo::DateTimeOriginal");
                        gth_test_simple_set_data_as_date (GTH_TEST_SIMPLE (date_test), date_time->date);
@@ -378,8 +377,7 @@ add_catalog_for_tag (GthOrganizeTask *self,
                        catalog_file = gth_catalog_get_file_for_tag (tag, ".search");
 
                        catalog = (GthCatalog *) gth_search_new ();
-                       gth_search_set_folder (GTH_SEARCH (catalog), self->priv->folder);
-                       gth_search_set_recursive (GTH_SEARCH (catalog), self->priv->recursive);
+                       gth_search_set_source (GTH_SEARCH (catalog), self->priv->folder, 
self->priv->recursive);
 
                        tag_test = gth_main_get_registered_object (GTH_TYPE_TEST, (self->priv->group_policy 
== GTH_GROUP_POLICY_TAG) ? "comment::category" : "general::tags");
                        gth_test_category_set (GTH_TEST_CATEGORY (tag_test), GTH_TEST_OP_CONTAINS, FALSE, 
tag);
diff --git a/extensions/search/actions.c b/extensions/search/actions.c
index 95fc70f7..512db192 100644
--- a/extensions/search/actions.c
+++ b/extensions/search/actions.c
@@ -73,8 +73,7 @@ gth_browser_activate_find (GSimpleAction *action,
        GtkWidget  *dialog;
 
        search = gth_search_new ();
-       gth_search_set_folder (search, gth_browser_get_location (browser));
-       gth_search_set_recursive (search, TRUE);
+       gth_search_set_source (search, gth_browser_get_location (browser), TRUE);
 
        dialog = gth_search_editor_dialog_new (_("Find"), search, GTK_WINDOW (browser));
        gtk_dialog_add_button (GTK_DIALOG (dialog), _GTK_LABEL_CANCEL, GTK_RESPONSE_CANCEL);
@@ -86,7 +85,7 @@ gth_browser_activate_find (GSimpleAction *action,
                          G_CALLBACK (search_editor_dialog__response_cb),
                          browser);
 
-       gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+       gtk_window_set_modal (GTK_WINDOW (dialog), FALSE);
        gtk_window_present (GTK_WINDOW (dialog));
        gth_search_editor_dialog_focus_first_rule (GTH_SEARCH_EDITOR_DIALOG (dialog));
 
diff --git a/extensions/search/callbacks.c b/extensions/search/callbacks.c
index c5eb180c..a5315ce5 100644
--- a/extensions/search/callbacks.c
+++ b/extensions/search/callbacks.c
@@ -177,8 +177,7 @@ search__dlg_catalog_properties_save (GtkBuilder  *builder,
        search = gth_search_editor_get_search (GTH_SEARCH_EDITOR (g_object_get_data (G_OBJECT(builder), 
"search_editor")), NULL);
        if (search != NULL) {
                g_file_info_set_attribute_boolean (file_data->info, "gthumb::search-modified", ! 
gth_search_equal (GTH_SEARCH (catalog), search));
-               gth_search_set_folder (GTH_SEARCH (catalog), gth_search_get_folder (search));
-               gth_search_set_recursive (GTH_SEARCH (catalog), gth_search_is_recursive (search));
+               gth_search_set_sources (GTH_SEARCH (catalog), gth_search_get_sources (search));
                gth_search_set_test (GTH_SEARCH (catalog), gth_search_get_test (search));
        }
 }
@@ -250,8 +249,9 @@ search__gth_organize_task_create_catalog (GthGroupPolicyData *data)
                        GthTest *test_chain;
 
                        data->catalog = (GthCatalog *) gth_search_new ();
-                       gth_search_set_folder (GTH_SEARCH (data->catalog), gth_organize_task_get_folder 
(data->task));
-                       gth_search_set_recursive (GTH_SEARCH (data->catalog), gth_organize_task_get_recursive 
(data->task));
+                       gth_search_set_source (GTH_SEARCH (data->catalog),
+                                              gth_organize_task_get_folder (data->task),
+                                              gth_organize_task_get_recursive (data->task));
 
                        date_test = gth_main_get_registered_object (GTH_TYPE_TEST, (policy == 
GTH_GROUP_POLICY_MODIFIED_DATE) ? "file::mtime" : "Embedded::Photo::DateTimeOriginal");
                        gth_test_simple_set_data_as_date (GTH_TEST_SIMPLE (date_test), data->date_time->date);
@@ -302,8 +302,9 @@ search__gth_organize_task_create_catalog (GthGroupPolicyData *data)
                        GthTest *test_chain;
 
                        data->catalog = (GthCatalog *) gth_search_new ();
-                       gth_search_set_folder (GTH_SEARCH (data->catalog), gth_organize_task_get_folder 
(data->task));
-                       gth_search_set_recursive (GTH_SEARCH (data->catalog), gth_organize_task_get_recursive 
(data->task));
+                       gth_search_set_source (GTH_SEARCH (data->catalog),
+                                              gth_organize_task_get_folder (data->task),
+                                              gth_organize_task_get_recursive (data->task));
 
                        tag_test = gth_main_get_registered_object (GTH_TYPE_TEST, (policy == 
GTH_GROUP_POLICY_TAG) ? "comment::category" : "general::tags");
                        gth_test_category_set (GTH_TEST_CATEGORY (tag_test), GTH_TEST_OP_CONTAINS, FALSE, 
data->tag);
diff --git a/extensions/search/data/ui/search-editor.ui b/extensions/search/data/ui/search-editor.ui
index 370cddaf..d716b298 100644
--- a/extensions/search/data/ui/search-editor.ui
+++ b/extensions/search/data/ui/search-editor.ui
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.19.0 -->
+<!-- Generated with glade 3.22.1 -->
 <interface>
   <requires lib="gtk+" version="3.12"/>
   <object class="GtkBox" id="search_editor">
@@ -9,26 +9,26 @@
     <property name="orientation">vertical</property>
     <property name="spacing">6</property>
     <child>
-      <object class="GtkBox" id="hbox2">
+      <object class="GtkGrid">
         <property name="visible">True</property>
         <property name="can_focus">False</property>
-        <property name="spacing">6</property>
+        <property name="row_spacing">6</property>
+        <property name="column_spacing">6</property>
         <child>
-          <object class="GtkLabel" id="start_at_label">
+          <object class="GtkLabel" id="match_label">
             <property name="visible">True</property>
             <property name="can_focus">False</property>
-            <property name="label" translatable="yes">Start _at:</property>
-            <property name="use_underline">True</property>
+            <property name="label" translatable="yes">_Match:</property>
+            <property name="use_markup">True</property>
             <property name="xalign">0</property>
           </object>
           <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">0</property>
+            <property name="left_attach">0</property>
+            <property name="top_attach">1</property>
           </packing>
         </child>
         <child>
-          <object class="GtkBox" id="location_box">
+          <object class="GtkBox" id="match_type_combobox_box">
             <property name="visible">True</property>
             <property name="can_focus">False</property>
             <child>
@@ -36,72 +36,55 @@
             </child>
           </object>
           <packing>
-            <property name="expand">True</property>
-            <property name="fill">True</property>
-            <property name="position">1</property>
+            <property name="left_attach">1</property>
+            <property name="top_attach">1</property>
           </packing>
         </child>
         <child>
-          <object class="GtkCheckButton" id="include_subfolders_checkbutton">
-            <property name="label" translatable="yes">_Include sub-folders</property>
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="receives_default">False</property>
-            <property name="use_underline">True</property>
-            <property name="xalign">0.5</property>
-            <property name="draw_indicator">True</property>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">2</property>
-          </packing>
-        </child>
-      </object>
-      <packing>
-        <property name="expand">False</property>
-        <property name="fill">True</property>
-        <property name="position">0</property>
-      </packing>
-    </child>
-    <child>
-      <object class="GtkBox" id="hbox1">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="spacing">6</property>
-        <child>
-          <object class="GtkLabel" id="match_label">
+          <object class="GtkBox" id="sources_box">
             <property name="visible">True</property>
             <property name="can_focus">False</property>
-            <property name="label" translatable="yes">_Match:</property>
-            <property name="use_markup">True</property>
-            <property name="xalign">0</property>
+            <property name="hexpand">True</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">6</property>
+            <child>
+              <placeholder/>
+            </child>
+            <child>
+              <placeholder/>
+            </child>
+            <child>
+              <placeholder/>
+            </child>
+            <child>
+              <placeholder/>
+            </child>
           </object>
           <packing>
-            <property name="expand">False</property>
-            <property name="fill">False</property>
-            <property name="position">0</property>
+            <property name="left_attach">1</property>
+            <property name="top_attach">0</property>
           </packing>
         </child>
         <child>
-          <object class="GtkBox" id="match_type_combobox_box">
+          <object class="GtkLabel" id="start_at_label1">
             <property name="visible">True</property>
             <property name="can_focus">False</property>
-            <child>
-              <placeholder/>
-            </child>
+            <property name="margin_top">6</property>
+            <property name="label" translatable="yes">Start _at:</property>
+            <property name="use_underline">True</property>
+            <property name="xalign">0</property>
+            <property name="yalign">0</property>
           </object>
           <packing>
-            <property name="expand">True</property>
-            <property name="fill">True</property>
-            <property name="position">1</property>
+            <property name="left_attach">0</property>
+            <property name="top_attach">0</property>
           </packing>
         </child>
       </object>
       <packing>
         <property name="expand">False</property>
         <property name="fill">True</property>
-        <property name="position">1</property>
+        <property name="position">0</property>
       </packing>
     </child>
     <child>
@@ -109,13 +92,14 @@
         <property name="visible">True</property>
         <property name="can_focus">False</property>
         <property name="margin_top">10</property>
+        <property name="margin_bottom">10</property>
         <property name="label" translatable="yes">Rules:</property>
         <property name="xalign">0</property>
       </object>
       <packing>
         <property name="expand">False</property>
         <property name="fill">True</property>
-        <property name="position">2</property>
+        <property name="position">1</property>
       </packing>
     </child>
     <child>
@@ -131,14 +115,8 @@
       <packing>
         <property name="expand">False</property>
         <property name="fill">True</property>
-        <property name="position">3</property>
+        <property name="position">2</property>
       </packing>
     </child>
   </object>
-  <object class="GtkSizeGroup" id="labels_sizegroup">
-    <widgets>
-      <widget name="start_at_label"/>
-      <widget name="match_label"/>
-    </widgets>
-  </object>
 </interface>
diff --git a/extensions/search/gth-search-editor.c b/extensions/search/gth-search-editor.c
index c1f42b0a..0fdc8bd0 100644
--- a/extensions/search/gth-search-editor.c
+++ b/extensions/search/gth-search-editor.c
@@ -24,6 +24,7 @@
 #include <gtk/gtk.h>
 #include <gthumb.h>
 #include "gth-search-editor.h"
+#include "gth-search-source-selector.h"
 
 
 #define GET_WIDGET(name) _gtk_builder_get_widget (self->priv->builder, (name))
@@ -31,7 +32,6 @@
 
 struct _GthSearchEditorPrivate {
        GtkBuilder *builder;
-       GtkWidget  *location_chooser;
        GtkWidget  *match_type_combobox;
 };
 
@@ -69,7 +69,6 @@ gth_search_editor_init (GthSearchEditor *dialog)
 {
        dialog->priv = gth_search_editor_get_instance_private (dialog);
        dialog->priv->builder = NULL;
-       dialog->priv->location_chooser = NULL;
        dialog->priv->match_type_combobox = NULL;
        gtk_orientable_set_orientation (GTK_ORIENTABLE (dialog), GTK_ORIENTATION_VERTICAL);
 }
@@ -78,15 +77,26 @@ gth_search_editor_init (GthSearchEditor *dialog)
 static void
 update_sensitivity (GthSearchEditor *self)
 {
-       GList *test_selectors;
+       GList *selectors;
        int    more_selectors;
        GList *scan;
 
-       test_selectors = gtk_container_get_children (GTK_CONTAINER (GET_WIDGET ("tests_box")));
-       more_selectors = (test_selectors != NULL) && (test_selectors->next != NULL);
-       for (scan = test_selectors; scan; scan = scan->next)
+       /* sources */
+
+       selectors = gtk_container_get_children (GTK_CONTAINER (GET_WIDGET ("sources_box")));
+       more_selectors = (selectors != NULL) && (selectors->next != NULL);
+       for (scan = selectors; scan; scan = scan->next)
+               gth_search_source_selector_can_remove (GTH_SEARCH_SOURCE_SELECTOR (scan->data), 
more_selectors);
+       g_list_free (selectors);
+
+       /* tests */
+
+       selectors = gtk_container_get_children (GTK_CONTAINER (GET_WIDGET ("tests_box")));
+       more_selectors = (selectors != NULL) && (selectors->next != NULL);
+       for (scan = selectors; scan; scan = scan->next)
                gth_test_selector_can_remove (GTH_TEST_SELECTOR (scan->data), more_selectors);
-       g_list_free (test_selectors);
+       g_list_free (selectors);
+
 }
 
 
@@ -102,13 +112,6 @@ gth_search_editor_construct (GthSearchEditor *self,
        gtk_container_set_border_width (GTK_CONTAINER (content), 0);
        gtk_box_pack_start (GTK_BOX (self), content, TRUE, TRUE, 0);
 
-       self->priv->location_chooser = g_object_new (GTH_TYPE_LOCATION_CHOOSER,
-                                                    "show-entry-points", TRUE,
-                                                    "relief", GTK_RELIEF_NORMAL,
-                                                    NULL);
-       gtk_widget_show (self->priv->location_chooser);
-       gtk_box_pack_start (GTK_BOX (GET_WIDGET ("location_box")), self->priv->location_chooser, TRUE, TRUE, 
0);
-
        self->priv->match_type_combobox = gtk_combo_box_text_new ();
        _gtk_combo_box_append_texts (GTK_COMBO_BOX_TEXT (self->priv->match_type_combobox),
                                     _("all the following rules"),
@@ -121,7 +124,6 @@ gth_search_editor_construct (GthSearchEditor *self,
 
        gtk_label_set_use_underline (GTK_LABEL (GET_WIDGET ("match_label")), TRUE);
        gtk_label_set_mnemonic_widget (GTK_LABEL (GET_WIDGET ("match_label")), 
self->priv->match_type_combobox);
-       gtk_label_set_mnemonic_widget (GTK_LABEL (GET_WIDGET ("start_at_label")), 
self->priv->location_chooser);
 
        gth_search_editor_set_search (self, search);
 }
@@ -194,17 +196,71 @@ _gth_search_editor_add_test (GthSearchEditor *self,
 }
 
 
+static GtkWidget *
+_gth_search_editor_add_source (GthSearchEditor *self,
+                              int              pos);
+
+
 static void
-_gth_search_editor_set_new_search (GthSearchEditor *self)
+test_selector_add_source_cb (GthTestSelector *selector,
+                            GthSearchEditor *self)
 {
-       GFile *home_location;
+       int pos;
 
-       home_location = g_file_new_for_uri (get_home_uri ());
-       gth_location_chooser_set_current (GTH_LOCATION_CHOOSER (self->priv->location_chooser), home_location);
-       g_object_unref (home_location);
+       pos = _gtk_container_get_pos (GTK_CONTAINER (GET_WIDGET ("sources_box")), (GtkWidget*) selector);
+       _gth_search_editor_add_source (self, pos == -1 ? -1 : pos + 1);
+       update_sensitivity (self);
+}
 
-       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("include_subfolders_checkbutton")), 
TRUE);
-       _gtk_container_remove_children (GTK_CONTAINER (GET_WIDGET ("tests_box")), NULL, NULL);
+
+static void
+test_selector_remove_source_cb (GthTestSelector *selector,
+                               GthSearchEditor *self)
+{
+       gtk_container_remove (GTK_CONTAINER (GET_WIDGET ("sources_box")), (GtkWidget*) selector);
+       update_sensitivity (self);
+}
+
+
+static GtkWidget *
+_gth_search_editor_add_source (GthSearchEditor *self,
+                              int              pos)
+{
+       GthSearchSource *source;
+       GtkWidget       *window;
+       GtkWidget       *source_selector;
+
+       source = NULL;
+       window = gtk_widget_get_toplevel (GTK_WIDGET (self));
+       if (GTK_IS_WINDOW (window))
+               window = (GtkWidget *) gtk_window_get_transient_for (GTK_WINDOW (window));
+       if (GTK_IS_WINDOW (window) && GTH_IS_BROWSER (window)) {
+               source = gth_search_source_new ();
+               gth_search_source_set_folder (source, gth_browser_get_location (GTH_BROWSER (window)));
+               gth_search_source_set_recursive (source, TRUE);
+       }
+       source_selector = gth_search_source_selector_new_with_source (source);
+       gtk_widget_show (source_selector);
+
+       g_signal_connect (G_OBJECT (source_selector),
+                         "add_source",
+                         G_CALLBACK (test_selector_add_source_cb),
+                         self);
+       g_signal_connect (G_OBJECT (source_selector),
+                         "remove_source",
+                         G_CALLBACK (test_selector_remove_source_cb),
+                         self);
+
+       gtk_box_pack_start (GTK_BOX (GET_WIDGET ("sources_box")), source_selector, FALSE, FALSE, 0);
+
+       if (pos >= 0)
+               gtk_box_reorder_child (GTK_BOX (GET_WIDGET ("sources_box")),
+                                      source_selector,
+                                      pos);
+
+       _g_object_unref (source);
+
+       return source_selector;
 }
 
 
@@ -212,40 +268,60 @@ void
 gth_search_editor_set_search (GthSearchEditor *self,
                              GthSearch       *search)
 {
-       GthTestChain *test;
-       GthMatchType  match_type;
+       int           n_sources;
+       int           n_tests;
+       GthMatchType  match_type = GTH_MATCH_TYPE_NONE;
+       GList        *scan;
 
-       _gth_search_editor_set_new_search (self);
+       /* sources */
 
-       if (search == NULL) {
-               _gth_search_editor_add_test (self, -1);
-               update_sensitivity (self);
-               return;
+       _gtk_container_remove_children (GTK_CONTAINER (GET_WIDGET ("sources_box")), NULL, NULL);
+       n_sources = 0;
+
+       if (search != NULL) {
+               for (scan = gth_search_get_sources (search); scan; scan = scan->next) {
+                       GthSearchSource *source = scan->data;
+                       GtkWidget       *source_selector;
+
+                       source_selector = _gth_search_editor_add_source (self, -1);
+                       gth_search_source_selector_set_source (GTH_SEARCH_SOURCE_SELECTOR (source_selector), 
source);
+                       n_sources += 1;
+               }
        }
 
-       gth_location_chooser_set_current (GTH_LOCATION_CHOOSER (self->priv->location_chooser), 
gth_search_get_folder (search));
-       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("include_subfolders_checkbutton")), 
gth_search_is_recursive (search));
+       /* tests */
 
-       test = gth_search_get_test (search);
-       match_type = (test != NULL) ? gth_test_chain_get_match_type (test) : GTH_MATCH_TYPE_NONE;
        _gtk_container_remove_children (GTK_CONTAINER (GET_WIDGET ("tests_box")), NULL, NULL);
-       if (match_type != GTH_MATCH_TYPE_NONE) {
-               GList *tests;
-               GList *scan;
+       n_tests = 0;
+
+       if (search != NULL) {
+               GthTestChain *test;
+
+               test = gth_search_get_test (search);
+               if (test != NULL)
+                       match_type = gth_test_chain_get_match_type (test);
+
+               if (match_type != GTH_MATCH_TYPE_NONE) {
+                       GList *tests;
+                       GList *scan;
 
-               tests = gth_test_chain_get_tests (test);
-               for (scan = tests; scan; scan = scan->next) {
-                       GthTest   *test = scan->data;
-                       GtkWidget *test_selector;
+                       tests = gth_test_chain_get_tests (test);
+                       for (scan = tests; scan; scan = scan->next) {
+                               GthTest   *test = scan->data;
+                               GtkWidget *test_selector;
 
-                       test_selector = _gth_search_editor_add_test (self, -1);
-                       gth_test_selector_set_test (GTH_TEST_SELECTOR (test_selector), test);
+                               test_selector = _gth_search_editor_add_test (self, -1);
+                               gth_test_selector_set_test (GTH_TEST_SELECTOR (test_selector), test);
+                               n_tests += 1;
+                       }
+                       _g_object_list_unref (tests);
                }
-               _g_object_list_unref (tests);
        }
-       else
-               _gth_search_editor_add_test (self, -1);
 
+       if (n_sources == 0)
+               _gth_search_editor_add_source (self, -1);
+       if (n_tests == 0)
+               _gth_search_editor_add_test (self, -1);
        gtk_combo_box_set_active (GTK_COMBO_BOX (self->priv->match_type_combobox), (match_type == 
GTH_MATCH_TYPE_ANY) ? 1 : 0);
 
        update_sensitivity (self);
@@ -257,22 +333,31 @@ gth_search_editor_get_search (GthSearchEditor  *self,
                              GError          **error)
 {
        GthSearch *search;
-       GFile     *folder;
+       GList     *sources;
        GthTest   *test;
-       GList     *test_selectors;
+       GList     *selectors;
        GList     *scan;
 
        search = gth_search_new ();
 
-       folder = gth_location_chooser_get_current (GTH_LOCATION_CHOOSER (self->priv->location_chooser));
-       if (folder != NULL)
-               gth_search_set_folder (search, folder);
+       /* sources */
 
-       gth_search_set_recursive (search, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (GET_WIDGET 
("include_subfolders_checkbutton"))));
+       sources = NULL;
+       selectors = gtk_container_get_children (GTK_CONTAINER (GET_WIDGET ("sources_box")));
+       for (scan = selectors; scan; scan = scan->next) {
+               GthSearchSourceSelector *selector = GTH_SEARCH_SOURCE_SELECTOR (scan->data);
+               sources = g_list_prepend (sources, gth_search_source_selector_get_source (selector));
+       }
+       g_list_free (selectors);
+       sources = g_list_reverse (sources);
+       gth_search_set_sources (search, sources);
+       _g_object_list_unref (sources);
+
+       /* tests */
 
        test = gth_test_chain_new (gtk_combo_box_get_active (GTK_COMBO_BOX (self->priv->match_type_combobox)) 
+ 1, NULL);
-       test_selectors = gtk_container_get_children (GTK_CONTAINER (GET_WIDGET ("tests_box")));
-       for (scan = test_selectors; scan; scan = scan->next) {
+       selectors = gtk_container_get_children (GTK_CONTAINER (GET_WIDGET ("tests_box")));
+       for (scan = selectors; scan; scan = scan->next) {
                GthTestSelector *test_selector = GTH_TEST_SELECTOR (scan->data);
                GthTest         *sub_test;
 
@@ -285,8 +370,9 @@ gth_search_editor_get_search (GthSearchEditor  *self,
                gth_test_chain_add_test (GTH_TEST_CHAIN (test), sub_test);
                g_object_unref (sub_test);
        }
-       g_list_free (test_selectors);
+       g_list_free (selectors);
        gth_search_set_test (search, GTH_TEST_CHAIN (test));
+       g_object_unref (test);
 
        return search;
 }
diff --git a/extensions/search/gth-search-source-selector.c b/extensions/search/gth-search-source-selector.c
new file mode 100644
index 00000000..03e564ed
--- /dev/null
+++ b/extensions/search/gth-search-source-selector.c
@@ -0,0 +1,240 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2019 The Free Software Foundation, Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <gtk/gtk.h>
+#include <gthumb.h>
+#include "gth-search-source-selector.h"
+
+
+enum {
+       ADD_SOURCE,
+       REMOVE_SOURCE,
+       LAST_SIGNAL
+};
+
+
+struct _GthSearchSourceSelectorPrivate {
+       GtkWidget *location_chooser;
+       GtkWidget *recursive_checkbutton;
+       GtkWidget *add_button;
+       GtkWidget *remove_button;
+};
+
+
+static guint gth_search_source_selector_signals[LAST_SIGNAL] = { 0 };
+
+
+G_DEFINE_TYPE_WITH_CODE (GthSearchSourceSelector,
+                        gth_search_source_selector,
+                        GTK_TYPE_BOX,
+                        G_ADD_PRIVATE (GthSearchSourceSelector))
+
+
+static void
+gth_search_source_selector_class_init (GthSearchSourceSelectorClass *klass)
+{
+       /* signals */
+
+       gth_search_source_selector_signals[ADD_SOURCE] =
+               g_signal_new ("add-source",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (GthSearchSourceSelectorClass, add_source),
+                             NULL, NULL,
+                             g_cclosure_marshal_VOID__VOID,
+                             G_TYPE_NONE,
+                             0);
+       gth_search_source_selector_signals[REMOVE_SOURCE] =
+               g_signal_new ("remove-source",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (GthSearchSourceSelectorClass, remove_source),
+                             NULL, NULL,
+                             g_cclosure_marshal_VOID__VOID,
+                             G_TYPE_NONE,
+                             0);
+}
+
+
+static void
+gth_search_source_selector_init (GthSearchSourceSelector *self)
+{
+       self->priv = gth_search_source_selector_get_instance_private (self);
+       self->priv->location_chooser = NULL;
+       self->priv->recursive_checkbutton = NULL;
+       self->priv->add_button = NULL;
+       self->priv->remove_button = NULL;
+       gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_HORIZONTAL);
+}
+
+
+static void
+add_button_clicked_cb (GtkButton               *button,
+                      GthSearchSourceSelector *self)
+{
+       g_signal_emit (self, gth_search_source_selector_signals[ADD_SOURCE], 0);
+}
+
+
+static void
+remove_button_clicked_cb (GtkButton               *button,
+                         GthSearchSourceSelector *self)
+{
+       g_signal_emit (self, gth_search_source_selector_signals[REMOVE_SOURCE], 0);
+}
+
+
+static void
+gth_search_source_selector_construct (GthSearchSourceSelector *self)
+{
+       GtkWidget *vbox;
+       GtkWidget *hbox;
+
+       gtk_box_set_spacing (GTK_BOX (self), 6);
+       gtk_container_set_border_width (GTK_CONTAINER (self), 0);
+
+       self->priv->location_chooser = g_object_new (GTH_TYPE_LOCATION_CHOOSER,
+                                                    "show-entry-points", TRUE,
+                                                    "relief", GTK_RELIEF_NORMAL,
+                                                    NULL);
+       gtk_widget_show (self->priv->location_chooser);
+
+       self->priv->recursive_checkbutton = gtk_check_button_new_with_mnemonic(_("_Include sub-folders"));
+       gtk_widget_show (self->priv->recursive_checkbutton);
+
+       /* add/remove buttons */
+
+       self->priv->add_button = gtk_button_new_from_icon_name ("list-add-symbolic", GTK_ICON_SIZE_BUTTON);
+       gtk_button_set_relief (GTK_BUTTON (self->priv->add_button), GTK_RELIEF_NONE);
+       gtk_widget_set_tooltip_text (self->priv->add_button, _("Add another location"));
+       gtk_widget_show_all (self->priv->add_button);
+
+       g_signal_connect (G_OBJECT (self->priv->add_button),
+                         "clicked",
+                         G_CALLBACK (add_button_clicked_cb),
+                         self);
+
+       self->priv->remove_button = gtk_button_new_from_icon_name ("list-remove-symbolic", 
GTK_ICON_SIZE_BUTTON);
+       gtk_button_set_relief (GTK_BUTTON (self->priv->remove_button), GTK_RELIEF_NONE);
+       gtk_widget_set_tooltip_text (self->priv->remove_button, _("Remove"));
+       gtk_widget_show_all (self->priv->remove_button);
+
+       g_signal_connect (G_OBJECT (self->priv->remove_button),
+                         "clicked",
+                         G_CALLBACK (remove_button_clicked_cb),
+                         self);
+
+       /**/
+
+       vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+       gtk_widget_show (vbox);
+
+       hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+       gtk_widget_show (hbox);
+
+       gtk_box_pack_start (GTK_BOX (hbox), self->priv->location_chooser, FALSE, FALSE, 0);
+       gtk_box_pack_start (GTK_BOX (hbox), self->priv->recursive_checkbutton, FALSE, FALSE, 0);
+       gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, FALSE, 0);
+       gtk_box_pack_start (GTK_BOX (self), vbox, FALSE, FALSE, 0);
+
+       gtk_box_pack_end (GTK_BOX (self), self->priv->add_button, FALSE, FALSE, 0);
+       gtk_box_pack_end (GTK_BOX (self), self->priv->remove_button, FALSE, FALSE, 0);
+}
+
+
+GtkWidget *
+gth_search_source_selector_new (void)
+{
+       GthSearchSourceSelector *self;
+
+       self = g_object_new (GTH_TYPE_SEARCH_SOURCE_SELECTOR, NULL);
+       gth_search_source_selector_construct (self);
+       gth_search_source_selector_set_source (self, NULL);
+
+       return (GtkWidget *) self;
+}
+
+
+GtkWidget *
+gth_search_source_selector_new_with_source (GthSearchSource *source)
+{
+       GthSearchSourceSelector *self;
+
+       self = g_object_new (GTH_TYPE_SEARCH_SOURCE_SELECTOR, NULL);
+       gth_search_source_selector_construct (self);
+       gth_search_source_selector_set_source (self, source);
+
+       return (GtkWidget *) self;
+}
+
+
+void
+gth_search_source_selector_set_source (GthSearchSourceSelector *self,
+                                      GthSearchSource         *source)
+{
+       GFile    *folder;
+       gboolean  recursive;
+
+       if (source != NULL) {
+               folder = _g_object_ref (gth_search_source_get_folder (source));
+               recursive = gth_search_source_is_recursive (source);
+       }
+       else {
+               folder = NULL;
+               recursive = TRUE;
+       }
+
+       if (folder == NULL)
+               folder = g_file_new_for_uri (get_home_uri ());
+
+       gth_location_chooser_set_current (GTH_LOCATION_CHOOSER (self->priv->location_chooser), folder);
+       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->priv->recursive_checkbutton), recursive);
+}
+
+
+GthSearchSource *
+gth_search_source_selector_get_source (GthSearchSourceSelector *self)
+{
+       GthSearchSource *source;
+
+       source = gth_search_source_new ();
+       gth_search_source_set_folder (source, gth_location_chooser_get_current (GTH_LOCATION_CHOOSER 
(self->priv->location_chooser)));
+       gth_search_source_set_recursive (source, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON 
(self->priv->recursive_checkbutton)));
+
+       return source;
+}
+
+
+void
+gth_search_source_selector_can_remove (GthSearchSourceSelector *self,
+                                      gboolean                 value)
+{
+       gtk_widget_set_sensitive (self->priv->remove_button, value);
+}
+
+
+void
+gth_search_source_selector_focus (GthSearchSourceSelector *self)
+{
+       gtk_widget_focus (self->priv->location_chooser);
+}
diff --git a/extensions/search/gth-search-source-selector.h b/extensions/search/gth-search-source-selector.h
new file mode 100644
index 00000000..8cd82d38
--- /dev/null
+++ b/extensions/search/gth-search-source-selector.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2019 The Free Software Foundation, Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GTH_SEARCH_SOURCE_SELECTOR_H
+#define GTH_SEARCH_SOURCE_SELECTOR_H
+
+#include <gtk/gtk.h>
+#include "gth-search-source.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_SEARCH_SOURCE_SELECTOR            (gth_search_source_selector_get_type ())
+#define GTH_SEARCH_SOURCE_SELECTOR(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
GTH_TYPE_SEARCH_SOURCE_SELECTOR, GthSearchSourceSelector))
+#define GTH_SEARCH_SOURCE_SELECTOR_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), 
GTH_TYPE_SEARCH_SOURCE_SELECTOR, GthSearchSourceSelectorClass))
+#define GTH_IS_SEARCH_SOURCE_SELECTOR(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
GTH_TYPE_SEARCH_SOURCE_SELECTOR))
+#define GTH_IS_SEARCH_SOURCE_SELECTOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), 
GTH_TYPE_SEARCH_SOURCE_SELECTOR))
+#define GTH_SEARCH_SOURCE_SELECTOR_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), 
GTH_TYPE_SEARCH_SOURCE_SELECTOR, GthSearchSourceSelectorClass))
+
+typedef struct _GthSearchSourceSelector        GthSearchSourceSelector;
+typedef struct _GthSearchSourceSelectorClass   GthSearchSourceSelectorClass;
+typedef struct _GthSearchSourceSelectorPrivate GthSearchSourceSelectorPrivate;
+
+struct _GthSearchSourceSelector {
+       GtkBox parent_instance;
+       GthSearchSourceSelectorPrivate * priv;
+};
+
+struct _GthSearchSourceSelectorClass {
+       GtkBoxClass parent_class;
+
+       void (*add_source)    (GthSearchSourceSelector *selector);
+       void (*remove_source) (GthSearchSourceSelector *selector);
+};
+
+GType          gth_search_source_selector_get_type             (void);
+GtkWidget *    gth_search_source_selector_new                  (void);
+GtkWidget *    gth_search_source_selector_new_with_source      (GthSearchSource                *source);
+void           gth_search_source_selector_set_source           (GthSearchSourceSelector        *selector,
+                                                                GthSearchSource                *source);
+GthSearchSource *
+               gth_search_source_selector_get_source           (GthSearchSourceSelector        *selector);
+void           gth_search_source_selector_can_remove           (GthSearchSourceSelector        *selector,
+                                                                gboolean                        value);
+void           gth_search_source_selector_focus                (GthSearchSourceSelector        *self);
+
+G_END_DECLS
+
+#endif /* GTH_SEARCH_SOURCE_SELECTOR_H */
diff --git a/extensions/search/gth-search-source.c b/extensions/search/gth-search-source.c
new file mode 100644
index 00000000..13ea82fe
--- /dev/null
+++ b/extensions/search/gth-search-source.c
@@ -0,0 +1,198 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2019 Free Software Foundation, Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <gthumb.h>
+#include "gth-search-source.h"
+
+
+static void gth_search_source_dom_domizable_interface_init (DomDomizableInterface *iface);
+static void gth_search_source_gth_duplicable_interface_init (GthDuplicableInterface *iface);
+
+
+struct _GthSearchSourcePrivate {
+       GFile    *folder;
+       gboolean  recursive;
+};
+
+
+G_DEFINE_TYPE_WITH_CODE (GthSearchSource,
+                        gth_search_source,
+                        G_TYPE_OBJECT,
+                        G_ADD_PRIVATE (GthSearchSource)
+                        G_IMPLEMENT_INTERFACE (DOM_TYPE_DOMIZABLE,
+                                               gth_search_source_dom_domizable_interface_init)
+                        G_IMPLEMENT_INTERFACE (GTH_TYPE_DUPLICABLE,
+                                               gth_search_source_gth_duplicable_interface_init))
+
+
+static DomDomizableInterface  *dom_domizable_parent_iface = NULL;
+static GthDuplicableInterface *gth_duplicable_parent_iface = NULL;
+
+
+static DomElement*
+gth_search_source_real_create_element (DomDomizable *base,
+                                      DomDocument  *doc)
+{
+       GthSearchSource *self = GTH_SEARCH_SOURCE (base);
+       char            *uri;
+       DomElement      *element;
+
+       g_return_val_if_fail (DOM_IS_DOCUMENT (doc), NULL);
+
+       uri = g_file_get_uri (self->priv->folder);
+       element = dom_document_create_element (doc, "source",
+                                              "uri", uri,
+                                              "recursive", (self->priv->recursive ? "true" : "false"),
+                                              NULL);
+       g_free (uri);
+
+       return element;
+}
+
+
+static void
+gth_search_source_real_load_from_element (DomDomizable *base,
+                                         DomElement   *element)
+{
+       GthSearchSource *self = GTH_SEARCH_SOURCE (base);
+       GFile           *folder;
+
+       g_return_if_fail (DOM_IS_ELEMENT (element));
+       g_return_if_fail (g_strcmp0 (element->tag_name, "source") == 0);
+
+       folder = g_file_new_for_uri (dom_element_get_attribute (element, "uri"));
+       gth_search_source_set_folder (self, folder);
+       g_object_unref (folder);
+
+       gth_search_source_set_recursive (self, (g_strcmp0 (dom_element_get_attribute (element, "recursive"), 
"true") == 0));
+}
+
+
+static GObject *
+gth_search_source_real_duplicate (GthDuplicable *duplicable)
+{
+       GthSearchSource *source = GTH_SEARCH_SOURCE (duplicable);
+       GthSearchSource *new_source;
+
+       new_source = gth_search_source_new ();
+
+       gth_search_source_set_folder (new_source, gth_search_source_get_folder (source));
+       gth_search_source_set_recursive (new_source, gth_search_source_is_recursive (source));
+
+       return (GObject *) new_source;
+}
+
+
+static void
+gth_search_source_finalize (GObject *object)
+{
+       GthSearchSource *source;
+
+       source = GTH_SEARCH_SOURCE (object);
+
+       if (source->priv->folder != NULL)
+               g_object_unref (source->priv->folder);
+
+       G_OBJECT_CLASS (gth_search_source_parent_class)->finalize (object);
+}
+
+
+static void
+gth_search_source_class_init (GthSearchSourceClass *class)
+{
+       GObjectClass *object_class;
+
+       object_class = G_OBJECT_CLASS (class);
+       object_class->finalize = gth_search_source_finalize;
+}
+
+
+static void
+gth_search_source_dom_domizable_interface_init (DomDomizableInterface *iface)
+{
+       dom_domizable_parent_iface = g_type_interface_peek_parent (iface);
+       iface->create_element = gth_search_source_real_create_element;
+       iface->load_from_element = gth_search_source_real_load_from_element;
+}
+
+
+static void
+gth_search_source_gth_duplicable_interface_init (GthDuplicableInterface *iface)
+{
+       gth_duplicable_parent_iface = g_type_interface_peek_parent (iface);
+       iface->duplicate = gth_search_source_real_duplicate;
+}
+
+
+static void
+gth_search_source_init (GthSearchSource *source)
+{
+       source->priv = gth_search_source_get_instance_private (source);
+       source->priv->folder = NULL;
+       source->priv->recursive = FALSE;
+}
+
+
+GthSearchSource *
+gth_search_source_new (void)
+{
+       return (GthSearchSource *) g_object_new (GTH_TYPE_SEARCH_SOURCE, NULL);
+}
+
+
+void
+gth_search_source_set_folder (GthSearchSource *source,
+                             GFile           *folder)
+{
+       _g_object_ref (folder);
+
+       if (source->priv->folder != NULL) {
+               g_object_unref (source->priv->folder);
+               source->priv->folder = NULL;
+       }
+
+       if (folder != NULL)
+               source->priv->folder = folder;
+}
+
+
+GFile *
+gth_search_source_get_folder (GthSearchSource *source)
+{
+       return source->priv->folder;
+}
+
+
+void
+gth_search_source_set_recursive (GthSearchSource *source,
+                                gboolean         recursive)
+{
+       source->priv->recursive = recursive;
+}
+
+
+gboolean
+gth_search_source_is_recursive (GthSearchSource *source)
+{
+       return source->priv->recursive;
+}
diff --git a/extensions/search/gth-search-source.h b/extensions/search/gth-search-source.h
new file mode 100644
index 00000000..b4398d2b
--- /dev/null
+++ b/extensions/search/gth-search-source.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2019 Free Software Foundation, Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GTH_SEARCH_SOURCE_H
+#define GTH_SEARCH_SOURCE_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#define GTH_TYPE_SEARCH_SOURCE         (gth_search_source_get_type ())
+#define GTH_SEARCH_SOURCE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GTH_TYPE_SEARCH_SOURCE, 
GthSearchSource))
+#define GTH_SEARCH_SOURCE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), GTH_TYPE_SEARCH_SOURCE, 
GthSearchSourceClass))
+#define GTH_IS_SEARCH_SOURCE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTH_TYPE_SEARCH_SOURCE))
+#define GTH_IS_SEARCH_SOURCE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GTH_TYPE_SEARCH_SOURCE))
+#define GTH_SEARCH_SOURCE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GTH_TYPE_SEARCH_SOURCE, 
GthSearchSourceClass))
+
+typedef struct _GthSearchSource         GthSearchSource;
+typedef struct _GthSearchSourcePrivate  GthSearchSourcePrivate;
+typedef struct _GthSearchSourceClass    GthSearchSourceClass;
+
+struct _GthSearchSource
+{
+       GObject __parent;
+       GthSearchSourcePrivate *priv;
+};
+
+struct _GthSearchSourceClass
+{
+       GObjectClass __parent_class;
+};
+
+GType                  gth_search_source_get_type      (void) G_GNUC_CONST;
+GthSearchSource *      gth_search_source_new           (void);
+void                   gth_search_source_set_folder    (GthSearchSource        *source,
+                                                        GFile                  *folder);
+GFile *                        gth_search_source_get_folder    (GthSearchSource        *source);
+void                   gth_search_source_set_recursive (GthSearchSource        *source,
+                                                        gboolean                recursive);
+gboolean               gth_search_source_is_recursive  (GthSearchSource        *source);
+
+#endif /* GTH_SEARCH_SOURCE_H */
diff --git a/extensions/search/gth-search-task.c b/extensions/search/gth-search-task.c
index 03612d67..781c65aa 100644
--- a/extensions/search/gth-search-task.c
+++ b/extensions/search/gth-search-task.c
@@ -24,6 +24,7 @@
 #include <glib/gi18n.h>
 #include <gthumb.h>
 #include <extensions/catalogs/gth-catalog.h>
+#include "gth-search-source.h"
 #include "gth-search-task.h"
 
 
@@ -39,6 +40,7 @@ struct _GthSearchTaskPrivate {
        GtkWidget     *dialog;
        GthFileSource *file_source;
        gsize          n_files;
+       GList         *current_location;
 };
 
 
@@ -124,34 +126,13 @@ save_search_result_copy_done_cb (void     **buffer,
 
 
 static void
-done_func (GObject  *object,
-          GError   *error,
-          gpointer  user_data)
+_gth_search_task_save_search_result (GthSearchTask *task)
 {
-       GthSearchTask *task = user_data;
        DomDocument   *doc;
        char          *data;
        gsize          size;
        GFile         *search_result_real_file;
 
-       gth_info_bar_set_secondary_text (GTH_INFO_BAR (task->priv->dialog), NULL);
-
-       task->priv->error = NULL;
-       if (error != NULL) {
-               if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
-                       task->priv->error = g_error_new_literal (GTH_TASK_ERROR, GTH_TASK_ERROR_CANCELLED, 
"");
-                       g_error_free (error);
-
-                       /* reset the cancellable because it's re-used below to
-                        * save the partial result. */
-                       g_cancellable_reset (gth_task_get_cancellable (GTH_TASK (task)));
-               }
-               else
-                       task->priv->error = error;
-       }
-
-       /* save the search result */
-
        doc = dom_document_new ();
        dom_element_append_child (DOM_ELEMENT (doc), dom_domizable_create_element (DOM_DOMIZABLE 
(task->priv->search), doc));
        data = dom_document_dump (doc, &size);
@@ -171,6 +152,40 @@ done_func (GObject  *object,
 }
 
 
+static void
+_gth_search_task_search_current_location (GthSearchTask *task);
+
+
+static void
+done_func (GObject  *object,
+          GError   *error,
+          gpointer  user_data)
+{
+       GthSearchTask *task = user_data;
+
+       gth_info_bar_set_secondary_text (GTH_INFO_BAR (task->priv->dialog), NULL);
+
+       task->priv->error = NULL;
+       if (error != NULL) {
+               if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+                       task->priv->error = g_error_new_literal (GTH_TASK_ERROR, GTH_TASK_ERROR_CANCELLED, 
"");
+                       g_error_free (error);
+
+                       /* reset the cancellable because it's re-used below to
+                        * save the partial result. */
+                       g_cancellable_reset (gth_task_get_cancellable (GTH_TASK (task)));
+               }
+               else
+                       task->priv->error = error;
+               _gth_search_task_save_search_result (task);
+               return;
+       }
+
+       task->priv->current_location = g_list_next (task->priv->current_location);
+       _gth_search_task_search_current_location (task);
+}
+
+
 static void
 update_secondary_text (GthSearchTask *task)
 {
@@ -256,17 +271,56 @@ start_dir_func (GFile      *directory,
 }
 
 
+static void
+_gth_search_task_search_current_location (GthSearchTask *task)
+{
+       GthSearchSource *search_location;
+       GSettings       *settings;
+       GString         *attributes;
+       const char      *test_attributes;
+
+       if (task->priv->current_location == NULL) {
+               _gth_search_task_save_search_result (task);
+               return;
+       }
+
+       settings = g_settings_new (GTHUMB_BROWSER_SCHEMA);
+       task->priv->show_hidden_files = g_settings_get_boolean (settings, PREF_BROWSER_SHOW_HIDDEN_FILES);
+
+       search_location = GTH_SEARCH_SOURCE (task->priv->current_location->data);
+       task->priv->file_source = gth_main_get_file_source (gth_search_source_get_folder (search_location));
+       gth_file_source_set_cancellable (task->priv->file_source, gth_task_get_cancellable (GTH_TASK (task)));
+
+       attributes = g_string_new (g_settings_get_boolean (settings, PREF_BROWSER_FAST_FILE_TYPE) ? 
GFILE_STANDARD_ATTRIBUTES_WITH_FAST_CONTENT_TYPE : GFILE_STANDARD_ATTRIBUTES_WITH_CONTENT_TYPE);
+       test_attributes = gth_test_get_attributes (GTH_TEST (task->priv->test));
+       if (test_attributes[0] != '\0') {
+               g_string_append (attributes, ",");
+               g_string_append (attributes, test_attributes);
+       }
+
+       task->priv->io_operation = TRUE;
+       gth_file_source_for_each_child (task->priv->file_source,
+                                       gth_search_source_get_folder (search_location),
+                                       gth_search_source_is_recursive (search_location),
+                                       attributes->str,
+                                       start_dir_func,
+                                       for_each_file_func,
+                                       done_func,
+                                       task);
+
+       g_string_free (attributes, TRUE);
+       g_object_unref (settings);
+}
+
+
 static void
 browser_location_ready_cb (GthBrowser    *browser,
                           GFile         *folder,
                           gboolean       error,
                           GthSearchTask *task)
 {
-       GtkWidget          *button;
+       GtkWidget   *button;
        InfoBarData *dialog_data;
-       GSettings          *settings;
-       GString            *attributes;
-       const char         *test_attributes;
 
        g_signal_handler_disconnect (task->priv->browser, task->priv->location_ready_id);
 
@@ -324,32 +378,8 @@ browser_location_ready_cb (GthBrowser    *browser,
                g_object_unref (general_filter);
        }
 
-       settings = g_settings_new (GTHUMB_BROWSER_SCHEMA);
-
-       task->priv->show_hidden_files = g_settings_get_boolean (settings, PREF_BROWSER_SHOW_HIDDEN_FILES);
-       task->priv->io_operation = TRUE;
-
-       task->priv->file_source = gth_main_get_file_source (gth_search_get_folder (task->priv->search));
-       gth_file_source_set_cancellable (task->priv->file_source, gth_task_get_cancellable (GTH_TASK (task)));
-
-       attributes = g_string_new (g_settings_get_boolean (settings, PREF_BROWSER_FAST_FILE_TYPE) ? 
GFILE_STANDARD_ATTRIBUTES_WITH_FAST_CONTENT_TYPE : GFILE_STANDARD_ATTRIBUTES_WITH_CONTENT_TYPE);
-       test_attributes = gth_test_get_attributes (GTH_TEST (task->priv->test));
-       if (test_attributes[0] != '\0') {
-               g_string_append (attributes, ",");
-               g_string_append (attributes, test_attributes);
-       }
-
-       gth_file_source_for_each_child (task->priv->file_source,
-                                       gth_search_get_folder (task->priv->search),
-                                       gth_search_is_recursive (task->priv->search),
-                                       attributes->str,
-                                       start_dir_func,
-                                       for_each_file_func,
-                                       done_func,
-                                       task);
-
-       g_object_unref (settings);
-       g_string_free (attributes, TRUE);
+       task->priv->current_location = gth_search_get_sources (task->priv->search);
+       _gth_search_task_search_current_location (task);
 }
 
 
@@ -462,6 +492,7 @@ gth_search_task_init (GthSearchTask *task)
        task->priv->dialog = NULL;
        task->priv->file_source = NULL;
        task->priv->n_files = 0;
+       task->priv->current_location = NULL;
 }
 
 
diff --git a/extensions/search/gth-search.c b/extensions/search/gth-search.c
index 7a5bbc04..d6608c26 100644
--- a/extensions/search/gth-search.c
+++ b/extensions/search/gth-search.c
@@ -23,6 +23,7 @@
 #include <glib/gi18n.h>
 #include <gthumb.h>
 #include "gth-search.h"
+#include "gth-search-source.h"
 
 
 #define SEARCH_FORMAT "1.0"
@@ -33,8 +34,7 @@ static void gth_search_gth_duplicable_interface_init (GthDuplicableInterface *if
 
 
 struct _GthSearchPrivate {
-       GFile        *folder;
-       gboolean      recursive;
+       GList        *sources;
        GthTestChain *test;
 };
 
@@ -75,16 +75,25 @@ gth_search_read_from_doc (GthCatalog *base,
        self = GTH_SEARCH (base);
        GTH_CATALOG_CLASS (gth_search_parent_class)->read_from_doc (GTH_CATALOG (self), root);
 
+       _g_object_list_unref (self->priv->sources);
+       self->priv->sources = NULL;
+
        gth_search_set_test (self, NULL);
+
        for (node = root->first_child; node; node = node->next_sibling) {
                if (g_strcmp0 (node->tag_name, "folder") == 0) {
-                       GFile *folder;
+                       GthSearchSource *source;
+                       GFile           *folder;
+
+                       source = gth_search_source_new ();
 
                        folder = g_file_new_for_uri (dom_element_get_attribute (node, "uri"));
-                       gth_search_set_folder (self, folder);
+                       gth_search_source_set_folder (source, folder);
                        g_object_unref (folder);
 
-                       gth_search_set_recursive (self, (g_strcmp0 (dom_element_get_attribute (node, 
"recursive"), "true") == 0));
+                       gth_search_source_set_recursive (source, (g_strcmp0 (dom_element_get_attribute (node, 
"recursive"), "true") == 0));
+
+                       self->priv->sources = g_list_prepend (self->priv->sources, source);
                }
                else if (g_strcmp0 (node->tag_name, "tests") == 0) {
                        GthTest *test;
@@ -93,7 +102,21 @@ gth_search_read_from_doc (GthCatalog *base,
                        dom_domizable_load_from_element (DOM_DOMIZABLE (test), node);
                        gth_search_set_test (self, GTH_TEST_CHAIN (test));
                }
+               else if (g_strcmp0 (node->tag_name, "sources") == 0) {
+                       DomElement *source_node;
+
+                       for (source_node = node->first_child; source_node; source_node = 
source_node->next_sibling) {
+                               if (g_strcmp0 (source_node->tag_name, "source") == 0) {
+                                       GthSearchSource *source;
+
+                                       source = gth_search_source_new ();
+                                       dom_domizable_load_from_element (DOM_DOMIZABLE (source), source_node);
+                                       self->priv->sources = g_list_prepend (self->priv->sources, source);
+                               }
+                       }
+               }
        }
+       self->priv->sources = g_list_reverse (self->priv->sources);
 }
 
 
@@ -102,15 +125,15 @@ _gth_search_write_to_doc (GthSearch   *self,
                          DomDocument *doc,
                          DomElement  *root)
 {
-       char *uri;
+       DomElement *sources;
+       GList      *scan;
 
-       uri = g_file_get_uri (self->priv->folder);
-       dom_element_append_child (root,
-                                 dom_document_create_element (doc, "folder",
-                                                              "uri", uri,
-                                                              "recursive", (self->priv->recursive ? "true" : 
"false"),
-                                                              NULL));
-       g_free (uri);
+       sources = dom_document_create_element (doc, "sources", NULL);
+       for (scan = self->priv->sources; scan; scan = scan->next) {
+               GthSearchSource *source = scan->data;
+               dom_element_append_child (sources, dom_domizable_create_element (DOM_DOMIZABLE (source), 
doc));
+       }
+       dom_element_append_child (root, sources);
 
        dom_element_append_child (root, dom_domizable_create_element (DOM_DOMIZABLE (self->priv->test), doc));
 }
@@ -162,8 +185,7 @@ gth_search_real_duplicate (GthDuplicable *duplicable)
 
        new_search = gth_search_new ();
 
-       gth_search_set_folder (new_search, gth_search_get_folder (search));
-       gth_search_set_recursive (new_search, gth_search_is_recursive (search));
+       gth_search_set_sources (new_search, gth_search_get_sources (search));
 
        if (search->priv->test != NULL)
                new_search->priv->test = (GthTestChain*) gth_duplicable_duplicate (GTH_DUPLICABLE 
(search->priv->test));
@@ -188,8 +210,7 @@ gth_search_finalize (GObject *object)
 
        search = GTH_SEARCH (object);
 
-       if (search->priv->folder != NULL)
-               g_object_unref (search->priv->folder);
+       _g_object_list_unref (search->priv->sources);
        if (search->priv->test != NULL)
                g_object_unref (search->priv->test);
 
@@ -234,8 +255,7 @@ static void
 gth_search_init (GthSearch *search)
 {
        search->priv = gth_search_get_instance_private (search);
-       search->priv->folder = NULL;
-       search->priv->recursive = FALSE;
+       search->priv->sources = NULL;
        search->priv->test = NULL;
 }
 
@@ -276,38 +296,36 @@ gth_search_new_from_data (void    *buffer,
 
 
 void
-gth_search_set_folder (GthSearch *search,
-                      GFile     *folder)
+gth_search_set_sources (GthSearch *search,
+                       GList     *sources /* GthSearchSource list */)
 {
-       if (search->priv->folder != NULL) {
-               g_object_unref (search->priv->folder);
-               search->priv->folder = NULL;
-       }
-
-       if (folder != NULL)
-               search->priv->folder = g_object_ref (folder);
+       _g_object_list_unref (search->priv->sources);
+       search->priv->sources = _g_object_list_ref (sources);
 }
 
 
-GFile *
-gth_search_get_folder (GthSearch *search)
+void
+gth_search_set_source (GthSearch *search,
+                      GFile     *folder,
+                      gboolean   recursive)
 {
-       return search->priv->folder;
-}
+       GthSearchSource *source;
 
+       _g_object_list_unref (search->priv->sources);
+       search->priv->sources = NULL;
 
-void
-gth_search_set_recursive (GthSearch *search,
-                         gboolean   recursive)
-{
-       search->priv->recursive = recursive;
+       source = gth_search_source_new ();
+       gth_search_source_set_folder (source, folder);
+       gth_search_source_set_recursive (source, recursive);
+
+       search->priv->sources = g_list_prepend (search->priv->sources, source);
 }
 
 
-gboolean
-gth_search_is_recursive (GthSearch *search)
+GList *
+gth_search_get_sources (GthSearch *search)
 {
-       return search->priv->recursive;
+       return search->priv->sources;
 }
 
 
diff --git a/extensions/search/gth-search.h b/extensions/search/gth-search.h
index b766d07b..f11d8e52 100644
--- a/extensions/search/gth-search.h
+++ b/extensions/search/gth-search.h
@@ -51,15 +51,15 @@ struct _GthSearchClass
 
 GType             gth_search_get_type         (void) G_GNUC_CONST;
 GthSearch *       gth_search_new              (void);
-GthSearch *       gth_search_new_from_data    (void         *buffer, 
+GthSearch *       gth_search_new_from_data    (void         *buffer,
                                               gsize         count,
                                               GError      **error);
-void              gth_search_set_folder       (GthSearch    *search,
-                                              GFile        *folder);
-GFile *           gth_search_get_folder       (GthSearch    *search);
-void              gth_search_set_recursive    (GthSearch    *search,
+void              gth_search_set_sources      (GthSearch    *search,
+                                              GList        *sources /* GthSearchSource list */);
+void              gth_search_set_source       (GthSearch    *search,
+                                              GFile        *folder,
                                               gboolean      recursive);
-gboolean          gth_search_is_recursive     (GthSearch    *search);
+GList *           gth_search_get_sources      (GthSearch    *search);
 void              gth_search_set_test         (GthSearch    *search,
                                               GthTestChain *test);
 GthTestChain *    gth_search_get_test         (GthSearch    *search);
diff --git a/extensions/search/meson.build b/extensions/search/meson.build
index 2e466d5a..7d1879b1 100644
--- a/extensions/search/meson.build
+++ b/extensions/search/meson.build
@@ -4,6 +4,8 @@ source_files = files(
   'gth-search.c',
   'gth-search-editor.c',
   'gth-search-editor-dialog.c',
+  'gth-search-source.c',
+  'gth-search-source-selector.c',
   'gth-search-task.c',
   'main.c'
 )


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]