%D%/pan-util.h \
%D%/pan-view.c \
%D%/pan-view.h \
+ %D%/pan-view-filter.c \
+ %D%/pan-view-filter.h \
%D%/pan-view-search.c \
%D%/pan-view-search.h
#include "pan-timeline.h"
+#include "metadata.h"
#include "pan-item.h"
#include "pan-util.h"
#include "pan-view.h"
+#include "ui_fileops.h"
void pan_timeline_compute(PanWindow *pw, FileData *dir_fd, gint *width, gint *height)
{
GList *list;
GList *work;
+ GHashTable *filter_kw_table;
+ GHashTableIter filter_kw_iter;
+ gchar *filter_kw;
gint x, y;
- time_t tc;
+ time_t group_start_date;
gint total;
gint count;
PanItem *pi_month = NULL;
gint y_height;
list = pan_list_tree(dir_fd, SORT_NONE, TRUE, pw->ignore_symlinks);
+ filter_kw_table = pw->filter_ui->filter_kw_table; // Shorthand.
if (pw->cache_list && pw->exif_date_enable)
{
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;
fd = work->data;
work = work->next;
- if (!pan_date_compare(fd->date, tc, PAN_DATE_LENGTH_DAY))
+ // Don't show images that fail the keyword test.
+ if (g_hash_table_size(filter_kw_table) > 0)
{
+ gint match_count = 0;
+ gint miss_count = 0;
+ // TODO(xsdg): OPTIMIZATION Do the search inside of metadata.c to avoid a
+ // bunch of string list copies.
+ // TODO(xsdg): Allow user to switch between union and intersection.
+ GList *img_keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
+ if (!img_keywords) continue;
+
+ g_hash_table_iter_init(&filter_kw_iter, filter_kw_table);
+ while (g_hash_table_iter_next(&filter_kw_iter, (void**)&filter_kw, NULL))
+ {
+ if (g_list_find_custom(img_keywords, filter_kw, (GCompareFunc)g_strcmp0))
+ {
+ ++match_count;
+ }
+ else
+ {
+ ++miss_count;
+ }
+ if (miss_count > 0) break;
+ }
+
+ string_list_free(img_keywords);
+ if (miss_count > 0 || match_count == 0) continue;
+ }
+
+ 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)
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;
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++;
GtkWidget *search_button_arrow;
};
+typedef struct _PanViewFilterUi PanViewFilterUi;
+struct _PanViewFilterUi
+{
+ GtkWidget *filter_box;
+ GtkWidget *filter_entry;
+ GtkWidget *filter_label;
+ GtkWidget *filter_button;
+ GtkWidget *filter_button_arrow;
+ GHashTable *filter_kw_table;
+ GtkWidget *filter_kw_hbox;
+};
+
typedef struct _PanWindow PanWindow;
struct _PanWindow
{
GtkWidget *label_zoom;
PanViewSearchUi *search_ui;
+ PanViewFilterUi *filter_ui;
GtkWidget *date_button;
--- /dev/null
+/*
+ * 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 "pan-item.h"
+#include "pan-util.h"
+#include "pan-view.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;
+
+ // 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:"));
+
+ 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);
+
+ /* 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.
+ */
+ ui->filter_kw_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+
+ 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)
+{
+ PanWindow *pw = data;
+ PanViewFilterUi *ui = pw->filter_ui;
+
+ g_hash_table_remove(ui->filter_kw_table, gtk_button_get_label(GTK_BUTTON(widget)));
+ gtk_widget_destroy(GTK_WIDGET(widget));
+
+ 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;
+
+ if (!text) return;
+
+ gtk_entry_set_text(GTK_ENTRY(ui->filter_entry), "");
+
+ if (g_hash_table_contains(ui->filter_kw_table, text))
+ {
+ pan_filter_status(pw, _("Already added…"));
+ return;
+ }
+
+ tab_completion_append_to_history(ui->filter_entry, text);
+
+ g_hash_table_add(ui->filter_kw_table, g_strdup(text));
+
+ kw_button = gtk_button_new_with_label(text);
+ gtk_box_pack_start(GTK_BOX(ui->filter_kw_hbox), kw_button, FALSE, FALSE, 0);
+ gtk_widget_show(kw_button);
+
+ g_signal_connect(G_OBJECT(kw_button), "clicked",
+ G_CALLBACK(pan_filter_kw_button_cb), pw);
+
+ pan_filter_status(pw, _("Added keyword…"));
+ 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);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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"
+
+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);
+
+#endif
+/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
{
if (ui_ptr == NULL || *ui_ptr == NULL) return;
- PanViewSearchUi *ui = *ui_ptr; // For convenience.
-
- // Note that g_clear_object handles already-NULL pointers.
- g_clear_object(&ui->search_label);
- g_clear_object(&ui->search_button);
- g_clear_object(&ui->search_box);
- g_clear_object(&ui->search_button_arrow);
- g_clear_object(&ui->search_button);
-
- g_free(ui);
+ g_free(*ui_ptr);
*ui_ptr = NULL;
}
#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"
}
}
-static void pan_layout_update(PanWindow *pw)
+void pan_layout_update(PanWindow *pw)
{
pan_window_message(pw, _("Sorting images..."));
pan_layout_update_idle(pw);
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_ui->search_entry));
+ gtk_widget_has_focus(pw->search_ui->search_entry) ||
+ gtk_widget_has_focus(pw->filter_ui->filter_entry));
if (focused)
{
}
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);
pw->search_ui = pan_search_ui_new(pw);
gtk_box_pack_start(GTK_BOX(vbox), pw->search_ui->search_box, FALSE, FALSE, 2);
+ /* 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 */
box = pref_box_new(vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
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);
g_signal_connect(G_OBJECT(pw->window), "key_press_event",
#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);