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