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