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