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