Merge merge-requests '495' and '458'
authorKlaus Ethgen <Klaus@Ethgen.de>
Sat, 8 Jul 2017 09:24:19 +0000 (10:24 +0100)
committerKlaus Ethgen <Klaus@Ethgen.de>
Sat, 8 Jul 2017 09:24:19 +0000 (10:24 +0100)
* github/merge-requests/495:
  Avoid c99 feature
  *phew* commented and simplified.
  Hopefully make it not crash
  It compiles!
  Remove all references to "IconData"
  update .gitignore
  Refactor: move view_file implementations to their own subdirectory.

* github/merge-requests/458:
  Add the ability to use regular expressions for Pan View keyword filtering.
  Add pan filtering to all of the pan view modes
  Revamp pan view filtering to support different modes and grouping.
  Move filter code into pan-fiew-filter.{c,h}
  Adds a keyword filtering feature to Timeline PanView.
  Pull the search UI construction code out into a distinct function.
  Start moving pan view search code to its own module

17 files changed:
doc/docbook/GuideOptionsGeneral.xml
doc/docbook/GuideReferenceConfig.xml
src/bar_gps.c
src/cache_maint.c
src/cellrenderericon.c
src/pan-view/Makefile.am
src/pan-view/pan-calendar.c
src/pan-view/pan-folder.c
src/pan-view/pan-grid.c
src/pan-view/pan-timeline.c
src/pan-view/pan-types.h
src/pan-view/pan-view-filter.c [new file with mode: 0644]
src/pan-view/pan-view-filter.h [new file with mode: 0644]
src/pan-view/pan-view-search.c [new file with mode: 0644]
src/pan-view/pan-view-search.h [new file with mode: 0644]
src/pan-view/pan-view.c
src/pan-view/pan-view.h

index e6dfd0e..a24dc44 100644 (file)
@@ -68,7 +68,8 @@
         <listitem>\r
           <para>\r
             Enable this to save thumbnails to disk, subsequent requests for a thumbnail will be faster. Thumbnails are cached into:\r
-            <programlisting>$HOME/.cache/.geeqie/thumbnails</programlisting>\r
+            <programlisting>$XDG_CACHE_HOME/geeqie/thumbnails/</programlisting>\r
+            <programlisting>($~/.cache/geeqie/thumbnails/)</programlisting>\r
             Refer to\r
             <link linkend="GuideReferenceThumbnails">Thumbnails Reference</link>\r
             for additional details.\r
index 6be6984..d78fc16 100644 (file)
@@ -2,9 +2,14 @@
 <section id="GuideReferenceConfig">\r
   <title id="titleGuideReferenceConfig">Configuration Files and Locations</title>\r
   <para>The following data lists the locations Geeqie uses for various actions. The uppercase symbols are environment variables. If they are not set on your system the fallback locations are listed in parentheses.</para>\r
+  <para>\r
+    Geqqie will first attempt to load a configuration file from:\r
+    <programlisting xml:space="preserve">/etc/geeqie/geeqierc.xml</programlisting>\r
+  </para>\r
+  <para>It will then continue with the following locations.</para>\r
   <para>\r
     Most of Geeqie's configuration files are contained in the folder, and sub-folders of:\r
-    <programlisting xml:space="preserve">$HOME/$XDG_CONFIG_HOME/geeqie/</programlisting>\r
+    <programlisting xml:space="preserve">$XDG_CONFIG_HOME/geeqie/</programlisting>\r
     <programlisting xml:space="preserve">($~/.config/geeqie/)</programlisting>\r
   </para>\r
   <para>\r
@@ -36,8 +41,9 @@
     <programlisting xml:space="preserve">.../accels</programlisting>\r
   </para>\r
   <para>\r
-    The default location for Collections is in the folder:\r
-    <programlisting xml:space="preserve">$HOME/.local/share/geeqie/collections</programlisting>\r
+    The location for Collections is in the folder:\r
+    <programlisting xml:space="preserve">$XDG_DATA_HOME/geeqie/collections</programlisting>\r
+    <programlisting xml:space="preserve">($~/.local/share/geeqie/collections)</programlisting>\r
   </para>\r
   <para>\r
     The lirc\r
