Sync to GQview 1.5.9 release.
[geeqie.git] / src / search.c
1 /*
2  * GQview
3  * (C) 2005 John Ellis
4  *
5  * Author: John Ellis
6  *
7  * This software is released under the GNU General Public License (GNU GPL).
8  * Please read the included file COPYING for more information.
9  * This software comes with no warranty of any kind, use at your own risk!
10  */
11
12
13 #include "gqview.h"
14 #include "search.h"
15
16 #include "bar_info.h"
17 #include "cache.h"
18 #include "collect.h"
19 #include "collect-table.h"
20 #include "dnd.h"
21 #include "dupe.h"
22 #include "image-load.h"
23 #include "info.h"
24 #include "editors.h"
25 #include "img-view.h"
26 #include "filelist.h"
27 #include "layout_image.h"
28 #include "menu.h"
29 #include "print.h"
30 #include "thumb.h"
31 #include "utilops.h"
32 #include "ui_bookmark.h"
33 #include "ui_fileops.h"
34 #include "ui_menu.h"
35 #include "ui_misc.h"
36 #include "ui_spinner.h"
37 #include "ui_tabcomp.h"
38 #include "ui_tree_edit.h"
39
40 #include <gdk/gdkkeysyms.h> /* for keyboard values */
41
42
43 #define DEF_SEARCH_WIDTH  700
44 #define DEF_SEARCH_HEIGHT 450
45
46 #define SEARCH_BUFFER_MATCH_LOAD 20
47 #define SEARCH_BUFFER_MATCH_HIT  5
48 #define SEARCH_BUFFER_MATCH_MISS 1
49 #define SEARCH_BUFFER_FLUSH_SIZE 99
50
51
52 typedef enum {
53         SEARCH_MATCH_NONE,
54         SEARCH_MATCH_EQUAL,
55         SEARCH_MATCH_CONTAINS,
56         SEARCH_MATCH_UNDER,
57         SEARCH_MATCH_OVER,
58         SEARCH_MATCH_BETWEEN,
59         SEARCH_MATCH_ALL,
60         SEARCH_MATCH_ANY
61 } MatchType;
62
63 enum {
64         SEARCH_COLUMN_POINTER = 0,
65         SEARCH_COLUMN_RANK,
66         SEARCH_COLUMN_THUMB,
67         SEARCH_COLUMN_NAME,
68         SEARCH_COLUMN_SIZE,
69         SEARCH_COLUMN_DATE,
70         SEARCH_COLUMN_DIMENSIONS,
71         SEARCH_COLUMN_PATH,
72         SEARCH_COLUMN_COUNT     /* total columns */
73 };
74
75 typedef struct _SearchData SearchData;
76 struct _SearchData
77 {
78         GtkWidget *window;
79
80         GtkWidget *button_thumbs;
81         GtkWidget *label_status;
82         GtkWidget *label_progress;
83         GtkWidget *button_start;
84         GtkWidget *button_stop;
85         GtkWidget *spinner;
86
87         GtkWidget *box_search;
88
89         GtkWidget *menu_path;
90         GtkWidget *path_entry;
91         GtkWidget *check_recurse;
92
93         GtkWidget *result_view;
94
95         GtkWidget *check_name;
96         GtkWidget *menu_name;
97         GtkWidget *entry_name;
98         GtkWidget *check_name_match_case;
99
100         GtkWidget *check_size;
101         GtkWidget *menu_size;
102         GtkWidget *spin_size;
103         GtkWidget *spin_size_end;
104
105         GtkWidget *check_date;
106         GtkWidget *menu_date;
107         GtkWidget *date_sel;
108         GtkWidget *date_sel_end;
109
110         GtkWidget *check_dimensions;
111         GtkWidget *menu_dimensions;
112         GtkWidget *spin_width;
113         GtkWidget *spin_height;
114         GtkWidget *spin_width_end;
115         GtkWidget *spin_height_end;
116
117         GtkWidget *check_similarity;
118         GtkWidget *spin_similarity;
119         GtkWidget *entry_similarity;
120
121         GtkWidget *check_keywords;
122         GtkWidget *menu_keywords;
123         GtkWidget *entry_keywords;
124
125         gchar *search_path;
126         gint   search_path_recurse;
127         gchar *search_name;
128         gint   search_name_match_case;
129         gint64 search_size;
130         gint64 search_size_end;
131         gint   search_date_y;
132         gint   search_date_m;
133         gint   search_date_d;
134         gint   search_date_end_y;
135         gint   search_date_end_m;
136         gint   search_date_end_d;
137         gint   search_width;
138         gint   search_height;
139         gint   search_width_end;
140         gint   search_height_end;
141         gint   search_similarity;
142         gchar *search_similarity_path;
143         CacheData *search_similarity_cd;
144         GList *search_keyword_list;
145
146         MatchType search_type;
147
148         MatchType match_name;
149         MatchType match_size;
150         MatchType match_date;
151         MatchType match_dimensions;
152         MatchType match_keywords;
153
154         gboolean match_name_enable;
155         gboolean match_size_enable;
156         gboolean match_date_enable;
157         gboolean match_dimensions_enable;
158         gboolean match_similarity_enable;
159         gboolean match_keywords_enable;
160
161         GList *search_folder_list;
162         GList *search_done_list;
163         GList *search_file_list;
164         GList *search_buffer_list;
165
166         gint search_count;
167         gint search_total;
168         gint search_buffer_count;
169
170         gint search_idle_id;
171         gint update_idle_id;
172
173         ImageLoader *img_loader;
174         CacheData   *img_cd;
175
176         FileData *click_fd;
177
178         ThumbLoader *thumb_loader;
179         gint thumb_enable;
180         FileData *thumb_fd;
181 };
182
183 typedef struct _MatchFileData MatchFileData;
184 struct _MatchFileData
185 {
186         FileData fd;
187         gint width;
188         gint height;
189         gint rank;
190 };
191
192 typedef struct _MatchList MatchList;
193 struct _MatchList
194 {
195         const gchar *text;
196         const MatchType type;
197 };
198
199 static const MatchList text_search_menu_path[] = {
200         { N_("folder"),         SEARCH_MATCH_NONE },
201         { N_("comments"),       SEARCH_MATCH_ALL },
202         { N_("results"),        SEARCH_MATCH_CONTAINS }
203 };
204
205 static const MatchList text_search_menu_name[] = {
206         { N_("contains"),       SEARCH_MATCH_CONTAINS },
207         { N_("is"),             SEARCH_MATCH_EQUAL }
208 };
209
210 static const MatchList text_search_menu_size[] = {
211         { N_("equal to"),       SEARCH_MATCH_EQUAL },
212         { N_("less than"),      SEARCH_MATCH_UNDER },
213         { N_("greater than"),   SEARCH_MATCH_OVER },
214         { N_("between"),        SEARCH_MATCH_BETWEEN }
215 };
216
217 static const MatchList text_search_menu_date[] = {
218         { N_("equal to"),       SEARCH_MATCH_EQUAL },
219         { N_("before"),         SEARCH_MATCH_UNDER },
220         { N_("after"),          SEARCH_MATCH_OVER },
221         { N_("between"),        SEARCH_MATCH_BETWEEN }
222 };
223
224 static const MatchList text_search_menu_keyword[] = {
225         { N_("match all"),      SEARCH_MATCH_ALL },
226         { N_("match any"),      SEARCH_MATCH_ANY },
227         { N_("exclude"),        SEARCH_MATCH_NONE }
228 };
229
230 static GList *search_window_list = NULL;
231
232
233 static gint search_result_selection_count(SearchData *sd, gint64 *bytes);
234 static gint search_result_count(SearchData *sd, gint64 *bytes);
235
236 static void search_window_close(SearchData *sd);
237
238
239 /*
240  *-------------------------------------------------------------------
241  * utils
242  *-------------------------------------------------------------------
243  */
244
245 static time_t convert_dmy_to_time(gint day, gint month, gint year)
246 {
247         struct tm lt;
248
249         lt.tm_sec = 0;
250         lt.tm_min = 0;
251         lt.tm_hour = 0;
252         lt.tm_mday = day;
253         lt.tm_mon = month - 1;
254         lt.tm_year = year - 1900;
255         lt.tm_isdst = 0;
256
257         return mktime(&lt);
258 }
259
260 static void search_status_update(SearchData *sd)
261 {
262         gchar *buf;
263         gint t;
264         gint s;
265         gint64 t_bytes;
266         gint64 s_bytes;
267         gchar *tt;
268         gchar *ts;
269
270         t = search_result_count(sd, &t_bytes);
271         s = search_result_selection_count(sd, &s_bytes);
272
273         if (s > 0)
274                 {
275                 tt = text_from_size_abrev(t_bytes);
276                 ts = text_from_size_abrev(s_bytes);
277                 buf = g_strdup_printf(_("%s, %d files (%s, %d)"), tt, t, ts, s);
278                 g_free(tt);
279                 g_free(ts);
280                 }
281         else
282                 {
283                 tt = text_from_size_abrev(t_bytes);
284                 buf = g_strdup_printf(_("%s, %d files"), tt, t);
285                 g_free(tt);
286                 }
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                 FileData *fd_n;
337                 n++;
338
339                 gtk_tree_model_get(store, iter, SEARCH_COLUMN_POINTER, &fd_n, -1);
340                 if (fd_n == 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                 FileData *fd_n;
362                 GtkTreeIter iter;
363
364                 gtk_tree_model_get_iter(store, &iter, tpath);
365                 gtk_tree_model_get(store, &iter, SEARCH_COLUMN_POINTER, &fd_n, -1);
366                 if (fd_n == 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                         FileData *fd;
397                         GtkTreeIter iter;
398
399                         gtk_tree_model_get_iter(store, &iter, tpath);
400                         gtk_tree_model_get(store, &iter, SEARCH_COLUMN_POINTER, &fd, -1);
401                         total += fd->size;
402
403                         if (list) plist = g_list_prepend(plist, g_strdup(fd->path));
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                         FileData *fd;
448
449                         gtk_tree_model_get(store, &iter, SEARCH_COLUMN_POINTER, &fd, -1);
450                         total += fd->size;
451
452                         if (list) plist = g_list_prepend(plist, g_strdup(fd->path));
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_path_list(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 = (FileData *)mfd;
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, fd,
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                 FileData *fd;
521
522                 gtk_tree_model_get(store, &iter, SEARCH_COLUMN_POINTER, &fd, -1);
523                 list = g_list_prepend(list, 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         FileData *fd;
538
539         gtk_tree_model_get(store, iter, SEARCH_COLUMN_POINTER, &fd, -1);
540         file_data_free(fd);
541
542         return FALSE;
543 }
544
545 static void search_result_clear(SearchData *sd)
546 {
547         GtkListStore *store;
548
549         store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(sd->result_view)));
550
551         gtk_tree_model_foreach(GTK_TREE_MODEL(store), search_result_free_node, sd);
552         gtk_list_store_clear(store);
553
554         sd->click_fd = NULL;
555
556         thumb_loader_free(sd->thumb_loader);
557         sd->thumb_loader = NULL;
558         sd->thumb_fd = NULL;
559
560         search_status_update(sd);
561 }
562
563 static void search_result_remove_item(SearchData *sd, FileData *fd, GtkTreeIter *iter)
564 {
565         GtkTreeModel *store;
566
567         if (!fd || !iter) return;
568
569         store = gtk_tree_view_get_model(GTK_TREE_VIEW(sd->result_view));
570
571         tree_view_move_cursor_away(GTK_TREE_VIEW(sd->result_view), iter, TRUE);
572
573         gtk_list_store_remove(GTK_LIST_STORE(store), iter);
574         if (sd->click_fd == fd) sd->click_fd = NULL;
575         if (sd->thumb_fd == fd) sd->thumb_fd = NULL;
576         file_data_free(fd);
577 }
578
579 static void search_result_remove(SearchData *sd, FileData *fd)
580 {
581         GtkTreeModel *store;
582         GtkTreeIter iter;
583         gint valid;
584
585         store = gtk_tree_view_get_model(GTK_TREE_VIEW(sd->result_view));
586         valid = gtk_tree_model_get_iter_first(store, &iter);
587         while (valid)
588                 {
589                 FileData *fd_n;
590
591                 gtk_tree_model_get(store, &iter, SEARCH_COLUMN_POINTER, &fd_n, -1);
592                 if (fd_n == fd)
593                         {
594                         search_result_remove_item(sd, fd_n, &iter);
595                         return;
596                         }
597
598                 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
599                 }
600 }
601
602 static void search_result_remove_selection(SearchData *sd)
603 {
604         GtkTreeSelection *selection;
605         GtkTreeModel *store;
606         GList *slist;
607         GList *flist = NULL;
608         GList *work;
609
610         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(sd->result_view));
611         slist = gtk_tree_selection_get_selected_rows(selection, &store);
612         work = slist;
613         while (work)
614                 {
615                 GtkTreePath *tpath = work->data;
616                 GtkTreeIter iter;
617                 FileData *fd;
618
619                 gtk_tree_model_get_iter(store, &iter, tpath);
620                 gtk_tree_model_get(store, &iter, SEARCH_COLUMN_POINTER, &fd, -1);
621                 flist = g_list_prepend(flist, fd);
622                 work = work->next;
623                 }
624         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
625         g_list_free(slist);
626
627         work = flist;
628         while (work)
629                 {
630                 FileData *fd = work->data;
631                 work = work->next;
632
633                 search_result_remove(sd, fd);
634                 }
635         g_list_free(flist);
636
637         search_status_update(sd);
638 }
639
640 static void search_result_edit_selected(SearchData *sd, gint n)
641 {
642         GList *list;
643
644         list = search_result_selection_list(sd);
645         start_editor_from_path_list(n, list);
646         path_list_free(list);
647 }
648
649 static void search_result_collection_from_selection(SearchData *sd)
650 {
651         CollectWindow *w;
652         GList *list;
653
654         list = search_result_selection_list(sd);
655         w = collection_window_new(NULL);
656         collection_table_add_path_list(w->table, list);
657         path_list_free(list);
658 }
659
660 static gint search_result_update_idle_cb(gpointer data)
661 {
662         SearchData *sd = data;
663
664         search_status_update(sd);
665
666         sd->update_idle_id = -1;
667         return FALSE;
668 }
669
670 static void search_result_update_idle_cancel(SearchData *sd)
671 {
672         if (sd->update_idle_id != -1) g_source_remove(sd->update_idle_id);
673         sd->update_idle_id = -1;
674 }
675
676 static gboolean search_result_select_cb(GtkTreeSelection *selection, GtkTreeModel *store,
677                                         GtkTreePath *tpath, gboolean selected, gpointer data)
678 {
679         SearchData *sd = data;
680
681         if (sd->update_idle_id == -1)
682                 {
683                 sd->update_idle_id = g_idle_add(search_result_update_idle_cb, sd);
684                 }
685
686         return TRUE;
687 }
688
689 /*
690  *-------------------------------------------------------------------
691  * result list thumbs
692  *-------------------------------------------------------------------
693  */
694
695 static void search_result_thumb_step(SearchData *sd);
696
697
698 static void search_result_thumb_set(SearchData *sd, FileData *fd, GtkTreeIter *iter)
699 {
700         GtkListStore *store;
701         GtkTreeIter iter_n;
702
703         store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(sd->result_view)));
704         if (!iter)
705                 {
706                 if (search_result_find_row(sd, fd, &iter_n) >= 0) iter = &iter_n;
707                 }
708
709         if (iter) gtk_list_store_set(store, iter, SEARCH_COLUMN_THUMB, fd->pixbuf, -1);
710 }
711
712 static void search_result_thumb_do(SearchData *sd)
713 {
714         FileData *fd;
715
716         if (!sd->thumb_loader || !sd->thumb_fd) return;
717         fd = sd->thumb_fd;
718
719         if (fd->pixbuf) g_object_unref(fd->pixbuf);
720         fd->pixbuf = thumb_loader_get_pixbuf(sd->thumb_loader, TRUE);
721
722         search_result_thumb_set(sd, fd, NULL);
723 }
724
725 static void search_result_thumb_done_cb(ThumbLoader *tl, gpointer data)
726 {
727         SearchData *sd = data;
728
729         search_result_thumb_do(sd);
730         search_result_thumb_step(sd);
731 }
732
733 static void search_result_thumb_step(SearchData *sd)
734 {
735         GtkTreeModel *store;
736         GtkTreeIter iter;
737         FileData *fd = NULL;
738         gint valid;
739         gint row = 0;
740         gint length = 0;
741
742         if (!sd->thumb_enable) return;
743
744         store = gtk_tree_view_get_model(GTK_TREE_VIEW(sd->result_view));
745
746         valid = gtk_tree_model_get_iter_first(store, &iter);
747         while (!fd && valid)
748                 {
749                 GdkPixbuf *pixbuf;
750
751                 length++;
752                 gtk_tree_model_get(store, &iter, SEARCH_COLUMN_POINTER, &fd, SEARCH_COLUMN_THUMB, &pixbuf, -1);
753                 if (pixbuf || fd->pixbuf)
754                         {
755                         if (!pixbuf) gtk_list_store_set(GTK_LIST_STORE(store), &iter, SEARCH_COLUMN_THUMB, fd->pixbuf, -1);
756                         row++;
757                         fd = NULL;
758                         }
759                 valid = gtk_tree_model_iter_next(store, &iter);
760                 }
761         if (valid)
762                 {
763                 while (gtk_tree_model_iter_next(store, &iter)) length++;
764                 }
765
766         if (!fd)
767                 {
768                 sd->thumb_fd = NULL;
769                 thumb_loader_free(sd->thumb_loader);
770                 sd->thumb_loader = NULL;
771
772                 search_progress_update(sd, TRUE, -1.0);
773                 return;
774                 }
775
776         search_progress_update(sd, FALSE, (gdouble)row/length);
777
778         sd->thumb_fd = fd;
779         thumb_loader_free(sd->thumb_loader);
780         sd->thumb_loader = thumb_loader_new(thumb_max_width, thumb_max_height);
781
782         thumb_loader_set_callbacks(sd->thumb_loader,
783                                    search_result_thumb_done_cb,
784                                    search_result_thumb_done_cb,
785                                    NULL,
786                                    sd);
787         if (!thumb_loader_start(sd->thumb_loader, fd->path))
788                 {
789                 search_result_thumb_do(sd);
790                 search_result_thumb_step(sd);
791                 }
792 }
793
794 static void search_result_thumb_height(SearchData *sd)
795 {
796         GtkTreeViewColumn *column;
797         GtkCellRenderer *cell;
798         GList *list;
799
800         column = gtk_tree_view_get_column(GTK_TREE_VIEW(sd->result_view), SEARCH_COLUMN_THUMB - 1);
801         if (!column) return;
802
803         gtk_tree_view_column_set_fixed_width(column, (sd->thumb_enable) ? thumb_max_width : 4);
804
805         list = gtk_tree_view_column_get_cell_renderers(column);
806         if (!list) return;
807         cell = list->data;
808         g_list_free(list);
809
810         g_object_set(G_OBJECT(cell), "height", (sd->thumb_enable) ? thumb_max_height : -1, NULL);
811         gtk_tree_view_columns_autosize(GTK_TREE_VIEW(sd->result_view));
812 }
813
814 static void search_result_thumb_enable(SearchData *sd, gint enable)
815 {
816         if (sd->thumb_enable == enable) return;
817
818         if (sd->thumb_enable)
819                 {
820                 GtkTreeModel *store;
821                 GtkTreeIter iter;
822                 gint valid;
823
824                 thumb_loader_free(sd->thumb_loader);
825                 sd->thumb_loader = NULL;
826
827                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(sd->result_view));
828                 valid = gtk_tree_model_get_iter_first(store, &iter);
829                 while (valid)
830                         {
831                         gtk_list_store_set(GTK_LIST_STORE(store), &iter, SEARCH_COLUMN_THUMB, NULL, -1);
832                         valid = gtk_tree_model_iter_next(store, &iter);
833                         }
834                 search_progress_update(sd, TRUE, -1.0);
835                 }
836
837         sd->thumb_enable = enable;
838
839         search_result_thumb_height(sd);
840         if (!sd->search_folder_list && !sd->search_file_list) search_result_thumb_step(sd);
841 }
842
843 /*
844  *-------------------------------------------------------------------
845  * result list menu
846  *-------------------------------------------------------------------
847  */
848
849 static void sr_menu_view_cb(GtkWidget *widget, gpointer data)
850 {
851         SearchData *sd = data;
852
853         if (sd->click_fd) layout_image_set_path(NULL, sd->click_fd->path);
854 }
855
856 static void sr_menu_viewnew_cb(GtkWidget *widget, gpointer data)
857 {
858         SearchData *sd = data;
859         GList *list;
860
861         list = search_result_selection_list(sd);
862         view_window_new_from_list(list);
863         path_list_free(list);
864 }
865
866 static void sr_menu_select_all_cb(GtkWidget *widget, gpointer data)
867 {
868         SearchData *sd = data;
869         GtkTreeSelection *selection;
870
871         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(sd->result_view));
872         gtk_tree_selection_select_all(selection);
873 }
874
875 static void sr_menu_select_none_cb(GtkWidget *widget, gpointer data)
876 {
877         SearchData *sd = data;
878         GtkTreeSelection *selection;
879
880         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(sd->result_view));
881         gtk_tree_selection_unselect_all(selection);
882 }
883
884 static void sr_menu_edit_cb(GtkWidget *widget, gpointer data)
885 {
886         SearchData *sd;
887         gint n;
888
889         sd = submenu_item_get_data(widget);
890         n = GPOINTER_TO_INT(data);
891         if (!sd) return;
892
893         search_result_edit_selected(sd, n);
894 }
895
896 static void sr_menu_info_cb(GtkWidget *widget, gpointer data)
897 {
898         SearchData *sd = data;
899
900         info_window_new(NULL, search_result_selection_list(sd));
901 }
902
903 static void sr_menu_collection_cb(GtkWidget *widget, gpointer data)
904 {
905         SearchData *sd = data;
906
907         search_result_collection_from_selection(sd);
908 }
909
910 static void sr_menu_print_cb(GtkWidget *widget, gpointer data)
911 {
912         SearchData *sd = data;
913         const gchar *path;
914
915         path = (sd->click_fd) ? sd->click_fd->path : NULL;
916
917         print_window_new(path, search_result_selection_list(sd),
918                          search_result_get_path_list(sd), sd->window);
919 }
920
921 static void sr_menu_copy_cb(GtkWidget *widget, gpointer data)
922 {
923         SearchData *sd = data;
924
925         file_util_copy(NULL, search_result_selection_list(sd), NULL, sd->window);
926 }
927
928 static void sr_menu_move_cb(GtkWidget *widget, gpointer data)
929 {
930         SearchData *sd = data;
931
932         file_util_move(NULL, search_result_selection_list(sd), NULL, sd->window);
933 }
934
935 static void sr_menu_rename_cb(GtkWidget *widget, gpointer data)
936 {
937         SearchData *sd = data;
938
939         file_util_rename(NULL, search_result_selection_list(sd), sd->window);
940 }
941
942 static void sr_menu_delete_cb(GtkWidget *widget, gpointer data)
943 {
944         SearchData *sd = data;
945
946         file_util_delete(NULL, search_result_selection_list(sd), sd->window);
947 }
948
949 static void sr_menu_remove_cb(GtkWidget *widget, gpointer data)
950 {
951         SearchData *sd = data;
952
953         search_result_remove_selection(sd);
954 }
955
956 static void sr_menu_clear_cb(GtkWidget *widget, gpointer data)
957 {
958         SearchData *sd = data;
959
960         search_result_clear(sd);
961 }
962
963 static GtkWidget *search_result_menu(SearchData *sd, gint on_row, gint empty)
964 {
965         GtkWidget *menu;
966         GtkWidget *item;
967
968         menu = popup_menu_short_lived();
969         menu_item_add_sensitive(menu, _("_View"), on_row,
970                                 G_CALLBACK(sr_menu_view_cb), sd);
971         menu_item_add_stock_sensitive(menu, _("View in _new window"), GTK_STOCK_NEW, on_row,
972                                       G_CALLBACK(sr_menu_viewnew_cb), sd);
973         menu_item_add_divider(menu);
974         menu_item_add_sensitive(menu, _("Select all"), !empty,
975                                 G_CALLBACK(sr_menu_select_all_cb), sd);
976         menu_item_add_sensitive(menu, _("Select none"), !empty,
977                                 G_CALLBACK(sr_menu_select_none_cb), sd);
978         menu_item_add_divider(menu);
979         submenu_add_edit(menu, &item, G_CALLBACK(sr_menu_edit_cb), sd);
980         if (!on_row) gtk_widget_set_sensitive(item, FALSE);
981         menu_item_add_stock_sensitive(menu, _("_Properties"), GTK_STOCK_PROPERTIES, on_row,
982                                       G_CALLBACK(sr_menu_info_cb), sd);
983         menu_item_add_stock_sensitive(menu, _("Add to new collection"), GTK_STOCK_INDEX, on_row,
984                                       G_CALLBACK(sr_menu_collection_cb), sd);
985         menu_item_add_stock_sensitive(menu, _("Print..."), GTK_STOCK_PRINT, on_row,
986                                       G_CALLBACK(sr_menu_print_cb), sd);
987         menu_item_add_divider(menu);
988         menu_item_add_stock_sensitive(menu, _("_Copy..."), GTK_STOCK_COPY, on_row,
989                                       G_CALLBACK(sr_menu_copy_cb), sd);
990         menu_item_add_sensitive(menu, _("_Move..."), on_row,
991                                 G_CALLBACK(sr_menu_move_cb), sd);
992         menu_item_add_sensitive(menu, _("_Rename..."), on_row,
993                                 G_CALLBACK(sr_menu_rename_cb), sd);
994         menu_item_add_stock_sensitive(menu, _("_Delete..."), GTK_STOCK_DELETE, on_row,
995                                       G_CALLBACK(sr_menu_delete_cb), sd);
996         menu_item_add_divider(menu);
997         menu_item_add_stock_sensitive(menu, _("Rem_ove"), GTK_STOCK_REMOVE, on_row,
998                                       G_CALLBACK(sr_menu_remove_cb), sd);
999         menu_item_add_stock_sensitive(menu, _("C_lear"), GTK_STOCK_CLEAR, !empty,
1000                                       G_CALLBACK(sr_menu_clear_cb), sd);
1001
1002         return menu;
1003 }
1004
1005 static void search_result_menu_pos_cb(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data)
1006 {
1007         SearchData *sd = data;
1008         GtkTreePath *tpath;
1009         gint cx, cy, cw, ch;
1010
1011         gtk_tree_view_get_cursor(GTK_TREE_VIEW(sd->result_view), &tpath, NULL);
1012         if (!tpath) return;
1013
1014         tree_view_get_cell_clamped(GTK_TREE_VIEW(sd->result_view), tpath,
1015                                    SEARCH_COLUMN_NAME - 1, TRUE, &cx, &cy, &cw, &ch);
1016         gtk_tree_path_free(tpath);
1017         cy += ch;
1018         popup_menu_position_clamp(menu, &cx, &cy, 0);
1019         *x = cx;
1020         *y = cy;
1021 }
1022
1023 /*
1024  *-------------------------------------------------------------------
1025  * result list input
1026  *-------------------------------------------------------------------
1027  */
1028
1029 static gint search_result_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
1030 {
1031         SearchData *sd = data;
1032         GtkTreeModel *store;
1033         GtkTreePath *tpath;
1034         GtkTreeIter iter;
1035         FileData *fd = NULL;
1036
1037         store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
1038
1039         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
1040                                           &tpath, NULL, NULL, NULL))
1041                 {
1042                 gtk_tree_model_get_iter(store, &iter, tpath);
1043                 gtk_tree_model_get(store, &iter, SEARCH_COLUMN_POINTER, &fd, -1);
1044                 gtk_tree_path_free(tpath);
1045                 }
1046
1047         sd->click_fd = fd;
1048
1049         if (bevent->button == 3)
1050                 {
1051                 GtkWidget *menu;
1052
1053                 menu = search_result_menu(sd, (fd != NULL), (search_result_count(sd, NULL) == 0));
1054                 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, bevent->button, bevent->time);
1055                 }
1056
1057         if (!fd) return FALSE;
1058
1059         if (bevent->button == 1 && bevent->type == GDK_2BUTTON_PRESS)
1060                 {
1061                 layout_image_set_path(NULL, fd->path);
1062                 }
1063
1064         if (bevent->button == 2) return TRUE;
1065
1066         if (bevent->button == 3)
1067                 {
1068                 if (!search_result_row_selected(sd, fd))
1069                         {
1070                         GtkTreeSelection *selection;
1071
1072                         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
1073                         gtk_tree_selection_unselect_all(selection);
1074                         gtk_tree_selection_select_iter(selection, &iter);
1075
1076                         tpath = gtk_tree_model_get_path(store, &iter);
1077                         gtk_tree_view_set_cursor(GTK_TREE_VIEW(widget), tpath, NULL, FALSE);
1078                         gtk_tree_path_free(tpath);
1079                         }
1080                 return TRUE;
1081                 }
1082
1083         if (bevent->button == 1 && bevent->type == GDK_BUTTON_PRESS &&
1084             !(bevent->state & GDK_SHIFT_MASK ) &&
1085             !(bevent->state & GDK_CONTROL_MASK ) &&
1086             search_result_row_selected(sd, fd))
1087                 {
1088                 /* this selection handled on release_cb */
1089                 gtk_widget_grab_focus(widget);
1090                 return TRUE;
1091                 }
1092
1093         return FALSE;
1094 }
1095
1096 static gint search_result_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
1097 {
1098         SearchData *sd = data;
1099         GtkTreeModel *store;
1100         GtkTreePath *tpath;
1101         GtkTreeIter iter;
1102
1103         FileData *fd = NULL;
1104
1105         if (bevent->button != 1 && bevent->button != 2) return TRUE;
1106
1107         store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
1108
1109         if ((bevent->x != 0 || bevent->y != 0) &&
1110             gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
1111                                           &tpath, NULL, NULL, NULL))
1112                 {
1113                 gtk_tree_model_get_iter(store, &iter, tpath);
1114                 gtk_tree_model_get(store, &iter, SEARCH_COLUMN_POINTER, &fd, -1);
1115                 gtk_tree_path_free(tpath);
1116                 }
1117
1118         if (bevent->button == 2)
1119                 {
1120                 if (fd && sd->click_fd == fd)
1121                         {
1122                         GtkTreeSelection *selection;
1123
1124                         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
1125                         if (search_result_row_selected(sd, fd))
1126                                 {
1127                                 gtk_tree_selection_unselect_iter(selection, &iter);
1128                                 }
1129                         else
1130                                 {
1131                                 gtk_tree_selection_select_iter(selection, &iter);
1132                                 }
1133                         }
1134                 return TRUE;
1135                 }
1136
1137         if (fd && sd->click_fd == fd &&
1138             !(bevent->state & GDK_SHIFT_MASK ) &&
1139             !(bevent->state & GDK_CONTROL_MASK ) &&
1140             search_result_row_selected(sd, fd))
1141                 {
1142                 GtkTreeSelection *selection;
1143
1144                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
1145                 gtk_tree_selection_unselect_all(selection);
1146                 gtk_tree_selection_select_iter(selection, &iter);
1147
1148                 tpath = gtk_tree_model_get_path(store, &iter);
1149                 gtk_tree_view_set_cursor(GTK_TREE_VIEW(widget), tpath, NULL, FALSE);
1150                 gtk_tree_path_free(tpath);
1151
1152                 return TRUE;
1153                 }
1154
1155         return FALSE;
1156 }
1157
1158 static gint search_result_keypress_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
1159 {
1160         SearchData *sd = data;
1161         gint stop_signal = FALSE;
1162         GtkTreeModel *store;
1163         GtkTreeSelection *selection;
1164         GList *slist;
1165         FileData *fd = NULL;
1166
1167         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(sd->result_view));
1168         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1169         if (slist)
1170                 {
1171                 GtkTreePath *tpath;
1172                 GtkTreeIter iter;
1173                 GList *last;
1174
1175                 last = g_list_last(slist);
1176                 tpath = last->data;
1177
1178                 /* last is newest selected file */
1179                 gtk_tree_model_get_iter(store, &iter, tpath);
1180                 gtk_tree_model_get(store, &iter, SEARCH_COLUMN_POINTER, &fd, -1);
1181                 }
1182         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
1183         g_list_free(slist);
1184
1185         if (event->state & GDK_CONTROL_MASK)
1186                 {
1187                 gint edit_val = -1;
1188
1189                 switch (event->keyval)
1190                         {
1191                         case '1':
1192                                 edit_val = 0;
1193                                 break;
1194                         case '2':
1195                                 edit_val = 1;
1196                                 break;
1197                         case '3':
1198                                 edit_val = 2;
1199                                 break;
1200                         case '4':
1201                                 edit_val = 3;
1202                                 break;
1203                         case '5':
1204                                 edit_val = 4;
1205                                 break;
1206                         case '6':
1207                                 edit_val = 5;
1208                                 break;
1209                         case '7':
1210                                 edit_val = 6;
1211                                 break;
1212                         case '8':
1213                                 edit_val = 7;
1214                                 break;
1215                         case '9':
1216                                 edit_val = 8;
1217                                 break;
1218                         case '0':
1219                                 edit_val = 9;
1220                                 break;
1221                         case 'C': case 'c':
1222                                 stop_signal = TRUE;
1223                                 file_util_copy(NULL, search_result_selection_list(sd), NULL, widget);
1224                                 break;
1225                         case 'M': case 'm':
1226                                 stop_signal = TRUE;
1227                                 file_util_move(NULL, search_result_selection_list(sd), NULL, widget);
1228                                 break;
1229                         case 'R': case 'r':
1230                                 stop_signal = TRUE;
1231                                 file_util_rename(NULL, search_result_selection_list(sd), widget);
1232                                 break;
1233                         case 'D': case 'd':
1234                                 stop_signal = TRUE;
1235                                 file_util_delete(NULL, search_result_selection_list(sd), widget);
1236                                 break;
1237                         case 'P': case 'p':
1238                                 stop_signal = TRUE;
1239                                 info_window_new(NULL,  search_result_selection_list(sd));
1240                                 break;
1241                         case 'A': case 'a':
1242                                 if (event->state & GDK_SHIFT_MASK)
1243                                         {
1244                                         gtk_tree_selection_unselect_all(selection);
1245                                         }
1246                                 else
1247                                         {
1248                                         gtk_tree_selection_select_all(selection);
1249                                         }
1250                                 stop_signal = TRUE;
1251                                 break;
1252                         case GDK_Delete: case GDK_KP_Delete:
1253                                 search_result_clear(sd);
1254                                 stop_signal = TRUE;
1255                                 break;
1256                         default:
1257                                 break;
1258                         }
1259
1260                 if (edit_val >= 0)
1261                         {
1262                         search_result_edit_selected(sd, edit_val);
1263                         stop_signal = TRUE;
1264                         }
1265                 }
1266         else
1267                 {
1268                 switch (event->keyval)
1269                         {
1270                         case GDK_Return: case GDK_KP_Enter:
1271                                 if (fd) layout_image_set_path(NULL, fd->path);
1272                                 stop_signal = TRUE;
1273                                 break;
1274                         case 'V': case 'v':
1275                                 {
1276                                 GList *list;
1277
1278                                 list = search_result_selection_list(sd);
1279                                 view_window_new_from_list(list);
1280                                 path_list_free(list);
1281                                 stop_signal = TRUE;
1282                                 }
1283                                 break;
1284                         case GDK_Delete: case GDK_KP_Delete:
1285                                 search_result_remove_selection(sd);
1286                                 stop_signal = TRUE;
1287                                 break;
1288                         case 'C': case 'c':
1289                                 search_result_collection_from_selection(sd);
1290                                 stop_signal = TRUE;
1291                                 break;
1292                         case GDK_Menu:
1293                         case GDK_F10:
1294                                 {
1295                                 GtkWidget *menu;
1296
1297                                 sd->click_fd = fd;
1298                                 menu = search_result_menu(sd, (fd != 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                                 stop_signal = TRUE;
1302                                 }
1303                                 break;
1304                         default:
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                 switch (event->keyval)
1320                         {
1321                         case 'T': case 't':
1322                                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(sd->button_thumbs),
1323                                         !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(sd->button_thumbs)));
1324                                 stop_signal = TRUE;
1325                                 break;
1326                         case 'W': case 'w':
1327                                 search_window_close(sd);
1328                                 stop_signal = TRUE;
1329                                 break;
1330                         default:
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_list(list, &length, (info == TARGET_TEXT_PLAIN));
1366                         path_list_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, 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 (enable_thumb_caching &&
1514                     sd->img_loader && sd->img_loader->path)
1515                         {
1516                         gchar *base;
1517                         const gchar *path;
1518                         mode_t mode = 0755;
1519
1520                         path = sd->img_loader->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->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->path);
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_cache_read(fd->path, &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                         path_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                 memcpy(mfd, fd, sizeof(FileData));
1875                 g_free(fd);
1876
1877                 mfd->width = width;
1878                 mfd->height = height;
1879                 mfd->rank = sim;
1880
1881                 sd->search_buffer_list = g_list_prepend(sd->search_buffer_list, mfd);
1882                 sd->search_buffer_count += SEARCH_BUFFER_MATCH_HIT;
1883                 sd->search_count++;
1884                 search_progress_update(sd, TRUE, -1.0);
1885                 }
1886         else
1887                 {
1888                 file_data_free(fd);
1889                 sd->search_buffer_count += SEARCH_BUFFER_MATCH_MISS;
1890                 }
1891
1892         return FALSE;
1893 }
1894
1895 static gint search_step_cb(gpointer data)
1896 {
1897         SearchData *sd = data;
1898         FileData *fd;
1899
1900         if (sd->search_buffer_count > SEARCH_BUFFER_FLUSH_SIZE)
1901                 {
1902                 search_buffer_flush(sd);
1903                 search_progress_update(sd, TRUE, -1.0);
1904                 }
1905
1906         if (sd->search_file_list)
1907                 {
1908                 if (search_file_next(sd))
1909                         {
1910                         sd->search_idle_id = -1;
1911                         return FALSE;
1912                         }
1913                 return TRUE;
1914                 }
1915
1916         if (!sd->search_file_list && !sd->search_folder_list)
1917                 {
1918                 sd->search_idle_id = -1;
1919
1920                 search_stop(sd);
1921                 search_result_thumb_step(sd);
1922
1923                 return FALSE;
1924                 }
1925
1926         fd = sd->search_folder_list->data;
1927
1928         if (g_list_find(sd->search_done_list, fd) == NULL)
1929                 {
1930                 GList *list = NULL;
1931                 GList *dlist = NULL;
1932                 gint success = FALSE;
1933
1934                 sd->search_done_list = g_list_prepend(sd->search_done_list, fd);
1935
1936                 if (sd->search_type == SEARCH_MATCH_NONE)
1937                         {
1938                         success = filelist_read(fd->path, &list, &dlist);
1939                         }
1940                 else if (sd->search_type == SEARCH_MATCH_ALL &&
1941                          sd->search_path &&
1942                          strlen(fd->path) >= strlen(sd->search_path))
1943                         {
1944                         const gchar *path;
1945
1946                         path = fd->path + strlen(sd->search_path);
1947                         if (path != fd->path) success = filelist_read(path, &list, NULL);
1948                         success |= filelist_read(fd->path, NULL, &dlist);
1949                         if (success)
1950                                 {
1951                                 GList *work;
1952
1953                                 work = list;
1954                                 while (work)
1955                                         {
1956                                         FileData *fdp;
1957                                         GList *link;
1958                                         gchar *meta_path;
1959
1960                                         fdp = work->data;
1961                                         link = work;
1962                                         work = work->next;
1963
1964                                         meta_path = cache_find_location(CACHE_TYPE_METADATA, fdp->path);
1965                                         if (!meta_path)
1966                                                 {
1967                                                 list = g_list_delete_link(list, link);
1968                                                 file_data_free(fdp);
1969                                                 }
1970                                         g_free(meta_path);
1971                                         }
1972                                 }
1973                         }
1974
1975                 if (success)
1976                         {
1977                         list = filelist_sort(list, SORT_NAME, TRUE);
1978                         sd->search_file_list = list;
1979
1980                         if (sd->search_path_recurse)
1981                                 {
1982                                 dlist = filelist_sort(dlist, SORT_NAME, TRUE);
1983                                 sd->search_folder_list = g_list_concat(dlist, sd->search_folder_list);
1984                                 }
1985                         else
1986                                 {
1987                                 filelist_free(dlist);
1988                                 }
1989                         }
1990                 }
1991         else
1992                 {
1993                 sd->search_folder_list = g_list_remove(sd->search_folder_list, fd);
1994                 sd->search_done_list = g_list_remove(sd->search_done_list, fd);
1995                 file_data_free(fd);
1996                 }
1997
1998         return TRUE;
1999 }
2000
2001 static void search_similarity_load_done_cb(ImageLoader *il, gpointer data)
2002 {
2003         SearchData *sd = data;
2004         search_file_load_process(sd, sd->search_similarity_cd);
2005 }
2006
2007 static void search_start(SearchData *sd)
2008 {
2009         search_stop(sd);
2010         search_result_clear(sd);
2011
2012         if (sd->search_path)
2013                 {
2014                 sd->search_folder_list = g_list_prepend(sd->search_folder_list,
2015                                                         file_data_new_simple(sd->search_path));
2016                 }
2017
2018         if (!sd->search_name_match_case)
2019                 {
2020                 /* convert to lowercase here, so that this is only done once per search */
2021                 gchar *tmp = g_utf8_strdown(sd->search_name, -1);
2022                 g_free(sd->search_name);
2023                 sd->search_name = tmp;
2024                 }
2025
2026         sd->search_count = 0;
2027         sd->search_total = 0;
2028
2029         gtk_widget_set_sensitive(sd->box_search, FALSE);
2030         spinner_set_interval(sd->spinner, SPINNER_SPEED);
2031         gtk_widget_set_sensitive(sd->button_start, FALSE);
2032         gtk_widget_set_sensitive(sd->button_stop, TRUE);
2033         search_progress_update(sd, TRUE, -1.0);
2034
2035         if (sd->match_similarity_enable &&
2036             !sd->search_similarity_cd &&
2037             isfile(sd->search_similarity_path))
2038                 {
2039                 gchar *cd_path;
2040
2041                 cd_path = cache_find_location(CACHE_TYPE_SIM, sd->search_similarity_path);
2042                 if (cd_path && filetime(sd->search_similarity_path) == filetime(cd_path))
2043                         {
2044                         sd->search_similarity_cd = cache_sim_data_load(cd_path);
2045                         }
2046                 g_free(cd_path);
2047
2048                 if (!sd->search_similarity_cd || !sd->search_similarity_cd->similarity)
2049                         {
2050                         if (!sd->search_similarity_cd)
2051                                 {
2052                                 sd->search_similarity_cd = cache_sim_data_new();
2053                                 }
2054
2055                         sd->img_loader = image_loader_new(sd->search_similarity_path);
2056                         image_loader_set_error_func(sd->img_loader, search_similarity_load_done_cb, sd);
2057                         if (image_loader_start(sd->img_loader, search_similarity_load_done_cb, sd))
2058                                 {
2059                                 return;
2060                                 }
2061                         image_loader_free(sd->img_loader);
2062                         sd->img_loader = NULL;
2063                         }
2064                         
2065                 }
2066
2067         sd->search_idle_id = g_idle_add(search_step_cb, sd);
2068 }
2069
2070 static void search_start_cb(GtkWidget *widget, gpointer data)
2071 {
2072         SearchData *sd = data;
2073         GtkTreeViewColumn *column;
2074         gchar *path;
2075
2076         if (sd->search_folder_list)
2077                 {
2078                 search_stop(sd);
2079                 search_result_thumb_step(sd);
2080                 return;
2081                 }
2082
2083         if (sd->match_name_enable) history_combo_append_history(sd->entry_name, NULL);
2084         g_free(sd->search_name);
2085         sd->search_name = g_strdup(gtk_entry_get_text(GTK_ENTRY(sd->entry_name)));
2086
2087         g_free(sd->search_similarity_path);
2088         sd->search_similarity_path = g_strdup(gtk_entry_get_text(GTK_ENTRY(sd->entry_similarity)));
2089         if (sd->match_similarity_enable)
2090                 {
2091                 if (!isfile(sd->search_similarity_path))
2092                         {
2093                         file_util_warning_dialog(_("File not found"),
2094                                                  _("Please enter an existing file for image content."),
2095                                                  GTK_STOCK_DIALOG_WARNING, sd->window);
2096                         return;
2097                         }
2098                 tab_completion_append_to_history(sd->entry_similarity, sd->search_similarity_path);
2099                 }
2100
2101         path_list_free(sd->search_keyword_list);
2102         sd->search_keyword_list = keyword_list_pull(sd->entry_keywords);
2103
2104         date_selection_get(sd->date_sel, &sd->search_date_d, &sd->search_date_m, &sd->search_date_y);
2105         date_selection_get(sd->date_sel_end, &sd->search_date_end_d, &sd->search_date_end_m, &sd->search_date_end_y);
2106
2107         column = gtk_tree_view_get_column(GTK_TREE_VIEW(sd->result_view), SEARCH_COLUMN_RANK - 1);
2108         gtk_tree_view_column_set_visible(column, sd->match_similarity_enable);
2109         if (!sd->match_similarity_enable)
2110                 {
2111                 GtkTreeSortable *sortable;
2112                 gint id;
2113                 GtkSortType order;
2114
2115                 sortable = GTK_TREE_SORTABLE(gtk_tree_view_get_model(GTK_TREE_VIEW(sd->result_view)));
2116                 if (gtk_tree_sortable_get_sort_column_id(sortable, &id, &order) &&
2117                     id == SEARCH_COLUMN_RANK)
2118                         {
2119                         gtk_tree_sortable_set_sort_column_id(sortable, SEARCH_COLUMN_PATH, GTK_SORT_ASCENDING);
2120                         }
2121                 }
2122
2123         if (sd->search_type == SEARCH_MATCH_NONE)
2124                 {
2125                 /* search path */
2126
2127                 path = remove_trailing_slash(gtk_entry_get_text(GTK_ENTRY(sd->path_entry)));
2128                 if (isdir(path))
2129                         {
2130                         g_free(sd->search_path);
2131                         sd->search_path = path;
2132                         path = NULL;
2133
2134                         tab_completion_append_to_history(sd->path_entry, sd->search_path);
2135
2136                         search_start(sd);
2137                         }
2138                 else
2139                         {
2140                         file_util_warning_dialog(_("Folder not found"),
2141                                                  _("Please enter an existing folder to search."),
2142                                                  GTK_STOCK_DIALOG_WARNING, sd->window);
2143                         }
2144
2145                 g_free(path);
2146                 }
2147         else if (sd->search_type == SEARCH_MATCH_ALL)
2148                 {
2149                 /* search metadata */
2150
2151                 g_free(sd->search_path);
2152                 sd->search_path = g_strconcat(homedir(), "/", GQVIEW_CACHE_RC_METADATA, NULL);
2153
2154                 search_start(sd);
2155                 }
2156         else if (sd->search_type == SEARCH_MATCH_CONTAINS)
2157                 {
2158                 /* search current result list */
2159                 GList *list;
2160
2161                 list = search_result_refine_list(sd);
2162
2163                 g_free(sd->search_path);
2164                 sd->search_path = NULL;
2165
2166                 search_start(sd);
2167
2168                 sd->search_file_list = g_list_concat(sd->search_file_list, list);
2169                 }
2170 }
2171
2172 /*
2173  *-------------------------------------------------------------------
2174  * window construct
2175  *-------------------------------------------------------------------
2176  */
2177
2178 enum {
2179         MENU_CHOICE_COLUMN_NAME = 0,
2180         MENU_CHOICE_COLUMN_VALUE
2181 };
2182
2183 static void search_thumb_toggle_cb(GtkWidget *button, gpointer data)
2184 {
2185         SearchData *sd = data;
2186
2187         search_result_thumb_enable(sd, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)));
2188 }
2189
2190 static gint sort_matchdata_dimensions(MatchFileData *a, MatchFileData *b)
2191 {
2192         gint sa;
2193         gint sb;
2194
2195         sa = a->width * a->height;
2196         sb = b->width * b->height;
2197
2198         if (sa > sb) return 1;
2199         if (sa < sb) return -1;
2200         return 0;
2201 }
2202
2203 static gint search_result_sort_cb(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer data)
2204 {
2205         gint n = GPOINTER_TO_INT(data);
2206         FileData *fda;
2207         FileData *fdb;
2208
2209         gtk_tree_model_get(model, a, SEARCH_COLUMN_POINTER, &fda, -1);
2210         gtk_tree_model_get(model, b, SEARCH_COLUMN_POINTER, &fdb, -1);
2211
2212         if (!fda || !fdb) return 0;
2213
2214         switch (n)
2215                 {
2216                 case SEARCH_COLUMN_RANK:
2217                         if (((MatchFileData *)fda)->rank > ((MatchFileData *)fdb)->rank) return 1;
2218                         if (((MatchFileData *)fda)->rank < ((MatchFileData *)fdb)->rank) return -1;
2219                         return 0;
2220                         break;
2221                 case SEARCH_COLUMN_NAME:
2222                         return CASE_SORT(fda->name, fdb->name);
2223                         break;
2224                 case SEARCH_COLUMN_SIZE:
2225                         if (fda->size > fdb->size) return 1;
2226                         if (fda->size < fdb->size) return -1;
2227                         return 0;
2228                         break;
2229                 case SEARCH_COLUMN_DATE:
2230                         if (fda->date > fdb->date) return 1;
2231                         if (fda->date < fdb->date) return -1;
2232                         return 0;
2233                         break;
2234                 case SEARCH_COLUMN_DIMENSIONS:
2235                         return sort_matchdata_dimensions((MatchFileData *)fda, (MatchFileData *)fdb);
2236                         break;
2237                 case SEARCH_COLUMN_PATH:
2238                         return CASE_SORT(fda->path, fdb->path);
2239                         break;
2240                 default:
2241                         break;
2242                 }
2243
2244         return 0;
2245 }
2246
2247 static void search_result_add_column(SearchData * sd, gint n, const gchar *title, gint image, gint right_justify)
2248 {
2249         GtkTreeViewColumn *column;
2250         GtkCellRenderer *renderer;
2251
2252         column = gtk_tree_view_column_new();
2253         gtk_tree_view_column_set_title(column, title);
2254         gtk_tree_view_column_set_min_width(column, 4);
2255
2256         if (n != SEARCH_COLUMN_THUMB) gtk_tree_view_column_set_resizable(column, TRUE);
2257
2258         if (!image)
2259                 {
2260                 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
2261                 renderer = gtk_cell_renderer_text_new();
2262                 if (right_justify) g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL);
2263                 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2264                 gtk_tree_view_column_add_attribute(column, renderer, "text", n);
2265
2266                 gtk_tree_view_column_set_sort_column_id(column, n);
2267                 }
2268         else
2269                 {
2270                 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2271                 renderer = gtk_cell_renderer_pixbuf_new();
2272                 cell_renderer_height_override(renderer);
2273                 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2274                 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", n);
2275                 }
2276
2277         gtk_tree_view_append_column(GTK_TREE_VIEW(sd->result_view), column);
2278 }
2279
2280 static void menu_choice_set_visible(GtkWidget *widget, gint visible)
2281 {
2282         if (visible)
2283                 {
2284                 if (!GTK_WIDGET_VISIBLE(widget)) gtk_widget_show(widget);
2285                 }
2286         else
2287                 {
2288                 if (GTK_WIDGET_VISIBLE(widget)) gtk_widget_hide(widget);
2289                 }
2290 }
2291
2292 static void menu_choice_path_cb(GtkWidget *combo, gpointer data)
2293 {
2294         SearchData *sd = data;
2295         GtkTreeModel *store;
2296         GtkTreeIter iter;
2297
2298         store = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
2299         if (!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter)) return;
2300         gtk_tree_model_get(store, &iter, MENU_CHOICE_COLUMN_VALUE, &sd->search_type, -1);
2301
2302         menu_choice_set_visible(gtk_widget_get_parent(sd->check_recurse),
2303                                 (sd->search_type == SEARCH_MATCH_NONE));
2304 }
2305
2306 static void menu_choice_name_cb(GtkWidget *combo, gpointer data)
2307 {
2308         SearchData *sd = data;
2309         GtkTreeModel *store;
2310         GtkTreeIter iter;
2311
2312         store = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
2313         if (!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter)) return;
2314         gtk_tree_model_get(store, &iter, MENU_CHOICE_COLUMN_VALUE, &sd->match_name, -1);
2315 }
2316
2317 static void menu_choice_size_cb(GtkWidget *combo, gpointer data)
2318 {
2319         SearchData *sd = data;
2320         GtkTreeModel *store;
2321         GtkTreeIter iter;
2322
2323         store = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
2324         if (!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter)) return;
2325         gtk_tree_model_get(store, &iter, MENU_CHOICE_COLUMN_VALUE, &sd->match_size, -1);
2326
2327         menu_choice_set_visible(gtk_widget_get_parent(sd->spin_size_end),
2328                                 (sd->match_size == SEARCH_MATCH_BETWEEN));
2329 }
2330
2331 static void menu_choice_date_cb(GtkWidget *combo, gpointer data)
2332 {
2333         SearchData *sd = data;
2334         GtkTreeModel *store;
2335         GtkTreeIter iter;
2336
2337         store = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
2338         if (!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter)) return;
2339         gtk_tree_model_get(store, &iter, MENU_CHOICE_COLUMN_VALUE, &sd->match_date, -1);
2340
2341         menu_choice_set_visible(gtk_widget_get_parent(sd->date_sel_end),
2342                                 (sd->match_date == SEARCH_MATCH_BETWEEN));
2343 }
2344
2345 static void menu_choice_dimensions_cb(GtkWidget *combo, gpointer data)
2346 {
2347         SearchData *sd = data;
2348         GtkTreeModel *store;
2349         GtkTreeIter iter;
2350
2351         store = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
2352         if (!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter)) return;
2353         gtk_tree_model_get(store, &iter, MENU_CHOICE_COLUMN_VALUE, &sd->match_dimensions, -1);
2354
2355         menu_choice_set_visible(gtk_widget_get_parent(sd->spin_width_end),
2356                                 (sd->match_dimensions == SEARCH_MATCH_BETWEEN));
2357 }
2358
2359 static void menu_choice_keyword_cb(GtkWidget *combo, gpointer data)
2360 {
2361         SearchData *sd = data;
2362         GtkTreeModel *store;
2363         GtkTreeIter iter;
2364
2365         store = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
2366         if (!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter)) return;
2367         gtk_tree_model_get(store, &iter, MENU_CHOICE_COLUMN_VALUE, &sd->match_keywords, -1);
2368 }
2369
2370 static void menu_choice_spin_cb(GtkAdjustment *adjustment, gpointer data)
2371 {
2372         gint *value = data;
2373
2374         *value = (gint)gtk_adjustment_get_value(adjustment);
2375 }
2376
2377 static GtkWidget *menu_spin(GtkWidget *box, gdouble min, gdouble max, gint value,
2378                             GCallback func, gpointer data)
2379 {
2380         GtkWidget *spin;
2381         GtkAdjustment *adj;
2382
2383         spin = gtk_spin_button_new_with_range(min, max, 1);
2384         gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), (gdouble)value);
2385         adj = gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(spin));
2386         if (func) g_signal_connect(G_OBJECT(adj), "value_changed",
2387                                    G_CALLBACK(func), data);
2388         gtk_box_pack_start(GTK_BOX(box), spin, FALSE, FALSE, 0);
2389         gtk_widget_show(spin);
2390
2391         return spin;
2392 }
2393
2394 static void menu_choice_check_cb(GtkWidget *button, gpointer data)
2395 {
2396         GtkWidget *widget = data;
2397         gboolean active;
2398         gboolean *value;
2399
2400         active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
2401         gtk_widget_set_sensitive(widget, active);
2402
2403         value = g_object_get_data(G_OBJECT(button), "check_var");
2404         if (value) *value = active;
2405 }
2406
2407 static GtkWidget *menu_choice_menu(const MatchList *items, gint item_count,
2408                                    GCallback func, gpointer data)
2409 {
2410         GtkWidget *combo;
2411         GtkCellRenderer *renderer;
2412         GtkListStore *store;
2413         gint i;
2414
2415         store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
2416         combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
2417         g_object_unref(store);
2418
2419         renderer = gtk_cell_renderer_text_new();
2420         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
2421         gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), renderer,
2422                                        "text", MENU_CHOICE_COLUMN_NAME, NULL);
2423
2424         for (i = 0; i < item_count; i++)
2425                 {
2426                 GtkTreeIter iter;
2427
2428                 gtk_list_store_append(store, &iter);
2429                 gtk_list_store_set(store, &iter, MENU_CHOICE_COLUMN_NAME, _(items[i].text),
2430                                                  MENU_CHOICE_COLUMN_VALUE, items[i].type, -1);
2431                 }
2432
2433         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
2434
2435         if (func) g_signal_connect(G_OBJECT(combo), "changed",
2436                                    G_CALLBACK(func), data);
2437
2438         return combo;
2439 }
2440
2441 static GtkWidget *menu_choice(GtkWidget *box, GtkWidget **check, GtkWidget **menu,
2442                               const gchar *text, gboolean *value,
2443                               const MatchList *items, gint item_count,
2444                               GCallback func, gpointer data)
2445 {
2446         GtkWidget *base_box;
2447         GtkWidget *hbox;
2448         GtkWidget *button;
2449         GtkWidget *option;
2450
2451         base_box = gtk_hbox_new(FALSE, PREF_PAD_GAP);
2452         gtk_box_pack_start(GTK_BOX(box), base_box, FALSE, FALSE, 0);
2453         gtk_widget_show(base_box);
2454
2455         button = gtk_check_button_new();
2456         if (value) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), *value);
2457         gtk_box_pack_start(GTK_BOX(base_box), button, FALSE, FALSE, 0);
2458         gtk_widget_show(button);
2459         if (check) *check = button;
2460         if (value) g_object_set_data(G_OBJECT(button), "check_var", value);
2461
2462         hbox = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
2463         gtk_box_pack_start(GTK_BOX(base_box), hbox, TRUE, TRUE, 0);
2464         gtk_widget_show(hbox);
2465
2466         g_signal_connect(G_OBJECT(button), "toggled",
2467                          G_CALLBACK(menu_choice_check_cb), hbox);
2468         gtk_widget_set_sensitive(hbox, (value) ? *value : FALSE);
2469
2470         pref_label_new(hbox, text);
2471
2472         if (!items && !menu) return hbox;
2473
2474         option = menu_choice_menu(items, item_count, func, data);
2475         gtk_box_pack_start(GTK_BOX(hbox), option, FALSE, FALSE, 0);
2476         gtk_widget_show(option);
2477         if (menu) *menu = option;
2478
2479         return hbox;
2480 }
2481
2482 static void search_window_close(SearchData *sd)
2483 {
2484         gtk_widget_destroy(sd->window);
2485 }
2486
2487 static gint search_window_delete_cb(GtkWidget *widget, GdkEventAny *event, gpointer data)
2488 {
2489         SearchData *sd = data;
2490
2491         search_window_close(sd);
2492         return TRUE;
2493 }
2494
2495 static void search_window_destroy_cb(GtkWidget *widget, gpointer data)
2496 {
2497         SearchData *sd = data;
2498
2499         search_window_list = g_list_remove(search_window_list, sd);
2500
2501         search_result_update_idle_cancel(sd);
2502
2503         filelist_free(sd->search_buffer_list);
2504         sd->search_buffer_list = NULL;
2505
2506         search_stop(sd);
2507         search_result_clear(sd);
2508
2509         g_free(sd->search_path);
2510         g_free(sd->search_name);
2511         g_free(sd->search_similarity_path);
2512         path_list_free(sd->search_keyword_list);
2513
2514         g_free(sd);
2515 }
2516
2517 void search_new(const gchar *path, const gchar *example_file)
2518 {
2519         SearchData *sd;
2520         GtkWidget *vbox;
2521         GtkWidget *hbox;
2522         GtkWidget *hbox2;
2523         GtkWidget *pad_box;
2524         GtkWidget *frame;
2525         GtkWidget *scrolled;
2526         GtkListStore *store;
2527         GtkTreeSortable *sortable;
2528         GtkTreeSelection *selection;
2529         GtkWidget *combo;
2530         GdkGeometry geometry;
2531
2532         sd = g_new0(SearchData, 1);
2533
2534         sd->search_path = g_strdup(path);
2535         sd->search_path_recurse = TRUE;
2536         sd->search_size = 0;
2537         sd->search_width = 640;
2538         sd->search_height = 480;
2539         sd->search_width_end = 1024;
2540         sd->search_height_end = 768;
2541         sd->search_name = NULL;
2542         sd->search_name_match_case = FALSE;
2543
2544         sd->search_type = SEARCH_MATCH_NONE;
2545
2546         sd->match_name = SEARCH_MATCH_CONTAINS;
2547         sd->match_size = SEARCH_MATCH_EQUAL;
2548         sd->match_date = SEARCH_MATCH_EQUAL;
2549         sd->match_dimensions = SEARCH_MATCH_EQUAL;
2550         sd->match_keywords = SEARCH_MATCH_ALL;
2551
2552         sd->match_name_enable = TRUE;
2553         sd->match_size_enable = FALSE;
2554         sd->match_date_enable = FALSE;
2555         sd->match_dimensions_enable = FALSE;
2556         sd->match_similarity_enable = FALSE;
2557         sd->match_keywords_enable = FALSE;
2558
2559         sd->search_similarity = 95;
2560         sd->search_similarity_path = g_strdup(example_file);
2561         sd->search_similarity_cd = NULL;
2562
2563         sd->search_idle_id = -1;
2564         sd->update_idle_id = -1;
2565
2566         sd->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2567         window_set_icon(sd->window, NULL, NULL);
2568
2569         gtk_window_set_resizable(GTK_WINDOW(sd->window), TRUE);
2570         gtk_window_set_title(GTK_WINDOW(sd->window), _("Image search - GQview"));
2571         gtk_window_set_wmclass(GTK_WINDOW(sd->window), "search", "GQview");
2572
2573         geometry.min_width = 32;
2574         geometry.min_height = 32;
2575         geometry.base_width = DEF_SEARCH_WIDTH;
2576         geometry.base_height = DEF_SEARCH_HEIGHT;
2577         gtk_window_set_geometry_hints(GTK_WINDOW(sd->window), NULL, &geometry,
2578                                       GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE);
2579
2580         gtk_window_set_default_size(GTK_WINDOW(sd->window), DEF_SEARCH_WIDTH, DEF_SEARCH_HEIGHT);
2581
2582         g_signal_connect(G_OBJECT(sd->window), "delete_event",
2583                          G_CALLBACK(search_window_delete_cb), sd);
2584         g_signal_connect(G_OBJECT(sd->window), "destroy",
2585                          G_CALLBACK(search_window_destroy_cb), sd);
2586
2587         g_signal_connect(G_OBJECT(sd->window), "key_press_event",
2588                          G_CALLBACK(search_window_keypress_cb), sd);
2589
2590         vbox = gtk_vbox_new(FALSE, PREF_PAD_GAP);
2591         gtk_container_set_border_width(GTK_CONTAINER(vbox), PREF_PAD_GAP);
2592         gtk_container_add(GTK_CONTAINER(sd->window), vbox);
2593         gtk_widget_show(vbox);
2594
2595         sd->box_search = pref_box_new(vbox, FALSE, GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
2596
2597         hbox = pref_box_new(sd->box_search, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
2598
2599         pref_label_new(hbox, _("Search:"));
2600
2601         sd->menu_path = menu_choice_menu(text_search_menu_path, sizeof(text_search_menu_path) / sizeof(MatchList),
2602                                          G_CALLBACK(menu_choice_path_cb), sd);
2603         gtk_box_pack_start(GTK_BOX(hbox), sd->menu_path, FALSE, FALSE, 0);
2604         gtk_widget_show(sd->menu_path);
2605
2606         hbox2 = pref_box_new(hbox, TRUE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
2607         combo = tab_completion_new_with_history(&sd->path_entry, sd->search_path,
2608                                                 "search_path", -1,
2609                                                 NULL, NULL);
2610         tab_completion_add_select_button(sd->path_entry, NULL, TRUE);
2611         gtk_box_pack_start(GTK_BOX(hbox2), combo, TRUE, TRUE, 0);
2612         gtk_widget_show(combo);
2613         sd->check_recurse = pref_checkbox_new_int(hbox2, _("Recurse"),
2614                                                   sd->search_path_recurse, &sd->search_path_recurse);
2615
2616         hbox = menu_choice(sd->box_search, &sd->check_name, &sd->menu_name,
2617                            _("File name"), &sd->match_name_enable,
2618                            text_search_menu_name, sizeof(text_search_menu_name) / sizeof(MatchList),
2619                            G_CALLBACK(menu_choice_name_cb), sd);
2620         combo = history_combo_new(&sd->entry_name, "", "search_name", -1);
2621         gtk_box_pack_start(GTK_BOX(hbox), combo, TRUE, TRUE, 0);
2622         gtk_widget_show(combo);
2623         pref_checkbox_new_int(hbox, _("Match case"),
2624                               sd->search_name_match_case, &sd->search_name_match_case);
2625
2626         hbox = menu_choice(sd->box_search, &sd->check_size, &sd->menu_size,
2627                            _("File size is"), &sd->match_size_enable,
2628                            text_search_menu_size, sizeof(text_search_menu_size) / sizeof(MatchList),
2629                            G_CALLBACK(menu_choice_size_cb), sd);
2630         sd->spin_size = menu_spin(hbox, 0, 1024*1024*1024, sd->search_size,
2631                                   G_CALLBACK(menu_choice_spin_cb), &sd->search_size);
2632         hbox2 = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
2633         gtk_box_pack_start(GTK_BOX(hbox), hbox2, FALSE, FALSE, 0);
2634         pref_label_new(hbox2, _("and"));
2635         sd->spin_size_end = menu_spin(hbox2, 0, 1024*1024*1024, sd->search_size_end,
2636                                       G_CALLBACK(menu_choice_spin_cb), &sd->search_size_end);
2637
2638         hbox = menu_choice(sd->box_search, &sd->check_date, &sd->menu_date,
2639                            _("File date is"), &sd->match_date_enable,
2640                            text_search_menu_date, sizeof(text_search_menu_date) / sizeof(MatchList),
2641                            G_CALLBACK(menu_choice_date_cb), sd);
2642         sd->date_sel = date_selection_new();
2643         date_selection_time_set(sd->date_sel, time(NULL));
2644         gtk_box_pack_start(GTK_BOX(hbox), sd->date_sel, FALSE, FALSE, 0);
2645         gtk_widget_show(sd->date_sel);
2646
2647         hbox2 = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
2648         gtk_box_pack_start(GTK_BOX(hbox), hbox2, FALSE, FALSE, 0);
2649         pref_label_new(hbox2, _("and"));
2650         sd->date_sel_end = date_selection_new();
2651         date_selection_time_set(sd->date_sel_end, time(NULL));
2652         gtk_box_pack_start(GTK_BOX(hbox2), sd->date_sel_end, FALSE, FALSE, 0);
2653         gtk_widget_show(sd->date_sel_end);
2654
2655         hbox = menu_choice(sd->box_search, &sd->check_dimensions, &sd->menu_dimensions,
2656                            _("Image dimensions are"), &sd->match_dimensions_enable,
2657                            text_search_menu_size, sizeof(text_search_menu_size) / sizeof(MatchList),
2658                            G_CALLBACK(menu_choice_dimensions_cb), sd);
2659         pad_box = pref_box_new(hbox, FALSE, GTK_ORIENTATION_HORIZONTAL, 2);
2660         sd->spin_width = menu_spin(pad_box, 0, 1000000, sd->search_width,
2661                                    G_CALLBACK(menu_choice_spin_cb), &sd->search_width);
2662         pref_label_new(pad_box, "x");
2663         sd->spin_height = menu_spin(pad_box, 0, 1000000, sd->search_height,
2664                                     G_CALLBACK(menu_choice_spin_cb), &sd->search_height);
2665         hbox2 = gtk_hbox_new(FALSE, 2);
2666         gtk_box_pack_start(GTK_BOX(hbox), hbox2, FALSE, FALSE, 0);
2667         pref_label_new(hbox2, _("and"));
2668         pref_spacer(hbox2, PREF_PAD_SPACE - 2*2);
2669         sd->spin_width_end = menu_spin(hbox2, 0, 1000000, sd->search_width_end,
2670                                        G_CALLBACK(menu_choice_spin_cb), &sd->search_width_end);
2671         pref_label_new(hbox2, "x");
2672         sd->spin_height_end = menu_spin(hbox2, 0, 1000000, sd->search_height_end,
2673                                         G_CALLBACK(menu_choice_spin_cb), &sd->search_height_end);
2674
2675         hbox = menu_choice(sd->box_search, &sd->check_similarity, NULL,
2676                            _("Image content is"), &sd->match_similarity_enable,
2677                            NULL, 0, NULL, sd);
2678         sd->spin_similarity = menu_spin(hbox, 80, 100, sd->search_similarity,
2679                                         G_CALLBACK(menu_choice_spin_cb), &sd->search_similarity);
2680
2681         /* xgettext:no-c-format */
2682         pref_label_new(hbox, _("% similar to"));
2683
2684         combo = tab_completion_new_with_history(&sd->entry_similarity,
2685                                                 (sd->search_similarity_path) ? sd->search_similarity_path : "",
2686                                                 "search_similarity_path", -1, NULL, NULL);
2687         tab_completion_add_select_button(sd->entry_similarity, NULL, FALSE);
2688         gtk_box_pack_start(GTK_BOX(hbox), combo, TRUE, TRUE, 0);
2689         gtk_widget_show(combo);
2690
2691         hbox = menu_choice(sd->box_search, &sd->check_keywords, &sd->menu_keywords,
2692                            _("Keywords"), &sd->match_keywords_enable,
2693                            text_search_menu_keyword, sizeof(text_search_menu_keyword) / sizeof(MatchList),
2694                            G_CALLBACK(menu_choice_keyword_cb), sd);
2695         sd->entry_keywords = gtk_entry_new();
2696         gtk_box_pack_start(GTK_BOX(hbox), sd->entry_keywords, TRUE, TRUE, 0);
2697         gtk_widget_set_sensitive(sd->entry_keywords, sd->match_keywords_enable);
2698         g_signal_connect(G_OBJECT(sd->check_keywords), "toggled",
2699                          G_CALLBACK(menu_choice_check_cb), sd->entry_keywords);
2700         gtk_widget_show(sd->entry_keywords);
2701
2702         scrolled = gtk_scrolled_window_new(NULL, NULL);
2703         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
2704         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
2705                                        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2706         gtk_box_pack_start(GTK_BOX(vbox), scrolled, TRUE, TRUE, 0);
2707         gtk_widget_show(scrolled);
2708
2709         store = gtk_list_store_new(8, G_TYPE_POINTER, G_TYPE_INT, GDK_TYPE_PIXBUF,
2710                                    G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
2711                                    G_TYPE_STRING, G_TYPE_STRING);
2712
2713         /* set up sorting */
2714         sortable = GTK_TREE_SORTABLE(store);
2715         gtk_tree_sortable_set_sort_func(sortable, SEARCH_COLUMN_RANK, search_result_sort_cb,
2716                                   GINT_TO_POINTER(SEARCH_COLUMN_RANK), NULL);
2717         gtk_tree_sortable_set_sort_func(sortable, SEARCH_COLUMN_NAME, search_result_sort_cb,
2718                                   GINT_TO_POINTER(SEARCH_COLUMN_NAME), NULL);
2719         gtk_tree_sortable_set_sort_func(sortable, SEARCH_COLUMN_SIZE, search_result_sort_cb,
2720                                   GINT_TO_POINTER(SEARCH_COLUMN_SIZE), NULL);
2721         gtk_tree_sortable_set_sort_func(sortable, SEARCH_COLUMN_DATE, search_result_sort_cb,
2722                                   GINT_TO_POINTER(SEARCH_COLUMN_DATE), NULL);
2723         gtk_tree_sortable_set_sort_func(sortable, SEARCH_COLUMN_DIMENSIONS, search_result_sort_cb,
2724                                   GINT_TO_POINTER(SEARCH_COLUMN_DIMENSIONS), NULL);
2725         gtk_tree_sortable_set_sort_func(sortable, SEARCH_COLUMN_PATH, search_result_sort_cb,
2726                                   GINT_TO_POINTER(SEARCH_COLUMN_PATH), NULL);
2727
2728 #if 0
2729         /* by default, search results are unsorted until user selects a sort column - for speed,
2730          * using sort slows search speed by an order of magnitude with 1000's of results :-/
2731          */
2732         gtk_tree_sortable_set_sort_column_id(sortable, SEARCH_COLUMN_PATH, GTK_SORT_ASCENDING);
2733 #endif
2734
2735         sd->result_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
2736         g_object_unref(store);
2737         gtk_container_add(GTK_CONTAINER(scrolled), sd->result_view);
2738         gtk_widget_show(sd->result_view);
2739
2740         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(sd->result_view));
2741         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
2742         gtk_tree_selection_set_select_function(selection, search_result_select_cb, sd, NULL);
2743
2744         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(sd->result_view), TRUE);
2745         gtk_tree_view_set_enable_search(GTK_TREE_VIEW(sd->result_view), FALSE);
2746
2747 #if 0
2748         gtk_tree_view_set_search_column(GTK_TREE_VIEW(sd->result_view), SEARCH_COLUMN_NAME);
2749 #endif
2750
2751         search_result_add_column(sd, SEARCH_COLUMN_RANK, _("Rank"), FALSE, FALSE);
2752         search_result_add_column(sd, SEARCH_COLUMN_THUMB, "", TRUE, FALSE);
2753         search_result_add_column(sd, SEARCH_COLUMN_NAME, _("Name"), FALSE, FALSE);
2754         search_result_add_column(sd, SEARCH_COLUMN_SIZE, _("Size"), FALSE, TRUE);
2755         search_result_add_column(sd, SEARCH_COLUMN_DATE, _("Date"), FALSE, TRUE);
2756         search_result_add_column(sd, SEARCH_COLUMN_DIMENSIONS, _("Dimensions"), FALSE, FALSE);
2757         search_result_add_column(sd, SEARCH_COLUMN_PATH, _("Path"), FALSE, FALSE);
2758
2759         search_dnd_init(sd);
2760
2761         g_signal_connect(G_OBJECT(sd->result_view), "button_press_event",
2762                          G_CALLBACK(search_result_press_cb), sd);
2763         g_signal_connect(G_OBJECT(sd->result_view), "button_release_event",
2764                          G_CALLBACK(search_result_release_cb), sd);
2765         g_signal_connect(G_OBJECT(sd->result_view), "key_press_event",
2766                          G_CALLBACK(search_result_keypress_cb), sd);
2767
2768         hbox = pref_box_new(vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
2769
2770         sd->button_thumbs = pref_checkbox_new(hbox, _("Thumbnails"), FALSE,
2771                                               G_CALLBACK(search_thumb_toggle_cb), sd);
2772
2773         frame = gtk_frame_new(NULL);
2774         gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
2775         gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, PREF_PAD_SPACE);
2776         gtk_widget_show(frame);
2777
2778         sd->label_status = gtk_label_new("");
2779         gtk_widget_set_size_request(sd->label_status, 50, -1);
2780         gtk_container_add(GTK_CONTAINER(frame), sd->label_status);
2781         gtk_widget_show(sd->label_status);
2782
2783         sd->label_progress = gtk_progress_bar_new();
2784         gtk_widget_set_size_request(sd->label_progress, 50, -1);
2785         gtk_box_pack_start(GTK_BOX(hbox), sd->label_progress, TRUE, TRUE, 0);
2786         gtk_widget_show(sd->label_progress);
2787
2788         sd->spinner = spinner_new(NULL, -1);
2789         gtk_box_pack_start(GTK_BOX(hbox), sd->spinner, FALSE, FALSE, 0);
2790         gtk_widget_show(sd->spinner);
2791
2792         sd->button_start = pref_button_new(hbox, GTK_STOCK_FIND, NULL, FALSE,
2793                                            G_CALLBACK(search_start_cb), sd);
2794         pref_spacer(hbox, PREF_PAD_BUTTON_GAP);
2795         sd->button_stop = pref_button_new(hbox, GTK_STOCK_STOP, NULL, FALSE,
2796                                           G_CALLBACK(search_start_cb), sd);
2797         gtk_widget_set_sensitive(sd->button_stop, FALSE);
2798
2799         search_status_update(sd);
2800         search_progress_update(sd, FALSE, -1.0);
2801
2802         search_window_list = g_list_append(search_window_list, sd);
2803
2804         gtk_widget_show(sd->window);
2805 }
2806
2807 /*
2808  *-------------------------------------------------------------------
2809  * maintenance (move, delete, etc.)
2810  *-------------------------------------------------------------------
2811  */
2812
2813 static void search_result_change_path(SearchData *sd, const gchar *path, const gchar *newpath)
2814 {
2815         GtkTreeModel *store;
2816         GtkTreeIter iter;
2817         gint valid;
2818
2819         store = gtk_tree_view_get_model(GTK_TREE_VIEW(sd->result_view));
2820         valid = gtk_tree_model_get_iter_first(store, &iter);
2821         while (valid)
2822                 {
2823                 GtkTreeIter current;
2824                 FileData *fd;
2825
2826                 current = iter;
2827                 valid = gtk_tree_model_iter_next(store, &iter);
2828
2829                 gtk_tree_model_get(store, &current, SEARCH_COLUMN_POINTER, &fd, -1);
2830                 if (strcmp(fd->path, path) == 0)
2831                         {
2832                         if (newpath)
2833                                 {
2834                                 g_free(fd->path);
2835                                 fd->path = g_strdup(newpath);
2836                                 fd->name = filename_from_path(fd->path);
2837
2838                                 gtk_list_store_set(GTK_LIST_STORE(store), &current,
2839                                                    SEARCH_COLUMN_NAME, fd->name,
2840                                                    SEARCH_COLUMN_PATH, fd->path, -1);
2841                                 }
2842                         else
2843                                 {
2844                                 search_result_remove_item(sd, fd, &current);
2845                                 }
2846                         }
2847                 }
2848 }
2849
2850 void search_maint_renamed(const gchar *source, const gchar *dest)
2851 {
2852         GList *work;
2853
2854         work = search_window_list;
2855         while (work)
2856                 {
2857                 SearchData *sd = work->data;
2858                 work = work->next;
2859
2860                 search_result_change_path(sd, source, dest);
2861                 }
2862 }
2863
2864 void search_maint_removed(const gchar *path)
2865 {
2866         search_maint_renamed(path, NULL);
2867 }
2868