2577d38ff45f38faaab6b3117f8f64d71f192a4d
[geeqie.git] / src / pan-view / pan-view-search.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-search.h"
23
24 #include "image.h"
25 #include "pan-calendar.h"
26 #include "pan-item.h"
27 #include "pan-util.h"
28 #include "pan-view.h"
29 #include "ui_tabcomp.h"
30 #include "ui_misc.h"
31
32 PanViewSearchUi *pan_search_ui_new(PanWindow *pw)
33 {
34         PanViewSearchUi *ui = g_new0(PanViewSearchUi, 1);
35         GtkWidget *combo;
36         GtkWidget *hbox;
37
38         // Build the actual search UI.
39         ui->search_box = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
40         pref_spacer(ui->search_box, 0);
41         pref_label_new(ui->search_box, _("Find:"));
42
43         hbox = gtk_hbox_new(TRUE, PREF_PAD_SPACE);
44         gtk_box_pack_start(GTK_BOX(ui->search_box), hbox, TRUE, TRUE, 0);
45         gtk_widget_show(hbox);
46
47         combo = tab_completion_new_with_history(&ui->search_entry, "", "pan_view_search", -1,
48                                                 pan_search_activate_cb, pw);
49         gtk_box_pack_start(GTK_BOX(hbox), combo, TRUE, TRUE, 0);
50         gtk_widget_show(combo);
51
52         ui->search_label = gtk_label_new("");
53         gtk_box_pack_start(GTK_BOX(hbox), ui->search_label, TRUE, TRUE, 0);
54         gtk_widget_show(ui->search_label);
55
56         // Build the spin-button to show/hide the search UI.
57         ui->search_button = gtk_toggle_button_new();
58         gtk_button_set_relief(GTK_BUTTON(ui->search_button), GTK_RELIEF_NONE);
59         gtk_button_set_focus_on_click(GTK_BUTTON(ui->search_button), FALSE);
60         hbox = gtk_hbox_new(FALSE, PREF_PAD_GAP);
61         gtk_container_add(GTK_CONTAINER(ui->search_button), hbox);
62         gtk_widget_show(hbox);
63         ui->search_button_arrow = gtk_arrow_new(GTK_ARROW_UP, GTK_SHADOW_NONE);
64         gtk_box_pack_start(GTK_BOX(hbox), ui->search_button_arrow, FALSE, FALSE, 0);
65         gtk_widget_show(ui->search_button_arrow);
66         pref_label_new(hbox, _("Find"));
67
68         g_signal_connect(G_OBJECT(ui->search_button), "clicked",
69                          G_CALLBACK(pan_search_toggle_cb), pw);
70
71         return ui;
72 }
73
74 void pan_search_ui_destroy(PanViewSearchUi **ui_ptr)
75 {
76         if (ui_ptr == NULL || *ui_ptr == NULL) return;
77
78         PanViewSearchUi *ui = *ui_ptr;  // For convenience.
79
80         // Note that g_clear_object handles already-NULL pointers.
81         g_clear_object(&ui->search_label);
82         g_clear_object(&ui->search_button);
83         g_clear_object(&ui->search_box);
84         g_clear_object(&ui->search_button_arrow);
85         g_clear_object(&ui->search_button);
86
87         g_free(ui);
88         *ui_ptr = NULL;
89 }
90
91 static void pan_search_status(PanWindow *pw, const gchar *text)
92 {
93         gtk_label_set_text(GTK_LABEL(pw->search_ui->search_label), (text) ? text : "");
94 }
95
96 static gint pan_search_by_path(PanWindow *pw, const gchar *path)
97 {
98         PanItem *pi;
99         GList *list;
100         GList *found;
101         PanItemType type;
102         gchar *buf;
103
104         type = (pw->size > PAN_IMAGE_SIZE_THUMB_LARGE) ? PAN_ITEM_IMAGE : PAN_ITEM_THUMB;
105
106         list = pan_item_find_by_path(pw, type, path, FALSE, FALSE);
107         if (!list) return FALSE;
108
109         found = g_list_find(list, pw->click_pi);
110         if (found && found->next)
111                 {
112                 found = found->next;
113                 pi = found->data;
114                 }
115         else
116                 {
117                 pi = list->data;
118                 }
119
120         pan_info_update(pw, pi);
121         image_scroll_to_point(pw->imd, pi->x + pi->width / 2, pi->y + pi->height / 2, 0.5, 0.5);
122
123         buf = g_strdup_printf("%s ( %d / %d )",
124                               (path[0] == G_DIR_SEPARATOR) ? _("path found") : _("filename found"),
125                               g_list_index(list, pi) + 1,
126                               g_list_length(list));
127         pan_search_status(pw, buf);
128         g_free(buf);
129
130         g_list_free(list);
131
132         return TRUE;
133 }
134
135 static gboolean pan_search_by_partial(PanWindow *pw, const gchar *text)
136 {
137         PanItem *pi;
138         GList *list;
139         GList *found;
140         PanItemType type;
141         gchar *buf;
142
143         type = (pw->size > PAN_IMAGE_SIZE_THUMB_LARGE) ? PAN_ITEM_IMAGE : PAN_ITEM_THUMB;
144
145         list = pan_item_find_by_path(pw, type, text, TRUE, FALSE);
146         if (!list) list = pan_item_find_by_path(pw, type, text, FALSE, TRUE);
147         if (!list)
148                 {
149                 gchar *needle;
150
151                 needle = g_utf8_strdown(text, -1);
152                 list = pan_item_find_by_path(pw, type, needle, TRUE, TRUE);
153                 g_free(needle);
154                 }
155         if (!list) return FALSE;
156
157         found = g_list_find(list, pw->click_pi);
158         if (found && found->next)
159                 {
160                 found = found->next;
161                 pi = found->data;
162                 }
163         else
164                 {
165                 pi = list->data;
166                 }
167
168         pan_info_update(pw, pi);
169         image_scroll_to_point(pw->imd, pi->x + pi->width / 2, pi->y + pi->height / 2, 0.5, 0.5);
170
171         buf = g_strdup_printf("%s ( %d / %d )",
172                               _("partial match"),
173                               g_list_index(list, pi) + 1,
174                               g_list_length(list));
175         pan_search_status(pw, buf);
176         g_free(buf);
177
178         g_list_free(list);
179
180         return TRUE;
181 }
182
183 static gboolean valid_date_separator(gchar c)
184 {
185         return (c == '/' || c == '-' || c == ' ' || c == '.' || c == ',');
186 }
187
188 static GList *pan_search_by_date_val(PanWindow *pw, PanItemType type,
189                                      gint year, gint month, gint day,
190                                      const gchar *key)
191 {
192         GList *list = NULL;
193         GList *work;
194
195         work = g_list_last(pw->list_static);
196         while (work)
197                 {
198                 PanItem *pi;
199
200                 pi = work->data;
201                 work = work->prev;
202
203                 if (pi->fd && (pi->type == type || type == PAN_ITEM_NONE) &&
204                     ((!key && !pi->key) || (key && pi->key && strcmp(key, pi->key) == 0)))
205                         {
206                         struct tm *tl;
207
208                         tl = localtime(&pi->fd->date);
209                         if (tl)
210                                 {
211                                 gint match;
212
213                                 match = (tl->tm_year == year - 1900);
214                                 if (match && month >= 0) match = (tl->tm_mon == month - 1);
215                                 if (match && day > 0) match = (tl->tm_mday == day);
216
217                                 if (match) list = g_list_prepend(list, pi);
218                                 }
219                         }
220                 }
221
222         return g_list_reverse(list);
223 }
224
225 static gboolean pan_search_by_date(PanWindow *pw, const gchar *text)
226 {
227         PanItem *pi = NULL;
228         GList *list = NULL;
229         GList *found;
230         gint year;
231         gint month = -1;
232         gint day = -1;
233         gchar *ptr;
234         gchar *mptr;
235         struct tm *lt;
236         time_t t;
237         gchar *message;
238         gchar *buf;
239         gchar *buf_count;
240
241         if (!text) return FALSE;
242
243         ptr = (gchar *)text;
244         while (*ptr != '\0')
245                 {
246                 if (!g_unichar_isdigit(*ptr) && !valid_date_separator(*ptr)) return FALSE;
247                 ptr++;
248                 }
249
250         t = time(NULL);
251         if (t == -1) return FALSE;
252         lt = localtime(&t);
253         if (!lt) return FALSE;
254
255         if (valid_date_separator(*text))
256                 {
257                 year = -1;
258                 mptr = (gchar *)text;
259                 }
260         else
261                 {
262                 year = (gint)strtol(text, &mptr, 10);
263                 if (mptr == text) return FALSE;
264                 }
265
266         if (*mptr != '\0' && valid_date_separator(*mptr))
267                 {
268                 gchar *dptr;
269
270                 mptr++;
271                 month = strtol(mptr, &dptr, 10);
272                 if (dptr == mptr)
273                         {
274                         if (valid_date_separator(*dptr))
275                                 {
276                                 month = lt->tm_mon + 1;
277                                 dptr++;
278                                 }
279                         else
280                                 {
281                                 month = -1;
282                                 }
283                         }
284                 if (dptr != mptr && *dptr != '\0' && valid_date_separator(*dptr))
285                         {
286                         gchar *eptr;
287                         dptr++;
288                         day = strtol(dptr, &eptr, 10);
289                         if (dptr == eptr)
290                                 {
291                                 day = lt->tm_mday;
292                                 }
293                         }
294                 }
295
296         if (year == -1)
297                 {
298                 year = lt->tm_year + 1900;
299                 }
300         else if (year < 100)
301                 {
302                 if (year > 70)
303                         year+= 1900;
304                 else
305                         year+= 2000;
306                 }
307
308         if (year < 1970 ||
309             month < -1 || month == 0 || month > 12 ||
310             day < -1 || day == 0 || day > 31) return FALSE;
311
312         t = pan_date_to_time(year, month, day);
313         if (t < 0) return FALSE;
314
315         if (pw->layout == PAN_LAYOUT_CALENDAR)
316                 {
317                 list = pan_search_by_date_val(pw, PAN_ITEM_BOX, year, month, day, "day");
318                 }
319         else
320                 {
321                 PanItemType type;
322
323                 type = (pw->size > PAN_IMAGE_SIZE_THUMB_LARGE) ? PAN_ITEM_IMAGE : PAN_ITEM_THUMB;
324                 list = pan_search_by_date_val(pw, type, year, month, day, NULL);
325                 }
326
327         if (list)
328                 {
329                 found = g_list_find(list, pw->search_pi);
330                 if (found && found->next)
331                         {
332                         found = found->next;
333                         pi = found->data;
334                         }
335                 else
336                         {
337                         pi = list->data;
338                         }
339                 }
340
341         pw->search_pi = pi;
342
343         if (pw->layout == PAN_LAYOUT_CALENDAR && pi && pi->type == PAN_ITEM_BOX)
344                 {
345                 pan_info_update(pw, NULL);
346                 pan_calendar_update(pw, pi);
347                 image_scroll_to_point(pw->imd,
348                                       pi->x + pi->width / 2,
349                                       pi->y + pi->height / 2, 0.5, 0.5);
350                 }
351         else if (pi)
352                 {
353                 pan_info_update(pw, pi);
354                 image_scroll_to_point(pw->imd,
355                                       pi->x - PAN_BOX_BORDER * 5 / 2,
356                                       pi->y, 0.0, 0.5);
357                 }
358
359         if (month > 0)
360                 {
361                 buf = pan_date_value_string(t, PAN_DATE_LENGTH_MONTH);
362                 if (day > 0)
363                         {
364                         gchar *tmp;
365                         tmp = buf;
366                         buf = g_strdup_printf("%d %s", day, tmp);
367                         g_free(tmp);
368                         }
369                 }
370         else
371                 {
372                 buf = pan_date_value_string(t, PAN_DATE_LENGTH_YEAR);
373                 }
374
375         if (pi)
376                 {
377                 buf_count = g_strdup_printf("( %d / %d )",
378                                             g_list_index(list, pi) + 1,
379                                             g_list_length(list));
380                 }
381         else
382                 {
383                 buf_count = g_strdup_printf("(%s)", _("no match"));
384                 }
385
386         message = g_strdup_printf("%s %s %s", _("Date:"), buf, buf_count);
387         g_free(buf);
388         g_free(buf_count);
389         pan_search_status(pw, message);
390         g_free(message);
391
392         g_list_free(list);
393
394         return TRUE;
395 }
396
397 void pan_search_activate_cb(const gchar *text, gpointer data)
398 {
399         PanWindow *pw = data;
400
401         if (!text) return;
402
403         tab_completion_append_to_history(pw->search_ui->search_entry, text);
404
405         if (pan_search_by_path(pw, text)) return;
406
407         if ((pw->layout == PAN_LAYOUT_TIMELINE ||
408              pw->layout == PAN_LAYOUT_CALENDAR) &&
409             pan_search_by_date(pw, text))
410                 {
411                 return;
412                 }
413
414         if (pan_search_by_partial(pw, text)) return;
415
416         pan_search_status(pw, _("no match"));
417 }
418
419 void pan_search_activate(PanWindow *pw)
420 {
421         gchar *text;
422
423         text = g_strdup(gtk_entry_get_text(GTK_ENTRY(pw->search_ui->search_entry)));
424         pan_search_activate_cb(text, pw);
425         g_free(text);
426 }
427
428 void pan_search_toggle_cb(GtkWidget *button, gpointer data)
429 {
430         PanWindow *pw = data;
431         PanViewSearchUi *ui = pw->search_ui;
432         gboolean visible;
433
434         visible = gtk_widget_get_visible(ui->search_box);
435         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)) == visible) return;
436
437         if (visible)
438                 {
439                 gtk_widget_hide(ui->search_box);
440                 gtk_arrow_set(GTK_ARROW(ui->search_button_arrow), GTK_ARROW_UP, GTK_SHADOW_NONE);
441                 }
442         else
443                 {
444                 gtk_widget_show(ui->search_box);
445                 gtk_arrow_set(GTK_ARROW(ui->search_button_arrow), GTK_ARROW_DOWN, GTK_SHADOW_NONE);
446                 gtk_widget_grab_focus(ui->search_entry);
447                 }
448 }
449
450 void pan_search_toggle_visible(PanWindow *pw, gboolean enable)
451 {
452         PanViewSearchUi *ui = pw->search_ui;
453         if (pw->fs) return;
454
455         if (enable)
456                 {
457                 if (gtk_widget_get_visible(ui->search_box))
458                         {
459                         gtk_widget_grab_focus(ui->search_entry);
460                         }
461                 else
462                         {
463                         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ui->search_button), TRUE);
464                         }
465                 }
466         else
467                 {
468                 if (gtk_widget_get_visible(ui->search_entry))
469                         {
470                         if (gtk_widget_has_focus(ui->search_entry))
471                                 {
472                                 gtk_widget_grab_focus(GTK_WIDGET(pw->imd->widget));
473                                 }
474                         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ui->search_button), FALSE);
475                         }
476                 }
477 }