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);
211 gboolean metadata_write_list(FileData *fd, const gchar *key, const GList *values)
213 if (!fd->modified_xmp)
215 fd->modified_xmp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)string_list_free);
217 g_hash_table_insert(fd->modified_xmp, g_strdup(key), string_list_copy((GList *)values));
220 exif_update_metadata(fd->exif, key, values);
222 metadata_write_queue_add(fd);
223 file_data_increment_version(fd);
224 file_data_send_notification(fd, NOTIFY_METADATA);
226 if (options->metadata.sync_grouped_files && metadata_check_key(group_keys, key))
228 GList *work = fd->sidecar_files;
232 FileData *sfd = work->data;
235 if (filter_file_class(sfd->extension, FORMAT_CLASS_META)) continue;
237 metadata_write_list(sfd, key, values);
245 gboolean metadata_write_string(FileData *fd, const gchar *key, const char *value)
247 GList *list = g_list_append(NULL, g_strdup(value));
248 gboolean ret = metadata_write_list(fd, key, list);
249 string_list_free(list);
253 gboolean metadata_write_int(FileData *fd, const gchar *key, guint64 value)
257 g_snprintf(string, sizeof(string), "%ld", value);
258 return metadata_write_string(fd, key, string);
262 *-------------------------------------------------------------------
263 * keyword / comment read/write
264 *-------------------------------------------------------------------
267 static gboolean metadata_file_write(gchar *path, GHashTable *modified_xmp)
270 GList *keywords = g_hash_table_lookup(modified_xmp, KEYWORD_KEY);
271 GList *comment_l = g_hash_table_lookup(modified_xmp, COMMENT_KEY);
272 gchar *comment = comment_l ? comment_l->data : NULL;
274 ssi = secure_open(path);
275 if (!ssi) return FALSE;
277 secure_fprintf(ssi, "#%s comment (%s)\n\n", GQ_APPNAME, VERSION);
279 secure_fprintf(ssi, "[keywords]\n");
280 while (keywords && secsave_errno == SS_ERR_NONE)
282 const gchar *word = keywords->data;
283 keywords = keywords->next;
285 secure_fprintf(ssi, "%s\n", word);
287 secure_fputc(ssi, '\n');
289 secure_fprintf(ssi, "[comment]\n");
290 secure_fprintf(ssi, "%s\n", (comment) ? comment : "");
292 secure_fprintf(ssi, "#end\n");
294 return (secure_close(ssi) == 0);
297 static gboolean metadata_legacy_write(FileData *fd)
299 gboolean success = FALSE;
300 gchar *metadata_pathl;
302 g_assert(fd->change && fd->change->dest);
304 DEBUG_1("Saving comment: %s", fd->change->dest);
306 metadata_pathl = path_from_utf8(fd->change->dest);
308 success = metadata_file_write(metadata_pathl, fd->modified_xmp);
310 g_free(metadata_pathl);
315 static gboolean metadata_file_read(gchar *path, GList **keywords, gchar **comment)
319 MetadataKey key = MK_NONE;
321 GString *comment_build = NULL;
323 f = fopen(path, "r");
324 if (!f) return FALSE;
326 while (fgets(s_buf, sizeof(s_buf), f))
330 if (*ptr == '#') continue;
331 if (*ptr == '[' && key != MK_COMMENT)
333 gchar *keystr = ++ptr;
336 while (*ptr != ']' && *ptr != '\n' && *ptr != '\0') ptr++;
341 if (g_ascii_strcasecmp(keystr, "keywords") == 0)
343 else if (g_ascii_strcasecmp(keystr, "comment") == 0)
355 while (*ptr != '\n' && *ptr != '\0') ptr++;
357 if (strlen(s_buf) > 0)
359 gchar *kw = utf8_validate_or_convert(s_buf);
361 list = g_list_prepend(list, kw);
366 if (!comment_build) comment_build = g_string_new("");
367 g_string_append(comment_build, s_buf);
376 *keywords = g_list_reverse(list);
380 string_list_free(list);
388 gchar *ptr = comment_build->str;
390 /* strip leading and trailing newlines */
391 while (*ptr == '\n') ptr++;
393 while (len > 0 && ptr[len - 1] == '\n') len--;
394 if (ptr[len] == '\n') len++; /* keep the last one */
397 gchar *text = g_strndup(ptr, len);
399 *comment = utf8_validate_or_convert(text);
403 g_string_free(comment_build, TRUE);
409 static void metadata_legacy_delete(FileData *fd, const gchar *except)
411 gchar *metadata_path;
412 gchar *metadata_pathl;
415 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
416 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
418 metadata_pathl = path_from_utf8(metadata_path);
419 unlink(metadata_pathl);
420 g_free(metadata_pathl);
421 g_free(metadata_path);
423 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
424 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
426 metadata_pathl = path_from_utf8(metadata_path);
427 unlink(metadata_pathl);
428 g_free(metadata_pathl);
429 g_free(metadata_path);
433 static gboolean metadata_legacy_read(FileData *fd, GList **keywords, gchar **comment)
435 gchar *metadata_path;
436 gchar *metadata_pathl;
437 gboolean success = FALSE;
439 if (!fd) return FALSE;
441 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
442 if (!metadata_path) return FALSE;
444 metadata_pathl = path_from_utf8(metadata_path);
446 success = metadata_file_read(metadata_pathl, keywords, comment);
448 g_free(metadata_pathl);
449 g_free(metadata_path);
454 static GList *remove_duplicate_strings_from_list(GList *list)
457 GHashTable *hashtable = g_hash_table_new(g_str_hash, g_str_equal);
458 GList *newlist = NULL;
462 gchar *key = work->data;
464 if (g_hash_table_lookup(hashtable, key) == NULL)
466 g_hash_table_insert(hashtable, (gpointer) key, GINT_TO_POINTER(1));
467 newlist = g_list_prepend(newlist, key);
472 g_hash_table_destroy(hashtable);
475 return g_list_reverse(newlist);
478 GList *metadata_read_list(FileData *fd, const gchar *key, MetadataFormat format)
482 if (!fd) return NULL;
484 /* unwritten data overide everything */
485 if (fd->modified_xmp && format == METADATA_PLAIN)
487 list = g_hash_table_lookup(fd->modified_xmp, key);
488 if (list) return string_list_copy(list);
492 Legacy metadata file is the primary source if it exists.
493 Merging the lists does not make much sense, because the existence of
494 legacy metadata file indicates that the other metadata sources are not
495 writable and thus it would not be possible to delete the keywords
496 that comes from the image file.
498 if (strcmp(key, KEYWORD_KEY) == 0)
500 if (metadata_legacy_read(fd, &list, NULL)) return list;
502 else if (strcmp(key, COMMENT_KEY) == 0)
504 gchar *comment = NULL;
505 if (metadata_legacy_read(fd, NULL, &comment)) return g_list_append(NULL, comment);
507 else if (strncmp(key, "file.", 5) == 0)
509 return g_list_append(NULL, metadata_file_info(fd, key, format));
512 exif = exif_read_fd(fd); /* this is cached, thus inexpensive */
513 if (!exif) return NULL;
514 list = exif_get_metadata(exif, key, format);
515 exif_free_fd(fd, exif);
519 gchar *metadata_read_string(FileData *fd, const gchar *key, MetadataFormat format)
521 GList *string_list = metadata_read_list(fd, key, format);
524 gchar *str = string_list->data;
525 string_list->data = NULL;
526 string_list_free(string_list);
532 guint64 metadata_read_int(FileData *fd, const gchar *key, guint64 fallback)
536 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
537 if (!string) return fallback;
539 ret = g_ascii_strtoull(string, &endptr, 10);
540 if (string == endptr) ret = fallback;
545 gdouble metadata_read_GPS_coord(FileData *fd, const gchar *key, gdouble fallback)
549 gdouble deg, min, sec;
551 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
552 if (!string) return fallback;
554 deg = g_ascii_strtod(string, &endptr);
557 min = g_ascii_strtod(endptr + 1, &endptr);
559 sec = g_ascii_strtod(endptr + 1, &endptr);
564 if (*endptr == 'S' || *endptr == 'W' || *endptr == 'N' || *endptr == 'E')
566 coord = deg + min /60.0 + sec / 3600.0;
568 if (*endptr == 'S' || *endptr == 'W') coord = -coord;
575 log_printf("unable to parse GPS coordinate '%s'\n", string);
582 gboolean metadata_append_string(FileData *fd, const gchar *key, const char *value)
584 gchar *str = metadata_read_string(fd, key, METADATA_PLAIN);
588 return metadata_write_string(fd, key, value);
592 gchar *new_string = g_strconcat(str, value, NULL);
593 gboolean ret = metadata_write_string(fd, key, new_string);
600 gboolean metadata_append_list(FileData *fd, const gchar *key, const GList *values)
602 GList *list = metadata_read_list(fd, key, METADATA_PLAIN);
606 return metadata_write_list(fd, key, values);
611 list = g_list_concat(list, string_list_copy(values));
612 list = remove_duplicate_strings_from_list(list);
614 ret = metadata_write_list(fd, key, list);
615 string_list_free(list);
620 gchar *find_string_in_list_utf8nocase(GList *list, const gchar *string)
622 gchar *string_casefold = g_utf8_casefold(string, -1);
626 gchar *haystack = list->data;
631 gchar *haystack_casefold = g_utf8_casefold(haystack, -1);
633 equal = (strcmp(haystack_casefold, string_casefold) == 0);
634 g_free(haystack_casefold);
638 g_free(string_casefold);
646 g_free(string_casefold);
651 #define KEYWORDS_SEPARATOR(c) ((c) == ',' || (c) == ';' || (c) == '\n' || (c) == '\r' || (c) == '\b')
653 GList *string_to_keywords_list(const gchar *text)
656 const gchar *ptr = text;
663 while (KEYWORDS_SEPARATOR(*ptr)) ptr++;
665 while (*ptr != '\0' && !KEYWORDS_SEPARATOR(*ptr))
671 /* trim starting and ending whitespaces */
672 while (l > 0 && g_ascii_isspace(*begin)) begin++, l--;
673 while (l > 0 && g_ascii_isspace(begin[l-1])) l--;
677 gchar *keyword = g_strndup(begin, l);
679 /* only add if not already in the list */
680 if (!find_string_in_list_utf8nocase(list, keyword))
681 list = g_list_append(list, keyword);
695 gboolean meta_data_get_keyword_mark(FileData *fd, gint n, gpointer data)
697 /* FIXME: do not use global keyword_tree */
700 gboolean found = FALSE;
701 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
705 if (keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path) &&
706 keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
713 gboolean meta_data_set_keyword_mark(FileData *fd, gint n, gboolean value, gpointer data)
716 GList *keywords = NULL;
719 if (!keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path)) return FALSE;
721 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
723 if (!!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords) != !!value)
727 keyword_tree_set(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
731 keyword_tree_reset(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
733 metadata_write_list(fd, KEYWORD_KEY, keywords);
736 string_list_free(keywords);
742 void meta_data_connect_mark_with_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *kw_iter, gint mark)
745 FileDataGetMarkFunc get_mark_func;
746 FileDataSetMarkFunc set_mark_func;
747 gpointer mark_func_data;
751 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
753 file_data_get_registered_mark_func(i, &get_mark_func, &set_mark_func, &mark_func_data);
754 if (get_mark_func == meta_data_get_keyword_mark)
756 GtkTreeIter old_kw_iter;
757 GList *old_path = mark_func_data;
759 if (keyword_tree_get_iter(keyword_tree, &old_kw_iter, old_path) &&
760 (i == mark || /* release any previous connection of given mark */
761 keyword_compare(keyword_tree, &old_kw_iter, kw_iter) == 0)) /* or given keyword */
763 file_data_register_mark_func(i, NULL, NULL, NULL, NULL);
764 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), &old_kw_iter, KEYWORD_COLUMN_MARK, "", -1);
770 if (mark >= 0 && mark < FILEDATA_MARKS_SIZE)
774 path = keyword_tree_get_path(keyword_tree, kw_iter);
775 file_data_register_mark_func(mark, meta_data_get_keyword_mark, meta_data_set_keyword_mark, path, (GDestroyNotify)string_list_free);
777 mark_str = g_strdup_printf("%d", mark + 1);
778 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), kw_iter, KEYWORD_COLUMN_MARK, mark_str, -1);
785 *-------------------------------------------------------------------
787 *-------------------------------------------------------------------
792 GtkTreeStore *keyword_tree;
794 gchar *keyword_get_name(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
797 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_NAME, &name, -1);
801 gchar *keyword_get_casefold(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
804 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_CASEFOLD, &casefold, -1);
808 gboolean keyword_get_is_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
811 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
815 void keyword_set(GtkTreeStore *keyword_tree, GtkTreeIter *iter, const gchar *name, gboolean is_keyword)
817 gchar *casefold = g_utf8_casefold(name, -1);
818 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_MARK, "",
819 KEYWORD_COLUMN_NAME, name,
820 KEYWORD_COLUMN_CASEFOLD, casefold,
821 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
825 gboolean keyword_compare(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
827 GtkTreePath *pa = gtk_tree_model_get_path(keyword_tree, a);
828 GtkTreePath *pb = gtk_tree_model_get_path(keyword_tree, b);
829 gint ret = gtk_tree_path_compare(pa, pb);
830 gtk_tree_path_free(pa);
831 gtk_tree_path_free(pb);
835 gboolean keyword_same_parent(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
837 GtkTreeIter parent_a;
838 GtkTreeIter parent_b;
840 gboolean valid_pa = gtk_tree_model_iter_parent(keyword_tree, &parent_a, a);
841 gboolean valid_pb = gtk_tree_model_iter_parent(keyword_tree, &parent_b, b);
843 if (valid_pa && valid_pb)
845 return keyword_compare(keyword_tree, &parent_a, &parent_b) == 0;
849 return (!valid_pa && !valid_pb); /* both are toplevel */
853 gboolean keyword_exists(GtkTreeModel *keyword_tree, GtkTreeIter *parent_ptr, GtkTreeIter *sibling, const gchar *name, gboolean exclude_sibling, GtkTreeIter *result)
857 gboolean toplevel = FALSE;
863 parent = *parent_ptr;
867 toplevel = !gtk_tree_model_iter_parent(keyword_tree, &parent, sibling);
874 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &iter, toplevel ? NULL : &parent)) return FALSE;
876 casefold = g_utf8_casefold(name, -1);
881 if (!(exclude_sibling && sibling && keyword_compare(keyword_tree, &iter, sibling) == 0))
883 if (options->metadata.tags_case_sensitive)
885 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
886 ret = strcmp(name, iter_name) == 0;
891 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
892 ret = strcmp(casefold, iter_casefold) == 0;
893 g_free(iter_casefold);
894 } // if (options->metadata.tags_cas...
898 if (result) *result = iter;
901 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) break;
908 void keyword_copy(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
911 gchar *mark, *name, *casefold;
914 /* do not copy KEYWORD_COLUMN_HIDE_IN, it fully shows the new subtree */
915 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), from, KEYWORD_COLUMN_MARK, &mark,
916 KEYWORD_COLUMN_NAME, &name,
917 KEYWORD_COLUMN_CASEFOLD, &casefold,
918 KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
920 gtk_tree_store_set(keyword_tree, to, KEYWORD_COLUMN_MARK, mark,
921 KEYWORD_COLUMN_NAME, name,
922 KEYWORD_COLUMN_CASEFOLD, casefold,
923 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
929 void keyword_copy_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
931 GtkTreeIter from_child;
933 keyword_copy(keyword_tree, to, from);
935 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &from_child, from)) return;
939 GtkTreeIter to_child;
940 gtk_tree_store_append(keyword_tree, &to_child, to);
941 keyword_copy_recursive(keyword_tree, &to_child, &from_child);
942 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &from_child)) return;
946 void keyword_move_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
948 keyword_copy_recursive(keyword_tree, to, from);
949 keyword_delete(keyword_tree, from);
952 GList *keyword_tree_get_path(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
955 GtkTreeIter iter = *iter_ptr;
960 path = g_list_prepend(path, keyword_get_name(keyword_tree, &iter));
961 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) break;
967 gboolean keyword_tree_get_iter(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList *path)
971 if (!gtk_tree_model_get_iter_first(keyword_tree, &iter)) return FALSE;
975 GtkTreeIter children;
978 gchar *name = keyword_get_name(keyword_tree, &iter);
979 if (strcmp(name, path->data) == 0) break;
981 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return FALSE;
990 if (!gtk_tree_model_iter_children(keyword_tree, &children, &iter)) return FALSE;
996 static gboolean keyword_tree_is_set_casefold(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *casefold_list)
998 if (!casefold_list) return FALSE;
1000 if (!keyword_get_is_keyword(keyword_tree, &iter))
1002 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1004 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1005 return FALSE; /* this should happen only on empty helpers */
1009 if (keyword_tree_is_set_casefold(keyword_tree, child, casefold_list)) return TRUE;
1010 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1018 if (keyword_get_is_keyword(keyword_tree, &iter))
1020 GList *work = casefold_list;
1021 gboolean found = FALSE;
1022 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
1025 const gchar *casefold = work->data;
1028 if (strcmp(iter_casefold, casefold) == 0)
1034 g_free(iter_casefold);
1035 if (!found) return FALSE;
1038 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1043 static gboolean keyword_tree_is_set_casefull(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *kw_list)
1045 if (!kw_list) return FALSE;
1047 if (!keyword_get_is_keyword(keyword_tree, &iter))
1049 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1051 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1052 return FALSE; /* this should happen only on empty helpers */
1056 if (keyword_tree_is_set_casefull(keyword_tree, child, kw_list)) return TRUE;
1057 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1065 if (keyword_get_is_keyword(keyword_tree, &iter))
1067 GList *work = kw_list;
1068 gboolean found = FALSE;
1069 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
1072 const gchar *name = work->data;
1075 if (strcmp(iter_name, name) == 0)
1082 if (!found) return FALSE;
1085 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1090 gboolean keyword_tree_is_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList *kw_list)
1093 GList *casefold_list = NULL;
1096 if (options->metadata.tags_case_sensitive)
1098 ret = keyword_tree_is_set_casefull(keyword_tree, *iter, kw_list);
1105 const gchar *kw = work->data;
1108 casefold_list = g_list_prepend(casefold_list, g_utf8_casefold(kw, -1));
1111 ret = keyword_tree_is_set_casefold(keyword_tree, *iter, casefold_list);
1113 string_list_free(casefold_list);
1119 void keyword_tree_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1121 GtkTreeIter iter = *iter_ptr;
1126 if (keyword_get_is_keyword(keyword_tree, &iter))
1128 gchar *name = keyword_get_name(keyword_tree, &iter);
1129 if (!find_string_in_list_utf8nocase(*kw_list, name))
1131 *kw_list = g_list_append(*kw_list, name);
1139 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1144 static void keyword_tree_reset1(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1148 if (!keyword_get_is_keyword(keyword_tree, iter)) return;
1150 name = keyword_get_name(keyword_tree, iter);
1151 found = find_string_in_list_utf8nocase(*kw_list, name);
1155 *kw_list = g_list_remove(*kw_list, found);
1161 static void keyword_tree_reset_recursive(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1164 keyword_tree_reset1(keyword_tree, iter, kw_list);
1166 if (!gtk_tree_model_iter_children(keyword_tree, &child, iter)) return;
1170 keyword_tree_reset_recursive(keyword_tree, &child, kw_list);
1171 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return;
1175 static gboolean keyword_tree_check_empty_children(GtkTreeModel *keyword_tree, GtkTreeIter *parent, GList *kw_list)
1179 if (!gtk_tree_model_iter_children(keyword_tree, &iter, parent))
1180 return TRUE; /* this should happen only on empty helpers */
1184 if (keyword_tree_is_set(keyword_tree, &iter, kw_list)) return FALSE;
1185 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return TRUE;
1189 void keyword_tree_reset(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1191 GtkTreeIter iter = *iter_ptr;
1193 keyword_tree_reset_recursive(keyword_tree, &iter, kw_list);
1195 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1198 while (keyword_tree_check_empty_children(keyword_tree, &iter, *kw_list))
1201 keyword_tree_reset1(keyword_tree, &iter, kw_list);
1202 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1207 void keyword_delete(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr)
1211 while (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, iter_ptr))
1213 keyword_delete(keyword_tree, &child);
1216 meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree), iter_ptr, -1);
1218 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter_ptr, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1221 gtk_tree_store_remove(keyword_tree, iter_ptr);
1225 void keyword_hide_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1228 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1229 if (!g_list_find(list, id))
1231 list = g_list_prepend(list, id);
1232 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1236 void keyword_show_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1239 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1240 list = g_list_remove(list, id);
1241 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1244 gboolean keyword_is_hidden_in(GtkTreeModel *keyword_tree, GtkTreeIter *iter, gpointer id)
1247 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1248 return !!g_list_find(list, id);
1251 static gboolean keyword_show_all_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
1253 keyword_show_in(GTK_TREE_STORE(model), iter, data);
1257 void keyword_show_all_in(GtkTreeStore *keyword_tree, gpointer id)
1259 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_all_in_cb, id);
1262 static void keyword_hide_unset_in_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr, gpointer id, GList *keywords)
1264 GtkTreeIter iter = *iter_ptr;
1267 if (!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
1269 keyword_hide_in(keyword_tree, &iter, id);
1270 /* no need to check children of hidden node */
1275 if (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, &iter))
1277 keyword_hide_unset_in_recursive(keyword_tree, &child, id, keywords);
1280 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1284 void keyword_hide_unset_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1287 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1288 keyword_hide_unset_in_recursive(keyword_tree, &iter, id, keywords);
1291 static gboolean keyword_show_set_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter_ptr, gpointer data)
1293 GtkTreeIter iter = *iter_ptr;
1294 GList *keywords = data;
1295 gpointer id = keywords->data;
1296 keywords = keywords->next; /* hack */
1297 if (keyword_tree_is_set(model, &iter, keywords))
1302 keyword_show_in(GTK_TREE_STORE(model), &iter, id);
1303 if (!gtk_tree_model_iter_parent(GTK_TREE_MODEL(keyword_tree), &parent, &iter)) break;
1310 void keyword_show_set_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1312 /* hack: pass id to keyword_hide_unset_in_cb in the list */
1313 keywords = g_list_prepend(keywords, id);
1314 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_set_in_cb, keywords);
1315 keywords = g_list_delete_link(keywords, keywords);
1319 void keyword_tree_new(void)
1321 if (keyword_tree) return;
1323 keyword_tree = gtk_tree_store_new(KEYWORD_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER);
1326 static GtkTreeIter keyword_tree_default_append(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar *name, gboolean is_keyword)
1329 gtk_tree_store_append(keyword_tree, &iter, parent);
1330 keyword_set(keyword_tree, &iter, name, is_keyword);
1334 void keyword_tree_new_default(void)
1336 if (keyword_tree) return;
1340 GtkTreeIter i1, i2, i3;
1342 i1 = keyword_tree_default_append(keyword_tree, NULL, _("People"), TRUE);
1343 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Family"), TRUE);
1344 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Free time"), TRUE);
1345 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Children"), TRUE);
1346 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sport"), TRUE);
1347 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Culture"), TRUE);
1348 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Festival"), TRUE);
1349 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Nature"), TRUE);
1350 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Animal"), TRUE);
1351 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Bird"), TRUE);
1352 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Insect"), TRUE);
1353 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Pets"), TRUE);
1354 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Wildlife"), TRUE);
1355 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Zoo"), TRUE);
1356 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Plant"), TRUE);
1357 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Tree"), TRUE);
1358 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Flower"), TRUE);
1359 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Water"), TRUE);
1360 i3 = keyword_tree_default_append(keyword_tree, &i2, _("River"), TRUE);
1361 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Lake"), TRUE);
1362 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Sea"), TRUE);
1363 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Landscape"), TRUE);
1364 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Art"), TRUE);
1365 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Statue"), TRUE);
1366 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Painting"), TRUE);
1367 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1368 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1369 i1 = keyword_tree_default_append(keyword_tree, NULL, _("City"), TRUE);
1370 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Park"), TRUE);
1371 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Street"), TRUE);
1372 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Square"), TRUE);
1373 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Architecture"), TRUE);
1374 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Buildings"), FALSE);
1375 i3 = keyword_tree_default_append(keyword_tree, &i2, _("House"), TRUE);
1376 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Cathedral"), TRUE);
1377 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Palace"), TRUE);
1378 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Castle"), TRUE);
1379 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Bridge"), TRUE);
1380 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Interior"), TRUE);
1381 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1382 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1383 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Places"), FALSE);
1384 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Conditions"), FALSE);
1385 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Night"), TRUE);
1386 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Lights"), TRUE);
1387 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Reflections"), TRUE);
1388 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sun"), TRUE);
1389 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Weather"), FALSE);
1390 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Fog"), TRUE);
1391 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Rain"), TRUE);
1392 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Clouds"), TRUE);
1393 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Snow"), TRUE);
1394 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Sunny weather"), TRUE);
1395 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Photo"), FALSE);
1396 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Edited"), TRUE);
1397 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Detail"), TRUE);
1398 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Macro"), TRUE);
1399 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Portrait"), TRUE);
1400 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Black and White"), TRUE);
1401 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Perspective"), TRUE);
1405 static void keyword_tree_node_write_config(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GString *outstr, gint indent)
1407 GtkTreeIter iter = *iter_ptr;
1410 GtkTreeIter children;
1413 WRITE_NL(); WRITE_STRING("<keyword ");
1414 name = keyword_get_name(keyword_tree, &iter);
1415 write_char_option(outstr, indent, "name", name);
1417 write_bool_option(outstr, indent, "kw", keyword_get_is_keyword(keyword_tree, &iter));
1418 if (gtk_tree_model_iter_children(keyword_tree, &children, &iter))
1422 keyword_tree_node_write_config(keyword_tree, &children, outstr, indent);
1424 WRITE_NL(); WRITE_STRING("</keyword>");
1430 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return;
1434 void keyword_tree_write_config(GString *outstr, gint indent)
1437 WRITE_NL(); WRITE_STRING("<keyword_tree>");
1440 if (keyword_tree && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1442 keyword_tree_node_write_config(GTK_TREE_MODEL(keyword_tree), &iter, outstr, indent);
1445 WRITE_NL(); WRITE_STRING("</keyword_tree>");
1448 GtkTreeIter *keyword_add_from_config(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar **attribute_names, const gchar **attribute_values)
1451 gboolean is_kw = TRUE;
1453 while (*attribute_names)
1455 const gchar *option = *attribute_names++;
1456 const gchar *value = *attribute_values++;
1458 if (READ_CHAR_FULL("name", name)) continue;
1459 if (READ_BOOL_FULL("kw", is_kw)) continue;
1461 log_printf("unknown attribute %s = %s\n", option, value);
1463 if (name && name[0])
1466 /* re-use existing keyword if any */
1467 if (!keyword_exists(GTK_TREE_MODEL(keyword_tree), parent, NULL, name, FALSE, &iter))
1469 gtk_tree_store_append(keyword_tree, &iter, parent);
1471 keyword_set(keyword_tree, &iter, name, is_kw);
1473 return gtk_tree_iter_copy(&iter);
1479 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */