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