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