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 <glib-object.h>
40 #include "layout-util.h"
41 #include "main-defines.h"
45 #include "secure-save.h"
46 #include "ui-fileops.h"
57 /* If contents change, keep GuideOptionsMetadata.xml up to date */
59 * @brief Tags that will be written to all files in a group - selected by: options->metadata.sync_grouped_files, Preferences/Metadata/Write The Same Description Tags To All Grouped Sidecars
61 static const gchar *group_keys[] = {
63 "Xmp.photoshop.Urgency",
64 "Xmp.photoshop.Category",
65 "Xmp.photoshop.SupplementalCategory",
68 "Xmp.photoshop.Instruction",
69 "Xmp.photoshop.DateCreated",
71 "Xmp.photoshop.AuthorsPosition",
73 "Xmp.photoshop.State",
74 "Xmp.iptc.CountryCode",
75 "Xmp.photoshop.Country",
76 "Xmp.photoshop.TransmissionReference",
77 "Xmp.photoshop.Headline",
78 "Xmp.photoshop.Credit",
79 "Xmp.photoshop.Source",
82 "Xmp.photoshop.CaptionWriter",
85 static gboolean metadata_write_queue_idle_cb(gpointer data);
86 static gboolean metadata_legacy_write(FileData *fd);
87 static void metadata_legacy_delete(FileData *fd, const gchar *except);
88 static gboolean metadata_file_read(gchar *path, GList **keywords, gchar **comment);
92 *-------------------------------------------------------------------
93 * long-term cache - keep keywords from whole dir in memory
94 *-------------------------------------------------------------------
97 /* fd->cached metadata list of lists
98 each particular list contains key as a first entry, then the values
101 static void metadata_cache_update(FileData *fd, const gchar *key, const GList *values)
105 work = fd->cached_metadata;
108 auto entry = static_cast<GList *>(work->data);
109 auto entry_key = static_cast<gchar *>(entry->data);
111 if (strcmp(entry_key, key) == 0)
113 /* key found - just replace values */
114 GList *old_values = entry->next;
115 entry->next = nullptr;
116 old_values->prev = nullptr;
117 g_list_free_full(old_values, g_free);
118 work->data = g_list_append(entry, string_list_copy(values));
119 DEBUG_1("updated %s %s\n", key, fd->path);
125 /* key not found - prepend new entry */
126 fd->cached_metadata = g_list_prepend(fd->cached_metadata,
127 g_list_prepend(string_list_copy(values), g_strdup(key)));
128 DEBUG_1("added %s %s\n", key, fd->path);
132 static const GList *metadata_cache_get(FileData *fd, const gchar *key)
136 work = fd->cached_metadata;
139 auto entry = static_cast<GList *>(work->data);
140 auto entry_key = static_cast<gchar *>(entry->data);
142 if (strcmp(entry_key, key) == 0)
145 DEBUG_1("found %s %s\n", key, fd->path);
151 DEBUG_1("not found %s %s\n", key, fd->path);
154 static void metadata_cache_remove(FileData *fd, const gchar *key)
158 work = fd->cached_metadata;
161 auto entry = static_cast<GList *>(work->data);
162 auto entry_key = static_cast<gchar *>(entry->data);
164 if (strcmp(entry_key, key) == 0)
167 g_list_free_full(entry, g_free);
168 fd->cached_metadata = g_list_delete_link(fd->cached_metadata, work);
169 DEBUG_1("removed %s %s\n", key, fd->path);
174 DEBUG_1("not removed %s %s\n", key, fd->path);
177 void metadata_cache_free(FileData *fd)
179 if (fd->cached_metadata) DEBUG_1("freed %s\n", fd->path);
181 g_list_free_full(fd->cached_metadata, [](gpointer data)
183 auto entry = static_cast<GList *>(data);
184 g_list_free_full(entry, g_free);
186 fd->cached_metadata = nullptr;
195 *-------------------------------------------------------------------
197 *-------------------------------------------------------------------
200 static GList *metadata_write_queue = nullptr;
201 static guint metadata_write_idle_id = 0; /* event source id */
203 static void metadata_write_queue_add(FileData *fd)
205 if (!g_list_find(metadata_write_queue, fd))
207 metadata_write_queue = g_list_prepend(metadata_write_queue, fd);
210 layout_util_status_update_write_all();
213 if (metadata_write_idle_id)
215 g_source_remove(metadata_write_idle_id);
216 metadata_write_idle_id = 0;
219 if (options->metadata.confirm_after_timeout)
221 metadata_write_idle_id = g_timeout_add(options->metadata.confirm_timeout * 1000, metadata_write_queue_idle_cb, nullptr);
226 gboolean metadata_write_queue_remove(FileData *fd)
228 g_hash_table_destroy(fd->modified_xmp);
229 fd->modified_xmp = nullptr;
231 metadata_write_queue = g_list_remove(metadata_write_queue, fd);
233 file_data_increment_version(fd);
234 file_data_send_notification(fd, NOTIFY_REREAD);
238 layout_util_status_update_write_all();
242 #pragma GCC diagnostic push
243 #pragma GCC diagnostic ignored "-Wunused-function"
244 gboolean metadata_write_queue_remove_list_unused(GList *list)
252 auto *fd = static_cast<FileData *>(work->data);
254 ret = ret && metadata_write_queue_remove(fd);
258 #pragma GCC diagnostic pop
260 void metadata_notify_cb(FileData *fd, NotifyType type, gpointer)
262 if (type & (NOTIFY_REREAD | NOTIFY_CHANGE))
264 metadata_cache_free(fd);
266 if (g_list_find(metadata_write_queue, fd))
268 DEBUG_1("Notify metadata: %s %04x", fd->path, type);
269 if (!isname(fd->path))
271 /* ignore deleted files */
272 metadata_write_queue_remove(fd);
278 gboolean metadata_write_queue_confirm(gboolean force_dialog, FileUtilDoneFunc done_func, gpointer done_data)
281 GList *to_approve = nullptr;
283 work = metadata_write_queue;
286 auto fd = static_cast<FileData *>(work->data);
289 if (!isname(fd->path))
291 /* ignore deleted files */
292 metadata_write_queue_remove(fd);
296 if (fd->change) continue; /* another operation in progress, skip this file for now */
298 to_approve = g_list_prepend(to_approve, file_data_ref(fd));
301 file_util_write_metadata(nullptr, to_approve, nullptr, force_dialog, done_func, done_data);
303 return (metadata_write_queue != nullptr);
306 static gboolean metadata_write_queue_idle_cb(gpointer)
308 metadata_write_queue_confirm(FALSE, nullptr, nullptr);
309 metadata_write_idle_id = 0;
313 gboolean metadata_write_perform(FileData *fd)
319 g_assert(fd->change);
321 lf = strlen(GQ_CACHE_EXT_METADATA);
322 if (fd->change->dest &&
323 g_ascii_strncasecmp(fd->change->dest + strlen(fd->change->dest) - lf, GQ_CACHE_EXT_METADATA, lf) == 0)
325 success = metadata_legacy_write(fd);
326 if (success) metadata_legacy_delete(fd, fd->change->dest);
330 /* write via exiv2 */
331 /* we can either use cached metadata which have fd->modified_xmp already applied
332 or read metadata from file and apply fd->modified_xmp
333 metadata are read also if the file was modified meanwhile */
334 exif = exif_read_fd(fd);
335 if (!exif) return FALSE;
337 success = (fd->change->dest) ? exif_write_sidecar(exif, fd->change->dest) : exif_write(exif); /* write modified metadata */
338 exif_free_fd(fd, exif);
340 if (fd->change->dest)
341 /* this will create a FileData for the sidecar and link it to the main file
342 (we can't wait until the sidecar is discovered by directory scanning because
343 exif_read_fd is called before that and it would read the main file only and
344 store the metadata in the cache)
347 @FIXME this does not catch new sidecars created by independent external programs
349 file_data_unref(file_data_new_group(fd->change->dest));
351 if (success) metadata_legacy_delete(fd, fd->change->dest);
355 gint metadata_queue_length()
357 return g_list_length(metadata_write_queue);
360 static gboolean metadata_check_key(const gchar *keys[], const gchar *key)
362 const gchar **k = keys;
366 if (strcmp(key, *k) == 0) return TRUE;
372 gboolean metadata_write_revert(FileData *fd, const gchar *key)
374 if (!fd->modified_xmp) return FALSE;
376 g_hash_table_remove(fd->modified_xmp, key);
378 if (g_hash_table_size(fd->modified_xmp) == 0)
380 metadata_write_queue_remove(fd);
384 /* reread the metadata to restore the original value */
385 file_data_increment_version(fd);
386 file_data_send_notification(fd, NOTIFY_REREAD);
391 gboolean metadata_write_list(FileData *fd, const gchar *key, const GList *values)
393 if (!fd->modified_xmp)
395 fd->modified_xmp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, reinterpret_cast<GDestroyNotify>(string_list_free));
397 g_hash_table_insert(fd->modified_xmp, g_strdup(key), string_list_copy(const_cast<GList *>(values)));
399 metadata_cache_remove(fd, key);
403 exif_update_metadata(fd->exif, key, values);
405 metadata_write_queue_add(fd);
406 file_data_increment_version(fd);
407 file_data_send_notification(fd, NOTIFY_METADATA);
409 if (options->metadata.sync_grouped_files && metadata_check_key(group_keys, key))
411 GList *work = fd->sidecar_files;
415 auto sfd = static_cast<FileData *>(work->data);
418 if (sfd->format_class == FORMAT_CLASS_META) continue;
420 metadata_write_list(sfd, key, values);
428 gboolean metadata_write_string(FileData *fd, const gchar *key, const char *value)
430 GList *list = g_list_append(nullptr, g_strdup(value));
431 gboolean ret = metadata_write_list(fd, key, list);
432 g_list_free_full(list, g_free);
436 gboolean metadata_write_int(FileData *fd, const gchar *key, guint64 value)
440 g_snprintf(string, sizeof(string), "%llu", static_cast<unsigned long long>(value));
441 return metadata_write_string(fd, key, string);
445 *-------------------------------------------------------------------
446 * keyword / comment read/write
447 *-------------------------------------------------------------------
450 static gboolean metadata_file_write(gchar *path, const GList *keywords, const gchar *comment)
454 ssi = secure_open(path);
455 if (!ssi) return FALSE;
457 secure_fprintf(ssi, "#%s comment (%s)\n\n", GQ_APPNAME, VERSION);
459 secure_fprintf(ssi, "[keywords]\n");
460 while (keywords && secsave_errno == SS_ERR_NONE)
462 auto word = static_cast<const gchar *>(keywords->data);
463 keywords = keywords->next;
465 secure_fprintf(ssi, "%s\n", word);
467 secure_fputc(ssi, '\n');
469 secure_fprintf(ssi, "[comment]\n");
470 secure_fprintf(ssi, "%s\n", (comment) ? comment : "");
472 secure_fprintf(ssi, "#end\n");
474 return (secure_close(ssi) == 0);
477 static gboolean metadata_legacy_write(FileData *fd)
479 gboolean success = FALSE;
480 gchar *metadata_pathl;
483 gboolean have_keywords;
484 gboolean have_comment;
485 const gchar *comment;
486 GList *orig_keywords = nullptr;
487 gchar *orig_comment = nullptr;
489 g_assert(fd->change && fd->change->dest);
491 DEBUG_1("Saving comment: %s", fd->change->dest);
493 if (!fd->modified_xmp) return TRUE;
495 metadata_pathl = path_from_utf8(fd->change->dest);
497 have_keywords = g_hash_table_lookup_extended(fd->modified_xmp, KEYWORD_KEY, nullptr, &keywords);
498 have_comment = g_hash_table_lookup_extended(fd->modified_xmp, COMMENT_KEY, nullptr, &comment_l);
499 comment = static_cast<const gchar *>((have_comment && comment_l) ? (static_cast<GList *>(comment_l))->data : nullptr);
501 if (!have_keywords || !have_comment) metadata_file_read(metadata_pathl, &orig_keywords, &orig_comment);
503 success = metadata_file_write(metadata_pathl,
504 have_keywords ? static_cast<GList *>(keywords) : orig_keywords,
505 have_comment ? comment : orig_comment);
507 g_free(metadata_pathl);
508 g_free(orig_comment);
509 g_list_free_full(orig_keywords, g_free);
514 static gboolean metadata_file_read(gchar *path, GList **keywords, gchar **comment)
518 MetadataKey key = MK_NONE;
519 GList *list = nullptr;
520 GString *comment_build = nullptr;
522 f = fopen(path, "r");
523 if (!f) return FALSE;
525 while (fgets(s_buf, sizeof(s_buf), f))
529 if (*ptr == '#') continue;
530 if (*ptr == '[' && key != MK_COMMENT)
532 gchar *keystr = ++ptr;
535 while (*ptr != ']' && *ptr != '\n' && *ptr != '\0') ptr++;
540 if (g_ascii_strcasecmp(keystr, "keywords") == 0)
542 else if (g_ascii_strcasecmp(keystr, "comment") == 0)
554 while (*ptr != '\n' && *ptr != '\0') ptr++;
556 if (strlen(s_buf) > 0)
558 gchar *kw = utf8_validate_or_convert(s_buf);
560 list = g_list_prepend(list, kw);
565 if (!comment_build) comment_build = g_string_new("");
566 g_string_append(comment_build, s_buf);
575 *keywords = g_list_reverse(list);
579 g_list_free_full(list, g_free);
587 gchar *ptr = comment_build->str;
589 /* strip leading and trailing newlines */
590 while (*ptr == '\n') ptr++;
592 while (len > 0 && ptr[len - 1] == '\n') len--;
593 if (ptr[len] == '\n') len++; /* keep the last one */
596 gchar *text = g_strndup(ptr, len);
598 *comment = utf8_validate_or_convert(text);
602 g_string_free(comment_build, TRUE);
608 static void metadata_legacy_delete(FileData *fd, const gchar *except)
610 gchar *metadata_path;
611 gchar *metadata_pathl;
614 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
615 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
617 metadata_pathl = path_from_utf8(metadata_path);
618 unlink(metadata_pathl);
619 g_free(metadata_pathl);
620 g_free(metadata_path);
624 /* without exiv2: do not delete xmp metadata because we are not able to convert it,
626 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
627 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
629 metadata_pathl = path_from_utf8(metadata_path);
630 unlink(metadata_pathl);
631 g_free(metadata_pathl);
632 g_free(metadata_path);
637 static gboolean metadata_legacy_read(FileData *fd, GList **keywords, gchar **comment)
639 gchar *metadata_path;
640 gchar *metadata_pathl;
641 gboolean success = FALSE;
643 if (!fd) return FALSE;
645 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
646 if (!metadata_path) return FALSE;
648 metadata_pathl = path_from_utf8(metadata_path);
650 success = metadata_file_read(metadata_pathl, keywords, comment);
652 g_free(metadata_pathl);
653 g_free(metadata_path);
658 static GList *remove_duplicate_strings_from_list(GList *list)
661 GHashTable *hashtable = g_hash_table_new(g_str_hash, g_str_equal);
662 GList *newlist = nullptr;
666 auto key = static_cast<gchar *>(work->data);
668 if (g_hash_table_lookup(hashtable, key) == nullptr)
670 g_hash_table_insert(hashtable, key, GINT_TO_POINTER(1));
671 newlist = g_list_prepend(newlist, key);
676 g_hash_table_destroy(hashtable);
679 return g_list_reverse(newlist);
682 GList *metadata_read_list(FileData *fd, const gchar *key, MetadataFormat format)
685 GList *list = nullptr;
686 const GList *cache_entry;
687 if (!fd) return nullptr;
689 /* unwritten data override everything */
690 if (fd->modified_xmp && format == METADATA_PLAIN)
692 list = static_cast<GList *>(g_hash_table_lookup(fd->modified_xmp, key));
693 if (list) return string_list_copy(list);
697 if (format == METADATA_PLAIN && strcmp(key, KEYWORD_KEY) == 0
698 && (cache_entry = metadata_cache_get(fd, key)))
700 return string_list_copy(cache_entry->next);
704 Legacy metadata file is the primary source if it exists.
705 Merging the lists does not make much sense, because the existence of
706 legacy metadata file indicates that the other metadata sources are not
707 writable and thus it would not be possible to delete the keywords
708 that comes from the image file.
710 if (strcmp(key, KEYWORD_KEY) == 0)
712 if (metadata_legacy_read(fd, &list, nullptr))
714 if (format == METADATA_PLAIN)
716 metadata_cache_update(fd, key, list);
721 else if (strcmp(key, COMMENT_KEY) == 0)
723 gchar *comment = nullptr;
724 if (metadata_legacy_read(fd, nullptr, &comment)) return g_list_append(nullptr, comment);
726 else if (strncmp(key, "file.", 5) == 0)
728 return g_list_append(nullptr, metadata_file_info(fd, key, format));
731 else if (strncmp(key, "lua.", 4) == 0)
733 return g_list_append(nullptr, metadata_lua_info(fd, key, format));
737 exif = exif_read_fd(fd); /* this is cached, thus inexpensive */
738 if (!exif) return nullptr;
739 list = exif_get_metadata(exif, key, format);
740 exif_free_fd(fd, exif);
742 if (format == METADATA_PLAIN && strcmp(key, KEYWORD_KEY) == 0)
744 metadata_cache_update(fd, key, list);
750 gchar *metadata_read_string(FileData *fd, const gchar *key, MetadataFormat format)
752 GList *string_list = metadata_read_list(fd, key, format);
755 auto str = static_cast<gchar *>(string_list->data);
756 string_list->data = nullptr;
757 g_list_free_full(string_list, g_free);
763 guint64 metadata_read_int(FileData *fd, const gchar *key, guint64 fallback)
767 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
768 if (!string) return fallback;
770 ret = g_ascii_strtoull(string, &endptr, 10);
771 if (string == endptr) ret = fallback;
776 gchar *metadata_read_rating_stars(FileData *fd)
779 gint n = metadata_read_int(fd, RATING_KEY, METADATA_PLAIN);
781 ret = convert_rating_to_stars(n);
786 gdouble metadata_read_GPS_coord(FileData *fd, const gchar *key, gdouble fallback)
794 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
795 if (!string) return fallback;
797 deg = g_ascii_strtod(string, &endptr);
800 min = g_ascii_strtod(endptr + 1, &endptr);
802 sec = g_ascii_strtod(endptr + 1, &endptr);
807 if (*endptr == 'S' || *endptr == 'W' || *endptr == 'N' || *endptr == 'E')
809 coord = deg + min /60.0 + sec / 3600.0;
811 if (*endptr == 'S' || *endptr == 'W') coord = -coord;
818 log_printf("unable to parse GPS coordinate '%s'\n", string);
825 gdouble metadata_read_GPS_direction(FileData *fd, const gchar *key, gdouble fallback)
830 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
831 if (!string) return fallback;
833 DEBUG_3("GPS_direction: %s\n", string);
834 deg = g_ascii_strtod(string, &endptr);
836 /* Expected text string is of the format e.g.:
848 log_printf("unable to parse GPS direction '%s: %f'\n", string, deg);
856 gboolean metadata_append_string(FileData *fd, const gchar *key, const char *value)
858 gchar *str = metadata_read_string(fd, key, METADATA_PLAIN);
862 return metadata_write_string(fd, key, value);
865 gchar *new_string = g_strconcat(str, value, NULL);
866 gboolean ret = metadata_write_string(fd, key, new_string);
872 gboolean metadata_write_GPS_coord(FileData *fd, const gchar *key, gdouble value)
887 min = (param * 60) - (deg * 60);
888 if (g_strcmp0(key, "Xmp.exif.GPSLongitude") == 0)
893 else if (g_strcmp0(key, "Xmp.exif.GPSLatitude") == 0)
900 log_printf("unknown GPS parameter key '%s'\n", key);
906 /* Avoid locale problems with commas and decimal points in numbers */
907 old_locale = setlocale(LC_ALL, nullptr);
908 saved_locale = strdup(old_locale);
909 if (saved_locale == nullptr)
913 setlocale(LC_ALL, "C");
915 coordinate = g_strdup_printf("%i,%lf,%s", deg, min, ref);
916 metadata_write_string(fd, key, coordinate );
918 setlocale(LC_ALL, saved_locale);
926 gboolean metadata_append_list(FileData *fd, const gchar *key, const GList *values)
928 GList *list = metadata_read_list(fd, key, METADATA_PLAIN);
932 return metadata_write_list(fd, key, values);
936 list = g_list_concat(list, string_list_copy(values));
937 list = remove_duplicate_strings_from_list(list);
939 ret = metadata_write_list(fd, key, list);
940 g_list_free_full(list, g_free);
945 * @see find_string_in_list
947 gchar *find_string_in_list_utf8nocase(GList *list, const gchar *string)
949 gchar *string_casefold = g_utf8_casefold(string, -1);
953 auto haystack = static_cast<gchar *>(list->data);
958 gchar *haystack_casefold = g_utf8_casefold(haystack, -1);
960 equal = (strcmp(haystack_casefold, string_casefold) == 0);
961 g_free(haystack_casefold);
965 g_free(string_casefold);
973 g_free(string_casefold);
978 * @see find_string_in_list
980 gchar *find_string_in_list_utf8case(GList *list, const gchar *string)
984 auto haystack = static_cast<gchar *>(list->data);
986 if (haystack && strcmp(haystack, string) == 0)
993 } // gchar *find_string_in_list_utf...
996 * @brief Find a existent string in a list.
998 * This is a switch between find_string_in_list_utf8case and
999 * find_string_in_list_utf8nocase to search with or without case for the
1000 * existence of a string.
1002 * @param list The list to search in
1003 * @param string The string to search for
1004 * @return The string or NULL
1006 * @see find_string_in_list_utf8case
1007 * @see find_string_in_list_utf8nocase
1009 gchar *find_string_in_list(GList *list, const gchar *string)
1011 if (options->metadata.keywords_case_sensitive)
1012 return find_string_in_list_utf8case(list, string);
1014 return find_string_in_list_utf8nocase(list, string);
1017 #define KEYWORDS_SEPARATOR(c) ((c) == ',' || (c) == ';' || (c) == '\n' || (c) == '\r' || (c) == '\b')
1019 GList *string_to_keywords_list(const gchar *text)
1021 GList *list = nullptr;
1022 const gchar *ptr = text;
1024 while (*ptr != '\0')
1029 while (KEYWORDS_SEPARATOR(*ptr)) ptr++;
1031 while (*ptr != '\0' && !KEYWORDS_SEPARATOR(*ptr))
1037 /* trim starting and ending whitespaces */
1038 while (l > 0 && g_ascii_isspace(*begin)) begin++, l--;
1039 while (l > 0 && g_ascii_isspace(begin[l-1])) l--;
1043 gchar *keyword = g_strndup(begin, l);
1045 /* only add if not already in the list */
1046 if (!find_string_in_list(list, keyword))
1047 list = g_list_append(list, keyword);
1061 gboolean meta_data_get_keyword_mark(FileData *fd, gint, gpointer data)
1063 /** @FIXME do not use global keyword_tree */
1064 auto path = static_cast<GList *>(data);
1066 gboolean found = FALSE;
1067 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
1071 if (keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path) &&
1072 keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
1079 gboolean meta_data_set_keyword_mark(FileData *fd, gint, gboolean value, gpointer data)
1081 auto path = static_cast<GList *>(data);
1082 GList *keywords = nullptr;
1085 if (!keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path)) return FALSE;
1087 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
1089 if (!!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords) != !!value)
1093 keyword_tree_set(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
1097 keyword_tree_reset(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
1099 metadata_write_list(fd, KEYWORD_KEY, keywords);
1102 g_list_free_full(keywords, g_free);
1108 void meta_data_connect_mark_with_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *kw_iter, gint mark)
1111 FileDataGetMarkFunc get_mark_func;
1112 FileDataSetMarkFunc set_mark_func;
1113 gpointer mark_func_data;
1117 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
1119 file_data_get_registered_mark_func(i, &get_mark_func, &set_mark_func, &mark_func_data);
1120 if (get_mark_func == meta_data_get_keyword_mark)
1122 GtkTreeIter old_kw_iter;
1123 auto old_path = static_cast<GList *>(mark_func_data);
1125 if (keyword_tree_get_iter(keyword_tree, &old_kw_iter, old_path) &&
1126 (i == mark || /* release any previous connection of given mark */
1127 keyword_compare(keyword_tree, &old_kw_iter, kw_iter) == 0)) /* or given keyword */
1129 file_data_register_mark_func(i, nullptr, nullptr, nullptr, nullptr);
1130 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), &old_kw_iter, KEYWORD_COLUMN_MARK, "", -1);
1136 if (mark >= 0 && mark < FILEDATA_MARKS_SIZE)
1140 path = keyword_tree_get_path(keyword_tree, kw_iter);
1141 file_data_register_mark_func(mark, meta_data_get_keyword_mark, meta_data_set_keyword_mark, path, reinterpret_cast<GDestroyNotify>(string_list_free));
1143 mark_str = g_strdup_printf("%d", (mark < 9 ? mark : -1) + 1);
1144 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), kw_iter, KEYWORD_COLUMN_MARK, mark_str, -1);
1151 *-------------------------------------------------------------------
1153 *-------------------------------------------------------------------
1158 GtkTreeStore *keyword_tree;
1160 gchar *keyword_get_name(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1163 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_NAME, &name, -1);
1167 gchar *keyword_get_mark(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1171 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_MARK, &mark_str, -1);
1175 gchar *keyword_get_casefold(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1178 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_CASEFOLD, &casefold, -1);
1182 gboolean keyword_get_is_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1184 gboolean is_keyword;
1185 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
1189 void keyword_set(GtkTreeStore *keyword_tree, GtkTreeIter *iter, const gchar *name, gboolean is_keyword)
1191 gchar *casefold = g_utf8_casefold(name, -1);
1192 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_MARK, "",
1193 KEYWORD_COLUMN_NAME, name,
1194 KEYWORD_COLUMN_CASEFOLD, casefold,
1195 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
1199 gboolean keyword_compare(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
1201 GtkTreePath *pa = gtk_tree_model_get_path(keyword_tree, a);
1202 GtkTreePath *pb = gtk_tree_model_get_path(keyword_tree, b);
1203 gint ret = gtk_tree_path_compare(pa, pb);
1204 gtk_tree_path_free(pa);
1205 gtk_tree_path_free(pb);
1209 gboolean keyword_same_parent(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
1211 GtkTreeIter parent_a;
1212 GtkTreeIter parent_b;
1214 gboolean valid_pa = gtk_tree_model_iter_parent(keyword_tree, &parent_a, a);
1215 gboolean valid_pb = gtk_tree_model_iter_parent(keyword_tree, &parent_b, b);
1217 if (valid_pa && valid_pb)
1219 return keyword_compare(keyword_tree, &parent_a, &parent_b) == 0;
1222 return (!valid_pa && !valid_pb); /* both are toplevel */
1225 gboolean keyword_exists(GtkTreeModel *keyword_tree, GtkTreeIter *parent_ptr, GtkTreeIter *sibling, const gchar *name, gboolean exclude_sibling, GtkTreeIter *result)
1229 gboolean toplevel = FALSE;
1235 parent = *parent_ptr;
1239 toplevel = !gtk_tree_model_iter_parent(keyword_tree, &parent, sibling);
1246 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &iter, toplevel ? nullptr : &parent)) return FALSE;
1248 casefold = g_utf8_casefold(name, -1);
1253 if (!exclude_sibling || !sibling || keyword_compare(keyword_tree, &iter, sibling) != 0)
1255 if (options->metadata.keywords_case_sensitive)
1257 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
1258 ret = strcmp(name, iter_name) == 0;
1263 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
1264 ret = strcmp(casefold, iter_casefold) == 0;
1265 g_free(iter_casefold);
1266 } // if (options->metadata.tags_cas...
1270 if (result) *result = iter;
1273 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) break;
1280 void keyword_copy(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
1286 gboolean is_keyword;
1288 /* do not copy KEYWORD_COLUMN_HIDE_IN, it fully shows the new subtree */
1289 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), from, KEYWORD_COLUMN_MARK, &mark,
1290 KEYWORD_COLUMN_NAME, &name,
1291 KEYWORD_COLUMN_CASEFOLD, &casefold,
1292 KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
1294 gtk_tree_store_set(keyword_tree, to, KEYWORD_COLUMN_MARK, mark,
1295 KEYWORD_COLUMN_NAME, name,
1296 KEYWORD_COLUMN_CASEFOLD, casefold,
1297 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
1303 void keyword_copy_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
1305 GtkTreeIter from_child;
1307 keyword_copy(keyword_tree, to, from);
1309 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &from_child, from)) return;
1313 GtkTreeIter to_child;
1314 gtk_tree_store_append(keyword_tree, &to_child, to);
1315 keyword_copy_recursive(keyword_tree, &to_child, &from_child);
1316 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &from_child)) return;
1320 void keyword_move_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
1322 keyword_copy_recursive(keyword_tree, to, from);
1323 keyword_delete(keyword_tree, from);
1326 GList *keyword_tree_get_path(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
1328 GList *path = nullptr;
1329 GtkTreeIter iter = *iter_ptr;
1334 path = g_list_prepend(path, keyword_get_name(keyword_tree, &iter));
1335 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) break;
1341 gboolean keyword_tree_get_iter(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList *path)
1345 if (!gtk_tree_model_get_iter_first(keyword_tree, &iter)) return FALSE;
1349 GtkTreeIter children;
1352 gchar *name = keyword_get_name(keyword_tree, &iter);
1353 if (strcmp(name, static_cast<const gchar *>(path->data)) == 0) break;
1355 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return FALSE;
1364 if (!gtk_tree_model_iter_children(keyword_tree, &children, &iter)) return FALSE;
1370 static gboolean keyword_tree_is_set_casefold(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *casefold_list)
1372 if (!casefold_list) return FALSE;
1374 if (!keyword_get_is_keyword(keyword_tree, &iter))
1376 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1378 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1379 return FALSE; /* this should happen only on empty helpers */
1383 if (keyword_tree_is_set_casefold(keyword_tree, child, casefold_list)) return TRUE;
1384 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1392 if (keyword_get_is_keyword(keyword_tree, &iter))
1394 GList *work = casefold_list;
1395 gboolean found = FALSE;
1396 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
1399 auto casefold = static_cast<const gchar *>(work->data);
1402 if (strcmp(iter_casefold, casefold) == 0)
1408 g_free(iter_casefold);
1409 if (!found) return FALSE;
1412 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1417 static gboolean keyword_tree_is_set_casefull(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *kw_list)
1419 if (!kw_list) return FALSE;
1421 if (!keyword_get_is_keyword(keyword_tree, &iter))
1423 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1425 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1426 return FALSE; /* this should happen only on empty helpers */
1430 if (keyword_tree_is_set_casefull(keyword_tree, child, kw_list)) return TRUE;
1431 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1439 if (keyword_get_is_keyword(keyword_tree, &iter))
1441 GList *work = kw_list;
1442 gboolean found = FALSE;
1443 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
1446 auto name = static_cast<const gchar *>(work->data);
1449 if (strcmp(iter_name, name) == 0)
1456 if (!found) return FALSE;
1459 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1464 gboolean keyword_tree_is_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList *kw_list)
1467 GList *casefold_list = nullptr;
1470 if (options->metadata.keywords_case_sensitive)
1472 ret = keyword_tree_is_set_casefull(keyword_tree, *iter, kw_list);
1479 auto kw = static_cast<const gchar *>(work->data);
1482 casefold_list = g_list_prepend(casefold_list, g_utf8_casefold(kw, -1));
1485 ret = keyword_tree_is_set_casefold(keyword_tree, *iter, casefold_list);
1487 g_list_free_full(casefold_list, g_free);
1493 void keyword_tree_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1495 GtkTreeIter iter = *iter_ptr;
1500 if (keyword_get_is_keyword(keyword_tree, &iter))
1502 gchar *name = keyword_get_name(keyword_tree, &iter);
1503 if (!find_string_in_list(*kw_list, name))
1505 *kw_list = g_list_append(*kw_list, name);
1513 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1518 GList *keyword_tree_get(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
1520 GtkTreeIter iter = *iter_ptr;
1521 GList *kw_list = nullptr;
1527 if (keyword_get_is_keyword(keyword_tree, &iter))
1529 gchar *name = keyword_get_name(keyword_tree, &iter);
1530 kw_list = g_list_append(kw_list, name);
1533 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return kw_list;
1536 } // GList *keyword_tree_get(GtkTre...
1538 static void keyword_tree_reset1(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1542 if (!keyword_get_is_keyword(keyword_tree, iter)) return;
1544 name = keyword_get_name(keyword_tree, iter);
1545 found = find_string_in_list(*kw_list, name);
1549 *kw_list = g_list_remove(*kw_list, found);
1555 static void keyword_tree_reset_recursive(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1558 keyword_tree_reset1(keyword_tree, iter, kw_list);
1560 if (!gtk_tree_model_iter_children(keyword_tree, &child, iter)) return;
1564 keyword_tree_reset_recursive(keyword_tree, &child, kw_list);
1565 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return;
1569 static gboolean keyword_tree_check_empty_children(GtkTreeModel *keyword_tree, GtkTreeIter *parent, GList *kw_list)
1573 if (!gtk_tree_model_iter_children(keyword_tree, &iter, parent))
1574 return TRUE; /* this should happen only on empty helpers */
1578 if (keyword_tree_is_set(keyword_tree, &iter, kw_list)) return FALSE;
1579 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return TRUE;
1583 void keyword_tree_reset(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1585 GtkTreeIter iter = *iter_ptr;
1587 keyword_tree_reset_recursive(keyword_tree, &iter, kw_list);
1589 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1592 while (keyword_tree_check_empty_children(keyword_tree, &iter, *kw_list))
1595 keyword_tree_reset1(keyword_tree, &iter, kw_list);
1596 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1601 void keyword_delete(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr)
1605 while (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, iter_ptr))
1607 keyword_delete(keyword_tree, &child);
1610 meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree), iter_ptr, -1);
1612 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter_ptr, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1615 gtk_tree_store_remove(keyword_tree, iter_ptr);
1619 void keyword_hide_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1622 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1623 if (!g_list_find(list, id))
1625 list = g_list_prepend(list, id);
1626 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1630 void keyword_show_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1633 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1634 list = g_list_remove(list, id);
1635 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1638 gboolean keyword_is_hidden_in(GtkTreeModel *keyword_tree, GtkTreeIter *iter, gpointer id)
1641 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1642 return !!g_list_find(list, id);
1645 static gboolean keyword_show_all_in_cb(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter, gpointer data)
1647 keyword_show_in(GTK_TREE_STORE(model), iter, data);
1651 void keyword_show_all_in(GtkTreeStore *keyword_tree, gpointer id)
1653 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_all_in_cb, id);
1656 static gboolean keyword_revert_hidden_in_cb(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter, gpointer data)
1658 if (keyword_is_hidden_in(GTK_TREE_MODEL(keyword_tree), iter, data))
1660 keyword_show_in(GTK_TREE_STORE(model), iter, data);
1665 void keyword_revert_hidden_in(GtkTreeStore *keyword_tree, gpointer id)
1667 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_revert_hidden_in_cb, id);
1670 static void keyword_hide_unset_in_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr, gpointer id, GList *keywords)
1672 GtkTreeIter iter = *iter_ptr;
1675 if (!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
1677 keyword_hide_in(keyword_tree, &iter, id);
1678 /* no need to check children of hidden node */
1683 if (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, &iter))
1685 keyword_hide_unset_in_recursive(keyword_tree, &child, id, keywords);
1688 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1692 void keyword_hide_unset_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1695 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1696 keyword_hide_unset_in_recursive(keyword_tree, &iter, id, keywords);
1699 static gboolean keyword_show_set_in_cb(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter_ptr, gpointer data)
1701 GtkTreeIter iter = *iter_ptr;
1702 auto keywords = static_cast<GList *>(data);
1703 gpointer id = keywords->data;
1704 keywords = keywords->next; /* hack */
1705 if (keyword_tree_is_set(model, &iter, keywords))
1710 keyword_show_in(GTK_TREE_STORE(model), &iter, id);
1711 if (!gtk_tree_model_iter_parent(GTK_TREE_MODEL(keyword_tree), &parent, &iter)) break;
1718 void keyword_show_set_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1720 /* hack: pass id to keyword_hide_unset_in_cb in the list */
1721 keywords = g_list_prepend(keywords, id);
1722 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_set_in_cb, keywords);
1723 keywords = g_list_delete_link(keywords, keywords);
1727 void keyword_tree_new()
1729 if (keyword_tree) return;
1731 keyword_tree = gtk_tree_store_new(KEYWORD_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER);
1734 static GtkTreeIter keyword_tree_default_append(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar *name, gboolean is_keyword)
1737 gtk_tree_store_append(keyword_tree, &iter, parent);
1738 keyword_set(keyword_tree, &iter, name, is_keyword);
1742 void keyword_tree_new_default()
1747 if (!keyword_tree) keyword_tree_new();
1749 i1 = keyword_tree_default_append(keyword_tree, nullptr, _("People"), TRUE);
1750 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Family"), TRUE);
1751 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Free time"), TRUE);
1752 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Children"), TRUE);
1753 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sport"), TRUE);
1754 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Culture"), TRUE);
1755 keyword_tree_default_append(keyword_tree, &i2, _("Festival"), TRUE);
1756 i1 = keyword_tree_default_append(keyword_tree, nullptr, _("Nature"), TRUE);
1757 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Animal"), TRUE);
1758 keyword_tree_default_append(keyword_tree, &i2, _("Bird"), TRUE);
1759 keyword_tree_default_append(keyword_tree, &i2, _("Insect"), TRUE);
1760 keyword_tree_default_append(keyword_tree, &i2, _("Pets"), TRUE);
1761 keyword_tree_default_append(keyword_tree, &i2, _("Wildlife"), TRUE);
1762 keyword_tree_default_append(keyword_tree, &i2, _("Zoo"), TRUE);
1763 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Plant"), TRUE);
1764 keyword_tree_default_append(keyword_tree, &i2, _("Tree"), TRUE);
1765 keyword_tree_default_append(keyword_tree, &i2, _("Flower"), TRUE);
1766 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Water"), TRUE);
1767 keyword_tree_default_append(keyword_tree, &i2, _("River"), TRUE);
1768 keyword_tree_default_append(keyword_tree, &i2, _("Lake"), TRUE);
1769 keyword_tree_default_append(keyword_tree, &i2, _("Sea"), TRUE);
1770 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Landscape"), TRUE);
1771 i1 = keyword_tree_default_append(keyword_tree, nullptr, _("Art"), TRUE);
1772 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Statue"), TRUE);
1773 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Painting"), TRUE);
1774 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1775 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1776 i1 = keyword_tree_default_append(keyword_tree, nullptr, _("City"), TRUE);
1777 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Park"), TRUE);
1778 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Street"), TRUE);
1779 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Square"), TRUE);
1780 i1 = keyword_tree_default_append(keyword_tree, nullptr, _("Architecture"), TRUE);
1781 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Buildings"), FALSE);
1782 keyword_tree_default_append(keyword_tree, &i2, _("House"), TRUE);
1783 keyword_tree_default_append(keyword_tree, &i2, _("Cathedral"), TRUE);
1784 keyword_tree_default_append(keyword_tree, &i2, _("Palace"), TRUE);
1785 keyword_tree_default_append(keyword_tree, &i2, _("Castle"), TRUE);
1786 keyword_tree_default_append(keyword_tree, &i2, _("Bridge"), TRUE);
1787 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Interior"), TRUE);
1788 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1789 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1790 i1 = keyword_tree_default_append(keyword_tree, nullptr, _("Places"), FALSE);
1791 i1 = keyword_tree_default_append(keyword_tree, nullptr, _("Conditions"), FALSE);
1792 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Night"), TRUE);
1793 keyword_tree_default_append(keyword_tree, &i2, _("Lights"), TRUE);
1794 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Reflections"), TRUE);
1795 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sun"), TRUE);
1796 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Weather"), FALSE);
1797 keyword_tree_default_append(keyword_tree, &i2, _("Fog"), TRUE);
1798 keyword_tree_default_append(keyword_tree, &i2, _("Rain"), TRUE);
1799 keyword_tree_default_append(keyword_tree, &i2, _("Clouds"), TRUE);
1800 keyword_tree_default_append(keyword_tree, &i2, _("Snow"), TRUE);
1801 keyword_tree_default_append(keyword_tree, &i2, _("Sunny weather"), TRUE);
1802 i1 = keyword_tree_default_append(keyword_tree, nullptr, _("Photo"), FALSE);
1803 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Edited"), TRUE);
1804 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Detail"), TRUE);
1805 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Macro"), TRUE);
1806 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Portrait"), TRUE);
1807 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Black and White"), TRUE);
1808 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Perspective"), TRUE);
1812 static void keyword_tree_node_write_config(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GString *outstr, gint indent)
1814 GtkTreeIter iter = *iter_ptr;
1817 GtkTreeIter children;
1821 WRITE_NL(); WRITE_STRING("<keyword ");
1822 name = keyword_get_name(keyword_tree, &iter);
1823 write_char_option(outstr, indent, "name", name);
1825 write_bool_option(outstr, indent, "kw", keyword_get_is_keyword(keyword_tree, &iter));
1826 mark_str = keyword_get_mark(keyword_tree, &iter);
1827 if (mark_str && mark_str[0])
1829 write_char_option(outstr, indent, "mark", mark_str);
1832 if (gtk_tree_model_iter_children(keyword_tree, &children, &iter))
1836 keyword_tree_node_write_config(keyword_tree, &children, outstr, indent);
1838 WRITE_NL(); WRITE_STRING("</keyword>");
1844 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return;
1848 void keyword_tree_write_config(GString *outstr, gint indent)
1851 WRITE_NL(); WRITE_STRING("<keyword_tree>");
1854 if (keyword_tree && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1856 keyword_tree_node_write_config(GTK_TREE_MODEL(keyword_tree), &iter, outstr, indent);
1859 WRITE_NL(); WRITE_STRING("</keyword_tree>");
1862 void keyword_tree_node_disconnect_marks(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
1864 GtkTreeIter iter = *iter_ptr;
1868 GtkTreeIter children;
1870 meta_data_connect_mark_with_keyword((keyword_tree), &iter, -1);
1872 if (gtk_tree_model_iter_children((keyword_tree), &children, &iter))
1874 keyword_tree_node_disconnect_marks((keyword_tree), &children);
1877 if (!gtk_tree_model_iter_next((keyword_tree), &iter)) return;
1881 void keyword_tree_disconnect_marks()
1885 if (keyword_tree && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1887 keyword_tree_node_disconnect_marks(GTK_TREE_MODEL(keyword_tree), &iter);
1891 GtkTreeIter *keyword_add_from_config(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar **attribute_names, const gchar **attribute_values)
1893 gchar *name = nullptr;
1894 gboolean is_kw = TRUE;
1895 gchar *mark_str = nullptr;
1897 while (*attribute_names)
1899 const gchar *option = *attribute_names++;
1900 const gchar *value = *attribute_values++;
1902 if (READ_CHAR_FULL("name", name)) continue;
1903 if (READ_BOOL_FULL("kw", is_kw)) continue;
1904 if (READ_CHAR_FULL("mark", mark_str)) continue;
1906 log_printf("unknown attribute %s = %s\n", option, value);
1908 if (name && name[0])
1911 /* re-use existing keyword if any */
1912 if (!keyword_exists(GTK_TREE_MODEL(keyword_tree), parent, nullptr, name, FALSE, &iter))
1914 gtk_tree_store_append(keyword_tree, &iter, parent);
1916 keyword_set(keyword_tree, &iter, name, is_kw);
1920 gint i = static_cast<gint>(atoi(mark_str));
1923 meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree),
1928 return gtk_tree_iter_copy(&iter);
1934 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */