c3ffdbc15a01cf27d30e18386a389d5dcb2adf96
[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 gchar *metadata_read_rating_stars(FileData *fd)
753 {
754         gchar *ret;
755         gint n = metadata_read_int(fd, RATING_KEY, METADATA_PLAIN);
756
757         ret = convert_rating_to_stars(n);
758
759         return ret;
760 }
761
762 gdouble metadata_read_GPS_coord(FileData *fd, const gchar *key, gdouble fallback)
763 {
764         gdouble coord;
765         gchar *endptr;
766         gdouble deg, min, sec;
767         gboolean ok = FALSE;
768         gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
769         if (!string) return fallback;
770
771         deg = g_ascii_strtod(string, &endptr);
772         if (*endptr == ',')
773                 {
774                 min = g_ascii_strtod(endptr + 1, &endptr);
775                 if (*endptr == ',')
776                         sec = g_ascii_strtod(endptr + 1, &endptr);
777                 else
778                         sec = 0.0;
779
780
781                 if (*endptr == 'S' || *endptr == 'W' || *endptr == 'N' || *endptr == 'E')
782                         {
783                         coord = deg + min /60.0 + sec / 3600.0;
784                         ok = TRUE;
785                         if (*endptr == 'S' || *endptr == 'W') coord = -coord;
786                         }
787                 }
788
789         if (!ok)
790                 {
791                 coord = fallback;
792                 log_printf("unable to parse GPS coordinate '%s'\n", string);
793                 }
794
795         g_free(string);
796         return coord;
797 }
798
799 gdouble metadata_read_GPS_direction(FileData *fd, const gchar *key, gdouble fallback)
800 {
801         gchar *endptr;
802         gdouble deg;
803         gboolean ok = FALSE;
804         gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
805         if (!string) return fallback;
806
807         DEBUG_3("GPS_direction: %s\n", string);
808         deg = g_ascii_strtod(string, &endptr);
809
810         /* Expected text string is of the format e.g.:
811          * 18000/100
812          */
813         if (*endptr == '/')
814                 {
815                 deg = deg/100;
816                 ok = TRUE;
817                 }
818
819         if (!ok)
820                 {
821                 deg = fallback;
822                 log_printf("unable to parse GPS direction '%s: %f'\n", string, deg);
823                 }
824
825         g_free(string);
826
827         return deg;
828 }
829
830 gboolean metadata_append_string(FileData *fd, const gchar *key, const char *value)
831 {
832         gchar *str = metadata_read_string(fd, key, METADATA_PLAIN);
833
834         if (!str)
835                 {
836                 return metadata_write_string(fd, key, value);
837                 }
838         else
839                 {
840                 gchar *new_string = g_strconcat(str, value, NULL);
841                 gboolean ret = metadata_write_string(fd, key, new_string);
842                 g_free(str);
843                 g_free(new_string);
844                 return ret;
845                 }
846 }
847
848 gboolean metadata_write_GPS_coord(FileData *fd, const gchar *key, gdouble value)
849 {
850         gint deg;
851         gdouble min;
852         gdouble param;
853         char *coordinate;
854         char *ref;
855         gboolean ok = TRUE;
856
857         param = value;
858         if (param < 0)
859                 param = -param;
860         deg = param;
861         min = (param * 60) - (deg * 60);
862         if (g_strcmp0(key, "Xmp.exif.GPSLongitude") == 0)
863                 if (value < 0)
864                         ref = "W";
865                 else
866                         ref = "E";
867         else if (g_strcmp0(key, "Xmp.exif.GPSLatitude") == 0)
868                 if (value < 0)
869                         ref = "S";
870                 else
871                         ref = "N";
872         else
873                 {
874                 log_printf("unknown GPS parameter key '%s'\n", key);
875                 ok = FALSE;
876                 }
877
878         if (ok)
879                 {
880                 coordinate = g_strdup_printf("%i,%lf,%s", deg, min, ref);
881                 metadata_write_string(fd, key, coordinate );
882                 g_free(coordinate);
883                 }
884
885         return ok;
886 }
887
888 gboolean metadata_append_list(FileData *fd, const gchar *key, const GList *values)
889 {
890         GList *list = metadata_read_list(fd, key, METADATA_PLAIN);
891
892         if (!list)
893                 {
894                 return metadata_write_list(fd, key, values);
895                 }
896         else
897                 {
898                 gboolean ret;
899                 list = g_list_concat(list, string_list_copy(values));
900                 list = remove_duplicate_strings_from_list(list);
901
902                 ret = metadata_write_list(fd, key, list);
903                 string_list_free(list);
904                 return ret;
905                 }
906 }
907
908 /**
909  * \see find_string_in_list
910  */
911 gchar *find_string_in_list_utf8nocase(GList *list, const gchar *string)
912 {
913         gchar *string_casefold = g_utf8_casefold(string, -1);
914
915         while (list)
916                 {
917                 gchar *haystack = list->data;
918
919                 if (haystack)
920                         {
921                         gboolean equal;
922                         gchar *haystack_casefold = g_utf8_casefold(haystack, -1);
923
924                         equal = (strcmp(haystack_casefold, string_casefold) == 0);
925                         g_free(haystack_casefold);
926
927                         if (equal)
928                                 {
929                                 g_free(string_casefold);
930                                 return haystack;
931                                 }
932                         }
933
934                 list = list->next;
935                 }
936
937         g_free(string_casefold);
938         return NULL;
939 }
940
941 /**
942  * \see find_string_in_list
943  */
944 gchar *find_string_in_list_utf8case(GList *list, const gchar *string)
945 {
946         while (list)
947                 {
948                 gchar *haystack = list->data;
949
950                 if (haystack && strcmp(haystack, string) == 0)
951                         return haystack;
952
953                 list = list->next;
954                 } // while (list)
955
956         return NULL;
957 } // gchar *find_string_in_list_utf...
958
959 /**
960  * \brief Find a existent string in a list.
961  *
962  * This is a switch between find_string_in_list_utf8case and
963  * find_string_in_list_utf8nocase to search with or without case for the
964  * existence of a string.
965  *
966  * \param list The list to search in
967  * \param string The string to search for
968  * \return The string or NULL
969  *
970  * \see find_string_in_list_utf8case
971  * \see find_string_in_list_utf8nocase
972  */
973 gchar *find_string_in_list(GList *list, const gchar *string)
974 {
975         if (options->metadata.keywords_case_sensitive)
976                 return find_string_in_list_utf8case(list, string);
977         else
978                 return find_string_in_list_utf8nocase(list, string);
979 }
980
981 #define KEYWORDS_SEPARATOR(c) ((c) == ',' || (c) == ';' || (c) == '\n' || (c) == '\r' || (c) == '\b')
982
983 GList *string_to_keywords_list(const gchar *text)
984 {
985         GList *list = NULL;
986         const gchar *ptr = text;
987
988         while (*ptr != '\0')
989                 {
990                 const gchar *begin;
991                 gint l = 0;
992
993                 while (KEYWORDS_SEPARATOR(*ptr)) ptr++;
994                 begin = ptr;
995                 while (*ptr != '\0' && !KEYWORDS_SEPARATOR(*ptr))
996                         {
997                         ptr++;
998                         l++;
999                         }
1000
1001                 /* trim starting and ending whitespaces */
1002                 while (l > 0 && g_ascii_isspace(*begin)) begin++, l--;
1003                 while (l > 0 && g_ascii_isspace(begin[l-1])) l--;
1004
1005                 if (l > 0)
1006                         {
1007                         gchar *keyword = g_strndup(begin, l);
1008
1009                         /* only add if not already in the list */
1010                         if (!find_string_in_list(list, keyword))
1011                                 list = g_list_append(list, keyword);
1012                         else
1013                                 g_free(keyword);
1014                         }
1015                 }
1016
1017         return list;
1018 }
1019
1020 /*
1021  * keywords to marks
1022  */
1023
1024
1025 gboolean meta_data_get_keyword_mark(FileData *fd, gint n, gpointer data)
1026 {
1027         /* FIXME: do not use global keyword_tree */
1028         GList *path = data;
1029         GList *keywords;
1030         gboolean found = FALSE;
1031         keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
1032         if (keywords)
1033                 {
1034                 GtkTreeIter iter;
1035                 if (keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path) &&
1036                     keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
1037                         found = TRUE;
1038
1039                 }
1040         return found;
1041 }
1042
1043 gboolean meta_data_set_keyword_mark(FileData *fd, gint n, gboolean value, gpointer data)
1044 {
1045         GList *path = data;
1046         GList *keywords = NULL;
1047         GtkTreeIter iter;
1048
1049         if (!keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path)) return FALSE;
1050
1051         keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
1052
1053         if (!!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords) != !!value)
1054                 {
1055                 if (value)
1056                         {
1057                         keyword_tree_set(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
1058                         }
1059                 else
1060                         {
1061                         keyword_tree_reset(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
1062                         }
1063                 metadata_write_list(fd, KEYWORD_KEY, keywords);
1064                 }
1065
1066         string_list_free(keywords);
1067         return TRUE;
1068 }
1069
1070
1071
1072 void meta_data_connect_mark_with_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *kw_iter, gint mark)
1073 {
1074
1075         FileDataGetMarkFunc get_mark_func;
1076         FileDataSetMarkFunc set_mark_func;
1077         gpointer mark_func_data;
1078
1079         gint i;
1080
1081         for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
1082                 {
1083                 file_data_get_registered_mark_func(i, &get_mark_func, &set_mark_func, &mark_func_data);
1084                 if (get_mark_func == meta_data_get_keyword_mark)
1085                         {
1086                         GtkTreeIter old_kw_iter;
1087                         GList *old_path = mark_func_data;
1088
1089                         if (keyword_tree_get_iter(keyword_tree, &old_kw_iter, old_path) &&
1090                             (i == mark || /* release any previous connection of given mark */
1091                              keyword_compare(keyword_tree, &old_kw_iter, kw_iter) == 0)) /* or given keyword */
1092                                 {
1093                                 file_data_register_mark_func(i, NULL, NULL, NULL, NULL);
1094                                 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), &old_kw_iter, KEYWORD_COLUMN_MARK, "", -1);
1095                                 }
1096                         }
1097                 }
1098
1099
1100         if (mark >= 0 && mark < FILEDATA_MARKS_SIZE)
1101                 {
1102                 GList *path;
1103                 gchar *mark_str;
1104                 path = keyword_tree_get_path(keyword_tree, kw_iter);
1105                 file_data_register_mark_func(mark, meta_data_get_keyword_mark, meta_data_set_keyword_mark, path, (GDestroyNotify)string_list_free);
1106
1107                 mark_str = g_strdup_printf("%d", (mark < 9 ? mark : -1) + 1);
1108                 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), kw_iter, KEYWORD_COLUMN_MARK, mark_str, -1);
1109                 g_free(mark_str);
1110                 }
1111 }
1112
1113
1114 /*
1115  *-------------------------------------------------------------------
1116  * keyword tree
1117  *-------------------------------------------------------------------
1118  */
1119
1120
1121
1122 GtkTreeStore *keyword_tree;
1123
1124 gchar *keyword_get_name(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1125 {
1126         gchar *name;
1127         gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_NAME, &name, -1);
1128         return name;
1129 }
1130
1131 gchar *keyword_get_mark(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1132 {
1133         gchar *mark_str;
1134
1135         gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_MARK, &mark_str, -1);
1136         return mark_str;
1137 }
1138
1139 gchar *keyword_get_casefold(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1140 {
1141         gchar *casefold;
1142         gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_CASEFOLD, &casefold, -1);
1143         return casefold;
1144 }
1145
1146 gboolean keyword_get_is_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1147 {
1148         gboolean is_keyword;
1149         gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
1150         return is_keyword;
1151 }
1152
1153 void keyword_set(GtkTreeStore *keyword_tree, GtkTreeIter *iter, const gchar *name, gboolean is_keyword)
1154 {
1155         gchar *casefold = g_utf8_casefold(name, -1);
1156         gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_MARK, "",
1157                                                 KEYWORD_COLUMN_NAME, name,
1158                                                 KEYWORD_COLUMN_CASEFOLD, casefold,
1159                                                 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
1160         g_free(casefold);
1161 }
1162
1163 gboolean keyword_compare(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
1164 {
1165         GtkTreePath *pa = gtk_tree_model_get_path(keyword_tree, a);
1166         GtkTreePath *pb = gtk_tree_model_get_path(keyword_tree, b);
1167         gint ret = gtk_tree_path_compare(pa, pb);
1168         gtk_tree_path_free(pa);
1169         gtk_tree_path_free(pb);
1170         return ret;
1171 }
1172
1173 gboolean keyword_same_parent(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
1174 {
1175         GtkTreeIter parent_a;
1176         GtkTreeIter parent_b;
1177
1178         gboolean valid_pa = gtk_tree_model_iter_parent(keyword_tree, &parent_a, a);
1179         gboolean valid_pb = gtk_tree_model_iter_parent(keyword_tree, &parent_b, b);
1180
1181         if (valid_pa && valid_pb)
1182                 {
1183                 return keyword_compare(keyword_tree, &parent_a, &parent_b) == 0;
1184                 }
1185         else
1186                 {
1187                 return (!valid_pa && !valid_pb); /* both are toplevel */
1188                 }
1189 }
1190
1191 gboolean keyword_exists(GtkTreeModel *keyword_tree, GtkTreeIter *parent_ptr, GtkTreeIter *sibling, const gchar *name, gboolean exclude_sibling, GtkTreeIter *result)
1192 {
1193         GtkTreeIter parent;
1194         GtkTreeIter iter;
1195         gboolean toplevel = FALSE;
1196         gboolean ret;
1197         gchar *casefold;
1198
1199         if (parent_ptr)
1200                 {
1201                 parent = *parent_ptr;
1202                 }
1203         else if (sibling)
1204                 {
1205                 toplevel = !gtk_tree_model_iter_parent(keyword_tree, &parent, sibling);
1206                 }
1207         else
1208                 {
1209                 toplevel = TRUE;
1210                 }
1211
1212         if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &iter, toplevel ? NULL : &parent)) return FALSE;
1213
1214         casefold = g_utf8_casefold(name, -1);
1215         ret = FALSE;
1216
1217         while (TRUE)
1218                 {
1219                 if (!(exclude_sibling && sibling && keyword_compare(keyword_tree, &iter, sibling) == 0))
1220                         {
1221                         if (options->metadata.keywords_case_sensitive)
1222                                 {
1223                                 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
1224                                 ret = strcmp(name, iter_name) == 0;
1225                                 g_free(iter_name);
1226                                 }
1227                         else
1228                                 {
1229                                 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
1230                                 ret = strcmp(casefold, iter_casefold) == 0;
1231                                 g_free(iter_casefold);
1232                                 } // if (options->metadata.tags_cas...
1233                         }
1234                 if (ret)
1235                         {
1236                         if (result) *result = iter;
1237                         break;
1238                         }
1239                 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) break;
1240                 }
1241         g_free(casefold);
1242         return ret;
1243 }
1244
1245
1246 void keyword_copy(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
1247 {
1248
1249         gchar *mark, *name, *casefold;
1250         gboolean is_keyword;
1251
1252         /* do not copy KEYWORD_COLUMN_HIDE_IN, it fully shows the new subtree */
1253         gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), from, KEYWORD_COLUMN_MARK, &mark,
1254                                                 KEYWORD_COLUMN_NAME, &name,
1255                                                 KEYWORD_COLUMN_CASEFOLD, &casefold,
1256                                                 KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
1257
1258         gtk_tree_store_set(keyword_tree, to, KEYWORD_COLUMN_MARK, mark,
1259                                                 KEYWORD_COLUMN_NAME, name,
1260                                                 KEYWORD_COLUMN_CASEFOLD, casefold,
1261                                                 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
1262         g_free(mark);
1263         g_free(name);
1264         g_free(casefold);
1265 }
1266
1267 void keyword_copy_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
1268 {
1269         GtkTreeIter from_child;
1270
1271         keyword_copy(keyword_tree, to, from);
1272
1273         if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &from_child, from)) return;
1274
1275         while (TRUE)
1276                 {
1277                 GtkTreeIter to_child;
1278                 gtk_tree_store_append(keyword_tree, &to_child, to);
1279                 keyword_copy_recursive(keyword_tree, &to_child, &from_child);
1280                 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &from_child)) return;
1281                 }
1282 }
1283
1284 void keyword_move_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
1285 {
1286         keyword_copy_recursive(keyword_tree, to, from);
1287         keyword_delete(keyword_tree, from);
1288 }
1289
1290 GList *keyword_tree_get_path(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
1291 {
1292         GList *path = NULL;
1293         GtkTreeIter iter = *iter_ptr;
1294
1295         while (TRUE)
1296                 {
1297                 GtkTreeIter parent;
1298                 path = g_list_prepend(path, keyword_get_name(keyword_tree, &iter));
1299                 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) break;
1300                 iter = parent;
1301                 }
1302         return path;
1303 }
1304
1305 gboolean keyword_tree_get_iter(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList *path)
1306 {
1307         GtkTreeIter iter;
1308
1309         if (!gtk_tree_model_get_iter_first(keyword_tree, &iter)) return FALSE;
1310
1311         while (TRUE)
1312                 {
1313                 GtkTreeIter children;
1314                 while (TRUE)
1315                         {
1316                         gchar *name = keyword_get_name(keyword_tree, &iter);
1317                         if (strcmp(name, path->data) == 0) break;
1318                         g_free(name);
1319                         if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return FALSE;
1320                         }
1321                 path = path->next;
1322                 if (!path)
1323                         {
1324                         *iter_ptr = iter;
1325                         return TRUE;
1326                         }
1327
1328                 if (!gtk_tree_model_iter_children(keyword_tree, &children, &iter)) return FALSE;
1329                 iter = children;
1330                 }
1331 }
1332
1333
1334 static gboolean keyword_tree_is_set_casefold(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *casefold_list)
1335 {
1336         if (!casefold_list) return FALSE;
1337
1338         if (!keyword_get_is_keyword(keyword_tree, &iter))
1339                 {
1340                 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1341                 GtkTreeIter child;
1342                 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1343                         return FALSE; /* this should happen only on empty helpers */
1344
1345                 while (TRUE)
1346                         {
1347                         if (keyword_tree_is_set_casefold(keyword_tree, child, casefold_list)) return TRUE;
1348                         if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1349                         }
1350                 }
1351
1352         while (TRUE)
1353                 {
1354                 GtkTreeIter parent;
1355
1356                 if (keyword_get_is_keyword(keyword_tree, &iter))
1357                         {
1358                         GList *work = casefold_list;
1359                         gboolean found = FALSE;
1360                         gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
1361                         while (work)
1362                                 {
1363                                 const gchar *casefold = work->data;
1364                                 work = work->next;
1365
1366                                 if (strcmp(iter_casefold, casefold) == 0)
1367                                         {
1368                                         found = TRUE;
1369                                         break;
1370                                         }
1371                                 }
1372                         g_free(iter_casefold);
1373                         if (!found) return FALSE;
1374                         }
1375
1376                 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1377                 iter = parent;
1378                 }
1379 }
1380
1381 static gboolean keyword_tree_is_set_casefull(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *kw_list)
1382 {
1383         if (!kw_list) return FALSE;
1384
1385         if (!keyword_get_is_keyword(keyword_tree, &iter))
1386                 {
1387                 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1388                 GtkTreeIter child;
1389                 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1390                         return FALSE; /* this should happen only on empty helpers */
1391
1392                 while (TRUE)
1393                         {
1394                         if (keyword_tree_is_set_casefull(keyword_tree, child, kw_list)) return TRUE;
1395                         if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1396                         }
1397                 }
1398
1399         while (TRUE)
1400                 {
1401                 GtkTreeIter parent;
1402
1403                 if (keyword_get_is_keyword(keyword_tree, &iter))
1404                         {
1405                         GList *work = kw_list;
1406                         gboolean found = FALSE;
1407                         gchar *iter_name = keyword_get_name(keyword_tree, &iter);
1408                         while (work)
1409                                 {
1410                                 const gchar *name = work->data;
1411                                 work = work->next;
1412
1413                                 if (strcmp(iter_name, name) == 0)
1414                                         {
1415                                         found = TRUE;
1416                                         break;
1417                                         }
1418                                 }
1419                         g_free(iter_name);
1420                         if (!found) return FALSE;
1421                         }
1422
1423                 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1424                 iter = parent;
1425                 }
1426 }
1427
1428 gboolean keyword_tree_is_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList *kw_list)
1429 {
1430         gboolean ret;
1431         GList *casefold_list = NULL;
1432         GList *work;
1433
1434         if (options->metadata.keywords_case_sensitive)
1435                 {
1436                 ret = keyword_tree_is_set_casefull(keyword_tree, *iter, kw_list);
1437                 }
1438         else
1439                 {
1440                 work = kw_list;
1441                 while (work)
1442                         {
1443                         const gchar *kw = work->data;
1444                         work = work->next;
1445
1446                         casefold_list = g_list_prepend(casefold_list, g_utf8_casefold(kw, -1));
1447                         }
1448
1449                 ret = keyword_tree_is_set_casefold(keyword_tree, *iter, casefold_list);
1450
1451                 string_list_free(casefold_list);
1452                 }
1453
1454         return ret;
1455 }
1456
1457 void keyword_tree_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1458 {
1459         GtkTreeIter iter = *iter_ptr;
1460         while (TRUE)
1461                 {
1462                 GtkTreeIter parent;
1463
1464                 if (keyword_get_is_keyword(keyword_tree, &iter))
1465                         {
1466                         gchar *name = keyword_get_name(keyword_tree, &iter);
1467                         if (!find_string_in_list(*kw_list, name))
1468                                 {
1469                                 *kw_list = g_list_append(*kw_list, name);
1470                                 }
1471                         else
1472                                 {
1473                                 g_free(name);
1474                                 }
1475                         }
1476
1477                 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1478                 iter = parent;
1479                 }
1480 }
1481
1482 GList *keyword_tree_get(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
1483 {
1484         GtkTreeIter iter = *iter_ptr;
1485         GList *kw_list = NULL;
1486
1487         while (TRUE)
1488                 {
1489                 GtkTreeIter parent;
1490
1491                 if (keyword_get_is_keyword(keyword_tree, &iter))
1492                         {
1493                         gchar *name = keyword_get_name(keyword_tree, &iter);
1494                         kw_list = g_list_append(kw_list, name);
1495                         }
1496
1497                 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return kw_list;
1498                 iter = parent;
1499                 }
1500 } // GList *keyword_tree_get(GtkTre...
1501
1502 static void keyword_tree_reset1(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1503 {
1504         gchar *found;
1505         gchar *name;
1506         if (!keyword_get_is_keyword(keyword_tree, iter)) return;
1507
1508         name = keyword_get_name(keyword_tree, iter);
1509         found = find_string_in_list(*kw_list, name);
1510
1511         if (found)
1512                 {
1513                 *kw_list = g_list_remove(*kw_list, found);
1514                 g_free(found);
1515                 }
1516         g_free(name);
1517 }
1518
1519 static void keyword_tree_reset_recursive(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1520 {
1521         GtkTreeIter child;
1522         keyword_tree_reset1(keyword_tree, iter, kw_list);
1523
1524         if (!gtk_tree_model_iter_children(keyword_tree, &child, iter)) return;
1525
1526         while (TRUE)
1527                 {
1528                 keyword_tree_reset_recursive(keyword_tree, &child, kw_list);
1529                 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return;
1530                 }
1531 }
1532
1533 static gboolean keyword_tree_check_empty_children(GtkTreeModel *keyword_tree, GtkTreeIter *parent, GList *kw_list)
1534 {
1535         GtkTreeIter iter;
1536
1537         if (!gtk_tree_model_iter_children(keyword_tree, &iter, parent))
1538                 return TRUE; /* this should happen only on empty helpers */
1539
1540         while (TRUE)
1541                 {
1542                 if (keyword_tree_is_set(keyword_tree, &iter, kw_list)) return FALSE;
1543                 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return TRUE;
1544                 }
1545 }
1546
1547 void keyword_tree_reset(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1548 {
1549         GtkTreeIter iter = *iter_ptr;
1550         GtkTreeIter parent;
1551         keyword_tree_reset_recursive(keyword_tree, &iter, kw_list);
1552
1553         if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1554         iter = parent;
1555
1556         while (keyword_tree_check_empty_children(keyword_tree, &iter, *kw_list))
1557                 {
1558                 GtkTreeIter parent;
1559                 keyword_tree_reset1(keyword_tree, &iter, kw_list);
1560                 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1561                 iter = parent;
1562                 }
1563 }
1564
1565 void keyword_delete(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr)
1566 {
1567         GList *list;
1568         GtkTreeIter child;
1569         while (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, iter_ptr))
1570                 {
1571                 keyword_delete(keyword_tree, &child);
1572                 }
1573
1574         meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree), iter_ptr, -1);
1575
1576         gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter_ptr, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1577         g_list_free(list);
1578
1579         gtk_tree_store_remove(keyword_tree, iter_ptr);
1580 }
1581
1582
1583 void keyword_hide_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1584 {
1585         GList *list;
1586         gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1587         if (!g_list_find(list, id))
1588                 {
1589                 list = g_list_prepend(list, id);
1590                 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1591                 }
1592 }
1593
1594 void keyword_show_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1595 {
1596         GList *list;
1597         gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1598         list = g_list_remove(list, id);
1599         gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1600 }
1601
1602 gboolean keyword_is_hidden_in(GtkTreeModel *keyword_tree, GtkTreeIter *iter, gpointer id)
1603 {
1604         GList *list;
1605         gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1606         return !!g_list_find(list, id);
1607 }
1608
1609 static gboolean keyword_show_all_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
1610 {
1611         keyword_show_in(GTK_TREE_STORE(model), iter, data);
1612         return FALSE;
1613 }
1614
1615 void keyword_show_all_in(GtkTreeStore *keyword_tree, gpointer id)
1616 {
1617         gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_all_in_cb, id);
1618 }
1619
1620 static gboolean keyword_revert_hidden_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
1621 {
1622         if (keyword_is_hidden_in(GTK_TREE_MODEL(keyword_tree), iter, data))
1623                 {
1624                 keyword_show_in(GTK_TREE_STORE(model), iter, data);
1625                 }
1626         return FALSE;
1627 }
1628
1629 void keyword_revert_hidden_in(GtkTreeStore *keyword_tree, gpointer id)
1630 {
1631         gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_revert_hidden_in_cb, id);
1632 }
1633
1634 static void keyword_hide_unset_in_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr, gpointer id, GList *keywords)
1635 {
1636         GtkTreeIter iter = *iter_ptr;
1637         while (TRUE)
1638                 {
1639                 if (!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
1640                         {
1641                         keyword_hide_in(keyword_tree, &iter, id);
1642                         /* no need to check children of hidden node */
1643                         }
1644                 else
1645                         {
1646                         GtkTreeIter child;
1647                         if (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, &iter))
1648                                 {
1649                                 keyword_hide_unset_in_recursive(keyword_tree, &child, id, keywords);
1650                                 }
1651                         }
1652                 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1653                 }
1654 }
1655
1656 void keyword_hide_unset_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1657 {
1658         GtkTreeIter iter;
1659         if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1660         keyword_hide_unset_in_recursive(keyword_tree, &iter, id, keywords);
1661 }
1662
1663 static gboolean keyword_show_set_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter_ptr, gpointer data)
1664 {
1665         GtkTreeIter iter = *iter_ptr;
1666         GList *keywords = data;
1667         gpointer id = keywords->data;
1668         keywords = keywords->next; /* hack */
1669         if (keyword_tree_is_set(model, &iter, keywords))
1670                 {
1671                 while (TRUE)
1672                         {
1673                         GtkTreeIter parent;
1674                         keyword_show_in(GTK_TREE_STORE(model), &iter, id);
1675                         if (!gtk_tree_model_iter_parent(GTK_TREE_MODEL(keyword_tree), &parent, &iter)) break;
1676                         iter = parent;
1677                         }
1678                 }
1679         return FALSE;
1680 }
1681
1682 void keyword_show_set_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1683 {
1684         /* hack: pass id to keyword_hide_unset_in_cb in the list */
1685         keywords = g_list_prepend(keywords, id);
1686         gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_set_in_cb, keywords);
1687         keywords = g_list_delete_link(keywords, keywords);
1688 }
1689
1690
1691 void keyword_tree_new(void)
1692 {
1693         if (keyword_tree) return;
1694
1695         keyword_tree = gtk_tree_store_new(KEYWORD_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER);
1696 }
1697
1698 static GtkTreeIter keyword_tree_default_append(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar *name, gboolean is_keyword)
1699 {
1700         GtkTreeIter iter;
1701         gtk_tree_store_append(keyword_tree, &iter, parent);
1702         keyword_set(keyword_tree, &iter, name, is_keyword);
1703         return iter;
1704 }
1705
1706 void keyword_tree_new_default(void)
1707 {
1708         GtkTreeIter i1, i2;
1709
1710         if (!keyword_tree) keyword_tree_new();
1711
1712         i1 = keyword_tree_default_append(keyword_tree, NULL, _("People"), TRUE);
1713                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Family"), TRUE);
1714                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Free time"), TRUE);
1715                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Children"), TRUE);
1716                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sport"), TRUE);
1717                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Culture"), TRUE);
1718                         keyword_tree_default_append(keyword_tree, &i2, _("Festival"), TRUE);
1719         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Nature"), TRUE);
1720                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Animal"), TRUE);
1721                         keyword_tree_default_append(keyword_tree, &i2, _("Bird"), TRUE);
1722                         keyword_tree_default_append(keyword_tree, &i2, _("Insect"), TRUE);
1723                         keyword_tree_default_append(keyword_tree, &i2, _("Pets"), TRUE);
1724                         keyword_tree_default_append(keyword_tree, &i2, _("Wildlife"), TRUE);
1725                         keyword_tree_default_append(keyword_tree, &i2, _("Zoo"), TRUE);
1726                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Plant"), TRUE);
1727                         keyword_tree_default_append(keyword_tree, &i2, _("Tree"), TRUE);
1728                         keyword_tree_default_append(keyword_tree, &i2, _("Flower"), TRUE);
1729                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Water"), TRUE);
1730                         keyword_tree_default_append(keyword_tree, &i2, _("River"), TRUE);
1731                         keyword_tree_default_append(keyword_tree, &i2, _("Lake"), TRUE);
1732                         keyword_tree_default_append(keyword_tree, &i2, _("Sea"), TRUE);
1733                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Landscape"), TRUE);
1734         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Art"), TRUE);
1735                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Statue"), TRUE);
1736                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Painting"), TRUE);
1737                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1738                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1739         i1 = keyword_tree_default_append(keyword_tree, NULL, _("City"), TRUE);
1740                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Park"), TRUE);
1741                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Street"), TRUE);
1742                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Square"), TRUE);
1743         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Architecture"), TRUE);
1744                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Buildings"), FALSE);
1745                         keyword_tree_default_append(keyword_tree, &i2, _("House"), TRUE);
1746                         keyword_tree_default_append(keyword_tree, &i2, _("Cathedral"), TRUE);
1747                         keyword_tree_default_append(keyword_tree, &i2, _("Palace"), TRUE);
1748                         keyword_tree_default_append(keyword_tree, &i2, _("Castle"), TRUE);
1749                         keyword_tree_default_append(keyword_tree, &i2, _("Bridge"), TRUE);
1750                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Interior"), TRUE);
1751                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1752                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1753         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Places"), FALSE);
1754         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Conditions"), FALSE);
1755                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Night"), TRUE);
1756                         keyword_tree_default_append(keyword_tree, &i2, _("Lights"), TRUE);
1757                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Reflections"), TRUE);
1758                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sun"), TRUE);
1759                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Weather"), FALSE);
1760                         keyword_tree_default_append(keyword_tree, &i2, _("Fog"), TRUE);
1761                         keyword_tree_default_append(keyword_tree, &i2, _("Rain"), TRUE);
1762                         keyword_tree_default_append(keyword_tree, &i2, _("Clouds"), TRUE);
1763                         keyword_tree_default_append(keyword_tree, &i2, _("Snow"), TRUE);
1764                         keyword_tree_default_append(keyword_tree, &i2, _("Sunny weather"), TRUE);
1765         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Photo"), FALSE);
1766                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Edited"), TRUE);
1767                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Detail"), TRUE);
1768                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Macro"), TRUE);
1769                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Portrait"), TRUE);
1770                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Black and White"), TRUE);
1771                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Perspective"), TRUE);
1772 }
1773
1774
1775 static void keyword_tree_node_write_config(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GString *outstr, gint indent)
1776 {
1777         GtkTreeIter iter = *iter_ptr;
1778         while (TRUE)
1779                 {
1780                 GtkTreeIter children;
1781                 gchar *name;
1782                 gchar *mark_str;
1783
1784                 WRITE_NL(); WRITE_STRING("<keyword ");
1785                 name = keyword_get_name(keyword_tree, &iter);
1786                 write_char_option(outstr, indent, "name", name);
1787                 g_free(name);
1788                 write_bool_option(outstr, indent, "kw", keyword_get_is_keyword(keyword_tree, &iter));
1789                 mark_str = keyword_get_mark(keyword_tree, &iter);
1790                 if (mark_str && mark_str[0])
1791                         {
1792                         write_char_option(outstr, indent, "mark", mark_str);
1793                         }
1794
1795                 if (gtk_tree_model_iter_children(keyword_tree, &children, &iter))
1796                         {
1797                         WRITE_STRING(">");
1798                         indent++;
1799                         keyword_tree_node_write_config(keyword_tree, &children, outstr, indent);
1800                         indent--;
1801                         WRITE_NL(); WRITE_STRING("</keyword>");
1802                         }
1803                 else
1804                         {
1805                         WRITE_STRING("/>");
1806                         }
1807                 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return;
1808                 }
1809 }
1810
1811 void keyword_tree_write_config(GString *outstr, gint indent)
1812 {
1813         GtkTreeIter iter;
1814         WRITE_NL(); WRITE_STRING("<keyword_tree>");
1815         indent++;
1816
1817         if (keyword_tree && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1818                 {
1819                 keyword_tree_node_write_config(GTK_TREE_MODEL(keyword_tree), &iter, outstr, indent);
1820                 }
1821         indent--;
1822         WRITE_NL(); WRITE_STRING("</keyword_tree>");
1823 }
1824
1825  void keyword_tree_node_disconnect_marks(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
1826 {
1827         GtkTreeIter iter = *iter_ptr;
1828
1829         while (TRUE)
1830                 {
1831                 GtkTreeIter children;
1832
1833                 meta_data_connect_mark_with_keyword((keyword_tree), &iter, -1);
1834
1835                 if (gtk_tree_model_iter_children((keyword_tree), &children, &iter))
1836                         {
1837                         keyword_tree_node_disconnect_marks((keyword_tree), &children);
1838                         }
1839
1840                 if (!gtk_tree_model_iter_next((keyword_tree), &iter)) return;
1841                 }
1842 }
1843
1844 void keyword_tree_disconnect_marks()
1845 {
1846         GtkTreeIter iter;
1847
1848         if (keyword_tree && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1849                 {
1850                 keyword_tree_node_disconnect_marks(GTK_TREE_MODEL(keyword_tree), &iter);
1851                 }
1852 }
1853
1854 GtkTreeIter *keyword_add_from_config(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar **attribute_names, const gchar **attribute_values)
1855 {
1856         gchar *name = NULL;
1857         gboolean is_kw = TRUE;
1858         gchar *mark_str = NULL;
1859
1860         while (*attribute_names)
1861                 {
1862                 const gchar *option = *attribute_names++;
1863                 const gchar *value = *attribute_values++;
1864
1865                 if (READ_CHAR_FULL("name", name)) continue;
1866                 if (READ_BOOL_FULL("kw", is_kw)) continue;
1867                 if (READ_CHAR_FULL("mark", mark_str)) continue;
1868
1869                 log_printf("unknown attribute %s = %s\n", option, value);
1870                 }
1871         if (name && name[0])
1872                 {
1873                 GtkTreeIter iter;
1874                 /* re-use existing keyword if any */
1875                 if (!keyword_exists(GTK_TREE_MODEL(keyword_tree), parent, NULL, name, FALSE, &iter))
1876                         {
1877                         gtk_tree_store_append(keyword_tree, &iter, parent);
1878                         }
1879                 keyword_set(keyword_tree, &iter, name, is_kw);
1880
1881                 if (mark_str)
1882                         {
1883                         gint i = (gint)atoi(mark_str);
1884                         if (i == 0) i = 10;
1885
1886                         meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree),
1887                                                                                         &iter, i - 1);
1888                         }
1889
1890                 g_free(name);
1891                 return gtk_tree_iter_copy(&iter);
1892                 }
1893         g_free(name);
1894         return NULL;
1895 }
1896
1897 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */