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 /* If contents change, keep GuideOptionsMetadata.xml up to date */
47 * @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
49 static const gchar *group_keys[] = {
51 "Xmp.photoshop.Urgency",
52 "Xmp.photoshop.Category",
53 "Xmp.photoshop.SupplementalCategory",
56 "Xmp.photoshop.Instruction",
57 "Xmp.photoshop.DateCreated",
59 "Xmp.photoshop.AuthorsPosition",
61 "Xmp.photoshop.State",
62 "Xmp.iptc.CountryCode",
63 "Xmp.photoshop.Country",
64 "Xmp.photoshop.TransmissionReference",
65 "Xmp.photoshop.Headline",
66 "Xmp.photoshop.Credit",
67 "Xmp.photoshop.Source",
70 "Xmp.photoshop.CaptionWriter",
73 static gboolean metadata_write_queue_idle_cb(gpointer data);
74 static gboolean metadata_legacy_write(FileData *fd);
75 static void metadata_legacy_delete(FileData *fd, const gchar *except);
76 static gboolean metadata_file_read(gchar *path, GList **keywords, gchar **comment);
80 *-------------------------------------------------------------------
81 * long-term cache - keep keywords from whole dir in memory
82 *-------------------------------------------------------------------
85 /* fd->cached metadata list of lists
86 each particular list contains key as a first entry, then the values
89 static void metadata_cache_update(FileData *fd, const gchar *key, const GList *values)
93 work = fd->cached_metadata;
96 GList *entry = work->data;
97 gchar *entry_key = entry->data;
99 if (strcmp(entry_key, key) == 0)
101 /* key found - just replace values */
102 GList *old_values = entry->next;
104 old_values->prev = NULL;
105 string_list_free(old_values);
106 work->data = g_list_append(entry, string_list_copy(values));
107 DEBUG_1("updated %s %s\n", key, fd->path);
113 /* key not found - prepend new entry */
114 fd->cached_metadata = g_list_prepend(fd->cached_metadata,
115 g_list_prepend(string_list_copy(values), g_strdup(key)));
116 DEBUG_1("added %s %s\n", key, fd->path);
120 static const GList *metadata_cache_get(FileData *fd, const gchar *key)
124 work = fd->cached_metadata;
127 GList *entry = work->data;
128 gchar *entry_key = entry->data;
130 if (strcmp(entry_key, key) == 0)
133 DEBUG_1("found %s %s\n", key, fd->path);
139 DEBUG_1("not found %s %s\n", key, fd->path);
142 static void metadata_cache_remove(FileData *fd, const gchar *key)
146 work = fd->cached_metadata;
149 GList *entry = work->data;
150 gchar *entry_key = entry->data;
152 if (strcmp(entry_key, key) == 0)
155 string_list_free(entry);
156 fd->cached_metadata = g_list_delete_link(fd->cached_metadata, work);
157 DEBUG_1("removed %s %s\n", key, fd->path);
162 DEBUG_1("not removed %s %s\n", key, fd->path);
165 void metadata_cache_free(FileData *fd)
168 if (fd->cached_metadata) DEBUG_1("freed %s\n", fd->path);
170 work = fd->cached_metadata;
173 GList *entry = work->data;
174 string_list_free(entry);
178 g_list_free(fd->cached_metadata);
179 fd->cached_metadata = NULL;
188 *-------------------------------------------------------------------
190 *-------------------------------------------------------------------
193 static GList *metadata_write_queue = NULL;
194 static guint metadata_write_idle_id = 0; /* event source id */
196 static void metadata_write_queue_add(FileData *fd)
198 if (!g_list_find(metadata_write_queue, fd))
200 metadata_write_queue = g_list_prepend(metadata_write_queue, fd);
203 layout_util_status_update_write_all();
206 if (metadata_write_idle_id)
208 g_source_remove(metadata_write_idle_id);
209 metadata_write_idle_id = 0;
212 if (options->metadata.confirm_after_timeout)
214 metadata_write_idle_id = g_timeout_add(options->metadata.confirm_timeout * 1000, metadata_write_queue_idle_cb, NULL);
219 gboolean metadata_write_queue_remove(FileData *fd)
221 g_hash_table_destroy(fd->modified_xmp);
222 fd->modified_xmp = NULL;
224 metadata_write_queue = g_list_remove(metadata_write_queue, fd);
226 file_data_increment_version(fd);
227 file_data_send_notification(fd, NOTIFY_REREAD);
231 layout_util_status_update_write_all();
235 gboolean metadata_write_queue_remove_list(GList *list)
243 FileData *fd = work->data;
245 ret = ret && metadata_write_queue_remove(fd);
250 void metadata_notify_cb(FileData *fd, NotifyType type, gpointer UNUSED(data))
252 if (type & (NOTIFY_REREAD | NOTIFY_CHANGE))
254 metadata_cache_free(fd);
256 if (g_list_find(metadata_write_queue, fd))
258 DEBUG_1("Notify metadata: %s %04x", fd->path, type);
259 if (!isname(fd->path))
261 /* ignore deleted files */
262 metadata_write_queue_remove(fd);
268 gboolean metadata_write_queue_confirm(gboolean force_dialog, FileUtilDoneFunc done_func, gpointer done_data)
271 GList *to_approve = NULL;
273 work = metadata_write_queue;
276 FileData *fd = work->data;
279 if (!isname(fd->path))
281 /* ignore deleted files */
282 metadata_write_queue_remove(fd);
286 if (fd->change) continue; /* another operation in progress, skip this file for now */
288 to_approve = g_list_prepend(to_approve, file_data_ref(fd));
291 file_util_write_metadata(NULL, to_approve, NULL, force_dialog, done_func, done_data);
293 return (metadata_write_queue != NULL);
296 static gboolean metadata_write_queue_idle_cb(gpointer UNUSED(data))
298 metadata_write_queue_confirm(FALSE, NULL, NULL);
299 metadata_write_idle_id = 0;
303 gboolean metadata_write_perform(FileData *fd)
309 g_assert(fd->change);
311 lf = strlen(GQ_CACHE_EXT_METADATA);
312 if (fd->change->dest &&
313 g_ascii_strncasecmp(fd->change->dest + strlen(fd->change->dest) - lf, GQ_CACHE_EXT_METADATA, lf) == 0)
315 success = metadata_legacy_write(fd);
316 if (success) metadata_legacy_delete(fd, fd->change->dest);
320 /* write via exiv2 */
321 /* we can either use cached metadata which have fd->modified_xmp already applied
322 or read metadata from file and apply fd->modified_xmp
323 metadata are read also if the file was modified meanwhile */
324 exif = exif_read_fd(fd);
325 if (!exif) return FALSE;
327 success = (fd->change->dest) ? exif_write_sidecar(exif, fd->change->dest) : exif_write(exif); /* write modified metadata */
328 exif_free_fd(fd, exif);
330 if (fd->change->dest)
331 /* this will create a FileData for the sidecar and link it to the main file
332 (we can't wait until the sidecar is discovered by directory scanning because
333 exif_read_fd is called before that and it would read the main file only and
334 store the metadata in the cache)
337 @FIXME this does not catch new sidecars created by independent external programs
339 file_data_unref(file_data_new_group(fd->change->dest));
341 if (success) metadata_legacy_delete(fd, fd->change->dest);
345 gint metadata_queue_length(void)
347 return g_list_length(metadata_write_queue);
350 static gboolean metadata_check_key(const gchar *keys[], const gchar *key)
352 const gchar **k = keys;
356 if (strcmp(key, *k) == 0) return TRUE;
362 gboolean metadata_write_revert(FileData *fd, const gchar *key)
364 if (!fd->modified_xmp) return FALSE;
366 g_hash_table_remove(fd->modified_xmp, key);
368 if (g_hash_table_size(fd->modified_xmp) == 0)
370 metadata_write_queue_remove(fd);
374 /* reread the metadata to restore the original value */
375 file_data_increment_version(fd);
376 file_data_send_notification(fd, NOTIFY_REREAD);
381 gboolean metadata_write_list(FileData *fd, const gchar *key, const GList *values)
383 if (!fd->modified_xmp)
385 fd->modified_xmp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)string_list_free);
387 g_hash_table_insert(fd->modified_xmp, g_strdup(key), string_list_copy((GList *)values));
389 metadata_cache_remove(fd, key);
393 exif_update_metadata(fd->exif, key, values);
395 metadata_write_queue_add(fd);
396 file_data_increment_version(fd);
397 file_data_send_notification(fd, NOTIFY_METADATA);
399 if (options->metadata.sync_grouped_files && metadata_check_key(group_keys, key))
401 GList *work = fd->sidecar_files;
405 FileData *sfd = work->data;
408 if (sfd->format_class == FORMAT_CLASS_META) continue;
410 metadata_write_list(sfd, key, values);
418 gboolean metadata_write_string(FileData *fd, const gchar *key, const char *value)
420 GList *list = g_list_append(NULL, g_strdup(value));
421 gboolean ret = metadata_write_list(fd, key, list);
422 string_list_free(list);
426 gboolean metadata_write_int(FileData *fd, const gchar *key, guint64 value)
430 g_snprintf(string, sizeof(string), "%llu", (unsigned long long) value);
431 return metadata_write_string(fd, key, string);
435 *-------------------------------------------------------------------
436 * keyword / comment read/write
437 *-------------------------------------------------------------------
440 static gboolean metadata_file_write(gchar *path, const GList *keywords, const gchar *comment)
444 ssi = secure_open(path);
445 if (!ssi) return FALSE;
447 secure_fprintf(ssi, "#%s comment (%s)\n\n", GQ_APPNAME, VERSION);
449 secure_fprintf(ssi, "[keywords]\n");
450 while (keywords && secsave_errno == SS_ERR_NONE)
452 const gchar *word = keywords->data;
453 keywords = keywords->next;
455 secure_fprintf(ssi, "%s\n", word);
457 secure_fputc(ssi, '\n');
459 secure_fprintf(ssi, "[comment]\n");
460 secure_fprintf(ssi, "%s\n", (comment) ? comment : "");
462 secure_fprintf(ssi, "#end\n");
464 return (secure_close(ssi) == 0);
467 static gboolean metadata_legacy_write(FileData *fd)
469 gboolean success = FALSE;
470 gchar *metadata_pathl;
473 gboolean have_keywords;
474 gboolean have_comment;
475 const gchar *comment;
476 GList *orig_keywords = NULL;
477 gchar *orig_comment = NULL;
479 g_assert(fd->change && fd->change->dest);
481 DEBUG_1("Saving comment: %s", fd->change->dest);
483 if (!fd->modified_xmp) return TRUE;
485 metadata_pathl = path_from_utf8(fd->change->dest);
487 have_keywords = g_hash_table_lookup_extended(fd->modified_xmp, KEYWORD_KEY, NULL, &keywords);
488 have_comment = g_hash_table_lookup_extended(fd->modified_xmp, COMMENT_KEY, NULL, &comment_l);
489 comment = (have_comment && comment_l) ? ((GList *)comment_l)->data : NULL;
491 if (!have_keywords || !have_comment) metadata_file_read(metadata_pathl, &orig_keywords, &orig_comment);
493 success = metadata_file_write(metadata_pathl,
494 have_keywords ? (GList *)keywords : orig_keywords,
495 have_comment ? comment : orig_comment);
497 g_free(metadata_pathl);
498 g_free(orig_comment);
499 string_list_free(orig_keywords);
504 static gboolean metadata_file_read(gchar *path, GList **keywords, gchar **comment)
508 MetadataKey key = MK_NONE;
510 GString *comment_build = NULL;
512 f = fopen(path, "r");
513 if (!f) return FALSE;
515 while (fgets(s_buf, sizeof(s_buf), f))
519 if (*ptr == '#') continue;
520 if (*ptr == '[' && key != MK_COMMENT)
522 gchar *keystr = ++ptr;
525 while (*ptr != ']' && *ptr != '\n' && *ptr != '\0') ptr++;
530 if (g_ascii_strcasecmp(keystr, "keywords") == 0)
532 else if (g_ascii_strcasecmp(keystr, "comment") == 0)
544 while (*ptr != '\n' && *ptr != '\0') ptr++;
546 if (strlen(s_buf) > 0)
548 gchar *kw = utf8_validate_or_convert(s_buf);
550 list = g_list_prepend(list, kw);
555 if (!comment_build) comment_build = g_string_new("");
556 g_string_append(comment_build, s_buf);
565 *keywords = g_list_reverse(list);
569 string_list_free(list);
577 gchar *ptr = comment_build->str;
579 /* strip leading and trailing newlines */
580 while (*ptr == '\n') ptr++;
582 while (len > 0 && ptr[len - 1] == '\n') len--;
583 if (ptr[len] == '\n') len++; /* keep the last one */
586 gchar *text = g_strndup(ptr, len);
588 *comment = utf8_validate_or_convert(text);
592 g_string_free(comment_build, TRUE);
598 static void metadata_legacy_delete(FileData *fd, const gchar *except)
600 gchar *metadata_path;
601 gchar *metadata_pathl;
604 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
605 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
607 metadata_pathl = path_from_utf8(metadata_path);
608 unlink(metadata_pathl);
609 g_free(metadata_pathl);
610 g_free(metadata_path);
614 /* without exiv2: do not delete xmp metadata because we are not able to convert it,
616 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
617 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
619 metadata_pathl = path_from_utf8(metadata_path);
620 unlink(metadata_pathl);
621 g_free(metadata_pathl);
622 g_free(metadata_path);
627 static gboolean metadata_legacy_read(FileData *fd, GList **keywords, gchar **comment)
629 gchar *metadata_path;
630 gchar *metadata_pathl;
631 gboolean success = FALSE;
633 if (!fd) return FALSE;
635 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
636 if (!metadata_path) return FALSE;
638 metadata_pathl = path_from_utf8(metadata_path);
640 success = metadata_file_read(metadata_pathl, keywords, comment);
642 g_free(metadata_pathl);
643 g_free(metadata_path);
648 static GList *remove_duplicate_strings_from_list(GList *list)
651 GHashTable *hashtable = g_hash_table_new(g_str_hash, g_str_equal);
652 GList *newlist = NULL;
656 gchar *key = work->data;
658 if (g_hash_table_lookup(hashtable, key) == NULL)
660 g_hash_table_insert(hashtable, (gpointer) key, GINT_TO_POINTER(1));
661 newlist = g_list_prepend(newlist, key);
666 g_hash_table_destroy(hashtable);
669 return g_list_reverse(newlist);
672 GList *metadata_read_list(FileData *fd, const gchar *key, MetadataFormat format)
676 const GList *cache_entry;
677 if (!fd) return NULL;
679 /* unwritten data override everything */
680 if (fd->modified_xmp && format == METADATA_PLAIN)
682 list = g_hash_table_lookup(fd->modified_xmp, key);
683 if (list) return string_list_copy(list);
687 if (format == METADATA_PLAIN && strcmp(key, KEYWORD_KEY) == 0
688 && (cache_entry = metadata_cache_get(fd, key)))
690 return string_list_copy(cache_entry->next);
694 Legacy metadata file is the primary source if it exists.
695 Merging the lists does not make much sense, because the existence of
696 legacy metadata file indicates that the other metadata sources are not
697 writable and thus it would not be possible to delete the keywords
698 that comes from the image file.
700 if (strcmp(key, KEYWORD_KEY) == 0)
702 if (metadata_legacy_read(fd, &list, NULL))
704 if (format == METADATA_PLAIN)
706 metadata_cache_update(fd, key, list);
711 else if (strcmp(key, COMMENT_KEY) == 0)
713 gchar *comment = NULL;
714 if (metadata_legacy_read(fd, NULL, &comment)) return g_list_append(NULL, comment);
716 else if (strncmp(key, "file.", 5) == 0)
718 return g_list_append(NULL, metadata_file_info(fd, key, format));
721 else if (strncmp(key, "lua.", 4) == 0)
723 return g_list_append(NULL, metadata_lua_info(fd, key, format));
727 exif = exif_read_fd(fd); /* this is cached, thus inexpensive */
728 if (!exif) return NULL;
729 list = exif_get_metadata(exif, key, format);
730 exif_free_fd(fd, exif);
732 if (format == METADATA_PLAIN && strcmp(key, KEYWORD_KEY) == 0)
734 metadata_cache_update(fd, key, list);
740 gchar *metadata_read_string(FileData *fd, const gchar *key, MetadataFormat format)
742 GList *string_list = metadata_read_list(fd, key, format);
745 gchar *str = string_list->data;
746 string_list->data = NULL;
747 string_list_free(string_list);
753 guint64 metadata_read_int(FileData *fd, const gchar *key, guint64 fallback)
757 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
758 if (!string) return fallback;
760 ret = g_ascii_strtoull(string, &endptr, 10);
761 if (string == endptr) ret = fallback;
766 gchar *metadata_read_rating_stars(FileData *fd)
769 gint n = metadata_read_int(fd, RATING_KEY, METADATA_PLAIN);
771 ret = convert_rating_to_stars(n);
776 gdouble metadata_read_GPS_coord(FileData *fd, const gchar *key, gdouble fallback)
780 gdouble deg, min, sec;
782 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
783 if (!string) return fallback;
785 deg = g_ascii_strtod(string, &endptr);
788 min = g_ascii_strtod(endptr + 1, &endptr);
790 sec = g_ascii_strtod(endptr + 1, &endptr);
795 if (*endptr == 'S' || *endptr == 'W' || *endptr == 'N' || *endptr == 'E')
797 coord = deg + min /60.0 + sec / 3600.0;
799 if (*endptr == 'S' || *endptr == 'W') coord = -coord;
806 log_printf("unable to parse GPS coordinate '%s'\n", string);
813 gdouble metadata_read_GPS_direction(FileData *fd, const gchar *key, gdouble fallback)
818 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
819 if (!string) return fallback;
821 DEBUG_3("GPS_direction: %s\n", string);
822 deg = g_ascii_strtod(string, &endptr);
824 /* Expected text string is of the format e.g.:
836 log_printf("unable to parse GPS direction '%s: %f'\n", string, deg);
844 gboolean metadata_append_string(FileData *fd, const gchar *key, const char *value)
846 gchar *str = metadata_read_string(fd, key, METADATA_PLAIN);
850 return metadata_write_string(fd, key, value);
854 gchar *new_string = g_strconcat(str, value, NULL);
855 gboolean ret = metadata_write_string(fd, key, new_string);
862 gboolean metadata_write_GPS_coord(FileData *fd, const gchar *key, gdouble value)
870 char *old_locale, *saved_locale;
876 min = (param * 60) - (deg * 60);
877 if (g_strcmp0(key, "Xmp.exif.GPSLongitude") == 0)
882 else if (g_strcmp0(key, "Xmp.exif.GPSLatitude") == 0)
889 log_printf("unknown GPS parameter key '%s'\n", key);
895 /* Avoid locale problems with commas and decimal points in numbers */
896 old_locale = setlocale(LC_ALL, NULL);
897 saved_locale = strdup(old_locale);
898 if (saved_locale == NULL)
902 setlocale(LC_ALL, "C");
904 coordinate = g_strdup_printf("%i,%lf,%s", deg, min, ref);
905 metadata_write_string(fd, key, coordinate );
907 setlocale(LC_ALL, saved_locale);
915 gboolean metadata_append_list(FileData *fd, const gchar *key, const GList *values)
917 GList *list = metadata_read_list(fd, key, METADATA_PLAIN);
921 return metadata_write_list(fd, key, values);
926 list = g_list_concat(list, string_list_copy(values));
927 list = remove_duplicate_strings_from_list(list);
929 ret = metadata_write_list(fd, key, list);
930 string_list_free(list);
936 * @see find_string_in_list
938 gchar *find_string_in_list_utf8nocase(GList *list, const gchar *string)
940 gchar *string_casefold = g_utf8_casefold(string, -1);
944 gchar *haystack = list->data;
949 gchar *haystack_casefold = g_utf8_casefold(haystack, -1);
951 equal = (strcmp(haystack_casefold, string_casefold) == 0);
952 g_free(haystack_casefold);
956 g_free(string_casefold);
964 g_free(string_casefold);
969 * @see find_string_in_list
971 gchar *find_string_in_list_utf8case(GList *list, const gchar *string)
975 gchar *haystack = list->data;
977 if (haystack && strcmp(haystack, string) == 0)
984 } // gchar *find_string_in_list_utf...
987 * @brief Find a existent string in a list.
989 * This is a switch between find_string_in_list_utf8case and
990 * find_string_in_list_utf8nocase to search with or without case for the
991 * existence of a string.
993 * @param list The list to search in
994 * @param string The string to search for
995 * @return The string or NULL
997 * @see find_string_in_list_utf8case
998 * @see find_string_in_list_utf8nocase
1000 gchar *find_string_in_list(GList *list, const gchar *string)
1002 if (options->metadata.keywords_case_sensitive)
1003 return find_string_in_list_utf8case(list, string);
1005 return find_string_in_list_utf8nocase(list, string);
1008 #define KEYWORDS_SEPARATOR(c) ((c) == ',' || (c) == ';' || (c) == '\n' || (c) == '\r' || (c) == '\b')
1010 GList *string_to_keywords_list(const gchar *text)
1013 const gchar *ptr = text;
1015 while (*ptr != '\0')
1020 while (KEYWORDS_SEPARATOR(*ptr)) ptr++;
1022 while (*ptr != '\0' && !KEYWORDS_SEPARATOR(*ptr))
1028 /* trim starting and ending whitespaces */
1029 while (l > 0 && g_ascii_isspace(*begin)) begin++, l--;
1030 while (l > 0 && g_ascii_isspace(begin[l-1])) l--;
1034 gchar *keyword = g_strndup(begin, l);
1036 /* only add if not already in the list */
1037 if (!find_string_in_list(list, keyword))
1038 list = g_list_append(list, keyword);
1052 gboolean meta_data_get_keyword_mark(FileData *fd, gint UNUSED(n), gpointer data)
1054 /** @FIXME do not use global keyword_tree */
1057 gboolean found = FALSE;
1058 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
1062 if (keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path) &&
1063 keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
1070 gboolean meta_data_set_keyword_mark(FileData *fd, gint UNUSED(n), gboolean value, gpointer data)
1073 GList *keywords = NULL;
1076 if (!keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path)) return FALSE;
1078 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
1080 if (!!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords) != !!value)
1084 keyword_tree_set(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
1088 keyword_tree_reset(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
1090 metadata_write_list(fd, KEYWORD_KEY, keywords);
1093 string_list_free(keywords);
1099 void meta_data_connect_mark_with_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *kw_iter, gint mark)
1102 FileDataGetMarkFunc get_mark_func;
1103 FileDataSetMarkFunc set_mark_func;
1104 gpointer mark_func_data;
1108 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
1110 file_data_get_registered_mark_func(i, &get_mark_func, &set_mark_func, &mark_func_data);
1111 if (get_mark_func == meta_data_get_keyword_mark)
1113 GtkTreeIter old_kw_iter;
1114 GList *old_path = mark_func_data;
1116 if (keyword_tree_get_iter(keyword_tree, &old_kw_iter, old_path) &&
1117 (i == mark || /* release any previous connection of given mark */
1118 keyword_compare(keyword_tree, &old_kw_iter, kw_iter) == 0)) /* or given keyword */
1120 file_data_register_mark_func(i, NULL, NULL, NULL, NULL);
1121 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), &old_kw_iter, KEYWORD_COLUMN_MARK, "", -1);
1127 if (mark >= 0 && mark < FILEDATA_MARKS_SIZE)
1131 path = keyword_tree_get_path(keyword_tree, kw_iter);
1132 file_data_register_mark_func(mark, meta_data_get_keyword_mark, meta_data_set_keyword_mark, path, (GDestroyNotify)string_list_free);
1134 mark_str = g_strdup_printf("%d", (mark < 9 ? mark : -1) + 1);
1135 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), kw_iter, KEYWORD_COLUMN_MARK, mark_str, -1);
1142 *-------------------------------------------------------------------
1144 *-------------------------------------------------------------------
1149 GtkTreeStore *keyword_tree;
1151 gchar *keyword_get_name(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1154 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_NAME, &name, -1);
1158 gchar *keyword_get_mark(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1162 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_MARK, &mark_str, -1);
1166 gchar *keyword_get_casefold(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1169 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_CASEFOLD, &casefold, -1);
1173 gboolean keyword_get_is_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1175 gboolean is_keyword;
1176 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
1180 void keyword_set(GtkTreeStore *keyword_tree, GtkTreeIter *iter, const gchar *name, gboolean is_keyword)
1182 gchar *casefold = g_utf8_casefold(name, -1);
1183 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_MARK, "",
1184 KEYWORD_COLUMN_NAME, name,
1185 KEYWORD_COLUMN_CASEFOLD, casefold,
1186 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
1190 gboolean keyword_compare(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
1192 GtkTreePath *pa = gtk_tree_model_get_path(keyword_tree, a);
1193 GtkTreePath *pb = gtk_tree_model_get_path(keyword_tree, b);
1194 gint ret = gtk_tree_path_compare(pa, pb);
1195 gtk_tree_path_free(pa);
1196 gtk_tree_path_free(pb);
1200 gboolean keyword_same_parent(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
1202 GtkTreeIter parent_a;
1203 GtkTreeIter parent_b;
1205 gboolean valid_pa = gtk_tree_model_iter_parent(keyword_tree, &parent_a, a);
1206 gboolean valid_pb = gtk_tree_model_iter_parent(keyword_tree, &parent_b, b);
1208 if (valid_pa && valid_pb)
1210 return keyword_compare(keyword_tree, &parent_a, &parent_b) == 0;
1214 return (!valid_pa && !valid_pb); /* both are toplevel */
1218 gboolean keyword_exists(GtkTreeModel *keyword_tree, GtkTreeIter *parent_ptr, GtkTreeIter *sibling, const gchar *name, gboolean exclude_sibling, GtkTreeIter *result)
1222 gboolean toplevel = FALSE;
1228 parent = *parent_ptr;
1232 toplevel = !gtk_tree_model_iter_parent(keyword_tree, &parent, sibling);
1239 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &iter, toplevel ? NULL : &parent)) return FALSE;
1241 casefold = g_utf8_casefold(name, -1);
1246 if (!(exclude_sibling && sibling && keyword_compare(keyword_tree, &iter, sibling) == 0))
1248 if (options->metadata.keywords_case_sensitive)
1250 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
1251 ret = strcmp(name, iter_name) == 0;
1256 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
1257 ret = strcmp(casefold, iter_casefold) == 0;
1258 g_free(iter_casefold);
1259 } // if (options->metadata.tags_cas...
1263 if (result) *result = iter;
1266 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) break;
1273 void keyword_copy(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
1276 gchar *mark, *name, *casefold;
1277 gboolean is_keyword;
1279 /* do not copy KEYWORD_COLUMN_HIDE_IN, it fully shows the new subtree */
1280 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), from, KEYWORD_COLUMN_MARK, &mark,
1281 KEYWORD_COLUMN_NAME, &name,
1282 KEYWORD_COLUMN_CASEFOLD, &casefold,
1283 KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
1285 gtk_tree_store_set(keyword_tree, to, KEYWORD_COLUMN_MARK, mark,
1286 KEYWORD_COLUMN_NAME, name,
1287 KEYWORD_COLUMN_CASEFOLD, casefold,
1288 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
1294 void keyword_copy_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
1296 GtkTreeIter from_child;
1298 keyword_copy(keyword_tree, to, from);
1300 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &from_child, from)) return;
1304 GtkTreeIter to_child;
1305 gtk_tree_store_append(keyword_tree, &to_child, to);
1306 keyword_copy_recursive(keyword_tree, &to_child, &from_child);
1307 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &from_child)) return;
1311 void keyword_move_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
1313 keyword_copy_recursive(keyword_tree, to, from);
1314 keyword_delete(keyword_tree, from);
1317 GList *keyword_tree_get_path(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
1320 GtkTreeIter iter = *iter_ptr;
1325 path = g_list_prepend(path, keyword_get_name(keyword_tree, &iter));
1326 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) break;
1332 gboolean keyword_tree_get_iter(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList *path)
1336 if (!gtk_tree_model_get_iter_first(keyword_tree, &iter)) return FALSE;
1340 GtkTreeIter children;
1343 gchar *name = keyword_get_name(keyword_tree, &iter);
1344 if (strcmp(name, path->data) == 0) break;
1346 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return FALSE;
1355 if (!gtk_tree_model_iter_children(keyword_tree, &children, &iter)) return FALSE;
1361 static gboolean keyword_tree_is_set_casefold(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *casefold_list)
1363 if (!casefold_list) return FALSE;
1365 if (!keyword_get_is_keyword(keyword_tree, &iter))
1367 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1369 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1370 return FALSE; /* this should happen only on empty helpers */
1374 if (keyword_tree_is_set_casefold(keyword_tree, child, casefold_list)) return TRUE;
1375 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1383 if (keyword_get_is_keyword(keyword_tree, &iter))
1385 GList *work = casefold_list;
1386 gboolean found = FALSE;
1387 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
1390 const gchar *casefold = work->data;
1393 if (strcmp(iter_casefold, casefold) == 0)
1399 g_free(iter_casefold);
1400 if (!found) return FALSE;
1403 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1408 static gboolean keyword_tree_is_set_casefull(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *kw_list)
1410 if (!kw_list) return FALSE;
1412 if (!keyword_get_is_keyword(keyword_tree, &iter))
1414 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1416 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1417 return FALSE; /* this should happen only on empty helpers */
1421 if (keyword_tree_is_set_casefull(keyword_tree, child, kw_list)) return TRUE;
1422 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1430 if (keyword_get_is_keyword(keyword_tree, &iter))
1432 GList *work = kw_list;
1433 gboolean found = FALSE;
1434 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
1437 const gchar *name = work->data;
1440 if (strcmp(iter_name, name) == 0)
1447 if (!found) return FALSE;
1450 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1455 gboolean keyword_tree_is_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList *kw_list)
1458 GList *casefold_list = NULL;
1461 if (options->metadata.keywords_case_sensitive)
1463 ret = keyword_tree_is_set_casefull(keyword_tree, *iter, kw_list);
1470 const gchar *kw = work->data;
1473 casefold_list = g_list_prepend(casefold_list, g_utf8_casefold(kw, -1));
1476 ret = keyword_tree_is_set_casefold(keyword_tree, *iter, casefold_list);
1478 string_list_free(casefold_list);
1484 void keyword_tree_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1486 GtkTreeIter iter = *iter_ptr;
1491 if (keyword_get_is_keyword(keyword_tree, &iter))
1493 gchar *name = keyword_get_name(keyword_tree, &iter);
1494 if (!find_string_in_list(*kw_list, name))
1496 *kw_list = g_list_append(*kw_list, name);
1504 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1509 GList *keyword_tree_get(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
1511 GtkTreeIter iter = *iter_ptr;
1512 GList *kw_list = NULL;
1518 if (keyword_get_is_keyword(keyword_tree, &iter))
1520 gchar *name = keyword_get_name(keyword_tree, &iter);
1521 kw_list = g_list_append(kw_list, name);
1524 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return kw_list;
1527 } // GList *keyword_tree_get(GtkTre...
1529 static void keyword_tree_reset1(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1533 if (!keyword_get_is_keyword(keyword_tree, iter)) return;
1535 name = keyword_get_name(keyword_tree, iter);
1536 found = find_string_in_list(*kw_list, name);
1540 *kw_list = g_list_remove(*kw_list, found);
1546 static void keyword_tree_reset_recursive(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1549 keyword_tree_reset1(keyword_tree, iter, kw_list);
1551 if (!gtk_tree_model_iter_children(keyword_tree, &child, iter)) return;
1555 keyword_tree_reset_recursive(keyword_tree, &child, kw_list);
1556 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return;
1560 static gboolean keyword_tree_check_empty_children(GtkTreeModel *keyword_tree, GtkTreeIter *parent, GList *kw_list)
1564 if (!gtk_tree_model_iter_children(keyword_tree, &iter, parent))
1565 return TRUE; /* this should happen only on empty helpers */
1569 if (keyword_tree_is_set(keyword_tree, &iter, kw_list)) return FALSE;
1570 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return TRUE;
1574 void keyword_tree_reset(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1576 GtkTreeIter iter = *iter_ptr;
1578 keyword_tree_reset_recursive(keyword_tree, &iter, kw_list);
1580 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1583 while (keyword_tree_check_empty_children(keyword_tree, &iter, *kw_list))
1586 keyword_tree_reset1(keyword_tree, &iter, kw_list);
1587 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1592 void keyword_delete(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr)
1596 while (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, iter_ptr))
1598 keyword_delete(keyword_tree, &child);
1601 meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree), iter_ptr, -1);
1603 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter_ptr, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1606 gtk_tree_store_remove(keyword_tree, iter_ptr);
1610 void keyword_hide_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1613 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1614 if (!g_list_find(list, id))
1616 list = g_list_prepend(list, id);
1617 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1621 void keyword_show_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1624 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1625 list = g_list_remove(list, id);
1626 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1629 gboolean keyword_is_hidden_in(GtkTreeModel *keyword_tree, GtkTreeIter *iter, gpointer id)
1632 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1633 return !!g_list_find(list, id);
1636 static gboolean keyword_show_all_in_cb(GtkTreeModel *model, GtkTreePath *UNUSED(path), GtkTreeIter *iter, gpointer data)
1638 keyword_show_in(GTK_TREE_STORE(model), iter, data);
1642 void keyword_show_all_in(GtkTreeStore *keyword_tree, gpointer id)
1644 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_all_in_cb, id);
1647 static gboolean keyword_revert_hidden_in_cb(GtkTreeModel *model, GtkTreePath *UNUSED(path), GtkTreeIter *iter, gpointer data)
1649 if (keyword_is_hidden_in(GTK_TREE_MODEL(keyword_tree), iter, data))
1651 keyword_show_in(GTK_TREE_STORE(model), iter, data);
1656 void keyword_revert_hidden_in(GtkTreeStore *keyword_tree, gpointer id)
1658 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_revert_hidden_in_cb, id);
1661 static void keyword_hide_unset_in_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr, gpointer id, GList *keywords)
1663 GtkTreeIter iter = *iter_ptr;
1666 if (!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
1668 keyword_hide_in(keyword_tree, &iter, id);
1669 /* no need to check children of hidden node */
1674 if (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, &iter))
1676 keyword_hide_unset_in_recursive(keyword_tree, &child, id, keywords);
1679 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1683 void keyword_hide_unset_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1686 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1687 keyword_hide_unset_in_recursive(keyword_tree, &iter, id, keywords);
1690 static gboolean keyword_show_set_in_cb(GtkTreeModel *model, GtkTreePath *UNUSED(path), GtkTreeIter *iter_ptr, gpointer data)
1692 GtkTreeIter iter = *iter_ptr;
1693 GList *keywords = data;
1694 gpointer id = keywords->data;
1695 keywords = keywords->next; /* hack */
1696 if (keyword_tree_is_set(model, &iter, keywords))
1701 keyword_show_in(GTK_TREE_STORE(model), &iter, id);
1702 if (!gtk_tree_model_iter_parent(GTK_TREE_MODEL(keyword_tree), &parent, &iter)) break;
1709 void keyword_show_set_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1711 /* hack: pass id to keyword_hide_unset_in_cb in the list */
1712 keywords = g_list_prepend(keywords, id);
1713 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_set_in_cb, keywords);
1714 keywords = g_list_delete_link(keywords, keywords);
1718 void keyword_tree_new(void)
1720 if (keyword_tree) return;
1722 keyword_tree = gtk_tree_store_new(KEYWORD_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER);
1725 static GtkTreeIter keyword_tree_default_append(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar *name, gboolean is_keyword)
1728 gtk_tree_store_append(keyword_tree, &iter, parent);
1729 keyword_set(keyword_tree, &iter, name, is_keyword);
1733 void keyword_tree_new_default(void)
1737 if (!keyword_tree) keyword_tree_new();
1739 i1 = keyword_tree_default_append(keyword_tree, NULL, _("People"), TRUE);
1740 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Family"), TRUE);
1741 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Free time"), TRUE);
1742 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Children"), TRUE);
1743 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sport"), TRUE);
1744 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Culture"), TRUE);
1745 keyword_tree_default_append(keyword_tree, &i2, _("Festival"), TRUE);
1746 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Nature"), TRUE);
1747 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Animal"), TRUE);
1748 keyword_tree_default_append(keyword_tree, &i2, _("Bird"), TRUE);
1749 keyword_tree_default_append(keyword_tree, &i2, _("Insect"), TRUE);
1750 keyword_tree_default_append(keyword_tree, &i2, _("Pets"), TRUE);
1751 keyword_tree_default_append(keyword_tree, &i2, _("Wildlife"), TRUE);
1752 keyword_tree_default_append(keyword_tree, &i2, _("Zoo"), TRUE);
1753 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Plant"), TRUE);
1754 keyword_tree_default_append(keyword_tree, &i2, _("Tree"), TRUE);
1755 keyword_tree_default_append(keyword_tree, &i2, _("Flower"), TRUE);
1756 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Water"), TRUE);
1757 keyword_tree_default_append(keyword_tree, &i2, _("River"), TRUE);
1758 keyword_tree_default_append(keyword_tree, &i2, _("Lake"), TRUE);
1759 keyword_tree_default_append(keyword_tree, &i2, _("Sea"), TRUE);
1760 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Landscape"), TRUE);
1761 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Art"), TRUE);
1762 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Statue"), TRUE);
1763 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Painting"), TRUE);
1764 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1765 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1766 i1 = keyword_tree_default_append(keyword_tree, NULL, _("City"), TRUE);
1767 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Park"), TRUE);
1768 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Street"), TRUE);
1769 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Square"), TRUE);
1770 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Architecture"), TRUE);
1771 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Buildings"), FALSE);
1772 keyword_tree_default_append(keyword_tree, &i2, _("House"), TRUE);
1773 keyword_tree_default_append(keyword_tree, &i2, _("Cathedral"), TRUE);
1774 keyword_tree_default_append(keyword_tree, &i2, _("Palace"), TRUE);
1775 keyword_tree_default_append(keyword_tree, &i2, _("Castle"), TRUE);
1776 keyword_tree_default_append(keyword_tree, &i2, _("Bridge"), TRUE);
1777 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Interior"), TRUE);
1778 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1779 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1780 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Places"), FALSE);
1781 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Conditions"), FALSE);
1782 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Night"), TRUE);
1783 keyword_tree_default_append(keyword_tree, &i2, _("Lights"), TRUE);
1784 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Reflections"), TRUE);
1785 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sun"), TRUE);
1786 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Weather"), FALSE);
1787 keyword_tree_default_append(keyword_tree, &i2, _("Fog"), TRUE);
1788 keyword_tree_default_append(keyword_tree, &i2, _("Rain"), TRUE);
1789 keyword_tree_default_append(keyword_tree, &i2, _("Clouds"), TRUE);
1790 keyword_tree_default_append(keyword_tree, &i2, _("Snow"), TRUE);
1791 keyword_tree_default_append(keyword_tree, &i2, _("Sunny weather"), TRUE);
1792 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Photo"), FALSE);
1793 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Edited"), TRUE);
1794 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Detail"), TRUE);
1795 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Macro"), TRUE);
1796 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Portrait"), TRUE);
1797 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Black and White"), TRUE);
1798 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Perspective"), TRUE);
1802 static void keyword_tree_node_write_config(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GString *outstr, gint indent)
1804 GtkTreeIter iter = *iter_ptr;
1807 GtkTreeIter children;
1811 WRITE_NL(); WRITE_STRING("<keyword ");
1812 name = keyword_get_name(keyword_tree, &iter);
1813 write_char_option(outstr, indent, "name", name);
1815 write_bool_option(outstr, indent, "kw", keyword_get_is_keyword(keyword_tree, &iter));
1816 mark_str = keyword_get_mark(keyword_tree, &iter);
1817 if (mark_str && mark_str[0])
1819 write_char_option(outstr, indent, "mark", mark_str);
1822 if (gtk_tree_model_iter_children(keyword_tree, &children, &iter))
1826 keyword_tree_node_write_config(keyword_tree, &children, outstr, indent);
1828 WRITE_NL(); WRITE_STRING("</keyword>");
1834 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return;
1838 void keyword_tree_write_config(GString *outstr, gint indent)
1841 WRITE_NL(); WRITE_STRING("<keyword_tree>");
1844 if (keyword_tree && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1846 keyword_tree_node_write_config(GTK_TREE_MODEL(keyword_tree), &iter, outstr, indent);
1849 WRITE_NL(); WRITE_STRING("</keyword_tree>");
1852 void keyword_tree_node_disconnect_marks(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
1854 GtkTreeIter iter = *iter_ptr;
1858 GtkTreeIter children;
1860 meta_data_connect_mark_with_keyword((keyword_tree), &iter, -1);
1862 if (gtk_tree_model_iter_children((keyword_tree), &children, &iter))
1864 keyword_tree_node_disconnect_marks((keyword_tree), &children);
1867 if (!gtk_tree_model_iter_next((keyword_tree), &iter)) return;
1871 void keyword_tree_disconnect_marks()
1875 if (keyword_tree && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1877 keyword_tree_node_disconnect_marks(GTK_TREE_MODEL(keyword_tree), &iter);
1881 GtkTreeIter *keyword_add_from_config(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar **attribute_names, const gchar **attribute_values)
1884 gboolean is_kw = TRUE;
1885 gchar *mark_str = NULL;
1887 while (*attribute_names)
1889 const gchar *option = *attribute_names++;
1890 const gchar *value = *attribute_values++;
1892 if (READ_CHAR_FULL("name", name)) continue;
1893 if (READ_BOOL_FULL("kw", is_kw)) continue;
1894 if (READ_CHAR_FULL("mark", mark_str)) continue;
1896 log_printf("unknown attribute %s = %s\n", option, value);
1898 if (name && name[0])
1901 /* re-use existing keyword if any */
1902 if (!keyword_exists(GTK_TREE_MODEL(keyword_tree), parent, NULL, name, FALSE, &iter))
1904 gtk_tree_store_append(keyword_tree, &iter, parent);
1906 keyword_set(keyword_tree, &iter, name, is_kw);
1910 gint i = (gint)atoi(mark_str);
1913 meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree),
1918 return gtk_tree_iter_copy(&iter);
1924 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */