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