PIKApp/libpika/pikaprocedureconfig.c

1170 lines
38 KiB
C
Raw Normal View History

2023-09-26 00:35:21 +02:00
/* 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;
};
2023-10-30 23:55:30 +01:00
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);
2023-09-26 00:35:21 +02:00
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, &parasite_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, &parasite_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);
}
/**
2023-10-30 23:55:30 +01:00
* 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:
2023-09-26 00:35:21 +02:00
* @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
2023-10-30 23:55:30 +01:00
_pika_procedure_config_begin_run (PikaProcedureConfig *config,
PikaImage *image,
PikaRunMode run_mode,
const PikaValueArray *args)
2023-09-26 00:35:21 +02:00
{
2023-10-30 23:55:30 +01:00
GParamSpec *run_mode_pspec;
2023-09-26 00:35:21 +02:00
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;
2023-10-30 23:55:30 +01:00
pika_procedure_config_set_values (config, args);
2023-09-26 00:35:21 +02:00
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;
}
}
2023-10-30 23:55:30 +01:00
/* 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);
2023-09-26 00:35:21 +02:00
g_free (pspecs);
}
/**
2023-10-30 23:55:30 +01:00
* _pika_procedure_config_end_run:
2023-09-26 00:35:21 +02:00
* @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
2023-10-30 23:55:30 +01:00
_pika_procedure_config_end_run (PikaProcedureConfig *config,
PikaPDBStatusType status)
2023-09-26 00:35:21 +02:00
{
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;
}
/**
2023-10-30 23:55:30 +01:00
* _pika_procedure_config_begin_export:
2023-09-26 00:35:21 +02:00
* @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 *
2023-10-30 23:55:30 +01:00
_pika_procedure_config_begin_export (PikaProcedureConfig *config,
PikaImage *original_image,
PikaRunMode run_mode,
const PikaValueArray *args,
const gchar *mime_type)
2023-09-26 00:35:21 +02:00
{
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);
}
}
}
2023-10-30 23:55:30 +01:00
_pika_procedure_config_begin_run (config, original_image, run_mode, args);
2023-09-26 00:35:21 +02:00
return config->priv->metadata;
}
/**
2023-10-30 23:55:30 +01:00
* _pika_procedure_config_end_export:
2023-09-26 00:35:21 +02:00
* @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
2023-10-30 23:55:30 +01:00
_pika_procedure_config_end_export (PikaProcedureConfig *config,
PikaImage *exported_image,
GFile *file,
PikaPDBStatusType status)
2023-09-26 00:35:21 +02:00
{
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;
2023-10-30 23:55:30 +01:00
_pika_procedure_config_end_run (config, status);
2023-09-26 00:35:21 +02:00
}
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;
}
2023-10-30 23:55:30 +01:00
/* 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
2023-09-26 00:35:21 +02:00
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;
}
2023-10-30 23:55:30 +01:00
static gboolean
2023-09-26 00:35:21 +02:00
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;
}
2023-10-30 23:55:30 +01:00
static gboolean
2023-09-26 00:35:21 +02:00
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;
}
2023-10-30 23:55:30 +01:00
static gboolean
2023-09-26 00:35:21 +02:00
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;
}
2023-10-30 23:55:30 +01:00
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);
}