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