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"
34 #include "layout-util.h"
43 /* If contents change, keep GuideOptionsMetadata.xml up to date */
45 * @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
47 static const gchar *group_keys[] = {
49 "Xmp.photoshop.Urgency",
50 "Xmp.photoshop.Category",
51 "Xmp.photoshop.SupplementalCategory",
54 "Xmp.photoshop.Instruction",
55 "Xmp.photoshop.DateCreated",
57 "Xmp.photoshop.AuthorsPosition",
59 "Xmp.photoshop.State",
60 "Xmp.iptc.CountryCode",
61 "Xmp.photoshop.Country",
62 "Xmp.photoshop.TransmissionReference",
63 "Xmp.photoshop.Headline",
64 "Xmp.photoshop.Credit",
65 "Xmp.photoshop.Source",
68 "Xmp.photoshop.CaptionWriter",
71 static gboolean metadata_write_queue_idle_cb(gpointer data);
72 static gboolean metadata_legacy_write(FileData *fd);
73 static void metadata_legacy_delete(FileData *fd, const gchar *except);
74 static gboolean metadata_file_read(gchar *path, GList **keywords, gchar **comment);
78 *-------------------------------------------------------------------
79 * long-term cache - keep keywords from whole dir in memory
80 *-------------------------------------------------------------------
83 /* fd->cached metadata list of lists
84 each particular list contains key as a first entry, then the values
87 static void metadata_cache_update(FileData *fd, const gchar *key, const GList *values)
91 work = fd->cached_metadata;
94 GList *entry = static_cast<GList *>(work->data);
95 gchar *entry_key = static_cast<gchar *>(entry->data);
97 if (strcmp(entry_key, key) == 0)
99 /* key found - just replace values */
100 GList *old_values = entry->next;
102 old_values->prev = NULL;
103 string_list_free(old_values);
104 work->data = g_list_append(entry, string_list_copy(values));
105 DEBUG_1("updated %s %s\n", key, fd->path);
111 /* key not found - prepend new entry */
112 fd->cached_metadata = g_list_prepend(fd->cached_metadata,
113 g_list_prepend(string_list_copy(values), g_strdup(key)));
114 DEBUG_1("added %s %s\n", key, fd->path);
118 static const GList *metadata_cache_get(FileData *fd, const gchar *key)
122 work = fd->cached_metadata;
125 GList *entry = static_cast<GList *>(work->data);
126 gchar *entry_key = static_cast<gchar *>(entry->data);
128 if (strcmp(entry_key, key) == 0)
131 DEBUG_1("found %s %s\n", key, fd->path);
137 DEBUG_1("not found %s %s\n", key, fd->path);
140 static void metadata_cache_remove(FileData *fd, const gchar *key)
144 work = fd->cached_metadata;
147 GList *entry = static_cast<GList *>(work->data);
148 gchar *entry_key = static_cast<gchar *>(entry->data);
150 if (strcmp(entry_key, key) == 0)
153 string_list_free(entry);
154 fd->cached_metadata = g_list_delete_link(fd->cached_metadata, work);
155 DEBUG_1("removed %s %s\n", key, fd->path);
160 DEBUG_1("not removed %s %s\n", key, fd->path);
163 void metadata_cache_free(FileData *fd)
166 if (fd->cached_metadata) DEBUG_1("freed %s\n", fd->path);
168 work = fd->cached_metadata;
171 GList *entry = static_cast<GList *>(work->data);
172 string_list_free(entry);
176 g_list_free(fd->cached_metadata);
177 fd->cached_metadata = NULL;
186 *-------------------------------------------------------------------
188 *-------------------------------------------------------------------
191 static GList *metadata_write_queue = NULL;
192 static guint metadata_write_idle_id = 0; /* event source id */
194 static void metadata_write_queue_add(FileData *fd)
196 if (!g_list_find(metadata_write_queue, fd))
198 metadata_write_queue = g_list_prepend(metadata_write_queue, fd);
201 layout_util_status_update_write_all();
204 if (metadata_write_idle_id)
206 g_source_remove(metadata_write_idle_id);
207 metadata_write_idle_id = 0;
210 if (options->metadata.confirm_after_timeout)
212 metadata_write_idle_id = g_timeout_add(options->metadata.confirm_timeout * 1000, metadata_write_queue_idle_cb, NULL);
217 gboolean metadata_write_queue_remove(FileData *fd)
219 g_hash_table_destroy(fd->modified_xmp);
220 fd->modified_xmp = NULL;
222 metadata_write_queue = g_list_remove(metadata_write_queue, fd);
224 file_data_increment_version(fd);
225 file_data_send_notification(fd, NOTIFY_REREAD);
229 layout_util_status_update_write_all();
233 //gboolean metadata_write_queue_remove_list(GList *list)
236 //gboolean ret = TRUE;
241 //FileData *fd = static_cast<//FileData *>(work->data);
243 //ret = ret && metadata_write_queue_remove(fd);
248 void metadata_notify_cb(FileData *fd, NotifyType type, gpointer UNUSED(data))
250 if (type & (NOTIFY_REREAD | NOTIFY_CHANGE))
252 metadata_cache_free(fd);
254 if (g_list_find(metadata_write_queue, fd))
256 DEBUG_1("Notify metadata: %s %04x", fd->path, type);
257 if (!isname(fd->path))
259 /* ignore deleted files */
260 metadata_write_queue_remove(fd);
266 gboolean metadata_write_queue_confirm(gboolean force_dialog, FileUtilDoneFunc done_func, gpointer done_data)
269 GList *to_approve = NULL;
271 work = metadata_write_queue;
274 FileData *fd = static_cast<FileData *>(work->data);
277 if (!isname(fd->path))
279 /* ignore deleted files */
280 metadata_write_queue_remove(fd);
284 if (fd->change) continue; /* another operation in progress, skip this file for now */
286 to_approve = g_list_prepend(to_approve, file_data_ref(fd));
289 file_util_write_metadata(NULL, to_approve, NULL, force_dialog, done_func, done_data);
291 return (metadata_write_queue != NULL);
294 static gboolean metadata_write_queue_idle_cb(gpointer UNUSED(data))
296 metadata_write_queue_confirm(FALSE, NULL, NULL);
297 metadata_write_idle_id = 0;
301 gboolean metadata_write_perform(FileData *fd)
307 g_assert(fd->change);
309 lf = strlen(GQ_CACHE_EXT_METADATA);
310 if (fd->change->dest &&
311 g_ascii_strncasecmp(fd->change->dest + strlen(fd->change->dest) - lf, GQ_CACHE_EXT_METADATA, lf) == 0)
313 success = metadata_legacy_write(fd);
314 if (success) metadata_legacy_delete(fd, fd->change->dest);
318 /* write via exiv2 */
319 /* we can either use cached metadata which have fd->modified_xmp already applied
320 or read metadata from file and apply fd->modified_xmp
321 metadata are read also if the file was modified meanwhile */
322 exif = exif_read_fd(fd);
323 if (!exif) return FALSE;
325 success = (fd->change->dest) ? exif_write_sidecar(exif, fd->change->dest) : exif_write(exif); /* write modified metadata */
326 exif_free_fd(fd, exif);
328 if (fd->change->dest)
329 /* this will create a FileData for the sidecar and link it to the main file
330 (we can't wait until the sidecar is discovered by directory scanning because
331 exif_read_fd is called before that and it would read the main file only and
332 store the metadata in the cache)
335 @FIXME this does not catch new sidecars created by independent external programs
337 file_data_unref(file_data_new_group(fd->change->dest));
339 if (success) metadata_legacy_delete(fd, fd->change->dest);
343 gint metadata_queue_length(void)
345 return g_list_length(metadata_write_queue);
348 static gboolean metadata_check_key(const gchar *keys[], const gchar *key)
350 const gchar **k = keys;
354 if (strcmp(key, *k) == 0) return TRUE;
360 gboolean metadata_write_revert(FileData *fd, const gchar *key)
362 if (!fd->modified_xmp) return FALSE;
364 g_hash_table_remove(fd->modified_xmp, key);
366 if (g_hash_table_size(fd->modified_xmp) == 0)
368 metadata_write_queue_remove(fd);
372 /* reread the metadata to restore the original value */
373 file_data_increment_version(fd);
374 file_data_send_notification(fd, NOTIFY_REREAD);
379 gboolean metadata_write_list(FileData *fd, const gchar *key, const GList *values)
381 if (!fd->modified_xmp)
383 fd->modified_xmp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)string_list_free);
385 g_hash_table_insert(fd->modified_xmp, g_strdup(key), string_list_copy((GList *)values));
387 metadata_cache_remove(fd, key);
391 exif_update_metadata(fd->exif, key, values);
393 metadata_write_queue_add(fd);
394 file_data_increment_version(fd);
395 file_data_send_notification(fd, NOTIFY_METADATA);
397 if (options->metadata.sync_grouped_files && metadata_check_key(group_keys, key))
399 GList *work = fd->sidecar_files;
403 FileData *sfd = static_cast<FileData *>(work->data);
406 if (sfd->format_class == FORMAT_CLASS_META) continue;
408 metadata_write_list(sfd, key, values);
416 gboolean metadata_write_string(FileData *fd, const gchar *key, const char *value)
418 GList *list = g_list_append(NULL, g_strdup(value));
419 gboolean ret = metadata_write_list(fd, key, list);
420 string_list_free(list);
424 gboolean metadata_write_int(FileData *fd, const gchar *key, guint64 value)
428 g_snprintf(string, sizeof(string), "%llu", (unsigned long long) value);
429 return metadata_write_string(fd, key, string);
433 *-------------------------------------------------------------------
434 * keyword / comment read/write
435 *-------------------------------------------------------------------
438 static gboolean metadata_file_write(gchar *path, const GList *keywords, const gchar *comment)
442 ssi = secure_open(path);
443 if (!ssi) return FALSE;
445 secure_fprintf(ssi, "#%s comment (%s)\n\n", GQ_APPNAME, VERSION);
447 secure_fprintf(ssi, "[keywords]\n");
448 while (keywords && secsave_errno == SS_ERR_NONE)
450 const gchar *word = static_cast<const gchar *>(keywords->data);
451 keywords = keywords->next;
453 secure_fprintf(ssi, "%s\n", word);
455 secure_fputc(ssi, '\n');
457 secure_fprintf(ssi, "[comment]\n");
458 secure_fprintf(ssi, "%s\n", (comment) ? comment : "");
460 secure_fprintf(ssi, "#end\n");
462 return (secure_close(ssi) == 0);
465 static gboolean metadata_legacy_write(FileData *fd)
467 gboolean success = FALSE;
468 gchar *metadata_pathl;
471 gboolean have_keywords;
472 gboolean have_comment;
473 const gchar *comment;
474 GList *orig_keywords = NULL;
475 gchar *orig_comment = NULL;
477 g_assert(fd->change && fd->change->dest);
479 DEBUG_1("Saving comment: %s", fd->change->dest);
481 if (!fd->modified_xmp) return TRUE;
483 metadata_pathl = path_from_utf8(fd->change->dest);
485 have_keywords = g_hash_table_lookup_extended(fd->modified_xmp, KEYWORD_KEY, NULL, &keywords);
486 have_comment = g_hash_table_lookup_extended(fd->modified_xmp, COMMENT_KEY, NULL, &comment_l);
487 comment = static_cast<const gchar *>((have_comment && comment_l) ? ((GList *)comment_l)->data : NULL);
489 if (!have_keywords || !have_comment) metadata_file_read(metadata_pathl, &orig_keywords, &orig_comment);
491 success = metadata_file_write(metadata_pathl,
492 have_keywords ? (GList *)keywords : orig_keywords,
493 have_comment ? comment : orig_comment);
495 g_free(metadata_pathl);
496 g_free(orig_comment);
497 string_list_free(orig_keywords);
502 static gboolean metadata_file_read(gchar *path, GList **keywords, gchar **comment)
506 MetadataKey key = MK_NONE;
508 GString *comment_build = NULL;
510 f = fopen(path, "r");
511 if (!f) return FALSE;
513 while (fgets(s_buf, sizeof(s_buf), f))
517 if (*ptr == '#') continue;
518 if (*ptr == '[' && key != MK_COMMENT)
520 gchar *keystr = ++ptr;
523 while (*ptr != ']' && *ptr != '\n' && *ptr != '\0') ptr++;
528 if (g_ascii_strcasecmp(keystr, "keywords") == 0)
530 else if (g_ascii_strcasecmp(keystr, "comment") == 0)
542 while (*ptr != '\n' && *ptr != '\0') ptr++;
544 if (strlen(s_buf) > 0)
546 gchar *kw = utf8_validate_or_convert(s_buf);
548 list = g_list_prepend(list, kw);
553 if (!comment_build) comment_build = g_string_new("");
554 g_string_append(comment_build, s_buf);
563 *keywords = g_list_reverse(list);
567 string_list_free(list);
575 gchar *ptr = comment_build->str;
577 /* strip leading and trailing newlines */
578 while (*ptr == '\n') ptr++;
580 while (len > 0 && ptr[len - 1] == '\n') len--;
581 if (ptr[len] == '\n') len++; /* keep the last one */
584 gchar *text = g_strndup(ptr, len);
586 *comment = utf8_validate_or_convert(text);
590 g_string_free(comment_build, TRUE);
596 static void metadata_legacy_delete(FileData *fd, const gchar *except)
598 gchar *metadata_path;
599 gchar *metadata_pathl;
602 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
603 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
605 metadata_pathl = path_from_utf8(metadata_path);
606 unlink(metadata_pathl);
607 g_free(metadata_pathl);
608 g_free(metadata_path);
612 /* without exiv2: do not delete xmp metadata because we are not able to convert it,
614 metadata_path = cache_find_location(CACHE_TYPE_XMP_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);
625 static gboolean metadata_legacy_read(FileData *fd, GList **keywords, gchar **comment)
627 gchar *metadata_path;
628 gchar *metadata_pathl;
629 gboolean success = FALSE;
631 if (!fd) return FALSE;
633 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
634 if (!metadata_path) return FALSE;
636 metadata_pathl = path_from_utf8(metadata_path);
638 success = metadata_file_read(metadata_pathl, keywords, comment);
640 g_free(metadata_pathl);
641 g_free(metadata_path);
646 static GList *remove_duplicate_strings_from_list(GList *list)
649 GHashTable *hashtable = g_hash_table_new(g_str_hash, g_str_equal);
650 GList *newlist = NULL;
654 gchar *key = static_cast<gchar *>(work->data);
656 if (g_hash_table_lookup(hashtable, key) == NULL)
658 g_hash_table_insert(hashtable, (gpointer) key, GINT_TO_POINTER(1));
659 newlist = g_list_prepend(newlist, key);
664 g_hash_table_destroy(hashtable);
667 return g_list_reverse(newlist);
670 GList *metadata_read_list(FileData *fd, const gchar *key, MetadataFormat format)
674 const GList *cache_entry;
675 if (!fd) return NULL;
677 /* unwritten data override everything */
678 if (fd->modified_xmp && format == METADATA_PLAIN)
680 list = static_cast<GList *>(g_hash_table_lookup(fd->modified_xmp, key));
681 if (list) return string_list_copy(list);
685 if (format == METADATA_PLAIN && strcmp(key, KEYWORD_KEY) == 0
686 && (cache_entry = metadata_cache_get(fd, key)))
688 return string_list_copy(cache_entry->next);
692 Legacy metadata file is the primary source if it exists.
693 Merging the lists does not make much sense, because the existence of
694 legacy metadata file indicates that the other metadata sources are not
695 writable and thus it would not be possible to delete the keywords
696 that comes from the image file.
698 if (strcmp(key, KEYWORD_KEY) == 0)
700 if (metadata_legacy_read(fd, &list, NULL))
702 if (format == METADATA_PLAIN)
704 metadata_cache_update(fd, key, list);
709 else if (strcmp(key, COMMENT_KEY) == 0)
711 gchar *comment = NULL;
712 if (metadata_legacy_read(fd, NULL, &comment)) return g_list_append(NULL, comment);
714 else if (strncmp(key, "file.", 5) == 0)
716 return g_list_append(NULL, metadata_file_info(fd, key, format));
719 else if (strncmp(key, "lua.", 4) == 0)
721 return g_list_append(NULL, metadata_lua_info(fd, key, format));
725 exif = exif_read_fd(fd); /* this is cached, thus inexpensive */
726 if (!exif) return NULL;
727 list = exif_get_metadata(exif, key, format);
728 exif_free_fd(fd, exif);
730 if (format == METADATA_PLAIN && strcmp(key, KEYWORD_KEY) == 0)
732 metadata_cache_update(fd, key, list);
738 gchar *metadata_read_string(FileData *fd, const gchar *key, MetadataFormat format)
740 GList *string_list = metadata_read_list(fd, key, format);
743 gchar *str = static_cast<gchar *>(string_list->data);
744 string_list->data = NULL;
745 string_list_free(string_list);
751 guint64 metadata_read_int(FileData *fd, const gchar *key, guint64 fallback)
755 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
756 if (!string) return fallback;
758 ret = g_ascii_strtoull(string, &endptr, 10);
759 if (string == endptr) ret = fallback;
764 gchar *metadata_read_rating_stars(FileData *fd)
767 gint n = metadata_read_int(fd, RATING_KEY, METADATA_PLAIN);
769 ret = convert_rating_to_stars(n);
774 gdouble metadata_read_GPS_coord(FileData *fd, const gchar *key, gdouble fallback)
778 gdouble deg, min, sec;
780 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
781 if (!string) return fallback;
783 deg = g_ascii_strtod(string, &endptr);
786 min = g_ascii_strtod(endptr + 1, &endptr);
788 sec = g_ascii_strtod(endptr + 1, &endptr);
793 if (*endptr == 'S' || *endptr == 'W' || *endptr == 'N' || *endptr == 'E')
795 coord = deg + min /60.0 + sec / 3600.0;
797 if (*endptr == 'S' || *endptr == 'W') coord = -coord;
804 log_printf("unable to parse GPS coordinate '%s'\n", string);
811 gdouble metadata_read_GPS_direction(FileData *fd, const gchar *key, gdouble fallback)
816 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
817 if (!string) return fallback;
819 DEBUG_3("GPS_direction: %s\n", string);
820 deg = g_ascii_strtod(string, &endptr);
822 /* Expected text string is of the format e.g.:
834 log_printf("unable to parse GPS direction '%s: %f'\n", string, deg);
842 gboolean metadata_append_string(FileData *fd, const gchar *key, const char *value)
844 gchar *str = metadata_read_string(fd, key, METADATA_PLAIN);
848 return metadata_write_string(fd, key, value);
852 gchar *new_string = g_strconcat(str, value, NULL);
853 gboolean ret = metadata_write_string(fd, key, new_string);
860 gboolean metadata_write_GPS_coord(FileData *fd, const gchar *key, gdouble value)
868 char *old_locale, *saved_locale;
874 min = (param * 60) - (deg * 60);
875 if (g_strcmp0(key, "Xmp.exif.GPSLongitude") == 0)
880 else if (g_strcmp0(key, "Xmp.exif.GPSLatitude") == 0)
887 log_printf("unknown GPS parameter key '%s'\n", key);
893 /* Avoid locale problems with commas and decimal points in numbers */
894 old_locale = setlocale(LC_ALL, NULL);
895 saved_locale = strdup(old_locale);
896 if (saved_locale == NULL)
900 setlocale(LC_ALL, "C");
902 coordinate = g_strdup_printf("%i,%lf,%s", deg, min, ref);
903 metadata_write_string(fd, key, coordinate );
905 setlocale(LC_ALL, saved_locale);
913 gboolean metadata_append_list(FileData *fd, const gchar *key, const GList *values)
915 GList *list = metadata_read_list(fd, key, METADATA_PLAIN);
919 return metadata_write_list(fd, key, values);
924 list = g_list_concat(list, string_list_copy(values));
925 list = remove_duplicate_strings_from_list(list);
927 ret = metadata_write_list(fd, key, list);
928 string_list_free(list);
934 * @see find_string_in_list
936 gchar *find_string_in_list_utf8nocase(GList *list, const gchar *string)
938 gchar *string_casefold = g_utf8_casefold(string, -1);
942 gchar *haystack = static_cast<gchar *>(list->data);
947 gchar *haystack_casefold = g_utf8_casefold(haystack, -1);
949 equal = (strcmp(haystack_casefold, string_casefold) == 0);
950 g_free(haystack_casefold);
954 g_free(string_casefold);
962 g_free(string_casefold);
967 * @see find_string_in_list
969 gchar *find_string_in_list_utf8case(GList *list, const gchar *string)
973 gchar *haystack = static_cast<gchar *>(list->data);
975 if (haystack && strcmp(haystack, string) == 0)
982 } // gchar *find_string_in_list_utf...
985 * @brief Find a existent string in a list.
987 * This is a switch between find_string_in_list_utf8case and
988 * find_string_in_list_utf8nocase to search with or without case for the
989 * existence of a string.
991 * @param list The list to search in
992 * @param string The string to search for
993 * @return The string or NULL
995 * @see find_string_in_list_utf8case
996 * @see find_string_in_list_utf8nocase
998 gchar *find_string_in_list(GList *list, const gchar *string)
1000 if (options->metadata.keywords_case_sensitive)
1001 return find_string_in_list_utf8case(list, string);
1003 return find_string_in_list_utf8nocase(list, string);
1006 #define KEYWORDS_SEPARATOR(c) ((c) == ',' || (c) == ';' || (c) == '\n' || (c) == '\r' || (c) == '\b')
1008 GList *string_to_keywords_list(const gchar *text)
1011 const gchar *ptr = text;
1013 while (*ptr != '\0')
1018 while (KEYWORDS_SEPARATOR(*ptr)) ptr++;
1020 while (*ptr != '\0' && !KEYWORDS_SEPARATOR(*ptr))
1026 /* trim starting and ending whitespaces */
1027 while (l > 0 && g_ascii_isspace(*begin)) begin++, l--;
1028 while (l > 0 && g_ascii_isspace(begin[l-1])) l--;
1032 gchar *keyword = g_strndup(begin, l);
1034 /* only add if not already in the list */
1035 if (!find_string_in_list(list, keyword))
1036 list = g_list_append(list, keyword);
1050 gboolean meta_data_get_keyword_mark(FileData *fd, gint UNUSED(n), gpointer data)
1052 /** @FIXME do not use global keyword_tree */
1053 GList *path = static_cast<GList *>(data);
1055 gboolean found = FALSE;
1056 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
1060 if (keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path) &&
1061 keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
1068 gboolean meta_data_set_keyword_mark(FileData *fd, gint UNUSED(n), gboolean value, gpointer data)
1070 GList *path = static_cast<GList *>(data);
1071 GList *keywords = NULL;
1074 if (!keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path)) return FALSE;
1076 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
1078 if (!!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords) != !!value)
1082 keyword_tree_set(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
1086 keyword_tree_reset(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
1088 metadata_write_list(fd, KEYWORD_KEY, keywords);
1091 string_list_free(keywords);
1097 void meta_data_connect_mark_with_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *kw_iter, gint mark)
1100 FileDataGetMarkFunc get_mark_func;
1101 FileDataSetMarkFunc set_mark_func;
1102 gpointer mark_func_data;
1106 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
1108 file_data_get_registered_mark_func(i, &get_mark_func, &set_mark_func, &mark_func_data);
1109 if (get_mark_func == meta_data_get_keyword_mark)
1111 GtkTreeIter old_kw_iter;
1112 GList *old_path = static_cast<GList *>(mark_func_data);
1114 if (keyword_tree_get_iter(keyword_tree, &old_kw_iter, old_path) &&
1115 (i == mark || /* release any previous connection of given mark */
1116 keyword_compare(keyword_tree, &old_kw_iter, kw_iter) == 0)) /* or given keyword */
1118 file_data_register_mark_func(i, NULL, NULL, NULL, NULL);
1119 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), &old_kw_iter, KEYWORD_COLUMN_MARK, "", -1);
1125 if (mark >= 0 && mark < FILEDATA_MARKS_SIZE)
1129 path = keyword_tree_get_path(keyword_tree, kw_iter);
1130 file_data_register_mark_func(mark, meta_data_get_keyword_mark, meta_data_set_keyword_mark, path, (GDestroyNotify)string_list_free);
1132 mark_str = g_strdup_printf("%d", (mark < 9 ? mark : -1) + 1);
1133 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), kw_iter, KEYWORD_COLUMN_MARK, mark_str, -1);
1140 *-------------------------------------------------------------------
1142 *-------------------------------------------------------------------
1147 GtkTreeStore *keyword_tree;
1149 gchar *keyword_get_name(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1152 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_NAME, &name, -1);
1156 gchar *keyword_get_mark(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1160 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_MARK, &mark_str, -1);
1164 gchar *keyword_get_casefold(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1167 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_CASEFOLD, &casefold, -1);
1171 gboolean keyword_get_is_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1173 gboolean is_keyword;
1174 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
1178 void keyword_set(GtkTreeStore *keyword_tree, GtkTreeIter *iter, const gchar *name, gboolean is_keyword)
1180 gchar *casefold = g_utf8_casefold(name, -1);
1181 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_MARK, "",
1182 KEYWORD_COLUMN_NAME, name,
1183 KEYWORD_COLUMN_CASEFOLD, casefold,
1184 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
1188 gboolean keyword_compare(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
1190 GtkTreePath *pa = gtk_tree_model_get_path(keyword_tree, a);
1191 GtkTreePath *pb = gtk_tree_model_get_path(keyword_tree, b);
1192 gint ret = gtk_tree_path_compare(pa, pb);
1193 gtk_tree_path_free(pa);
1194 gtk_tree_path_free(pb);
1198 gboolean keyword_same_parent(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
1200 GtkTreeIter parent_a;
1201 GtkTreeIter parent_b;
1203 gboolean valid_pa = gtk_tree_model_iter_parent(keyword_tree, &parent_a, a);
1204 gboolean valid_pb = gtk_tree_model_iter_parent(keyword_tree, &parent_b, b);
1206 if (valid_pa && valid_pb)
1208 return keyword_compare(keyword_tree, &parent_a, &parent_b) == 0;
1212 return (!valid_pa && !valid_pb); /* both are toplevel */
1216 gboolean keyword_exists(GtkTreeModel *keyword_tree, GtkTreeIter *parent_ptr, GtkTreeIter *sibling, const gchar *name, gboolean exclude_sibling, GtkTreeIter *result)
1220 gboolean toplevel = FALSE;
1226 parent = *parent_ptr;
1230 toplevel = !gtk_tree_model_iter_parent(keyword_tree, &parent, sibling);
1237 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &iter, toplevel ? NULL : &parent)) return FALSE;
1239 casefold = g_utf8_casefold(name, -1);
1244 if (!(exclude_sibling && sibling && keyword_compare(keyword_tree, &iter, sibling) == 0))
1246 if (options->metadata.keywords_case_sensitive)
1248 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
1249 ret = strcmp(name, iter_name) == 0;
1254 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
1255 ret = strcmp(casefold, iter_casefold) == 0;
1256 g_free(iter_casefold);
1257 } // if (options->metadata.tags_cas...
1261 if (result) *result = iter;
1264 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) break;
1271 void keyword_copy(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
1274 gchar *mark, *name, *casefold;
1275 gboolean is_keyword;
1277 /* do not copy KEYWORD_COLUMN_HIDE_IN, it fully shows the new subtree */
1278 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), from, KEYWORD_COLUMN_MARK, &mark,
1279 KEYWORD_COLUMN_NAME, &name,
1280 KEYWORD_COLUMN_CASEFOLD, &casefold,
1281 KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
1283 gtk_tree_store_set(keyword_tree, to, KEYWORD_COLUMN_MARK, mark,
1284 KEYWORD_COLUMN_NAME, name,
1285 KEYWORD_COLUMN_CASEFOLD, casefold,
1286 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
1292 void keyword_copy_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
1294 GtkTreeIter from_child;
1296 keyword_copy(keyword_tree, to, from);
1298 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &from_child, from)) return;
1302 GtkTreeIter to_child;
1303 gtk_tree_store_append(keyword_tree, &to_child, to);
1304 keyword_copy_recursive(keyword_tree, &to_child, &from_child);
1305 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &from_child)) return;
1309 void keyword_move_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
1311 keyword_copy_recursive(keyword_tree, to, from);
1312 keyword_delete(keyword_tree, from);
1315 GList *keyword_tree_get_path(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
1318 GtkTreeIter iter = *iter_ptr;
1323 path = g_list_prepend(path, keyword_get_name(keyword_tree, &iter));
1324 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) break;
1330 gboolean keyword_tree_get_iter(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList *path)
1334 if (!gtk_tree_model_get_iter_first(keyword_tree, &iter)) return FALSE;
1338 GtkTreeIter children;
1341 gchar *name = keyword_get_name(keyword_tree, &iter);
1342 if (strcmp(name, static_cast<const gchar *>(path->data)) == 0) break;
1344 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return FALSE;
1353 if (!gtk_tree_model_iter_children(keyword_tree, &children, &iter)) return FALSE;
1359 static gboolean keyword_tree_is_set_casefold(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *casefold_list)
1361 if (!casefold_list) return FALSE;
1363 if (!keyword_get_is_keyword(keyword_tree, &iter))
1365 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1367 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1368 return FALSE; /* this should happen only on empty helpers */
1372 if (keyword_tree_is_set_casefold(keyword_tree, child, casefold_list)) return TRUE;
1373 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1381 if (keyword_get_is_keyword(keyword_tree, &iter))
1383 GList *work = casefold_list;
1384 gboolean found = FALSE;
1385 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
1388 const gchar *casefold = static_cast<const gchar *>(work->data);
1391 if (strcmp(iter_casefold, casefold) == 0)
1397 g_free(iter_casefold);
1398 if (!found) return FALSE;
1401 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1406 static gboolean keyword_tree_is_set_casefull(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *kw_list)
1408 if (!kw_list) return FALSE;
1410 if (!keyword_get_is_keyword(keyword_tree, &iter))
1412 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1414 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1415 return FALSE; /* this should happen only on empty helpers */
1419 if (keyword_tree_is_set_casefull(keyword_tree, child, kw_list)) return TRUE;
1420 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1428 if (keyword_get_is_keyword(keyword_tree, &iter))
1430 GList *work = kw_list;
1431 gboolean found = FALSE;
1432 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
1435 const gchar *name = static_cast<const gchar *>(work->data);
1438 if (strcmp(iter_name, name) == 0)
1445 if (!found) return FALSE;
1448 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1453 gboolean keyword_tree_is_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList *kw_list)
1456 GList *casefold_list = NULL;
1459 if (options->metadata.keywords_case_sensitive)
1461 ret = keyword_tree_is_set_casefull(keyword_tree, *iter, kw_list);
1468 const gchar *kw = static_cast<const gchar *>(work->data);
1471 casefold_list = g_list_prepend(casefold_list, g_utf8_casefold(kw, -1));
1474 ret = keyword_tree_is_set_casefold(keyword_tree, *iter, casefold_list);
1476 string_list_free(casefold_list);
1482 void keyword_tree_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1484 GtkTreeIter iter = *iter_ptr;
1489 if (keyword_get_is_keyword(keyword_tree, &iter))
1491 gchar *name = keyword_get_name(keyword_tree, &iter);
1492 if (!find_string_in_list(*kw_list, name))
1494 *kw_list = g_list_append(*kw_list, name);
1502 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1507 GList *keyword_tree_get(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
1509 GtkTreeIter iter = *iter_ptr;
1510 GList *kw_list = NULL;
1516 if (keyword_get_is_keyword(keyword_tree, &iter))
1518 gchar *name = keyword_get_name(keyword_tree, &iter);
1519 kw_list = g_list_append(kw_list, name);
1522 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return kw_list;
1525 } // GList *keyword_tree_get(GtkTre...
1527 static void keyword_tree_reset1(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1531 if (!keyword_get_is_keyword(keyword_tree, iter)) return;
1533 name = keyword_get_name(keyword_tree, iter);
1534 found = find_string_in_list(*kw_list, name);
1538 *kw_list = g_list_remove(*kw_list, found);
1544 static void keyword_tree_reset_recursive(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1547 keyword_tree_reset1(keyword_tree, iter, kw_list);
1549 if (!gtk_tree_model_iter_children(keyword_tree, &child, iter)) return;
1553 keyword_tree_reset_recursive(keyword_tree, &child, kw_list);
1554 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return;
1558 static gboolean keyword_tree_check_empty_children(GtkTreeModel *keyword_tree, GtkTreeIter *parent, GList *kw_list)
1562 if (!gtk_tree_model_iter_children(keyword_tree, &iter, parent))
1563 return TRUE; /* this should happen only on empty helpers */
1567 if (keyword_tree_is_set(keyword_tree, &iter, kw_list)) return FALSE;
1568 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return TRUE;
1572 void keyword_tree_reset(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1574 GtkTreeIter iter = *iter_ptr;
1576 keyword_tree_reset_recursive(keyword_tree, &iter, kw_list);
1578 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1581 while (keyword_tree_check_empty_children(keyword_tree, &iter, *kw_list))
1584 keyword_tree_reset1(keyword_tree, &iter, kw_list);
1585 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1590 void keyword_delete(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr)
1594 while (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, iter_ptr))
1596 keyword_delete(keyword_tree, &child);
1599 meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree), iter_ptr, -1);
1601 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter_ptr, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1604 gtk_tree_store_remove(keyword_tree, iter_ptr);
1608 void keyword_hide_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1611 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1612 if (!g_list_find(list, id))
1614 list = g_list_prepend(list, id);
1615 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1619 void keyword_show_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 list = g_list_remove(list, id);
1624 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1627 gboolean keyword_is_hidden_in(GtkTreeModel *keyword_tree, GtkTreeIter *iter, gpointer id)
1630 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1631 return !!g_list_find(list, id);
1634 static gboolean keyword_show_all_in_cb(GtkTreeModel *model, GtkTreePath *UNUSED(path), GtkTreeIter *iter, gpointer data)
1636 keyword_show_in(GTK_TREE_STORE(model), iter, data);
1640 void keyword_show_all_in(GtkTreeStore *keyword_tree, gpointer id)
1642 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_all_in_cb, id);
1645 static gboolean keyword_revert_hidden_in_cb(GtkTreeModel *model, GtkTreePath *UNUSED(path), GtkTreeIter *iter, gpointer data)
1647 if (keyword_is_hidden_in(GTK_TREE_MODEL(keyword_tree), iter, data))
1649 keyword_show_in(GTK_TREE_STORE(model), iter, data);
1654 void keyword_revert_hidden_in(GtkTreeStore *keyword_tree, gpointer id)
1656 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_revert_hidden_in_cb, id);
1659 static void keyword_hide_unset_in_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr, gpointer id, GList *keywords)
1661 GtkTreeIter iter = *iter_ptr;
1664 if (!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
1666 keyword_hide_in(keyword_tree, &iter, id);
1667 /* no need to check children of hidden node */
1672 if (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, &iter))
1674 keyword_hide_unset_in_recursive(keyword_tree, &child, id, keywords);
1677 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1681 void keyword_hide_unset_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1684 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1685 keyword_hide_unset_in_recursive(keyword_tree, &iter, id, keywords);
1688 static gboolean keyword_show_set_in_cb(GtkTreeModel *model, GtkTreePath *UNUSED(path), GtkTreeIter *iter_ptr, gpointer data)
1690 GtkTreeIter iter = *iter_ptr;
1691 GList *keywords = static_cast<GList *>(data);
1692 gpointer id = keywords->data;
1693 keywords = keywords->next; /* hack */
1694 if (keyword_tree_is_set(model, &iter, keywords))
1699 keyword_show_in(GTK_TREE_STORE(model), &iter, id);
1700 if (!gtk_tree_model_iter_parent(GTK_TREE_MODEL(keyword_tree), &parent, &iter)) break;
1707 void keyword_show_set_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1709 /* hack: pass id to keyword_hide_unset_in_cb in the list */
1710 keywords = g_list_prepend(keywords, id);
1711 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_set_in_cb, keywords);
1712 keywords = g_list_delete_link(keywords, keywords);
1716 void keyword_tree_new(void)
1718 if (keyword_tree) return;
1720 keyword_tree = gtk_tree_store_new(KEYWORD_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER);
1723 static GtkTreeIter keyword_tree_default_append(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar *name, gboolean is_keyword)
1726 gtk_tree_store_append(keyword_tree, &iter, parent);
1727 keyword_set(keyword_tree, &iter, name, is_keyword);
1731 void keyword_tree_new_default(void)
1735 if (!keyword_tree) keyword_tree_new();
1737 i1 = keyword_tree_default_append(keyword_tree, NULL, _("People"), TRUE);
1738 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Family"), TRUE);
1739 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Free time"), TRUE);
1740 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Children"), TRUE);
1741 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sport"), TRUE);
1742 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Culture"), TRUE);
1743 keyword_tree_default_append(keyword_tree, &i2, _("Festival"), TRUE);
1744 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Nature"), TRUE);
1745 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Animal"), TRUE);
1746 keyword_tree_default_append(keyword_tree, &i2, _("Bird"), TRUE);
1747 keyword_tree_default_append(keyword_tree, &i2, _("Insect"), TRUE);
1748 keyword_tree_default_append(keyword_tree, &i2, _("Pets"), TRUE);
1749 keyword_tree_default_append(keyword_tree, &i2, _("Wildlife"), TRUE);
1750 keyword_tree_default_append(keyword_tree, &i2, _("Zoo"), TRUE);
1751 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Plant"), TRUE);
1752 keyword_tree_default_append(keyword_tree, &i2, _("Tree"), TRUE);
1753 keyword_tree_default_append(keyword_tree, &i2, _("Flower"), TRUE);
1754 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Water"), TRUE);
1755 keyword_tree_default_append(keyword_tree, &i2, _("River"), TRUE);
1756 keyword_tree_default_append(keyword_tree, &i2, _("Lake"), TRUE);
1757 keyword_tree_default_append(keyword_tree, &i2, _("Sea"), TRUE);
1758 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Landscape"), TRUE);
1759 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Art"), TRUE);
1760 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Statue"), TRUE);
1761 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Painting"), TRUE);
1762 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1763 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1764 i1 = keyword_tree_default_append(keyword_tree, NULL, _("City"), TRUE);
1765 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Park"), TRUE);
1766 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Street"), TRUE);
1767 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Square"), TRUE);
1768 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Architecture"), TRUE);
1769 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Buildings"), FALSE);
1770 keyword_tree_default_append(keyword_tree, &i2, _("House"), TRUE);
1771 keyword_tree_default_append(keyword_tree, &i2, _("Cathedral"), TRUE);
1772 keyword_tree_default_append(keyword_tree, &i2, _("Palace"), TRUE);
1773 keyword_tree_default_append(keyword_tree, &i2, _("Castle"), TRUE);
1774 keyword_tree_default_append(keyword_tree, &i2, _("Bridge"), TRUE);
1775 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Interior"), TRUE);
1776 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1777 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1778 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Places"), FALSE);
1779 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Conditions"), FALSE);
1780 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Night"), TRUE);
1781 keyword_tree_default_append(keyword_tree, &i2, _("Lights"), TRUE);
1782 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Reflections"), TRUE);
1783 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sun"), TRUE);
1784 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Weather"), FALSE);
1785 keyword_tree_default_append(keyword_tree, &i2, _("Fog"), TRUE);
1786 keyword_tree_default_append(keyword_tree, &i2, _("Rain"), TRUE);
1787 keyword_tree_default_append(keyword_tree, &i2, _("Clouds"), TRUE);
1788 keyword_tree_default_append(keyword_tree, &i2, _("Snow"), TRUE);
1789 keyword_tree_default_append(keyword_tree, &i2, _("Sunny weather"), TRUE);
1790 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Photo"), FALSE);
1791 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Edited"), TRUE);
1792 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Detail"), TRUE);
1793 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Macro"), TRUE);
1794 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Portrait"), TRUE);
1795 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Black and White"), TRUE);
1796 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Perspective"), TRUE);
1800 static void keyword_tree_node_write_config(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GString *outstr, gint indent)
1802 GtkTreeIter iter = *iter_ptr;
1805 GtkTreeIter children;
1809 WRITE_NL(); WRITE_STRING("<keyword ");
1810 name = keyword_get_name(keyword_tree, &iter);
1811 write_char_option(outstr, indent, "name", name);
1813 write_bool_option(outstr, indent, "kw", keyword_get_is_keyword(keyword_tree, &iter));
1814 mark_str = keyword_get_mark(keyword_tree, &iter);
1815 if (mark_str && mark_str[0])
1817 write_char_option(outstr, indent, "mark", mark_str);
1820 if (gtk_tree_model_iter_children(keyword_tree, &children, &iter))
1824 keyword_tree_node_write_config(keyword_tree, &children, outstr, indent);
1826 WRITE_NL(); WRITE_STRING("</keyword>");
1832 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return;
1836 void keyword_tree_write_config(GString *outstr, gint indent)
1839 WRITE_NL(); WRITE_STRING("<keyword_tree>");
1842 if (keyword_tree && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1844 keyword_tree_node_write_config(GTK_TREE_MODEL(keyword_tree), &iter, outstr, indent);
1847 WRITE_NL(); WRITE_STRING("</keyword_tree>");
1850 void keyword_tree_node_disconnect_marks(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
1852 GtkTreeIter iter = *iter_ptr;
1856 GtkTreeIter children;
1858 meta_data_connect_mark_with_keyword((keyword_tree), &iter, -1);
1860 if (gtk_tree_model_iter_children((keyword_tree), &children, &iter))
1862 keyword_tree_node_disconnect_marks((keyword_tree), &children);
1865 if (!gtk_tree_model_iter_next((keyword_tree), &iter)) return;
1869 void keyword_tree_disconnect_marks()
1873 if (keyword_tree && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1875 keyword_tree_node_disconnect_marks(GTK_TREE_MODEL(keyword_tree), &iter);
1879 GtkTreeIter *keyword_add_from_config(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar **attribute_names, const gchar **attribute_values)
1882 gboolean is_kw = TRUE;
1883 gchar *mark_str = NULL;
1885 while (*attribute_names)
1887 const gchar *option = *attribute_names++;
1888 const gchar *value = *attribute_values++;
1890 if (READ_CHAR_FULL("name", name)) continue;
1891 if (READ_BOOL_FULL("kw", is_kw)) continue;
1892 if (READ_CHAR_FULL("mark", mark_str)) continue;
1894 log_printf("unknown attribute %s = %s\n", option, value);
1896 if (name && name[0])
1899 /* re-use existing keyword if any */
1900 if (!keyword_exists(GTK_TREE_MODEL(keyword_tree), parent, NULL, name, FALSE, &iter))
1902 gtk_tree_store_append(keyword_tree, &iter, parent);
1904 keyword_set(keyword_tree, &iter, name, is_kw);
1908 gint i = (gint)atoi(mark_str);
1911 meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree),
1916 return gtk_tree_iter_copy(&iter);
1922 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */