Move filter code into pan-fiew-filter.{c,h}
[geeqie.git] / src / pan-view / pan-view-filter.c
1 /*
2  * Copyright (C) 2006 John Ellis
3  * Copyright (C) 2008 - 2016 The Geeqie Team
4  *
5  * Author: John Ellis
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include "pan-view-filter.h"
23
24 #include "image.h"
25 #include "metadata.h"
26 #include "pan-item.h"
27 #include "pan-util.h"
28 #include "pan-view.h"
29 #include "ui_fileops.h"
30 #include "ui_tabcomp.h"
31 #include "ui_misc.h"
32
33 PanViewFilterUi *pan_filter_ui_new(PanWindow *pw)
34 {
35         PanViewFilterUi *ui = g_new0(PanViewFilterUi, 1);
36         GtkWidget *combo;
37         GtkWidget *hbox;
38
39         // Build the actual filter UI.
40         ui->filter_box = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
41         pref_spacer(ui->filter_box, 0);
42         pref_label_new(ui->filter_box, _("Keyword Filter:"));
43
44         hbox = gtk_hbox_new(TRUE, PREF_PAD_SPACE);
45         gtk_box_pack_start(GTK_BOX(ui->filter_box), hbox, TRUE, TRUE, 0);
46         gtk_widget_show(hbox);
47
48         combo = tab_completion_new_with_history(&ui->filter_entry, "", "pan_view_filter", -1,
49                                                 pan_filter_activate_cb, pw);
50         gtk_box_pack_start(GTK_BOX(hbox), combo, TRUE, TRUE, 0);
51         gtk_widget_show(combo);
52
53         // TODO(xsdg): Figure out whether it's useful to keep this label around.
54         ui->filter_label = gtk_label_new("");
55         //gtk_box_pack_start(GTK_BOX(hbox), ui->filter_label, FALSE, FALSE, 0);
56         //gtk_widget_show(ui->filter_label);
57
58         ui->filter_kw_hbox = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
59         gtk_box_pack_start(GTK_BOX(hbox), ui->filter_kw_hbox, TRUE, TRUE, 0);
60         gtk_widget_show(ui->filter_kw_hbox);
61
62         // Build the spin-button to show/hide the filter UI.
63         ui->filter_button = gtk_toggle_button_new();
64         gtk_button_set_relief(GTK_BUTTON(ui->filter_button), GTK_RELIEF_NONE);
65         gtk_button_set_focus_on_click(GTK_BUTTON(ui->filter_button), FALSE);
66         hbox = gtk_hbox_new(FALSE, PREF_PAD_GAP);
67         gtk_container_add(GTK_CONTAINER(ui->filter_button), hbox);
68         gtk_widget_show(hbox);
69         ui->filter_button_arrow = gtk_arrow_new(GTK_ARROW_UP, GTK_SHADOW_NONE);
70         gtk_box_pack_start(GTK_BOX(hbox), ui->filter_button_arrow, FALSE, FALSE, 0);
71         gtk_widget_show(ui->filter_button_arrow);
72         pref_label_new(hbox, _("Filter"));
73
74         g_signal_connect(G_OBJECT(ui->filter_button), "clicked",
75                          G_CALLBACK(pan_filter_toggle_cb), pw);
76
77         /* Since we're using the GHashTable as a HashSet (in which key and value pointers
78          * are always identical), specifying key _and_ value destructor callbacks will
79          * cause a double-free.
80          */
81         ui->filter_kw_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
82
83         return ui;
84 }
85
86 void pan_filter_ui_destroy(PanViewFilterUi **ui_ptr)
87 {
88         if (ui_ptr == NULL || *ui_ptr == NULL) return;
89
90         // Note that g_clear_pointer handles already-NULL pointers.
91         g_clear_pointer(&(*ui_ptr)->filter_kw_table, g_hash_table_destroy);
92
93         g_free(*ui_ptr);
94         *ui_ptr = NULL;
95 }
96
97 static void pan_filter_status(PanWindow *pw, const gchar *text)
98 {
99         gtk_label_set_text(GTK_LABEL(pw->filter_ui->filter_label), (text) ? text : "");
100 }
101
102 static void pan_filter_kw_button_cb(GtkButton *widget, gpointer data)
103 {
104         PanWindow *pw = data;
105         PanViewFilterUi *ui = pw->filter_ui;
106
107         g_hash_table_remove(ui->filter_kw_table, gtk_button_get_label(GTK_BUTTON(widget)));
108         gtk_widget_destroy(GTK_WIDGET(widget));
109
110         pan_filter_status(pw, _("Removed keyword…"));
111         pan_layout_update(pw);
112 }
113
114 void pan_filter_activate_cb(const gchar *text, gpointer data)
115 {
116         GtkWidget *kw_button;
117         PanWindow *pw = data;
118         PanViewFilterUi *ui = pw->filter_ui;
119
120         if (!text) return;
121
122         gtk_entry_set_text(GTK_ENTRY(ui->filter_entry), "");
123
124         if (g_hash_table_contains(ui->filter_kw_table, text))
125                 {
126                 pan_filter_status(pw, _("Already added…"));
127                 return;
128                 }
129
130         tab_completion_append_to_history(ui->filter_entry, text);
131
132         g_hash_table_add(ui->filter_kw_table, g_strdup(text));
133
134         kw_button = gtk_button_new_with_label(text);
135         gtk_box_pack_start(GTK_BOX(ui->filter_kw_hbox), kw_button, FALSE, FALSE, 0);
136         gtk_widget_show(kw_button);
137
138         g_signal_connect(G_OBJECT(kw_button), "clicked",
139                          G_CALLBACK(pan_filter_kw_button_cb), pw);
140
141         pan_filter_status(pw, _("Added keyword…"));
142         pan_layout_update(pw);
143 }
144
145 void pan_filter_activate(PanWindow *pw)
146 {
147         gchar *text;
148
149         text = g_strdup(gtk_entry_get_text(GTK_ENTRY(pw->filter_ui->filter_entry)));
150         pan_filter_activate_cb(text, pw);
151         g_free(text);
152 }
153
154 void pan_filter_toggle_cb(GtkWidget *button, gpointer data)
155 {
156         PanWindow *pw = data;
157         PanViewFilterUi *ui = pw->filter_ui;
158         gboolean visible;
159
160         visible = gtk_widget_get_visible(ui->filter_box);
161         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)) == visible) return;
162
163         if (visible)
164                 {
165                 gtk_widget_hide(ui->filter_box);
166                 gtk_arrow_set(GTK_ARROW(ui->filter_button_arrow), GTK_ARROW_UP, GTK_SHADOW_NONE);
167                 }
168         else
169                 {
170                 gtk_widget_show(ui->filter_box);
171                 gtk_arrow_set(GTK_ARROW(ui->filter_button_arrow), GTK_ARROW_DOWN, GTK_SHADOW_NONE);
172                 gtk_widget_grab_focus(ui->filter_entry);
173                 }
174 }
175
176 void pan_filter_toggle_visible(PanWindow *pw, gboolean enable)
177 {
178         PanViewFilterUi *ui = pw->filter_ui;
179         if (pw->fs) return;
180
181         if (enable)
182                 {
183                 if (gtk_widget_get_visible(ui->filter_box))
184                         {
185                         gtk_widget_grab_focus(ui->filter_entry);
186                         }
187                 else
188                         {
189                         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ui->filter_button), TRUE);
190                         }
191                 }
192         else
193                 {
194                 if (gtk_widget_get_visible(ui->filter_entry))
195                         {
196                         if (gtk_widget_has_focus(ui->filter_entry))
197                                 {
198                                 gtk_widget_grab_focus(GTK_WIDGET(pw->imd->widget));
199                                 }
200                         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ui->filter_button), FALSE);
201                         }
202                 }
203 }
204
205 gboolean pan_filter_fd_list(GList **fd_list, GHashTable *kw_table, PanViewFilterMode mode)
206 {
207         GList *work;
208         gboolean modified = FALSE;
209         GHashTableIter kw_iter;
210         gchar *filter_kw;
211
212
213         if (!fd_list || !*fd_list || g_hash_table_size(kw_table) == 0) return modified;
214
215         // TODO(xsdg): Pay attention to filter mode.
216         work = *fd_list;
217         while (work)
218                 {
219                 FileData *fd = work->data;
220                 GList *last_work = work;
221                 work = work->next;
222
223                 // TODO(xsdg): OPTIMIZATION Do the search inside of metadata.c to avoid a
224                 // bunch of string list copies.
225                 GList *img_keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
226                 if (!img_keywords)
227                         {
228                         *fd_list = g_list_delete_link(*fd_list, last_work);
229                         modified = TRUE;
230                         continue;
231                         }
232
233                 gint match_count = 0;
234                 gint miss_count = 0;
235                 g_hash_table_iter_init(&kw_iter, kw_table);
236                 while (g_hash_table_iter_next(&kw_iter, (void**)&filter_kw, NULL))
237                         {
238                         if (g_list_find_custom(img_keywords, filter_kw, (GCompareFunc)g_strcmp0))
239                                 {
240                                 ++match_count;
241                                 }
242                         else
243                                 {
244                                 ++miss_count;
245                                 }
246                         if (miss_count > 0) break;
247                         }
248
249                 string_list_free(img_keywords);
250                 if (miss_count > 0 || match_count == 0)
251                         {
252                         *fd_list = g_list_delete_link(*fd_list, last_work);
253                         modified = TRUE;
254                         }
255                 }
256
257         return modified;
258 }