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