gimp/plug-ins/metadata/xmp-model.c

1070 lines
33 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* xmp-model.c - treeview model for XMP metadata
*
* Copyright (C) 2004-2005, Raphaël Quinet <raphael@gimp.org>
*
* This library is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <string.h>
#include <gtk/gtk.h>
#include <libgimp/gimp.h>
#include "libgimp/stdplugins-intl.h"
#include "xmp-schemas.h"
#include "xmp-parse.h"
#include "xmp-model.h"
/* Used for converting row-changed events into property-changed and
* schema-changed events.*/
#define XMP_MODEL_SCHEMA 0
#define XMP_MODEL_PROPERTY 1
/* local function declarations */
static void tree_model_row_changed (GtkTreeModel *model,
GtkTreePath *path,
GtkTreeIter *iter,
gpointer user_data);
static XMPSchema * find_xmp_schema_by_iter (XMPModel *xmp_model,
GtkTreeIter *iter);
enum
{
PROPERTY_CHANGED,
SCHEMA_CHANGED,
LAST_SIGNAL
};
static void xmp_model_finalize (GObject *object);
G_DEFINE_TYPE (XMPModel, xmp_model, GTK_TYPE_TREE_STORE);
static guint xmp_model_signals[LAST_SIGNAL] = { 0 };
static void
xmp_model_class_init (XMPModelClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
xmp_model_signals[PROPERTY_CHANGED] =
g_signal_new ("property-changed",
GIMP_TYPE_XMP_MODEL,
G_SIGNAL_DETAILED,
G_STRUCT_OFFSET (XMPModelClass, property_changed),
NULL, NULL,
g_cclosure_marshal_VOID__BOXED,
G_TYPE_NONE, 1,
GTK_TYPE_TREE_ITER);
object_class->finalize = xmp_model_finalize;
klass->property_changed = NULL;
}
static void
xmp_model_init (XMPModel *xmp_model)
{
GType types[XMP_MODEL_NUM_COLUMNS];
types[COL_XMP_NAME] = G_TYPE_STRING;
types[COL_XMP_VALUE] = G_TYPE_STRING;
types[COL_XMP_VALUE_RAW] = G_TYPE_POINTER;
types[COL_XMP_TYPE_XREF] = G_TYPE_POINTER;
types[COL_XMP_WIDGET_XREF] = G_TYPE_POINTER;
types[COL_XMP_EDITABLE] = G_TYPE_INT;
types[COL_XMP_EDIT_ICON] = GDK_TYPE_PIXBUF;
types[COL_XMP_VISIBLE] = G_TYPE_BOOLEAN;
types[COL_XMP_WEIGHT] = G_TYPE_INT;
types[COL_XMP_WEIGHT_SET] = G_TYPE_BOOLEAN;
gtk_tree_store_set_column_types (GTK_TREE_STORE (xmp_model),
XMP_MODEL_NUM_COLUMNS, types);
xmp_model->custom_schemas = NULL;
xmp_model->custom_properties = NULL;
xmp_model->cached_schema = NULL;
g_signal_connect (GTK_TREE_MODEL (xmp_model), "row-changed",
G_CALLBACK (tree_model_row_changed),
NULL);
}
static void
xmp_model_finalize (GObject *object)
{
XMPModel *xmp_model = XMP_MODEL (object);
GtkTreeModel *model = xmp_model_get_tree_model (xmp_model);
GtkTreeIter iter;
GtkTreeIter child;
gchar **value_array;
gint i;
/* we used XMP_FLAG_DEFER_VALUE_FREE for the parser, so now we must free
all value arrays */
if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter))
{
do
{
if (gtk_tree_model_iter_children (model, &child, &iter))
{
gchar **last_value_array = NULL;
do
{
gtk_tree_model_get (model, &child,
COL_XMP_VALUE_RAW, &value_array,
-1);
if (value_array != last_value_array)
{
/* FIXME: this does not free everything */
for (i = 0; value_array[i] != NULL; i++)
g_free (value_array[i]);
g_free (value_array);
}
last_value_array = value_array;
}
while (gtk_tree_model_iter_next (model, &child));
}
}
while (gtk_tree_model_iter_next (model, &iter));
}
G_OBJECT_CLASS (xmp_model_parent_class)->finalize (object);
}
/**
* xmp_model_new:
*
* Return value: a new #XMPModel.
**/
XMPModel *
xmp_model_new (void)
{
return g_object_new (GIMP_TYPE_XMP_MODEL, NULL);
}
/**
* xmp_model_is_empty:
* @xmp_model: an #XMPModel
*
* Return value: %TRUE if @xmp_model is empty (no shemas, no properties)
**/
gboolean
xmp_model_is_empty (XMPModel *xmp_model)
{
GtkTreeIter iter;
g_return_val_if_fail (xmp_model != NULL, TRUE);
if ((xmp_model->custom_schemas != NULL)
|| (xmp_model->custom_properties != NULL))
return FALSE;
return !gtk_tree_model_get_iter_first (GTK_TREE_MODEL (xmp_model),
&iter);
}
/* translate a row-changed event into a property-changed or
* schema-changed event with the detail.
*/
static void
tree_model_row_changed (GtkTreeModel *model,
GtkTreePath *path,
GtkTreeIter *iter,
gpointer user_data)
{
gint depth;
XMPSchema *schema;
/* 1. check which iter depth the change was: 0 for schema 1 for
* property? */
depth = gtk_tree_store_iter_depth (GTK_TREE_STORE (model), iter);
if (depth == XMP_MODEL_SCHEMA)
{
/* 2. If a schema has changed, emit a schema changed signal */
}
if (depth == XMP_MODEL_PROPERTY)
{
schema = find_xmp_schema_by_iter (XMP_MODEL (model), iter);
xmp_model_property_changed (XMP_MODEL (model), schema, iter);
}
}
static XMPSchema *
find_xmp_schema_by_iter (XMPModel *xmp_model,
GtkTreeIter *child)
{
GtkTreeIter parent;
XMPSchema *schema;
if (! gtk_tree_model_iter_parent (GTK_TREE_MODEL (xmp_model), &parent, child))
return NULL;
gtk_tree_model_get (GTK_TREE_MODEL (xmp_model), &parent,
COL_XMP_TYPE_XREF, &schema,
-1);
return schema;
}
/* check if the given schema_uri matches a known schema; else return NULL */
static XMPSchema *
find_xmp_schema_by_uri (XMPModel *xmp_model,
const gchar *schema_uri)
{
int i;
GSList *list;
const gchar *c;
/* check if we know about this schema (exact match for URI) */
for (i = 0; xmp_schemas[i].uri != NULL; ++i)
{
if (! strcmp (xmp_schemas[i].uri, schema_uri))
{
#ifdef DEBUG_XMP_MODEL
if (xmp_schemas[i].name != NULL)
g_print ("%s \t[%s]\n", xmp_schemas[i].name, xmp_schemas[i].uri);
else
g_print ("(no name) \t[%s]\n", xmp_schemas[i].uri);
#endif
return &(xmp_schemas[i]);
}
}
/* this is not a standard shema; now check the custom schemas */
for (list = xmp_model->custom_schemas; list != NULL; list = list->next)
{
if (! strcmp (((XMPSchema *)(list->data))->uri, schema_uri))
{
#ifdef DEBUG_XMP_MODEL
g_print ("CUSTOM %s \t[%s]\n",
((XMPSchema *)(list->data))->name,
((XMPSchema *)(list->data))->uri);
#endif
return (XMPSchema *)(list->data);
}
}
/* now check for some common errors and results of bad encoding: */
/* - check for "http:" without "//", or missing "http://" */
for (i = 0; xmp_schemas[i].uri != NULL; ++i)
{
if (g_str_has_prefix (xmp_schemas[i].uri, "http://")
&& ((! strcmp (xmp_schemas[i].uri + 7, schema_uri))
|| (g_str_has_prefix (schema_uri, "http:")
&& ! strcmp (xmp_schemas[i].uri + 7, schema_uri + 5))
))
{
#ifdef DEBUG_XMP_MODEL
g_print ("%s \t~~~[%s]\n", xmp_schemas[i].name, xmp_schemas[i].uri);
#endif
return &(xmp_schemas[i]);
}
}
/* - check for errors such as "name (uri)" or "name (prefix, uri)" */
for (c = schema_uri; *c; c++)
if ((*c == '(') || (*c == ' ') || (*c == ','))
{
gint len;
c++;
while (*c == ' ')
c++;
if (! *c)
break;
for (len = 1; c[len]; len++)
if ((c[len] == ')') || (c[len] == ' '))
break;
for (i = 0; xmp_schemas[i].uri != NULL; ++i)
{
if (! strncmp (xmp_schemas[i].uri, c, len))
{
#ifdef DEBUG_XMP_MODEL
g_print ("%s \t~~~[%s]\n", xmp_schemas[i].name,
xmp_schemas[i].uri);
#endif
return &(xmp_schemas[i]);
}
}
}
#ifdef DEBUG_XMP_MODEL
g_print ("Unknown schema URI %s\n", schema_uri);
#endif
return NULL;
}
/* check if the given prefix matches a known schema; else return NULL */
static XMPSchema *
find_xmp_schema_prefix (XMPModel *xmp_model,
const gchar *prefix)
{
int i;
GSList *list;
for (i = 0; xmp_schemas[i].uri != NULL; ++i)
if (! strcmp (xmp_schemas[i].prefix, prefix))
return &(xmp_schemas[i]);
for (list = xmp_model->custom_schemas; list != NULL; list = list->next)
if (! strcmp (((XMPSchema *)(list->data))->prefix, prefix))
return (XMPSchema *)(list->data);
return NULL;
}
/* make the next lookup a bit faster if the tree is not modified */
static void
cache_iter_for_schema (XMPModel *xmp_model,
XMPSchema *schema,
GtkTreeIter *iter)
{
xmp_model->cached_schema = schema;
if (iter != NULL)
memcpy (&(xmp_model->cached_schema_iter), iter, sizeof (GtkTreeIter));
}
/* find the GtkTreeIter for the given schema and return TRUE if the schema was
found in the tree; else return FALSE */
static gboolean
find_iter_for_schema (XMPModel *xmp_model,
XMPSchema *schema,
GtkTreeIter *iter)
{
XMPSchema *schema_xref;
/* common case: return the cached iter */
if (schema == xmp_model->cached_schema)
{
memcpy (iter, &(xmp_model->cached_schema_iter), sizeof (GtkTreeIter));
return TRUE;
}
/* find where this schema has been stored in the tree */
if (! gtk_tree_model_get_iter_first (GTK_TREE_MODEL (xmp_model),
iter))
return FALSE;
do
{
gtk_tree_model_get (GTK_TREE_MODEL (xmp_model), iter,
COL_XMP_TYPE_XREF, &schema_xref,
-1);
if (schema_xref == schema)
{
cache_iter_for_schema (xmp_model, schema, iter);
return TRUE;
}
}
while (gtk_tree_model_iter_next (GTK_TREE_MODEL (xmp_model), iter));
return FALSE;
}
/* remove a property from the list of children of schema_iter */
static void
find_and_remove_property (XMPModel *xmp_model,
XMPProperty *property,
GtkTreeIter *schema_iter)
{
GtkTreeIter child_iter;
XMPProperty *property_xref;
if (! gtk_tree_model_iter_children (GTK_TREE_MODEL (xmp_model),
&child_iter, schema_iter))
return;
for (;;)
{
gtk_tree_model_get (GTK_TREE_MODEL (xmp_model), &child_iter,
COL_XMP_TYPE_XREF, &property_xref,
-1);
if (property_xref == property)
{
if (! gtk_tree_store_remove (GTK_TREE_STORE (xmp_model),
&child_iter))
break;
}
else
{
if (! gtk_tree_model_iter_next (GTK_TREE_MODEL(xmp_model),
&child_iter))
break;
}
}
}
/* add a schema to the tree */
static void
add_known_schema (XMPModel *xmp_model,
XMPSchema *schema,
GtkTreeIter *iter)
{
gtk_tree_store_append (GTK_TREE_STORE (xmp_model), iter, NULL);
gtk_tree_store_set (GTK_TREE_STORE (xmp_model), iter,
COL_XMP_NAME, schema->name,
COL_XMP_VALUE, schema->uri,
COL_XMP_VALUE_RAW, NULL,
COL_XMP_TYPE_XREF, schema,
COL_XMP_WIDGET_XREF, NULL,
COL_XMP_EDITABLE, FALSE,
COL_XMP_EDIT_ICON, NULL,
COL_XMP_VISIBLE, FALSE,
COL_XMP_WEIGHT, PANGO_WEIGHT_BOLD,
COL_XMP_WEIGHT_SET, TRUE,
-1);
cache_iter_for_schema (xmp_model, schema, iter);
}
/* called by the XMP parser - new schema */
static gpointer
parse_start_schema (XMPParseContext *context,
const gchar *ns_uri,
const gchar *ns_prefix,
gpointer user_data,
GError **error)
{
XMPModel *xmp_model = user_data;
GtkTreeIter iter;
XMPSchema *schema;
g_return_val_if_fail (xmp_model != NULL, NULL);
schema = find_xmp_schema_by_uri (xmp_model, ns_uri);
if (schema == NULL)
{
/* add schema to custom_schemas */
schema = g_new (XMPSchema, 1);
schema->uri = g_strdup (ns_uri);
schema->prefix = g_strdup (ns_prefix);
schema->name = schema->uri;
schema->properties = NULL;
xmp_model->custom_schemas = g_slist_prepend (xmp_model->custom_schemas,
schema);
}
else if (find_iter_for_schema (xmp_model, schema, &iter))
{
/* already in the tree, so no need to add it again */
return schema;
}
/* schemas with NULL names are special and should not go in the tree */
if (schema->name == NULL)
{
cache_iter_for_schema (xmp_model, NULL, NULL);
return schema;
}
/* if the schema is not in the tree yet, add it now */
add_known_schema (xmp_model, schema, &iter);
return schema;
}
/* called by the XMP parser - end of schema */
static void
parse_end_schema (XMPParseContext *context,
gpointer ns_user_data,
gpointer user_data,
GError **error)
{
XMPModel *xmp_model = user_data;
XMPSchema *schema = ns_user_data;
g_return_if_fail (xmp_model != NULL);
g_return_if_fail (schema != NULL);
xmp_model->cached_schema = NULL;
#ifdef DEBUG_XMP_MODEL
if (schema->name)
g_print ("End of %s\n", schema->name);
#endif
}
/* called by the XMP parser - new property */
static void
parse_set_property (XMPParseContext *context,
const gchar *name,
XMPParseType type,
const gchar **value,
gpointer ns_user_data,
gpointer user_data,
GError **error)
{
XMPModel *xmp_model = user_data;
XMPSchema *schema = ns_user_data;
int i;
const gchar *ns_prefix;
XMPProperty *property;
GtkTreeIter iter;
GtkTreeIter child_iter;
gchar *tmp_name;
gchar *tmp_value;
g_return_if_fail (xmp_model != NULL);
g_return_if_fail (schema != NULL);
if (! find_iter_for_schema (xmp_model, schema, &iter))
{
g_printerr ("Unable to set XMP property '%s' because its schema is bad",
name);
return;
}
ns_prefix = schema->prefix;
property = NULL;
if (schema->properties != NULL)
for (i = 0; schema->properties[i].name != NULL; ++i)
if (! strcmp (schema->properties[i].name, name))
{
property = &(schema->properties[i]);
break;
}
/* if the same property was already present, remove it (replace it) */
if (property != NULL)
find_and_remove_property (xmp_model, property, &iter);
switch (type)
{
case XMP_PTYPE_TEXT:
#ifdef DEBUG_XMP_MODEL
g_print ("\t%s:%s = \"%s\"\n", ns_prefix, name, value[0]);
#endif
if (property != NULL)
/* FIXME */;
else
{
property = g_new (XMPProperty, 1);
property->name = g_strdup (name);
property->type = XMP_TYPE_TEXT;
property->editable = TRUE;
xmp_model->custom_properties =
g_slist_prepend (xmp_model->custom_properties, property);
}
gtk_tree_store_append (GTK_TREE_STORE (xmp_model), &child_iter, &iter);
gtk_tree_store_set (GTK_TREE_STORE (xmp_model), &child_iter,
COL_XMP_NAME, name,
COL_XMP_VALUE, value[0],
COL_XMP_VALUE_RAW, value,
COL_XMP_TYPE_XREF, property,
COL_XMP_WIDGET_XREF, NULL,
COL_XMP_EDITABLE, property->editable,
COL_XMP_EDIT_ICON, NULL,
COL_XMP_VISIBLE, TRUE,
COL_XMP_WEIGHT, PANGO_WEIGHT_NORMAL,
COL_XMP_WEIGHT_SET, FALSE,
-1);
break;
case XMP_PTYPE_RESOURCE:
#ifdef DEBUG_XMP_MODEL
g_print ("\t%s:%s @ = \"%s\"\n", ns_prefix, name,
value[0]);
#endif
if (property != NULL)
/* FIXME */;
else
{
property = g_new (XMPProperty, 1);
property->name = g_strdup (name);
property->type = XMP_TYPE_URI;
property->editable = TRUE;
xmp_model->custom_properties =
g_slist_prepend (xmp_model->custom_properties, property);
}
tmp_name = g_strconcat (name, " @", NULL);
gtk_tree_store_append (GTK_TREE_STORE (xmp_model), &child_iter, &iter);
gtk_tree_store_set (GTK_TREE_STORE (xmp_model), &child_iter,
COL_XMP_NAME, tmp_name,
COL_XMP_VALUE, value[0],
COL_XMP_VALUE_RAW, value,
COL_XMP_TYPE_XREF, property,
COL_XMP_WIDGET_XREF, NULL,
COL_XMP_EDITABLE, property->editable,
COL_XMP_EDIT_ICON, NULL,
COL_XMP_VISIBLE, TRUE,
COL_XMP_WEIGHT, PANGO_WEIGHT_NORMAL,
COL_XMP_WEIGHT_SET, FALSE,
-1);
g_free (tmp_name);
break;
case XMP_PTYPE_ORDERED_LIST:
case XMP_PTYPE_UNORDERED_LIST:
#ifdef DEBUG_XMP_MODEL
g_print ("\t%s:%s [] =", ns_prefix, name);
for (i = 0; value[i] != NULL; i++)
if (i == 0)
g_print (" \"%s\"", value[i]);
else
g_print (", \"%s\"", value[i]);
g_print ("\n");
#endif
if (property != NULL)
/* FIXME */;
else
{
property = g_new (XMPProperty, 1);
property->name = g_strdup (name);
property->type = ((type == XMP_PTYPE_ORDERED_LIST)
? XMP_TYPE_TEXT_BAG
: XMP_TYPE_TEXT_SEQ);
property->editable = TRUE;
xmp_model->custom_properties =
g_slist_prepend (xmp_model->custom_properties, property);
}
tmp_name = g_strconcat (name, " []", NULL);
tmp_value = g_strjoinv ("; ", (gchar **) value);
gtk_tree_store_append (GTK_TREE_STORE (xmp_model), &child_iter, &iter);
gtk_tree_store_set (GTK_TREE_STORE (xmp_model), &child_iter,
COL_XMP_NAME, tmp_name,
COL_XMP_VALUE, tmp_value,
COL_XMP_VALUE_RAW, value,
COL_XMP_TYPE_XREF, property,
COL_XMP_WIDGET_XREF, NULL,
COL_XMP_EDITABLE, property->editable,
COL_XMP_EDIT_ICON, NULL,
COL_XMP_VISIBLE, TRUE,
COL_XMP_WEIGHT, PANGO_WEIGHT_NORMAL,
COL_XMP_WEIGHT_SET, FALSE,
-1);
g_free (tmp_value);
g_free (tmp_name);
break;
case XMP_PTYPE_ALT_THUMBS:
#ifdef DEBUG_XMP_MODEL
for (i = 0; value[i] != NULL; i += 2)
g_print ("\t%s:%s [size:%d] = \"...\"\n", ns_prefix, name,
*(int *)(value[i]));
g_print ("\n");
#endif
if (property != NULL)
/* FIXME */;
else
{
property = g_new (XMPProperty, 1);
property->name = g_strdup (name);
property->type = XMP_TYPE_THUMBNAIL_ALT;
property->editable = TRUE;
xmp_model->custom_properties =
g_slist_prepend (xmp_model->custom_properties, property);
}
tmp_name = g_strconcat (name, " []", NULL);
gtk_tree_store_append (GTK_TREE_STORE (xmp_model), &child_iter, &iter);
gtk_tree_store_set (GTK_TREE_STORE (xmp_model), &child_iter,
COL_XMP_NAME, tmp_name,
COL_XMP_VALUE, "[FIXME: display thumbnails]",
COL_XMP_VALUE_RAW, value,
COL_XMP_TYPE_XREF, property,
COL_XMP_WIDGET_XREF, NULL,
COL_XMP_EDITABLE, property->editable,
COL_XMP_EDIT_ICON, NULL,
COL_XMP_VISIBLE, TRUE,
COL_XMP_WEIGHT, PANGO_WEIGHT_NORMAL,
COL_XMP_WEIGHT_SET, FALSE,
-1);
g_free (tmp_name);
break;
case XMP_PTYPE_ALT_LANG:
#ifdef DEBUG_XMP_MODEL
for (i = 0; value[i] != NULL; i += 2)
g_print ("\t%s:%s [lang:%s] = \"%s\"\n", ns_prefix, name,
value[i], value[i + 1]);
#endif
if (property != NULL)
/* FIXME */;
else
{
property = g_new (XMPProperty, 1);
property->name = g_strdup (name);
property->type = XMP_TYPE_LANG_ALT;
property->editable = TRUE;
xmp_model->custom_properties =
g_slist_prepend (xmp_model->custom_properties, property);
}
for (i = 0; value[i] != NULL; i += 2)
{
tmp_name = g_strconcat (name, " [", value[i], "]", NULL);
gtk_tree_store_append (GTK_TREE_STORE (xmp_model), &child_iter, &iter);
gtk_tree_store_set (GTK_TREE_STORE (xmp_model), &child_iter,
COL_XMP_NAME, tmp_name,
COL_XMP_VALUE, value[i + 1],
COL_XMP_VALUE_RAW, value,
COL_XMP_TYPE_XREF, property,
COL_XMP_WIDGET_XREF, NULL,
COL_XMP_EDITABLE, property->editable,
COL_XMP_EDIT_ICON, NULL,
COL_XMP_VISIBLE, TRUE,
COL_XMP_WEIGHT, PANGO_WEIGHT_NORMAL,
COL_XMP_WEIGHT_SET, FALSE,
-1);
g_free (tmp_name);
}
break;
case XMP_PTYPE_STRUCTURE:
#ifdef DEBUG_XMP_MODEL
for (i = 2; value[i] != NULL; i += 2)
g_print ("\t%s:%s [%s] = \"%s\"\n", ns_prefix, name,
value[i], value[i + 1]);
#endif
if (property != NULL)
/* FIXME */;
else
{
property = g_new (XMPProperty, 1);
property->name = g_strdup (name);
property->type = XMP_TYPE_GENERIC_STRUCTURE;
property->editable = TRUE;
xmp_model->custom_properties =
g_slist_prepend (xmp_model->custom_properties, property);
}
for (i = 2; value[i] != NULL; i += 2)
{
tmp_name = g_strconcat (name, " [", value[i], "]", NULL);
gtk_tree_store_append (GTK_TREE_STORE (xmp_model), &child_iter, &iter);
gtk_tree_store_set (GTK_TREE_STORE (xmp_model), &child_iter,
COL_XMP_NAME, tmp_name,
COL_XMP_VALUE, value[i + 1],
COL_XMP_VALUE_RAW, value,
COL_XMP_TYPE_XREF, property,
COL_XMP_WIDGET_XREF, NULL,
COL_XMP_EDITABLE, property->editable,
COL_XMP_EDIT_ICON, NULL,
COL_XMP_VISIBLE, TRUE,
COL_XMP_WEIGHT, PANGO_WEIGHT_NORMAL,
COL_XMP_WEIGHT_SET, FALSE,
-1);
g_free (tmp_name);
}
break;
default:
#ifdef DEBUG_XMP_MODEL
g_print ("\t%s:%s = ?\n", ns_prefix, name);
#endif
break;
}
}
/* called by the XMP parser - parse error */
static void
parse_error (XMPParseContext *context,
GError *error,
gpointer user_data)
{
g_printerr ("While parsing XMP metadata:\n%s\n", error->message);
}
static const XMPParser xmp_parser =
{
parse_start_schema,
parse_end_schema,
parse_set_property,
parse_error
};
/**
* xmp_model_parse_buffer:
* @xmp_model: pointer to the #XMPModel in which the results will be stored
* @buffer: buffer to be parsed
* @buffer_length: length of the @buffer
* @skip_other_data: if %TRUE, allow arbitrary data before XMP packet marker
* @error: return location for a #GError
*
* Parse a buffer containing XMP metadata and merge the parsed contents into
* the supplied @xmp_model. If @skip_other_data is %TRUE, then the parser
* will try to find the <?xpacket...?> marker in the buffer, skipping any
* unknown data found before it.
*
* (Note: this calls the functions from xmp_parse.c, which will call the
* functions in this file through the xmp_parser structure defined above.)
*
* Return value: %TRUE on success, %FALSE if an error was set
**/
gboolean
xmp_model_parse_buffer (XMPModel *xmp_model,
const gchar *buffer,
gssize buffer_length,
gboolean skip_other_data,
GError **error)
{
XMPParseFlags flags;
XMPParseContext *context;
flags = XMP_FLAG_DEFER_VALUE_FREE; /* we will free the array ourselves */
if (skip_other_data)
flags |= XMP_FLAG_FIND_XPACKET;
context = xmp_parse_context_new (&xmp_parser, flags, xmp_model, NULL);
if (! xmp_parse_context_parse (context, buffer, buffer_length, error))
{
xmp_parse_context_free (context);
return FALSE;
}
if (! xmp_parse_context_end_parse (context, error))
{
xmp_parse_context_free (context);
return FALSE;
}
xmp_parse_context_free (context);
return TRUE;
}
/**
* xmp_model_parse_file:
* @xmp_model: pointer to the #XMPModel in which the results will be stored
* @filename: name of the file containing XMP metadata to parse
* @error: return location for a #GError
*
* Try to find XMP metadata in a file and merge its contents into the supplied
* @xmp_model.
*
* Return value: %TRUE on success, %FALSE if an error was set
**/
gboolean
xmp_model_parse_file (XMPModel *xmp_model,
const gchar *filename,
GError **error)
{
gchar *buffer;
gsize buffer_length;
g_return_val_if_fail (filename != NULL, FALSE);
if (! g_file_get_contents (filename, &buffer, &buffer_length, error))
return FALSE;
if (! xmp_model_parse_buffer (xmp_model, buffer, buffer_length, TRUE, error))
return FALSE;
g_free (buffer);
return TRUE;
}
/**
* xmp_model_get_tree_model:
* @xmp_model: pointer to an #XMPModel
*
* Return a pointer to the #GtkTreeModel contained in the #XMPModel.
**/
GtkTreeModel *
xmp_model_get_tree_model (XMPModel *xmp_model)
{
g_return_val_if_fail (xmp_model != NULL, NULL);
return GTK_TREE_MODEL (xmp_model);
}
/**
* xmp_model_get_scalar_property:
* @xmp_model: pointer to an #XMPModel
* @schema_name: full URI or usual prefix of the schema
* @property_name: name of the property to store
*
* Store a new value for the specified XMP property.
*
* Return value: string representation of the value of that property,
* or %NULL if the property does not exist
**/
const gchar *
xmp_model_get_scalar_property (XMPModel *xmp_model,
const gchar *schema_name,
const gchar *property_name)
{
XMPSchema *schema;
GtkTreeIter iter;
XMPProperty *property = NULL;
GtkTreeIter child_iter;
int i;
XMPProperty *property_xref;
const gchar *value;
g_return_val_if_fail (xmp_model != NULL, NULL);
g_return_val_if_fail (schema_name != NULL, NULL);
g_return_val_if_fail (property_name != NULL, NULL);
schema = find_xmp_schema_by_uri (xmp_model, schema_name);
if (! schema)
schema = find_xmp_schema_prefix (xmp_model, schema_name);
if (! schema)
return NULL;
if (! find_iter_for_schema (xmp_model, schema, &iter))
return NULL;
if (schema->properties != NULL)
for (i = 0; schema->properties[i].name != NULL; ++i)
if (! strcmp (schema->properties[i].name, property_name))
{
property = &(schema->properties[i]);
break;
}
if (property == NULL)
return NULL;
if (! gtk_tree_model_iter_children (GTK_TREE_MODEL (xmp_model),
&child_iter, &iter))
return NULL;
do
{
gtk_tree_model_get (GTK_TREE_MODEL (xmp_model), &child_iter,
COL_XMP_TYPE_XREF, &property_xref,
COL_XMP_VALUE, &value,
-1);
if (property_xref == property)
return value;
}
while (gtk_tree_model_iter_next (GTK_TREE_MODEL(xmp_model),
&child_iter));
return NULL;
}
/**
* xmp_model_set_scalar_property:
* @xmp_model: pointer to an #XMPModel
* @schema_name: full URI or usual prefix of the schema
* @property_name: name of the property to store
* @property_value: value to store
*
* Store a new value for the specified XMP property.
*
* Return value: %TRUE if the property was set, %FALSE if an error
* occurred (for example, the @schema_name is invalid)
**/
gboolean
xmp_model_set_scalar_property (XMPModel *xmp_model,
const gchar *schema_name,
const gchar *property_name,
const gchar *property_value)
{
XMPSchema *schema;
XMPProperty *property = NULL;
GtkTreeIter iter;
GtkTreeIter child_iter;
int i;
gchar **value;
g_return_val_if_fail (xmp_model != NULL, FALSE);
g_return_val_if_fail (schema_name != NULL, FALSE);
g_return_val_if_fail (property_name != NULL, FALSE);
g_return_val_if_fail (property_value != NULL, FALSE);
schema = find_xmp_schema_by_uri (xmp_model, schema_name);
if (! schema)
schema = find_xmp_schema_prefix (xmp_model, schema_name);
if (! schema)
return FALSE;
if (! find_iter_for_schema (xmp_model, schema, &iter))
add_known_schema (xmp_model, schema, &iter);
if (schema->properties != NULL)
for (i = 0; schema->properties[i].name != NULL; ++i)
if (! strcmp (schema->properties[i].name, property_name))
{
property = &(schema->properties[i]);
break;
}
if (property != NULL)
{
find_and_remove_property (xmp_model, property, &iter);
}
else
{
property = g_new (XMPProperty, 1);
property->name = g_strdup (property_name);
property->type = XMP_TYPE_TEXT;
property->editable = TRUE;
xmp_model->custom_properties =
g_slist_prepend (xmp_model->custom_properties, property);
}
value = g_new (gchar *, 2);
value[0] = g_strdup (property_value);
value[1] = NULL;
gtk_tree_store_append (GTK_TREE_STORE (xmp_model), &child_iter, &iter);
gtk_tree_store_set (GTK_TREE_STORE (xmp_model), &child_iter,
COL_XMP_NAME, g_strdup (property_name),
COL_XMP_VALUE, value[0],
COL_XMP_VALUE_RAW, value,
COL_XMP_TYPE_XREF, property,
COL_XMP_WIDGET_XREF, NULL,
COL_XMP_EDITABLE, property->editable,
COL_XMP_EDIT_ICON, NULL,
COL_XMP_VISIBLE, TRUE,
COL_XMP_WEIGHT, PANGO_WEIGHT_NORMAL,
COL_XMP_WEIGHT_SET, FALSE,
-1);
return TRUE;
}
/**
* xmp_model_property_changed:
* @xmp_model: An #XMPModel
* @schema: An #XMPSchema the property belongs to
* @iter: A valid #GtkTreeIter pointing to the changed row
*
* Emits the "property-changed" event based on the @tree_model with
* detail. The detail is a joined string of xmp-schema-prefix and
* xmp-property-name (e.g. property-changed::dc:DocumentID).
**/
void
xmp_model_property_changed (XMPModel *xmp_model,
XMPSchema *schema,
GtkTreeIter *iter)
{
GQuark detail;
gchar *joined;
const gchar *property_name;
g_return_if_fail (GIMP_IS_XMP_MODEL (xmp_model));
g_return_if_fail (iter != NULL);
gtk_tree_model_get (GTK_TREE_MODEL (xmp_model), iter,
COL_XMP_NAME, &property_name,
-1);
joined = g_strjoin (":", schema->prefix, property_name, NULL);
detail = g_quark_from_string (joined);
g_signal_emit (xmp_model, xmp_model_signals[PROPERTY_CHANGED], detail, iter);
}