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 <= 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