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