better check for notify type
[geeqie.git] / src / collect.c
1 /*
2  * Geeqie
3  * (C) 2006 John Ellis
4  * Copyright (C) 2008 The Geeqie Team
5  *
6  * Author: John Ellis
7  *
8  * This software is released under the GNU General Public License (GNU GPL).
9  * Please read the included file COPYING for more information.
10  * This software comes with no warranty of any kind, use at your own risk!
11  */
12
13
14 #include "main.h"
15 #include "collect.h"
16
17 #include "collect-dlg.h"
18 #include "collect-io.h"
19 #include "collect-table.h"
20 #include "editors.h"
21 #include "filedata.h"
22 #include "img-view.h"
23 #include "info.h"
24 #include "layout.h"
25 #include "layout_image.h"
26 #include "pixbuf_util.h"
27 #include "print.h"
28 #include "utilops.h"
29 #include "ui_fileops.h"
30 #include "ui_tree_edit.h"
31 #include "window.h"
32
33 #include <gdk/gdkkeysyms.h> /* for keyboard values */
34
35
36 #define COLLECT_DEF_WIDTH 440
37 #define COLLECT_DEF_HEIGHT 450
38
39 static GList *collection_list = NULL;
40 static GList *collection_window_list = NULL;
41
42 static void collection_window_get_geometry(CollectWindow *cw);
43 static void collection_window_refresh(CollectWindow *cw);
44 static void collection_window_update_title(CollectWindow *cw);
45 static void collection_window_add(CollectWindow *cw, CollectInfo *ci);
46 static void collection_window_insert(CollectWindow *cw, CollectInfo *ci);
47 static void collection_window_remove(CollectWindow *cw, CollectInfo *ci);
48 static void collection_window_update(CollectWindow *cw, CollectInfo *ci);
49
50 static void collection_window_close(CollectWindow *cw);
51
52 static void collection_notify_cb(FileData *fd, NotifyType type, gpointer data);
53
54 /*
55  *-------------------------------------------------------------------
56  * data, list handling
57  *-------------------------------------------------------------------
58  */
59
60 CollectInfo *collection_info_new(FileData *fd, struct stat *st, GdkPixbuf *pixbuf)
61 {
62         CollectInfo *ci;
63
64         if (!fd) return NULL;
65
66         ci = g_new0(CollectInfo, 1);
67         ci->fd = file_data_ref(fd);
68
69         ci->pixbuf = pixbuf;
70         if (ci->pixbuf) g_object_ref(ci->pixbuf);
71
72         return ci;
73 }
74
75 void collection_info_free_thumb(CollectInfo *ci)
76 {
77         if (ci->pixbuf) g_object_unref(ci->pixbuf);
78         ci->pixbuf = NULL;
79 }
80
81 void collection_info_free(CollectInfo *ci)
82 {
83         if (!ci) return;
84
85         file_data_unref(ci->fd);
86         collection_info_free_thumb(ci);
87         g_free(ci);
88 }
89
90 void collection_info_set_thumb(CollectInfo *ci, GdkPixbuf *pixbuf)
91 {
92         if (pixbuf) g_object_ref(pixbuf);
93         collection_info_free_thumb(ci);
94         ci->pixbuf = pixbuf;
95 }
96
97 gint collection_info_load_thumb(CollectInfo *ci)
98 {
99         if (!ci) return FALSE;
100
101         collection_info_free_thumb(ci);
102
103         log_printf("collection_info_load_thumb not implemented!\n(because an instant thumb loader not implemented)");
104         return FALSE;
105 #if 0
106         if (create_thumbnail(ci->fd->path, &ci->pixmap, &ci->mask) < 0) return FALSE;
107
108         if (ci->pixmap) gdk_pixmap_ref(ci->pixmap);
109         if (ci->mask) gdk_bitmap_ref(ci->mask);
110
111         return TRUE;
112 #endif
113 }
114
115 void collection_list_free(GList *list)
116 {
117         GList *work;
118         work = list;
119         while (work)
120                 {
121                 collection_info_free((CollectInfo *)work->data);
122                 work = work->next;
123                 }
124         g_list_free(list);
125 }
126
127 /* an ugly static var, well what ya gonna do ? */
128 static SortType collection_list_sort_method = SORT_NAME;
129
130 static gint collection_list_sort_cb(gconstpointer a, gconstpointer b)
131 {
132         const CollectInfo *cia = a;
133         const CollectInfo *cib = b;
134
135         switch(collection_list_sort_method)
136                 {
137                 case SORT_NAME:
138                         break;
139                 case SORT_NONE:
140                         return 0;
141                         break;
142                 case SORT_SIZE:
143                         if (cia->fd->size < cib->fd->size) return -1;
144                         if (cia->fd->size > cib->fd->size) return 1;
145                         return 0;
146                         break;
147                 case SORT_TIME:
148                         if (cia->fd->date < cib->fd->date) return -1;
149                         if (cia->fd->date > cib->fd->date) return 1;
150                         return 0;
151                         break;
152                 case SORT_PATH:
153                         return utf8_compare(cia->fd->path, cib->fd->path, options->file_sort.case_sensitive);
154                         break;
155 #ifdef HAVE_STRVERSCMP
156                 case SORT_NUMBER:
157                         return strverscmp(cia->fd->name, cib->fd->name);
158                         break;
159 #endif
160                 default:
161                         break;
162                 }
163
164         if (options->file_sort.case_sensitive)
165                 return strcmp(cia->fd->collate_key_name, cib->fd->collate_key_name);
166         else
167                 return strcmp(cia->fd->collate_key_name_nocase, cib->fd->collate_key_name_nocase);
168 }
169
170 GList *collection_list_sort(GList *list, SortType method)
171 {
172         if (method == SORT_NONE) return list;
173
174         collection_list_sort_method = method;
175
176         return g_list_sort(list, collection_list_sort_cb);
177 }
178
179 GList *collection_list_add(GList *list, CollectInfo *ci, SortType method)
180 {
181         if (method != SORT_NONE)
182                 {
183                 collection_list_sort_method = method;
184                 list = g_list_insert_sorted(list, ci, collection_list_sort_cb);
185                 }
186         else
187                 {
188                 list = g_list_append(list, ci);
189                 }
190
191         return list;
192 }
193
194 GList *collection_list_insert(GList *list, CollectInfo *ci, CollectInfo *insert_ci, SortType method)
195 {
196         if (method != SORT_NONE)
197                 {
198                 collection_list_sort_method = method;
199                 list = g_list_insert_sorted(list, ci, collection_list_sort_cb);
200                 }
201         else
202                 {
203                 GList *point;
204
205                 point = g_list_find(list, insert_ci);
206                 list = uig_list_insert_link(list, point, ci);
207                 }
208
209         return list;
210 }
211
212 GList *collection_list_remove(GList *list, CollectInfo *ci)
213 {
214         list = g_list_remove(list, ci);
215         collection_info_free(ci);
216         return list;
217 }
218
219 CollectInfo *collection_list_find_fd(GList *list, FileData *fd)
220 {
221         GList *work = list;
222
223         while (work)
224                 {
225                 CollectInfo *ci = work->data;
226                 if (ci->fd == fd) return ci;
227                 work = work->next;
228                 }
229
230         return NULL;
231 }
232
233 #if 0
234 static GList *collection_list_find_link(GList *list, gchar *path)
235 {
236         GList *work = list;
237
238         while (work)
239                 {
240                 CollectInfo *ci = work->data;
241                 if (strcmp(ci->fd->path, path) == 0) return work;
242                 work = work->next;
243                 }
244
245         return NULL;
246 }
247
248 static gint collection_list_find_index(GList *list, gchar *path)
249 {
250         gint c = 0;
251         GList *work = list;
252
253         while (work)
254                 {
255                 CollectInfo *ci = work->data;
256                 if (strcmp(ci->fd->path, path) == 0) return c;
257                 work = work->next;
258                 c++;
259                 }
260
261         return -1;
262 }
263 #endif
264
265 GList *collection_list_to_filelist(GList *list)
266 {
267         GList *filelist = NULL;
268         GList *work = list;
269
270         while (work)
271                 {
272                 CollectInfo *info = work->data;
273                 filelist = g_list_prepend(filelist, file_data_ref(info->fd));
274                 work = work->next;
275                 }
276
277         filelist = g_list_reverse(filelist);
278         return filelist;
279 }
280
281 CollectWindow *collection_window_find(CollectionData *cd)
282 {
283         GList *work;
284
285         work = collection_window_list;
286         while (work)
287                 {
288                 CollectWindow *cw = work->data;
289                 if (cw->cd == cd) return cw;
290                 work = work->next;
291                 }
292
293         return NULL;
294 }
295
296 CollectWindow *collection_window_find_by_path(const gchar *path)
297 {
298         GList *work;
299
300         if (!path) return NULL;
301
302         work = collection_window_list;
303         while (work)
304                 {
305                 CollectWindow *cw = work->data;
306                 if (cw->cd->path && strcmp(cw->cd->path, path) == 0) return cw;
307                 work = work->next;
308                 }
309
310         return NULL;
311 }
312
313 /*
314  *-------------------------------------------------------------------
315  * please use these to actually add/remove stuff
316  *-------------------------------------------------------------------
317  */
318
319 CollectionData *collection_new(const gchar *path)
320 {
321         CollectionData *cd;
322         static gint untitled_counter = 0;
323
324         cd = g_new0(CollectionData, 1);
325
326         collection_list = g_list_append(collection_list, cd);
327
328         cd->ref = 1;    /* starts with a ref of 1 */
329
330         cd->list = NULL;
331         cd->sort_method = SORT_NONE;
332         cd->thumb_loader = NULL;
333         cd->info_updated_func = NULL;
334
335         cd->window_read = FALSE;
336         cd->window_x = 0;
337         cd->window_y = 0;
338         cd->window_w = COLLECT_DEF_WIDTH;
339         cd->window_h = COLLECT_DEF_HEIGHT;
340
341         cd->changed = FALSE;
342
343         cd->existence = g_hash_table_new(NULL, NULL);
344
345         if (path)
346                 {
347                 cd->path = g_strdup(path);
348                 cd->name = g_strdup(filename_from_path(cd->path));
349                 /* load it */
350                 }
351         else
352                 {
353                 cd->path = NULL;
354
355                 if (untitled_counter == 0)
356                         {
357                         cd->name = g_strdup(_("Untitled"));
358                         }
359                 else
360                         {
361                         cd->name = g_strdup_printf(_("Untitled (%d)"), untitled_counter + 1);
362                         }
363
364                 untitled_counter++;
365                 }
366
367         file_data_register_notify_func(collection_notify_cb, cd, NOTIFY_PRIORITY_MEDIUM);
368
369         return cd;
370 }
371
372 void collection_free(CollectionData *cd)
373 {
374         if (!cd) return;
375
376         DEBUG_1("collection \"%s\" freed", cd->name);
377
378         collection_load_stop(cd);
379         collection_list_free(cd->list);
380         
381         file_data_unregister_notify_func(collection_notify_cb, cd);
382
383         collection_list = g_list_remove(collection_list, cd);
384
385         g_hash_table_destroy(cd->existence);
386
387         g_free(cd->path);
388         g_free(cd->name);
389
390         g_free(cd);
391 }
392
393 void collection_ref(CollectionData *cd)
394 {
395         cd->ref++;
396
397         DEBUG_1("collection \"%s\" ref count = %d", cd->name, cd->ref);
398 }
399
400 void collection_unref(CollectionData *cd)
401 {
402         cd->ref--;
403
404         DEBUG_1("collection \"%s\" ref count = %d", cd->name, cd->ref);
405
406         if (cd->ref < 1)
407                 {
408                 collection_free(cd);
409                 }
410 }
411
412 void collection_path_changed(CollectionData *cd)
413 {
414         collection_window_update_title(collection_window_find(cd));
415 }
416
417 gint collection_to_number(CollectionData *cd)
418 {
419         return g_list_index(collection_list, cd);
420 }
421
422 CollectionData *collection_from_number(gint n)
423 {
424         return g_list_nth_data(collection_list, n);
425 }
426
427 CollectionData *collection_from_dnd_data(const gchar *data, GList **list, GList **info_list)
428 {
429         CollectionData *cd;
430         gint n;
431
432         if (strncmp(data, "COLLECTION:", 11) != 0) return NULL;
433
434         n = (gint)strtol(data + 11, NULL, 10);
435         cd = collection_from_number(n);
436
437         if (!cd || (!list && !info_list))
438                 {
439                 return cd;
440                 }
441         else
442                 {
443                 GList *work = NULL;
444                 GList *infol = NULL;
445                 gint b, e;
446
447                 b = 0;
448                 while (data[b] != '\0' && data[b] != '\n' ) b++;
449                 b++;
450                 e = b;
451
452                 while (data[b] != '\0')
453                         {
454                         CollectInfo *info;
455
456                         while (data[e] != '\n' && data[e] != '\0') e++;
457                         n = (gint)strtol(data + b, NULL, 10);
458
459                         info = g_list_nth_data(cd->list, n);
460                         if (info && list) work = g_list_append(work, file_data_ref(info->fd));
461                         if (info && info_list) infol = g_list_append(infol, info);
462
463                         while (data[e] == '\n') e++;
464                         b = e;
465                         }
466                 if (list) *list = work;
467                 if (info_list) *info_list = infol;
468                 }
469
470         return cd;
471 }
472
473 gchar *collection_info_list_to_dnd_data(CollectionData *cd, GList *list, gint *length)
474 {
475         gchar *uri_text = NULL;
476         gint total;
477         GList *work;
478         gint n;
479         GList *temp;
480         gchar *ptr;
481
482         n = collection_to_number(cd);
483
484         if (!list || n < 0)
485                 {
486                 *length = 0;
487                 return NULL;
488                 }
489
490         temp = NULL;
491         temp = g_list_prepend(temp, g_strdup_printf("COLLECTION:%d\n", n));
492         work = list;
493         while (work)
494                 {
495                 n = g_list_index(cd->list, work->data);
496                 if (n >= 0)
497                         {
498                         temp = g_list_prepend(temp, g_strdup_printf("%d\n", n));
499                         }
500                 work = work->next;
501                 }
502
503         total = 0;
504         work = temp;
505         while (work)
506                 {
507                 total += strlen((gchar *)work->data);
508                 work = work->next;
509                 }
510         total += 1;
511
512         uri_text = g_malloc(total);
513         ptr = uri_text;
514
515         work = g_list_last(temp);
516         while (work)
517                 {
518                 gchar *text = work->data;
519
520                 work = work->prev;
521
522                 strcpy(ptr, text);
523                 ptr += strlen(text);
524                 }
525
526         ptr[0] = '\0';
527
528         string_list_free(temp);
529
530         *length = total;
531
532         return uri_text;
533 }
534
535 gint collection_info_valid(CollectionData *cd, CollectInfo *info)
536 {
537         if (collection_to_number(cd) < 0) return FALSE;
538
539         return (g_list_index(cd->list, info) != 0);
540 }
541
542 CollectInfo *collection_next_by_info(CollectionData *cd, CollectInfo *info)
543 {
544         GList *work;
545
546         work = g_list_find(cd->list, info);
547
548         if (!work) return NULL;
549         work = work->next;
550         if (work) return work->data;
551         return NULL;
552 }
553
554 CollectInfo *collection_prev_by_info(CollectionData *cd, CollectInfo *info)
555 {
556         GList *work;
557
558         work = g_list_find(cd->list, info);
559
560         if (!work) return NULL;
561         work = work->prev;
562         if (work) return work->data;
563         return NULL;
564 }
565
566 CollectInfo *collection_get_first(CollectionData *cd)
567 {
568         if (cd->list) return cd->list->data;
569
570         return NULL;
571 }
572
573 CollectInfo *collection_get_last(CollectionData *cd)
574 {
575         GList *list;
576
577         list = g_list_last(cd->list);
578
579         if (list) return list->data;
580
581         return NULL;
582 }
583
584 void collection_set_sort_method(CollectionData *cd, SortType method)
585 {
586         if (!cd) return;
587
588         if (cd->sort_method == method) return;
589
590         cd->sort_method = method;
591         cd->list = collection_list_sort(cd->list, cd->sort_method);
592         if (cd->list) cd->changed = TRUE;
593
594         collection_window_refresh(collection_window_find(cd));
595 }
596
597 void collection_set_update_info_func(CollectionData *cd,
598                                      void (*func)(CollectionData *, CollectInfo *, gpointer), gpointer data)
599 {
600         cd->info_updated_func = func;
601         cd->info_updated_data = data;
602 }
603
604 static CollectInfo *collection_info_new_if_not_exists(CollectionData *cd, struct stat *st, FileData *fd)
605 {
606         CollectInfo *ci;
607
608         if (g_hash_table_lookup(cd->existence, fd->path)) return NULL;
609
610         ci = collection_info_new(fd, st, NULL);
611         if (ci) g_hash_table_insert(cd->existence, fd->path, "");
612         return ci;
613 }
614
615 gint collection_add_check(CollectionData *cd, FileData *fd, gint sorted, gint must_exist)
616 {
617         struct stat st;
618         gint valid;
619
620         if (must_exist)
621                 {
622                 valid = (stat_utf8(fd->path, &st) && !S_ISDIR(st.st_mode));
623                 }
624         else
625                 {
626                 valid = TRUE;
627                 st.st_size = 0;
628                 st.st_mtime = 0;
629                 }
630
631         if (valid)
632                 {
633                 CollectInfo *ci;
634
635                 ci = collection_info_new_if_not_exists(cd, &st, fd);
636                 if (!ci) return FALSE;
637                 DEBUG_3("add to collection: %s", fd->path);
638
639                 cd->list = collection_list_add(cd->list, ci, sorted ? cd->sort_method : SORT_NONE);
640                 cd->changed = TRUE;
641
642                 if (!sorted || cd->sort_method == SORT_NONE)
643                         {
644                         collection_window_add(collection_window_find(cd), ci);
645                         }
646                 else
647                         {
648                         collection_window_insert(collection_window_find(cd), ci);
649                         }
650                 }
651
652         return valid;
653 }
654
655 gint collection_add(CollectionData *cd, FileData *fd, gint sorted)
656 {
657         return collection_add_check(cd, fd, sorted, TRUE);
658 }
659
660 gint collection_insert(CollectionData *cd, FileData *fd, CollectInfo *insert_ci, gint sorted)
661 {
662         struct stat st;
663
664         if (!insert_ci) return collection_add(cd, fd, sorted);
665
666         if (stat_utf8(fd->path, &st) >= 0 && !S_ISDIR(st.st_mode))
667                 {
668                 CollectInfo *ci;
669
670                 ci = collection_info_new_if_not_exists(cd, &st, fd);
671                 if (!ci) return FALSE;
672
673                 DEBUG_3("insert in collection: %s", fd->path);
674
675                 cd->list = collection_list_insert(cd->list, ci, insert_ci, sorted ? cd->sort_method : SORT_NONE);
676                 cd->changed = TRUE;
677
678                 collection_window_insert(collection_window_find(cd), ci);
679
680                 return TRUE;
681                 }
682
683         return FALSE;
684 }
685
686 gint collection_remove(CollectionData *cd, FileData *fd)
687 {
688         CollectInfo *ci;
689
690         ci = collection_list_find_fd(cd->list, fd);
691
692         if (!ci) return FALSE;
693
694         g_hash_table_remove(cd->existence, fd->path);
695
696         cd->list = g_list_remove(cd->list, ci);
697         cd->changed = TRUE;
698
699         collection_window_remove(collection_window_find(cd), ci);
700         collection_info_free(ci);
701
702         return TRUE;
703 }
704
705 static void collection_remove_by_info(CollectionData *cd, CollectInfo *info)
706 {
707         if (!info || !g_list_find(cd->list, info)) return;
708
709         cd->list = g_list_remove(cd->list, info);
710         cd->changed = (cd->list != NULL);
711
712         collection_window_remove(collection_window_find(cd), info);
713         collection_info_free(info);
714 }
715
716 void collection_remove_by_info_list(CollectionData *cd, GList *list)
717 {
718         GList *work;
719
720         if (!list) return;
721
722         if (!list->next)
723                 {
724                 /* more efficient (in collect-table) to remove a single item this way */
725                 collection_remove_by_info(cd, (CollectInfo *)list->data);
726                 return;
727                 }
728
729         work = list;
730         while (work)
731                 {
732                 cd->list = collection_list_remove(cd->list, work->data);
733                 work = work->next;
734                 }
735         cd->changed = (cd->list != NULL);
736
737         collection_window_refresh(collection_window_find(cd));
738 }
739
740 gint collection_rename(CollectionData *cd, FileData *fd)
741 {
742         CollectInfo *ci;
743         ci = collection_list_find_fd(cd->list, fd);
744
745         if (!ci) return FALSE;
746
747         cd->changed = TRUE;
748
749         collection_window_update(collection_window_find(cd), ci);
750
751         return TRUE;
752 }
753
754 void collection_update_geometry(CollectionData *cd)
755 {
756         collection_window_get_geometry(collection_window_find(cd));
757 }
758
759 /*
760  *-------------------------------------------------------------------
761  * simple maintenance for renaming, deleting
762  *-------------------------------------------------------------------
763  */
764
765 static void collection_notify_cb(FileData *fd, NotifyType type, gpointer data)
766 {
767         CollectionData *cd = data;
768
769         if (type != NOTIFY_TYPE_CHANGE || !fd->change) return;
770         
771         switch(fd->change->type)
772                 {
773                 case FILEDATA_CHANGE_MOVE:
774                 case FILEDATA_CHANGE_RENAME:
775                         collection_rename(cd, fd);
776                         break;
777                 case FILEDATA_CHANGE_COPY:
778                         break;
779                 case FILEDATA_CHANGE_DELETE:
780                         while (collection_remove(cd, fd));
781                         break;
782                 case FILEDATA_CHANGE_UNSPECIFIED:
783                         break;
784                 }
785
786 }
787
788
789 /*
790  *-------------------------------------------------------------------
791  * window key presses
792  *-------------------------------------------------------------------
793  */
794
795 static gint collection_window_keypress(GtkWidget *widget, GdkEventKey *event, gpointer data)
796 {
797         CollectWindow *cw = data;
798         gint stop_signal = FALSE;
799         gint edit_val = -1;
800         GList *list;
801
802         if (event->state & GDK_CONTROL_MASK)
803                 {
804                 stop_signal = TRUE;
805                 switch (event->keyval)
806                         {
807                         case '1':
808                                 edit_val = 0;
809                                 break;
810                         case '2':
811                                 edit_val = 1;
812                                 break;
813                         case '3':
814                                 edit_val = 2;
815                                 break;
816                         case '4':
817                                 edit_val = 3;
818                                 break;
819                         case '5':
820                                 edit_val = 4;
821                                 break;
822                         case '6':
823                                 edit_val = 5;
824                                 break;
825                         case '7':
826                                 edit_val = 6;
827                                 break;
828                         case '8':
829                                 edit_val = 7;
830                                 break;
831                         case '9':
832                                 edit_val = 8;
833                                 break;
834                         case '0':
835                                 edit_val = 9;
836                                 break;
837                         case 'A': case 'a':
838                                 if (event->state & GDK_SHIFT_MASK)
839                                         {
840                                         collection_table_unselect_all(cw->table);
841                                         }
842                                 else
843                                         {
844                                         collection_table_select_all(cw->table);
845                                         }
846                                 break;
847                         case 'L': case 'l':
848                                 list = layout_list(NULL);
849                                 if (list)
850                                         {
851                                         collection_table_add_filelist(cw->table, list);
852                                         filelist_free(list);
853                                         }
854                                 break;
855                         case 'C': case 'c':
856                                 file_util_copy(NULL, collection_table_selection_get_list(cw->table), NULL, cw->window);
857                                 break;
858                         case 'M': case 'm':
859                                 file_util_move(NULL, collection_table_selection_get_list(cw->table), NULL, cw->window);
860                                 break;
861                         case 'R': case 'r':
862                                 file_util_rename(NULL, collection_table_selection_get_list(cw->table), cw->window);
863                                 break;
864                         case 'D': case 'd':
865                                 file_util_delete(NULL, collection_table_selection_get_list(cw->table), cw->window);
866                                 break;
867                         case 'P': case 'p':
868                                 info_window_new(NULL, collection_table_selection_get_list(cw->table), NULL);
869                                 break;
870                         case 'S': case 's':
871                                 collection_dialog_save_as(NULL, cw->cd);
872                                 break;
873                         case 'W': case 'w':
874                                 collection_window_close(cw);
875                                 break;
876                         default:
877                                 stop_signal = FALSE;
878                                 break;
879                         }
880                 }
881         else
882                 {
883                 stop_signal = TRUE;
884                 switch (event->keyval)
885                         {
886                         case GDK_Return: case GDK_KP_Enter:
887                                 layout_image_set_collection(NULL, cw->cd,
888                                         collection_table_get_focus_info(cw->table));
889                                 break;
890                         case 'V': case 'v':
891                                 view_window_new_from_collection(cw->cd,
892                                         collection_table_get_focus_info(cw->table));
893                                 break;
894                         case 'S': case 's':
895                                 if (!cw->cd->path)
896                                         {
897                                         collection_dialog_save_as(NULL, cw->cd);
898                                         }
899                                 else if (!collection_save(cw->cd, cw->cd->path))
900                                         {
901                                         log_printf("failed saving to collection path: %s\n", cw->cd->path);
902                                         }
903                                 break;
904                         case 'A': case 'a':
905                                 collection_dialog_append(NULL, cw->cd);
906                                 break;
907                         case 'N': case 'n':
908                                 collection_set_sort_method(cw->cd, SORT_NAME);
909                                 break;
910 #ifdef HAVE_STRVERSCMP
911                         case 'I': case 'i':
912                                 collection_set_sort_method(cw->cd, SORT_NUMBER);
913                                 break;
914 #endif
915                         case 'D': case 'd':
916                                 collection_set_sort_method(cw->cd, SORT_TIME);
917                                 break;
918                         case 'B': case 'b':
919                                 collection_set_sort_method(cw->cd, SORT_SIZE);
920                                 break;
921                         case 'P': case 'p':
922                                 if (event->state & GDK_SHIFT_MASK)
923                                         {
924                                         CollectInfo *info;
925
926                                         info = collection_table_get_focus_info(cw->table);
927
928                                         print_window_new(info->fd, collection_table_selection_get_list(cw->table),
929                                                          collection_list_to_filelist(cw->cd->list), cw->window);
930                                         }
931                                 else
932                                         {
933                                         collection_set_sort_method(cw->cd, SORT_PATH);
934                                         }
935                                 break;
936                         case GDK_Delete: case GDK_KP_Delete:
937                                 list = g_list_copy(cw->table->selection);
938                                 if (list)
939                                         {
940                                         collection_remove_by_info_list(cw->cd, list);
941                                         g_list_free(list);
942                                         }
943                                 else
944                                         {
945                                         collection_remove_by_info(cw->cd, collection_table_get_focus_info(cw->table));
946                                         }
947                                 break;
948                         default:
949                                 stop_signal = FALSE;
950                                 break;
951                         }
952                 }
953
954         if (edit_val != -1)
955                 {
956                 list = collection_table_selection_get_list(cw->table);
957                 file_util_start_editor_from_filelist(edit_val, list, cw->window);
958                 filelist_free(list);
959                 }
960
961         return stop_signal;
962 }
963
964 /*
965  *-------------------------------------------------------------------
966  * window
967  *-------------------------------------------------------------------
968  */
969 static void collection_window_get_geometry(CollectWindow *cw)
970 {
971         CollectionData *cd;
972
973         if (!cw) return;
974
975         cd = cw->cd;
976         gdk_window_get_position(cw->window->window, &cd->window_x, &cd->window_y);
977         gdk_drawable_get_size(cw->window->window, &cd->window_w, &cd->window_h);
978         cd->window_read = TRUE;
979 }
980
981 static void collection_window_refresh(CollectWindow *cw)
982 {
983         if (!cw) return;
984
985         collection_table_refresh(cw->table);
986 }
987
988 static void collection_window_update_title(CollectWindow *cw)
989 {
990         gchar *buf;
991
992         if (!cw) return;
993
994         buf = g_strdup_printf(_("%s - Collection - %s"), cw->cd->name, GQ_APPNAME);
995         gtk_window_set_title(GTK_WINDOW(cw->window), buf);
996         g_free(buf);
997 }
998
999 static void collection_window_update_info(CollectionData *cd, CollectInfo *ci, gpointer data)
1000 {
1001         CollectWindow *cw = data;
1002
1003         collection_table_file_update(cw->table, ci);
1004 }
1005
1006 static void collection_window_add(CollectWindow *cw, CollectInfo *ci)
1007 {
1008         if (!cw) return;
1009
1010         if (!ci->pixbuf) collection_load_thumb_idle(cw->cd);
1011         collection_table_file_add(cw->table, ci);
1012 }
1013
1014 static void collection_window_insert(CollectWindow *cw, CollectInfo *ci)
1015 {
1016         if (!cw) return;
1017
1018         if (!ci->pixbuf) collection_load_thumb_idle(cw->cd);
1019         collection_table_file_insert(cw->table, ci);
1020         if (!cw) return;
1021 }
1022
1023 #if 0
1024 static void collection_window_move(CollectWindow *cw, CollectInfo *ci)
1025 {
1026         if (!cw) return;
1027 }
1028 #endif
1029
1030 static void collection_window_remove(CollectWindow *cw, CollectInfo *ci)
1031 {
1032         if (!cw) return;
1033
1034         collection_table_file_remove(cw->table, ci);
1035 }
1036
1037 static void collection_window_update(CollectWindow *cw, CollectInfo *ci)
1038 {
1039         if (!cw) return;
1040
1041         collection_table_file_update(cw->table, ci);
1042         collection_table_file_update(cw->table, NULL);
1043 }
1044
1045 static void collection_window_close_final(CollectWindow *cw)
1046 {
1047         if (cw->close_dialog) return;
1048
1049         collection_window_list = g_list_remove(collection_window_list, cw);
1050         collection_window_get_geometry(cw);
1051
1052         gtk_widget_destroy(cw->window);
1053
1054         collection_set_update_info_func(cw->cd, NULL, NULL);
1055         collection_unref(cw->cd);
1056
1057         g_free(cw);
1058 }
1059
1060 static void collection_close_save_cb(GenericDialog *gd, gpointer data)
1061 {
1062         CollectWindow *cw = data;
1063
1064         cw->close_dialog = NULL;
1065         generic_dialog_close(gd);
1066
1067         if (!cw->cd->path)
1068                 {
1069                 collection_dialog_save_close(NULL, cw->cd);
1070                 return;
1071                 }
1072         else if (!collection_save(cw->cd, cw->cd->path))
1073                 {
1074                 gchar *buf;
1075                 buf = g_strdup_printf(_("Failed to save the collection:\n%s"), cw->cd->path);
1076                 warning_dialog(_("Save Failed"), buf, GTK_STOCK_DIALOG_ERROR, cw->window);
1077                 g_free(buf);
1078                 return;
1079                 }
1080
1081         collection_window_close_final(cw);
1082 }
1083
1084 static void collection_close_close_cb(GenericDialog *gd, gpointer data)
1085 {
1086         CollectWindow *cw = data;
1087
1088         cw->close_dialog = NULL;
1089         generic_dialog_close(gd);
1090
1091         collection_window_close_final(cw);
1092 }
1093
1094 static void collection_close_cancel_cb(GenericDialog *gd, gpointer data)
1095 {
1096         CollectWindow *cw = data;
1097
1098         cw->close_dialog = NULL;
1099         generic_dialog_close(gd);
1100 }
1101
1102 static void collection_close_dlg_show(CollectWindow *cw)
1103 {
1104         GenericDialog *gd;
1105
1106         if (cw->close_dialog)
1107                 {
1108                 gtk_window_present(GTK_WINDOW(cw->close_dialog));
1109                 return;
1110                 }
1111
1112         gd = generic_dialog_new(_("Close collection"),
1113                                 GQ_WMCLASS, "close_collection", cw->window, FALSE,
1114                                 collection_close_cancel_cb, cw);
1115         generic_dialog_add_message(gd, GTK_STOCK_DIALOG_QUESTION,
1116                                    _("Close collection"),
1117                                    _("Collection has been modified.\nSave first?"));
1118
1119         generic_dialog_add_button(gd, GTK_STOCK_SAVE, NULL, collection_close_save_cb, TRUE);
1120         generic_dialog_add_button(gd, GTK_STOCK_DELETE, _("_Discard"), collection_close_close_cb, FALSE);
1121
1122         cw->close_dialog = gd->dialog;
1123
1124         gtk_widget_show(gd->dialog);
1125 }
1126
1127 static void collection_window_close(CollectWindow *cw)
1128 {
1129         if (!cw->cd->changed && !cw->close_dialog)
1130                 {
1131                 collection_window_close_final(cw);
1132                 return;
1133                 }
1134
1135         collection_close_dlg_show(cw);
1136 }
1137
1138 void collection_window_close_by_collection(CollectionData *cd)
1139 {
1140         CollectWindow *cw;
1141
1142         cw = collection_window_find(cd);
1143         if (cw) collection_window_close_final(cw);
1144 }
1145
1146 gint collection_window_modified_exists(void)
1147 {
1148         GList *work;
1149
1150         work = collection_window_list;
1151         while (work)
1152                 {
1153                 CollectWindow *cw = work->data;
1154                 if (cw->cd->changed) return TRUE;
1155                 work = work->next;
1156                 }
1157
1158         return FALSE;
1159 }
1160
1161 static gint collection_window_delete(GtkWidget *widget, GdkEvent *event, gpointer data)
1162 {
1163         CollectWindow *cw = data;
1164         collection_window_close(cw);
1165
1166         return TRUE;
1167 }
1168
1169 CollectWindow *collection_window_new(const gchar *path)
1170 {
1171         CollectWindow *cw;
1172         GtkWidget *vbox;
1173         GtkWidget *frame;
1174         GtkWidget *status_label;
1175         GtkWidget *extra_label;
1176         GdkGeometry geometry;
1177
1178         cw = g_new0(CollectWindow, 1);
1179
1180         cw->close_dialog = NULL;
1181
1182         collection_window_list = g_list_append(collection_window_list, cw);
1183
1184         cw->cd = collection_new(path);
1185
1186         cw->window = window_new(GTK_WINDOW_TOPLEVEL, "collection", PIXBUF_INLINE_ICON_BOOK, NULL, NULL);
1187
1188         geometry.min_width = 32;
1189         geometry.min_height = 32;
1190         geometry.base_width = COLLECT_DEF_WIDTH;
1191         geometry.base_height = COLLECT_DEF_HEIGHT;
1192         gtk_window_set_geometry_hints(GTK_WINDOW(cw->window), NULL, &geometry,
1193                                       GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE);
1194
1195
1196         if (options->layout.save_window_positions && path && collection_load_only_geometry(cw->cd, path))
1197                 {
1198                 /* FIXME: x, y is not implemented */
1199                 gtk_window_set_default_size(GTK_WINDOW(cw->window), cw->cd->window_w, cw->cd->window_h);
1200                 }
1201         else
1202                 {
1203                 gtk_window_set_default_size(GTK_WINDOW(cw->window), COLLECT_DEF_WIDTH, COLLECT_DEF_HEIGHT);
1204                 }
1205
1206         gtk_window_set_resizable(GTK_WINDOW(cw->window), TRUE);
1207         collection_window_update_title(cw);
1208         gtk_container_set_border_width(GTK_CONTAINER(cw->window), 0);
1209
1210         g_signal_connect(G_OBJECT(cw->window), "delete_event",
1211                          G_CALLBACK(collection_window_delete), cw);
1212
1213         g_signal_connect(G_OBJECT(cw->window), "key_press_event",
1214                          G_CALLBACK(collection_window_keypress), cw);
1215
1216         vbox = gtk_vbox_new(FALSE, 0);
1217         gtk_container_add(GTK_CONTAINER(cw->window), vbox);
1218         gtk_widget_show(vbox);
1219
1220         cw->table = collection_table_new(cw->cd);
1221         gtk_box_pack_start(GTK_BOX(vbox), cw->table->scrolled, TRUE, TRUE, 0);
1222         gtk_widget_show(cw->table->scrolled);
1223
1224         cw->status_box = gtk_hbox_new(TRUE, 0);
1225         gtk_box_pack_start(GTK_BOX(vbox), cw->status_box, FALSE, FALSE, 0);
1226         gtk_widget_show(cw->status_box);
1227
1228         frame = gtk_frame_new(NULL);
1229         gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
1230         gtk_box_pack_start(GTK_BOX(cw->status_box), frame, TRUE, TRUE, 0);
1231         gtk_widget_show(frame);
1232
1233         status_label = gtk_label_new("");
1234         gtk_container_add(GTK_CONTAINER(frame), status_label);
1235         gtk_widget_show(status_label);
1236
1237         extra_label = gtk_progress_bar_new();
1238         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(extra_label), 0.0);
1239         gtk_box_pack_start(GTK_BOX(cw->status_box), extra_label, TRUE, TRUE, 0);
1240         gtk_widget_show(extra_label);
1241
1242         collection_table_set_labels(cw->table, status_label, extra_label);
1243
1244         gtk_widget_show(cw->window);
1245         gtk_widget_grab_focus(cw->table->listview);
1246
1247         collection_set_update_info_func(cw->cd, collection_window_update_info, cw);
1248
1249         if (path && *path == G_DIR_SEPARATOR) collection_load_begin(cw->cd, NULL, COLLECTION_LOAD_NONE);
1250
1251         return cw;
1252 }