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