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