2 * Copyright (C) 2004 John Ellis
3 * Copyright (C) 2008 - 2016 The Geeqie Team
5 * Authors: John Ellis, Laurent Monin
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
31 #include "secure_save.h"
32 #include "ui_fileops.h"
35 #include "filefilter.h"
36 #include "layout_util.h"
45 static const gchar *group_keys[] = { /* tags that will be written to all files in a group, options->metadata.sync_grouped_files */
47 "Xmp.photoshop.Urgency",
48 "Xmp.photoshop.Category",
49 "Xmp.photoshop.SupplementalCategory",
52 "Xmp.photoshop.Instruction",
53 "Xmp.photoshop.DateCreated",
55 "Xmp.photoshop.AuthorsPosition",
57 "Xmp.photoshop.State",
58 "Xmp.iptc.CountryCode",
59 "Xmp.photoshop.Country",
60 "Xmp.photoshop.TransmissionReference",
61 "Xmp.photoshop.Headline",
62 "Xmp.photoshop.Credit",
63 "Xmp.photoshop.Source",
66 "Xmp.photoshop.CaptionWriter",
69 static gboolean metadata_write_queue_idle_cb(gpointer data);
70 static gboolean metadata_legacy_write(FileData *fd);
71 static void metadata_legacy_delete(FileData *fd, const gchar *except);
72 static gboolean metadata_file_read(gchar *path, GList **keywords, gchar **comment);
76 *-------------------------------------------------------------------
77 * long-term cache - keep keywords from whole dir in memory
78 *-------------------------------------------------------------------
81 /* fd->cached metadata list of lists
82 each particular list contains key as a first entry, then the values
85 static void metadata_cache_update(FileData *fd, const gchar *key, const GList *values)
89 work = fd->cached_metadata;
92 GList *entry = work->data;
93 gchar *entry_key = entry->data;
95 if (strcmp(entry_key, key) == 0)
97 /* key found - just replace values */
98 GList *old_values = entry->next;
100 old_values->prev = NULL;
101 string_list_free(old_values);
102 work->data = g_list_append(entry, string_list_copy(values));
103 DEBUG_1("updated %s %s\n", key, fd->path);
109 /* key not found - prepend new entry */
110 fd->cached_metadata = g_list_prepend(fd->cached_metadata,
111 g_list_prepend(string_list_copy(values), g_strdup(key)));
112 DEBUG_1("added %s %s\n", key, fd->path);
116 static const GList *metadata_cache_get(FileData *fd, const gchar *key)
120 work = fd->cached_metadata;
123 GList *entry = work->data;
124 gchar *entry_key = entry->data;
126 if (strcmp(entry_key, key) == 0)
129 DEBUG_1("found %s %s\n", key, fd->path);
135 DEBUG_1("not found %s %s\n", key, fd->path);
138 static void metadata_cache_remove(FileData *fd, const gchar *key)
142 work = fd->cached_metadata;
145 GList *entry = work->data;
146 gchar *entry_key = entry->data;
148 if (strcmp(entry_key, key) == 0)
151 string_list_free(entry);
152 fd->cached_metadata = g_list_delete_link(fd->cached_metadata, work);
153 DEBUG_1("removed %s %s\n", key, fd->path);
158 DEBUG_1("not removed %s %s\n", key, fd->path);
161 void metadata_cache_free(FileData *fd)
164 if (fd->cached_metadata) DEBUG_1("freed %s\n", fd->path);
166 work = fd->cached_metadata;
169 GList *entry = work->data;
170 string_list_free(entry);
174 g_list_free(fd->cached_metadata);
175 fd->cached_metadata = NULL;
184 *-------------------------------------------------------------------
186 *-------------------------------------------------------------------
189 static GList *metadata_write_queue = NULL;
190 static guint metadata_write_idle_id = 0; /* event source id */
192 static void metadata_write_queue_add(FileData *fd)
194 if (!g_list_find(metadata_write_queue, fd))
196 metadata_write_queue = g_list_prepend(metadata_write_queue, fd);
199 layout_util_status_update_write_all();
202 if (metadata_write_idle_id)
204 g_source_remove(metadata_write_idle_id);
205 metadata_write_idle_id = 0;
208 if (options->metadata.confirm_after_timeout)
210 metadata_write_idle_id = g_timeout_add(options->metadata.confirm_timeout * 1000, metadata_write_queue_idle_cb, NULL);
215 gboolean metadata_write_queue_remove(FileData *fd)
217 g_hash_table_destroy(fd->modified_xmp);
218 fd->modified_xmp = NULL;
220 metadata_write_queue = g_list_remove(metadata_write_queue, fd);
222 file_data_increment_version(fd);
223 file_data_send_notification(fd, NOTIFY_REREAD);
227 layout_util_status_update_write_all();
231 gboolean metadata_write_queue_remove_list(GList *list)
239 FileData *fd = work->data;
241 ret = ret && metadata_write_queue_remove(fd);
246 void metadata_notify_cb(FileData *fd, NotifyType type, gpointer data)
248 if (type & (NOTIFY_REREAD | NOTIFY_CHANGE))
250 metadata_cache_free(fd);
252 if (g_list_find(metadata_write_queue, fd))
254 DEBUG_1("Notify metadata: %s %04x", fd->path, type);
255 if (!isname(fd->path))
257 /* ignore deleted files */
258 metadata_write_queue_remove(fd);
264 gboolean metadata_write_queue_confirm(gboolean force_dialog, FileUtilDoneFunc done_func, gpointer done_data)
267 GList *to_approve = NULL;
269 work = metadata_write_queue;
272 FileData *fd = work->data;
275 if (!isname(fd->path))
277 /* ignore deleted files */
278 metadata_write_queue_remove(fd);
282 if (fd->change) continue; /* another operation in progress, skip this file for now */
284 to_approve = g_list_prepend(to_approve, file_data_ref(fd));
287 file_util_write_metadata(NULL, to_approve, NULL, force_dialog, done_func, done_data);
289 return (metadata_write_queue != NULL);
292 static gboolean metadata_write_queue_idle_cb(gpointer data)
294 metadata_write_queue_confirm(FALSE, NULL, NULL);
295 metadata_write_idle_id = 0;
299 gboolean metadata_write_perform(FileData *fd)
305 g_assert(fd->change);
307 lf = strlen(GQ_CACHE_EXT_METADATA);
308 if (fd->change->dest &&
309 g_ascii_strncasecmp(fd->change->dest + strlen(fd->change->dest) - lf, GQ_CACHE_EXT_METADATA, lf) == 0)
311 success = metadata_legacy_write(fd);
312 if (success) metadata_legacy_delete(fd, fd->change->dest);
316 /* write via exiv2 */
317 /* we can either use cached metadata which have fd->modified_xmp already applied
318 or read metadata from file and apply fd->modified_xmp
319 metadata are read also if the file was modified meanwhile */
320 exif = exif_read_fd(fd);
321 if (!exif) return FALSE;
323 success = (fd->change->dest) ? exif_write_sidecar(exif, fd->change->dest) : exif_write(exif); /* write modified metadata */
324 exif_free_fd(fd, exif);
326 if (fd->change->dest)
327 /* this will create a FileData for the sidecar and link it to the main file
328 (we can't wait until the sidecar is discovered by directory scanning because
329 exif_read_fd is called before that and it would read the main file only and
330 store the metadata in the cache)
331 FIXME: this does not catch new sidecars created by independent external programs
333 file_data_unref(file_data_new_group(fd->change->dest));
335 if (success) metadata_legacy_delete(fd, fd->change->dest);
339 gint metadata_queue_length(void)
341 return g_list_length(metadata_write_queue);
344 static gboolean metadata_check_key(const gchar *keys[], const gchar *key)
346 const gchar **k = keys;
350 if (strcmp(key, *k) == 0) return TRUE;
356 gboolean metadata_write_revert(FileData *fd, const gchar *key)
358 if (!fd->modified_xmp) return FALSE;
360 g_hash_table_remove(fd->modified_xmp, key);
362 if (g_hash_table_size(fd->modified_xmp) == 0)
364 metadata_write_queue_remove(fd);
368 /* reread the metadata to restore the original value */
369 file_data_increment_version(fd);
370 file_data_send_notification(fd, NOTIFY_REREAD);
375 gboolean metadata_write_list(FileData *fd, const gchar *key, const GList *values)
377 if (!fd->modified_xmp)
379 fd->modified_xmp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)string_list_free);
381 g_hash_table_insert(fd->modified_xmp, g_strdup(key), string_list_copy((GList *)values));
383 metadata_cache_remove(fd, key);
387 exif_update_metadata(fd->exif, key, values);
389 metadata_write_queue_add(fd);
390 file_data_increment_version(fd);
391 file_data_send_notification(fd, NOTIFY_METADATA);
393 if (options->metadata.sync_grouped_files && metadata_check_key(group_keys, key))
395 GList *work = fd->sidecar_files;
399 FileData *sfd = work->data;
402 if (sfd->format_class == FORMAT_CLASS_META) continue;
404 metadata_write_list(sfd, key, values);
412 gboolean metadata_write_string(FileData *fd, const gchar *key, const char *value)
414 GList *list = g_list_append(NULL, g_strdup(value));
415 gboolean ret = metadata_write_list(fd, key, list);
416 string_list_free(list);
420 gboolean metadata_write_int(FileData *fd, const gchar *key, guint64 value)
424 g_snprintf(string, sizeof(string), "%llu", (unsigned long long) value);
425 return metadata_write_string(fd, key, string);
429 *-------------------------------------------------------------------
430 * keyword / comment read/write
431 *-------------------------------------------------------------------
434 static gboolean metadata_file_write(gchar *path, const GList *keywords, const gchar *comment)
438 ssi = secure_open(path);
439 if (!ssi) return FALSE;
441 secure_fprintf(ssi, "#%s comment (%s)\n\n", GQ_APPNAME, VERSION);
443 secure_fprintf(ssi, "[keywords]\n");
444 while (keywords && secsave_errno == SS_ERR_NONE)
446 const gchar *word = keywords->data;
447 keywords = keywords->next;
449 secure_fprintf(ssi, "%s\n", word);
451 secure_fputc(ssi, '\n');
453 secure_fprintf(ssi, "[comment]\n");
454 secure_fprintf(ssi, "%s\n", (comment) ? comment : "");
456 secure_fprintf(ssi, "#end\n");
458 return (secure_close(ssi) == 0);
461 static gboolean metadata_legacy_write(FileData *fd)
463 gboolean success = FALSE;
464 gchar *metadata_pathl;
467 gboolean have_keywords;
468 gboolean have_comment;
469 const gchar *comment;
470 GList *orig_keywords = NULL;
471 gchar *orig_comment = NULL;
473 g_assert(fd->change && fd->change->dest);
475 DEBUG_1("Saving comment: %s", fd->change->dest);
477 if (!fd->modified_xmp) return TRUE;
479 metadata_pathl = path_from_utf8(fd->change->dest);
481 have_keywords = g_hash_table_lookup_extended(fd->modified_xmp, KEYWORD_KEY, NULL, &keywords);
482 have_comment = g_hash_table_lookup_extended(fd->modified_xmp, COMMENT_KEY, NULL, &comment_l);
483 comment = (have_comment && comment_l) ? ((GList *)comment_l)->data : NULL;
485 if (!have_keywords || !have_comment) metadata_file_read(metadata_pathl, &orig_keywords, &orig_comment);
487 success = metadata_file_write(metadata_pathl,
488 have_keywords ? (GList *)keywords : orig_keywords,
489 have_comment ? comment : orig_comment);
491 g_free(metadata_pathl);
492 g_free(orig_comment);
493 string_list_free(orig_keywords);
498 static gboolean metadata_file_read(gchar *path, GList **keywords, gchar **comment)
502 MetadataKey key = MK_NONE;
504 GString *comment_build = NULL;
506 f = fopen(path, "r");
507 if (!f) return FALSE;
509 while (fgets(s_buf, sizeof(s_buf), f))
513 if (*ptr == '#') continue;
514 if (*ptr == '[' && key != MK_COMMENT)
516 gchar *keystr = ++ptr;
519 while (*ptr != ']' && *ptr != '\n' && *ptr != '\0') ptr++;
524 if (g_ascii_strcasecmp(keystr, "keywords") == 0)
526 else if (g_ascii_strcasecmp(keystr, "comment") == 0)
538 while (*ptr != '\n' && *ptr != '\0') ptr++;
540 if (strlen(s_buf) > 0)
542 gchar *kw = utf8_validate_or_convert(s_buf);
544 list = g_list_prepend(list, kw);
549 if (!comment_build) comment_build = g_string_new("");
550 g_string_append(comment_build, s_buf);
559 *keywords = g_list_reverse(list);
563 string_list_free(list);
571 gchar *ptr = comment_build->str;
573 /* strip leading and trailing newlines */
574 while (*ptr == '\n') ptr++;
576 while (len > 0 && ptr[len - 1] == '\n') len--;
577 if (ptr[len] == '\n') len++; /* keep the last one */
580 gchar *text = g_strndup(ptr, len);
582 *comment = utf8_validate_or_convert(text);
586 g_string_free(comment_build, TRUE);
592 static void metadata_legacy_delete(FileData *fd, const gchar *except)
594 gchar *metadata_path;
595 gchar *metadata_pathl;
598 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
599 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
601 metadata_pathl = path_from_utf8(metadata_path);
602 unlink(metadata_pathl);
603 g_free(metadata_pathl);
604 g_free(metadata_path);
608 /* without exiv2: do not delete xmp metadata because we are not able to convert it,
610 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
611 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
613 metadata_pathl = path_from_utf8(metadata_path);
614 unlink(metadata_pathl);
615 g_free(metadata_pathl);
616 g_free(metadata_path);
621 static gboolean metadata_legacy_read(FileData *fd, GList **keywords, gchar **comment)
623 gchar *metadata_path;
624 gchar *metadata_pathl;
625 gboolean success = FALSE;
627 if (!fd) return FALSE;
629 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
630 if (!metadata_path) return FALSE;
632 metadata_pathl = path_from_utf8(metadata_path);
634 success = metadata_file_read(metadata_pathl, keywords, comment);
636 g_free(metadata_pathl);
637 g_free(metadata_path);
642 static GList *remove_duplicate_strings_from_list(GList *list)
645 GHashTable *hashtable = g_hash_table_new(g_str_hash, g_str_equal);
646 GList *newlist = NULL;
650 gchar *key = work->data;
652 if (g_hash_table_lookup(hashtable, key) == NULL)
654 g_hash_table_insert(hashtable, (gpointer) key, GINT_TO_POINTER(1));
655 newlist = g_list_prepend(newlist, key);
660 g_hash_table_destroy(hashtable);
663 return g_list_reverse(newlist);
666 GList *metadata_read_list(FileData *fd, const gchar *key, MetadataFormat format)
670 const GList *cache_entry;
671 if (!fd) return NULL;
673 /* unwritten data override everything */
674 if (fd->modified_xmp && format == METADATA_PLAIN)
676 list = g_hash_table_lookup(fd->modified_xmp, key);
677 if (list) return string_list_copy(list);
681 if (format == METADATA_PLAIN && strcmp(key, KEYWORD_KEY) == 0
682 && (cache_entry = metadata_cache_get(fd, key)))
684 return string_list_copy(cache_entry->next);
688 Legacy metadata file is the primary source if it exists.
689 Merging the lists does not make much sense, because the existence of
690 legacy metadata file indicates that the other metadata sources are not
691 writable and thus it would not be possible to delete the keywords
692 that comes from the image file.
694 if (strcmp(key, KEYWORD_KEY) == 0)
696 if (metadata_legacy_read(fd, &list, NULL))
698 if (format == METADATA_PLAIN)
700 metadata_cache_update(fd, key, list);
705 else if (strcmp(key, COMMENT_KEY) == 0)
707 gchar *comment = NULL;
708 if (metadata_legacy_read(fd, NULL, &comment)) return g_list_append(NULL, comment);
710 else if (strncmp(key, "file.", 5) == 0)
712 return g_list_append(NULL, metadata_file_info(fd, key, format));
715 else if (strncmp(key, "lua.", 4) == 0)
717 return g_list_append(NULL, metadata_lua_info(fd, key, format));
721 exif = exif_read_fd(fd); /* this is cached, thus inexpensive */
722 if (!exif) return NULL;
723 list = exif_get_metadata(exif, key, format);
724 exif_free_fd(fd, exif);
726 if (format == METADATA_PLAIN && strcmp(key, KEYWORD_KEY) == 0)
728 metadata_cache_update(fd, key, list);
734 gchar *metadata_read_string(FileData *fd, const gchar *key, MetadataFormat format)
736 GList *string_list = metadata_read_list(fd, key, format);
739 gchar *str = string_list->data;
740 string_list->data = NULL;
741 string_list_free(string_list);
747 guint64 metadata_read_int(FileData *fd, const gchar *key, guint64 fallback)
751 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
752 if (!string) return fallback;
754 ret = g_ascii_strtoull(string, &endptr, 10);
755 if (string == endptr) ret = fallback;
760 gchar *metadata_read_rating_stars(FileData *fd)
763 gint n = metadata_read_int(fd, RATING_KEY, METADATA_PLAIN);
765 ret = convert_rating_to_stars(n);
770 gdouble metadata_read_GPS_coord(FileData *fd, const gchar *key, gdouble fallback)
774 gdouble deg, min, sec;
776 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
777 if (!string) return fallback;
779 deg = g_ascii_strtod(string, &endptr);
782 min = g_ascii_strtod(endptr + 1, &endptr);
784 sec = g_ascii_strtod(endptr + 1, &endptr);
789 if (*endptr == 'S' || *endptr == 'W' || *endptr == 'N' || *endptr == 'E')
791 coord = deg + min /60.0 + sec / 3600.0;
793 if (*endptr == 'S' || *endptr == 'W') coord = -coord;
800 log_printf("unable to parse GPS coordinate '%s'\n", string);
807 gdouble metadata_read_GPS_direction(FileData *fd, const gchar *key, gdouble fallback)
812 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
813 if (!string) return fallback;
815 DEBUG_3("GPS_direction: %s\n", string);
816 deg = g_ascii_strtod(string, &endptr);
818 /* Expected text string is of the format e.g.:
830 log_printf("unable to parse GPS direction '%s: %f'\n", string, deg);
838 gboolean metadata_append_string(FileData *fd, const gchar *key, const char *value)
840 gchar *str = metadata_read_string(fd, key, METADATA_PLAIN);
844 return metadata_write_string(fd, key, value);
848 gchar *new_string = g_strconcat(str, value, NULL);
849 gboolean ret = metadata_write_string(fd, key, new_string);
856 gboolean metadata_write_GPS_coord(FileData *fd, const gchar *key, gdouble value)
864 char *old_locale, *saved_locale;
870 min = (param * 60) - (deg * 60);
871 if (g_strcmp0(key, "Xmp.exif.GPSLongitude") == 0)
876 else if (g_strcmp0(key, "Xmp.exif.GPSLatitude") == 0)
883 log_printf("unknown GPS parameter key '%s'\n", key);
889 /* Avoid locale problems with commas and decimal points in numbers */
890 old_locale = setlocale(LC_ALL, NULL);
891 saved_locale = strdup(old_locale);
892 if (saved_locale == NULL)
896 setlocale(LC_ALL, "C");
898 coordinate = g_strdup_printf("%i,%lf,%s", deg, min, ref);
899 metadata_write_string(fd, key, coordinate );
901 setlocale(LC_ALL, saved_locale);
909 gboolean metadata_append_list(FileData *fd, const gchar *key, const GList *values)
911 GList *list = metadata_read_list(fd, key, METADATA_PLAIN);
915 return metadata_write_list(fd, key, values);
920 list = g_list_concat(list, string_list_copy(values));
921 list = remove_duplicate_strings_from_list(list);
923 ret = metadata_write_list(fd, key, list);
924 string_list_free(list);
930 * \see find_string_in_list
932 gchar *find_string_in_list_utf8nocase(GList *list, const gchar *string)
934 gchar *string_casefold = g_utf8_casefold(string, -1);
938 gchar *haystack = list->data;
943 gchar *haystack_casefold = g_utf8_casefold(haystack, -1);
945 equal = (strcmp(haystack_casefold, string_casefold) == 0);
946 g_free(haystack_casefold);
950 g_free(string_casefold);
958 g_free(string_casefold);
963 * \see find_string_in_list
965 gchar *find_string_in_list_utf8case(GList *list, const gchar *string)
969 gchar *haystack = list->data;
971 if (haystack && strcmp(haystack, string) == 0)
978 } // gchar *find_string_in_list_utf...
981 * \brief Find a existent string in a list.
983 * This is a switch between find_string_in_list_utf8case and
984 * find_string_in_list_utf8nocase to search with or without case for the
985 * existence of a string.
987 * \param list The list to search in
988 * \param string The string to search for
989 * \return The string or NULL
991 * \see find_string_in_list_utf8case
992 * \see find_string_in_list_utf8nocase
994 gchar *find_string_in_list(GList *list, const gchar *string)
996 if (options->metadata.keywords_case_sensitive)
997 return find_string_in_list_utf8case(list, string);
999 return find_string_in_list_utf8nocase(list, string);
1002 #define KEYWORDS_SEPARATOR(c) ((c) == ',' || (c) == ';' || (c) == '\n' || (c) == '\r' || (c) == '\b')
1004 GList *string_to_keywords_list(const gchar *text)
1007 const gchar *ptr = text;
1009 while (*ptr != '\0')
1014 while (KEYWORDS_SEPARATOR(*ptr)) ptr++;
1016 while (*ptr != '\0' && !KEYWORDS_SEPARATOR(*ptr))
1022 /* trim starting and ending whitespaces */
1023 while (l > 0 && g_ascii_isspace(*begin)) begin++, l--;
1024 while (l > 0 && g_ascii_isspace(begin[l-1])) l--;
1028 gchar *keyword = g_strndup(begin, l);
1030 /* only add if not already in the list */
1031 if (!find_string_in_list(list, keyword))
1032 list = g_list_append(list, keyword);
1046 gboolean meta_data_get_keyword_mark(FileData *fd, gint n, gpointer data)
1048 /* FIXME: do not use global keyword_tree */
1051 gboolean found = FALSE;
1052 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
1056 if (keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path) &&
1057 keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
1064 gboolean meta_data_set_keyword_mark(FileData *fd, gint n, gboolean value, gpointer data)
1067 GList *keywords = NULL;
1070 if (!keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path)) return FALSE;
1072 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
1074 if (!!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords) != !!value)
1078 keyword_tree_set(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
1082 keyword_tree_reset(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
1084 metadata_write_list(fd, KEYWORD_KEY, keywords);
1087 string_list_free(keywords);
1093 void meta_data_connect_mark_with_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *kw_iter, gint mark)
1096 FileDataGetMarkFunc get_mark_func;
1097 FileDataSetMarkFunc set_mark_func;
1098 gpointer mark_func_data;
1102 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
1104 file_data_get_registered_mark_func(i, &get_mark_func, &set_mark_func, &mark_func_data);
1105 if (get_mark_func == meta_data_get_keyword_mark)
1107 GtkTreeIter old_kw_iter;
1108 GList *old_path = mark_func_data;
1110 if (keyword_tree_get_iter(keyword_tree, &old_kw_iter, old_path) &&
1111 (i == mark || /* release any previous connection of given mark */
1112 keyword_compare(keyword_tree, &old_kw_iter, kw_iter) == 0)) /* or given keyword */
1114 file_data_register_mark_func(i, NULL, NULL, NULL, NULL);
1115 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), &old_kw_iter, KEYWORD_COLUMN_MARK, "", -1);
1121 if (mark >= 0 && mark < FILEDATA_MARKS_SIZE)
1125 path = keyword_tree_get_path(keyword_tree, kw_iter);
1126 file_data_register_mark_func(mark, meta_data_get_keyword_mark, meta_data_set_keyword_mark, path, (GDestroyNotify)string_list_free);
1128 mark_str = g_strdup_printf("%d", (mark < 9 ? mark : -1) + 1);
1129 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), kw_iter, KEYWORD_COLUMN_MARK, mark_str, -1);
1136 *-------------------------------------------------------------------
1138 *-------------------------------------------------------------------
1143 GtkTreeStore *keyword_tree;
1145 gchar *keyword_get_name(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1148 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_NAME, &name, -1);
1152 gchar *keyword_get_mark(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1156 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_MARK, &mark_str, -1);
1160 gchar *keyword_get_casefold(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1163 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_CASEFOLD, &casefold, -1);
1167 gboolean keyword_get_is_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1169 gboolean is_keyword;
1170 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
1174 void keyword_set(GtkTreeStore *keyword_tree, GtkTreeIter *iter, const gchar *name, gboolean is_keyword)
1176 gchar *casefold = g_utf8_casefold(name, -1);
1177 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_MARK, "",
1178 KEYWORD_COLUMN_NAME, name,
1179 KEYWORD_COLUMN_CASEFOLD, casefold,
1180 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
1184 gboolean keyword_compare(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
1186 GtkTreePath *pa = gtk_tree_model_get_path(keyword_tree, a);
1187 GtkTreePath *pb = gtk_tree_model_get_path(keyword_tree, b);
1188 gint ret = gtk_tree_path_compare(pa, pb);
1189 gtk_tree_path_free(pa);
1190 gtk_tree_path_free(pb);
1194 gboolean keyword_same_parent(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
1196 GtkTreeIter parent_a;
1197 GtkTreeIter parent_b;
1199 gboolean valid_pa = gtk_tree_model_iter_parent(keyword_tree, &parent_a, a);
1200 gboolean valid_pb = gtk_tree_model_iter_parent(keyword_tree, &parent_b, b);
1202 if (valid_pa && valid_pb)
1204 return keyword_compare(keyword_tree, &parent_a, &parent_b) == 0;
1208 return (!valid_pa && !valid_pb); /* both are toplevel */
1212 gboolean keyword_exists(GtkTreeModel *keyword_tree, GtkTreeIter *parent_ptr, GtkTreeIter *sibling, const gchar *name, gboolean exclude_sibling, GtkTreeIter *result)
1216 gboolean toplevel = FALSE;
1222 parent = *parent_ptr;
1226 toplevel = !gtk_tree_model_iter_parent(keyword_tree, &parent, sibling);
1233 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &iter, toplevel ? NULL : &parent)) return FALSE;
1235 casefold = g_utf8_casefold(name, -1);
1240 if (!(exclude_sibling && sibling && keyword_compare(keyword_tree, &iter, sibling) == 0))
1242 if (options->metadata.keywords_case_sensitive)
1244 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
1245 ret = strcmp(name, iter_name) == 0;
1250 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
1251 ret = strcmp(casefold, iter_casefold) == 0;
1252 g_free(iter_casefold);
1253 } // if (options->metadata.tags_cas...
1257 if (result) *result = iter;
1260 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) break;
1267 void keyword_copy(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
1270 gchar *mark, *name, *casefold;
1271 gboolean is_keyword;
1273 /* do not copy KEYWORD_COLUMN_HIDE_IN, it fully shows the new subtree */
1274 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), from, KEYWORD_COLUMN_MARK, &mark,
1275 KEYWORD_COLUMN_NAME, &name,
1276 KEYWORD_COLUMN_CASEFOLD, &casefold,
1277 KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
1279 gtk_tree_store_set(keyword_tree, to, KEYWORD_COLUMN_MARK, mark,
1280 KEYWORD_COLUMN_NAME, name,
1281 KEYWORD_COLUMN_CASEFOLD, casefold,
1282 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
1288 void keyword_copy_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
1290 GtkTreeIter from_child;
1292 keyword_copy(keyword_tree, to, from);
1294 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &from_child, from)) return;
1298 GtkTreeIter to_child;
1299 gtk_tree_store_append(keyword_tree, &to_child, to);
1300 keyword_copy_recursive(keyword_tree, &to_child, &from_child);
1301 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &from_child)) return;
1305 void keyword_move_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
1307 keyword_copy_recursive(keyword_tree, to, from);
1308 keyword_delete(keyword_tree, from);
1311 GList *keyword_tree_get_path(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
1314 GtkTreeIter iter = *iter_ptr;
1319 path = g_list_prepend(path, keyword_get_name(keyword_tree, &iter));
1320 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) break;
1326 gboolean keyword_tree_get_iter(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList *path)
1330 if (!gtk_tree_model_get_iter_first(keyword_tree, &iter)) return FALSE;
1334 GtkTreeIter children;
1337 gchar *name = keyword_get_name(keyword_tree, &iter);
1338 if (strcmp(name, path->data) == 0) break;
1340 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return FALSE;
1349 if (!gtk_tree_model_iter_children(keyword_tree, &children, &iter)) return FALSE;
1355 static gboolean keyword_tree_is_set_casefold(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *casefold_list)
1357 if (!casefold_list) return FALSE;
1359 if (!keyword_get_is_keyword(keyword_tree, &iter))
1361 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1363 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1364 return FALSE; /* this should happen only on empty helpers */
1368 if (keyword_tree_is_set_casefold(keyword_tree, child, casefold_list)) return TRUE;
1369 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1377 if (keyword_get_is_keyword(keyword_tree, &iter))
1379 GList *work = casefold_list;
1380 gboolean found = FALSE;
1381 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
1384 const gchar *casefold = work->data;
1387 if (strcmp(iter_casefold, casefold) == 0)
1393 g_free(iter_casefold);
1394 if (!found) return FALSE;
1397 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1402 static gboolean keyword_tree_is_set_casefull(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *kw_list)
1404 if (!kw_list) return FALSE;
1406 if (!keyword_get_is_keyword(keyword_tree, &iter))
1408 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1410 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1411 return FALSE; /* this should happen only on empty helpers */
1415 if (keyword_tree_is_set_casefull(keyword_tree, child, kw_list)) return TRUE;
1416 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1424 if (keyword_get_is_keyword(keyword_tree, &iter))
1426 GList *work = kw_list;
1427 gboolean found = FALSE;
1428 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
1431 const gchar *name = work->data;
1434 if (strcmp(iter_name, name) == 0)
1441 if (!found) return FALSE;
1444 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1449 gboolean keyword_tree_is_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList *kw_list)
1452 GList *casefold_list = NULL;
1455 if (options->metadata.keywords_case_sensitive)
1457 ret = keyword_tree_is_set_casefull(keyword_tree, *iter, kw_list);
1464 const gchar *kw = work->data;
1467 casefold_list = g_list_prepend(casefold_list, g_utf8_casefold(kw, -1));
1470 ret = keyword_tree_is_set_casefold(keyword_tree, *iter, casefold_list);
1472 string_list_free(casefold_list);
1478 void keyword_tree_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1480 GtkTreeIter iter = *iter_ptr;
1485 if (keyword_get_is_keyword(keyword_tree, &iter))
1487 gchar *name = keyword_get_name(keyword_tree, &iter);
1488 if (!find_string_in_list(*kw_list, name))
1490 *kw_list = g_list_append(*kw_list, name);
1498 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1503 GList *keyword_tree_get(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
1505 GtkTreeIter iter = *iter_ptr;
1506 GList *kw_list = NULL;
1512 if (keyword_get_is_keyword(keyword_tree, &iter))
1514 gchar *name = keyword_get_name(keyword_tree, &iter);
1515 kw_list = g_list_append(kw_list, name);
1518 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return kw_list;
1521 } // GList *keyword_tree_get(GtkTre...
1523 static void keyword_tree_reset1(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1527 if (!keyword_get_is_keyword(keyword_tree, iter)) return;
1529 name = keyword_get_name(keyword_tree, iter);
1530 found = find_string_in_list(*kw_list, name);
1534 *kw_list = g_list_remove(*kw_list, found);
1540 static void keyword_tree_reset_recursive(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1543 keyword_tree_reset1(keyword_tree, iter, kw_list);
1545 if (!gtk_tree_model_iter_children(keyword_tree, &child, iter)) return;
1549 keyword_tree_reset_recursive(keyword_tree, &child, kw_list);
1550 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return;
1554 static gboolean keyword_tree_check_empty_children(GtkTreeModel *keyword_tree, GtkTreeIter *parent, GList *kw_list)
1558 if (!gtk_tree_model_iter_children(keyword_tree, &iter, parent))
1559 return TRUE; /* this should happen only on empty helpers */
1563 if (keyword_tree_is_set(keyword_tree, &iter, kw_list)) return FALSE;
1564 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return TRUE;
1568 void keyword_tree_reset(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1570 GtkTreeIter iter = *iter_ptr;
1572 keyword_tree_reset_recursive(keyword_tree, &iter, kw_list);
1574 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1577 while (keyword_tree_check_empty_children(keyword_tree, &iter, *kw_list))
1580 keyword_tree_reset1(keyword_tree, &iter, kw_list);
1581 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1586 void keyword_delete(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr)
1590 while (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, iter_ptr))
1592 keyword_delete(keyword_tree, &child);
1595 meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree), iter_ptr, -1);
1597 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter_ptr, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1600 gtk_tree_store_remove(keyword_tree, iter_ptr);
1604 void keyword_hide_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1607 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1608 if (!g_list_find(list, id))
1610 list = g_list_prepend(list, id);
1611 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1615 void keyword_show_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1618 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1619 list = g_list_remove(list, id);
1620 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1623 gboolean keyword_is_hidden_in(GtkTreeModel *keyword_tree, GtkTreeIter *iter, gpointer id)
1626 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1627 return !!g_list_find(list, id);
1630 static gboolean keyword_show_all_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
1632 keyword_show_in(GTK_TREE_STORE(model), iter, data);
1636 void keyword_show_all_in(GtkTreeStore *keyword_tree, gpointer id)
1638 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_all_in_cb, id);
1641 static gboolean keyword_revert_hidden_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
1643 if (keyword_is_hidden_in(GTK_TREE_MODEL(keyword_tree), iter, data))
1645 keyword_show_in(GTK_TREE_STORE(model), iter, data);
1650 void keyword_revert_hidden_in(GtkTreeStore *keyword_tree, gpointer id)
1652 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_revert_hidden_in_cb, id);
1655 static void keyword_hide_unset_in_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr, gpointer id, GList *keywords)
1657 GtkTreeIter iter = *iter_ptr;
1660 if (!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
1662 keyword_hide_in(keyword_tree, &iter, id);
1663 /* no need to check children of hidden node */
1668 if (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, &iter))
1670 keyword_hide_unset_in_recursive(keyword_tree, &child, id, keywords);
1673 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1677 void keyword_hide_unset_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1680 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1681 keyword_hide_unset_in_recursive(keyword_tree, &iter, id, keywords);
1684 static gboolean keyword_show_set_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter_ptr, gpointer data)
1686 GtkTreeIter iter = *iter_ptr;
1687 GList *keywords = data;
1688 gpointer id = keywords->data;
1689 keywords = keywords->next; /* hack */
1690 if (keyword_tree_is_set(model, &iter, keywords))
1695 keyword_show_in(GTK_TREE_STORE(model), &iter, id);
1696 if (!gtk_tree_model_iter_parent(GTK_TREE_MODEL(keyword_tree), &parent, &iter)) break;
1703 void keyword_show_set_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1705 /* hack: pass id to keyword_hide_unset_in_cb in the list */
1706 keywords = g_list_prepend(keywords, id);
1707 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_set_in_cb, keywords);
1708 keywords = g_list_delete_link(keywords, keywords);
1712 void keyword_tree_new(void)
1714 if (keyword_tree) return;
1716 keyword_tree = gtk_tree_store_new(KEYWORD_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER);
1719 static GtkTreeIter keyword_tree_default_append(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar *name, gboolean is_keyword)
1722 gtk_tree_store_append(keyword_tree, &iter, parent);
1723 keyword_set(keyword_tree, &iter, name, is_keyword);
1727 void keyword_tree_new_default(void)
1731 if (!keyword_tree) keyword_tree_new();
1733 i1 = keyword_tree_default_append(keyword_tree, NULL, _("People"), TRUE);
1734 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Family"), TRUE);
1735 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Free time"), TRUE);
1736 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Children"), TRUE);
1737 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sport"), TRUE);
1738 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Culture"), TRUE);
1739 keyword_tree_default_append(keyword_tree, &i2, _("Festival"), TRUE);
1740 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Nature"), TRUE);
1741 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Animal"), TRUE);
1742 keyword_tree_default_append(keyword_tree, &i2, _("Bird"), TRUE);
1743 keyword_tree_default_append(keyword_tree, &i2, _("Insect"), TRUE);
1744 keyword_tree_default_append(keyword_tree, &i2, _("Pets"), TRUE);
1745 keyword_tree_default_append(keyword_tree, &i2, _("Wildlife"), TRUE);
1746 keyword_tree_default_append(keyword_tree, &i2, _("Zoo"), TRUE);
1747 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Plant"), TRUE);
1748 keyword_tree_default_append(keyword_tree, &i2, _("Tree"), TRUE);
1749 keyword_tree_default_append(keyword_tree, &i2, _("Flower"), TRUE);
1750 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Water"), TRUE);
1751 keyword_tree_default_append(keyword_tree, &i2, _("River"), TRUE);
1752 keyword_tree_default_append(keyword_tree, &i2, _("Lake"), TRUE);
1753 keyword_tree_default_append(keyword_tree, &i2, _("Sea"), TRUE);
1754 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Landscape"), TRUE);
1755 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Art"), TRUE);
1756 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Statue"), TRUE);
1757 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Painting"), TRUE);
1758 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1759 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1760 i1 = keyword_tree_default_append(keyword_tree, NULL, _("City"), TRUE);
1761 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Park"), TRUE);
1762 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Street"), TRUE);
1763 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Square"), TRUE);
1764 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Architecture"), TRUE);
1765 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Buildings"), FALSE);
1766 keyword_tree_default_append(keyword_tree, &i2, _("House"), TRUE);
1767 keyword_tree_default_append(keyword_tree, &i2, _("Cathedral"), TRUE);
1768 keyword_tree_default_append(keyword_tree, &i2, _("Palace"), TRUE);
1769 keyword_tree_default_append(keyword_tree, &i2, _("Castle"), TRUE);
1770 keyword_tree_default_append(keyword_tree, &i2, _("Bridge"), TRUE);
1771 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Interior"), TRUE);
1772 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1773 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1774 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Places"), FALSE);
1775 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Conditions"), FALSE);
1776 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Night"), TRUE);
1777 keyword_tree_default_append(keyword_tree, &i2, _("Lights"), TRUE);
1778 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Reflections"), TRUE);
1779 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sun"), TRUE);
1780 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Weather"), FALSE);
1781 keyword_tree_default_append(keyword_tree, &i2, _("Fog"), TRUE);
1782 keyword_tree_default_append(keyword_tree, &i2, _("Rain"), TRUE);
1783 keyword_tree_default_append(keyword_tree, &i2, _("Clouds"), TRUE);
1784 keyword_tree_default_append(keyword_tree, &i2, _("Snow"), TRUE);
1785 keyword_tree_default_append(keyword_tree, &i2, _("Sunny weather"), TRUE);
1786 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Photo"), FALSE);
1787 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Edited"), TRUE);
1788 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Detail"), TRUE);
1789 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Macro"), TRUE);
1790 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Portrait"), TRUE);
1791 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Black and White"), TRUE);
1792 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Perspective"), TRUE);
1796 static void keyword_tree_node_write_config(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GString *outstr, gint indent)
1798 GtkTreeIter iter = *iter_ptr;
1801 GtkTreeIter children;
1805 WRITE_NL(); WRITE_STRING("<keyword ");
1806 name = keyword_get_name(keyword_tree, &iter);
1807 write_char_option(outstr, indent, "name", name);
1809 write_bool_option(outstr, indent, "kw", keyword_get_is_keyword(keyword_tree, &iter));
1810 mark_str = keyword_get_mark(keyword_tree, &iter);
1811 if (mark_str && mark_str[0])
1813 write_char_option(outstr, indent, "mark", mark_str);
1816 if (gtk_tree_model_iter_children(keyword_tree, &children, &iter))
1820 keyword_tree_node_write_config(keyword_tree, &children, outstr, indent);
1822 WRITE_NL(); WRITE_STRING("</keyword>");
1828 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return;
1832 void keyword_tree_write_config(GString *outstr, gint indent)
1835 WRITE_NL(); WRITE_STRING("<keyword_tree>");
1838 if (keyword_tree && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1840 keyword_tree_node_write_config(GTK_TREE_MODEL(keyword_tree), &iter, outstr, indent);
1843 WRITE_NL(); WRITE_STRING("</keyword_tree>");
1846 void keyword_tree_node_disconnect_marks(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
1848 GtkTreeIter iter = *iter_ptr;
1852 GtkTreeIter children;
1854 meta_data_connect_mark_with_keyword((keyword_tree), &iter, -1);
1856 if (gtk_tree_model_iter_children((keyword_tree), &children, &iter))
1858 keyword_tree_node_disconnect_marks((keyword_tree), &children);
1861 if (!gtk_tree_model_iter_next((keyword_tree), &iter)) return;
1865 void keyword_tree_disconnect_marks()
1869 if (keyword_tree && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1871 keyword_tree_node_disconnect_marks(GTK_TREE_MODEL(keyword_tree), &iter);
1875 GtkTreeIter *keyword_add_from_config(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar **attribute_names, const gchar **attribute_values)
1878 gboolean is_kw = TRUE;
1879 gchar *mark_str = NULL;
1881 while (*attribute_names)
1883 const gchar *option = *attribute_names++;
1884 const gchar *value = *attribute_values++;
1886 if (READ_CHAR_FULL("name", name)) continue;
1887 if (READ_BOOL_FULL("kw", is_kw)) continue;
1888 if (READ_CHAR_FULL("mark", mark_str)) continue;
1890 log_printf("unknown attribute %s = %s\n", option, value);
1892 if (name && name[0])
1895 /* re-use existing keyword if any */
1896 if (!keyword_exists(GTK_TREE_MODEL(keyword_tree), parent, NULL, name, FALSE, &iter))
1898 gtk_tree_store_append(keyword_tree, &iter, parent);
1900 keyword_set(keyword_tree, &iter, name, is_kw);
1904 gint i = (gint)atoi(mark_str);
1907 meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree),
1912 return gtk_tree_iter_copy(&iter);
1918 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */