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