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