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