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