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);
62 static gboolean metadata_file_read(gchar *path, GList **keywords, gchar **comment);
67 *-------------------------------------------------------------------
69 *-------------------------------------------------------------------
72 static GList *metadata_write_queue = NULL;
73 static guint metadata_write_idle_id = 0; /* event source id */
75 static void metadata_write_queue_add(FileData *fd)
77 if (!g_list_find(metadata_write_queue, fd))
79 metadata_write_queue = g_list_prepend(metadata_write_queue, fd);
82 layout_util_status_update_write_all();
85 if (metadata_write_idle_id)
87 g_source_remove(metadata_write_idle_id);
88 metadata_write_idle_id = 0;
91 if (options->metadata.confirm_after_timeout)
93 metadata_write_idle_id = g_timeout_add(options->metadata.confirm_timeout * 1000, metadata_write_queue_idle_cb, NULL);
98 gboolean metadata_write_queue_remove(FileData *fd)
100 g_hash_table_destroy(fd->modified_xmp);
101 fd->modified_xmp = NULL;
103 metadata_write_queue = g_list_remove(metadata_write_queue, fd);
105 file_data_increment_version(fd);
106 file_data_send_notification(fd, NOTIFY_REREAD);
110 layout_util_status_update_write_all();
114 gboolean metadata_write_queue_remove_list(GList *list)
122 FileData *fd = work->data;
124 ret = ret && metadata_write_queue_remove(fd);
130 gboolean metadata_write_queue_confirm(gboolean force_dialog, FileUtilDoneFunc done_func, gpointer done_data)
133 GList *to_approve = NULL;
135 work = metadata_write_queue;
138 FileData *fd = work->data;
141 if (fd->change) continue; /* another operation in progress, skip this file for now */
143 to_approve = g_list_prepend(to_approve, file_data_ref(fd));
146 file_util_write_metadata(NULL, to_approve, NULL, force_dialog, done_func, done_data);
148 return (metadata_write_queue != NULL);
151 static gboolean metadata_write_queue_idle_cb(gpointer data)
153 metadata_write_queue_confirm(FALSE, NULL, NULL);
154 metadata_write_idle_id = 0;
158 gboolean metadata_write_perform(FileData *fd)
163 g_assert(fd->change);
165 if (fd->change->dest &&
166 strcmp(extension_from_path(fd->change->dest), GQ_CACHE_EXT_METADATA) == 0)
168 success = metadata_legacy_write(fd);
169 if (success) metadata_legacy_delete(fd, fd->change->dest);
173 /* write via exiv2 */
174 /* we can either use cached metadata which have fd->modified_xmp already applied
175 or read metadata from file and apply fd->modified_xmp
176 metadata are read also if the file was modified meanwhile */
177 exif = exif_read_fd(fd);
178 if (!exif) return FALSE;
180 success = (fd->change->dest) ? exif_write_sidecar(exif, fd->change->dest) : exif_write(exif); /* write modified metadata */
181 exif_free_fd(fd, exif);
183 if (fd->change->dest)
184 /* this will create a FileData for the sidecar and link it to the main file
185 (we can't wait until the sidecar is discovered by directory scanning because
186 exif_read_fd is called before that and it would read the main file only and
187 store the metadata in the cache)
188 FIXME: this does not catch new sidecars created by independent external programs
190 file_data_unref(file_data_new_simple(fd->change->dest));
192 if (success) metadata_legacy_delete(fd, fd->change->dest);
196 gint metadata_queue_length(void)
198 return g_list_length(metadata_write_queue);
201 static gboolean metadata_check_key(const gchar *keys[], const gchar *key)
203 const gchar **k = keys;
207 if (strcmp(key, *k) == 0) return TRUE;
213 gboolean metadata_write_revert(FileData *fd, const gchar *key)
215 if (!fd->modified_xmp) return FALSE;
217 g_hash_table_remove(fd->modified_xmp, key);
219 if (g_hash_table_size(fd->modified_xmp) == 0)
221 metadata_write_queue_remove(fd);
225 /* reread the metadata to restore the original value */
226 file_data_increment_version(fd);
227 file_data_send_notification(fd, NOTIFY_REREAD);
232 gboolean metadata_write_list(FileData *fd, const gchar *key, const GList *values)
234 if (!fd->modified_xmp)
236 fd->modified_xmp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)string_list_free);
238 g_hash_table_insert(fd->modified_xmp, g_strdup(key), string_list_copy((GList *)values));
241 exif_update_metadata(fd->exif, key, values);
243 metadata_write_queue_add(fd);
244 file_data_increment_version(fd);
245 file_data_send_notification(fd, NOTIFY_METADATA);
247 if (options->metadata.sync_grouped_files && metadata_check_key(group_keys, key))
249 GList *work = fd->sidecar_files;
253 FileData *sfd = work->data;
256 if (filter_file_class(sfd->extension, FORMAT_CLASS_META)) continue;
258 metadata_write_list(sfd, key, values);
266 gboolean metadata_write_string(FileData *fd, const gchar *key, const char *value)
268 GList *list = g_list_append(NULL, g_strdup(value));
269 gboolean ret = metadata_write_list(fd, key, list);
270 string_list_free(list);
274 gboolean metadata_write_int(FileData *fd, const gchar *key, guint64 value)
278 g_snprintf(string, sizeof(string), "%ld", value);
279 return metadata_write_string(fd, key, string);
283 *-------------------------------------------------------------------
284 * keyword / comment read/write
285 *-------------------------------------------------------------------
288 static gboolean metadata_file_write(gchar *path, const GList *keywords, const gchar *comment)
292 ssi = secure_open(path);
293 if (!ssi) return FALSE;
295 secure_fprintf(ssi, "#%s comment (%s)\n\n", GQ_APPNAME, VERSION);
297 secure_fprintf(ssi, "[keywords]\n");
298 while (keywords && secsave_errno == SS_ERR_NONE)
300 const gchar *word = keywords->data;
301 keywords = keywords->next;
303 secure_fprintf(ssi, "%s\n", word);
305 secure_fputc(ssi, '\n');
307 secure_fprintf(ssi, "[comment]\n");
308 secure_fprintf(ssi, "%s\n", (comment) ? comment : "");
310 secure_fprintf(ssi, "#end\n");
312 return (secure_close(ssi) == 0);
315 static gboolean metadata_legacy_write(FileData *fd)
317 gboolean success = FALSE;
318 gchar *metadata_pathl;
321 gboolean have_keywords;
322 gboolean have_comment;
323 const gchar *comment;
324 GList *orig_keywords = NULL;
325 gchar *orig_comment = NULL;
327 g_assert(fd->change && fd->change->dest);
329 DEBUG_1("Saving comment: %s", fd->change->dest);
331 if (!fd->modified_xmp) return TRUE;
333 metadata_pathl = path_from_utf8(fd->change->dest);
335 have_keywords = g_hash_table_lookup_extended(fd->modified_xmp, KEYWORD_KEY, NULL, &keywords);
336 have_comment = g_hash_table_lookup_extended(fd->modified_xmp, COMMENT_KEY, NULL, &comment_l);
337 comment = (have_comment && comment_l) ? ((GList *)comment_l)->data : NULL;
339 if (!have_keywords || !have_comment) metadata_file_read(metadata_pathl, &orig_keywords, &orig_comment);
341 success = metadata_file_write(metadata_pathl,
342 have_keywords ? (GList *)keywords : orig_keywords,
343 have_comment ? comment : orig_comment);
345 g_free(metadata_pathl);
346 g_free(orig_comment);
347 string_list_free(orig_keywords);
352 static gboolean metadata_file_read(gchar *path, GList **keywords, gchar **comment)
356 MetadataKey key = MK_NONE;
358 GString *comment_build = NULL;
360 f = fopen(path, "r");
361 if (!f) return FALSE;
363 while (fgets(s_buf, sizeof(s_buf), f))
367 if (*ptr == '#') continue;
368 if (*ptr == '[' && key != MK_COMMENT)
370 gchar *keystr = ++ptr;
373 while (*ptr != ']' && *ptr != '\n' && *ptr != '\0') ptr++;
378 if (g_ascii_strcasecmp(keystr, "keywords") == 0)
380 else if (g_ascii_strcasecmp(keystr, "comment") == 0)
392 while (*ptr != '\n' && *ptr != '\0') ptr++;
394 if (strlen(s_buf) > 0)
396 gchar *kw = utf8_validate_or_convert(s_buf);
398 list = g_list_prepend(list, kw);
403 if (!comment_build) comment_build = g_string_new("");
404 g_string_append(comment_build, s_buf);
413 *keywords = g_list_reverse(list);
417 string_list_free(list);
425 gchar *ptr = comment_build->str;
427 /* strip leading and trailing newlines */
428 while (*ptr == '\n') ptr++;
430 while (len > 0 && ptr[len - 1] == '\n') len--;
431 if (ptr[len] == '\n') len++; /* keep the last one */
434 gchar *text = g_strndup(ptr, len);
436 *comment = utf8_validate_or_convert(text);
440 g_string_free(comment_build, TRUE);
446 static void metadata_legacy_delete(FileData *fd, const gchar *except)
448 gchar *metadata_path;
449 gchar *metadata_pathl;
452 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
453 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
455 metadata_pathl = path_from_utf8(metadata_path);
456 unlink(metadata_pathl);
457 g_free(metadata_pathl);
458 g_free(metadata_path);
462 /* without exiv2: do not delete xmp metadata because we are not able to convert it,
464 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
465 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
467 metadata_pathl = path_from_utf8(metadata_path);
468 unlink(metadata_pathl);
469 g_free(metadata_pathl);
470 g_free(metadata_path);
475 static gboolean metadata_legacy_read(FileData *fd, GList **keywords, gchar **comment)
477 gchar *metadata_path;
478 gchar *metadata_pathl;
479 gboolean success = FALSE;
481 if (!fd) return FALSE;
483 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
484 if (!metadata_path) return FALSE;
486 metadata_pathl = path_from_utf8(metadata_path);
488 success = metadata_file_read(metadata_pathl, keywords, comment);
490 g_free(metadata_pathl);
491 g_free(metadata_path);
496 static GList *remove_duplicate_strings_from_list(GList *list)
499 GHashTable *hashtable = g_hash_table_new(g_str_hash, g_str_equal);
500 GList *newlist = NULL;
504 gchar *key = work->data;
506 if (g_hash_table_lookup(hashtable, key) == NULL)
508 g_hash_table_insert(hashtable, (gpointer) key, GINT_TO_POINTER(1));
509 newlist = g_list_prepend(newlist, key);
514 g_hash_table_destroy(hashtable);
517 return g_list_reverse(newlist);
520 GList *metadata_read_list(FileData *fd, const gchar *key, MetadataFormat format)
524 if (!fd) return NULL;
526 /* unwritten data overide everything */
527 if (fd->modified_xmp && format == METADATA_PLAIN)
529 list = g_hash_table_lookup(fd->modified_xmp, key);
530 if (list) return string_list_copy(list);
534 Legacy metadata file is the primary source if it exists.
535 Merging the lists does not make much sense, because the existence of
536 legacy metadata file indicates that the other metadata sources are not
537 writable and thus it would not be possible to delete the keywords
538 that comes from the image file.
540 if (strcmp(key, KEYWORD_KEY) == 0)
542 if (metadata_legacy_read(fd, &list, NULL)) return list;
544 else if (strcmp(key, COMMENT_KEY) == 0)
546 gchar *comment = NULL;
547 if (metadata_legacy_read(fd, NULL, &comment)) return g_list_append(NULL, comment);
549 else if (strncmp(key, "file.", 5) == 0)
551 return g_list_append(NULL, metadata_file_info(fd, key, format));
554 exif = exif_read_fd(fd); /* this is cached, thus inexpensive */
555 if (!exif) return NULL;
556 list = exif_get_metadata(exif, key, format);
557 exif_free_fd(fd, exif);
561 gchar *metadata_read_string(FileData *fd, const gchar *key, MetadataFormat format)
563 GList *string_list = metadata_read_list(fd, key, format);
566 gchar *str = string_list->data;
567 string_list->data = NULL;
568 string_list_free(string_list);
574 guint64 metadata_read_int(FileData *fd, const gchar *key, guint64 fallback)
578 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
579 if (!string) return fallback;
581 ret = g_ascii_strtoull(string, &endptr, 10);
582 if (string == endptr) ret = fallback;
587 gdouble metadata_read_GPS_coord(FileData *fd, const gchar *key, gdouble fallback)
591 gdouble deg, min, sec;
593 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
594 if (!string) return fallback;
596 deg = g_ascii_strtod(string, &endptr);
599 min = g_ascii_strtod(endptr + 1, &endptr);
601 sec = g_ascii_strtod(endptr + 1, &endptr);
606 if (*endptr == 'S' || *endptr == 'W' || *endptr == 'N' || *endptr == 'E')
608 coord = deg + min /60.0 + sec / 3600.0;
610 if (*endptr == 'S' || *endptr == 'W') coord = -coord;
617 log_printf("unable to parse GPS coordinate '%s'\n", string);
624 gboolean metadata_append_string(FileData *fd, const gchar *key, const char *value)
626 gchar *str = metadata_read_string(fd, key, METADATA_PLAIN);
630 return metadata_write_string(fd, key, value);
634 gchar *new_string = g_strconcat(str, value, NULL);
635 gboolean ret = metadata_write_string(fd, key, new_string);
642 gboolean metadata_append_list(FileData *fd, const gchar *key, const GList *values)
644 GList *list = metadata_read_list(fd, key, METADATA_PLAIN);
648 return metadata_write_list(fd, key, values);
653 list = g_list_concat(list, string_list_copy(values));
654 list = remove_duplicate_strings_from_list(list);
656 ret = metadata_write_list(fd, key, list);
657 string_list_free(list);
662 gchar *find_string_in_list_utf8nocase(GList *list, const gchar *string)
664 gchar *string_casefold = g_utf8_casefold(string, -1);
668 gchar *haystack = list->data;
673 gchar *haystack_casefold = g_utf8_casefold(haystack, -1);
675 equal = (strcmp(haystack_casefold, string_casefold) == 0);
676 g_free(haystack_casefold);
680 g_free(string_casefold);
688 g_free(string_casefold);
693 #define KEYWORDS_SEPARATOR(c) ((c) == ',' || (c) == ';' || (c) == '\n' || (c) == '\r' || (c) == '\b')
695 GList *string_to_keywords_list(const gchar *text)
698 const gchar *ptr = text;
705 while (KEYWORDS_SEPARATOR(*ptr)) ptr++;
707 while (*ptr != '\0' && !KEYWORDS_SEPARATOR(*ptr))
713 /* trim starting and ending whitespaces */
714 while (l > 0 && g_ascii_isspace(*begin)) begin++, l--;
715 while (l > 0 && g_ascii_isspace(begin[l-1])) l--;
719 gchar *keyword = g_strndup(begin, l);
721 /* only add if not already in the list */
722 if (!find_string_in_list_utf8nocase(list, keyword))
723 list = g_list_append(list, keyword);
737 gboolean meta_data_get_keyword_mark(FileData *fd, gint n, gpointer data)
739 /* FIXME: do not use global keyword_tree */
742 gboolean found = FALSE;
743 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
747 if (keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path) &&
748 keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
755 gboolean meta_data_set_keyword_mark(FileData *fd, gint n, gboolean value, gpointer data)
758 GList *keywords = NULL;
761 if (!keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path)) return FALSE;
763 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
765 if (!!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords) != !!value)
769 keyword_tree_set(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
773 keyword_tree_reset(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
775 metadata_write_list(fd, KEYWORD_KEY, keywords);
778 string_list_free(keywords);
784 void meta_data_connect_mark_with_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *kw_iter, gint mark)
787 FileDataGetMarkFunc get_mark_func;
788 FileDataSetMarkFunc set_mark_func;
789 gpointer mark_func_data;
793 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
795 file_data_get_registered_mark_func(i, &get_mark_func, &set_mark_func, &mark_func_data);
796 if (get_mark_func == meta_data_get_keyword_mark)
798 GtkTreeIter old_kw_iter;
799 GList *old_path = mark_func_data;
801 if (keyword_tree_get_iter(keyword_tree, &old_kw_iter, old_path) &&
802 (i == mark || /* release any previous connection of given mark */
803 keyword_compare(keyword_tree, &old_kw_iter, kw_iter) == 0)) /* or given keyword */
805 file_data_register_mark_func(i, NULL, NULL, NULL, NULL);
806 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), &old_kw_iter, KEYWORD_COLUMN_MARK, "", -1);
812 if (mark >= 0 && mark < FILEDATA_MARKS_SIZE)
816 path = keyword_tree_get_path(keyword_tree, kw_iter);
817 file_data_register_mark_func(mark, meta_data_get_keyword_mark, meta_data_set_keyword_mark, path, (GDestroyNotify)string_list_free);
819 mark_str = g_strdup_printf("%d", mark + 1);
820 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), kw_iter, KEYWORD_COLUMN_MARK, mark_str, -1);
827 *-------------------------------------------------------------------
829 *-------------------------------------------------------------------
834 GtkTreeStore *keyword_tree;
836 gchar *keyword_get_name(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
839 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_NAME, &name, -1);
843 gchar *keyword_get_casefold(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
846 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_CASEFOLD, &casefold, -1);
850 gboolean keyword_get_is_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
853 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
857 void keyword_set(GtkTreeStore *keyword_tree, GtkTreeIter *iter, const gchar *name, gboolean is_keyword)
859 gchar *casefold = g_utf8_casefold(name, -1);
860 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_MARK, "",
861 KEYWORD_COLUMN_NAME, name,
862 KEYWORD_COLUMN_CASEFOLD, casefold,
863 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
867 gboolean keyword_compare(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
869 GtkTreePath *pa = gtk_tree_model_get_path(keyword_tree, a);
870 GtkTreePath *pb = gtk_tree_model_get_path(keyword_tree, b);
871 gint ret = gtk_tree_path_compare(pa, pb);
872 gtk_tree_path_free(pa);
873 gtk_tree_path_free(pb);
877 gboolean keyword_same_parent(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
879 GtkTreeIter parent_a;
880 GtkTreeIter parent_b;
882 gboolean valid_pa = gtk_tree_model_iter_parent(keyword_tree, &parent_a, a);
883 gboolean valid_pb = gtk_tree_model_iter_parent(keyword_tree, &parent_b, b);
885 if (valid_pa && valid_pb)
887 return keyword_compare(keyword_tree, &parent_a, &parent_b) == 0;
891 return (!valid_pa && !valid_pb); /* both are toplevel */
895 gboolean keyword_exists(GtkTreeModel *keyword_tree, GtkTreeIter *parent_ptr, GtkTreeIter *sibling, const gchar *name, gboolean exclude_sibling, GtkTreeIter *result)
899 gboolean toplevel = FALSE;
905 parent = *parent_ptr;
909 toplevel = !gtk_tree_model_iter_parent(keyword_tree, &parent, sibling);
916 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &iter, toplevel ? NULL : &parent)) return FALSE;
918 casefold = g_utf8_casefold(name, -1);
923 if (!(exclude_sibling && sibling && keyword_compare(keyword_tree, &iter, sibling) == 0))
925 if (options->metadata.keywords_case_sensitive)
927 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
928 ret = strcmp(name, iter_name) == 0;
933 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
934 ret = strcmp(casefold, iter_casefold) == 0;
935 g_free(iter_casefold);
936 } // if (options->metadata.tags_cas...
940 if (result) *result = iter;
943 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) break;
950 void keyword_copy(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
953 gchar *mark, *name, *casefold;
956 /* do not copy KEYWORD_COLUMN_HIDE_IN, it fully shows the new subtree */
957 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), from, KEYWORD_COLUMN_MARK, &mark,
958 KEYWORD_COLUMN_NAME, &name,
959 KEYWORD_COLUMN_CASEFOLD, &casefold,
960 KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
962 gtk_tree_store_set(keyword_tree, to, KEYWORD_COLUMN_MARK, mark,
963 KEYWORD_COLUMN_NAME, name,
964 KEYWORD_COLUMN_CASEFOLD, casefold,
965 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
971 void keyword_copy_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
973 GtkTreeIter from_child;
975 keyword_copy(keyword_tree, to, from);
977 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &from_child, from)) return;
981 GtkTreeIter to_child;
982 gtk_tree_store_append(keyword_tree, &to_child, to);
983 keyword_copy_recursive(keyword_tree, &to_child, &from_child);
984 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &from_child)) return;
988 void keyword_move_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
990 keyword_copy_recursive(keyword_tree, to, from);
991 keyword_delete(keyword_tree, from);
994 GList *keyword_tree_get_path(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
997 GtkTreeIter iter = *iter_ptr;
1002 path = g_list_prepend(path, keyword_get_name(keyword_tree, &iter));
1003 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) break;
1009 gboolean keyword_tree_get_iter(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList *path)
1013 if (!gtk_tree_model_get_iter_first(keyword_tree, &iter)) return FALSE;
1017 GtkTreeIter children;
1020 gchar *name = keyword_get_name(keyword_tree, &iter);
1021 if (strcmp(name, path->data) == 0) break;
1023 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return FALSE;
1032 if (!gtk_tree_model_iter_children(keyword_tree, &children, &iter)) return FALSE;
1038 static gboolean keyword_tree_is_set_casefold(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *casefold_list)
1040 if (!casefold_list) return FALSE;
1042 if (!keyword_get_is_keyword(keyword_tree, &iter))
1044 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1046 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1047 return FALSE; /* this should happen only on empty helpers */
1051 if (keyword_tree_is_set_casefold(keyword_tree, child, casefold_list)) return TRUE;
1052 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1060 if (keyword_get_is_keyword(keyword_tree, &iter))
1062 GList *work = casefold_list;
1063 gboolean found = FALSE;
1064 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
1067 const gchar *casefold = work->data;
1070 if (strcmp(iter_casefold, casefold) == 0)
1076 g_free(iter_casefold);
1077 if (!found) return FALSE;
1080 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1085 static gboolean keyword_tree_is_set_casefull(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *kw_list)
1087 if (!kw_list) return FALSE;
1089 if (!keyword_get_is_keyword(keyword_tree, &iter))
1091 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1093 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1094 return FALSE; /* this should happen only on empty helpers */
1098 if (keyword_tree_is_set_casefull(keyword_tree, child, kw_list)) return TRUE;
1099 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1107 if (keyword_get_is_keyword(keyword_tree, &iter))
1109 GList *work = kw_list;
1110 gboolean found = FALSE;
1111 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
1114 const gchar *name = work->data;
1117 if (strcmp(iter_name, name) == 0)
1124 if (!found) return FALSE;
1127 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1132 gboolean keyword_tree_is_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList *kw_list)
1135 GList *casefold_list = NULL;
1138 if (options->metadata.keywords_case_sensitive)
1140 ret = keyword_tree_is_set_casefull(keyword_tree, *iter, kw_list);
1147 const gchar *kw = work->data;
1150 casefold_list = g_list_prepend(casefold_list, g_utf8_casefold(kw, -1));
1153 ret = keyword_tree_is_set_casefold(keyword_tree, *iter, casefold_list);
1155 string_list_free(casefold_list);
1161 void keyword_tree_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1163 GtkTreeIter iter = *iter_ptr;
1168 if (keyword_get_is_keyword(keyword_tree, &iter))
1170 gchar *name = keyword_get_name(keyword_tree, &iter);
1171 if (!find_string_in_list_utf8nocase(*kw_list, name))
1173 *kw_list = g_list_append(*kw_list, name);
1181 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1186 static void keyword_tree_reset1(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1190 if (!keyword_get_is_keyword(keyword_tree, iter)) return;
1192 name = keyword_get_name(keyword_tree, iter);
1193 found = find_string_in_list_utf8nocase(*kw_list, name);
1197 *kw_list = g_list_remove(*kw_list, found);
1203 static void keyword_tree_reset_recursive(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1206 keyword_tree_reset1(keyword_tree, iter, kw_list);
1208 if (!gtk_tree_model_iter_children(keyword_tree, &child, iter)) return;
1212 keyword_tree_reset_recursive(keyword_tree, &child, kw_list);
1213 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return;
1217 static gboolean keyword_tree_check_empty_children(GtkTreeModel *keyword_tree, GtkTreeIter *parent, GList *kw_list)
1221 if (!gtk_tree_model_iter_children(keyword_tree, &iter, parent))
1222 return TRUE; /* this should happen only on empty helpers */
1226 if (keyword_tree_is_set(keyword_tree, &iter, kw_list)) return FALSE;
1227 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return TRUE;
1231 void keyword_tree_reset(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1233 GtkTreeIter iter = *iter_ptr;
1235 keyword_tree_reset_recursive(keyword_tree, &iter, kw_list);
1237 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1240 while (keyword_tree_check_empty_children(keyword_tree, &iter, *kw_list))
1243 keyword_tree_reset1(keyword_tree, &iter, kw_list);
1244 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1249 void keyword_delete(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr)
1253 while (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, iter_ptr))
1255 keyword_delete(keyword_tree, &child);
1258 meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree), iter_ptr, -1);
1260 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter_ptr, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1263 gtk_tree_store_remove(keyword_tree, iter_ptr);
1267 void keyword_hide_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1270 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1271 if (!g_list_find(list, id))
1273 list = g_list_prepend(list, id);
1274 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1278 void keyword_show_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1281 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1282 list = g_list_remove(list, id);
1283 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1286 gboolean keyword_is_hidden_in(GtkTreeModel *keyword_tree, GtkTreeIter *iter, gpointer id)
1289 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1290 return !!g_list_find(list, id);
1293 static gboolean keyword_show_all_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
1295 keyword_show_in(GTK_TREE_STORE(model), iter, data);
1299 void keyword_show_all_in(GtkTreeStore *keyword_tree, gpointer id)
1301 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_all_in_cb, id);
1304 static void keyword_hide_unset_in_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr, gpointer id, GList *keywords)
1306 GtkTreeIter iter = *iter_ptr;
1309 if (!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
1311 keyword_hide_in(keyword_tree, &iter, id);
1312 /* no need to check children of hidden node */
1317 if (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, &iter))
1319 keyword_hide_unset_in_recursive(keyword_tree, &child, id, keywords);
1322 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1326 void keyword_hide_unset_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1329 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1330 keyword_hide_unset_in_recursive(keyword_tree, &iter, id, keywords);
1333 static gboolean keyword_show_set_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter_ptr, gpointer data)
1335 GtkTreeIter iter = *iter_ptr;
1336 GList *keywords = data;
1337 gpointer id = keywords->data;
1338 keywords = keywords->next; /* hack */
1339 if (keyword_tree_is_set(model, &iter, keywords))
1344 keyword_show_in(GTK_TREE_STORE(model), &iter, id);
1345 if (!gtk_tree_model_iter_parent(GTK_TREE_MODEL(keyword_tree), &parent, &iter)) break;
1352 void keyword_show_set_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1354 /* hack: pass id to keyword_hide_unset_in_cb in the list */
1355 keywords = g_list_prepend(keywords, id);
1356 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_set_in_cb, keywords);
1357 keywords = g_list_delete_link(keywords, keywords);
1361 void keyword_tree_new(void)
1363 if (keyword_tree) return;
1365 keyword_tree = gtk_tree_store_new(KEYWORD_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER);
1368 static GtkTreeIter keyword_tree_default_append(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar *name, gboolean is_keyword)
1371 gtk_tree_store_append(keyword_tree, &iter, parent);
1372 keyword_set(keyword_tree, &iter, name, is_keyword);
1376 void keyword_tree_new_default(void)
1378 GtkTreeIter i1, i2, i3;
1380 if (!keyword_tree) keyword_tree_new();
1382 i1 = keyword_tree_default_append(keyword_tree, NULL, _("People"), TRUE);
1383 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Family"), TRUE);
1384 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Free time"), TRUE);
1385 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Children"), TRUE);
1386 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sport"), TRUE);
1387 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Culture"), TRUE);
1388 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Festival"), TRUE);
1389 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Nature"), TRUE);
1390 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Animal"), TRUE);
1391 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Bird"), TRUE);
1392 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Insect"), TRUE);
1393 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Pets"), TRUE);
1394 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Wildlife"), TRUE);
1395 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Zoo"), TRUE);
1396 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Plant"), TRUE);
1397 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Tree"), TRUE);
1398 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Flower"), TRUE);
1399 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Water"), TRUE);
1400 i3 = keyword_tree_default_append(keyword_tree, &i2, _("River"), TRUE);
1401 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Lake"), TRUE);
1402 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Sea"), TRUE);
1403 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Landscape"), TRUE);
1404 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Art"), TRUE);
1405 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Statue"), TRUE);
1406 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Painting"), TRUE);
1407 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1408 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1409 i1 = keyword_tree_default_append(keyword_tree, NULL, _("City"), TRUE);
1410 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Park"), TRUE);
1411 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Street"), TRUE);
1412 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Square"), TRUE);
1413 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Architecture"), TRUE);
1414 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Buildings"), FALSE);
1415 i3 = keyword_tree_default_append(keyword_tree, &i2, _("House"), TRUE);
1416 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Cathedral"), TRUE);
1417 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Palace"), TRUE);
1418 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Castle"), TRUE);
1419 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Bridge"), TRUE);
1420 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Interior"), TRUE);
1421 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1422 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1423 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Places"), FALSE);
1424 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Conditions"), FALSE);
1425 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Night"), TRUE);
1426 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Lights"), TRUE);
1427 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Reflections"), TRUE);
1428 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sun"), TRUE);
1429 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Weather"), FALSE);
1430 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Fog"), TRUE);
1431 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Rain"), TRUE);
1432 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Clouds"), TRUE);
1433 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Snow"), TRUE);
1434 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Sunny weather"), TRUE);
1435 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Photo"), FALSE);
1436 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Edited"), TRUE);
1437 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Detail"), TRUE);
1438 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Macro"), TRUE);
1439 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Portrait"), TRUE);
1440 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Black and White"), TRUE);
1441 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Perspective"), TRUE);
1445 static void keyword_tree_node_write_config(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GString *outstr, gint indent)
1447 GtkTreeIter iter = *iter_ptr;
1450 GtkTreeIter children;
1453 WRITE_NL(); WRITE_STRING("<keyword ");
1454 name = keyword_get_name(keyword_tree, &iter);
1455 write_char_option(outstr, indent, "name", name);
1457 write_bool_option(outstr, indent, "kw", keyword_get_is_keyword(keyword_tree, &iter));
1458 if (gtk_tree_model_iter_children(keyword_tree, &children, &iter))
1462 keyword_tree_node_write_config(keyword_tree, &children, outstr, indent);
1464 WRITE_NL(); WRITE_STRING("</keyword>");
1470 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return;
1474 void keyword_tree_write_config(GString *outstr, gint indent)
1477 WRITE_NL(); WRITE_STRING("<keyword_tree>");
1480 if (keyword_tree && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1482 keyword_tree_node_write_config(GTK_TREE_MODEL(keyword_tree), &iter, outstr, indent);
1485 WRITE_NL(); WRITE_STRING("</keyword_tree>");
1488 GtkTreeIter *keyword_add_from_config(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar **attribute_names, const gchar **attribute_values)
1491 gboolean is_kw = TRUE;
1493 while (*attribute_names)
1495 const gchar *option = *attribute_names++;
1496 const gchar *value = *attribute_values++;
1498 if (READ_CHAR_FULL("name", name)) continue;
1499 if (READ_BOOL_FULL("kw", is_kw)) continue;
1501 log_printf("unknown attribute %s = %s\n", option, value);
1503 if (name && name[0])
1506 /* re-use existing keyword if any */
1507 if (!keyword_exists(GTK_TREE_MODEL(keyword_tree), parent, NULL, name, FALSE, &iter))
1509 gtk_tree_store_append(keyword_tree, &iter, parent);
1511 keyword_set(keyword_tree, &iter, name, is_kw);
1513 return gtk_tree_iter_copy(&iter);
1519 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */