added an option to write image orientation to the metadata
[geeqie.git] / src / metadata.c
1 /*
2  * Geeqie
3  * (C) 2004 John Ellis
4  * Copyright (C) 2008 - 2009 The Geeqie Team
5  *
6  * Author: John Ellis, Laurent Monin
7  *
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!
11  */
12
13
14 #include "main.h"
15 #include "metadata.h"
16
17 #include "cache.h"
18 #include "exif.h"
19 #include "filedata.h"
20 #include "misc.h"
21 #include "secure_save.h"
22 #include "ui_fileops.h"
23 #include "ui_misc.h"
24 #include "utilops.h"
25 #include "filefilter.h"
26 #include "layout.h"
27 #include "rcfile.h"
28
29 typedef enum {
30         MK_NONE,
31         MK_KEYWORDS,
32         MK_COMMENT
33 } MetadataKey;
34
35 static const gchar *group_keys[] = {KEYWORD_KEY, COMMENT_KEY, NULL}; /* tags that will be written to all files in a group */
36
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);
40
41
42
43 /*
44  *-------------------------------------------------------------------
45  * write queue
46  *-------------------------------------------------------------------
47  */
48
49 static GList *metadata_write_queue = NULL;
50 static guint metadata_write_idle_id = 0; /* event source id */
51
52 static void metadata_write_queue_add(FileData *fd)
53 {
54         if (!g_list_find(metadata_write_queue, fd))
55                 {
56                 metadata_write_queue = g_list_prepend(metadata_write_queue, fd);
57                 file_data_ref(fd);
58                 
59                 layout_status_update_write_all();
60                 }
61
62         if (metadata_write_idle_id) 
63                 {
64                 g_source_remove(metadata_write_idle_id);
65                 metadata_write_idle_id = 0;
66                 }
67         
68         if (options->metadata.confirm_after_timeout)
69                 {
70                 metadata_write_idle_id = g_timeout_add(options->metadata.confirm_timeout * 1000, metadata_write_queue_idle_cb, NULL);
71                 }
72 }
73
74
75 gboolean metadata_write_queue_remove(FileData *fd)
76 {
77         g_hash_table_destroy(fd->modified_xmp);
78         fd->modified_xmp = NULL;
79
80         metadata_write_queue = g_list_remove(metadata_write_queue, fd);
81         
82         file_data_increment_version(fd);
83         file_data_send_notification(fd, NOTIFY_REREAD);
84
85         file_data_unref(fd);
86
87         layout_status_update_write_all();
88         return TRUE;
89 }
90
91 gboolean metadata_write_queue_remove_list(GList *list)
92 {
93         GList *work;
94         gboolean ret = TRUE;
95         
96         work = list;
97         while (work)
98                 {
99                 FileData *fd = work->data;
100                 work = work->next;
101                 ret = ret && metadata_write_queue_remove(fd);
102                 }
103         return ret;
104 }
105
106
107 gboolean metadata_write_queue_confirm(FileUtilDoneFunc done_func, gpointer done_data)
108 {
109         GList *work;
110         GList *to_approve = NULL;
111         
112         work = metadata_write_queue;
113         while (work)
114                 {
115                 FileData *fd = work->data;
116                 work = work->next;
117                 
118                 if (fd->change) continue; /* another operation in progress, skip this file for now */
119                 
120                 to_approve = g_list_prepend(to_approve, file_data_ref(fd));
121                 }
122
123         file_util_write_metadata(NULL, to_approve, NULL, done_func, done_data);
124         
125         filelist_free(to_approve);
126         
127         return (metadata_write_queue != NULL);
128 }
129
130 static gboolean metadata_write_queue_idle_cb(gpointer data)
131 {
132         metadata_write_queue_confirm(NULL, NULL);
133         metadata_write_idle_id = 0;
134         return FALSE;
135 }
136
137 gboolean metadata_write_perform(FileData *fd)
138 {
139         gboolean success;
140         ExifData *exif;
141         
142         g_assert(fd->change);
143         
144         if (fd->change->dest && 
145             strcmp(extension_from_path(fd->change->dest), GQ_CACHE_EXT_METADATA) == 0)
146                 {
147                 success = metadata_legacy_write(fd);
148                 if (success) metadata_legacy_delete(fd, fd->change->dest);
149                 return success;
150                 }
151
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;
158
159         success = (fd->change->dest) ? exif_write_sidecar(exif, fd->change->dest) : exif_write(exif); /* write modified metadata */
160         exif_free_fd(fd, exif);
161
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
168                 */
169                 file_data_unref(file_data_new_simple(fd->change->dest)); 
170                 
171         if (success) metadata_legacy_delete(fd, fd->change->dest);
172         return success;
173 }
174
175 gint metadata_queue_length(void)
176 {
177         return g_list_length(metadata_write_queue);
178 }
179
180 static gboolean metadata_check_key(const gchar *keys[], const gchar *key)
181 {
182         const gchar **k = keys;
183         
184         while (*k)
185                 {
186                 if (strcmp(key, *k) == 0) return TRUE;
187                 k++;
188                 }
189         return FALSE;
190 }
191
192 gboolean metadata_write_revert(FileData *fd, const gchar *key)
193 {
194         if (!fd->modified_xmp) return FALSE;
195         
196         g_hash_table_remove(fd->modified_xmp, key);
197         
198         if (g_hash_table_size(fd->modified_xmp) == 0)
199                 {
200                 metadata_write_queue_remove(fd);
201                 }
202         else
203                 {
204                 /* reread the metadata to restore the original value */
205                 file_data_increment_version(fd);
206                 file_data_send_notification(fd, NOTIFY_REREAD);
207                 }
208 }
209
210 gboolean metadata_write_list(FileData *fd, const gchar *key, const GList *values)
211 {
212         if (!fd->modified_xmp)
213                 {
214                 fd->modified_xmp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)string_list_free);
215                 }
216         g_hash_table_insert(fd->modified_xmp, g_strdup(key), string_list_copy((GList *)values));
217         if (fd->exif)
218                 {
219                 exif_update_metadata(fd->exif, key, values);
220                 }
221         metadata_write_queue_add(fd);
222         file_data_increment_version(fd);
223         file_data_send_notification(fd, NOTIFY_METADATA);
224
225         if (options->metadata.sync_grouped_files && metadata_check_key(group_keys, key))
226                 {
227                 GList *work = fd->sidecar_files;
228                 
229                 while (work)
230                         {
231                         FileData *sfd = work->data;
232                         work = work->next;
233                         
234                         if (filter_file_class(sfd->extension, FORMAT_CLASS_META)) continue; 
235
236                         metadata_write_list(sfd, key, values);
237                         }
238                 }
239
240
241         return TRUE;
242 }
243         
244 gboolean metadata_write_string(FileData *fd, const gchar *key, const char *value)
245 {
246         GList *list = g_list_append(NULL, g_strdup(value));
247         gboolean ret = metadata_write_list(fd, key, list);
248         string_list_free(list);
249         return ret;
250 }
251
252 gboolean metadata_write_int(FileData *fd, const gchar *key, guint64 value)
253 {
254         gchar string[50];
255         
256         g_snprintf(string, sizeof(string), "%ld", value);
257         return metadata_write_string(fd, key, string);
258 }
259
260 /*
261  *-------------------------------------------------------------------
262  * keyword / comment read/write
263  *-------------------------------------------------------------------
264  */
265
266 static gboolean metadata_file_write(gchar *path, GHashTable *modified_xmp)
267 {
268         SecureSaveInfo *ssi;
269         GList *keywords = g_hash_table_lookup(modified_xmp, KEYWORD_KEY);
270         GList *comment_l = g_hash_table_lookup(modified_xmp, COMMENT_KEY);
271         gchar *comment = comment_l ? comment_l->data : NULL;
272
273         ssi = secure_open(path);
274         if (!ssi) return FALSE;
275
276         secure_fprintf(ssi, "#%s comment (%s)\n\n", GQ_APPNAME, VERSION);
277
278         secure_fprintf(ssi, "[keywords]\n");
279         while (keywords && secsave_errno == SS_ERR_NONE)
280                 {
281                 const gchar *word = keywords->data;
282                 keywords = keywords->next;
283
284                 secure_fprintf(ssi, "%s\n", word);
285                 }
286         secure_fputc(ssi, '\n');
287
288         secure_fprintf(ssi, "[comment]\n");
289         secure_fprintf(ssi, "%s\n", (comment) ? comment : "");
290
291         secure_fprintf(ssi, "#end\n");
292
293         return (secure_close(ssi) == 0);
294 }
295
296 static gboolean metadata_legacy_write(FileData *fd)
297 {
298         gboolean success = FALSE;
299         gchar *metadata_pathl;
300
301         g_assert(fd->change && fd->change->dest);
302
303         DEBUG_1("Saving comment: %s", fd->change->dest);
304
305         metadata_pathl = path_from_utf8(fd->change->dest);
306
307         success = metadata_file_write(metadata_pathl, fd->modified_xmp);
308
309         g_free(metadata_pathl);
310
311         return success;
312 }
313
314 static gboolean metadata_file_read(gchar *path, GList **keywords, gchar **comment)
315 {
316         FILE *f;
317         gchar s_buf[1024];
318         MetadataKey key = MK_NONE;
319         GList *list = NULL;
320         GString *comment_build = NULL;
321
322         f = fopen(path, "r");
323         if (!f) return FALSE;
324
325         while (fgets(s_buf, sizeof(s_buf), f))
326                 {
327                 gchar *ptr = s_buf;
328
329                 if (*ptr == '#') continue;
330                 if (*ptr == '[' && key != MK_COMMENT)
331                         {
332                         gchar *keystr = ++ptr;
333                         
334                         key = MK_NONE;
335                         while (*ptr != ']' && *ptr != '\n' && *ptr != '\0') ptr++;
336                         
337                         if (*ptr == ']')
338                                 {
339                                 *ptr = '\0';
340                                 if (g_ascii_strcasecmp(keystr, "keywords") == 0)
341                                         key = MK_KEYWORDS;
342                                 else if (g_ascii_strcasecmp(keystr, "comment") == 0)
343                                         key = MK_COMMENT;
344                                 }
345                         continue;
346                         }
347                 
348                 switch (key)
349                         {
350                         case MK_NONE:
351                                 break;
352                         case MK_KEYWORDS:
353                                 {
354                                 while (*ptr != '\n' && *ptr != '\0') ptr++;
355                                 *ptr = '\0';
356                                 if (strlen(s_buf) > 0)
357                                         {
358                                         gchar *kw = utf8_validate_or_convert(s_buf);
359
360                                         list = g_list_prepend(list, kw);
361                                         }
362                                 }
363                                 break;
364                         case MK_COMMENT:
365                                 if (!comment_build) comment_build = g_string_new("");
366                                 g_string_append(comment_build, s_buf);
367                                 break;
368                         }
369                 }
370         
371         fclose(f);
372
373         if (keywords) 
374                 {
375                 *keywords = g_list_reverse(list);
376                 }
377         else
378                 {
379                 string_list_free(list);
380                 }
381                 
382         if (comment_build)
383                 {
384                 if (comment)
385                         {
386                         gint len;
387                         gchar *ptr = comment_build->str;
388
389                         /* strip leading and trailing newlines */
390                         while (*ptr == '\n') ptr++;
391                         len = strlen(ptr);
392                         while (len > 0 && ptr[len - 1] == '\n') len--;
393                         if (ptr[len] == '\n') len++; /* keep the last one */
394                         if (len > 0)
395                                 {
396                                 gchar *text = g_strndup(ptr, len);
397
398                                 *comment = utf8_validate_or_convert(text);
399                                 g_free(text);
400                                 }
401                         }
402                 g_string_free(comment_build, TRUE);
403                 }
404
405         return TRUE;
406 }
407
408 static void metadata_legacy_delete(FileData *fd, const gchar *except)
409 {
410         gchar *metadata_path;
411         gchar *metadata_pathl;
412         if (!fd) return;
413
414         metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
415         if (metadata_path && (!except || strcmp(metadata_path, except) != 0)) 
416                 {
417                 metadata_pathl = path_from_utf8(metadata_path);
418                 unlink(metadata_pathl);
419                 g_free(metadata_pathl);
420                 g_free(metadata_path);
421                 }
422         metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
423         if (metadata_path && (!except || strcmp(metadata_path, except) != 0)) 
424                 {
425                 metadata_pathl = path_from_utf8(metadata_path);
426                 unlink(metadata_pathl);
427                 g_free(metadata_pathl);
428                 g_free(metadata_path);
429                 }
430 }
431
432 static gboolean metadata_legacy_read(FileData *fd, GList **keywords, gchar **comment)
433 {
434         gchar *metadata_path;
435         gchar *metadata_pathl;
436         gboolean success = FALSE;
437         
438         if (!fd) return FALSE;
439
440         metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
441         if (!metadata_path) return FALSE;
442
443         metadata_pathl = path_from_utf8(metadata_path);
444
445         success = metadata_file_read(metadata_pathl, keywords, comment);
446
447         g_free(metadata_pathl);
448         g_free(metadata_path);
449
450         return success;
451 }
452
453 static GList *remove_duplicate_strings_from_list(GList *list)
454 {
455         GList *work = list;
456         GHashTable *hashtable = g_hash_table_new(g_str_hash, g_str_equal);
457         GList *newlist = NULL;
458
459         while (work)
460                 {
461                 gchar *key = work->data;
462
463                 if (g_hash_table_lookup(hashtable, key) == NULL)
464                         {
465                         g_hash_table_insert(hashtable, (gpointer) key, GINT_TO_POINTER(1));
466                         newlist = g_list_prepend(newlist, key);
467                         }
468                 work = work->next;
469                 }
470
471         g_hash_table_destroy(hashtable);
472         g_list_free(list);
473
474         return g_list_reverse(newlist);
475 }
476
477 GList *metadata_read_list(FileData *fd, const gchar *key, MetadataFormat format)
478 {
479         ExifData *exif;
480         GList *list = NULL;
481         if (!fd) return NULL;
482
483         /* unwritten data overide everything */
484         if (fd->modified_xmp && format == METADATA_PLAIN)
485                 {
486                 list = g_hash_table_lookup(fd->modified_xmp, key);
487                 if (list) return string_list_copy(list);
488                 }
489
490         /* 
491             Legacy metadata file is the primary source if it exists.
492             Merging the lists does not make much sense, because the existence of
493             legacy metadata file indicates that the other metadata sources are not
494             writable and thus it would not be possible to delete the keywords
495             that comes from the image file.
496         */
497         if (strcmp(key, KEYWORD_KEY) == 0)
498                 {
499                 if (metadata_legacy_read(fd, &list, NULL)) return list;
500                 }
501         else if (strcmp(key, COMMENT_KEY) == 0)
502                 {
503                 gchar *comment = NULL;
504                 if (metadata_legacy_read(fd, NULL, &comment)) return g_list_append(NULL, comment);
505                 }
506         else if (strncmp(key, "file.", 5) == 0)
507                 {
508                 return g_list_append(NULL, metadata_file_info(fd, key, format));
509                 }
510         
511         exif = exif_read_fd(fd); /* this is cached, thus inexpensive */
512         if (!exif) return NULL;
513         list = exif_get_metadata(exif, key, format);
514         exif_free_fd(fd, exif);
515         return list;
516 }
517
518 gchar *metadata_read_string(FileData *fd, const gchar *key, MetadataFormat format)
519 {
520         GList *string_list = metadata_read_list(fd, key, format);
521         if (string_list)
522                 {
523                 gchar *str = string_list->data;
524                 string_list->data = NULL;
525                 string_list_free(string_list);
526                 return str;
527                 }
528         return NULL;
529 }
530
531 guint64 metadata_read_int(FileData *fd, const gchar *key, guint64 fallback)
532 {
533         guint64 ret;
534         gchar *endptr;
535         gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
536         if (!string) return fallback;
537         
538         ret = g_ascii_strtoull(string, &endptr, 10);
539         if (string == endptr) ret = fallback;
540         g_free(string);
541         return ret;
542 }
543
544 gdouble metadata_read_GPS_coord(FileData *fd, const gchar *key, gdouble fallback)
545 {
546         gdouble coord;
547         gchar *endptr;
548         gdouble deg, min, sec;
549         gboolean ok = FALSE;
550         gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
551         if (!string) return fallback;
552         
553         deg = g_ascii_strtod(string, &endptr);
554         if (*endptr == ',')
555                 {
556                 min = g_ascii_strtod(endptr + 1, &endptr);
557                 if (*endptr == ',')
558                         sec = g_ascii_strtod(endptr + 1, &endptr);
559                 else 
560                         sec = 0.0;
561                 
562                 
563                 if (*endptr == 'S' || *endptr == 'W' || *endptr == 'N' || *endptr == 'E') 
564                         {
565                         coord = deg + min /60.0 + sec / 3600.0;
566                         ok = TRUE;
567                         if (*endptr == 'S' || *endptr == 'W') coord = -coord;
568                         }
569                 }
570         
571         if (!ok)
572                 {
573                 coord = fallback;
574                 log_printf("unable to parse GPS coordinate '%s'\n", string);
575                 }
576         
577         g_free(string);
578         return coord;
579 }
580         
581 gboolean metadata_append_string(FileData *fd, const gchar *key, const char *value)
582 {
583         gchar *str = metadata_read_string(fd, key, METADATA_PLAIN);
584         
585         if (!str) 
586                 {
587                 return metadata_write_string(fd, key, value);
588                 }
589         else
590                 {
591                 gchar *new_string = g_strconcat(str, value, NULL);
592                 gboolean ret = metadata_write_string(fd, key, new_string);
593                 g_free(str);
594                 g_free(new_string);
595                 return ret;
596                 }
597 }
598
599 gboolean metadata_append_list(FileData *fd, const gchar *key, const GList *values)
600 {
601         GList *list = metadata_read_list(fd, key, METADATA_PLAIN);
602         
603         if (!list) 
604                 {
605                 return metadata_write_list(fd, key, values);
606                 }
607         else
608                 {
609                 gboolean ret;
610                 list = g_list_concat(list, string_list_copy(values));
611                 list = remove_duplicate_strings_from_list(list);
612                 
613                 ret = metadata_write_list(fd, key, list);
614                 string_list_free(list);
615                 return ret;
616                 }
617 }
618
619 gchar *find_string_in_list_utf8nocase(GList *list, const gchar *string)
620 {
621         gchar *string_casefold = g_utf8_casefold(string, -1);
622
623         while (list)
624                 {
625                 gchar *haystack = list->data;
626                 
627                 if (haystack)
628                         {
629                         gboolean equal;
630                         gchar *haystack_casefold = g_utf8_casefold(haystack, -1);
631
632                         equal = (strcmp(haystack_casefold, string_casefold) == 0);
633                         g_free(haystack_casefold);
634
635                         if (equal)
636                                 {
637                                 g_free(string_casefold);
638                                 return haystack;
639                                 }
640                         }
641         
642                 list = list->next;
643                 }
644         
645         g_free(string_casefold);
646         return NULL;
647 }
648
649
650 #define KEYWORDS_SEPARATOR(c) ((c) == ',' || (c) == ';' || (c) == '\n' || (c) == '\r' || (c) == '\b')
651
652 GList *string_to_keywords_list(const gchar *text)
653 {
654         GList *list = NULL;
655         const gchar *ptr = text;
656
657         while (*ptr != '\0')
658                 {
659                 const gchar *begin;
660                 gint l = 0;
661
662                 while (KEYWORDS_SEPARATOR(*ptr)) ptr++;
663                 begin = ptr;
664                 while (*ptr != '\0' && !KEYWORDS_SEPARATOR(*ptr))
665                         {
666                         ptr++;
667                         l++;
668                         }
669
670                 /* trim starting and ending whitespaces */
671                 while (l > 0 && g_ascii_isspace(*begin)) begin++, l--;
672                 while (l > 0 && g_ascii_isspace(begin[l-1])) l--;
673
674                 if (l > 0)
675                         {
676                         gchar *keyword = g_strndup(begin, l);
677
678                         /* only add if not already in the list */
679                         if (!find_string_in_list_utf8nocase(list, keyword))
680                                 list = g_list_append(list, keyword);
681                         else
682                                 g_free(keyword);
683                         }
684                 }
685
686         return list;
687 }
688
689 /*
690  * keywords to marks
691  */
692  
693
694 gboolean meta_data_get_keyword_mark(FileData *fd, gint n, gpointer data)
695 {
696         /* FIXME: do not use global keyword_tree */
697         GList *path = data;
698         GList *keywords;
699         gboolean found = FALSE;
700         keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
701         if (keywords)
702                 {
703                 GtkTreeIter iter;
704                 if (keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path) &&
705                     keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
706                         found = TRUE;
707
708                 }
709         return found;
710 }
711
712 gboolean meta_data_set_keyword_mark(FileData *fd, gint n, gboolean value, gpointer data)
713 {
714         GList *path = data;
715         GList *keywords = NULL;
716         GtkTreeIter iter;
717         
718         if (!keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path)) return FALSE;
719
720         keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
721
722         if (!!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords) != !!value)
723                 {
724                 if (value) 
725                         {
726                         keyword_tree_set(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
727                         }
728                 else
729                         {
730                         keyword_tree_reset(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
731                         }
732                 metadata_write_list(fd, KEYWORD_KEY, keywords);
733                 }
734
735         string_list_free(keywords);
736         return TRUE;
737 }
738
739
740
741 void meta_data_connect_mark_with_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *kw_iter, gint mark)
742 {
743
744         FileDataGetMarkFunc get_mark_func;
745         FileDataSetMarkFunc set_mark_func;
746         gpointer mark_func_data;
747
748         gint i;
749
750         for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
751                 {
752                 file_data_get_registered_mark_func(i, &get_mark_func, &set_mark_func, &mark_func_data);
753                 if (get_mark_func == meta_data_get_keyword_mark) 
754                         {
755                         GtkTreeIter old_kw_iter;
756                         GList *old_path = mark_func_data;
757                         
758                         if (keyword_tree_get_iter(keyword_tree, &old_kw_iter, old_path) && 
759                             (i == mark || /* release any previous connection of given mark */
760                              keyword_compare(keyword_tree, &old_kw_iter, kw_iter) == 0)) /* or given keyword */
761                                 {
762                                 file_data_register_mark_func(i, NULL, NULL, NULL, NULL);
763                                 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), &old_kw_iter, KEYWORD_COLUMN_MARK, "", -1);
764                                 }
765                         }
766                 }
767
768
769         if (mark >= 0 && mark < FILEDATA_MARKS_SIZE)
770                 {
771                 GList *path;
772                 gchar *mark_str;
773                 path = keyword_tree_get_path(keyword_tree, kw_iter);
774                 file_data_register_mark_func(mark, meta_data_get_keyword_mark, meta_data_set_keyword_mark, path, (GDestroyNotify)string_list_free);
775                 
776                 mark_str = g_strdup_printf("%d", mark + 1);
777                 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), kw_iter, KEYWORD_COLUMN_MARK, mark_str, -1);
778                 g_free(mark_str);
779                 }
780 }
781
782
783 /*
784  *-------------------------------------------------------------------
785  * keyword tree
786  *-------------------------------------------------------------------
787  */
788
789
790
791 GtkTreeStore *keyword_tree;
792
793 gchar *keyword_get_name(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
794 {
795         gchar *name;
796         gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_NAME, &name, -1);
797         return name;
798 }
799
800 gchar *keyword_get_casefold(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
801 {
802         gchar *casefold;
803         gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_CASEFOLD, &casefold, -1);
804         return casefold;
805 }
806
807 gboolean keyword_get_is_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
808 {
809         gboolean is_keyword;
810         gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
811         return is_keyword;
812 }
813
814 void keyword_set(GtkTreeStore *keyword_tree, GtkTreeIter *iter, const gchar *name, gboolean is_keyword)
815 {
816         gchar *casefold = g_utf8_casefold(name, -1);
817         gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_MARK, "",
818                                                 KEYWORD_COLUMN_NAME, name,
819                                                 KEYWORD_COLUMN_CASEFOLD, casefold,
820                                                 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
821         g_free(casefold);
822 }
823
824 gboolean keyword_compare(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
825 {
826         GtkTreePath *pa = gtk_tree_model_get_path(keyword_tree, a);
827         GtkTreePath *pb = gtk_tree_model_get_path(keyword_tree, b);
828         gint ret = gtk_tree_path_compare(pa, pb);
829         gtk_tree_path_free(pa);
830         gtk_tree_path_free(pb);
831         return ret;
832 }
833
834 gboolean keyword_same_parent(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
835 {
836         GtkTreeIter parent_a;
837         GtkTreeIter parent_b;
838         
839         gboolean valid_pa = gtk_tree_model_iter_parent(keyword_tree, &parent_a, a);
840         gboolean valid_pb = gtk_tree_model_iter_parent(keyword_tree, &parent_b, b);
841
842         if (valid_pa && valid_pb)
843                 {
844                 return keyword_compare(keyword_tree, &parent_a, &parent_b) == 0;
845                 }
846         else
847                 {
848                 return (!valid_pa && !valid_pb); /* both are toplevel */
849                 }
850 }
851
852 gboolean keyword_exists(GtkTreeModel *keyword_tree, GtkTreeIter *parent_ptr, GtkTreeIter *sibling, const gchar *name, gboolean exclude_sibling, GtkTreeIter *result)
853 {
854         GtkTreeIter parent;
855         GtkTreeIter iter;
856         gboolean toplevel = FALSE;
857         gboolean ret;
858         gchar *casefold;
859         
860         if (parent_ptr)
861                 {
862                 parent = *parent_ptr;
863                 }
864         else if (sibling)
865                 {
866                 toplevel = !gtk_tree_model_iter_parent(keyword_tree, &parent, sibling);
867                 }
868         else
869                 {
870                 toplevel = TRUE;
871                 }
872         
873         if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &iter, toplevel ? NULL : &parent)) return FALSE;
874         
875         casefold = g_utf8_casefold(name, -1);
876         ret = FALSE;
877         
878         while (TRUE)
879                 {
880                 if (!(exclude_sibling && sibling && keyword_compare(keyword_tree, &iter, sibling) == 0))
881                         {
882                         if (options->metadata.tags_case_sensitive)
883                                 {
884                                 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
885                                 ret = strcmp(name, iter_name) == 0;
886                                 g_free(iter_name);
887                                 }
888                         else
889                                 {
890                                 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
891                                 ret = strcmp(casefold, iter_casefold) == 0;
892                                 g_free(iter_casefold);
893                                 } // if (options->metadata.tags_cas...
894                         }
895                 if (ret) 
896                         {
897                         if (result) *result = iter;
898                         break;
899                         }
900                 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) break;
901                 }
902         g_free(casefold);
903         return ret;
904 }
905
906
907 void keyword_copy(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
908 {
909
910         gchar *mark, *name, *casefold;
911         gboolean is_keyword;
912
913         /* do not copy KEYWORD_COLUMN_HIDE_IN, it fully shows the new subtree */
914         gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), from, KEYWORD_COLUMN_MARK, &mark,
915                                                 KEYWORD_COLUMN_NAME, &name,
916                                                 KEYWORD_COLUMN_CASEFOLD, &casefold,
917                                                 KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
918
919         gtk_tree_store_set(keyword_tree, to, KEYWORD_COLUMN_MARK, mark,
920                                                 KEYWORD_COLUMN_NAME, name,
921                                                 KEYWORD_COLUMN_CASEFOLD, casefold,
922                                                 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
923         g_free(mark);
924         g_free(name);
925         g_free(casefold);
926 }
927
928 void keyword_copy_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
929 {
930         GtkTreeIter from_child;
931         
932         keyword_copy(keyword_tree, to, from);
933         
934         if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &from_child, from)) return;
935         
936         while (TRUE)
937                 {
938                 GtkTreeIter to_child;
939                 gtk_tree_store_append(keyword_tree, &to_child, to);
940                 keyword_copy_recursive(keyword_tree, &to_child, &from_child);
941                 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &from_child)) return;
942                 }
943 }
944
945 void keyword_move_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
946 {
947         keyword_copy_recursive(keyword_tree, to, from);
948         keyword_delete(keyword_tree, from);
949 }
950
951 GList *keyword_tree_get_path(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
952 {
953         GList *path = NULL;
954         GtkTreeIter iter = *iter_ptr;
955         
956         while (TRUE)
957                 {
958                 GtkTreeIter parent;
959                 path = g_list_prepend(path, keyword_get_name(keyword_tree, &iter));
960                 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) break;
961                 iter = parent;
962                 }
963         return path;
964 }
965
966 gboolean keyword_tree_get_iter(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList *path)
967 {
968         GtkTreeIter iter;
969
970         if (!gtk_tree_model_get_iter_first(keyword_tree, &iter)) return FALSE;
971         
972         while (TRUE)
973                 {
974                 GtkTreeIter children;
975                 while (TRUE)
976                         {
977                         gchar *name = keyword_get_name(keyword_tree, &iter);
978                         if (strcmp(name, path->data) == 0) break;
979                         g_free(name);
980                         if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return FALSE;
981                         }
982                 path = path->next;
983                 if (!path) 
984                         {
985                         *iter_ptr = iter;
986                         return TRUE;
987                         }
988                         
989                 if (!gtk_tree_model_iter_children(keyword_tree, &children, &iter)) return FALSE;
990                 iter = children;
991                 }
992 }
993
994
995 static gboolean keyword_tree_is_set_casefold(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *casefold_list)
996 {
997         if (!casefold_list) return FALSE;
998
999         if (!keyword_get_is_keyword(keyword_tree, &iter))
1000                 {
1001                 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1002                 GtkTreeIter child;
1003                 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter)) 
1004                         return FALSE; /* this should happen only on empty helpers */
1005
1006                 while (TRUE)
1007                         {
1008                         if (keyword_tree_is_set_casefold(keyword_tree, child, casefold_list)) return TRUE;
1009                         if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1010                         }
1011                 }
1012         
1013         while (TRUE)
1014                 {
1015                 GtkTreeIter parent;
1016
1017                 if (keyword_get_is_keyword(keyword_tree, &iter))
1018                         {
1019                         GList *work = casefold_list;
1020                         gboolean found = FALSE;
1021                         gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
1022                         while (work)
1023                                 {
1024                                 const gchar *casefold = work->data;
1025                                 work = work->next;
1026
1027                                 if (strcmp(iter_casefold, casefold) == 0)
1028                                         {
1029                                         found = TRUE;
1030                                         break;
1031                                         }
1032                                 }
1033                         g_free(iter_casefold);
1034                         if (!found) return FALSE;
1035                         }
1036                 
1037                 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1038                 iter = parent;
1039                 }
1040 }
1041
1042 static gboolean keyword_tree_is_set_casefull(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *kw_list)
1043 {
1044         if (!kw_list) return FALSE;
1045
1046         if (!keyword_get_is_keyword(keyword_tree, &iter))
1047                 {
1048                 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1049                 GtkTreeIter child;
1050                 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1051                         return FALSE; /* this should happen only on empty helpers */
1052
1053                 while (TRUE)
1054                         {
1055                         if (keyword_tree_is_set_casefull(keyword_tree, child, kw_list)) return TRUE;
1056                         if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1057                         }
1058                 }
1059
1060         while (TRUE)
1061                 {
1062                 GtkTreeIter parent;
1063
1064                 if (keyword_get_is_keyword(keyword_tree, &iter))
1065                         {
1066                         GList *work = kw_list;
1067                         gboolean found = FALSE;
1068                         gchar *iter_name = keyword_get_name(keyword_tree, &iter);
1069                         while (work)
1070                                 {
1071                                 const gchar *name = work->data;
1072                                 work = work->next;
1073
1074                                 if (strcmp(iter_name, name) == 0)
1075                                         {
1076                                         found = TRUE;
1077                                         break;
1078                                         }
1079                                 }
1080                         g_free(iter_name);
1081                         if (!found) return FALSE;
1082                         }
1083
1084                 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1085                 iter = parent;
1086                 }
1087 }
1088
1089 gboolean keyword_tree_is_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList *kw_list)
1090 {
1091         gboolean ret;
1092         GList *casefold_list = NULL;
1093         GList *work;
1094
1095         if (options->metadata.tags_case_sensitive)
1096                 {
1097                 ret = keyword_tree_is_set_casefull(keyword_tree, *iter, kw_list);
1098                 }
1099         else
1100                 {
1101                 work = kw_list;
1102                 while (work)
1103                         {
1104                         const gchar *kw = work->data;
1105                         work = work->next;
1106
1107                         casefold_list = g_list_prepend(casefold_list, g_utf8_casefold(kw, -1));
1108                         }
1109
1110                 ret = keyword_tree_is_set_casefold(keyword_tree, *iter, casefold_list);
1111
1112                 string_list_free(casefold_list);
1113                 }
1114
1115         return ret;
1116 }
1117
1118 void keyword_tree_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1119 {
1120         GtkTreeIter iter = *iter_ptr;
1121         while (TRUE)
1122                 {
1123                 GtkTreeIter parent;
1124
1125                 if (keyword_get_is_keyword(keyword_tree, &iter))
1126                         {
1127                         gchar *name = keyword_get_name(keyword_tree, &iter);
1128                         if (!find_string_in_list_utf8nocase(*kw_list, name))
1129                                 {
1130                                 *kw_list = g_list_append(*kw_list, name);
1131                                 }
1132                         else
1133                                 {
1134                                 g_free(name);
1135                                 }
1136                         }
1137
1138                 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1139                 iter = parent;
1140                 }
1141 }
1142
1143 static void keyword_tree_reset1(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1144 {
1145         gchar *found;
1146         gchar *name;
1147         if (!keyword_get_is_keyword(keyword_tree, iter)) return;
1148
1149         name = keyword_get_name(keyword_tree, iter);
1150         found = find_string_in_list_utf8nocase(*kw_list, name);
1151
1152         if (found)
1153                 {
1154                 *kw_list = g_list_remove(*kw_list, found);
1155                 g_free(found);
1156                 }
1157         g_free(name);
1158 }
1159
1160 static void keyword_tree_reset_recursive(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1161 {
1162         GtkTreeIter child;
1163         keyword_tree_reset1(keyword_tree, iter, kw_list);
1164         
1165         if (!gtk_tree_model_iter_children(keyword_tree, &child, iter)) return;
1166
1167         while (TRUE)
1168                 {
1169                 keyword_tree_reset_recursive(keyword_tree, &child, kw_list);
1170                 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return;
1171                 }
1172 }
1173
1174 static gboolean keyword_tree_check_empty_children(GtkTreeModel *keyword_tree, GtkTreeIter *parent, GList *kw_list)
1175 {
1176         GtkTreeIter iter;
1177         
1178         if (!gtk_tree_model_iter_children(keyword_tree, &iter, parent)) 
1179                 return TRUE; /* this should happen only on empty helpers */
1180
1181         while (TRUE)
1182                 {
1183                 if (keyword_tree_is_set(keyword_tree, &iter, kw_list)) return FALSE;
1184                 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return TRUE;
1185                 }
1186 }
1187
1188 void keyword_tree_reset(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1189 {
1190         GtkTreeIter iter = *iter_ptr;
1191         GtkTreeIter parent;
1192         keyword_tree_reset_recursive(keyword_tree, &iter, kw_list);
1193
1194         if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1195         iter = parent;
1196         
1197         while (keyword_tree_check_empty_children(keyword_tree, &iter, *kw_list))
1198                 {
1199                 GtkTreeIter parent;
1200                 keyword_tree_reset1(keyword_tree, &iter, kw_list);
1201                 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1202                 iter = parent;
1203                 }
1204 }
1205
1206 void keyword_delete(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr)
1207 {
1208         GList *list;
1209         GtkTreeIter child;
1210         while (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, iter_ptr))
1211                 {
1212                 keyword_delete(keyword_tree, &child);
1213                 }
1214         
1215         meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree), iter_ptr, -1);
1216
1217         gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter_ptr, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1218         g_list_free(list);
1219         
1220         gtk_tree_store_remove(keyword_tree, iter_ptr);
1221 }
1222
1223
1224 void keyword_hide_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1225 {
1226         GList *list;
1227         gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1228         if (!g_list_find(list, id))
1229                 {
1230                 list = g_list_prepend(list, id);
1231                 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1232                 }
1233 }
1234
1235 void keyword_show_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1236 {
1237         GList *list;
1238         gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1239         list = g_list_remove(list, id);
1240         gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1241 }
1242
1243 gboolean keyword_is_hidden_in(GtkTreeModel *keyword_tree, GtkTreeIter *iter, gpointer id)
1244 {
1245         GList *list;
1246         gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1247         return !!g_list_find(list, id);
1248 }
1249
1250 static gboolean keyword_show_all_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
1251 {
1252         keyword_show_in(GTK_TREE_STORE(model), iter, data);
1253         return FALSE;
1254 }
1255
1256 void keyword_show_all_in(GtkTreeStore *keyword_tree, gpointer id)
1257 {
1258         gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_all_in_cb, id);
1259 }
1260
1261 static void keyword_hide_unset_in_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr, gpointer id, GList *keywords)
1262 {
1263         GtkTreeIter iter = *iter_ptr;
1264         while (TRUE)
1265                 {
1266                 if (!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
1267                         {
1268                         keyword_hide_in(keyword_tree, &iter, id);
1269                         /* no need to check children of hidden node */
1270                         }
1271                 else
1272                         {
1273                         GtkTreeIter child;
1274                         if (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, &iter)) 
1275                                 {
1276                                 keyword_hide_unset_in_recursive(keyword_tree, &child, id, keywords);
1277                                 }
1278                         }
1279                 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1280                 }
1281 }
1282
1283 void keyword_hide_unset_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1284 {
1285         GtkTreeIter iter;
1286         if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1287         keyword_hide_unset_in_recursive(keyword_tree, &iter, id, keywords);
1288 }
1289
1290 static gboolean keyword_show_set_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter_ptr, gpointer data)
1291 {
1292         GtkTreeIter iter = *iter_ptr;
1293         GList *keywords = data;
1294         gpointer id = keywords->data;
1295         keywords = keywords->next; /* hack */
1296         if (keyword_tree_is_set(model, &iter, keywords))
1297                 {
1298                 while (TRUE)
1299                         {
1300                         GtkTreeIter parent;
1301                         keyword_show_in(GTK_TREE_STORE(model), &iter, id);
1302                         if (!gtk_tree_model_iter_parent(GTK_TREE_MODEL(keyword_tree), &parent, &iter)) break;
1303                         iter = parent;
1304                         }
1305                 }
1306         return FALSE;
1307 }
1308
1309 void keyword_show_set_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1310 {
1311         /* hack: pass id to keyword_hide_unset_in_cb in the list */
1312         keywords = g_list_prepend(keywords, id);
1313         gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_set_in_cb, keywords);
1314         keywords = g_list_delete_link(keywords, keywords);
1315 }
1316
1317
1318 void keyword_tree_new(void)
1319 {
1320         if (keyword_tree) return;
1321         
1322         keyword_tree = gtk_tree_store_new(KEYWORD_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER);
1323 }
1324
1325 static GtkTreeIter keyword_tree_default_append(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar *name, gboolean is_keyword)
1326 {
1327         GtkTreeIter iter;
1328         gtk_tree_store_append(keyword_tree, &iter, parent);
1329         keyword_set(keyword_tree, &iter, name, is_keyword);
1330         return iter;
1331 }
1332
1333 void keyword_tree_new_default(void)
1334 {
1335         if (keyword_tree) return;
1336         
1337         keyword_tree_new();
1338
1339         GtkTreeIter i1, i2, i3;
1340
1341         i1 = keyword_tree_default_append(keyword_tree, NULL, _("People"), TRUE); 
1342                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Family"), TRUE); 
1343                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Free time"), TRUE); 
1344                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Children"), TRUE); 
1345                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sport"), TRUE); 
1346                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Culture"), TRUE); 
1347                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Festival"), TRUE); 
1348         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Nature"), TRUE); 
1349                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Animal"), TRUE); 
1350                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Bird"), TRUE); 
1351                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Insect"), TRUE); 
1352                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Pets"), TRUE); 
1353                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Wildlife"), TRUE); 
1354                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Zoo"), TRUE); 
1355                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Plant"), TRUE); 
1356                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Tree"), TRUE); 
1357                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Flower"), TRUE); 
1358                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Water"), TRUE); 
1359                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("River"), TRUE); 
1360                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Lake"), TRUE); 
1361                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Sea"), TRUE); 
1362                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Landscape"), TRUE); 
1363         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Art"), TRUE); 
1364                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Statue"), TRUE); 
1365                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Painting"), TRUE); 
1366                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE); 
1367                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE); 
1368         i1 = keyword_tree_default_append(keyword_tree, NULL, _("City"), TRUE); 
1369                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Park"), TRUE); 
1370                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Street"), TRUE); 
1371                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Square"), TRUE); 
1372         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Architecture"), TRUE); 
1373                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Buildings"), FALSE); 
1374                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("House"), TRUE); 
1375                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Cathedral"), TRUE); 
1376                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Palace"), TRUE); 
1377                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Castle"), TRUE); 
1378                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Bridge"), TRUE); 
1379                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Interior"), TRUE); 
1380                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE); 
1381                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE); 
1382         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Places"), FALSE); 
1383         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Conditions"), FALSE); 
1384                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Night"), TRUE); 
1385                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Lights"), TRUE); 
1386                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Reflections"), TRUE); 
1387                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sun"), TRUE); 
1388                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Weather"), FALSE); 
1389                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Fog"), TRUE); 
1390                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Rain"), TRUE); 
1391                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Clouds"), TRUE); 
1392                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Snow"), TRUE); 
1393                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Sunny weather"), TRUE); 
1394         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Photo"), FALSE); 
1395                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Edited"), TRUE); 
1396                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Detail"), TRUE); 
1397                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Macro"), TRUE); 
1398                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Portrait"), TRUE); 
1399                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Black and White"), TRUE); 
1400                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Perspective"), TRUE); 
1401 }
1402
1403
1404 static void keyword_tree_node_write_config(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GString *outstr, gint indent)
1405 {
1406         GtkTreeIter iter = *iter_ptr;
1407         while (TRUE)
1408                 {
1409                 GtkTreeIter children;
1410                 gchar *name;
1411
1412                 WRITE_NL(); WRITE_STRING("<keyword ");
1413                 name = keyword_get_name(keyword_tree, &iter);
1414                 write_char_option(outstr, indent, "name", name);
1415                 g_free(name);
1416                 write_bool_option(outstr, indent, "kw", keyword_get_is_keyword(keyword_tree, &iter));
1417                 if (gtk_tree_model_iter_children(keyword_tree, &children, &iter)) 
1418                         {
1419                         WRITE_STRING(">");
1420                         indent++;
1421                         keyword_tree_node_write_config(keyword_tree, &children, outstr, indent);
1422                         indent--;
1423                         WRITE_NL(); WRITE_STRING("</keyword>");
1424                         }
1425                 else 
1426                         {
1427                         WRITE_STRING("/>");
1428                         }
1429                 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return;
1430                 }
1431 }
1432
1433 void keyword_tree_write_config(GString *outstr, gint indent)
1434 {
1435         GtkTreeIter iter;
1436         WRITE_NL(); WRITE_STRING("<keyword_tree>");
1437         indent++;
1438         
1439         if (keyword_tree && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1440                 {
1441                 keyword_tree_node_write_config(GTK_TREE_MODEL(keyword_tree), &iter, outstr, indent);
1442                 }
1443         indent--;
1444         WRITE_NL(); WRITE_STRING("</keyword_tree>");
1445 }
1446
1447 GtkTreeIter *keyword_add_from_config(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar **attribute_names, const gchar **attribute_values)
1448 {
1449         gchar *name = NULL;
1450         gboolean is_kw = TRUE;
1451
1452         while (*attribute_names)
1453                 {
1454                 const gchar *option = *attribute_names++;
1455                 const gchar *value = *attribute_values++;
1456
1457                 if (READ_CHAR_FULL("name", name)) continue;
1458                 if (READ_BOOL_FULL("kw", is_kw)) continue;
1459
1460                 log_printf("unknown attribute %s = %s\n", option, value);
1461                 }
1462         if (name && name[0]) 
1463                 {
1464                 GtkTreeIter iter;
1465                 /* re-use existing keyword if any */
1466                 if (!keyword_exists(GTK_TREE_MODEL(keyword_tree), parent, NULL, name, FALSE, &iter))
1467                         {
1468                         gtk_tree_store_append(keyword_tree, &iter, parent);
1469                         }
1470                 keyword_set(keyword_tree, &iter, name, is_kw);
1471                 g_free(name);
1472                 return gtk_tree_iter_copy(&iter);
1473                 }
1474         g_free(name);
1475         return NULL;
1476 }
1477
1478 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */