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