4 * Copyright (C) 2008 - 2010 The Geeqie Team
6 * Author: John Ellis, Laurent Monin
8 * This software is released under the GNU General Public License (GNU GPL).
9 * Please read the included file COPYING for more information.
10 * This software comes with no warranty of any kind, use at your own risk!
21 #include "secure_save.h"
22 #include "ui_fileops.h"
25 #include "filefilter.h"
26 #include "layout_util.h"
35 static const gchar *group_keys[] = { /* tags that will be written to all files in a group, options->metadata.sync_grouped_files */
37 "Xmp.photoshop.Urgency",
38 "Xmp.photoshop.Category",
39 "Xmp.photoshop.SupplementalCategory",
42 "Xmp.photoshop.Instruction",
43 "Xmp.photoshop.DateCreated",
45 "Xmp.photoshop.AuthorsPosition",
47 "Xmp.photoshop.State",
48 "Xmp.iptc.CountryCode",
49 "Xmp.photoshop.Country",
50 "Xmp.photoshop.TransmissionReference",
51 "Xmp.photoshop.Headline",
52 "Xmp.photoshop.Credit",
53 "Xmp.photoshop.Source",
56 "Xmp.photoshop.CaptionWriter",
59 static gboolean metadata_write_queue_idle_cb(gpointer data);
60 static gboolean metadata_legacy_write(FileData *fd);
61 static void metadata_legacy_delete(FileData *fd, const gchar *except);
62 static gboolean metadata_file_read(gchar *path, GList **keywords, gchar **comment);
67 *-------------------------------------------------------------------
69 *-------------------------------------------------------------------
72 static GList *metadata_write_queue = NULL;
73 static guint metadata_write_idle_id = 0; /* event source id */
75 static void metadata_write_queue_add(FileData *fd)
77 if (!g_list_find(metadata_write_queue, fd))
79 metadata_write_queue = g_list_prepend(metadata_write_queue, fd);
82 layout_util_status_update_write_all();
85 if (metadata_write_idle_id)
87 g_source_remove(metadata_write_idle_id);
88 metadata_write_idle_id = 0;
91 if (options->metadata.confirm_after_timeout)
93 metadata_write_idle_id = g_timeout_add(options->metadata.confirm_timeout * 1000, metadata_write_queue_idle_cb, NULL);
98 gboolean metadata_write_queue_remove(FileData *fd)
100 g_hash_table_destroy(fd->modified_xmp);
101 fd->modified_xmp = NULL;
103 metadata_write_queue = g_list_remove(metadata_write_queue, fd);
105 file_data_increment_version(fd);
106 file_data_send_notification(fd, NOTIFY_REREAD);
110 layout_util_status_update_write_all();
114 gboolean metadata_write_queue_remove_list(GList *list)
122 FileData *fd = work->data;
124 ret = ret && metadata_write_queue_remove(fd);
129 void metadata_notify_cb(FileData *fd, NotifyType type, gpointer data)
131 if ((type & (NOTIFY_REREAD | NOTIFY_CHANGE)) && g_list_find(metadata_write_queue, fd))
133 DEBUG_1("Notify metadata: %s %04x", fd->path, type);
134 if (!isname(fd->path))
136 /* ignore deleted files */
137 metadata_write_queue_remove(fd);
142 gboolean metadata_write_queue_confirm(gboolean force_dialog, FileUtilDoneFunc done_func, gpointer done_data)
145 GList *to_approve = NULL;
147 work = metadata_write_queue;
150 FileData *fd = work->data;
153 if (!isname(fd->path))
155 /* ignore deleted files */
156 metadata_write_queue_remove(fd);
160 if (fd->change) continue; /* another operation in progress, skip this file for now */
162 to_approve = g_list_prepend(to_approve, file_data_ref(fd));
165 file_util_write_metadata(NULL, to_approve, NULL, force_dialog, done_func, done_data);
167 return (metadata_write_queue != NULL);
170 static gboolean metadata_write_queue_idle_cb(gpointer data)
172 metadata_write_queue_confirm(FALSE, NULL, NULL);
173 metadata_write_idle_id = 0;
177 gboolean metadata_write_perform(FileData *fd)
182 g_assert(fd->change);
184 if (fd->change->dest &&
185 strcmp(extension_from_path(fd->change->dest), GQ_CACHE_EXT_METADATA) == 0)
187 success = metadata_legacy_write(fd);
188 if (success) metadata_legacy_delete(fd, fd->change->dest);
192 /* write via exiv2 */
193 /* we can either use cached metadata which have fd->modified_xmp already applied
194 or read metadata from file and apply fd->modified_xmp
195 metadata are read also if the file was modified meanwhile */
196 exif = exif_read_fd(fd);
197 if (!exif) return FALSE;
199 success = (fd->change->dest) ? exif_write_sidecar(exif, fd->change->dest) : exif_write(exif); /* write modified metadata */
200 exif_free_fd(fd, exif);
202 if (fd->change->dest)
203 /* this will create a FileData for the sidecar and link it to the main file
204 (we can't wait until the sidecar is discovered by directory scanning because
205 exif_read_fd is called before that and it would read the main file only and
206 store the metadata in the cache)
207 FIXME: this does not catch new sidecars created by independent external programs
209 file_data_unref(file_data_new_simple(fd->change->dest));
211 if (success) metadata_legacy_delete(fd, fd->change->dest);
215 gint metadata_queue_length(void)
217 return g_list_length(metadata_write_queue);
220 static gboolean metadata_check_key(const gchar *keys[], const gchar *key)
222 const gchar **k = keys;
226 if (strcmp(key, *k) == 0) return TRUE;
232 gboolean metadata_write_revert(FileData *fd, const gchar *key)
234 if (!fd->modified_xmp) return FALSE;
236 g_hash_table_remove(fd->modified_xmp, key);
238 if (g_hash_table_size(fd->modified_xmp) == 0)
240 metadata_write_queue_remove(fd);
244 /* reread the metadata to restore the original value */
245 file_data_increment_version(fd);
246 file_data_send_notification(fd, NOTIFY_REREAD);
251 gboolean metadata_write_list(FileData *fd, const gchar *key, const GList *values)
253 if (!fd->modified_xmp)
255 fd->modified_xmp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)string_list_free);
257 g_hash_table_insert(fd->modified_xmp, g_strdup(key), string_list_copy((GList *)values));
260 exif_update_metadata(fd->exif, key, values);
262 metadata_write_queue_add(fd);
263 file_data_increment_version(fd);
264 file_data_send_notification(fd, NOTIFY_METADATA);
266 if (options->metadata.sync_grouped_files && metadata_check_key(group_keys, key))
268 GList *work = fd->sidecar_files;
272 FileData *sfd = work->data;
275 if (filter_file_class(sfd->extension, FORMAT_CLASS_META)) continue;
277 metadata_write_list(sfd, key, values);
285 gboolean metadata_write_string(FileData *fd, const gchar *key, const char *value)
287 GList *list = g_list_append(NULL, g_strdup(value));
288 gboolean ret = metadata_write_list(fd, key, list);
289 string_list_free(list);
293 gboolean metadata_write_int(FileData *fd, const gchar *key, guint64 value)
297 g_snprintf(string, sizeof(string), "%ld", value);
298 return metadata_write_string(fd, key, string);
302 *-------------------------------------------------------------------
303 * keyword / comment read/write
304 *-------------------------------------------------------------------
307 static gboolean metadata_file_write(gchar *path, const GList *keywords, const gchar *comment)
311 ssi = secure_open(path);
312 if (!ssi) return FALSE;
314 secure_fprintf(ssi, "#%s comment (%s)\n\n", GQ_APPNAME, VERSION);
316 secure_fprintf(ssi, "[keywords]\n");
317 while (keywords && secsave_errno == SS_ERR_NONE)
319 const gchar *word = keywords->data;
320 keywords = keywords->next;
322 secure_fprintf(ssi, "%s\n", word);
324 secure_fputc(ssi, '\n');
326 secure_fprintf(ssi, "[comment]\n");
327 secure_fprintf(ssi, "%s\n", (comment) ? comment : "");
329 secure_fprintf(ssi, "#end\n");
331 return (secure_close(ssi) == 0);
334 static gboolean metadata_legacy_write(FileData *fd)
336 gboolean success = FALSE;
337 gchar *metadata_pathl;
340 gboolean have_keywords;
341 gboolean have_comment;
342 const gchar *comment;
343 GList *orig_keywords = NULL;
344 gchar *orig_comment = NULL;
346 g_assert(fd->change && fd->change->dest);
348 DEBUG_1("Saving comment: %s", fd->change->dest);
350 if (!fd->modified_xmp) return TRUE;
352 metadata_pathl = path_from_utf8(fd->change->dest);
354 have_keywords = g_hash_table_lookup_extended(fd->modified_xmp, KEYWORD_KEY, NULL, &keywords);
355 have_comment = g_hash_table_lookup_extended(fd->modified_xmp, COMMENT_KEY, NULL, &comment_l);
356 comment = (have_comment && comment_l) ? ((GList *)comment_l)->data : NULL;
358 if (!have_keywords || !have_comment) metadata_file_read(metadata_pathl, &orig_keywords, &orig_comment);
360 success = metadata_file_write(metadata_pathl,
361 have_keywords ? (GList *)keywords : orig_keywords,
362 have_comment ? comment : orig_comment);
364 g_free(metadata_pathl);
365 g_free(orig_comment);
366 string_list_free(orig_keywords);
371 static gboolean metadata_file_read(gchar *path, GList **keywords, gchar **comment)
375 MetadataKey key = MK_NONE;
377 GString *comment_build = NULL;
379 f = fopen(path, "r");
380 if (!f) return FALSE;
382 while (fgets(s_buf, sizeof(s_buf), f))
386 if (*ptr == '#') continue;
387 if (*ptr == '[' && key != MK_COMMENT)
389 gchar *keystr = ++ptr;
392 while (*ptr != ']' && *ptr != '\n' && *ptr != '\0') ptr++;
397 if (g_ascii_strcasecmp(keystr, "keywords") == 0)
399 else if (g_ascii_strcasecmp(keystr, "comment") == 0)
411 while (*ptr != '\n' && *ptr != '\0') ptr++;
413 if (strlen(s_buf) > 0)
415 gchar *kw = utf8_validate_or_convert(s_buf);
417 list = g_list_prepend(list, kw);
422 if (!comment_build) comment_build = g_string_new("");
423 g_string_append(comment_build, s_buf);
432 *keywords = g_list_reverse(list);
436 string_list_free(list);
444 gchar *ptr = comment_build->str;
446 /* strip leading and trailing newlines */
447 while (*ptr == '\n') ptr++;
449 while (len > 0 && ptr[len - 1] == '\n') len--;
450 if (ptr[len] == '\n') len++; /* keep the last one */
453 gchar *text = g_strndup(ptr, len);
455 *comment = utf8_validate_or_convert(text);
459 g_string_free(comment_build, TRUE);
465 static void metadata_legacy_delete(FileData *fd, const gchar *except)
467 gchar *metadata_path;
468 gchar *metadata_pathl;
471 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
472 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
474 metadata_pathl = path_from_utf8(metadata_path);
475 unlink(metadata_pathl);
476 g_free(metadata_pathl);
477 g_free(metadata_path);
481 /* without exiv2: do not delete xmp metadata because we are not able to convert it,
483 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
484 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
486 metadata_pathl = path_from_utf8(metadata_path);
487 unlink(metadata_pathl);
488 g_free(metadata_pathl);
489 g_free(metadata_path);
494 static gboolean metadata_legacy_read(FileData *fd, GList **keywords, gchar **comment)
496 gchar *metadata_path;
497 gchar *metadata_pathl;
498 gboolean success = FALSE;
500 if (!fd) return FALSE;
502 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
503 if (!metadata_path) return FALSE;
505 metadata_pathl = path_from_utf8(metadata_path);
507 success = metadata_file_read(metadata_pathl, keywords, comment);
509 g_free(metadata_pathl);
510 g_free(metadata_path);
515 static GList *remove_duplicate_strings_from_list(GList *list)
518 GHashTable *hashtable = g_hash_table_new(g_str_hash, g_str_equal);
519 GList *newlist = NULL;
523 gchar *key = work->data;
525 if (g_hash_table_lookup(hashtable, key) == NULL)
527 g_hash_table_insert(hashtable, (gpointer) key, GINT_TO_POINTER(1));
528 newlist = g_list_prepend(newlist, key);
533 g_hash_table_destroy(hashtable);
536 return g_list_reverse(newlist);
539 GList *metadata_read_list(FileData *fd, const gchar *key, MetadataFormat format)
543 if (!fd) return NULL;
545 /* unwritten data overide everything */
546 if (fd->modified_xmp && format == METADATA_PLAIN)
548 list = g_hash_table_lookup(fd->modified_xmp, key);
549 if (list) return string_list_copy(list);
553 Legacy metadata file is the primary source if it exists.
554 Merging the lists does not make much sense, because the existence of
555 legacy metadata file indicates that the other metadata sources are not
556 writable and thus it would not be possible to delete the keywords
557 that comes from the image file.
559 if (strcmp(key, KEYWORD_KEY) == 0)
561 if (metadata_legacy_read(fd, &list, NULL)) return list;
563 else if (strcmp(key, COMMENT_KEY) == 0)
565 gchar *comment = NULL;
566 if (metadata_legacy_read(fd, NULL, &comment)) return g_list_append(NULL, comment);
568 else if (strncmp(key, "file.", 5) == 0)
570 return g_list_append(NULL, metadata_file_info(fd, key, format));
573 exif = exif_read_fd(fd); /* this is cached, thus inexpensive */
574 if (!exif) return NULL;
575 list = exif_get_metadata(exif, key, format);
576 exif_free_fd(fd, exif);
580 gchar *metadata_read_string(FileData *fd, const gchar *key, MetadataFormat format)
582 GList *string_list = metadata_read_list(fd, key, format);
585 gchar *str = string_list->data;
586 string_list->data = NULL;
587 string_list_free(string_list);
593 guint64 metadata_read_int(FileData *fd, const gchar *key, guint64 fallback)
597 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
598 if (!string) return fallback;
600 ret = g_ascii_strtoull(string, &endptr, 10);
601 if (string == endptr) ret = fallback;
606 gdouble metadata_read_GPS_coord(FileData *fd, const gchar *key, gdouble fallback)
610 gdouble deg, min, sec;
612 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
613 if (!string) return fallback;
615 deg = g_ascii_strtod(string, &endptr);
618 min = g_ascii_strtod(endptr + 1, &endptr);
620 sec = g_ascii_strtod(endptr + 1, &endptr);
625 if (*endptr == 'S' || *endptr == 'W' || *endptr == 'N' || *endptr == 'E')
627 coord = deg + min /60.0 + sec / 3600.0;
629 if (*endptr == 'S' || *endptr == 'W') coord = -coord;
636 log_printf("unable to parse GPS coordinate '%s'\n", string);
643 gboolean metadata_append_string(FileData *fd, const gchar *key, const char *value)
645 gchar *str = metadata_read_string(fd, key, METADATA_PLAIN);
649 return metadata_write_string(fd, key, value);
653 gchar *new_string = g_strconcat(str, value, NULL);
654 gboolean ret = metadata_write_string(fd, key, new_string);
661 gboolean metadata_append_list(FileData *fd, const gchar *key, const GList *values)
663 GList *list = metadata_read_list(fd, key, METADATA_PLAIN);
667 return metadata_write_list(fd, key, values);
672 list = g_list_concat(list, string_list_copy(values));
673 list = remove_duplicate_strings_from_list(list);
675 ret = metadata_write_list(fd, key, list);
676 string_list_free(list);
682 * \see find_string_in_list
684 gchar *find_string_in_list_utf8nocase(GList *list, const gchar *string)
686 gchar *string_casefold = g_utf8_casefold(string, -1);
690 gchar *haystack = list->data;
695 gchar *haystack_casefold = g_utf8_casefold(haystack, -1);
697 equal = (strcmp(haystack_casefold, string_casefold) == 0);
698 g_free(haystack_casefold);
702 g_free(string_casefold);
710 g_free(string_casefold);
715 * \see find_string_in_list
717 gchar *find_string_in_list_utf8case(GList *list, const gchar *string)
721 gchar *haystack = list->data;
723 if (haystack && strcmp(haystack, string) == 0)
730 } // gchar *find_string_in_list_utf...
733 * \brief Find a existent string in a list.
735 * This is a switch between find_string_in_list_utf8case and
736 * find_string_in_list_utf8nocase to search with or without case for the
737 * existence of a string.
739 * \param list The list to search in
740 * \param string The string to search for
741 * \return The string or NULL
743 * \see find_string_in_list_utf8case
744 * \see find_string_in_list_utf8nocase
746 gchar *find_string_in_list(GList *list, const gchar *string)
748 if (options->metadata.keywords_case_sensitive)
749 return find_string_in_list_utf8case(list, string);
751 return find_string_in_list_utf8nocase(list, string);
754 #define KEYWORDS_SEPARATOR(c) ((c) == ',' || (c) == ';' || (c) == '\n' || (c) == '\r' || (c) == '\b')
756 GList *string_to_keywords_list(const gchar *text)
759 const gchar *ptr = text;
766 while (KEYWORDS_SEPARATOR(*ptr)) ptr++;
768 while (*ptr != '\0' && !KEYWORDS_SEPARATOR(*ptr))
774 /* trim starting and ending whitespaces */
775 while (l > 0 && g_ascii_isspace(*begin)) begin++, l--;
776 while (l > 0 && g_ascii_isspace(begin[l-1])) l--;
780 gchar *keyword = g_strndup(begin, l);
782 /* only add if not already in the list */
783 if (!find_string_in_list(list, keyword))
784 list = g_list_append(list, keyword);
798 gboolean meta_data_get_keyword_mark(FileData *fd, gint n, gpointer data)
800 /* FIXME: do not use global keyword_tree */
803 gboolean found = FALSE;
804 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
808 if (keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path) &&
809 keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
816 gboolean meta_data_set_keyword_mark(FileData *fd, gint n, gboolean value, gpointer data)
819 GList *keywords = NULL;
822 if (!keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path)) return FALSE;
824 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
826 if (!!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords) != !!value)
830 keyword_tree_set(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
834 keyword_tree_reset(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
836 metadata_write_list(fd, KEYWORD_KEY, keywords);
839 string_list_free(keywords);
845 void meta_data_connect_mark_with_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *kw_iter, gint mark)
848 FileDataGetMarkFunc get_mark_func;
849 FileDataSetMarkFunc set_mark_func;
850 gpointer mark_func_data;
854 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
856 file_data_get_registered_mark_func(i, &get_mark_func, &set_mark_func, &mark_func_data);
857 if (get_mark_func == meta_data_get_keyword_mark)
859 GtkTreeIter old_kw_iter;
860 GList *old_path = mark_func_data;
862 if (keyword_tree_get_iter(keyword_tree, &old_kw_iter, old_path) &&
863 (i == mark || /* release any previous connection of given mark */
864 keyword_compare(keyword_tree, &old_kw_iter, kw_iter) == 0)) /* or given keyword */
866 file_data_register_mark_func(i, NULL, NULL, NULL, NULL);
867 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), &old_kw_iter, KEYWORD_COLUMN_MARK, "", -1);
873 if (mark >= 0 && mark < FILEDATA_MARKS_SIZE)
877 path = keyword_tree_get_path(keyword_tree, kw_iter);
878 file_data_register_mark_func(mark, meta_data_get_keyword_mark, meta_data_set_keyword_mark, path, (GDestroyNotify)string_list_free);
880 mark_str = g_strdup_printf("%d", mark + 1);
881 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), kw_iter, KEYWORD_COLUMN_MARK, mark_str, -1);
888 *-------------------------------------------------------------------
890 *-------------------------------------------------------------------
895 GtkTreeStore *keyword_tree;
897 gchar *keyword_get_name(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
900 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_NAME, &name, -1);
904 gchar *keyword_get_casefold(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
907 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_CASEFOLD, &casefold, -1);
911 gboolean keyword_get_is_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
914 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
918 void keyword_set(GtkTreeStore *keyword_tree, GtkTreeIter *iter, const gchar *name, gboolean is_keyword)
920 gchar *casefold = g_utf8_casefold(name, -1);
921 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_MARK, "",
922 KEYWORD_COLUMN_NAME, name,
923 KEYWORD_COLUMN_CASEFOLD, casefold,
924 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
928 gboolean keyword_compare(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
930 GtkTreePath *pa = gtk_tree_model_get_path(keyword_tree, a);
931 GtkTreePath *pb = gtk_tree_model_get_path(keyword_tree, b);
932 gint ret = gtk_tree_path_compare(pa, pb);
933 gtk_tree_path_free(pa);
934 gtk_tree_path_free(pb);
938 gboolean keyword_same_parent(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
940 GtkTreeIter parent_a;
941 GtkTreeIter parent_b;
943 gboolean valid_pa = gtk_tree_model_iter_parent(keyword_tree, &parent_a, a);
944 gboolean valid_pb = gtk_tree_model_iter_parent(keyword_tree, &parent_b, b);
946 if (valid_pa && valid_pb)
948 return keyword_compare(keyword_tree, &parent_a, &parent_b) == 0;
952 return (!valid_pa && !valid_pb); /* both are toplevel */
956 gboolean keyword_exists(GtkTreeModel *keyword_tree, GtkTreeIter *parent_ptr, GtkTreeIter *sibling, const gchar *name, gboolean exclude_sibling, GtkTreeIter *result)
960 gboolean toplevel = FALSE;
966 parent = *parent_ptr;
970 toplevel = !gtk_tree_model_iter_parent(keyword_tree, &parent, sibling);
977 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &iter, toplevel ? NULL : &parent)) return FALSE;
979 casefold = g_utf8_casefold(name, -1);
984 if (!(exclude_sibling && sibling && keyword_compare(keyword_tree, &iter, sibling) == 0))
986 if (options->metadata.keywords_case_sensitive)
988 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
989 ret = strcmp(name, iter_name) == 0;
994 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
995 ret = strcmp(casefold, iter_casefold) == 0;
996 g_free(iter_casefold);
997 } // if (options->metadata.tags_cas...
1001 if (result) *result = iter;
1004 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) break;
1011 void keyword_copy(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
1014 gchar *mark, *name, *casefold;
1015 gboolean is_keyword;
1017 /* do not copy KEYWORD_COLUMN_HIDE_IN, it fully shows the new subtree */
1018 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), from, KEYWORD_COLUMN_MARK, &mark,
1019 KEYWORD_COLUMN_NAME, &name,
1020 KEYWORD_COLUMN_CASEFOLD, &casefold,
1021 KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
1023 gtk_tree_store_set(keyword_tree, to, KEYWORD_COLUMN_MARK, mark,
1024 KEYWORD_COLUMN_NAME, name,
1025 KEYWORD_COLUMN_CASEFOLD, casefold,
1026 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
1032 void keyword_copy_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
1034 GtkTreeIter from_child;
1036 keyword_copy(keyword_tree, to, from);
1038 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &from_child, from)) return;
1042 GtkTreeIter to_child;
1043 gtk_tree_store_append(keyword_tree, &to_child, to);
1044 keyword_copy_recursive(keyword_tree, &to_child, &from_child);
1045 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &from_child)) return;
1049 void keyword_move_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
1051 keyword_copy_recursive(keyword_tree, to, from);
1052 keyword_delete(keyword_tree, from);
1055 GList *keyword_tree_get_path(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
1058 GtkTreeIter iter = *iter_ptr;
1063 path = g_list_prepend(path, keyword_get_name(keyword_tree, &iter));
1064 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) break;
1070 gboolean keyword_tree_get_iter(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList *path)
1074 if (!gtk_tree_model_get_iter_first(keyword_tree, &iter)) return FALSE;
1078 GtkTreeIter children;
1081 gchar *name = keyword_get_name(keyword_tree, &iter);
1082 if (strcmp(name, path->data) == 0) break;
1084 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return FALSE;
1093 if (!gtk_tree_model_iter_children(keyword_tree, &children, &iter)) return FALSE;
1099 static gboolean keyword_tree_is_set_casefold(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *casefold_list)
1101 if (!casefold_list) return FALSE;
1103 if (!keyword_get_is_keyword(keyword_tree, &iter))
1105 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1107 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1108 return FALSE; /* this should happen only on empty helpers */
1112 if (keyword_tree_is_set_casefold(keyword_tree, child, casefold_list)) return TRUE;
1113 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1121 if (keyword_get_is_keyword(keyword_tree, &iter))
1123 GList *work = casefold_list;
1124 gboolean found = FALSE;
1125 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
1128 const gchar *casefold = work->data;
1131 if (strcmp(iter_casefold, casefold) == 0)
1137 g_free(iter_casefold);
1138 if (!found) return FALSE;
1141 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1146 static gboolean keyword_tree_is_set_casefull(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *kw_list)
1148 if (!kw_list) return FALSE;
1150 if (!keyword_get_is_keyword(keyword_tree, &iter))
1152 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1154 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1155 return FALSE; /* this should happen only on empty helpers */
1159 if (keyword_tree_is_set_casefull(keyword_tree, child, kw_list)) return TRUE;
1160 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1168 if (keyword_get_is_keyword(keyword_tree, &iter))
1170 GList *work = kw_list;
1171 gboolean found = FALSE;
1172 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
1175 const gchar *name = work->data;
1178 if (strcmp(iter_name, name) == 0)
1185 if (!found) return FALSE;
1188 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1193 gboolean keyword_tree_is_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList *kw_list)
1196 GList *casefold_list = NULL;
1199 if (options->metadata.keywords_case_sensitive)
1201 ret = keyword_tree_is_set_casefull(keyword_tree, *iter, kw_list);
1208 const gchar *kw = work->data;
1211 casefold_list = g_list_prepend(casefold_list, g_utf8_casefold(kw, -1));
1214 ret = keyword_tree_is_set_casefold(keyword_tree, *iter, casefold_list);
1216 string_list_free(casefold_list);
1222 void keyword_tree_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1224 GtkTreeIter iter = *iter_ptr;
1229 if (keyword_get_is_keyword(keyword_tree, &iter))
1231 gchar *name = keyword_get_name(keyword_tree, &iter);
1232 if (!find_string_in_list(*kw_list, name))
1234 *kw_list = g_list_append(*kw_list, name);
1242 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1247 static void keyword_tree_reset1(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1251 if (!keyword_get_is_keyword(keyword_tree, iter)) return;
1253 name = keyword_get_name(keyword_tree, iter);
1254 found = find_string_in_list(*kw_list, name);
1258 *kw_list = g_list_remove(*kw_list, found);
1264 static void keyword_tree_reset_recursive(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1267 keyword_tree_reset1(keyword_tree, iter, kw_list);
1269 if (!gtk_tree_model_iter_children(keyword_tree, &child, iter)) return;
1273 keyword_tree_reset_recursive(keyword_tree, &child, kw_list);
1274 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return;
1278 static gboolean keyword_tree_check_empty_children(GtkTreeModel *keyword_tree, GtkTreeIter *parent, GList *kw_list)
1282 if (!gtk_tree_model_iter_children(keyword_tree, &iter, parent))
1283 return TRUE; /* this should happen only on empty helpers */
1287 if (keyword_tree_is_set(keyword_tree, &iter, kw_list)) return FALSE;
1288 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return TRUE;
1292 void keyword_tree_reset(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1294 GtkTreeIter iter = *iter_ptr;
1296 keyword_tree_reset_recursive(keyword_tree, &iter, kw_list);
1298 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1301 while (keyword_tree_check_empty_children(keyword_tree, &iter, *kw_list))
1304 keyword_tree_reset1(keyword_tree, &iter, kw_list);
1305 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1310 void keyword_delete(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr)
1314 while (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, iter_ptr))
1316 keyword_delete(keyword_tree, &child);
1319 meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree), iter_ptr, -1);
1321 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter_ptr, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1324 gtk_tree_store_remove(keyword_tree, iter_ptr);
1328 void keyword_hide_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1331 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1332 if (!g_list_find(list, id))
1334 list = g_list_prepend(list, id);
1335 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1339 void keyword_show_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1342 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1343 list = g_list_remove(list, id);
1344 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1347 gboolean keyword_is_hidden_in(GtkTreeModel *keyword_tree, GtkTreeIter *iter, gpointer id)
1350 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1351 return !!g_list_find(list, id);
1354 static gboolean keyword_show_all_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
1356 keyword_show_in(GTK_TREE_STORE(model), iter, data);
1360 void keyword_show_all_in(GtkTreeStore *keyword_tree, gpointer id)
1362 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_all_in_cb, id);
1365 static void keyword_hide_unset_in_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr, gpointer id, GList *keywords)
1367 GtkTreeIter iter = *iter_ptr;
1370 if (!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
1372 keyword_hide_in(keyword_tree, &iter, id);
1373 /* no need to check children of hidden node */
1378 if (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, &iter))
1380 keyword_hide_unset_in_recursive(keyword_tree, &child, id, keywords);
1383 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1387 void keyword_hide_unset_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1390 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1391 keyword_hide_unset_in_recursive(keyword_tree, &iter, id, keywords);
1394 static gboolean keyword_show_set_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter_ptr, gpointer data)
1396 GtkTreeIter iter = *iter_ptr;
1397 GList *keywords = data;
1398 gpointer id = keywords->data;
1399 keywords = keywords->next; /* hack */
1400 if (keyword_tree_is_set(model, &iter, keywords))
1405 keyword_show_in(GTK_TREE_STORE(model), &iter, id);
1406 if (!gtk_tree_model_iter_parent(GTK_TREE_MODEL(keyword_tree), &parent, &iter)) break;
1413 void keyword_show_set_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1415 /* hack: pass id to keyword_hide_unset_in_cb in the list */
1416 keywords = g_list_prepend(keywords, id);
1417 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_set_in_cb, keywords);
1418 keywords = g_list_delete_link(keywords, keywords);
1422 void keyword_tree_new(void)
1424 if (keyword_tree) return;
1426 keyword_tree = gtk_tree_store_new(KEYWORD_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER);
1429 static GtkTreeIter keyword_tree_default_append(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar *name, gboolean is_keyword)
1432 gtk_tree_store_append(keyword_tree, &iter, parent);
1433 keyword_set(keyword_tree, &iter, name, is_keyword);
1437 void keyword_tree_new_default(void)
1439 GtkTreeIter i1, i2, i3;
1441 if (!keyword_tree) keyword_tree_new();
1443 i1 = keyword_tree_default_append(keyword_tree, NULL, _("People"), TRUE);
1444 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Family"), TRUE);
1445 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Free time"), TRUE);
1446 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Children"), TRUE);
1447 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sport"), TRUE);
1448 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Culture"), TRUE);
1449 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Festival"), TRUE);
1450 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Nature"), TRUE);
1451 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Animal"), TRUE);
1452 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Bird"), TRUE);
1453 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Insect"), TRUE);
1454 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Pets"), TRUE);
1455 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Wildlife"), TRUE);
1456 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Zoo"), TRUE);
1457 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Plant"), TRUE);
1458 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Tree"), TRUE);
1459 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Flower"), TRUE);
1460 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Water"), TRUE);
1461 i3 = keyword_tree_default_append(keyword_tree, &i2, _("River"), TRUE);
1462 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Lake"), TRUE);
1463 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Sea"), TRUE);
1464 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Landscape"), TRUE);
1465 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Art"), TRUE);
1466 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Statue"), TRUE);
1467 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Painting"), TRUE);
1468 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1469 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1470 i1 = keyword_tree_default_append(keyword_tree, NULL, _("City"), TRUE);
1471 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Park"), TRUE);
1472 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Street"), TRUE);
1473 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Square"), TRUE);
1474 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Architecture"), TRUE);
1475 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Buildings"), FALSE);
1476 i3 = keyword_tree_default_append(keyword_tree, &i2, _("House"), TRUE);
1477 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Cathedral"), TRUE);
1478 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Palace"), TRUE);
1479 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Castle"), TRUE);
1480 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Bridge"), TRUE);
1481 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Interior"), TRUE);
1482 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1483 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1484 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Places"), FALSE);
1485 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Conditions"), FALSE);
1486 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Night"), TRUE);
1487 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Lights"), TRUE);
1488 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Reflections"), TRUE);
1489 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sun"), TRUE);
1490 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Weather"), FALSE);
1491 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Fog"), TRUE);
1492 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Rain"), TRUE);
1493 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Clouds"), TRUE);
1494 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Snow"), TRUE);
1495 i3 = keyword_tree_default_append(keyword_tree, &i2, _("Sunny weather"), TRUE);
1496 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Photo"), FALSE);
1497 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Edited"), TRUE);
1498 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Detail"), TRUE);
1499 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Macro"), TRUE);
1500 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Portrait"), TRUE);
1501 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Black and White"), TRUE);
1502 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Perspective"), TRUE);
1506 static void keyword_tree_node_write_config(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GString *outstr, gint indent)
1508 GtkTreeIter iter = *iter_ptr;
1511 GtkTreeIter children;
1514 WRITE_NL(); WRITE_STRING("<keyword ");
1515 name = keyword_get_name(keyword_tree, &iter);
1516 write_char_option(outstr, indent, "name", name);
1518 write_bool_option(outstr, indent, "kw", keyword_get_is_keyword(keyword_tree, &iter));
1519 if (gtk_tree_model_iter_children(keyword_tree, &children, &iter))
1523 keyword_tree_node_write_config(keyword_tree, &children, outstr, indent);
1525 WRITE_NL(); WRITE_STRING("</keyword>");
1531 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return;
1535 void keyword_tree_write_config(GString *outstr, gint indent)
1538 WRITE_NL(); WRITE_STRING("<keyword_tree>");
1541 if (keyword_tree && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1543 keyword_tree_node_write_config(GTK_TREE_MODEL(keyword_tree), &iter, outstr, indent);
1546 WRITE_NL(); WRITE_STRING("</keyword_tree>");
1549 GtkTreeIter *keyword_add_from_config(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar **attribute_names, const gchar **attribute_values)
1552 gboolean is_kw = TRUE;
1554 while (*attribute_names)
1556 const gchar *option = *attribute_names++;
1557 const gchar *value = *attribute_values++;
1559 if (READ_CHAR_FULL("name", name)) continue;
1560 if (READ_BOOL_FULL("kw", is_kw)) continue;
1562 log_printf("unknown attribute %s = %s\n", option, value);
1564 if (name && name[0])
1567 /* re-use existing keyword if any */
1568 if (!keyword_exists(GTK_TREE_MODEL(keyword_tree), parent, NULL, name, FALSE, &iter))
1570 gtk_tree_store_append(keyword_tree, &iter, parent);
1572 keyword_set(keyword_tree, &iter, name, is_kw);
1574 return gtk_tree_iter_copy(&iter);
1580 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */