/*
- * Geeqie
- * (C) 2004 John Ellis
- * Copyright (C) 2008 - 2009 The Geeqie Team
+ * Copyright (C) 2004 John Ellis
+ * Copyright (C) 2008 - 2016 The Geeqie Team
*
- * Author: John Ellis, Laurent Monin
+ * Authors: John Ellis, Laurent Monin
*
- * This software is released under the GNU General Public License (GNU GPL).
- * Please read the included file COPYING for more information.
- * This software comes with no warranty of any kind, use at your own risk!
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-
#include "main.h"
#include "metadata.h"
#include "ui_misc.h"
#include "utilops.h"
#include "filefilter.h"
-#include "layout.h"
+#include "layout_util.h"
#include "rcfile.h"
typedef enum {
MK_COMMENT
} MetadataKey;
-static const gchar *group_keys[] = {KEYWORD_KEY, COMMENT_KEY, NULL}; /* tags that will be written to all files in a group */
+static const gchar *group_keys[] = { /* tags that will be written to all files in a group, options->metadata.sync_grouped_files */
+ "Xmp.dc.title",
+ "Xmp.photoshop.Urgency",
+ "Xmp.photoshop.Category",
+ "Xmp.photoshop.SupplementalCategory",
+ "Xmp.dc.subject",
+ "Xmp.iptc.Location",
+ "Xmp.photoshop.Instruction",
+ "Xmp.photoshop.DateCreated",
+ "Xmp.dc.creator",
+ "Xmp.photoshop.AuthorsPosition",
+ "Xmp.photoshop.City",
+ "Xmp.photoshop.State",
+ "Xmp.iptc.CountryCode",
+ "Xmp.photoshop.Country",
+ "Xmp.photoshop.TransmissionReference",
+ "Xmp.photoshop.Headline",
+ "Xmp.photoshop.Credit",
+ "Xmp.photoshop.Source",
+ "Xmp.dc.rights",
+ "Xmp.dc.description",
+ "Xmp.photoshop.CaptionWriter",
+ NULL};
static gboolean metadata_write_queue_idle_cb(gpointer data);
static gboolean metadata_legacy_write(FileData *fd);
static void metadata_legacy_delete(FileData *fd, const gchar *except);
+static gboolean metadata_file_read(gchar *path, GList **keywords, gchar **comment);
+
+
+/*
+ *-------------------------------------------------------------------
+ * long-term cache - keep keywords from whole dir in memory
+ *-------------------------------------------------------------------
+ */
+
+/* fd->cached metadata list of lists
+ each particular list contains key as a first entry, then the values
+*/
+
+static void metadata_cache_update(FileData *fd, const gchar *key, const GList *values)
+{
+ GList *work;
+
+ work = fd->cached_metadata;
+ while (work)
+ {
+ GList *entry = work->data;
+ gchar *entry_key = entry->data;
+
+ if (strcmp(entry_key, key) == 0)
+ {
+ /* key found - just replace values */
+ GList *old_values = entry->next;
+ entry->next = NULL;
+ old_values->prev = NULL;
+ string_list_free(old_values);
+ work->data = g_list_append(entry, string_list_copy(values));
+ DEBUG_1("updated %s %s\n", key, fd->path);
+ return;
+ }
+ work = work->next;
+ }
+
+ /* key not found - prepend new entry */
+ fd->cached_metadata = g_list_prepend(fd->cached_metadata,
+ g_list_prepend(string_list_copy(values), g_strdup(key)));
+ DEBUG_1("added %s %s\n", key, fd->path);
+
+}
+
+static const GList *metadata_cache_get(FileData *fd, const gchar *key)
+{
+ GList *work;
+
+ work = fd->cached_metadata;
+ while (work)
+ {
+ GList *entry = work->data;
+ gchar *entry_key = entry->data;
+
+ if (strcmp(entry_key, key) == 0)
+ {
+ /* key found */
+ DEBUG_1("found %s %s\n", key, fd->path);
+ return entry;
+ }
+ work = work->next;
+ }
+ return NULL;
+ DEBUG_1("not found %s %s\n", key, fd->path);
+}
+
+static void metadata_cache_remove(FileData *fd, const gchar *key)
+{
+ GList *work;
+
+ work = fd->cached_metadata;
+ while (work)
+ {
+ GList *entry = work->data;
+ gchar *entry_key = entry->data;
+
+ if (strcmp(entry_key, key) == 0)
+ {
+ /* key found */
+ string_list_free(entry);
+ fd->cached_metadata = g_list_delete_link(fd->cached_metadata, work);
+ DEBUG_1("removed %s %s\n", key, fd->path);
+ return;
+ }
+ work = work->next;
+ }
+ DEBUG_1("not removed %s %s\n", key, fd->path);
+}
+
+void metadata_cache_free(FileData *fd)
+{
+ GList *work;
+ if (fd->cached_metadata) DEBUG_1("freed %s\n", fd->path);
+
+ work = fd->cached_metadata;
+ while (work)
+ {
+ GList *entry = work->data;
+ string_list_free(entry);
+
+ work = work->next;
+ }
+ g_list_free(fd->cached_metadata);
+ fd->cached_metadata = NULL;
+}
+
+
+
{
metadata_write_queue = g_list_prepend(metadata_write_queue, fd);
file_data_ref(fd);
-
- layout_status_update_write_all();
+
+ layout_util_status_update_write_all();
}
- if (metadata_write_idle_id)
+ if (metadata_write_idle_id)
{
g_source_remove(metadata_write_idle_id);
metadata_write_idle_id = 0;
}
-
+
if (options->metadata.confirm_after_timeout)
{
metadata_write_idle_id = g_timeout_add(options->metadata.confirm_timeout * 1000, metadata_write_queue_idle_cb, NULL);
fd->modified_xmp = NULL;
metadata_write_queue = g_list_remove(metadata_write_queue, fd);
-
+
file_data_increment_version(fd);
file_data_send_notification(fd, NOTIFY_REREAD);
file_data_unref(fd);
- layout_status_update_write_all();
+ layout_util_status_update_write_all();
return TRUE;
}
{
GList *work;
gboolean ret = TRUE;
-
+
work = list;
while (work)
{
return ret;
}
+void metadata_notify_cb(FileData *fd, NotifyType type, gpointer data)
+{
+ if (type & (NOTIFY_REREAD | NOTIFY_CHANGE))
+ {
+ metadata_cache_free(fd);
-gboolean metadata_write_queue_confirm(FileUtilDoneFunc done_func, gpointer done_data)
+ if (g_list_find(metadata_write_queue, fd))
+ {
+ DEBUG_1("Notify metadata: %s %04x", fd->path, type);
+ if (!isname(fd->path))
+ {
+ /* ignore deleted files */
+ metadata_write_queue_remove(fd);
+ }
+ }
+ }
+}
+
+gboolean metadata_write_queue_confirm(gboolean force_dialog, FileUtilDoneFunc done_func, gpointer done_data)
{
GList *work;
GList *to_approve = NULL;
-
+
work = metadata_write_queue;
while (work)
{
FileData *fd = work->data;
work = work->next;
-
+
+ if (!isname(fd->path))
+ {
+ /* ignore deleted files */
+ metadata_write_queue_remove(fd);
+ continue;
+ }
+
if (fd->change) continue; /* another operation in progress, skip this file for now */
-
+
to_approve = g_list_prepend(to_approve, file_data_ref(fd));
}
- file_util_write_metadata(NULL, to_approve, NULL, done_func, done_data);
-
- filelist_free(to_approve);
-
+ file_util_write_metadata(NULL, to_approve, NULL, force_dialog, done_func, done_data);
+
return (metadata_write_queue != NULL);
}
static gboolean metadata_write_queue_idle_cb(gpointer data)
{
- metadata_write_queue_confirm(NULL, NULL);
+ metadata_write_queue_confirm(FALSE, NULL, NULL);
metadata_write_idle_id = 0;
return FALSE;
}
{
gboolean success;
ExifData *exif;
-
+
g_assert(fd->change);
-
- if (fd->change->dest &&
- strcmp(extension_from_path(fd->change->dest), GQ_CACHE_EXT_METADATA) == 0)
+
+ if (fd->change->dest &&
+ strcmp(registered_extension_from_path(fd->change->dest), GQ_CACHE_EXT_METADATA) == 0)
{
success = metadata_legacy_write(fd);
if (success) metadata_legacy_delete(fd, fd->change->dest);
}
/* write via exiv2 */
- /* we can either use cached metadata which have fd->modified_xmp already applied
+ /* we can either use cached metadata which have fd->modified_xmp already applied
or read metadata from file and apply fd->modified_xmp
metadata are read also if the file was modified meanwhile */
- exif = exif_read_fd(fd);
+ exif = exif_read_fd(fd);
if (!exif) return FALSE;
success = (fd->change->dest) ? exif_write_sidecar(exif, fd->change->dest) : exif_write(exif); /* write modified metadata */
exif_free_fd(fd, exif);
if (fd->change->dest)
- /* this will create a FileData for the sidecar and link it to the main file
+ /* this will create a FileData for the sidecar and link it to the main file
(we can't wait until the sidecar is discovered by directory scanning because
- exif_read_fd is called before that and it would read the main file only and
+ exif_read_fd is called before that and it would read the main file only and
store the metadata in the cache)
FIXME: this does not catch new sidecars created by independent external programs
*/
- file_data_unref(file_data_new_simple(fd->change->dest));
-
+ file_data_unref(file_data_new_group(fd->change->dest));
+
if (success) metadata_legacy_delete(fd, fd->change->dest);
return success;
}
static gboolean metadata_check_key(const gchar *keys[], const gchar *key)
{
const gchar **k = keys;
-
+
while (*k)
{
if (strcmp(key, *k) == 0) return TRUE;
return FALSE;
}
+gboolean metadata_write_revert(FileData *fd, const gchar *key)
+{
+ if (!fd->modified_xmp) return FALSE;
+
+ g_hash_table_remove(fd->modified_xmp, key);
+
+ if (g_hash_table_size(fd->modified_xmp) == 0)
+ {
+ metadata_write_queue_remove(fd);
+ }
+ else
+ {
+ /* reread the metadata to restore the original value */
+ file_data_increment_version(fd);
+ file_data_send_notification(fd, NOTIFY_REREAD);
+ }
+ return TRUE;
+}
+
gboolean metadata_write_list(FileData *fd, const gchar *key, const GList *values)
{
if (!fd->modified_xmp)
fd->modified_xmp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)string_list_free);
}
g_hash_table_insert(fd->modified_xmp, g_strdup(key), string_list_copy((GList *)values));
+
+ metadata_cache_remove(fd, key);
+
if (fd->exif)
{
exif_update_metadata(fd->exif, key, values);
if (options->metadata.sync_grouped_files && metadata_check_key(group_keys, key))
{
GList *work = fd->sidecar_files;
-
+
while (work)
{
FileData *sfd = work->data;
work = work->next;
-
- if (filter_file_class(sfd->extension, FORMAT_CLASS_META)) continue;
+
+ if (filter_file_class(sfd->extension, FORMAT_CLASS_META)) continue;
metadata_write_list(sfd, key, values);
}
return TRUE;
}
-
+
gboolean metadata_write_string(FileData *fd, const gchar *key, const char *value)
{
GList *list = g_list_append(NULL, g_strdup(value));
return ret;
}
+gboolean metadata_write_int(FileData *fd, const gchar *key, guint64 value)
+{
+ gchar string[50];
+
+ g_snprintf(string, sizeof(string), "%llu", (unsigned long long) value);
+ return metadata_write_string(fd, key, string);
+}
/*
*-------------------------------------------------------------------
*-------------------------------------------------------------------
*/
-static gboolean metadata_file_write(gchar *path, GHashTable *modified_xmp)
+static gboolean metadata_file_write(gchar *path, const GList *keywords, const gchar *comment)
{
SecureSaveInfo *ssi;
- GList *keywords = g_hash_table_lookup(modified_xmp, KEYWORD_KEY);
- GList *comment_l = g_hash_table_lookup(modified_xmp, COMMENT_KEY);
- gchar *comment = comment_l ? comment_l->data : NULL;
ssi = secure_open(path);
if (!ssi) return FALSE;
{
gboolean success = FALSE;
gchar *metadata_pathl;
+ gpointer keywords;
+ gpointer comment_l;
+ gboolean have_keywords;
+ gboolean have_comment;
+ const gchar *comment;
+ GList *orig_keywords = NULL;
+ gchar *orig_comment = NULL;
g_assert(fd->change && fd->change->dest);
DEBUG_1("Saving comment: %s", fd->change->dest);
+ if (!fd->modified_xmp) return TRUE;
+
metadata_pathl = path_from_utf8(fd->change->dest);
- success = metadata_file_write(metadata_pathl, fd->modified_xmp);
+ have_keywords = g_hash_table_lookup_extended(fd->modified_xmp, KEYWORD_KEY, NULL, &keywords);
+ have_comment = g_hash_table_lookup_extended(fd->modified_xmp, COMMENT_KEY, NULL, &comment_l);
+ comment = (have_comment && comment_l) ? ((GList *)comment_l)->data : NULL;
+
+ if (!have_keywords || !have_comment) metadata_file_read(metadata_pathl, &orig_keywords, &orig_comment);
+
+ success = metadata_file_write(metadata_pathl,
+ have_keywords ? (GList *)keywords : orig_keywords,
+ have_comment ? comment : orig_comment);
g_free(metadata_pathl);
+ g_free(orig_comment);
+ string_list_free(orig_keywords);
return success;
}
if (*ptr == '[' && key != MK_COMMENT)
{
gchar *keystr = ++ptr;
-
+
key = MK_NONE;
while (*ptr != ']' && *ptr != '\n' && *ptr != '\0') ptr++;
-
+
if (*ptr == ']')
{
*ptr = '\0';
}
continue;
}
-
+
switch (key)
{
case MK_NONE:
break;
}
}
-
+
fclose(f);
- if (keywords)
+ if (keywords)
{
*keywords = g_list_reverse(list);
}
{
string_list_free(list);
}
-
+
if (comment_build)
{
if (comment)
if (!fd) return;
metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
- if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
+ if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
{
metadata_pathl = path_from_utf8(metadata_path);
unlink(metadata_pathl);
g_free(metadata_pathl);
g_free(metadata_path);
}
+
+#ifdef HAVE_EXIV2
+ /* without exiv2: do not delete xmp metadata because we are not able to convert it,
+ just ignore it */
metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
- if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
+ if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
{
metadata_pathl = path_from_utf8(metadata_path);
unlink(metadata_pathl);
g_free(metadata_pathl);
g_free(metadata_path);
}
+#endif
}
static gboolean metadata_legacy_read(FileData *fd, GList **keywords, gchar **comment)
gchar *metadata_path;
gchar *metadata_pathl;
gboolean success = FALSE;
-
+
if (!fd) return FALSE;
metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
{
ExifData *exif;
GList *list = NULL;
+ const GList *cache_entry;
if (!fd) return NULL;
/* unwritten data overide everything */
if (list) return string_list_copy(list);
}
- /*
+
+ if (format == METADATA_PLAIN && strcmp(key, KEYWORD_KEY) == 0
+ && (cache_entry = metadata_cache_get(fd, key)))
+ {
+ return string_list_copy(cache_entry->next);
+ }
+
+ /*
Legacy metadata file is the primary source if it exists.
Merging the lists does not make much sense, because the existence of
legacy metadata file indicates that the other metadata sources are not
*/
if (strcmp(key, KEYWORD_KEY) == 0)
{
- if (metadata_legacy_read(fd, &list, NULL)) return list;
- }
+ if (metadata_legacy_read(fd, &list, NULL))
+ {
+ if (format == METADATA_PLAIN)
+ {
+ metadata_cache_update(fd, key, list);
+ }
+ return list;
+ }
+ }
else if (strcmp(key, COMMENT_KEY) == 0)
{
gchar *comment = NULL;
{
return g_list_append(NULL, metadata_file_info(fd, key, format));
}
-
+
exif = exif_read_fd(fd); /* this is cached, thus inexpensive */
if (!exif) return NULL;
list = exif_get_metadata(exif, key, format);
exif_free_fd(fd, exif);
+
+ if (format == METADATA_PLAIN && strcmp(key, KEYWORD_KEY) == 0)
+ {
+ metadata_cache_update(fd, key, list);
+ }
+
return list;
}
gchar *endptr;
gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
if (!string) return fallback;
-
+
ret = g_ascii_strtoull(string, &endptr, 10);
if (string == endptr) ret = fallback;
g_free(string);
gboolean ok = FALSE;
gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
if (!string) return fallback;
-
+
deg = g_ascii_strtod(string, &endptr);
if (*endptr == ',')
{
min = g_ascii_strtod(endptr + 1, &endptr);
if (*endptr == ',')
sec = g_ascii_strtod(endptr + 1, &endptr);
- else
+ else
sec = 0.0;
-
-
- if (*endptr == 'S' || *endptr == 'W' || *endptr == 'N' || *endptr == 'E')
+
+
+ if (*endptr == 'S' || *endptr == 'W' || *endptr == 'N' || *endptr == 'E')
{
coord = deg + min /60.0 + sec / 3600.0;
ok = TRUE;
if (*endptr == 'S' || *endptr == 'W') coord = -coord;
}
}
-
+
if (!ok)
{
coord = fallback;
log_printf("unable to parse GPS coordinate '%s'\n", string);
}
-
+
g_free(string);
return coord;
}
-
+
+gdouble metadata_read_GPS_direction(FileData *fd, const gchar *key, gdouble fallback)
+{
+ gchar *endptr;
+ gdouble deg;
+ gboolean ok = FALSE;
+ gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
+ if (!string) return fallback;
+
+ DEBUG_3("GPS_direction: %s\n", string);
+ deg = g_ascii_strtod(string, &endptr);
+
+ /* Expected text string is of the format e.g.:
+ * 18000/100
+ */
+ if (*endptr == '/')
+ {
+ deg = deg/100;
+ ok = TRUE;
+ }
+
+ if (!ok)
+ {
+ deg = fallback;
+ log_printf("unable to parse GPS direction '%s: %f'\n", string, deg);
+ }
+
+ g_free(string);
+
+ return deg;
+}
+
gboolean metadata_append_string(FileData *fd, const gchar *key, const char *value)
{
gchar *str = metadata_read_string(fd, key, METADATA_PLAIN);
-
- if (!str)
+
+ if (!str)
{
return metadata_write_string(fd, key, value);
}
}
}
+gboolean metadata_write_GPS_coord(FileData *fd, const gchar *key, gdouble value)
+{
+ gint deg;
+ gdouble min;
+ gdouble param;
+ char *coordinate;
+ char *ref;
+ gboolean ok = TRUE;
+
+ param = value;
+ if (param < 0)
+ param = -param;
+ deg = param;
+ min = (param * 60) - (deg * 60);
+ if (g_strcmp0(key, "Xmp.exif.GPSLongitude") == 0)
+ if (value < 0)
+ ref = "W";
+ else
+ ref = "E";
+ else if (g_strcmp0(key, "Xmp.exif.GPSLatitude") == 0)
+ if (value < 0)
+ ref = "S";
+ else
+ ref = "N";
+ else
+ {
+ log_printf("unknown GPS parameter key '%s'\n", key);
+ ok = FALSE;
+ }
+
+ if (ok)
+ {
+ coordinate = g_strdup_printf("%i,%lf,%s", deg, min, ref);
+ metadata_write_string(fd, key, coordinate );
+ g_free(coordinate);
+ }
+
+ return ok;
+}
+
gboolean metadata_append_list(FileData *fd, const gchar *key, const GList *values)
{
GList *list = metadata_read_list(fd, key, METADATA_PLAIN);
-
- if (!list)
+
+ if (!list)
{
return metadata_write_list(fd, key, values);
}
gboolean ret;
list = g_list_concat(list, string_list_copy(values));
list = remove_duplicate_strings_from_list(list);
-
+
ret = metadata_write_list(fd, key, list);
string_list_free(list);
return ret;
}
}
+/**
+ * \see find_string_in_list
+ */
gchar *find_string_in_list_utf8nocase(GList *list, const gchar *string)
{
gchar *string_casefold = g_utf8_casefold(string, -1);
while (list)
{
gchar *haystack = list->data;
-
+
if (haystack)
{
gboolean equal;
return haystack;
}
}
-
+
list = list->next;
}
-
+
g_free(string_casefold);
return NULL;
}
+/**
+ * \see find_string_in_list
+ */
+gchar *find_string_in_list_utf8case(GList *list, const gchar *string)
+{
+ while (list)
+ {
+ gchar *haystack = list->data;
+
+ if (haystack && strcmp(haystack, string) == 0)
+ return haystack;
+
+ list = list->next;
+ } // while (list)
+
+ return NULL;
+} // gchar *find_string_in_list_utf...
+
+/**
+ * \brief Find a existent string in a list.
+ *
+ * This is a switch between find_string_in_list_utf8case and
+ * find_string_in_list_utf8nocase to search with or without case for the
+ * existence of a string.
+ *
+ * \param list The list to search in
+ * \param string The string to search for
+ * \return The string or NULL
+ *
+ * \see find_string_in_list_utf8case
+ * \see find_string_in_list_utf8nocase
+ */
+gchar *find_string_in_list(GList *list, const gchar *string)
+{
+ if (options->metadata.keywords_case_sensitive)
+ return find_string_in_list_utf8case(list, string);
+ else
+ return find_string_in_list_utf8nocase(list, string);
+}
#define KEYWORDS_SEPARATOR(c) ((c) == ',' || (c) == ';' || (c) == '\n' || (c) == '\r' || (c) == '\b')
gchar *keyword = g_strndup(begin, l);
/* only add if not already in the list */
- if (!find_string_in_list_utf8nocase(list, keyword))
+ if (!find_string_in_list(list, keyword))
list = g_list_append(list, keyword);
else
g_free(keyword);
/*
* keywords to marks
*/
-
+
gboolean meta_data_get_keyword_mark(FileData *fd, gint n, gpointer data)
{
GList *path = data;
GList *keywords = NULL;
GtkTreeIter iter;
-
+
if (!keyword_tree_get_iter(GTK_TREE_MODEL(keyword_tree), &iter, path)) return FALSE;
keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
if (!!keyword_tree_is_set(GTK_TREE_MODEL(keyword_tree), &iter, keywords) != !!value)
{
- if (value)
+ if (value)
{
keyword_tree_set(GTK_TREE_MODEL(keyword_tree), &iter, &keywords);
}
for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
{
file_data_get_registered_mark_func(i, &get_mark_func, &set_mark_func, &mark_func_data);
- if (get_mark_func == meta_data_get_keyword_mark)
+ if (get_mark_func == meta_data_get_keyword_mark)
{
GtkTreeIter old_kw_iter;
GList *old_path = mark_func_data;
-
- if (keyword_tree_get_iter(keyword_tree, &old_kw_iter, old_path) &&
+
+ if (keyword_tree_get_iter(keyword_tree, &old_kw_iter, old_path) &&
(i == mark || /* release any previous connection of given mark */
keyword_compare(keyword_tree, &old_kw_iter, kw_iter) == 0)) /* or given keyword */
{
gchar *mark_str;
path = keyword_tree_get_path(keyword_tree, kw_iter);
file_data_register_mark_func(mark, meta_data_get_keyword_mark, meta_data_set_keyword_mark, path, (GDestroyNotify)string_list_free);
-
+
mark_str = g_strdup_printf("%d", mark + 1);
gtk_tree_store_set(GTK_TREE_STORE(keyword_tree), kw_iter, KEYWORD_COLUMN_MARK, mark_str, -1);
g_free(mark_str);
return name;
}
+gchar *keyword_get_mark(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
+{
+ gchar *mark_str;
+
+ gtk_tree_model_get(keyword_tree, iter, KEYWORD_COLUMN_MARK, &mark_str, -1);
+ return mark_str;
+}
+
gchar *keyword_get_casefold(GtkTreeModel *keyword_tree, GtkTreeIter *iter)
{
gchar *casefold;
{
GtkTreeIter parent_a;
GtkTreeIter parent_b;
-
+
gboolean valid_pa = gtk_tree_model_iter_parent(keyword_tree, &parent_a, a);
gboolean valid_pb = gtk_tree_model_iter_parent(keyword_tree, &parent_b, b);
gboolean toplevel = FALSE;
gboolean ret;
gchar *casefold;
-
+
if (parent_ptr)
{
parent = *parent_ptr;
{
toplevel = TRUE;
}
-
+
if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &iter, toplevel ? NULL : &parent)) return FALSE;
-
+
casefold = g_utf8_casefold(name, -1);
ret = FALSE;
-
+
while (TRUE)
{
if (!(exclude_sibling && sibling && keyword_compare(keyword_tree, &iter, sibling) == 0))
{
- if (options->metadata.tags_case_sensitive)
+ if (options->metadata.keywords_case_sensitive)
{
gchar *iter_name = keyword_get_name(keyword_tree, &iter);
ret = strcmp(name, iter_name) == 0;
g_free(iter_casefold);
} // if (options->metadata.tags_cas...
}
- if (ret)
+ if (ret)
{
if (result) *result = iter;
break;
void keyword_copy_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
{
GtkTreeIter from_child;
-
+
keyword_copy(keyword_tree, to, from);
-
+
if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &from_child, from)) return;
-
+
while (TRUE)
{
GtkTreeIter to_child;
{
GList *path = NULL;
GtkTreeIter iter = *iter_ptr;
-
+
while (TRUE)
{
GtkTreeIter parent;
GtkTreeIter iter;
if (!gtk_tree_model_get_iter_first(keyword_tree, &iter)) return FALSE;
-
+
while (TRUE)
{
GtkTreeIter children;
if (!gtk_tree_model_iter_next(keyword_tree, &iter)) return FALSE;
}
path = path->next;
- if (!path)
+ if (!path)
{
*iter_ptr = iter;
return TRUE;
}
-
+
if (!gtk_tree_model_iter_children(keyword_tree, &children, &iter)) return FALSE;
iter = children;
}
{
/* for the purpose of expanding and hiding, a helper is set if it has any children set */
GtkTreeIter child;
- if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
+ if (!gtk_tree_model_iter_children(keyword_tree, &child, &iter))
return FALSE; /* this should happen only on empty helpers */
while (TRUE)
if (!gtk_tree_model_iter_next(keyword_tree, &child)) return FALSE;
}
}
-
+
while (TRUE)
{
GtkTreeIter parent;
g_free(iter_casefold);
if (!found) return FALSE;
}
-
+
if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return TRUE;
iter = parent;
}
GList *casefold_list = NULL;
GList *work;
- if (options->metadata.tags_case_sensitive)
+ if (options->metadata.keywords_case_sensitive)
{
ret = keyword_tree_is_set_casefull(keyword_tree, *iter, kw_list);
}
if (keyword_get_is_keyword(keyword_tree, &iter))
{
gchar *name = keyword_get_name(keyword_tree, &iter);
- if (!find_string_in_list_utf8nocase(*kw_list, name))
+ if (!find_string_in_list(*kw_list, name))
{
*kw_list = g_list_append(*kw_list, name);
}
}
}
+GList *keyword_tree_get(GtkTreeModel *keyword_tree, GtkTreeIter *iter_ptr)
+{
+ GtkTreeIter iter = *iter_ptr;
+ GList *kw_list = NULL;
+
+ while (TRUE)
+ {
+ GtkTreeIter parent;
+
+ if (keyword_get_is_keyword(keyword_tree, &iter))
+ {
+ gchar *name = keyword_get_name(keyword_tree, &iter);
+ kw_list = g_list_append(kw_list, name);
+ }
+
+ if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return kw_list;
+ iter = parent;
+ }
+} // GList *keyword_tree_get(GtkTre...
+
static void keyword_tree_reset1(GtkTreeModel *keyword_tree, GtkTreeIter *iter, GList **kw_list)
{
gchar *found;
if (!keyword_get_is_keyword(keyword_tree, iter)) return;
name = keyword_get_name(keyword_tree, iter);
- found = find_string_in_list_utf8nocase(*kw_list, name);
+ found = find_string_in_list(*kw_list, name);
if (found)
{
{
GtkTreeIter child;
keyword_tree_reset1(keyword_tree, iter, kw_list);
-
+
if (!gtk_tree_model_iter_children(keyword_tree, &child, iter)) return;
while (TRUE)
static gboolean keyword_tree_check_empty_children(GtkTreeModel *keyword_tree, GtkTreeIter *parent, GList *kw_list)
{
GtkTreeIter iter;
-
- if (!gtk_tree_model_iter_children(keyword_tree, &iter, parent))
+
+ if (!gtk_tree_model_iter_children(keyword_tree, &iter, parent))
return TRUE; /* this should happen only on empty helpers */
while (TRUE)
if (!gtk_tree_model_iter_parent(keyword_tree, &parent, &iter)) return;
iter = parent;
-
+
while (keyword_tree_check_empty_children(keyword_tree, &iter, *kw_list))
{
GtkTreeIter parent;
{
keyword_delete(keyword_tree, &child);
}
-
+
meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree), iter_ptr, -1);
gtk_tree_model_get(GTK_TREE_MODEL(keyword_tree), iter_ptr, KEYWORD_COLUMN_HIDE_IN, &list, -1);
g_list_free(list);
-
+
gtk_tree_store_remove(keyword_tree, iter_ptr);
}
gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_all_in_cb, id);
}
+static gboolean keyword_revert_hidden_in_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
+{
+ if (keyword_is_hidden_in(GTK_TREE_MODEL(keyword_tree), iter, data))
+ {
+ keyword_show_in(GTK_TREE_STORE(model), iter, data);
+ }
+ return FALSE;
+}
+
+void keyword_revert_hidden_in(GtkTreeStore *keyword_tree, gpointer id)
+{
+ gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_revert_hidden_in_cb, id);
+}
+
static void keyword_hide_unset_in_recursive(GtkTreeStore *keyword_tree, GtkTreeIter *iter_ptr, gpointer id, GList *keywords)
{
GtkTreeIter iter = *iter_ptr;
else
{
GtkTreeIter child;
- if (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, &iter))
+ if (gtk_tree_model_iter_children(GTK_TREE_MODEL(keyword_tree), &child, &iter))
{
keyword_hide_unset_in_recursive(keyword_tree, &child, id, keywords);
}
void keyword_tree_new(void)
{
if (keyword_tree) return;
-
+
keyword_tree = gtk_tree_store_new(KEYWORD_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER);
}
void keyword_tree_new_default(void)
{
- if (keyword_tree) return;
-
- keyword_tree_new();
-
- GtkTreeIter i1, i2, i3;
-
- i1 = keyword_tree_default_append(keyword_tree, NULL, _("People"), TRUE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Family"), TRUE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Free time"), TRUE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Children"), TRUE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sport"), TRUE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Culture"), TRUE);
- i3 = keyword_tree_default_append(keyword_tree, &i2, _("Festival"), TRUE);
- i1 = keyword_tree_default_append(keyword_tree, NULL, _("Nature"), TRUE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Animal"), TRUE);
- i3 = keyword_tree_default_append(keyword_tree, &i2, _("Bird"), TRUE);
- i3 = keyword_tree_default_append(keyword_tree, &i2, _("Insect"), TRUE);
- i3 = keyword_tree_default_append(keyword_tree, &i2, _("Pets"), TRUE);
- i3 = keyword_tree_default_append(keyword_tree, &i2, _("Wildlife"), TRUE);
- i3 = keyword_tree_default_append(keyword_tree, &i2, _("Zoo"), TRUE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Plant"), TRUE);
- i3 = keyword_tree_default_append(keyword_tree, &i2, _("Tree"), TRUE);
- i3 = keyword_tree_default_append(keyword_tree, &i2, _("Flower"), TRUE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Water"), TRUE);
- i3 = keyword_tree_default_append(keyword_tree, &i2, _("River"), TRUE);
- i3 = keyword_tree_default_append(keyword_tree, &i2, _("Lake"), TRUE);
- i3 = keyword_tree_default_append(keyword_tree, &i2, _("Sea"), TRUE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Landscape"), TRUE);
- i1 = keyword_tree_default_append(keyword_tree, NULL, _("Art"), TRUE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Statue"), TRUE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Painting"), TRUE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
- i1 = keyword_tree_default_append(keyword_tree, NULL, _("City"), TRUE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Park"), TRUE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Street"), TRUE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Square"), TRUE);
- i1 = keyword_tree_default_append(keyword_tree, NULL, _("Architecture"), TRUE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Buildings"), FALSE);
- i3 = keyword_tree_default_append(keyword_tree, &i2, _("House"), TRUE);
- i3 = keyword_tree_default_append(keyword_tree, &i2, _("Cathedral"), TRUE);
- i3 = keyword_tree_default_append(keyword_tree, &i2, _("Palace"), TRUE);
- i3 = keyword_tree_default_append(keyword_tree, &i2, _("Castle"), TRUE);
- i3 = keyword_tree_default_append(keyword_tree, &i2, _("Bridge"), TRUE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Interior"), TRUE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
- i1 = keyword_tree_default_append(keyword_tree, NULL, _("Places"), FALSE);
- i1 = keyword_tree_default_append(keyword_tree, NULL, _("Conditions"), FALSE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Night"), TRUE);
- i3 = keyword_tree_default_append(keyword_tree, &i2, _("Lights"), TRUE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Reflections"), TRUE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sun"), TRUE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Weather"), FALSE);
- i3 = keyword_tree_default_append(keyword_tree, &i2, _("Fog"), TRUE);
- i3 = keyword_tree_default_append(keyword_tree, &i2, _("Rain"), TRUE);
- i3 = keyword_tree_default_append(keyword_tree, &i2, _("Clouds"), TRUE);
- i3 = keyword_tree_default_append(keyword_tree, &i2, _("Snow"), TRUE);
- i3 = keyword_tree_default_append(keyword_tree, &i2, _("Sunny weather"), TRUE);
- i1 = keyword_tree_default_append(keyword_tree, NULL, _("Photo"), FALSE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Edited"), TRUE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Detail"), TRUE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Macro"), TRUE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Portrait"), TRUE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Black and White"), TRUE);
- i2 = keyword_tree_default_append(keyword_tree, &i1, _("Perspective"), TRUE);
+ GtkTreeIter i1, i2;
+
+ if (!keyword_tree) keyword_tree_new();
+
+ i1 = keyword_tree_default_append(keyword_tree, NULL, _("People"), TRUE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Family"), TRUE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Free time"), TRUE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Children"), TRUE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sport"), TRUE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Culture"), TRUE);
+ keyword_tree_default_append(keyword_tree, &i2, _("Festival"), TRUE);
+ i1 = keyword_tree_default_append(keyword_tree, NULL, _("Nature"), TRUE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Animal"), TRUE);
+ keyword_tree_default_append(keyword_tree, &i2, _("Bird"), TRUE);
+ keyword_tree_default_append(keyword_tree, &i2, _("Insect"), TRUE);
+ keyword_tree_default_append(keyword_tree, &i2, _("Pets"), TRUE);
+ keyword_tree_default_append(keyword_tree, &i2, _("Wildlife"), TRUE);
+ keyword_tree_default_append(keyword_tree, &i2, _("Zoo"), TRUE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Plant"), TRUE);
+ keyword_tree_default_append(keyword_tree, &i2, _("Tree"), TRUE);
+ keyword_tree_default_append(keyword_tree, &i2, _("Flower"), TRUE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Water"), TRUE);
+ keyword_tree_default_append(keyword_tree, &i2, _("River"), TRUE);
+ keyword_tree_default_append(keyword_tree, &i2, _("Lake"), TRUE);
+ keyword_tree_default_append(keyword_tree, &i2, _("Sea"), TRUE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Landscape"), TRUE);
+ i1 = keyword_tree_default_append(keyword_tree, NULL, _("Art"), TRUE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Statue"), TRUE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Painting"), TRUE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
+ i1 = keyword_tree_default_append(keyword_tree, NULL, _("City"), TRUE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Park"), TRUE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Street"), TRUE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Square"), TRUE);
+ i1 = keyword_tree_default_append(keyword_tree, NULL, _("Architecture"), TRUE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Buildings"), FALSE);
+ keyword_tree_default_append(keyword_tree, &i2, _("House"), TRUE);
+ keyword_tree_default_append(keyword_tree, &i2, _("Cathedral"), TRUE);
+ keyword_tree_default_append(keyword_tree, &i2, _("Palace"), TRUE);
+ keyword_tree_default_append(keyword_tree, &i2, _("Castle"), TRUE);
+ keyword_tree_default_append(keyword_tree, &i2, _("Bridge"), TRUE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Interior"), TRUE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Historic"), TRUE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Modern"), TRUE);
+ i1 = keyword_tree_default_append(keyword_tree, NULL, _("Places"), FALSE);
+ i1 = keyword_tree_default_append(keyword_tree, NULL, _("Conditions"), FALSE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Night"), TRUE);
+ keyword_tree_default_append(keyword_tree, &i2, _("Lights"), TRUE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Reflections"), TRUE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Sun"), TRUE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Weather"), FALSE);
+ keyword_tree_default_append(keyword_tree, &i2, _("Fog"), TRUE);
+ keyword_tree_default_append(keyword_tree, &i2, _("Rain"), TRUE);
+ keyword_tree_default_append(keyword_tree, &i2, _("Clouds"), TRUE);
+ keyword_tree_default_append(keyword_tree, &i2, _("Snow"), TRUE);
+ keyword_tree_default_append(keyword_tree, &i2, _("Sunny weather"), TRUE);
+ i1 = keyword_tree_default_append(keyword_tree, NULL, _("Photo"), FALSE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Edited"), TRUE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Detail"), TRUE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Macro"), TRUE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Portrait"), TRUE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Black and White"), TRUE);
+ i2 = keyword_tree_default_append(keyword_tree, &i1, _("Perspective"), TRUE);
}
{
GtkTreeIter children;
gchar *name;
+ gchar *mark_str;
WRITE_NL(); WRITE_STRING("<keyword ");
name = keyword_get_name(keyword_tree, &iter);
write_char_option(outstr, indent, "name", name);
g_free(name);
write_bool_option(outstr, indent, "kw", keyword_get_is_keyword(keyword_tree, &iter));
- if (gtk_tree_model_iter_children(keyword_tree, &children, &iter))
+ mark_str = keyword_get_mark(keyword_tree, &iter);
+ if (mark_str && mark_str[0])
+ {
+ write_char_option(outstr, indent, "mark", mark_str);
+ }
+
+ if (gtk_tree_model_iter_children(keyword_tree, &children, &iter))
{
WRITE_STRING(">");
indent++;
indent--;
WRITE_NL(); WRITE_STRING("</keyword>");
}
- else
+ else
{
WRITE_STRING("/>");
}
GtkTreeIter iter;
WRITE_NL(); WRITE_STRING("<keyword_tree>");
indent++;
-
+
if (keyword_tree && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(keyword_tree), &iter))
{
keyword_tree_node_write_config(GTK_TREE_MODEL(keyword_tree), &iter, outstr, indent);
{
gchar *name = NULL;
gboolean is_kw = TRUE;
+ gchar *mark_str = NULL;
while (*attribute_names)
{
if (READ_CHAR_FULL("name", name)) continue;
if (READ_BOOL_FULL("kw", is_kw)) continue;
+ if (READ_CHAR_FULL("mark", mark_str)) continue;
log_printf("unknown attribute %s = %s\n", option, value);
}
- if (name && name[0])
+ if (name && name[0])
{
GtkTreeIter iter;
/* re-use existing keyword if any */
gtk_tree_store_append(keyword_tree, &iter, parent);
}
keyword_set(keyword_tree, &iter, name, is_kw);
+
+ if (mark_str)
+ {
+ meta_data_connect_mark_with_keyword(GTK_TREE_MODEL(keyword_tree),
+ &iter, (gint)atoi(mark_str) - 1);
+ }
+
g_free(name);
return gtk_tree_iter_copy(&iter);
}