1
1
mirror of https://gitlab.gnome.org/GNOME/gimp.git synced 2025-10-06 09:32:41 +02:00

Compare commits

...

66 Commits

Author SHA1 Message Date
Roman Joost
47d8b68cd3 metadata: Improved formatting of the README
Markdown formatted.
2013-03-06 19:55:41 +10:00
Roman Joost
51d52dc9a1 metadata: For XMP_TYPE_ALT_LANG only set the first value.
The value arrays first value is the encoded x-default. Use that in the
UI to manipulate.
2013-03-06 19:55:41 +10:00
Roman Joost
9798eef3a5 metadata: Improved documentation 2013-03-06 19:55:41 +10:00
Roman Joost
96a49b90ab metadata: also test raw values in the #XMPModel 2013-03-05 20:40:26 +10:00
Roman Joost
8bc8afdbb4 metadata: Fixup of freeing up memory. 2013-03-05 14:05:48 +10:00
Roman Joost
fc17f9cf42 metadata: Bootstrapping integration test for various property types. 2013-03-05 13:38:04 +10:00
Roman Joost
9760b41486 metadata: Test with harder to parse values.
My first implementation of converting string values to "raw" values
would fail if the user would use characters I'm splitting on. This
implementation should be more implementation independant.
2013-03-05 11:02:19 +10:00
Roman Joost
6252cd8fa4 metadata: Code cleanup. 2013-03-04 20:08:05 +10:00
Roman Joost
5f71c0a2d9 metadata: Don't test properties we don't have support for yet. 2013-03-04 20:07:46 +10:00
Roman Joost
b540496b69 metadata: Improved documentation and little house keeping 2013-03-04 20:02:53 +10:00
Roman Joost
d085efe756 metadata: Set a default for XMP_TYPE_LANG_ALT 2013-03-04 19:47:17 +10:00
Roman Joost
6fe3e365d2 metadata: Fixed test to accomodate libgexiv2 integration 2013-03-04 19:47:17 +10:00
Roman Joost
8209f480c6 metadata: Use xmp_model_set_scalar_property as accessor.
With libgexiv2, we get preprocessed data. Having two columns storing
different information (scalar and raw values) makes it hard to keep the
data consistent.
Now the [set|get]_scalar methods can be used to store values and act as
accessor and getter methods. The underlying set|get methods will
automatically set the corresponding scalar string values anyways.
2013-03-04 19:47:17 +10:00
Roman Joost
3993ff9aa8 metadata: Preprocess the values from libgexiv2 2013-03-04 19:45:38 +10:00
Roman Joost
9e2ec9cf55 metadata: Helper function.
Becase we'll use libgexiv2 to retrieve scalar string values, we'll most
likely have to preprocess some of them in order to correctly
store/edit/export them again. This patch is based on a hunch with the
integration and might not work out as expected.
2013-03-04 14:46:39 +10:00
Roman Joost
c796242a7c metadata; Extended the readme document. 2013-03-04 10:21:44 +10:00
Roman Joost
9dc69dbfd8 metadata: tests also with exif data 2013-02-28 10:51:58 +10:00
Roman Joost
a8c1babd73 metadata: file parsing of XMP data replaced by libgexiv2 2013-02-28 10:51:58 +10:00
Roman Joost
6a86a91fe0 metadata: don't build the dump anymore.
Since we're replacing the parser by gexiv2, this tool isn't needed anymore.
2013-02-27 12:35:01 +10:00
Roman Joost
27cb8a06b6 metadata: fixed print debugging 2013-02-27 12:24:14 +10:00
Roman Joost
5f2bd201df configure: Setup to use libgexiv2 2013-02-26 14:12:15 +10:00
Roman Joost
05cf2bf893 metadata: Retabbed 2013-02-20 09:38:12 +10:00
Roman Joost
5925566d85 Removed obsolete object file to build. 2013-02-15 09:39:32 +10:00
Roman Joost
d60873cdb5 Declare test data instead of initializing it. 2013-02-15 09:39:32 +10:00
Roman Joost
607a6581ca Workaround for EXIF data import which crashes the plug-in.
This workaround allows to run the plug-in when opening a JPEG. The
import/merge of EXIF data is currently not working. All data is set as a
scalar property. This is invalid data by the XMP metadata
standard.  A proper fix will need more code for the import in
exif-decode.c
2013-02-15 09:39:32 +10:00
Roman Joost
f905f42f0b Revert "Use the realize signal to update the icons in the advanced tab."
This reverts commit 5491b567135aeeea83ab943450a1ab9709ef1e32. The reason
for that is, that we can use a
gtk_tree_view_insert_column_with_data_func to setup the treeview instead
of using signals.
2013-02-15 09:39:32 +10:00
Roman Joost
482d1ad4e7 Allow wrapping of the GtkTextViews. 2013-02-15 09:39:32 +10:00
Roman Joost
3914ac2480 Use the realize signal to update the icons in the advanced tab. 2013-02-15 09:39:32 +10:00
Roman Joost
8a80a00e65 Don't use a temporary name indicating the data type.
This will break the detailed signal subscription and lead to not
updating widgets during import.
2013-02-15 09:39:32 +10:00
Roman Joost
893f990618 No need to free strings in a treemodel.
According to the documentation the strings are freed automatically.
2013-02-15 09:39:32 +10:00
Roman Joost
db44a29dcb Avoid circular updating of widgets by blocking.
If the user changes values in the treeview, we don't want that the
widget updates the XMPModel again.
2013-02-15 09:39:32 +10:00
Roman Joost
36881974d1 Set the icon in the interface advanced tab.
Instead of rendering the icons for the advanced view from this widget,
we keep this responsibility in the interface (in the treeview). This
also revealed a bug the icon to disappear because of circular signals
being handled/fired.
2013-02-15 09:39:31 +10:00
Roman Joost
540e3b392a Set the property icons in the advanced tab on focus.
Once the tree view is focused by the user, we update the icons. This is
currently only necessary, because the XMPModel functions overwrite the
icon once the user changes a property. An update function should handle
this in the future to make this perhaps obsolete.
2013-02-15 09:39:31 +10:00
Roman Joost
ded93df773 Use a helper function to extract updated sequences.
If the user changed sequence properties in the UI, we need to split up
the changed string representation or use the raw value otherwise.
2013-02-15 09:39:14 +10:00
Roman Joost
fd3a20b993 Use dc:subject to store keyword properties.
Currently we're using a text widget. Perhaps a tag widget is more
suitable to enter keywords.
2013-02-15 09:39:14 +10:00
Roman Joost
cdc3527ad9 Test setting/getting properties in empty XMPModels. 2013-02-15 09:39:14 +10:00
Roman Joost
deaf61f332 Serialize XMP_TYPE_TEXT values correctly.
The description writer is a XMP_TYPE_TEXT property, which should be
serialized correctly._
2013-02-15 09:39:14 +10:00
Roman Joost
b807a919a3 Serialize values if no raw data is set.
Correctly serialize XMP Metadata in case the user creates an empty image
and attaches metadata to it.
2013-02-15 09:39:14 +10:00
Roman Joost
e1a6f31b5e Replaced test for exporting on empty XMPModel.
Replaced test, which assures that values set on an empty model are also
correctly serialized.
2013-02-15 09:39:14 +10:00
Roman Joost
6ebf31e7c9 Changed the serialisation handler for XMP_TYPE_TEXT_SEQ.
This handler now correctly serialises data set in the entry widgets.
2013-02-15 09:39:14 +10:00
Roman Joost
9195f5c724 New test case to test import/edit/export/import.
This test should assure that imported data is not touched until edited.
If it is edited it should be only merged on export.
2013-02-15 09:39:14 +10:00
Roman Joost
e1a39c4eff New functional test to make sure raw values are updated. 2013-02-15 09:39:14 +10:00
Roman Joost
6c0d870fb8 Merge changes back into raw values during serialisation
In case widgets changed the string representation of the raw values,
merge them back when serializing.
2013-02-15 09:39:13 +10:00
Roman Joost
fcd777f730 Preserve the raw values when setting scalar values. 2013-02-15 09:39:13 +10:00
Roman Joost
23a257335a Consistent variable naming in tests.
Be consistent with the variable naming with the source code
(xmp-model.c).
2013-02-15 09:39:13 +10:00
Roman Joost
0333090da5 Don't try to free raw values which are NULL.
If only scalar values are set in a new XMPModel (e.g. from a widget),
the raw values will be NULL.
2013-02-15 09:39:13 +10:00
Roman Joost
c500d40a51 New testsetup for functional tests.
I'd like to test a round trip of XMPMetadata, that is: parsing, editing
and exporting the metadata again.
2013-02-15 09:39:13 +10:00
Roman Joost
0f5a5917f9 Don't set and create data structures.
Use the API of the XMPModel to set scalar values. This will not alter
the raw values. The XMPModel will have to keep track and merge changes
if the raw data is exported.
2013-02-15 09:39:13 +10:00
Roman Joost
c3edffd206 Allow to set any type of property via xmp_model_set_scalar_property
This makes it easier for widgets to simply manipulate the string
representation of the set raw values. Only if the raw values are
exported we have to make sure, that the changed value is merged into the
raw value and exported.
2013-02-15 09:39:13 +10:00
Roman Joost
30c6574c74 Revert "Refactored xmp_model_set_scalar_property to use xmp_model_set_property"
This reverts commit 82b2a949c0470ec9dcf9621ca690c3e4c3046399. The reason
for this revert is, because I think it is easier to operate on the
string representation of the value while the user is changing it in the
editor. If the XMPModel is serialized again, we have to make sure we
merge the changed value with the raw value. In this way, we can also
make sure to keep the raw value as it was during import time.
2013-02-15 09:39:13 +10:00
Roman Joost
166d7685bf New test to test the xmp_model_find_by function 2013-02-15 09:39:13 +10:00
Roman Joost
c75a2c5d09 xmp_model_set_property now handles XMP_TYPE_TEXT_(SEQ|BAQ)
Refactored the deserialisation of XMP_TYPE_TEXT_(SEQ|BAQ) types from the
parser into the xmp_model_set_property function to reuse this code. This
is important to deal with widgets which set eg. DC creator.
2013-02-15 09:39:13 +10:00
Roman Joost
009fb5e76b Simple parser test to read from a file with XMP metadata.
This test should be a base to test parsing property types into the
XMPModel.
2013-02-15 09:39:13 +10:00
Roman Joost
be06d09260 Block changed signals from the XMPModel.
When setting properties from our XMPModel widget, avoid circular
updating by subscribed changed events from the XMPModel.
2013-02-15 09:39:13 +10:00
Roman Joost
1e81d9d05c Added test for returning (raw) values. 2013-02-15 09:39:13 +10:00
Roman Joost
62e8455318 Added tests to test setting and getting scalar properties. 2013-02-15 09:39:13 +10:00
Roman Joost
744beb479f Bootstrapping unittests to test the XMPModel. 2013-02-15 09:39:13 +10:00
Roman Joost
501db6b924 Use the new set_property value.
We can't set the scalar value, as the value we're manipulating is not of
type TEXT
2013-02-15 09:39:13 +10:00
Roman Joost
2ce13dc603 New function: xmp_model_get_raw_property_value.
This function returns the raw value from the model. This is needed for
the widget to be able to manipulate the property value depending in it's
XMPType.
2013-02-15 09:39:12 +10:00
Roman Joost
0d9b025eb0 New function: xmp_model_find_xmptype_by
New function to return an XMPType. This is needed for widgets to
determine which raw value has to be set.
2013-02-15 09:39:12 +10:00
Roman Joost
ddab2dc394 Refactored xmp_model_set_scalar_property to use xmp_model_set_property 2013-02-15 09:39:12 +10:00
Roman Joost
9a001d543a Reuse the xmp_model_set_property function by the xmp-parser.
The parse can now reuse the xmp_model_set_property function for
XMP_TYPE_TEXT and XMP_TYPE_LANG_ALT properties to avoid code
duplication.
2013-02-15 09:39:12 +10:00
Roman Joost
190986a323 Use a general function to set raw values in the XMPModel.
The XMPModel only provided setting scalar values of XMP_TYPE_TEXT where
as for most schemas we need to set different types. The
xmp_model_set_property method tries to address that to set values of
various schema types in the model.
2013-02-15 09:39:12 +10:00
Roman Joost
8b0c9172b6 Bail out if the property value is faulty.
This usually shouldn't happen, but if the value is corrupt, the
metadata-browser shouldn't just crash.
2013-02-15 09:39:12 +10:00
Roman Joost
4369ec96e6 Revert "Default XMP_TYPE_LANG_ALT to "x-default" according to spec."
This reverts commit a4ccfcee72, because
the encoder needs to generate XMP data with attributes and their
language code if they're set in the model. This commit was hiding the
problem, that the xmp_model_entry widgets changed the raw value in the
tree model and set a wrong type of value therefore overwriting important
data.
2013-02-15 09:39:12 +10:00
Roman Joost
9ee2ddc84d Update the xmp-model widgets in case values have been already set.
This commit makes use of the persistent XMP metadata parasite. The
editor rereads the parasite and updates the widgets with the current
attached XMP data.
2013-02-15 09:39:12 +10:00
20 changed files with 1524 additions and 353 deletions

View File

@@ -61,6 +61,7 @@ m4_define([libcurl_required_version], [7.15.1])
m4_define([dbus_glib_required_version], [0.70])
m4_define([libgudev_required_version], [167])
m4_define([exif_required_version], [0.6.15])
m4_define([gexiv2_required_version], [0.4])
m4_define([lcms_required_version], [2.2])
m4_define([libpng_required_version], [1.2.37])
m4_define([liblzma_required_version], [5.0.0])
@@ -1299,6 +1300,23 @@ AC_SUBST(EXIF_LIBS)
AM_CONDITIONAL(HAVE_LIBEXIF, test "x$have_libexif" = xyes)
#####################
# Check for libgexiv2
#####################
AC_ARG_WITH(libgexiv2, [ --without-libgexiv2 don't build the metadata plug-in])
have_libgexiv2=no
if test "x$with_libgexiv2" != xno; then
have_libgexiv2=yes
PKG_CHECK_MODULES(GEXIV2, gexiv2 >= gexiv2_required_version,
AC_DEFINE(HAVE_LIBGEXIV2, 1, [libgexi2v is available]),
have_libgexiv2="no (libgexiv2 not found or too old)")
fi
AM_CONDITIONAL(HAVE_LIBGEXIV2, test "x$have_libgexiv2" = xyes)
#################
# Check for libaa
#################
@@ -2240,6 +2258,8 @@ plug-ins/lighting/images/Makefile
plug-ins/map-object/Makefile
plug-ins/maze/Makefile
plug-ins/metadata/Makefile
plug-ins/metadata/tests/Makefile
plug-ins/metadata/tests/files/Makefile
plug-ins/pagecurl/Makefile
plug-ins/print/Makefile
plug-ins/pygimp/Makefile
@@ -2381,6 +2401,7 @@ Optional Plug-Ins:
Plug-In Features:
EXIF support: $have_libexif
XMP support: $have_libexiv2
Optional Modules:
ALSA (MIDI Input): $have_alsa

View File

@@ -1,4 +1,7 @@
## Process this file with automake to produce Makefile.in
SUBDIRS = \
tests
libgimpui = $(top_builddir)/libgimp/libgimpui-$(GIMP_API_VERSION).la
libgimpwidgets = $(top_builddir)/libgimpwidgets/libgimpwidgets-$(GIMP_API_VERSION).la
@@ -49,18 +52,6 @@ metadata_SOURCES = \
# iptc-decode.h \
# iptc-decode.c
noinst_PROGRAMS = xmpdump
xmpdump_SOURCES = \
xmpdump.c \
xmp-schemas.h \
xmp-schemas.c \
xmp-encode.h \
xmp-encode.c \
xmp-model.h \
xmp-model.c \
xmp-parse.h \
xmp-parse.c
INCLUDES = \
-I$(top_srcdir) \
$(GTK_CFLAGS) \
@@ -77,6 +68,7 @@ LDADD = \
$(libgimpcolor) \
$(libgimpbase) \
$(EXIF_LIBS) \
$(GEXIV2_LIBS) \
$(GTK_LIBS) \
$(RT_LIBS) \
$(INTLLIBS) \

View File

@@ -1,11 +1,27 @@
What is this?
-------------
=============
This is the beginning of an editor for metadata. It is far from
complete yet.
History
-------
Raphaël Quinet is the original author of the editor, but couldn't
finish it. I - Róman Joost - started over as a C beginner hacking on the
editor. In the hope to improve the thing I continued following Raphaëls
initial path.
I want to leave this document as is, since I think Raphaëls thoughts a
still valid. Since his work a lot has changed in terms of parsing and
generating XMP.
That's why I'll be integrating the parsing component with a metadata
library instead of continuing the work of that component.
Current status
--------------
- It does not read metadata, except if there is an XMP block in the
image file.
- It does not write metadata, it can only export XMP into a separate
@@ -14,63 +30,88 @@ Current status
basic scalar types (strings, integers, ...). It is currently not
possible to add new properties using the editor (but this can be
done via the PDB call plug_in_metadata_set_simple()).
- Basically, the current code is only useful for viewing some XMP
metadata if the image file contains an XMP block. The import/export
functions are also supposed to work. Import merges, so it is
possible for example to import a license file such as the XMP files
from Creative Commons in order to apply a license to an image.
- The import/export functions are also supposed to work. Import merges,
so it is possible for example to import a license file such as the XMP
files from Creative Commons in order to apply a license to an image.
- The metadata is saved in a persistent parasite that is overwritten
after each call. Caution: the editor should not be open while you
are trying to test some of the PDB calls, otherwise you would
discard all changes as soon as you press OK. This is a problem that
cannot be solved easily as long as the metadata editor is a plug-in.
Design
------
1. Scenario:
1. A user opens a file (e.g. a jpeg)
1. A file plug-in (e.g. jpeg-load) finds an XMP header and calls the
metadata plug-in through the PDB to decode the XMP.
1. The metadata plug-in parses the XMP and stores the result in a
parasite.
1. The file has finished loading.
1. The user opens the metadata editor from the menu.
1. The metadata editor finds the XMP packet stored in the parasite.
1. The parasite is parsed again and the data read into the XMPModel
1. The editor has finished loading.
1. The data is displayed in the editor.
How to find test images?
------------------------
The easiest way is to try a Google search for "<?xpacket". This is
the marker that is used at the beginning of XMP packets. Many images
or PDF files are incorrectly served as text/plain and therefore
indexed by Google as text files. By looking for XMP tags such as
"<?xpacket", it is possible to find a large number of files containing
XMP metadata.
The Exiv2 project provides quite a big amount of test images:
http://www.exiv2.org/
Work in progress:
-----------------
- improve XMP code generation (xmp-gen.c) - currently, some structured
- libgexiv2 integration
* Reducing amount of code to maintain
* Less error prone and sharing of code.
- improve XMP code generation (xmp-encode.c) - currently, some structured
property types are not written back to the buffer, so the round-trip
decode/encode can silently discard some properties
- add tabs for easy editing of metadata, connect them to the tree
- fix the conversion to/from EXIF (exif-parse.c, exif-gen.c)
- fix the conversion to/from EXIF (exif-decode.c)
- register PDB functions correctly (for setting/getting metadata)
- update the JPEG plug-in
Near future:
- add custom callbacks for editing in the tree, validate entries, ...
- smarter display of rational values, closed sets, ...
- improve the layout of the various tabs
Not-so-near future:
- move some of this stuff into the core
-------------------------------------------------------------------------------
Files modified (2004-08-24):
configure.in (added /plug-ins/metadata/Makefile)
menus/image-menu.xml.in (added placeholder "<Image>/File/Info")
plug-ins/Makefile.am (added "metadata" in SUBDIRS)
plug-ins/common/jpeg.c (changed to get/set metadata through PDB)
New files:
plug-ins/metadata/Makefile.am (generates Makefile.in to get a Makefile)
plug-ins/metadata/metadata.c (main part: registers the plug-in, etc.)
plug-ins/metadata/interface.h
plug-ins/metadata/exif-decode.c (converts EXIF into the XMP tree model)
plug-ins/metadata/exif-decode.h
plug-ins/metadata/interface.c (user interface: widgets and callbacks)
plug-ins/metadata/xmp-model.h
plug-ins/metadata/interface.h
plug-ins/metadata/metadata.c (main part: registers the plug-in, etc.)
plug-ins/metadata/xmp-encode.c (generates XMP metadata from the tree model)
plug-ins/metadata/xmp-encode.h
plug-ins/metadata/xmp-model.c (model for the treeview, list of XMP schemas)
plug-ins/metadata/xmp-parse.h
plug-ins/metadata/xmp-model.h
plug-ins/metadata/xmp-parse.c (simple parser for XMP metadata)
plug-ins/metadata/xmp-gen.h
plug-ins/metadata/xmp-gen.c (generates XMP metadata from the tree model)
plug-ins/metadata/exif-parse.h
plug-ins/metadata/exif-parse.c (converts EXIF into the XMP tree model)
plug-ins/metadata/exif-gen.h
plug-ins/metadata/exif-gen.c (generates EXIF metadata from the tree model)
plug-ins/metadata/xmp-parse.h
plug-ins/metadata/xmp-schemas.c (holds schema definitions for XMP metadata)
plug-ins/metadata/xmp-schemas.h
UI:
plug-ins/metadata/gimpxmpmodelentry.c (Entry widget to string values.)
plug-ins/metadata/gimpxmpmodelentry.h
plug-ins/metadata/gimpxmpmodeltext.c (Entry widget for text values)
plug-ins/metadata/gimpxmpmodeltext.h
plug-ins/metadata/gimpxmpmodelwidget.c (Interface Widget which is subscribed to change events.)
plug-ins/metadata/gimpxmpmodelwidget.h

View File

@@ -86,7 +86,15 @@ static void
gimp_xmp_model_entry_set_text (GimpXmpModelWidget *widget,
const gchar *tree_value)
{
g_signal_handlers_block_by_func (widget,
gimp_xmp_model_entry_changed,
NULL);
gtk_entry_set_text (GTK_ENTRY (widget), tree_value);
g_signal_handlers_unblock_by_func (widget,
gimp_xmp_model_entry_changed,
NULL);
}
static void

View File

@@ -54,10 +54,6 @@ static void gimp_xmp_model_widget_xmpmodel_changed (XMPModel *xmp
const gchar * find_schema_prefix (const gchar *schema_uri);
void set_property_edit_icon (GtkWidget *widget,
XMPModel *xmp_model,
GtkTreeIter *iter);
GType
gimp_xmp_model_widget_interface_get_type (void)
@@ -143,6 +139,7 @@ gimp_xmp_model_widget_constructor (GObject *object)
GimpXmpModelWidget *widget = GIMP_XMP_MODEL_WIDGET (object);
GimpXmpModelWidgetPrivate *priv;
gchar *signal;
const gchar *value;
priv = GIMP_XMP_MODEL_WIDGET_GET_PRIVATE (object);
@@ -154,6 +151,13 @@ gimp_xmp_model_widget_constructor (GObject *object)
G_CALLBACK (gimp_xmp_model_widget_xmpmodel_changed),
widget);
// update the widget in case the xmp-model has already a value set
value = xmp_model_get_scalar_property (priv->xmp_model,
priv->schema_uri,
priv->property_name);
if (value != NULL)
gimp_xmp_model_widget_set_text (widget, value);
g_free (signal);
}
@@ -258,20 +262,15 @@ gimp_xmp_model_widget_xmpmodel_changed (XMPModel *xmp_model,
GimpXmpModelWidgetPrivate *priv = GIMP_XMP_MODEL_WIDGET_GET_PRIVATE (widget);
const gchar *tree_value;
const gchar *property_name;
GdkPixbuf *icon;
gtk_tree_model_get (GTK_TREE_MODEL (xmp_model), iter,
COL_XMP_NAME, &property_name,
COL_XMP_VALUE, &tree_value,
COL_XMP_EDIT_ICON, &icon,
-1);
if (! strcmp (priv->property_name, property_name))
gimp_xmp_model_widget_set_text (widget, tree_value);
if (icon == NULL)
set_property_edit_icon (GTK_WIDGET (widget), priv->xmp_model, iter);
return;
}
@@ -306,12 +305,22 @@ void
gimp_xmp_model_widget_changed (GimpXmpModelWidget *widget,
const gchar *value)
{
GimpXmpModelWidgetPrivate *priv = GIMP_XMP_MODEL_WIDGET_GET_PRIVATE (widget);
GimpXmpModelWidgetPrivate *priv = GIMP_XMP_MODEL_WIDGET_GET_PRIVATE (widget);
g_signal_handlers_block_by_func (priv->xmp_model,
gimp_xmp_model_widget_xmpmodel_changed,
widget);
xmp_model_set_scalar_property (priv->xmp_model,
priv->schema_uri,
priv->property_name,
value);
g_signal_handlers_unblock_by_func (priv->xmp_model,
gimp_xmp_model_widget_xmpmodel_changed,
widget);
}
/**
@@ -332,35 +341,3 @@ find_schema_prefix (const gchar *schema_uri)
return NULL;
}
void
set_property_edit_icon (GtkWidget *widget,
XMPModel *xmp_model,
GtkTreeIter *iter)
{
GdkPixbuf *icon;
gboolean editable;
gtk_tree_model_get (GTK_TREE_MODEL (xmp_model), iter,
COL_XMP_EDITABLE, &editable,
COL_XMP_EDIT_ICON, &icon,
-1);
if (editable == XMP_AUTO_UPDATE)
{
icon = gtk_widget_render_icon (GTK_WIDGET (widget), GIMP_STOCK_WILBER,
GTK_ICON_SIZE_MENU, NULL);
gtk_tree_store_set (GTK_TREE_STORE (xmp_model), iter,
COL_XMP_EDIT_ICON, icon,
-1);
}
else if (editable == TRUE)
{
icon = gtk_widget_render_icon (GTK_WIDGET (widget), GTK_STOCK_EDIT,
GTK_ICON_SIZE_MENU, NULL);
gtk_tree_store_set (GTK_TREE_STORE (xmp_model), iter,
COL_XMP_EDIT_ICON, icon,
-1);
}
return;
}

View File

@@ -65,8 +65,6 @@ typedef struct
{
GtkWidget *dlg;
XMPModel *xmp_model;
GdkPixbuf *edit_icon;
GdkPixbuf *auto_icon;
gboolean run_ok;
} MetadataGui;
@@ -79,14 +77,9 @@ tree_value_edited (GtkCellRendererText *cell,
GtkTreeModel *model = data;
GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
GtkTreeIter iter;
gchar *old_text;
gtk_tree_model_get_iter (model, &iter, path);
gtk_tree_model_get (model, &iter, COL_XMP_VALUE, &old_text, -1);
g_free (old_text);
/* FIXME: update value[] array */
/* FIXME: check widget xref and update other widget if not NULL */
gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
COL_XMP_VALUE, new_text,
-1);
@@ -161,35 +154,55 @@ icon_foreach_func (GtkTreeModel *model,
GtkTreeIter *iter,
gpointer user_data)
{
gboolean editable;
MetadataGui *mgui = user_data;
gboolean editable;
GtkTreeView *treeview = GTK_TREE_VIEW (user_data);
GdkPixbuf *icon = NULL;
gtk_tree_model_get (model, iter,
COL_XMP_EDITABLE, &editable,
-1);
if (editable == XMP_AUTO_UPDATE)
{
icon = gtk_widget_render_icon (GTK_WIDGET (treeview),
GIMP_STOCK_WILBER,
GTK_ICON_SIZE_MENU, NULL);
gtk_tree_store_set (GTK_TREE_STORE (model), iter,
COL_XMP_EDIT_ICON, mgui->auto_icon,
COL_XMP_EDIT_ICON, icon,
-1);
}
else if (editable == TRUE)
{
icon = gtk_widget_render_icon (GTK_WIDGET (treeview),
GTK_STOCK_EDIT,
GTK_ICON_SIZE_MENU, NULL);
gtk_tree_store_set (GTK_TREE_STORE (model), iter,
COL_XMP_EDIT_ICON, mgui->edit_icon,
COL_XMP_EDIT_ICON, icon,
-1);
}
else
{
gtk_tree_store_set (GTK_TREE_STORE (model), iter,
COL_XMP_EDIT_ICON, NULL,
-1);
}
if (icon != NULL)
g_object_unref (icon);
return FALSE;
}
static void
update_icons (MetadataGui *mgui)
update_icons (GtkTreeView *treeview,
GtkDirectionType direction,
gpointer user_data)
{
GtkTreeModel *model;
/* add the edit icon to the rows that are editable */
model = xmp_model_get_tree_model (mgui->xmp_model);
gtk_tree_model_foreach (model, icon_foreach_func, mgui);
model = gtk_tree_view_get_model (treeview);
gtk_tree_model_foreach (GTK_TREE_MODEL (model), icon_foreach_func, treeview);
}
static void
@@ -243,6 +256,8 @@ add_description_tab (GtkWidget *notebook,
"property-name", "description",
"xmp-model", mgui->xmp_model,
NULL);
gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (text_view),
GTK_WRAP_WORD_CHAR);
gtk_container_add (GTK_CONTAINER (scrolled_window), text_view);
gimp_table_attach_aligned (GTK_TABLE (table), 0, 2,
_("_Description:"), 0.0, 0.5,
@@ -264,8 +279,8 @@ add_description_tab (GtkWidget *notebook,
GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC);
text_view = g_object_new (GIMP_TYPE_XMP_MODEL_TEXT,
"schema-uri", XMP_SCHEMA_PDF,
"property-name", "Keywords",
"schema-uri", XMP_SCHEMA_DUBLIN_CORE,
"property-name", "subject",
"xmp-model", mgui->xmp_model,
NULL);
gtk_container_add (GTK_CONTAINER (scrolled_window), text_view);
@@ -340,11 +355,14 @@ add_thumbnail_tab (GtkWidget *notebook)
}
static void
add_advanced_tab (GtkWidget *notebook,
GtkTreeModel *model)
add_advanced_tab (GtkWidget *notebook,
MetadataGui *mgui)
{
GtkWidget *sw;
GtkWidget *treeview;
GtkWidget *sw;
GtkWidget *treeview;
GtkTreeModel *model;
model = xmp_model_get_tree_model (mgui->xmp_model);
/* Advanced tab */
sw = gtk_scrolled_window_new (NULL, NULL);
@@ -364,6 +382,10 @@ add_advanced_tab (GtkWidget *notebook,
gtk_container_add (GTK_CONTAINER (sw), treeview);
/* update property icons when the user views this tab */
g_signal_connect (treeview, "focus",
G_CALLBACK (update_icons), NULL);
/* expand all rows after the treeview widget has been realized */
g_signal_connect (treeview, "realize",
G_CALLBACK (gtk_tree_view_expand_all),
@@ -428,8 +450,6 @@ import_dialog_response (GtkWidget *dlg,
return;
}
update_icons (mgui);
g_free (buffer);
g_free (filename);
}
@@ -641,12 +661,6 @@ metadata_dialog (gint32 image_ID,
gtk_widget_show (notebook);
mgui.xmp_model = xmp_model;
mgui.edit_icon = gtk_widget_render_icon (mgui.dlg, GTK_STOCK_EDIT,
GTK_ICON_SIZE_MENU, NULL);
mgui.auto_icon = gtk_widget_render_icon (mgui.dlg, GIMP_STOCK_WILBER,
GTK_ICON_SIZE_MENU, NULL);
update_icons (&mgui);
mgui.run_ok = FALSE;
/* add the tabs to the notebook */
@@ -656,7 +670,7 @@ metadata_dialog (gint32 image_ID,
add_camera1_tab (notebook);
add_camera2_tab (notebook);
add_thumbnail_tab (notebook);
add_advanced_tab (notebook, xmp_model_get_tree_model (mgui.xmp_model));
add_advanced_tab (notebook, &mgui);
gtk_window_set_default_size (GTK_WINDOW (mgui.dlg), 400, 500);
gtk_widget_show (mgui.dlg);
@@ -664,9 +678,5 @@ metadata_dialog (gint32 image_ID,
/* run, baby, run! */
gtk_main ();
/* clean up and return */
g_object_unref (mgui.auto_icon);
g_object_unref (mgui.edit_icon);
return mgui.run_ok;
}

View File

@@ -526,12 +526,11 @@ run (const gchar *name,
run_mode = param[0].data.d_int32;
if (run_mode == GIMP_RUN_INTERACTIVE)
{
if (! metadata_dialog (image_ID, xmp_model))
status = GIMP_PDB_CANCEL;
status = GIMP_PDB_CANCEL;
if (metadata_dialog (image_ID, xmp_model))
status = GIMP_PDB_SUCCESS;
}
g_printerr ("Not implemented yet (EDITOR_PROC)\n");
status = GIMP_PDB_EXECUTION_ERROR;
}
else
{

6
plug-ins/metadata/tests/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
/Makefile
/Makefile.in
/.deps
/.libs
test-xmp-model
test-xmp-model-functional

View File

@@ -0,0 +1,48 @@
SUBDIRS = \
files
libgimpui = $(top_builddir)/libgimp/libgimpui-$(GIMP_API_VERSION).la
libgimpwidgets = $(top_builddir)/libgimpwidgets/libgimpwidgets-$(GIMP_API_VERSION).la
libgimp = $(top_builddir)/libgimp/libgimp-$(GIMP_API_VERSION).la
libgimpconfig = $(top_builddir)/libgimpconfig/libgimpconfig-$(GIMP_API_VERSION).la
libgimpcolor = $(top_builddir)/libgimpcolor/libgimpcolor-$(GIMP_API_VERSION).la
libgimpbase = $(top_builddir)/libgimpbase/libgimpbase-$(GIMP_API_VERSION).la
libgimpmath = $(top_builddir)/libgimpmath/libgimpmath-$(GIMP_API_VERSION).la
LDADD = \
$(libgimpui) \
$(libgimpwidgets) \
$(libgimp) \
$(libgimpmath) \
$(libgimpconfig) \
$(libgimpcolor) \
$(libgimpbase) \
$(GEXIV2_LIBS) \
$(GTK_LIBS) \
$(GLIB_LIBS) \
../xmp-parse.o \
../xmp-schemas.o \
../xmp-encode.o \
../xmp-model.o
AM_CPPFLAGS = \
-I$(top_srcdir) \
$(GTK_CFLAGS) \
$(EXIF_CFLAGS) \
-I$(includedir) \
-I ../
TESTS_ENVIRONMENT = \
GIMP_TESTING_ABS_TOP_SRCDIR=@abs_top_srcdir@
TESTS = \
test-xmp-model \
test-xmp-model-functional
EXTRA_PROGRAMS = $(TESTS)
CLEANFILES = $(EXTRA_PROGRAMS)

View File

@@ -0,0 +1,2 @@
EXTRA_DIST = \
test.xmp

View File

@@ -0,0 +1,89 @@
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 4.1-c036 46.276720, Mon Feb 19 2007 22:40:08 ">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about=""
xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:format>application/vnd.adobe.photoshop</dc:format>
<dc:title>
<rdf:Alt>
<rdf:li xml:lang="x-default">Document Title</rdf:li>
</rdf:Alt>
</dc:title>
<dc:creator>
<rdf:Seq>
<rdf:li>Roman Joost</rdf:li>
</rdf:Seq>
</dc:creator>
<dc:description>
<rdf:Alt>
<rdf:li xml:lang="x-default">This is an example export from Photoshop</rdf:li>
</rdf:Alt>
</dc:description>
<dc:subject>
<rdf:Bag>
<rdf:li>Test</rdf:li>
<rdf:li>Image</rdf:li>
<rdf:li>Photoshop</rdf:li>
</rdf:Bag>
</dc:subject>
<dc:rights>
<rdf:Alt>
<rdf:li xml:lang="x-default">Don't copy!</rdf:li>
</rdf:Alt>
</dc:rights>
</rdf:Description>
<rdf:Description rdf:about=""
xmlns:xap="http://ns.adobe.com/xap/1.0/">
<xap:CreatorTool>Adobe Photoshop CS3 Windows</xap:CreatorTool>
<xap:CreateDate>2011-09-09T11:34:36+10:00</xap:CreateDate>
<xap:ModifyDate>2011-09-09T11:34:36+10:00</xap:ModifyDate>
<xap:MetadataDate>2011-09-09T11:34:36+10:00</xap:MetadataDate>
</rdf:Description>
<rdf:Description rdf:about=""
xmlns:xapMM="http://ns.adobe.com/xap/1.0/mm/">
<xapMM:DocumentID>uuid:E50AF9014DB3E0118751F8D311BA6F96</xapMM:DocumentID>
<xapMM:InstanceID>uuid:E50AF9014DB3E0118751F8D311BA6F96</xapMM:InstanceID>
</rdf:Description>
<rdf:Description rdf:about=""
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
<photoshop:ColorMode>0</photoshop:ColorMode>
<photoshop:AuthorsPosition>Mr</photoshop:AuthorsPosition>
<photoshop:DateCreated>2011-09-09</photoshop:DateCreated>
<photoshop:City>Brisbane</photoshop:City>
<photoshop:State>QLD</photoshop:State>
<photoshop:Country>Australia</photoshop:Country>
</rdf:Description>
<rdf:Description rdf:about=""
xmlns:xapRights="http://ns.adobe.com/xap/1.0/rights/">
<xapRights:Marked>True</xapRights:Marked>
<xapRights:WebStatement>http://www.gimp.org</xapRights:WebStatement>
</rdf:Description>
<rdf:Description rdf:about=""
xmlns:illustrator="http://ns.adobe.com/illustrator/1.0/">
<illustrator:StartupProfile>Print</illustrator:StartupProfile>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>

View File

@@ -0,0 +1,37 @@
<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>
<x:xmpmeta xmlns:x='adobe:ns:meta/'>
<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
<rdf:Description xmlns:dc='http://purl.org/dc/elements/1.1/'>
<dc:title>
<rdf:Alt>
<rdf:li xml:lang='x-default'>image title</rdf:li>
</rdf:Alt>
</dc:title>
<dc:creator>
<rdf:Seq>
<rdf:li>roman</rdf:li>
</rdf:Seq>
</dc:creator>
<dc:description>
<rdf:Alt>
<rdf:li xml:lang='x-default'>Hello, World</rdf:li>
<rdf:li xml:lang='de_DE'>Hallo, "Welt!"</rdf:li>
</rdf:Alt>
</dc:description>
<dc:subject>
<rdf:Bag>
<rdf:li>Test</rdf:li>
<rdf:li>Image</rdf:li>
<rdf:li>Photoshop</rdf:li>
</rdf:Bag>
</dc:subject>
</rdf:Description>
<rdf:Description rdf:about=""
xmlns:exif="http://ns.adobe.com/exif/1.0/">
<exif:ExifVersion>0220</exif:ExifVersion>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
<?xpacket end='r'?>

View File

@@ -0,0 +1,14 @@
# Exiv2 command file
# Run with exiv2 -m test_xmp.conf test_xmp.jpg
del Xmp.dc.creator
set Xmp.dc.creator XmpSeq "1) Wilber"
set Xmp.dc.creator "2) Wilma"
del Xmp.dc.contributor
set Xmp.dc.contributor "Róman Joost"
set Xmp.dc.contributor "Maiko Joost"
del Xmp.dc.title
set Xmp.dc.title LangAlt Hello, World
set Xmp.dc.title LangAlt lang=ja こ

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1,293 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 2011 Róman Joost <romanofski@gimp.org>
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <glib.h>
#include <gtk/gtk.h>
#include "xmp-parse.h"
#include "xmp-encode.h"
#include "xmp-model.h"
#define ADD_TEST(function) \
g_test_add ("/metadata-xmp-model/" #function, \
GimpTestFixture, \
NULL, \
gimp_test_xmp_model_setup, \
function, \
gimp_test_xmp_model_teardown);
typedef struct
{
XMPModel *xmp_model;
} GimpTestFixture;
typedef struct
{
const gchar *schema_name;
const gchar *name;
int pos;
const gchar *expected_value;
const gchar *expected_values[10];
} TestDataEntry;
static TestDataEntry propertiestotest[] =
{
{ XMP_PREFIX_DUBLIN_CORE, "title", 1, NULL, { NULL } },
{ XMP_PREFIX_DUBLIN_CORE, "creator", 0, NULL, { NULL } },
{ XMP_PREFIX_DUBLIN_CORE, "description", 1, NULL, { NULL } },
{ XMP_PREFIX_DUBLIN_CORE, "subject", 0, NULL, { NULL } },
{ NULL, NULL, 0, NULL, { NULL } }
};
TestDataEntry * const import_exportdata = propertiestotest;
/**
* This testdata tests different types in the #XMPModel and what they
* return for the editor.
* Note: The pos attribute is ignored.
*/
static TestDataEntry _xmp_property_values_view[] =
{
{ XMP_PREFIX_DUBLIN_CORE, "title", 0, "Hello, World,",
{"x-default", "Hello, World,", "ja", "\xe3\x81\x93"} },
{ XMP_PREFIX_DUBLIN_CORE, "creator", 0, "1) Wilber, 2) Wilma",
{"1) Wilber, 2) Wilma"} },
{ NULL, NULL, 0, NULL, { NULL } }
};
TestDataEntry * const xmp_property_values_view = _xmp_property_values_view;
static void gimp_test_xmp_model_setup (GimpTestFixture *fixture,
gconstpointer data);
static void gimp_test_xmp_model_teardown (GimpTestFixture *fixture,
gconstpointer data);
/**
* gimp_test_xmp_model_setup:
* @fixture: GimpTestFixture fixture
* @data:
*
* Test fixture to setup an XMPModel.
**/
static void
gimp_test_xmp_model_setup (GimpTestFixture *fixture,
gconstpointer data)
{
fixture->xmp_model = xmp_model_new ();
}
static void
gimp_test_xmp_model_teardown (GimpTestFixture *fixture,
gconstpointer data)
{
g_object_unref (fixture->xmp_model);
}
/**
* test_xmp_model_value_types
* @fixture:
* @data:
*
* Test if different value types are correctly set. The string
* representation of the value should be correct.
**/
static void
test_xmp_model_value_types (GimpTestFixture *fixture,
gconstpointer data)
{
int i, j;
guint a, b;
const gchar *value;
const gchar **raw = NULL;
TestDataEntry *testdata;
GError **error = NULL;
gchar *uri = NULL;
uri = g_build_filename (g_getenv ("GIMP_TESTING_ABS_TOP_SRCDIR"),
"plug-ins/metadata/tests/files/test_xmp.jpg",
NULL);
xmp_model_parse_file (fixture->xmp_model, uri, error);
g_free (uri);
for (i = 0; xmp_property_values_view[i].name != NULL; ++i)
{
testdata = &(xmp_property_values_view[i]);
/* view representation for the editor */
value = xmp_model_get_scalar_property (fixture->xmp_model,
testdata->schema_name,
testdata->name);
g_assert_cmpstr (value, ==, testdata->expected_value);
/* internal data, which the view representation is created from */
raw = xmp_model_get_raw_property_value (fixture->xmp_model,
testdata->schema_name,
testdata->name);
a = g_strv_length ((gchar **) raw);
b = g_strv_length ((gchar **) testdata->expected_values);
g_assert_cmpuint (a, ==, b);
for (j = 0; raw[j] != NULL; j++)
{
g_assert_cmpstr (raw[j], ==, testdata->expected_values[j]);
}
}
}
/**
* test_xmp_model_import_export_structures:
* @fixture:
* @data:
*
* Test to assure the round trip of data import, editing, export is
* working.
**/
static void
test_xmp_model_import_export_structures (GimpTestFixture *fixture,
gconstpointer data)
{
int i;
gboolean result;
const gchar *before_value;
const gchar *after_value;
GString *buffer;
TestDataEntry *testdata;
const gchar *scalarvalue = "test";
GError **error = NULL;
gchar *uri = NULL;
uri = g_build_filename (g_getenv ("GIMP_TESTING_ABS_TOP_SRCDIR"),
"plug-ins/metadata/tests/files/test.xmp",
NULL);
xmp_model_parse_file (fixture->xmp_model, uri, error);
g_free (uri);
for (i = 0; import_exportdata[i].name != NULL; ++i)
{
testdata = &(import_exportdata[i]);
/* backup the original raw value */
before_value = xmp_model_get_scalar_property (fixture->xmp_model,
testdata->schema_name,
testdata->name);
g_assert (before_value != NULL);
/* set a new scalar value */
result = xmp_model_set_scalar_property (fixture->xmp_model,
testdata->schema_name,
testdata->name,
scalarvalue);
g_assert (result == TRUE);
/* export */
buffer = g_string_new ("GIMP_TEST");
xmp_generate_packet (fixture->xmp_model, buffer);
/* import */
xmp_model_parse_buffer (fixture->xmp_model,
buffer->str,
buffer->len,
TRUE,
error);
after_value = xmp_model_get_scalar_property (fixture->xmp_model,
testdata->schema_name,
testdata->name);
/* check that the scalar value is correctly exported */
g_assert (after_value != NULL);
g_assert_cmpstr (after_value, ==, scalarvalue);
}
}
/**
* test_xmp_model_import_export:
* @fixture:
* @data:
*
* Functional test, which assures that changes in the string
* representation is correctly merged on export. This test starts of
* with inserting scalar values only.
**/
static void
test_xmp_model_import_export (GimpTestFixture *fixture,
gconstpointer data)
{
int i;
gboolean result;
GString *buffer;
TestDataEntry *testdata;
const gchar **after_value;
const gchar *scalarvalue = "test";
GError **error = NULL;
for (i = 0; import_exportdata[i].name != NULL; ++i)
{
testdata = &(import_exportdata[i]);
/* set a new scalar value */
result = xmp_model_set_scalar_property (fixture->xmp_model,
testdata->schema_name,
testdata->name,
scalarvalue);
g_assert (result == TRUE);
/* export */
buffer = g_string_new ("GIMP_TEST");
xmp_generate_packet (fixture->xmp_model, buffer);
/* import */
xmp_model_parse_buffer (fixture->xmp_model,
buffer->str,
buffer->len,
TRUE,
error);
after_value = xmp_model_get_raw_property_value (fixture->xmp_model,
testdata->schema_name,
testdata->name);
/* check that the scalar value is correctly exported */
g_assert (after_value != NULL);
g_assert_cmpstr (after_value[testdata->pos], ==, scalarvalue);
}
}
int main(int argc, char **argv)
{
gint result = -1;
g_type_init();
g_test_init (&argc, &argv, NULL);
ADD_TEST (test_xmp_model_import_export);
ADD_TEST (test_xmp_model_import_export_structures);
ADD_TEST (test_xmp_model_value_types);
result = g_test_run ();
return result;
}

View File

@@ -0,0 +1,291 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 2011 Róman Joost <romanofski@gimp.org>
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <glib.h>
#include <gtk/gtk.h>
#include "xmp-parse.h"
#include "xmp-encode.h"
#include "xmp-model.h"
#define ADD_TEST(function) \
g_test_add ("/metadata-xmp-model/" #function, \
GimpTestFixture, \
NULL, \
gimp_test_xmp_model_setup, \
function, \
gimp_test_xmp_model_teardown);
typedef struct
{
XMPModel *xmp_model;
} GimpTestFixture;
static void gimp_test_xmp_model_setup (GimpTestFixture *fixture,
gconstpointer data);
static void gimp_test_xmp_model_teardown (GimpTestFixture *fixture,
gconstpointer data);
/**
* gimp_test_xmp_model_setup:
* @fixture: GimpTestFixture fixture
* @data:
*
* Test fixture to setup an XMPModel.
**/
static void
gimp_test_xmp_model_setup (GimpTestFixture *fixture,
gconstpointer data)
{
fixture->xmp_model = xmp_model_new ();
}
static void
gimp_test_xmp_model_teardown (GimpTestFixture *fixture,
gconstpointer data)
{
g_object_unref (fixture->xmp_model);
}
/**
* test_xmp_model_is_empty:
* @fixture:
* @data:
*
* Test to assert that newly created models are empty
**/
static void
test_xmp_model_is_empty (GimpTestFixture *fixture,
gconstpointer data)
{
XMPModel *xmp_model;
xmp_model = xmp_model_new ();
g_assert (xmp_model_is_empty (xmp_model));
}
/**
* test_xmp_model_set_get_scalar_property:
* @fixture:
* @data:
*
* The test asserts the API to get/set scalar values. This should also
* work for many XMPProperties as we operate on one column in the
* XMPModel. The COL_XMP_VALUE column is the string representation of
* the COL_XMP_VALUE_RAW column, which will be updated upon export.
**/
static void
test_xmp_model_set_get_scalar_property (GimpTestFixture *fixture,
gconstpointer data)
{
const gchar *property_name = NULL;
const gchar *scalar_value;
gboolean result;
/* Schema is nonsense, so nothing is set */
result = xmp_model_set_scalar_property (fixture->xmp_model,
"SCHEMA",
"key",
"value");
g_assert (result == FALSE);
g_assert (xmp_model_is_empty (fixture->xmp_model) == TRUE);
/* Contributor is a scalar property. When set, we expect the XMPModel
* not to be empty any more and that we can retrieve the same value.
**/
property_name = "me_text";
result = xmp_model_set_scalar_property (fixture->xmp_model,
"dc",
"contributor",
property_name);
g_assert (result == TRUE);
g_assert (xmp_model_is_empty (fixture->xmp_model) == FALSE);
scalar_value = xmp_model_get_scalar_property (fixture->xmp_model,
"dc",
"contributor");
g_assert_cmpstr (scalar_value, ==, property_name);
/* Now we assure, that we can even set titles, which is of type
* XMP_TYPE_LANG_ALT.
* The scalar value returned is always the first value next to the
* language specifier.
**/
property_name = "lang=\"x-default\" title_lang_alt";
result = xmp_model_set_scalar_property (fixture->xmp_model,
"dc",
"title",
property_name);
g_assert (result == TRUE);
scalar_value = xmp_model_get_scalar_property (fixture->xmp_model,
"dc",
"title");
g_assert_cmpstr (scalar_value, ==, "title_lang_alt");
/* Setting an XMP_TYPE_LANG_ALT value without a language identifier,
* we'll assume it is 'x-default' and the accessor returns the same
* value.
*/
result = xmp_model_set_scalar_property (fixture->xmp_model,
"dc",
"title",
"me too");
g_assert (result == TRUE);
scalar_value = xmp_model_get_scalar_property (fixture->xmp_model,
"dc", "title");
g_assert_cmpstr (scalar_value, ==, "me too");
}
/**
* test_xmp_model_find_xmptype_by:
* @fixture:
* @data:
*
* Tests returning the correct property type by given schema and
* property_name
**/
static void
test_xmp_model_find_xmptype_by (GimpTestFixture *fixture,
gconstpointer data)
{
XMPType type;
type = xmp_model_find_xmptype_by (fixture->xmp_model, "non", "sense");
g_assert (type == -1);
type = xmp_model_find_xmptype_by (fixture->xmp_model, "dc", "title");
g_assert (type == XMP_TYPE_LANG_ALT);
type = xmp_model_find_xmptype_by (fixture->xmp_model, "dc", "contributor");
g_assert (type == XMP_TYPE_TEXT);
}
/**
* test_xmp_model_get_raw_property_value:
* @fixture:
* @data:
*
* Tests the xmp_model_get_raw_property_value, which returns raw values
* from the XMPModel.
**/
static void
test_xmp_model_get_raw_property_value (GimpTestFixture *fixture,
gconstpointer data)
{
gchar *expected_alt[] = {"en_GB", "my title", NULL};
gchar *expected_seq[] = {"Wilber", NULL};
const gchar **result = NULL;
// NULL is returned if no value is set by given schema and property
// name
g_assert (xmp_model_get_raw_property_value (fixture->xmp_model,
"dc", "title") == NULL);
// XMP_TYPE_LANG_ALT
// Note: XMP_TYPE_TEXT is tested with wrapper function
// xmp_model_set_scalar_property
xmp_model_set_property (fixture->xmp_model,
XMP_TYPE_LANG_ALT,
"dc",
"title",
(const gchar **) g_strdupv (expected_alt));
result = xmp_model_get_raw_property_value (fixture->xmp_model,
"dc", "title");
g_assert (result != NULL);
g_assert_cmpstr (result[0], ==, expected_alt[0]);
// XMP_TYPE_TEXT_SEQ
xmp_model_set_property (fixture->xmp_model,
XMP_TYPE_TEXT_SEQ,
"dc",
"creator",
(const gchar**) g_strdupv (expected_seq));
result = xmp_model_get_raw_property_value (fixture->xmp_model,
"dc", "creator");
g_assert (result != NULL);
g_assert_cmpstr (result[0], ==, expected_seq[0]);
}
/**
* test_xmp_model_parse_file:
* @fixture:
* @data:
*
* A very simple test parsing a file with XMP DC metadata.
**/
static void
test_xmp_model_parse_file (GimpTestFixture *fixture,
gconstpointer data)
{
gchar *uri = NULL;
const gchar *value = NULL;
GError *error = NULL;
uri = g_build_filename (g_getenv ("GIMP_TESTING_ABS_TOP_SRCDIR"),
"plug-ins/metadata/tests/files/test.xmp",
NULL);
g_assert (uri != NULL);
xmp_model_parse_file (fixture->xmp_model, uri, &error);
g_assert (! xmp_model_is_empty (fixture->xmp_model));
// title
value = xmp_model_get_scalar_property (fixture->xmp_model, "dc", "title");
g_assert_cmpstr (value, == , "image title");
// creator
value = xmp_model_get_scalar_property (fixture->xmp_model, "dc", "creator");
g_assert_cmpstr (value, == , "roman");
// description
value = xmp_model_get_scalar_property (fixture->xmp_model, "dc", "description");
g_assert_cmpstr (value, == , "Hello, World,");
g_free (uri);
}
int main(int argc, char **argv)
{
gint result = -1;
g_type_init();
g_test_init (&argc, &argv, NULL);
ADD_TEST (test_xmp_model_is_empty);
ADD_TEST (test_xmp_model_find_xmptype_by);
ADD_TEST (test_xmp_model_set_get_scalar_property);
ADD_TEST (test_xmp_model_get_raw_property_value);
ADD_TEST (test_xmp_model_parse_file);
result = g_test_run ();
return result;
}

View File

@@ -31,13 +31,45 @@
#include "xmp-schemas.h"
static void gen_element (GString *buffer,
gint indent,
const gchar *prefix,
const gchar *name,
const gchar *value,
...) G_GNUC_NULL_TERMINATED;
static void gen_element (GString *buffer,
gint indent,
const gchar *prefix,
const gchar *name,
const gchar *value,
...) G_GNUC_NULL_TERMINATED;
gchar ** get_extracted_values (const gchar *value,
const gchar **value_array);
/*
* Helper function to return the updated array of values. In case value
* != NULL the values are separated by ';' and cleaned of leading
* whitespace, otherwise the value_array will be returned.
*
* Return value: a newly-allocated %NULL-terminated array of strings.
* Use g_strfreev() to free it.
*/
gchar **
get_extracted_values (const gchar *value,
const gchar **value_array)
{
int i;
gchar **str_array;
if (value != NULL)
{
str_array = g_strsplit (value, ";", 0);
for (i = 0; str_array[i] != NULL; i++)
str_array[i] = g_strchug (str_array[i]);
}
else
{
str_array = (gchar **) value_array;
}
return str_array;
}
static void
gen_schema_start (GString *buffer,
@@ -105,10 +137,12 @@ static void
gen_property (GString *buffer,
const XMPSchema *schema,
const XMPProperty *property,
const gchar *value,
const gchar **value_array)
{
gint i;
const gchar *ns_prefix;
gchar **updated_values = NULL;
switch (property->type)
{
@@ -120,7 +154,9 @@ gen_property (GString *buffer,
case XMP_TYPE_TEXT:
case XMP_TYPE_RATIONAL:
gen_element (buffer, 2,
schema->prefix, property->name, value_array[0],
schema->prefix,
property->name,
(value != NULL) ? value : value_array[0],
NULL);
break;
@@ -130,10 +166,13 @@ gen_property (GString *buffer,
case XMP_TYPE_JOB_BAG:
g_string_append_printf (buffer, " <%s:%s>\n <rdf:Bag>\n",
schema->prefix, property->name);
for (i = 0; value_array[i] != NULL; i++)
updated_values = get_extracted_values (value, value_array);
for (i = 0; updated_values[i] != NULL; i++)
{
gen_element (buffer, 4,
"rdf", "li", value_array[i],
"rdf", "li", updated_values[i],
NULL);
}
g_string_append_printf (buffer, " </rdf:Bag>\n </%s:%s>\n",
@@ -146,12 +185,15 @@ gen_property (GString *buffer,
case XMP_TYPE_RATIONAL_SEQ:
g_string_append_printf (buffer, " <%s:%s>\n <rdf:Seq>\n",
schema->prefix, property->name);
for (i = 0; value_array[i] != NULL; i++)
{
gen_element (buffer, 4,
"rdf", "li", value_array[i],
NULL);
}
updated_values = get_extracted_values (value, value_array);
for (i = 0; updated_values[i] != NULL; i++)
{
gen_element (buffer, 4,
"rdf", "li", updated_values[i],
NULL);
}
g_string_append_printf (buffer, " </rdf:Seq>\n </%s:%s>\n",
schema->prefix, property->name);
break;
@@ -159,10 +201,31 @@ gen_property (GString *buffer,
case XMP_TYPE_LANG_ALT:
g_string_append_printf (buffer, " <%s:%s>\n <rdf:Alt>\n",
schema->prefix, property->name);
gen_element (buffer, 4,
"rdf", "li", value_array[0],
"xml:lang", "x-default",
NULL);
if (value_array == NULL && value != NULL)
{
gen_element (buffer, 4,
"rdf", "li", value,
"xml:lang", "x-default",
NULL);
}
else
{
for (i = 0; value_array[i] != NULL; i += 2)
{
if (value_array[i+1] == NULL)
{
g_printerr("Bailing out:%s:%s", schema->prefix, property->name);
break;
}
if (i == 0 && value_array[i+1] != value)
value_array[i+1] = g_strdup (value);
gen_element (buffer, 4,
"rdf", "li", value_array[i + 1],
"xml:lang", value_array[i],
NULL);
}
}
g_string_append_printf (buffer, " </rdf:Alt>\n </%s:%s>\n",
schema->prefix, property->name);
break;
@@ -188,8 +251,9 @@ gen_property (GString *buffer,
case XMP_TYPE_DEVICE_SETTINGS:
case XMP_TYPE_CONTACT_INFO:
case XMP_TYPE_GENERIC_STRUCTURE:
if (value_array[0] && value_array[1]
&& !! strcmp (value_array[1], schema->uri))
if ((value_array != NULL)
&& (value_array[0] && value_array[1]
&& !! strcmp (value_array[1], schema->uri)))
{
g_string_append_printf (buffer,
" <%s:%s rdf:parseType='Resource'\n"
@@ -205,13 +269,21 @@ gen_property (GString *buffer,
schema->prefix, property->name);
ns_prefix = schema->prefix;
}
if (value_array[0] && value_array[1])
if (value_array != NULL && value_array[0] && value_array[1])
{
for (i = 2; value_array[i] != NULL; i += 2)
{
gen_element (buffer, 3,
ns_prefix, value_array[i], value_array[i + 1],
NULL);
}
}
else
{
gen_element (buffer, 3,
ns_prefix, property->name, value,
NULL);
}
g_string_append_printf (buffer, " </%s:%s>\n",
schema->prefix, property->name);
break;
@@ -225,6 +297,9 @@ gen_property (GString *buffer,
g_printerr ("Unknown property type for %s", property->name);
break;
}
g_strfreev (updated_values);
}
/**
@@ -244,6 +319,7 @@ xmp_generate_packet (XMPModel *xmp_model,
GtkTreeIter child;
const XMPSchema *schema;
gpointer saved_ref;
const gchar *saved_value;
g_return_if_fail (xmp_model != NULL);
g_return_if_fail (buffer != NULL);
@@ -268,21 +344,26 @@ xmp_generate_packet (XMPModel *xmp_model,
if (gtk_tree_model_iter_children (model, &child, &iter))
{
saved_ref = NULL;
saved_value = NULL;
do
{
const XMPProperty *property;
const gchar **value_array;
const gchar *value;
gtk_tree_model_get (model, &child,
COL_XMP_TYPE_XREF, &property,
COL_XMP_VALUE, &value,
COL_XMP_VALUE_RAW, &value_array,
-1);
/* do not process structured types multiple times */
if (saved_ref != value_array)
if (saved_ref != value_array
|| (value != NULL && saved_value != value))
{
saved_ref = value_array;
saved_value = value;
g_return_if_fail (property->name != NULL);
gen_property (buffer, schema, property, value_array);
gen_property (buffer, schema, property, value, value_array);
}
}
while (gtk_tree_model_iter_next (model, &child));

View File

@@ -25,6 +25,8 @@
#include <libgimp/gimp.h>
#include <gexiv2/gexiv2.h>
#include "libgimp/stdplugins-intl.h"
#include "xmp-schemas.h"
@@ -45,6 +47,8 @@ static void tree_model_row_changed (GtkTreeModel *model,
static XMPSchema * find_xmp_schema_by_iter (XMPModel *xmp_model,
GtkTreeIter *iter);
gchar** convert_to_xmp_model_raw_value (const gchar *in);
enum
{
PROPERTY_CHANGED,
@@ -135,7 +139,8 @@ xmp_model_finalize (GObject *object)
gtk_tree_model_get (model, &child,
COL_XMP_VALUE_RAW, &value_array,
-1);
if (value_array != last_value_array)
if (value_array != last_value_array
&& value_array != NULL)
{
/* FIXME: this does not free everything */
for (i = 0; value_array[i] != NULL; i++)
@@ -212,6 +217,70 @@ tree_model_row_changed (GtkTreeModel *model,
}
}
/**
* convert_to_xmp_model_raw_value:
* @in: a string which is converted in to a raw value for the #XMPModel
*
* Utility function in order to translate XMP_TYPE_LANG_ALT values to an
* array of strings. Each odd index in the array is the language
* specifier (e.g. x-default, de_DE, etc). Each even array index is the
* value.
*
* A single string is converted to a two element array and the string is
* marked as 'x-default'.
*
* TODO: Possibly this function can be ignored after the full
* integration of gexiv2
*
* Return value: array of strings.
*/
gchar**
convert_to_xmp_model_raw_value (const gchar *in)
{
gchar **splitted = NULL;
gchar **result;
gchar *value = NULL;
GSList *list = NULL;
GSList *list_iter;
int i;
/* extract the language and corresponding value.
* TODO: Fix up values ending with a separator charactor (e.g. ',')
*/
splitted = g_regex_split_simple ("lang=\"([\\w_\\-]+)\"", in, 0, 0);
for (i=0; splitted[i] != NULL; i++)
{
value = g_strstrip (splitted[i]);
if (strcmp (value, ""))
list = g_slist_append (list, g_strdup (value));
}
/* in case we only extracted one element, the language identifier is
* missing. We'll simply append one */
i = g_slist_length (list);
if (i == 1)
{
list = g_slist_prepend (list, "x-default");
i++;
}
/* put it back into an array and return it
*/
result = g_new (gchar*, i + 1);
result[i--] = NULL;
list = g_slist_reverse(list);
for (list_iter = list; list_iter != NULL; list_iter = list_iter->next)
{
result[i--] = list_iter->data;
}
g_slist_free (list);
g_strfreev (splitted);
return g_strdupv(result);
}
static XMPSchema *
find_xmp_schema_by_iter (XMPModel *xmp_model,
GtkTreeIter *child)
@@ -516,12 +585,12 @@ parse_set_property (XMPParseContext *context,
{
XMPModel *xmp_model = user_data;
XMPSchema *schema = ns_user_data;
XMPType xmptype;
int i;
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);
@@ -553,35 +622,12 @@ parse_set_property (XMPParseContext *context,
#ifdef DEBUG_XMP_MODEL
g_print ("\t%s:%s = \"%s\"\n", schema->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);
xmp_model_set_property (xmp_model, XMP_TYPE_TEXT, schema->uri, name, value);
break;
case XMP_PTYPE_RESOURCE:
#ifdef DEBUG_XMP_MODEL
g_print ("\t%s:%s @ = \"%s\"\n", ns_prefix, name,
g_print ("\t%s:%s @ = \"%s\"\n", schema->prefix, name,
value[0]);
#endif
if (property != NULL)
@@ -614,52 +660,16 @@ parse_set_property (XMPParseContext *context,
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);
xmptype = ((type == XMP_PTYPE_ORDERED_LIST)
? XMP_TYPE_TEXT_BAG
: XMP_TYPE_TEXT_SEQ);
xmp_model_set_property (xmp_model, xmptype, schema->uri, name, value);
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,
g_print ("\t%s:%s [size:%d] = \"...\"\n", schema->prefix, name,
*(int *)(value[i]));
g_print ("\n");
#endif
@@ -695,44 +705,16 @@ parse_set_property (XMPParseContext *context,
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,
g_print ("\t%s:%s [lang:%s] = \"%s\"\n", schema->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);
}
xmp_model_set_property (xmp_model, XMP_TYPE_LANG_ALT, schema->uri, name, value);
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,
g_print ("\t%s:%s [%s] = \"%s\"\n", schema->prefix, name,
value[i], value[i + 1]);
#endif
if (property != NULL)
@@ -768,7 +750,7 @@ parse_set_property (XMPParseContext *context,
default:
#ifdef DEBUG_XMP_MODEL
g_print ("\t%s:%s = ?\n", ns_prefix, name);
g_print ("\t%s:%s = ?\n", schema->prefix, name);
#endif
break;
}
@@ -858,19 +840,41 @@ xmp_model_parse_file (XMPModel *xmp_model,
const gchar *filename,
GError **error)
{
gchar *buffer;
gsize buffer_length;
int i = 0;
GExiv2Metadata *metadata = NULL;
gchar **tags = NULL;
gchar *tag = NULL;
gchar *value = NULL;
gchar **val_array = NULL;
g_return_val_if_fail (filename != NULL, FALSE);
if (! g_file_get_contents (filename, &buffer, &buffer_length, error))
metadata = gexiv2_metadata_new ();
if (! gexiv2_metadata_open_path (metadata, filename, error))
return FALSE;
if (! xmp_model_parse_buffer (xmp_model, buffer, buffer_length, TRUE, error))
return FALSE;
tags = gexiv2_metadata_get_xmp_tags (metadata);
g_free (buffer);
for (i = 0; tags[i] != NULL; i++)
{
tag = tags[i];
/* the value is of the form: Xmp.<schema>.<property_name>. Split and
* use the last items of the array to set the value.
*/
value = gexiv2_metadata_get_xmp_tag_string (metadata, tag);
val_array = g_strsplit (tag, ".", 0);
if (! xmp_model_set_scalar_property (xmp_model,
val_array[1],
val_array[2],
value))
g_printerr ("\n Unable to set XMP tag: %s:%s - %s\n",
val_array[1], val_array[2], value);
}
g_free (val_array);
g_free (value);
g_free (tag);
g_free (tags);
gexiv2_metadata_free (metadata);
return TRUE;
}
@@ -954,6 +958,72 @@ xmp_model_get_scalar_property (XMPModel *xmp_model,
return NULL;
}
/**
* xmp_model_get_raw_property_value:
* @xmp_model: pointer to an #XMPModel
* @schema_name: full URI or usual prefix of the schema
* @property_name: name of the raw property value to return
*
* Returns a copy of the currently stored value.
*
* Return value: raw value or %NULL otherwise
**/
const gchar **
xmp_model_get_raw_property_value (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_RAW, &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
@@ -962,6 +1032,9 @@ xmp_model_get_scalar_property (XMPModel *xmp_model,
* @property_value: value to store
*
* Store a new value for the specified XMP property.
* This is a wrapper function for xmp_model_set_property which sets the
* raw values. The @property_value is converted to a raw value and
* passed to xmp_model_set_property.
*
* Return value: %TRUE if the property was set, %FALSE if an error
* occurred (for example, the @schema_name is invalid)
@@ -972,17 +1045,68 @@ xmp_model_set_scalar_property (XMPModel *xmp_model,
const gchar *property_name,
const gchar *property_value)
{
XMPSchema *schema;
XMPProperty *property = NULL;
GtkTreeIter iter;
GtkTreeIter child_iter;
int i;
gchar **value;
gchar **value = NULL;
XMPType type;
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);
type = xmp_model_find_xmptype_by (xmp_model, schema_name, property_name);
/* TODO: Ugh - perhaps this can be done better */
if (type == XMP_TYPE_LANG_ALT)
value = convert_to_xmp_model_raw_value (property_value);
else
{
value = g_new (gchar *, 2);
value[0] = g_strdup (property_value);
value[1] = NULL;
}
return xmp_model_set_property (xmp_model,
type,
schema_name,
property_name,
(const gchar**) value);
}
/**
* xmp_model_set_property:
* @xmp_model: pointer to an #XMPModel
* @schema_name: full URI or usual prefix of the schema
* @type: An #XMPTYPE used in the #XMPProperty
* @property_name: name of the property to store
* @value: value to store
*
* Store a new value for the specified XMP property.
*
* Note: Currently only sets property values for XMP_TYPE_TEXT,
* XMP_TYPE_LANG_ALT, XMP_TYPE_TEXT_SEQ, XMP_TYPE_TEXT_BAG.
*
* Return value: %TRUE if the property was set, %FALSE if an error
* occurred (for example, the @schema_name is invalid)
**/
gboolean
xmp_model_set_property (XMPModel *xmp_model,
XMPType type,
const gchar *schema_name,
const gchar *property_name,
const gchar **value)
{
XMPSchema *schema;
XMPProperty *property = NULL;
gboolean result = FALSE;
GtkTreeIter iter;
GtkTreeIter child_iter;
int i;
gchar *tmp_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 (value != NULL, FALSE);
schema = find_xmp_schema_by_uri (xmp_model, schema_name);
if (! schema)
schema = find_xmp_schema_prefix (xmp_model, schema_name);
@@ -1002,37 +1126,162 @@ xmp_model_set_scalar_property (XMPModel *xmp_model,
}
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;
find_and_remove_property (xmp_model, property, &iter);
xmp_model->custom_properties =
g_slist_prepend (xmp_model->custom_properties, property);
switch (type)
{
case XMP_TYPE_TEXT_BAG:
case XMP_TYPE_TEXT_SEQ:
#ifdef DEBUG_XMP_MODEL
g_print ("\t%s:%s [] =", schema->prefix, property_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 (property_name);
property->type = type;
property->editable = TRUE;
xmp_model->custom_properties =
g_slist_prepend (xmp_model->custom_properties, property);
}
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, property_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);
result = TRUE;
break;
case XMP_TYPE_TEXT:
#ifdef DEBUG_XMP_MODEL
g_print ("\t%s:%s = \"%s\"\n", schema->prefix, property_name, value[0]);
#endif
if (property != NULL)
/* FIXME */;
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);
}
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, 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);
result = TRUE;
break;
case XMP_TYPE_LANG_ALT:
#ifdef DEBUG_XMP_MODEL
for (i = 0; value[i] != NULL; i += 2)
g_print ("\t%s:%s [lang:%s] = \"%s\"\n", schema->prefix, property_name,
value[i], value[i + 1]);
#endif
if (property != NULL)
/* FIXME */;
else
{
property = g_new (XMPProperty, 1);
property->name = g_strdup (property_name);
property->type = XMP_TYPE_LANG_ALT;
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, property_name,
COL_XMP_VALUE, value[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);
result = TRUE;
break;
default:
#ifdef DEBUG_XMP_MODEL
g_print ("\t%s:%s = ?\n", schema->prefix, property_name);
#endif
break;
}
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;
return result;
}
/**
* xmp_model_find_xmptype_by:
* @xmp_model: An #XMPModel
* @schema_name: An #XMPSchema the property belongs to. Can be a schema
* URI or prefix.
* @property_name: The name of the property to find the type for
*
* Finds the corresponding XMPType for the given schema and property
* name.
*
* Return value: XMPType or -1 if the property type can not be found.
**/
XMPType
xmp_model_find_xmptype_by (XMPModel *xmp_model,
const gchar *schema_name,
const gchar *property_name)
{
XMPSchema *schema;
XMPProperty *property = NULL;
int i;
schema = find_xmp_schema_by_uri (xmp_model, schema_name);
if (! schema)
schema = find_xmp_schema_prefix (xmp_model, schema_name);
if (schema)
{
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]);
return property->type;
}
}
return -1;
}
/**

View File

@@ -105,16 +105,29 @@ gboolean xmp_model_parse_file (XMPModel *xmp_model,
GtkTreeModel *xmp_model_get_tree_model (XMPModel *xmp_model);
const gchar **xmp_model_get_raw_property_value (XMPModel *xmp_model,
const gchar *schema_name,
const gchar *property_name);
const gchar *xmp_model_get_scalar_property (XMPModel *xmp_model,
const gchar *schema_name,
const gchar *property_name);
XMPType xmp_model_find_xmptype_by (XMPModel *xmp_model,
const gchar *schema_name,
const gchar *property_name);
gboolean xmp_model_set_property (XMPModel *xmp_model,
XMPType type,
const gchar *schema_name,
const gchar *property_name,
const gchar **value);
gboolean xmp_model_set_scalar_property (XMPModel *xmp_model,
const gchar *schema_name,
const gchar *property_name,
const gchar *property_value);
/* Signals */
void xmp_model_property_changed (XMPModel *xmp_model,
XMPSchema *schema,

View File

@@ -617,10 +617,10 @@ start_element_handler (GMarkupParseContext *markup_context,
{
case STATE_INSIDE_XPACKET:
if (! strcmp (element_name, "x:xmpmeta")
|| ! strcmp (element_name, "x:xapmeta")
|| ! strcmp (element_name, "x:xapmeta")
|| matches_with_prefix (element_name, context->xmp_prefix,
context->xmp_prefix_len, "xmpmeta"))
context->state = STATE_INSIDE_XMPMETA;
context->state = STATE_INSIDE_XMPMETA;
else if (matches_rdf (element_name, context, "RDF"))
{
/* the x:xmpmeta element is missing, but this is allowed */
@@ -628,33 +628,33 @@ start_element_handler (GMarkupParseContext *markup_context,
context->state = STATE_INSIDE_RDF;
}
else
parse_error_element (context, error, "x:xmpmeta",
parse_error_element (context, error, "x:xmpmeta",
FALSE, element_name);
break;
case STATE_INSIDE_XMPMETA:
if (matches_rdf (element_name, context, "RDF"))
context->state = STATE_INSIDE_RDF;
context->state = STATE_INSIDE_RDF;
else
parse_error_element (context, error, "rdf:RDF",
parse_error_element (context, error, "rdf:RDF",
FALSE, element_name);
break;
case STATE_INSIDE_RDF:
if (matches_rdf (element_name, context, "Description"))
{
{
XMLNameSpace *ns;
gboolean about_seen = FALSE;
context->state = STATE_INSIDE_TOPLEVEL_DESC;
for (attr = 0; attribute_names[attr] != NULL; ++attr)
{
context->state = STATE_INSIDE_TOPLEVEL_DESC;
for (attr = 0; attribute_names[attr] != NULL; ++attr)
{
if (matches_rdf (attribute_names[attr], context, "about")
|| ! strcmp (attribute_names[attr], "about") /* old style */)
about_seen = TRUE;
else if (g_str_has_prefix (attribute_names[attr], "xmlns"))
else if (g_str_has_prefix (attribute_names[attr], "xmlns"))
; /* the namespace has already been pushed on the stack */
else
else
{
ns = new_property_in_ns (context, attribute_names[attr]);
if (ns != NULL)
@@ -675,9 +675,9 @@ start_element_handler (GMarkupParseContext *markup_context,
parse_error (context, error, XMP_ERROR_MISSING_ABOUT,
_("Required attribute rdf:about missing in <%s>"),
element_name);
}
}
else
parse_error_element (context, error, "rdf:Description",
parse_error_element (context, error, "rdf:Description",
FALSE, element_name);
break;
@@ -714,37 +714,37 @@ start_element_handler (GMarkupParseContext *markup_context,
case STATE_INSIDE_PROPERTY:
if (matches_rdf (element_name, context, "Description"))
{
{
context->saved_state = context->state;
context->state = STATE_INSIDE_QDESC;
for (attr = 0; attribute_names[attr] != NULL; ++attr)
{
if (g_str_has_prefix (attribute_names[attr], "xmlns"))
{
/* this desc. is a structure, not a property qualifier */
context->state = STATE_INSIDE_STRUCT_ADD_NS;
}
else
context->state = STATE_INSIDE_QDESC;
for (attr = 0; attribute_names[attr] != NULL; ++attr)
{
if (g_str_has_prefix (attribute_names[attr], "xmlns"))
{
/* this desc. is a structure, not a property qualifier */
context->state = STATE_INSIDE_STRUCT_ADD_NS;
}
else
unknown_attribute (context, error, element_name,
attribute_names[attr],
attribute_values[attr]);
}
}
}
}
else if (matches_rdf (element_name, context, "Alt"))
context->state = STATE_INSIDE_ALT;
context->state = STATE_INSIDE_ALT;
else if (matches_rdf (element_name, context, "Bag"))
context->state = STATE_INSIDE_BAG;
context->state = STATE_INSIDE_BAG;
else if (matches_rdf (element_name, context, "Seq"))
context->state = STATE_INSIDE_SEQ;
context->state = STATE_INSIDE_SEQ;
else
unknown_element (context, error, element_name);
break;
case STATE_INSIDE_QDESC:
if (matches_rdf (element_name, context, "value"))
context->state = STATE_INSIDE_QDESC_VALUE;
context->state = STATE_INSIDE_QDESC_VALUE;
else
context->state = STATE_INSIDE_QDESC_QUAL;
context->state = STATE_INSIDE_QDESC_QUAL;
break;
case STATE_INSIDE_STRUCT_ADD_NS:
@@ -788,22 +788,22 @@ start_element_handler (GMarkupParseContext *markup_context,
case STATE_INSIDE_ALT:
if (matches_rdf (element_name, context, "li"))
{
context->state = STATE_INSIDE_ALT_LI;
for (attr = 0; attribute_names[attr] != NULL; ++attr)
{
if (matches_rdf (attribute_names[attr], context, "parseType")
{
context->state = STATE_INSIDE_ALT_LI;
for (attr = 0; attribute_names[attr] != NULL; ++attr)
{
if (matches_rdf (attribute_names[attr], context, "parseType")
&& ! strcmp (attribute_values[attr], "Resource"))
context->state = STATE_INSIDE_ALT_LI_RSC;
else if (! strcmp (attribute_names[attr], "xml:lang"))
else if (! strcmp (attribute_names[attr], "xml:lang"))
add_property_value (context, XMP_PTYPE_ALT_LANG,
g_strdup (attribute_values[attr]),
NULL);
else
else
unknown_attribute (context, error, element_name,
attribute_names[attr],
attribute_values[attr]);
}
}
/* rdf:Alt is not an ordered list, but some broken XMP files use */
/* it instead of rdf:Seq. Workaround: if we did not find some */
/* attributes for the valid cases ALT_LANG or ALT_LI_RSC, then */
@@ -812,55 +812,55 @@ start_element_handler (GMarkupParseContext *markup_context,
&& (context->state != STATE_INSIDE_ALT_LI_RSC))
add_property_value (context, XMP_PTYPE_ORDERED_LIST,
NULL, NULL);
}
}
else
parse_error_element (context, error, "rdf:li",
parse_error_element (context, error, "rdf:li",
FALSE, element_name);
break;
case STATE_INSIDE_BAG:
if (matches_rdf (element_name, context, "li"))
{
{
context->state = STATE_INSIDE_BAG_LI;
for (attr = 0; attribute_names[attr] != NULL; ++attr)
{
if (matches_rdf (attribute_names[attr], context, "parseType")
for (attr = 0; attribute_names[attr] != NULL; ++attr)
{
if (matches_rdf (attribute_names[attr], context, "parseType")
&& ! strcmp (attribute_values[attr], "Resource"))
context->state = STATE_INSIDE_BAG_LI_RSC;
else
else
unknown_attribute (context, error, element_name,
attribute_names[attr],
attribute_values[attr]);
}
}
if (context->state != STATE_INSIDE_BAG_LI_RSC)
add_property_value (context, XMP_PTYPE_UNORDERED_LIST,
NULL, NULL);
}
}
else
parse_error_element (context, error, "rdf:li",
parse_error_element (context, error, "rdf:li",
FALSE, element_name);
break;
case STATE_INSIDE_SEQ:
if (matches_rdf (element_name, context, "li"))
{
{
context->state = STATE_INSIDE_SEQ_LI;
for (attr = 0; attribute_names[attr] != NULL; ++attr)
{
if (matches_rdf (attribute_names[attr], context, "parseType")
for (attr = 0; attribute_names[attr] != NULL; ++attr)
{
if (matches_rdf (attribute_names[attr], context, "parseType")
&& ! strcmp (attribute_values[attr], "Resource"))
context->state = STATE_INSIDE_SEQ_LI_RSC;
else
else
unknown_attribute (context, error, element_name,
attribute_names[attr],
attribute_values[attr]);
}
}
if (context->state != STATE_INSIDE_SEQ_LI_RSC)
add_property_value (context, XMP_PTYPE_ORDERED_LIST,
NULL, NULL);
}
}
else
parse_error_element (context, error, "rdf:li",
parse_error_element (context, error, "rdf:li",
FALSE, element_name);
break;
@@ -872,7 +872,7 @@ start_element_handler (GMarkupParseContext *markup_context,
context->state = STATE_INSIDE_QDESC;
}
else
parse_error_element (context, error, "rdf:Description",
parse_error_element (context, error, "rdf:Description",
TRUE, element_name);
break;
@@ -1028,7 +1028,7 @@ end_element_handler (GMarkupParseContext *markup_context,
default:
parse_error (context, error, XMP_ERROR_PARSE,
_("End of element <%s> not expected in this context"),
_("End of element <%s> not expected in this context"),
element_name);
break;
}
@@ -1123,8 +1123,8 @@ text_handler (GMarkupParseContext *markup_context,
case STATE_INSIDE_QDESC_QUAL:
#ifdef DEBUG_XMP_PARSER
g_print ("ignoring qualifier for part of \"%s\"[]: \"%.*s\"\n",
context->property,
(int)text_len, text);
context->property,
(int)text_len, text);
#endif
/* FIXME: notify the user? add a way to collect qualifiers? */
break;
@@ -1135,8 +1135,8 @@ text_handler (GMarkupParseContext *markup_context,
default:
if (! is_whitespace_string (text))
parse_error (context, error, XMP_ERROR_INVALID_CONTENT,
_("The current element (<%s>) cannot contain text"),
parse_error (context, error, XMP_ERROR_INVALID_CONTENT,
_("The current element (<%s>) cannot contain text"),
g_markup_parse_context_get_element (markup_context));
break;
}
@@ -1158,9 +1158,9 @@ passthrough_handler (GMarkupParseContext *markup_context,
case STATE_AFTER_XPACKET:
if ((text_len >= 21)
&& (! strncmp (passthrough_text, "<?xpacket begin=", 16)))
context->state = STATE_INSIDE_XPACKET;
context->state = STATE_INSIDE_XPACKET;
else
parse_error (context, error, XMP_ERROR_PARSE,
parse_error (context, error, XMP_ERROR_PARSE,
_("XMP packets must start with <?xpacket begin=...?>"));
break;
@@ -1172,9 +1172,9 @@ passthrough_handler (GMarkupParseContext *markup_context,
case STATE_AFTER_XMPMETA:
if ((text_len >= 19)
&& (! strncmp (passthrough_text, "<?xpacket end=", 14)))
context->state = STATE_AFTER_XPACKET;
context->state = STATE_AFTER_XPACKET;
else
parse_error (context, error, XMP_ERROR_PARSE,
parse_error (context, error, XMP_ERROR_PARSE,
_("XMP packets must end with <?xpacket end=...?>"));
break;