4 * Copyright (C) 2008 - 2009 The Geeqie Team
6 * Author: John Ellis, Laurent Monin
8 * This software is released under the GNU General Public License (GNU GPL).
9 * Please read the included file COPYING for more information.
10 * This software comes with no warranty of any kind, use at your own risk!
21 #include "secure_save.h"
22 #include "ui_fileops.h"
25 #include "filefilter.h"
26 #include "layout_util.h"
35 static const gchar *group_keys[] = { /* tags that will be written to all files in a group, options->metadata.sync_grouped_files */
37 "Xmp.photoshop.Urgency",
38 "Xmp.photoshop.Category",
39 "Xmp.photoshop.SupplementalCategory",
42 "Xmp.photoshop.Instruction",
43 "Xmp.photoshop.DateCreated",
45 "Xmp.photoshop.AuthorsPosition",
47 "Xmp.photoshop.State",
48 "Xmp.iptc.CountryCode",
49 "Xmp.photoshop.Country",
50 "Xmp.photoshop.TransmissionReference",
51 "Xmp.photoshop.Headline",
52 "Xmp.photoshop.Credit",
53 "Xmp.photoshop.Source",
56 "Xmp.photoshop.CaptionWriter",
59 static gboolean metadata_write_queue_idle_cb(gpointer data);
60 static gboolean metadata_legacy_write(FileData *fd);
61 static void metadata_legacy_delete(FileData *fd, const gchar *except);
66 *-------------------------------------------------------------------
68 *-------------------------------------------------------------------
71 static GList *metadata_write_queue = NULL;
72 static guint metadata_write_idle_id = 0; /* event source id */
74 static void metadata_write_queue_add(FileData *fd)
76 if (!g_list_find(metadata_write_queue, fd))
78 metadata_write_queue = g_list_prepend(metadata_write_queue, fd);
81 layout_util_status_update_write_all();
84 if (metadata_write_idle_id)
86 g_source_remove(metadata_write_idle_id);
87 metadata_write_idle_id = 0;
90 if (options->metadata.confirm_after_timeout)
92 metadata_write_idle_id = g_timeout_add(options->metadata.confirm_timeout * 1000, metadata_write_queue_idle_cb, NULL);
97 gboolean metadata_write_queue_remove(FileData *fd)
99 g_hash_table_destroy(fd->modified_xmp);
100 fd->modified_xmp = NULL;
102 metadata_write_queue = g_list_remove(metadata_write_queue, fd);
104 file_data_increment_version(fd);
105 file_data_send_notification(fd, NOTIFY_REREAD);
109 layout_util_status_update_write_all();
113 gboolean metadata_write_queue_remove_list(GList *list)
121 FileData *fd = work->data;
123 ret = ret && metadata_write_queue_remove(fd);
129 gboolean metadata_write_queue_confirm(gboolean force_dialog, FileUtilDoneFunc done_func, gpointer done_data)
132 GList *to_approve = NULL;
134 work = metadata_write_queue;
137 FileData *fd = work->data;
140 if (fd->change) continue; /* another operation in progress, skip this file for now */
142 to_approve = g_list_prepend(to_approve, file_data_ref(fd));
145 file_util_write_metadata(NULL, to_approve, NULL, force_dialog, done_func, done_data);
147 filelist_free(to_approve);
149 return (metadata_write_queue != NULL);
152 static gboolean metadata_write_queue_idle_cb(gpointer data)
154 metadata_write_queue_confirm(FALSE, NULL, NULL);
155 metadata_write_idle_id = 0;
159 gboolean metadata_write_perform(FileData *fd)
164 g_assert(fd->change);
166 if (fd->change->dest &&
167 strcmp(extension_from_path(fd->change->dest), GQ_CACHE_EXT_METADATA) == 0)
169 success = metadata_legacy_write(fd);
170 if (success) metadata_legacy_delete(fd, fd->change->dest);
174 /* write via exiv2 */
175 /* we can either use cached metadata which have fd->modified_xmp already applied
176 or read metadata from file and apply fd->modified_xmp
177 metadata are read also if the file was modified meanwhile */
178 exif = exif_read_fd(fd);
179 if (!exif) return FALSE;
181 success = (fd->change->dest) ? exif_write_sidecar(exif, fd->change->dest) : exif_write(exif); /* write modified metadata */
182 exif_free_fd(fd, exif);
184 if (fd->change->dest)
185 /* this will create a FileData for the sidecar and link it to the main file
186 (we can't wait until the sidecar is discovered by directory scanning because
187 exif_read_fd is called before that and it would read the main file only and
188 store the metadata in the cache)
189 FIXME: this does not catch new sidecars created by independent external programs
191 file_data_unref(file_data_new_simple(fd->change->dest));
193 if (success) metadata_legacy_delete(fd, fd->change->dest);
197 gint metadata_queue_length(void)
199 return g_list_length(metadata_write_queue);
202 static gboolean metadata_check_key(const gchar *keys[], const gchar *key)
204 const gchar **k = keys;
208 if (strcmp(key, *k) == 0) return TRUE;
214 gboolean metadata_write_revert(FileData *fd, const gchar *key)
216 if (!fd->modified_xmp) return FALSE;
218 g_hash_table_remove(fd->modified_xmp, key);
220 if (g_hash_table_size(fd->modified_xmp) == 0)
222 metadata_write_queue_remove(fd);
226 /* reread the metadata to restore the original value */
227 file_data_increment_version(fd);
228 file_data_send_notification(fd, NOTIFY_REREAD);
233 gboolean metadata_write_list(FileData *fd, const gchar *key, const GList *values)
235 if (!fd->modified_xmp)
237 fd->modified_xmp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)string_list_free);
239 g_hash_table_insert(fd->modified_xmp, g_strdup(key), string_list_copy((GList *)values));
242 exif_update_metadata(fd->exif, key, values);
244 metadata_write_queue_add(fd);
245 file_data_increment_version(fd);
246 file_data_send_notification(fd, NOTIFY_METADATA);
248 if (options->metadata.sync_grouped_files && metadata_check_key(group_keys, key))
250 GList *work = fd->sidecar_files;
254 FileData *sfd = work->data;
257 if (filter_file_class(sfd->extension, FORMAT_CLASS_META)) continue;
259 metadata_write_list(sfd, key, values);
267 gboolean metadata_write_string(FileData *fd, const gchar *key, const char *value)
269 GList *list = g_list_append(NULL, g_strdup(value));
270 gboolean ret = metadata_write_list(fd, key, list);
271 string_list_free(list);
275 gboolean metadata_write_int(FileData *fd, const gchar *key, guint64 value)
279 g_snprintf(string, sizeof(string), "%ld", value);
280 return metadata_write_string(fd, key, string);
284 *-------------------------------------------------------------------
285 * keyword / comment read/write
286 *-------------------------------------------------------------------
289 static gboolean metadata_file_write(gchar *path, GHashTable *modified_xmp)
292 GList *keywords = g_hash_table_lookup(modified_xmp, KEYWORD_KEY);
293 GList *comment_l = g_hash_table_lookup(modified_xmp, COMMENT_KEY);
294 gchar *comment = comment_l ? comment_l->data : NULL;
296 ssi = secure_open(path);
297 if (!ssi) return FALSE;
299 secure_fprintf(ssi, "#%s comment (%s)\n\n", GQ_APPNAME, VERSION);
301 secure_fprintf(ssi, "[keywords]\n");
302 while (keywords && secsave_errno == SS_ERR_NONE)
304 const gchar *word = keywords->data;
305 keywords = keywords->next;
307 secure_fprintf(ssi, "%s\n", word);
309 secure_fputc(ssi, '\n');
311 secure_fprintf(ssi, "[comment]\n");
312 secure_fprintf(ssi, "%s\n", (comment) ? comment : "");
314 secure_fprintf(ssi, "#end\n");
316 return (secure_close(ssi) == 0);
319 static gboolean metadata_legacy_write(FileData *fd)
321 gboolean success = FALSE;
322 gchar *metadata_pathl;
324 g_assert(fd->change && fd->change->dest);
326 DEBUG_1("Saving comment: %s", fd->change->dest);
328 metadata_pathl = path_from_utf8(fd->change->dest);
330 success = metadata_file_write(metadata_pathl, fd->modified_xmp);
332 g_free(metadata_pathl);
337 static gboolean metadata_file_read(gchar *path, GList **keywords, gchar **comment)
341 MetadataKey key = MK_NONE;
343 GString *comment_build = NULL;
345 f = fopen(path, "r");
346 if (!f) return FALSE;
348 while (fgets(s_buf, sizeof(s_buf), f))
352 if (*ptr == '#') continue;
353 if (*ptr == '[' && key != MK_COMMENT)
355 gchar *keystr = ++ptr;
358 while (*ptr != ']' && *ptr != '\n' && *ptr != '\0') ptr++;
363 if (g_ascii_strcasecmp(keystr, "keywords") == 0)
365 else if (g_ascii_strcasecmp(keystr, "comment") == 0)
377 while (*ptr != '\n' && *ptr != '\0') ptr++;
379 if (strlen(s_buf) > 0)
381 gchar *kw = utf8_validate_or_convert(s_buf);
383 list = g_list_prepend(list, kw);
388 if (!comment_build) comment_build = g_string_new("");
389 g_string_append(comment_build, s_buf);
398 *keywords = g_list_reverse(list);
402 string_list_free(list);
410 gchar *ptr = comment_build->str;
412 /* strip leading and trailing newlines */
413 while (*ptr == '\n') ptr++;
415 while (len > 0 && ptr[len - 1] == '\n') len--;
416 if (ptr[len] == '\n') len++; /* keep the last one */
419 gchar *text = g_strndup(ptr, len);
421 *comment = utf8_validate_or_convert(text);
425 g_string_free(comment_build, TRUE);
431 static void metadata_legacy_delete(FileData *fd, const gchar *except)
433 gchar *metadata_path;
434 gchar *metadata_pathl;
437 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
438 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
440 metadata_pathl = path_from_utf8(metadata_path);
441 unlink(metadata_pathl);
442 g_free(metadata_pathl);
443 g_free(metadata_path);
445 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
446 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
448 metadata_pathl = path_from_utf8(metadata_path);
449 unlink(metadata_pathl);
450 g_free(metadata_pathl);
451 g_free(metadata_path);
455 static gboolean metadata_legacy_read(FileData *fd, GList **keywords, gchar **comment)
457 gchar *metadata_path;
458 gchar *metadata_pathl;
459 gboolean success = FALSE;
461 if (!fd) return FALSE;
463 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
464 if (!metadata_path) return FALSE;
466 metadata_pathl = path_from_utf8(metadata_path);
468 success = metadata_file_read(metadata_pathl, keywords, comment);
470 g_free(metadata_pathl);
471 g_free(metadata_path);
476 static GList *remove_duplicate_strings_from_list(GList *list)
479 GHashTable *hashtable = g_hash_table_new(g_str_hash, g_str_equal);
480 GList *newlist = NULL;
484 gchar *key = work->data;
486 if (g_hash_table_lookup(hashtable, key) == NULL)
488 g_hash_table_insert(hashtable, (gpointer) key, GINT_TO_POINTER(1));
489 newlist = g_list_prepend(newlist, key);
494 g_hash_table_destroy(hashtable);
497 return g_list_reverse(newlist);
500 GList *metadata_read_list(FileData *fd, const gchar *key, MetadataFormat format)
504 if (!fd) return NULL;
506 /* unwritten data overide everything */
507 if (fd->modified_xmp && format == METADATA_PLAIN)
509 list = g_hash_table_lookup(fd->modified_xmp, key);
510 if (list) return string_list_copy(list);
514 Legacy metadata file is the primary source if it exists.
515 Merging the lists does not make much sense, because the existence of
516 legacy metadata file indicates that the other metadata sources are not
517 writable and thus it would not be possible to delete the keywords
518 that comes from the image file.
520 if (strcmp(key, KEYWORD_KEY) == 0)
522 if (metadata_legacy_read(fd, &list, NULL)) return list;
524 else if (strcmp(key, COMMENT_KEY) == 0)
526 gchar *comment = NULL;
527 if (metadata_legacy_read(fd, NULL, &comment)) return g_list_append(NULL, comment);
529 else if (strncmp(key, "file.", 5) == 0)
531 return g_list_append(NULL, metadata_file_info(fd, key, format));
534 exif = exif_read_fd(fd); /* this is cached, thus inexpensive */
535 if (!exif) return NULL;
536 list = exif_get_metadata(exif, key, format);
537 exif_free_fd(fd, exif);
541 gchar *metadata_read_string(FileData *fd, const gchar *key, MetadataFormat format)
543 GList *string_list = metadata_read_list(fd, key, format);
546 gchar *str = string_list->data;
547 string_list->data = NULL;
548 string_list_free(string_list);
554 guint64 metadata_read_int(FileData *fd, const gchar *key, guint64 fallback)
558 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
559 if (!string) return fallback;
561 ret = g_ascii_strtoull(string, &endptr, 10);
562 if (string == endptr) ret = fallback;
567 gdouble metadata_read_GPS_coord(FileData *fd, const gchar *key, gdouble fallback)
571 gdouble deg, min, sec;
573 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
574 if (!string) return fallback;
576 deg = g_ascii_strtod(string, &endptr);
579 min = g_ascii_strtod(endptr + 1, &endptr);
581 sec = g_ascii_strtod(endptr + 1, &endptr);
586 if (*endptr == 'S' || *endptr == 'W' || *endptr == 'N' || *endptr == 'E')
588 coord = deg + min /60.0 + sec / 3600.0;
590 if (*endptr == 'S' || *endptr == 'W') coord = -coord;
597 log_printf("unable to parse GPS coordinate '%s'\n", string);
604 gboolean metadata_append_string(FileData *fd, const gchar *key, const char *value)
606 gchar *str = metadata_read_string(fd, key, METADATA_PLAIN);
610 return metadata_write_string(fd, key, value);
614 gchar *new_string = g_strconcat(str, value, NULL);
615 gboolean ret = metadata_write_string(fd, key, new_string);
622 gboolean metadata_append_list(FileData *fd, const gchar *key, const GList *values)
624 GList *list = metadata_read_list(fd, key, METADATA_PLAIN);
628 return metadata_write_list(fd, key, values);
633 list = g_list_concat(list, string_list_copy(values));
634 list = remove_duplicate_strings_from_list(list);
636 ret = metadata_write_list(fd, key, list);
637 string_list_free(list);
642 gchar *find_string_in_list_utf8nocase(GList *list, const gchar *string)
644 gchar *string_casefold = g_utf8_casefold(string, -1);
648 gchar *haystack = list->data;
653 gchar *haystack_casefold = g_utf8_casefold(haystack, -1);
655 equal = (strcmp(haystack_casefold, string_casefold) == 0);
656 g_free(haystack_casefold);
660 g_free(string_casefold);
668 g_free(string_casefold);
673 #define KEYWORDS_SEPARATOR(c) ((c) == ',' || (c) == ';' || (c) == '\n' || (c) == '\r' || (c) == '\b')
675 GList *string_to_keywords_list(const gchar *text)
678 const gchar *ptr = text;
685 while (KEYWORDS_SEPARATOR(*ptr)) ptr++;
687 while (*ptr != '\0' && !KEYWORDS_SEPARATOR(*ptr))
693 /* trim starting and ending whitespaces */
694 while (l > 0 && g_ascii_isspace(*begin)) begin++, l--;
695 while (l > 0 && g_ascii_isspace(begin[l-1])) l--;
699 gchar *keyword = g_strndup(begin, l);
701 /* only add if not already in the list */
702 if (!find_string_in_list_utf8nocase(list, keyword))
703 list = g_list_append(list, keyword);
717 gboolean meta_data_get_keyword_mark(FileData *fd, gint n, gpointer data)
719 /* FIXME: do not use global keyword_tree */
722 gboolean found = FALSE;
723 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
727 if (keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path) &&
728 keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
735 gboolean meta_data_set_keyword_mark(FileData *fd, gint n, gboolean value, gpointer data)
738 GList *keywords = NULL;
741 if (!keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path)) return FALSE;
743 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
745 if (!!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords) != !!value)
749 keyword_tree_set(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
753 keyword_tree_reset(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
755 metadata_write_list(fd, KEYWORD_KEY, keywords);
758 string_list_free(keywords);
764 void meta_data_connect_mark_with_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *kw_iter, gint mark)
767 FileDataGetMarkFunc get_mark_func;
768 FileDataSetMarkFunc set_mark_func;
769 gpointer mark_func_data;
773 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
775 file_data_get_registered_mark_func(i, &get_mark_func, &set_mark_func, &mark_func_data);
776 if (get_mark_func == meta_data_get_keyword_mark)
778 GtkTreeIter old_kw_iter;
779 GList *old_path = mark_func_data;
781 if (keyword_tree_get_iter(keyword_tree, &old_kw_iter, old_path) &&
782 (i == mark || /* release any previous connection of given mark */
783 keyword_compare(keyword_tree, &old_kw_iter, kw_iter) == 0)) /* or given keyword */
785 file_data_register_mark_func(i, NULL, NULL, NULL, NULL);
786 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), &old_kw_iter, KEYWORD_COLUMN_MARK, "", -1);
792 if (mark >= 0 && mark < FILEDATA_MARKS_SIZE)
796 path = keyword_tree_get_path(keyword_tree, kw_iter);
797 file_data_register_mark_func(mark, meta_data_get_keyword_mark, meta_data_set_keyword_mark, path, (GDestroyNotify)string_list_free);
799 mark_str = g_strdup_printf("%d", mark + 1);
800 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), kw_iter, KEYWORD_COLUMN_MARK, mark_str, -1);
807 *-------------------------------------------------------------------
809 *-------------------------------------------------------------------
814 GtkTreeStore *keyword_tree;
816 gchar *keyword_get_name(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
819 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_NAME, &name, -1);
823 gchar *keyword_get_casefold(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
826 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_CASEFOLD, &casefold, -1);
830 gboolean keyword_get_is_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
833 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
837 void keyword_set(GtkTreeStore *keyword_tree, GtkTreeIter *iter, const gchar *name, gboolean is_keyword)
839 gchar *casefold = g_utf8_casefold(name, -1);
840 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_MARK, "",
841 KEYWORD_COLUMN_NAME, name,
842 KEYWORD_COLUMN_CASEFOLD, casefold,
843 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
847 gboolean keyword_compare(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
849 GtkTreePath *pa = gtk_tree_model_get_path(keyword_tree, a);
850 GtkTreePath *pb = gtk_tree_model_get_path(keyword_tree, b);
851 gint ret = gtk_tree_path_compare(pa, pb);
852 gtk_tree_path_free(pa);
853 gtk_tree_path_free(pb);
857 gboolean keyword_same_parent(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
859 GtkTreeIter parent_a;
860 GtkTreeIter parent_b;
862 gboolean valid_pa = gtk_tree_model_iter_parent(keyword_tree, &parent_a, a);
863 gboolean valid_pb = gtk_tree_model_iter_parent(keyword_tree, &parent_b, b);
865 if (valid_pa && valid_pb)
867 return keyword_compare(keyword_tree, &parent_a, &parent_b) == 0;
871 return (!valid_pa && !valid_pb); /* both are toplevel */
875 gboolean keyword_exists(GtkTreeModel *keyword_tree, GtkTreeIter *parent_ptr, GtkTreeIter *sibling, const gchar *name, gboolean exclude_sibling, GtkTreeIter *result)
879 gboolean toplevel = FALSE;
885 parent = *parent_ptr;
889 toplevel = !gtk_tree_model_iter_parent(keyword_tree, &parent, sibling);
896 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &iter, toplevel ? NULL : &parent)) return FALSE;
898 casefold = g_utf8_casefold(name, -1);
903 if (!(exclude_sibling && sibling && keyword_compare(keyword_tree, &iter, sibling) == 0))
905 if (options->metadata.keywords_case_sensitive)
907 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
908 ret = strcmp(name, iter_name) == 0;
913 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
914 ret = strcmp(casefold, iter_casefold) == 0;
915 g_free(iter_casefold);
916 } // if (options->metadata.tags_cas...
920 if (result) *result = iter;
923 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) break;
930 void keyword_copy(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
933 gchar *mark, *name, *casefold;
936 /* do not copy KEYWORD_COLUMN_HIDE_IN, it fully shows the new subtree */
937 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), from, KEYWORD_COLUMN_MARK, &mark,
938 KEYWORD_COLUMN_NAME, &name,
939 KEYWORD_COLUMN_CASEFOLD, &casefold,
940 KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
942 gtk_tree_store_set(keyword_tree, to, KEYWORD_COLUMN_MARK, mark,
943 KEYWORD_COLUMN_NAME, name,
944 KEYWORD_COLUMN_CASEFOLD, casefold,
945 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
951 void keyword_copy_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
953 GtkTreeIter from_child;
955 keyword_copy(keyword_tree, to, from);
957 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &from_child, from)) return;
961 GtkTreeIter to_child;
962 gtk_tree_store_append(keyword_tree, &to_child, to);
963 keyword_copy_recursive(keyword_tree, &to_child, &from_child);
964 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &from_child)) return;
968 void keyword_move_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
970 keyword_copy_recursive(keyword_tree, to, from);
971 keyword_delete(keyword_tree, from);
974 GList *keyword_tree_get_path(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
977 GtkTreeIter iter = *iter_ptr;
982 path = g_list_prepend(path, keyword_get_name(keyword_tree, &iter));
983 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) break;
989 gboolean keyword_tree_get_iter(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList *path)
993 if (!gtk_tree_model_get_iter_first(keyword_tree, &iter)) return FALSE;
997 GtkTreeIter children;
1000 gchar *name = keyword_get_name(keyword_tree, &iter);
1001 if (strcmp(name, path->data) == 0) break;
1003 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return FALSE;
1012 if (!gtk_tree_model_iter_children(keyword_tree, &children, &iter)) return FALSE;
1018 static gboolean keyword_tree_is_set_casefold(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *casefold_list)
1020 if (!casefold_list) return FALSE;
1022 if (!keyword_get_is_keyword(keyword_tree, &iter))
1024 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1026 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1027 return FALSE; /* this should happen only on empty helpers */
1031 if (keyword_tree_is_set_casefold(keyword_tree, child, casefold_list)) return TRUE;
1032 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1040 if (keyword_get_is_keyword(keyword_tree, &iter))
1042 GList *work = casefold_list;
1043 gboolean found = FALSE;
1044 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
1047 const gchar *casefold = work->data;
1050 if (strcmp(iter_casefold, casefold) == 0)
1056 g_free(iter_casefold);
1057 if (!found) return FALSE;
1060 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1065 static gboolean keyword_tree_is_set_casefull(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *kw_list)
1067 if (!kw_list) return FALSE;
1069 if (!keyword_get_is_keyword(keyword_tree, &iter))
1071 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1073 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1074 return FALSE; /* this should happen only on empty helpers */
1078 if (keyword_tree_is_set_casefull(keyword_tree, child, kw_list)) return TRUE;
1079 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1087 if (keyword_get_is_keyword(keyword_tree, &iter))
1089 GList *work = kw_list;
1090 gboolean found = FALSE;
1091 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
1094 const gchar *name = work->data;
1097 if (strcmp(iter_name, name) == 0)
1104 if (!found) return FALSE;
1107 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1112 gboolean keyword_tree_is_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList *kw_list)
1115 GList *casefold_list = NULL;
1118 if (options->metadata.keywords_case_sensitive)
1120 ret = keyword_tree_is_set_casefull(keyword_tree, *iter, kw_list);
1127 const gchar *kw = work->data;
1130 casefold_list = g_list_prepend(casefold_list, g_utf8_casefold(kw, -1));
1133 ret = keyword_tree_is_set_casefold(keyword_tree, *iter, casefold_list);
1135 string_list_free(casefold_list);
1141 void keyword_tree_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1143 GtkTreeIter iter = *iter_ptr;
1148 if (keyword_get_is_keyword(keyword_tree, &iter))
1150 gchar *name = keyword_get_name(keyword_tree, &iter);
1151 if (!find_string_in_list_utf8nocase(*kw_list, name))
1153 *kw_list = g_list_append(*kw_list, name);
1161 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1166 static void keyword_tree_reset1(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1170 if (!keyword_get_is_keyword(keyword_tree, iter)) return;
1172 name = keyword_get_name(keyword_tree, iter);
1173 found = find_string_in_list_utf8nocase(*kw_list, name);
1177 *kw_list = g_list_remove(*kw_list, found);
1183 static void keyword_tree_reset_recursive(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1186 keyword_tree_reset1(keyword_tree, iter, kw_list);
1188 if (!gtk_tree_model_iter_children(keyword_tree, &child, iter)) return;
1192 keyword_tree_reset_recursive(keyword_tree, &child, kw_list);
1193 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return;
1197 static gboolean keyword_tree_check_empty_children(GtkTreeModel *keyword_tree, GtkTreeIter *parent, GList *kw_list)
1201 if (!gtk_tree_model_iter_children(keyword_tree, &iter, parent))
1202 return TRUE; /* this should happen only on empty helpers */
1206 if (keyword_tree_is_set(keyword_tree, &iter, kw_list)) return FALSE;
1207 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return TRUE;
1211 void keyword_tree_reset(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1213 GtkTreeIter iter = *iter_ptr;
1215 keyword_tree_reset_recursive(keyword_tree, &iter, kw_list);
1217 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1220 while (keyword_tree_check_empty_children(keyword_tree, &iter, *kw_list))
1223 keyword_tree_reset1(keyword_tree, &iter, kw_list);
1224 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1229 void keyword_delete(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr)
1233 while (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, iter_ptr))
1235 keyword_delete(keyword_tree, &child);
1238 meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree), iter_ptr, -1);
1240 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter_ptr, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1243 gtk_tree_store_remove(keyword_tree, iter_ptr);
1247 void keyword_hide_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1250 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1251 if (!g_list_find(list, id))
1253 list = g_list_prepend(list, id);
1254 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1258 void keyword_show_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1261 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1262 list = g_list_remove(list, id);
1263 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1266 gboolean keyword_is_hidden_in(GtkTreeModel *keyword_tree, GtkTreeIter *iter, gpointer id)
1269 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1270 return !!g_list_find(list, id);
1273 static gboolean keyword_show_all_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
1275 keyword_show_in(GTK_TREE_STORE(model), iter, data);
1279 void keyword_show_all_in(GtkTreeStore *keyword_tree, gpointer id)
1281 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_all_in_cb, id);
1284 static void keyword_hide_unset_in_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr, gpointer id, GList *keywords)
1286 GtkTreeIter iter = *iter_ptr;
1289 if (!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
1291 keyword_hide_in(keyword_tree, &iter, id);
1292 /* no need to check children of hidden node */
1297 if (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, &iter))
1299 keyword_hide_unset_in_recursive(keyword_tree, &child, id, keywords);
1302 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1306 void keyword_hide_unset_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1309 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1310 keyword_hide_unset_in_recursive(keyword_tree, &iter, id, keywords);
1313 static gboolean keyword_show_set_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter_ptr, gpointer data)
1315 GtkTreeIter iter = *iter_ptr;
1316 GList *keywords = data;
1317 gpointer id = keywords->data;
1318 keywords = keywords->next; /* hack */
1319 if (keyword_tree_is_set(model, &iter, keywords))
1324 keyword_show_in(GTK_TREE_STORE(model), &iter, id);
1325 if (!gtk_tree_model_iter_parent(GTK_TREE_MODEL(keyword_tree), &parent, &iter)) break;
1332 void keyword_show_set_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1334 /* hack: pass id to keyword_hide_unset_in_cb in the list */
1335 keywords = g_list_prepend(keywords, id);
1336 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_set_in_cb, keywords);
1337 keywords = g_list_delete_link(keywords, keywords);
1341 void keyword_tree_new(void)
1343 if (keyword_tree) return;
1345 keyword_tree = gtk_tree_store_new(KEYWORD_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER);
1348 static GtkTreeIter keyword_tree_default_append(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar *name, gboolean is_keyword)
1351 gtk_tree_store_append(keyword_tree, &iter, parent);
1352 keyword_set(keyword_tree, &iter, name, is_keyword);
1356 void keyword_tree_new_default(void)
1358 GtkTreeIter i1, i2, i3;
1360 if (!keyword_tree) keyword_tree_new();
1362 i1 = keyword_tree_default_append(keyword_tree, NULL, _("People"), TRUE);
1363 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Family"), TRUE);
1364 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Free time"), TRUE);
1365 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Children"), TRUE);
1366 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sport"), TRUE);
1367 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Culture"), TRUE);
1368 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Festival"), TRUE);
1369 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Nature"), TRUE);
1370 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Animal"), TRUE);
1371 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Bird"), TRUE);
1372 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Insect"), TRUE);
1373 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Pets"), TRUE);
1374 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Wildlife"), TRUE);
1375 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Zoo"), TRUE);
1376 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Plant"), TRUE);
1377 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Tree"), TRUE);
1378 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Flower"), TRUE);
1379 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Water"), TRUE);
1380 i3 = keyword_tree_default_append(keyword_tree, &i2, _("River"), TRUE);
1381 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Lake"), TRUE);
1382 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Sea"), TRUE);
1383 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Landscape"), TRUE);
1384 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Art"), TRUE);
1385 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Statue"), TRUE);
1386 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Painting"), TRUE);
1387 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1388 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1389 i1 = keyword_tree_default_append(keyword_tree, NULL, _("City"), TRUE);
1390 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Park"), TRUE);
1391 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Street"), TRUE);
1392 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Square"), TRUE);
1393 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Architecture"), TRUE);
1394 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Buildings"), FALSE);
1395 i3 = keyword_tree_default_append(keyword_tree, &i2, _("House"), TRUE);
1396 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Cathedral"), TRUE);
1397 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Palace"), TRUE);
1398 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Castle"), TRUE);
1399 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Bridge"), TRUE);
1400 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Interior"), TRUE);
1401 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1402 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1403 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Places"), FALSE);
1404 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Conditions"), FALSE);
1405 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Night"), TRUE);
1406 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Lights"), TRUE);
1407 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Reflections"), TRUE);
1408 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sun"), TRUE);
1409 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Weather"), FALSE);
1410 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Fog"), TRUE);
1411 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Rain"), TRUE);
1412 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Clouds"), TRUE);
1413 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Snow"), TRUE);
1414 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Sunny weather"), TRUE);
1415 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Photo"), FALSE);
1416 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Edited"), TRUE);
1417 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Detail"), TRUE);
1418 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Macro"), TRUE);
1419 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Portrait"), TRUE);
1420 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Black and White"), TRUE);
1421 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Perspective"), TRUE);
1425 static void keyword_tree_node_write_config(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GString *outstr, gint indent)
1427 GtkTreeIter iter = *iter_ptr;
1430 GtkTreeIter children;
1433 WRITE_NL(); WRITE_STRING("<keyword ");
1434 name = keyword_get_name(keyword_tree, &iter);
1435 write_char_option(outstr, indent, "name", name);
1437 write_bool_option(outstr, indent, "kw", keyword_get_is_keyword(keyword_tree, &iter));
1438 if (gtk_tree_model_iter_children(keyword_tree, &children, &iter))
1442 keyword_tree_node_write_config(keyword_tree, &children, outstr, indent);
1444 WRITE_NL(); WRITE_STRING("</keyword>");
1450 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return;
1454 void keyword_tree_write_config(GString *outstr, gint indent)
1457 WRITE_NL(); WRITE_STRING("<keyword_tree>");
1460 if (keyword_tree && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1462 keyword_tree_node_write_config(GTK_TREE_MODEL(keyword_tree), &iter, outstr, indent);
1465 WRITE_NL(); WRITE_STRING("</keyword_tree>");
1468 GtkTreeIter *keyword_add_from_config(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar **attribute_names, const gchar **attribute_values)
1471 gboolean is_kw = TRUE;
1473 while (*attribute_names)
1475 const gchar *option = *attribute_names++;
1476 const gchar *value = *attribute_values++;
1478 if (READ_CHAR_FULL("name", name)) continue;
1479 if (READ_BOOL_FULL("kw", is_kw)) continue;
1481 log_printf("unknown attribute %s = %s\n", option, value);
1483 if (name && name[0])
1486 /* re-use existing keyword if any */
1487 if (!keyword_exists(GTK_TREE_MODEL(keyword_tree), parent, NULL, name, FALSE, &iter))
1489 gtk_tree_store_append(keyword_tree, &iter, parent);
1491 keyword_set(keyword_tree, &iter, name, is_kw);
1493 return gtk_tree_iter_copy(&iter);
1499 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */