forked from openkylin/gimp
590 lines
17 KiB
C
590 lines
17 KiB
C
/* LIBGIMP - The GIMP Library
|
|
* Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
|
|
*
|
|
* gimpvaluearray.c ported from GValueArray
|
|
*
|
|
* 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
|
|
* Library 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 <string.h>
|
|
|
|
#include <glib-object.h>
|
|
|
|
#include "gimpbasetypes.h"
|
|
|
|
#include "gimpvaluearray.h"
|
|
|
|
|
|
/**
|
|
* SECTION:gimpvaluearray
|
|
* @short_description: A container structure to maintain an array of
|
|
* generic values
|
|
* @see_also: #GValue, #GParamSpecValueArray, gimp_param_spec_value_array()
|
|
* @title: GimpValueArray
|
|
*
|
|
* The prime purpose of a #GimpValueArray is for it to be used as an
|
|
* object property that holds an array of values. A #GimpValueArray wraps
|
|
* an array of #GValue elements in order for it to be used as a boxed
|
|
* type through %GIMP_TYPE_VALUE_ARRAY.
|
|
*/
|
|
|
|
|
|
#define GROUP_N_VALUES (1) /* power of 2 !! */
|
|
|
|
|
|
/**
|
|
* GimpValueArray:
|
|
*
|
|
* A #GimpValueArray contains an array of #GValue elements.
|
|
*
|
|
* Since: 2.10
|
|
*/
|
|
struct _GimpValueArray
|
|
{
|
|
gint n_values;
|
|
GValue *values;
|
|
|
|
gint n_prealloced;
|
|
gint ref_count;
|
|
};
|
|
|
|
|
|
G_DEFINE_BOXED_TYPE (GimpValueArray, gimp_value_array,
|
|
gimp_value_array_ref, gimp_value_array_unref)
|
|
|
|
|
|
/**
|
|
* gimp_value_array_index:
|
|
* @value_array: #GimpValueArray to get a value from
|
|
* @index: index of the value of interest
|
|
*
|
|
* Return a pointer to the value at @index contained in @value_array.
|
|
*
|
|
* Returns: (transfer none): pointer to a value at @index in @value_array
|
|
*
|
|
* Since: 2.10
|
|
*/
|
|
GValue *
|
|
gimp_value_array_index (const GimpValueArray *value_array,
|
|
gint index)
|
|
{
|
|
g_return_val_if_fail (value_array != NULL, NULL);
|
|
g_return_val_if_fail (index < value_array->n_values, NULL);
|
|
|
|
return value_array->values + index;
|
|
}
|
|
|
|
static inline void
|
|
value_array_grow (GimpValueArray *value_array,
|
|
gint n_values,
|
|
gboolean zero_init)
|
|
{
|
|
g_return_if_fail ((guint) n_values >= (guint) value_array->n_values);
|
|
|
|
value_array->n_values = n_values;
|
|
if (value_array->n_values > value_array->n_prealloced)
|
|
{
|
|
gint i = value_array->n_prealloced;
|
|
|
|
value_array->n_prealloced = (value_array->n_values + GROUP_N_VALUES - 1) & ~(GROUP_N_VALUES - 1);
|
|
value_array->values = g_renew (GValue, value_array->values, value_array->n_prealloced);
|
|
|
|
if (!zero_init)
|
|
i = value_array->n_values;
|
|
|
|
memset (value_array->values + i, 0,
|
|
(value_array->n_prealloced - i) * sizeof (value_array->values[0]));
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
value_array_shrink (GimpValueArray *value_array)
|
|
{
|
|
if (value_array->n_prealloced >= value_array->n_values + GROUP_N_VALUES)
|
|
{
|
|
value_array->n_prealloced = (value_array->n_values + GROUP_N_VALUES - 1) & ~(GROUP_N_VALUES - 1);
|
|
value_array->values = g_renew (GValue, value_array->values, value_array->n_prealloced);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gimp_value_array_new:
|
|
* @n_prealloced: number of values to preallocate space for
|
|
*
|
|
* Allocate and initialize a new #GimpValueArray, optionally preserve space
|
|
* for @n_prealloced elements. New arrays always contain 0 elements,
|
|
* regardless of the value of @n_prealloced.
|
|
*
|
|
* Returns: a newly allocated #GimpValueArray with 0 values
|
|
*
|
|
* Since: 2.10
|
|
*/
|
|
GimpValueArray *
|
|
gimp_value_array_new (gint n_prealloced)
|
|
{
|
|
GimpValueArray *value_array = g_slice_new (GimpValueArray);
|
|
|
|
value_array->n_values = 0;
|
|
value_array->n_prealloced = 0;
|
|
value_array->values = NULL;
|
|
value_array_grow (value_array, n_prealloced, TRUE);
|
|
value_array->n_values = 0;
|
|
value_array->ref_count = 1;
|
|
|
|
return value_array;
|
|
}
|
|
|
|
/**
|
|
* gimp_value_array_ref:
|
|
* @value_array: #GimpValueArray to ref
|
|
*
|
|
* Adds a reference to a #GimpValueArray.
|
|
*
|
|
* Return value: the same @value_array
|
|
*
|
|
* Since: 2.10
|
|
*/
|
|
GimpValueArray *
|
|
gimp_value_array_ref (GimpValueArray *value_array)
|
|
{
|
|
g_return_val_if_fail (value_array != NULL, NULL);
|
|
|
|
value_array->ref_count++;
|
|
|
|
return value_array;
|
|
}
|
|
|
|
/**
|
|
* gimp_value_array_unref:
|
|
* @value_array: #GimpValueArray to unref
|
|
*
|
|
* Unref a #GimpValueArray. If the reference count drops to zero, the
|
|
* array including its contents are freed.
|
|
*
|
|
* Since: 2.10
|
|
*/
|
|
void
|
|
gimp_value_array_unref (GimpValueArray *value_array)
|
|
{
|
|
g_return_if_fail (value_array != NULL);
|
|
|
|
value_array->ref_count--;
|
|
|
|
if (value_array->ref_count < 1)
|
|
{
|
|
gint i;
|
|
|
|
for (i = 0; i < value_array->n_values; i++)
|
|
{
|
|
GValue *value = value_array->values + i;
|
|
|
|
if (G_VALUE_TYPE (value) != 0) /* we allow unset values in the array */
|
|
g_value_unset (value);
|
|
}
|
|
|
|
g_free (value_array->values);
|
|
g_slice_free (GimpValueArray, value_array);
|
|
}
|
|
}
|
|
|
|
gint
|
|
gimp_value_array_length (const GimpValueArray *value_array)
|
|
{
|
|
g_return_val_if_fail (value_array != NULL, 0);
|
|
|
|
return value_array->n_values;
|
|
}
|
|
|
|
/**
|
|
* gimp_value_array_prepend:
|
|
* @value_array: #GimpValueArray to add an element to
|
|
* @value: (allow-none): #GValue to copy into #GimpValueArray, or %NULL
|
|
*
|
|
* Insert a copy of @value as first element of @value_array. If @value is
|
|
* %NULL, an uninitialized value is prepended.
|
|
*
|
|
* Returns: (transfer none): the #GimpValueArray passed in as @value_array
|
|
*
|
|
* Since: 2.10
|
|
*/
|
|
GimpValueArray *
|
|
gimp_value_array_prepend (GimpValueArray *value_array,
|
|
const GValue *value)
|
|
{
|
|
g_return_val_if_fail (value_array != NULL, NULL);
|
|
|
|
return gimp_value_array_insert (value_array, 0, value);
|
|
}
|
|
|
|
/**
|
|
* gimp_value_array_append:
|
|
* @value_array: #GimpValueArray to add an element to
|
|
* @value: (allow-none): #GValue to copy into #GimpValueArray, or %NULL
|
|
*
|
|
* Insert a copy of @value as last element of @value_array. If @value is
|
|
* %NULL, an uninitialized value is appended.
|
|
*
|
|
* Returns: (transfer none): the #GimpValueArray passed in as @value_array
|
|
*
|
|
* Since: 2.10
|
|
*/
|
|
GimpValueArray *
|
|
gimp_value_array_append (GimpValueArray *value_array,
|
|
const GValue *value)
|
|
{
|
|
g_return_val_if_fail (value_array != NULL, NULL);
|
|
|
|
return gimp_value_array_insert (value_array, value_array->n_values, value);
|
|
}
|
|
|
|
/**
|
|
* gimp_value_array_insert:
|
|
* @value_array: #GimpValueArray to add an element to
|
|
* @index: insertion position, must be <= gimp_value_array_length()
|
|
* @value: (allow-none): #GValue to copy into #GimpValueArray, or %NULL
|
|
*
|
|
* Insert a copy of @value at specified position into @value_array. If @value
|
|
* is %NULL, an uninitialized value is inserted.
|
|
*
|
|
* Returns: (transfer none): the #GimpValueArray passed in as @value_array
|
|
*
|
|
* Since: 2.10
|
|
*/
|
|
GimpValueArray *
|
|
gimp_value_array_insert (GimpValueArray *value_array,
|
|
gint index,
|
|
const GValue *value)
|
|
{
|
|
gint i;
|
|
|
|
g_return_val_if_fail (value_array != NULL, NULL);
|
|
g_return_val_if_fail (index <= value_array->n_values, value_array);
|
|
|
|
i = value_array->n_values;
|
|
value_array_grow (value_array, value_array->n_values + 1, FALSE);
|
|
|
|
if (index + 1 < value_array->n_values)
|
|
memmove (value_array->values + index + 1, value_array->values + index,
|
|
(i - index) * sizeof (value_array->values[0]));
|
|
|
|
memset (value_array->values + index, 0, sizeof (value_array->values[0]));
|
|
|
|
if (value)
|
|
{
|
|
g_value_init (value_array->values + index, G_VALUE_TYPE (value));
|
|
g_value_copy (value, value_array->values + index);
|
|
}
|
|
|
|
return value_array;
|
|
}
|
|
|
|
/**
|
|
* gimp_value_array_remove:
|
|
* @value_array: #GimpValueArray to remove an element from
|
|
* @index: position of value to remove, which must be less than
|
|
* gimp_value_array_length()
|
|
*
|
|
* Remove the value at position @index from @value_array.
|
|
*
|
|
* Returns: (transfer none): the #GimpValueArray passed in as @value_array
|
|
*
|
|
* Since: 2.10
|
|
*/
|
|
GimpValueArray *
|
|
gimp_value_array_remove (GimpValueArray *value_array,
|
|
gint index)
|
|
{
|
|
g_return_val_if_fail (value_array != NULL, NULL);
|
|
g_return_val_if_fail (index < value_array->n_values, value_array);
|
|
|
|
if (G_VALUE_TYPE (value_array->values + index) != 0)
|
|
g_value_unset (value_array->values + index);
|
|
|
|
value_array->n_values--;
|
|
|
|
if (index < value_array->n_values)
|
|
memmove (value_array->values + index, value_array->values + index + 1,
|
|
(value_array->n_values - index) * sizeof (value_array->values[0]));
|
|
|
|
value_array_shrink (value_array);
|
|
|
|
if (value_array->n_prealloced > value_array->n_values)
|
|
memset (value_array->values + value_array->n_values, 0, sizeof (value_array->values[0]));
|
|
|
|
return value_array;
|
|
}
|
|
|
|
void
|
|
gimp_value_array_truncate (GimpValueArray *value_array,
|
|
gint n_values)
|
|
{
|
|
gint i;
|
|
|
|
g_return_if_fail (value_array != NULL);
|
|
g_return_if_fail (n_values > 0 && n_values <= value_array->n_values);
|
|
|
|
for (i = value_array->n_values; i > n_values; i--)
|
|
gimp_value_array_remove (value_array, i - 1);
|
|
}
|
|
|
|
|
|
/*
|
|
* GIMP_TYPE_PARAM_VALUE_ARRAY
|
|
*/
|
|
|
|
static void gimp_param_value_array_class_init (GParamSpecClass *klass);
|
|
static void gimp_param_value_array_init (GParamSpec *pspec);
|
|
static void gimp_param_value_array_finalize (GParamSpec *pspec);
|
|
static void gimp_param_value_array_set_default (GParamSpec *pspec,
|
|
GValue *value);
|
|
static gboolean gimp_param_value_array_validate (GParamSpec *pspec,
|
|
GValue *value);
|
|
static gint gimp_param_value_array_values_cmp (GParamSpec *pspec,
|
|
const GValue *value1,
|
|
const GValue *value2);
|
|
|
|
GType
|
|
gimp_param_value_array_get_type (void)
|
|
{
|
|
static GType type = 0;
|
|
|
|
if (! type)
|
|
{
|
|
const GTypeInfo info =
|
|
{
|
|
sizeof (GParamSpecClass),
|
|
NULL, NULL,
|
|
(GClassInitFunc) gimp_param_value_array_class_init,
|
|
NULL, NULL,
|
|
sizeof (GimpParamSpecValueArray),
|
|
0,
|
|
(GInstanceInitFunc) gimp_param_value_array_init
|
|
};
|
|
|
|
type = g_type_register_static (G_TYPE_PARAM_BOXED,
|
|
"GimpParamValueArray", &info, 0);
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
|
|
static void
|
|
gimp_param_value_array_class_init (GParamSpecClass *klass)
|
|
{
|
|
klass->value_type = GIMP_TYPE_VALUE_ARRAY;
|
|
klass->finalize = gimp_param_value_array_finalize;
|
|
klass->value_set_default = gimp_param_value_array_set_default;
|
|
klass->value_validate = gimp_param_value_array_validate;
|
|
klass->values_cmp = gimp_param_value_array_values_cmp;
|
|
}
|
|
|
|
static void
|
|
gimp_param_value_array_init (GParamSpec *pspec)
|
|
{
|
|
GimpParamSpecValueArray *aspec = GIMP_PARAM_SPEC_VALUE_ARRAY (pspec);
|
|
|
|
aspec->element_spec = NULL;
|
|
aspec->fixed_n_elements = 0; /* disable */
|
|
}
|
|
|
|
static inline guint
|
|
gimp_value_array_ensure_size (GimpValueArray *value_array,
|
|
guint fixed_n_elements)
|
|
{
|
|
guint changed = 0;
|
|
|
|
if (fixed_n_elements)
|
|
{
|
|
while (gimp_value_array_length (value_array) < fixed_n_elements)
|
|
{
|
|
gimp_value_array_append (value_array, NULL);
|
|
changed++;
|
|
}
|
|
|
|
while (gimp_value_array_length (value_array) > fixed_n_elements)
|
|
{
|
|
gimp_value_array_remove (value_array,
|
|
gimp_value_array_length (value_array) - 1);
|
|
changed++;
|
|
}
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
static void
|
|
gimp_param_value_array_finalize (GParamSpec *pspec)
|
|
{
|
|
GimpParamSpecValueArray *aspec = GIMP_PARAM_SPEC_VALUE_ARRAY (pspec);
|
|
GParamSpecClass *parent_class;
|
|
|
|
parent_class = g_type_class_peek (g_type_parent (GIMP_TYPE_PARAM_VALUE_ARRAY));
|
|
|
|
g_clear_pointer (&aspec->element_spec, g_param_spec_unref);
|
|
|
|
parent_class->finalize (pspec);
|
|
}
|
|
|
|
static void
|
|
gimp_param_value_array_set_default (GParamSpec *pspec,
|
|
GValue *value)
|
|
{
|
|
GimpParamSpecValueArray *aspec = GIMP_PARAM_SPEC_VALUE_ARRAY (pspec);
|
|
|
|
if (! value->data[0].v_pointer && aspec->fixed_n_elements)
|
|
value->data[0].v_pointer = gimp_value_array_new (aspec->fixed_n_elements);
|
|
|
|
if (value->data[0].v_pointer)
|
|
{
|
|
/* g_value_reset (value); already done */
|
|
gimp_value_array_ensure_size (value->data[0].v_pointer,
|
|
aspec->fixed_n_elements);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gimp_param_value_array_validate (GParamSpec *pspec,
|
|
GValue *value)
|
|
{
|
|
GimpParamSpecValueArray *aspec = GIMP_PARAM_SPEC_VALUE_ARRAY (pspec);
|
|
GimpValueArray *value_array = value->data[0].v_pointer;
|
|
guint changed = 0;
|
|
|
|
if (! value->data[0].v_pointer && aspec->fixed_n_elements)
|
|
value->data[0].v_pointer = gimp_value_array_new (aspec->fixed_n_elements);
|
|
|
|
if (value->data[0].v_pointer)
|
|
{
|
|
/* ensure array size validity */
|
|
changed += gimp_value_array_ensure_size (value_array,
|
|
aspec->fixed_n_elements);
|
|
|
|
/* ensure array values validity against a present element spec */
|
|
if (aspec->element_spec)
|
|
{
|
|
GParamSpec *element_spec = aspec->element_spec;
|
|
gint length = gimp_value_array_length (value_array);
|
|
gint i;
|
|
|
|
for (i = 0; i < length; i++)
|
|
{
|
|
GValue *element = gimp_value_array_index (value_array, i);
|
|
|
|
/* need to fixup value type, or ensure that the array
|
|
* value is initialized at all
|
|
*/
|
|
if (! g_value_type_compatible (G_VALUE_TYPE (element),
|
|
G_PARAM_SPEC_VALUE_TYPE (element_spec)))
|
|
{
|
|
if (G_VALUE_TYPE (element) != 0)
|
|
g_value_unset (element);
|
|
|
|
g_value_init (element, G_PARAM_SPEC_VALUE_TYPE (element_spec));
|
|
g_param_value_set_default (element_spec, element);
|
|
changed++;
|
|
}
|
|
|
|
/* validate array value against element_spec */
|
|
changed += g_param_value_validate (element_spec, element);
|
|
}
|
|
}
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
static gint
|
|
gimp_param_value_array_values_cmp (GParamSpec *pspec,
|
|
const GValue *value1,
|
|
const GValue *value2)
|
|
{
|
|
GimpParamSpecValueArray *aspec = GIMP_PARAM_SPEC_VALUE_ARRAY (pspec);
|
|
GimpValueArray *value_array1 = value1->data[0].v_pointer;
|
|
GimpValueArray *value_array2 = value2->data[0].v_pointer;
|
|
gint length1;
|
|
gint length2;
|
|
|
|
if (!value_array1 || !value_array2)
|
|
return value_array2 ? -1 : value_array1 != value_array2;
|
|
|
|
length1 = gimp_value_array_length (value_array1);
|
|
length2 = gimp_value_array_length (value_array2);
|
|
|
|
if (length1 != length2)
|
|
{
|
|
return length1 < length2 ? -1 : 1;
|
|
}
|
|
else if (! aspec->element_spec)
|
|
{
|
|
/* we need an element specification for comparisons, so there's
|
|
* not much to compare here, try to at least provide stable
|
|
* lesser/greater result
|
|
*/
|
|
return length1 < length2 ? -1 : length1 > length2;
|
|
}
|
|
else /* length1 == length2 */
|
|
{
|
|
guint i;
|
|
|
|
for (i = 0; i < length1; i++)
|
|
{
|
|
GValue *element1 = gimp_value_array_index (value_array1, i);
|
|
GValue *element2 = gimp_value_array_index (value_array2, i);
|
|
gint cmp;
|
|
|
|
/* need corresponding element types, provide stable result
|
|
* otherwise
|
|
*/
|
|
if (G_VALUE_TYPE (element1) != G_VALUE_TYPE (element2))
|
|
return G_VALUE_TYPE (element1) < G_VALUE_TYPE (element2) ? -1 : 1;
|
|
|
|
cmp = g_param_values_cmp (aspec->element_spec, element1, element2);
|
|
if (cmp)
|
|
return cmp;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
GParamSpec *
|
|
gimp_param_spec_value_array (const gchar *name,
|
|
const gchar *nick,
|
|
const gchar *blurb,
|
|
GParamSpec *element_spec,
|
|
GParamFlags flags)
|
|
{
|
|
GimpParamSpecValueArray *aspec;
|
|
|
|
if (element_spec)
|
|
g_return_val_if_fail (G_IS_PARAM_SPEC (element_spec), NULL);
|
|
|
|
aspec = g_param_spec_internal (GIMP_TYPE_PARAM_VALUE_ARRAY,
|
|
name,
|
|
nick,
|
|
blurb,
|
|
flags);
|
|
if (element_spec)
|
|
{
|
|
aspec->element_spec = g_param_spec_ref (element_spec);
|
|
g_param_spec_sink (element_spec);
|
|
}
|
|
|
|
return G_PARAM_SPEC (aspec);
|
|
}
|