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.
29 #include "secure_save.h"
30 #include "ui_fileops.h"
33 #include "filefilter.h"
34 #include "layout_util.h"
43 static const gchar *group_keys[] = { /* tags that will be written to all files in a group, options->metadata.sync_grouped_files */
45 "Xmp.photoshop.Urgency",
46 "Xmp.photoshop.Category",
47 "Xmp.photoshop.SupplementalCategory",
50 "Xmp.photoshop.Instruction",
51 "Xmp.photoshop.DateCreated",
53 "Xmp.photoshop.AuthorsPosition",
55 "Xmp.photoshop.State",
56 "Xmp.iptc.CountryCode",
57 "Xmp.photoshop.Country",
58 "Xmp.photoshop.TransmissionReference",
59 "Xmp.photoshop.Headline",
60 "Xmp.photoshop.Credit",
61 "Xmp.photoshop.Source",
64 "Xmp.photoshop.CaptionWriter",
67 static gboolean metadata_write_queue_idle_cb(gpointer data);
68 static gboolean metadata_legacy_write(FileData *fd);
69 static void metadata_legacy_delete(FileData *fd, const gchar *except);
70 static gboolean metadata_file_read(gchar *path, GList **keywords, gchar **comment);
74 *-------------------------------------------------------------------
75 * long-term cache - keep keywords from whole dir in memory
76 *-------------------------------------------------------------------
79 /* fd->cached metadata list of lists
80 each particular list contains key as a first entry, then the values
83 static void metadata_cache_update(FileData *fd, const gchar *key, const GList *values)
87 work = fd->cached_metadata;
90 GList *entry = work->data;
91 gchar *entry_key = entry->data;
93 if (strcmp(entry_key, key) == 0)
95 /* key found - just replace values */
96 GList *old_values = entry->next;
98 old_values->prev = NULL;
99 string_list_free(old_values);
100 work->data = g_list_append(entry, string_list_copy(values));
101 DEBUG_1("updated %s %s\n", key, fd->path);
107 /* key not found - prepend new entry */
108 fd->cached_metadata = g_list_prepend(fd->cached_metadata,
109 g_list_prepend(string_list_copy(values), g_strdup(key)));
110 DEBUG_1("added %s %s\n", key, fd->path);
114 static const GList *metadata_cache_get(FileData *fd, const gchar *key)
118 work = fd->cached_metadata;
121 GList *entry = work->data;
122 gchar *entry_key = entry->data;
124 if (strcmp(entry_key, key) == 0)
127 DEBUG_1("found %s %s\n", key, fd->path);
133 DEBUG_1("not found %s %s\n", key, fd->path);
136 static void metadata_cache_remove(FileData *fd, const gchar *key)
140 work = fd->cached_metadata;
143 GList *entry = work->data;
144 gchar *entry_key = entry->data;
146 if (strcmp(entry_key, key) == 0)
149 string_list_free(entry);
150 fd->cached_metadata = g_list_delete_link(fd->cached_metadata, work);
151 DEBUG_1("removed %s %s\n", key, fd->path);
156 DEBUG_1("not removed %s %s\n", key, fd->path);
159 void metadata_cache_free(FileData *fd)
162 if (fd->cached_metadata) DEBUG_1("freed %s\n", fd->path);
164 work = fd->cached_metadata;
167 GList *entry = work->data;
168 string_list_free(entry);
172 g_list_free(fd->cached_metadata);
173 fd->cached_metadata = NULL;
182 *-------------------------------------------------------------------
184 *-------------------------------------------------------------------
187 static GList *metadata_write_queue = NULL;
188 static guint metadata_write_idle_id = 0; /* event source id */
190 static void metadata_write_queue_add(FileData *fd)
192 if (!g_list_find(metadata_write_queue, fd))
194 metadata_write_queue = g_list_prepend(metadata_write_queue, fd);
197 layout_util_status_update_write_all();
200 if (metadata_write_idle_id)
202 g_source_remove(metadata_write_idle_id);
203 metadata_write_idle_id = 0;
206 if (options->metadata.confirm_after_timeout)
208 metadata_write_idle_id = g_timeout_add(options->metadata.confirm_timeout * 1000, metadata_write_queue_idle_cb, NULL);
213 gboolean metadata_write_queue_remove(FileData *fd)
215 g_hash_table_destroy(fd->modified_xmp);
216 fd->modified_xmp = NULL;
218 metadata_write_queue = g_list_remove(metadata_write_queue, fd);
220 file_data_increment_version(fd);
221 file_data_send_notification(fd, NOTIFY_REREAD);
225 layout_util_status_update_write_all();
229 gboolean metadata_write_queue_remove_list(GList *list)
237 FileData *fd = work->data;
239 ret = ret && metadata_write_queue_remove(fd);
244 void metadata_notify_cb(FileData *fd, NotifyType type, gpointer data)
246 if (type & (NOTIFY_REREAD | NOTIFY_CHANGE))
248 metadata_cache_free(fd);
250 if (g_list_find(metadata_write_queue, fd))
252 DEBUG_1("Notify metadata: %s %04x", fd->path, type);
253 if (!isname(fd->path))
255 /* ignore deleted files */
256 metadata_write_queue_remove(fd);
262 gboolean metadata_write_queue_confirm(gboolean force_dialog, FileUtilDoneFunc done_func, gpointer done_data)
265 GList *to_approve = NULL;
267 work = metadata_write_queue;
270 FileData *fd = work->data;
273 if (!isname(fd->path))
275 /* ignore deleted files */
276 metadata_write_queue_remove(fd);
280 if (fd->change) continue; /* another operation in progress, skip this file for now */
282 to_approve = g_list_prepend(to_approve, file_data_ref(fd));
285 file_util_write_metadata(NULL, to_approve, NULL, force_dialog, done_func, done_data);
287 return (metadata_write_queue != NULL);
290 static gboolean metadata_write_queue_idle_cb(gpointer data)
292 metadata_write_queue_confirm(FALSE, NULL, NULL);
293 metadata_write_idle_id = 0;
297 gboolean metadata_write_perform(FileData *fd)
302 g_assert(fd->change);
304 if (fd->change->dest &&
305 strcmp(extension_from_path(fd->change->dest), GQ_CACHE_EXT_METADATA) == 0)
307 success = metadata_legacy_write(fd);
308 if (success) metadata_legacy_delete(fd, fd->change->dest);
312 /* write via exiv2 */
313 /* we can either use cached metadata which have fd->modified_xmp already applied
314 or read metadata from file and apply fd->modified_xmp
315 metadata are read also if the file was modified meanwhile */
316 exif = exif_read_fd(fd);
317 if (!exif) return FALSE;
319 success = (fd->change->dest) ? exif_write_sidecar(exif, fd->change->dest) : exif_write(exif); /* write modified metadata */
320 exif_free_fd(fd, exif);
322 if (fd->change->dest)
323 /* this will create a FileData for the sidecar and link it to the main file
324 (we can't wait until the sidecar is discovered by directory scanning because
325 exif_read_fd is called before that and it would read the main file only and
326 store the metadata in the cache)
327 FIXME: this does not catch new sidecars created by independent external programs
329 file_data_unref(file_data_new_group(fd->change->dest));
331 if (success) metadata_legacy_delete(fd, fd->change->dest);
335 gint metadata_queue_length(void)
337 return g_list_length(metadata_write_queue);
340 static gboolean metadata_check_key(const gchar *keys[], const gchar *key)
342 const gchar **k = keys;
346 if (strcmp(key, *k) == 0) return TRUE;
352 gboolean metadata_write_revert(FileData *fd, const gchar *key)
354 if (!fd->modified_xmp) return FALSE;
356 g_hash_table_remove(fd->modified_xmp, key);
358 if (g_hash_table_size(fd->modified_xmp) == 0)
360 metadata_write_queue_remove(fd);
364 /* reread the metadata to restore the original value */
365 file_data_increment_version(fd);
366 file_data_send_notification(fd, NOTIFY_REREAD);
371 gboolean metadata_write_list(FileData *fd, const gchar *key, const GList *values)
373 if (!fd->modified_xmp)
375 fd->modified_xmp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)string_list_free);
377 g_hash_table_insert(fd->modified_xmp, g_strdup(key), string_list_copy((GList *)values));
379 metadata_cache_remove(fd, key);
383 exif_update_metadata(fd->exif, key, values);
385 metadata_write_queue_add(fd);
386 file_data_increment_version(fd);
387 file_data_send_notification(fd, NOTIFY_METADATA);
389 if (options->metadata.sync_grouped_files && metadata_check_key(group_keys, key))
391 GList *work = fd->sidecar_files;
395 FileData *sfd = work->data;
398 if (filter_file_class(sfd->extension, FORMAT_CLASS_META)) continue;
400 metadata_write_list(sfd, key, values);
408 gboolean metadata_write_string(FileData *fd, const gchar *key, const char *value)
410 GList *list = g_list_append(NULL, g_strdup(value));
411 gboolean ret = metadata_write_list(fd, key, list);
412 string_list_free(list);
416 gboolean metadata_write_int(FileData *fd, const gchar *key, guint64 value)
420 g_snprintf(string, sizeof(string), "%llu", (unsigned long long) value);
421 return metadata_write_string(fd, key, string);
425 *-------------------------------------------------------------------
426 * keyword / comment read/write
427 *-------------------------------------------------------------------
430 static gboolean metadata_file_write(gchar *path, const GList *keywords, const gchar *comment)
434 ssi = secure_open(path);
435 if (!ssi) return FALSE;
437 secure_fprintf(ssi, "#%s comment (%s)\n\n", GQ_APPNAME, VERSION);
439 secure_fprintf(ssi, "[keywords]\n");
440 while (keywords && secsave_errno == SS_ERR_NONE)
442 const gchar *word = keywords->data;
443 keywords = keywords->next;
445 secure_fprintf(ssi, "%s\n", word);
447 secure_fputc(ssi, '\n');
449 secure_fprintf(ssi, "[comment]\n");
450 secure_fprintf(ssi, "%s\n", (comment) ? comment : "");
452 secure_fprintf(ssi, "#end\n");
454 return (secure_close(ssi) == 0);
457 static gboolean metadata_legacy_write(FileData *fd)
459 gboolean success = FALSE;
460 gchar *metadata_pathl;
463 gboolean have_keywords;
464 gboolean have_comment;
465 const gchar *comment;
466 GList *orig_keywords = NULL;
467 gchar *orig_comment = NULL;
469 g_assert(fd->change && fd->change->dest);
471 DEBUG_1("Saving comment: %s", fd->change->dest);
473 if (!fd->modified_xmp) return TRUE;
475 metadata_pathl = path_from_utf8(fd->change->dest);
477 have_keywords = g_hash_table_lookup_extended(fd->modified_xmp, KEYWORD_KEY, NULL, &keywords);
478 have_comment = g_hash_table_lookup_extended(fd->modified_xmp, COMMENT_KEY, NULL, &comment_l);
479 comment = (have_comment && comment_l) ? ((GList *)comment_l)->data : NULL;
481 if (!have_keywords || !have_comment) metadata_file_read(metadata_pathl, &orig_keywords, &orig_comment);
483 success = metadata_file_write(metadata_pathl,
484 have_keywords ? (GList *)keywords : orig_keywords,
485 have_comment ? comment : orig_comment);
487 g_free(metadata_pathl);
488 g_free(orig_comment);
489 string_list_free(orig_keywords);
494 static gboolean metadata_file_read(gchar *path, GList **keywords, gchar **comment)
498 MetadataKey key = MK_NONE;
500 GString *comment_build = NULL;
502 f = fopen(path, "r");
503 if (!f) return FALSE;
505 while (fgets(s_buf, sizeof(s_buf), f))
509 if (*ptr == '#') continue;
510 if (*ptr == '[' && key != MK_COMMENT)
512 gchar *keystr = ++ptr;
515 while (*ptr != ']' && *ptr != '\n' && *ptr != '\0') ptr++;
520 if (g_ascii_strcasecmp(keystr, "keywords") == 0)
522 else if (g_ascii_strcasecmp(keystr, "comment") == 0)
534 while (*ptr != '\n' && *ptr != '\0') ptr++;
536 if (strlen(s_buf) > 0)
538 gchar *kw = utf8_validate_or_convert(s_buf);
540 list = g_list_prepend(list, kw);
545 if (!comment_build) comment_build = g_string_new("");
546 g_string_append(comment_build, s_buf);
555 *keywords = g_list_reverse(list);
559 string_list_free(list);
567 gchar *ptr = comment_build->str;
569 /* strip leading and trailing newlines */
570 while (*ptr == '\n') ptr++;
572 while (len > 0 && ptr[len - 1] == '\n') len--;
573 if (ptr[len] == '\n') len++; /* keep the last one */
576 gchar *text = g_strndup(ptr, len);
578 *comment = utf8_validate_or_convert(text);
582 g_string_free(comment_build, TRUE);
588 static void metadata_legacy_delete(FileData *fd, const gchar *except)
590 gchar *metadata_path;
591 gchar *metadata_pathl;
594 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
595 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
597 metadata_pathl = path_from_utf8(metadata_path);
598 unlink(metadata_pathl);
599 g_free(metadata_pathl);
600 g_free(metadata_path);
604 /* without exiv2: do not delete xmp metadata because we are not able to convert it,
606 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
607 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
609 metadata_pathl = path_from_utf8(metadata_path);
610 unlink(metadata_pathl);
611 g_free(metadata_pathl);
612 g_free(metadata_path);
617 static gboolean metadata_legacy_read(FileData *fd, GList **keywords, gchar **comment)
619 gchar *metadata_path;
620 gchar *metadata_pathl;
621 gboolean success = FALSE;
623 if (!fd) return FALSE;
625 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
626 if (!metadata_path) return FALSE;
628 metadata_pathl = path_from_utf8(metadata_path);
630 success = metadata_file_read(metadata_pathl, keywords, comment);
632 g_free(metadata_pathl);
633 g_free(metadata_path);
638 static GList *remove_duplicate_strings_from_list(GList *list)
641 GHashTable *hashtable = g_hash_table_new(g_str_hash, g_str_equal);
642 GList *newlist = NULL;
646 gchar *key = work->data;
648 if (g_hash_table_lookup(hashtable, key) == NULL)
650 g_hash_table_insert(hashtable, (gpointer) key, GINT_TO_POINTER(1));
651 newlist = g_list_prepend(newlist, key);
656 g_hash_table_destroy(hashtable);
659 return g_list_reverse(newlist);
662 GList *metadata_read_list(FileData *fd, const gchar *key, MetadataFormat format)
666 const GList *cache_entry;
667 if (!fd) return NULL;
669 /* unwritten data overide everything */
670 if (fd->modified_xmp && format == METADATA_PLAIN)
672 list = g_hash_table_lookup(fd->modified_xmp, key);
673 if (list) return string_list_copy(list);
677 if (format == METADATA_PLAIN && strcmp(key, KEYWORD_KEY) == 0
678 && (cache_entry = metadata_cache_get(fd, key)))
680 return string_list_copy(cache_entry->next);
684 Legacy metadata file is the primary source if it exists.
685 Merging the lists does not make much sense, because the existence of
686 legacy metadata file indicates that the other metadata sources are not
687 writable and thus it would not be possible to delete the keywords
688 that comes from the image file.
690 if (strcmp(key, KEYWORD_KEY) == 0)
692 if (metadata_legacy_read(fd, &list, NULL))
694 if (format == METADATA_PLAIN)
696 metadata_cache_update(fd, key, list);
701 else if (strcmp(key, COMMENT_KEY) == 0)
703 gchar *comment = NULL;
704 if (metadata_legacy_read(fd, NULL, &comment)) return g_list_append(NULL, comment);
706 else if (strncmp(key, "file.", 5) == 0)
708 return g_list_append(NULL, metadata_file_info(fd, key, format));
711 exif = exif_read_fd(fd); /* this is cached, thus inexpensive */
712 if (!exif) return NULL;
713 list = exif_get_metadata(exif, key, format);
714 exif_free_fd(fd, exif);
716 if (format == METADATA_PLAIN && strcmp(key, KEYWORD_KEY) == 0)
718 metadata_cache_update(fd, key, list);
724 gchar *metadata_read_string(FileData *fd, const gchar *key, MetadataFormat format)
726 GList *string_list = metadata_read_list(fd, key, format);
729 gchar *str = string_list->data;
730 string_list->data = NULL;
731 string_list_free(string_list);
737 guint64 metadata_read_int(FileData *fd, const gchar *key, guint64 fallback)
741 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
742 if (!string) return fallback;
744 ret = g_ascii_strtoull(string, &endptr, 10);
745 if (string == endptr) ret = fallback;
750 gdouble metadata_read_GPS_coord(FileData *fd, const gchar *key, gdouble fallback)
754 gdouble deg, min, sec;
756 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
757 if (!string) return fallback;
759 deg = g_ascii_strtod(string, &endptr);
762 min = g_ascii_strtod(endptr + 1, &endptr);
764 sec = g_ascii_strtod(endptr + 1, &endptr);
769 if (*endptr == 'S' || *endptr == 'W' || *endptr == 'N' || *endptr == 'E')
771 coord = deg + min /60.0 + sec / 3600.0;
773 if (*endptr == 'S' || *endptr == 'W') coord = -coord;
780 log_printf("unable to parse GPS coordinate '%s'\n", string);
787 gboolean metadata_append_string(FileData *fd, const gchar *key, const char *value)
789 gchar *str = metadata_read_string(fd, key, METADATA_PLAIN);
793 return metadata_write_string(fd, key, value);
797 gchar *new_string = g_strconcat(str, value, NULL);
798 gboolean ret = metadata_write_string(fd, key, new_string);
805 gboolean metadata_append_list(FileData *fd, const gchar *key, const GList *values)
807 GList *list = metadata_read_list(fd, key, METADATA_PLAIN);
811 return metadata_write_list(fd, key, values);
816 list = g_list_concat(list, string_list_copy(values));
817 list = remove_duplicate_strings_from_list(list);
819 ret = metadata_write_list(fd, key, list);
820 string_list_free(list);
826 * \see find_string_in_list
828 gchar *find_string_in_list_utf8nocase(GList *list, const gchar *string)
830 gchar *string_casefold = g_utf8_casefold(string, -1);
834 gchar *haystack = list->data;
839 gchar *haystack_casefold = g_utf8_casefold(haystack, -1);
841 equal = (strcmp(haystack_casefold, string_casefold) == 0);
842 g_free(haystack_casefold);
846 g_free(string_casefold);
854 g_free(string_casefold);
859 * \see find_string_in_list
861 gchar *find_string_in_list_utf8case(GList *list, const gchar *string)
865 gchar *haystack = list->data;
867 if (haystack && strcmp(haystack, string) == 0)
874 } // gchar *find_string_in_list_utf...
877 * \brief Find a existent string in a list.
879 * This is a switch between find_string_in_list_utf8case and
880 * find_string_in_list_utf8nocase to search with or without case for the
881 * existence of a string.
883 * \param list The list to search in
884 * \param string The string to search for
885 * \return The string or NULL
887 * \see find_string_in_list_utf8case
888 * \see find_string_in_list_utf8nocase
890 gchar *find_string_in_list(GList *list, const gchar *string)
892 if (options->metadata.keywords_case_sensitive)
893 return find_string_in_list_utf8case(list, string);
895 return find_string_in_list_utf8nocase(list, string);
898 #define KEYWORDS_SEPARATOR(c) ((c) == ',' || (c) == ';' || (c) == '\n' || (c) == '\r' || (c) == '\b')
900 GList *string_to_keywords_list(const gchar *text)
903 const gchar *ptr = text;
910 while (KEYWORDS_SEPARATOR(*ptr)) ptr++;
912 while (*ptr != '\0' && !KEYWORDS_SEPARATOR(*ptr))
918 /* trim starting and ending whitespaces */
919 while (l > 0 && g_ascii_isspace(*begin)) begin++, l--;
920 while (l > 0 && g_ascii_isspace(begin[l-1])) l--;
924 gchar *keyword = g_strndup(begin, l);
926 /* only add if not already in the list */
927 if (!find_string_in_list(list, keyword))
928 list = g_list_append(list, keyword);
942 gboolean meta_data_get_keyword_mark(FileData *fd, gint n, gpointer data)
944 /* FIXME: do not use global keyword_tree */
947 gboolean found = FALSE;
948 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
952 if (keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path) &&
953 keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
960 gboolean meta_data_set_keyword_mark(FileData *fd, gint n, gboolean value, gpointer data)
963 GList *keywords = NULL;
966 if (!keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path)) return FALSE;
968 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
970 if (!!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords) != !!value)
974 keyword_tree_set(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
978 keyword_tree_reset(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
980 metadata_write_list(fd, KEYWORD_KEY, keywords);
983 string_list_free(keywords);
989 void meta_data_connect_mark_with_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *kw_iter, gint mark)
992 FileDataGetMarkFunc get_mark_func;
993 FileDataSetMarkFunc set_mark_func;
994 gpointer mark_func_data;
998 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
1000 file_data_get_registered_mark_func(i, &get_mark_func, &set_mark_func, &mark_func_data);
1001 if (get_mark_func == meta_data_get_keyword_mark)
1003 GtkTreeIter old_kw_iter;
1004 GList *old_path = mark_func_data;
1006 if (keyword_tree_get_iter(keyword_tree, &old_kw_iter, old_path) &&
1007 (i == mark || /* release any previous connection of given mark */
1008 keyword_compare(keyword_tree, &old_kw_iter, kw_iter) == 0)) /* or given keyword */
1010 file_data_register_mark_func(i, NULL, NULL, NULL, NULL);
1011 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), &old_kw_iter, KEYWORD_COLUMN_MARK, "", -1);
1017 if (mark >= 0 && mark < FILEDATA_MARKS_SIZE)
1021 path = keyword_tree_get_path(keyword_tree, kw_iter);
1022 file_data_register_mark_func(mark, meta_data_get_keyword_mark, meta_data_set_keyword_mark, path, (GDestroyNotify)string_list_free);
1024 mark_str = g_strdup_printf("%d", mark + 1);
1025 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), kw_iter, KEYWORD_COLUMN_MARK, mark_str, -1);
1032 *-------------------------------------------------------------------
1034 *-------------------------------------------------------------------
1039 GtkTreeStore *keyword_tree;
1041 gchar *keyword_get_name(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1044 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_NAME, &name, -1);
1048 gchar *keyword_get_casefold(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1051 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_CASEFOLD, &casefold, -1);
1055 gboolean keyword_get_is_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1057 gboolean is_keyword;
1058 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
1062 void keyword_set(GtkTreeStore *keyword_tree, GtkTreeIter *iter, const gchar *name, gboolean is_keyword)
1064 gchar *casefold = g_utf8_casefold(name, -1);
1065 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_MARK, "",
1066 KEYWORD_COLUMN_NAME, name,
1067 KEYWORD_COLUMN_CASEFOLD, casefold,
1068 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
1072 gboolean keyword_compare(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
1074 GtkTreePath *pa = gtk_tree_model_get_path(keyword_tree, a);
1075 GtkTreePath *pb = gtk_tree_model_get_path(keyword_tree, b);
1076 gint ret = gtk_tree_path_compare(pa, pb);
1077 gtk_tree_path_free(pa);
1078 gtk_tree_path_free(pb);
1082 gboolean keyword_same_parent(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
1084 GtkTreeIter parent_a;
1085 GtkTreeIter parent_b;
1087 gboolean valid_pa = gtk_tree_model_iter_parent(keyword_tree, &parent_a, a);
1088 gboolean valid_pb = gtk_tree_model_iter_parent(keyword_tree, &parent_b, b);
1090 if (valid_pa && valid_pb)
1092 return keyword_compare(keyword_tree, &parent_a, &parent_b) == 0;
1096 return (!valid_pa && !valid_pb); /* both are toplevel */
1100 gboolean keyword_exists(GtkTreeModel *keyword_tree, GtkTreeIter *parent_ptr, GtkTreeIter *sibling, const gchar *name, gboolean exclude_sibling, GtkTreeIter *result)
1104 gboolean toplevel = FALSE;
1110 parent = *parent_ptr;
1114 toplevel = !gtk_tree_model_iter_parent(keyword_tree, &parent, sibling);
1121 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &iter, toplevel ? NULL : &parent)) return FALSE;
1123 casefold = g_utf8_casefold(name, -1);
1128 if (!(exclude_sibling && sibling && keyword_compare(keyword_tree, &iter, sibling) == 0))
1130 if (options->metadata.keywords_case_sensitive)
1132 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
1133 ret = strcmp(name, iter_name) == 0;
1138 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
1139 ret = strcmp(casefold, iter_casefold) == 0;
1140 g_free(iter_casefold);
1141 } // if (options->metadata.tags_cas...
1145 if (result) *result = iter;
1148 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) break;
1155 void keyword_copy(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
1158 gchar *mark, *name, *casefold;
1159 gboolean is_keyword;
1161 /* do not copy KEYWORD_COLUMN_HIDE_IN, it fully shows the new subtree */
1162 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), from, KEYWORD_COLUMN_MARK, &mark,
1163 KEYWORD_COLUMN_NAME, &name,
1164 KEYWORD_COLUMN_CASEFOLD, &casefold,
1165 KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
1167 gtk_tree_store_set(keyword_tree, to, KEYWORD_COLUMN_MARK, mark,
1168 KEYWORD_COLUMN_NAME, name,
1169 KEYWORD_COLUMN_CASEFOLD, casefold,
1170 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
1176 void keyword_copy_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
1178 GtkTreeIter from_child;
1180 keyword_copy(keyword_tree, to, from);
1182 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &from_child, from)) return;
1186 GtkTreeIter to_child;
1187 gtk_tree_store_append(keyword_tree, &to_child, to);
1188 keyword_copy_recursive(keyword_tree, &to_child, &from_child);
1189 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &from_child)) return;
1193 void keyword_move_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
1195 keyword_copy_recursive(keyword_tree, to, from);
1196 keyword_delete(keyword_tree, from);
1199 GList *keyword_tree_get_path(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
1202 GtkTreeIter iter = *iter_ptr;
1207 path = g_list_prepend(path, keyword_get_name(keyword_tree, &iter));
1208 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) break;
1214 gboolean keyword_tree_get_iter(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList *path)
1218 if (!gtk_tree_model_get_iter_first(keyword_tree, &iter)) return FALSE;
1222 GtkTreeIter children;
1225 gchar *name = keyword_get_name(keyword_tree, &iter);
1226 if (strcmp(name, path->data) == 0) break;
1228 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return FALSE;
1237 if (!gtk_tree_model_iter_children(keyword_tree, &children, &iter)) return FALSE;
1243 static gboolean keyword_tree_is_set_casefold(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *casefold_list)
1245 if (!casefold_list) return FALSE;
1247 if (!keyword_get_is_keyword(keyword_tree, &iter))
1249 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1251 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1252 return FALSE; /* this should happen only on empty helpers */
1256 if (keyword_tree_is_set_casefold(keyword_tree, child, casefold_list)) return TRUE;
1257 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1265 if (keyword_get_is_keyword(keyword_tree, &iter))
1267 GList *work = casefold_list;
1268 gboolean found = FALSE;
1269 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
1272 const gchar *casefold = work->data;
1275 if (strcmp(iter_casefold, casefold) == 0)
1281 g_free(iter_casefold);
1282 if (!found) return FALSE;
1285 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1290 static gboolean keyword_tree_is_set_casefull(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *kw_list)
1292 if (!kw_list) return FALSE;
1294 if (!keyword_get_is_keyword(keyword_tree, &iter))
1296 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1298 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1299 return FALSE; /* this should happen only on empty helpers */
1303 if (keyword_tree_is_set_casefull(keyword_tree, child, kw_list)) return TRUE;
1304 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1312 if (keyword_get_is_keyword(keyword_tree, &iter))
1314 GList *work = kw_list;
1315 gboolean found = FALSE;
1316 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
1319 const gchar *name = work->data;
1322 if (strcmp(iter_name, name) == 0)
1329 if (!found) return FALSE;
1332 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1337 gboolean keyword_tree_is_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList *kw_list)
1340 GList *casefold_list = NULL;
1343 if (options->metadata.keywords_case_sensitive)
1345 ret = keyword_tree_is_set_casefull(keyword_tree, *iter, kw_list);
1352 const gchar *kw = work->data;
1355 casefold_list = g_list_prepend(casefold_list, g_utf8_casefold(kw, -1));
1358 ret = keyword_tree_is_set_casefold(keyword_tree, *iter, casefold_list);
1360 string_list_free(casefold_list);
1366 void keyword_tree_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1368 GtkTreeIter iter = *iter_ptr;
1373 if (keyword_get_is_keyword(keyword_tree, &iter))
1375 gchar *name = keyword_get_name(keyword_tree, &iter);
1376 if (!find_string_in_list(*kw_list, name))
1378 *kw_list = g_list_append(*kw_list, name);
1386 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1391 GList *keyword_tree_get(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
1393 GtkTreeIter iter = *iter_ptr;
1394 GList *kw_list = NULL;
1400 if (keyword_get_is_keyword(keyword_tree, &iter))
1402 gchar *name = keyword_get_name(keyword_tree, &iter);
1403 kw_list = g_list_append(kw_list, name);
1406 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return kw_list;
1409 } // GList *keyword_tree_get(GtkTre...
1411 static void keyword_tree_reset1(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1415 if (!keyword_get_is_keyword(keyword_tree, iter)) return;
1417 name = keyword_get_name(keyword_tree, iter);
1418 found = find_string_in_list(*kw_list, name);
1422 *kw_list = g_list_remove(*kw_list, found);
1428 static void keyword_tree_reset_recursive(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1431 keyword_tree_reset1(keyword_tree, iter, kw_list);
1433 if (!gtk_tree_model_iter_children(keyword_tree, &child, iter)) return;
1437 keyword_tree_reset_recursive(keyword_tree, &child, kw_list);
1438 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return;
1442 static gboolean keyword_tree_check_empty_children(GtkTreeModel *keyword_tree, GtkTreeIter *parent, GList *kw_list)
1446 if (!gtk_tree_model_iter_children(keyword_tree, &iter, parent))
1447 return TRUE; /* this should happen only on empty helpers */
1451 if (keyword_tree_is_set(keyword_tree, &iter, kw_list)) return FALSE;
1452 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return TRUE;
1456 void keyword_tree_reset(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1458 GtkTreeIter iter = *iter_ptr;
1460 keyword_tree_reset_recursive(keyword_tree, &iter, kw_list);
1462 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1465 while (keyword_tree_check_empty_children(keyword_tree, &iter, *kw_list))
1468 keyword_tree_reset1(keyword_tree, &iter, kw_list);
1469 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1474 void keyword_delete(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr)
1478 while (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, iter_ptr))
1480 keyword_delete(keyword_tree, &child);
1483 meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree), iter_ptr, -1);
1485 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter_ptr, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1488 gtk_tree_store_remove(keyword_tree, iter_ptr);
1492 void keyword_hide_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1495 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1496 if (!g_list_find(list, id))
1498 list = g_list_prepend(list, id);
1499 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1503 void keyword_show_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1506 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1507 list = g_list_remove(list, id);
1508 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1511 gboolean keyword_is_hidden_in(GtkTreeModel *keyword_tree, GtkTreeIter *iter, gpointer id)
1514 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1515 return !!g_list_find(list, id);
1518 static gboolean keyword_show_all_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
1520 keyword_show_in(GTK_TREE_STORE(model), iter, data);
1524 void keyword_show_all_in(GtkTreeStore *keyword_tree, gpointer id)
1526 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_all_in_cb, id);
1529 static void keyword_hide_unset_in_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr, gpointer id, GList *keywords)
1531 GtkTreeIter iter = *iter_ptr;
1534 if (!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
1536 keyword_hide_in(keyword_tree, &iter, id);
1537 /* no need to check children of hidden node */
1542 if (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, &iter))
1544 keyword_hide_unset_in_recursive(keyword_tree, &child, id, keywords);
1547 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1551 void keyword_hide_unset_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1554 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1555 keyword_hide_unset_in_recursive(keyword_tree, &iter, id, keywords);
1558 static gboolean keyword_show_set_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter_ptr, gpointer data)
1560 GtkTreeIter iter = *iter_ptr;
1561 GList *keywords = data;
1562 gpointer id = keywords->data;
1563 keywords = keywords->next; /* hack */
1564 if (keyword_tree_is_set(model, &iter, keywords))
1569 keyword_show_in(GTK_TREE_STORE(model), &iter, id);
1570 if (!gtk_tree_model_iter_parent(GTK_TREE_MODEL(keyword_tree), &parent, &iter)) break;
1577 void keyword_show_set_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1579 /* hack: pass id to keyword_hide_unset_in_cb in the list */
1580 keywords = g_list_prepend(keywords, id);
1581 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_set_in_cb, keywords);
1582 keywords = g_list_delete_link(keywords, keywords);
1586 void keyword_tree_new(void)
1588 if (keyword_tree) return;
1590 keyword_tree = gtk_tree_store_new(KEYWORD_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER);
1593 static GtkTreeIter keyword_tree_default_append(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar *name, gboolean is_keyword)
1596 gtk_tree_store_append(keyword_tree, &iter, parent);
1597 keyword_set(keyword_tree, &iter, name, is_keyword);
1601 void keyword_tree_new_default(void)
1605 if (!keyword_tree) keyword_tree_new();
1607 i1 = keyword_tree_default_append(keyword_tree, NULL, _("People"), TRUE);
1608 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Family"), TRUE);
1609 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Free time"), TRUE);
1610 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Children"), TRUE);
1611 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sport"), TRUE);
1612 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Culture"), TRUE);
1613 keyword_tree_default_append(keyword_tree, &i2, _("Festival"), TRUE);
1614 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Nature"), TRUE);
1615 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Animal"), TRUE);
1616 keyword_tree_default_append(keyword_tree, &i2, _("Bird"), TRUE);
1617 keyword_tree_default_append(keyword_tree, &i2, _("Insect"), TRUE);
1618 keyword_tree_default_append(keyword_tree, &i2, _("Pets"), TRUE);
1619 keyword_tree_default_append(keyword_tree, &i2, _("Wildlife"), TRUE);
1620 keyword_tree_default_append(keyword_tree, &i2, _("Zoo"), TRUE);
1621 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Plant"), TRUE);
1622 keyword_tree_default_append(keyword_tree, &i2, _("Tree"), TRUE);
1623 keyword_tree_default_append(keyword_tree, &i2, _("Flower"), TRUE);
1624 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Water"), TRUE);
1625 keyword_tree_default_append(keyword_tree, &i2, _("River"), TRUE);
1626 keyword_tree_default_append(keyword_tree, &i2, _("Lake"), TRUE);
1627 keyword_tree_default_append(keyword_tree, &i2, _("Sea"), TRUE);
1628 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Landscape"), TRUE);
1629 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Art"), TRUE);
1630 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Statue"), TRUE);
1631 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Painting"), TRUE);
1632 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1633 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1634 i1 = keyword_tree_default_append(keyword_tree, NULL, _("City"), TRUE);
1635 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Park"), TRUE);
1636 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Street"), TRUE);
1637 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Square"), TRUE);
1638 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Architecture"), TRUE);
1639 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Buildings"), FALSE);
1640 keyword_tree_default_append(keyword_tree, &i2, _("House"), TRUE);
1641 keyword_tree_default_append(keyword_tree, &i2, _("Cathedral"), TRUE);
1642 keyword_tree_default_append(keyword_tree, &i2, _("Palace"), TRUE);
1643 keyword_tree_default_append(keyword_tree, &i2, _("Castle"), TRUE);
1644 keyword_tree_default_append(keyword_tree, &i2, _("Bridge"), TRUE);
1645 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Interior"), TRUE);
1646 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1647 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1648 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Places"), FALSE);
1649 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Conditions"), FALSE);
1650 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Night"), TRUE);
1651 keyword_tree_default_append(keyword_tree, &i2, _("Lights"), TRUE);
1652 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Reflections"), TRUE);
1653 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sun"), TRUE);
1654 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Weather"), FALSE);
1655 keyword_tree_default_append(keyword_tree, &i2, _("Fog"), TRUE);
1656 keyword_tree_default_append(keyword_tree, &i2, _("Rain"), TRUE);
1657 keyword_tree_default_append(keyword_tree, &i2, _("Clouds"), TRUE);
1658 keyword_tree_default_append(keyword_tree, &i2, _("Snow"), TRUE);
1659 keyword_tree_default_append(keyword_tree, &i2, _("Sunny weather"), TRUE);
1660 i1 = keyword_tree_default_append(keyword_tree, NULL, _("Photo"), FALSE);
1661 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Edited"), TRUE);
1662 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Detail"), TRUE);
1663 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Macro"), TRUE);
1664 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Portrait"), TRUE);
1665 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Black and White"), TRUE);
1666 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Perspective"), TRUE);
1670 static void keyword_tree_node_write_config(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GString *outstr, gint indent)
1672 GtkTreeIter iter = *iter_ptr;
1675 GtkTreeIter children;
1678 WRITE_NL(); WRITE_STRING("<keyword ");
1679 name = keyword_get_name(keyword_tree, &iter);
1680 write_char_option(outstr, indent, "name", name);
1682 write_bool_option(outstr, indent, "kw", keyword_get_is_keyword(keyword_tree, &iter));
1683 if (gtk_tree_model_iter_children(keyword_tree, &children, &iter))
1687 keyword_tree_node_write_config(keyword_tree, &children, outstr, indent);
1689 WRITE_NL(); WRITE_STRING("</keyword>");
1695 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return;
1699 void keyword_tree_write_config(GString *outstr, gint indent)
1702 WRITE_NL(); WRITE_STRING("<keyword_tree>");
1705 if (keyword_tree && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1707 keyword_tree_node_write_config(GTK_TREE_MODEL(keyword_tree), &iter, outstr, indent);
1710 WRITE_NL(); WRITE_STRING("</keyword_tree>");
1713 GtkTreeIter *keyword_add_from_config(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar **attribute_names, const gchar **attribute_values)
1716 gboolean is_kw = TRUE;
1718 while (*attribute_names)
1720 const gchar *option = *attribute_names++;
1721 const gchar *value = *attribute_values++;
1723 if (READ_CHAR_FULL("name", name)) continue;
1724 if (READ_BOOL_FULL("kw", is_kw)) continue;
1726 log_printf("unknown attribute %s = %s\n", option, value);
1728 if (name && name[0])
1731 /* re-use existing keyword if any */
1732 if (!keyword_exists(GTK_TREE_MODEL(keyword_tree), parent, NULL, name, FALSE, &iter))
1734 gtk_tree_store_append(keyword_tree, &iter, parent);
1736 keyword_set(keyword_tree, &iter, name, is_kw);
1738 return gtk_tree_iter_copy(&iter);
1744 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */