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