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