index 40bb368..fa0213d 100644 (file)
@@ -230,7 +230,7 @@ static void bar_pane_gps_close_save_cb(GenericDialog *gd, gpointer data)
 
        if (info == TARGET_TEXT_PLAIN)
                {
-               location = decode_geo_parameters(gtk_selection_data_get_data(selection_data));
+               location = decode_geo_parameters((gchar *)gtk_selection_data_get_data(selection_data));
                if (!(g_strstr_len(location,-1,"Error")))
                        {
                        latlong = g_strsplit(location, " ", 2);
index 07aa4c3..ed92b54 100644 (file)
@@ -138,6 +138,7 @@ static gboolean cache_maintain_home_cb(gpointer data)
        gboolean still_have_a_file = TRUE;
        gsize base_length;
        const gchar *cache_folder;
+       gboolean filter_disable;
 
        if (cm->metadata)
                {
@@ -162,6 +163,13 @@ static gboolean cache_maintain_home_cb(gpointer data)
 
        DEBUG_1("purge chk (%d) \"%s\"", (cm->clear && !cm->metadata), fd->path);
 
+/**
+ * It is necessary to disable the file filter when clearing the cache,
+ * otherwise the .sim (file similarity) files are not deleted.
+ */
+       filter_disable = options->file_filter.disable;
+       options->file_filter.disable = TRUE;
+
        if (g_list_find(cm->done_list, fd) == NULL)
                {
                cm->done_list = g_list_prepend(cm->done_list, fd);
@@ -198,6 +206,8 @@ static gboolean cache_maintain_home_cb(gpointer data)
                                }
                        }
                }
+       options->file_filter.disable = filter_disable;
+
        filelist_free(list);
 
        cm->list = g_list_concat(dlist, cm->list);
@@ -1222,7 +1232,7 @@ void cache_manager_show(void)
        button = pref_table_button(table, 0, 1, GTK_STOCK_DELETE, _("Clear cache"), FALSE,
                                   G_CALLBACK(cache_manager_main_clear_cb), cache_manager);
        gtk_size_group_add_widget(sizegroup, button);
-       pref_table_label(table, 1, 1, _("Delete all cached thumbnails."), 0.0);
+       pref_table_label(table, 1, 1, _("Delete all cached data."), 0.0);
 
 
        group = pref_group_new(gd->vbox, FALSE, _("Shared thumbnail cache"), GTK_ORIENTATION_VERTICAL);
index cc815ec..5f42e1c 100644 (file)
@@ -683,7 +683,11 @@ gqv_cell_renderer_icon_render(GtkCellRenderer              *cell,
        GdkPixbuf *pixbuf;
        const gchar *text;
        GdkRectangle cell_rect;
+#if GTK_CHECK_VERSION(3,0,0)
+       GtkStateFlags state;
+#else
        GtkStateType state;
+#endif
        gint xpad, ypad;
 
 
@@ -712,16 +716,28 @@ gqv_cell_renderer_icon_render(GtkCellRenderer             *cell,
        if ((flags & GTK_CELL_RENDERER_SELECTED) == GTK_CELL_RENDERER_SELECTED)
                {
                if (gtk_widget_has_focus(widget))
+#if GTK_CHECK_VERSION(3,0,0)
+                       state = GTK_STATE_FLAG_SELECTED;
+               else
+                       state = GTK_STATE_FLAG_ACTIVE;
+#else
                        state = GTK_STATE_SELECTED;
                else
                        state = GTK_STATE_ACTIVE;
+#endif
                }
        else
                {
                if (gtk_widget_get_state(widget) == GTK_STATE_INSENSITIVE)
+#if GTK_CHECK_VERSION(3,0,0)
+                       state = GTK_STATE_FLAG_INSENSITIVE;
+               else
+                       state = GTK_STATE_FLAG_NORMAL;
+#else
                        state = GTK_STATE_INSENSITIVE;
                else
                        state = GTK_STATE_NORMAL;
+#endif
                }
 
 #if GTK_CHECK_VERSION(3,0,0)
index 70c9423..2401b5e 100644 (file)
@@ -13,4 +13,9 @@ module_pan_view = \
        %D%/pan-util.c  \
        %D%/pan-util.h  \
        %D%/pan-view.c  \
-       %D%/pan-view.h
+       %D%/pan-view.h  \
+       %D%/pan-view-filter.c   \
+       %D%/pan-view-filter.h   \
+       %D%/pan-view-search.c   \
+       %D%/pan-view-search.h
+
index 91aa003..3400fbb 100644 (file)
@@ -26,6 +26,7 @@
 
 #include "pan-util.h"
 #include "pan-view.h"
+#include "pan-view-filter.h"
 #include "pixbuf_util.h"
 
 #define PAN_CAL_POPUP_COLOR 220, 220, 220
@@ -200,6 +201,7 @@ void pan_calendar_compute(PanWindow *pw, FileData *dir_fd, gint *width, gint *he
        gint end_month = 0;
 
        list = pan_list_tree(dir_fd, SORT_NONE, TRUE, pw->ignore_symlinks);
+       pan_filter_fd_list(&list, pw->filter_ui->filter_elements);
 
        if (pw->cache_list && pw->exif_date_enable)
                {
index 65ac306..95748ee 100644 (file)
@@ -25,6 +25,7 @@
 
 #include "pan-item.h"
 #include "pan-util.h"
+#include "pan-view-filter.h"
 
 static void pan_flower_size(PanWindow *pw, gint *width, gint *height)
 {
@@ -242,6 +243,8 @@ static FlowerGroup *pan_flower_group(PanWindow *pw, FileData *dir_fd, gint x, gi
        f = filelist_sort(f, SORT_NAME, TRUE);
        d = filelist_sort(d, SORT_NAME, TRUE);
 
+       pan_filter_fd_list(&f, pw->filter_ui->filter_elements);
+
        pi_box = pan_item_text_new(pw, x, y, dir_fd->path, PAN_TEXT_ATTR_NONE,
                                   PAN_TEXT_BORDER_SIZE,
                                   PAN_TEXT_COLOR, 255);
@@ -386,6 +389,8 @@ static void pan_folder_tree_path(PanWindow *pw, FileData *dir_fd,
        f = filelist_sort(f, SORT_NAME, TRUE);
        d = filelist_sort(d, SORT_NAME, TRUE);
 
+       pan_filter_fd_list(&f, pw->filter_ui->filter_elements);
+
        *x = PAN_BOX_BORDER + ((*level) * MAX(PAN_BOX_BORDER, PAN_THUMB_GAP));
 
        pi_box = pan_item_text_new(pw, *x, *y, dir_fd->path, PAN_TEXT_ATTR_NONE,
index b441b83..5d28e4b 100644 (file)
@@ -25,6 +25,7 @@
 
 #include "pan-item.h"
 #include "pan-util.h"
+#include "pan-view-filter.h"
 
 void pan_grid_compute(PanWindow *pw, FileData *dir_fd, gint *width, gint *height)
 {
@@ -35,6 +36,7 @@ void pan_grid_compute(PanWindow *pw, FileData *dir_fd, gint *width, gint *height
        gint next_y;
 
        list = pan_list_tree(dir_fd, SORT_NAME, TRUE, pw->ignore_symlinks);
+       pan_filter_fd_list(&list, pw->filter_ui->filter_elements);
 
        grid_size = (gint)sqrt((gdouble)g_list_length(list));
        if (pw->size > PAN_IMAGE_SIZE_THUMB_LARGE)
index 9a96420..1162795 100644 (file)
 #include "pan-item.h"
 #include "pan-util.h"
 #include "pan-view.h"
+#include "pan-view-filter.h"
 
 void pan_timeline_compute(PanWindow *pw, FileData *dir_fd, gint *width, gint *height)
 {
        GList *list;
        GList *work;
        gint x, y;
-       time_t tc;
+       time_t group_start_date;
        gint total;
        gint count;
        PanItem *pi_month = NULL;
@@ -41,6 +42,7 @@ void pan_timeline_compute(PanWindow *pw, FileData *dir_fd, gint *width, gint *he
        gint y_height;
 
        list = pan_list_tree(dir_fd, SORT_NONE, TRUE, pw->ignore_symlinks);
+       pan_filter_fd_list(&list, pw->filter_ui->filter_elements);
 
        if (pw->cache_list && pw->exif_date_enable)
                {
@@ -61,7 +63,8 @@ void pan_timeline_compute(PanWindow *pw, FileData *dir_fd, gint *width, gint *he
        day_start = month_start;
        x_width = 0;
        y_height = 0;
-       tc = 0;
+       group_start_date = 0;
+       // total and count are used to enforce a stride of PAN_GROUP_MAX thumbs.
        total = 0;
        count = 0;
        work = list;
@@ -73,13 +76,15 @@ void pan_timeline_compute(PanWindow *pw, FileData *dir_fd, gint *width, gint *he
                fd = work->data;
                work = work->next;
 
-               if (!pan_date_compare(fd->date, tc, PAN_DATE_LENGTH_DAY))
+               if (!pan_date_compare(fd->date, group_start_date, PAN_DATE_LENGTH_DAY))
                        {
+                       // FD starts a new day group.
                        GList *needle;
                        gchar *buf;
 
-                       if (!pan_date_compare(fd->date, tc, PAN_DATE_LENGTH_MONTH))
+                       if (!pan_date_compare(fd->date, group_start_date, PAN_DATE_LENGTH_MONTH))
                                {
+                               // FD starts a new month group.
                                pi_day = NULL;
 
                                if (pi_month)
@@ -114,7 +119,7 @@ void pan_timeline_compute(PanWindow *pw, FileData *dir_fd, gint *width, gint *he
 
                        if (pi_day) x = pi_day->x + pi_day->width + PAN_BOX_BORDER;
 
-                       tc = fd->date;
+                       group_start_date = fd->date;
                        total = 1;
                        count = 0;
 
@@ -124,7 +129,7 @@ void pan_timeline_compute(PanWindow *pw, FileData *dir_fd, gint *width, gint *he
                                FileData *nfd;
 
                                nfd = needle->data;
-                               if (pan_date_compare(nfd->date, tc, PAN_DATE_LENGTH_DAY))
+                               if (pan_date_compare(nfd->date, group_start_date, PAN_DATE_LENGTH_DAY))
                                        {
                                        needle = needle->next;
                                        total++;
index 5318b38..9fd8916 100644 (file)
@@ -169,6 +169,19 @@ struct _PanItem {
        gboolean queued;
 };
 
+typedef struct _PanViewSearchUi PanViewSearchUi;
+struct _PanViewSearchUi
+{
+       GtkWidget *search_box;
+       GtkWidget *search_entry;
+       GtkWidget *search_label;
+       GtkWidget *search_button;
+       GtkWidget *search_button_arrow;
+};
+
+// Defined in pan-view-filter.h
+typedef struct _PanViewFilterUi PanViewFilterUi;
+
 typedef struct _PanWindow PanWindow;
 struct _PanWindow
 {
@@ -182,11 +195,8 @@ struct _PanWindow
        GtkWidget *label_message;
        GtkWidget *label_zoom;
 
-       GtkWidget *search_box;
-       GtkWidget *search_entry;
-       GtkWidget *search_label;
-       GtkWidget *search_button;
-       GtkWidget *search_button_arrow;
+       PanViewSearchUi *search_ui;
+       PanViewFilterUi *filter_ui;
 
        GtkWidget *date_button;
 
diff --git a/src/pan-view/pan-view-filter.c b/src/pan-view/pan-view-filter.c
new file mode 100644 (file)
index 0000000..5865d66
--- /dev/null
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2006 John Ellis
+ * Copyright (C) 2008 - 2016 The Geeqie Team
+ *
+ * Author: John Ellis
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "pan-view-filter.h"
+
+#include "image.h"
+#include "metadata.h"
+#include "pan-item.h"
+#include "pan-util.h"
+#include "pan-view.h"
+#include "ui_fileops.h"
+#include "ui_tabcomp.h"
+#include "ui_misc.h"
+
+PanViewFilterUi *pan_filter_ui_new(PanWindow *pw)
+{
+       PanViewFilterUi *ui = g_new0(PanViewFilterUi, 1);
+       GtkWidget *combo;
+       GtkWidget *hbox;
+
+       /* Since we're using the GHashTable as a HashSet (in which key and value pointers
+        * are always identical), specifying key _and_ value destructor callbacks will
+        * cause a double-free.
+        */
+       {
+               GtkTreeIter iter;
+               ui->filter_mode_model = gtk_list_store_new(3, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING);
+               gtk_list_store_append(ui->filter_mode_model, &iter);
+               gtk_list_store_set(ui->filter_mode_model, &iter,
+                                  0, PAN_VIEW_FILTER_REQUIRE, 1, _("Require"), 2, _("R"), -1);
+               gtk_list_store_append(ui->filter_mode_model, &iter);
+               gtk_list_store_set(ui->filter_mode_model, &iter,
+                                  0, PAN_VIEW_FILTER_EXCLUDE, 1, _("Exclude"), 2, _("E"), -1);
+               gtk_list_store_append(ui->filter_mode_model, &iter);
+               gtk_list_store_set(ui->filter_mode_model, &iter,
+                                  0, PAN_VIEW_FILTER_INCLUDE, 1, _("Include"), 2, _("I"), -1);
+               gtk_list_store_append(ui->filter_mode_model, &iter);
+               gtk_list_store_set(ui->filter_mode_model, &iter,
+                                  0, PAN_VIEW_FILTER_GROUP, 1, _("Group"), 2, _("G"), -1);
+
+               ui->filter_mode_combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(ui->filter_mode_model));
+               gtk_combo_box_set_focus_on_click(GTK_COMBO_BOX(ui->filter_mode_combo), FALSE);
+               gtk_combo_box_set_active(GTK_COMBO_BOX(ui->filter_mode_combo), 0);
+
+               GtkCellRenderer *render = gtk_cell_renderer_text_new();
+               gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(ui->filter_mode_combo), render, TRUE);
+               gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(ui->filter_mode_combo), render, "text", 1, NULL);
+       }
+
+       // Build the actual filter UI.
+       ui->filter_box = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
+       pref_spacer(ui->filter_box, 0);
+       pref_label_new(ui->filter_box, _("Keyword Filter:"));
+
+       gtk_box_pack_start(GTK_BOX(ui->filter_box), ui->filter_mode_combo, TRUE, TRUE, 0);
+       gtk_widget_show(ui->filter_mode_combo);
+
+       hbox = gtk_hbox_new(TRUE, PREF_PAD_SPACE);
+       gtk_box_pack_start(GTK_BOX(ui->filter_box), hbox, TRUE, TRUE, 0);
+       gtk_widget_show(hbox);
+
+       combo = tab_completion_new_with_history(&ui->filter_entry, "", "pan_view_filter", -1,
+                                               pan_filter_activate_cb, pw);
+       gtk_box_pack_start(GTK_BOX(hbox), combo, TRUE, TRUE, 0);
+       gtk_widget_show(combo);
+
+       // TODO(xsdg): Figure out whether it's useful to keep this label around.
+       ui->filter_label = gtk_label_new("");
+       //gtk_box_pack_start(GTK_BOX(hbox), ui->filter_label, FALSE, FALSE, 0);
+       //gtk_widget_show(ui->filter_label);
+
+       ui->filter_kw_hbox = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
+       gtk_box_pack_start(GTK_BOX(hbox), ui->filter_kw_hbox, TRUE, TRUE, 0);
+       gtk_widget_show(ui->filter_kw_hbox);
+
+       // Build the spin-button to show/hide the filter UI.
+       ui->filter_button = gtk_toggle_button_new();
+       gtk_button_set_relief(GTK_BUTTON(ui->filter_button), GTK_RELIEF_NONE);
+       gtk_button_set_focus_on_click(GTK_BUTTON(ui->filter_button), FALSE);
+       hbox = gtk_hbox_new(FALSE, PREF_PAD_GAP);
+       gtk_container_add(GTK_CONTAINER(ui->filter_button), hbox);
+       gtk_widget_show(hbox);
+       ui->filter_button_arrow = gtk_arrow_new(GTK_ARROW_UP, GTK_SHADOW_NONE);
+       gtk_box_pack_start(GTK_BOX(hbox), ui->filter_button_arrow, FALSE, FALSE, 0);
+       gtk_widget_show(ui->filter_button_arrow);
+       pref_label_new(hbox, _("Filter"));
+
+       g_signal_connect(G_OBJECT(ui->filter_button), "clicked",
+                        G_CALLBACK(pan_filter_toggle_cb), pw);
+
+       return ui;
+}
+
+void pan_filter_ui_destroy(PanViewFilterUi **ui_ptr)
+{
+       if (ui_ptr == NULL || *ui_ptr == NULL) return;
+
+       // Note that g_clear_pointer handles already-NULL pointers.
+       //g_clear_pointer(&(*ui_ptr)->filter_kw_table, g_hash_table_destroy);
+
+       g_free(*ui_ptr);
+       *ui_ptr = NULL;
+}
+
+static void pan_filter_status(PanWindow *pw, const gchar *text)
+{
+       gtk_label_set_text(GTK_LABEL(pw->filter_ui->filter_label), (text) ? text : "");
+}
+
+static void pan_filter_kw_button_cb(GtkButton *widget, gpointer data)
+{
+       PanFilterCallbackState *cb_state = data;
+       PanWindow *pw = cb_state->pw;
+       PanViewFilterUi *ui = pw->filter_ui;
+
+       // TODO(xsdg): Fix filter element pointed object memory leak.
+       ui->filter_elements = g_list_delete_link(ui->filter_elements, cb_state->filter_element);
+       gtk_widget_destroy(GTK_WIDGET(widget));
+       g_free(cb_state);
+
+       pan_filter_status(pw, _("Removed keyword…"));
+       pan_layout_update(pw);
+}
+
+void pan_filter_activate_cb(const gchar *text, gpointer data)
+{
+       GtkWidget *kw_button;
+       PanWindow *pw = data;
+       PanViewFilterUi *ui = pw->filter_ui;
+       GtkTreeIter iter;
+
+       if (!text) return;
+
+       // Get all relevant state and reset UI.
+       gtk_combo_box_get_active_iter(GTK_COMBO_BOX(ui->filter_mode_combo), &iter);
+       gtk_entry_set_text(GTK_ENTRY(ui->filter_entry), "");
+       tab_completion_append_to_history(ui->filter_entry, text);
+
+       // Add new filter element.
+       PanViewFilterElement *element = g_new0(PanViewFilterElement, 1);
+       gtk_tree_model_get(GTK_TREE_MODEL(ui->filter_mode_model), &iter, 0, &element->mode, -1);
+       element->keyword = g_strdup(text);
+       if (g_strcmp0(text, g_regex_escape_string(text, -1)))
+               {
+               // It's an actual regex, so compile
+               element->kw_regex = g_regex_new(text, G_REGEX_ANCHORED | G_REGEX_OPTIMIZE, G_REGEX_MATCH_ANCHORED, NULL);
+               }
+       ui->filter_elements = g_list_append(ui->filter_elements, element);
+
+       // Get the short version of the mode value.
+       gchar *short_mode;
+       gtk_tree_model_get(GTK_TREE_MODEL(ui->filter_mode_model), &iter, 2, &short_mode, -1);
+
+       // Create the button.
+       // TODO(xsdg): Use MVC so that the button list is an actual representation of the GList
+       gchar *label = g_strdup_printf("(%s) %s", short_mode, text);
+       kw_button = gtk_button_new_with_label(label);
+       g_clear_pointer(&label, g_free);
+
+       gtk_box_pack_start(GTK_BOX(ui->filter_kw_hbox), kw_button, FALSE, FALSE, 0);
+       gtk_widget_show(kw_button);
+
+       PanFilterCallbackState *cb_state = g_new0(PanFilterCallbackState, 1);
+       cb_state->pw = pw;
+       cb_state->filter_element = g_list_last(ui->filter_elements);
+
+       g_signal_connect(G_OBJECT(kw_button), "clicked",
+                        G_CALLBACK(pan_filter_kw_button_cb), cb_state);
+
+       pan_layout_update(pw);
+}
+
+void pan_filter_activate(PanWindow *pw)
+{
+       gchar *text;
+
+       text = g_strdup(gtk_entry_get_text(GTK_ENTRY(pw->filter_ui->filter_entry)));
+       pan_filter_activate_cb(text, pw);
+       g_free(text);
+}
+
+void pan_filter_toggle_cb(GtkWidget *button, gpointer data)
+{
+       PanWindow *pw = data;
+       PanViewFilterUi *ui = pw->filter_ui;
+       gboolean visible;
+
+       visible = gtk_widget_get_visible(ui->filter_box);
+       if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)) == visible) return;
+
+       if (visible)
+               {
+               gtk_widget_hide(ui->filter_box);
+               gtk_arrow_set(GTK_ARROW(ui->filter_button_arrow), GTK_ARROW_UP, GTK_SHADOW_NONE);
+               }
+       else
+               {
+               gtk_widget_show(ui->filter_box);
+               gtk_arrow_set(GTK_ARROW(ui->filter_button_arrow), GTK_ARROW_DOWN, GTK_SHADOW_NONE);
+               gtk_widget_grab_focus(ui->filter_entry);
+               }
+}
+
+void pan_filter_toggle_visible(PanWindow *pw, gboolean enable)
+{
+       PanViewFilterUi *ui = pw->filter_ui;
+       if (pw->fs) return;
+
+       if (enable)
+               {
+               if (gtk_widget_get_visible(ui->filter_box))
+                       {
+                       gtk_widget_grab_focus(ui->filter_entry);
+                       }
+               else
+                       {
+                       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ui->filter_button), TRUE);
+                       }
+               }
+       else
+               {
+               if (gtk_widget_get_visible(ui->filter_entry))
+                       {
+                       if (gtk_widget_has_focus(ui->filter_entry))
+                               {
+                               gtk_widget_grab_focus(GTK_WIDGET(pw->imd->widget));
+                               }
+                       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ui->filter_button), FALSE);
+                       }
+               }
+}
+
+static gboolean pan_view_list_contains_kw_pattern(GList *haystack, PanViewFilterElement *filter, gchar **found_kw)
+{
+       if (filter->kw_regex)
+               {
+               // regex compile succeeded; attempt regex match.
+               GList *work = g_list_first(haystack);
+               while (work)
+                       {
+                       gchar *keyword = work->data;
+                       work = work->next;
+                       if (g_regex_match(filter->kw_regex, keyword, 0x0, NULL))
+                               {
+                               if (found_kw) *found_kw = keyword;
+                               return TRUE;
+                               }
+                       }
+               return FALSE;
+               }
+       else
+               {
+               // regex compile failed; fall back to exact string match.
+               GList *found_elem = g_list_find_custom(haystack, filter->keyword, (GCompareFunc)g_strcmp0);
+               if (found_elem && found_kw) *found_kw = found_elem->data;
+               return !!found_elem;
+               }
+}
+
+gboolean pan_filter_fd_list(GList **fd_list, GList *filter_elements)
+{
+       GList *work;
+       gboolean modified = FALSE;
+       GHashTable *seen_kw_table = NULL;
+
+       if (!fd_list || !*fd_list || !filter_elements) return modified;
+
+       // seen_kw_table is only valid in this scope, so don't take ownership of any strings.
+       seen_kw_table = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
+
+       work = *fd_list;
+       while (work)
+               {
+               FileData *fd = work->data;
+               GList *last_work = work;
+               work = work->next;
+
+               // TODO(xsdg): OPTIMIZATION Do the search inside of metadata.c to avoid a
+               // bunch of string list copies.
+               GList *img_keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
+
+               // TODO(xsdg): OPTIMIZATION Determine a heuristic for when to linear-search the
+               // keywords list, and when to build a hash table for the image's keywords.
+               gboolean should_reject = FALSE;
+               gchar *group_kw = NULL;
+               GList *filter_element = filter_elements;
+               while (filter_element)
+                       {
+                       PanViewFilterElement *filter = filter_element->data;
+                       filter_element = filter_element->next;
+                       gchar *found_kw = NULL;
+                       gboolean has_kw = pan_view_list_contains_kw_pattern(img_keywords, filter, &found_kw);
+
+                       switch (filter->mode)
+                               {
+                               case PAN_VIEW_FILTER_REQUIRE:
+                                       should_reject |= !has_kw;
+                                       break;
+                               case PAN_VIEW_FILTER_EXCLUDE:
+                                       should_reject |= has_kw;
+                                       break;
+                               case PAN_VIEW_FILTER_INCLUDE:
+                                       if (has_kw) should_reject = FALSE;
+                                       break;
+                               case PAN_VIEW_FILTER_GROUP:
+                                       if (has_kw)
+                                               {
+                                               if (g_hash_table_contains(seen_kw_table, found_kw))
+                                                       {
+                                                       should_reject = TRUE;
+                                                       }
+                                               else if (group_kw == NULL)
+                                                       {
+                                                       group_kw = found_kw;
+                                                       }
+                                               }
+                                       break;
+                               }
+                       }
+
+               if (!should_reject && group_kw != NULL) g_hash_table_add(seen_kw_table, group_kw);
+
+               group_kw = NULL;  // group_kw references an item from img_keywords.
+               string_list_free(img_keywords);
+
+               if (should_reject)
+                       {
+                       *fd_list = g_list_delete_link(*fd_list, last_work);
+                       modified = TRUE;
+                       }
+               }
+
+       g_hash_table_destroy(seen_kw_table);
+       return modified;
+}
diff --git a/src/pan-view/pan-view-filter.h b/src/pan-view/pan-view-filter.h
new file mode 100644 (file)
index 0000000..f3e3e11
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2006 John Ellis
+ * Copyright (C) 2008 - 2016 The Geeqie Team
+ *
+ * Author: John Ellis
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PAN_VIEW_PAN_VIEW_FILTER_H
+#define PAN_VIEW_PAN_VIEW_FILTER_H
+
+#include "main.h"
+#include "pan-types.h"
+
+typedef enum {
+       PAN_VIEW_FILTER_REQUIRE,
+       PAN_VIEW_FILTER_EXCLUDE,
+       PAN_VIEW_FILTER_INCLUDE,
+       PAN_VIEW_FILTER_GROUP
+} PanViewFilterMode;
+
+typedef struct _PanViewFilterElement PanViewFilterElement;
+struct _PanViewFilterElement
+{
+       PanViewFilterMode mode;
+       gchar *keyword;
+       GRegex *kw_regex;
+};
+
+typedef struct _PanFilterCallbackState PanFilterCallbackState;
+struct _PanFilterCallbackState
+{
+       PanWindow *pw;
+       GList *filter_element;
+};
+
+struct _PanViewFilterUi
+{
+       GtkWidget *filter_box;
+       GtkWidget *filter_entry;
+       GtkWidget *filter_label;
+       GtkWidget *filter_button;
+       GtkWidget *filter_button_arrow;
+       GtkWidget *filter_kw_hbox;
+       GtkListStore *filter_mode_model;
+       GtkWidget *filter_mode_combo;
+       GList *filter_elements;  // List of PanViewFilterElement.
+};
+
+void pan_filter_toggle_visible(PanWindow *pw, gboolean enable);
+void pan_filter_activate(PanWindow *pw);
+void pan_filter_activate_cb(const gchar *text, gpointer data);
+void pan_filter_toggle_cb(GtkWidget *button, gpointer data);
+
+// Creates a new PanViewFilterUi instance and returns it.
+PanViewFilterUi *pan_filter_ui_new(PanWindow *pw);
+
+// Destroys the specified PanViewFilterUi and sets the pointer to NULL.
+void pan_filter_ui_destroy(PanViewFilterUi **ui);
+
+gboolean pan_filter_fd_list(GList **fd_list, GList *filter_elements);
+
+#endif
+/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
diff --git a/src/pan-view/pan-view-search.c b/src/pan-view/pan-view-search.c
new file mode 100644 (file)
index 0000000..5ddef13
--- /dev/null
@@ -0,0 +1,468 @@
+/*
+ * Copyright (C) 2006 John Ellis
+ * Copyright (C) 2008 - 2016 The Geeqie Team
+ *
+ * Author: John Ellis
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "pan-view-search.h"
+
+#include "image.h"
+#include "pan-calendar.h"
+#include "pan-item.h"
+#include "pan-util.h"
+#include "pan-view.h"
+#include "ui_tabcomp.h"
+#include "ui_misc.h"
+
+PanViewSearchUi *pan_search_ui_new(PanWindow *pw)
+{
+       PanViewSearchUi *ui = g_new0(PanViewSearchUi, 1);
+       GtkWidget *combo;
+       GtkWidget *hbox;
+
+       // Build the actual search UI.
+       ui->search_box = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
+       pref_spacer(ui->search_box, 0);
+       pref_label_new(ui->search_box, _("Find:"));
+
+       hbox = gtk_hbox_new(TRUE, PREF_PAD_SPACE);
+       gtk_box_pack_start(GTK_BOX(ui->search_box), hbox, TRUE, TRUE, 0);
+       gtk_widget_show(hbox);
+
+       combo = tab_completion_new_with_history(&ui->search_entry, "", "pan_view_search", -1,
+                                               pan_search_activate_cb, pw);
+       gtk_box_pack_start(GTK_BOX(hbox), combo, TRUE, TRUE, 0);
+       gtk_widget_show(combo);
+
+       ui->search_label = gtk_label_new("");
+       gtk_box_pack_start(GTK_BOX(hbox), ui->search_label, TRUE, TRUE, 0);
+       gtk_widget_show(ui->search_label);
+
+       // Build the spin-button to show/hide the search UI.
+       ui->search_button = gtk_toggle_button_new();
+       gtk_button_set_relief(GTK_BUTTON(ui->search_button), GTK_RELIEF_NONE);
+       gtk_button_set_focus_on_click(GTK_BUTTON(ui->search_button), FALSE);
+       hbox = gtk_hbox_new(FALSE, PREF_PAD_GAP);
+       gtk_container_add(GTK_CONTAINER(ui->search_button), hbox);
+       gtk_widget_show(hbox);
+       ui->search_button_arrow = gtk_arrow_new(GTK_ARROW_UP, GTK_SHADOW_NONE);
+       gtk_box_pack_start(GTK_BOX(hbox), ui->search_button_arrow, FALSE, FALSE, 0);
+       gtk_widget_show(ui->search_button_arrow);
+       pref_label_new(hbox, _("Find"));
+
+       g_signal_connect(G_OBJECT(ui->search_button), "clicked",
+                        G_CALLBACK(pan_search_toggle_cb), pw);
+
+       return ui;
+}
+
+void pan_search_ui_destroy(PanViewSearchUi **ui_ptr)
+{
+       if (ui_ptr == NULL || *ui_ptr == NULL) return;
+
+       g_free(*ui_ptr);
+       *ui_ptr = NULL;
+}
+
+static void pan_search_status(PanWindow *pw, const gchar *text)
+{
+       gtk_label_set_text(GTK_LABEL(pw->search_ui->search_label), (text) ? text : "");
+}
+
+static gint pan_search_by_path(PanWindow *pw, const gchar *path)
+{
+       PanItem *pi;
+       GList *list;
+       GList *found;
+       PanItemType type;
+       gchar *buf;
+
+       type = (pw->size > PAN_IMAGE_SIZE_THUMB_LARGE) ? PAN_ITEM_IMAGE : PAN_ITEM_THUMB;
+
+       list = pan_item_find_by_path(pw, type, path, FALSE, FALSE);
+       if (!list) return FALSE;
+
+       found = g_list_find(list, pw->click_pi);
+       if (found && found->next)
+               {
+               found = found->next;
+               pi = found->data;
+               }
+       else
+               {
+               pi = list->data;
+               }
+
+       pan_info_update(pw, pi);
+       image_scroll_to_point(pw->imd, pi->x + pi->width / 2, pi->y + pi->height / 2, 0.5, 0.5);
+
+       buf = g_strdup_printf("%s ( %d / %d )",
+                             (path[0] == G_DIR_SEPARATOR) ? _("path found") : _("filename found"),
+                             g_list_index(list, pi) + 1,
+                             g_list_length(list));
+       pan_search_status(pw, buf);
+       g_free(buf);
+
+       g_list_free(list);
+
+       return TRUE;
+}
+
+static gboolean pan_search_by_partial(PanWindow *pw, const gchar *text)
+{
+       PanItem *pi;
+       GList *list;
+       GList *found;
+       PanItemType type;
+       gchar *buf;
+
+       type = (pw->size > PAN_IMAGE_SIZE_THUMB_LARGE) ? PAN_ITEM_IMAGE : PAN_ITEM_THUMB;
+
+       list = pan_item_find_by_path(pw, type, text, TRUE, FALSE);
+       if (!list) list = pan_item_find_by_path(pw, type, text, FALSE, TRUE);
+       if (!list)
+               {
+               gchar *needle;
+
+               needle = g_utf8_strdown(text, -1);
+               list = pan_item_find_by_path(pw, type, needle, TRUE, TRUE);
+               g_free(needle);
+               }
+       if (!list) return FALSE;
+
+       found = g_list_find(list, pw->click_pi);
+       if (found && found->next)
+               {
+               found = found->next;
+               pi = found->data;
+               }
+       else
+               {
+               pi = list->data;
+               }
+
+       pan_info_update(pw, pi);
+       image_scroll_to_point(pw->imd, pi->x + pi->width / 2, pi->y + pi->height / 2, 0.5, 0.5);
+
+       buf = g_strdup_printf("%s ( %d / %d )",
+                             _("partial match"),
+                             g_list_index(list, pi) + 1,
+                             g_list_length(list));
+       pan_search_status(pw, buf);
+       g_free(buf);
+
+       g_list_free(list);
+
+       return TRUE;
+}
+
+static gboolean valid_date_separator(gchar c)
+{
+       return (c == '/' || c == '-' || c == ' ' || c == '.' || c == ',');
+}
+
+static GList *pan_search_by_date_val(PanWindow *pw, PanItemType type,
+                                    gint year, gint month, gint day,
+                                    const gchar *key)
+{
+       GList *list = NULL;
+       GList *work;
+
+       work = g_list_last(pw->list_static);
+       while (work)
+               {
+               PanItem *pi;
+
+               pi = work->data;
+               work = work->prev;
+
+               if (pi->fd && (pi->type == type || type == PAN_ITEM_NONE) &&
+                   ((!key && !pi->key) || (key && pi->key && strcmp(key, pi->key) == 0)))
+                       {
+                       struct tm *tl;
+
+                       tl = localtime(&pi->fd->date);
+                       if (tl)
+                               {
+                               gint match;
+
+                               match = (tl->tm_year == year - 1900);
+                               if (match && month >= 0) match = (tl->tm_mon == month - 1);
+                               if (match && day > 0) match = (tl->tm_mday == day);
+
+                               if (match) list = g_list_prepend(list, pi);
+                               }
+                       }
+               }
+
+       return g_list_reverse(list);
+}
+
+static gboolean pan_search_by_date(PanWindow *pw, const gchar *text)
+{
+       PanItem *pi = NULL;
+       GList *list = NULL;
+       GList *found;
+       gint year;
+       gint month = -1;
+       gint day = -1;
+       gchar *ptr;
+       gchar *mptr;
+       struct tm *lt;
+       time_t t;
+       gchar *message;
+       gchar *buf;
+       gchar *buf_count;
+
+       if (!text) return FALSE;
+
+       ptr = (gchar *)text;
+       while (*ptr != '\0')
+               {
+               if (!g_unichar_isdigit(*ptr) && !valid_date_separator(*ptr)) return FALSE;
+               ptr++;
+               }
+
+       t = time(NULL);
+       if (t == -1) return FALSE;
+       lt = localtime(&t);
+       if (!lt) return FALSE;
+
+       if (valid_date_separator(*text))
+               {
+               year = -1;
+               mptr = (gchar *)text;
+               }
+       else
+               {
+               year = (gint)strtol(text, &mptr, 10);
+               if (mptr == text) return FALSE;
+               }
+
+       if (*mptr != '\0' && valid_date_separator(*mptr))
+               {
+               gchar *dptr;
+
+               mptr++;
+               month = strtol(mptr, &dptr, 10);
+               if (dptr == mptr)
+                       {
+                       if (valid_date_separator(*dptr))
+                               {
+                               month = lt->tm_mon + 1;
+                               dptr++;
+                               }
+                       else
+                               {
+                               month = -1;
+                               }
+                       }
+               if (dptr != mptr && *dptr != '\0' && valid_date_separator(*dptr))
+                       {
+                       gchar *eptr;
+                       dptr++;
+                       day = strtol(dptr, &eptr, 10);
+                       if (dptr == eptr)
+                               {
+                               day = lt->tm_mday;
+                               }
+                       }
+               }
+
+       if (year == -1)
+               {
+               year = lt->tm_year + 1900;
+               }
+       else if (year < 100)
+               {
+               if (year > 70)
+                       year+= 1900;
+               else
+                       year+= 2000;
+               }
+
+       if (year < 1970 ||
+           month < -1 || month == 0 || month > 12 ||
+           day < -1 || day == 0 || day > 31) return FALSE;
+
+       t = pan_date_to_time(year, month, day);
+       if (t < 0) return FALSE;
+
+       if (pw->layout == PAN_LAYOUT_CALENDAR)
+               {
+               list = pan_search_by_date_val(pw, PAN_ITEM_BOX, year, month, day, "day");
+               }
+       else
+               {
+               PanItemType type;
+
+               type = (pw->size > PAN_IMAGE_SIZE_THUMB_LARGE) ? PAN_ITEM_IMAGE : PAN_ITEM_THUMB;
+               list = pan_search_by_date_val(pw, type, year, month, day, NULL);
+               }
+
+       if (list)
+               {
+               found = g_list_find(list, pw->search_pi);
+               if (found && found->next)
+                       {
+                       found = found->next;
+                       pi = found->data;
+                       }
+               else
+                       {
+                       pi = list->data;
+                       }
+               }
+
+       pw->search_pi = pi;
+
+       if (pw->layout == PAN_LAYOUT_CALENDAR && pi && pi->type == PAN_ITEM_BOX)
+               {
+               pan_info_update(pw, NULL);
+               pan_calendar_update(pw, pi);
+               image_scroll_to_point(pw->imd,
+                                     pi->x + pi->width / 2,
+                                     pi->y + pi->height / 2, 0.5, 0.5);
+               }
+       else if (pi)
+               {
+               pan_info_update(pw, pi);
+               image_scroll_to_point(pw->imd,
+                                     pi->x - PAN_BOX_BORDER * 5 / 2,
+                                     pi->y, 0.0, 0.5);
+               }
+
+       if (month > 0)
+               {
+               buf = pan_date_value_string(t, PAN_DATE_LENGTH_MONTH);
+               if (day > 0)
+                       {
+                       gchar *tmp;
+                       tmp = buf;
+                       buf = g_strdup_printf("%d %s", day, tmp);
+                       g_free(tmp);
+                       }
+               }
+       else
+               {
+               buf = pan_date_value_string(t, PAN_DATE_LENGTH_YEAR);
+               }
+
+       if (pi)
+               {
+               buf_count = g_strdup_printf("( %d / %d )",
+                                           g_list_index(list, pi) + 1,
+                                           g_list_length(list));
+               }
+       else
+               {
+               buf_count = g_strdup_printf("(%s)", _("no match"));
+               }
+
+       message = g_strdup_printf("%s %s %s", _("Date:"), buf, buf_count);
+       g_free(buf);
+       g_free(buf_count);
+       pan_search_status(pw, message);
+       g_free(message);
+
+       g_list_free(list);
+
+       return TRUE;
+}
+
+void pan_search_activate_cb(const gchar *text, gpointer data)
+{
+       PanWindow *pw = data;
+
+       if (!text) return;
+
+       tab_completion_append_to_history(pw->search_ui->search_entry, text);
+
+       if (pan_search_by_path(pw, text)) return;
+
+       if ((pw->layout == PAN_LAYOUT_TIMELINE ||
+            pw->layout == PAN_LAYOUT_CALENDAR) &&
+           pan_search_by_date(pw, text))
+               {
+               return;
+               }
+
+       if (pan_search_by_partial(pw, text)) return;
+
+       pan_search_status(pw, _("no match"));
+}
+
+void pan_search_activate(PanWindow *pw)
+{
+       gchar *text;
+
+       text = g_strdup(gtk_entry_get_text(GTK_ENTRY(pw->search_ui->search_entry)));
+       pan_search_activate_cb(text, pw);
+       g_free(text);
+}
+
+void pan_search_toggle_cb(GtkWidget *button, gpointer data)
+{
+       PanWindow *pw = data;
+       PanViewSearchUi *ui = pw->search_ui;
+       gboolean visible;
+
+       visible = gtk_widget_get_visible(ui->search_box);
+       if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)) == visible) return;
+
+       if (visible)
+               {
+               gtk_widget_hide(ui->search_box);
+               gtk_arrow_set(GTK_ARROW(ui->search_button_arrow), GTK_ARROW_UP, GTK_SHADOW_NONE);
+               }
+       else
+               {
+               gtk_widget_show(ui->search_box);
+               gtk_arrow_set(GTK_ARROW(ui->search_button_arrow), GTK_ARROW_DOWN, GTK_SHADOW_NONE);
+               gtk_widget_grab_focus(ui->search_entry);
+               }
+}
+
+void pan_search_toggle_visible(PanWindow *pw, gboolean enable)
+{
+       PanViewSearchUi *ui = pw->search_ui;
+       if (pw->fs) return;
+
+       if (enable)
+               {
+               if (gtk_widget_get_visible(ui->search_box))
+                       {
+                       gtk_widget_grab_focus(ui->search_entry);
+                       }
+               else
+                       {
+                       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ui->search_button), TRUE);
+                       }
+               }
+       else
+               {
+               if (gtk_widget_get_visible(ui->search_entry))
+                       {
+                       if (gtk_widget_has_focus(ui->search_entry))
+                               {
+                               gtk_widget_grab_focus(GTK_WIDGET(pw->imd->widget));
+                               }
+                       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ui->search_button), FALSE);
+                       }
+               }
+}
diff --git a/src/pan-view/pan-view-search.h b/src/pan-view/pan-view-search.h
new file mode 100644 (file)
index 0000000..88784b7
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2006 John Ellis
+ * Copyright (C) 2008 - 2016 The Geeqie Team
+ *
+ * Author: John Ellis
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PAN_VIEW_PAN_VIEW_SEARCH_H
+#define PAN_VIEW_PAN_VIEW_SEARCH_H
+
+#include "main.h"
+#include "pan-types.h"
+
+void pan_search_toggle_visible(PanWindow *pw, gboolean enable);
+void pan_search_activate(PanWindow *pw);
+void pan_search_activate_cb(const gchar *text, gpointer data);
+void pan_search_toggle_cb(GtkWidget *button, gpointer data);
+
+// Creates a new PanViewSearchUi instance and returns it.
+PanViewSearchUi *pan_search_ui_new(PanWindow *pw);
+
+// Destroys the specified PanViewSearchUi and sets the pointer to NULL.
+void pan_search_ui_destroy(PanViewSearchUi **ui);
+
+#endif
+/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
index db70b65..59c4cc0 100644 (file)
@@ -38,6 +38,8 @@
 #include "pan-item.h"
 #include "pan-timeline.h"
 #include "pan-util.h"
+#include "pan-view-filter.h"
+#include "pan-view-search.h"
 #include "pixbuf-renderer.h"
 #include "pixbuf_util.h"
 #include "thumb.h"
@@ -78,9 +80,6 @@ static void pan_layout_update_idle(PanWindow *pw);
 
 static void pan_fullscreen_toggle(PanWindow *pw, gboolean force_off);
 
-static void pan_search_toggle_visible(PanWindow *pw, gboolean enable);
-static void pan_search_activate(PanWindow *pw);
-
 static void pan_window_close(PanWindow *pw);
 
 static GtkWidget *pan_popup_menu(PanWindow *pw);
@@ -1072,7 +1071,7 @@ static void pan_layout_update_idle(PanWindow *pw)
                }
 }
 
-static void pan_layout_update(PanWindow *pw)
+void pan_layout_update(PanWindow *pw)
 {
        pan_window_message(pw, _("Sorting images..."));
        pan_layout_update_idle(pw);
@@ -1134,7 +1133,8 @@ static gboolean pan_window_key_press_cb(GtkWidget *widget, GdkEventKey *event, g
        imd_widget = gtk_container_get_focus_child(GTK_CONTAINER(pw->imd->widget));
        focused = (pw->fs || (imd_widget && gtk_widget_has_focus(imd_widget)));
        on_entry = (gtk_widget_has_focus(pw->path_entry) ||
-                   gtk_widget_has_focus(pw->search_entry));
+                   gtk_widget_has_focus(pw->search_ui->search_entry) ||
+                   gtk_widget_has_focus(pw->filter_ui->filter_entry));
 
        if (focused)
                {
@@ -1248,6 +1248,7 @@ static gboolean pan_window_key_press_cb(GtkWidget *widget, GdkEventKey *event, g
 
                if (stop_signal) return stop_signal;
 
+               // Don't steal characters from entry boxes.
                if (!on_entry)
                        {
                        stop_signal = TRUE;
@@ -1326,7 +1327,7 @@ static void pan_info_add_exif(PanTextAlignment *ta, FileData *fd)
 }
 
 
-static void pan_info_update(PanWindow *pw, PanItem *pi)
+void pan_info_update(PanWindow *pw, PanItem *pi)
 {
        PanTextAlignment *ta;
        PanItem *pbox;
@@ -1450,399 +1451,6 @@ static void pan_info_update(PanWindow *pw, PanItem *pi)
 }
 
 
-/*
- *-----------------------------------------------------------------------------
- * search
- *-----------------------------------------------------------------------------
- */
-
-static void pan_search_status(PanWindow *pw, const gchar *text)
-{
-       gtk_label_set_text(GTK_LABEL(pw->search_label), (text) ? text : "");
-}
-
-static gint pan_search_by_path(PanWindow *pw, const gchar *path)
-{
-       PanItem *pi;
-       GList *list;
-       GList *found;
-       PanItemType type;
-       gchar *buf;
-
-       type = (pw->size > PAN_IMAGE_SIZE_THUMB_LARGE) ? PAN_ITEM_IMAGE : PAN_ITEM_THUMB;
-
-       list = pan_item_find_by_path(pw, type, path, FALSE, FALSE);
-       if (!list) return FALSE;
-
-       found = g_list_find(list, pw->click_pi);
-       if (found && found->next)
-               {
-               found = found->next;
-               pi = found->data;
-               }
-       else
-               {
-               pi = list->data;
-               }
-
-       pan_info_update(pw, pi);
-       image_scroll_to_point(pw->imd, pi->x + pi->width / 2, pi->y + pi->height / 2, 0.5, 0.5);
-
-       buf = g_strdup_printf("%s ( %d / %d )",
-                             (path[0] == G_DIR_SEPARATOR) ? _("path found") : _("filename found"),
-                             g_list_index(list, pi) + 1,
-                             g_list_length(list));
-       pan_search_status(pw, buf);
-       g_free(buf);
-
-       g_list_free(list);
-
-       return TRUE;
-}
-
-static gboolean pan_search_by_partial(PanWindow *pw, const gchar *text)
-{
-       PanItem *pi;
-       GList *list;
-       GList *found;
-       PanItemType type;
-       gchar *buf;
-
-       type = (pw->size > PAN_IMAGE_SIZE_THUMB_LARGE) ? PAN_ITEM_IMAGE : PAN_ITEM_THUMB;
-
-       list = pan_item_find_by_path(pw, type, text, TRUE, FALSE);
-       if (!list) list = pan_item_find_by_path(pw, type, text, FALSE, TRUE);
-       if (!list)
-               {
-               gchar *needle;
-
-               needle = g_utf8_strdown(text, -1);
-               list = pan_item_find_by_path(pw, type, needle, TRUE, TRUE);
-               g_free(needle);
-               }
-       if (!list) return FALSE;
-
-       found = g_list_find(list, pw->click_pi);
-       if (found && found->next)
-               {
-               found = found->next;
-               pi = found->data;
-               }
-       else
-               {
-               pi = list->data;
-               }
-
-       pan_info_update(pw, pi);
-       image_scroll_to_point(pw->imd, pi->x + pi->width / 2, pi->y + pi->height / 2, 0.5, 0.5);
-
-       buf = g_strdup_printf("%s ( %d / %d )",
-                             _("partial match"),
-                             g_list_index(list, pi) + 1,
-                             g_list_length(list));
-       pan_search_status(pw, buf);
-       g_free(buf);
-
-       g_list_free(list);
-
-       return TRUE;
-}
-
-static gboolean valid_date_separator(gchar c)
-{
-       return (c == '/' || c == '-' || c == ' ' || c == '.' || c == ',');
-}
-
-static GList *pan_search_by_date_val(PanWindow *pw, PanItemType type,
-                                    gint year, gint month, gint day,
-                                    const gchar *key)
-{
-       GList *list = NULL;
-       GList *work;
-
-       work = g_list_last(pw->list_static);
-       while (work)
-               {
-               PanItem *pi;
-
-               pi = work->data;
-               work = work->prev;
-
-               if (pi->fd && (pi->type == type || type == PAN_ITEM_NONE) &&
-                   ((!key && !pi->key) || (key && pi->key && strcmp(key, pi->key) == 0)))
-                       {
-                       struct tm *tl;
-
-                       tl = localtime(&pi->fd->date);
-                       if (tl)
-                               {
-                               gint match;
-
-                               match = (tl->tm_year == year - 1900);
-                               if (match && month >= 0) match = (tl->tm_mon == month - 1);
-                               if (match && day > 0) match = (tl->tm_mday == day);
-
-                               if (match) list = g_list_prepend(list, pi);
-                               }
-                       }
-               }
-
-       return g_list_reverse(list);
-}
-
-static gboolean pan_search_by_date(PanWindow *pw, const gchar *text)
-{
-       PanItem *pi = NULL;
-       GList *list = NULL;
-       GList *found;
-       gint year;
-       gint month = -1;
-       gint day = -1;
-       gchar *ptr;
-       gchar *mptr;
-       struct tm *lt;
-       time_t t;
-       gchar *message;
-       gchar *buf;
-       gchar *buf_count;
-
-       if (!text) return FALSE;
-
-       ptr = (gchar *)text;
-       while (*ptr != '\0')
-               {
-               if (!g_unichar_isdigit(*ptr) && !valid_date_separator(*ptr)) return FALSE;
-               ptr++;
-               }
-
-       t = time(NULL);
-       if (t == -1) return FALSE;
-       lt = localtime(&t);
-       if (!lt) return FALSE;
-
-       if (valid_date_separator(*text))
-               {
-               year = -1;
-               mptr = (gchar *)text;
-               }
-       else
-               {
-               year = (gint)strtol(text, &mptr, 10);
-               if (mptr == text) return FALSE;
-               }
-
-       if (*mptr != '\0' && valid_date_separator(*mptr))
-               {
-               gchar *dptr;
-
-               mptr++;
-               month = strtol(mptr, &dptr, 10);
-               if (dptr == mptr)
-                       {
-                       if (valid_date_separator(*dptr))
-                               {
-                               month = lt->tm_mon + 1;
-                               dptr++;
-                               }
-                       else
-                               {
-                               month = -1;
-                               }
-                       }
-               if (dptr != mptr && *dptr != '\0' && valid_date_separator(*dptr))
-                       {
-                       gchar *eptr;
-                       dptr++;
-                       day = strtol(dptr, &eptr, 10);
-                       if (dptr == eptr)
-                               {
-                               day = lt->tm_mday;
-                               }
-                       }
-               }
-
-       if (year == -1)
-               {
-               year = lt->tm_year + 1900;
-               }
-       else if (year < 100)
-               {
-               if (year > 70)
-                       year+= 1900;
-               else
-                       year+= 2000;
-               }
-
-       if (year < 1970 ||
-           month < -1 || month == 0 || month > 12 ||
-           day < -1 || day == 0 || day > 31) return FALSE;
-
-       t = pan_date_to_time(year, month, day);
-       if (t < 0) return FALSE;
-
-       if (pw->layout == PAN_LAYOUT_CALENDAR)
-               {
-               list = pan_search_by_date_val(pw, PAN_ITEM_BOX, year, month, day, "day");
-               }
-       else
-               {
-               PanItemType type;
-
-               type = (pw->size > PAN_IMAGE_SIZE_THUMB_LARGE) ? PAN_ITEM_IMAGE : PAN_ITEM_THUMB;
-               list = pan_search_by_date_val(pw, type, year, month, day, NULL);
-               }
-
-       if (list)
-               {
-               found = g_list_find(list, pw->search_pi);
-               if (found && found->next)
-                       {
-                       found = found->next;
-                       pi = found->data;
-                       }
-               else
-                       {
-                       pi = list->data;
-                       }
-               }
-
-       pw->search_pi = pi;
-
-       if (pw->layout == PAN_LAYOUT_CALENDAR && pi && pi->type == PAN_ITEM_BOX)
-               {
-               pan_info_update(pw, NULL);
-               pan_calendar_update(pw, pi);
-               image_scroll_to_point(pw->imd,
-                                     pi->x + pi->width / 2,
-                                     pi->y + pi->height / 2, 0.5, 0.5);
-               }
-       else if (pi)
-               {
-               pan_info_update(pw, pi);
-               image_scroll_to_point(pw->imd,
-                                     pi->x - PAN_BOX_BORDER * 5 / 2,
-                                     pi->y, 0.0, 0.5);
-               }
-
-       if (month > 0)
-               {
-               buf = pan_date_value_string(t, PAN_DATE_LENGTH_MONTH);
-               if (day > 0)
-                       {
-                       gchar *tmp;
-                       tmp = buf;
-                       buf = g_strdup_printf("%d %s", day, tmp);
-                       g_free(tmp);
-                       }
-               }
-       else
-               {
-               buf = pan_date_value_string(t, PAN_DATE_LENGTH_YEAR);
-               }
-
-       if (pi)
-               {
-               buf_count = g_strdup_printf("( %d / %d )",
-                                           g_list_index(list, pi) + 1,
-                                           g_list_length(list));
-               }
-       else
-               {
-               buf_count = g_strdup_printf("(%s)", _("no match"));
-               }
-
-       message = g_strdup_printf("%s %s %s", _("Date:"), buf, buf_count);
-       g_free(buf);
-       g_free(buf_count);
-       pan_search_status(pw, message);
-       g_free(message);
-
-       g_list_free(list);
-
-       return TRUE;
-}
-
-static void pan_search_activate_cb(const gchar *text, gpointer data)
-{
-       PanWindow *pw = data;
-
-       if (!text) return;
-
-       tab_completion_append_to_history(pw->search_entry, text);
-
-       if (pan_search_by_path(pw, text)) return;
-
-       if ((pw->layout == PAN_LAYOUT_TIMELINE ||
-            pw->layout == PAN_LAYOUT_CALENDAR) &&
-           pan_search_by_date(pw, text))
-               {
-               return;
-               }
-
-       if (pan_search_by_partial(pw, text)) return;
-
-       pan_search_status(pw, _("no match"));
-}
-
-static void pan_search_activate(PanWindow *pw)
-{
-       gchar *text;
-
-       text = g_strdup(gtk_entry_get_text(GTK_ENTRY(pw->search_entry)));
-       pan_search_activate_cb(text, pw);
-       g_free(text);
-}
-
-static void pan_search_toggle_cb(GtkWidget *button, gpointer data)
-{
-       PanWindow *pw = data;
-       gboolean visible;
-
-       visible = gtk_widget_get_visible(pw->search_box);
-       if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)) == visible) return;
-
-       if (visible)
-               {
-               gtk_widget_hide(pw->search_box);
-               gtk_arrow_set(GTK_ARROW(pw->search_button_arrow), GTK_ARROW_UP, GTK_SHADOW_NONE);
-               }
-       else
-               {
-               gtk_widget_show(pw->search_box);
-               gtk_arrow_set(GTK_ARROW(pw->search_button_arrow), GTK_ARROW_DOWN, GTK_SHADOW_NONE);
-               gtk_widget_grab_focus(pw->search_entry);
-               }
-}
-
-static void pan_search_toggle_visible(PanWindow *pw, gboolean enable)
-{
-       if (pw->fs) return;
-
-       if (enable)
-               {
-               if (gtk_widget_get_visible(pw->search_box))
-                       {
-                       gtk_widget_grab_focus(pw->search_entry);
-                       }
-               else
-                       {
-                       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pw->search_button), TRUE);
-                       }
-               }
-       else
-               {
-               if (gtk_widget_get_visible(pw->search_entry))
-                       {
-                       if (gtk_widget_has_focus(pw->search_entry))
-                               {
-                               gtk_widget_grab_focus(GTK_WIDGET(pw->imd->widget));
-                               }
-                       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pw->search_button), FALSE);
-                       }
-               }
-}
-
-
 /*
  *-----------------------------------------------------------------------------
  * main window
@@ -2129,6 +1737,8 @@ static void pan_window_close(PanWindow *pw)
                }
 
        pan_fullscreen_toggle(pw, TRUE);
+       pan_search_ui_destroy(&pw->search_ui);
+       pan_filter_ui_destroy(&pw->filter_ui);
        gtk_widget_destroy(pw->window);
 
        pan_window_items_free(pw);
@@ -2274,24 +1884,12 @@ static void pan_window_new_real(FileData *dir_fd)
 
        /* find bar */
 
-       pw->search_box = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
-       gtk_box_pack_start(GTK_BOX(vbox), pw->search_box, FALSE, FALSE, 2);
+       pw->search_ui = pan_search_ui_new(pw);
+       gtk_box_pack_start(GTK_BOX(vbox), pw->search_ui->search_box, FALSE, FALSE, 2);
 
-       pref_spacer(pw->search_box, 0);
-       pref_label_new(pw->search_box, _("Find:"));
-
-       hbox = gtk_hbox_new(TRUE, PREF_PAD_SPACE);
-       gtk_box_pack_start(GTK_BOX(pw->search_box), hbox, TRUE, TRUE, 0);
-       gtk_widget_show(hbox);
-
-       combo = tab_completion_new_with_history(&pw->search_entry, "", "pan_view_search", -1,
-                                               pan_search_activate_cb, pw);
-       gtk_box_pack_start(GTK_BOX(hbox), combo, TRUE, TRUE, 0);
-       gtk_widget_show(combo);
-
-       pw->search_label = gtk_label_new("");
-       gtk_box_pack_start(GTK_BOX(hbox), pw->search_label, TRUE, TRUE, 0);
-       gtk_widget_show(pw->search_label);
+    /* filter bar */
+    pw->filter_ui = pan_filter_ui_new(pw);
+    gtk_box_pack_start(GTK_BOX(vbox), pw->filter_ui->filter_box, FALSE, FALSE, 2);
 
        /* status bar */
 
@@ -2320,21 +1918,13 @@ static void pan_window_new_real(FileData *dir_fd)
        gtk_container_add(GTK_CONTAINER(frame), pw->label_zoom);
        gtk_widget_show(pw->label_zoom);
 
-       pw->search_button = gtk_toggle_button_new();
-       gtk_button_set_relief(GTK_BUTTON(pw->search_button), GTK_RELIEF_NONE);
-       gtk_button_set_focus_on_click(GTK_BUTTON(pw->search_button), FALSE);
-       hbox = gtk_hbox_new(FALSE, PREF_PAD_GAP);
-       gtk_container_add(GTK_CONTAINER(pw->search_button), hbox);
-       gtk_widget_show(hbox);
-       pw->search_button_arrow = gtk_arrow_new(GTK_ARROW_UP, GTK_SHADOW_NONE);
-       gtk_box_pack_start(GTK_BOX(hbox), pw->search_button_arrow, FALSE, FALSE, 0);
-       gtk_widget_show(pw->search_button_arrow);
-       pref_label_new(hbox, _("Find"));
-
-       gtk_box_pack_end(GTK_BOX(box), pw->search_button, FALSE, FALSE, 0);
-       gtk_widget_show(pw->search_button);
-       g_signal_connect(G_OBJECT(pw->search_button), "clicked",
-                        G_CALLBACK(pan_search_toggle_cb), pw);
+       // Add the "Find" button to the status bar area.
+       gtk_box_pack_end(GTK_BOX(box), pw->search_ui->search_button, FALSE, FALSE, 0);
+       gtk_widget_show(pw->search_ui->search_button);
+
+       // Add the "Filter" button to the status bar area.
+       gtk_box_pack_end(GTK_BOX(box), pw->filter_ui->filter_button, FALSE, FALSE, 0);
+       gtk_widget_show(pw->filter_ui->filter_button);
 
        g_signal_connect(G_OBJECT(pw->window), "delete_event",
                         G_CALLBACK(pan_window_delete_cb), pw);
index 7a7ddc7..a17c6ab 100644 (file)
@@ -25,6 +25,7 @@
 #include "main.h"
 #include "pan-types.h"
 
+void pan_layout_update(PanWindow *pw);
 GList *pan_layout_intersect(PanWindow *pw, gint x, gint y, gint width, gint height);
 void pan_layout_resize(PanWindow *pw);
 
@@ -32,6 +33,7 @@ void pan_cache_sync_date(PanWindow *pw, GList *list);
 
 GList *pan_cache_sort(GList *list, SortType method, gboolean ascend);
 
+void pan_info_update(PanWindow *pw, PanItem *pi);
 
 #endif
 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */