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