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