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