1 /* -*- mode:C; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2 /*
3 * Clutter.
4 *
5 * An OpenGL based 'interactive canvas' library.
6 *
7 * Authored By: Tomas Frydrych <tf@openedhand.com>
8 * Emmanuele Bassi <ebassi@openedhand.com>
9 *
10 * Copyright (C) 2007 OpenedHand
11 *
12 * This library is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Lesser General Public
14 * License as published by the Free Software Foundation; either
15 * version 2 of the License, or (at your option) any later version.
16 *
17 * This library is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * Lesser General Public License for more details.
21 *
22 * You should have received a copy of the GNU Lesser General Public
23 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
24 *
25 *
26 */
27
28 /**
29 * SECTION:clutter-units
30 * @short_description: A logical distance unit
31 *
32 * #ClutterUnits is a structure holding a logical distance value along with
33 * its type, expressed as a value of the #ClutterUnitType enumeration. It is
34 * possible to use #ClutterUnits to store a position or a size in units
35 * different than pixels, and convert them whenever needed (for instance
36 * inside the #ClutterActorClass.allocate() virtual function, or inside the
37 * #ClutterActorClass.get_preferred_width() and #ClutterActorClass.get_preferred_height()
38 * virtual functions.
39 *
40 * In order to register a #ClutterUnits property, the #ClutterParamSpecUnits
41 * #GParamSpec sub-class should be used:
42 *
43 * |[
44 * GParamSpec *pspec;
45 *
46 * pspec = clutter_param_spec_units ("active-width",
47 * "Width",
48 * "Width of the active area, in millimeters",
49 * CLUTTER_UNIT_MM,
50 * 0.0, 12.0,
51 * 12.0,
52 * G_PARAM_READWRITE);
53 * g_object_class_install_property (gobject_class, PROP_WIDTH, pspec);
54 * ]|
55 *
56 * A #GValue holding units can be manipulated using clutter_value_set_units()
57 * and clutter_value_get_units(). #GValue<!-- -->s containing a #ClutterUnits
58 * value can also be transformed to #GValue<!-- -->s initialized with
59 * %G_TYPE_INT, %G_TYPE_FLOAT and %G_TYPE_STRING through implicit conversion
60 * and using g_value_transform().
61 *
62 * #ClutterUnits is available since Clutter 1.0
63 */
64
65 #ifdef HAVE_CONFIG_H
66 #include "clutter-build-config.h"
67 #endif
68
69 #include <stdlib.h>
70
71 #include <glib-object.h>
72 #include <gobject/gvaluecollector.h>
73
74 #include "clutter-backend-private.h"
75 #include "clutter-interval.h"
76 #include "clutter-private.h"
77 #include "clutter-units.h"
78
79 #define DPI_FALLBACK (96.0)
80
81 #define FLOAT_EPSILON (1e-30)
82
83 static gfloat
units_mm_to_pixels(gfloat mm)84 units_mm_to_pixels (gfloat mm)
85 {
86 ClutterBackend *backend;
87 gdouble dpi;
88
89 backend = clutter_get_default_backend ();
90 dpi = clutter_backend_get_resolution (backend);
91 if (dpi < 0)
92 dpi = DPI_FALLBACK;
93
94 return mm * dpi / 25.4;
95 }
96
97 static gfloat
units_cm_to_pixels(gfloat cm)98 units_cm_to_pixels (gfloat cm)
99 {
100 return units_mm_to_pixels (cm * 10);
101 }
102
103 static gfloat
units_pt_to_pixels(gfloat pt)104 units_pt_to_pixels (gfloat pt)
105 {
106 ClutterBackend *backend;
107 gdouble dpi;
108
109 backend = clutter_get_default_backend ();
110 dpi = clutter_backend_get_resolution (backend);
111 if (dpi < 0)
112 dpi = DPI_FALLBACK;
113
114 return pt * dpi / 72.0;
115 }
116
117 static gfloat
units_em_to_pixels(const gchar * font_name,gfloat em)118 units_em_to_pixels (const gchar *font_name,
119 gfloat em)
120 {
121 ClutterBackend *backend = clutter_get_default_backend ();
122
123 if (font_name == NULL || *font_name == '\0')
124 return em * _clutter_backend_get_units_per_em (backend, NULL);
125 else
126 {
127 PangoFontDescription *font_desc;
128 gfloat res;
129
130 font_desc = pango_font_description_from_string (font_name);
131 if (font_desc == NULL)
132 res = -1.0;
133 else
134 {
135 res = em * _clutter_backend_get_units_per_em (backend, font_desc);
136
137 pango_font_description_free (font_desc);
138 }
139
140 return res;
141 }
142 }
143
144 /**
145 * clutter_units_from_mm:
146 * @units: (out caller-allocates): a #ClutterUnits
147 * @mm: millimeters
148 *
149 * Stores a value in millimiters inside @units
150 *
151 * Since: 1.0
152 */
153 void
clutter_units_from_mm(ClutterUnits * units,gfloat mm)154 clutter_units_from_mm (ClutterUnits *units,
155 gfloat mm)
156 {
157 ClutterBackend *backend;
158
159 g_return_if_fail (units != NULL);
160
161 backend = clutter_get_default_backend ();
162
163 units->unit_type = CLUTTER_UNIT_MM;
164 units->value = mm;
165 units->pixels = units_mm_to_pixels (mm);
166 units->pixels_set = TRUE;
167 units->serial = _clutter_backend_get_units_serial (backend);
168 }
169
170 /**
171 * clutter_units_from_cm:
172 * @units: (out caller-allocates): a #ClutterUnits
173 * @cm: centimeters
174 *
175 * Stores a value in centimeters inside @units
176 *
177 * Since: 1.2
178 */
179 void
clutter_units_from_cm(ClutterUnits * units,gfloat cm)180 clutter_units_from_cm (ClutterUnits *units,
181 gfloat cm)
182 {
183 ClutterBackend *backend;
184
185 g_return_if_fail (units != NULL);
186
187 backend = clutter_get_default_backend ();
188
189 units->unit_type = CLUTTER_UNIT_CM;
190 units->value = cm;
191 units->pixels = units_cm_to_pixels (cm);
192 units->pixels_set = TRUE;
193 units->serial = _clutter_backend_get_units_serial (backend);
194 }
195
196 /**
197 * clutter_units_from_pt:
198 * @units: (out caller-allocates): a #ClutterUnits
199 * @pt: typographic points
200 *
201 * Stores a value in typographic points inside @units
202 *
203 * Since: 1.0
204 */
205 void
clutter_units_from_pt(ClutterUnits * units,gfloat pt)206 clutter_units_from_pt (ClutterUnits *units,
207 gfloat pt)
208 {
209 ClutterBackend *backend;
210
211 g_return_if_fail (units != NULL);
212
213 backend = clutter_get_default_backend ();
214
215 units->unit_type = CLUTTER_UNIT_POINT;
216 units->value = pt;
217 units->pixels = units_pt_to_pixels (pt);
218 units->pixels_set = TRUE;
219 units->serial = _clutter_backend_get_units_serial (backend);
220 }
221
222 /**
223 * clutter_units_from_em:
224 * @units: (out caller-allocates): a #ClutterUnits
225 * @em: em
226 *
227 * Stores a value in em inside @units, using the default font
228 * name as returned by clutter_backend_get_font_name()
229 *
230 * Since: 1.0
231 */
232 void
clutter_units_from_em(ClutterUnits * units,gfloat em)233 clutter_units_from_em (ClutterUnits *units,
234 gfloat em)
235 {
236 ClutterBackend *backend;
237
238 g_return_if_fail (units != NULL);
239
240 backend = clutter_get_default_backend ();
241
242 units->unit_type = CLUTTER_UNIT_EM;
243 units->value = em;
244 units->pixels = units_em_to_pixels (NULL, em);
245 units->pixels_set = TRUE;
246 units->serial = _clutter_backend_get_units_serial (backend);
247 }
248
249 /**
250 * clutter_units_from_em_for_font:
251 * @units: (out caller-allocates): a #ClutterUnits
252 * @font_name: (allow-none): the font name and size
253 * @em: em
254 *
255 * Stores a value in em inside @units using @font_name
256 *
257 * Since: 1.0
258 */
259 void
clutter_units_from_em_for_font(ClutterUnits * units,const gchar * font_name,gfloat em)260 clutter_units_from_em_for_font (ClutterUnits *units,
261 const gchar *font_name,
262 gfloat em)
263 {
264 ClutterBackend *backend;
265
266 g_return_if_fail (units != NULL);
267
268 backend = clutter_get_default_backend ();
269
270 units->unit_type = CLUTTER_UNIT_EM;
271 units->value = em;
272 units->pixels = units_em_to_pixels (font_name, em);
273 units->pixels_set = TRUE;
274 units->serial = _clutter_backend_get_units_serial (backend);
275 }
276
277 /**
278 * clutter_units_from_pixels:
279 * @units: (out caller-allocates): a #ClutterUnits
280 * @px: pixels
281 *
282 * Stores a value in pixels inside @units
283 *
284 * Since: 1.0
285 */
286 void
clutter_units_from_pixels(ClutterUnits * units,gint px)287 clutter_units_from_pixels (ClutterUnits *units,
288 gint px)
289 {
290 ClutterBackend *backend;
291
292 g_return_if_fail (units != NULL);
293
294 backend = clutter_get_default_backend ();
295
296 units->unit_type = CLUTTER_UNIT_PIXEL;
297 units->value = px;
298 units->pixels = px;
299 units->pixels_set = TRUE;
300 units->serial = _clutter_backend_get_units_serial (backend);
301 }
302
303 /**
304 * clutter_units_get_unit_type:
305 * @units: a #ClutterUnits
306 *
307 * Retrieves the unit type of the value stored inside @units
308 *
309 * Return value: a unit type
310 *
311 * Since: 1.0
312 */
313 ClutterUnitType
clutter_units_get_unit_type(const ClutterUnits * units)314 clutter_units_get_unit_type (const ClutterUnits *units)
315 {
316 g_return_val_if_fail (units != NULL, CLUTTER_UNIT_PIXEL);
317
318 return units->unit_type;
319 }
320
321 /**
322 * clutter_units_get_unit_value:
323 * @units: a #ClutterUnits
324 *
325 * Retrieves the value stored inside @units
326 *
327 * Return value: the value stored inside a #ClutterUnits
328 *
329 * Since: 1.0
330 */
331 gfloat
clutter_units_get_unit_value(const ClutterUnits * units)332 clutter_units_get_unit_value (const ClutterUnits *units)
333 {
334 g_return_val_if_fail (units != NULL, 0.0);
335
336 return units->value;
337 }
338
339 /**
340 * clutter_units_copy:
341 * @units: the #ClutterUnits to copy
342 *
343 * Copies @units
344 *
345 * Return value: (transfer full): the newly created copy of a
346 * #ClutterUnits structure. Use clutter_units_free() to free
347 * the allocated resources
348 *
349 * Since: 1.0
350 */
351 ClutterUnits *
clutter_units_copy(const ClutterUnits * units)352 clutter_units_copy (const ClutterUnits *units)
353 {
354 if (units != NULL)
355 return g_slice_dup (ClutterUnits, units);
356
357 return NULL;
358 }
359
360 /**
361 * clutter_units_free:
362 * @units: the #ClutterUnits to free
363 *
364 * Frees the resources allocated by @units
365 *
366 * You should only call this function on a #ClutterUnits
367 * created using clutter_units_copy()
368 *
369 * Since: 1.0
370 */
371 void
clutter_units_free(ClutterUnits * units)372 clutter_units_free (ClutterUnits *units)
373 {
374 if (units != NULL)
375 g_slice_free (ClutterUnits, units);
376 }
377
378 /**
379 * clutter_units_to_pixels:
380 * @units: units to convert
381 *
382 * Converts a value in #ClutterUnits to pixels
383 *
384 * Return value: the value in pixels
385 *
386 * Since: 1.0
387 */
388 gfloat
clutter_units_to_pixels(ClutterUnits * units)389 clutter_units_to_pixels (ClutterUnits *units)
390 {
391 ClutterBackend *backend;
392
393 g_return_val_if_fail (units != NULL, 0.0);
394
395 /* if the backend settings changed we evict the cached value */
396 backend = clutter_get_default_backend ();
397 if (units->serial != _clutter_backend_get_units_serial (backend))
398 units->pixels_set = FALSE;
399
400 if (units->pixels_set)
401 return units->pixels;
402
403 switch (units->unit_type)
404 {
405 case CLUTTER_UNIT_MM:
406 units->pixels = units_mm_to_pixels (units->value);
407 break;
408
409 case CLUTTER_UNIT_CM:
410 units->pixels = units_cm_to_pixels (units->value);
411 break;
412
413 case CLUTTER_UNIT_POINT:
414 units->pixels = units_pt_to_pixels (units->value);
415 break;
416
417 case CLUTTER_UNIT_EM:
418 units->pixels = units_em_to_pixels (NULL, units->value);
419 break;
420
421 case CLUTTER_UNIT_PIXEL:
422 units->pixels = units->value;
423 break;
424 }
425
426 units->pixels_set = TRUE;
427 units->serial = _clutter_backend_get_units_serial (backend);
428
429 return units->pixels;
430 }
431
432 /**
433 * clutter_units_from_string:
434 * @units: (out caller-allocates): a #ClutterUnits
435 * @str: the string to convert
436 *
437 * Parses a value and updates @units with it
438 *
439 * A #ClutterUnits expressed in string should match:
440 *
441 * |[
442 * units: wsp* unit-value wsp* unit-name? wsp*
443 * unit-value: number
444 * unit-name: 'px' | 'pt' | 'mm' | 'em' | 'cm'
445 * number: digit+
446 * | digit* sep digit+
447 * sep: '.' | ','
448 * digit: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
449 * wsp: (#0x20 | #0x9 | #0xA | #0xB | #0xC | #0xD)+
450 * ]|
451 *
452 * For instance, these are valid strings:
453 *
454 * |[
455 * 10 px
456 * 5.1 em
457 * 24 pt
458 * 12.6 mm
459 * .3 cm
460 * ]|
461 *
462 * While these are not:
463 *
464 * |[
465 * 42 cats
466 * omg!1!ponies
467 * ]|
468 *
469 * If no unit is specified, pixels are assumed.
470 *
471 * Return value: %TRUE if the string was successfully parsed,
472 * and %FALSE otherwise
473 *
474 * Since: 1.0
475 */
476 gboolean
clutter_units_from_string(ClutterUnits * units,const gchar * str)477 clutter_units_from_string (ClutterUnits *units,
478 const gchar *str)
479 {
480 ClutterBackend *backend;
481 ClutterUnitType unit_type;
482 gfloat value;
483
484 g_return_val_if_fail (units != NULL, FALSE);
485 g_return_val_if_fail (str != NULL, FALSE);
486
487 /* strip leading space */
488 while (g_ascii_isspace (*str))
489 str++;
490
491 if (*str == '\0')
492 return FALSE;
493
494 /* integer part */
495 value = (gfloat) strtoul (str, (char **) &str, 10);
496
497 if (*str == '.' || *str == ',')
498 {
499 gfloat divisor = 0.1;
500
501 /* 5.cm is not a valid number */
502 if (!g_ascii_isdigit (*++str))
503 return FALSE;
504
505 while (g_ascii_isdigit (*str))
506 {
507 value += (*str - '0') * divisor;
508 divisor *= 0.1;
509 str++;
510 }
511 }
512
513 while (g_ascii_isspace (*str))
514 str++;
515
516 /* assume pixels by default, if no unit is specified */
517 if (*str == '\0')
518 unit_type = CLUTTER_UNIT_PIXEL;
519 else if (strncmp (str, "em", 2) == 0)
520 {
521 unit_type = CLUTTER_UNIT_EM;
522 str += 2;
523 }
524 else if (strncmp (str, "mm", 2) == 0)
525 {
526 unit_type = CLUTTER_UNIT_MM;
527 str += 2;
528 }
529 else if (strncmp (str, "cm", 2) == 0)
530 {
531 unit_type = CLUTTER_UNIT_CM;
532 str += 2;
533 }
534 else if (strncmp (str, "pt", 2) == 0)
535 {
536 unit_type = CLUTTER_UNIT_POINT;
537 str += 2;
538 }
539 else if (strncmp (str, "px", 2) == 0)
540 {
541 unit_type = CLUTTER_UNIT_PIXEL;
542 str += 2;
543 }
544 else
545 return FALSE;
546
547 /* ensure the unit is only followed by white space */
548 while (g_ascii_isspace (*str))
549 str++;
550 if (*str != '\0')
551 return FALSE;
552
553 backend = clutter_get_default_backend ();
554
555 units->unit_type = unit_type;
556 units->value = value;
557 units->pixels_set = FALSE;
558 units->serial = _clutter_backend_get_units_serial (backend);
559
560 return TRUE;
561 }
562
563 static const gchar *
clutter_unit_type_name(ClutterUnitType unit_type)564 clutter_unit_type_name (ClutterUnitType unit_type)
565 {
566 switch (unit_type)
567 {
568 case CLUTTER_UNIT_MM:
569 return "mm";
570
571 case CLUTTER_UNIT_CM:
572 return "cm";
573
574 case CLUTTER_UNIT_POINT:
575 return "pt";
576
577 case CLUTTER_UNIT_EM:
578 return "em";
579
580 case CLUTTER_UNIT_PIXEL:
581 return "px";
582 }
583
584 g_warning ("Invalid unit type %d", (int) unit_type);
585
586 return "<invalid>";
587 }
588
589 /**
590 * clutter_units_to_string:
591 * @units: a #ClutterUnits
592 *
593 * Converts @units into a string
594 *
595 * See clutter_units_from_string() for the units syntax and for
596 * examples of output
597 *
598 * Fractional values are truncated to the second decimal
599 * position for em, mm and cm, and to the first decimal position for
600 * typographic points. Pixels are integers.
601 *
602 * Return value: a newly allocated string containing the encoded
603 * #ClutterUnits value. Use free() to free the string
604 *
605 * Since: 1.0
606 */
607 gchar *
clutter_units_to_string(const ClutterUnits * units)608 clutter_units_to_string (const ClutterUnits *units)
609 {
610 const gchar *unit_name = NULL;
611 const gchar *fmt = NULL;
612 gchar buf[G_ASCII_DTOSTR_BUF_SIZE];
613
614 g_return_val_if_fail (units != NULL, NULL);
615
616 switch (units->unit_type)
617 {
618 /* special case: there is no such thing as "half a pixel", so
619 * we round up to the nearest integer using C default
620 */
621 case CLUTTER_UNIT_PIXEL:
622 return g_strdup_printf ("%d px", (int) units->value);
623
624 case CLUTTER_UNIT_MM:
625 unit_name = "mm";
626 fmt = "%.2f";
627 break;
628
629 case CLUTTER_UNIT_CM:
630 unit_name = "cm";
631 fmt = "%.2f";
632 break;
633
634 case CLUTTER_UNIT_POINT:
635 unit_name = "pt";
636 fmt = "%.1f";
637 break;
638
639 case CLUTTER_UNIT_EM:
640 unit_name = "em";
641 fmt = "%.2f";
642 break;
643
644 default:
645 g_assert_not_reached ();
646 break;
647 }
648
649 g_ascii_formatd (buf, G_ASCII_DTOSTR_BUF_SIZE, fmt, units->value);
650
651 return g_strconcat (buf, " ", unit_name, NULL);
652 }
653
654 /*
655 * ClutterInterval integration
656 */
657
658 static gboolean
clutter_units_progress(const GValue * a,const GValue * b,gdouble progress,GValue * retval)659 clutter_units_progress (const GValue *a,
660 const GValue *b,
661 gdouble progress,
662 GValue *retval)
663 {
664 ClutterUnits *a_units = (ClutterUnits *) clutter_value_get_units (a);
665 ClutterUnits *b_units = (ClutterUnits *) clutter_value_get_units (b);
666 ClutterUnits res;
667 gfloat a_px, b_px, value;
668
669 a_px = clutter_units_to_pixels (a_units);
670 b_px = clutter_units_to_pixels (b_units);
671 value = progress * (b_px - a_px) + a_px;
672
673 clutter_units_from_pixels (&res, value);
674 clutter_value_set_units (retval, &res);
675
676 return TRUE;
677 }
678
679 /*
680 * GValue and GParamSpec integration
681 */
682
683 /* units to integer */
684 static void
clutter_value_transform_units_int(const GValue * src,GValue * dest)685 clutter_value_transform_units_int (const GValue *src,
686 GValue *dest)
687 {
688 dest->data[0].v_int = clutter_units_to_pixels (src->data[0].v_pointer);
689 }
690
691 /* integer to units */
692 static void
clutter_value_transform_int_units(const GValue * src,GValue * dest)693 clutter_value_transform_int_units (const GValue *src,
694 GValue *dest)
695 {
696 clutter_units_from_pixels (dest->data[0].v_pointer, src->data[0].v_int);
697 }
698
699 /* units to float */
700 static void
clutter_value_transform_units_float(const GValue * src,GValue * dest)701 clutter_value_transform_units_float (const GValue *src,
702 GValue *dest)
703 {
704 dest->data[0].v_float = clutter_units_to_pixels (src->data[0].v_pointer);
705 }
706
707 /* float to units */
708 static void
clutter_value_transform_float_units(const GValue * src,GValue * dest)709 clutter_value_transform_float_units (const GValue *src,
710 GValue *dest)
711 {
712 clutter_units_from_pixels (dest->data[0].v_pointer, src->data[0].v_float);
713 }
714
715 /* units to string */
716 static void
clutter_value_transform_units_string(const GValue * src,GValue * dest)717 clutter_value_transform_units_string (const GValue *src,
718 GValue *dest)
719 {
720 gchar *string = clutter_units_to_string (src->data[0].v_pointer);
721
722 g_value_take_string (dest, string);
723 }
724
725 /* string to units */
726 static void
clutter_value_transform_string_units(const GValue * src,GValue * dest)727 clutter_value_transform_string_units (const GValue *src,
728 GValue *dest)
729 {
730 ClutterUnits units = { CLUTTER_UNIT_PIXEL, 0.0f };
731
732 clutter_units_from_string (&units, g_value_get_string (src));
733
734 clutter_value_set_units (dest, &units);
735 }
736
737 G_DEFINE_BOXED_TYPE_WITH_CODE (ClutterUnits, clutter_units,
738 clutter_units_copy,
739 clutter_units_free,
740 CLUTTER_REGISTER_VALUE_TRANSFORM_TO (G_TYPE_INT, clutter_value_transform_units_int)
741 CLUTTER_REGISTER_VALUE_TRANSFORM_TO (G_TYPE_FLOAT, clutter_value_transform_units_float)
742 CLUTTER_REGISTER_VALUE_TRANSFORM_TO (G_TYPE_STRING, clutter_value_transform_units_string)
743 CLUTTER_REGISTER_VALUE_TRANSFORM_FROM (G_TYPE_INT, clutter_value_transform_int_units)
744 CLUTTER_REGISTER_VALUE_TRANSFORM_FROM (G_TYPE_FLOAT, clutter_value_transform_float_units)
745 CLUTTER_REGISTER_VALUE_TRANSFORM_FROM (G_TYPE_STRING, clutter_value_transform_string_units)
746 CLUTTER_REGISTER_INTERVAL_PROGRESS (clutter_units_progress));
747
748 /**
749 * clutter_value_set_units:
750 * @value: a #GValue initialized to %CLUTTER_TYPE_UNITS
751 * @units: the units to set
752 *
753 * Sets @value to @units
754 *
755 * Since: 0.8
756 */
757 void
clutter_value_set_units(GValue * value,const ClutterUnits * units)758 clutter_value_set_units (GValue *value,
759 const ClutterUnits *units)
760 {
761 g_return_if_fail (CLUTTER_VALUE_HOLDS_UNITS (value));
762
763 value->data[0].v_pointer = clutter_units_copy (units);
764 }
765
766 /**
767 * clutter_value_get_units:
768 * @value: a #GValue initialized to %CLUTTER_TYPE_UNITS
769 *
770 * Gets the #ClutterUnits contained in @value.
771 *
772 * Return value: the units inside the passed #GValue
773 *
774 * Since: 0.8
775 */
776 const ClutterUnits *
clutter_value_get_units(const GValue * value)777 clutter_value_get_units (const GValue *value)
778 {
779 g_return_val_if_fail (CLUTTER_VALUE_HOLDS_UNITS (value), NULL);
780
781 return value->data[0].v_pointer;
782 }
783
784 static void
param_units_init(GParamSpec * pspec)785 param_units_init (GParamSpec *pspec)
786 {
787 ClutterParamSpecUnits *uspec = CLUTTER_PARAM_SPEC_UNITS (pspec);
788
789 uspec->minimum = -G_MAXFLOAT;
790 uspec->maximum = G_MAXFLOAT;
791 uspec->default_value = 0.0f;
792 uspec->default_type = CLUTTER_UNIT_PIXEL;
793 }
794
795 static void
param_units_set_default(GParamSpec * pspec,GValue * value)796 param_units_set_default (GParamSpec *pspec,
797 GValue *value)
798 {
799 ClutterParamSpecUnits *uspec = CLUTTER_PARAM_SPEC_UNITS (pspec);
800 ClutterUnits units;
801
802 units.unit_type = uspec->default_type;
803 units.value = uspec->default_value;
804 units.pixels_set = FALSE;
805
806 clutter_value_set_units (value, &units);
807 }
808
809 static gboolean
param_units_validate(GParamSpec * pspec,GValue * value)810 param_units_validate (GParamSpec *pspec,
811 GValue *value)
812 {
813 ClutterParamSpecUnits *uspec = CLUTTER_PARAM_SPEC_UNITS (pspec);
814 ClutterUnits *units = value->data[0].v_pointer;
815 ClutterUnitType otype = units->unit_type;
816 gfloat oval = units->value;
817
818 g_assert (CLUTTER_IS_PARAM_SPEC_UNITS (pspec));
819
820 if (otype != uspec->default_type)
821 {
822 gchar *str = clutter_units_to_string (units);
823
824 g_warning ("The units value of '%s' does not have the same unit "
825 "type as declared by the ClutterParamSpecUnits of '%s'",
826 str,
827 clutter_unit_type_name (uspec->default_type));
828
829 free (str);
830
831 return FALSE;
832 }
833
834 units->value = CLAMP (units->value,
835 uspec->minimum,
836 uspec->maximum);
837
838 return units->value != oval;
839 }
840
841 static gint
param_units_values_cmp(GParamSpec * pspec,const GValue * value1,const GValue * value2)842 param_units_values_cmp (GParamSpec *pspec,
843 const GValue *value1,
844 const GValue *value2)
845 {
846 ClutterUnits *units1 = value1->data[0].v_pointer;
847 ClutterUnits *units2 = value2->data[0].v_pointer;
848 gfloat v1, v2;
849
850 if (units1->unit_type == units2->unit_type)
851 {
852 v1 = units1->value;
853 v2 = units2->value;
854 }
855 else
856 {
857 v1 = clutter_units_to_pixels (units1);
858 v2 = clutter_units_to_pixels (units2);
859 }
860
861 if (v1 < v2)
862 return - (v2 - v1 > FLOAT_EPSILON);
863 else
864 return v1 - v2 > FLOAT_EPSILON;
865 }
866
867 GType
clutter_param_units_get_type(void)868 clutter_param_units_get_type (void)
869 {
870 static GType pspec_type = 0;
871
872 if (G_UNLIKELY (pspec_type == 0))
873 {
874 const GParamSpecTypeInfo pspec_info = {
875 sizeof (ClutterParamSpecUnits),
876 16,
877 param_units_init,
878 CLUTTER_TYPE_UNITS,
879 NULL,
880 param_units_set_default,
881 param_units_validate,
882 param_units_values_cmp,
883 };
884
885 pspec_type = g_param_type_register_static (I_("ClutterParamSpecUnit"),
886 &pspec_info);
887 }
888
889 return pspec_type;
890 }
891
892 /**
893 * clutter_param_spec_units: (skip)
894 * @name: name of the property
895 * @nick: short name
896 * @blurb: description (can be translatable)
897 * @default_type: the default type for the #ClutterUnits
898 * @minimum: lower boundary
899 * @maximum: higher boundary
900 * @default_value: default value
901 * @flags: flags for the param spec
902 *
903 * Creates a #GParamSpec for properties using #ClutterUnits.
904 *
905 * Return value: the newly created #GParamSpec
906 *
907 * Since: 1.0
908 */
909 GParamSpec *
clutter_param_spec_units(const gchar * name,const gchar * nick,const gchar * blurb,ClutterUnitType default_type,gfloat minimum,gfloat maximum,gfloat default_value,GParamFlags flags)910 clutter_param_spec_units (const gchar *name,
911 const gchar *nick,
912 const gchar *blurb,
913 ClutterUnitType default_type,
914 gfloat minimum,
915 gfloat maximum,
916 gfloat default_value,
917 GParamFlags flags)
918 {
919 ClutterParamSpecUnits *uspec;
920
921 g_return_val_if_fail (default_value >= minimum && default_value <= maximum,
922 NULL);
923
924 uspec = g_param_spec_internal (CLUTTER_TYPE_PARAM_UNITS,
925 name, nick, blurb,
926 flags);
927
928 uspec->default_type = default_type;
929 uspec->minimum = minimum;
930 uspec->maximum = maximum;
931 uspec->default_value = default_value;
932
933 return G_PARAM_SPEC (uspec);
934 }
935