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