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