comparison src/search.c @ 9:d907d608745f

Sync to GQview 1.5.9 release. ######## DO NOT BASE ENHANCEMENTS OR TRANSLATION UPDATES ON CODE IN THIS CVS! This CVS is never up to date with current development and is provided solely for reference purposes, please use the latest official release package when making any changes or translation updates. ########
author gqview
date Sat, 26 Feb 2005 00:13:35 +0000
parents
children 04ff0df3ad2f
comparison
equal deleted inserted replaced
8:e0d0593d519e 9:d907d608745f
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