4 * Copyright (C) 2008 - 2009 The Geeqie Team
6 * Author: John Ellis, Laurent Monin
8 * This software is released under the GNU General Public License (GNU GPL).
9 * Please read the included file COPYING for more information.
10 * This software comes with no warranty of any kind, use at your own risk!
21 #include "secure_save.h"
22 #include "ui_fileops.h"
25 #include "filefilter.h"
35 static const gchar *group_keys[] = {KEYWORD_KEY, COMMENT_KEY, NULL}; /* tags that will be written to all files in a group */
37 static gboolean metadata_write_queue_idle_cb(gpointer data);
38 static gint metadata_legacy_write(FileData *fd);
39 static void metadata_legacy_delete(FileData *fd, const gchar *except);
44 *-------------------------------------------------------------------
46 *-------------------------------------------------------------------
49 static GList *metadata_write_queue = NULL;
50 static gint metadata_write_idle_id = -1;
52 static void metadata_write_queue_add(FileData *fd)
54 if (!g_list_find(metadata_write_queue, fd))
56 metadata_write_queue = g_list_prepend(metadata_write_queue, fd);
59 layout_status_update_write_all();
62 if (metadata_write_idle_id != -1)
64 g_source_remove(metadata_write_idle_id);
65 metadata_write_idle_id = -1;
68 if (options->metadata.confirm_after_timeout)
70 metadata_write_idle_id = g_timeout_add(options->metadata.confirm_timeout * 1000, metadata_write_queue_idle_cb, NULL);
75 gboolean metadata_write_queue_remove(FileData *fd)
77 g_hash_table_destroy(fd->modified_xmp);
78 fd->modified_xmp = NULL;
80 metadata_write_queue = g_list_remove(metadata_write_queue, fd);
82 file_data_increment_version(fd);
83 file_data_send_notification(fd, NOTIFY_TYPE_REREAD);
87 layout_status_update_write_all();
91 gboolean metadata_write_queue_remove_list(GList *list)
99 FileData *fd = work->data;
101 ret = ret && metadata_write_queue_remove(fd);
107 gboolean metadata_write_queue_confirm(FileUtilDoneFunc done_func, gpointer done_data)
110 GList *to_approve = NULL;
112 work = metadata_write_queue;
115 FileData *fd = work->data;
118 if (fd->change) continue; /* another operation in progress, skip this file for now */
120 to_approve = g_list_prepend(to_approve, file_data_ref(fd));
123 file_util_write_metadata(NULL, to_approve, NULL, done_func, done_data);
125 filelist_free(to_approve);
127 return (metadata_write_queue != NULL);
130 static gboolean metadata_write_queue_idle_cb(gpointer data)
132 metadata_write_queue_confirm(NULL, NULL);
133 metadata_write_idle_id = -1;
137 gboolean metadata_write_perform(FileData *fd)
142 g_assert(fd->change);
144 if (fd->change->dest &&
145 strcmp(extension_from_path(fd->change->dest), GQ_CACHE_EXT_METADATA) == 0)
147 success = metadata_legacy_write(fd);
148 if (success) metadata_legacy_delete(fd, fd->change->dest);
152 /* write via exiv2 */
153 /* we can either use cached metadata which have fd->modified_xmp already applied
154 or read metadata from file and apply fd->modified_xmp
155 metadata are read also if the file was modified meanwhile */
156 exif = exif_read_fd(fd);
157 if (!exif) return FALSE;
159 success = (fd->change->dest) ? exif_write_sidecar(exif, fd->change->dest) : exif_write(exif); /* write modified metadata */
160 exif_free_fd(fd, exif);
162 if (fd->change->dest)
163 /* this will create a FileData for the sidecar and link it to the main file
164 (we can't wait until the sidecar is discovered by directory scanning because
165 exif_read_fd is called before that and it would read the main file only and
166 store the metadata in the cache)
167 FIXME: this does not catch new sidecars created by independent external programs
169 file_data_unref(file_data_new_simple(fd->change->dest));
171 if (success) metadata_legacy_delete(fd, fd->change->dest);
175 gint metadata_queue_length(void)
177 return g_list_length(metadata_write_queue);
180 static gboolean metadata_check_key(const gchar *keys[], const gchar *key)
182 const gchar **k = keys;
186 if (strcmp(key, *k) == 0) return TRUE;
192 gboolean metadata_write_list(FileData *fd, const gchar *key, const GList *values)
194 if (!fd->modified_xmp)
196 fd->modified_xmp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)string_list_free);
198 g_hash_table_insert(fd->modified_xmp, g_strdup(key), string_list_copy((GList *)values));
201 exif_update_metadata(fd->exif, key, values);
203 metadata_write_queue_add(fd);
204 file_data_increment_version(fd);
205 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
207 if (options->metadata.sync_grouped_files && metadata_check_key(group_keys, key))
209 GList *work = fd->sidecar_files;
213 FileData *sfd = work->data;
216 if (filter_file_class(sfd->extension, FORMAT_CLASS_META)) continue;
218 metadata_write_list(sfd, key, values);
226 gboolean metadata_write_string(FileData *fd, const gchar *key, const char *value)
228 GList *list = g_list_append(NULL, g_strdup(value));
229 gboolean ret = metadata_write_list(fd, key, list);
230 string_list_free(list);
236 *-------------------------------------------------------------------
237 * keyword / comment read/write
238 *-------------------------------------------------------------------
241 static gint metadata_file_write(gchar *path, GHashTable *modified_xmp)
244 GList *keywords = g_hash_table_lookup(modified_xmp, KEYWORD_KEY);
245 GList *comment_l = g_hash_table_lookup(modified_xmp, COMMENT_KEY);
246 gchar *comment = comment_l ? comment_l->data : NULL;
248 ssi = secure_open(path);
249 if (!ssi) return FALSE;
251 secure_fprintf(ssi, "#%s comment (%s)\n\n", GQ_APPNAME, VERSION);
253 secure_fprintf(ssi, "[keywords]\n");
254 while (keywords && secsave_errno == SS_ERR_NONE)
256 const gchar *word = keywords->data;
257 keywords = keywords->next;
259 secure_fprintf(ssi, "%s\n", word);
261 secure_fputc(ssi, '\n');
263 secure_fprintf(ssi, "[comment]\n");
264 secure_fprintf(ssi, "%s\n", (comment) ? comment : "");
266 secure_fprintf(ssi, "#end\n");
268 return (secure_close(ssi) == 0);
271 static gint metadata_legacy_write(FileData *fd)
273 gint success = FALSE;
275 g_assert(fd->change && fd->change->dest);
276 gchar *metadata_pathl;
278 DEBUG_1("Saving comment: %s", fd->change->dest);
280 metadata_pathl = path_from_utf8(fd->change->dest);
282 success = metadata_file_write(metadata_pathl, fd->modified_xmp);
284 g_free(metadata_pathl);
289 static gint metadata_file_read(gchar *path, GList **keywords, gchar **comment)
293 MetadataKey key = MK_NONE;
295 GString *comment_build = NULL;
297 f = fopen(path, "r");
298 if (!f) return FALSE;
300 while (fgets(s_buf, sizeof(s_buf), f))
304 if (*ptr == '#') continue;
305 if (*ptr == '[' && key != MK_COMMENT)
307 gchar *keystr = ++ptr;
310 while (*ptr != ']' && *ptr != '\n' && *ptr != '\0') ptr++;
315 if (g_ascii_strcasecmp(keystr, "keywords") == 0)
317 else if (g_ascii_strcasecmp(keystr, "comment") == 0)
329 while (*ptr != '\n' && *ptr != '\0') ptr++;
331 if (strlen(s_buf) > 0)
333 gchar *kw = utf8_validate_or_convert(s_buf);
335 list = g_list_prepend(list, kw);
340 if (!comment_build) comment_build = g_string_new("");
341 g_string_append(comment_build, s_buf);
350 *keywords = g_list_reverse(list);
354 string_list_free(list);
362 gchar *ptr = comment_build->str;
364 /* strip leading and trailing newlines */
365 while (*ptr == '\n') ptr++;
367 while (len > 0 && ptr[len - 1] == '\n') len--;
368 if (ptr[len] == '\n') len++; /* keep the last one */
371 gchar *text = g_strndup(ptr, len);
373 *comment = utf8_validate_or_convert(text);
377 g_string_free(comment_build, TRUE);
383 static void metadata_legacy_delete(FileData *fd, const gchar *except)
385 gchar *metadata_path;
386 gchar *metadata_pathl;
389 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
390 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
392 metadata_pathl = path_from_utf8(metadata_path);
393 unlink(metadata_pathl);
394 g_free(metadata_pathl);
395 g_free(metadata_path);
397 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
398 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
400 metadata_pathl = path_from_utf8(metadata_path);
401 unlink(metadata_pathl);
402 g_free(metadata_pathl);
403 g_free(metadata_path);
407 static gint metadata_legacy_read(FileData *fd, GList **keywords, gchar **comment)
409 gchar *metadata_path;
410 gchar *metadata_pathl;
411 gint success = FALSE;
412 if (!fd) return FALSE;
414 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
415 if (!metadata_path) return FALSE;
417 metadata_pathl = path_from_utf8(metadata_path);
419 success = metadata_file_read(metadata_pathl, keywords, comment);
421 g_free(metadata_pathl);
422 g_free(metadata_path);
427 static GList *remove_duplicate_strings_from_list(GList *list)
430 GHashTable *hashtable = g_hash_table_new(g_str_hash, g_str_equal);
431 GList *newlist = NULL;
435 gchar *key = work->data;
437 if (g_hash_table_lookup(hashtable, key) == NULL)
439 g_hash_table_insert(hashtable, (gpointer) key, GINT_TO_POINTER(1));
440 newlist = g_list_prepend(newlist, key);
445 g_hash_table_destroy(hashtable);
448 return g_list_reverse(newlist);
451 GList *metadata_read_list(FileData *fd, const gchar *key, MetadataFormat format)
455 if (!fd) return NULL;
457 /* unwritten data overide everything */
458 if (fd->modified_xmp && format == METADATA_PLAIN)
460 list = g_hash_table_lookup(fd->modified_xmp, key);
461 if (list) return string_list_copy(list);
465 Legacy metadata file is the primary source if it exists.
466 Merging the lists does not make much sense, because the existence of
467 legacy metadata file indicates that the other metadata sources are not
468 writable and thus it would not be possible to delete the keywords
469 that comes from the image file.
471 if (strcmp(key, KEYWORD_KEY) == 0)
473 if (metadata_legacy_read(fd, &list, NULL)) return list;
476 if (strcmp(key, COMMENT_KEY) == 0)
478 gchar *comment = NULL;
479 if (metadata_legacy_read(fd, NULL, &comment)) return g_list_append(NULL, comment);
482 exif = exif_read_fd(fd); /* this is cached, thus inexpensive */
483 if (!exif) return NULL;
484 list = exif_get_metadata(exif, key, format);
485 exif_free_fd(fd, exif);
489 gchar *metadata_read_string(FileData *fd, const gchar *key, MetadataFormat format)
491 GList *string_list = metadata_read_list(fd, key, format);
494 gchar *str = string_list->data;
495 string_list->data = NULL;
496 string_list_free(string_list);
502 guint64 metadata_read_int(FileData *fd, const gchar *key, guint64 fallback)
506 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
507 if (!string) return fallback;
509 ret = g_ascii_strtoull(string, &endptr, 10);
510 if (string == endptr) ret = fallback;
515 gboolean metadata_append_string(FileData *fd, const gchar *key, const char *value)
517 gchar *str = metadata_read_string(fd, key, METADATA_PLAIN);
521 return metadata_write_string(fd, key, value);
525 gchar *new_string = g_strconcat(str, value, NULL);
526 gboolean ret = metadata_write_string(fd, key, new_string);
533 gboolean metadata_append_list(FileData *fd, const gchar *key, const GList *values)
535 GList *list = metadata_read_list(fd, key, METADATA_PLAIN);
539 return metadata_write_list(fd, key, values);
544 list = g_list_concat(list, string_list_copy(values));
545 list = remove_duplicate_strings_from_list(list);
547 ret = metadata_write_list(fd, key, list);
548 string_list_free(list);
553 gchar *find_string_in_list_utf8nocase(GList *list, const gchar *string)
555 gchar *string_casefold = g_utf8_casefold(string, -1);
559 gchar *haystack = list->data;
564 gchar *haystack_casefold = g_utf8_casefold(haystack, -1);
566 equal = (strcmp(haystack_casefold, string_casefold) == 0);
567 g_free(haystack_casefold);
571 g_free(string_casefold);
579 g_free(string_casefold);
584 #define KEYWORDS_SEPARATOR(c) ((c) == ',' || (c) == ';' || (c) == '\n' || (c) == '\r' || (c) == '\b')
586 GList *string_to_keywords_list(const gchar *text)
589 const gchar *ptr = text;
596 while (KEYWORDS_SEPARATOR(*ptr)) ptr++;
598 while (*ptr != '\0' && !KEYWORDS_SEPARATOR(*ptr))
604 /* trim starting and ending whitespaces */
605 while (l > 0 && g_ascii_isspace(*begin)) begin++, l--;
606 while (l > 0 && g_ascii_isspace(begin[l-1])) l--;
610 gchar *keyword = g_strndup(begin, l);
612 /* only add if not already in the list */
613 if (!find_string_in_list_utf8nocase(list, keyword))
614 list = g_list_append(list, keyword);
628 gboolean meta_data_get_keyword_mark(FileData *fd, gint n, gpointer data)
630 /* FIXME: do not use global keyword_tree */
633 gboolean found = FALSE;
634 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
638 if (keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path) &&
639 keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
646 gboolean meta_data_set_keyword_mark(FileData *fd, gint n, gboolean value, gpointer data)
649 GList *keywords = NULL;
652 if (!keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path)) return FALSE;
654 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
656 if (!!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords) != !!value)
660 keyword_tree_set(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
664 keyword_tree_reset(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
666 metadata_write_list(fd, KEYWORD_KEY, keywords);
669 string_list_free(keywords);
675 void meta_data_connect_mark_with_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *kw_iter, gint mark)
678 FileDataGetMarkFunc get_mark_func;
679 FileDataSetMarkFunc set_mark_func;
680 gpointer mark_func_data;
684 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
686 file_data_get_registered_mark_func(i, &get_mark_func, &set_mark_func, &mark_func_data);
687 if (get_mark_func == meta_data_get_keyword_mark)
689 GtkTreeIter old_kw_iter;
690 GList *old_path = mark_func_data;
692 if (keyword_tree_get_iter(keyword_tree, &old_kw_iter, old_path) &&
693 (i == mark || /* release any previous connection of given mark */
694 keyword_compare(keyword_tree, &old_kw_iter, kw_iter) == 0)) /* or given keyword */
696 file_data_register_mark_func(i, NULL, NULL, NULL, NULL);
697 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), &old_kw_iter, KEYWORD_COLUMN_MARK, "", -1);
703 if (mark >= 0 && mark < FILEDATA_MARKS_SIZE)
707 path = keyword_tree_get_path(keyword_tree, kw_iter);
708 file_data_register_mark_func(mark, meta_data_get_keyword_mark, meta_data_set_keyword_mark, path, (GDestroyNotify)string_list_free);
710 mark_str = g_strdup_printf("%d", mark + 1);
711 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), kw_iter, KEYWORD_COLUMN_MARK, mark_str, -1);
718 *-------------------------------------------------------------------
720 *-------------------------------------------------------------------
725 GtkTreeStore *keyword_tree;
727 gchar *keyword_get_name(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
730 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_NAME, &name, -1);
734 gchar *keyword_get_casefold(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
737 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_CASEFOLD, &casefold, -1);
741 gboolean keyword_get_is_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
744 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
748 void keyword_set(GtkTreeStore *keyword_tree, GtkTreeIter *iter, const gchar *name, gboolean is_keyword)
750 gchar *casefold = g_utf8_casefold(name, -1);
751 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_MARK, "",
752 KEYWORD_COLUMN_NAME, name,
753 KEYWORD_COLUMN_CASEFOLD, casefold,
754 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
758 gboolean keyword_compare(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
760 GtkTreePath *pa = gtk_tree_model_get_path(keyword_tree, a);
761 GtkTreePath *pb = gtk_tree_model_get_path(keyword_tree, b);
762 gint ret = gtk_tree_path_compare(pa, pb);
763 gtk_tree_path_free(pa);
764 gtk_tree_path_free(pb);
768 void keyword_copy(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
771 gchar *mark, *name, *casefold;
774 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), from, KEYWORD_COLUMN_MARK, &mark,
775 KEYWORD_COLUMN_NAME, &name,
776 KEYWORD_COLUMN_CASEFOLD, &casefold,
777 KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
779 gtk_tree_store_set(keyword_tree, to, KEYWORD_COLUMN_MARK, mark,
780 KEYWORD_COLUMN_NAME, name,
781 KEYWORD_COLUMN_CASEFOLD, casefold,
782 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
788 void keyword_copy_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
790 GtkTreeIter from_child;
792 keyword_copy(keyword_tree, to, from);
794 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &from_child, from)) return;
798 GtkTreeIter to_child;
799 gtk_tree_store_append(keyword_tree, &to_child, to);
800 keyword_copy_recursive(keyword_tree, &to_child, &from_child);
801 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &from_child)) return;
805 void keyword_move_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
807 keyword_copy_recursive(keyword_tree, to, from);
808 gtk_tree_store_remove(keyword_tree, from);
811 GList *keyword_tree_get_path(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
814 GtkTreeIter iter = *iter_ptr;
819 path = g_list_prepend(path, keyword_get_name(keyword_tree, &iter));
820 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) break;
826 gboolean keyword_tree_get_iter(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList *path)
830 if (!gtk_tree_model_get_iter_first(keyword_tree, &iter)) return FALSE;
834 GtkTreeIter children;
837 gchar *name = keyword_get_name(keyword_tree, &iter);
838 if (strcmp(name, path->data) == 0) break;
840 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return FALSE;
849 if (!gtk_tree_model_iter_children(keyword_tree, &children, &iter)) return FALSE;
855 static gboolean keyword_tree_is_set_casefold(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *casefold_list)
857 if (!casefold_list) return FALSE;
863 if (keyword_get_is_keyword(keyword_tree, &iter))
865 GList *work = casefold_list;
866 gboolean found = FALSE;
867 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
870 const gchar *casefold = work->data;
873 if (strcmp(iter_casefold, casefold) == 0)
879 g_free(iter_casefold);
880 if (!found) return FALSE;
883 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
888 gboolean keyword_tree_is_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList *kw_list)
891 GList *casefold_list = NULL;
894 if (!keyword_get_is_keyword(keyword_tree, iter)) return FALSE;
899 const gchar *kw = work->data;
902 casefold_list = g_list_prepend(casefold_list, g_utf8_casefold(kw, -1));
905 ret = keyword_tree_is_set_casefold(keyword_tree, *iter, casefold_list);
907 string_list_free(casefold_list);
911 void keyword_tree_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
913 GtkTreeIter iter = *iter_ptr;
918 if (keyword_get_is_keyword(keyword_tree, &iter))
920 gchar *name = keyword_get_name(keyword_tree, &iter);
921 if (!find_string_in_list_utf8nocase(*kw_list, name))
923 *kw_list = g_list_append(*kw_list, name);
931 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
936 static void keyword_tree_reset1(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
940 if (!keyword_get_is_keyword(keyword_tree, iter)) return;
942 name = keyword_get_name(keyword_tree, iter);
943 found = find_string_in_list_utf8nocase(*kw_list, name);
947 *kw_list = g_list_remove(*kw_list, found);
953 static void keyword_tree_reset_recursive(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
956 keyword_tree_reset1(keyword_tree, iter, kw_list);
958 if (!gtk_tree_model_iter_children(keyword_tree, &child, iter)) return;
962 keyword_tree_reset_recursive(keyword_tree, &child, kw_list);
963 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return;
967 static gboolean keyword_tree_check_empty_children(GtkTreeModel *keyword_tree, GtkTreeIter *parent, GList *kw_list)
971 if (!gtk_tree_model_iter_children(keyword_tree, &iter, parent))
972 return TRUE; /* this should happen only on empty helpers */
976 if (keyword_get_is_keyword(keyword_tree, &iter))
978 if (keyword_tree_is_set(keyword_tree, &iter, kw_list)) return FALSE;
982 /* for helpers we have to check recursively */
983 if (!keyword_tree_check_empty_children(keyword_tree, &iter, kw_list)) return FALSE;
986 if (!gtk_tree_model_iter_next(keyword_tree, &iter))
993 void keyword_tree_reset(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
995 GtkTreeIter iter = *iter_ptr;
997 keyword_tree_reset_recursive(keyword_tree, &iter, kw_list);
999 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1002 while (keyword_tree_check_empty_children(keyword_tree, &iter, *kw_list))
1005 keyword_tree_reset1(keyword_tree, &iter, kw_list);
1006 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1011 void keyword_delete(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr)
1013 gtk_tree_store_remove(keyword_tree, iter_ptr);
1017 void keyword_tree_new(void)
1019 if (keyword_tree) return;
1021 keyword_tree = gtk_tree_store_new(KEYWORD_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
1025 void keyword_tree_new_default(void)
1027 if (keyword_tree) return;
1031 GtkTreeIter i1, i2, i3;
1033 gtk_tree_store_append(keyword_tree, &i1, NULL);
1034 keyword_set(keyword_tree, &i1, "animal", TRUE);
1036 gtk_tree_store_append(keyword_tree, &i2, &i1);
1037 keyword_set(keyword_tree, &i2, "mammal", TRUE);
1039 gtk_tree_store_append(keyword_tree, &i3, &i2);
1040 keyword_set(keyword_tree, &i3, "dog", TRUE);
1042 gtk_tree_store_append(keyword_tree, &i3, &i2);
1043 keyword_set(keyword_tree, &i3, "cat", TRUE);
1045 gtk_tree_store_append(keyword_tree, &i2, &i1);
1046 keyword_set(keyword_tree, &i2, "insect", TRUE);
1048 gtk_tree_store_append(keyword_tree, &i3, &i2);
1049 keyword_set(keyword_tree, &i3, "fly", TRUE);
1051 gtk_tree_store_append(keyword_tree, &i3, &i2);
1052 keyword_set(keyword_tree, &i3, "dragonfly", TRUE);
1054 gtk_tree_store_append(keyword_tree, &i1, NULL);
1055 keyword_set(keyword_tree, &i1, "daytime", FALSE);
1057 gtk_tree_store_append(keyword_tree, &i2, &i1);
1058 keyword_set(keyword_tree, &i2, "morning", TRUE);
1060 gtk_tree_store_append(keyword_tree, &i2, &i1);
1061 keyword_set(keyword_tree, &i2, "noon", TRUE);
1066 static void keyword_tree_node_write_config(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GString *outstr, gint indent)
1068 GtkTreeIter iter = *iter_ptr;
1071 GtkTreeIter children;
1074 WRITE_STRING("<keyword\n");
1076 name = keyword_get_name(keyword_tree, &iter);
1077 write_char_option(outstr, indent, "name", name);
1079 write_bool_option(outstr, indent, "kw", keyword_get_is_keyword(keyword_tree, &iter));
1081 WRITE_STRING(">\n");
1083 if (gtk_tree_model_iter_children(keyword_tree, &children, &iter))
1085 keyword_tree_node_write_config(keyword_tree, &children, outstr, indent);
1088 WRITE_STRING("</keyword>\n");
1089 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return;
1093 void keyword_tree_write_config(GString *outstr, gint indent)
1096 WRITE_STRING("<keyword_tree>\n");
1099 if (keyword_tree && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1101 keyword_tree_node_write_config(GTK_TREE_MODEL(keyword_tree), &iter, outstr, indent);
1104 WRITE_STRING("</keyword_tree>\n");
1107 GtkTreeIter *keyword_add_from_config(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar **attribute_names, const gchar **attribute_values)
1110 gboolean is_kw = TRUE;
1112 while (*attribute_names)
1114 const gchar *option = *attribute_names++;
1115 const gchar *value = *attribute_values++;
1117 if (READ_CHAR_FULL("name", name)) continue;
1118 if (READ_BOOL_FULL("kw", is_kw)) continue;
1120 DEBUG_1("unknown attribute %s = %s", option, value);
1122 if (name && name[0])
1125 gtk_tree_store_append(keyword_tree, &iter, parent);
1126 keyword_set(keyword_tree, &iter, name, is_kw);
1128 return gtk_tree_iter_copy(&iter);
1134 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */