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 gboolean 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_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_METADATA);
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 gboolean 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 gboolean metadata_legacy_write(FileData *fd)
273 gboolean success = FALSE;
274 gchar *metadata_pathl;
276 g_assert(fd->change && fd->change->dest);
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 gboolean 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 gboolean metadata_legacy_read(FileData *fd, GList **keywords, gchar **comment)
409 gchar *metadata_path;
410 gchar *metadata_pathl;
411 gboolean success = FALSE;
413 if (!fd) return FALSE;
415 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
416 if (!metadata_path) return FALSE;
418 metadata_pathl = path_from_utf8(metadata_path);
420 success = metadata_file_read(metadata_pathl, keywords, comment);
422 g_free(metadata_pathl);
423 g_free(metadata_path);
428 static GList *remove_duplicate_strings_from_list(GList *list)
431 GHashTable *hashtable = g_hash_table_new(g_str_hash, g_str_equal);
432 GList *newlist = NULL;
436 gchar *key = work->data;
438 if (g_hash_table_lookup(hashtable, key) == NULL)
440 g_hash_table_insert(hashtable, (gpointer) key, GINT_TO_POINTER(1));
441 newlist = g_list_prepend(newlist, key);
446 g_hash_table_destroy(hashtable);
449 return g_list_reverse(newlist);
452 GList *metadata_read_list(FileData *fd, const gchar *key, MetadataFormat format)
456 if (!fd) return NULL;
458 /* unwritten data overide everything */
459 if (fd->modified_xmp && format == METADATA_PLAIN)
461 list = g_hash_table_lookup(fd->modified_xmp, key);
462 if (list) return string_list_copy(list);
466 Legacy metadata file is the primary source if it exists.
467 Merging the lists does not make much sense, because the existence of
468 legacy metadata file indicates that the other metadata sources are not
469 writable and thus it would not be possible to delete the keywords
470 that comes from the image file.
472 if (strcmp(key, KEYWORD_KEY) == 0)
474 if (metadata_legacy_read(fd, &list, NULL)) return list;
477 if (strcmp(key, COMMENT_KEY) == 0)
479 gchar *comment = NULL;
480 if (metadata_legacy_read(fd, NULL, &comment)) return g_list_append(NULL, comment);
483 exif = exif_read_fd(fd); /* this is cached, thus inexpensive */
484 if (!exif) return NULL;
485 list = exif_get_metadata(exif, key, format);
486 exif_free_fd(fd, exif);
490 gchar *metadata_read_string(FileData *fd, const gchar *key, MetadataFormat format)
492 GList *string_list = metadata_read_list(fd, key, format);
495 gchar *str = string_list->data;
496 string_list->data = NULL;
497 string_list_free(string_list);
503 guint64 metadata_read_int(FileData *fd, const gchar *key, guint64 fallback)
507 gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
508 if (!string) return fallback;
510 ret = g_ascii_strtoull(string, &endptr, 10);
511 if (string == endptr) ret = fallback;
516 gboolean metadata_append_string(FileData *fd, const gchar *key, const char *value)
518 gchar *str = metadata_read_string(fd, key, METADATA_PLAIN);
522 return metadata_write_string(fd, key, value);
526 gchar *new_string = g_strconcat(str, value, NULL);
527 gboolean ret = metadata_write_string(fd, key, new_string);
534 gboolean metadata_append_list(FileData *fd, const gchar *key, const GList *values)
536 GList *list = metadata_read_list(fd, key, METADATA_PLAIN);
540 return metadata_write_list(fd, key, values);
545 list = g_list_concat(list, string_list_copy(values));
546 list = remove_duplicate_strings_from_list(list);
548 ret = metadata_write_list(fd, key, list);
549 string_list_free(list);
554 gchar *find_string_in_list_utf8nocase(GList *list, const gchar *string)
556 gchar *string_casefold = g_utf8_casefold(string, -1);
560 gchar *haystack = list->data;
565 gchar *haystack_casefold = g_utf8_casefold(haystack, -1);
567 equal = (strcmp(haystack_casefold, string_casefold) == 0);
568 g_free(haystack_casefold);
572 g_free(string_casefold);
580 g_free(string_casefold);
585 #define KEYWORDS_SEPARATOR(c) ((c) == ',' || (c) == ';' || (c) == '\n' || (c) == '\r' || (c) == '\b')
587 GList *string_to_keywords_list(const gchar *text)
590 const gchar *ptr = text;
597 while (KEYWORDS_SEPARATOR(*ptr)) ptr++;
599 while (*ptr != '\0' && !KEYWORDS_SEPARATOR(*ptr))
605 /* trim starting and ending whitespaces */
606 while (l > 0 && g_ascii_isspace(*begin)) begin++, l--;
607 while (l > 0 && g_ascii_isspace(begin[l-1])) l--;
611 gchar *keyword = g_strndup(begin, l);
613 /* only add if not already in the list */
614 if (!find_string_in_list_utf8nocase(list, keyword))
615 list = g_list_append(list, keyword);
629 gboolean meta_data_get_keyword_mark(FileData *fd, gint n, gpointer data)
631 /* FIXME: do not use global keyword_tree */
634 gboolean found = FALSE;
635 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
639 if (keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path) &&
640 keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
647 gboolean meta_data_set_keyword_mark(FileData *fd, gint n, gboolean value, gpointer data)
650 GList *keywords = NULL;
653 if (!keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path)) return FALSE;
655 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
657 if (!!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords) != !!value)
661 keyword_tree_set(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
665 keyword_tree_reset(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
667 metadata_write_list(fd, KEYWORD_KEY, keywords);
670 string_list_free(keywords);
676 void meta_data_connect_mark_with_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *kw_iter, gint mark)
679 FileDataGetMarkFunc get_mark_func;
680 FileDataSetMarkFunc set_mark_func;
681 gpointer mark_func_data;
685 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
687 file_data_get_registered_mark_func(i, &get_mark_func, &set_mark_func, &mark_func_data);
688 if (get_mark_func == meta_data_get_keyword_mark)
690 GtkTreeIter old_kw_iter;
691 GList *old_path = mark_func_data;
693 if (keyword_tree_get_iter(keyword_tree, &old_kw_iter, old_path) &&
694 (i == mark || /* release any previous connection of given mark */
695 keyword_compare(keyword_tree, &old_kw_iter, kw_iter) == 0)) /* or given keyword */
697 file_data_register_mark_func(i, NULL, NULL, NULL, NULL);
698 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), &old_kw_iter, KEYWORD_COLUMN_MARK, "", -1);
704 if (mark >= 0 && mark < FILEDATA_MARKS_SIZE)
708 path = keyword_tree_get_path(keyword_tree, kw_iter);
709 file_data_register_mark_func(mark, meta_data_get_keyword_mark, meta_data_set_keyword_mark, path, (GDestroyNotify)string_list_free);
711 mark_str = g_strdup_printf("%d", mark + 1);
712 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), kw_iter, KEYWORD_COLUMN_MARK, mark_str, -1);
719 *-------------------------------------------------------------------
721 *-------------------------------------------------------------------
726 GtkTreeStore *keyword_tree;
728 gchar *keyword_get_name(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
731 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_NAME, &name, -1);
735 gchar *keyword_get_casefold(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
738 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_CASEFOLD, &casefold, -1);
742 gboolean keyword_get_is_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
745 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
749 void keyword_set(GtkTreeStore *keyword_tree, GtkTreeIter *iter, const gchar *name, gboolean is_keyword)
751 gchar *casefold = g_utf8_casefold(name, -1);
752 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_MARK, "",
753 KEYWORD_COLUMN_NAME, name,
754 KEYWORD_COLUMN_CASEFOLD, casefold,
755 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
759 gboolean keyword_compare(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
761 GtkTreePath *pa = gtk_tree_model_get_path(keyword_tree, a);
762 GtkTreePath *pb = gtk_tree_model_get_path(keyword_tree, b);
763 gint ret = gtk_tree_path_compare(pa, pb);
764 gtk_tree_path_free(pa);
765 gtk_tree_path_free(pb);
769 gboolean keyword_same_parent(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
771 GtkTreeIter parent_a;
772 GtkTreeIter parent_b;
774 gboolean valid_pa = gtk_tree_model_iter_parent(keyword_tree, &parent_a, a);
775 gboolean valid_pb = gtk_tree_model_iter_parent(keyword_tree, &parent_b, b);
777 if (valid_pa && valid_pb)
779 return keyword_compare(keyword_tree, &parent_a, &parent_b) == 0;
783 return (!valid_pa && !valid_pb); /* both are toplevel */
787 gboolean keyword_exists(GtkTreeModel *keyword_tree, GtkTreeIter *parent_ptr, GtkTreeIter *sibling, const gchar *name, gboolean exclude_sibling)
791 gboolean toplevel = FALSE;
797 parent = *parent_ptr;
801 toplevel = !gtk_tree_model_iter_parent(keyword_tree, &parent, sibling);
808 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &iter, toplevel ? NULL : &parent)) return FALSE;
810 casefold = g_utf8_casefold(name, -1);
815 if (!(exclude_sibling && sibling && keyword_compare(keyword_tree, &iter, sibling) == 0))
817 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
818 ret = strcmp(casefold, iter_casefold) == 0;
819 g_free(iter_casefold);
822 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) break;
829 void keyword_copy(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
832 gchar *mark, *name, *casefold;
835 /* do not copy KEYWORD_COLUMN_HIDE_IN, it fully shows the new subtree */
836 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), from, KEYWORD_COLUMN_MARK, &mark,
837 KEYWORD_COLUMN_NAME, &name,
838 KEYWORD_COLUMN_CASEFOLD, &casefold,
839 KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
841 gtk_tree_store_set(keyword_tree, to, KEYWORD_COLUMN_MARK, mark,
842 KEYWORD_COLUMN_NAME, name,
843 KEYWORD_COLUMN_CASEFOLD, casefold,
844 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
850 void keyword_copy_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
852 GtkTreeIter from_child;
854 keyword_copy(keyword_tree, to, from);
856 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &from_child, from)) return;
860 GtkTreeIter to_child;
861 gtk_tree_store_append(keyword_tree, &to_child, to);
862 keyword_copy_recursive(keyword_tree, &to_child, &from_child);
863 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &from_child)) return;
867 void keyword_move_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
869 keyword_copy_recursive(keyword_tree, to, from);
870 keyword_delete(keyword_tree, from);
873 GList *keyword_tree_get_path(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
876 GtkTreeIter iter = *iter_ptr;
881 path = g_list_prepend(path, keyword_get_name(keyword_tree, &iter));
882 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) break;
888 gboolean keyword_tree_get_iter(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList *path)
892 if (!gtk_tree_model_get_iter_first(keyword_tree, &iter)) return FALSE;
896 GtkTreeIter children;
899 gchar *name = keyword_get_name(keyword_tree, &iter);
900 if (strcmp(name, path->data) == 0) break;
902 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return FALSE;
911 if (!gtk_tree_model_iter_children(keyword_tree, &children, &iter)) return FALSE;
917 static gboolean keyword_tree_is_set_casefold(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *casefold_list)
919 if (!casefold_list) return FALSE;
921 if (!keyword_get_is_keyword(keyword_tree, &iter))
923 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
925 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
926 return FALSE; /* this should happen only on empty helpers */
930 if (keyword_tree_is_set_casefold(keyword_tree, child, casefold_list)) return TRUE;
931 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
939 if (keyword_get_is_keyword(keyword_tree, &iter))
941 GList *work = casefold_list;
942 gboolean found = FALSE;
943 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
946 const gchar *casefold = work->data;
949 if (strcmp(iter_casefold, casefold) == 0)
955 g_free(iter_casefold);
956 if (!found) return FALSE;
959 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
964 gboolean keyword_tree_is_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList *kw_list)
967 GList *casefold_list = NULL;
973 const gchar *kw = work->data;
976 casefold_list = g_list_prepend(casefold_list, g_utf8_casefold(kw, -1));
979 ret = keyword_tree_is_set_casefold(keyword_tree, *iter, casefold_list);
981 string_list_free(casefold_list);
985 void keyword_tree_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
987 GtkTreeIter iter = *iter_ptr;
992 if (keyword_get_is_keyword(keyword_tree, &iter))
994 gchar *name = keyword_get_name(keyword_tree, &iter);
995 if (!find_string_in_list_utf8nocase(*kw_list, name))
997 *kw_list = g_list_append(*kw_list, name);
1005 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1010 static void keyword_tree_reset1(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1014 if (!keyword_get_is_keyword(keyword_tree, iter)) return;
1016 name = keyword_get_name(keyword_tree, iter);
1017 found = find_string_in_list_utf8nocase(*kw_list, name);
1021 *kw_list = g_list_remove(*kw_list, found);
1027 static void keyword_tree_reset_recursive(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1030 keyword_tree_reset1(keyword_tree, iter, kw_list);
1032 if (!gtk_tree_model_iter_children(keyword_tree, &child, iter)) return;
1036 keyword_tree_reset_recursive(keyword_tree, &child, kw_list);
1037 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return;
1041 static gboolean keyword_tree_check_empty_children(GtkTreeModel *keyword_tree, GtkTreeIter *parent, GList *kw_list)
1045 if (!gtk_tree_model_iter_children(keyword_tree, &iter, parent))
1046 return TRUE; /* this should happen only on empty helpers */
1050 if (keyword_tree_is_set(keyword_tree, &iter, kw_list)) return FALSE;
1051 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return TRUE;
1055 void keyword_tree_reset(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1057 GtkTreeIter iter = *iter_ptr;
1059 keyword_tree_reset_recursive(keyword_tree, &iter, kw_list);
1061 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1064 while (keyword_tree_check_empty_children(keyword_tree, &iter, *kw_list))
1067 keyword_tree_reset1(keyword_tree, &iter, kw_list);
1068 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1073 void keyword_delete(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr)
1077 while (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, iter_ptr))
1079 keyword_delete(keyword_tree, &child);
1082 meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree), iter_ptr, -1);
1084 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter_ptr, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1087 gtk_tree_store_remove(keyword_tree, iter_ptr);
1091 void keyword_hide_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1094 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1095 if (!g_list_find(list, id))
1097 list = g_list_prepend(list, id);
1098 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1102 void keyword_show_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1105 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1106 list = g_list_remove(list, id);
1107 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1110 gboolean keyword_is_hidden_in(GtkTreeModel *keyword_tree, GtkTreeIter *iter, gpointer id)
1113 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1114 return !!g_list_find(list, id);
1117 static gboolean keyword_show_all_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
1119 keyword_show_in(GTK_TREE_STORE(model), iter, data);
1123 void keyword_show_all_in(GtkTreeStore *keyword_tree, gpointer id)
1125 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_all_in_cb, id);
1128 static void keyword_hide_unset_in_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr, gpointer id, GList *keywords)
1130 GtkTreeIter iter = *iter_ptr;
1133 if (!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
1135 keyword_hide_in(keyword_tree, &iter, id);
1136 /* no need to check children of hidden node */
1141 if (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, &iter))
1143 keyword_hide_unset_in_recursive(keyword_tree, &child, id, keywords);
1146 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1150 void keyword_hide_unset_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1153 gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter);
1154 keyword_hide_unset_in_recursive(keyword_tree, &iter, id, keywords);
1157 static gboolean keyword_show_set_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter_ptr, gpointer data)
1159 GtkTreeIter iter = *iter_ptr;
1160 GList *keywords = data;
1161 gpointer id = keywords->data;
1162 keywords = keywords->next; /* hack */
1163 if (keyword_tree_is_set(model, &iter, keywords))
1168 keyword_show_in(GTK_TREE_STORE(model), &iter, id);
1169 if (!gtk_tree_model_iter_parent(GTK_TREE_MODEL(keyword_tree), &parent, &iter)) break;
1176 void keyword_show_set_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1178 /* hack: pass id to keyword_hide_unset_in_cb in the list */
1179 keywords = g_list_prepend(keywords, id);
1180 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_set_in_cb, keywords);
1181 keywords = g_list_delete_link(keywords, keywords);
1185 void keyword_tree_new(void)
1187 if (keyword_tree) return;
1189 keyword_tree = gtk_tree_store_new(KEYWORD_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER);
1193 void keyword_tree_new_default(void)
1195 if (keyword_tree) return;
1199 GtkTreeIter i1, i2, i3;
1201 gtk_tree_store_append(keyword_tree, &i1, NULL);
1202 keyword_set(keyword_tree, &i1, "animal", TRUE);
1204 gtk_tree_store_append(keyword_tree, &i2, &i1);
1205 keyword_set(keyword_tree, &i2, "mammal", TRUE);
1207 gtk_tree_store_append(keyword_tree, &i3, &i2);
1208 keyword_set(keyword_tree, &i3, "dog", TRUE);
1210 gtk_tree_store_append(keyword_tree, &i3, &i2);
1211 keyword_set(keyword_tree, &i3, "cat", TRUE);
1213 gtk_tree_store_append(keyword_tree, &i2, &i1);
1214 keyword_set(keyword_tree, &i2, "insect", TRUE);
1216 gtk_tree_store_append(keyword_tree, &i3, &i2);
1217 keyword_set(keyword_tree, &i3, "fly", TRUE);
1219 gtk_tree_store_append(keyword_tree, &i3, &i2);
1220 keyword_set(keyword_tree, &i3, "dragonfly", TRUE);
1222 gtk_tree_store_append(keyword_tree, &i1, NULL);
1223 keyword_set(keyword_tree, &i1, "daytime", FALSE);
1225 gtk_tree_store_append(keyword_tree, &i2, &i1);
1226 keyword_set(keyword_tree, &i2, "morning", TRUE);
1228 gtk_tree_store_append(keyword_tree, &i2, &i1);
1229 keyword_set(keyword_tree, &i2, "noon", TRUE);
1234 static void keyword_tree_node_write_config(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GString *outstr, gint indent)
1236 GtkTreeIter iter = *iter_ptr;
1239 GtkTreeIter children;
1242 WRITE_NL(); WRITE_STRING("<keyword ");
1243 name = keyword_get_name(keyword_tree, &iter);
1244 write_char_option(outstr, indent, "name", name);
1246 write_bool_option(outstr, indent, "kw", keyword_get_is_keyword(keyword_tree, &iter));
1247 if (gtk_tree_model_iter_children(keyword_tree, &children, &iter))
1251 keyword_tree_node_write_config(keyword_tree, &children, outstr, indent);
1253 WRITE_NL(); WRITE_STRING("</keyword>");
1259 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return;
1263 void keyword_tree_write_config(GString *outstr, gint indent)
1266 WRITE_NL(); WRITE_STRING("<keyword_tree>");
1269 if (keyword_tree && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1271 keyword_tree_node_write_config(GTK_TREE_MODEL(keyword_tree), &iter, outstr, indent);
1274 WRITE_NL(); WRITE_STRING("</keyword_tree>");
1277 GtkTreeIter *keyword_add_from_config(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar **attribute_names, const gchar **attribute_values)
1280 gboolean is_kw = TRUE;
1282 while (*attribute_names)
1284 const gchar *option = *attribute_names++;
1285 const gchar *value = *attribute_values++;
1287 if (READ_CHAR_FULL("name", name)) continue;
1288 if (READ_BOOL_FULL("kw", is_kw)) continue;
1290 DEBUG_1("unknown attribute %s = %s", option, value);
1292 if (name && name[0])
1295 gtk_tree_store_append(keyword_tree, &iter, parent);
1296 keyword_set(keyword_tree, &iter, name, is_kw);
1298 return gtk_tree_iter_copy(&iter);
1304 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */