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