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"
35 static const gchar *group_keys[] = {KEYWORD_KEY, COMMENT_KEY, NULL}; /* tags that will be written to all files in a group */
37 static gboolean metadata_write_queue_idle_cb(gpointer data);
38 static gboolean metadata_legacy_write(FileData *fd);
39 static void metadata_legacy_delete(FileData *fd, const gchar *except);
44 *-------------------------------------------------------------------
46 *-------------------------------------------------------------------
49 static GList *metadata_write_queue = NULL;
50 static guint metadata_write_idle_id = 0; /* event source id */
52 static void metadata_write_queue_add(FileData *fd)
54 if (!g_list_find(metadata_write_queue, fd))
56 metadata_write_queue = g_list_prepend(metadata_write_queue, fd);
59 layout_status_update_write_all();
62 if (metadata_write_idle_id)
64 g_source_remove(metadata_write_idle_id);
65 metadata_write_idle_id = 0;
68 if (options->metadata.confirm_after_timeout)
70 metadata_write_idle_id = g_timeout_add(options->metadata.confirm_timeout * 1000, metadata_write_queue_idle_cb, NULL);
75 gboolean metadata_write_queue_remove(FileData *fd)
77 g_hash_table_destroy(fd->modified_xmp);
78 fd->modified_xmp = NULL;
80 metadata_write_queue = g_list_remove(metadata_write_queue, fd);
82 file_data_increment_version(fd);
83 file_data_send_notification(fd, NOTIFY_REREAD);
87 layout_status_update_write_all();
91 gboolean metadata_write_queue_remove_list(GList *list)
99 FileData *fd = work->data;
101 ret = ret && metadata_write_queue_remove(fd);
107 gboolean metadata_write_queue_confirm(FileUtilDoneFunc done_func, gpointer done_data)
110 GList *to_approve = NULL;
112 work = metadata_write_queue;
115 FileData *fd = work->data;
118 if (fd->change) continue; /* another operation in progress, skip this file for now */
120 to_approve = g_list_prepend(to_approve, file_data_ref(fd));
123 file_util_write_metadata(NULL, to_approve, NULL, done_func, done_data);
125 filelist_free(to_approve);
127 return (metadata_write_queue != NULL);
130 static gboolean metadata_write_queue_idle_cb(gpointer data)
132 metadata_write_queue_confirm(NULL, NULL);
133 metadata_write_idle_id = 0;
137 gboolean metadata_write_perform(FileData *fd)
142 g_assert(fd->change);
144 if (fd->change->dest &&
145 strcmp(extension_from_path(fd->change->dest), GQ_CACHE_EXT_METADATA) == 0)
147 success = metadata_legacy_write(fd);
148 if (success) metadata_legacy_delete(fd, fd->change->dest);
152 /* write via exiv2 */
153 /* we can either use cached metadata which have fd->modified_xmp already applied
154 or read metadata from file and apply fd->modified_xmp
155 metadata are read also if the file was modified meanwhile */
156 exif = exif_read_fd(fd);
157 if (!exif) return FALSE;
159 success = (fd->change->dest) ? exif_write_sidecar(exif, fd->change->dest) : exif_write(exif); /* write modified metadata */
160 exif_free_fd(fd, exif);
162 if (fd->change->dest)
163 /* this will create a FileData for the sidecar and link it to the main file
164 (we can't wait until the sidecar is discovered by directory scanning because
165 exif_read_fd is called before that and it would read the main file only and
166 store the metadata in the cache)
167 FIXME: this does not catch new sidecars created by independent external programs
169 file_data_unref(file_data_new_simple(fd->change->dest));
171 if (success) metadata_legacy_delete(fd, fd->change->dest);
175 gint metadata_queue_length(void)
177 return g_list_length(metadata_write_queue);
180 static gboolean metadata_check_key(const gchar *keys[], const gchar *key)
182 const gchar **k = keys;
186 if (strcmp(key, *k) == 0) return TRUE;
192 gboolean metadata_write_list(FileData *fd, const gchar *key, const GList *values)
194 if (!fd->modified_xmp)
196 fd->modified_xmp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)string_list_free);
198 g_hash_table_insert(fd->modified_xmp, g_strdup(key), string_list_copy((GList *)values));
201 exif_update_metadata(fd->exif, key, values);
203 metadata_write_queue_add(fd);
204 file_data_increment_version(fd);
205 file_data_send_notification(fd, NOTIFY_METADATA);
207 if (options->metadata.sync_grouped_files && metadata_check_key(group_keys, key))
209 GList *work = fd->sidecar_files;
213 FileData *sfd = work->data;
216 if (filter_file_class(sfd->extension, FORMAT_CLASS_META)) continue;
218 metadata_write_list(sfd, key, values);
226 gboolean metadata_write_string(FileData *fd, const gchar *key, const char *value)
228 GList *list = g_list_append(NULL, g_strdup(value));
229 gboolean ret = metadata_write_list(fd, key, list);
230 string_list_free(list);
236 *-------------------------------------------------------------------
237 * keyword / comment read/write
238 *-------------------------------------------------------------------
241 static gboolean metadata_file_write(gchar *path, GHashTable *modified_xmp)
244 GList *keywords = g_hash_table_lookup(modified_xmp, KEYWORD_KEY);
245 GList *comment_l = g_hash_table_lookup(modified_xmp, COMMENT_KEY);
246 gchar *comment = comment_l ? comment_l->data : NULL;
248 ssi = secure_open(path);
249 if (!ssi) return FALSE;
251 secure_fprintf(ssi, "#%s comment (%s)\n\n", GQ_APPNAME, VERSION);
253 secure_fprintf(ssi, "[keywords]\n");
254 while (keywords && secsave_errno == SS_ERR_NONE)
256 const gchar *word = keywords->data;
257 keywords = keywords->next;
259 secure_fprintf(ssi, "%s\n", word);
261 secure_fputc(ssi, '\n');
263 secure_fprintf(ssi, "[comment]\n");
264 secure_fprintf(ssi, "%s\n", (comment) ? comment : "");
266 secure_fprintf(ssi, "#end\n");
268 return (secure_close(ssi) == 0);
271 static gboolean metadata_legacy_write(FileData *fd)
273 gboolean success = FALSE;
274 gchar *metadata_pathl;
276 g_assert(fd->change && fd->change->dest);
278 DEBUG_1("Saving comment: %s", fd->change->dest);
280 metadata_pathl = path_from_utf8(fd->change->dest);
282 success = metadata_file_write(metadata_pathl, fd->modified_xmp);
284 g_free(metadata_pathl);
289 static gboolean metadata_file_read(gchar *path, GList **keywords, gchar **comment)
293 MetadataKey key = MK_NONE;
295 GString *comment_build = NULL;
297 f = fopen(path, "r");
298 if (!f) return FALSE;
300 while (fgets(s_buf, sizeof(s_buf), f))
304 if (*ptr == '#') continue;
305 if (*ptr == '[' && key != MK_COMMENT)
307 gchar *keystr = ++ptr;
310 while (*ptr != ']' && *ptr != '\n' && *ptr != '\0') ptr++;
315 if (g_ascii_strcasecmp(keystr, "keywords") == 0)
317 else if (g_ascii_strcasecmp(keystr, "comment") == 0)
329 while (*ptr != '\n' && *ptr != '\0') ptr++;
331 if (strlen(s_buf) > 0)
333 gchar *kw = utf8_validate_or_convert(s_buf);
335 list = g_list_prepend(list, kw);
340 if (!comment_build) comment_build = g_string_new("");
341 g_string_append(comment_build, s_buf);
350 *keywords = g_list_reverse(list);
354 string_list_free(list);
362 gchar *ptr = comment_build->str;
364 /* strip leading and trailing newlines */
365 while (*ptr == '\n') ptr++;
367 while (len > 0 && ptr[len - 1] == '\n') len--;
368 if (ptr[len] == '\n') len++; /* keep the last one */
371 gchar *text = g_strndup(ptr, len);
373 *comment = utf8_validate_or_convert(text);
377 g_string_free(comment_build, TRUE);
383 static void metadata_legacy_delete(FileData *fd, const gchar *except)
385 gchar *metadata_path;
386 gchar *metadata_pathl;
389 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
390 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
392 metadata_pathl = path_from_utf8(metadata_path);
393 unlink(metadata_pathl);
394 g_free(metadata_pathl);
395 g_free(metadata_path);
397 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
398 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
400 metadata_pathl = path_from_utf8(metadata_path);
401 unlink(metadata_pathl);
402 g_free(metadata_pathl);
403 g_free(metadata_path);
407 static gboolean metadata_legacy_read(FileData *fd, GList **keywords, gchar **comment)
409 gchar *metadata_path;
410 gchar *metadata_pathl;
411 gboolean success = FALSE;
413 if (!fd) return FALSE;
415 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
416 if (!metadata_path) return FALSE;
418 metadata_pathl = path_from_utf8(metadata_path);
420 success = metadata_file_read(metadata_pathl, keywords, comment);
422 g_free(metadata_pathl);
423 g_free(metadata_path);
428 static GList *remove_duplicate_strings_from_list(GList *list)
431 GHashTable *hashtable = g_hash_table_new(g_str_hash, g_str_equal);
432 GList *newlist = NULL;
436 gchar *key = work->data;
438 if (g_hash_table_lookup(hashtable, key) == NULL)
440 g_hash_table_insert(hashtable, (gpointer) key, GINT_TO_POINTER(1));
441 newlist = g_list_prepend(newlist, key);
446 g_hash_table_destroy(hashtable);
449 return g_list_reverse(newlist);
452 GList *metadata_read_list(FileData *fd, const gchar *key, MetadataFormat format)
456 if (!fd) return NULL;
458 /* unwritten data overide everything */
459 if (fd->modified_xmp && format == METADATA_PLAIN)
461 list = g_hash_table_lookup(fd->modified_xmp, key);
462 if (list) return string_list_copy(list);
466 Legacy metadata file is the primary source if it exists.
467 Merging the lists does not make much sense, because the existence of
468 legacy metadata file indicates that the other metadata sources are not
469 writable and thus it would not be possible to delete the keywords
470 that comes from the image file.
472 if (strcmp(key, KEYWORD_KEY) == 0)
474 if (metadata_legacy_read(fd, &list, NULL)) return list;
476 else if (strcmp(key, COMMENT_KEY) == 0)
478 gchar *comment = NULL;
479 if (metadata_legacy_read(fd, NULL, &comment)) return g_list_append(NULL, comment);
481 else if (strncmp(key, "file.", 5) == 0)
483 return g_list_append(NULL, metadata_file_info(fd, key, format));
486 exif = exif_read_fd(fd); /* this is cached, thus inexpensive */
487 if (!exif) return NULL;
488 list = exif_get_metadata(exif, key, format);
489 exif_free_fd(fd, exif);
493 gchar *metadata_read_string(FileData *fd, const gchar *key, MetadataFormat format)
495 GList *string_list = metadata_read_list(fd, key, format);
498 gchar *str = string_list->data;
499 string_list->data = NULL;
500 string_list_free(string_list);
506 guint64 metadata_read_int(FileData *fd, const gchar *key, guint64 fallback)
510 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
511 if (!string) return fallback;
513 ret = g_ascii_strtoull(string, &endptr, 10);
514 if (string == endptr) ret = fallback;
519 gdouble metadata_read_GPS_coord(FileData *fd, const gchar *key, gdouble fallback)
523 gdouble deg, min, sec;
525 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
526 if (!string) return fallback;
528 deg = g_ascii_strtod(string, &endptr);
531 min = g_ascii_strtod(endptr + 1, &endptr);
533 sec = g_ascii_strtod(endptr + 1, &endptr);
538 if (*endptr == 'S' || *endptr == 'W' || *endptr == 'N' || *endptr == 'E')
540 coord = deg + min /60.0 + sec / 3600.0;
542 if (*endptr == 'S' || *endptr == 'W') coord = -coord;
549 log_printf("unable to parse GPS coordinate '%s'\n", string);
556 gboolean metadata_append_string(FileData *fd, const gchar *key, const char *value)
558 gchar *str = metadata_read_string(fd, key, METADATA_PLAIN);
562 return metadata_write_string(fd, key, value);
566 gchar *new_string = g_strconcat(str, value, NULL);
567 gboolean ret = metadata_write_string(fd, key, new_string);
574 gboolean metadata_append_list(FileData *fd, const gchar *key, const GList *values)
576 GList *list = metadata_read_list(fd, key, METADATA_PLAIN);
580 return metadata_write_list(fd, key, values);
585 list = g_list_concat(list, string_list_copy(values));
586 list = remove_duplicate_strings_from_list(list);
588 ret = metadata_write_list(fd, key, list);
589 string_list_free(list);
594 gchar *find_string_in_list_utf8nocase(GList *list, const gchar *string)
596 gchar *string_casefold = g_utf8_casefold(string, -1);
600 gchar *haystack = list->data;
605 gchar *haystack_casefold = g_utf8_casefold(haystack, -1);
607 equal = (strcmp(haystack_casefold, string_casefold) == 0);
608 g_free(haystack_casefold);
612 g_free(string_casefold);
620 g_free(string_casefold);
625 #define KEYWORDS_SEPARATOR(c) ((c) == ',' || (c) == ';' || (c) == '\n' || (c) == '\r' || (c) == '\b')
627 GList *string_to_keywords_list(const gchar *text)
630 const gchar *ptr = text;
637 while (KEYWORDS_SEPARATOR(*ptr)) ptr++;
639 while (*ptr != '\0' && !KEYWORDS_SEPARATOR(*ptr))
645 /* trim starting and ending whitespaces */
646 while (l > 0 && g_ascii_isspace(*begin)) begin++, l--;
647 while (l > 0 && g_ascii_isspace(begin[l-1])) l--;
651 gchar *keyword = g_strndup(begin, l);
653 /* only add if not already in the list */
654 if (!find_string_in_list_utf8nocase(list, keyword))
655 list = g_list_append(list, keyword);
669 gboolean meta_data_get_keyword_mark(FileData *fd, gint n, gpointer data)
671 /* FIXME: do not use global keyword_tree */
674 gboolean found = FALSE;
675 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
679 if (keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path) &&
680 keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
687 gboolean meta_data_set_keyword_mark(FileData *fd, gint n, gboolean value, gpointer data)
690 GList *keywords = NULL;
693 if (!keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path)) return FALSE;
695 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
697 if (!!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords) != !!value)
701 keyword_tree_set(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
705 keyword_tree_reset(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
707 metadata_write_list(fd, KEYWORD_KEY, keywords);
710 string_list_free(keywords);
716 void meta_data_connect_mark_with_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *kw_iter, gint mark)
719 FileDataGetMarkFunc get_mark_func;
720 FileDataSetMarkFunc set_mark_func;
721 gpointer mark_func_data;
725 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
727 file_data_get_registered_mark_func(i, &get_mark_func, &set_mark_func, &mark_func_data);
728 if (get_mark_func == meta_data_get_keyword_mark)
730 GtkTreeIter old_kw_iter;
731 GList *old_path = mark_func_data;
733 if (keyword_tree_get_iter(keyword_tree, &old_kw_iter, old_path) &&
734 (i == mark || /* release any previous connection of given mark */
735 keyword_compare(keyword_tree, &old_kw_iter, kw_iter) == 0)) /* or given keyword */
737 file_data_register_mark_func(i, NULL, NULL, NULL, NULL);
738 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), &old_kw_iter, KEYWORD_COLUMN_MARK, "", -1);
744 if (mark >= 0 && mark < FILEDATA_MARKS_SIZE)
748 path = keyword_tree_get_path(keyword_tree, kw_iter);
749 file_data_register_mark_func(mark, meta_data_get_keyword_mark, meta_data_set_keyword_mark, path, (GDestroyNotify)string_list_free);
751 mark_str = g_strdup_printf("%d", mark + 1);
752 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), kw_iter, KEYWORD_COLUMN_MARK, mark_str, -1);
759 *-------------------------------------------------------------------
761 *-------------------------------------------------------------------
766 GtkTreeStore *keyword_tree;
768 gchar *keyword_get_name(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
771 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_NAME, &name, -1);
775 gchar *keyword_get_casefold(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
778 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_CASEFOLD, &casefold, -1);
782 gboolean keyword_get_is_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
785 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
789 void keyword_set(GtkTreeStore *keyword_tree, GtkTreeIter *iter, const gchar *name, gboolean is_keyword)
791 gchar *casefold = g_utf8_casefold(name, -1);
792 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_MARK, "",
793 KEYWORD_COLUMN_NAME, name,
794 KEYWORD_COLUMN_CASEFOLD, casefold,
795 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
799 gboolean keyword_compare(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
801 GtkTreePath *pa = gtk_tree_model_get_path(keyword_tree, a);
802 GtkTreePath *pb = gtk_tree_model_get_path(keyword_tree, b);
803 gint ret = gtk_tree_path_compare(pa, pb);
804 gtk_tree_path_free(pa);
805 gtk_tree_path_free(pb);
809 gboolean keyword_same_parent(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
811 GtkTreeIter parent_a;
812 GtkTreeIter parent_b;
814 gboolean valid_pa = gtk_tree_model_iter_parent(keyword_tree, &parent_a, a);
815 gboolean valid_pb = gtk_tree_model_iter_parent(keyword_tree, &parent_b, b);
817 if (valid_pa && valid_pb)
819 return keyword_compare(keyword_tree, &parent_a, &parent_b) == 0;
823 return (!valid_pa && !valid_pb); /* both are toplevel */
827 gboolean keyword_exists(GtkTreeModel *keyword_tree, GtkTreeIter *parent_ptr, GtkTreeIter *sibling, const gchar *name, gboolean exclude_sibling, GtkTreeIter *result)
831 gboolean toplevel = FALSE;
837 parent = *parent_ptr;
841 toplevel = !gtk_tree_model_iter_parent(keyword_tree, &parent, sibling);
848 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &iter, toplevel ? NULL : &parent)) return FALSE;
850 casefold = g_utf8_casefold(name, -1);
855 if (!(exclude_sibling && sibling && keyword_compare(keyword_tree, &iter, sibling) == 0))
857 if (options->metadata.tags_case_sensitive)
859 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
860 ret = strcmp(name, iter_name) == 0;
865 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
866 ret = strcmp(casefold, iter_casefold) == 0;
867 g_free(iter_casefold);
868 } // if (options->metadata.tags_cas...
872 if (result) *result = iter;
875 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) break;
882 void keyword_copy(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
885 gchar *mark, *name, *casefold;
888 /* do not copy KEYWORD_COLUMN_HIDE_IN, it fully shows the new subtree */
889 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), from, KEYWORD_COLUMN_MARK, &mark,
890 KEYWORD_COLUMN_NAME, &name,
891 KEYWORD_COLUMN_CASEFOLD, &casefold,
892 KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
894 gtk_tree_store_set(keyword_tree, to, KEYWORD_COLUMN_MARK, mark,
895 KEYWORD_COLUMN_NAME, name,
896 KEYWORD_COLUMN_CASEFOLD, casefold,
897 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
903 void keyword_copy_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
905 GtkTreeIter from_child;
907 keyword_copy(keyword_tree, to, from);
909 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &from_child, from)) return;
913 GtkTreeIter to_child;
914 gtk_tree_store_append(keyword_tree, &to_child, to);
915 keyword_copy_recursive(keyword_tree, &to_child, &from_child);
916 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &from_child)) return;
920 void keyword_move_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
922 keyword_copy_recursive(keyword_tree, to, from);
923 keyword_delete(keyword_tree, from);
926 GList *keyword_tree_get_path(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
929 GtkTreeIter iter = *iter_ptr;
934 path = g_list_prepend(path, keyword_get_name(keyword_tree, &iter));
935 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) break;
941 gboolean keyword_tree_get_iter(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList *path)
945 if (!gtk_tree_model_get_iter_first(keyword_tree, &iter)) return FALSE;
949 GtkTreeIter children;
952 gchar *name = keyword_get_name(keyword_tree, &iter);
953 if (strcmp(name, path->data) == 0) break;
955 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return FALSE;
964 if (!gtk_tree_model_iter_children(keyword_tree, &children, &iter)) return FALSE;
970 static gboolean keyword_tree_is_set_casefold(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *casefold_list)
972 if (!casefold_list) return FALSE;
974 if (!keyword_get_is_keyword(keyword_tree, &iter))
976 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
978 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
979 return FALSE; /* this should happen only on empty helpers */
983 if (keyword_tree_is_set_casefold(keyword_tree, child, casefold_list)) return TRUE;
984 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
992 if (keyword_get_is_keyword(keyword_tree, &iter))
994 GList *work = casefold_list;
995 gboolean found = FALSE;
996 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
999 const gchar *casefold = work->data;
1002 if (strcmp(iter_casefold, casefold) == 0)
1008 g_free(iter_casefold);
1009 if (!found) return FALSE;
1012 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1017 static gboolean keyword_tree_is_set_casefull(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *kw_list)
1019 if (!kw_list) return FALSE;
1021 if (!keyword_get_is_keyword(keyword_tree, &iter))
1023 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1025 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1026 return FALSE; /* this should happen only on empty helpers */
1030 if (keyword_tree_is_set_casefull(keyword_tree, child, kw_list)) return TRUE;
1031 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1039 if (keyword_get_is_keyword(keyword_tree, &iter))
1041 GList *work = kw_list;
1042 gboolean found = FALSE;
1043 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
1046 const gchar *name = work->data;
1049 if (strcmp(iter_name, name) == 0)
1056 if (!found) return FALSE;
1059 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1064 gboolean keyword_tree_is_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList *kw_list)
1067 GList *casefold_list = NULL;
1070 if (options->metadata.tags_case_sensitive)
1072 ret = keyword_tree_is_set_casefull(keyword_tree, *iter, kw_list);
1079 const gchar *kw = work->data;
1082 casefold_list = g_list_prepend(casefold_list, g_utf8_casefold(kw, -1));
1085 ret = keyword_tree_is_set_casefold(keyword_tree, *iter, casefold_list);
1087 string_list_free(casefold_list);
1093 void keyword_tree_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1095 GtkTreeIter iter = *iter_ptr;
1100 if (keyword_get_is_keyword(keyword_tree, &iter))
1102 gchar *name = keyword_get_name(keyword_tree, &iter);
1103 if (!find_string_in_list_utf8nocase(*kw_list, name))
1105 *kw_list = g_list_append(*kw_list, name);
1113 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1118 static void keyword_tree_reset1(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1122 if (!keyword_get_is_keyword(keyword_tree, iter)) return;
1124 name = keyword_get_name(keyword_tree, iter);
1125 found = find_string_in_list_utf8nocase(*kw_list, name);
1129 *kw_list = g_list_remove(*kw_list, found);
1135 static void keyword_tree_reset_recursive(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1138 keyword_tree_reset1(keyword_tree, iter, kw_list);
1140 if (!gtk_tree_model_iter_children(keyword_tree, &child, iter)) return;
1144 keyword_tree_reset_recursive(keyword_tree, &child, kw_list);
1145 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return;
1149 static gboolean keyword_tree_check_empty_children(GtkTreeModel *keyword_tree, GtkTreeIter *parent, GList *kw_list)
1153 if (!gtk_tree_model_iter_children(keyword_tree, &iter, parent))
1154 return TRUE; /* this should happen only on empty helpers */
1158 if (keyword_tree_is_set(keyword_tree, &iter, kw_list)) return FALSE;
1159 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return TRUE;
1163 void keyword_tree_reset(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1165 GtkTreeIter iter = *iter_ptr;
1167 keyword_tree_reset_recursive(keyword_tree, &iter, kw_list);
1169 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1172 while (keyword_tree_check_empty_children(keyword_tree, &iter, *kw_list))
1175 keyword_tree_reset1(keyword_tree, &iter, kw_list);
1176 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1181 void keyword_delete(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr)
1185 while (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, iter_ptr))
1187 keyword_delete(keyword_tree, &child);
1190 meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree), iter_ptr, -1);
1192 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter_ptr, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1195 gtk_tree_store_remove(keyword_tree, iter_ptr);
1199 void keyword_hide_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1202 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1203 if (!g_list_find(list, id))
1205 list = g_list_prepend(list, id);
1206 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1210 void keyword_show_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1213 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1214 list = g_list_remove(list, id);
1215 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1218 gboolean keyword_is_hidden_in(GtkTreeModel *keyword_tree, GtkTreeIter *iter, gpointer id)
1221 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1222 return !!g_list_find(list, id);
1225 static gboolean keyword_show_all_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
1227 keyword_show_in(GTK_TREE_STORE(model), iter, data);
1231 void keyword_show_all_in(GtkTreeStore *keyword_tree, gpointer id)
1233 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_all_in_cb, id);
1236 static void keyword_hide_unset_in_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr, gpointer id, GList *keywords)
1238 GtkTreeIter iter = *iter_ptr;
1241 if (!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
1243 keyword_hide_in(keyword_tree, &iter, id);
1244 /* no need to check children of hidden node */
1249 if (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, &iter))
1251 keyword_hide_unset_in_recursive(keyword_tree, &child, id, keywords);
1254 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1258 void keyword_hide_unset_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1261 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1262 keyword_hide_unset_in_recursive(keyword_tree, &iter, id, keywords);
1265 static gboolean keyword_show_set_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter_ptr, gpointer data)
1267 GtkTreeIter iter = *iter_ptr;
1268 GList *keywords = data;
1269 gpointer id = keywords->data;
1270 keywords = keywords->next; /* hack */
1271 if (keyword_tree_is_set(model, &iter, keywords))
1276 keyword_show_in(GTK_TREE_STORE(model), &iter, id);
1277 if (!gtk_tree_model_iter_parent(GTK_TREE_MODEL(keyword_tree), &parent, &iter)) break;
1284 void keyword_show_set_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1286 /* hack: pass id to keyword_hide_unset_in_cb in the list */
1287 keywords = g_list_prepend(keywords, id);
1288 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_set_in_cb, keywords);
1289 keywords = g_list_delete_link(keywords, keywords);
1293 void keyword_tree_new(void)
1295 if (keyword_tree) return;
1297 keyword_tree = gtk_tree_store_new(KEYWORD_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER);
1300 static GtkTreeIter keyword_tree_default_append(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar *name, gboolean is_keyword)
1303 gtk_tree_store_append(keyword_tree, &iter, parent);
1304 keyword_set(keyword_tree, &iter, name, is_keyword);
1308 void keyword_tree_new_default(void)
1310 if (keyword_tree) return;
1314 GtkTreeIter i1, i2, i3;
1316 i1 = keyword_tree_default_append(keyword_tree, NULL, _("People"), TRUE);
1317 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Family"), TRUE);
1318 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Free time"), TRUE);
1319 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Children"), TRUE);
1320 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sport"), TRUE);
1321 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Culture"), TRUE);
1322 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Festival"), TRUE);
1323 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Nature"), TRUE);
1324 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Animal"), TRUE);
1325 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Bird"), TRUE);
1326 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Insect"), TRUE);
1327 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Pets"), TRUE);
1328 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Wildlife"), TRUE);
1329 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Zoo"), TRUE);
1330 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Plant"), TRUE);
1331 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Tree"), TRUE);
1332 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Flower"), TRUE);
1333 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Water"), TRUE);
1334 i3 = keyword_tree_default_append(keyword_tree, &i2, _("River"), TRUE);
1335 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Lake"), TRUE);
1336 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Sea"), TRUE);
1337 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Landscape"), TRUE);
1338 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Art"), TRUE);
1339 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Statue"), TRUE);
1340 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Painting"), TRUE);
1341 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1342 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1343 i1 = keyword_tree_default_append(keyword_tree, NULL, _("City"), TRUE);
1344 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Park"), TRUE);
1345 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Street"), TRUE);
1346 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Square"), TRUE);
1347 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Architecture"), TRUE);
1348 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Buildings"), FALSE);
1349 i3 = keyword_tree_default_append(keyword_tree, &i2, _("House"), TRUE);
1350 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Cathedral"), TRUE);
1351 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Palace"), TRUE);
1352 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Castle"), TRUE);
1353 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Bridge"), TRUE);
1354 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Interior"), TRUE);
1355 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1356 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1357 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Places"), FALSE);
1358 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Conditions"), FALSE);
1359 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Night"), TRUE);
1360 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Lights"), TRUE);
1361 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Reflections"), TRUE);
1362 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sun"), TRUE);
1363 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Weather"), FALSE);
1364 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Fog"), TRUE);
1365 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Rain"), TRUE);
1366 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Clouds"), TRUE);
1367 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Snow"), TRUE);
1368 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Sunny weather"), TRUE);
1369 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Photo"), FALSE);
1370 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Edited"), TRUE);
1371 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Detail"), TRUE);
1372 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Macro"), TRUE);
1373 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Portrait"), TRUE);
1374 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Black and White"), TRUE);
1375 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Perspective"), TRUE);
1379 static void keyword_tree_node_write_config(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GString *outstr, gint indent)
1381 GtkTreeIter iter = *iter_ptr;
1384 GtkTreeIter children;
1387 WRITE_NL(); WRITE_STRING("<keyword ");
1388 name = keyword_get_name(keyword_tree, &iter);
1389 write_char_option(outstr, indent, "name", name);
1391 write_bool_option(outstr, indent, "kw", keyword_get_is_keyword(keyword_tree, &iter));
1392 if (gtk_tree_model_iter_children(keyword_tree, &children, &iter))
1396 keyword_tree_node_write_config(keyword_tree, &children, outstr, indent);
1398 WRITE_NL(); WRITE_STRING("</keyword>");
1404 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return;
1408 void keyword_tree_write_config(GString *outstr, gint indent)
1411 WRITE_NL(); WRITE_STRING("<keyword_tree>");
1414 if (keyword_tree && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1416 keyword_tree_node_write_config(GTK_TREE_MODEL(keyword_tree), &iter, outstr, indent);
1419 WRITE_NL(); WRITE_STRING("</keyword_tree>");
1422 GtkTreeIter *keyword_add_from_config(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar **attribute_names, const gchar **attribute_values)
1425 gboolean is_kw = TRUE;
1427 while (*attribute_names)
1429 const gchar *option = *attribute_names++;
1430 const gchar *value = *attribute_values++;
1432 if (READ_CHAR_FULL("name", name)) continue;
1433 if (READ_BOOL_FULL("kw", is_kw)) continue;
1435 log_printf("unknown attribute %s = %s\n", option, value);
1437 if (name && name[0])
1440 /* re-use existing keyword if any */
1441 if (!keyword_exists(GTK_TREE_MODEL(keyword_tree), parent, NULL, name, FALSE, &iter))
1443 gtk_tree_store_append(keyword_tree, &iter, parent);
1445 keyword_set(keyword_tree, &iter, name, is_kw);
1447 return gtk_tree_iter_copy(&iter);
1453 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */