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