1170 lines
38 KiB
C
1170 lines
38 KiB
C
/* LIBPIKA - The PIKA Library
|
|
* Copyright (C) 1995-2003 Peter Mattis and Spencer Kimball
|
|
*
|
|
* pikaprocedureconfig.c
|
|
* Copyright (C) 2019 Michael Natterer <mitch@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
|
|
* <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "pika.h"
|
|
#include "pikaimagemetadata.h"
|
|
|
|
#include "pikaprocedureconfig-private.h"
|
|
|
|
|
|
/**
|
|
* PikaProcedureConfig:
|
|
*
|
|
* The base class for [class@Procedure] specific config objects and the main
|
|
* interface to manage aspects of [class@Procedure]'s arguments such as
|
|
* persistency of the last used arguments across PIKA sessions.
|
|
*
|
|
* A procedure config is created by a [class@Procedure] using
|
|
* [method@Procedure.create_config] and its properties match the
|
|
* procedure's arguments and auxiliary arguments in number, order and
|
|
* type.
|
|
*
|
|
* It implements the [struct@Config] interface and therefore has all its
|
|
* serialization and deserialization features.
|
|
*
|
|
* Since: 3.0
|
|
**/
|
|
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_PROCEDURE,
|
|
N_PROPS
|
|
};
|
|
|
|
|
|
struct _PikaProcedureConfigPrivate
|
|
{
|
|
PikaProcedure *procedure;
|
|
|
|
PikaImage *image;
|
|
PikaRunMode run_mode;
|
|
|
|
PikaMetadata *metadata;
|
|
gchar *mime_type;
|
|
PikaMetadataSaveFlags metadata_flags;
|
|
gboolean metadata_saved;
|
|
};
|
|
|
|
|
|
static void pika_procedure_config_constructed (GObject *object);
|
|
static void pika_procedure_config_dispose (GObject *object);
|
|
static void pika_procedure_config_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec);
|
|
static void pika_procedure_config_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec);
|
|
|
|
|
|
static void pika_procedure_config_set_values (PikaProcedureConfig *config,
|
|
const PikaValueArray *values);
|
|
|
|
static gboolean pika_procedure_config_load_last (PikaProcedureConfig *config,
|
|
GError **error);
|
|
static gboolean pika_procedure_config_save_last (PikaProcedureConfig *config,
|
|
GError **error);
|
|
|
|
static gboolean pika_procedure_config_load_parasite (PikaProcedureConfig *config,
|
|
PikaImage *image,
|
|
GError **error);
|
|
static gboolean pika_procedure_config_save_parasite (PikaProcedureConfig *config,
|
|
PikaImage *image,
|
|
GError **error);
|
|
|
|
static GFile * pika_procedure_config_get_file (PikaProcedureConfig *config,
|
|
const gchar *extension);
|
|
static gchar * pika_procedure_config_parasite_name (PikaProcedureConfig *config,
|
|
const gchar *suffix);
|
|
|
|
|
|
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (PikaProcedureConfig, pika_procedure_config,
|
|
G_TYPE_OBJECT)
|
|
|
|
#define parent_class pika_procedure_config_parent_class
|
|
|
|
static GParamSpec *props[N_PROPS] = { NULL, };
|
|
|
|
static const struct
|
|
{
|
|
const gchar *name;
|
|
PikaMetadataSaveFlags flag;
|
|
}
|
|
metadata_properties[] =
|
|
{
|
|
{ "save-exif", PIKA_METADATA_SAVE_EXIF },
|
|
{ "save-xmp", PIKA_METADATA_SAVE_XMP },
|
|
{ "save-iptc", PIKA_METADATA_SAVE_IPTC },
|
|
{ "save-thumbnail", PIKA_METADATA_SAVE_THUMBNAIL },
|
|
{ "save-color-profile", PIKA_METADATA_SAVE_COLOR_PROFILE },
|
|
{ "save-comment", PIKA_METADATA_SAVE_COMMENT }
|
|
};
|
|
|
|
|
|
static void
|
|
pika_procedure_config_class_init (PikaProcedureConfigClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->constructed = pika_procedure_config_constructed;
|
|
object_class->dispose = pika_procedure_config_dispose;
|
|
object_class->set_property = pika_procedure_config_set_property;
|
|
object_class->get_property = pika_procedure_config_get_property;
|
|
|
|
props[PROP_PROCEDURE] =
|
|
g_param_spec_object ("procedure",
|
|
"Procedure",
|
|
"The procedure this config object is used for",
|
|
PIKA_TYPE_PROCEDURE,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT_ONLY);
|
|
|
|
g_object_class_install_properties (object_class, N_PROPS, props);
|
|
}
|
|
|
|
static void
|
|
pika_procedure_config_init (PikaProcedureConfig *config)
|
|
{
|
|
config->priv = pika_procedure_config_get_instance_private (config);
|
|
|
|
config->priv->run_mode = -1;
|
|
}
|
|
|
|
static void
|
|
pika_procedure_config_constructed (GObject *object)
|
|
{
|
|
PikaProcedureConfig *config = PIKA_PROCEDURE_CONFIG (object);
|
|
|
|
G_OBJECT_CLASS (parent_class)->constructed (object);
|
|
|
|
g_assert (PIKA_IS_PROCEDURE (config->priv->procedure));
|
|
}
|
|
|
|
static void
|
|
pika_procedure_config_dispose (GObject *object)
|
|
{
|
|
PikaProcedureConfig *config = PIKA_PROCEDURE_CONFIG (object);
|
|
|
|
g_clear_object (&config->priv->procedure);
|
|
g_clear_object (&config->priv->metadata);
|
|
g_clear_pointer (&config->priv->mime_type, g_free);
|
|
|
|
G_OBJECT_CLASS (parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
pika_procedure_config_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
PikaProcedureConfig *config = PIKA_PROCEDURE_CONFIG (object);
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_PROCEDURE:
|
|
config->priv->procedure = g_value_dup_object (value);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
pika_procedure_config_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
PikaProcedureConfig *config = PIKA_PROCEDURE_CONFIG (object);
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_PROCEDURE:
|
|
g_value_set_object (value, config->priv->procedure);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/* public functions */
|
|
|
|
/**
|
|
* pika_procedure_config_get_procedure:
|
|
* @config: a procedure config
|
|
*
|
|
* This function returns the [class@Procedure] which created @config, see
|
|
* [method@Procedure.create_config].
|
|
*
|
|
* Returns: (transfer none): The procedure which created this config.
|
|
*
|
|
* Since: 3.0
|
|
**/
|
|
PikaProcedure *
|
|
pika_procedure_config_get_procedure (PikaProcedureConfig *config)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_PROCEDURE_CONFIG (config), NULL);
|
|
|
|
return config->priv->procedure;
|
|
}
|
|
|
|
static void
|
|
pika_procedure_config_get_parasite (PikaProcedureConfig *config,
|
|
GParamSpec *pspec)
|
|
{
|
|
PikaParasite *parasite;
|
|
gchar *value = NULL;
|
|
|
|
/* for now we only support strings */
|
|
if (! config->priv->image ||
|
|
! G_IS_PARAM_SPEC_STRING (pspec))
|
|
return;
|
|
|
|
parasite = pika_image_get_parasite (config->priv->image, pspec->name);
|
|
|
|
if (parasite)
|
|
{
|
|
guint32 parasite_size;
|
|
|
|
value = (gchar *) pika_parasite_get_data (parasite, ¶site_size);
|
|
value = g_strndup (value, parasite_size);
|
|
pika_parasite_free (parasite);
|
|
|
|
if (value && ! strlen (value))
|
|
g_clear_pointer (&value, g_free);
|
|
}
|
|
|
|
if (! value)
|
|
{
|
|
/* special case "pika-comment" here, yes this is bad hack */
|
|
if (! strcmp (pspec->name, "pika-comment"))
|
|
{
|
|
value = pika_get_default_comment ();
|
|
}
|
|
}
|
|
|
|
if (value && strlen (value))
|
|
g_object_set (config,
|
|
pspec->name, value,
|
|
NULL);
|
|
|
|
g_free (value);
|
|
}
|
|
|
|
static void
|
|
pika_procedure_config_set_parasite (PikaProcedureConfig *config,
|
|
GParamSpec *pspec)
|
|
{
|
|
PikaParasite *parasite;
|
|
gchar *value;
|
|
|
|
/* for now we only support strings */
|
|
if (! config->priv->image ||
|
|
! G_IS_PARAM_SPEC_STRING (pspec))
|
|
return;
|
|
|
|
g_object_get (config,
|
|
pspec->name, &value,
|
|
NULL);
|
|
|
|
parasite = pika_image_get_parasite (config->priv->image, pspec->name);
|
|
|
|
if (parasite)
|
|
{
|
|
/* it there is a parasite, always override it if its value was
|
|
* changed
|
|
*/
|
|
gchar *image_value;
|
|
guint32 parasite_size;
|
|
|
|
image_value = (gchar *) pika_parasite_get_data (parasite, ¶site_size);
|
|
image_value = g_strndup (image_value, parasite_size);
|
|
pika_parasite_free (parasite);
|
|
|
|
if (g_strcmp0 (value, image_value))
|
|
{
|
|
if (value && strlen (value))
|
|
{
|
|
parasite = pika_parasite_new (pspec->name,
|
|
PIKA_PARASITE_PERSISTENT,
|
|
strlen (value) + 1,
|
|
value);
|
|
pika_image_attach_parasite (config->priv->image,
|
|
parasite);
|
|
pika_parasite_free (parasite);
|
|
}
|
|
else
|
|
{
|
|
pika_image_detach_parasite (config->priv->image,
|
|
pspec->name);
|
|
}
|
|
}
|
|
|
|
g_free (image_value);
|
|
}
|
|
else
|
|
{
|
|
/* otherwise, set the parasite if the value was changed from
|
|
* the default value
|
|
*/
|
|
gchar *default_value = NULL;
|
|
|
|
/* special case "pika-comment" here, yes this is bad hack */
|
|
if (! strcmp (pspec->name, "pika-comment"))
|
|
{
|
|
default_value = pika_get_default_comment ();
|
|
}
|
|
|
|
if (g_strcmp0 (value, default_value) &&
|
|
value && strlen (value))
|
|
{
|
|
parasite = pika_parasite_new (pspec->name,
|
|
PIKA_PARASITE_PERSISTENT,
|
|
strlen (value) + 1,
|
|
value);
|
|
pika_image_attach_parasite (config->priv->image,
|
|
parasite);
|
|
pika_parasite_free (parasite);
|
|
}
|
|
|
|
g_free (default_value);
|
|
}
|
|
|
|
g_free (value);
|
|
}
|
|
|
|
/**
|
|
* pika_procedure_config_save_metadata:
|
|
* @config: a #PikaProcedureConfig
|
|
* @exported_image: the image that was actually exported
|
|
* @file: the file @exported_image was written to
|
|
*
|
|
* Note: There is normally no need to call this function because it's
|
|
* already called by [class@SaveProcedure] at the end of the `run()` callback.
|
|
*
|
|
* Only use this function if the [class@Metadata] passed as argument of a
|
|
* [class@SaveProcedure]'s run() method needs to be written at a specific
|
|
* point of the export, other than its end.
|
|
*
|
|
* This function syncs back @config's export properties to the
|
|
* metadata's [flags@MetadataSaveFlags] and writes the metadata to @file
|
|
* using [method@Image.metadata_save_finish].
|
|
*
|
|
* The metadata is only ever written once. If this function has been
|
|
* called explicitly, it will do nothing when called a second time at the end of
|
|
* the `run()` callback.
|
|
*
|
|
* Since: 3.0
|
|
**/
|
|
void
|
|
pika_procedure_config_save_metadata (PikaProcedureConfig *config,
|
|
PikaImage *exported_image,
|
|
GFile *file)
|
|
{
|
|
g_return_if_fail (PIKA_IS_PROCEDURE_CONFIG (config));
|
|
g_return_if_fail (PIKA_IS_IMAGE (exported_image));
|
|
g_return_if_fail (G_IS_FILE (file));
|
|
|
|
if (config->priv->metadata && ! config->priv->metadata_saved)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_GET_CLASS (config);
|
|
GError *error = NULL;
|
|
gint i;
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (metadata_properties); i++)
|
|
{
|
|
const gchar *prop_name = metadata_properties[i].name;
|
|
PikaMetadataSaveFlags prop_flag = metadata_properties[i].flag;
|
|
GParamSpec *pspec;
|
|
gboolean value;
|
|
|
|
pspec = g_object_class_find_property (object_class, prop_name);
|
|
if (pspec)
|
|
{
|
|
g_object_get (config,
|
|
prop_name, &value,
|
|
NULL);
|
|
|
|
if (value)
|
|
config->priv->metadata_flags |= prop_flag;
|
|
else
|
|
config->priv->metadata_flags &= ~prop_flag;
|
|
}
|
|
else
|
|
{
|
|
config->priv->metadata_flags &= ~prop_flag;
|
|
}
|
|
}
|
|
|
|
if (! pika_image_metadata_save_finish (exported_image,
|
|
config->priv->mime_type,
|
|
config->priv->metadata,
|
|
config->priv->metadata_flags,
|
|
file, &error))
|
|
{
|
|
if (error)
|
|
{
|
|
/* Even though a failure to write metadata is not enough
|
|
reason to say we failed to save the image, we should
|
|
still notify the user about the problem. */
|
|
g_message ("%s: saving metadata failed: %s",
|
|
G_STRFUNC, error->message);
|
|
g_error_free (error);
|
|
}
|
|
}
|
|
|
|
config->priv->metadata_saved = TRUE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* pika_procedure_config_get_choice_id:
|
|
* @config: a #PikaProcedureConfig
|
|
* @property_name: the name of a [struct@ParamSpecChoice] property.
|
|
*
|
|
* A utility function which will get the current string value of a
|
|
* [struct@ParamSpecChoice] property in @config and convert it to the integer ID
|
|
* mapped to this value.
|
|
* This makes it easy to work with an Enum type locally, within a plug-in code.
|
|
*
|
|
* Since: 3.0
|
|
**/
|
|
gint
|
|
pika_procedure_config_get_choice_id (PikaProcedureConfig *config,
|
|
const gchar *property_name)
|
|
{
|
|
GParamSpec *param_spec;
|
|
PikaParamSpecChoice *cspec;
|
|
gchar *value = NULL;
|
|
gint id;
|
|
|
|
param_spec = g_object_class_find_property (G_OBJECT_GET_CLASS (config),
|
|
property_name);
|
|
|
|
if (! param_spec)
|
|
{
|
|
g_warning ("%s: %s has no property named '%s'",
|
|
G_STRFUNC,
|
|
g_type_name (G_TYPE_FROM_INSTANCE (config)),
|
|
property_name);
|
|
return 0;
|
|
}
|
|
|
|
if (! g_type_is_a (G_TYPE_FROM_INSTANCE (param_spec), PIKA_TYPE_PARAM_CHOICE))
|
|
{
|
|
g_warning ("%s: property '%s' of %s is not a PikaParamSpecChoice.",
|
|
G_STRFUNC,
|
|
param_spec->name,
|
|
g_type_name (param_spec->owner_type));
|
|
return 0;
|
|
}
|
|
|
|
cspec = PIKA_PARAM_SPEC_CHOICE (param_spec);
|
|
g_object_get (config,
|
|
property_name, &value,
|
|
NULL);
|
|
id = pika_choice_get_id (cspec->choice, value);
|
|
|
|
g_free (value);
|
|
|
|
return id;
|
|
}
|
|
|
|
|
|
/* Functions only used by PikaProcedure classes */
|
|
|
|
/**
|
|
* _pika_procedure_config_get_values:
|
|
* @config: a #PikaProcedureConfig
|
|
* @values: a #PikaValueArray
|
|
*
|
|
* Gets the values from @config's properties and stores them in
|
|
* @values.
|
|
*
|
|
* See [method@ProcedureConfig.set_values].
|
|
*
|
|
* Since: 3.0
|
|
**/
|
|
void
|
|
_pika_procedure_config_get_values (PikaProcedureConfig *config,
|
|
PikaValueArray *values)
|
|
{
|
|
GParamSpec **pspecs;
|
|
guint n_pspecs;
|
|
gint n_aux_args;
|
|
gint n_values;
|
|
gint i;
|
|
|
|
g_return_if_fail (PIKA_IS_PROCEDURE_CONFIG (config));
|
|
g_return_if_fail (values != NULL);
|
|
|
|
pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (config),
|
|
&n_pspecs);
|
|
pika_procedure_get_aux_arguments (config->priv->procedure, &n_aux_args);
|
|
n_values = pika_value_array_length (values);
|
|
|
|
/* The config will have 1 additional property: "procedure". */
|
|
g_return_if_fail (n_pspecs == n_values + n_aux_args + 1);
|
|
|
|
for (i = 1; i < n_pspecs; i++)
|
|
{
|
|
GParamSpec *pspec = pspecs[i];
|
|
GValue *value = pika_value_array_index (values, i - 1);
|
|
|
|
g_object_get_property (G_OBJECT (config), pspec->name, value);
|
|
}
|
|
|
|
g_free (pspecs);
|
|
}
|
|
|
|
/**
|
|
* _pika_procedure_config_begin_run:
|
|
* @config: a #PikaProcedureConfig
|
|
* @image: (nullable): a #PikaImage or %NULL
|
|
* @run_mode: the #PikaRunMode passed to a [class@Procedure]'s run()
|
|
* @args: the #PikaValueArray passed to a [class@Procedure]'s run()
|
|
*
|
|
* Populates @config with values for a [class@Procedure]'s run(),
|
|
* depending on @run_mode.
|
|
*
|
|
* If @run_mode is %PIKA_RUN_INTERACTIVE or %PIKA_RUN_WITH_LAST_VALS,
|
|
* the saved values from the procedure's last run() are loaded and set
|
|
* on @config. If @image is not %NULL, the last used values for this
|
|
* image are tried first, and if no image-specific values are found
|
|
* the globally saved last used values are used. If no saved last used
|
|
* values are found, the procedure's default argument values are used.
|
|
*
|
|
* If @run_mode is %PIKA_RUN_NONINTERACTIVE, the contents of @args are
|
|
* set on @config using [method@ProcedureConfig.set_values].
|
|
*
|
|
* After setting @config's properties like described above, arguments
|
|
* and auxiliary arguments are automatically synced from image
|
|
* parasites of the same name if they were set to
|
|
* %PIKA_ARGUMENT_SYNC_PARASITE with [class@Procedure.set_argument_sync]:
|
|
*
|
|
* String properties are set to the value of the image parasite if
|
|
* @run_mode is %PIKA_RUN_INTERACTIVE or %PIKA_RUN_WITH_LAST_VALS, or
|
|
* if the corresponding argument is an auxiliary argument. As a
|
|
* special case, a property named "pika-comment" will default to
|
|
* [func@get_default_comment] if there is no "pika-comment" parasite.
|
|
*
|
|
* After calling this function, the @args passed to run() should be
|
|
* left alone and @config be treated as the procedure's arguments.
|
|
*
|
|
* It is possible to get @config's resulting values back into @args by
|
|
* calling [method@ProcedureConfig.get_values], as long as modified
|
|
* @args are written back to @config using [method@ProcedureConfig.set_values]
|
|
* before the call to [method@ProcedureConfig.end_run].
|
|
*
|
|
* This function should be used at the beginning of a procedure's
|
|
* run() and be paired with a call to [method@ProcedureConfig.end_run]
|
|
* at the end of run().
|
|
*
|
|
* Since: 3.0
|
|
**/
|
|
void
|
|
_pika_procedure_config_begin_run (PikaProcedureConfig *config,
|
|
PikaImage *image,
|
|
PikaRunMode run_mode,
|
|
const PikaValueArray *args)
|
|
{
|
|
GParamSpec *run_mode_pspec;
|
|
GParamSpec **pspecs;
|
|
guint n_pspecs;
|
|
gint i;
|
|
gboolean loaded = FALSE;
|
|
GError *error = NULL;
|
|
|
|
g_return_if_fail (PIKA_IS_PROCEDURE_CONFIG (config));
|
|
g_return_if_fail (image == NULL || PIKA_IS_IMAGE (image));
|
|
g_return_if_fail (args != NULL);
|
|
|
|
config->priv->image = image;
|
|
config->priv->run_mode = run_mode;
|
|
|
|
pika_procedure_config_set_values (config, args);
|
|
|
|
switch (run_mode)
|
|
{
|
|
case PIKA_RUN_INTERACTIVE :
|
|
case PIKA_RUN_WITH_LAST_VALS:
|
|
if (image)
|
|
{
|
|
loaded = pika_procedure_config_load_parasite (config, image,
|
|
&error);
|
|
if (! loaded && error)
|
|
{
|
|
g_printerr ("Loading last used values from parasite failed: %s\n",
|
|
error->message);
|
|
g_clear_error (&error);
|
|
}
|
|
}
|
|
|
|
if (! loaded &&
|
|
! pika_procedure_config_load_last (config, &error) && error)
|
|
{
|
|
g_printerr ("Loading last used values from disk failed: %s\n",
|
|
error->message);
|
|
g_clear_error (&error);
|
|
}
|
|
break;
|
|
|
|
case PIKA_RUN_NONINTERACTIVE:
|
|
break;
|
|
}
|
|
|
|
pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (config),
|
|
&n_pspecs);
|
|
|
|
for (i = 0; i < n_pspecs; i++)
|
|
{
|
|
GParamSpec *pspec = pspecs[i];
|
|
|
|
/* skip our own properties */
|
|
if (pspec->owner_type == PIKA_TYPE_PROCEDURE_CONFIG)
|
|
continue;
|
|
|
|
switch (pika_procedure_get_argument_sync (config->priv->procedure,
|
|
pspec->name))
|
|
{
|
|
case PIKA_ARGUMENT_SYNC_PARASITE:
|
|
/* we sync the property from the image parasite if it is an
|
|
* aux argument, or if we run interactively, because the
|
|
* parasite should be global to the image and not depend on
|
|
* whatever parasite another image had when last using this
|
|
* procedure
|
|
*/
|
|
if (pika_procedure_find_aux_argument (config->priv->procedure,
|
|
pspec->name) ||
|
|
(run_mode != PIKA_RUN_NONINTERACTIVE))
|
|
{
|
|
pika_procedure_config_get_parasite (config, pspec);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Always set the run-mode in the end, which should be influenced neither by
|
|
* the default property value, nor last run's value.
|
|
*/
|
|
run_mode_pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (config), "run-mode");
|
|
if (run_mode_pspec != NULL && G_IS_PARAM_SPEC_ENUM (run_mode_pspec))
|
|
g_object_set (config, "run-mode", run_mode, NULL);
|
|
|
|
g_free (pspecs);
|
|
}
|
|
|
|
/**
|
|
* _pika_procedure_config_end_run:
|
|
* @config: a #PikaProcedureConfig
|
|
* @status: the return status of the [class@Procedure]'s run()
|
|
*
|
|
* This function is the counterpart of
|
|
* [method@ProcedureConfig.begin_run] and must always be called in
|
|
* pairs in a procedure's run(), before returning return values.
|
|
*
|
|
* If the @run_mode passed to [method@ProcedureConfig.end_run] was
|
|
* %PIKA_RUN_INTERACTIVE, @config is saved as last used values to be
|
|
* used when the procedure runs again. Additionally, if the #PikaImage
|
|
* passed to [method@ProcedureConfig.begin_run] was not %NULL, @config is
|
|
* attached to @image as last used values for this image using a
|
|
* #PikaParasite and [method@Image.attach_parasite].
|
|
*
|
|
* If @run_mode was not %PIKA_RUN_NONINTERACTIVE, this function also
|
|
* conveniently calls [func@displays_flush], which is what most
|
|
* procedures want and doesn't do any harm if called redundantly.
|
|
*
|
|
* After a %PIKA_RUN_INTERACTIVE run, %PIKA_ARGUMENT_SYNC_PARASITE
|
|
* values that have been changed are written back to their
|
|
* corresponding image parasite.
|
|
*
|
|
* See [method@ProcedureConfig.begin_run].
|
|
*
|
|
* Since: 3.0
|
|
**/
|
|
void
|
|
_pika_procedure_config_end_run (PikaProcedureConfig *config,
|
|
PikaPDBStatusType status)
|
|
{
|
|
g_return_if_fail (PIKA_IS_PROCEDURE_CONFIG (config));
|
|
|
|
if (config->priv->run_mode != PIKA_RUN_NONINTERACTIVE)
|
|
pika_displays_flush ();
|
|
|
|
if (status == PIKA_PDB_SUCCESS &&
|
|
config->priv->run_mode == PIKA_RUN_INTERACTIVE)
|
|
{
|
|
GParamSpec **pspecs;
|
|
guint n_pspecs;
|
|
gint i;
|
|
GError *error = NULL;
|
|
|
|
if (config->priv->image)
|
|
pika_procedure_config_save_parasite (config, config->priv->image,
|
|
NULL);
|
|
|
|
if (! pika_procedure_config_save_last (config, &error))
|
|
{
|
|
g_printerr ("Saving last used values to disk failed: %s\n",
|
|
error->message);
|
|
g_clear_error (&error);
|
|
}
|
|
|
|
pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (config),
|
|
&n_pspecs);
|
|
|
|
for (i = 0; i < n_pspecs; i++)
|
|
{
|
|
GParamSpec *pspec = pspecs[i];
|
|
|
|
/* skip our own properties */
|
|
if (pspec->owner_type == PIKA_TYPE_PROCEDURE_CONFIG)
|
|
continue;
|
|
|
|
switch (pika_procedure_get_argument_sync (config->priv->procedure,
|
|
pspec->name))
|
|
{
|
|
case PIKA_ARGUMENT_SYNC_PARASITE:
|
|
pika_procedure_config_set_parasite (config, pspec);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
g_free (pspecs);
|
|
}
|
|
|
|
config->priv->image = NULL;
|
|
config->priv->run_mode = -1;
|
|
}
|
|
|
|
/**
|
|
* _pika_procedure_config_begin_export:
|
|
* @config: a #PikaProcedureConfig
|
|
* @original_image: the image passed to run()
|
|
* @run_mode: the #PikaRunMode passed to a [class@Procedure]'s run()
|
|
* @args: the value array passed to a [class@Procedure]'s run()
|
|
* @mime_type: (nullable): exported file format's mime type, or %NULL.
|
|
*
|
|
* This is a variant of [method@ProcedureConfig.begin_run] to be used
|
|
* by file export procedures using [class@SaveProcedure]. It must be
|
|
* paired with a call to [method@ProcedureConfig.end_export] at the end
|
|
* of run().
|
|
*
|
|
* It does everything [method@ProcedureConfig.begin_run] does but
|
|
* provides additional features to automate file export:
|
|
*
|
|
* If @mime_type is non-%NULL, exporting metadata is handled
|
|
* automatically, by calling [method@Image.metadata_save_prepare] and
|
|
* syncing its returned [flags@MetadataSaveFlags] with @config's
|
|
* properties. (The corresponding [method@Image.metadata_save_finish]
|
|
* will be called by [method@ProcedureConfig.end_export]).
|
|
*
|
|
* The following boolean arguments of the used [class@SaveProcedure] are
|
|
* synced. The procedure can but must not provide these arguments.
|
|
*
|
|
* - "save-exif" for %PIKA_METADATA_SAVE_EXIF.
|
|
* - "save-xmp" for %PIKA_METADATA_SAVE_XMP.
|
|
* - "save-iptc" for %PIKA_METADATA_SAVE_IPTC.
|
|
* - "save-thumbnail" for %PIKA_METADATA_SAVE_THUMBNAIL.
|
|
* - "save-color-profile" for %PIKA_METADATA_SAVE_COLOR_PROFILE.
|
|
* - "save-comment" for %PIKA_METADATA_SAVE_COMMENT.
|
|
*
|
|
* The values from the [flags@MetadataSaveFlags] will only ever be used
|
|
* to set these properties to %FALSE, overriding the user's saved
|
|
* default values for the procedure, but NOT overriding the last used
|
|
* values from exporting @original_image or the last used values from
|
|
* exporting any other image using this procedure.
|
|
*
|
|
* If @mime_type is %NULL, [class@Metadata] handling is skipped. The
|
|
* procedure can still have all of the above listed boolean arguments,
|
|
* but must take care of their default values itself. The easiest way
|
|
* to do this is by simply using [func@export_comment], [func@export_exif] etc.
|
|
* as default values for these arguments when adding them using
|
|
* PIKA_PROC_ARG_BOOLEAN() or PIKA_PROC_AUX_ARG_BOOLEAN().
|
|
*
|
|
* Returns: (transfer none) (nullable): The metadata to be used
|
|
* for this export, or %NULL if @original_image doesn't have
|
|
* metadata.
|
|
*
|
|
* Since: 3.0
|
|
**/
|
|
PikaMetadata *
|
|
_pika_procedure_config_begin_export (PikaProcedureConfig *config,
|
|
PikaImage *original_image,
|
|
PikaRunMode run_mode,
|
|
const PikaValueArray *args,
|
|
const gchar *mime_type)
|
|
{
|
|
GObjectClass *object_class;
|
|
|
|
g_return_val_if_fail (PIKA_IS_PROCEDURE_CONFIG (config), NULL);
|
|
g_return_val_if_fail (PIKA_IS_IMAGE (original_image), NULL);
|
|
g_return_val_if_fail (args != NULL, NULL);
|
|
|
|
object_class = G_OBJECT_GET_CLASS (config);
|
|
|
|
if (mime_type)
|
|
{
|
|
PikaMetadataSaveFlags metadata_flags;
|
|
gint i;
|
|
|
|
config->priv->metadata =
|
|
pika_image_metadata_save_prepare (original_image,
|
|
mime_type,
|
|
&metadata_flags);
|
|
|
|
if (config->priv->metadata)
|
|
{
|
|
config->priv->mime_type = g_strdup (mime_type);
|
|
config->priv->metadata_flags = metadata_flags;
|
|
}
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (metadata_properties); i++)
|
|
{
|
|
/* we only disable properties based on metadata flags here
|
|
* and never enable them, so we don't override the user's
|
|
* saved default values that are passed to us via "args"
|
|
*/
|
|
if (! (metadata_flags & metadata_properties[i].flag))
|
|
{
|
|
const gchar *prop_name = metadata_properties[i].name;
|
|
GParamSpec *pspec;
|
|
|
|
pspec = g_object_class_find_property (object_class, prop_name);
|
|
if (pspec)
|
|
g_object_set (config,
|
|
prop_name, FALSE,
|
|
NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
_pika_procedure_config_begin_run (config, original_image, run_mode, args);
|
|
|
|
return config->priv->metadata;
|
|
}
|
|
|
|
/**
|
|
* _pika_procedure_config_end_export:
|
|
* @config: a #PikaProcedureConfig
|
|
* @exported_image: the image that was actually exported
|
|
* @file: the #GFile @exported_image was written to
|
|
* @status: the return status of the [class@Procedure]'s run()
|
|
*
|
|
* This is a variant of [method@ProcedureConfig.end_run] to be used by
|
|
* file export procedures using [class@SaveProcedure]. It must be paired
|
|
* with a call to [method@ProcedureConfig.begin_export] at the
|
|
* beginning of run().
|
|
*
|
|
* It does everything [method@ProcedureConfig.begin_run] does but
|
|
* provides additional features to automate file export:
|
|
*
|
|
* If @status is %PIKA_PDB_SUCCESS, and
|
|
* [method@ProcedureConfig.begin_export] returned a [class@Metadata], this
|
|
* function calls [method@ProcedureConfig.save_metadata], which syncs
|
|
* back @config's export properties to the metadata's
|
|
* [flags@MetadataSaveFlags] and writes metadata to @file using
|
|
* [method@Image.metadata_save_finish].
|
|
*
|
|
* Since: 3.0
|
|
**/
|
|
void
|
|
_pika_procedure_config_end_export (PikaProcedureConfig *config,
|
|
PikaImage *exported_image,
|
|
GFile *file,
|
|
PikaPDBStatusType status)
|
|
{
|
|
g_return_if_fail (PIKA_IS_PROCEDURE_CONFIG (config));
|
|
g_return_if_fail (PIKA_IS_IMAGE (exported_image));
|
|
g_return_if_fail (G_IS_FILE (file));
|
|
|
|
if (status == PIKA_PDB_SUCCESS)
|
|
{
|
|
pika_procedure_config_save_metadata (config, exported_image, file);
|
|
}
|
|
|
|
g_clear_object (&config->priv->metadata);
|
|
g_clear_pointer (&config->priv->mime_type, g_free);
|
|
config->priv->metadata_flags = 0;
|
|
config->priv->metadata_saved = FALSE;
|
|
|
|
_pika_procedure_config_end_run (config, status);
|
|
}
|
|
|
|
gboolean
|
|
pika_procedure_config_has_default (PikaProcedureConfig *config)
|
|
{
|
|
GFile *file;
|
|
gboolean success;
|
|
|
|
g_return_val_if_fail (PIKA_IS_PROCEDURE_CONFIG (config), FALSE);
|
|
|
|
file = pika_procedure_config_get_file (config, ".default");
|
|
|
|
success = g_file_query_exists (file, NULL);
|
|
g_object_unref (file);
|
|
|
|
return success;
|
|
}
|
|
|
|
gboolean
|
|
pika_procedure_config_load_default (PikaProcedureConfig *config,
|
|
GError **error)
|
|
{
|
|
GFile *file;
|
|
gboolean success;
|
|
|
|
g_return_val_if_fail (PIKA_IS_PROCEDURE_CONFIG (config), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
file = pika_procedure_config_get_file (config, ".default");
|
|
|
|
success = pika_config_deserialize_file (PIKA_CONFIG (config),
|
|
file,
|
|
NULL, error);
|
|
|
|
if (! success && error && (*error)->code == PIKA_CONFIG_ERROR_OPEN_ENOENT)
|
|
{
|
|
g_clear_error (error);
|
|
}
|
|
|
|
g_object_unref (file);
|
|
|
|
return success;
|
|
}
|
|
|
|
gboolean
|
|
pika_procedure_config_save_default (PikaProcedureConfig *config,
|
|
GError **error)
|
|
{
|
|
GFile *file;
|
|
gboolean success;
|
|
|
|
g_return_val_if_fail (PIKA_IS_PROCEDURE_CONFIG (config), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
file = pika_procedure_config_get_file (config, ".default");
|
|
|
|
success = pika_config_serialize_to_file (PIKA_CONFIG (config),
|
|
file,
|
|
"settings",
|
|
"end of settings",
|
|
NULL, error);
|
|
|
|
g_object_unref (file);
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
/* private functions */
|
|
|
|
/**
|
|
* pika_procedure_config_set_values:
|
|
* @config: a #PikaProcedureConfig
|
|
* @values: a #PikaValueArray
|
|
*
|
|
* Sets the values from @values on @config's properties.
|
|
*
|
|
* The number, order and types of values in @values must match the
|
|
* number, order and types of @config's properties.
|
|
*
|
|
* This function is meant to be used on @values which are passed as
|
|
* arguments to the run() function of the [class@Procedure] which created
|
|
* this @config. See [method@Procedure.create_config].
|
|
*
|
|
* Since: 3.0
|
|
**/
|
|
static void
|
|
pika_procedure_config_set_values (PikaProcedureConfig *config,
|
|
const PikaValueArray *values)
|
|
{
|
|
GParamSpec **pspecs;
|
|
guint n_pspecs;
|
|
gint n_aux_args;
|
|
gint n_values;
|
|
gint i;
|
|
|
|
g_return_if_fail (PIKA_IS_PROCEDURE_CONFIG (config));
|
|
g_return_if_fail (values != NULL);
|
|
|
|
pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (config),
|
|
&n_pspecs);
|
|
pika_procedure_get_aux_arguments (config->priv->procedure, &n_aux_args);
|
|
n_values = pika_value_array_length (values);
|
|
|
|
/* The first property is the procedure, all others are arguments. */
|
|
g_return_if_fail (n_pspecs == n_values + n_aux_args + 1);
|
|
|
|
for (i = 0; i < n_values; i++)
|
|
{
|
|
GParamSpec *pspec = pspecs[i + 1];
|
|
GValue *value = pika_value_array_index (values, i);
|
|
|
|
g_object_set_property (G_OBJECT (config), pspec->name, value);
|
|
}
|
|
|
|
g_free (pspecs);
|
|
}
|
|
|
|
static gboolean
|
|
pika_procedure_config_load_last (PikaProcedureConfig *config,
|
|
GError **error)
|
|
{
|
|
GFile *file;
|
|
gboolean success;
|
|
|
|
g_return_val_if_fail (PIKA_IS_PROCEDURE_CONFIG (config), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
file = pika_procedure_config_get_file (config, ".last");
|
|
|
|
success = pika_config_deserialize_file (PIKA_CONFIG (config),
|
|
file,
|
|
NULL, error);
|
|
|
|
if (! success && (*error)->code == PIKA_CONFIG_ERROR_OPEN_ENOENT)
|
|
{
|
|
g_clear_error (error);
|
|
}
|
|
|
|
g_object_unref (file);
|
|
|
|
return success;
|
|
}
|
|
|
|
static gboolean
|
|
pika_procedure_config_save_last (PikaProcedureConfig *config,
|
|
GError **error)
|
|
{
|
|
GFile *file;
|
|
gboolean success;
|
|
|
|
g_return_val_if_fail (PIKA_IS_PROCEDURE_CONFIG (config), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
file = pika_procedure_config_get_file (config, ".last");
|
|
|
|
success = pika_config_serialize_to_file (PIKA_CONFIG (config),
|
|
file,
|
|
"settings",
|
|
"end of settings",
|
|
NULL, error);
|
|
|
|
g_object_unref (file);
|
|
|
|
return success;
|
|
}
|
|
|
|
static gboolean
|
|
pika_procedure_config_load_parasite (PikaProcedureConfig *config,
|
|
PikaImage *image,
|
|
GError **error)
|
|
{
|
|
gchar *name;
|
|
PikaParasite *parasite;
|
|
gboolean success;
|
|
|
|
g_return_val_if_fail (PIKA_IS_PROCEDURE_CONFIG (config), FALSE);
|
|
g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
name = pika_procedure_config_parasite_name (config, "-last");
|
|
parasite = pika_image_get_parasite (image, name);
|
|
g_free (name);
|
|
|
|
if (! parasite)
|
|
return FALSE;
|
|
|
|
success = pika_config_deserialize_parasite (PIKA_CONFIG (config),
|
|
parasite,
|
|
NULL, error);
|
|
pika_parasite_free (parasite);
|
|
|
|
return success;
|
|
}
|
|
|
|
static gboolean
|
|
pika_procedure_config_save_parasite (PikaProcedureConfig *config,
|
|
PikaImage *image,
|
|
GError **error)
|
|
{
|
|
gchar *name;
|
|
PikaParasite *parasite;
|
|
|
|
g_return_val_if_fail (PIKA_IS_PROCEDURE_CONFIG (config), FALSE);
|
|
g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
name = pika_procedure_config_parasite_name (config, "-last");
|
|
parasite = pika_config_serialize_to_parasite (PIKA_CONFIG (config),
|
|
name,
|
|
PIKA_PARASITE_PERSISTENT,
|
|
NULL);
|
|
g_free (name);
|
|
|
|
if (! parasite)
|
|
return FALSE;
|
|
|
|
pika_image_attach_parasite (image, parasite);
|
|
pika_parasite_free (parasite);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GFile *
|
|
pika_procedure_config_get_file (PikaProcedureConfig *config,
|
|
const gchar *extension)
|
|
{
|
|
GFile *file;
|
|
gchar *basename;
|
|
|
|
basename = g_strconcat (G_OBJECT_TYPE_NAME (config), extension, NULL);
|
|
file = pika_directory_file ("plug-in-settings", basename, NULL);
|
|
g_free (basename);
|
|
|
|
return file;
|
|
}
|
|
|
|
static gchar *
|
|
pika_procedure_config_parasite_name (PikaProcedureConfig *config,
|
|
const gchar *suffix)
|
|
{
|
|
return g_strconcat (G_OBJECT_TYPE_NAME (config), suffix, NULL);
|
|
}
|