Adds a keyword filtering feature to Timeline PanView.
[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 "pan-item.h"
26 #include "pan-util.h"
27 #include "pan-view.h"
28 #include "ui_tabcomp.h"
29 #include "ui_misc.h"
30
31 PanViewFilterUi *pan_filter_ui_new(PanWindow *pw)
32 {
33         PanViewFilterUi *ui = g_new0(PanViewFilterUi, 1);
34         GtkWidget *combo;
35         GtkWidget *hbox;
36
37         // Build the actual filter UI.
38         ui->filter_box = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
39         pref_spacer(ui->filter_box, 0);
40         pref_label_new(ui->filter_box, _("Keyword Filter:"));
41
42         hbox = gtk_hbox_new(TRUE, PREF_PAD_SPACE);
43         gtk_box_pack_start(GTK_BOX(ui->filter_box), hbox, TRUE, TRUE, 0);
44         gtk_widget_show(hbox);
45
46         combo = tab_completion_new_with_history(&ui->filter_entry, "", "pan_view_filter", -1,
47                                                 pan_filter_activate_cb, pw);
48         gtk_box_pack_start(GTK_BOX(hbox), combo, TRUE, TRUE, 0);
49         gtk_widget_show(combo);
50
51         // TODO(xsdg): Figure out whether it's useful to keep this label around.
52         ui->filter_label = gtk_label_new("");
53         //gtk_box_pack_start(GTK_BOX(hbox), ui->filter_label, FALSE, FALSE, 0);
54         //gtk_widget_show(ui->filter_label);
55
56         ui->filter_kw_hbox = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
57         gtk_box_pack_start(GTK_BOX(hbox), ui->filter_kw_hbox, TRUE, TRUE, 0);
58         gtk_widget_show(ui->filter_kw_hbox);
59
60         // Build the spin-button to show/hide the filter UI.
61         ui->filter_button = gtk_toggle_button_new();
62         gtk_button_set_relief(GTK_BUTTON(ui->filter_button), GTK_RELIEF_NONE);
63         gtk_button_set_focus_on_click(GTK_BUTTON(ui->filter_button), FALSE);
64         hbox = gtk_hbox_new(FALSE, PREF_PAD_GAP);
65         gtk_container_add(GTK_CONTAINER(ui->filter_button), hbox);
66         gtk_widget_show(hbox);
67         ui->filter_button_arrow = gtk_arrow_new(GTK_ARROW_UP, GTK_SHADOW_NONE);
68         gtk_box_pack_start(GTK_BOX(hbox), ui->filter_button_arrow, FALSE, FALSE, 0);
69         gtk_widget_show(ui->filter_button_arrow);
70         pref_label_new(hbox, _("Filter"));
71
72         g_signal_connect(G_OBJECT(ui->filter_button), "clicked",
73                          G_CALLBACK(pan_filter_toggle_cb), pw);
74
75         /* Since we're using the GHashTable as a HashSet (in which key and value pointers
76          * are always identical), specifying key _and_ value destructor callbacks will
77          * cause a double-free.
78          */
79         ui->filter_kw_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
80
81         return ui;
82 }
83
84 void pan_filter_ui_destroy(PanViewFilterUi **ui_ptr)
85 {
86         if (ui_ptr == NULL || *ui_ptr == NULL) return;
87
88         // Note that g_clear_pointer handles already-NULL pointers.
89         g_clear_pointer(&(*ui_ptr)->filter_kw_table, g_hash_table_destroy);
90
91         g_free(*ui_ptr);
92         *ui_ptr = NULL;
93 }
94
95 static void pan_filter_status(PanWindow *pw, const gchar *text)
96 {
97         gtk_label_set_text(GTK_LABEL(pw->filter_ui->filter_label), (text) ? text : "");
98 }
99
100 static void pan_filter_kw_button_cb(GtkButton *widget, gpointer data)
101 {
102         PanWindow *pw = data;
103         PanViewFilterUi *ui = pw->filter_ui;
104
105         g_hash_table_remove(ui->filter_kw_table, gtk_button_get_label(GTK_BUTTON(widget)));
106         gtk_widget_destroy(GTK_WIDGET(widget));
107
108         pan_filter_status(pw, _("Removed keyword…"));
109         pan_layout_update(pw);
110 }
111
112 void pan_filter_activate_cb(const gchar *text, gpointer data)
113 {
114         GtkWidget *kw_button;
115         PanWindow *pw = data;
116         PanViewFilterUi *ui = pw->filter_ui;
117
118         if (!text) return;
119
120         gtk_entry_set_text(GTK_ENTRY(ui->filter_entry), "");
121
122         if (g_hash_table_contains(ui->filter_kw_table, text))
123                 {
124                 pan_filter_status(pw, _("Already added…"));
125                 return;
126                 }
127
128         tab_completion_append_to_history(ui->filter_entry, text);
129
130         g_hash_table_add(ui->filter_kw_table, g_strdup(text));
131
132         kw_button = gtk_button_new_with_label(text);
133         gtk_box_pack_start(GTK_BOX(ui->filter_kw_hbox), kw_button, FALSE, FALSE, 0);
134         gtk_widget_show(kw_button);
135
136         g_signal_connect(G_OBJECT(kw_button), "clicked",
137                          G_CALLBACK(pan_filter_kw_button_cb), pw);
138
139         pan_filter_status(pw, _("Added keyword…"));
140         pan_layout_update(pw);
141 }
142
143 void pan_filter_activate(PanWindow *pw)
144 {
145         gchar *text;
146
147         text = g_strdup(gtk_entry_get_text(GTK_ENTRY(pw->filter_ui->filter_entry)));
148         pan_filter_activate_cb(text, pw);
149         g_free(text);
150 }
151
152 void pan_filter_toggle_cb(GtkWidget *button, gpointer data)
153 {
154         PanWindow *pw = data;
155         PanViewFilterUi *ui = pw->filter_ui;
156         gboolean visible;
157
158         visible = gtk_widget_get_visible(ui->filter_box);
159         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)) == visible) return;
160
161         if (visible)
162                 {
163                 gtk_widget_hide(ui->filter_box);
164                 gtk_arrow_set(GTK_ARROW(ui->filter_button_arrow), GTK_ARROW_UP, GTK_SHADOW_NONE);
165                 }
166         else
167                 {
168                 gtk_widget_show(ui->filter_box);
169                 gtk_arrow_set(GTK_ARROW(ui->filter_button_arrow), GTK_ARROW_DOWN, GTK_SHADOW_NONE);
170                 gtk_widget_grab_focus(ui->filter_entry);
171                 }
172 }
173
174 void pan_filter_toggle_visible(PanWindow *pw, gboolean enable)
175 {
176         PanViewFilterUi *ui = pw->filter_ui;
177         if (pw->fs) return;
178
179         if (enable)
180                 {
181                 if (gtk_widget_get_visible(ui->filter_box))
182                         {
183                         gtk_widget_grab_focus(ui->filter_entry);
184                         }
185                 else
186                         {
187                         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ui->filter_button), TRUE);
188                         }
189                 }
190         else
191                 {
192                 if (gtk_widget_get_visible(ui->filter_entry))
193                         {
194                         if (gtk_widget_has_focus(ui->filter_entry))
195                                 {
196                                 gtk_widget_grab_focus(GTK_WIDGET(pw->imd->widget));
197                                 }
198                         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ui->filter_button), FALSE);
199                         }
200                 }
201 }