4 * Copyright (C) 2008 - 2009 The Geeqie Team
6 * Author: John Ellis, Laurent Monin
8 * This software is released under the GNU General Public License (GNU GPL).
9 * Please read the included file COPYING for more information.
10 * This software comes with no warranty of any kind, use at your own risk!
21 #include "secure_save.h"
22 #include "ui_fileops.h"
25 #include "filefilter.h"
26 #include "layout_util.h"
35 static const gchar *group_keys[] = { /* tags that will be written to all files in a group, options->metadata.sync_grouped_files */
37 "Xmp.photoshop.Urgency",
38 "Xmp.photoshop.Category",
39 "Xmp.photoshop.SupplementalCategory",
42 "Xmp.photoshop.Instruction",
43 "Xmp.photoshop.DateCreated",
45 "Xmp.photoshop.AuthorsPosition",
47 "Xmp.photoshop.State",
48 "Xmp.iptc.CountryCode",
49 "Xmp.photoshop.Country",
50 "Xmp.photoshop.TransmissionReference",
51 "Xmp.photoshop.Headline",
52 "Xmp.photoshop.Credit",
53 "Xmp.photoshop.Source",
56 "Xmp.photoshop.CaptionWriter",
59 static gboolean metadata_write_queue_idle_cb(gpointer data);
60 static gboolean metadata_legacy_write(FileData *fd);
61 static void metadata_legacy_delete(FileData *fd, const gchar *except);
66 *-------------------------------------------------------------------
68 *-------------------------------------------------------------------
71 static GList *metadata_write_queue = NULL;
72 static guint metadata_write_idle_id = 0; /* event source id */
74 static void metadata_write_queue_add(FileData *fd)
76 if (!g_list_find(metadata_write_queue, fd))
78 metadata_write_queue = g_list_prepend(metadata_write_queue, fd);
81 layout_util_status_update_write_all();
84 if (metadata_write_idle_id)
86 g_source_remove(metadata_write_idle_id);
87 metadata_write_idle_id = 0;
90 if (options->metadata.confirm_after_timeout)
92 metadata_write_idle_id = g_timeout_add(options->metadata.confirm_timeout * 1000, metadata_write_queue_idle_cb, NULL);
97 gboolean metadata_write_queue_remove(FileData *fd)
99 g_hash_table_destroy(fd->modified_xmp);
100 fd->modified_xmp = NULL;
102 metadata_write_queue = g_list_remove(metadata_write_queue, fd);
104 file_data_increment_version(fd);
105 file_data_send_notification(fd, NOTIFY_REREAD);
109 layout_util_status_update_write_all();
113 gboolean metadata_write_queue_remove_list(GList *list)
121 FileData *fd = work->data;
123 ret = ret && metadata_write_queue_remove(fd);
129 gboolean metadata_write_queue_confirm(gboolean force_dialog, FileUtilDoneFunc done_func, gpointer done_data)
132 GList *to_approve = NULL;
134 work = metadata_write_queue;
137 FileData *fd = work->data;
140 if (fd->change) continue; /* another operation in progress, skip this file for now */
142 to_approve = g_list_prepend(to_approve, file_data_ref(fd));
145 file_util_write_metadata(NULL, to_approve, NULL, force_dialog, done_func, done_data);
147 return (metadata_write_queue != NULL);
150 static gboolean metadata_write_queue_idle_cb(gpointer data)
152 metadata_write_queue_confirm(FALSE, NULL, NULL);
153 metadata_write_idle_id = 0;
157 gboolean metadata_write_perform(FileData *fd)
162 g_assert(fd->change);
164 if (fd->change->dest &&
165 strcmp(extension_from_path(fd->change->dest), GQ_CACHE_EXT_METADATA) == 0)
167 success = metadata_legacy_write(fd);
168 if (success) metadata_legacy_delete(fd, fd->change->dest);
172 /* write via exiv2 */
173 /* we can either use cached metadata which have fd->modified_xmp already applied
174 or read metadata from file and apply fd->modified_xmp
175 metadata are read also if the file was modified meanwhile */
176 exif = exif_read_fd(fd);
177 if (!exif) return FALSE;
179 success = (fd->change->dest) ? exif_write_sidecar(exif, fd->change->dest) : exif_write(exif); /* write modified metadata */
180 exif_free_fd(fd, exif);
182 if (fd->change->dest)
183 /* this will create a FileData for the sidecar and link it to the main file
184 (we can't wait until the sidecar is discovered by directory scanning because
185 exif_read_fd is called before that and it would read the main file only and
186 store the metadata in the cache)
187 FIXME: this does not catch new sidecars created by independent external programs
189 file_data_unref(file_data_new_simple(fd->change->dest));
191 if (success) metadata_legacy_delete(fd, fd->change->dest);
195 gint metadata_queue_length(void)
197 return g_list_length(metadata_write_queue);
200 static gboolean metadata_check_key(const gchar *keys[], const gchar *key)
202 const gchar **k = keys;
206 if (strcmp(key, *k) == 0) return TRUE;
212 gboolean metadata_write_revert(FileData *fd, const gchar *key)
214 if (!fd->modified_xmp) return FALSE;
216 g_hash_table_remove(fd->modified_xmp, key);
218 if (g_hash_table_size(fd->modified_xmp) == 0)
220 metadata_write_queue_remove(fd);
224 /* reread the metadata to restore the original value */
225 file_data_increment_version(fd);
226 file_data_send_notification(fd, NOTIFY_REREAD);
231 gboolean metadata_write_list(FileData *fd, const gchar *key, const GList *values)
233 if (!fd->modified_xmp)
235 fd->modified_xmp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)string_list_free);
237 g_hash_table_insert(fd->modified_xmp, g_strdup(key), string_list_copy((GList *)values));
240 exif_update_metadata(fd->exif, key, values);
242 metadata_write_queue_add(fd);
243 file_data_increment_version(fd);
244 file_data_send_notification(fd, NOTIFY_METADATA);
246 if (options->metadata.sync_grouped_files && metadata_check_key(group_keys, key))
248 GList *work = fd->sidecar_files;
252 FileData *sfd = work->data;
255 if (filter_file_class(sfd->extension, FORMAT_CLASS_META)) continue;
257 metadata_write_list(sfd, key, values);
265 gboolean metadata_write_string(FileData *fd, const gchar *key, const char *value)
267 GList *list = g_list_append(NULL, g_strdup(value));
268 gboolean ret = metadata_write_list(fd, key, list);
269 string_list_free(list);
273 gboolean metadata_write_int(FileData *fd, const gchar *key, guint64 value)
277 g_snprintf(string, sizeof(string), "%ld", value);
278 return metadata_write_string(fd, key, string);
282 *-------------------------------------------------------------------
283 * keyword / comment read/write
284 *-------------------------------------------------------------------
287 static gboolean metadata_file_write(gchar *path, GHashTable *modified_xmp)
290 GList *keywords = g_hash_table_lookup(modified_xmp, KEYWORD_KEY);
291 GList *comment_l = g_hash_table_lookup(modified_xmp, COMMENT_KEY);
292 gchar *comment = comment_l ? comment_l->data : NULL;
294 ssi = secure_open(path);
295 if (!ssi) return FALSE;
297 secure_fprintf(ssi, "#%s comment (%s)\n\n", GQ_APPNAME, VERSION);
299 secure_fprintf(ssi, "[keywords]\n");
300 while (keywords && secsave_errno == SS_ERR_NONE)
302 const gchar *word = keywords->data;
303 keywords = keywords->next;
305 secure_fprintf(ssi, "%s\n", word);
307 secure_fputc(ssi, '\n');
309 secure_fprintf(ssi, "[comment]\n");
310 secure_fprintf(ssi, "%s\n", (comment) ? comment : "");
312 secure_fprintf(ssi, "#end\n");
314 return (secure_close(ssi) == 0);
317 static gboolean metadata_legacy_write(FileData *fd)
319 gboolean success = FALSE;
320 gchar *metadata_pathl;
322 g_assert(fd->change && fd->change->dest);
324 DEBUG_1("Saving comment: %s", fd->change->dest);
326 metadata_pathl = path_from_utf8(fd->change->dest);
328 success = metadata_file_write(metadata_pathl, fd->modified_xmp);
330 g_free(metadata_pathl);
335 static gboolean metadata_file_read(gchar *path, GList **keywords, gchar **comment)
339 MetadataKey key = MK_NONE;
341 GString *comment_build = NULL;
343 f = fopen(path, "r");
344 if (!f) return FALSE;
346 while (fgets(s_buf, sizeof(s_buf), f))
350 if (*ptr == '#') continue;
351 if (*ptr == '[' && key != MK_COMMENT)
353 gchar *keystr = ++ptr;
356 while (*ptr != ']' && *ptr != '\n' && *ptr != '\0') ptr++;
361 if (g_ascii_strcasecmp(keystr, "keywords") == 0)
363 else if (g_ascii_strcasecmp(keystr, "comment") == 0)
375 while (*ptr != '\n' && *ptr != '\0') ptr++;
377 if (strlen(s_buf) > 0)
379 gchar *kw = utf8_validate_or_convert(s_buf);
381 list = g_list_prepend(list, kw);
386 if (!comment_build) comment_build = g_string_new("");
387 g_string_append(comment_build, s_buf);
396 *keywords = g_list_reverse(list);
400 string_list_free(list);
408 gchar *ptr = comment_build->str;
410 /* strip leading and trailing newlines */
411 while (*ptr == '\n') ptr++;
413 while (len > 0 && ptr[len - 1] == '\n') len--;
414 if (ptr[len] == '\n') len++; /* keep the last one */
417 gchar *text = g_strndup(ptr, len);
419 *comment = utf8_validate_or_convert(text);
423 g_string_free(comment_build, TRUE);
429 static void metadata_legacy_delete(FileData *fd, const gchar *except)
431 gchar *metadata_path;
432 gchar *metadata_pathl;
435 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
436 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
438 metadata_pathl = path_from_utf8(metadata_path);
439 unlink(metadata_pathl);
440 g_free(metadata_pathl);
441 g_free(metadata_path);
443 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
444 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
446 metadata_pathl = path_from_utf8(metadata_path);
447 unlink(metadata_pathl);
448 g_free(metadata_pathl);
449 g_free(metadata_path);
453 static gboolean metadata_legacy_read(FileData *fd, GList **keywords, gchar **comment)
455 gchar *metadata_path;
456 gchar *metadata_pathl;
457 gboolean success = FALSE;
459 if (!fd) return FALSE;
461 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
462 if (!metadata_path) return FALSE;
464 metadata_pathl = path_from_utf8(metadata_path);
466 success = metadata_file_read(metadata_pathl, keywords, comment);
468 g_free(metadata_pathl);
469 g_free(metadata_path);
474 static GList *remove_duplicate_strings_from_list(GList *list)
477 GHashTable *hashtable = g_hash_table_new(g_str_hash, g_str_equal);
478 GList *newlist = NULL;
482 gchar *key = work->data;
484 if (g_hash_table_lookup(hashtable, key) == NULL)
486 g_hash_table_insert(hashtable, (gpointer) key, GINT_TO_POINTER(1));
487 newlist = g_list_prepend(newlist, key);
492 g_hash_table_destroy(hashtable);
495 return g_list_reverse(newlist);
498 GList *metadata_read_list(FileData *fd, const gchar *key, MetadataFormat format)
502 if (!fd) return NULL;
504 /* unwritten data overide everything */
505 if (fd->modified_xmp && format == METADATA_PLAIN)
507 list = g_hash_table_lookup(fd->modified_xmp, key);
508 if (list) return string_list_copy(list);
512 Legacy metadata file is the primary source if it exists.
513 Merging the lists does not make much sense, because the existence of
514 legacy metadata file indicates that the other metadata sources are not
515 writable and thus it would not be possible to delete the keywords
516 that comes from the image file.
518 if (strcmp(key, KEYWORD_KEY) == 0)
520 if (metadata_legacy_read(fd, &list, NULL)) return list;
522 else if (strcmp(key, COMMENT_KEY) == 0)
524 gchar *comment = NULL;
525 if (metadata_legacy_read(fd, NULL, &comment)) return g_list_append(NULL, comment);
527 else if (strncmp(key, "file.", 5) == 0)
529 return g_list_append(NULL, metadata_file_info(fd, key, format));
532 exif = exif_read_fd(fd); /* this is cached, thus inexpensive */
533 if (!exif) return NULL;
534 list = exif_get_metadata(exif, key, format);
535 exif_free_fd(fd, exif);
539 gchar *metadata_read_string(FileData *fd, const gchar *key, MetadataFormat format)
541 GList *string_list = metadata_read_list(fd, key, format);
544 gchar *str = string_list->data;
545 string_list->data = NULL;
546 string_list_free(string_list);
552 guint64 metadata_read_int(FileData *fd, const gchar *key, guint64 fallback)
556 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
557 if (!string) return fallback;
559 ret = g_ascii_strtoull(string, &endptr, 10);
560 if (string == endptr) ret = fallback;
565 gdouble metadata_read_GPS_coord(FileData *fd, const gchar *key, gdouble fallback)
569 gdouble deg, min, sec;
571 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
572 if (!string) return fallback;
574 deg = g_ascii_strtod(string, &endptr);
577 min = g_ascii_strtod(endptr + 1, &endptr);
579 sec = g_ascii_strtod(endptr + 1, &endptr);
584 if (*endptr == 'S' || *endptr == 'W' || *endptr == 'N' || *endptr == 'E')
586 coord = deg + min /60.0 + sec / 3600.0;
588 if (*endptr == 'S' || *endptr == 'W') coord = -coord;
595 log_printf("unable to parse GPS coordinate '%s'\n", string);
602 gboolean metadata_append_string(FileData *fd, const gchar *key, const char *value)
604 gchar *str = metadata_read_string(fd, key, METADATA_PLAIN);
608 return metadata_write_string(fd, key, value);
612 gchar *new_string = g_strconcat(str, value, NULL);
613 gboolean ret = metadata_write_string(fd, key, new_string);
620 gboolean metadata_append_list(FileData *fd, const gchar *key, const GList *values)
622 GList *list = metadata_read_list(fd, key, METADATA_PLAIN);
626 return metadata_write_list(fd, key, values);
631 list = g_list_concat(list, string_list_copy(values));
632 list = remove_duplicate_strings_from_list(list);
634 ret = metadata_write_list(fd, key, list);
635 string_list_free(list);
640 gchar *find_string_in_list_utf8nocase(GList *list, const gchar *string)
642 gchar *string_casefold = g_utf8_casefold(string, -1);
646 gchar *haystack = list->data;
651 gchar *haystack_casefold = g_utf8_casefold(haystack, -1);
653 equal = (strcmp(haystack_casefold, string_casefold) == 0);
654 g_free(haystack_casefold);
658 g_free(string_casefold);
666 g_free(string_casefold);
671 #define KEYWORDS_SEPARATOR(c) ((c) == ',' || (c) == ';' || (c) == '\n' || (c) == '\r' || (c) == '\b')
673 GList *string_to_keywords_list(const gchar *text)
676 const gchar *ptr = text;
683 while (KEYWORDS_SEPARATOR(*ptr)) ptr++;
685 while (*ptr != '\0' && !KEYWORDS_SEPARATOR(*ptr))
691 /* trim starting and ending whitespaces */
692 while (l > 0 && g_ascii_isspace(*begin)) begin++, l--;
693 while (l > 0 && g_ascii_isspace(begin[l-1])) l--;
697 gchar *keyword = g_strndup(begin, l);
699 /* only add if not already in the list */
700 if (!find_string_in_list_utf8nocase(list, keyword))
701 list = g_list_append(list, keyword);
715 gboolean meta_data_get_keyword_mark(FileData *fd, gint n, gpointer data)
717 /* FIXME: do not use global keyword_tree */
720 gboolean found = FALSE;
721 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
725 if (keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path) &&
726 keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
733 gboolean meta_data_set_keyword_mark(FileData *fd, gint n, gboolean value, gpointer data)
736 GList *keywords = NULL;
739 if (!keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path)) return FALSE;
741 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
743 if (!!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords) != !!value)
747 keyword_tree_set(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
751 keyword_tree_reset(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
753 metadata_write_list(fd, KEYWORD_KEY, keywords);
756 string_list_free(keywords);
762 void meta_data_connect_mark_with_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *kw_iter, gint mark)
765 FileDataGetMarkFunc get_mark_func;
766 FileDataSetMarkFunc set_mark_func;
767 gpointer mark_func_data;
771 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
773 file_data_get_registered_mark_func(i, &get_mark_func, &set_mark_func, &mark_func_data);
774 if (get_mark_func == meta_data_get_keyword_mark)
776 GtkTreeIter old_kw_iter;
777 GList *old_path = mark_func_data;
779 if (keyword_tree_get_iter(keyword_tree, &old_kw_iter, old_path) &&
780 (i == mark || /* release any previous connection of given mark */
781 keyword_compare(keyword_tree, &old_kw_iter, kw_iter) == 0)) /* or given keyword */
783 file_data_register_mark_func(i, NULL, NULL, NULL, NULL);
784 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), &old_kw_iter, KEYWORD_COLUMN_MARK, "", -1);
790 if (mark >= 0 && mark < FILEDATA_MARKS_SIZE)
794 path = keyword_tree_get_path(keyword_tree, kw_iter);
795 file_data_register_mark_func(mark, meta_data_get_keyword_mark, meta_data_set_keyword_mark, path, (GDestroyNotify)string_list_free);
797 mark_str = g_strdup_printf("%d", mark + 1);
798 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), kw_iter, KEYWORD_COLUMN_MARK, mark_str, -1);
805 *-------------------------------------------------------------------
807 *-------------------------------------------------------------------
812 GtkTreeStore *keyword_tree;
814 gchar *keyword_get_name(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
817 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_NAME, &name, -1);
821 gchar *keyword_get_casefold(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
824 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_CASEFOLD, &casefold, -1);
828 gboolean keyword_get_is_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
831 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
835 void keyword_set(GtkTreeStore *keyword_tree, GtkTreeIter *iter, const gchar *name, gboolean is_keyword)
837 gchar *casefold = g_utf8_casefold(name, -1);
838 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_MARK, "",
839 KEYWORD_COLUMN_NAME, name,
840 KEYWORD_COLUMN_CASEFOLD, casefold,
841 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
845 gboolean keyword_compare(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
847 GtkTreePath *pa = gtk_tree_model_get_path(keyword_tree, a);
848 GtkTreePath *pb = gtk_tree_model_get_path(keyword_tree, b);
849 gint ret = gtk_tree_path_compare(pa, pb);
850 gtk_tree_path_free(pa);
851 gtk_tree_path_free(pb);
855 gboolean keyword_same_parent(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
857 GtkTreeIter parent_a;
858 GtkTreeIter parent_b;
860 gboolean valid_pa = gtk_tree_model_iter_parent(keyword_tree, &parent_a, a);
861 gboolean valid_pb = gtk_tree_model_iter_parent(keyword_tree, &parent_b, b);
863 if (valid_pa && valid_pb)
865 return keyword_compare(keyword_tree, &parent_a, &parent_b) == 0;
869 return (!valid_pa && !valid_pb); /* both are toplevel */
873 gboolean keyword_exists(GtkTreeModel *keyword_tree, GtkTreeIter *parent_ptr, GtkTreeIter *sibling, const gchar *name, gboolean exclude_sibling, GtkTreeIter *result)
877 gboolean toplevel = FALSE;
883 parent = *parent_ptr;
887 toplevel = !gtk_tree_model_iter_parent(keyword_tree, &parent, sibling);
894 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &iter, toplevel ? NULL : &parent)) return FALSE;
896 casefold = g_utf8_casefold(name, -1);
901 if (!(exclude_sibling && sibling && keyword_compare(keyword_tree, &iter, sibling) == 0))
903 if (options->metadata.keywords_case_sensitive)
905 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
906 ret = strcmp(name, iter_name) == 0;
911 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
912 ret = strcmp(casefold, iter_casefold) == 0;
913 g_free(iter_casefold);
914 } // if (options->metadata.tags_cas...
918 if (result) *result = iter;
921 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) break;
928 void keyword_copy(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
931 gchar *mark, *name, *casefold;
934 /* do not copy KEYWORD_COLUMN_HIDE_IN, it fully shows the new subtree */
935 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), from, KEYWORD_COLUMN_MARK, &mark,
936 KEYWORD_COLUMN_NAME, &name,
937 KEYWORD_COLUMN_CASEFOLD, &casefold,
938 KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
940 gtk_tree_store_set(keyword_tree, to, KEYWORD_COLUMN_MARK, mark,
941 KEYWORD_COLUMN_NAME, name,
942 KEYWORD_COLUMN_CASEFOLD, casefold,
943 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
949 void keyword_copy_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
951 GtkTreeIter from_child;
953 keyword_copy(keyword_tree, to, from);
955 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &from_child, from)) return;
959 GtkTreeIter to_child;
960 gtk_tree_store_append(keyword_tree, &to_child, to);
961 keyword_copy_recursive(keyword_tree, &to_child, &from_child);
962 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &from_child)) return;
966 void keyword_move_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
968 keyword_copy_recursive(keyword_tree, to, from);
969 keyword_delete(keyword_tree, from);
972 GList *keyword_tree_get_path(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
975 GtkTreeIter iter = *iter_ptr;
980 path = g_list_prepend(path, keyword_get_name(keyword_tree, &iter));
981 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) break;
987 gboolean keyword_tree_get_iter(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList *path)
991 if (!gtk_tree_model_get_iter_first(keyword_tree, &iter)) return FALSE;
995 GtkTreeIter children;
998 gchar *name = keyword_get_name(keyword_tree, &iter);
999 if (strcmp(name, path->data) == 0) break;
1001 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return FALSE;
1010 if (!gtk_tree_model_iter_children(keyword_tree, &children, &iter)) return FALSE;
1016 static gboolean keyword_tree_is_set_casefold(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *casefold_list)
1018 if (!casefold_list) return FALSE;
1020 if (!keyword_get_is_keyword(keyword_tree, &iter))
1022 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1024 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1025 return FALSE; /* this should happen only on empty helpers */
1029 if (keyword_tree_is_set_casefold(keyword_tree, child, casefold_list)) return TRUE;
1030 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1038 if (keyword_get_is_keyword(keyword_tree, &iter))
1040 GList *work = casefold_list;
1041 gboolean found = FALSE;
1042 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
1045 const gchar *casefold = work->data;
1048 if (strcmp(iter_casefold, casefold) == 0)
1054 g_free(iter_casefold);
1055 if (!found) return FALSE;
1058 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1063 static gboolean keyword_tree_is_set_casefull(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *kw_list)
1065 if (!kw_list) return FALSE;
1067 if (!keyword_get_is_keyword(keyword_tree, &iter))
1069 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1071 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1072 return FALSE; /* this should happen only on empty helpers */
1076 if (keyword_tree_is_set_casefull(keyword_tree, child, kw_list)) return TRUE;
1077 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1085 if (keyword_get_is_keyword(keyword_tree, &iter))
1087 GList *work = kw_list;
1088 gboolean found = FALSE;
1089 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
1092 const gchar *name = work->data;
1095 if (strcmp(iter_name, name) == 0)
1102 if (!found) return FALSE;
1105 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1110 gboolean keyword_tree_is_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList *kw_list)
1113 GList *casefold_list = NULL;
1116 if (options->metadata.keywords_case_sensitive)
1118 ret = keyword_tree_is_set_casefull(keyword_tree, *iter, kw_list);
1125 const gchar *kw = work->data;
1128 casefold_list = g_list_prepend(casefold_list, g_utf8_casefold(kw, -1));
1131 ret = keyword_tree_is_set_casefold(keyword_tree, *iter, casefold_list);
1133 string_list_free(casefold_list);
1139 void keyword_tree_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1141 GtkTreeIter iter = *iter_ptr;
1146 if (keyword_get_is_keyword(keyword_tree, &iter))
1148 gchar *name = keyword_get_name(keyword_tree, &iter);
1149 if (!find_string_in_list_utf8nocase(*kw_list, name))
1151 *kw_list = g_list_append(*kw_list, name);
1159 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1164 static void keyword_tree_reset1(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1168 if (!keyword_get_is_keyword(keyword_tree, iter)) return;
1170 name = keyword_get_name(keyword_tree, iter);
1171 found = find_string_in_list_utf8nocase(*kw_list, name);
1175 *kw_list = g_list_remove(*kw_list, found);
1181 static void keyword_tree_reset_recursive(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1184 keyword_tree_reset1(keyword_tree, iter, kw_list);
1186 if (!gtk_tree_model_iter_children(keyword_tree, &child, iter)) return;
1190 keyword_tree_reset_recursive(keyword_tree, &child, kw_list);
1191 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return;
1195 static gboolean keyword_tree_check_empty_children(GtkTreeModel *keyword_tree, GtkTreeIter *parent, GList *kw_list)
1199 if (!gtk_tree_model_iter_children(keyword_tree, &iter, parent))
1200 return TRUE; /* this should happen only on empty helpers */
1204 if (keyword_tree_is_set(keyword_tree, &iter, kw_list)) return FALSE;
1205 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return TRUE;
1209 void keyword_tree_reset(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1211 GtkTreeIter iter = *iter_ptr;
1213 keyword_tree_reset_recursive(keyword_tree, &iter, kw_list);
1215 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1218 while (keyword_tree_check_empty_children(keyword_tree, &iter, *kw_list))
1221 keyword_tree_reset1(keyword_tree, &iter, kw_list);
1222 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1227 void keyword_delete(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr)
1231 while (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, iter_ptr))
1233 keyword_delete(keyword_tree, &child);
1236 meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree), iter_ptr, -1);
1238 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter_ptr, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1241 gtk_tree_store_remove(keyword_tree, iter_ptr);
1245 void keyword_hide_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1248 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1249 if (!g_list_find(list, id))
1251 list = g_list_prepend(list, id);
1252 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1256 void keyword_show_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1259 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1260 list = g_list_remove(list, id);
1261 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1264 gboolean keyword_is_hidden_in(GtkTreeModel *keyword_tree, GtkTreeIter *iter, gpointer id)
1267 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1268 return !!g_list_find(list, id);
1271 static gboolean keyword_show_all_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
1273 keyword_show_in(GTK_TREE_STORE(model), iter, data);
1277 void keyword_show_all_in(GtkTreeStore *keyword_tree, gpointer id)
1279 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_all_in_cb, id);
1282 static void keyword_hide_unset_in_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr, gpointer id, GList *keywords)
1284 GtkTreeIter iter = *iter_ptr;
1287 if (!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
1289 keyword_hide_in(keyword_tree, &iter, id);
1290 /* no need to check children of hidden node */
1295 if (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, &iter))
1297 keyword_hide_unset_in_recursive(keyword_tree, &child, id, keywords);
1300 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1304 void keyword_hide_unset_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1307 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1308 keyword_hide_unset_in_recursive(keyword_tree, &iter, id, keywords);
1311 static gboolean keyword_show_set_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter_ptr, gpointer data)
1313 GtkTreeIter iter = *iter_ptr;
1314 GList *keywords = data;
1315 gpointer id = keywords->data;
1316 keywords = keywords->next; /* hack */
1317 if (keyword_tree_is_set(model, &iter, keywords))
1322 keyword_show_in(GTK_TREE_STORE(model), &iter, id);
1323 if (!gtk_tree_model_iter_parent(GTK_TREE_MODEL(keyword_tree), &parent, &iter)) break;
1330 void keyword_show_set_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1332 /* hack: pass id to keyword_hide_unset_in_cb in the list */
1333 keywords = g_list_prepend(keywords, id);
1334 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_set_in_cb, keywords);
1335 keywords = g_list_delete_link(keywords, keywords);
1339 void keyword_tree_new(void)
1341 if (keyword_tree) return;
1343 keyword_tree = gtk_tree_store_new(KEYWORD_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER);
1346 static GtkTreeIter keyword_tree_default_append(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar *name, gboolean is_keyword)
1349 gtk_tree_store_append(keyword_tree, &iter, parent);
1350 keyword_set(keyword_tree, &iter, name, is_keyword);
1354 void keyword_tree_new_default(void)
1356 GtkTreeIter i1, i2, i3;
1358 if (!keyword_tree) keyword_tree_new();
1360 i1 = keyword_tree_default_append(keyword_tree, NULL, _("People"), TRUE);
1361 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Family"), TRUE);
1362 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Free time"), TRUE);
1363 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Children"), TRUE);
1364 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sport"), TRUE);
1365 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Culture"), TRUE);
1366 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Festival"), TRUE);
1367 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Nature"), TRUE);
1368 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Animal"), TRUE);
1369 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Bird"), TRUE);
1370 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Insect"), TRUE);
1371 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Pets"), TRUE);
1372 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Wildlife"), TRUE);
1373 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Zoo"), TRUE);
1374 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Plant"), TRUE);
1375 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Tree"), TRUE);
1376 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Flower"), TRUE);
1377 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Water"), TRUE);
1378 i3 = keyword_tree_default_append(keyword_tree, &i2, _("River"), TRUE);
1379 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Lake"), TRUE);
1380 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Sea"), TRUE);
1381 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Landscape"), TRUE);
1382 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Art"), TRUE);
1383 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Statue"), TRUE);
1384 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Painting"), TRUE);
1385 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1386 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1387 i1 = keyword_tree_default_append(keyword_tree, NULL, _("City"), TRUE);
1388 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Park"), TRUE);
1389 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Street"), TRUE);
1390 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Square"), TRUE);
1391 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Architecture"), TRUE);
1392 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Buildings"), FALSE);
1393 i3 = keyword_tree_default_append(keyword_tree, &i2, _("House"), TRUE);
1394 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Cathedral"), TRUE);
1395 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Palace"), TRUE);
1396 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Castle"), TRUE);
1397 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Bridge"), TRUE);
1398 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Interior"), TRUE);
1399 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1400 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1401 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Places"), FALSE);
1402 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Conditions"), FALSE);
1403 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Night"), TRUE);
1404 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Lights"), TRUE);
1405 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Reflections"), TRUE);
1406 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sun"), TRUE);
1407 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Weather"), FALSE);
1408 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Fog"), TRUE);
1409 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Rain"), TRUE);
1410 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Clouds"), TRUE);
1411 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Snow"), TRUE);
1412 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Sunny weather"), TRUE);
1413 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Photo"), FALSE);
1414 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Edited"), TRUE);
1415 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Detail"), TRUE);
1416 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Macro"), TRUE);
1417 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Portrait"), TRUE);
1418 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Black and White"), TRUE);
1419 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Perspective"), TRUE);
1423 static void keyword_tree_node_write_config(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GString *outstr, gint indent)
1425 GtkTreeIter iter = *iter_ptr;
1428 GtkTreeIter children;
1431 WRITE_NL(); WRITE_STRING("<keyword ");
1432 name = keyword_get_name(keyword_tree, &iter);
1433 write_char_option(outstr, indent, "name", name);
1435 write_bool_option(outstr, indent, "kw", keyword_get_is_keyword(keyword_tree, &iter));
1436 if (gtk_tree_model_iter_children(keyword_tree, &children, &iter))
1440 keyword_tree_node_write_config(keyword_tree, &children, outstr, indent);
1442 WRITE_NL(); WRITE_STRING("</keyword>");
1448 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return;
1452 void keyword_tree_write_config(GString *outstr, gint indent)
1455 WRITE_NL(); WRITE_STRING("<keyword_tree>");
1458 if (keyword_tree && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1460 keyword_tree_node_write_config(GTK_TREE_MODEL(keyword_tree), &iter, outstr, indent);
1463 WRITE_NL(); WRITE_STRING("</keyword_tree>");
1466 GtkTreeIter *keyword_add_from_config(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar **attribute_names, const gchar **attribute_values)
1469 gboolean is_kw = TRUE;
1471 while (*attribute_names)
1473 const gchar *option = *attribute_names++;
1474 const gchar *value = *attribute_values++;
1476 if (READ_CHAR_FULL("name", name)) continue;
1477 if (READ_BOOL_FULL("kw", is_kw)) continue;
1479 log_printf("unknown attribute %s = %s\n", option, value);
1481 if (name && name[0])
1484 /* re-use existing keyword if any */
1485 if (!keyword_exists(GTK_TREE_MODEL(keyword_tree), parent, NULL, name, FALSE, &iter))
1487 gtk_tree_store_append(keyword_tree, &iter, parent);
1489 keyword_set(keyword_tree, &iter, name, is_kw);
1491 return gtk_tree_iter_copy(&iter);
1497 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */