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