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