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