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