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 /* do not copy KEYWORD_COLUMN_HIDE_IN, it fully shows the new subtree */
775 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), from, KEYWORD_COLUMN_MARK, &mark,
776 KEYWORD_COLUMN_NAME, &name,
777 KEYWORD_COLUMN_CASEFOLD, &casefold,
778 KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
780 gtk_tree_store_set(keyword_tree, to, KEYWORD_COLUMN_MARK, mark,
781 KEYWORD_COLUMN_NAME, name,
782 KEYWORD_COLUMN_CASEFOLD, casefold,
783 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
789 void keyword_copy_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
791 GtkTreeIter from_child;
793 keyword_copy(keyword_tree, to, from);
795 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &from_child, from)) return;
799 GtkTreeIter to_child;
800 gtk_tree_store_append(keyword_tree, &to_child, to);
801 keyword_copy_recursive(keyword_tree, &to_child, &from_child);
802 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &from_child)) return;
806 void keyword_move_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
808 keyword_copy_recursive(keyword_tree, to, from);
809 keyword_delete(keyword_tree, from);
812 GList *keyword_tree_get_path(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
815 GtkTreeIter iter = *iter_ptr;
820 path = g_list_prepend(path, keyword_get_name(keyword_tree, &iter));
821 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) break;
827 gboolean keyword_tree_get_iter(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList *path)
831 if (!gtk_tree_model_get_iter_first(keyword_tree, &iter)) return FALSE;
835 GtkTreeIter children;
838 gchar *name = keyword_get_name(keyword_tree, &iter);
839 if (strcmp(name, path->data) == 0) break;
841 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return FALSE;
850 if (!gtk_tree_model_iter_children(keyword_tree, &children, &iter)) return FALSE;
856 static gboolean keyword_tree_is_set_casefold(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *casefold_list)
858 if (!casefold_list) return FALSE;
864 if (keyword_get_is_keyword(keyword_tree, &iter))
866 GList *work = casefold_list;
867 gboolean found = FALSE;
868 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
871 const gchar *casefold = work->data;
874 if (strcmp(iter_casefold, casefold) == 0)
880 g_free(iter_casefold);
881 if (!found) return FALSE;
884 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
889 gboolean keyword_tree_is_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList *kw_list)
892 GList *casefold_list = NULL;
895 if (!keyword_get_is_keyword(keyword_tree, iter)) return FALSE;
900 const gchar *kw = work->data;
903 casefold_list = g_list_prepend(casefold_list, g_utf8_casefold(kw, -1));
906 ret = keyword_tree_is_set_casefold(keyword_tree, *iter, casefold_list);
908 string_list_free(casefold_list);
912 void keyword_tree_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
914 GtkTreeIter iter = *iter_ptr;
919 if (keyword_get_is_keyword(keyword_tree, &iter))
921 gchar *name = keyword_get_name(keyword_tree, &iter);
922 if (!find_string_in_list_utf8nocase(*kw_list, name))
924 *kw_list = g_list_append(*kw_list, name);
932 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
937 static void keyword_tree_reset1(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
941 if (!keyword_get_is_keyword(keyword_tree, iter)) return;
943 name = keyword_get_name(keyword_tree, iter);
944 found = find_string_in_list_utf8nocase(*kw_list, name);
948 *kw_list = g_list_remove(*kw_list, found);
954 static void keyword_tree_reset_recursive(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
957 keyword_tree_reset1(keyword_tree, iter, kw_list);
959 if (!gtk_tree_model_iter_children(keyword_tree, &child, iter)) return;
963 keyword_tree_reset_recursive(keyword_tree, &child, kw_list);
964 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return;
968 static gboolean keyword_tree_check_empty_children(GtkTreeModel *keyword_tree, GtkTreeIter *parent, GList *kw_list)
972 if (!gtk_tree_model_iter_children(keyword_tree, &iter, parent))
973 return TRUE; /* this should happen only on empty helpers */
977 if (keyword_get_is_keyword(keyword_tree, &iter))
979 if (keyword_tree_is_set(keyword_tree, &iter, kw_list)) return FALSE;
983 /* for helpers we have to check recursively */
984 if (!keyword_tree_check_empty_children(keyword_tree, &iter, kw_list)) return FALSE;
987 if (!gtk_tree_model_iter_next(keyword_tree, &iter))
994 void keyword_tree_reset(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
996 GtkTreeIter iter = *iter_ptr;
998 keyword_tree_reset_recursive(keyword_tree, &iter, kw_list);
1000 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1003 while (keyword_tree_check_empty_children(keyword_tree, &iter, *kw_list))
1006 keyword_tree_reset1(keyword_tree, &iter, kw_list);
1007 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1012 void keyword_delete(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr)
1016 while (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, iter_ptr))
1018 keyword_delete(keyword_tree, &child);
1021 meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree), iter_ptr, -1);
1023 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter_ptr, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1026 gtk_tree_store_remove(keyword_tree, iter_ptr);
1030 void keyword_hide_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1033 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1034 if (!g_list_find(list, id))
1036 list = g_list_prepend(list, id);
1037 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1041 void keyword_show_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1044 gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1045 list = g_list_remove(list, id);
1046 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1049 gboolean keyword_is_hidden_in(GtkTreeModel *keyword_tree, GtkTreeIter *iter, gpointer id)
1052 gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1053 return !!g_list_find(list, id);
1056 static gboolean keyword_show_all_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
1058 keyword_show_in(GTK_TREE_STORE(model), iter, data);
1062 void keyword_show_all_in(GtkTreeStore *keyword_tree, gpointer id)
1064 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_all_in_cb, id);
1067 static void keyword_hide_unset_in_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr, gpointer id, GList *keywords)
1069 GtkTreeIter iter = *iter_ptr;
1072 if (keyword_get_is_keyword(GTK_TREE_MODEL(keyword_tree), &iter) &&
1073 !keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
1075 keyword_hide_in(keyword_tree, &iter, id);
1076 /* no need to check children of hidden node */
1081 if (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, &iter))
1083 keyword_hide_unset_in_recursive(keyword_tree, &child, id, keywords);
1086 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1090 void keyword_hide_unset_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1093 gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter);
1094 keyword_hide_unset_in_recursive(keyword_tree, &iter, id, keywords);
1097 static gboolean keyword_show_set_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter_ptr, gpointer data)
1099 GtkTreeIter iter = *iter_ptr;
1100 GList *keywords = data;
1101 gpointer id = keywords->data;
1102 keywords = keywords->next; /* hack */
1103 if (keyword_tree_is_set(model, &iter, keywords))
1108 keyword_show_in(GTK_TREE_STORE(model), &iter, id);
1109 if (!gtk_tree_model_iter_parent(GTK_TREE_MODEL(keyword_tree), &parent, &iter)) break;
1116 void keyword_show_set_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1118 /* hack: pass id to keyword_hide_unset_in_cb in the list */
1119 keywords = g_list_prepend(keywords, id);
1120 gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_set_in_cb, keywords);
1121 keywords = g_list_delete_link(keywords, keywords);
1125 void keyword_tree_new(void)
1127 if (keyword_tree) return;
1129 keyword_tree = gtk_tree_store_new(KEYWORD_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER);
1133 void keyword_tree_new_default(void)
1135 if (keyword_tree) return;
1139 GtkTreeIter i1, i2, i3;
1141 gtk_tree_store_append(keyword_tree, &i1, NULL);
1142 keyword_set(keyword_tree, &i1, "animal", TRUE);
1144 gtk_tree_store_append(keyword_tree, &i2, &i1);
1145 keyword_set(keyword_tree, &i2, "mammal", TRUE);
1147 gtk_tree_store_append(keyword_tree, &i3, &i2);
1148 keyword_set(keyword_tree, &i3, "dog", TRUE);
1150 gtk_tree_store_append(keyword_tree, &i3, &i2);
1151 keyword_set(keyword_tree, &i3, "cat", TRUE);
1153 gtk_tree_store_append(keyword_tree, &i2, &i1);
1154 keyword_set(keyword_tree, &i2, "insect", TRUE);
1156 gtk_tree_store_append(keyword_tree, &i3, &i2);
1157 keyword_set(keyword_tree, &i3, "fly", TRUE);
1159 gtk_tree_store_append(keyword_tree, &i3, &i2);
1160 keyword_set(keyword_tree, &i3, "dragonfly", TRUE);
1162 gtk_tree_store_append(keyword_tree, &i1, NULL);
1163 keyword_set(keyword_tree, &i1, "daytime", FALSE);
1165 gtk_tree_store_append(keyword_tree, &i2, &i1);
1166 keyword_set(keyword_tree, &i2, "morning", TRUE);
1168 gtk_tree_store_append(keyword_tree, &i2, &i1);
1169 keyword_set(keyword_tree, &i2, "noon", TRUE);
1174 static void keyword_tree_node_write_config(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GString *outstr, gint indent)
1176 GtkTreeIter iter = *iter_ptr;
1179 GtkTreeIter children;
1182 WRITE_STRING("<keyword\n");
1184 name = keyword_get_name(keyword_tree, &iter);
1185 write_char_option(outstr, indent, "name", name);
1187 write_bool_option(outstr, indent, "kw", keyword_get_is_keyword(keyword_tree, &iter));
1189 WRITE_STRING(">\n");
1191 if (gtk_tree_model_iter_children(keyword_tree, &children, &iter))
1193 keyword_tree_node_write_config(keyword_tree, &children, outstr, indent);
1196 WRITE_STRING("</keyword>\n");
1197 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return;
1201 void keyword_tree_write_config(GString *outstr, gint indent)
1204 WRITE_STRING("<keyword_tree>\n");
1207 if (keyword_tree && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1209 keyword_tree_node_write_config(GTK_TREE_MODEL(keyword_tree), &iter, outstr, indent);
1212 WRITE_STRING("</keyword_tree>\n");
1215 GtkTreeIter *keyword_add_from_config(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar **attribute_names, const gchar **attribute_values)
1218 gboolean is_kw = TRUE;
1220 while (*attribute_names)
1222 const gchar *option = *attribute_names++;
1223 const gchar *value = *attribute_values++;
1225 if (READ_CHAR_FULL("name", name)) continue;
1226 if (READ_BOOL_FULL("kw", is_kw)) continue;
1228 DEBUG_1("unknown attribute %s = %s", option, value);
1230 if (name && name[0])
1233 gtk_tree_store_append(keyword_tree, &iter, parent);
1234 keyword_set(keyword_tree, &iter, name, is_kw);
1236 return gtk_tree_iter_copy(&iter);
1242 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */