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