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 = 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);
460 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
461 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
463 metadata_pathl = path_from_utf8(metadata_path);
464 unlink(metadata_pathl);
465 g_free(metadata_pathl);
466 g_free(metadata_path);
470 static gboolean metadata_legacy_read(FileData *fd, GList **keywords, gchar **comment)
472 gchar *metadata_path;
473 gchar *metadata_pathl;
474 gboolean success = FALSE;
476 if (!fd) return FALSE;
478 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
479 if (!metadata_path) return FALSE;
481 metadata_pathl = path_from_utf8(metadata_path);
483 success = metadata_file_read(metadata_pathl, keywords, comment);
485 g_free(metadata_pathl);
486 g_free(metadata_path);
491 static GList *remove_duplicate_strings_from_list(GList *list)
494 GHashTable *hashtable = g_hash_table_new(g_str_hash, g_str_equal);
495 GList *newlist = NULL;
499 gchar *key = work->data;
501 if (g_hash_table_lookup(hashtable, key) == NULL)
503 g_hash_table_insert(hashtable, (gpointer) key, GINT_TO_POINTER(1));
504 newlist = g_list_prepend(newlist, key);
509 g_hash_table_destroy(hashtable);
512 return g_list_reverse(newlist);
515 GList *metadata_read_list(FileData *fd, const gchar *key, MetadataFormat format)
519 if (!fd) return NULL;
521 /* unwritten data overide everything */
522 if (fd->modified_xmp && format == METADATA_PLAIN)
524 list = g_hash_table_lookup(fd->modified_xmp, key);
525 if (list) return string_list_copy(list);
529 Legacy metadata file is the primary source if it exists.
530 Merging the lists does not make much sense, because the existence of
531 legacy metadata file indicates that the other metadata sources are not
532 writable and thus it would not be possible to delete the keywords
533 that comes from the image file.
535 if (strcmp(key, KEYWORD_KEY) == 0)
537 if (metadata_legacy_read(fd, &list, NULL)) return list;
539 else if (strcmp(key, COMMENT_KEY) == 0)
541 gchar *comment = NULL;
542 if (metadata_legacy_read(fd, NULL, &comment)) return g_list_append(NULL, comment);
544 else if (strncmp(key, "file.", 5) == 0)
546 return g_list_append(NULL, metadata_file_info(fd, key, format));
549 exif = exif_read_fd(fd); /* this is cached, thus inexpensive */
550 if (!exif) return NULL;
551 list = exif_get_metadata(exif, key, format);
552 exif_free_fd(fd, exif);
556 gchar *metadata_read_string(FileData *fd, const gchar *key, MetadataFormat format)
558 GList *string_list = metadata_read_list(fd, key, format);
561 gchar *str = string_list->data;
562 string_list->data = NULL;
563 string_list_free(string_list);
569 guint64 metadata_read_int(FileData *fd, const gchar *key, guint64 fallback)
573 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
574 if (!string) return fallback;
576 ret = g_ascii_strtoull(string, &endptr, 10);
577 if (string == endptr) ret = fallback;
582 gdouble metadata_read_GPS_coord(FileData *fd, const gchar *key, gdouble fallback)
586 gdouble deg, min, sec;
588 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
589 if (!string) return fallback;
591 deg = g_ascii_strtod(string, &endptr);
594 min = g_ascii_strtod(endptr + 1, &endptr);
596 sec = g_ascii_strtod(endptr + 1, &endptr);
601 if (*endptr == 'S' || *endptr == 'W' || *endptr == 'N' || *endptr == 'E')
603 coord = deg + min /60.0 + sec / 3600.0;
605 if (*endptr == 'S' || *endptr == 'W') coord = -coord;
612 log_printf("unable to parse GPS coordinate '%s'\n", string);
619 gboolean metadata_append_string(FileData *fd, const gchar *key, const char *value)
621 gchar *str = metadata_read_string(fd, key, METADATA_PLAIN);
625 return metadata_write_string(fd, key, value);
629 gchar *new_string = g_strconcat(str, value, NULL);
630 gboolean ret = metadata_write_string(fd, key, new_string);
637 gboolean metadata_append_list(FileData *fd, const gchar *key, const GList *values)
639 GList *list = metadata_read_list(fd, key, METADATA_PLAIN);
643 return metadata_write_list(fd, key, values);
648 list = g_list_concat(list, string_list_copy(values));
649 list = remove_duplicate_strings_from_list(list);
651 ret = metadata_write_list(fd, key, list);
652 string_list_free(list);
657 gchar *find_string_in_list_utf8nocase(GList *list, const gchar *string)
659 gchar *string_casefold = g_utf8_casefold(string, -1);
663 gchar *haystack = list->data;
668 gchar *haystack_casefold = g_utf8_casefold(haystack, -1);
670 equal = (strcmp(haystack_casefold, string_casefold) == 0);
671 g_free(haystack_casefold);
675 g_free(string_casefold);
683 g_free(string_casefold);
688 #define KEYWORDS_SEPARATOR(c) ((c) == ',' || (c) == ';' || (c) == '\n' || (c) == '\r' || (c) == '\b')
690 GList *string_to_keywords_list(const gchar *text)
693 const gchar *ptr = text;
700 while (KEYWORDS_SEPARATOR(*ptr)) ptr++;
702 while (*ptr != '\0' && !KEYWORDS_SEPARATOR(*ptr))
708 /* trim starting and ending whitespaces */
709 while (l > 0 && g_ascii_isspace(*begin)) begin++, l--;
710 while (l > 0 && g_ascii_isspace(begin[l-1])) l--;
714 gchar *keyword = g_strndup(begin, l);
716 /* only add if not already in the list */
717 if (!find_string_in_list_utf8nocase(list, keyword))
718 list = g_list_append(list, keyword);
732 gboolean meta_data_get_keyword_mark(FileData *fd, gint n, gpointer data)
734 /* FIXME: do not use global keyword_tree */
737 gboolean found = FALSE;
738 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
742 if (keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path) &&
743 keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
750 gboolean meta_data_set_keyword_mark(FileData *fd, gint n, gboolean value, gpointer data)
753 GList *keywords = NULL;
756 if (!keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path)) return FALSE;
758 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
760 if (!!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords) != !!value)
764 keyword_tree_set(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
768 keyword_tree_reset(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
770 metadata_write_list(fd, KEYWORD_KEY, keywords);
773 string_list_free(keywords);
779 void meta_data_connect_mark_with_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *kw_iter, gint mark)
782 FileDataGetMarkFunc get_mark_func;
783 FileDataSetMarkFunc set_mark_func;
784 gpointer mark_func_data;
788 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
790 file_data_get_registered_mark_func(i, &get_mark_func, &set_mark_func, &mark_func_data);
791 if (get_mark_func == meta_data_get_keyword_mark)
793 GtkTreeIter old_kw_iter;
794 GList *old_path = mark_func_data;
796 if (keyword_tree_get_iter(keyword_tree, &old_kw_iter, old_path) &&
797 (i == mark || /* release any previous connection of given mark */
798 keyword_compare(keyword_tree, &old_kw_iter, kw_iter) == 0)) /* or given keyword */
800 file_data_register_mark_func(i, NULL, NULL, NULL, NULL);
801 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), &old_kw_iter, KEYWORD_COLUMN_MARK, "", -1);
807 if (mark >= 0 && mark < FILEDATA_MARKS_SIZE)
811 path = keyword_tree_get_path(keyword_tree, kw_iter);
812 file_data_register_mark_func(mark, meta_data_get_keyword_mark, meta_data_set_keyword_mark, path, (GDestroyNotify)string_list_free);
814 mark_str = g_strdup_printf("%d", mark + 1);
815 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), kw_iter, KEYWORD_COLUMN_MARK, mark_str, -1);
822 *-------------------------------------------------------------------
824 *-------------------------------------------------------------------
829 GtkTreeStore *keyword_tree;
831 gchar *keyword_get_name(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
834 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_NAME, &name, -1);
838 gchar *keyword_get_casefold(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
841 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_CASEFOLD, &casefold, -1);
845 gboolean keyword_get_is_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
848 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
852 void keyword_set(GtkTreeStore *keyword_tree, GtkTreeIter *iter, const gchar *name, gboolean is_keyword)
854 gchar *casefold = g_utf8_casefold(name, -1);
855 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_MARK, "",
856 KEYWORD_COLUMN_NAME, name,
857 KEYWORD_COLUMN_CASEFOLD, casefold,
858 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
862 gboolean keyword_compare(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
864 GtkTreePath *pa = gtk_tree_model_get_path(keyword_tree, a);
865 GtkTreePath *pb = gtk_tree_model_get_path(keyword_tree, b);
866 gint ret = gtk_tree_path_compare(pa, pb);
867 gtk_tree_path_free(pa);
868 gtk_tree_path_free(pb);
872 gboolean keyword_same_parent(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
874 GtkTreeIter parent_a;
875 GtkTreeIter parent_b;
877 gboolean valid_pa = gtk_tree_model_iter_parent(keyword_tree, &parent_a, a);
878 gboolean valid_pb = gtk_tree_model_iter_parent(keyword_tree, &parent_b, b);
880 if (valid_pa && valid_pb)
882 return keyword_compare(keyword_tree, &parent_a, &parent_b) == 0;
886 return (!valid_pa && !valid_pb); /* both are toplevel */
890 gboolean keyword_exists(GtkTreeModel *keyword_tree, GtkTreeIter *parent_ptr, GtkTreeIter *sibling, const gchar *name, gboolean exclude_sibling, GtkTreeIter *result)
894 gboolean toplevel = FALSE;
900 parent = *parent_ptr;
904 toplevel = !gtk_tree_model_iter_parent(keyword_tree, &parent, sibling);
911 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &iter, toplevel ? NULL : &parent)) return FALSE;
913 casefold = g_utf8_casefold(name, -1);
918 if (!(exclude_sibling && sibling && keyword_compare(keyword_tree, &iter, sibling) == 0))
920 if (options->metadata.keywords_case_sensitive)
922 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
923 ret = strcmp(name, iter_name) == 0;
928 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
929 ret = strcmp(casefold, iter_casefold) == 0;
930 g_free(iter_casefold);
931 } // if (options->metadata.tags_cas...
935 if (result) *result = iter;
938 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) break;
945 void keyword_copy(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
948 gchar *mark, *name, *casefold;
951 /* do not copy KEYWORD_COLUMN_HIDE_IN, it fully shows the new subtree */
952 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), from, KEYWORD_COLUMN_MARK, &mark,
953 KEYWORD_COLUMN_NAME, &name,
954 KEYWORD_COLUMN_CASEFOLD, &casefold,
955 KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
957 gtk_tree_store_set(keyword_tree, to, KEYWORD_COLUMN_MARK, mark,
958 KEYWORD_COLUMN_NAME, name,
959 KEYWORD_COLUMN_CASEFOLD, casefold,
960 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
966 void keyword_copy_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
968 GtkTreeIter from_child;
970 keyword_copy(keyword_tree, to, from);
972 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &from_child, from)) return;
976 GtkTreeIter to_child;
977 gtk_tree_store_append(keyword_tree, &to_child, to);
978 keyword_copy_recursive(keyword_tree, &to_child, &from_child);
979 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &from_child)) return;
983 void keyword_move_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
985 keyword_copy_recursive(keyword_tree, to, from);
986 keyword_delete(keyword_tree, from);
989 GList *keyword_tree_get_path(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
992 GtkTreeIter iter = *iter_ptr;
997 path = g_list_prepend(path, keyword_get_name(keyword_tree, &iter));
998 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) break;
1004 gboolean keyword_tree_get_iter(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList *path)
1008 if (!gtk_tree_model_get_iter_first(keyword_tree, &iter)) return FALSE;
1012 GtkTreeIter children;
1015 gchar *name = keyword_get_name(keyword_tree, &iter);
1016 if (strcmp(name, path->data) == 0) break;
1018 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return FALSE;
1027 if (!gtk_tree_model_iter_children(keyword_tree, &children, &iter)) return FALSE;
1033 static gboolean keyword_tree_is_set_casefold(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *casefold_list)
1035 if (!casefold_list) return FALSE;
1037 if (!keyword_get_is_keyword(keyword_tree, &iter))
1039 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1041 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1042 return FALSE; /* this should happen only on empty helpers */
1046 if (keyword_tree_is_set_casefold(keyword_tree, child, casefold_list)) return TRUE;
1047 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1055 if (keyword_get_is_keyword(keyword_tree, &iter))
1057 GList *work = casefold_list;
1058 gboolean found = FALSE;
1059 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
1062 const gchar *casefold = work->data;
1065 if (strcmp(iter_casefold, casefold) == 0)
1071 g_free(iter_casefold);
1072 if (!found) return FALSE;
1075 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1080 static gboolean keyword_tree_is_set_casefull(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *kw_list)
1082 if (!kw_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_casefull(keyword_tree, child, kw_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 = kw_list;
1105 gboolean found = FALSE;
1106 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
1109 const gchar *name = work->data;
1112 if (strcmp(iter_name, name) == 0)
1119 if (!found) return FALSE;
1122 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1127 gboolean keyword_tree_is_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList *kw_list)
1130 GList *casefold_list = NULL;
1133 if (options->metadata.keywords_case_sensitive)
1135 ret = keyword_tree_is_set_casefull(keyword_tree, *iter, kw_list);
1142 const gchar *kw = work->data;
1145 casefold_list = g_list_prepend(casefold_list, g_utf8_casefold(kw, -1));
1148 ret = keyword_tree_is_set_casefold(keyword_tree, *iter, casefold_list);
1150 string_list_free(casefold_list);
1156 void keyword_tree_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1158 GtkTreeIter iter = *iter_ptr;
1163 if (keyword_get_is_keyword(keyword_tree, &iter))
1165 gchar *name = keyword_get_name(keyword_tree, &iter);
1166 if (!find_string_in_list_utf8nocase(*kw_list, name))
1168 *kw_list = g_list_append(*kw_list, name);
1176 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1181 static void keyword_tree_reset1(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1185 if (!keyword_get_is_keyword(keyword_tree, iter)) return;
1187 name = keyword_get_name(keyword_tree, iter);
1188 found = find_string_in_list_utf8nocase(*kw_list, name);
1192 *kw_list = g_list_remove(*kw_list, found);
1198 static void keyword_tree_reset_recursive(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1201 keyword_tree_reset1(keyword_tree, iter, kw_list);
1203 if (!gtk_tree_model_iter_children(keyword_tree, &child, iter)) return;
1207 keyword_tree_reset_recursive(keyword_tree, &child, kw_list);
1208 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return;
1212 static gboolean keyword_tree_check_empty_children(GtkTreeModel *keyword_tree, GtkTreeIter *parent, GList *kw_list)
1216 if (!gtk_tree_model_iter_children(keyword_tree, &iter, parent))
1217 return TRUE; /* this should happen only on empty helpers */
1221 if (keyword_tree_is_set(keyword_tree, &iter, kw_list)) return FALSE;
1222 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return TRUE;
1226 void keyword_tree_reset(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1228 GtkTreeIter iter = *iter_ptr;
1230 keyword_tree_reset_recursive(keyword_tree, &iter, kw_list);
1232 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1235 while (keyword_tree_check_empty_children(keyword_tree, &iter, *kw_list))
1238 keyword_tree_reset1(keyword_tree, &iter, kw_list);
1239 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1244 void keyword_delete(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr)
1248 while (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, iter_ptr))
1250 keyword_delete(keyword_tree, &child);
1253 meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree), iter_ptr, -1);
1255 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter_ptr, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1258 gtk_tree_store_remove(keyword_tree, iter_ptr);
1262 void keyword_hide_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1265 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1266 if (!g_list_find(list, id))
1268 list = g_list_prepend(list, id);
1269 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1273 void keyword_show_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1276 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1277 list = g_list_remove(list, id);
1278 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1281 gboolean keyword_is_hidden_in(GtkTreeModel *keyword_tree, GtkTreeIter *iter, gpointer id)
1284 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1285 return !!g_list_find(list, id);
1288 static gboolean keyword_show_all_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
1290 keyword_show_in(GTK_TREE_STORE(model), iter, data);
1294 void keyword_show_all_in(GtkTreeStore *keyword_tree, gpointer id)
1296 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_all_in_cb, id);
1299 static void keyword_hide_unset_in_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr, gpointer id, GList *keywords)
1301 GtkTreeIter iter = *iter_ptr;
1304 if (!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
1306 keyword_hide_in(keyword_tree, &iter, id);
1307 /* no need to check children of hidden node */
1312 if (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, &iter))
1314 keyword_hide_unset_in_recursive(keyword_tree, &child, id, keywords);
1317 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1321 void keyword_hide_unset_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1324 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1325 keyword_hide_unset_in_recursive(keyword_tree, &iter, id, keywords);
1328 static gboolean keyword_show_set_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter_ptr, gpointer data)
1330 GtkTreeIter iter = *iter_ptr;
1331 GList *keywords = data;
1332 gpointer id = keywords->data;
1333 keywords = keywords->next; /* hack */
1334 if (keyword_tree_is_set(model, &iter, keywords))
1339 keyword_show_in(GTK_TREE_STORE(model), &iter, id);
1340 if (!gtk_tree_model_iter_parent(GTK_TREE_MODEL(keyword_tree), &parent, &iter)) break;
1347 void keyword_show_set_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1349 /* hack: pass id to keyword_hide_unset_in_cb in the list */
1350 keywords = g_list_prepend(keywords, id);
1351 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_set_in_cb, keywords);
1352 keywords = g_list_delete_link(keywords, keywords);
1356 void keyword_tree_new(void)
1358 if (keyword_tree) return;
1360 keyword_tree = gtk_tree_store_new(KEYWORD_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER);
1363 static GtkTreeIter keyword_tree_default_append(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar *name, gboolean is_keyword)
1366 gtk_tree_store_append(keyword_tree, &iter, parent);
1367 keyword_set(keyword_tree, &iter, name, is_keyword);
1371 void keyword_tree_new_default(void)
1373 GtkTreeIter i1, i2, i3;
1375 if (!keyword_tree) keyword_tree_new();
1377 i1 = keyword_tree_default_append(keyword_tree, NULL, _("People"), TRUE);
1378 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Family"), TRUE);
1379 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Free time"), TRUE);
1380 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Children"), TRUE);
1381 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sport"), TRUE);
1382 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Culture"), TRUE);
1383 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Festival"), TRUE);
1384 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Nature"), TRUE);
1385 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Animal"), TRUE);
1386 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Bird"), TRUE);
1387 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Insect"), TRUE);
1388 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Pets"), TRUE);
1389 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Wildlife"), TRUE);
1390 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Zoo"), TRUE);
1391 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Plant"), TRUE);
1392 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Tree"), TRUE);
1393 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Flower"), TRUE);
1394 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Water"), TRUE);
1395 i3 = keyword_tree_default_append(keyword_tree, &i2, _("River"), TRUE);
1396 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Lake"), TRUE);
1397 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Sea"), TRUE);
1398 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Landscape"), TRUE);
1399 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Art"), TRUE);
1400 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Statue"), TRUE);
1401 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Painting"), TRUE);
1402 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1403 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1404 i1 = keyword_tree_default_append(keyword_tree, NULL, _("City"), TRUE);
1405 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Park"), TRUE);
1406 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Street"), TRUE);
1407 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Square"), TRUE);
1408 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Architecture"), TRUE);
1409 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Buildings"), FALSE);
1410 i3 = keyword_tree_default_append(keyword_tree, &i2, _("House"), TRUE);
1411 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Cathedral"), TRUE);
1412 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Palace"), TRUE);
1413 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Castle"), TRUE);
1414 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Bridge"), TRUE);
1415 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Interior"), TRUE);
1416 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1417 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1418 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Places"), FALSE);
1419 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Conditions"), FALSE);
1420 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Night"), TRUE);
1421 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Lights"), TRUE);
1422 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Reflections"), TRUE);
1423 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sun"), TRUE);
1424 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Weather"), FALSE);
1425 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Fog"), TRUE);
1426 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Rain"), TRUE);
1427 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Clouds"), TRUE);
1428 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Snow"), TRUE);
1429 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Sunny weather"), TRUE);
1430 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Photo"), FALSE);
1431 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Edited"), TRUE);
1432 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Detail"), TRUE);
1433 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Macro"), TRUE);
1434 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Portrait"), TRUE);
1435 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Black and White"), TRUE);
1436 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Perspective"), TRUE);
1440 static void keyword_tree_node_write_config(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GString *outstr, gint indent)
1442 GtkTreeIter iter = *iter_ptr;
1445 GtkTreeIter children;
1448 WRITE_NL(); WRITE_STRING("<keyword ");
1449 name = keyword_get_name(keyword_tree, &iter);
1450 write_char_option(outstr, indent, "name", name);
1452 write_bool_option(outstr, indent, "kw", keyword_get_is_keyword(keyword_tree, &iter));
1453 if (gtk_tree_model_iter_children(keyword_tree, &children, &iter))
1457 keyword_tree_node_write_config(keyword_tree, &children, outstr, indent);
1459 WRITE_NL(); WRITE_STRING("</keyword>");
1465 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return;
1469 void keyword_tree_write_config(GString *outstr, gint indent)
1472 WRITE_NL(); WRITE_STRING("<keyword_tree>");
1475 if (keyword_tree && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1477 keyword_tree_node_write_config(GTK_TREE_MODEL(keyword_tree), &iter, outstr, indent);
1480 WRITE_NL(); WRITE_STRING("</keyword_tree>");
1483 GtkTreeIter *keyword_add_from_config(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar **attribute_names, const gchar **attribute_values)
1486 gboolean is_kw = TRUE;
1488 while (*attribute_names)
1490 const gchar *option = *attribute_names++;
1491 const gchar *value = *attribute_values++;
1493 if (READ_CHAR_FULL("name", name)) continue;
1494 if (READ_BOOL_FULL("kw", is_kw)) continue;
1496 log_printf("unknown attribute %s = %s\n", option, value);
1498 if (name && name[0])
1501 /* re-use existing keyword if any */
1502 if (!keyword_exists(GTK_TREE_MODEL(keyword_tree), parent, NULL, name, FALSE, &iter))
1504 gtk_tree_store_append(keyword_tree, &iter, parent);
1506 keyword_set(keyword_tree, &iter, name, is_kw);
1508 return gtk_tree_iter_copy(&iter);
1514 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */