1 /*
2 * Copyright (C) 2006 John Ellis
3 * Copyright (C) 2008 - 2016 The Geeqie Team
4 *
5 * Author: John Ellis
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 */
21
22 #include "main.h"
23 #include "collect.h"
24
25 #include "collect-dlg.h"
26 #include "collect-io.h"
27 #include "collect-table.h"
28 #include "editors.h"
29 #include "filedata.h"
30 #include "img-view.h"
31 #include "layout.h"
32 #include "layout_image.h"
33 #include "layout_util.h"
34 #include "misc.h"
35 #include "pixbuf_util.h"
36 #include "print.h"
37 #include "ui_fileops.h"
38 #include "ui_tree_edit.h"
39 #include "utilops.h"
40 #include "window.h"
41
42 #include <gdk/gdkkeysyms.h> /* for keyboard values */
43
44
45 #define COLLECT_DEF_WIDTH 440
46 #define COLLECT_DEF_HEIGHT 450
47
48 /**
49 * list of paths to collections */
50
51 /**
52 * @brief List of currently open Collections.
53 *
54 * Type ::_CollectionData
55 */
56 static GList *collection_list = NULL;
57
58 /**
59 * @brief List of currently open Collection windows.
60 *
61 * Type ::_CollectWindow
62 */
63 static GList *collection_window_list = NULL;
64
65 static void collection_window_get_geometry(CollectWindow *cw);
66 static void collection_window_refresh(CollectWindow *cw);
67 static void collection_window_update_title(CollectWindow *cw);
68 static void collection_window_add(CollectWindow *cw, CollectInfo *ci);
69 static void collection_window_insert(CollectWindow *cw, CollectInfo *ci);
70 static void collection_window_remove(CollectWindow *cw, CollectInfo *ci);
71 static void collection_window_update(CollectWindow *cw, CollectInfo *ci);
72
73 static void collection_window_close(CollectWindow *cw);
74
75 static void collection_notify_cb(FileData *fd, NotifyType type, gpointer data);
76
77 /*
78 *-------------------------------------------------------------------
79 * data, list handling
80 *-------------------------------------------------------------------
81 */
82
collection_info_new(FileData * fd,struct stat * st,GdkPixbuf * pixbuf)83 CollectInfo *collection_info_new(FileData *fd, struct stat *st, GdkPixbuf *pixbuf)
84 {
85 CollectInfo *ci;
86
87 if (!fd) return NULL;
88
89 ci = g_new0(CollectInfo, 1);
90 ci->fd = file_data_ref(fd);
91
92 ci->pixbuf = pixbuf;
93 if (ci->pixbuf) g_object_ref(ci->pixbuf);
94
95 return ci;
96 }
97
collection_info_free_thumb(CollectInfo * ci)98 void collection_info_free_thumb(CollectInfo *ci)
99 {
100 if (ci->pixbuf) g_object_unref(ci->pixbuf);
101 ci->pixbuf = NULL;
102 }
103
collection_info_free(CollectInfo * ci)104 void collection_info_free(CollectInfo *ci)
105 {
106 if (!ci) return;
107
108 file_data_unref(ci->fd);
109 collection_info_free_thumb(ci);
110 g_free(ci);
111 }
112
collection_info_set_thumb(CollectInfo * ci,GdkPixbuf * pixbuf)113 void collection_info_set_thumb(CollectInfo *ci, GdkPixbuf *pixbuf)
114 {
115 if (pixbuf) g_object_ref(pixbuf);
116 collection_info_free_thumb(ci);
117 ci->pixbuf = pixbuf;
118 }
119
collection_info_load_thumb(CollectInfo * ci)120 gboolean collection_info_load_thumb(CollectInfo *ci)
121 {
122 if (!ci) return FALSE;
123
124 collection_info_free_thumb(ci);
125
126 log_printf("collection_info_load_thumb not implemented!\n(because an instant thumb loader not implemented)");
127 return FALSE;
128 }
129
collection_list_free(GList * list)130 void collection_list_free(GList *list)
131 {
132 GList *work;
133 work = list;
134 while (work)
135 {
136 collection_info_free((CollectInfo *)work->data);
137 work = work->next;
138 }
139 g_list_free(list);
140 }
141
142 /* an ugly static var, well what ya gonna do ? */
143 static SortType collection_list_sort_method = SORT_NAME;
144
collection_list_sort_cb(gconstpointer a,gconstpointer b)145 static gint collection_list_sort_cb(gconstpointer a, gconstpointer b)
146 {
147 const CollectInfo *cia = a;
148 const CollectInfo *cib = b;
149
150 switch (collection_list_sort_method)
151 {
152 case SORT_NAME:
153 break;
154 case SORT_NONE:
155 return 0;
156 break;
157 case SORT_SIZE:
158 if (cia->fd->size < cib->fd->size) return -1;
159 if (cia->fd->size > cib->fd->size) return 1;
160 return 0;
161 break;
162 case SORT_TIME:
163 if (cia->fd->date < cib->fd->date) return -1;
164 if (cia->fd->date > cib->fd->date) return 1;
165 return 0;
166 break;
167 case SORT_CTIME:
168 if (cia->fd->cdate < cib->fd->cdate) return -1;
169 if (cia->fd->cdate > cib->fd->cdate) return 1;
170 return 0;
171 break;
172 case SORT_EXIFTIME:
173 if (cia->fd->exifdate < cib->fd->exifdate) return -1;
174 if (cia->fd->exifdate > cib->fd->exifdate) return 1;
175 break;
176 case SORT_EXIFTIMEDIGITIZED:
177 if (cia->fd->exifdate_digitized < cib->fd->exifdate_digitized) return -1;
178 if (cia->fd->exifdate_digitized > cib->fd->exifdate_digitized) return 1;
179 break;
180 case SORT_RATING:
181 if (cia->fd->rating < cib->fd->rating) return -1;
182 if (cia->fd->rating > cib->fd->rating) return 1;
183 break;
184 case SORT_PATH:
185 return utf8_compare(cia->fd->path, cib->fd->path, options->file_sort.case_sensitive);
186 break;
187 case SORT_CLASS:
188 if (cia->fd->format_class < cib->fd->format_class) return -1;
189 if (cia->fd->format_class > cib->fd->format_class) return 1;
190 break;
191 #ifdef HAVE_STRVERSCMP
192 case SORT_NUMBER:
193 return strverscmp(cia->fd->name, cib->fd->name);
194 break;
195 #endif
196 default:
197 break;
198 }
199
200 if (options->file_sort.case_sensitive)
201 return strcmp(cia->fd->collate_key_name, cib->fd->collate_key_name);
202 else
203 return strcmp(cia->fd->collate_key_name_nocase, cib->fd->collate_key_name_nocase);
204 }
205
collection_list_sort(GList * list,SortType method)206 GList *collection_list_sort(GList *list, SortType method)
207 {
208 if (method == SORT_NONE) return list;
209
210 collection_list_sort_method = method;
211
212 return g_list_sort(list, collection_list_sort_cb);
213 }
214
collection_list_randomize(GList * list)215 GList *collection_list_randomize(GList *list)
216 {
217 guint random, length, i;
218 gpointer tmp;
219 GList *nlist, *olist;
220
221 length = g_list_length(list);
222 if (!length) return NULL;
223
224 srand((unsigned int)time(NULL)); // Initialize random generator (hasn't to be that much strong)
225
226 for (i = 0; i < length; i++)
227 {
228 random = (guint) (1.0 * length * rand()/(RAND_MAX + 1.0));
229 olist = g_list_nth(list, i);
230 nlist = g_list_nth(list, random);
231 tmp = olist->data;
232 olist->data = nlist->data;
233 nlist->data = tmp;
234 }
235
236 return list;
237 }
238
collection_list_add(GList * list,CollectInfo * ci,SortType method)239 GList *collection_list_add(GList *list, CollectInfo *ci, SortType method)
240 {
241 if (method != SORT_NONE)
242 {
243 collection_list_sort_method = method;
244 list = g_list_insert_sorted(list, ci, collection_list_sort_cb);
245 }
246 else
247 {
248 list = g_list_append(list, ci);
249 }
250
251 return list;
252 }
253
collection_list_insert(GList * list,CollectInfo * ci,CollectInfo * insert_ci,SortType method)254 GList *collection_list_insert(GList *list, CollectInfo *ci, CollectInfo *insert_ci, SortType method)
255 {
256 if (method != SORT_NONE)
257 {
258 collection_list_sort_method = method;
259 list = g_list_insert_sorted(list, ci, collection_list_sort_cb);
260 }
261 else
262 {
263 GList *point;
264
265 point = g_list_find(list, insert_ci);
266 list = uig_list_insert_link(list, point, ci);
267 }
268
269 return list;
270 }
271
collection_list_remove(GList * list,CollectInfo * ci)272 GList *collection_list_remove(GList *list, CollectInfo *ci)
273 {
274 list = g_list_remove(list, ci);
275 collection_info_free(ci);
276 return list;
277 }
278
collection_list_find_fd(GList * list,FileData * fd)279 CollectInfo *collection_list_find_fd(GList *list, FileData *fd)
280 {
281 GList *work = list;
282
283 while (work)
284 {
285 CollectInfo *ci = work->data;
286 if (ci->fd == fd) return ci;
287 work = work->next;
288 }
289
290 return NULL;
291 }
292
collection_list_to_filelist(GList * list)293 GList *collection_list_to_filelist(GList *list)
294 {
295 GList *filelist = NULL;
296 GList *work = list;
297
298 while (work)
299 {
300 CollectInfo *info = work->data;
301 filelist = g_list_prepend(filelist, file_data_ref(info->fd));
302 work = work->next;
303 }
304
305 filelist = g_list_reverse(filelist);
306 return filelist;
307 }
308
collection_window_find(CollectionData * cd)309 CollectWindow *collection_window_find(CollectionData *cd)
310 {
311 GList *work;
312
313 work = collection_window_list;
314 while (work)
315 {
316 CollectWindow *cw = work->data;
317 if (cw->cd == cd) return cw;
318 work = work->next;
319 }
320
321 return NULL;
322 }
323
collection_window_find_by_path(const gchar * path)324 CollectWindow *collection_window_find_by_path(const gchar *path)
325 {
326 GList *work;
327
328 if (!path) return NULL;
329
330 work = collection_window_list;
331 while (work)
332 {
333 CollectWindow *cw = work->data;
334 if (cw->cd->path && strcmp(cw->cd->path, path) == 0) return cw;
335 work = work->next;
336 }
337
338 return NULL;
339 }
340
341 /**
342 * @brief Checks string for existence of Collection.
343 * @param[in] param Filename, with or without extension of any collection
344 * @returns full pathname if found or NULL
345 *
346 * Return value must be freed with g_free()
347 */
collection_path(const gchar * param)348 gchar *collection_path(const gchar *param)
349 {
350 gchar *path = NULL;
351 gchar *full_name = NULL;
352
353 if (file_extension_match(param, GQ_COLLECTION_EXT))
354 {
355 path = g_build_filename(get_collections_dir(), param, NULL);
356 }
357 else if (file_extension_match(param, NULL))
358 {
359 full_name = g_strconcat(param, GQ_COLLECTION_EXT, NULL);
360 path = g_build_filename(get_collections_dir(), full_name, NULL);
361 }
362
363 if (!isfile(path))
364 {
365 g_free(path);
366 path = NULL;
367 }
368
369 g_free(full_name);
370 return path;
371 }
372
373 /**
374 * @brief Checks input string for existence of Collection.
375 * @param[in] param Filename with or without extension of any collection
376 * @returns TRUE if found
377 *
378 *
379 */
is_collection(const gchar * param)380 gboolean is_collection(const gchar *param)
381 {
382 gchar *name = NULL;
383
384 name = collection_path(param);
385 if (name)
386 {
387 g_free(name);
388 return TRUE;
389 }
390 return FALSE;
391 }
392
393 /**
394 * @brief Creates a text list of the image paths of the contents of a Collection
395 * @param[in] name The name of the collection, with or wihout extension
396 * @param[inout] contents A GString to which the image paths are appended
397 *
398 *
399 */
collection_contents(const gchar * name,GString ** contents)400 void collection_contents(const gchar *name, GString **contents)
401 {
402 gchar *path;
403 CollectionData *cd;
404 CollectInfo *ci;
405 GList *work;
406 FileData *fd;
407
408 if (is_collection(name))
409 {
410 path = collection_path(name);
411 cd = collection_new("");
412 collection_load(cd, path, COLLECTION_LOAD_APPEND);
413 work = cd->list;
414 while (work)
415 {
416 ci = work->data;
417 fd = ci->fd;
418 *contents = g_string_append(*contents, g_strdup(fd->path));
419 *contents = g_string_append(*contents, "\n");
420
421 work = work->next;
422 }
423 g_free(path);
424 collection_free(cd);
425 }
426 }
427
428 /**
429 * @brief Returns a list of filedatas of the contents of a Collection
430 * @param[in] name The name of the collection, with or wihout extension
431 *
432 *
433 */
collection_contents_fd(const gchar * name)434 GList *collection_contents_fd(const gchar *name)
435 {
436 gchar *path;
437 CollectionData *cd;
438 CollectInfo *ci;
439 GList *work;
440 FileData *fd;
441 GList *list = NULL;
442
443 if (is_collection(name))
444 {
445 path = collection_path(name);
446 cd = collection_new("");
447 collection_load(cd, path, COLLECTION_LOAD_APPEND);
448 work = cd->list;
449 while (work)
450 {
451 ci = work->data;
452 fd = ci->fd;
453 list = g_list_append(list, ci->fd);
454
455 work = work->next;
456 }
457 g_free(path);
458 collection_free(cd);
459 }
460
461 return list;
462 }
463
464 /*
465 *-------------------------------------------------------------------
466 * please use these to actually add/remove stuff
467 *-------------------------------------------------------------------
468 */
469
collection_new(const gchar * path)470 CollectionData *collection_new(const gchar *path)
471 {
472 CollectionData *cd;
473 static gint untitled_counter = 0;
474
475 cd = g_new0(CollectionData, 1);
476
477 cd->ref = 1; /* starts with a ref of 1 */
478 cd->sort_method = SORT_NONE;
479 cd->window_w = COLLECT_DEF_WIDTH;
480 cd->window_h = COLLECT_DEF_HEIGHT;
481 cd->existence = g_hash_table_new(NULL, NULL);
482
483 if (path)
484 {
485 cd->path = g_strdup(path);
486 cd->name = g_strdup(filename_from_path(cd->path));
487 /* load it */
488 }
489 else
490 {
491 if (untitled_counter == 0)
492 {
493 cd->name = g_strdup(_("Untitled"));
494 }
495 else
496 {
497 cd->name = g_strdup_printf(_("Untitled (%d)"), untitled_counter + 1);
498 }
499
500 untitled_counter++;
501 }
502
503 file_data_register_notify_func(collection_notify_cb, cd, NOTIFY_PRIORITY_MEDIUM);
504
505
506 collection_list = g_list_append(collection_list, cd);
507
508 return cd;
509 }
510
collection_free(CollectionData * cd)511 void collection_free(CollectionData *cd)
512 {
513 if (!cd) return;
514
515 DEBUG_1("collection \"%s\" freed", cd->name);
516
517 collection_load_stop(cd);
518 collection_list_free(cd->list);
519
520 file_data_unregister_notify_func(collection_notify_cb, cd);
521
522 collection_list = g_list_remove(collection_list, cd);
523
524 g_hash_table_destroy(cd->existence);
525
526 g_free(cd->path);
527 g_free(cd->name);
528
529 g_free(cd);
530 }
531
collection_ref(CollectionData * cd)532 void collection_ref(CollectionData *cd)
533 {
534 cd->ref++;
535
536 DEBUG_1("collection \"%s\" ref count = %d", cd->name, cd->ref);
537 }
538
collection_unref(CollectionData * cd)539 void collection_unref(CollectionData *cd)
540 {
541 cd->ref--;
542
543 DEBUG_1("collection \"%s\" ref count = %d", cd->name, cd->ref);
544
545 if (cd->ref < 1)
546 {
547 collection_free(cd);
548 }
549 }
550
collection_path_changed(CollectionData * cd)551 void collection_path_changed(CollectionData *cd)
552 {
553 collection_window_update_title(collection_window_find(cd));
554 }
555
collection_to_number(CollectionData * cd)556 gint collection_to_number(CollectionData *cd)
557 {
558 return g_list_index(collection_list, cd);
559 }
560
collection_from_number(gint n)561 CollectionData *collection_from_number(gint n)
562 {
563 return g_list_nth_data(collection_list, n);
564 }
565
collection_from_dnd_data(const gchar * data,GList ** list,GList ** info_list)566 CollectionData *collection_from_dnd_data(const gchar *data, GList **list, GList **info_list)
567 {
568 CollectionData *cd;
569 gint collection_number;
570 const gchar *ptr;
571
572 if (list) *list = NULL;
573 if (info_list) *info_list = NULL;
574
575 if (strncmp(data, "COLLECTION:", 11) != 0) return NULL;
576
577 ptr = data + 11;
578
579 collection_number = atoi(ptr);
580 cd = collection_from_number(collection_number);
581 if (!cd) return NULL;
582
583 if (!list && !info_list) return cd;
584
585 while (*ptr != '\0' && *ptr != '\n' ) ptr++;
586 if (*ptr == '\0') return cd;
587 ptr++;
588
589 while (*ptr != '\0')
590 {
591 guint item_number;
592 CollectInfo *info;
593
594 item_number = (guint) atoi(ptr);
595 while (*ptr != '\n' && *ptr != '\0') ptr++;
596 if (*ptr == '\0')
597 break;
598 else
599 while (*ptr == '\n') ptr++;
600
601 info = g_list_nth_data(cd->list, item_number);
602 if (!info) continue;
603
604 if (list) *list = g_list_append(*list, file_data_ref(info->fd));
605 if (info_list) *info_list = g_list_append(*info_list, info);
606 }
607
608 return cd;
609 }
610
collection_info_list_to_dnd_data(CollectionData * cd,GList * list,gint * length)611 gchar *collection_info_list_to_dnd_data(CollectionData *cd, GList *list, gint *length)
612 {
613 GList *work;
614 GList *temp = NULL;
615 gchar *ptr;
616 gchar *text;
617 gchar *uri_text;
618 gint collection_number;
619
620 *length = 0;
621 if (!list) return NULL;
622
623 collection_number = collection_to_number(cd);
624 if (collection_number < 0) return NULL;
625
626 text = g_strdup_printf("COLLECTION:%d\n", collection_number);
627 *length += strlen(text);
628 temp = g_list_prepend(temp, text);
629
630 work = list;
631 while (work)
632 {
633 gint item_number = g_list_index(cd->list, work->data);
634
635 work = work->next;
636
637 if (item_number < 0) continue;
638
639 text = g_strdup_printf("%d\n", item_number);
640 temp = g_list_prepend(temp, text);
641 *length += strlen(text);
642 }
643
644 *length += 1; /* ending nul char */
645
646 uri_text = g_malloc(*length);
647 ptr = uri_text;
648
649 work = g_list_last(temp);
650 while (work)
651 {
652 gint len;
653 gchar *text = work->data;
654
655 work = work->prev;
656
657 len = strlen(text);
658 memcpy(ptr, text, len);
659 ptr += len;
660 }
661
662 ptr[0] = '\0';
663
664 string_list_free(temp);
665
666 return uri_text;
667 }
668
collection_info_valid(CollectionData * cd,CollectInfo * info)669 gint collection_info_valid(CollectionData *cd, CollectInfo *info)
670 {
671 if (collection_to_number(cd) < 0) return FALSE;
672
673 return (g_list_index(cd->list, info) != 0);
674 }
675
collection_next_by_info(CollectionData * cd,CollectInfo * info)676 CollectInfo *collection_next_by_info(CollectionData *cd, CollectInfo *info)
677 {
678 GList *work;
679
680 work = g_list_find(cd->list, info);
681
682 if (!work) return NULL;
683 work = work->next;
684 if (work) return work->data;
685 return NULL;
686 }
687
collection_prev_by_info(CollectionData * cd,CollectInfo * info)688 CollectInfo *collection_prev_by_info(CollectionData *cd, CollectInfo *info)
689 {
690 GList *work;
691
692 work = g_list_find(cd->list, info);
693
694 if (!work) return NULL;
695 work = work->prev;
696 if (work) return work->data;
697 return NULL;
698 }
699
collection_get_first(CollectionData * cd)700 CollectInfo *collection_get_first(CollectionData *cd)
701 {
702 if (cd->list) return cd->list->data;
703
704 return NULL;
705 }
706
collection_get_last(CollectionData * cd)707 CollectInfo *collection_get_last(CollectionData *cd)
708 {
709 GList *list;
710
711 list = g_list_last(cd->list);
712
713 if (list) return list->data;
714
715 return NULL;
716 }
717
collection_set_sort_method(CollectionData * cd,SortType method)718 void collection_set_sort_method(CollectionData *cd, SortType method)
719 {
720 if (!cd) return;
721
722 if (cd->sort_method == method) return;
723
724 cd->sort_method = method;
725 cd->list = collection_list_sort(cd->list, cd->sort_method);
726 if (cd->list) cd->changed = TRUE;
727
728 collection_window_refresh(collection_window_find(cd));
729 }
730
collection_randomize(CollectionData * cd)731 void collection_randomize(CollectionData *cd)
732 {
733 if (!cd) return;
734
735 cd->list = collection_list_randomize(cd->list);
736 cd->sort_method = SORT_NONE;
737 if (cd->list) cd->changed = TRUE;
738
739 collection_window_refresh(collection_window_find(cd));
740 }
741
collection_set_update_info_func(CollectionData * cd,void (* func)(CollectionData *,CollectInfo *,gpointer),gpointer data)742 void collection_set_update_info_func(CollectionData *cd,
743 void (*func)(CollectionData *, CollectInfo *, gpointer), gpointer data)
744 {
745 cd->info_updated_func = func;
746 cd->info_updated_data = data;
747 }
748
collection_info_new_if_not_exists(CollectionData * cd,struct stat * st,FileData * fd)749 static CollectInfo *collection_info_new_if_not_exists(CollectionData *cd, struct stat *st, FileData *fd)
750 {
751 CollectInfo *ci;
752
753 if (g_hash_table_lookup(cd->existence, fd->path)) return NULL;
754
755 ci = collection_info_new(fd, st, NULL);
756 if (ci) g_hash_table_insert(cd->existence, fd->path, "");
757 return ci;
758 }
759
collection_add_check(CollectionData * cd,FileData * fd,gboolean sorted,gboolean must_exist)760 gboolean collection_add_check(CollectionData *cd, FileData *fd, gboolean sorted, gboolean must_exist)
761 {
762 struct stat st;
763 gboolean valid;
764
765 if (!fd) return FALSE;
766
767 g_assert(fd->magick == FD_MAGICK);
768
769 if (must_exist)
770 {
771 valid = (stat_utf8(fd->path, &st) && !S_ISDIR(st.st_mode));
772 }
773 else
774 {
775 valid = TRUE;
776 st.st_size = 0;
777 st.st_mtime = 0;
778 }
779
780 if (valid)
781 {
782 CollectInfo *ci;
783
784 ci = collection_info_new_if_not_exists(cd, &st, fd);
785 if (!ci) return FALSE;
786 DEBUG_3("add to collection: %s", fd->path);
787
788 cd->list = collection_list_add(cd->list, ci, sorted ? cd->sort_method : SORT_NONE);
789 cd->changed = TRUE;
790
791 if (!sorted || cd->sort_method == SORT_NONE)
792 {
793 collection_window_add(collection_window_find(cd), ci);
794 }
795 else
796 {
797 collection_window_insert(collection_window_find(cd), ci);
798 }
799 }
800
801 return valid;
802 }
803
collection_add(CollectionData * cd,FileData * fd,gboolean sorted)804 gboolean collection_add(CollectionData *cd, FileData *fd, gboolean sorted)
805 {
806 return collection_add_check(cd, fd, sorted, TRUE);
807 }
808
collection_insert(CollectionData * cd,FileData * fd,CollectInfo * insert_ci,gboolean sorted)809 gboolean collection_insert(CollectionData *cd, FileData *fd, CollectInfo *insert_ci, gboolean sorted)
810 {
811 struct stat st;
812
813 if (!insert_ci) return collection_add(cd, fd, sorted);
814
815 if (stat_utf8(fd->path, &st) >= 0 && !S_ISDIR(st.st_mode))
816 {
817 CollectInfo *ci;
818
819 ci = collection_info_new_if_not_exists(cd, &st, fd);
820 if (!ci) return FALSE;
821
822 DEBUG_3("insert in collection: %s", fd->path);
823
824 cd->list = collection_list_insert(cd->list, ci, insert_ci, sorted ? cd->sort_method : SORT_NONE);
825 cd->changed = TRUE;
826
827 collection_window_insert(collection_window_find(cd), ci);
828
829 return TRUE;
830 }
831
832 return FALSE;
833 }
834
collection_remove(CollectionData * cd,FileData * fd)835 gboolean collection_remove(CollectionData *cd, FileData *fd)
836 {
837 CollectInfo *ci;
838
839 ci = collection_list_find_fd(cd->list, fd);
840
841 if (!ci) return FALSE;
842
843 g_hash_table_remove(cd->existence, fd->path);
844
845 cd->list = g_list_remove(cd->list, ci);
846 cd->changed = TRUE;
847
848 collection_window_remove(collection_window_find(cd), ci);
849 collection_info_free(ci);
850
851 return TRUE;
852 }
853
collection_remove_by_info(CollectionData * cd,CollectInfo * info)854 static void collection_remove_by_info(CollectionData *cd, CollectInfo *info)
855 {
856 if (!info || !g_list_find(cd->list, info)) return;
857
858 cd->list = g_list_remove(cd->list, info);
859 cd->changed = (cd->list != NULL);
860
861 collection_window_remove(collection_window_find(cd), info);
862 collection_info_free(info);
863 }
864
collection_remove_by_info_list(CollectionData * cd,GList * list)865 void collection_remove_by_info_list(CollectionData *cd, GList *list)
866 {
867 GList *work;
868
869 if (!list) return;
870
871 if (!list->next)
872 {
873 /* more efficient (in collect-table) to remove a single item this way */
874 collection_remove_by_info(cd, (CollectInfo *)list->data);
875 return;
876 }
877
878 work = list;
879 while (work)
880 {
881 cd->list = collection_list_remove(cd->list, work->data);
882 work = work->next;
883 }
884 cd->changed = (cd->list != NULL);
885
886 collection_window_refresh(collection_window_find(cd));
887 }
888
collection_rename(CollectionData * cd,FileData * fd)889 gboolean collection_rename(CollectionData *cd, FileData *fd)
890 {
891 CollectInfo *ci;
892 ci = collection_list_find_fd(cd->list, fd);
893
894 if (!ci) return FALSE;
895
896 cd->changed = TRUE;
897
898 collection_window_update(collection_window_find(cd), ci);
899
900 return TRUE;
901 }
902
collection_update_geometry(CollectionData * cd)903 void collection_update_geometry(CollectionData *cd)
904 {
905 collection_window_get_geometry(collection_window_find(cd));
906 }
907
908 /*
909 *-------------------------------------------------------------------
910 * simple maintenance for renaming, deleting
911 *-------------------------------------------------------------------
912 */
913
collection_notify_cb(FileData * fd,NotifyType type,gpointer data)914 static void collection_notify_cb(FileData *fd, NotifyType type, gpointer data)
915 {
916 CollectionData *cd = data;
917
918 if (!(type & NOTIFY_CHANGE) || !fd->change) return;
919
920 DEBUG_1("Notify collection: %s %04x", fd->path, type);
921
922 switch (fd->change->type)
923 {
924 case FILEDATA_CHANGE_MOVE:
925 case FILEDATA_CHANGE_RENAME:
926 collection_rename(cd, fd);
927 break;
928 case FILEDATA_CHANGE_COPY:
929 break;
930 case FILEDATA_CHANGE_DELETE:
931 while (collection_remove(cd, fd));
932 break;
933 case FILEDATA_CHANGE_UNSPECIFIED:
934 case FILEDATA_CHANGE_WRITE_METADATA:
935 break;
936 }
937
938 }
939
940
941 /*
942 *-------------------------------------------------------------------
943 * window key presses
944 *-------------------------------------------------------------------
945 */
946
collection_window_keypress(GtkWidget * widget,GdkEventKey * event,gpointer data)947 static gboolean collection_window_keypress(GtkWidget *widget, GdkEventKey *event, gpointer data)
948 {
949 CollectWindow *cw = data;
950 gboolean stop_signal = FALSE;
951 GList *list;
952
953 if (event->state & GDK_CONTROL_MASK)
954 {
955 stop_signal = TRUE;
956 switch (event->keyval)
957 {
958 case '1':
959 case '2':
960 case '3':
961 case '4':
962 case '5':
963 case '6':
964 case '7':
965 case '8':
966 case '9':
967 case '0':
968 break;
969 case 'A': case 'a':
970 if (event->state & GDK_SHIFT_MASK)
971 {
972 collection_table_unselect_all(cw->table);
973 }
974 else
975 {
976 collection_table_select_all(cw->table);
977 }
978 break;
979 case 'L': case 'l':
980 list = layout_list(NULL);
981 if (list)
982 {
983 collection_table_add_filelist(cw->table, list);
984 filelist_free(list);
985 }
986 break;
987 case 'C': case 'c':
988 file_util_copy(NULL, collection_table_selection_get_list(cw->table), NULL, cw->window);
989 break;
990 case 'M': case 'm':
991 file_util_move(NULL, collection_table_selection_get_list(cw->table), NULL, cw->window);
992 break;
993 case 'R': case 'r':
994 file_util_rename(NULL, collection_table_selection_get_list(cw->table), cw->window);
995 break;
996 case 'D': case 'd':
997 options->file_ops.safe_delete_enable = TRUE;
998 file_util_delete(NULL, collection_table_selection_get_list(cw->table), cw->window);
999 break;
1000 case 'S': case 's':
1001 collection_dialog_save_as(NULL, cw->cd);
1002 break;
1003 case 'W': case 'w':
1004 collection_window_close(cw);
1005 break;
1006 default:
1007 stop_signal = FALSE;
1008 break;
1009 }
1010 }
1011 else
1012 {
1013 stop_signal = TRUE;
1014 switch (event->keyval)
1015 {
1016 case GDK_KEY_Return: case GDK_KEY_KP_Enter:
1017 layout_image_set_collection(NULL, cw->cd,
1018 collection_table_get_focus_info(cw->table));
1019 break;
1020 case 'V': case 'v':
1021 view_window_new_from_collection(cw->cd,
1022 collection_table_get_focus_info(cw->table));
1023 break;
1024 case 'S': case 's':
1025 if (!cw->cd->path)
1026 {
1027 collection_dialog_save_as(NULL, cw->cd);
1028 }
1029 else if (!collection_save(cw->cd, cw->cd->path))
1030 {
1031 log_printf("failed saving to collection path: %s\n", cw->cd->path);
1032 }
1033 break;
1034 case 'A': case 'a':
1035 collection_dialog_append(NULL, cw->cd);
1036 break;
1037 case 'N': case 'n':
1038 collection_set_sort_method(cw->cd, SORT_NAME);
1039 break;
1040 #ifdef HAVE_STRVERSCMP
1041 case 'I': case 'i':
1042 collection_set_sort_method(cw->cd, SORT_NUMBER);
1043 break;
1044 #endif
1045 case 'D': case 'd':
1046 collection_set_sort_method(cw->cd, SORT_TIME);
1047 break;
1048 case 'B': case 'b':
1049 collection_set_sort_method(cw->cd, SORT_SIZE);
1050 break;
1051 case 'P': case 'p':
1052 if (event->state & GDK_SHIFT_MASK)
1053 {
1054 CollectInfo *info;
1055
1056 info = collection_table_get_focus_info(cw->table);
1057
1058 print_window_new(info->fd, collection_table_selection_get_list(cw->table),
1059 collection_list_to_filelist(cw->cd->list), cw->window);
1060 }
1061 else
1062 {
1063 collection_set_sort_method(cw->cd, SORT_PATH);
1064 }
1065 break;
1066 case 'R': case 'r':
1067 if (event->state & GDK_MOD1_MASK)
1068 {
1069 options->collections.rectangular_selection = !(options->collections.rectangular_selection);
1070 }
1071 break;
1072 case GDK_KEY_Delete: case GDK_KEY_KP_Delete:
1073 list = g_list_copy(cw->table->selection);
1074 if (list)
1075 {
1076 collection_remove_by_info_list(cw->cd, list);
1077 g_list_free(list);
1078 }
1079 else
1080 {
1081 collection_remove_by_info(cw->cd, collection_table_get_focus_info(cw->table));
1082 }
1083 break;
1084 default:
1085 stop_signal = FALSE;
1086 break;
1087 }
1088 }
1089 if (!stop_signal && is_help_key(event))
1090 {
1091 help_window_show("GuideCollections.html");
1092 stop_signal = TRUE;
1093 }
1094
1095 return stop_signal;
1096 }
1097
1098 /*
1099 *-------------------------------------------------------------------
1100 * window
1101 *-------------------------------------------------------------------
1102 */
collection_window_get_geometry(CollectWindow * cw)1103 static void collection_window_get_geometry(CollectWindow *cw)
1104 {
1105 CollectionData *cd;
1106 GdkWindow *window;
1107
1108 if (!cw) return;
1109
1110 cd = cw->cd;
1111 window = gtk_widget_get_window(cw->window);
1112 gdk_window_get_position(window, &cd->window_x, &cd->window_y);
1113 cd->window_w = gdk_window_get_width(window);
1114 cd->window_h = gdk_window_get_height(window);
1115 cd->window_read = TRUE;
1116 }
1117
collection_window_refresh(CollectWindow * cw)1118 static void collection_window_refresh(CollectWindow *cw)
1119 {
1120 if (!cw) return;
1121
1122 collection_table_refresh(cw->table);
1123 }
1124
collection_window_update_title(CollectWindow * cw)1125 static void collection_window_update_title(CollectWindow *cw)
1126 {
1127 gboolean free_name = FALSE;
1128 gchar *name;
1129 gchar *buf;
1130
1131 if (!cw) return;
1132
1133 if (file_extension_match(cw->cd->name, GQ_COLLECTION_EXT))
1134 {
1135 name = remove_extension_from_path(cw->cd->name);
1136 free_name = TRUE;
1137 }
1138 else
1139 {
1140 name = cw->cd->name;
1141 }
1142
1143 buf = g_strdup_printf(_("%s - Collection - %s"), name, GQ_APPNAME);
1144 if (free_name) g_free(name);
1145 gtk_window_set_title(GTK_WINDOW(cw->window), buf);
1146 g_free(buf);
1147 }
1148
collection_window_update_info(CollectionData * cd,CollectInfo * ci,gpointer data)1149 static void collection_window_update_info(CollectionData *cd, CollectInfo *ci, gpointer data)
1150 {
1151 CollectWindow *cw = data;
1152
1153 collection_table_file_update(cw->table, ci);
1154 }
1155
collection_window_add(CollectWindow * cw,CollectInfo * ci)1156 static void collection_window_add(CollectWindow *cw, CollectInfo *ci)
1157 {
1158 if (!cw) return;
1159
1160 if (!ci->pixbuf) collection_load_thumb_idle(cw->cd);
1161 collection_table_file_add(cw->table, ci);
1162 }
1163
collection_window_insert(CollectWindow * cw,CollectInfo * ci)1164 static void collection_window_insert(CollectWindow *cw, CollectInfo *ci)
1165 {
1166 if (!cw) return;
1167
1168 if (!ci->pixbuf) collection_load_thumb_idle(cw->cd);
1169 collection_table_file_insert(cw->table, ci);
1170 if (!cw) return;
1171 }
1172
collection_window_remove(CollectWindow * cw,CollectInfo * ci)1173 static void collection_window_remove(CollectWindow *cw, CollectInfo *ci)
1174 {
1175 if (!cw) return;
1176
1177 collection_table_file_remove(cw->table, ci);
1178 }
1179
collection_window_update(CollectWindow * cw,CollectInfo * ci)1180 static void collection_window_update(CollectWindow *cw, CollectInfo *ci)
1181 {
1182 if (!cw) return;
1183
1184 collection_table_file_update(cw->table, ci);
1185 collection_table_file_update(cw->table, NULL);
1186 }
1187
collection_window_close_final(CollectWindow * cw)1188 static void collection_window_close_final(CollectWindow *cw)
1189 {
1190 if (cw->close_dialog) return;
1191
1192 collection_window_list = g_list_remove(collection_window_list, cw);
1193 collection_window_get_geometry(cw);
1194
1195 gtk_widget_destroy(cw->window);
1196
1197 collection_set_update_info_func(cw->cd, NULL, NULL);
1198 collection_unref(cw->cd);
1199
1200 g_free(cw);
1201 }
1202
collection_close_save_cb(GenericDialog * gd,gpointer data)1203 static void collection_close_save_cb(GenericDialog *gd, gpointer data)
1204 {
1205 CollectWindow *cw = data;
1206
1207 cw->close_dialog = NULL;
1208 generic_dialog_close(gd);
1209
1210 if (!cw->cd->path)
1211 {
1212 collection_dialog_save_close(NULL, cw->cd);
1213 return;
1214 }
1215 else if (!collection_save(cw->cd, cw->cd->path))
1216 {
1217 gchar *buf;
1218 buf = g_strdup_printf(_("Failed to save the collection:\n%s"), cw->cd->path);
1219 warning_dialog(_("Save Failed"), buf, GTK_STOCK_DIALOG_ERROR, cw->window);
1220 g_free(buf);
1221 return;
1222 }
1223
1224 collection_window_close_final(cw);
1225 }
1226
collection_close_close_cb(GenericDialog * gd,gpointer data)1227 static void collection_close_close_cb(GenericDialog *gd, gpointer data)
1228 {
1229 CollectWindow *cw = data;
1230
1231 cw->close_dialog = NULL;
1232 generic_dialog_close(gd);
1233
1234 collection_window_close_final(cw);
1235 }
1236
collection_close_cancel_cb(GenericDialog * gd,gpointer data)1237 static void collection_close_cancel_cb(GenericDialog *gd, gpointer data)
1238 {
1239 CollectWindow *cw = data;
1240
1241 cw->close_dialog = NULL;
1242 generic_dialog_close(gd);
1243 }
1244
collection_close_dlg_show(CollectWindow * cw)1245 static void collection_close_dlg_show(CollectWindow *cw)
1246 {
1247 GenericDialog *gd;
1248
1249 if (cw->close_dialog)
1250 {
1251 gtk_window_present(GTK_WINDOW(cw->close_dialog));
1252 return;
1253 }
1254
1255 gd = generic_dialog_new(_("Close collection"),
1256 "close_collection", cw->window, FALSE,
1257 collection_close_cancel_cb, cw);
1258 generic_dialog_add_message(gd, GTK_STOCK_DIALOG_QUESTION,
1259 _("Close collection"),
1260 _("Collection has been modified.\nSave first?"), TRUE);
1261
1262 generic_dialog_add_button(gd, GTK_STOCK_SAVE, NULL, collection_close_save_cb, TRUE);
1263 generic_dialog_add_button(gd, GTK_STOCK_DELETE, _("_Discard"), collection_close_close_cb, FALSE);
1264
1265 cw->close_dialog = gd->dialog;
1266
1267 gtk_widget_show(gd->dialog);
1268 }
1269
collection_window_close(CollectWindow * cw)1270 static void collection_window_close(CollectWindow *cw)
1271 {
1272 if (!cw->cd->changed && !cw->close_dialog)
1273 {
1274 collection_window_close_final(cw);
1275 return;
1276 }
1277
1278 collection_close_dlg_show(cw);
1279 }
1280
collection_window_close_by_collection(CollectionData * cd)1281 void collection_window_close_by_collection(CollectionData *cd)
1282 {
1283 CollectWindow *cw;
1284
1285 cw = collection_window_find(cd);
1286 if (cw) collection_window_close_final(cw);
1287 }
1288
1289 /**
1290 * @brief Check if any Collection windows have unsaved data
1291 * @returns TRUE if unsaved data exists
1292 *
1293 * Also saves window geometry for Collection windows that have
1294 * no unsaved data
1295 */
collection_window_modified_exists(void)1296 gboolean collection_window_modified_exists(void)
1297 {
1298 GList *work;
1299 gboolean ret;
1300
1301 ret = FALSE;
1302
1303 work = collection_window_list;
1304 while (work)
1305 {
1306 CollectWindow *cw = work->data;
1307 if (cw->cd->changed)
1308 {
1309 ret = TRUE;
1310 }
1311 else
1312 {
1313 if (!collection_save(cw->table->cd, cw->table->cd->path))
1314 {
1315 log_printf("failed saving to collection path: %s\n", cw->table->cd->path);
1316 }
1317 }
1318 work = work->next;
1319 }
1320
1321 return ret;
1322 }
1323
collection_window_delete(GtkWidget * widget,GdkEvent * event,gpointer data)1324 static gboolean collection_window_delete(GtkWidget *widget, GdkEvent *event, gpointer data)
1325 {
1326 CollectWindow *cw = data;
1327 collection_window_close(cw);
1328
1329 return TRUE;
1330 }
1331
collection_window_new(const gchar * path)1332 CollectWindow *collection_window_new(const gchar *path)
1333 {
1334 CollectWindow *cw;
1335 GtkWidget *vbox;
1336 GtkWidget *frame;
1337 GtkWidget *status_label;
1338 GtkWidget *extra_label;
1339 GdkGeometry geometry;
1340
1341 /* If the collection is already opened in another window, return that one */
1342 cw = collection_window_find_by_path(path);
1343 if (cw)
1344 {
1345 return cw;
1346 }
1347
1348 cw = g_new0(CollectWindow, 1);
1349
1350 collection_window_list = g_list_append(collection_window_list, cw);
1351
1352 cw->cd = collection_new(path);
1353
1354 cw->window = window_new(GTK_WINDOW_TOPLEVEL, "collection", PIXBUF_INLINE_ICON_BOOK, NULL, NULL);
1355 DEBUG_NAME(cw->window);
1356
1357 geometry.min_width = DEFAULT_MINIMAL_WINDOW_SIZE;
1358 geometry.min_height = DEFAULT_MINIMAL_WINDOW_SIZE;
1359 geometry.base_width = COLLECT_DEF_WIDTH;
1360 geometry.base_height = COLLECT_DEF_HEIGHT;
1361 gtk_window_set_geometry_hints(GTK_WINDOW(cw->window), NULL, &geometry,
1362 GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE);
1363
1364 if (options->collections_on_top)
1365 {
1366 gtk_window_set_keep_above(GTK_WINDOW(cw->window), TRUE);
1367 }
1368
1369 if (options->save_window_positions && path && collection_load_only_geometry(cw->cd, path))
1370 {
1371 gtk_window_set_default_size(GTK_WINDOW(cw->window), cw->cd->window_w, cw->cd->window_h);
1372 gtk_window_move(GTK_WINDOW(cw->window), cw->cd->window_x, cw->cd->window_y);
1373 }
1374 else
1375 {
1376 gtk_window_set_default_size(GTK_WINDOW(cw->window), COLLECT_DEF_WIDTH, COLLECT_DEF_HEIGHT);
1377 }
1378
1379 gtk_window_set_resizable(GTK_WINDOW(cw->window), TRUE);
1380 collection_window_update_title(cw);
1381 gtk_container_set_border_width(GTK_CONTAINER(cw->window), 0);
1382
1383 g_signal_connect(G_OBJECT(cw->window), "delete_event",
1384 G_CALLBACK(collection_window_delete), cw);
1385
1386 g_signal_connect(G_OBJECT(cw->window), "key_press_event",
1387 G_CALLBACK(collection_window_keypress), cw);
1388
1389 vbox = gtk_vbox_new(FALSE, 0);
1390 gtk_container_add(GTK_CONTAINER(cw->window), vbox);
1391 gtk_widget_show(vbox);
1392
1393 cw->table = collection_table_new(cw->cd);
1394 gtk_box_pack_start(GTK_BOX(vbox), cw->table->scrolled, TRUE, TRUE, 0);
1395 gtk_widget_show(cw->table->scrolled);
1396
1397 cw->status_box = gtk_hbox_new(TRUE, 0);
1398 gtk_box_pack_start(GTK_BOX(vbox), cw->status_box, FALSE, FALSE, 0);
1399 gtk_widget_show(cw->status_box);
1400
1401 frame = gtk_frame_new(NULL);
1402 DEBUG_NAME(frame);
1403 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
1404 gtk_box_pack_start(GTK_BOX(cw->status_box), frame, TRUE, TRUE, 0);
1405 gtk_widget_show(frame);
1406
1407 status_label = gtk_label_new("");
1408 gtk_container_add(GTK_CONTAINER(frame), status_label);
1409 gtk_widget_show(status_label);
1410
1411 extra_label = gtk_progress_bar_new();
1412 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(extra_label), 0.0);
1413 #if GTK_CHECK_VERSION(3,0,0)
1414 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(extra_label), "");
1415 gtk_progress_bar_set_show_text(GTK_PROGRESS_BAR(extra_label), TRUE);
1416 #endif
1417 gtk_box_pack_start(GTK_BOX(cw->status_box), extra_label, TRUE, TRUE, 0);
1418 gtk_widget_show(extra_label);
1419
1420 collection_table_set_labels(cw->table, status_label, extra_label);
1421
1422 gtk_widget_show(cw->window);
1423 gtk_widget_grab_focus(cw->table->listview);
1424
1425 collection_set_update_info_func(cw->cd, collection_window_update_info, cw);
1426
1427 if (path && *path == G_DIR_SEPARATOR) collection_load_begin(cw->cd, NULL, COLLECTION_LOAD_NONE);
1428
1429 return cw;
1430 }
1431 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
1432