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