fixed writting to gqview legacy format
[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 = 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         metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
461         if (metadata_path && (!except || strcmp(metadata_path, except) != 0)) 
462                 {
463                 metadata_pathl = path_from_utf8(metadata_path);
464                 unlink(metadata_pathl);
465                 g_free(metadata_pathl);
466                 g_free(metadata_path);
467                 }
468 }
469
470 static gboolean metadata_legacy_read(FileData *fd, GList **keywords, gchar **comment)
471 {
472         gchar *metadata_path;
473         gchar *metadata_pathl;
474         gboolean success = FALSE;
475         
476         if (!fd) return FALSE;
477
478         metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
479         if (!metadata_path) return FALSE;
480
481         metadata_pathl = path_from_utf8(metadata_path);
482
483         success = metadata_file_read(metadata_pathl, keywords, comment);
484
485         g_free(metadata_pathl);
486         g_free(metadata_path);
487
488         return success;
489 }
490
491 static GList *remove_duplicate_strings_from_list(GList *list)
492 {
493         GList *work = list;
494         GHashTable *hashtable = g_hash_table_new(g_str_hash, g_str_equal);
495         GList *newlist = NULL;
496
497         while (work)
498                 {
499                 gchar *key = work->data;
500
501                 if (g_hash_table_lookup(hashtable, key) == NULL)
502                         {
503                         g_hash_table_insert(hashtable, (gpointer) key, GINT_TO_POINTER(1));
504                         newlist = g_list_prepend(newlist, key);
505                         }
506                 work = work->next;
507                 }
508
509         g_hash_table_destroy(hashtable);
510         g_list_free(list);
511
512         return g_list_reverse(newlist);
513 }
514
515 GList *metadata_read_list(FileData *fd, const gchar *key, MetadataFormat format)
516 {
517         ExifData *exif;
518         GList *list = NULL;
519         if (!fd) return NULL;
520
521         /* unwritten data overide everything */
522         if (fd->modified_xmp && format == METADATA_PLAIN)
523                 {
524                 list = g_hash_table_lookup(fd->modified_xmp, key);
525                 if (list) return string_list_copy(list);
526                 }
527
528         /* 
529             Legacy metadata file is the primary source if it exists.
530             Merging the lists does not make much sense, because the existence of
531             legacy metadata file indicates that the other metadata sources are not
532             writable and thus it would not be possible to delete the keywords
533             that comes from the image file.
534         */
535         if (strcmp(key, KEYWORD_KEY) == 0)
536                 {
537                 if (metadata_legacy_read(fd, &list, NULL)) return list;
538                 }
539         else if (strcmp(key, COMMENT_KEY) == 0)
540                 {
541                 gchar *comment = NULL;
542                 if (metadata_legacy_read(fd, NULL, &comment)) return g_list_append(NULL, comment);
543                 }
544         else if (strncmp(key, "file.", 5) == 0)
545                 {
546                 return g_list_append(NULL, metadata_file_info(fd, key, format));
547                 }
548         
549         exif = exif_read_fd(fd); /* this is cached, thus inexpensive */
550         if (!exif) return NULL;
551         list = exif_get_metadata(exif, key, format);
552         exif_free_fd(fd, exif);
553         return list;
554 }
555
556 gchar *metadata_read_string(FileData *fd, const gchar *key, MetadataFormat format)
557 {
558         GList *string_list = metadata_read_list(fd, key, format);
559         if (string_list)
560                 {
561                 gchar *str = string_list->data;
562                 string_list->data = NULL;
563                 string_list_free(string_list);
564                 return str;
565                 }
566         return NULL;
567 }
568
569 guint64 metadata_read_int(FileData *fd, const gchar *key, guint64 fallback)
570 {
571         guint64 ret;
572         gchar *endptr;
573         gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
574         if (!string) return fallback;
575         
576         ret = g_ascii_strtoull(string, &endptr, 10);
577         if (string == endptr) ret = fallback;
578         g_free(string);
579         return ret;
580 }
581
582 gdouble metadata_read_GPS_coord(FileData *fd, const gchar *key, gdouble fallback)
583 {
584         gdouble coord;
585         gchar *endptr;
586         gdouble deg, min, sec;
587         gboolean ok = FALSE;
588         gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
589         if (!string) return fallback;
590         
591         deg = g_ascii_strtod(string, &endptr);
592         if (*endptr == ',')
593                 {
594                 min = g_ascii_strtod(endptr + 1, &endptr);
595                 if (*endptr == ',')
596                         sec = g_ascii_strtod(endptr + 1, &endptr);
597                 else 
598                         sec = 0.0;
599                 
600                 
601                 if (*endptr == 'S' || *endptr == 'W' || *endptr == 'N' || *endptr == 'E') 
602                         {
603                         coord = deg + min /60.0 + sec / 3600.0;
604                         ok = TRUE;
605                         if (*endptr == 'S' || *endptr == 'W') coord = -coord;
606                         }
607                 }
608         
609         if (!ok)
610                 {
611                 coord = fallback;
612                 log_printf("unable to parse GPS coordinate '%s'\n", string);
613                 }
614         
615         g_free(string);
616         return coord;
617 }
618         
619 gboolean metadata_append_string(FileData *fd, const gchar *key, const char *value)
620 {
621         gchar *str = metadata_read_string(fd, key, METADATA_PLAIN);
622         
623         if (!str) 
624                 {
625                 return metadata_write_string(fd, key, value);
626                 }
627         else
628                 {
629                 gchar *new_string = g_strconcat(str, value, NULL);
630                 gboolean ret = metadata_write_string(fd, key, new_string);
631                 g_free(str);
632                 g_free(new_string);
633                 return ret;
634                 }
635 }
636
637 gboolean metadata_append_list(FileData *fd, const gchar *key, const GList *values)
638 {
639         GList *list = metadata_read_list(fd, key, METADATA_PLAIN);
640         
641         if (!list) 
642                 {
643                 return metadata_write_list(fd, key, values);
644                 }
645         else
646                 {
647                 gboolean ret;
648                 list = g_list_concat(list, string_list_copy(values));
649                 list = remove_duplicate_strings_from_list(list);
650                 
651                 ret = metadata_write_list(fd, key, list);
652                 string_list_free(list);
653                 return ret;
654                 }
655 }
656
657 gchar *find_string_in_list_utf8nocase(GList *list, const gchar *string)
658 {
659         gchar *string_casefold = g_utf8_casefold(string, -1);
660
661         while (list)
662                 {
663                 gchar *haystack = list->data;
664                 
665                 if (haystack)
666                         {
667                         gboolean equal;
668                         gchar *haystack_casefold = g_utf8_casefold(haystack, -1);
669
670                         equal = (strcmp(haystack_casefold, string_casefold) == 0);
671                         g_free(haystack_casefold);
672
673                         if (equal)
674                                 {
675                                 g_free(string_casefold);
676                                 return haystack;
677                                 }
678                         }
679         
680                 list = list->next;
681                 }
682         
683         g_free(string_casefold);
684         return NULL;
685 }
686
687
688 #define KEYWORDS_SEPARATOR(c) ((c) == ',' || (c) == ';' || (c) == '\n' || (c) == '\r' || (c) == '\b')
689
690 GList *string_to_keywords_list(const gchar *text)
691 {
692         GList *list = NULL;
693         const gchar *ptr = text;
694
695         while (*ptr != '\0')
696                 {
697                 const gchar *begin;
698                 gint l = 0;
699
700                 while (KEYWORDS_SEPARATOR(*ptr)) ptr++;
701                 begin = ptr;
702                 while (*ptr != '\0' && !KEYWORDS_SEPARATOR(*ptr))
703                         {
704                         ptr++;
705                         l++;
706                         }
707
708                 /* trim starting and ending whitespaces */
709                 while (l > 0 && g_ascii_isspace(*begin)) begin++, l--;
710                 while (l > 0 && g_ascii_isspace(begin[l-1])) l--;
711
712                 if (l > 0)
713                         {
714                         gchar *keyword = g_strndup(begin, l);
715
716                         /* only add if not already in the list */
717                         if (!find_string_in_list_utf8nocase(list, keyword))
718                                 list = g_list_append(list, keyword);
719                         else
720                                 g_free(keyword);
721                         }
722                 }
723
724         return list;
725 }
726
727 /*
728  * keywords to marks
729  */
730  
731
732 gboolean meta_data_get_keyword_mark(FileData *fd, gint n, gpointer data)
733 {
734         /* FIXME: do not use global keyword_tree */
735         GList *path = data;
736         GList *keywords;
737         gboolean found = FALSE;
738         keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
739         if (keywords)
740                 {
741                 GtkTreeIter iter;
742                 if (keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path) &&
743                     keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
744                         found = TRUE;
745
746                 }
747         return found;
748 }
749
750 gboolean meta_data_set_keyword_mark(FileData *fd, gint n, gboolean value, gpointer data)
751 {
752         GList *path = data;
753         GList *keywords = NULL;
754         GtkTreeIter iter;
755         
756         if (!keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path)) return FALSE;
757
758         keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
759
760         if (!!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords) != !!value)
761                 {
762                 if (value) 
763                         {
764                         keyword_tree_set(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
765                         }
766                 else
767                         {
768                         keyword_tree_reset(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
769                         }
770                 metadata_write_list(fd, KEYWORD_KEY, keywords);
771                 }
772
773         string_list_free(keywords);
774         return TRUE;
775 }
776
777
778
779 void meta_data_connect_mark_with_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *kw_iter, gint mark)
780 {
781
782         FileDataGetMarkFunc get_mark_func;
783         FileDataSetMarkFunc set_mark_func;
784         gpointer mark_func_data;
785
786         gint i;
787
788         for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
789                 {
790                 file_data_get_registered_mark_func(i, &get_mark_func, &set_mark_func, &mark_func_data);
791                 if (get_mark_func == meta_data_get_keyword_mark) 
792                         {
793                         GtkTreeIter old_kw_iter;
794                         GList *old_path = mark_func_data;
795                         
796                         if (keyword_tree_get_iter(keyword_tree, &old_kw_iter, old_path) && 
797                             (i == mark || /* release any previous connection of given mark */
798                              keyword_compare(keyword_tree, &old_kw_iter, kw_iter) == 0)) /* or given keyword */
799                                 {
800                                 file_data_register_mark_func(i, NULL, NULL, NULL, NULL);
801                                 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), &old_kw_iter, KEYWORD_COLUMN_MARK, "", -1);
802                                 }
803                         }
804                 }
805
806
807         if (mark >= 0 && mark < FILEDATA_MARKS_SIZE)
808                 {
809                 GList *path;
810                 gchar *mark_str;
811                 path = keyword_tree_get_path(keyword_tree, kw_iter);
812                 file_data_register_mark_func(mark, meta_data_get_keyword_mark, meta_data_set_keyword_mark, path, (GDestroyNotify)string_list_free);
813                 
814                 mark_str = g_strdup_printf("%d", mark + 1);
815                 gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), kw_iter, KEYWORD_COLUMN_MARK, mark_str, -1);
816                 g_free(mark_str);
817                 }
818 }
819
820
821 /*
822  *-------------------------------------------------------------------
823  * keyword tree
824  *-------------------------------------------------------------------
825  */
826
827
828
829 GtkTreeStore *keyword_tree;
830
831 gchar *keyword_get_name(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
832 {
833         gchar *name;
834         gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_NAME, &name, -1);
835         return name;
836 }
837
838 gchar *keyword_get_casefold(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
839 {
840         gchar *casefold;
841         gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_CASEFOLD, &casefold, -1);
842         return casefold;
843 }
844
845 gboolean keyword_get_is_keyword(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
846 {
847         gboolean is_keyword;
848         gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
849         return is_keyword;
850 }
851
852 void keyword_set(GtkTreeStore *keyword_tree, GtkTreeIter *iter, const gchar *name, gboolean is_keyword)
853 {
854         gchar *casefold = g_utf8_casefold(name, -1);
855         gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_MARK, "",
856                                                 KEYWORD_COLUMN_NAME, name,
857                                                 KEYWORD_COLUMN_CASEFOLD, casefold,
858                                                 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
859         g_free(casefold);
860 }
861
862 gboolean keyword_compare(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
863 {
864         GtkTreePath *pa = gtk_tree_model_get_path(keyword_tree, a);
865         GtkTreePath *pb = gtk_tree_model_get_path(keyword_tree, b);
866         gint ret = gtk_tree_path_compare(pa, pb);
867         gtk_tree_path_free(pa);
868         gtk_tree_path_free(pb);
869         return ret;
870 }
871
872 gboolean keyword_same_parent(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTreeIter *b)
873 {
874         GtkTreeIter parent_a;
875         GtkTreeIter parent_b;
876         
877         gboolean valid_pa = gtk_tree_model_iter_parent(keyword_tree, &parent_a, a);
878         gboolean valid_pb = gtk_tree_model_iter_parent(keyword_tree, &parent_b, b);
879
880         if (valid_pa && valid_pb)
881                 {
882                 return keyword_compare(keyword_tree, &parent_a, &parent_b) == 0;
883                 }
884         else
885                 {
886                 return (!valid_pa && !valid_pb); /* both are toplevel */
887                 }
888 }
889
890 gboolean keyword_exists(GtkTreeModel *keyword_tree, GtkTreeIter *parent_ptr, GtkTreeIter *sibling, const gchar *name, gboolean exclude_sibling, GtkTreeIter *result)
891 {
892         GtkTreeIter parent;
893         GtkTreeIter iter;
894         gboolean toplevel = FALSE;
895         gboolean ret;
896         gchar *casefold;
897         
898         if (parent_ptr)
899                 {
900                 parent = *parent_ptr;
901                 }
902         else if (sibling)
903                 {
904                 toplevel = !gtk_tree_model_iter_parent(keyword_tree, &parent, sibling);
905                 }
906         else
907                 {
908                 toplevel = TRUE;
909                 }
910         
911         if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &iter, toplevel ? NULL : &parent)) return FALSE;
912         
913         casefold = g_utf8_casefold(name, -1);
914         ret = FALSE;
915         
916         while (TRUE)
917                 {
918                 if (!(exclude_sibling && sibling && keyword_compare(keyword_tree, &iter, sibling) == 0))
919                         {
920                         if (options->metadata.keywords_case_sensitive)
921                                 {
922                                 gchar *iter_name = keyword_get_name(keyword_tree, &iter);
923                                 ret = strcmp(name, iter_name) == 0;
924                                 g_free(iter_name);
925                                 }
926                         else
927                                 {
928                                 gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
929                                 ret = strcmp(casefold, iter_casefold) == 0;
930                                 g_free(iter_casefold);
931                                 } // if (options->metadata.tags_cas...
932                         }
933                 if (ret) 
934                         {
935                         if (result) *result = iter;
936                         break;
937                         }
938                 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) break;
939                 }
940         g_free(casefold);
941         return ret;
942 }
943
944
945 void keyword_copy(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
946 {
947
948         gchar *mark, *name, *casefold;
949         gboolean is_keyword;
950
951         /* do not copy KEYWORD_COLUMN_HIDE_IN, it fully shows the new subtree */
952         gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), from, KEYWORD_COLUMN_MARK, &mark,
953                                                 KEYWORD_COLUMN_NAME, &name,
954                                                 KEYWORD_COLUMN_CASEFOLD, &casefold,
955                                                 KEYWORD_COLUMN_IS_KEYWORD, &is_keyword, -1);
956
957         gtk_tree_store_set(keyword_tree, to, KEYWORD_COLUMN_MARK, mark,
958                                                 KEYWORD_COLUMN_NAME, name,
959                                                 KEYWORD_COLUMN_CASEFOLD, casefold,
960                                                 KEYWORD_COLUMN_IS_KEYWORD, is_keyword, -1);
961         g_free(mark);
962         g_free(name);
963         g_free(casefold);
964 }
965
966 void keyword_copy_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
967 {
968         GtkTreeIter from_child;
969         
970         keyword_copy(keyword_tree, to, from);
971         
972         if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &from_child, from)) return;
973         
974         while (TRUE)
975                 {
976                 GtkTreeIter to_child;
977                 gtk_tree_store_append(keyword_tree, &to_child, to);
978                 keyword_copy_recursive(keyword_tree, &to_child, &from_child);
979                 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &from_child)) return;
980                 }
981 }
982
983 void keyword_move_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
984 {
985         keyword_copy_recursive(keyword_tree, to, from);
986         keyword_delete(keyword_tree, from);
987 }
988
989 GList *keyword_tree_get_path(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
990 {
991         GList *path = NULL;
992         GtkTreeIter iter = *iter_ptr;
993         
994         while (TRUE)
995                 {
996                 GtkTreeIter parent;
997                 path = g_list_prepend(path, keyword_get_name(keyword_tree, &iter));
998                 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) break;
999                 iter = parent;
1000                 }
1001         return path;
1002 }
1003
1004 gboolean keyword_tree_get_iter(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList *path)
1005 {
1006         GtkTreeIter iter;
1007
1008         if (!gtk_tree_model_get_iter_first(keyword_tree, &iter)) return FALSE;
1009         
1010         while (TRUE)
1011                 {
1012                 GtkTreeIter children;
1013                 while (TRUE)
1014                         {
1015                         gchar *name = keyword_get_name(keyword_tree, &iter);
1016                         if (strcmp(name, path->data) == 0) break;
1017                         g_free(name);
1018                         if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return FALSE;
1019                         }
1020                 path = path->next;
1021                 if (!path) 
1022                         {
1023                         *iter_ptr = iter;
1024                         return TRUE;
1025                         }
1026                         
1027                 if (!gtk_tree_model_iter_children(keyword_tree, &children, &iter)) return FALSE;
1028                 iter = children;
1029                 }
1030 }
1031
1032
1033 static gboolean keyword_tree_is_set_casefold(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *casefold_list)
1034 {
1035         if (!casefold_list) return FALSE;
1036
1037         if (!keyword_get_is_keyword(keyword_tree, &iter))
1038                 {
1039                 /* for the purpose of expanding and hiding, a helper is set if it has any children set */
1040                 GtkTreeIter child;
1041                 if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter)) 
1042                         return FALSE; /* this should happen only on empty helpers */
1043
1044                 while (TRUE)
1045                         {
1046                         if (keyword_tree_is_set_casefold(keyword_tree, child, casefold_list)) return TRUE;
1047                         if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
1048                         }
1049                 }
1050         
1051         while (TRUE)
1052                 {
1053                 GtkTreeIter parent;
1054
1055                 if (keyword_get_is_keyword(keyword_tree, &iter))
1056                         {
1057                         GList *work = casefold_list;
1058                         gboolean found = FALSE;
1059                         gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
1060                         while (work)
1061                                 {
1062                                 const gchar *casefold = work->data;
1063                                 work = work->next;
1064
1065                                 if (strcmp(iter_casefold, casefold) == 0)
1066                                         {
1067                                         found = TRUE;
1068                                         break;
1069                                         }
1070                                 }
1071                         g_free(iter_casefold);
1072                         if (!found) return FALSE;
1073                         }
1074                 
1075                 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
1076                 iter = parent;
1077                 }
1078 }
1079
1080 static gboolean keyword_tree_is_set_casefull(GtkTreeModel *keyword_tree, GtkTreeIter iter, GList *kw_list)
1081 {
1082         if (!kw_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_casefull(keyword_tree, child, kw_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 = kw_list;
1105                         gboolean found = FALSE;
1106                         gchar *iter_name = keyword_get_name(keyword_tree, &iter);
1107                         while (work)
1108                                 {
1109                                 const gchar *name = work->data;
1110                                 work = work->next;
1111
1112                                 if (strcmp(iter_name, name) == 0)
1113                                         {
1114                                         found = TRUE;
1115                                         break;
1116                                         }
1117                                 }
1118                         g_free(iter_name);
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 gboolean keyword_tree_is_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList *kw_list)
1128 {
1129         gboolean ret;
1130         GList *casefold_list = NULL;
1131         GList *work;
1132
1133         if (options->metadata.keywords_case_sensitive)
1134                 {
1135                 ret = keyword_tree_is_set_casefull(keyword_tree, *iter, kw_list);
1136                 }
1137         else
1138                 {
1139                 work = kw_list;
1140                 while (work)
1141                         {
1142                         const gchar *kw = work->data;
1143                         work = work->next;
1144
1145                         casefold_list = g_list_prepend(casefold_list, g_utf8_casefold(kw, -1));
1146                         }
1147
1148                 ret = keyword_tree_is_set_casefold(keyword_tree, *iter, casefold_list);
1149
1150                 string_list_free(casefold_list);
1151                 }
1152
1153         return ret;
1154 }
1155
1156 void keyword_tree_set(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1157 {
1158         GtkTreeIter iter = *iter_ptr;
1159         while (TRUE)
1160                 {
1161                 GtkTreeIter parent;
1162
1163                 if (keyword_get_is_keyword(keyword_tree, &iter))
1164                         {
1165                         gchar *name = keyword_get_name(keyword_tree, &iter);
1166                         if (!find_string_in_list_utf8nocase(*kw_list, name))
1167                                 {
1168                                 *kw_list = g_list_append(*kw_list, name);
1169                                 }
1170                         else
1171                                 {
1172                                 g_free(name);
1173                                 }
1174                         }
1175
1176                 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1177                 iter = parent;
1178                 }
1179 }
1180
1181 static void keyword_tree_reset1(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1182 {
1183         gchar *found;
1184         gchar *name;
1185         if (!keyword_get_is_keyword(keyword_tree, iter)) return;
1186
1187         name = keyword_get_name(keyword_tree, iter);
1188         found = find_string_in_list_utf8nocase(*kw_list, name);
1189
1190         if (found)
1191                 {
1192                 *kw_list = g_list_remove(*kw_list, found);
1193                 g_free(found);
1194                 }
1195         g_free(name);
1196 }
1197
1198 static void keyword_tree_reset_recursive(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
1199 {
1200         GtkTreeIter child;
1201         keyword_tree_reset1(keyword_tree, iter, kw_list);
1202         
1203         if (!gtk_tree_model_iter_children(keyword_tree, &child, iter)) return;
1204
1205         while (TRUE)
1206                 {
1207                 keyword_tree_reset_recursive(keyword_tree, &child, kw_list);
1208                 if (!gtk_tree_model_iter_next(keyword_tree, &child)) return;
1209                 }
1210 }
1211
1212 static gboolean keyword_tree_check_empty_children(GtkTreeModel *keyword_tree, GtkTreeIter *parent, GList *kw_list)
1213 {
1214         GtkTreeIter iter;
1215         
1216         if (!gtk_tree_model_iter_children(keyword_tree, &iter, parent)) 
1217                 return TRUE; /* this should happen only on empty helpers */
1218
1219         while (TRUE)
1220                 {
1221                 if (keyword_tree_is_set(keyword_tree, &iter, kw_list)) return FALSE;
1222                 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return TRUE;
1223                 }
1224 }
1225
1226 void keyword_tree_reset(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GList **kw_list)
1227 {
1228         GtkTreeIter iter = *iter_ptr;
1229         GtkTreeIter parent;
1230         keyword_tree_reset_recursive(keyword_tree, &iter, kw_list);
1231
1232         if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1233         iter = parent;
1234         
1235         while (keyword_tree_check_empty_children(keyword_tree, &iter, *kw_list))
1236                 {
1237                 GtkTreeIter parent;
1238                 keyword_tree_reset1(keyword_tree, &iter, kw_list);
1239                 if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
1240                 iter = parent;
1241                 }
1242 }
1243
1244 void keyword_delete(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr)
1245 {
1246         GList *list;
1247         GtkTreeIter child;
1248         while (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, iter_ptr))
1249                 {
1250                 keyword_delete(keyword_tree, &child);
1251                 }
1252         
1253         meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree), iter_ptr, -1);
1254
1255         gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter_ptr, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1256         g_list_free(list);
1257         
1258         gtk_tree_store_remove(keyword_tree, iter_ptr);
1259 }
1260
1261
1262 void keyword_hide_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1263 {
1264         GList *list;
1265         gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1266         if (!g_list_find(list, id))
1267                 {
1268                 list = g_list_prepend(list, id);
1269                 gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1270                 }
1271 }
1272
1273 void keyword_show_in(GtkTreeStore *keyword_tree, GtkTreeIter *iter, gpointer id)
1274 {
1275         GList *list;
1276         gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1277         list = g_list_remove(list, id);
1278         gtk_tree_store_set(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, list, -1);
1279 }
1280
1281 gboolean keyword_is_hidden_in(GtkTreeModel *keyword_tree, GtkTreeIter *iter, gpointer id)
1282 {
1283         GList *list;
1284         gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_HIDE_IN, &list, -1);
1285         return !!g_list_find(list, id);
1286 }
1287
1288 static gboolean keyword_show_all_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
1289 {
1290         keyword_show_in(GTK_TREE_STORE(model), iter, data);
1291         return FALSE;
1292 }
1293
1294 void keyword_show_all_in(GtkTreeStore *keyword_tree, gpointer id)
1295 {
1296         gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_all_in_cb, id);
1297 }
1298
1299 static void keyword_hide_unset_in_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr, gpointer id, GList *keywords)
1300 {
1301         GtkTreeIter iter = *iter_ptr;
1302         while (TRUE)
1303                 {
1304                 if (!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords))
1305                         {
1306                         keyword_hide_in(keyword_tree, &iter, id);
1307                         /* no need to check children of hidden node */
1308                         }
1309                 else
1310                         {
1311                         GtkTreeIter child;
1312                         if (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, &iter)) 
1313                                 {
1314                                 keyword_hide_unset_in_recursive(keyword_tree, &child, id, keywords);
1315                                 }
1316                         }
1317                 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1318                 }
1319 }
1320
1321 void keyword_hide_unset_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1322 {
1323         GtkTreeIter iter;
1324         if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter)) return;
1325         keyword_hide_unset_in_recursive(keyword_tree, &iter, id, keywords);
1326 }
1327
1328 static gboolean keyword_show_set_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter_ptr, gpointer data)
1329 {
1330         GtkTreeIter iter = *iter_ptr;
1331         GList *keywords = data;
1332         gpointer id = keywords->data;
1333         keywords = keywords->next; /* hack */
1334         if (keyword_tree_is_set(model, &iter, keywords))
1335                 {
1336                 while (TRUE)
1337                         {
1338                         GtkTreeIter parent;
1339                         keyword_show_in(GTK_TREE_STORE(model), &iter, id);
1340                         if (!gtk_tree_model_iter_parent(GTK_TREE_MODEL(keyword_tree), &parent, &iter)) break;
1341                         iter = parent;
1342                         }
1343                 }
1344         return FALSE;
1345 }
1346
1347 void keyword_show_set_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywords)
1348 {
1349         /* hack: pass id to keyword_hide_unset_in_cb in the list */
1350         keywords = g_list_prepend(keywords, id);
1351         gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_set_in_cb, keywords);
1352         keywords = g_list_delete_link(keywords, keywords);
1353 }
1354
1355
1356 void keyword_tree_new(void)
1357 {
1358         if (keyword_tree) return;
1359         
1360         keyword_tree = gtk_tree_store_new(KEYWORD_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER);
1361 }
1362
1363 static GtkTreeIter keyword_tree_default_append(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar *name, gboolean is_keyword)
1364 {
1365         GtkTreeIter iter;
1366         gtk_tree_store_append(keyword_tree, &iter, parent);
1367         keyword_set(keyword_tree, &iter, name, is_keyword);
1368         return iter;
1369 }
1370
1371 void keyword_tree_new_default(void)
1372 {
1373         GtkTreeIter i1, i2, i3;
1374
1375         if (!keyword_tree) keyword_tree_new();
1376
1377         i1 = keyword_tree_default_append(keyword_tree, NULL, _("People"), TRUE); 
1378                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Family"), TRUE); 
1379                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Free time"), TRUE); 
1380                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Children"), TRUE); 
1381                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sport"), TRUE); 
1382                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Culture"), TRUE); 
1383                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Festival"), TRUE); 
1384         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Nature"), TRUE); 
1385                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Animal"), TRUE); 
1386                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Bird"), TRUE); 
1387                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Insect"), TRUE); 
1388                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Pets"), TRUE); 
1389                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Wildlife"), TRUE); 
1390                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Zoo"), TRUE); 
1391                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Plant"), TRUE); 
1392                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Tree"), TRUE); 
1393                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Flower"), TRUE); 
1394                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Water"), TRUE); 
1395                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("River"), TRUE); 
1396                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Lake"), TRUE); 
1397                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Sea"), TRUE); 
1398                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Landscape"), TRUE); 
1399         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Art"), TRUE); 
1400                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Statue"), TRUE); 
1401                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Painting"), TRUE); 
1402                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE); 
1403                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE); 
1404         i1 = keyword_tree_default_append(keyword_tree, NULL, _("City"), TRUE); 
1405                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Park"), TRUE); 
1406                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Street"), TRUE); 
1407                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Square"), TRUE); 
1408         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Architecture"), TRUE); 
1409                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Buildings"), FALSE); 
1410                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("House"), TRUE); 
1411                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Cathedral"), TRUE); 
1412                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Palace"), TRUE); 
1413                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Castle"), TRUE); 
1414                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Bridge"), TRUE); 
1415                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Interior"), TRUE); 
1416                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE); 
1417                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE); 
1418         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Places"), FALSE); 
1419         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Conditions"), FALSE); 
1420                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Night"), TRUE); 
1421                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Lights"), TRUE); 
1422                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Reflections"), TRUE); 
1423                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sun"), TRUE); 
1424                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Weather"), FALSE); 
1425                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Fog"), TRUE); 
1426                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Rain"), TRUE); 
1427                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Clouds"), TRUE); 
1428                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Snow"), TRUE); 
1429                         i3 = keyword_tree_default_append(keyword_tree, &i2, _("Sunny weather"), TRUE); 
1430         i1 = keyword_tree_default_append(keyword_tree, NULL, _("Photo"), FALSE); 
1431                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Edited"), TRUE); 
1432                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Detail"), TRUE); 
1433                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Macro"), TRUE); 
1434                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Portrait"), TRUE); 
1435                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Black and White"), TRUE); 
1436                 i2 = keyword_tree_default_append(keyword_tree, &i1, _("Perspective"), TRUE); 
1437 }
1438
1439
1440 static void keyword_tree_node_write_config(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr, GString *outstr, gint indent)
1441 {
1442         GtkTreeIter iter = *iter_ptr;
1443         while (TRUE)
1444                 {
1445                 GtkTreeIter children;
1446                 gchar *name;
1447
1448                 WRITE_NL(); WRITE_STRING("<keyword ");
1449                 name = keyword_get_name(keyword_tree, &iter);
1450                 write_char_option(outstr, indent, "name", name);
1451                 g_free(name);
1452                 write_bool_option(outstr, indent, "kw", keyword_get_is_keyword(keyword_tree, &iter));
1453                 if (gtk_tree_model_iter_children(keyword_tree, &children, &iter)) 
1454                         {
1455                         WRITE_STRING(">");
1456                         indent++;
1457                         keyword_tree_node_write_config(keyword_tree, &children, outstr, indent);
1458                         indent--;
1459                         WRITE_NL(); WRITE_STRING("</keyword>");
1460                         }
1461                 else 
1462                         {
1463                         WRITE_STRING("/>");
1464                         }
1465                 if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return;
1466                 }
1467 }
1468
1469 void keyword_tree_write_config(GString *outstr, gint indent)
1470 {
1471         GtkTreeIter iter;
1472         WRITE_NL(); WRITE_STRING("<keyword_tree>");
1473         indent++;
1474         
1475         if (keyword_tree && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
1476                 {
1477                 keyword_tree_node_write_config(GTK_TREE_MODEL(keyword_tree), &iter, outstr, indent);
1478                 }
1479         indent--;
1480         WRITE_NL(); WRITE_STRING("</keyword_tree>");
1481 }
1482
1483 GtkTreeIter *keyword_add_from_config(GtkTreeStore *keyword_tree, GtkTreeIter *parent, const gchar **attribute_names, const gchar **attribute_values)
1484 {
1485         gchar *name = NULL;
1486         gboolean is_kw = TRUE;
1487
1488         while (*attribute_names)
1489                 {
1490                 const gchar *option = *attribute_names++;
1491                 const gchar *value = *attribute_values++;
1492
1493                 if (READ_CHAR_FULL("name", name)) continue;
1494                 if (READ_BOOL_FULL("kw", is_kw)) continue;
1495
1496                 log_printf("unknown attribute %s = %s\n", option, value);
1497                 }
1498         if (name && name[0]) 
1499                 {
1500                 GtkTreeIter iter;
1501                 /* re-use existing keyword if any */
1502                 if (!keyword_exists(GTK_TREE_MODEL(keyword_tree), parent, NULL, name, FALSE, &iter))
1503                         {
1504                         gtk_tree_store_append(keyword_tree, &iter, parent);
1505                         }
1506                 keyword_set(keyword_tree, &iter, name, is_kw);
1507                 g_free(name);
1508                 return gtk_tree_iter_copy(&iter);
1509                 }
1510         g_free(name);
1511         return NULL;
1512 }
1513
1514 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */