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