1 /* LIBGIMP - The GIMP Library
2  * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
3  *
4  * gimpvaluearray.c ported from GValueArray
5  *
6  * This library is free software: you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 3 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library.  If not, see
18  * <https://www.gnu.org/licenses/>.
19  */
20 
21 #include "config.h"
22 
23 #include <string.h>
24 
25 #include <glib-object.h>
26 
27 #include "gimpbasetypes.h"
28 
29 #include "gimpvaluearray.h"
30 
31 
32 /**
33  * SECTION:gimpvaluearray
34  * @short_description: A container structure to maintain an array of
35  *     generic values
36  * @see_also: #GValue, #GParamSpecValueArray, gimp_param_spec_value_array()
37  * @title: GimpValueArray
38  *
39  * The prime purpose of a #GimpValueArray is for it to be used as an
40  * object property that holds an array of values. A #GimpValueArray wraps
41  * an array of #GValue elements in order for it to be used as a boxed
42  * type through %GIMP_TYPE_VALUE_ARRAY.
43  */
44 
45 
46 #define GROUP_N_VALUES (1) /* power of 2 !! */
47 
48 
49 /**
50  * GimpValueArray:
51  *
52  * A #GimpValueArray contains an array of #GValue elements.
53  *
54  * Since: 2.10
55  */
56 struct _GimpValueArray
57 {
58   gint    n_values;
59   GValue *values;
60 
61   gint    n_prealloced;
62   gint    ref_count;
63 };
64 
65 
G_DEFINE_BOXED_TYPE(GimpValueArray,gimp_value_array,gimp_value_array_ref,gimp_value_array_unref)66 G_DEFINE_BOXED_TYPE (GimpValueArray, gimp_value_array,
67                      gimp_value_array_ref, gimp_value_array_unref)
68 
69 
70 /**
71  * gimp_value_array_index:
72  * @value_array: #GimpValueArray to get a value from
73  * @index: index of the value of interest
74  *
75  * Return a pointer to the value at @index contained in @value_array.
76  *
77  * Returns: (transfer none): pointer to a value at @index in @value_array
78  *
79  * Since: 2.10
80  */
81 GValue *
82 gimp_value_array_index (const GimpValueArray *value_array,
83                         gint                  index)
84 {
85   g_return_val_if_fail (value_array != NULL, NULL);
86   g_return_val_if_fail (index < value_array->n_values, NULL);
87 
88   return value_array->values + index;
89 }
90 
91 static inline void
value_array_grow(GimpValueArray * value_array,gint n_values,gboolean zero_init)92 value_array_grow (GimpValueArray *value_array,
93                   gint            n_values,
94                   gboolean        zero_init)
95 {
96   g_return_if_fail ((guint) n_values >= (guint) value_array->n_values);
97 
98   value_array->n_values = n_values;
99   if (value_array->n_values > value_array->n_prealloced)
100     {
101       gint i = value_array->n_prealloced;
102 
103       value_array->n_prealloced = (value_array->n_values + GROUP_N_VALUES - 1) & ~(GROUP_N_VALUES - 1);
104       value_array->values = g_renew (GValue, value_array->values, value_array->n_prealloced);
105 
106       if (!zero_init)
107         i = value_array->n_values;
108 
109       memset (value_array->values + i, 0,
110               (value_array->n_prealloced - i) * sizeof (value_array->values[0]));
111     }
112 }
113 
114 static inline void
value_array_shrink(GimpValueArray * value_array)115 value_array_shrink (GimpValueArray *value_array)
116 {
117   if (value_array->n_prealloced >= value_array->n_values + GROUP_N_VALUES)
118     {
119       value_array->n_prealloced = (value_array->n_values + GROUP_N_VALUES - 1) & ~(GROUP_N_VALUES - 1);
120       value_array->values = g_renew (GValue, value_array->values, value_array->n_prealloced);
121     }
122 }
123 
124 /**
125  * gimp_value_array_new:
126  * @n_prealloced: number of values to preallocate space for
127  *
128  * Allocate and initialize a new #GimpValueArray, optionally preserve space
129  * for @n_prealloced elements. New arrays always contain 0 elements,
130  * regardless of the value of @n_prealloced.
131  *
132  * Returns: a newly allocated #GimpValueArray with 0 values
133  *
134  * Since: 2.10
135  */
136 GimpValueArray *
gimp_value_array_new(gint n_prealloced)137 gimp_value_array_new (gint n_prealloced)
138 {
139   GimpValueArray *value_array = g_slice_new (GimpValueArray);
140 
141   value_array->n_values = 0;
142   value_array->n_prealloced = 0;
143   value_array->values = NULL;
144   value_array_grow (value_array, n_prealloced, TRUE);
145   value_array->n_values = 0;
146   value_array->ref_count = 1;
147 
148   return value_array;
149 }
150 
151 /**
152  * gimp_value_array_ref:
153  * @value_array: #GimpValueArray to ref
154  *
155  * Adds a reference to a #GimpValueArray.
156  *
157  * Return value: the same @value_array
158  *
159  * Since: 2.10
160  */
161 GimpValueArray *
gimp_value_array_ref(GimpValueArray * value_array)162 gimp_value_array_ref (GimpValueArray *value_array)
163 {
164   g_return_val_if_fail (value_array != NULL, NULL);
165 
166   value_array->ref_count++;
167 
168   return value_array;
169 }
170 
171 /**
172  * gimp_value_array_unref:
173  * @value_array: #GimpValueArray to unref
174  *
175  * Unref a #GimpValueArray. If the reference count drops to zero, the
176  * array including its contents are freed.
177  *
178  * Since: 2.10
179  */
180 void
gimp_value_array_unref(GimpValueArray * value_array)181 gimp_value_array_unref (GimpValueArray *value_array)
182 {
183   g_return_if_fail (value_array != NULL);
184 
185   value_array->ref_count--;
186 
187   if (value_array->ref_count < 1)
188     {
189       gint i;
190 
191       for (i = 0; i < value_array->n_values; i++)
192         {
193           GValue *value = value_array->values + i;
194 
195           if (G_VALUE_TYPE (value) != 0) /* we allow unset values in the array */
196             g_value_unset (value);
197         }
198 
199       g_free (value_array->values);
200       g_slice_free (GimpValueArray, value_array);
201     }
202 }
203 
204 gint
gimp_value_array_length(const GimpValueArray * value_array)205 gimp_value_array_length (const GimpValueArray *value_array)
206 {
207   g_return_val_if_fail (value_array != NULL, 0);
208 
209   return value_array->n_values;
210 }
211 
212 /**
213  * gimp_value_array_prepend:
214  * @value_array: #GimpValueArray to add an element to
215  * @value: (allow-none): #GValue to copy into #GimpValueArray, or %NULL
216  *
217  * Insert a copy of @value as first element of @value_array. If @value is
218  * %NULL, an uninitialized value is prepended.
219  *
220  * Returns: (transfer none): the #GimpValueArray passed in as @value_array
221  *
222  * Since: 2.10
223  */
224 GimpValueArray *
gimp_value_array_prepend(GimpValueArray * value_array,const GValue * value)225 gimp_value_array_prepend (GimpValueArray *value_array,
226                           const GValue   *value)
227 {
228   g_return_val_if_fail (value_array != NULL, NULL);
229 
230   return gimp_value_array_insert (value_array, 0, value);
231 }
232 
233 /**
234  * gimp_value_array_append:
235  * @value_array: #GimpValueArray to add an element to
236  * @value: (allow-none): #GValue to copy into #GimpValueArray, or %NULL
237  *
238  * Insert a copy of @value as last element of @value_array. If @value is
239  * %NULL, an uninitialized value is appended.
240  *
241  * Returns: (transfer none): the #GimpValueArray passed in as @value_array
242  *
243  * Since: 2.10
244  */
245 GimpValueArray *
gimp_value_array_append(GimpValueArray * value_array,const GValue * value)246 gimp_value_array_append (GimpValueArray *value_array,
247                          const GValue   *value)
248 {
249   g_return_val_if_fail (value_array != NULL, NULL);
250 
251   return gimp_value_array_insert (value_array, value_array->n_values, value);
252 }
253 
254 /**
255  * gimp_value_array_insert:
256  * @value_array: #GimpValueArray to add an element to
257  * @index: insertion position, must be &lt;= gimp_value_array_length()
258  * @value: (allow-none): #GValue to copy into #GimpValueArray, or %NULL
259  *
260  * Insert a copy of @value at specified position into @value_array. If @value
261  * is %NULL, an uninitialized value is inserted.
262  *
263  * Returns: (transfer none): the #GimpValueArray passed in as @value_array
264  *
265  * Since: 2.10
266  */
267 GimpValueArray *
gimp_value_array_insert(GimpValueArray * value_array,gint index,const GValue * value)268 gimp_value_array_insert (GimpValueArray *value_array,
269                          gint            index,
270                          const GValue   *value)
271 {
272   gint i;
273 
274   g_return_val_if_fail (value_array != NULL, NULL);
275   g_return_val_if_fail (index <= value_array->n_values, value_array);
276 
277   i = value_array->n_values;
278   value_array_grow (value_array, value_array->n_values + 1, FALSE);
279 
280   if (index + 1 < value_array->n_values)
281     memmove (value_array->values + index + 1, value_array->values + index,
282              (i - index) * sizeof (value_array->values[0]));
283 
284   memset (value_array->values + index, 0, sizeof (value_array->values[0]));
285 
286   if (value)
287     {
288       g_value_init (value_array->values + index, G_VALUE_TYPE (value));
289       g_value_copy (value, value_array->values + index);
290     }
291 
292   return value_array;
293 }
294 
295 /**
296  * gimp_value_array_remove:
297  * @value_array: #GimpValueArray to remove an element from
298  * @index: position of value to remove, which must be less than
299  *         gimp_value_array_length()
300  *
301  * Remove the value at position @index from @value_array.
302  *
303  * Returns: (transfer none): the #GimpValueArray passed in as @value_array
304  *
305  * Since: 2.10
306  */
307 GimpValueArray *
gimp_value_array_remove(GimpValueArray * value_array,gint index)308 gimp_value_array_remove (GimpValueArray *value_array,
309                          gint            index)
310 {
311   g_return_val_if_fail (value_array != NULL, NULL);
312   g_return_val_if_fail (index < value_array->n_values, value_array);
313 
314   if (G_VALUE_TYPE (value_array->values + index) != 0)
315     g_value_unset (value_array->values + index);
316 
317   value_array->n_values--;
318 
319   if (index < value_array->n_values)
320     memmove (value_array->values + index, value_array->values + index + 1,
321              (value_array->n_values - index) * sizeof (value_array->values[0]));
322 
323   value_array_shrink (value_array);
324 
325   if (value_array->n_prealloced > value_array->n_values)
326     memset (value_array->values + value_array->n_values, 0, sizeof (value_array->values[0]));
327 
328   return value_array;
329 }
330 
331 void
gimp_value_array_truncate(GimpValueArray * value_array,gint n_values)332 gimp_value_array_truncate (GimpValueArray *value_array,
333                            gint            n_values)
334 {
335   gint i;
336 
337   g_return_if_fail (value_array != NULL);
338   g_return_if_fail (n_values > 0 && n_values <= value_array->n_values);
339 
340   for (i = value_array->n_values; i > n_values; i--)
341     gimp_value_array_remove (value_array, i - 1);
342 }
343 
344 
345 /*
346  * GIMP_TYPE_PARAM_VALUE_ARRAY
347  */
348 
349 static void       gimp_param_value_array_class_init  (GParamSpecClass *klass);
350 static void       gimp_param_value_array_init        (GParamSpec      *pspec);
351 static void       gimp_param_value_array_finalize    (GParamSpec      *pspec);
352 static void       gimp_param_value_array_set_default (GParamSpec      *pspec,
353                                                       GValue          *value);
354 static gboolean   gimp_param_value_array_validate    (GParamSpec      *pspec,
355                                                       GValue          *value);
356 static gint       gimp_param_value_array_values_cmp  (GParamSpec      *pspec,
357                                                       const GValue    *value1,
358                                                       const GValue    *value2);
359 
360 GType
gimp_param_value_array_get_type(void)361 gimp_param_value_array_get_type (void)
362 {
363   static GType type = 0;
364 
365   if (! type)
366     {
367       const GTypeInfo info =
368       {
369         sizeof (GParamSpecClass),
370         NULL, NULL,
371         (GClassInitFunc) gimp_param_value_array_class_init,
372         NULL, NULL,
373         sizeof (GimpParamSpecValueArray),
374         0,
375         (GInstanceInitFunc) gimp_param_value_array_init
376       };
377 
378       type = g_type_register_static (G_TYPE_PARAM_BOXED,
379                                      "GimpParamValueArray", &info, 0);
380     }
381 
382   return type;
383 }
384 
385 
386 static void
gimp_param_value_array_class_init(GParamSpecClass * klass)387 gimp_param_value_array_class_init (GParamSpecClass *klass)
388 {
389   klass->value_type        = GIMP_TYPE_VALUE_ARRAY;
390   klass->finalize          = gimp_param_value_array_finalize;
391   klass->value_set_default = gimp_param_value_array_set_default;
392   klass->value_validate    = gimp_param_value_array_validate;
393   klass->values_cmp        = gimp_param_value_array_values_cmp;
394 }
395 
396 static void
gimp_param_value_array_init(GParamSpec * pspec)397 gimp_param_value_array_init (GParamSpec *pspec)
398 {
399   GimpParamSpecValueArray *aspec = GIMP_PARAM_SPEC_VALUE_ARRAY (pspec);
400 
401   aspec->element_spec = NULL;
402   aspec->fixed_n_elements = 0; /* disable */
403 }
404 
405 static inline guint
gimp_value_array_ensure_size(GimpValueArray * value_array,guint fixed_n_elements)406 gimp_value_array_ensure_size (GimpValueArray *value_array,
407                               guint           fixed_n_elements)
408 {
409   guint changed = 0;
410 
411   if (fixed_n_elements)
412     {
413       while (gimp_value_array_length (value_array) < fixed_n_elements)
414         {
415           gimp_value_array_append (value_array, NULL);
416           changed++;
417         }
418 
419       while (gimp_value_array_length (value_array) > fixed_n_elements)
420         {
421           gimp_value_array_remove (value_array,
422                                    gimp_value_array_length (value_array) - 1);
423           changed++;
424         }
425     }
426 
427   return changed;
428 }
429 
430 static void
gimp_param_value_array_finalize(GParamSpec * pspec)431 gimp_param_value_array_finalize (GParamSpec *pspec)
432 {
433   GimpParamSpecValueArray *aspec = GIMP_PARAM_SPEC_VALUE_ARRAY (pspec);
434   GParamSpecClass         *parent_class;
435 
436   parent_class = g_type_class_peek (g_type_parent (GIMP_TYPE_PARAM_VALUE_ARRAY));
437 
438   g_clear_pointer (&aspec->element_spec, g_param_spec_unref);
439 
440   parent_class->finalize (pspec);
441 }
442 
443 static void
gimp_param_value_array_set_default(GParamSpec * pspec,GValue * value)444 gimp_param_value_array_set_default (GParamSpec *pspec,
445                                     GValue     *value)
446 {
447   GimpParamSpecValueArray *aspec = GIMP_PARAM_SPEC_VALUE_ARRAY (pspec);
448 
449   if (! value->data[0].v_pointer && aspec->fixed_n_elements)
450     value->data[0].v_pointer = gimp_value_array_new (aspec->fixed_n_elements);
451 
452   if (value->data[0].v_pointer)
453     {
454       /* g_value_reset (value);  already done */
455       gimp_value_array_ensure_size (value->data[0].v_pointer,
456                                     aspec->fixed_n_elements);
457     }
458 }
459 
460 static gboolean
gimp_param_value_array_validate(GParamSpec * pspec,GValue * value)461 gimp_param_value_array_validate (GParamSpec *pspec,
462                                  GValue     *value)
463 {
464   GimpParamSpecValueArray *aspec       = GIMP_PARAM_SPEC_VALUE_ARRAY (pspec);
465   GimpValueArray          *value_array = value->data[0].v_pointer;
466   guint                    changed     = 0;
467 
468   if (! value->data[0].v_pointer && aspec->fixed_n_elements)
469     value->data[0].v_pointer = gimp_value_array_new (aspec->fixed_n_elements);
470 
471   if (value->data[0].v_pointer)
472     {
473       /* ensure array size validity */
474       changed += gimp_value_array_ensure_size (value_array,
475                                                aspec->fixed_n_elements);
476 
477       /* ensure array values validity against a present element spec */
478       if (aspec->element_spec)
479         {
480           GParamSpec *element_spec = aspec->element_spec;
481           gint        length       = gimp_value_array_length (value_array);
482           gint        i;
483 
484           for (i = 0; i < length; i++)
485             {
486               GValue *element = gimp_value_array_index (value_array, i);
487 
488               /* need to fixup value type, or ensure that the array
489                * value is initialized at all
490                */
491               if (! g_value_type_compatible (G_VALUE_TYPE (element),
492                                              G_PARAM_SPEC_VALUE_TYPE (element_spec)))
493                 {
494                   if (G_VALUE_TYPE (element) != 0)
495                     g_value_unset (element);
496 
497                   g_value_init (element, G_PARAM_SPEC_VALUE_TYPE (element_spec));
498                   g_param_value_set_default (element_spec, element);
499                   changed++;
500                 }
501 
502               /* validate array value against element_spec */
503               changed += g_param_value_validate (element_spec, element);
504             }
505         }
506     }
507 
508   return changed;
509 }
510 
511 static gint
gimp_param_value_array_values_cmp(GParamSpec * pspec,const GValue * value1,const GValue * value2)512 gimp_param_value_array_values_cmp (GParamSpec   *pspec,
513                                    const GValue *value1,
514                                    const GValue *value2)
515 {
516   GimpParamSpecValueArray *aspec        = GIMP_PARAM_SPEC_VALUE_ARRAY (pspec);
517   GimpValueArray          *value_array1 = value1->data[0].v_pointer;
518   GimpValueArray          *value_array2 = value2->data[0].v_pointer;
519   gint                     length1;
520   gint                     length2;
521 
522   if (!value_array1 || !value_array2)
523     return value_array2 ? -1 : value_array1 != value_array2;
524 
525   length1 = gimp_value_array_length (value_array1);
526   length2 = gimp_value_array_length (value_array2);
527 
528   if (length1 != length2)
529     {
530       return length1 < length2 ? -1 : 1;
531     }
532   else if (! aspec->element_spec)
533     {
534       /* we need an element specification for comparisons, so there's
535        * not much to compare here, try to at least provide stable
536        * lesser/greater result
537        */
538       return length1 < length2 ? -1 : length1 > length2;
539     }
540   else /* length1 == length2 */
541     {
542       guint i;
543 
544       for (i = 0; i < length1; i++)
545         {
546           GValue *element1 = gimp_value_array_index (value_array1, i);
547           GValue *element2 = gimp_value_array_index (value_array2, i);
548           gint    cmp;
549 
550           /* need corresponding element types, provide stable result
551            * otherwise
552            */
553           if (G_VALUE_TYPE (element1) != G_VALUE_TYPE (element2))
554             return G_VALUE_TYPE (element1) < G_VALUE_TYPE (element2) ? -1 : 1;
555 
556           cmp = g_param_values_cmp (aspec->element_spec, element1, element2);
557           if (cmp)
558             return cmp;
559         }
560 
561       return 0;
562     }
563 }
564 
565 GParamSpec *
gimp_param_spec_value_array(const gchar * name,const gchar * nick,const gchar * blurb,GParamSpec * element_spec,GParamFlags flags)566 gimp_param_spec_value_array (const gchar *name,
567                              const gchar *nick,
568                              const gchar *blurb,
569                              GParamSpec  *element_spec,
570                              GParamFlags  flags)
571 {
572   GimpParamSpecValueArray *aspec;
573 
574   if (element_spec)
575     g_return_val_if_fail (G_IS_PARAM_SPEC (element_spec), NULL);
576 
577   aspec = g_param_spec_internal (GIMP_TYPE_PARAM_VALUE_ARRAY,
578                                  name,
579                                  nick,
580                                  blurb,
581                                  flags);
582   if (element_spec)
583     {
584       aspec->element_spec = g_param_spec_ref (element_spec);
585       g_param_spec_sink (element_spec);
586     }
587 
588   return G_PARAM_SPEC (aspec);
589 }
590