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