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