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