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);
663 * \see find_string_in_list
665 gchar *find_string_in_list_utf8nocase(GList *list, const gchar *string)
667 gchar *string_casefold = g_utf8_casefold(string, -1);
671 gchar *haystack = list->data;
676 gchar *haystack_casefold = g_utf8_casefold(haystack, -1);
678 equal = (strcmp(haystack_casefold, string_casefold) == 0);
679 g_free(haystack_casefold);
683 g_free(string_casefold);
691 g_free(string_casefold);
696 * \see find_string_in_list
698 gchar *find_string_in_list_utf8case(GList *list, const gchar *string)
702 gchar *haystack = list->data;
704 if (haystack && strcmp(haystack, string) == 0)
711 } // gchar *find_string_in_list_utf...
714 * \brief Find a existent string in a list.
716 * This is a switch between find_string_in_list_utf8case and
717 * find_string_in_list_utf8nocase to search with or without case for the
718 * existence of a string.
720 * \param list The list to search in
721 * \param string The string to search for
722 * \return The string or NULL
724 * \see find_string_in_list_utf8case
725 * \see find_string_in_list_utf8nocase
727 gchar *find_string_in_list(GList *list, const gchar *string)
729 if (options->metadata.keywords_case_sensitive)
730 return find_string_in_list_utf8case(list, string);
732 return find_string_in_list_utf8nocase(list, string);
735 #define KEYWORDS_SEPARATOR(c) ((c) == ',' || (c) == ';' || (c) == '\n' || (c) == '\r' || (c) == '\b')
737 GList *string_to_keywords_list(const gchar *text)
740 const gchar *ptr = text;
747 while (KEYWORDS_SEPARATOR(*ptr)) ptr++;
749 while (*ptr != '\0' && !KEYWORDS_SEPARATOR(*ptr))
755 /* trim starting and ending whitespaces */
756 while (l > 0 && g_ascii_isspace(*begin)) begin++, l--;
757 while (l > 0 && g_ascii_isspace(begin[l-1])) l--;
761 gchar *keyword = g_strndup(begin, l);
763 /* only add if not already in the list */
764 if (!find_string_in_list(list, keyword))
765 list = g_list_append(list, keyword);
779 gboolean meta_data_get_keyword_mark(FileData *fd, gint n, gpointer data)
781 /* FIXME: do not use global keyword_tree */
784 gboolean found = FALSE;
785 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
789 if (keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path) &&
790 keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
797 gboolean meta_data_set_keyword_mark(FileData *fd, gint n, gboolean value, gpointer data)
800 GList *keywords = NULL;
803 if (!keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path)) return FALSE;
805 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
807 if (!!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords) != !!value)
811 keyword_tree_set(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
815 keyword_tree_reset(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
817 metadata_write_list(fd, KEYWORD_KEY, keywords);
820 string_list_free(keywords);
826 void meta_data_connect_mark_with_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *kw_iter, gint mark)
829 FileDataGetMarkFunc get_mark_func;
830 FileDataSetMarkFunc set_mark_func;
831 gpointer mark_func_data;
835 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
837 file_data_get_registered_mark_func(i, &get_mark_func, &set_mark_func, &mark_func_data);
838 if (get_mark_func == meta_data_get_keyword_mark)
840 GtkTreeIter old_kw_iter;
841 GList *old_path = mark_func_data;
843 if (keyword_tree_get_iter(keyword_tree, &old_kw_iter, old_path) &&
844 (i == mark || /* release any previous connection of given mark */
845 keyword_compare(keyword_tree, &old_kw_iter, kw_iter) == 0)) /* or given keyword */
847 file_data_register_mark_func(i, NULL, NULL, NULL, NULL);
848 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), &old_kw_iter, KEYWORD_COLUMN_MARK, "", -1);
854 if (mark >= 0 && mark < FILEDATA_MARKS_SIZE)
858 path = keyword_tree_get_path(keyword_tree, kw_iter);
859 file_data_register_mark_func(mark, meta_data_get_keyword_mark, meta_data_set_keyword_mark, path, (GDestroyNotify)string_list_free);
861 mark_str = g_strdup_printf("%d", mark + 1);
862 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), kw_iter, KEYWORD_COLUMN_MARK, mark_str, -1);
869 *-------------------------------------------------------------------
871 *-------------------------------------------------------------------
876 GtkTreeStore *keyword_tree;
878 gchar *keyword_get_name(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
881 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_NAME, &name, -1);
885 gchar *keyword_get_casefold(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
888 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_CASEFOLD, &casefold, -1);
892 gboolean keyword_get_is_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
895 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
899 void keyword_set(GtkTreeStore *keyword_tree, GtkTreeIter *iter, const gchar *name, gboolean is_keyword)
901 gchar *casefold = g_utf8_casefold(name, -1);
902 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_MARK, "",
903 KEYWORD_COLUMN_NAME, name,
904 KEYWORD_COLUMN_CASEFOLD, casefold,
905 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
909 gboolean keyword_compare(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
911 GtkTreePath *pa = gtk_tree_model_get_path(keyword_tree, a);
912 GtkTreePath *pb = gtk_tree_model_get_path(keyword_tree, b);
913 gint ret = gtk_tree_path_compare(pa, pb);
914 gtk_tree_path_free(pa);
915 gtk_tree_path_free(pb);
919 gboolean keyword_same_parent(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
921 GtkTreeIter parent_a;
922 GtkTreeIter parent_b;
924 gboolean valid_pa = gtk_tree_model_iter_parent(keyword_tree, &parent_a, a);
925 gboolean valid_pb = gtk_tree_model_iter_parent(keyword_tree, &parent_b, b);
927 if (valid_pa && valid_pb)
929 return keyword_compare(keyword_tree, &parent_a, &parent_b) == 0;
933 return (!valid_pa && !valid_pb); /* both are toplevel */
937 gboolean keyword_exists(GtkTreeModel *keyword_tree, GtkTreeIter *parent_ptr, GtkTreeIter *sibling, const gchar *name, gboolean exclude_sibling, GtkTreeIter *result)
941 gboolean toplevel = FALSE;
947 parent = *parent_ptr;
951 toplevel = !gtk_tree_model_iter_parent(keyword_tree, &parent, sibling);
958 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &iter, toplevel ? NULL : &parent)) return FALSE;
960 casefold = g_utf8_casefold(name, -1);
965 if (!(exclude_sibling && sibling && keyword_compare(keyword_tree, &iter, sibling) == 0))
967 if (options->metadata.keywords_case_sensitive)
969 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
970 ret = strcmp(name, iter_name) == 0;
975 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
976 ret = strcmp(casefold, iter_casefold) == 0;
977 g_free(iter_casefold);
978 } // if (options->metadata.tags_cas...
982 if (result) *result = iter;
985 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) break;
992 void keyword_copy(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
995 gchar *mark, *name, *casefold;
998 /* do not copy KEYWORD_COLUMN_HIDE_IN, it fully shows the new subtree */
999 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), from, KEYWORD_COLUMN_MARK, &mark,
1000 KEYWORD_COLUMN_NAME, &name,
1001 KEYWORD_COLUMN_CASEFOLD, &casefold,
1002 KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
1004 gtk_tree_store_set(keyword_tree, to, KEYWORD_COLUMN_MARK, mark,
1005 KEYWORD_COLUMN_NAME, name,
1006 KEYWORD_COLUMN_CASEFOLD, casefold,
1007 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
1013 void keyword_copy_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
1015 GtkTreeIter from_child;
1017 keyword_copy(keyword_tree, to, from);
1019 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &from_child, from)) return;
1023 GtkTreeIter to_child;
1024 gtk_tree_store_append(keyword_tree, &to_child, to);
1025 keyword_copy_recursive(keyword_tree, &to_child, &from_child);
1026 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &from_child)) return;
1030 void keyword_move_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
1032 keyword_copy_recursive(keyword_tree, to, from);
1033 keyword_delete(keyword_tree, from);
1036 GList *keyword_tree_get_path(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
1039 GtkTreeIter iter = *iter_ptr;
1044 path = g_list_prepend(path, keyword_get_name(keyword_tree, &iter));
1045 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) break;
1051 gboolean keyword_tree_get_iter(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList *path)
1055 if (!gtk_tree_model_get_iter_first(keyword_tree, &iter)) return FALSE;
1059 GtkTreeIter children;
1062 gchar *name = keyword_get_name(keyword_tree, &iter);
1063 if (strcmp(name, path->data) == 0) break;
1065 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return FALSE;
1074 if (!gtk_tree_model_iter_children(keyword_tree, &children, &iter)) return FALSE;
1080 static gboolean keyword_tree_is_set_casefold(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *casefold_list)
1082 if (!casefold_list) return FALSE;
1084 if (!keyword_get_is_keyword(keyword_tree, &iter))
1086 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1088 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1089 return FALSE; /* this should happen only on empty helpers */
1093 if (keyword_tree_is_set_casefold(keyword_tree, child, casefold_list)) return TRUE;
1094 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1102 if (keyword_get_is_keyword(keyword_tree, &iter))
1104 GList *work = casefold_list;
1105 gboolean found = FALSE;
1106 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
1109 const gchar *casefold = work->data;
1112 if (strcmp(iter_casefold, casefold) == 0)
1118 g_free(iter_casefold);
1119 if (!found) return FALSE;
1122 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1127 static gboolean keyword_tree_is_set_casefull(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *kw_list)
1129 if (!kw_list) return FALSE;
1131 if (!keyword_get_is_keyword(keyword_tree, &iter))
1133 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1135 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1136 return FALSE; /* this should happen only on empty helpers */
1140 if (keyword_tree_is_set_casefull(keyword_tree, child, kw_list)) return TRUE;
1141 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1149 if (keyword_get_is_keyword(keyword_tree, &iter))
1151 GList *work = kw_list;
1152 gboolean found = FALSE;
1153 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
1156 const gchar *name = work->data;
1159 if (strcmp(iter_name, name) == 0)
1166 if (!found) return FALSE;
1169 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1174 gboolean keyword_tree_is_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList *kw_list)
1177 GList *casefold_list = NULL;
1180 if (options->metadata.keywords_case_sensitive)
1182 ret = keyword_tree_is_set_casefull(keyword_tree, *iter, kw_list);
1189 const gchar *kw = work->data;
1192 casefold_list = g_list_prepend(casefold_list, g_utf8_casefold(kw, -1));
1195 ret = keyword_tree_is_set_casefold(keyword_tree, *iter, casefold_list);
1197 string_list_free(casefold_list);
1203 void keyword_tree_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1205 GtkTreeIter iter = *iter_ptr;
1210 if (keyword_get_is_keyword(keyword_tree, &iter))
1212 gchar *name = keyword_get_name(keyword_tree, &iter);
1213 if (!find_string_in_list(*kw_list, name))
1215 *kw_list = g_list_append(*kw_list, name);
1223 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1228 static void keyword_tree_reset1(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1232 if (!keyword_get_is_keyword(keyword_tree, iter)) return;
1234 name = keyword_get_name(keyword_tree, iter);
1235 found = find_string_in_list(*kw_list, name);
1239 *kw_list = g_list_remove(*kw_list, found);
1245 static void keyword_tree_reset_recursive(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1248 keyword_tree_reset1(keyword_tree, iter, kw_list);
1250 if (!gtk_tree_model_iter_children(keyword_tree, &child, iter)) return;
1254 keyword_tree_reset_recursive(keyword_tree, &child, kw_list);
1255 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return;
1259 static gboolean keyword_tree_check_empty_children(GtkTreeModel *keyword_tree, GtkTreeIter *parent, GList *kw_list)
1263 if (!gtk_tree_model_iter_children(keyword_tree, &iter, parent))
1264 return TRUE; /* this should happen only on empty helpers */
1268 if (keyword_tree_is_set(keyword_tree, &iter, kw_list)) return FALSE;
1269 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return TRUE;
1273 void keyword_tree_reset(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1275 GtkTreeIter iter = *iter_ptr;
1277 keyword_tree_reset_recursive(keyword_tree, &iter, kw_list);
1279 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1282 while (keyword_tree_check_empty_children(keyword_tree, &iter, *kw_list))
1285 keyword_tree_reset1(keyword_tree, &iter, kw_list);
1286 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1291 void keyword_delete(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr)
1295 while (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, iter_ptr))
1297 keyword_delete(keyword_tree, &child);
1300 meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree), iter_ptr, -1);
1302 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter_ptr, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1305 gtk_tree_store_remove(keyword_tree, iter_ptr);
1309 void keyword_hide_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1312 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1313 if (!g_list_find(list, id))
1315 list = g_list_prepend(list, id);
1316 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1320 void keyword_show_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1323 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1324 list = g_list_remove(list, id);
1325 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1328 gboolean keyword_is_hidden_in(GtkTreeModel *keyword_tree, GtkTreeIter *iter, gpointer id)
1331 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1332 return !!g_list_find(list, id);
1335 static gboolean keyword_show_all_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
1337 keyword_show_in(GTK_TREE_STORE(model), iter, data);
1341 void keyword_show_all_in(GtkTreeStore *keyword_tree, gpointer id)
1343 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_all_in_cb, id);
1346 static void keyword_hide_unset_in_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr, gpointer id, GList *keywords)
1348 GtkTreeIter iter = *iter_ptr;
1351 if (!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
1353 keyword_hide_in(keyword_tree, &iter, id);
1354 /* no need to check children of hidden node */
1359 if (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, &iter))
1361 keyword_hide_unset_in_recursive(keyword_tree, &child, id, keywords);
1364 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1368 void keyword_hide_unset_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1371 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1372 keyword_hide_unset_in_recursive(keyword_tree, &iter, id, keywords);
1375 static gboolean keyword_show_set_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter_ptr, gpointer data)
1377 GtkTreeIter iter = *iter_ptr;
1378 GList *keywords = data;
1379 gpointer id = keywords->data;
1380 keywords = keywords->next; /* hack */
1381 if (keyword_tree_is_set(model, &iter, keywords))
1386 keyword_show_in(GTK_TREE_STORE(model), &iter, id);
1387 if (!gtk_tree_model_iter_parent(GTK_TREE_MODEL(keyword_tree), &parent, &iter)) break;
1394 void keyword_show_set_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1396 /* hack: pass id to keyword_hide_unset_in_cb in the list */
1397 keywords = g_list_prepend(keywords, id);
1398 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_set_in_cb, keywords);
1399 keywords = g_list_delete_link(keywords, keywords);
1403 void keyword_tree_new(void)
1405 if (keyword_tree) return;
1407 keyword_tree = gtk_tree_store_new(KEYWORD_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER);
1410 static GtkTreeIter keyword_tree_default_append(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar *name, gboolean is_keyword)
1413 gtk_tree_store_append(keyword_tree, &iter, parent);
1414 keyword_set(keyword_tree, &iter, name, is_keyword);
1418 void keyword_tree_new_default(void)
1420 GtkTreeIter i1, i2, i3;
1422 if (!keyword_tree) keyword_tree_new();
1424 i1 = keyword_tree_default_append(keyword_tree, NULL, _("People"), TRUE);
1425 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Family"), TRUE);
1426 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Free time"), TRUE);
1427 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Children"), TRUE);
1428 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sport"), TRUE);
1429 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Culture"), TRUE);
1430 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Festival"), TRUE);
1431 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Nature"), TRUE);
1432 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Animal"), TRUE);
1433 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Bird"), TRUE);
1434 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Insect"), TRUE);
1435 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Pets"), TRUE);
1436 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Wildlife"), TRUE);
1437 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Zoo"), TRUE);
1438 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Plant"), TRUE);
1439 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Tree"), TRUE);
1440 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Flower"), TRUE);
1441 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Water"), TRUE);
1442 i3 = keyword_tree_default_append(keyword_tree, &i2, _("River"), TRUE);
1443 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Lake"), TRUE);
1444 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Sea"), TRUE);
1445 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Landscape"), TRUE);
1446 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Art"), TRUE);
1447 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Statue"), TRUE);
1448 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Painting"), TRUE);
1449 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1450 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1451 i1 = keyword_tree_default_append(keyword_tree, NULL, _("City"), TRUE);
1452 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Park"), TRUE);
1453 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Street"), TRUE);
1454 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Square"), TRUE);
1455 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Architecture"), TRUE);
1456 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Buildings"), FALSE);
1457 i3 = keyword_tree_default_append(keyword_tree, &i2, _("House"), TRUE);
1458 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Cathedral"), TRUE);
1459 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Palace"), TRUE);
1460 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Castle"), TRUE);
1461 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Bridge"), TRUE);
1462 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Interior"), TRUE);
1463 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1464 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1465 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Places"), FALSE);
1466 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Conditions"), FALSE);
1467 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Night"), TRUE);
1468 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Lights"), TRUE);
1469 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Reflections"), TRUE);
1470 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sun"), TRUE);
1471 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Weather"), FALSE);
1472 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Fog"), TRUE);
1473 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Rain"), TRUE);
1474 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Clouds"), TRUE);
1475 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Snow"), TRUE);
1476 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Sunny weather"), TRUE);
1477 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Photo"), FALSE);
1478 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Edited"), TRUE);
1479 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Detail"), TRUE);
1480 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Macro"), TRUE);
1481 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Portrait"), TRUE);
1482 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Black and White"), TRUE);
1483 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Perspective"), TRUE);
1487 static void keyword_tree_node_write_config(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GString *outstr, gint indent)
1489 GtkTreeIter iter = *iter_ptr;
1492 GtkTreeIter children;
1495 WRITE_NL(); WRITE_STRING("<keyword ");
1496 name = keyword_get_name(keyword_tree, &iter);
1497 write_char_option(outstr, indent, "name", name);
1499 write_bool_option(outstr, indent, "kw", keyword_get_is_keyword(keyword_tree, &iter));
1500 if (gtk_tree_model_iter_children(keyword_tree, &children, &iter))
1504 keyword_tree_node_write_config(keyword_tree, &children, outstr, indent);
1506 WRITE_NL(); WRITE_STRING("</keyword>");
1512 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return;
1516 void keyword_tree_write_config(GString *outstr, gint indent)
1519 WRITE_NL(); WRITE_STRING("<keyword_tree>");
1522 if (keyword_tree && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1524 keyword_tree_node_write_config(GTK_TREE_MODEL(keyword_tree), &iter, outstr, indent);
1527 WRITE_NL(); WRITE_STRING("</keyword_tree>");
1530 GtkTreeIter *keyword_add_from_config(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar **attribute_names, const gchar **attribute_values)
1533 gboolean is_kw = TRUE;
1535 while (*attribute_names)
1537 const gchar *option = *attribute_names++;
1538 const gchar *value = *attribute_values++;
1540 if (READ_CHAR_FULL("name", name)) continue;
1541 if (READ_BOOL_FULL("kw", is_kw)) continue;
1543 log_printf("unknown attribute %s = %s\n", option, value);
1545 if (name && name[0])
1548 /* re-use existing keyword if any */
1549 if (!keyword_exists(GTK_TREE_MODEL(keyword_tree), parent, NULL, name, FALSE, &iter))
1551 gtk_tree_store_append(keyword_tree, &iter, parent);
1553 keyword_set(keyword_tree, &iter, name, is_kw);
1555 return gtk_tree_iter_copy(&iter);
1561 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */