4bc5334833460d16005bdc336b0d358684062622
[geeqie.git] / src / search.c
1 /*
2  * Copyright (C) 2005 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 "main.h"
23 #include "search.h"
24
25 #include "cache.h"
26 #include "collect.h"
27 #include "collect-table.h"
28 #include "dnd.h"
29 #include "dupe.h"
30 #include "editors.h"
31 #include "filedata.h"
32 #include "image-load.h"
33 #include "img-view.h"
34 #include "layout.h"
35 #include "math.h"
36 #include "menu.h"
37 #include "metadata.h"
38 #include "misc.h"
39 #include "print.h"
40 #include "thumb.h"
41 #include "ui_bookmark.h"
42 #include "ui_fileops.h"
43 #include "ui_menu.h"
44 #include "ui_misc.h"
45 #include "ui_spinner.h"
46 #include "ui_tabcomp.h"
47 #include "ui_tree_edit.h"
48 #include "uri_utils.h"
49 #include "utilops.h"
50 #include "window.h"
51 #include "bar_keywords.h"
52
53 #include <gdk/gdkkeysyms.h> /* for keyboard values */
54
55
56 #define DEF_SEARCH_WIDTH  700
57 #define DEF_SEARCH_HEIGHT 450
58
59 #define SEARCH_BUFFER_MATCH_LOAD 20
60 #define SEARCH_BUFFER_MATCH_HIT  5
61 #define SEARCH_BUFFER_MATCH_MISS 1
62 #define SEARCH_BUFFER_FLUSH_SIZE 99
63
64 #define GEOCODE_NAME "geocode-parameters.awk"
65
66 typedef enum {
67         SEARCH_MATCH_NONE,
68         SEARCH_MATCH_EQUAL,
69         SEARCH_MATCH_CONTAINS,
70         SEARCH_MATCH_UNDER,
71         SEARCH_MATCH_OVER,
72         SEARCH_MATCH_BETWEEN,
73         SEARCH_MATCH_ALL,
74         SEARCH_MATCH_ANY
75 } MatchType;
76
77 enum {
78         SEARCH_COLUMN_POINTER = 0,
79         SEARCH_COLUMN_RANK,
80         SEARCH_COLUMN_THUMB,
81         SEARCH_COLUMN_NAME,
82         SEARCH_COLUMN_SIZE,
83         SEARCH_COLUMN_DATE,
84         SEARCH_COLUMN_DIMENSIONS,
85         SEARCH_COLUMN_PATH,
86         SEARCH_COLUMN_COUNT     /* total columns */
87 };
88
89 typedef struct _SearchData SearchData;
90 struct _SearchData
91 {
92         GtkWidget *window;
93
94         GtkWidget *button_thumbs;
95         GtkWidget *label_status;
96         GtkWidget *label_progress;
97         GtkWidget *button_start;
98         GtkWidget *button_stop;
99         GtkWidget *spinner;
100
101         GtkWidget *box_search;
102
103         GtkWidget *menu_path;
104         GtkWidget *path_entry;
105         GtkWidget *check_recurse;
106
107         GtkWidget *result_view;
108
109         GtkWidget *check_name;
110         GtkWidget *menu_name;
111         GtkWidget *entry_name;
112         GtkWidget *check_name_match_case;
113
114         GtkWidget *check_size;
115         GtkWidget *menu_size;
116         GtkWidget *spin_size;
117         GtkWidget *spin_size_end;
118
119         GtkWidget *check_date;
120         GtkWidget *menu_date;
121         GtkWidget *date_sel;
122         GtkWidget *date_sel_end;
123
124         GtkWidget *check_dimensions;
125         GtkWidget *menu_dimensions;
126         GtkWidget *spin_width;
127         GtkWidget *spin_height;
128         GtkWidget *spin_width_end;
129         GtkWidget *spin_height_end;
130
131         GtkWidget *check_similarity;
132         GtkWidget *spin_similarity;
133         GtkWidget *entry_similarity;
134
135         GtkWidget *check_keywords;
136         GtkWidget *menu_keywords;
137         GtkWidget *entry_keywords;
138
139         GtkWidget *check_comment;
140         GtkWidget *menu_comment;
141         GtkWidget *entry_comment;
142
143         FileData *search_dir_fd;
144         gboolean   search_path_recurse;
145         gchar *search_name;
146         gboolean   search_name_match_case;
147         gint64 search_size;
148         gint64 search_size_end;
149         gint   search_date_y;
150         gint   search_date_m;
151         gint   search_date_d;
152         gint   search_date_end_y;
153         gint   search_date_end_m;
154         gint   search_date_end_d;
155         gint   search_width;
156         gint   search_height;
157         gint   search_width_end;
158         gint   search_height_end;
159         gint   search_similarity;
160         gchar *search_similarity_path;
161         CacheData *search_similarity_cd;
162         GList *search_keyword_list;
163         gchar *search_comment;
164         gboolean   search_comment_match_case;
165         gboolean   search_date_exif;
166
167         MatchType search_type;
168
169         MatchType match_name;
170         MatchType match_size;
171         MatchType match_date;
172         MatchType match_dimensions;
173         MatchType match_keywords;
174         MatchType match_comment;
175         MatchType match_gps;
176
177         gboolean match_name_enable;
178         gboolean match_size_enable;
179         gboolean match_date_enable;
180         gboolean match_dimensions_enable;
181         gboolean match_similarity_enable;
182         gboolean match_keywords_enable;
183         gboolean match_comment_enable;
184
185         GList *search_folder_list;
186         GList *search_done_list;
187         GList *search_file_list;
188         GList *search_buffer_list;
189
190         gint search_count;
191         gint search_total;
192         gint search_buffer_count;
193
194         guint search_idle_id; /* event source id */
195         guint update_idle_id; /* event source id */
196
197         ImageLoader *img_loader;
198         CacheData   *img_cd;
199
200         FileData *click_fd;
201
202         ThumbLoader *thumb_loader;
203         gboolean thumb_enable;
204         FileData *thumb_fd;
205
206         /* Used for lat/long coordinate search
207         */
208         gint search_gps;
209         gdouble search_lat, search_lon;
210         GtkWidget *entry_gps_coord;
211         GtkWidget *check_gps;
212         GtkWidget *spin_gps;
213         GtkWidget *units_gps;
214         GtkWidget *menu_gps;
215         gboolean match_gps_enable;
216
217 };
218
219 typedef struct _MatchFileData MatchFileData;
220 struct _MatchFileData
221 {
222         FileData *fd;
223         gint width;
224         gint height;
225         gint rank;
226 };
227
228 typedef struct _MatchList MatchList;
229 struct _MatchList
230 {
231         const gchar *text;
232         const MatchType type;
233 };
234
235 static const MatchList text_search_menu_path[] = {
236         { N_("folder"),         SEARCH_MATCH_NONE },
237         { N_("comments"),       SEARCH_MATCH_ALL },
238         { N_("results"),        SEARCH_MATCH_CONTAINS }
239 };
240
241 static const MatchList text_search_menu_name[] = {
242         { N_("contains"),       SEARCH_MATCH_CONTAINS },
243         { N_("is"),             SEARCH_MATCH_EQUAL }
244 };
245
246 static const MatchList text_search_menu_size[] = {
247         { N_("equal to"),       SEARCH_MATCH_EQUAL },
248         { N_("less than"),      SEARCH_MATCH_UNDER },
249         { N_("greater than"),   SEARCH_MATCH_OVER },
250         { N_("between"),        SEARCH_MATCH_BETWEEN }
251 };
252
253 static const MatchList text_search_menu_date[] = {
254         { N_("equal to"),       SEARCH_MATCH_EQUAL },
255         { N_("before"),         SEARCH_MATCH_UNDER },
256         { N_("after"),          SEARCH_MATCH_OVER },
257         { N_("between"),        SEARCH_MATCH_BETWEEN }
258 };
259
260 static const MatchList text_search_menu_keyword[] = {
261         { N_("match all"),      SEARCH_MATCH_ALL },
262         { N_("match any"),      SEARCH_MATCH_ANY },
263         { N_("exclude"),        SEARCH_MATCH_NONE }
264 };
265
266 static const MatchList text_search_menu_comment[] = {
267         { N_("contains"),       SEARCH_MATCH_CONTAINS },
268         { N_("miss"),           SEARCH_MATCH_NONE }
269 };
270
271 static const MatchList text_search_menu_gps[] = {
272         { N_("not geocoded"),   SEARCH_MATCH_NONE },
273         { N_("less than"),      SEARCH_MATCH_UNDER },
274         { N_("greater than"),   SEARCH_MATCH_OVER }
275 };
276
277 static GList *search_window_list = NULL;
278
279
280 static gint search_result_selection_count(SearchData *sd, gint64 *bytes);
281 static gint search_result_count(SearchData *sd, gint64 *bytes);
282
283 static void search_window_close(SearchData *sd);
284
285 static void search_notify_cb(FileData *fd, NotifyType type, gpointer data);
286
287 /*
288  *-------------------------------------------------------------------
289  * utils
290  *-------------------------------------------------------------------
291  */
292
293 static time_t convert_dmy_to_time(gint day, gint month, gint year)
294 {
295         struct tm lt;
296
297         lt.tm_sec = 0;
298         lt.tm_min = 0;
299         lt.tm_hour = 0;
300         lt.tm_mday = day;
301         lt.tm_mon = month - 1;
302         lt.tm_year = year - 1900;
303         lt.tm_isdst = 0;
304
305         return mktime(&lt);
306 }
307
308 static void search_status_update(SearchData *sd)
309 {
310         gchar *buf;
311         gint t;
312         gint s;
313         gint64 t_bytes;
314         gint64 s_bytes;
315         gchar *tt;
316
317         t = search_result_count(sd, &t_bytes);
318         s = search_result_selection_count(sd, &s_bytes);
319
320         tt = text_from_size_abrev(t_bytes);
321
322         if (s > 0)
323                 {
324                 gchar *ts = text_from_size_abrev(s_bytes);
325                 buf = g_strdup_printf(_("%s, %d files (%s, %d)"), tt, t, ts, s);
326                 g_free(ts);
327                 }
328         else
329                 {
330                 buf = g_strdup_printf(_("%s, %d files"), tt, t);
331                 }
332
333         g_free(tt);
334
335         gtk_label_set_text(GTK_LABEL(sd->label_status), buf);
336         g_free(buf);
337 }
338
339 static void search_progress_update(SearchData *sd, gboolean search, gdouble thumbs)
340 {
341
342         if (search || thumbs >= 0.0)
343                 {
344                 gchar *buf;
345                 const gchar *message;
346
347                 if (search && (sd->search_folder_list || sd->search_file_list))
348                         message = _("Searching...");
349                 else if (thumbs >= 0.0)
350                         message = _("Loading thumbs...");
351                 else
352                         message = "";
353
354                 buf = g_strdup_printf("%s(%d / %d)", message, sd->search_count, sd->search_total);
355                 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(sd->label_progress), buf);
356                 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(sd->label_progress),
357                                               (thumbs >= 0.0) ? thumbs : 0.0);
358                 g_free(buf);
359                 }
360         else
361                 {
362                 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(sd->label_progress), "");
363                 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(sd->label_progress), 0.0);
364                 }
365 }
366
367 /*
368  *-------------------------------------------------------------------
369  * result list
370  *-------------------------------------------------------------------
371  */
372
373 static gint search_result_find_row(SearchData *sd, FileData *fd, GtkTreeIter *iter)
374 {
375         GtkTreeModel *store;
376         gboolean valid;
377         gint n = 0;
378
379         store = gtk_tree_view_get_model(GTK_TREE_VIEW(sd->result_view));
380         valid = gtk_tree_model_get_iter_first(store, iter);
381         while (valid)
382                 {
383                 MatchFileData *mfd;
384                 n++;
385
386                 gtk_tree_model_get(store, iter, SEARCH_COLUMN_POINTER, &mfd, -1);
387                 if (mfd->fd == fd) return n;
388                 valid = gtk_tree_model_iter_next(store, iter);
389                 }
390
391         return -1;
392 }
393
394 static gboolean search_result_row_selected(SearchData *sd, FileData *fd)
395 {
396         GtkTreeModel *store;
397         GtkTreeSelection *selection;
398         GList *slist;
399         GList *work;
400         gboolean found = FALSE;
401
402         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(sd->result_view));
403         slist = gtk_tree_selection_get_selected_rows(selection, &store);
404         work = slist;
405         while (!found && work)
406                 {
407                 GtkTreePath *tpath = work->data;
408                 MatchFileData *mfd_n;
409                 GtkTreeIter iter;
410
411                 gtk_tree_model_get_iter(store, &iter, tpath);
412                 gtk_tree_model_get(store, &iter, SEARCH_COLUMN_POINTER, &mfd_n, -1);
413                 if (mfd_n->fd == fd) found = TRUE;
414                 work = work->next;
415                 }
416         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
417         g_list_free(slist);
418
419         return found;
420 }
421
422 static gint search_result_selection_util(SearchData *sd, gint64 *bytes, GList **list)
423 {
424         GtkTreeModel *store;
425         GtkTreeSelection *selection;
426         GList *slist;
427         GList *work;
428         gint n = 0;
429         gint64 total = 0;
430         GList *plist = NULL;
431
432         store = gtk_tree_view_get_model(GTK_TREE_VIEW(sd->result_view));
433         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(sd->result_view));
434         slist = gtk_tree_selection_get_selected_rows(selection, &store);
435         work = slist;
436         while (work)
437                 {
438                 n++;
439
440                 if (bytes || list)
441                         {
442                         GtkTreePath *tpath = work->data;
443                         MatchFileData *mfd;
444                         GtkTreeIter iter;
445
446                         gtk_tree_model_get_iter(store, &iter, tpath);
447                         gtk_tree_model_get(store, &iter, SEARCH_COLUMN_POINTER, &mfd, -1);
448                         total += mfd->fd->size;
449
450                         if (list) plist = g_list_prepend(plist, file_data_ref(mfd->fd));
451                         }
452
453                 work = work->next;
454                 }
455         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
456         g_list_free(slist);
457
458         if (bytes) *bytes = total;
459         if (list) *list = g_list_reverse(plist);
460
461         return n;
462 }
463
464 static GList *search_result_selection_list(SearchData *sd)
465 {
466         GList *list;
467
468         search_result_selection_util(sd, NULL, &list);
469         return list;
470 }
471
472 static gint search_result_selection_count(SearchData *sd, gint64 *bytes)
473 {
474         return search_result_selection_util(sd, bytes, NULL);
475 }
476
477 static gint search_result_util(SearchData *sd, gint64 *bytes, GList **list)
478 {
479         GtkTreeModel *store;
480         GtkTreeIter iter;
481         gboolean valid;
482         gint n = 0;
483         gint64 total = 0;
484         GList *plist = NULL;
485
486         store = gtk_tree_view_get_model(GTK_TREE_VIEW(sd->result_view));
487
488         valid = gtk_tree_model_get_iter_first(store, &iter);
489         while (valid)
490                 {
491                 n++;
492                 if (bytes || list)
493                         {
494                         MatchFileData *mfd;
495
496                         gtk_tree_model_get(store, &iter, SEARCH_COLUMN_POINTER, &mfd, -1);
497                         total += mfd->fd->size;
498
499                         if (list) plist = g_list_prepend(plist, file_data_ref(mfd->fd));
500                         }
501                 valid = gtk_tree_model_iter_next(store, &iter);
502                 }
503
504         if (bytes) *bytes = total;
505         if (list) *list = g_list_reverse(plist);
506
507         return n;
508 }
509
510 static GList *search_result_get_filelist(SearchData *sd)
511 {
512         GList *list = NULL;
513
514         search_result_util(sd, NULL, &list);
515         return list;
516 }
517
518 static gint search_result_count(SearchData *sd, gint64 *bytes)
519 {
520         return search_result_util(sd, bytes, NULL);
521 }
522
523 static void search_result_append(SearchData *sd, MatchFileData *mfd)
524 {
525         FileData *fd;
526         GtkListStore *store;
527         GtkTreeIter iter;
528         gchar *text_size;
529         gchar *text_dim = NULL;
530
531         fd = mfd->fd;
532
533         if (!fd) return;
534
535         text_size = text_from_size(fd->size);
536         if (mfd->width > 0 && mfd->height > 0) text_dim = g_strdup_printf("%d x %d", mfd->width, mfd->height);
537
538         store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(sd->result_view)));
539         gtk_list_store_append(store, &iter);
540         gtk_list_store_set(store, &iter,
541                                 SEARCH_COLUMN_POINTER, mfd,
542                                 SEARCH_COLUMN_RANK, mfd->rank,
543                                 SEARCH_COLUMN_THUMB, fd->thumb_pixbuf,
544                                 SEARCH_COLUMN_NAME, fd->name,
545                                 SEARCH_COLUMN_SIZE, text_size,
546                                 SEARCH_COLUMN_DATE, text_from_time(fd->date),
547                                 SEARCH_COLUMN_DIMENSIONS, text_dim,
548                                 SEARCH_COLUMN_PATH, fd->path,
549                                 -1);
550
551         g_free(text_size);
552         g_free(text_dim);
553 }
554
555 static GList *search_result_refine_list(SearchData *sd)
556 {
557         GList *list = NULL;
558         GtkTreeModel *store;
559         GtkTreeIter iter;
560         gboolean valid;
561
562         store = gtk_tree_view_get_model(GTK_TREE_VIEW(sd->result_view));
563
564         valid = gtk_tree_model_get_iter_first(store, &iter);
565         while (valid)
566                 {
567                 MatchFileData *mfd;
568
569                 gtk_tree_model_get(store, &iter, SEARCH_COLUMN_POINTER, &mfd, -1);
570                 list = g_list_prepend(list, mfd->fd);
571
572                 valid = gtk_tree_model_iter_next(store, &iter);
573                 }
574
575         /* clear it here, so that the FileData in list is not freed */
576         gtk_list_store_clear(GTK_LIST_STORE(store));
577
578         return g_list_reverse(list);
579 }
580
581 static gboolean search_result_free_node(GtkTreeModel *store, GtkTreePath *tpath,
582                                         GtkTreeIter *iter, gpointer data)
583 {
584         MatchFileData *mfd;
585
586         gtk_tree_model_get(store, iter, SEARCH_COLUMN_POINTER, &mfd, -1);
587         file_data_unref(mfd->fd);
588         g_free(mfd);
589
590         return FALSE;
591 }
592
593 static void search_result_clear(SearchData *sd)
594 {
595         GtkListStore *store;
596
597         store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(sd->result_view)));
598
599         gtk_tree_model_foreach(GTK_TREE_MODEL(store), search_result_free_node, sd);
600         gtk_list_store_clear(store);
601
602         sd->click_fd = NULL;
603
604         thumb_loader_free(sd->thumb_loader);
605         sd->thumb_loader = NULL;
606         sd->thumb_fd = NULL;
607
608         search_status_update(sd);
609 }
610
611 static void search_result_remove_item(SearchData *sd, MatchFileData *mfd, GtkTreeIter *iter)
612 {
613         GtkTreeModel *store;
614
615         if (!mfd || !iter) return;
616
617         store = gtk_tree_view_get_model(GTK_TREE_VIEW(sd->result_view));
618
619         tree_view_move_cursor_away(GTK_TREE_VIEW(sd->result_view), iter, TRUE);
620
621         gtk_list_store_remove(GTK_LIST_STORE(store), iter);
622         if (sd->click_fd == mfd->fd) sd->click_fd = NULL;
623         if (sd->thumb_fd == mfd->fd) sd->thumb_fd = NULL;
624         file_data_unref(mfd->fd);
625         g_free(mfd);
626 }
627
628 static void search_result_remove(SearchData *sd, FileData *fd)
629 {
630         GtkTreeModel *store;
631         GtkTreeIter iter;
632         gboolean valid;
633
634         store = gtk_tree_view_get_model(GTK_TREE_VIEW(sd->result_view));
635         valid = gtk_tree_model_get_iter_first(store, &iter);
636         while (valid)
637                 {
638                 MatchFileData *mfd;
639
640                 gtk_tree_model_get(store, &iter, SEARCH_COLUMN_POINTER, &mfd, -1);
641                 if (mfd->fd == fd)
642                         {
643                         search_result_remove_item(sd, mfd, &iter);
644                         return;
645                         }
646
647                 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
648                 }
649 }
650
651 static void search_result_remove_selection(SearchData *sd)
652 {
653         GtkTreeSelection *selection;
654         GtkTreeModel *store;
655         GList *slist;
656         GList *flist = NULL;
657         GList *work;
658
659         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(sd->result_view));
660         slist = gtk_tree_selection_get_selected_rows(selection, &store);
661         work = slist;
662         while (work)
663                 {
664                 GtkTreePath *tpath = work->data;
665                 GtkTreeIter iter;
666                 MatchFileData *mfd;
667
668                 gtk_tree_model_get_iter(store, &iter, tpath);
669                 gtk_tree_model_get(store, &iter, SEARCH_COLUMN_POINTER, &mfd, -1);
670                 flist = g_list_prepend(flist, mfd->fd);
671                 work = work->next;
672                 }
673         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
674         g_list_free(slist);
675
676         work = flist;
677         while (work)
678                 {
679                 FileData *fd = work->data;
680                 work = work->next;
681
682                 search_result_remove(sd, fd);
683                 }
684         g_list_free(flist);
685
686         search_status_update(sd);
687 }
688
689 static void search_result_edit_selected(SearchData *sd, const gchar *key)
690 {
691         file_util_start_editor_from_filelist(key, search_result_selection_list(sd), NULL, sd->window);
692 }
693
694 static void search_result_collection_from_selection(SearchData *sd)
695 {
696         CollectWindow *w;
697         GList *list;
698
699         list = search_result_selection_list(sd);
700         w = collection_window_new(NULL);
701         collection_table_add_filelist(w->table, list);
702         filelist_free(list);
703 }
704
705 static gboolean search_result_update_idle_cb(gpointer data)
706 {
707         SearchData *sd = data;
708
709         search_status_update(sd);
710
711         sd->update_idle_id = 0;
712         return FALSE;
713 }
714
715 static void search_result_update_idle_cancel(SearchData *sd)
716 {
717         if (sd->update_idle_id)
718                 {
719                 g_source_remove(sd->update_idle_id);
720                 sd->update_idle_id = 0;
721                 }
722 }
723
724 static gboolean search_result_select_cb(GtkTreeSelection *selection, GtkTreeModel *store,
725                                         GtkTreePath *tpath, gboolean selected, gpointer data)
726 {
727         SearchData *sd = data;
728
729         if (!sd->update_idle_id)
730                 {
731                 sd->update_idle_id = g_idle_add(search_result_update_idle_cb, sd);
732                 }
733
734         return TRUE;
735 }
736
737 /*
738  *-------------------------------------------------------------------
739  * result list thumbs
740  *-------------------------------------------------------------------
741  */
742
743 static void search_result_thumb_step(SearchData *sd);
744
745
746 static void search_result_thumb_set(SearchData *sd, FileData *fd, GtkTreeIter *iter)
747 {
748         GtkListStore *store;
749         GtkTreeIter iter_n;
750
751         store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(sd->result_view)));
752         if (!iter)
753                 {
754                 if (search_result_find_row(sd, fd, &iter_n) >= 0) iter = &iter_n;
755                 }
756
757         if (iter) gtk_list_store_set(store, iter, SEARCH_COLUMN_THUMB, fd->thumb_pixbuf, -1);
758 }
759
760 static void search_result_thumb_do(SearchData *sd)
761 {
762         FileData *fd;
763
764         if (!sd->thumb_loader || !sd->thumb_fd) return;
765         fd = sd->thumb_fd;
766
767         search_result_thumb_set(sd, fd, NULL);
768 }
769
770 static void search_result_thumb_done_cb(ThumbLoader *tl, gpointer data)
771 {
772         SearchData *sd = data;
773
774         search_result_thumb_do(sd);
775         search_result_thumb_step(sd);
776 }
777
778 static void search_result_thumb_step(SearchData *sd)
779 {
780         GtkTreeModel *store;
781         GtkTreeIter iter;
782         MatchFileData *mfd = NULL;
783         gboolean valid;
784         gint row = 0;
785         gint length = 0;
786
787         store = gtk_tree_view_get_model(GTK_TREE_VIEW(sd->result_view));
788         valid = gtk_tree_model_get_iter_first(store, &iter);
789         if (!sd->thumb_enable)
790                 {
791                 while (valid)
792                         {
793                         gtk_list_store_set(GTK_LIST_STORE(store), &iter, SEARCH_COLUMN_THUMB, NULL, -1);
794                         valid = gtk_tree_model_iter_next(store, &iter);
795                         }
796                 return;
797                 }
798
799         while (!mfd && valid)
800                 {
801                 GdkPixbuf *pixbuf;
802
803                 length++;
804                 gtk_tree_model_get(store, &iter, SEARCH_COLUMN_POINTER, &mfd, SEARCH_COLUMN_THUMB, &pixbuf, -1);
805                 if (pixbuf || mfd->fd->thumb_pixbuf)
806                         {
807                         if (!pixbuf) gtk_list_store_set(GTK_LIST_STORE(store), &iter, SEARCH_COLUMN_THUMB, mfd->fd->thumb_pixbuf, -1);
808                         row++;
809                         mfd = NULL;
810                         }
811                 valid = gtk_tree_model_iter_next(store, &iter);
812                 }
813         if (valid)
814                 {
815                 while (gtk_tree_model_iter_next(store, &iter)) length++;
816                 }
817
818         if (!mfd)
819                 {
820                 sd->thumb_fd = NULL;
821                 thumb_loader_free(sd->thumb_loader);
822                 sd->thumb_loader = NULL;
823
824                 search_progress_update(sd, TRUE, -1.0);
825                 return;
826                 }
827
828         search_progress_update(sd, FALSE, (gdouble)row/length);
829
830         sd->thumb_fd = mfd->fd;
831         thumb_loader_free(sd->thumb_loader);
832         sd->thumb_loader = thumb_loader_new(options->thumbnails.max_width, options->thumbnails.max_height);
833
834         thumb_loader_set_callbacks(sd->thumb_loader,
835                                    search_result_thumb_done_cb,
836                                    search_result_thumb_done_cb,
837                                    NULL,
838                                    sd);
839         if (!thumb_loader_start(sd->thumb_loader, mfd->fd))
840                 {
841                 search_result_thumb_do(sd);
842                 search_result_thumb_step(sd);
843                 }
844 }
845
846 static void search_result_thumb_height(SearchData *sd)
847 {
848         GtkTreeViewColumn *column;
849         GtkCellRenderer *cell;
850         GList *list;
851
852         column = gtk_tree_view_get_column(GTK_TREE_VIEW(sd->result_view), SEARCH_COLUMN_THUMB - 1);
853         if (!column) return;
854
855         gtk_tree_view_column_set_fixed_width(column, (sd->thumb_enable) ? options->thumbnails.max_width : 4);
856
857         list = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(column));
858         if (!list) return;
859         cell = list->data;
860         g_list_free(list);
861
862         g_object_set(G_OBJECT(cell), "height", (sd->thumb_enable) ? options->thumbnails.max_height : -1, NULL);
863         gtk_tree_view_columns_autosize(GTK_TREE_VIEW(sd->result_view));
864 }
865
866 static void search_result_thumb_enable(SearchData *sd, gboolean enable)
867 {
868         if (sd->thumb_enable == enable) return;
869
870         if (sd->thumb_enable)
871                 {
872                 GtkTreeModel *store;
873                 GtkTreeIter iter;
874                 gboolean valid;
875
876                 thumb_loader_free(sd->thumb_loader);
877                 sd->thumb_loader = NULL;
878
879                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(sd->result_view));
880                 valid = gtk_tree_model_get_iter_first(store, &iter);
881                 while (valid)
882                         {
883                         gtk_list_store_set(GTK_LIST_STORE(store), &iter, SEARCH_COLUMN_THUMB, NULL, -1);
884                         valid = gtk_tree_model_iter_next(store, &iter);
885                         }
886                 search_progress_update(sd, TRUE, -1.0);
887                 }
888
889         sd->thumb_enable = enable;
890
891         search_result_thumb_height(sd);
892         if (!sd->search_folder_list && !sd->search_file_list) search_result_thumb_step(sd);
893 }
894
895 /*
896  *-------------------------------------------------------------------
897  * result list menu
898  *-------------------------------------------------------------------
899  */
900
901 static void sr_menu_view_cb(GtkWidget *widget, gpointer data)
902 {
903         SearchData *sd = data;
904
905         if (sd->click_fd) layout_set_fd(NULL, sd->click_fd);
906 }
907
908 static void sr_menu_viewnew_cb(GtkWidget *widget, gpointer data)
909 {
910         SearchData *sd = data;
911         GList *list;
912
913         list = search_result_selection_list(sd);
914         view_window_new_from_list(list);
915         filelist_free(list);
916 }
917
918 static void sr_menu_select_all_cb(GtkWidget *widget, gpointer data)
919 {
920         SearchData *sd = data;
921         GtkTreeSelection *selection;
922
923         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(sd->result_view));
924         gtk_tree_selection_select_all(selection);
925 }
926
927 static void sr_menu_select_none_cb(GtkWidget *widget, gpointer data)
928 {
929         SearchData *sd = data;
930         GtkTreeSelection *selection;
931
932         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(sd->result_view));
933         gtk_tree_selection_unselect_all(selection);
934 }
935
936 static void sr_menu_edit_cb(GtkWidget *widget, gpointer data)
937 {
938         SearchData *sd;
939         const gchar *key = data;
940
941         sd = submenu_item_get_data(widget);
942         if (!sd) return;
943
944         search_result_edit_selected(sd, key);
945 }
946
947 static void sr_menu_collection_cb(GtkWidget *widget, gpointer data)
948 {
949         SearchData *sd = data;
950
951         search_result_collection_from_selection(sd);
952 }
953
954 static void sr_menu_print_cb(GtkWidget *widget, gpointer data)
955 {
956         SearchData *sd = data;
957
958         print_window_new(sd->click_fd, search_result_selection_list(sd),
959                          search_result_get_filelist(sd), sd->window);
960 }
961
962 static void sr_menu_copy_cb(GtkWidget *widget, gpointer data)
963 {
964         SearchData *sd = data;
965
966         file_util_copy(NULL, search_result_selection_list(sd), NULL, sd->window);
967 }
968
969 static void sr_menu_move_cb(GtkWidget *widget, gpointer data)
970 {
971         SearchData *sd = data;
972
973         file_util_move(NULL, search_result_selection_list(sd), NULL, sd->window);
974 }
975
976 static void sr_menu_rename_cb(GtkWidget *widget, gpointer data)
977 {
978         SearchData *sd = data;
979
980         file_util_rename(NULL, search_result_selection_list(sd), sd->window);
981 }
982
983 static void sr_menu_delete_cb(GtkWidget *widget, gpointer data)
984 {
985         SearchData *sd = data;
986
987         file_util_delete(NULL, search_result_selection_list(sd), sd->window);
988 }
989
990 static void sr_menu_copy_path_cb(GtkWidget *widget, gpointer data)
991 {
992         SearchData *sd = data;
993
994         file_util_copy_path_list_to_clipboard(search_result_selection_list(sd));
995 }
996
997 static void sr_menu_remove_cb(GtkWidget *widget, gpointer data)
998 {
999         SearchData *sd = data;
1000
1001         search_result_remove_selection(sd);
1002 }
1003
1004 static void sr_menu_clear_cb(GtkWidget *widget, gpointer data)
1005 {
1006         SearchData *sd = data;
1007
1008         search_result_clear(sd);
1009 }
1010
1011 static void search_result_menu_destroy_cb(GtkWidget *widget, gpointer data)
1012 {
1013         GList *editmenu_fd_list = data;
1014
1015         filelist_free(editmenu_fd_list);
1016 }
1017
1018 static GtkWidget *search_result_menu(SearchData *sd, gboolean on_row, gboolean empty)
1019 {
1020         GtkWidget *menu;
1021         GtkWidget *item;
1022         GList *editmenu_fd_list;
1023
1024         menu = popup_menu_short_lived();
1025
1026         menu_item_add_sensitive(menu, _("_View"), on_row,
1027                                 G_CALLBACK(sr_menu_view_cb), sd);
1028         menu_item_add_stock_sensitive(menu, _("View in _new window"), GTK_STOCK_NEW, on_row,
1029                                       G_CALLBACK(sr_menu_viewnew_cb), sd);
1030         menu_item_add_divider(menu);
1031         menu_item_add_sensitive(menu, _("Select all"), !empty,
1032                                 G_CALLBACK(sr_menu_select_all_cb), sd);
1033         menu_item_add_sensitive(menu, _("Select none"), !empty,
1034                                 G_CALLBACK(sr_menu_select_none_cb), sd);
1035         menu_item_add_divider(menu);
1036
1037         editmenu_fd_list = search_result_selection_list(sd);
1038         g_signal_connect(G_OBJECT(menu), "destroy",
1039                          G_CALLBACK(search_result_menu_destroy_cb), editmenu_fd_list);
1040         submenu_add_edit(menu, &item, G_CALLBACK(sr_menu_edit_cb), sd, editmenu_fd_list);
1041         if (!on_row) gtk_widget_set_sensitive(item, FALSE);
1042         menu_item_add_stock_sensitive(menu, _("Add to new collection"), GTK_STOCK_INDEX, on_row,
1043                                       G_CALLBACK(sr_menu_collection_cb), sd);
1044         menu_item_add_stock_sensitive(menu, _("Print..."), GTK_STOCK_PRINT, on_row,
1045                                       G_CALLBACK(sr_menu_print_cb), sd);
1046         menu_item_add_divider(menu);
1047         menu_item_add_stock_sensitive(menu, _("_Copy..."), GTK_STOCK_COPY, on_row,
1048                                       G_CALLBACK(sr_menu_copy_cb), sd);
1049         menu_item_add_sensitive(menu, _("_Move..."), on_row,
1050                                 G_CALLBACK(sr_menu_move_cb), sd);
1051         menu_item_add_sensitive(menu, _("_Rename..."), on_row,
1052                                 G_CALLBACK(sr_menu_rename_cb), sd);
1053         menu_item_add_stock_sensitive(menu, _("_Delete..."), GTK_STOCK_DELETE, on_row,
1054                                       G_CALLBACK(sr_menu_delete_cb), sd);
1055         menu_item_add_sensitive(menu, _("_Copy path"), on_row,
1056                                 G_CALLBACK(sr_menu_copy_path_cb), sd);
1057         menu_item_add_divider(menu);
1058         menu_item_add_stock_sensitive(menu, _("Rem_ove"), GTK_STOCK_REMOVE, on_row,
1059                                       G_CALLBACK(sr_menu_remove_cb), sd);
1060         menu_item_add_stock_sensitive(menu, _("C_lear"), GTK_STOCK_CLEAR, !empty,
1061                                       G_CALLBACK(sr_menu_clear_cb), sd);
1062
1063         return menu;
1064 }
1065
1066 static void search_result_menu_pos_cb(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data)
1067 {
1068         SearchData *sd = data;
1069         GtkTreePath *tpath;
1070         gint cx, cy, cw, ch;
1071
1072         gtk_tree_view_get_cursor(GTK_TREE_VIEW(sd->result_view), &tpath, NULL);
1073         if (!tpath) return;
1074
1075         tree_view_get_cell_clamped(GTK_TREE_VIEW(sd->result_view), tpath,
1076                                    SEARCH_COLUMN_NAME - 1, TRUE, &cx, &cy, &cw, &ch);
1077         gtk_tree_path_free(tpath);
1078         cy += ch;
1079         popup_menu_position_clamp(menu, &cx, &cy, 0);
1080         *x = cx;
1081         *y = cy;
1082 }
1083
1084 /*
1085  *-------------------------------------------------------------------
1086  * result list input
1087  *-------------------------------------------------------------------
1088  */
1089
1090 static gboolean search_result_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
1091 {
1092         SearchData *sd = data;
1093         GtkTreeModel *store;
1094         GtkTreePath *tpath;
1095         GtkTreeIter iter;
1096         MatchFileData *mfd = NULL;
1097
1098         store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
1099
1100         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
1101                                           &tpath, NULL, NULL, NULL))
1102                 {
1103                 gtk_tree_model_get_iter(store, &iter, tpath);
1104                 gtk_tree_model_get(store, &iter, SEARCH_COLUMN_POINTER, &mfd, -1);
1105                 gtk_tree_path_free(tpath);
1106                 }
1107
1108         sd->click_fd = mfd ? mfd->fd : NULL;
1109
1110         if (bevent->button == MOUSE_BUTTON_RIGHT)
1111                 {
1112                 GtkWidget *menu;
1113
1114                 menu = search_result_menu(sd, (mfd != NULL), (search_result_count(sd, NULL) == 0));
1115                 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, bevent->button, bevent->time);
1116                 }
1117
1118         if (!mfd) return FALSE;
1119
1120         if (bevent->button == MOUSE_BUTTON_LEFT && bevent->type == GDK_2BUTTON_PRESS)
1121                 {
1122                 layout_set_fd(NULL, mfd->fd);
1123                 }
1124
1125         if (bevent->button == MOUSE_BUTTON_MIDDLE) return TRUE;
1126
1127         if (bevent->button == MOUSE_BUTTON_RIGHT)
1128                 {
1129                 if (!search_result_row_selected(sd, mfd->fd))
1130                         {
1131                         GtkTreeSelection *selection;
1132
1133                         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
1134                         gtk_tree_selection_unselect_all(selection);
1135                         gtk_tree_selection_select_iter(selection, &iter);
1136
1137                         tpath = gtk_tree_model_get_path(store, &iter);
1138                         gtk_tree_view_set_cursor(GTK_TREE_VIEW(widget), tpath, NULL, FALSE);
1139                         gtk_tree_path_free(tpath);
1140                         }
1141                 return TRUE;
1142                 }
1143
1144         if (bevent->button == MOUSE_BUTTON_LEFT && bevent->type == GDK_BUTTON_PRESS &&
1145             !(bevent->state & GDK_SHIFT_MASK ) &&
1146             !(bevent->state & GDK_CONTROL_MASK ) &&
1147             search_result_row_selected(sd, mfd->fd))
1148                 {
1149                 /* this selection handled on release_cb */
1150                 gtk_widget_grab_focus(widget);
1151                 return TRUE;
1152                 }
1153
1154         return FALSE;
1155 }
1156
1157 static gboolean search_result_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
1158 {
1159         SearchData *sd = data;
1160         GtkTreeModel *store;
1161         GtkTreePath *tpath;
1162         GtkTreeIter iter;
1163
1164         MatchFileData *mfd = NULL;
1165
1166         if (bevent->button != MOUSE_BUTTON_LEFT && bevent->button != MOUSE_BUTTON_MIDDLE) return TRUE;
1167
1168         store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
1169
1170         if ((bevent->x != 0 || bevent->y != 0) &&
1171             gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
1172                                           &tpath, NULL, NULL, NULL))
1173                 {
1174                 gtk_tree_model_get_iter(store, &iter, tpath);
1175                 gtk_tree_model_get(store, &iter, SEARCH_COLUMN_POINTER, &mfd, -1);
1176                 gtk_tree_path_free(tpath);
1177                 }
1178
1179         if (bevent->button == MOUSE_BUTTON_MIDDLE)
1180                 {
1181                 if (mfd && sd->click_fd == mfd->fd)
1182                         {
1183                         GtkTreeSelection *selection;
1184
1185                         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
1186                         if (search_result_row_selected(sd, mfd->fd))
1187                                 {
1188                                 gtk_tree_selection_unselect_iter(selection, &iter);
1189                                 }
1190                         else
1191                                 {
1192                                 gtk_tree_selection_select_iter(selection, &iter);
1193                                 }
1194                         }
1195                 return TRUE;
1196                 }
1197
1198         if (mfd && sd->click_fd == mfd->fd &&
1199             !(bevent->state & GDK_SHIFT_MASK ) &&
1200             !(bevent->state & GDK_CONTROL_MASK ) &&
1201             search_result_row_selected(sd, mfd->fd))
1202                 {
1203                 GtkTreeSelection *selection;
1204
1205                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
1206                 gtk_tree_selection_unselect_all(selection);
1207                 gtk_tree_selection_select_iter(selection, &iter);
1208
1209                 tpath = gtk_tree_model_get_path(store, &iter);
1210                 gtk_tree_view_set_cursor(GTK_TREE_VIEW(widget), tpath, NULL, FALSE);
1211                 gtk_tree_path_free(tpath);
1212
1213                 return TRUE;
1214                 }
1215
1216         return FALSE;
1217 }
1218
1219 static gboolean search_result_keypress_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
1220 {
1221         SearchData *sd = data;
1222         gboolean stop_signal = FALSE;
1223         GtkTreeModel *store;
1224         GtkTreeSelection *selection;
1225         GList *slist;
1226         MatchFileData *mfd = NULL;
1227
1228         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(sd->result_view));
1229         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1230         if (slist)
1231                 {
1232                 GtkTreePath *tpath;
1233                 GtkTreeIter iter;
1234                 GList *last;
1235
1236                 last = g_list_last(slist);
1237                 tpath = last->data;
1238
1239                 /* last is newest selected file */
1240                 gtk_tree_model_get_iter(store, &iter, tpath);
1241                 gtk_tree_model_get(store, &iter, SEARCH_COLUMN_POINTER, &mfd, -1);
1242                 }
1243         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
1244         g_list_free(slist);
1245
1246         if (event->state & GDK_CONTROL_MASK)
1247                 {
1248                 stop_signal = TRUE;
1249                 switch (event->keyval)
1250                         {
1251                         case '1':
1252                         case '2':
1253                         case '3':
1254                         case '4':
1255                         case '5':
1256                         case '6':
1257                         case '7':
1258                         case '8':
1259                         case '9':
1260                         case '0':
1261                                 break;
1262                         case 'C': case 'c':
1263                                 file_util_copy(NULL, search_result_selection_list(sd), NULL, widget);
1264                                 break;
1265                         case 'M': case 'm':
1266                                 file_util_move(NULL, search_result_selection_list(sd), NULL, widget);
1267                                 break;
1268                         case 'R': case 'r':
1269                                 file_util_rename(NULL, search_result_selection_list(sd), widget);
1270                                 break;
1271                         case 'D': case 'd':
1272                                 file_util_delete(NULL, search_result_selection_list(sd), widget);
1273                                 break;
1274                         case 'A': case 'a':
1275                                 if (event->state & GDK_SHIFT_MASK)
1276                                         {
1277                                         gtk_tree_selection_unselect_all(selection);
1278                                         }
1279                                 else
1280                                         {
1281                                         gtk_tree_selection_select_all(selection);
1282                                         }
1283                                 break;
1284                         case GDK_KEY_Delete: case GDK_KEY_KP_Delete:
1285                                 search_result_clear(sd);
1286                                 break;
1287                         default:
1288                                 stop_signal = FALSE;
1289                                 break;
1290                         }
1291                 }
1292         else
1293                 {
1294                 stop_signal = TRUE;
1295                 switch (event->keyval)
1296                         {
1297                         case GDK_KEY_Return: case GDK_KEY_KP_Enter:
1298                                 if (mfd) layout_set_fd(NULL, mfd->fd);
1299                                 break;
1300                         case 'V': case 'v':
1301                                 {
1302                                 GList *list;
1303
1304                                 list = search_result_selection_list(sd);
1305                                 view_window_new_from_list(list);
1306                                 filelist_free(list);
1307                                 }
1308                                 break;
1309                         case GDK_KEY_Delete: case GDK_KEY_KP_Delete:
1310                                 search_result_remove_selection(sd);
1311                                 break;
1312                         case 'C': case 'c':
1313                                 search_result_collection_from_selection(sd);
1314                                 break;
1315                         case GDK_KEY_Menu:
1316                         case GDK_KEY_F10:
1317                                 {
1318                                 GtkWidget *menu;
1319
1320                                 if (mfd)
1321                                         sd->click_fd = mfd->fd;
1322                                 else
1323                                         sd->click_fd = NULL;
1324
1325                                 menu = search_result_menu(sd, (mfd != NULL), (search_result_count(sd, NULL) > 0));
1326                                 gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
1327                                                search_result_menu_pos_cb, sd, 0, GDK_CURRENT_TIME);
1328                                 }
1329                                 break;
1330                         case GDK_KEY_F1:
1331                                 help_window_show("GuideReferenceKeyboardShortcuts.html#SearchKeyboardShortcuts");
1332                                 break;
1333                         default:
1334                                 stop_signal = FALSE;
1335                                 break;
1336                         }
1337                 }
1338
1339         return stop_signal;
1340 }
1341
1342 static gboolean search_window_keypress_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
1343 {
1344         SearchData *sd = data;
1345         gboolean stop_signal = FALSE;
1346
1347         if (event->state & GDK_CONTROL_MASK)
1348                 {
1349                 stop_signal = TRUE;
1350                 switch (event->keyval)
1351                         {
1352                         case 'T': case 't':
1353                                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(sd->button_thumbs),
1354                                         !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(sd->button_thumbs)));
1355                                 break;
1356                         case 'W': case 'w':
1357                                 search_window_close(sd);
1358                                 break;
1359                         default:
1360                                 stop_signal = FALSE;
1361                                 break;
1362                         }
1363                 }
1364
1365         return stop_signal;
1366 }
1367
1368 /*
1369  *-------------------------------------------------------------------
1370  * dnd
1371  *-------------------------------------------------------------------
1372  */
1373
1374 static GtkTargetEntry result_drag_types[] = {
1375         { "text/uri-list", 0, TARGET_URI_LIST },
1376         { "text/plain", 0, TARGET_TEXT_PLAIN }
1377 };
1378 static gint n_result_drag_types = 2;
1379
1380 static GtkTargetEntry result_drop_types[] = {
1381         { "text/uri-list", 0, TARGET_URI_LIST },
1382         { "text/plain", 0, TARGET_TEXT_PLAIN }
1383 };
1384 static gint n_result_drop_types = 2;
1385
1386 static void search_dnd_data_set(GtkWidget *widget, GdkDragContext *context,
1387                                 GtkSelectionData *selection_data, guint info,
1388                                 guint time, gpointer data)
1389 {
1390         SearchData *sd = data;
1391         GList *list;
1392
1393         list = search_result_selection_list(sd);
1394         if (!list) return;
1395
1396         uri_selection_data_set_uris_from_filelist(selection_data, list);
1397         filelist_free(list);
1398 }
1399
1400 static void search_dnd_begin(GtkWidget *widget, GdkDragContext *context, gpointer data)
1401 {
1402         SearchData *sd = data;
1403
1404         if (sd->click_fd && !search_result_row_selected(sd, sd->click_fd))
1405                 {
1406                 GtkListStore *store;
1407                 GtkTreeIter iter;
1408
1409                 store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(widget)));
1410                 if (search_result_find_row(sd, sd->click_fd, &iter) >= 0)
1411                         {
1412                         GtkTreeSelection *selection;
1413                         GtkTreePath *tpath;
1414
1415                         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
1416                         gtk_tree_selection_unselect_all(selection);
1417                         gtk_tree_selection_select_iter(selection, &iter);
1418
1419                         tpath = gtk_tree_model_get_path(GTK_TREE_MODEL(store), &iter);
1420                         gtk_tree_view_set_cursor(GTK_TREE_VIEW(widget), tpath, NULL, FALSE);
1421                         gtk_tree_path_free(tpath);
1422                         }
1423                 }
1424
1425         if (sd->thumb_enable &&
1426             sd->click_fd && sd->click_fd->thumb_pixbuf)
1427                 {
1428                 dnd_set_drag_icon(widget, context, sd->click_fd->thumb_pixbuf, search_result_selection_count(sd, NULL));
1429                 }
1430 }
1431
1432 #define BUFSIZE 128
1433
1434 static gchar *decode_geo_parameters(const gchar *input_text)
1435 {
1436         gchar *message;
1437         gchar *path = g_build_filename(get_rc_dir(), GEOCODE_NAME, NULL);
1438         gchar *cmd = g_strconcat("echo \'", input_text, "\'  | awk -f ", path, NULL);
1439
1440         if (g_file_test(path, G_FILE_TEST_EXISTS))
1441                 {
1442                 gchar buf[BUFSIZE];
1443                 FILE *fp;
1444
1445                 if ((fp = popen(cmd, "r")) == NULL)
1446                         {
1447                         message = g_strconcat("Error: opening pipe\n", input_text, NULL);
1448                         }
1449                 else
1450                         {
1451                         fgets(buf, BUFSIZE, fp);
1452                         message = g_strconcat(buf, NULL);
1453
1454                         if(pclose(fp))
1455                                 {
1456                                 message = g_strconcat("Error: Command not found or exited with error status\n", input_text, NULL);
1457                                 }
1458                         }
1459                 }
1460         else
1461                 {
1462                 message = g_strconcat(input_text, NULL);
1463                 }
1464
1465         g_free(path);
1466         g_free(cmd);
1467         return message;
1468 }
1469
1470 static void search_gps_dnd_received_cb(GtkWidget *pane, GdkDragContext *context,
1471                                                                                 gint x, gint y,
1472                                                                                 GtkSelectionData *selection_data, guint info,
1473                                                                                 guint time, gpointer data)
1474 {
1475         SearchData *sd = data;
1476         GList *list;
1477         gdouble latitude, longitude;
1478         FileData *fd;
1479
1480         if (info == TARGET_URI_LIST)
1481                 {
1482                 list = uri_filelist_from_gtk_selection_data(selection_data);
1483
1484                 /* If more than one file, use only the first file in a list.
1485                 */
1486                 if (list != NULL)
1487                         {
1488                         fd = list->data;
1489                         latitude = metadata_read_GPS_coord(fd, "Xmp.exif.GPSLatitude", 1000);
1490                         longitude = metadata_read_GPS_coord(fd, "Xmp.exif.GPSLongitude", 1000);
1491                         if (latitude != 1000 && longitude != 1000)
1492                                 {
1493                                 gtk_entry_set_text(GTK_ENTRY(sd->entry_gps_coord),
1494                                                         g_strdup_printf("%lf %lf", latitude, longitude));
1495                                 }
1496                         else
1497                                 {
1498                                 gtk_entry_set_text(GTK_ENTRY(sd->entry_gps_coord), "Image is not geocoded");
1499                                 }
1500                         }
1501                 }
1502
1503         if (info == TARGET_TEXT_PLAIN)
1504                 {
1505                 gtk_entry_set_text(GTK_ENTRY(sd->entry_gps_coord),"");
1506                 }
1507 }
1508
1509 static void search_dnd_init(SearchData *sd)
1510 {
1511         gtk_drag_source_set(sd->result_view, GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
1512                             result_drag_types, n_result_drag_types,
1513                             GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
1514         g_signal_connect(G_OBJECT(sd->result_view), "drag_data_get",
1515                          G_CALLBACK(search_dnd_data_set), sd);
1516         g_signal_connect(G_OBJECT(sd->result_view), "drag_begin",
1517                          G_CALLBACK(search_dnd_begin), sd);
1518
1519         gtk_drag_dest_set(GTK_WIDGET(sd->entry_gps_coord),
1520                                          GTK_DEST_DEFAULT_ALL,
1521                                           result_drop_types, n_result_drop_types,
1522                                          GDK_ACTION_COPY);
1523
1524         g_signal_connect(G_OBJECT(sd->entry_gps_coord), "drag_data_received",
1525                                         G_CALLBACK(search_gps_dnd_received_cb), sd);
1526 }
1527
1528 /*
1529  *-------------------------------------------------------------------
1530  * search core
1531  *-------------------------------------------------------------------
1532  */
1533
1534 #define MATCH_IS_BETWEEN(val, a, b)  (b > a ? (val >= a && val <= b) : (val >= b && val <= a))
1535
1536 static gboolean search_step_cb(gpointer data);
1537
1538
1539 static void search_buffer_flush(SearchData *sd)
1540 {
1541         GList *work;
1542
1543         work = g_list_last(sd->search_buffer_list);
1544         while (work)
1545                 {
1546                 MatchFileData *mfd = work->data;
1547                 work = work->prev;
1548
1549                 search_result_append(sd, mfd);
1550                 }
1551
1552         g_list_free(sd->search_buffer_list);
1553         sd->search_buffer_list = NULL;
1554         sd->search_buffer_count = 0;
1555 }
1556
1557 static void search_stop(SearchData *sd)
1558 {
1559         if (sd->search_idle_id)
1560                 {
1561                 g_source_remove(sd->search_idle_id);
1562                 sd->search_idle_id = 0;
1563                 }
1564
1565         image_loader_free(sd->img_loader);
1566         sd->img_loader = NULL;
1567         cache_sim_data_free(sd->img_cd);
1568         sd->img_cd = NULL;
1569
1570         cache_sim_data_free(sd->search_similarity_cd);
1571         sd->search_similarity_cd = NULL;
1572
1573         search_buffer_flush(sd);
1574
1575         filelist_free(sd->search_folder_list);
1576         sd->search_folder_list = NULL;
1577
1578         g_list_free(sd->search_done_list);
1579         sd->search_done_list = NULL;
1580
1581         filelist_free(sd->search_file_list);
1582         sd->search_file_list = NULL;
1583
1584         gtk_widget_set_sensitive(sd->box_search, TRUE);
1585         spinner_set_interval(sd->spinner, -1);
1586         gtk_widget_set_sensitive(sd->button_start, TRUE);
1587         gtk_widget_set_sensitive(sd->button_stop, FALSE);
1588         search_progress_update(sd, TRUE, -1.0);
1589         search_status_update(sd);
1590 }
1591
1592 static void search_file_load_process(SearchData *sd, CacheData *cd)
1593 {
1594         GdkPixbuf *pixbuf;
1595
1596         pixbuf = image_loader_get_pixbuf(sd->img_loader);
1597
1598         if (cd && pixbuf)
1599                 {
1600                 if (!cd->dimensions)
1601                         {
1602                         cache_sim_data_set_dimensions(cd, gdk_pixbuf_get_width(pixbuf),
1603                                                           gdk_pixbuf_get_height(pixbuf));
1604                         }
1605
1606                 if (sd->match_similarity_enable && !cd->similarity)
1607                         {
1608                         ImageSimilarityData *sim;
1609
1610                         sim = image_sim_new_from_pixbuf(pixbuf);
1611                         cache_sim_data_set_similarity(cd, sim);
1612                         image_sim_free(sim);
1613                         }
1614
1615                 if (options->thumbnails.enable_caching &&
1616                     sd->img_loader && image_loader_get_fd(sd->img_loader))
1617                         {
1618                         gchar *base;
1619                         const gchar *path;
1620                         mode_t mode = 0755;
1621
1622                         path = image_loader_get_fd(sd->img_loader)->path;
1623                         base = cache_get_location(CACHE_TYPE_SIM, path, FALSE, &mode);
1624                         if (recursive_mkdir_if_not_exists(base, mode))
1625                                 {
1626                                 g_free(cd->path);
1627                                 cd->path = cache_get_location(CACHE_TYPE_SIM, path, TRUE, NULL);
1628                                 if (cache_sim_data_save(cd))
1629                                         {
1630                                         filetime_set(cd->path, filetime(image_loader_get_fd(sd->img_loader)->path));
1631                                         }
1632                                 }
1633                         g_free(base);
1634                         }
1635                 }
1636
1637         image_loader_free(sd->img_loader);
1638         sd->img_loader = NULL;
1639
1640         sd->search_idle_id = g_idle_add(search_step_cb, sd);
1641 }
1642
1643 static void search_file_load_done_cb(ImageLoader *il, gpointer data)
1644 {
1645         SearchData *sd = data;
1646         search_file_load_process(sd, sd->img_cd);
1647 }
1648
1649 static gboolean search_file_do_extra(SearchData *sd, FileData *fd, gint *match,
1650                                      gint *width, gint *height, gint *simval)
1651 {
1652         gboolean new_data = FALSE;
1653         gboolean tmatch = TRUE;
1654         gboolean tested = FALSE;
1655
1656         if (!sd->img_cd)
1657                 {
1658                 gchar *cd_path;
1659
1660                 new_data = TRUE;
1661
1662                 cd_path = cache_find_location(CACHE_TYPE_SIM, fd->path);
1663                 if (cd_path && filetime(fd->path) == filetime(cd_path))
1664                         {
1665                         sd->img_cd = cache_sim_data_load(cd_path);
1666                         }
1667                 g_free(cd_path);
1668                 }
1669
1670         if (!sd->img_cd)
1671                 {
1672                 sd->img_cd = cache_sim_data_new();
1673                 }
1674
1675         if (new_data)
1676                 {
1677                 if ((sd->match_dimensions_enable && !sd->img_cd->dimensions) ||
1678                     (sd->match_similarity_enable && !sd->img_cd->similarity))
1679                         {
1680                         sd->img_loader = image_loader_new(fd);
1681                         g_signal_connect(G_OBJECT(sd->img_loader), "error", (GCallback)search_file_load_done_cb, sd);
1682                         g_signal_connect(G_OBJECT(sd->img_loader), "done", (GCallback)search_file_load_done_cb, sd);
1683                         if (image_loader_start(sd->img_loader))
1684                                 {
1685                                 return TRUE;
1686                                 }
1687                         else
1688                                 {
1689                                 image_loader_free(sd->img_loader);
1690                                 sd->img_loader = NULL;
1691                                 }
1692                         }
1693                 }
1694
1695         if (tmatch && sd->match_dimensions_enable && sd->img_cd->dimensions)
1696                 {
1697                 CacheData *cd = sd->img_cd;
1698
1699                 tmatch = FALSE;
1700                 tested = TRUE;
1701
1702                 if (sd->match_dimensions == SEARCH_MATCH_EQUAL)
1703                         {
1704                         tmatch = (cd->width == sd->search_width && cd->height == sd->search_height);
1705                         }
1706                 else if (sd->match_dimensions == SEARCH_MATCH_UNDER)
1707                         {
1708                         tmatch = (cd->width < sd->search_width && cd->height < sd->search_height);
1709                         }
1710                 else if (sd->match_dimensions == SEARCH_MATCH_OVER)
1711                         {
1712                         tmatch = (cd->width > sd->search_width && cd->height > sd->search_height);
1713                         }
1714                 else if (sd->match_dimensions == SEARCH_MATCH_BETWEEN)
1715                         {
1716                         tmatch = (MATCH_IS_BETWEEN(cd->width, sd->search_width, sd->search_width_end) &&
1717                                   MATCH_IS_BETWEEN(cd->height, sd->search_height, sd->search_height_end));
1718                         }
1719                 }
1720
1721         if (tmatch && sd->match_similarity_enable && sd->img_cd->similarity)
1722                 {
1723                 gdouble value = 0.0;
1724
1725                 tmatch = FALSE;
1726                 tested = TRUE;
1727
1728                 /* fixme: implement similarity checking */
1729                 if (sd->search_similarity_cd && sd->search_similarity_cd->similarity)
1730                         {
1731                         gdouble result;
1732
1733                         result = image_sim_compare_fast(sd->search_similarity_cd->sim, sd->img_cd->sim,
1734                                                         (gdouble)sd->search_similarity / 100.0);
1735                         result *= 100.0;
1736                         if (result >= (gdouble)sd->search_similarity)
1737                                 {
1738                                 tmatch = TRUE;
1739                                 value = (gint)result;
1740                                 }
1741                         }
1742
1743                 if (simval) *simval = value;
1744                 }
1745
1746         if (sd->img_cd->dimensions)
1747                 {
1748                 if (width) *width = sd->img_cd->width;
1749                 if (height) *height = sd->img_cd->height;
1750                 }
1751
1752         cache_sim_data_free(sd->img_cd);
1753         sd->img_cd = NULL;
1754
1755         *match = (tmatch && tested);
1756
1757         return FALSE;
1758 }
1759
1760 static gboolean search_file_next(SearchData *sd)
1761 {
1762         FileData *fd;
1763         gboolean match = TRUE;
1764         gboolean tested = FALSE;
1765         gboolean extra_only = FALSE;
1766         gint width = 0;
1767         gint height = 0;
1768         gint sim = 0;
1769         time_t file_date;
1770
1771         if (!sd->search_file_list) return FALSE;
1772
1773         if (sd->img_cd)
1774                 {
1775                 /* on end of a CacheData load, skip recomparing non-extra match types */
1776                 extra_only = TRUE;
1777                 match = FALSE;
1778                 }
1779         else
1780                 {
1781                 sd->search_total++;
1782                 }
1783
1784         fd = sd->search_file_list->data;
1785
1786         if (match && sd->match_name_enable && sd->search_name)
1787                 {
1788                 tested = TRUE;
1789                 match = FALSE;
1790
1791                 if (sd->match_name == SEARCH_MATCH_EQUAL)
1792                         {
1793                         if (sd->search_name_match_case)
1794                                 {
1795                                 match = (strcmp(fd->name, sd->search_name) == 0);
1796                                 }
1797                         else
1798                                 {
1799                                 match = (g_ascii_strcasecmp(fd->name, sd->search_name) == 0);
1800                                 }
1801                         }
1802                 else if (sd->match_name == SEARCH_MATCH_CONTAINS)
1803                         {
1804                         if (sd->search_name_match_case)
1805                                 {
1806                                 match = (strstr(fd->name, sd->search_name) != NULL);
1807                                 }
1808                         else
1809                                 {
1810                                 /* sd->search_name is converted in search_start() */
1811                                 gchar *haystack = g_utf8_strdown(fd->name, -1);
1812                                 match = (strstr(haystack, sd->search_name) != NULL);
1813                                 g_free(haystack);
1814                                 }
1815                         }
1816                 }
1817
1818         if (match && sd->match_size_enable)
1819                 {
1820                 tested = TRUE;
1821                 match = FALSE;
1822
1823                 if (sd->match_size == SEARCH_MATCH_EQUAL)
1824                         {
1825                         match = (fd->size == sd->search_size);
1826                         }
1827                 else if (sd->match_size == SEARCH_MATCH_UNDER)
1828                         {
1829                         match = (fd->size < sd->search_size);
1830                         }
1831                 else if (sd->match_size == SEARCH_MATCH_OVER)
1832                         {
1833                         match = (fd->size > sd->search_size);
1834                         }
1835                 else if (sd->match_size == SEARCH_MATCH_BETWEEN)
1836                         {
1837                         match = MATCH_IS_BETWEEN(fd->size, sd->search_size, sd->search_size_end);
1838                         }
1839                 }
1840
1841         if (match && sd->match_date_enable)
1842                 {
1843                 tested = TRUE;
1844                 match = FALSE;
1845
1846                 if (sd->search_date_exif)
1847                         {
1848                         read_exif_time_data(fd);
1849                         file_date = fd->exifdate;
1850                         }
1851                 else
1852                         {
1853                         file_date = fd->date;
1854                         }
1855
1856                 if (sd->match_date == SEARCH_MATCH_EQUAL)
1857                         {
1858                         struct tm *lt;
1859
1860                         lt = localtime(&file_date);
1861                         match = (lt &&
1862                                  lt->tm_year == sd->search_date_y - 1900 &&
1863                                  lt->tm_mon == sd->search_date_m - 1 &&
1864                                  lt->tm_mday == sd->search_date_d);
1865                         }
1866                 else if (sd->match_date == SEARCH_MATCH_UNDER)
1867                         {
1868                         match = (file_date < convert_dmy_to_time(sd->search_date_d, sd->search_date_m, sd->search_date_y));
1869                         }
1870                 else if (sd->match_date == SEARCH_MATCH_OVER)
1871                         {
1872                         match = (file_date > convert_dmy_to_time(sd->search_date_d, sd->search_date_m, sd->search_date_y) + 60 * 60 * 24 - 1);
1873                         }
1874                 else if (sd->match_date == SEARCH_MATCH_BETWEEN)
1875                         {
1876                         time_t a = convert_dmy_to_time(sd->search_date_d, sd->search_date_m, sd->search_date_y);
1877                         time_t b = convert_dmy_to_time(sd->search_date_end_d, sd->search_date_end_m, sd->search_date_end_y);
1878
1879                         if (b >= a)
1880                                 {
1881                                 b += 60 * 60 * 24 - 1;
1882                                 }
1883                         else
1884                                 {
1885                                 a += 60 * 60 * 24 - 1;
1886                                 }
1887                         match = MATCH_IS_BETWEEN(file_date, a, b);
1888                         }
1889                 }
1890
1891         if (match && sd->match_keywords_enable && sd->search_keyword_list)
1892                 {
1893                 GList *list;
1894
1895                 tested = TRUE;
1896                 match = FALSE;
1897
1898                 list = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
1899
1900                 if (list)
1901                         {
1902                         GList *needle;
1903                         GList *haystack;
1904
1905                         if (sd->match_keywords == SEARCH_MATCH_ALL)
1906                                 {
1907                                 gboolean found = TRUE;
1908
1909                                 needle = sd->search_keyword_list;
1910                                 while (needle && found)
1911                                         {
1912                                         found = FALSE;
1913                                         haystack = list;
1914                                         while (haystack && !found)
1915                                                 {
1916                                                 found = (g_ascii_strcasecmp((gchar *)needle->data,
1917                                                                     (gchar *)haystack->data) == 0);
1918                                                 haystack = haystack->next;
1919                                                 }
1920                                         needle = needle->next;
1921                                         }
1922
1923                                 match = found;
1924                                 }
1925                         else if (sd->match_keywords == SEARCH_MATCH_ANY)
1926                                 {
1927                                 gboolean found = FALSE;
1928
1929                                 needle = sd->search_keyword_list;
1930                                 while (needle && !found)
1931                                         {
1932                                         haystack = list;
1933                                         while (haystack && !found)
1934                                                 {
1935                                                 found = (g_ascii_strcasecmp((gchar *)needle->data,
1936                                                                     (gchar *)haystack->data) == 0);
1937                                                 haystack = haystack->next;
1938                                                 }
1939                                         needle = needle->next;
1940                                         }
1941
1942                                 match = found;
1943                                 }
1944                         else if (sd->match_keywords == SEARCH_MATCH_NONE)
1945                                 {
1946                                 gboolean found = FALSE;
1947
1948                                 needle = sd->search_keyword_list;
1949                                 while (needle && !found)
1950                                         {
1951                                         haystack = list;
1952                                         while (haystack && !found)
1953                                                 {
1954                                                 found = (g_ascii_strcasecmp((gchar *)needle->data,
1955                                                                     (gchar *)haystack->data) == 0);
1956                                                 haystack = haystack->next;
1957                                                 }
1958                                         needle = needle->next;
1959                                         }
1960
1961                                 match = !found;
1962                                 }
1963                         string_list_free(list);
1964                         }
1965                 else
1966                         {
1967                         match = (sd->match_keywords == SEARCH_MATCH_NONE);
1968                         }
1969                 }
1970
1971         if (match && sd->match_comment_enable && sd->search_comment && strlen(sd->search_comment))
1972                 {
1973                 gchar *comment;
1974
1975                 tested = TRUE;
1976                 match = FALSE;
1977
1978                 comment = metadata_read_string(fd, COMMENT_KEY, METADATA_PLAIN);
1979
1980                 if (comment)
1981                         {
1982                         if (!sd->search_comment_match_case)
1983                                 {
1984                                 gchar *tmp = g_utf8_strdown(comment, -1);
1985                                 g_free(comment);
1986                                 comment = tmp;
1987                                 }
1988
1989                         if (sd->match_comment == SEARCH_MATCH_CONTAINS)
1990                                 {
1991                                 match = (strstr(comment, sd->search_comment) != NULL);
1992                                 }
1993                         else if (sd->match_comment == SEARCH_MATCH_NONE)
1994                                 {
1995                                 match = (strstr(comment, sd->search_comment) == NULL);
1996                                 }
1997                         g_free(comment);
1998                         }
1999                 else
2000                         {
2001                         match = (sd->match_comment == SEARCH_MATCH_NONE);
2002                         }
2003                 }
2004
2005         if (match && sd->match_gps_enable)
2006                 {
2007                 /* Calculate the distance the image is from the specified origin.
2008                 * This is a standard algorithm. A simplified one may be faster.
2009                 */
2010                 #define RADIANS  0.0174532925
2011                 #define KM_EARTH_RADIUS 6371
2012                 #define MILES_EARTH_RADIUS 3959
2013                 #define NAUTICAL_MILES_EARTH_RADIUS 3440
2014
2015                 gdouble latitude, longitude, range, conversion;
2016
2017                 if (g_strcmp0(gtk_combo_box_text_get_active_text(
2018                                                 GTK_COMBO_BOX_TEXT(sd->units_gps)), _("km")) == 0)
2019                         {
2020                         conversion = KM_EARTH_RADIUS;
2021                         }
2022                 else if (g_strcmp0(gtk_combo_box_text_get_active_text(
2023                                                 GTK_COMBO_BOX_TEXT(sd->units_gps)), _("miles")) == 0)
2024                         {
2025                         conversion = MILES_EARTH_RADIUS;
2026                         }
2027                 else
2028                         {
2029                         conversion = NAUTICAL_MILES_EARTH_RADIUS;
2030                         }
2031
2032                 tested = TRUE;
2033                 match = FALSE;
2034
2035                 latitude = metadata_read_GPS_coord(fd, "Xmp.exif.GPSLatitude", 1000);
2036                 longitude = metadata_read_GPS_coord(fd, "Xmp.exif.GPSLongitude", 1000);
2037                 if (latitude != 1000 && longitude != 1000)
2038                         {
2039                         range = conversion * acos(sin(latitude * RADIANS) *
2040                                                 sin(sd->search_lat * RADIANS) + cos(latitude * RADIANS) *
2041                                                 cos(sd->search_lat * RADIANS) * cos((sd->search_lon -
2042                                                 longitude) * RADIANS));
2043                         if (sd->match_gps == SEARCH_MATCH_UNDER)
2044                                 {
2045                                 if (sd->search_gps >= range)
2046                                         match = TRUE;
2047                                 }
2048                         else if (sd->match_gps == SEARCH_MATCH_OVER)
2049                                 {
2050                                 if (sd->search_gps < range)
2051                                         match = TRUE;
2052                                 }
2053                         }
2054                 else if (sd->match_gps == SEARCH_MATCH_NONE)
2055                         {
2056                         match = TRUE;
2057                         }
2058                 }
2059
2060         if ((match || extra_only) && (sd->match_dimensions_enable ||
2061                                                                 sd ->match_similarity_enable))
2062                 {
2063                 tested = TRUE;
2064
2065                 if (search_file_do_extra(sd, fd, &match, &width, &height, &sim))
2066                         {
2067                         sd->search_buffer_count += SEARCH_BUFFER_MATCH_LOAD;
2068                         return TRUE;
2069                         }
2070                 }
2071
2072         sd->search_file_list = g_list_remove(sd->search_file_list, fd);
2073
2074         if (tested && match)
2075                 {
2076                 MatchFileData *mfd;
2077
2078                 mfd = g_new(MatchFileData, 1);
2079                 mfd->fd = fd;
2080
2081                 mfd->width = width;
2082                 mfd->height = height;
2083                 mfd->rank = sim;
2084
2085                 sd->search_buffer_list = g_list_prepend(sd->search_buffer_list, mfd);
2086                 sd->search_buffer_count += SEARCH_BUFFER_MATCH_HIT;
2087                 sd->search_count++;
2088                 search_progress_update(sd, TRUE, -1.0);
2089                 }
2090         else
2091                 {
2092                 file_data_unref(fd);
2093                 sd->search_buffer_count += SEARCH_BUFFER_MATCH_MISS;
2094                 }
2095
2096         return FALSE;
2097 }
2098
2099 static gboolean search_step_cb(gpointer data)
2100 {
2101         SearchData *sd = data;
2102         FileData *fd;
2103
2104         if (sd->search_buffer_count > SEARCH_BUFFER_FLUSH_SIZE)
2105                 {
2106                 search_buffer_flush(sd);
2107                 search_progress_update(sd, TRUE, -1.0);
2108                 }
2109
2110         if (sd->search_file_list)
2111                 {
2112                 if (search_file_next(sd))
2113                         {
2114                         sd->search_idle_id = 0;
2115                         return FALSE;
2116                         }
2117                 return TRUE;
2118                 }
2119
2120         if (!sd->search_file_list && !sd->search_folder_list)
2121                 {
2122                 sd->search_idle_id = 0;
2123
2124                 search_stop(sd);
2125                 search_result_thumb_step(sd);
2126
2127                 return FALSE;
2128                 }
2129
2130         fd = sd->search_folder_list->data;
2131
2132         if (g_list_find(sd->search_done_list, fd) == NULL)
2133                 {
2134                 GList *list = NULL;
2135                 GList *dlist = NULL;
2136                 gboolean success = FALSE;
2137
2138                 sd->search_done_list = g_list_prepend(sd->search_done_list, fd);
2139
2140                 if (sd->search_type == SEARCH_MATCH_NONE)
2141                         {
2142                         success = filelist_read(fd, &list, &dlist);
2143                         }
2144                 else if (sd->search_type == SEARCH_MATCH_ALL &&
2145                          sd->search_dir_fd &&
2146                          strlen(fd->path) >= strlen(sd->search_dir_fd->path))
2147                         {
2148                         const gchar *path;
2149
2150                         path = fd->path + strlen(sd->search_dir_fd->path);
2151                         if (path != fd->path)
2152                                 {
2153                                 FileData *dir_fd = file_data_new_dir(path);
2154                                 success = filelist_read(dir_fd, &list, NULL);
2155                                 file_data_unref(dir_fd);
2156                                 }
2157                         success |= filelist_read(fd, NULL, &dlist);
2158                         if (success)
2159                                 {
2160                                 GList *work;
2161
2162                                 work = list;
2163                                 while (work)
2164                                         {
2165                                         FileData *fdp;
2166                                         GList *link;
2167                                         gchar *meta_path;
2168
2169                                         fdp = work->data;
2170                                         link = work;
2171                                         work = work->next;
2172
2173                                         meta_path = cache_find_location(CACHE_TYPE_METADATA, fdp->path);
2174                                         if (!meta_path)
2175                                                 {
2176                                                 list = g_list_delete_link(list, link);
2177                                                 file_data_unref(fdp);
2178                                                 }
2179                                         g_free(meta_path);
2180                                         }
2181                                 }
2182                         }
2183
2184                 if (success)
2185                         {
2186                         list = filelist_sort(list, SORT_NAME, TRUE);
2187                         sd->search_file_list = list;
2188
2189                         if (sd->search_path_recurse)
2190                                 {
2191                                 dlist = filelist_sort(dlist, SORT_NAME, TRUE);
2192                                 sd->search_folder_list = g_list_concat(dlist, sd->search_folder_list);
2193                                 }
2194                         else
2195                                 {
2196                                 filelist_free(dlist);
2197                                 }
2198                         }
2199                 }
2200         else
2201                 {
2202                 sd->search_folder_list = g_list_remove(sd->search_folder_list, fd);
2203                 sd->search_done_list = g_list_remove(sd->search_done_list, fd);
2204                 file_data_unref(fd);
2205                 }
2206
2207         return TRUE;
2208 }
2209
2210 static void search_similarity_load_done_cb(ImageLoader *il, gpointer data)
2211 {
2212         SearchData *sd = data;
2213         search_file_load_process(sd, sd->search_similarity_cd);
2214 }
2215
2216 static void search_start(SearchData *sd)
2217 {
2218         search_stop(sd);
2219         search_result_clear(sd);
2220
2221         if (sd->search_dir_fd)
2222                 {
2223                 sd->search_folder_list = g_list_prepend(sd->search_folder_list, file_data_ref(sd->search_dir_fd));
2224                 }
2225
2226         if (!sd->search_name_match_case)
2227                 {
2228                 /* convert to lowercase here, so that this is only done once per search */
2229                 gchar *tmp = g_utf8_strdown(sd->search_name, -1);
2230                 g_free(sd->search_name);
2231                 sd->search_name = tmp;
2232                 }
2233
2234         if (!sd->search_comment_match_case)
2235                 {
2236                 /* convert to lowercase here, so that this is only done once per search */
2237                 gchar *tmp = g_utf8_strdown(sd->search_comment, -1);
2238                 g_free(sd->search_comment);
2239                 sd->search_comment = tmp;
2240                 }
2241
2242         sd->search_count = 0;
2243         sd->search_total = 0;
2244
2245         gtk_widget_set_sensitive(sd->box_search, FALSE);
2246         spinner_set_interval(sd->spinner, SPINNER_SPEED);
2247         gtk_widget_set_sensitive(sd->button_start, FALSE);
2248         gtk_widget_set_sensitive(sd->button_stop, TRUE);
2249         search_progress_update(sd, TRUE, -1.0);
2250
2251         if (sd->match_similarity_enable &&
2252             !sd->search_similarity_cd &&
2253             isfile(sd->search_similarity_path))
2254                 {
2255                 gchar *cd_path;
2256
2257                 cd_path = cache_find_location(CACHE_TYPE_SIM, sd->search_similarity_path);
2258                 if (cd_path && filetime(sd->search_similarity_path) == filetime(cd_path))
2259                         {
2260                         sd->search_similarity_cd = cache_sim_data_load(cd_path);
2261                         }
2262                 g_free(cd_path);
2263
2264                 if (!sd->search_similarity_cd || !sd->search_similarity_cd->similarity)
2265                         {
2266                         if (!sd->search_similarity_cd)
2267                                 {
2268                                 sd->search_similarity_cd = cache_sim_data_new();
2269                                 }
2270
2271                         sd->img_loader = image_loader_new(file_data_new_group(sd->search_similarity_path));
2272                         g_signal_connect(G_OBJECT(sd->img_loader), "error", (GCallback)search_similarity_load_done_cb, sd);
2273                         g_signal_connect(G_OBJECT(sd->img_loader), "done", (GCallback)search_similarity_load_done_cb, sd);
2274                         if (image_loader_start(sd->img_loader))
2275                                 {
2276                                 return;
2277                                 }
2278                         image_loader_free(sd->img_loader);
2279                         sd->img_loader = NULL;
2280                         }
2281
2282                 }
2283
2284         sd->search_idle_id = g_idle_add(search_step_cb, sd);
2285 }
2286
2287 static void search_start_cb(GtkWidget *widget, gpointer data)
2288 {
2289         SearchData *sd = data;
2290         GtkTreeViewColumn *column;
2291         gchar *path;
2292         gchar *entry_text;
2293
2294         if (sd->search_folder_list)
2295                 {
2296                 search_stop(sd);
2297                 search_result_thumb_step(sd);
2298                 return;
2299                 }
2300
2301         if (sd->match_name_enable) history_combo_append_history(sd->entry_name, NULL);
2302         g_free(sd->search_name);
2303         sd->search_name = g_strdup(gtk_entry_get_text(GTK_ENTRY(sd->entry_name)));
2304
2305         /* XXX */
2306         g_free(sd->search_comment);
2307         sd->search_comment = g_strdup(gtk_entry_get_text(GTK_ENTRY(sd->entry_comment)));
2308
2309         g_free(sd->search_similarity_path);
2310         sd->search_similarity_path = g_strdup(gtk_entry_get_text(GTK_ENTRY(sd->entry_similarity)));
2311         if (sd->match_similarity_enable)
2312                 {
2313                 if (!isfile(sd->search_similarity_path))
2314                         {
2315                         file_util_warning_dialog(_("File not found"),
2316                                                  _("Please enter an existing file for image content."),
2317                                                  GTK_STOCK_DIALOG_WARNING, sd->window);
2318                         return;
2319                         }
2320                 tab_completion_append_to_history(sd->entry_similarity, sd->search_similarity_path);
2321                 }
2322
2323         /* Check the coordinate entry.
2324         * If the result is not sensible, it should get blocked.
2325         */
2326         if (sd->match_gps_enable)
2327                 {
2328                 if (sd->match_gps != SEARCH_MATCH_NONE)
2329                         {
2330                         entry_text = decode_geo_parameters(gtk_entry_get_text(
2331                                                                                 GTK_ENTRY(sd->entry_gps_coord)));
2332
2333                         sd->search_lat = 1000;
2334                         sd->search_lon = 1000;
2335                         sscanf(entry_text," %lf  %lf ", &sd->search_lat, &sd->search_lon );
2336                         if (!(entry_text != NULL && !g_strstr_len(entry_text, -1, "Error") &&
2337                                                 sd->search_lat >= -90 && sd->search_lat <= 90 &&
2338                                                 sd->search_lon >= -180 && sd->search_lon <= 180))
2339                                 {
2340                                 file_util_warning_dialog(_(
2341                                                 "Entry does not contain a valid lat/long value"),
2342                                                         entry_text, GTK_STOCK_DIALOG_WARNING, sd->window);
2343                                 return;
2344                                 }
2345                         g_free(entry_text);
2346                         }
2347                 }
2348
2349         string_list_free(sd->search_keyword_list);
2350         sd->search_keyword_list = keyword_list_pull(sd->entry_keywords);
2351
2352         date_selection_get(sd->date_sel, &sd->search_date_d, &sd->search_date_m, &sd->search_date_y);
2353         date_selection_get(sd->date_sel_end, &sd->search_date_end_d, &sd->search_date_end_m, &sd->search_date_end_y);
2354
2355         column = gtk_tree_view_get_column(GTK_TREE_VIEW(sd->result_view), SEARCH_COLUMN_DIMENSIONS - 1);
2356         gtk_tree_view_column_set_visible(column, sd->match_dimensions_enable);
2357
2358         column = gtk_tree_view_get_column(GTK_TREE_VIEW(sd->result_view), SEARCH_COLUMN_RANK - 1);
2359         gtk_tree_view_column_set_visible(column, sd->match_similarity_enable);
2360         if (!sd->match_similarity_enable)
2361                 {
2362                 GtkTreeSortable *sortable;
2363                 gint id;
2364                 GtkSortType order;
2365
2366                 sortable = GTK_TREE_SORTABLE(gtk_tree_view_get_model(GTK_TREE_VIEW(sd->result_view)));
2367                 if (gtk_tree_sortable_get_sort_column_id(sortable, &id, &order) &&
2368                     id == SEARCH_COLUMN_RANK)
2369                         {
2370                         gtk_tree_sortable_set_sort_column_id(sortable, SEARCH_COLUMN_PATH, GTK_SORT_ASCENDING);
2371                         }
2372                 }
2373
2374         if (sd->search_type == SEARCH_MATCH_NONE)
2375                 {
2376                 /* search path */
2377
2378                 path = remove_trailing_slash(gtk_entry_get_text(GTK_ENTRY(sd->path_entry)));
2379                 if (isdir(path))
2380                         {
2381                         file_data_unref(sd->search_dir_fd);
2382                         sd->search_dir_fd = file_data_new_dir(path);
2383
2384                         tab_completion_append_to_history(sd->path_entry, sd->search_dir_fd->path);
2385
2386                         search_start(sd);
2387                         }
2388                 else
2389                         {
2390                         file_util_warning_dialog(_("Folder not found"),
2391                                                  _("Please enter an existing folder to search."),
2392                                                  GTK_STOCK_DIALOG_WARNING, sd->window);
2393                         }
2394
2395                 g_free(path);
2396                 }
2397         else if (sd->search_type == SEARCH_MATCH_ALL)
2398                 {
2399                 /* search metadata */
2400                 file_data_unref(sd->search_dir_fd);
2401                 sd->search_dir_fd = file_data_new_dir(get_metadata_cache_dir());
2402                 search_start(sd);
2403                 }
2404         else if (sd->search_type == SEARCH_MATCH_CONTAINS)
2405                 {
2406                 /* search current result list */
2407                 GList *list;
2408
2409                 list = search_result_refine_list(sd);
2410
2411                 file_data_unref(sd->search_dir_fd);
2412                 sd->search_dir_fd = NULL;
2413
2414                 search_start(sd);
2415
2416                 sd->search_file_list = g_list_concat(sd->search_file_list, list);
2417                 }
2418 }
2419
2420 /*
2421  *-------------------------------------------------------------------
2422  * window construct
2423  *-------------------------------------------------------------------
2424  */
2425
2426 enum {
2427         MENU_CHOICE_COLUMN_NAME = 0,
2428         MENU_CHOICE_COLUMN_VALUE
2429 };
2430
2431 static void search_thumb_toggle_cb(GtkWidget *button, gpointer data)
2432 {
2433         SearchData *sd = data;
2434
2435         search_result_thumb_enable(sd, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)));
2436 }
2437
2438 static gint sort_matchdata_dimensions(MatchFileData *a, MatchFileData *b)
2439 {
2440         gint sa;
2441         gint sb;
2442
2443         sa = a->width * a->height;
2444         sb = b->width * b->height;
2445
2446         if (sa > sb) return 1;
2447         if (sa < sb) return -1;
2448         return 0;
2449 }
2450
2451 static gint search_result_sort_cb(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer data)
2452 {
2453         gint n = GPOINTER_TO_INT(data);
2454         MatchFileData *fda;
2455         MatchFileData *fdb;
2456
2457         gtk_tree_model_get(model, a, SEARCH_COLUMN_POINTER, &fda, -1);
2458         gtk_tree_model_get(model, b, SEARCH_COLUMN_POINTER, &fdb, -1);
2459
2460         if (!fda || !fdb) return 0;
2461
2462         switch (n)
2463                 {
2464                 case SEARCH_COLUMN_RANK:
2465                         if (((MatchFileData *)fda)->rank > (fdb)->rank) return 1;
2466                         if (((MatchFileData *)fda)->rank < (fdb)->rank) return -1;
2467                         return 0;
2468                         break;
2469                 case SEARCH_COLUMN_NAME:
2470                         if (options->file_sort.case_sensitive)
2471                                 return strcmp(fda->fd->collate_key_name, fdb->fd->collate_key_name);
2472                         else
2473                                 return strcmp(fda->fd->collate_key_name_nocase, fdb->fd->collate_key_name_nocase);
2474                         break;
2475                 case SEARCH_COLUMN_SIZE:
2476                         if (fda->fd->size > fdb->fd->size) return 1;
2477                         if (fda->fd->size < fdb->fd->size) return -1;
2478                         return 0;
2479                         break;
2480                 case SEARCH_COLUMN_DATE:
2481                         if (fda->fd->date > fdb->fd->date) return 1;
2482                         if (fda->fd->date < fdb->fd->date) return -1;
2483                         return 0;
2484                         break;
2485                 case SEARCH_COLUMN_DIMENSIONS:
2486                         return sort_matchdata_dimensions(fda, fdb);
2487                         break;
2488                 case SEARCH_COLUMN_PATH:
2489                         return utf8_compare(fda->fd->path, fdb->fd->path, options->file_sort.case_sensitive);
2490                         break;
2491                 default:
2492                         break;
2493                 }
2494
2495         return 0;
2496 }
2497
2498 static void search_result_add_column(SearchData * sd, gint n, const gchar *title, gboolean image, gboolean right_justify)
2499 {
2500         GtkTreeViewColumn *column;
2501         GtkCellRenderer *renderer;
2502
2503         column = gtk_tree_view_column_new();
2504         gtk_tree_view_column_set_title(column, title);
2505         gtk_tree_view_column_set_min_width(column, 4);
2506
2507         if (n != SEARCH_COLUMN_THUMB) gtk_tree_view_column_set_resizable(column, TRUE);
2508
2509         if (!image)
2510                 {
2511                 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
2512                 renderer = gtk_cell_renderer_text_new();
2513                 if (right_justify) g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL);
2514                 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2515                 gtk_tree_view_column_add_attribute(column, renderer, "text", n);
2516
2517                 gtk_tree_view_column_set_sort_column_id(column, n);
2518                 }
2519         else
2520                 {
2521                 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2522                 renderer = gtk_cell_renderer_pixbuf_new();
2523                 cell_renderer_height_override(renderer);
2524                 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2525                 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", n);
2526                 }
2527
2528         gtk_tree_view_append_column(GTK_TREE_VIEW(sd->result_view), column);
2529 }
2530
2531 static void menu_choice_set_visible(GtkWidget *widget, gboolean visible)
2532 {
2533         if (visible)
2534                 {
2535                 if (!gtk_widget_get_visible(widget)) gtk_widget_show(widget);
2536                 }
2537         else
2538                 {
2539                 if (gtk_widget_get_visible(widget)) gtk_widget_hide(widget);
2540                 }
2541 }
2542
2543 static gboolean menu_choice_get_match_type(GtkWidget *combo, MatchType *type)
2544 {
2545         GtkTreeModel *store;
2546         GtkTreeIter iter;
2547
2548         store = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
2549         if (!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter)) return FALSE;
2550         gtk_tree_model_get(store, &iter, MENU_CHOICE_COLUMN_VALUE, type, -1);
2551         return TRUE;
2552 }
2553
2554 static void menu_choice_path_cb(GtkWidget *combo, gpointer data)
2555 {
2556         SearchData *sd = data;
2557
2558         if (!menu_choice_get_match_type(combo, &sd->search_type)) return;
2559
2560         menu_choice_set_visible(gtk_widget_get_parent(sd->check_recurse),
2561                                 (sd->search_type == SEARCH_MATCH_NONE));
2562 }
2563
2564 static void menu_choice_name_cb(GtkWidget *combo, gpointer data)
2565 {
2566         SearchData *sd = data;
2567
2568         if (!menu_choice_get_match_type(combo, &sd->match_name)) return;
2569 }
2570
2571 static void menu_choice_size_cb(GtkWidget *combo, gpointer data)
2572 {
2573         SearchData *sd = data;
2574
2575         if (!menu_choice_get_match_type(combo, &sd->match_size)) return;
2576
2577         menu_choice_set_visible(gtk_widget_get_parent(sd->spin_size_end),
2578                                 (sd->match_size == SEARCH_MATCH_BETWEEN));
2579 }
2580
2581 static void menu_choice_date_cb(GtkWidget *combo, gpointer data)
2582 {
2583         SearchData *sd = data;
2584
2585         if (!menu_choice_get_match_type(combo, &sd->match_date)) return;
2586
2587         menu_choice_set_visible(gtk_widget_get_parent(sd->date_sel_end),
2588                                 (sd->match_date == SEARCH_MATCH_BETWEEN));
2589 }
2590
2591 static void menu_choice_dimensions_cb(GtkWidget *combo, gpointer data)
2592 {
2593         SearchData *sd = data;
2594
2595         if (!menu_choice_get_match_type(combo, &sd->match_dimensions)) return;
2596
2597         menu_choice_set_visible(gtk_widget_get_parent(sd->spin_width_end),
2598                                 (sd->match_dimensions == SEARCH_MATCH_BETWEEN));
2599 }
2600
2601 static void menu_choice_keyword_cb(GtkWidget *combo, gpointer data)
2602 {
2603         SearchData *sd = data;
2604
2605         if (!menu_choice_get_match_type(combo, &sd->match_keywords)) return;
2606 }
2607
2608 static void menu_choice_comment_cb(GtkWidget *combo, gpointer data)
2609 {
2610         SearchData *sd = data;
2611
2612         if (!menu_choice_get_match_type(combo, &sd->match_comment)) return;
2613 }
2614
2615 static void menu_choice_spin_cb(GtkAdjustment *adjustment, gpointer data)
2616 {
2617         gint *value = data;
2618
2619         *value = (gint)gtk_adjustment_get_value(adjustment);
2620 }
2621
2622 static void menu_choice_gps_cb(GtkWidget *combo, gpointer data)
2623 {
2624         SearchData *sd = data;
2625
2626         if (!menu_choice_get_match_type(combo, &sd->match_gps)) return;
2627
2628         menu_choice_set_visible(gtk_widget_get_parent(sd->spin_gps),
2629                                         (sd->match_gps != SEARCH_MATCH_NONE));
2630 }
2631
2632 static GtkWidget *menu_spin(GtkWidget *box, gdouble min, gdouble max, gint value,
2633                             GCallback func, gpointer data)
2634 {
2635         GtkWidget *spin;
2636         GtkAdjustment *adj;
2637
2638         spin = gtk_spin_button_new_with_range(min, max, 1);
2639         gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), (gdouble)value);
2640         adj = gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(spin));
2641         if (func) g_signal_connect(G_OBJECT(adj), "value_changed",
2642                                    G_CALLBACK(func), data);
2643         gtk_box_pack_start(GTK_BOX(box), spin, FALSE, FALSE, 0);
2644         gtk_widget_show(spin);
2645
2646         return spin;
2647 }
2648
2649 static void menu_choice_check_cb(GtkWidget *button, gpointer data)
2650 {
2651         GtkWidget *widget = data;
2652         gboolean active;
2653         gboolean *value;
2654
2655         active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
2656         gtk_widget_set_sensitive(widget, active);
2657
2658         value = g_object_get_data(G_OBJECT(button), "check_var");
2659         if (value) *value = active;
2660 }
2661
2662 static GtkWidget *menu_choice_menu(const MatchList *items, gint item_count,
2663                                    GCallback func, gpointer data)
2664 {
2665         GtkWidget *combo;
2666         GtkCellRenderer *renderer;
2667         GtkListStore *store;
2668         gint i;
2669
2670         store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
2671         combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
2672         g_object_unref(store);
2673
2674         renderer = gtk_cell_renderer_text_new();
2675         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
2676         gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), renderer,
2677                                        "text", MENU_CHOICE_COLUMN_NAME, NULL);
2678
2679         for (i = 0; i < item_count; i++)
2680                 {
2681                 GtkTreeIter iter;
2682
2683                 gtk_list_store_append(store, &iter);
2684                 gtk_list_store_set(store, &iter, MENU_CHOICE_COLUMN_NAME, _(items[i].text),
2685                                                  MENU_CHOICE_COLUMN_VALUE, items[i].type, -1);
2686                 }
2687
2688         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
2689
2690         if (func) g_signal_connect(G_OBJECT(combo), "changed",
2691                                    G_CALLBACK(func), data);
2692
2693         return combo;
2694 }
2695
2696 static GtkWidget *menu_choice(GtkWidget *box, GtkWidget **check, GtkWidget **menu,
2697                               const gchar *text, gboolean *value,
2698                               const MatchList *items, gint item_count,
2699                               GCallback func, gpointer data)
2700 {
2701         GtkWidget *base_box;
2702         GtkWidget *hbox;
2703         GtkWidget *button;
2704         GtkWidget *option;
2705
2706         base_box = gtk_hbox_new(FALSE, PREF_PAD_GAP);
2707         gtk_box_pack_start(GTK_BOX(box), base_box, FALSE, FALSE, 0);
2708         gtk_widget_show(base_box);
2709
2710         button = gtk_check_button_new();
2711         if (value) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), *value);
2712         gtk_box_pack_start(GTK_BOX(base_box), button, FALSE, FALSE, 0);
2713         gtk_widget_show(button);
2714         if (check) *check = button;
2715         if (value) g_object_set_data(G_OBJECT(button), "check_var", value);
2716
2717         hbox = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
2718         gtk_box_pack_start(GTK_BOX(base_box), hbox, TRUE, TRUE, 0);
2719         gtk_widget_show(hbox);
2720
2721         g_signal_connect(G_OBJECT(button), "toggled",
2722                          G_CALLBACK(menu_choice_check_cb), hbox);
2723         gtk_widget_set_sensitive(hbox, (value) ? *value : FALSE);
2724
2725         pref_label_new(hbox, text);
2726
2727         if (!items && !menu) return hbox;
2728
2729         option = menu_choice_menu(items, item_count, func, data);
2730         gtk_box_pack_start(GTK_BOX(hbox), option, FALSE, FALSE, 0);
2731         gtk_widget_show(option);
2732         if (menu) *menu = option;
2733
2734         return hbox;
2735 }
2736
2737 static void search_window_close(SearchData *sd)
2738 {
2739         gtk_widget_destroy(sd->window);
2740 }
2741
2742 static gboolean search_window_delete_cb(GtkWidget *widget, GdkEventAny *event, gpointer data)
2743 {
2744         SearchData *sd = data;
2745
2746         search_window_close(sd);
2747         return TRUE;
2748 }
2749
2750 static void search_window_destroy_cb(GtkWidget *widget, gpointer data)
2751 {
2752         SearchData *sd = data;
2753
2754         search_window_list = g_list_remove(search_window_list, sd);
2755
2756         search_result_update_idle_cancel(sd);
2757
2758         filelist_free(sd->search_buffer_list);
2759         sd->search_buffer_list = NULL;
2760
2761         search_stop(sd);
2762         search_result_clear(sd);
2763
2764         file_data_unref(sd->search_dir_fd);
2765
2766         g_free(sd->search_name);
2767         g_free(sd->search_comment);
2768         g_free(sd->search_similarity_path);
2769         string_list_free(sd->search_keyword_list);
2770
2771         file_data_unregister_notify_func(search_notify_cb, sd);
2772
2773         g_free(sd);
2774 }
2775
2776 void search_new(FileData *dir_fd, FileData *example_file)
2777 {
2778         SearchData *sd;
2779         GtkWidget *vbox;
2780         GtkWidget *hbox;
2781         GtkWidget *hbox2;
2782         GtkWidget *pad_box;
2783         GtkWidget *frame;
2784         GtkWidget *scrolled;
2785         GtkListStore *store;
2786         GtkTreeSortable *sortable;
2787         GtkTreeSelection *selection;
2788         GtkWidget *combo;
2789         GdkGeometry geometry;
2790
2791         sd = g_new0(SearchData, 1);
2792
2793         sd->search_dir_fd = file_data_ref(dir_fd);
2794         sd->search_path_recurse = TRUE;
2795         sd->search_size = 0;
2796         sd->search_width = 640;
2797         sd->search_height = 480;
2798         sd->search_width_end = 1024;
2799         sd->search_height_end = 768;
2800
2801         sd->search_type = SEARCH_MATCH_NONE;
2802
2803         sd->match_name = SEARCH_MATCH_CONTAINS;
2804         sd->match_size = SEARCH_MATCH_EQUAL;
2805         sd->match_date = SEARCH_MATCH_EQUAL;
2806         sd->match_dimensions = SEARCH_MATCH_EQUAL;
2807         sd->match_keywords = SEARCH_MATCH_ALL;
2808         sd->match_comment = SEARCH_MATCH_CONTAINS;
2809
2810         sd->match_name_enable = TRUE;
2811
2812         sd->search_similarity = 95;
2813
2814         sd->search_gps = 1;
2815         sd->match_gps = SEARCH_MATCH_NONE;
2816
2817         if (example_file)
2818                 {
2819                 sd->search_similarity_path = g_strdup(example_file->path);
2820                 }
2821
2822         sd->window = window_new(GTK_WINDOW_TOPLEVEL, "search", NULL, NULL, _("Image search"));
2823
2824         gtk_window_set_resizable(GTK_WINDOW(sd->window), TRUE);
2825
2826         geometry.min_width = DEFAULT_MINIMAL_WINDOW_SIZE;
2827         geometry.min_height = DEFAULT_MINIMAL_WINDOW_SIZE;
2828         geometry.base_width = DEF_SEARCH_WIDTH;
2829         geometry.base_height = DEF_SEARCH_HEIGHT;
2830         gtk_window_set_geometry_hints(GTK_WINDOW(sd->window), NULL, &geometry,
2831                                       GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE);
2832
2833         gtk_window_set_default_size(GTK_WINDOW(sd->window), DEF_SEARCH_WIDTH, DEF_SEARCH_HEIGHT);
2834
2835         g_signal_connect(G_OBJECT(sd->window), "delete_event",
2836                          G_CALLBACK(search_window_delete_cb), sd);
2837         g_signal_connect(G_OBJECT(sd->window), "destroy",
2838                          G_CALLBACK(search_window_destroy_cb), sd);
2839
2840         g_signal_connect(G_OBJECT(sd->window), "key_press_event",
2841                          G_CALLBACK(search_window_keypress_cb), sd);
2842
2843         vbox = gtk_vbox_new(FALSE, PREF_PAD_GAP);
2844         gtk_container_set_border_width(GTK_CONTAINER(vbox), PREF_PAD_GAP);
2845         gtk_container_add(GTK_CONTAINER(sd->window), vbox);
2846         gtk_widget_show(vbox);
2847
2848         sd->box_search = pref_box_new(vbox, FALSE, GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
2849
2850         hbox = pref_box_new(sd->box_search, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
2851
2852         pref_label_new(hbox, _("Search:"));
2853
2854         sd->menu_path = menu_choice_menu(text_search_menu_path, sizeof(text_search_menu_path) / sizeof(MatchList),
2855                                          G_CALLBACK(menu_choice_path_cb), sd);
2856         gtk_box_pack_start(GTK_BOX(hbox), sd->menu_path, FALSE, FALSE, 0);
2857         gtk_widget_show(sd->menu_path);
2858
2859         hbox2 = pref_box_new(hbox, TRUE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
2860         combo = tab_completion_new_with_history(&sd->path_entry, sd->search_dir_fd->path,
2861                                                 "search_path", -1,
2862                                                 NULL, NULL);
2863         tab_completion_add_select_button(sd->path_entry, NULL, TRUE);
2864         gtk_box_pack_start(GTK_BOX(hbox2), combo, TRUE, TRUE, 0);
2865         gtk_widget_show(combo);
2866         sd->check_recurse = pref_checkbox_new_int(hbox2, _("Recurse"),
2867                                                   sd->search_path_recurse, &sd->search_path_recurse);
2868
2869         /* Search for file name */
2870         hbox = menu_choice(sd->box_search, &sd->check_name, &sd->menu_name,
2871                            _("File name"), &sd->match_name_enable,
2872                            text_search_menu_name, sizeof(text_search_menu_name) / sizeof(MatchList),
2873                            G_CALLBACK(menu_choice_name_cb), sd);
2874         combo = history_combo_new(&sd->entry_name, "", "search_name", -1);
2875         gtk_box_pack_start(GTK_BOX(hbox), combo, TRUE, TRUE, 0);
2876         gtk_widget_show(combo);
2877         pref_checkbox_new_int(hbox, _("Match case"),
2878                               sd->search_name_match_case, &sd->search_name_match_case);
2879
2880         /* Search for file size */
2881         hbox = menu_choice(sd->box_search, &sd->check_size, &sd->menu_size,
2882                            _("File size is"), &sd->match_size_enable,
2883                            text_search_menu_size, sizeof(text_search_menu_size) / sizeof(MatchList),
2884                            G_CALLBACK(menu_choice_size_cb), sd);
2885         sd->spin_size = menu_spin(hbox, 0, 1024*1024*1024, sd->search_size,
2886                                   G_CALLBACK(menu_choice_spin_cb), &sd->search_size);
2887         hbox2 = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
2888         gtk_box_pack_start(GTK_BOX(hbox), hbox2, FALSE, FALSE, 0);
2889         pref_label_new(hbox2, _("and"));
2890         sd->spin_size_end = menu_spin(hbox2, 0, 1024*1024*1024, sd->search_size_end,
2891                                       G_CALLBACK(menu_choice_spin_cb), &sd->search_size_end);
2892
2893         /* Search for file date */
2894         hbox = menu_choice(sd->box_search, &sd->check_date, &sd->menu_date,
2895                            _("File date is"), &sd->match_date_enable,
2896                            text_search_menu_date, sizeof(text_search_menu_date) / sizeof(MatchList),
2897                            G_CALLBACK(menu_choice_date_cb), sd);
2898         sd->date_sel = date_selection_new();
2899         date_selection_time_set(sd->date_sel, time(NULL));
2900         gtk_box_pack_start(GTK_BOX(hbox), sd->date_sel, FALSE, FALSE, 0);
2901         gtk_widget_show(sd->date_sel);
2902
2903         hbox2 = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
2904         gtk_box_pack_start(GTK_BOX(hbox), hbox2, FALSE, FALSE, 0);
2905         pref_label_new(hbox2, _("and"));
2906         sd->date_sel_end = date_selection_new();
2907         date_selection_time_set(sd->date_sel_end, time(NULL));
2908         gtk_box_pack_start(GTK_BOX(hbox2), sd->date_sel_end, FALSE, FALSE, 0);
2909         gtk_widget_show(sd->date_sel_end);
2910         pref_checkbox_new_int(hbox, _("Exif date"),
2911                                 sd->search_date_exif, &sd->search_date_exif);
2912
2913         /* Search for image dimensions */
2914         hbox = menu_choice(sd->box_search, &sd->check_dimensions, &sd->menu_dimensions,
2915                            _("Image dimensions are"), &sd->match_dimensions_enable,
2916                            text_search_menu_size, sizeof(text_search_menu_size) / sizeof(MatchList),
2917                            G_CALLBACK(menu_choice_dimensions_cb), sd);
2918         pad_box = pref_box_new(hbox, FALSE, GTK_ORIENTATION_HORIZONTAL, 2);
2919         sd->spin_width = menu_spin(pad_box, 0, 1000000, sd->search_width,
2920                                    G_CALLBACK(menu_choice_spin_cb), &sd->search_width);
2921         pref_label_new(pad_box, "x");
2922         sd->spin_height = menu_spin(pad_box, 0, 1000000, sd->search_height,
2923                                     G_CALLBACK(menu_choice_spin_cb), &sd->search_height);
2924         hbox2 = gtk_hbox_new(FALSE, 2);
2925         gtk_box_pack_start(GTK_BOX(hbox), hbox2, FALSE, FALSE, 0);
2926         pref_label_new(hbox2, _("and"));
2927         pref_spacer(hbox2, PREF_PAD_SPACE - 2*2);
2928         sd->spin_width_end = menu_spin(hbox2, 0, 1000000, sd->search_width_end,
2929                                        G_CALLBACK(menu_choice_spin_cb), &sd->search_width_end);
2930         pref_label_new(hbox2, "x");
2931         sd->spin_height_end = menu_spin(hbox2, 0, 1000000, sd->search_height_end,
2932                                         G_CALLBACK(menu_choice_spin_cb), &sd->search_height_end);
2933
2934         /* Search for image similarity */
2935         hbox = menu_choice(sd->box_search, &sd->check_similarity, NULL,
2936                            _("Image content is"), &sd->match_similarity_enable,
2937                            NULL, 0, NULL, sd);
2938         sd->spin_similarity = menu_spin(hbox, 80, 100, sd->search_similarity,
2939                                         G_CALLBACK(menu_choice_spin_cb), &sd->search_similarity);
2940
2941         /* xgettext:no-c-format */
2942         pref_label_new(hbox, _("% similar to"));
2943
2944         combo = tab_completion_new_with_history(&sd->entry_similarity,
2945                                                 (sd->search_similarity_path) ? sd->search_similarity_path : "",
2946                                                 "search_similarity_path", -1, NULL, NULL);
2947         tab_completion_add_select_button(sd->entry_similarity, NULL, FALSE);
2948         gtk_box_pack_start(GTK_BOX(hbox), combo, TRUE, TRUE, 0);
2949         gtk_widget_show(combo);
2950
2951         /* Search for image keywords */
2952         hbox = menu_choice(sd->box_search, &sd->check_keywords, &sd->menu_keywords,
2953                            _("Keywords"), &sd->match_keywords_enable,
2954                            text_search_menu_keyword, sizeof(text_search_menu_keyword) / sizeof(MatchList),
2955                            G_CALLBACK(menu_choice_keyword_cb), sd);
2956         sd->entry_keywords = gtk_entry_new();
2957         gtk_box_pack_start(GTK_BOX(hbox), sd->entry_keywords, TRUE, TRUE, 0);
2958         gtk_widget_set_sensitive(sd->entry_keywords, sd->match_keywords_enable);
2959         g_signal_connect(G_OBJECT(sd->check_keywords), "toggled",
2960                          G_CALLBACK(menu_choice_check_cb), sd->entry_keywords);
2961         gtk_widget_show(sd->entry_keywords);
2962
2963         /* Search for image comment */
2964         hbox = menu_choice(sd->box_search, &sd->check_comment, &sd->menu_comment,
2965                         _("Comment"), &sd->match_comment_enable,
2966                         text_search_menu_comment, sizeof(text_search_menu_comment) / sizeof(MatchList),
2967                         G_CALLBACK(menu_choice_comment_cb), sd);
2968         sd->entry_comment = gtk_entry_new();
2969         gtk_box_pack_start(GTK_BOX(hbox), sd->entry_comment, TRUE, TRUE, 0);
2970         gtk_widget_set_sensitive(sd->entry_comment, sd->match_comment_enable);
2971         g_signal_connect(G_OBJECT(sd->check_comment), "toggled",
2972                         G_CALLBACK(menu_choice_check_cb), sd->entry_comment);
2973         gtk_widget_show(sd->entry_comment);
2974         pref_checkbox_new_int(hbox, _("Match case"),
2975                               sd->search_comment_match_case, &sd->search_comment_match_case);
2976
2977         /* Search for images within a specified range of a lat/long coordinate
2978         */
2979         hbox = menu_choice(sd->box_search, &sd->check_gps, &sd->menu_gps,
2980                            _("Image is"), &sd->match_gps_enable,
2981                            text_search_menu_gps, sizeof(text_search_menu_gps) / sizeof(MatchList),
2982                            G_CALLBACK(menu_choice_gps_cb), sd);
2983
2984         hbox2 = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
2985         gtk_box_pack_start(GTK_BOX(hbox), hbox2, FALSE, FALSE, 0);
2986         sd->spin_gps = menu_spin(hbox2, 1, 9999, sd->search_gps,
2987                                                                    G_CALLBACK(menu_choice_spin_cb), &sd->search_gps);
2988
2989         sd->units_gps = gtk_combo_box_text_new();
2990         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(sd->units_gps), _("km"));
2991         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(sd->units_gps), _("miles"));
2992         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(sd->units_gps), _("n.m."));
2993         gtk_box_pack_start(GTK_BOX(hbox2), sd->units_gps, FALSE, FALSE, 0);
2994         gtk_combo_box_set_active(GTK_COMBO_BOX(sd->units_gps), 0);
2995         gtk_widget_set_tooltip_text(sd->units_gps, "kilometres, miles or nautical miles");
2996         gtk_widget_show(sd->units_gps);
2997
2998         pref_label_new(hbox2, _("from"));
2999
3000         sd->entry_gps_coord = gtk_entry_new();
3001         gtk_editable_set_editable(GTK_EDITABLE(sd->entry_gps_coord), TRUE);
3002         gtk_widget_set_has_tooltip(sd->entry_gps_coord, TRUE);
3003         gtk_widget_set_tooltip_text(sd->entry_gps_coord, _("Enter a coordinate in the form:\n89.123 179.456\nor drag-and-drop a geo-coded image\nor left-click on the map and paste\nor cut-and-paste or drag-and-drop\nan internet search URL\nSee the Help file"));
3004         gtk_box_pack_start(GTK_BOX(hbox2), sd->entry_gps_coord, TRUE, TRUE, 0);
3005         gtk_widget_set_sensitive(sd->entry_gps_coord, TRUE);
3006
3007         gtk_widget_show(sd->entry_gps_coord);
3008
3009         /* Done the types of searches */
3010
3011         scrolled = gtk_scrolled_window_new(NULL, NULL);
3012         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
3013         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
3014                                        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
3015         gtk_box_pack_start(GTK_BOX(vbox), scrolled, TRUE, TRUE, 0);
3016         gtk_widget_show(scrolled);
3017
3018         store = gtk_list_store_new(8, G_TYPE_POINTER, G_TYPE_INT, GDK_TYPE_PIXBUF,
3019                                    G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
3020                                    G_TYPE_STRING, G_TYPE_STRING);
3021
3022         /* set up sorting */
3023         sortable = GTK_TREE_SORTABLE(store);
3024         gtk_tree_sortable_set_sort_func(sortable, SEARCH_COLUMN_RANK, search_result_sort_cb,
3025                                   GINT_TO_POINTER(SEARCH_COLUMN_RANK), NULL);
3026         gtk_tree_sortable_set_sort_func(sortable, SEARCH_COLUMN_NAME, search_result_sort_cb,
3027                                   GINT_TO_POINTER(SEARCH_COLUMN_NAME), NULL);
3028         gtk_tree_sortable_set_sort_func(sortable, SEARCH_COLUMN_SIZE, search_result_sort_cb,
3029                                   GINT_TO_POINTER(SEARCH_COLUMN_SIZE), NULL);
3030         gtk_tree_sortable_set_sort_func(sortable, SEARCH_COLUMN_DATE, search_result_sort_cb,
3031                                   GINT_TO_POINTER(SEARCH_COLUMN_DATE), NULL);
3032         gtk_tree_sortable_set_sort_func(sortable, SEARCH_COLUMN_DIMENSIONS, search_result_sort_cb,
3033                                   GINT_TO_POINTER(SEARCH_COLUMN_DIMENSIONS), NULL);
3034         gtk_tree_sortable_set_sort_func(sortable, SEARCH_COLUMN_PATH, search_result_sort_cb,
3035                                   GINT_TO_POINTER(SEARCH_COLUMN_PATH), NULL);
3036
3037 #if 0
3038         /* by default, search results are unsorted until user selects a sort column - for speed,
3039          * using sort slows search speed by an order of magnitude with 1000's of results :-/
3040          */
3041         gtk_tree_sortable_set_sort_column_id(sortable, SEARCH_COLUMN_PATH, GTK_SORT_ASCENDING);
3042 #endif
3043
3044         sd->result_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
3045         g_object_unref(store);
3046         gtk_container_add(GTK_CONTAINER(scrolled), sd->result_view);
3047         gtk_widget_show(sd->result_view);
3048
3049         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(sd->result_view));
3050         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
3051         gtk_tree_selection_set_select_function(selection, search_result_select_cb, sd, NULL);
3052
3053         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(sd->result_view), TRUE);
3054         gtk_tree_view_set_enable_search(GTK_TREE_VIEW(sd->result_view), FALSE);
3055
3056         search_result_add_column(sd, SEARCH_COLUMN_RANK, _("Rank"), FALSE, FALSE);
3057         search_result_add_column(sd, SEARCH_COLUMN_THUMB, "", TRUE, FALSE);
3058         search_result_add_column(sd, SEARCH_COLUMN_NAME, _("Name"), FALSE, FALSE);
3059         search_result_add_column(sd, SEARCH_COLUMN_SIZE, _("Size"), FALSE, TRUE);
3060         search_result_add_column(sd, SEARCH_COLUMN_DATE, _("Date"), FALSE, TRUE);
3061         search_result_add_column(sd, SEARCH_COLUMN_DIMENSIONS, _("Dimensions"), FALSE, FALSE);
3062         search_result_add_column(sd, SEARCH_COLUMN_PATH, _("Path"), FALSE, FALSE);
3063
3064         search_dnd_init(sd);
3065
3066         g_signal_connect(G_OBJECT(sd->result_view), "button_press_event",
3067                          G_CALLBACK(search_result_press_cb), sd);
3068         g_signal_connect(G_OBJECT(sd->result_view), "button_release_event",
3069                          G_CALLBACK(search_result_release_cb), sd);
3070         g_signal_connect(G_OBJECT(sd->result_view), "key_press_event",
3071                          G_CALLBACK(search_result_keypress_cb), sd);
3072
3073         hbox = pref_box_new(vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
3074
3075         sd->button_thumbs = pref_checkbox_new(hbox, _("Thumbnails"), FALSE,
3076                                               G_CALLBACK(search_thumb_toggle_cb), sd);
3077
3078         frame = gtk_frame_new(NULL);
3079         gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
3080         gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, PREF_PAD_SPACE);
3081         gtk_widget_show(frame);
3082
3083         sd->label_status = gtk_label_new("");
3084         gtk_widget_set_size_request(sd->label_status, 50, -1);
3085         gtk_container_add(GTK_CONTAINER(frame), sd->label_status);
3086         gtk_widget_show(sd->label_status);
3087
3088         sd->label_progress = gtk_progress_bar_new();
3089         gtk_widget_set_size_request(sd->label_progress, 50, -1);
3090 #if GTK_CHECK_VERSION(3,0,0)
3091         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(sd->label_progress), "");
3092         gtk_progress_bar_set_show_text(GTK_PROGRESS_BAR(sd->label_progress), TRUE);
3093 #endif
3094         gtk_box_pack_start(GTK_BOX(hbox), sd->label_progress, TRUE, TRUE, 0);
3095         gtk_widget_show(sd->label_progress);
3096
3097         sd->spinner = spinner_new(NULL, -1);
3098         gtk_box_pack_start(GTK_BOX(hbox), sd->spinner, FALSE, FALSE, 0);
3099         gtk_widget_show(sd->spinner);
3100
3101         sd->button_start = pref_button_new(hbox, GTK_STOCK_FIND, NULL, FALSE,
3102                                            G_CALLBACK(search_start_cb), sd);
3103         pref_spacer(hbox, PREF_PAD_BUTTON_GAP);
3104         sd->button_stop = pref_button_new(hbox, GTK_STOCK_STOP, NULL, FALSE,
3105                                           G_CALLBACK(search_start_cb), sd);
3106         gtk_widget_set_sensitive(sd->button_stop, FALSE);
3107
3108         search_status_update(sd);
3109         search_progress_update(sd, FALSE, -1.0);
3110
3111         search_window_list = g_list_append(search_window_list, sd);
3112
3113         file_data_register_notify_func(search_notify_cb, sd, NOTIFY_PRIORITY_MEDIUM);
3114
3115         gtk_widget_show(sd->window);
3116 }
3117
3118 /*
3119  *-------------------------------------------------------------------
3120  * maintenance (move, delete, etc.)
3121  *-------------------------------------------------------------------
3122  */
3123
3124 static void search_result_change_path(SearchData *sd, FileData *fd)
3125 {
3126         GtkTreeModel *store;
3127         GtkTreeIter iter;
3128         gboolean valid;
3129
3130         store = gtk_tree_view_get_model(GTK_TREE_VIEW(sd->result_view));
3131         valid = gtk_tree_model_get_iter_first(store, &iter);
3132         while (valid)
3133                 {
3134                 GtkTreeIter current;
3135                 MatchFileData *mfd;
3136
3137                 current = iter;
3138                 valid = gtk_tree_model_iter_next(store, &iter);
3139
3140                 gtk_tree_model_get(store, &current, SEARCH_COLUMN_POINTER, &mfd, -1);
3141                 if (mfd->fd == fd)
3142                         {
3143                         if (fd->change && fd->change->dest)
3144                                 {
3145                                 gtk_list_store_set(GTK_LIST_STORE(store), &current,
3146                                                    SEARCH_COLUMN_NAME, mfd->fd->name,
3147                                                    SEARCH_COLUMN_PATH, mfd->fd->path, -1);
3148                                 }
3149                         else
3150                                 {
3151                                 search_result_remove_item(sd, mfd, &current);
3152                                 }
3153                         }
3154                 }
3155 }
3156
3157 static void search_notify_cb(FileData *fd, NotifyType type, gpointer data)
3158 {
3159         SearchData *sd = data;
3160
3161         if (!(type & NOTIFY_CHANGE) || !fd->change) return;
3162
3163         DEBUG_1("Notify search: %s %04x", fd->path, type);
3164
3165         switch (fd->change->type)
3166                 {
3167                 case FILEDATA_CHANGE_MOVE:
3168                 case FILEDATA_CHANGE_RENAME:
3169                 case FILEDATA_CHANGE_DELETE:
3170                         search_result_change_path(sd, fd);
3171                         break;
3172                 case FILEDATA_CHANGE_COPY:
3173                 case FILEDATA_CHANGE_UNSPECIFIED:
3174                 case FILEDATA_CHANGE_WRITE_METADATA:
3175                         break;
3176                 }
3177 }
3178 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */