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_revert(FileData *fd, const gchar *key)
194 if (!fd->modified_xmp) return FALSE;
196 g_hash_table_remove(fd->modified_xmp, key);
198 if (g_hash_table_size(fd->modified_xmp) == 0)
200 metadata_write_queue_remove(fd);
204 /* reread the metadata to restore the original value */
205 file_data_increment_version(fd);
206 file_data_send_notification(fd, NOTIFY_REREAD);
210 gboolean metadata_write_list(FileData *fd, const gchar *key, const GList *values)
212 if (!fd->modified_xmp)
214 fd->modified_xmp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)string_list_free);
216 g_hash_table_insert(fd->modified_xmp, g_strdup(key), string_list_copy((GList *)values));
219 exif_update_metadata(fd->exif, key, values);
221 metadata_write_queue_add(fd);
222 file_data_increment_version(fd);
223 file_data_send_notification(fd, NOTIFY_METADATA);
225 if (options->metadata.sync_grouped_files && metadata_check_key(group_keys, key))
227 GList *work = fd->sidecar_files;
231 FileData *sfd = work->data;
234 if (filter_file_class(sfd->extension, FORMAT_CLASS_META)) continue;
236 metadata_write_list(sfd, key, values);
244 gboolean metadata_write_string(FileData *fd, const gchar *key, const char *value)
246 GList *list = g_list_append(NULL, g_strdup(value));
247 gboolean ret = metadata_write_list(fd, key, list);
248 string_list_free(list);
252 gboolean metadata_write_int(FileData *fd, const gchar *key, guint64 value)
256 g_snprintf(string, sizeof(string), "%ld", value);
257 return metadata_write_string(fd, key, string);
261 *-------------------------------------------------------------------
262 * keyword / comment read/write
263 *-------------------------------------------------------------------
266 static gboolean metadata_file_write(gchar *path, GHashTable *modified_xmp)
269 GList *keywords = g_hash_table_lookup(modified_xmp, KEYWORD_KEY);
270 GList *comment_l = g_hash_table_lookup(modified_xmp, COMMENT_KEY);
271 gchar *comment = comment_l ? comment_l->data : NULL;
273 ssi = secure_open(path);
274 if (!ssi) return FALSE;
276 secure_fprintf(ssi, "#%s comment (%s)\n\n", GQ_APPNAME, VERSION);
278 secure_fprintf(ssi, "[keywords]\n");
279 while (keywords && secsave_errno == SS_ERR_NONE)
281 const gchar *word = keywords->data;
282 keywords = keywords->next;
284 secure_fprintf(ssi, "%s\n", word);
286 secure_fputc(ssi, '\n');
288 secure_fprintf(ssi, "[comment]\n");
289 secure_fprintf(ssi, "%s\n", (comment) ? comment : "");
291 secure_fprintf(ssi, "#end\n");
293 return (secure_close(ssi) == 0);
296 static gboolean metadata_legacy_write(FileData *fd)
298 gboolean success = FALSE;
299 gchar *metadata_pathl;
301 g_assert(fd->change && fd->change->dest);
303 DEBUG_1("Saving comment: %s", fd->change->dest);
305 metadata_pathl = path_from_utf8(fd->change->dest);
307 success = metadata_file_write(metadata_pathl, fd->modified_xmp);
309 g_free(metadata_pathl);
314 static gboolean metadata_file_read(gchar *path, GList **keywords, gchar **comment)
318 MetadataKey key = MK_NONE;
320 GString *comment_build = NULL;
322 f = fopen(path, "r");
323 if (!f) return FALSE;
325 while (fgets(s_buf, sizeof(s_buf), f))
329 if (*ptr == '#') continue;
330 if (*ptr == '[' && key != MK_COMMENT)
332 gchar *keystr = ++ptr;
335 while (*ptr != ']' && *ptr != '\n' && *ptr != '\0') ptr++;
340 if (g_ascii_strcasecmp(keystr, "keywords") == 0)
342 else if (g_ascii_strcasecmp(keystr, "comment") == 0)
354 while (*ptr != '\n' && *ptr != '\0') ptr++;
356 if (strlen(s_buf) > 0)
358 gchar *kw = utf8_validate_or_convert(s_buf);
360 list = g_list_prepend(list, kw);
365 if (!comment_build) comment_build = g_string_new("");
366 g_string_append(comment_build, s_buf);
375 *keywords = g_list_reverse(list);
379 string_list_free(list);
387 gchar *ptr = comment_build->str;
389 /* strip leading and trailing newlines */
390 while (*ptr == '\n') ptr++;
392 while (len > 0 && ptr[len - 1] == '\n') len--;
393 if (ptr[len] == '\n') len++; /* keep the last one */
396 gchar *text = g_strndup(ptr, len);
398 *comment = utf8_validate_or_convert(text);
402 g_string_free(comment_build, TRUE);
408 static void metadata_legacy_delete(FileData *fd, const gchar *except)
410 gchar *metadata_path;
411 gchar *metadata_pathl;
414 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
415 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
417 metadata_pathl = path_from_utf8(metadata_path);
418 unlink(metadata_pathl);
419 g_free(metadata_pathl);
420 g_free(metadata_path);
422 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
423 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
425 metadata_pathl = path_from_utf8(metadata_path);
426 unlink(metadata_pathl);
427 g_free(metadata_pathl);
428 g_free(metadata_path);
432 static gboolean metadata_legacy_read(FileData *fd, GList **keywords, gchar **comment)
434 gchar *metadata_path;
435 gchar *metadata_pathl;
436 gboolean success = FALSE;
438 if (!fd) return FALSE;
440 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
441 if (!metadata_path) return FALSE;
443 metadata_pathl = path_from_utf8(metadata_path);
445 success = metadata_file_read(metadata_pathl, keywords, comment);
447 g_free(metadata_pathl);
448 g_free(metadata_path);
453 static GList *remove_duplicate_strings_from_list(GList *list)
456 GHashTable *hashtable = g_hash_table_new(g_str_hash, g_str_equal);
457 GList *newlist = NULL;
461 gchar *key = work->data;
463 if (g_hash_table_lookup(hashtable, key) == NULL)
465 g_hash_table_insert(hashtable, (gpointer) key, GINT_TO_POINTER(1));
466 newlist = g_list_prepend(newlist, key);
471 g_hash_table_destroy(hashtable);
474 return g_list_reverse(newlist);
477 GList *metadata_read_list(FileData *fd, const gchar *key, MetadataFormat format)
481 if (!fd) return NULL;
483 /* unwritten data overide everything */
484 if (fd->modified_xmp && format == METADATA_PLAIN)
486 list = g_hash_table_lookup(fd->modified_xmp, key);
487 if (list) return string_list_copy(list);
491 Legacy metadata file is the primary source if it exists.
492 Merging the lists does not make much sense, because the existence of
493 legacy metadata file indicates that the other metadata sources are not
494 writable and thus it would not be possible to delete the keywords
495 that comes from the image file.
497 if (strcmp(key, KEYWORD_KEY) == 0)
499 if (metadata_legacy_read(fd, &list, NULL)) return list;
501 else if (strcmp(key, COMMENT_KEY) == 0)
503 gchar *comment = NULL;
504 if (metadata_legacy_read(fd, NULL, &comment)) return g_list_append(NULL, comment);
506 else if (strncmp(key, "file.", 5) == 0)
508 return g_list_append(NULL, metadata_file_info(fd, key, format));
511 exif = exif_read_fd(fd); /* this is cached, thus inexpensive */
512 if (!exif) return NULL;
513 list = exif_get_metadata(exif, key, format);
514 exif_free_fd(fd, exif);
518 gchar *metadata_read_string(FileData *fd, const gchar *key, MetadataFormat format)
520 GList *string_list = metadata_read_list(fd, key, format);
523 gchar *str = string_list->data;
524 string_list->data = NULL;
525 string_list_free(string_list);
531 guint64 metadata_read_int(FileData *fd, const gchar *key, guint64 fallback)
535 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
536 if (!string) return fallback;
538 ret = g_ascii_strtoull(string, &endptr, 10);
539 if (string == endptr) ret = fallback;
544 gdouble metadata_read_GPS_coord(FileData *fd, const gchar *key, gdouble fallback)
548 gdouble deg, min, sec;
550 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
551 if (!string) return fallback;
553 deg = g_ascii_strtod(string, &endptr);
556 min = g_ascii_strtod(endptr + 1, &endptr);
558 sec = g_ascii_strtod(endptr + 1, &endptr);
563 if (*endptr == 'S' || *endptr == 'W' || *endptr == 'N' || *endptr == 'E')
565 coord = deg + min /60.0 + sec / 3600.0;
567 if (*endptr == 'S' || *endptr == 'W') coord = -coord;
574 log_printf("unable to parse GPS coordinate '%s'\n", string);
581 gboolean metadata_append_string(FileData *fd, const gchar *key, const char *value)
583 gchar *str = metadata_read_string(fd, key, METADATA_PLAIN);
587 return metadata_write_string(fd, key, value);
591 gchar *new_string = g_strconcat(str, value, NULL);
592 gboolean ret = metadata_write_string(fd, key, new_string);
599 gboolean metadata_append_list(FileData *fd, const gchar *key, const GList *values)
601 GList *list = metadata_read_list(fd, key, METADATA_PLAIN);
605 return metadata_write_list(fd, key, values);
610 list = g_list_concat(list, string_list_copy(values));
611 list = remove_duplicate_strings_from_list(list);
613 ret = metadata_write_list(fd, key, list);
614 string_list_free(list);
619 gchar *find_string_in_list_utf8nocase(GList *list, const gchar *string)
621 gchar *string_casefold = g_utf8_casefold(string, -1);
625 gchar *haystack = list->data;
630 gchar *haystack_casefold = g_utf8_casefold(haystack, -1);
632 equal = (strcmp(haystack_casefold, string_casefold) == 0);
633 g_free(haystack_casefold);
637 g_free(string_casefold);
645 g_free(string_casefold);
650 #define KEYWORDS_SEPARATOR(c) ((c) == ',' || (c) == ';' || (c) == '\n' || (c) == '\r' || (c) == '\b')
652 GList *string_to_keywords_list(const gchar *text)
655 const gchar *ptr = text;
662 while (KEYWORDS_SEPARATOR(*ptr)) ptr++;
664 while (*ptr != '\0' && !KEYWORDS_SEPARATOR(*ptr))
670 /* trim starting and ending whitespaces */
671 while (l > 0 && g_ascii_isspace(*begin)) begin++, l--;
672 while (l > 0 && g_ascii_isspace(begin[l-1])) l--;
676 gchar *keyword = g_strndup(begin, l);
678 /* only add if not already in the list */
679 if (!find_string_in_list_utf8nocase(list, keyword))
680 list = g_list_append(list, keyword);
694 gboolean meta_data_get_keyword_mark(FileData *fd, gint n, gpointer data)
696 /* FIXME: do not use global keyword_tree */
699 gboolean found = FALSE;
700 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
704 if (keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path) &&
705 keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
712 gboolean meta_data_set_keyword_mark(FileData *fd, gint n, gboolean value, gpointer data)
715 GList *keywords = NULL;
718 if (!keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path)) return FALSE;
720 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
722 if (!!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords) != !!value)
726 keyword_tree_set(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
730 keyword_tree_reset(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
732 metadata_write_list(fd, KEYWORD_KEY, keywords);
735 string_list_free(keywords);
741 void meta_data_connect_mark_with_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *kw_iter, gint mark)
744 FileDataGetMarkFunc get_mark_func;
745 FileDataSetMarkFunc set_mark_func;
746 gpointer mark_func_data;
750 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
752 file_data_get_registered_mark_func(i, &get_mark_func, &set_mark_func, &mark_func_data);
753 if (get_mark_func == meta_data_get_keyword_mark)
755 GtkTreeIter old_kw_iter;
756 GList *old_path = mark_func_data;
758 if (keyword_tree_get_iter(keyword_tree, &old_kw_iter, old_path) &&
759 (i == mark || /* release any previous connection of given mark */
760 keyword_compare(keyword_tree, &old_kw_iter, kw_iter) == 0)) /* or given keyword */
762 file_data_register_mark_func(i, NULL, NULL, NULL, NULL);
763 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), &old_kw_iter, KEYWORD_COLUMN_MARK, "", -1);
769 if (mark >= 0 && mark < FILEDATA_MARKS_SIZE)
773 path = keyword_tree_get_path(keyword_tree, kw_iter);
774 file_data_register_mark_func(mark, meta_data_get_keyword_mark, meta_data_set_keyword_mark, path, (GDestroyNotify)string_list_free);
776 mark_str = g_strdup_printf("%d", mark + 1);
777 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), kw_iter, KEYWORD_COLUMN_MARK, mark_str, -1);
784 *-------------------------------------------------------------------
786 *-------------------------------------------------------------------
791 GtkTreeStore *keyword_tree;
793 gchar *keyword_get_name(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
796 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_NAME, &name, -1);
800 gchar *keyword_get_casefold(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
803 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_CASEFOLD, &casefold, -1);
807 gboolean keyword_get_is_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
810 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
814 void keyword_set(GtkTreeStore *keyword_tree, GtkTreeIter *iter, const gchar *name, gboolean is_keyword)
816 gchar *casefold = g_utf8_casefold(name, -1);
817 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_MARK, "",
818 KEYWORD_COLUMN_NAME, name,
819 KEYWORD_COLUMN_CASEFOLD, casefold,
820 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
824 gboolean keyword_compare(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
826 GtkTreePath *pa = gtk_tree_model_get_path(keyword_tree, a);
827 GtkTreePath *pb = gtk_tree_model_get_path(keyword_tree, b);
828 gint ret = gtk_tree_path_compare(pa, pb);
829 gtk_tree_path_free(pa);
830 gtk_tree_path_free(pb);
834 gboolean keyword_same_parent(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
836 GtkTreeIter parent_a;
837 GtkTreeIter parent_b;
839 gboolean valid_pa = gtk_tree_model_iter_parent(keyword_tree, &parent_a, a);
840 gboolean valid_pb = gtk_tree_model_iter_parent(keyword_tree, &parent_b, b);
842 if (valid_pa && valid_pb)
844 return keyword_compare(keyword_tree, &parent_a, &parent_b) == 0;
848 return (!valid_pa && !valid_pb); /* both are toplevel */
852 gboolean keyword_exists(GtkTreeModel *keyword_tree, GtkTreeIter *parent_ptr, GtkTreeIter *sibling, const gchar *name, gboolean exclude_sibling, GtkTreeIter *result)
856 gboolean toplevel = FALSE;
862 parent = *parent_ptr;
866 toplevel = !gtk_tree_model_iter_parent(keyword_tree, &parent, sibling);
873 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &iter, toplevel ? NULL : &parent)) return FALSE;
875 casefold = g_utf8_casefold(name, -1);
880 if (!(exclude_sibling && sibling && keyword_compare(keyword_tree, &iter, sibling) == 0))
882 if (options->metadata.tags_case_sensitive)
884 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
885 ret = strcmp(name, iter_name) == 0;
890 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
891 ret = strcmp(casefold, iter_casefold) == 0;
892 g_free(iter_casefold);
893 } // if (options->metadata.tags_cas...
897 if (result) *result = iter;
900 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) break;
907 void keyword_copy(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
910 gchar *mark, *name, *casefold;
913 /* do not copy KEYWORD_COLUMN_HIDE_IN, it fully shows the new subtree */
914 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), from, KEYWORD_COLUMN_MARK, &mark,
915 KEYWORD_COLUMN_NAME, &name,
916 KEYWORD_COLUMN_CASEFOLD, &casefold,
917 KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
919 gtk_tree_store_set(keyword_tree, to, KEYWORD_COLUMN_MARK, mark,
920 KEYWORD_COLUMN_NAME, name,
921 KEYWORD_COLUMN_CASEFOLD, casefold,
922 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
928 void keyword_copy_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
930 GtkTreeIter from_child;
932 keyword_copy(keyword_tree, to, from);
934 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &from_child, from)) return;
938 GtkTreeIter to_child;
939 gtk_tree_store_append(keyword_tree, &to_child, to);
940 keyword_copy_recursive(keyword_tree, &to_child, &from_child);
941 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &from_child)) return;
945 void keyword_move_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
947 keyword_copy_recursive(keyword_tree, to, from);
948 keyword_delete(keyword_tree, from);
951 GList *keyword_tree_get_path(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
954 GtkTreeIter iter = *iter_ptr;
959 path = g_list_prepend(path, keyword_get_name(keyword_tree, &iter));
960 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) break;
966 gboolean keyword_tree_get_iter(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList *path)
970 if (!gtk_tree_model_get_iter_first(keyword_tree, &iter)) return FALSE;
974 GtkTreeIter children;
977 gchar *name = keyword_get_name(keyword_tree, &iter);
978 if (strcmp(name, path->data) == 0) break;
980 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return FALSE;
989 if (!gtk_tree_model_iter_children(keyword_tree, &children, &iter)) return FALSE;
995 static gboolean keyword_tree_is_set_casefold(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *casefold_list)
997 if (!casefold_list) return FALSE;
999 if (!keyword_get_is_keyword(keyword_tree, &iter))
1001 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1003 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1004 return FALSE; /* this should happen only on empty helpers */
1008 if (keyword_tree_is_set_casefold(keyword_tree, child, casefold_list)) return TRUE;
1009 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1017 if (keyword_get_is_keyword(keyword_tree, &iter))
1019 GList *work = casefold_list;
1020 gboolean found = FALSE;
1021 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
1024 const gchar *casefold = work->data;
1027 if (strcmp(iter_casefold, casefold) == 0)
1033 g_free(iter_casefold);
1034 if (!found) return FALSE;
1037 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1042 static gboolean keyword_tree_is_set_casefull(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *kw_list)
1044 if (!kw_list) return FALSE;
1046 if (!keyword_get_is_keyword(keyword_tree, &iter))
1048 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1050 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1051 return FALSE; /* this should happen only on empty helpers */
1055 if (keyword_tree_is_set_casefull(keyword_tree, child, kw_list)) return TRUE;
1056 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1064 if (keyword_get_is_keyword(keyword_tree, &iter))
1066 GList *work = kw_list;
1067 gboolean found = FALSE;
1068 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
1071 const gchar *name = work->data;
1074 if (strcmp(iter_name, name) == 0)
1081 if (!found) return FALSE;
1084 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1089 gboolean keyword_tree_is_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList *kw_list)
1092 GList *casefold_list = NULL;
1095 if (options->metadata.tags_case_sensitive)
1097 ret = keyword_tree_is_set_casefull(keyword_tree, *iter, kw_list);
1104 const gchar *kw = work->data;
1107 casefold_list = g_list_prepend(casefold_list, g_utf8_casefold(kw, -1));
1110 ret = keyword_tree_is_set_casefold(keyword_tree, *iter, casefold_list);
1112 string_list_free(casefold_list);
1118 void keyword_tree_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1120 GtkTreeIter iter = *iter_ptr;
1125 if (keyword_get_is_keyword(keyword_tree, &iter))
1127 gchar *name = keyword_get_name(keyword_tree, &iter);
1128 if (!find_string_in_list_utf8nocase(*kw_list, name))
1130 *kw_list = g_list_append(*kw_list, name);
1138 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1143 static void keyword_tree_reset1(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1147 if (!keyword_get_is_keyword(keyword_tree, iter)) return;
1149 name = keyword_get_name(keyword_tree, iter);
1150 found = find_string_in_list_utf8nocase(*kw_list, name);
1154 *kw_list = g_list_remove(*kw_list, found);
1160 static void keyword_tree_reset_recursive(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1163 keyword_tree_reset1(keyword_tree, iter, kw_list);
1165 if (!gtk_tree_model_iter_children(keyword_tree, &child, iter)) return;
1169 keyword_tree_reset_recursive(keyword_tree, &child, kw_list);
1170 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return;
1174 static gboolean keyword_tree_check_empty_children(GtkTreeModel *keyword_tree, GtkTreeIter *parent, GList *kw_list)
1178 if (!gtk_tree_model_iter_children(keyword_tree, &iter, parent))
1179 return TRUE; /* this should happen only on empty helpers */
1183 if (keyword_tree_is_set(keyword_tree, &iter, kw_list)) return FALSE;
1184 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return TRUE;
1188 void keyword_tree_reset(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1190 GtkTreeIter iter = *iter_ptr;
1192 keyword_tree_reset_recursive(keyword_tree, &iter, kw_list);
1194 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1197 while (keyword_tree_check_empty_children(keyword_tree, &iter, *kw_list))
1200 keyword_tree_reset1(keyword_tree, &iter, kw_list);
1201 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1206 void keyword_delete(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr)
1210 while (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, iter_ptr))
1212 keyword_delete(keyword_tree, &child);
1215 meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree), iter_ptr, -1);
1217 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter_ptr, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1220 gtk_tree_store_remove(keyword_tree, iter_ptr);
1224 void keyword_hide_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1227 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1228 if (!g_list_find(list, id))
1230 list = g_list_prepend(list, id);
1231 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1235 void keyword_show_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1238 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1239 list = g_list_remove(list, id);
1240 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1243 gboolean keyword_is_hidden_in(GtkTreeModel *keyword_tree, GtkTreeIter *iter, gpointer id)
1246 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1247 return !!g_list_find(list, id);
1250 static gboolean keyword_show_all_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
1252 keyword_show_in(GTK_TREE_STORE(model), iter, data);
1256 void keyword_show_all_in(GtkTreeStore *keyword_tree, gpointer id)
1258 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_all_in_cb, id);
1261 static void keyword_hide_unset_in_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr, gpointer id, GList *keywords)
1263 GtkTreeIter iter = *iter_ptr;
1266 if (!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
1268 keyword_hide_in(keyword_tree, &iter, id);
1269 /* no need to check children of hidden node */
1274 if (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, &iter))
1276 keyword_hide_unset_in_recursive(keyword_tree, &child, id, keywords);
1279 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1283 void keyword_hide_unset_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1286 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1287 keyword_hide_unset_in_recursive(keyword_tree, &iter, id, keywords);
1290 static gboolean keyword_show_set_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter_ptr, gpointer data)
1292 GtkTreeIter iter = *iter_ptr;
1293 GList *keywords = data;
1294 gpointer id = keywords->data;
1295 keywords = keywords->next; /* hack */
1296 if (keyword_tree_is_set(model, &iter, keywords))
1301 keyword_show_in(GTK_TREE_STORE(model), &iter, id);
1302 if (!gtk_tree_model_iter_parent(GTK_TREE_MODEL(keyword_tree), &parent, &iter)) break;
1309 void keyword_show_set_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1311 /* hack: pass id to keyword_hide_unset_in_cb in the list */
1312 keywords = g_list_prepend(keywords, id);
1313 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_set_in_cb, keywords);
1314 keywords = g_list_delete_link(keywords, keywords);
1318 void keyword_tree_new(void)
1320 if (keyword_tree) return;
1322 keyword_tree = gtk_tree_store_new(KEYWORD_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER);
1325 static GtkTreeIter keyword_tree_default_append(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar *name, gboolean is_keyword)
1328 gtk_tree_store_append(keyword_tree, &iter, parent);
1329 keyword_set(keyword_tree, &iter, name, is_keyword);
1333 void keyword_tree_new_default(void)
1335 if (keyword_tree) return;
1339 GtkTreeIter i1, i2, i3;
1341 i1 = keyword_tree_default_append(keyword_tree, NULL, _("People"), TRUE);
1342 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Family"), TRUE);
1343 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Free time"), TRUE);
1344 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Children"), TRUE);
1345 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sport"), TRUE);
1346 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Culture"), TRUE);
1347 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Festival"), TRUE);
1348 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Nature"), TRUE);
1349 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Animal"), TRUE);
1350 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Bird"), TRUE);
1351 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Insect"), TRUE);
1352 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Pets"), TRUE);
1353 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Wildlife"), TRUE);
1354 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Zoo"), TRUE);
1355 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Plant"), TRUE);
1356 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Tree"), TRUE);
1357 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Flower"), TRUE);
1358 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Water"), TRUE);
1359 i3 = keyword_tree_default_append(keyword_tree, &i2, _("River"), TRUE);
1360 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Lake"), TRUE);
1361 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Sea"), TRUE);
1362 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Landscape"), TRUE);
1363 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Art"), TRUE);
1364 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Statue"), TRUE);
1365 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Painting"), TRUE);
1366 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1367 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1368 i1 = keyword_tree_default_append(keyword_tree, NULL, _("City"), TRUE);
1369 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Park"), TRUE);
1370 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Street"), TRUE);
1371 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Square"), TRUE);
1372 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Architecture"), TRUE);
1373 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Buildings"), FALSE);
1374 i3 = keyword_tree_default_append(keyword_tree, &i2, _("House"), TRUE);
1375 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Cathedral"), TRUE);
1376 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Palace"), TRUE);
1377 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Castle"), TRUE);
1378 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Bridge"), TRUE);
1379 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Interior"), TRUE);
1380 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1381 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1382 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Places"), FALSE);
1383 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Conditions"), FALSE);
1384 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Night"), TRUE);
1385 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Lights"), TRUE);
1386 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Reflections"), TRUE);
1387 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sun"), TRUE);
1388 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Weather"), FALSE);
1389 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Fog"), TRUE);
1390 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Rain"), TRUE);
1391 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Clouds"), TRUE);
1392 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Snow"), TRUE);
1393 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Sunny weather"), TRUE);
1394 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Photo"), FALSE);
1395 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Edited"), TRUE);
1396 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Detail"), TRUE);
1397 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Macro"), TRUE);
1398 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Portrait"), TRUE);
1399 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Black and White"), TRUE);
1400 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Perspective"), TRUE);
1404 static void keyword_tree_node_write_config(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GString *outstr, gint indent)
1406 GtkTreeIter iter = *iter_ptr;
1409 GtkTreeIter children;
1412 WRITE_NL(); WRITE_STRING("<keyword ");
1413 name = keyword_get_name(keyword_tree, &iter);
1414 write_char_option(outstr, indent, "name", name);
1416 write_bool_option(outstr, indent, "kw", keyword_get_is_keyword(keyword_tree, &iter));
1417 if (gtk_tree_model_iter_children(keyword_tree, &children, &iter))
1421 keyword_tree_node_write_config(keyword_tree, &children, outstr, indent);
1423 WRITE_NL(); WRITE_STRING("</keyword>");
1429 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return;
1433 void keyword_tree_write_config(GString *outstr, gint indent)
1436 WRITE_NL(); WRITE_STRING("<keyword_tree>");
1439 if (keyword_tree && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1441 keyword_tree_node_write_config(GTK_TREE_MODEL(keyword_tree), &iter, outstr, indent);
1444 WRITE_NL(); WRITE_STRING("</keyword_tree>");
1447 GtkTreeIter *keyword_add_from_config(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar **attribute_names, const gchar **attribute_values)
1450 gboolean is_kw = TRUE;
1452 while (*attribute_names)
1454 const gchar *option = *attribute_names++;
1455 const gchar *value = *attribute_values++;
1457 if (READ_CHAR_FULL("name", name)) continue;
1458 if (READ_BOOL_FULL("kw", is_kw)) continue;
1460 log_printf("unknown attribute %s = %s\n", option, value);
1462 if (name && name[0])
1465 /* re-use existing keyword if any */
1466 if (!keyword_exists(GTK_TREE_MODEL(keyword_tree), parent, NULL, name, FALSE, &iter))
1468 gtk_tree_store_append(keyword_tree, &iter, parent);
1470 keyword_set(keyword_tree, &iter, name, is_kw);
1472 return gtk_tree_iter_copy(&iter);
1478 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */