Fix #357: Save mark-and-keyword connections
[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_write_GPS_coord(FileData *fd, const gchar *key, gdouble value)
837 {
838         gint deg;
839         gdouble min;
840         gdouble param;
841         char *coordinate;
842         char *ref;
843         gboolean ok = TRUE;
844
845         param = value;
846         if (param < 0)
847                 param = -param;
848         deg = param;
849         min = (param * 60) - (deg * 60);
850         if (g_strcmp0(key, "Xmp.exif.GPSLongitude") == 0)
851                 if (value < 0)
852                         ref = "W";
853                 else
854                         ref = "E";
855         else if (g_strcmp0(key, "Xmp.exif.GPSLatitude") == 0)
856                 if (value < 0)
857                         ref = "S";
858                 else
859                         ref = "N";
860         else
861                 {
862                 log_printf("unknown GPS parameter key '%s'\n", key);
863                 ok = FALSE;
864                 }
865
866         if (ok)
867                 {
868                 coordinate = g_strdup_printf("%i,%lf,%s", deg, min, ref);
869                 metadata_write_string(fd, key, coordinate );
870                 g_free(coordinate);
871                 }
872
873         return ok;
874 }
875
876 gboolean metadata_append_list(FileData *fd, const gchar *key, const GList *values)
877 {
878         GList *list = metadata_read_list(fd, key, METADATA_PLAIN);
879
880         if (!list)
881                 {
882                 return metadata_write_list(fd, key, values);
883                 }
884         else
885                 {
886                 gboolean ret;
887                 list = g_list_concat(list, string_list_copy(values));
888                 list = remove_duplicate_strings_from_list(list);
889
890                 ret = metadata_write_list(fd, key, list);
891                 string_list_free(list);
892                 return ret;
893                 }
894 }
895
896 /**
897  * \see find_string_in_list
898  */
899 gchar *find_string_in_list_utf8nocase(GList *list, const gchar *string)
900 {
901         gchar *string_casefold = g_utf8_casefold(string, -1);
902
903         while (list)
904                 {
905                 gchar *haystack = list->data;
906
907                 if (haystack)
908                         {
909                         gboolean equal;
910                         gchar *haystack_casefold = g_utf8_casefold(haystack, -1);
911
912                         equal = (strcmp(haystack_casefold, string_casefold) == 0);
913                         g_free(haystack_casefold);
914
915                         if (equal)
916                                 {
917                                 g_free(string_casefold);
918                                 return haystack;
919                                 }
920                         }
921
922                 list = list->next;
923                 }
924
925         g_free(string_casefold);
926         return NULL;
927 }
928
929 /**
930  * \see find_string_in_list
931  */
932 gchar *find_string_in_list_utf8case(GList *list, const gchar *string)
933 {
934         while (list)
935                 {
936                 gchar *haystack = list->data;
937
938                 if (haystack && strcmp(haystack, string) == 0)
939                         return haystack;
940
941                 list = list->next;
942                 } // while (list)
943
944         return NULL;
945 } // gchar *find_string_in_list_utf...
946
947 /**
948  * \brief Find a existent string in a list.
949  *
950  * This is a switch between find_string_in_list_utf8case and
951  * find_string_in_list_utf8nocase to search with or without case for the
952  * existence of a string.
953  *
954  * \param list The list to search in
955  * \param string The string to search for
956  * \return The string or NULL
957  *
958  * \see find_string_in_list_utf8case
959  * \see find_string_in_list_utf8nocase
960  */
961 gchar *find_string_in_list(GList *list, const gchar *string)
962 {
963         if (options->metadata.keywords_case_sensitive)
964                 return find_string_in_list_utf8case(list, string);
965         else
966                 return find_string_in_list_utf8nocase(list, string);
967 }
968
969 #define KEYWORDS_SEPARATOR(c) ((c) == ',' || (c) == ';' || (c) == '\n' || (c) == '\r' || (c) == '\b')
970
971 GList *string_to_keywords_list(const gchar *text)
972 {
973         GList *list = NULL;
974         const gchar *ptr = text;
975
976         while (*ptr != '\0')
977                 {
978                 const gchar *begin;
979                 gint l = 0;
980
981                 while (KEYWORDS_SEPARATOR(*ptr)) ptr++;
982                 begin = ptr;
983                 while (*ptr != '\0' && !KEYWORDS_SEPARATOR(*ptr))
984                         {
985                         ptr++;
986                         l++;
987                         }
988
989                 /* trim starting and ending whitespaces */
990                 while (l > 0 && g_ascii_isspace(*begin)) begin++, l--;
991                 while (l > 0 && g_ascii_isspace(begin[l-1])) l--;
992
993                 if (l > 0)
994                         {
995                         gchar *keyword = g_strndup(begin, l);
996
997                         /* only add if not already in the list */
998                         if (!find_string_in_list(list, keyword))
999                                 list = g_list_append(list, keyword);
1000                         else
1001                                 g_free(keyword);
1002                         }
1003                 }
1004
1005         return list;
1006 }
1007
1008 /*
1009  * keywords to marks
1010  */
1011
1012
1013 gboolean meta_data_get_keyword_mark(FileData *fd, gint n, gpointer data)
1014 {
1015         /* FIXME: do not use global keyword_tree */
1016         GList *path = data;
1017         GList *keywords;
1018         gboolean found = FALSE;
1019         keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
1020         if (keywords)
1021                 {
1022                 GtkTreeIter iter;
1023                 if (keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path) &&
1024                     keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
1025                         found = TRUE;
1026
1027                 }
1028         return found;
1029 }
1030
1031 gboolean meta_data_set_keyword_mark(FileData *fd, gint n, gboolean value, gpointer data)
1032 {
1033         GList *path = data;
1034         GList *keywords = NULL;
1035         GtkTreeIter iter;
1036
1037         if (!keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path)) return FALSE;
1038
1039         keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
1040
1041         if (!!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords) != !!value)
1042                 {
1043                 if (value)
1044                         {
1045                         keyword_tree_set(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
1046                         }
1047                 else
1048                         {
1049                         keyword_tree_reset(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
1050                         }
1051                 metadata_write_list(fd, KEYWORD_KEY, keywords);
1052                 }
1053
1054         string_list_free(keywords);
1055         return TRUE;
1056 }
1057
1058
1059
1060 void meta_data_connect_mark_with_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *kw_iter, gint mark)
1061 {
1062
1063         FileDataGetMarkFunc get_mark_func;
1064         FileDataSetMarkFunc set_mark_func;
1065         gpointer mark_func_data;
1066
1067         gint i;
1068
1069         for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
1070                 {
1071                 file_data_get_registered_mark_func(i, &get_mark_func, &set_mark_func, &mark_func_data);
1072                 if (get_mark_func == meta_data_get_keyword_mark)
1073                         {
1074                         GtkTreeIter old_kw_iter;
1075                         GList *old_path = mark_func_data;
1076
1077                         if (keyword_tree_get_iter(keyword_tree, &old_kw_iter, old_path) &&
1078                             (i == mark || /* release any previous connection of given mark */
1079                              keyword_compare(keyword_tree, &old_kw_iter, kw_iter) == 0)) /* or given keyword */
1080                                 {
1081                                 file_data_register_mark_func(i, NULL, NULL, NULL, NULL);
1082                                 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), &old_kw_iter, KEYWORD_COLUMN_MARK, "", -1);
1083                                 }
1084                         }
1085                 }
1086
1087
1088         if (mark >= 0 && mark < FILEDATA_MARKS_SIZE)
1089                 {
1090                 GList *path;
1091                 gchar *mark_str;
1092                 path = keyword_tree_get_path(keyword_tree, kw_iter);
1093                 file_data_register_mark_func(mark, meta_data_get_keyword_mark, meta_data_set_keyword_mark, path, (GDestroyNotify)string_list_free);
1094
1095                 mark_str = g_strdup_printf("%d", mark + 1);
1096                 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), kw_iter, KEYWORD_COLUMN_MARK, mark_str, -1);
1097                 g_free(mark_str);
1098                 }
1099 }
1100
1101
1102 /*
1103  *-------------------------------------------------------------------
1104  * keyword tree
1105  *-------------------------------------------------------------------
1106  */
1107
1108
1109
1110 GtkTreeStore *keyword_tree;
1111
1112 gchar *keyword_get_name(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1113 {
1114         gchar *name;
1115         gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_NAME, &name, -1);
1116         return name;
1117 }
1118
1119 gchar *keyword_get_mark(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1120 {
1121         gchar *mark_str;
1122
1123         gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_MARK, &mark_str, -1);
1124         return mark_str;
1125 }
1126
1127 gchar *keyword_get_casefold(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1128 {
1129         gchar *casefold;
1130         gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_CASEFOLD, &casefold, -1);
1131         return casefold;
1132 }
1133
1134 gboolean keyword_get_is_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
1135 {
1136         gboolean is_keyword;
1137         gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
1138         return is_keyword;
1139 }
1140
1141 void keyword_set(GtkTreeStore *keyword_tree, GtkTreeIter *iter, const gchar *name, gboolean is_keyword)
1142 {
1143         gchar *casefold = g_utf8_casefold(name, -1);
1144         gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_MARK, "",
1145                                                 KEYWORD_COLUMN_NAME, name,
1146                                                 KEYWORD_COLUMN_CASEFOLD, casefold,
1147                                                 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
1148         g_free(casefold);
1149 }
1150
1151 gboolean keyword_compare(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
1152 {
1153         GtkTreePath *pa = gtk_tree_model_get_path(keyword_tree, a);
1154         GtkTreePath *pb = gtk_tree_model_get_path(keyword_tree, b);
1155         gint ret = gtk_tree_path_compare(pa, pb);
1156         gtk_tree_path_free(pa);
1157         gtk_tree_path_free(pb);
1158         return ret;
1159 }
1160
1161 gboolean keyword_same_parent(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
1162 {
1163         GtkTreeIter parent_a;
1164         GtkTreeIter parent_b;
1165
1166         gboolean valid_pa = gtk_tree_model_iter_parent(keyword_tree, &parent_a, a);
1167         gboolean valid_pb = gtk_tree_model_iter_parent(keyword_tree, &parent_b, b);
1168
1169         if (valid_pa && valid_pb)
1170                 {
1171                 return keyword_compare(keyword_tree, &parent_a, &parent_b) == 0;
1172                 }
1173         else
1174                 {
1175                 return (!valid_pa && !valid_pb); /* both are toplevel */
1176                 }
1177 }
1178
1179 gboolean keyword_exists(GtkTreeModel *keyword_tree, GtkTreeIter *parent_ptr, GtkTreeIter *sibling, const gchar *name, gboolean exclude_sibling, GtkTreeIter *result)
1180 {
1181         GtkTreeIter parent;
1182         GtkTreeIter iter;
1183         gboolean toplevel = FALSE;
1184         gboolean ret;
1185         gchar *casefold;
1186
1187         if (parent_ptr)
1188                 {
1189                 parent = *parent_ptr;
1190                 }
1191         else if (sibling)
1192                 {
1193                 toplevel = !gtk_tree_model_iter_parent(keyword_tree, &parent, sibling);
1194                 }
1195         else
1196                 {
1197                 toplevel = TRUE;
1198                 }
1199
1200         if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &iter, toplevel ? NULL : &parent)) return FALSE;
1201
1202         casefold = g_utf8_casefold(name, -1);
1203         ret = FALSE;
1204
1205         while (TRUE)
1206                 {
1207                 if (!(exclude_sibling && sibling && keyword_compare(keyword_tree, &iter, sibling) == 0))
1208                         {
1209                         if (options->metadata.keywords_case_sensitive)
1210                                 {
1211                                 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
1212                                 ret = strcmp(name, iter_name) == 0;
1213                                 g_free(iter_name);
1214                                 }
1215                         else
1216                                 {
1217                                 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
1218                                 ret = strcmp(casefold, iter_casefold) == 0;
1219                                 g_free(iter_casefold);
1220                                 } // if (options->metadata.tags_cas...
1221                         }
1222                 if (ret)
1223                         {
1224                         if (result) *result = iter;
1225                         break;
1226                         }
1227                 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) break;
1228                 }
1229         g_free(casefold);
1230         return ret;
1231 }
1232
1233
1234 void keyword_copy(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
1235 {
1236
1237         gchar *mark, *name, *casefold;
1238         gboolean is_keyword;
1239
1240         /* do not copy KEYWORD_COLUMN_HIDE_IN, it fully shows the new subtree */
1241         gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), from, KEYWORD_COLUMN_MARK, &mark,
1242                                                 KEYWORD_COLUMN_NAME, &name,
1243                                                 KEYWORD_COLUMN_CASEFOLD, &casefold,
1244                                                 KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
1245
1246         gtk_tree_store_set(keyword_tree, to, KEYWORD_COLUMN_MARK, mark,
1247                                                 KEYWORD_COLUMN_NAME, name,
1248                                                 KEYWORD_COLUMN_CASEFOLD, casefold,
1249                                                 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
1250         g_free(mark);
1251         g_free(name);
1252         g_free(casefold);
1253 }
1254
1255 void keyword_copy_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
1256 {
1257         GtkTreeIter from_child;
1258
1259         keyword_copy(keyword_tree, to, from);
1260
1261         if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &from_child, from)) return;
1262
1263         while (TRUE)
1264                 {
1265                 GtkTreeIter to_child;
1266                 gtk_tree_store_append(keyword_tree, &to_child, to);
1267                 keyword_copy_recursive(keyword_tree, &to_child, &from_child);
1268                 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &from_child)) return;
1269                 }
1270 }
1271
1272 void keyword_move_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
1273 {
1274         keyword_copy_recursive(keyword_tree, to, from);
1275         keyword_delete(keyword_tree, from);
1276 }
1277
1278 GList *keyword_tree_get_path(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
1279 {
1280         GList *path = NULL;
1281         GtkTreeIter iter = *iter_ptr;
1282
1283         while (TRUE)
1284                 {
1285                 GtkTreeIter parent;
1286                 path = g_list_prepend(path, keyword_get_name(keyword_tree, &iter));
1287                 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) break;
1288                 iter = parent;
1289                 }
1290         return path;
1291 }
1292
1293 gboolean keyword_tree_get_iter(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList *path)
1294 {
1295         GtkTreeIter iter;
1296
1297         if (!gtk_tree_model_get_iter_first(keyword_tree, &iter)) return FALSE;
1298
1299         while (TRUE)
1300                 {
1301                 GtkTreeIter children;
1302                 while (TRUE)
1303                         {
1304                         gchar *name = keyword_get_name(keyword_tree, &iter);
1305                         if (strcmp(name, path->data) == 0) break;
1306                         g_free(name);
1307                         if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return FALSE;
1308                         }
1309                 path = path->next;
1310                 if (!path)
1311                         {
1312                         *iter_ptr = iter;
1313                         return TRUE;
1314                         }
1315
1316                 if (!gtk_tree_model_iter_children(keyword_tree, &children, &iter)) return FALSE;
1317                 iter = children;
1318                 }
1319 }
1320
1321
1322 static gboolean keyword_tree_is_set_casefold(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *casefold_list)
1323 {
1324         if (!casefold_list) return FALSE;
1325
1326         if (!keyword_get_is_keyword(keyword_tree, &iter))
1327                 {
1328                 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1329                 GtkTreeIter child;
1330                 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1331                         return FALSE; /* this should happen only on empty helpers */
1332
1333                 while (TRUE)
1334                         {
1335                         if (keyword_tree_is_set_casefold(keyword_tree, child, casefold_list)) return TRUE;
1336                         if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1337                         }
1338                 }
1339
1340         while (TRUE)
1341                 {
1342                 GtkTreeIter parent;
1343
1344                 if (keyword_get_is_keyword(keyword_tree, &iter))
1345                         {
1346                         GList *work = casefold_list;
1347                         gboolean found = FALSE;
1348                         gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
1349                         while (work)
1350                                 {
1351                                 const gchar *casefold = work->data;
1352                                 work = work->next;
1353
1354                                 if (strcmp(iter_casefold, casefold) == 0)
1355                                         {
1356                                         found = TRUE;
1357                                         break;
1358                                         }
1359                                 }
1360                         g_free(iter_casefold);
1361                         if (!found) return FALSE;
1362                         }
1363
1364                 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1365                 iter = parent;
1366                 }
1367 }
1368
1369 static gboolean keyword_tree_is_set_casefull(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *kw_list)
1370 {
1371         if (!kw_list) return FALSE;
1372
1373         if (!keyword_get_is_keyword(keyword_tree, &iter))
1374                 {
1375                 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1376                 GtkTreeIter child;
1377                 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
1378                         return FALSE; /* this should happen only on empty helpers */
1379
1380                 while (TRUE)
1381                         {
1382                         if (keyword_tree_is_set_casefull(keyword_tree, child, kw_list)) return TRUE;
1383                         if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1384                         }
1385                 }
1386
1387         while (TRUE)
1388                 {
1389                 GtkTreeIter parent;
1390
1391                 if (keyword_get_is_keyword(keyword_tree, &iter))
1392                         {
1393                         GList *work = kw_list;
1394                         gboolean found = FALSE;
1395                         gchar *iter_name = keyword_get_name(keyword_tree, &iter);
1396                         while (work)
1397                                 {
1398                                 const gchar *name = work->data;
1399                                 work = work->next;
1400
1401                                 if (strcmp(iter_name, name) == 0)
1402                                         {
1403                                         found = TRUE;
1404                                         break;
1405                                         }
1406                                 }
1407                         g_free(iter_name);
1408                         if (!found) return FALSE;
1409                         }
1410
1411                 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1412                 iter = parent;
1413                 }
1414 }
1415
1416 gboolean keyword_tree_is_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList *kw_list)
1417 {
1418         gboolean ret;
1419         GList *casefold_list = NULL;
1420         GList *work;
1421
1422         if (options->metadata.keywords_case_sensitive)
1423                 {
1424                 ret = keyword_tree_is_set_casefull(keyword_tree, *iter, kw_list);
1425                 }
1426         else
1427                 {
1428                 work = kw_list;
1429                 while (work)
1430                         {
1431                         const gchar *kw = work->data;
1432                         work = work->next;
1433
1434                         casefold_list = g_list_prepend(casefold_list, g_utf8_casefold(kw, -1));
1435                         }
1436
1437                 ret = keyword_tree_is_set_casefold(keyword_tree, *iter, casefold_list);
1438
1439                 string_list_free(casefold_list);
1440                 }
1441
1442         return ret;
1443 }
1444
1445 void keyword_tree_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1446 {
1447         GtkTreeIter iter = *iter_ptr;
1448         while (TRUE)
1449                 {
1450                 GtkTreeIter parent;
1451
1452                 if (keyword_get_is_keyword(keyword_tree, &iter))
1453                         {
1454                         gchar *name = keyword_get_name(keyword_tree, &iter);
1455                         if (!find_string_in_list(*kw_list, name))
1456                                 {
1457                                 *kw_list = g_list_append(*kw_list, name);
1458                                 }
1459                         else
1460                                 {
1461                                 g_free(name);
1462                                 }
1463                         }
1464
1465                 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1466                 iter = parent;
1467                 }
1468 }
1469
1470 GList *keyword_tree_get(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
1471 {
1472         GtkTreeIter iter = *iter_ptr;
1473         GList *kw_list = NULL;
1474
1475         while (TRUE)
1476                 {
1477                 GtkTreeIter parent;
1478
1479                 if (keyword_get_is_keyword(keyword_tree, &iter))
1480                         {
1481                         gchar *name = keyword_get_name(keyword_tree, &iter);
1482                         kw_list = g_list_append(kw_list, name);
1483                         }
1484
1485                 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return kw_list;
1486                 iter = parent;
1487                 }
1488 } // GList *keyword_tree_get(GtkTre...
1489
1490 static void keyword_tree_reset1(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1491 {
1492         gchar *found;
1493         gchar *name;
1494         if (!keyword_get_is_keyword(keyword_tree, iter)) return;
1495
1496         name = keyword_get_name(keyword_tree, iter);
1497         found = find_string_in_list(*kw_list, name);
1498
1499         if (found)
1500                 {
1501                 *kw_list = g_list_remove(*kw_list, found);
1502                 g_free(found);
1503                 }
1504         g_free(name);
1505 }
1506
1507 static void keyword_tree_reset_recursive(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1508 {
1509         GtkTreeIter child;
1510         keyword_tree_reset1(keyword_tree, iter, kw_list);
1511
1512         if (!gtk_tree_model_iter_children(keyword_tree, &child, iter)) return;
1513
1514         while (TRUE)
1515                 {
1516                 keyword_tree_reset_recursive(keyword_tree, &child, kw_list);
1517                 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return;
1518                 }
1519 }
1520
1521 static gboolean keyword_tree_check_empty_children(GtkTreeModel *keyword_tree, GtkTreeIter *parent, GList *kw_list)
1522 {
1523         GtkTreeIter iter;
1524
1525         if (!gtk_tree_model_iter_children(keyword_tree, &iter, parent))
1526                 return TRUE; /* this should happen only on empty helpers */
1527
1528         while (TRUE)
1529                 {
1530                 if (keyword_tree_is_set(keyword_tree, &iter, kw_list)) return FALSE;
1531                 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return TRUE;
1532                 }
1533 }
1534
1535 void keyword_tree_reset(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1536 {
1537         GtkTreeIter iter = *iter_ptr;
1538         GtkTreeIter parent;
1539         keyword_tree_reset_recursive(keyword_tree, &iter, kw_list);
1540
1541         if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1542         iter = parent;
1543
1544         while (keyword_tree_check_empty_children(keyword_tree, &iter, *kw_list))
1545                 {
1546                 GtkTreeIter parent;
1547                 keyword_tree_reset1(keyword_tree, &iter, kw_list);
1548                 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1549                 iter = parent;
1550                 }
1551 }
1552
1553 void keyword_delete(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr)
1554 {
1555         GList *list;
1556         GtkTreeIter child;
1557         while (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, iter_ptr))
1558                 {
1559                 keyword_delete(keyword_tree, &child);
1560                 }
1561
1562         meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree), iter_ptr, -1);
1563
1564         gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter_ptr, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1565         g_list_free(list);
1566
1567         gtk_tree_store_remove(keyword_tree, iter_ptr);
1568 }
1569
1570
1571 void keyword_hide_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1572 {
1573         GList *list;
1574         gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1575         if (!g_list_find(list, id))
1576                 {
1577                 list = g_list_prepend(list, id);
1578                 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1579                 }
1580 }
1581
1582 void keyword_show_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1583 {
1584         GList *list;
1585         gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1586         list = g_list_remove(list, id);
1587         gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1588 }
1589
1590 gboolean keyword_is_hidden_in(GtkTreeModel *keyword_tree, GtkTreeIter *iter, gpointer id)
1591 {
1592         GList *list;
1593         gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1594         return !!g_list_find(list, id);
1595 }
1596
1597 static gboolean keyword_show_all_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
1598 {
1599         keyword_show_in(GTK_TREE_STORE(model), iter, data);
1600         return FALSE;
1601 }
1602
1603 void keyword_show_all_in(GtkTreeStore *keyword_tree, gpointer id)
1604 {
1605         gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_all_in_cb, id);
1606 }
1607
1608 static void keyword_hide_unset_in_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr, gpointer id, GList *keywords)
1609 {
1610         GtkTreeIter iter = *iter_ptr;
1611         while (TRUE)
1612                 {
1613                 if (!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
1614                         {
1615                         keyword_hide_in(keyword_tree, &iter, id);
1616                         /* no need to check children of hidden node */
1617                         }
1618                 else
1619                         {
1620                         GtkTreeIter child;
1621                         if (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, &iter))
1622                                 {
1623                                 keyword_hide_unset_in_recursive(keyword_tree, &child, id, keywords);
1624                                 }
1625                         }
1626                 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1627                 }
1628 }
1629
1630 void keyword_hide_unset_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1631 {
1632         GtkTreeIter iter;
1633         if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1634         keyword_hide_unset_in_recursive(keyword_tree, &iter, id, keywords);
1635 }
1636
1637 static gboolean keyword_show_set_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter_ptr, gpointer data)
1638 {
1639         GtkTreeIter iter = *iter_ptr;
1640         GList *keywords = data;
1641         gpointer id = keywords->data;
1642         keywords = keywords->next; /* hack */
1643         if (keyword_tree_is_set(model, &iter, keywords))
1644                 {
1645                 while (TRUE)
1646                         {
1647                         GtkTreeIter parent;
1648                         keyword_show_in(GTK_TREE_STORE(model), &iter, id);
1649                         if (!gtk_tree_model_iter_parent(GTK_TREE_MODEL(keyword_tree), &parent, &iter)) break;
1650                         iter = parent;
1651                         }
1652                 }
1653         return FALSE;
1654 }
1655
1656 void keyword_show_set_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1657 {
1658         /* hack: pass id to keyword_hide_unset_in_cb in the list */
1659         keywords = g_list_prepend(keywords, id);
1660         gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_set_in_cb, keywords);
1661         keywords = g_list_delete_link(keywords, keywords);
1662 }
1663
1664
1665 void keyword_tree_new(void)
1666 {
1667         if (keyword_tree) return;
1668
1669         keyword_tree = gtk_tree_store_new(KEYWORD_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER);
1670 }
1671
1672 static GtkTreeIter keyword_tree_default_append(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar *name, gboolean is_keyword)
1673 {
1674         GtkTreeIter iter;
1675         gtk_tree_store_append(keyword_tree, &iter, parent);
1676         keyword_set(keyword_tree, &iter, name, is_keyword);
1677         return iter;
1678 }
1679
1680 void keyword_tree_new_default(void)
1681 {
1682         GtkTreeIter i1, i2;
1683
1684         if (!keyword_tree) keyword_tree_new();
1685
1686         i1 = keyword_tree_default_append(keyword_tree, NULL, _("People"), TRUE);
1687                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Family"), TRUE);
1688                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Free time"), TRUE);
1689                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Children"), TRUE);
1690                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sport"), TRUE);
1691                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Culture"), TRUE);
1692                         keyword_tree_default_append(keyword_tree, &i2, _("Festival"), TRUE);
1693         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Nature"), TRUE);
1694                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Animal"), TRUE);
1695                         keyword_tree_default_append(keyword_tree, &i2, _("Bird"), TRUE);
1696                         keyword_tree_default_append(keyword_tree, &i2, _("Insect"), TRUE);
1697                         keyword_tree_default_append(keyword_tree, &i2, _("Pets"), TRUE);
1698                         keyword_tree_default_append(keyword_tree, &i2, _("Wildlife"), TRUE);
1699                         keyword_tree_default_append(keyword_tree, &i2, _("Zoo"), TRUE);
1700                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Plant"), TRUE);
1701                         keyword_tree_default_append(keyword_tree, &i2, _("Tree"), TRUE);
1702                         keyword_tree_default_append(keyword_tree, &i2, _("Flower"), TRUE);
1703                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Water"), TRUE);
1704                         keyword_tree_default_append(keyword_tree, &i2, _("River"), TRUE);
1705                         keyword_tree_default_append(keyword_tree, &i2, _("Lake"), TRUE);
1706                         keyword_tree_default_append(keyword_tree, &i2, _("Sea"), TRUE);
1707                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Landscape"), TRUE);
1708         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Art"), TRUE);
1709                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Statue"), TRUE);
1710                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Painting"), TRUE);
1711                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1712                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1713         i1 = keyword_tree_default_append(keyword_tree, NULL, _("City"), TRUE);
1714                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Park"), TRUE);
1715                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Street"), TRUE);
1716                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Square"), TRUE);
1717         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Architecture"), TRUE);
1718                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Buildings"), FALSE);
1719                         keyword_tree_default_append(keyword_tree, &i2, _("House"), TRUE);
1720                         keyword_tree_default_append(keyword_tree, &i2, _("Cathedral"), TRUE);
1721                         keyword_tree_default_append(keyword_tree, &i2, _("Palace"), TRUE);
1722                         keyword_tree_default_append(keyword_tree, &i2, _("Castle"), TRUE);
1723                         keyword_tree_default_append(keyword_tree, &i2, _("Bridge"), TRUE);
1724                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Interior"), TRUE);
1725                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
1726                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
1727         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Places"), FALSE);
1728         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Conditions"), FALSE);
1729                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Night"), TRUE);
1730                         keyword_tree_default_append(keyword_tree, &i2, _("Lights"), TRUE);
1731                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Reflections"), TRUE);
1732                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sun"), TRUE);
1733                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Weather"), FALSE);
1734                         keyword_tree_default_append(keyword_tree, &i2, _("Fog"), TRUE);
1735                         keyword_tree_default_append(keyword_tree, &i2, _("Rain"), TRUE);
1736                         keyword_tree_default_append(keyword_tree, &i2, _("Clouds"), TRUE);
1737                         keyword_tree_default_append(keyword_tree, &i2, _("Snow"), TRUE);
1738                         keyword_tree_default_append(keyword_tree, &i2, _("Sunny weather"), TRUE);
1739         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Photo"), FALSE);
1740                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Edited"), TRUE);
1741                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Detail"), TRUE);
1742                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Macro"), TRUE);
1743                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Portrait"), TRUE);
1744                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Black and White"), TRUE);
1745                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Perspective"), TRUE);
1746 }
1747
1748
1749 static void keyword_tree_node_write_config(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GString *outstr, gint indent)
1750 {
1751         GtkTreeIter iter = *iter_ptr;
1752         while (TRUE)
1753                 {
1754                 GtkTreeIter children;
1755                 gchar *name;
1756                 gchar *mark_str;
1757
1758                 WRITE_NL(); WRITE_STRING("<keyword ");
1759                 name = keyword_get_name(keyword_tree, &iter);
1760                 write_char_option(outstr, indent, "name", name);
1761                 g_free(name);
1762                 write_bool_option(outstr, indent, "kw", keyword_get_is_keyword(keyword_tree, &iter));
1763                 mark_str = keyword_get_mark(keyword_tree, &iter);
1764                 if (mark_str && mark_str[0])
1765                         {
1766                         write_char_option(outstr, indent, "mark", mark_str);
1767                         }
1768
1769                 if (gtk_tree_model_iter_children(keyword_tree, &children, &iter))
1770                         {
1771                         WRITE_STRING(">");
1772                         indent++;
1773                         keyword_tree_node_write_config(keyword_tree, &children, outstr, indent);
1774                         indent--;
1775                         WRITE_NL(); WRITE_STRING("</keyword>");
1776                         }
1777                 else
1778                         {
1779                         WRITE_STRING("/>");
1780                         }
1781                 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return;
1782                 }
1783 }
1784
1785 void keyword_tree_write_config(GString *outstr, gint indent)
1786 {
1787         GtkTreeIter iter;
1788         WRITE_NL(); WRITE_STRING("<keyword_tree>");
1789         indent++;
1790
1791         if (keyword_tree && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1792                 {
1793                 keyword_tree_node_write_config(GTK_TREE_MODEL(keyword_tree), &iter, outstr, indent);
1794                 }
1795         indent--;
1796         WRITE_NL(); WRITE_STRING("</keyword_tree>");
1797 }
1798
1799 GtkTreeIter *keyword_add_from_config(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar **attribute_names, const gchar **attribute_values)
1800 {
1801         gchar *name = NULL;
1802         gboolean is_kw = TRUE;
1803         gchar *mark_str = NULL;
1804
1805         while (*attribute_names)
1806                 {
1807                 const gchar *option = *attribute_names++;
1808                 const gchar *value = *attribute_values++;
1809
1810                 if (READ_CHAR_FULL("name", name)) continue;
1811                 if (READ_BOOL_FULL("kw", is_kw)) continue;
1812                 if (READ_CHAR_FULL("mark", mark_str)) continue;
1813
1814                 log_printf("unknown attribute %s = %s\n", option, value);
1815                 }
1816         if (name && name[0])
1817                 {
1818                 GtkTreeIter iter;
1819                 /* re-use existing keyword if any */
1820                 if (!keyword_exists(GTK_TREE_MODEL(keyword_tree), parent, NULL, name, FALSE, &iter))
1821                         {
1822                         gtk_tree_store_append(keyword_tree, &iter, parent);
1823                         }
1824                 keyword_set(keyword_tree, &iter, name, is_kw);
1825
1826                 if (mark_str)
1827                         {
1828                         meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree),
1829                                                                                         &iter, (gint)atoi(mark_str) - 1);
1830                         }
1831
1832                 g_free(name);
1833                 return gtk_tree_iter_copy(&iter);
1834                 }
1835         g_free(name);
1836         return NULL;
1837 }
1838
1839 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */