Simplify vflist_get_formatted()
[geeqie.git] / src / metadata.c
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 "ui_misc.h"
34 #include "utilops.h"
35 #include "filefilter.h"
36 #include "layout_util.h"
37 #include "rcfile.h"
38
39 typedef enum {
40         MK_NONE,
41         MK_KEYWORDS,
42         MK_COMMENT
43 } MetadataKey;
44
45 static const gchar *group_keys[] = { /* tags that will be written to all files in a group, options->metadata.sync_grouped_files */
46         "Xmp.dc.title",
47         "Xmp.photoshop.Urgency",
48         "Xmp.photoshop.Category",
49         "Xmp.photoshop.SupplementalCategory",
50         "Xmp.dc.subject",
51         "Xmp.iptc.Location",
52         "Xmp.photoshop.Instruction",
53         "Xmp.photoshop.DateCreated",
54         "Xmp.dc.creator",
55         "Xmp.photoshop.AuthorsPosition",
56         "Xmp.photoshop.City",
57         "Xmp.photoshop.State",
58         "Xmp.iptc.CountryCode",
59         "Xmp.photoshop.Country",
60         "Xmp.photoshop.TransmissionReference",
61         "Xmp.photoshop.Headline",
62         "Xmp.photoshop.Credit",
63         "Xmp.photoshop.Source",
64         "Xmp.dc.rights",
65         "Xmp.dc.description",
66         "Xmp.photoshop.CaptionWriter",
67         NULL};
68
69 static gboolean metadata_write_queue_idle_cb(gpointer data);
70 static gboolean metadata_legacy_write(FileData *fd);
71 static void metadata_legacy_delete(FileData *fd, const gchar *except);
72 static gboolean metadata_file_read(gchar *path, GList **keywords, gchar **comment);
73
74
75 /*
76  *-------------------------------------------------------------------
77  * long-term cache - keep keywords from whole dir in memory
78  *-------------------------------------------------------------------
79  */
80
81 /* fd->cached metadata list of lists
82    each particular list contains key as a first entry, then the values
83 */
84
85 static void metadata_cache_update(FileData *fd, const gchar *key, const GList *values)
86 {
87         GList *work;
88
89         work = fd->cached_metadata;
90         while (work)
91                 {
92                 GList *entry = work->data;
93                 gchar *entry_key = entry->data;
94
95                 if (strcmp(entry_key, key) == 0)
96                         {
97                         /* key found - just replace values */
98                         GList *old_values = entry->next;
99                         entry->next = NULL;
100                         old_values->prev = NULL;
101                         string_list_free(old_values);
102                         work->data = g_list_append(entry, string_list_copy(values));
103                         DEBUG_1("updated %s %s\n", key, fd->path);
104                         return;
105                         }
106                 work = work->next;
107                 }
108
109         /* key not found - prepend new entry */
110         fd->cached_metadata = g_list_prepend(fd->cached_metadata,
111                                 g_list_prepend(string_list_copy(values), g_strdup(key)));
112         DEBUG_1("added %s %s\n", key, fd->path);
113
114 }
115
116 static const GList *metadata_cache_get(FileData *fd, const gchar *key)
117 {
118         GList *work;
119
120         work = fd->cached_metadata;
121         while (work)
122                 {
123                 GList *entry = work->data;
124                 gchar *entry_key = entry->data;
125
126                 if (strcmp(entry_key, key) == 0)
127                         {
128                         /* key found */
129                         DEBUG_1("found %s %s\n", key, fd->path);
130                         return entry;
131                         }
132                 work = work->next;
133                 }
134         return NULL;
135         DEBUG_1("not found %s %s\n", key, fd->path);
136 }
137
138 static void metadata_cache_remove(FileData *fd, const gchar *key)
139 {
140         GList *work;
141
142         work = fd->cached_metadata;
143         while (work)
144                 {
145                 GList *entry = work->data;
146                 gchar *entry_key = entry->data;
147
148                 if (strcmp(entry_key, key) == 0)
149                         {
150                         /* key found */
151                         string_list_free(entry);
152                         fd->cached_metadata = g_list_delete_link(fd->cached_metadata, work);
153                         DEBUG_1("removed %s %s\n", key, fd->path);
154                         return;
155                         }
156                 work = work->next;
157                 }
158         DEBUG_1("not removed %s %s\n", key, fd->path);
159 }
160
161 void metadata_cache_free(FileData *fd)
162 {
163         GList *work;
164         if (fd->cached_metadata) DEBUG_1("freed %s\n", fd->path);
165
166         work = fd->cached_metadata;
167         while (work)
168                 {
169                 GList *entry = work->data;
170                 string_list_free(entry);
171
172                 work = work->next;
173                 }
174         g_list_free(fd->cached_metadata);
175         fd->cached_metadata = NULL;
176 }
177
178
179
180
181
182
183 /*
184  *-------------------------------------------------------------------
185  * write queue
186  *-------------------------------------------------------------------
187  */
188
189 static GList *metadata_write_queue = NULL;
190 static guint metadata_write_idle_id = 0; /* event source id */
191
192 static void metadata_write_queue_add(FileData *fd)
193 {
194         if (!g_list_find(metadata_write_queue, fd))
195                 {
196                 metadata_write_queue = g_list_prepend(metadata_write_queue, fd);
197                 file_data_ref(fd);
198
199                 layout_util_status_update_write_all();
200                 }
201
202         if (metadata_write_idle_id)
203                 {
204                 g_source_remove(metadata_write_idle_id);
205                 metadata_write_idle_id = 0;
206                 }
207
208         if (options->metadata.confirm_after_timeout)
209                 {
210                 metadata_write_idle_id = g_timeout_add(options->metadata.confirm_timeout * 1000, metadata_write_queue_idle_cb, NULL);
211                 }
212 }
213
214
215 gboolean metadata_write_queue_remove(FileData *fd)
216 {
217         g_hash_table_destroy(fd->modified_xmp);
218         fd->modified_xmp = NULL;
219
220         metadata_write_queue = g_list_remove(metadata_write_queue, fd);
221
222         file_data_increment_version(fd);
223         file_data_send_notification(fd, NOTIFY_REREAD);
224
225         file_data_unref(fd);
226
227         layout_util_status_update_write_all();
228         return TRUE;
229 }
230
231 gboolean metadata_write_queue_remove_list(GList *list)
232 {
233         GList *work;
234         gboolean ret = TRUE;
235
236         work = list;
237         while (work)
238                 {
239                 FileData *fd = work->data;
240                 work = work->next;
241                 ret = ret && metadata_write_queue_remove(fd);
242                 }
243         return ret;
244 }
245
246 void metadata_notify_cb(FileData *fd, NotifyType type, gpointer data)
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 = NULL;
268
269         work = metadata_write_queue;
270         while (work)
271                 {
272                 FileData *fd = 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(NULL, to_approve, NULL, force_dialog, done_func, done_data);
288
289         return (metadata_write_queue != NULL);
290 }
291
292 static gboolean metadata_write_queue_idle_cb(gpointer data)
293 {
294         metadata_write_queue_confirm(FALSE, NULL, NULL);
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(void)
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, (GDestroyNotify)string_list_free);
382                 }
383         g_hash_table_insert(fd->modified_xmp, g_strdup(key), string_list_copy((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                         FileData *sfd = 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(NULL, g_strdup(value));
417         gboolean ret = metadata_write_list(fd, key, list);
418         string_list_free(list);
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", (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                 const gchar *word = 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 = NULL;
473         gchar *orig_comment = NULL;
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, NULL, &keywords);
484         have_comment = g_hash_table_lookup_extended(fd->modified_xmp, COMMENT_KEY, NULL, &comment_l);
485         comment = (have_comment && comment_l) ? ((GList *)comment_l)->data : NULL;
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 ? (GList *)keywords : orig_keywords,
491                                       have_comment ? comment : orig_comment);
492
493         g_free(metadata_pathl);
494         g_free(orig_comment);
495         string_list_free(orig_keywords);
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 = NULL;
506         GString *comment_build = NULL;
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                 string_list_free(list);
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 = NULL;
649
650         while (work)
651                 {
652                 gchar *key = work->data;
653
654                 if (g_hash_table_lookup(hashtable, key) == NULL)
655                         {
656                         g_hash_table_insert(hashtable, (gpointer) 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 = NULL;
672         const GList *cache_entry;
673         if (!fd) return NULL;
674
675         /* unwritten data override everything */
676         if (fd->modified_xmp && format == METADATA_PLAIN)
677                 {
678                 list = 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, NULL))
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 = NULL;
710                 if (metadata_legacy_read(fd, NULL, &comment)) return g_list_append(NULL, comment);
711                 }
712         else if (strncmp(key, "file.", 5) == 0)
713                 {
714                 return g_list_append(NULL, 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(NULL, 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 NULL;
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                 gchar *str = string_list->data;
742                 string_list->data = NULL;
743                 string_list_free(string_list);
744                 return str;
745                 }
746         return NULL;
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         else
849                 {
850                 gchar *new_string = g_strconcat(str, value, NULL);
851                 gboolean ret = metadata_write_string(fd, key, new_string);
852                 g_free(str);
853                 g_free(new_string);
854                 return ret;
855                 }
856 }
857
858 gboolean metadata_write_GPS_coord(FileData *fd, const gchar *key, gdouble value)
859 {
860         gint deg;
861         gdouble min;
862         gdouble param;
863         char *coordinate;
864         char *ref;
865         gboolean ok = TRUE;
866         char *old_locale, *saved_locale;
867
868         param = value;
869         if (param < 0)
870                 param = -param;
871         deg = param;
872         min = (param * 60) - (deg * 60);
873         if (g_strcmp0(key, "Xmp.exif.GPSLongitude") == 0)
874                 if (value < 0)
875                         ref = "W";
876                 else
877                         ref = "E";
878         else if (g_strcmp0(key, "Xmp.exif.GPSLatitude") == 0)
879                 if (value < 0)
880                         ref = "S";
881                 else
882                         ref = "N";
883         else
884                 {
885                 log_printf("unknown GPS parameter key '%s'\n", key);
886                 ok = FALSE;
887                 }
888
889         if (ok)
890                 {
891                 /* Avoid locale problems with commas and decimal points in numbers */
892                 old_locale = setlocale(LC_ALL, NULL);
893                 saved_locale = strdup(old_locale);
894                 if (saved_locale == NULL)
895                         {
896                         return FALSE;
897                         }
898                 setlocale(LC_ALL, "C");
899
900                 coordinate = g_strdup_printf("%i,%lf,%s", deg, min, ref);
901                 metadata_write_string(fd, key, coordinate );
902
903                 setlocale(LC_ALL, saved_locale);
904                 free(saved_locale);
905                 g_free(coordinate);
906                 }
907
908         return ok;
909 }
910
911 gboolean metadata_append_list(FileData *fd, const gchar *key, const GList *values)
912 {
913         GList *list = metadata_read_list(fd, key, METADATA_PLAIN);
914
915         if (!list)
916                 {
917                 return metadata_write_list(fd, key, values);
918                 }
919         else
920                 {
921                 gboolean ret;
922                 list = g_list_concat(list, string_list_copy(values));
923                 list = remove_duplicate_strings_from_list(list);
924
925                 ret = metadata_write_list(fd, key, list);
926                 string_list_free(list);
927                 return ret;
928                 }
929 }
930
931 /**
932  * @see find_string_in_list
933  */
934 gchar *find_string_in_list_utf8nocase(GList *list, const gchar *string)
935 {
936         gchar *string_casefold = g_utf8_casefold(string, -1);
937
938         while (list)
939                 {
940                 gchar *haystack = list->data;
941
942                 if (haystack)
943                         {
944                         gboolean equal;
945                         gchar *haystack_casefold = g_utf8_casefold(haystack, -1);
946
947                         equal = (strcmp(haystack_casefold, string_casefold) == 0);
948                         g_free(haystack_casefold);
949
950                         if (equal)
951                                 {
952                                 g_free(string_casefold);
953                                 return haystack;
954                                 }
955                         }
956
957                 list = list->next;
958                 }
959
960         g_free(string_casefold);
961         return NULL;
962 }
963
964 /**
965  * @see find_string_in_list
966  */
967 gchar *find_string_in_list_utf8case(GList *list, const gchar *string)
968 {
969         while (list)
970                 {
971                 gchar *haystack = list->data;
972
973                 if (haystack && strcmp(haystack, string) == 0)
974                         return haystack;
975
976                 list = list->next;
977                 } // while (list)
978
979         return NULL;
980 } // gchar *find_string_in_list_utf...
981
982 /**
983  * @brief Find a existent string in a list.
984  *
985  * This is a switch between find_string_in_list_utf8case and
986  * find_string_in_list_utf8nocase to search with or without case for the
987  * existence of a string.
988  *
989  * @param list The list to search in
990  * @param string The string to search for
991  * @return The string or NULL
992  *
993  * @see find_string_in_list_utf8case
994  * @see find_string_in_list_utf8nocase
995  */
996 gchar *find_string_in_list(GList *list, const gchar *string)
997 {
998         if (options->metadata.keywords_case_sensitive)
999                 return find_string_in_list_utf8case(list, string);
1000         else
1001                 return find_string_in_list_utf8nocase(list, string);
1002 }
1003
1004 #define KEYWORDS_SEPARATOR(c) ((c) == ',' || (c) == ';' || (c) == '\n' || (c) == '\r' || (c) == '\b')
1005
1006 GList *string_to_keywords_list(const gchar *text)
1007 {
1008         GList *list = NULL;
1009         const gchar *ptr = text;
1010
1011         while (*ptr != '\0')
1012                 {
1013                 const gchar *begin;
1014                 gint l = 0;
1015
1016                 while (KEYWORDS_SEPARATOR(*ptr)) ptr++;
1017                 begin = ptr;
1018                 while (*ptr != '\0' && !KEYWORDS_SEPARATOR(*ptr))
1019                         {
1020                         ptr++;
1021                         l++;
1022                         }
1023
1024                 /* trim starting and ending whitespaces */
1025                 while (l > 0 && g_ascii_isspace(*begin)) begin++, l--;
1026                 while (l > 0 && g_ascii_isspace(begin[l-1])) l--;
1027
1028                 if (l > 0)
1029                         {
1030                         gchar *keyword = g_strndup(begin, l);
1031
1032                         /* only add if not already in the list */
1033                         if (!find_string_in_list(list, keyword))
1034                                 list = g_list_append(list, keyword);
1035                         else
1036                                 g_free(keyword);
1037                         }
1038                 }
1039
1040         return list;
1041 }
1042
1043 /*
1044  * keywords to marks
1045  */
1046
1047
1048 gboolean meta_data_get_keyword_mark(FileData *fd, gint n, gpointer data)
1049 {
1050         /** @FIXME do not use global keyword_tree */
1051         GList *path = data;
1052         GList *keywords;
1053         gboolean found = FALSE;
1054         keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
1055         if (keywords)
1056                 {
1057                 GtkTreeIter iter;
1058                 if (keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path) &&
1059                     keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
1060                         found = TRUE;
1061
1062                 }
1063         return found;
1064 }
1065
1066 gboolean meta_data_set_keyword_mark(FileData *fd, gint n, gboolean value, gpointer data)
1067 {
1068         GList *path = data;
1069         GList *keywords = NULL;
1070         GtkTreeIter iter;
1071
1072         if (!keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path)) return FALSE;
1073
1074         keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
1075
1076         if (!!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords) != !!value)
1077                 {
1078                 if (value)
1079                         {
1080                         keyword_tree_set(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
1081                         }
1082                 else
1083                         {
1084                         keyword_tree_reset(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
1085                         }
1086                 metadata_write_list(fd, KEYWORD_KEY, keywords);
1087                 }
1088
1089         string_list_free(keywords);
1090         return TRUE;
1091 }
1092
1093
1094
1095 void meta_data_connect_mark_with_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *kw_iter, gint mark)
1096 {
1097
1098         FileDataGetMarkFunc get_mark_func;
1099         FileDataSetMarkFunc set_mark_func;
1100         gpointer mark_func_data;
1101
1102         gint i;
1103
1104         for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
1105                 {
1106                 file_data_get_registered_mark_func(i, &get_mark_func, &set_mark_func, &mark_func_data);
1107                 if (get_mark_func == meta_data_get_keyword_mark)
1108                         {
1109                         GtkTreeIter old_kw_iter;
1110                         GList *old_path = mark_func_data;
1111
1112                         if (keyword_tree_get_iter(keyword_tree, &old_kw_iter, old_path) &&
1113                             (i == mark || /* release any previous connection of given mark */
1114                              keyword_compare(keyword_tree, &old_kw_iter, kw_iter) == 0)) /* or given keyword */
1115                                 {
1116                                 file_data_register_mark_func(i, NULL, NULL, NULL, NULL);
1117                                 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), &old_kw_iter, KEYWORD_COLUMN_MARK, "", -1);
1118                                 }
1119                         }
1120                 }
1121
1122
1123         if (mark >= 0 && mark < FILEDATA_MARKS_SIZE)
1124                 {
1125                 GList *path;
1126                 gchar *mark_str;
1127                 path = keyword_tree_get_path(keyword_tree, kw_iter);
1128                 file_data_register_mark_func(mark, meta_data_get_keyword_mark, meta_data_set_keyword_mark, path, (GDestroyNotify)string_list_free);
1129
1130                 mark_str = g_strdup_printf("%d", (mark < 9 ? mark : -1) + 1);
1131                 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), kw_iter, KEYWORD_COLUMN_MARK, mark_str, -1);
1132                 g_free(mark_str);
1133                 }
1134 }
1135
1136
1137 /*
1138  *-------------------------------------------------------------------
1139  * keyword tree
1140  *-------------------------------------------------------------------
1141  */
1142
1143
1144
1145 GtkTreeStore *keyword_tree;
1146
1147 gchar *keyword_get_name(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1148 {
1149         gchar *name;
1150         gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_NAME, &name, -1);
1151         return name;
1152 }
1153
1154 gchar *keyword_get_mark(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1155 {
1156         gchar *mark_str;
1157
1158         gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_MARK, &mark_str, -1);
1159         return mark_str;
1160 }
1161
1162 gchar *keyword_get_casefold(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1163 {
1164         gchar *casefold;
1165         gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_CASEFOLD, &casefold, -1);
1166         return casefold;
1167 }
1168
1169 gboolean keyword_get_is_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1170 {
1171         gboolean is_keyword;
1172         gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
1173         return is_keyword;
1174 }
1175
1176 void keyword_set(GtkTreeStore *keyword_tree, GtkTreeIter *iter, const gchar *name, gboolean is_keyword)
1177 {
1178         gchar *casefold = g_utf8_casefold(name, -1);
1179         gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_MARK, "",
1180                                                 KEYWORD_COLUMN_NAME, name,
1181                                                 KEYWORD_COLUMN_CASEFOLD, casefold,
1182                                                 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
1183         g_free(casefold);
1184 }
1185
1186 gboolean keyword_compare(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
1187 {
1188         GtkTreePath *pa = gtk_tree_model_get_path(keyword_tree, a);
1189         GtkTreePath *pb = gtk_tree_model_get_path(keyword_tree, b);
1190         gint ret = gtk_tree_path_compare(pa, pb);
1191         gtk_tree_path_free(pa);
1192         gtk_tree_path_free(pb);
1193         return ret;
1194 }
1195
1196 gboolean keyword_same_parent(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
1197 {
1198         GtkTreeIter parent_a;
1199         GtkTreeIter parent_b;
1200
1201         gboolean valid_pa = gtk_tree_model_iter_parent(keyword_tree, &parent_a, a);
1202         gboolean valid_pb = gtk_tree_model_iter_parent(keyword_tree, &parent_b, b);
1203
1204         if (valid_pa && valid_pb)
1205                 {
1206                 return keyword_compare(keyword_tree, &parent_a, &parent_b) == 0;
1207                 }
1208         else
1209                 {
1210                 return (!valid_pa && !valid_pb); /* both are toplevel */
1211                 }
1212 }
1213
1214 gboolean keyword_exists(GtkTreeModel *keyword_tree, GtkTreeIter *parent_ptr, GtkTreeIter *sibling, const gchar *name, gboolean exclude_sibling, GtkTreeIter *result)
1215 {
1216         GtkTreeIter parent;
1217         GtkTreeIter iter;
1218         gboolean toplevel = FALSE;
1219         gboolean ret;
1220         gchar *casefold;
1221
1222         if (parent_ptr)
1223                 {
1224                 parent = *parent_ptr;
1225                 }
1226         else if (sibling)
1227                 {
1228                 toplevel = !gtk_tree_model_iter_parent(keyword_tree, &parent, sibling);
1229                 }
1230         else
1231                 {
1232                 toplevel = TRUE;
1233                 }
1234
1235         if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &iter, toplevel ? NULL : &parent)) return FALSE;
1236
1237         casefold = g_utf8_casefold(name, -1);
1238         ret = FALSE;
1239
1240         while (TRUE)
1241                 {
1242                 if (!(exclude_sibling && sibling && keyword_compare(keyword_tree, &iter, sibling) == 0))
1243                         {
1244                         if (options->metadata.keywords_case_sensitive)
1245                                 {
1246                                 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
1247                                 ret = strcmp(name, iter_name) == 0;
1248                                 g_free(iter_name);
1249                                 }
1250                         else
1251                                 {
1252                                 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
1253                                 ret = strcmp(casefold, iter_casefold) == 0;
1254                                 g_free(iter_casefold);
1255                                 } // if (options->metadata.tags_cas...
1256                         }
1257                 if (ret)
1258                         {
1259                         if (result) *result = iter;
1260                         break;
1261                         }
1262                 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) break;
1263                 }
1264         g_free(casefold);
1265         return ret;
1266 }
1267
1268
1269 void keyword_copy(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
1270 {
1271
1272         gchar *mark, *name, *casefold;
1273         gboolean is_keyword;
1274
1275         /* do not copy KEYWORD_COLUMN_HIDE_IN, it fully shows the new subtree */
1276         gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), from, KEYWORD_COLUMN_MARK, &mark,
1277                                                 KEYWORD_COLUMN_NAME, &name,
1278                                                 KEYWORD_COLUMN_CASEFOLD, &casefold,
1279                                                 KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
1280
1281         gtk_tree_store_set(keyword_tree, to, KEYWORD_COLUMN_MARK, mark,
1282                                                 KEYWORD_COLUMN_NAME, name,
1283                                                 KEYWORD_COLUMN_CASEFOLD, casefold,
1284                                                 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
1285         g_free(mark);
1286         g_free(name);
1287         g_free(casefold);
1288 }
1289
1290 void keyword_copy_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
1291 {
1292         GtkTreeIter from_child;
1293
1294         keyword_copy(keyword_tree, to, from);
1295
1296         if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &from_child, from)) return;
1297
1298         while (TRUE)
1299                 {
1300                 GtkTreeIter to_child;
1301                 gtk_tree_store_append(keyword_tree, &to_child, to);
1302                 keyword_copy_recursive(keyword_tree, &to_child, &from_child);
1303                 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &from_child)) return;
1304                 }
1305 }
1306
1307 void keyword_move_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
1308 {
1309         keyword_copy_recursive(keyword_tree, to, from);
1310         keyword_delete(keyword_tree, from);
1311 }
1312
1313 GList *keyword_tree_get_path(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
1314 {
1315         GList *path = NULL;
1316         GtkTreeIter iter = *iter_ptr;
1317
1318         while (TRUE)
1319                 {
1320                 GtkTreeIter parent;
1321                 path = g_list_prepend(path, keyword_get_name(keyword_tree, &iter));
1322                 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) break;
1323                 iter = parent;
1324                 }
1325         return path;
1326 }
1327
1328 gboolean keyword_tree_get_iter(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList *path)
1329 {
1330         GtkTreeIter iter;
1331
1332         if (!gtk_tree_model_get_iter_first(keyword_tree, &iter)) return FALSE;
1333
1334         while (TRUE)
1335                 {
1336                 GtkTreeIter children;
1337                 while (TRUE)
1338                         {
1339                         gchar *name = keyword_get_name(keyword_tree, &iter);
1340                         if (strcmp(name, path->data) == 0) break;
1341                         g_free(name);
1342                         if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return FALSE;
1343                         }
1344                 path = path->next;
1345                 if (!path)
1346                         {
1347                         *iter_ptr = iter;
1348                         return TRUE;
1349                         }
1350
1351                 if (!gtk_tree_model_iter_children(keyword_tree, &children, &iter)) return FALSE;
1352                 iter = children;
1353                 }
1354 }
1355
1356
1357 static gboolean keyword_tree_is_set_casefold(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *casefold_list)
1358 {
1359         if (!casefold_list) return FALSE;
1360
1361         if (!keyword_get_is_keyword(keyword_tree, &iter))
1362                 {
1363                 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1364                 GtkTreeIter child;
1365                 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1366                         return FALSE; /* this should happen only on empty helpers */
1367
1368                 while (TRUE)
1369                         {
1370                         if (keyword_tree_is_set_casefold(keyword_tree, child, casefold_list)) return TRUE;
1371                         if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1372                         }
1373                 }
1374
1375         while (TRUE)
1376                 {
1377                 GtkTreeIter parent;
1378
1379                 if (keyword_get_is_keyword(keyword_tree, &iter))
1380                         {
1381                         GList *work = casefold_list;
1382                         gboolean found = FALSE;
1383                         gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
1384                         while (work)
1385                                 {
1386                                 const gchar *casefold = work->data;
1387                                 work = work->next;
1388
1389                                 if (strcmp(iter_casefold, casefold) == 0)
1390                                         {
1391                                         found = TRUE;
1392                                         break;
1393                                         }
1394                                 }
1395                         g_free(iter_casefold);
1396                         if (!found) return FALSE;
1397                         }
1398
1399                 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1400                 iter = parent;
1401                 }
1402 }
1403
1404 static gboolean keyword_tree_is_set_casefull(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *kw_list)
1405 {
1406         if (!kw_list) return FALSE;
1407
1408         if (!keyword_get_is_keyword(keyword_tree, &iter))
1409                 {
1410                 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1411                 GtkTreeIter child;
1412                 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1413                         return FALSE; /* this should happen only on empty helpers */
1414
1415                 while (TRUE)
1416                         {
1417                         if (keyword_tree_is_set_casefull(keyword_tree, child, kw_list)) return TRUE;
1418                         if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1419                         }
1420                 }
1421
1422         while (TRUE)
1423                 {
1424                 GtkTreeIter parent;
1425
1426                 if (keyword_get_is_keyword(keyword_tree, &iter))
1427                         {
1428                         GList *work = kw_list;
1429                         gboolean found = FALSE;
1430                         gchar *iter_name = keyword_get_name(keyword_tree, &iter);
1431                         while (work)
1432                                 {
1433                                 const gchar *name = work->data;
1434                                 work = work->next;
1435
1436                                 if (strcmp(iter_name, name) == 0)
1437                                         {
1438                                         found = TRUE;
1439                                         break;
1440                                         }
1441                                 }
1442                         g_free(iter_name);
1443                         if (!found) return FALSE;
1444                         }
1445
1446                 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1447                 iter = parent;
1448                 }
1449 }
1450
1451 gboolean keyword_tree_is_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList *kw_list)
1452 {
1453         gboolean ret;
1454         GList *casefold_list = NULL;
1455         GList *work;
1456
1457         if (options->metadata.keywords_case_sensitive)
1458                 {
1459                 ret = keyword_tree_is_set_casefull(keyword_tree, *iter, kw_list);
1460                 }
1461         else
1462                 {
1463                 work = kw_list;
1464                 while (work)
1465                         {
1466                         const gchar *kw = work->data;
1467                         work = work->next;
1468
1469                         casefold_list = g_list_prepend(casefold_list, g_utf8_casefold(kw, -1));
1470                         }
1471
1472                 ret = keyword_tree_is_set_casefold(keyword_tree, *iter, casefold_list);
1473
1474                 string_list_free(casefold_list);
1475                 }
1476
1477         return ret;
1478 }
1479
1480 void keyword_tree_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1481 {
1482         GtkTreeIter iter = *iter_ptr;
1483         while (TRUE)
1484                 {
1485                 GtkTreeIter parent;
1486
1487                 if (keyword_get_is_keyword(keyword_tree, &iter))
1488                         {
1489                         gchar *name = keyword_get_name(keyword_tree, &iter);
1490                         if (!find_string_in_list(*kw_list, name))
1491                                 {
1492                                 *kw_list = g_list_append(*kw_list, name);
1493                                 }
1494                         else
1495                                 {
1496                                 g_free(name);
1497                                 }
1498                         }
1499
1500                 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1501                 iter = parent;
1502                 }
1503 }
1504
1505 GList *keyword_tree_get(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
1506 {
1507         GtkTreeIter iter = *iter_ptr;
1508         GList *kw_list = NULL;
1509
1510         while (TRUE)
1511                 {
1512                 GtkTreeIter parent;
1513
1514                 if (keyword_get_is_keyword(keyword_tree, &iter))
1515                         {
1516                         gchar *name = keyword_get_name(keyword_tree, &iter);
1517                         kw_list = g_list_append(kw_list, name);
1518                         }
1519
1520                 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return kw_list;
1521                 iter = parent;
1522                 }
1523 } // GList *keyword_tree_get(GtkTre...
1524
1525 static void keyword_tree_reset1(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1526 {
1527         gchar *found;
1528         gchar *name;
1529         if (!keyword_get_is_keyword(keyword_tree, iter)) return;
1530
1531         name = keyword_get_name(keyword_tree, iter);
1532         found = find_string_in_list(*kw_list, name);
1533
1534         if (found)
1535                 {
1536                 *kw_list = g_list_remove(*kw_list, found);
1537                 g_free(found);
1538                 }
1539         g_free(name);
1540 }
1541
1542 static void keyword_tree_reset_recursive(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1543 {
1544         GtkTreeIter child;
1545         keyword_tree_reset1(keyword_tree, iter, kw_list);
1546
1547         if (!gtk_tree_model_iter_children(keyword_tree, &child, iter)) return;
1548
1549         while (TRUE)
1550                 {
1551                 keyword_tree_reset_recursive(keyword_tree, &child, kw_list);
1552                 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return;
1553                 }
1554 }
1555
1556 static gboolean keyword_tree_check_empty_children(GtkTreeModel *keyword_tree, GtkTreeIter *parent, GList *kw_list)
1557 {
1558         GtkTreeIter iter;
1559
1560         if (!gtk_tree_model_iter_children(keyword_tree, &iter, parent))
1561                 return TRUE; /* this should happen only on empty helpers */
1562
1563         while (TRUE)
1564                 {
1565                 if (keyword_tree_is_set(keyword_tree, &iter, kw_list)) return FALSE;
1566                 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return TRUE;
1567                 }
1568 }
1569
1570 void keyword_tree_reset(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1571 {
1572         GtkTreeIter iter = *iter_ptr;
1573         GtkTreeIter parent;
1574         keyword_tree_reset_recursive(keyword_tree, &iter, kw_list);
1575
1576         if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1577         iter = parent;
1578
1579         while (keyword_tree_check_empty_children(keyword_tree, &iter, *kw_list))
1580                 {
1581                 GtkTreeIter parent;
1582                 keyword_tree_reset1(keyword_tree, &iter, kw_list);
1583                 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1584                 iter = parent;
1585                 }
1586 }
1587
1588 void keyword_delete(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr)
1589 {
1590         GList *list;
1591         GtkTreeIter child;
1592         while (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, iter_ptr))
1593                 {
1594                 keyword_delete(keyword_tree, &child);
1595                 }
1596
1597         meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree), iter_ptr, -1);
1598
1599         gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter_ptr, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1600         g_list_free(list);
1601
1602         gtk_tree_store_remove(keyword_tree, iter_ptr);
1603 }
1604
1605
1606 void keyword_hide_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1607 {
1608         GList *list;
1609         gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1610         if (!g_list_find(list, id))
1611                 {
1612                 list = g_list_prepend(list, id);
1613                 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1614                 }
1615 }
1616
1617 void keyword_show_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1618 {
1619         GList *list;
1620         gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1621         list = g_list_remove(list, id);
1622         gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1623 }
1624
1625 gboolean keyword_is_hidden_in(GtkTreeModel *keyword_tree, GtkTreeIter *iter, gpointer id)
1626 {
1627         GList *list;
1628         gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1629         return !!g_list_find(list, id);
1630 }
1631
1632 static gboolean keyword_show_all_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
1633 {
1634         keyword_show_in(GTK_TREE_STORE(model), iter, data);
1635         return FALSE;
1636 }
1637
1638 void keyword_show_all_in(GtkTreeStore *keyword_tree, gpointer id)
1639 {
1640         gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_all_in_cb, id);
1641 }
1642
1643 static gboolean keyword_revert_hidden_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
1644 {
1645         if (keyword_is_hidden_in(GTK_TREE_MODEL(keyword_tree), iter, data))
1646                 {
1647                 keyword_show_in(GTK_TREE_STORE(model), iter, data);
1648                 }
1649         return FALSE;
1650 }
1651
1652 void keyword_revert_hidden_in(GtkTreeStore *keyword_tree, gpointer id)
1653 {
1654         gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_revert_hidden_in_cb, id);
1655 }
1656
1657 static void keyword_hide_unset_in_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr, gpointer id, GList *keywords)
1658 {
1659         GtkTreeIter iter = *iter_ptr;
1660         while (TRUE)
1661                 {
1662                 if (!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
1663                         {
1664                         keyword_hide_in(keyword_tree, &iter, id);
1665                         /* no need to check children of hidden node */
1666                         }
1667                 else
1668                         {
1669                         GtkTreeIter child;
1670                         if (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, &iter))
1671                                 {
1672                                 keyword_hide_unset_in_recursive(keyword_tree, &child, id, keywords);
1673                                 }
1674                         }
1675                 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1676                 }
1677 }
1678
1679 void keyword_hide_unset_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1680 {
1681         GtkTreeIter iter;
1682         if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1683         keyword_hide_unset_in_recursive(keyword_tree, &iter, id, keywords);
1684 }
1685
1686 static gboolean keyword_show_set_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter_ptr, gpointer data)
1687 {
1688         GtkTreeIter iter = *iter_ptr;
1689         GList *keywords = data;
1690         gpointer id = keywords->data;
1691         keywords = keywords->next; /* hack */
1692         if (keyword_tree_is_set(model, &iter, keywords))
1693                 {
1694                 while (TRUE)
1695                         {
1696                         GtkTreeIter parent;
1697                         keyword_show_in(GTK_TREE_STORE(model), &iter, id);
1698                         if (!gtk_tree_model_iter_parent(GTK_TREE_MODEL(keyword_tree), &parent, &iter)) break;
1699                         iter = parent;
1700                         }
1701                 }
1702         return FALSE;
1703 }
1704
1705 void keyword_show_set_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1706 {
1707         /* hack: pass id to keyword_hide_unset_in_cb in the list */
1708         keywords = g_list_prepend(keywords, id);
1709         gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_set_in_cb, keywords);
1710         keywords = g_list_delete_link(keywords, keywords);
1711 }
1712
1713
1714 void keyword_tree_new(void)
1715 {
1716         if (keyword_tree) return;
1717
1718         keyword_tree = gtk_tree_store_new(KEYWORD_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER);
1719 }
1720
1721 static GtkTreeIter keyword_tree_default_append(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar *name, gboolean is_keyword)
1722 {
1723         GtkTreeIter iter;
1724         gtk_tree_store_append(keyword_tree, &iter, parent);
1725         keyword_set(keyword_tree, &iter, name, is_keyword);
1726         return iter;
1727 }
1728
1729 void keyword_tree_new_default(void)
1730 {
1731         GtkTreeIter i1, i2;
1732
1733         if (!keyword_tree) keyword_tree_new();
1734
1735         i1 = keyword_tree_default_append(keyword_tree, NULL, _("People"), TRUE);
1736                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Family"), TRUE);
1737                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Free time"), TRUE);
1738                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Children"), TRUE);
1739                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sport"), TRUE);
1740                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Culture"), TRUE);
1741                         keyword_tree_default_append(keyword_tree, &i2, _("Festival"), TRUE);
1742         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Nature"), TRUE);
1743                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Animal"), TRUE);
1744                         keyword_tree_default_append(keyword_tree, &i2, _("Bird"), TRUE);
1745                         keyword_tree_default_append(keyword_tree, &i2, _("Insect"), TRUE);
1746                         keyword_tree_default_append(keyword_tree, &i2, _("Pets"), TRUE);
1747                         keyword_tree_default_append(keyword_tree, &i2, _("Wildlife"), TRUE);
1748                         keyword_tree_default_append(keyword_tree, &i2, _("Zoo"), TRUE);
1749                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Plant"), TRUE);
1750                         keyword_tree_default_append(keyword_tree, &i2, _("Tree"), TRUE);
1751                         keyword_tree_default_append(keyword_tree, &i2, _("Flower"), TRUE);
1752                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Water"), TRUE);
1753                         keyword_tree_default_append(keyword_tree, &i2, _("River"), TRUE);
1754                         keyword_tree_default_append(keyword_tree, &i2, _("Lake"), TRUE);
1755                         keyword_tree_default_append(keyword_tree, &i2, _("Sea"), TRUE);
1756                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Landscape"), TRUE);
1757         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Art"), TRUE);
1758                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Statue"), TRUE);
1759                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Painting"), TRUE);
1760                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1761                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1762         i1 = keyword_tree_default_append(keyword_tree, NULL, _("City"), TRUE);
1763                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Park"), TRUE);
1764                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Street"), TRUE);
1765                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Square"), TRUE);
1766         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Architecture"), TRUE);
1767                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Buildings"), FALSE);
1768                         keyword_tree_default_append(keyword_tree, &i2, _("House"), TRUE);
1769                         keyword_tree_default_append(keyword_tree, &i2, _("Cathedral"), TRUE);
1770                         keyword_tree_default_append(keyword_tree, &i2, _("Palace"), TRUE);
1771                         keyword_tree_default_append(keyword_tree, &i2, _("Castle"), TRUE);
1772                         keyword_tree_default_append(keyword_tree, &i2, _("Bridge"), TRUE);
1773                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Interior"), TRUE);
1774                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1775                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1776         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Places"), FALSE);
1777         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Conditions"), FALSE);
1778                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Night"), TRUE);
1779                         keyword_tree_default_append(keyword_tree, &i2, _("Lights"), TRUE);
1780                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Reflections"), TRUE);
1781                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sun"), TRUE);
1782                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Weather"), FALSE);
1783                         keyword_tree_default_append(keyword_tree, &i2, _("Fog"), TRUE);
1784                         keyword_tree_default_append(keyword_tree, &i2, _("Rain"), TRUE);
1785                         keyword_tree_default_append(keyword_tree, &i2, _("Clouds"), TRUE);
1786                         keyword_tree_default_append(keyword_tree, &i2, _("Snow"), TRUE);
1787                         keyword_tree_default_append(keyword_tree, &i2, _("Sunny weather"), TRUE);
1788         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Photo"), FALSE);
1789                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Edited"), TRUE);
1790                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Detail"), TRUE);
1791                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Macro"), TRUE);
1792                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Portrait"), TRUE);
1793                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Black and White"), TRUE);
1794                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Perspective"), TRUE);
1795 }
1796
1797
1798 static void keyword_tree_node_write_config(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GString *outstr, gint indent)
1799 {
1800         GtkTreeIter iter = *iter_ptr;
1801         while (TRUE)
1802                 {
1803                 GtkTreeIter children;
1804                 gchar *name;
1805                 gchar *mark_str;
1806
1807                 WRITE_NL(); WRITE_STRING("<keyword ");
1808                 name = keyword_get_name(keyword_tree, &iter);
1809                 write_char_option(outstr, indent, "name", name);
1810                 g_free(name);
1811                 write_bool_option(outstr, indent, "kw", keyword_get_is_keyword(keyword_tree, &iter));
1812                 mark_str = keyword_get_mark(keyword_tree, &iter);
1813                 if (mark_str && mark_str[0])
1814                         {
1815                         write_char_option(outstr, indent, "mark", mark_str);
1816                         }
1817
1818                 if (gtk_tree_model_iter_children(keyword_tree, &children, &iter))
1819                         {
1820                         WRITE_STRING(">");
1821                         indent++;
1822                         keyword_tree_node_write_config(keyword_tree, &children, outstr, indent);
1823                         indent--;
1824                         WRITE_NL(); WRITE_STRING("</keyword>");
1825                         }
1826                 else
1827                         {
1828                         WRITE_STRING("/>");
1829                         }
1830                 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return;
1831                 }
1832 }
1833
1834 void keyword_tree_write_config(GString *outstr, gint indent)
1835 {
1836         GtkTreeIter iter;
1837         WRITE_NL(); WRITE_STRING("<keyword_tree>");
1838         indent++;
1839
1840         if (keyword_tree && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1841                 {
1842                 keyword_tree_node_write_config(GTK_TREE_MODEL(keyword_tree), &iter, outstr, indent);
1843                 }
1844         indent--;
1845         WRITE_NL(); WRITE_STRING("</keyword_tree>");
1846 }
1847
1848  void keyword_tree_node_disconnect_marks(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
1849 {
1850         GtkTreeIter iter = *iter_ptr;
1851
1852         while (TRUE)
1853                 {
1854                 GtkTreeIter children;
1855
1856                 meta_data_connect_mark_with_keyword((keyword_tree), &iter, -1);
1857
1858                 if (gtk_tree_model_iter_children((keyword_tree), &children, &iter))
1859                         {
1860                         keyword_tree_node_disconnect_marks((keyword_tree), &children);
1861                         }
1862
1863                 if (!gtk_tree_model_iter_next((keyword_tree), &iter)) return;
1864                 }
1865 }
1866
1867 void keyword_tree_disconnect_marks()
1868 {
1869         GtkTreeIter iter;
1870
1871         if (keyword_tree && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1872                 {
1873                 keyword_tree_node_disconnect_marks(GTK_TREE_MODEL(keyword_tree), &iter);
1874                 }
1875 }
1876
1877 GtkTreeIter *keyword_add_from_config(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar **attribute_names, const gchar **attribute_values)
1878 {
1879         gchar *name = NULL;
1880         gboolean is_kw = TRUE;
1881         gchar *mark_str = NULL;
1882
1883         while (*attribute_names)
1884                 {
1885                 const gchar *option = *attribute_names++;
1886                 const gchar *value = *attribute_values++;
1887
1888                 if (READ_CHAR_FULL("name", name)) continue;
1889                 if (READ_BOOL_FULL("kw", is_kw)) continue;
1890                 if (READ_CHAR_FULL("mark", mark_str)) continue;
1891
1892                 log_printf("unknown attribute %s = %s\n", option, value);
1893                 }
1894         if (name && name[0])
1895                 {
1896                 GtkTreeIter iter;
1897                 /* re-use existing keyword if any */
1898                 if (!keyword_exists(GTK_TREE_MODEL(keyword_tree), parent, NULL, name, FALSE, &iter))
1899                         {
1900                         gtk_tree_store_append(keyword_tree, &iter, parent);
1901                         }
1902                 keyword_set(keyword_tree, &iter, name, is_kw);
1903
1904                 if (mark_str)
1905                         {
1906                         gint i = (gint)atoi(mark_str);
1907                         if (i == 0) i = 10;
1908
1909                         meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree),
1910                                                                                         &iter, i - 1);
1911                         }
1912
1913                 g_free(name);
1914                 return gtk_tree_iter_copy(&iter);
1915                 }
1916         g_free(name);
1917         return NULL;
1918 }
1919
1920 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */