]> Pileus Git - ~andy/gtk/blob - gtk/gtkstyleproperties.c
styleproperties: constify gtk_style_property_lookup()
[~andy/gtk] / gtk / gtkstyleproperties.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 2010 Carlos Garnacho <carlosg@gnome.org>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #include "config.h"
21
22 #include "gtkstyleproperties.h"
23
24 #include <stdlib.h>
25 #include <gobject/gvaluecollector.h>
26 #include <cairo-gobject.h>
27
28 #include "gtktypebuiltins.h"
29 #include "gtkstyleprovider.h"
30 #include "gtksymboliccolor.h"
31 #include "gtkprivate.h"
32 #include "gtkthemingengine.h"
33 #include "gtkanimationdescription.h"
34 #include "gtkborder.h"
35 #include "gtkgradient.h"
36 #include "gtk9slice.h"
37 #include "gtkshadowprivate.h"
38 #include "gtkintl.h"
39
40 /**
41  * SECTION:gtkstyleproperties
42  * @Short_description: Store for style property information
43  * @Title: GtkStyleProperties
44  *
45  * GtkStyleProperties provides the storage for style information
46  * that is used by #GtkStyleContext and other #GtkStyleProvider
47  * implementations.
48  *
49  * Before style properties can be stored in GtkStyleProperties, they
50  * must be registered with gtk_style_properties_register_property().
51  *
52  * Unless you are writing a #GtkStyleProvider implementation, you
53  * are unlikely to use this API directly, as gtk_style_context_get()
54  * and its variants are the preferred way to access styling information
55  * from widget implementations and theming engine implementations
56  * should use the APIs provided by #GtkThemingEngine instead.
57  */
58
59 typedef struct GtkStylePropertiesPrivate GtkStylePropertiesPrivate;
60 typedef struct PropertyData PropertyData;
61 typedef struct _GtkStyleProperty GtkStyleProperty;
62 typedef struct ValueData ValueData;
63
64 struct _GtkStyleProperty
65 {
66   GParamSpec *pspec;
67   GtkStylePropertyParser parse_func;
68 };
69
70 struct ValueData
71 {
72   GtkStateFlags state;
73   GValue value;
74 };
75
76 struct PropertyData
77 {
78   GArray *values;
79 };
80
81 struct GtkStylePropertiesPrivate
82 {
83   GHashTable *color_map;
84   GHashTable *properties;
85 };
86
87 static GHashTable *properties = NULL;
88
89 static void gtk_style_properties_provider_init (GtkStyleProviderIface *iface);
90 static void gtk_style_properties_finalize      (GObject      *object);
91
92
93 G_DEFINE_TYPE_EXTENDED (GtkStyleProperties, gtk_style_properties, G_TYPE_OBJECT, 0,
94                         G_IMPLEMENT_INTERFACE (GTK_TYPE_STYLE_PROVIDER,
95                                                gtk_style_properties_provider_init));
96
97 static void
98 gtk_style_properties_class_init (GtkStylePropertiesClass *klass)
99 {
100   GObjectClass *object_class = G_OBJECT_CLASS (klass);
101   GParamSpec *pspec;
102
103   object_class->finalize = gtk_style_properties_finalize;
104
105   /* Initialize default property set */
106   pspec = g_param_spec_boxed ("color",
107                               "Foreground color",
108                               "Foreground color",
109                               GDK_TYPE_RGBA, 0);
110   gtk_style_param_set_inherit (pspec, TRUE);
111   gtk_style_properties_register_property (NULL, pspec);
112
113   pspec = g_param_spec_boxed ("font",
114                               "Font Description",
115                               "Font Description",
116                               PANGO_TYPE_FONT_DESCRIPTION, 0);
117   gtk_style_param_set_inherit (pspec, TRUE);
118   gtk_style_properties_register_property (NULL, pspec);
119
120   pspec = g_param_spec_boxed ("text-shadow",
121                               "Text shadow",
122                               "Text shadow",
123                               GTK_TYPE_SHADOW, 0);
124   gtk_style_param_set_inherit (pspec, TRUE);
125   gtk_style_properties_register_property (NULL, pspec);
126
127   gtk_style_properties_register_property (NULL,
128                                           g_param_spec_boxed ("margin",
129                                                               "Margin",
130                                                               "Margin",
131                                                               GTK_TYPE_BORDER, 0));
132   gtk_style_properties_register_property (NULL,
133                                           g_param_spec_boxed ("padding",
134                                                               "Padding",
135                                                               "Padding",
136                                                               GTK_TYPE_BORDER, 0));
137   gtk_style_properties_register_property (NULL,
138                                           g_param_spec_boxed ("border-width",
139                                                               "Border width",
140                                                               "Border width, in pixels",
141                                                               GTK_TYPE_BORDER, 0));
142   gtk_style_properties_register_property (NULL,
143                                           g_param_spec_int ("border-radius",
144                                                             "Border radius",
145                                                             "Border radius, in pixels",
146                                                             0, G_MAXINT, 0, 0));
147   gtk_style_properties_register_property (NULL,
148                                           g_param_spec_enum ("border-style",
149                                                              "Border style",
150                                                              "Border style",
151                                                              GTK_TYPE_BORDER_STYLE,
152                                                              GTK_BORDER_STYLE_NONE, 0));
153   gtk_style_properties_register_property (NULL,
154                                           g_param_spec_boxed ("border-color",
155                                                               "Border color",
156                                                               "Border color",
157                                                               GDK_TYPE_RGBA, 0));
158   gtk_style_properties_register_property (NULL,
159                                           g_param_spec_boxed ("background-color",
160                                                               "Background color",
161                                                               "Background color",
162                                                               GDK_TYPE_RGBA, 0));
163   gtk_style_properties_register_property (NULL,
164                                           g_param_spec_boxed ("background-image",
165                                                               "Background Image",
166                                                               "Background Image",
167                                                               CAIRO_GOBJECT_TYPE_PATTERN, 0));
168   gtk_style_properties_register_property (NULL,
169                                           g_param_spec_boxed ("border-image",
170                                                               "Border Image",
171                                                               "Border Image",
172                                                               GTK_TYPE_9SLICE, 0));
173   gtk_style_properties_register_property (NULL,
174                                           g_param_spec_object ("engine",
175                                                                "Theming Engine",
176                                                                "Theming Engine",
177                                                                GTK_TYPE_THEMING_ENGINE, 0));
178   gtk_style_properties_register_property (NULL,
179                                           g_param_spec_boxed ("transition",
180                                                               "Transition animation description",
181                                                               "Transition animation description",
182                                                               GTK_TYPE_ANIMATION_DESCRIPTION, 0));
183
184   /* Private property holding the binding sets */
185   gtk_style_properties_register_property (NULL,
186                                           g_param_spec_boxed ("gtk-key-bindings",
187                                                               "Key bindings",
188                                                               "Key bindings",
189                                                               G_TYPE_PTR_ARRAY, 0));
190
191   g_type_class_add_private (object_class, sizeof (GtkStylePropertiesPrivate));
192 }
193
194 static PropertyData *
195 property_data_new (void)
196 {
197   PropertyData *data;
198
199   data = g_slice_new0 (PropertyData);
200   data->values = g_array_new (FALSE, FALSE, sizeof (ValueData));
201
202   return data;
203 }
204
205 static void
206 property_data_remove_values (PropertyData *data)
207 {
208   guint i;
209
210   for (i = 0; i < data->values->len; i++)
211     {
212       ValueData *value_data;
213
214       value_data = &g_array_index (data->values, ValueData, i);
215
216       if (G_IS_VALUE (&value_data->value))
217         g_value_unset (&value_data->value);
218     }
219
220   if (data->values->len > 0)
221     g_array_remove_range (data->values, 0, data->values->len);
222 }
223
224 static void
225 property_data_free (PropertyData *data)
226 {
227   property_data_remove_values (data);
228   g_array_free (data->values, TRUE);
229   g_slice_free (PropertyData, data);
230 }
231
232 static gboolean
233 property_data_find_position (PropertyData  *data,
234                              GtkStateFlags  state,
235                              guint         *pos)
236 {
237   gint min, max, mid;
238   gboolean found = FALSE;
239   guint position;
240
241   if (pos)
242     *pos = 0;
243
244   if (data->values->len == 0)
245     return FALSE;
246
247   /* Find position for the given state, or the position where
248    * it would be if not found, the array is ordered by the
249    * state flags.
250    */
251   min = 0;
252   max = data->values->len - 1;
253
254   do
255     {
256       ValueData *value_data;
257
258       mid = (min + max) / 2;
259       value_data = &g_array_index (data->values, ValueData, mid);
260
261       if (value_data->state == state)
262         {
263           found = TRUE;
264           position = mid;
265         }
266       else if (value_data->state < state)
267           position = min = mid + 1;
268       else
269         {
270           max = mid - 1;
271           position = mid;
272         }
273     }
274   while (!found && min <= max);
275
276   if (pos)
277     *pos = position;
278
279   return found;
280 }
281
282 static GValue *
283 property_data_get_value (PropertyData  *data,
284                          GtkStateFlags  state)
285 {
286   ValueData *val_data;
287   guint pos;
288
289   if (!property_data_find_position (data, state, &pos))
290     {
291       ValueData new = { 0 };
292
293       new.state = state;
294       g_array_insert_val (data->values, pos, new);
295     }
296
297   val_data = &g_array_index (data->values, ValueData, pos);
298
299   return &val_data->value;
300 }
301
302 static GValue *
303 property_data_match_state (PropertyData  *data,
304                            GtkStateFlags  state)
305 {
306   guint pos;
307   gint i;
308
309   if (property_data_find_position (data, state, &pos))
310     {
311       ValueData *val_data;
312
313       /* Exact match */
314       val_data = &g_array_index (data->values, ValueData, pos);
315       return &val_data->value;
316     }
317
318   if (pos >= data->values->len)
319     pos = data->values->len - 1;
320
321   /* No exact match, go downwards the list to find
322    * the closest match to the given state flags, as
323    * a side effect, there is an implicit precedence
324    * of higher flags over the smaller ones.
325    */
326   for (i = pos; i >= 0; i--)
327     {
328       ValueData *val_data;
329
330       val_data = &g_array_index (data->values, ValueData, i);
331
332        /* Check whether any of the requested
333         * flags are set, and no other flags are.
334         *
335         * Also, no flags acts as a wildcard, such
336         * value should be always in the first position
337         * in the array (if present) anyways.
338         */
339       if (val_data->state == 0 ||
340           ((val_data->state & state) != 0 &&
341            (val_data->state & ~state) == 0))
342         return &val_data->value;
343     }
344
345   return NULL;
346 }
347
348 static void
349 gtk_style_properties_init (GtkStyleProperties *props)
350 {
351   GtkStylePropertiesPrivate *priv;
352
353   priv = props->priv = G_TYPE_INSTANCE_GET_PRIVATE (props,
354                                                     GTK_TYPE_STYLE_PROPERTIES,
355                                                     GtkStylePropertiesPrivate);
356
357   priv->properties = g_hash_table_new_full (NULL, NULL, NULL,
358                                             (GDestroyNotify) property_data_free);
359 }
360
361 static void
362 gtk_style_properties_finalize (GObject *object)
363 {
364   GtkStylePropertiesPrivate *priv;
365   GtkStyleProperties *props;
366
367   props = GTK_STYLE_PROPERTIES (object);
368   priv = props->priv;
369   g_hash_table_destroy (priv->properties);
370
371   if (priv->color_map)
372     g_hash_table_destroy (priv->color_map);
373
374   G_OBJECT_CLASS (gtk_style_properties_parent_class)->finalize (object);
375 }
376
377 GtkStyleProperties *
378 gtk_style_properties_get_style (GtkStyleProvider *provider,
379                                 GtkWidgetPath    *path)
380 {
381   /* Return style set itself */
382   return g_object_ref (provider);
383 }
384
385 static void
386 gtk_style_properties_provider_init (GtkStyleProviderIface *iface)
387 {
388   iface->get_style = gtk_style_properties_get_style;
389 }
390
391 static const GtkStyleProperty *
392 gtk_style_property_lookup (const char *name)
393 {
394   if (!properties)
395     return NULL;
396
397   return g_hash_table_lookup (properties, name);
398 }
399
400 /* Property registration functions */
401
402 /**
403  * gtk_style_properties_register_property: (skip)
404  * @parse_func: parsing function to use, or %NULL
405  * @pspec: the #GParamSpec for the new property
406  *
407  * Registers a property so it can be used in the CSS file format.
408  * This function is the low-level equivalent of
409  * gtk_theming_engine_register_property(), if you are implementing
410  * a theming engine, you want to use that function instead.
411  *
412  * Since: 3.0
413  **/
414 void
415 gtk_style_properties_register_property (GtkStylePropertyParser  parse_func,
416                                         GParamSpec             *pspec)
417 {
418   const GtkStyleProperty *existing;
419   GtkStyleProperty *node;
420
421   g_return_if_fail (G_IS_PARAM_SPEC (pspec));
422
423   /* stuff is never freed, so no need for free functions */
424   if (G_UNLIKELY (!properties))
425     properties = g_hash_table_new (g_str_hash, g_str_equal);
426
427   existing = gtk_style_property_lookup (pspec->name);
428   if (existing != NULL)
429     {
430       g_warning ("Property \"%s\" was already registered with type %s",
431                  pspec->name, g_type_name (existing->pspec->value_type));
432       return;
433     }
434
435   node = g_slice_new0 (GtkStyleProperty);
436   node->pspec = pspec;
437   node->parse_func = parse_func;
438
439   g_hash_table_insert (properties, pspec->name, node);
440 }
441
442 /**
443  * gtk_style_properties_lookup_property: (skip)
444  * @property_name: property name to look up
445  * @parse_func: (out): return location for the parse function
446  * @pspec: (out) (transfer none): return location for the #GParamSpec
447  *
448  * Returns %TRUE if a property has been registered, if @pspec or
449  * @parse_func are not %NULL, the #GParamSpec and parsing function
450  * will be respectively returned.
451  *
452  * Returns: %TRUE if the property is registered, %FALSE otherwise
453  *
454  * Since: 3.0
455  **/
456 gboolean
457 gtk_style_properties_lookup_property (const gchar             *property_name,
458                                       GtkStylePropertyParser  *parse_func,
459                                       GParamSpec             **pspec)
460 {
461   const GtkStyleProperty *node;
462   GtkStylePropertiesClass *klass;
463   gboolean found = FALSE;
464
465   g_return_val_if_fail (property_name != NULL, FALSE);
466
467   klass = g_type_class_ref (GTK_TYPE_STYLE_PROPERTIES);
468
469   node = gtk_style_property_lookup (property_name);
470
471   if (node)
472     {
473       if (pspec)
474         *pspec = node->pspec;
475
476       if (parse_func)
477         *parse_func = node->parse_func;
478
479       found = TRUE;
480     }
481
482   g_type_class_unref (klass);
483
484   return found;
485 }
486
487 /* GParamSpec functionality */
488
489 enum {
490   GTK_STYLE_PROPERTY_INHERIT = 1 << G_PARAM_USER_SHIFT
491 };
492
493 /**
494  * gtk_style_param_set_inherit:
495  * @pspec: A style param
496  * @inherit: whether the @pspec value should be inherited
497  *
498  * Sets whether a param spec installed with function such as
499  * gtk_style_properties_register_property() or
500  * gtk_widget_class_install_style_property() should inherit their
501  * value from the parent widget if it is not set instead of using
502  * the default value of @pspec. See the
503  * <ulink url="http://www.w3.org/TR/CSS21/cascade.html#inheritance">
504  * CSS specification's description of inheritance</ulink> for a
505  * longer description of this concept.
506  *
507  * By default, param specs do not inherit their value.
508  **/
509 void
510 gtk_style_param_set_inherit (GParamSpec *pspec,
511                              gboolean    inherit)
512 {
513   if (inherit)
514     pspec->flags |= GTK_STYLE_PROPERTY_INHERIT;
515   else
516     pspec->flags &= ~GTK_STYLE_PROPERTY_INHERIT;
517 }
518
519 /**
520  * gtk_style_param_get_inherit:
521  * @pspec: a style param
522  *
523  * Checks if the value of this param should be inherited from the parent
524  * #GtkWidget instead of using the default value when it has not been
525  * specified. See gtk_style_param_set_inherit() for more details.
526  *
527  * Returns: %TRUE if the param should inherit its value
528  **/
529 gboolean
530 gtk_style_param_get_inherit (GParamSpec *pspec)
531 {
532   return (pspec->flags & GTK_STYLE_PROPERTY_INHERIT) ? TRUE : FALSE;
533 }
534
535 /* GtkStyleProperties methods */
536
537 /**
538  * gtk_style_properties_new:
539  *
540  * Returns a newly created #GtkStyleProperties
541  *
542  * Returns: a new #GtkStyleProperties
543  **/
544 GtkStyleProperties *
545 gtk_style_properties_new (void)
546 {
547   return g_object_new (GTK_TYPE_STYLE_PROPERTIES, NULL);
548 }
549
550 /**
551  * gtk_style_properties_map_color:
552  * @props: a #GtkStyleProperties
553  * @name: color name
554  * @color: #GtkSymbolicColor to map @name to
555  *
556  * Maps @color so it can be referenced by @name. See
557  * gtk_style_properties_lookup_color()
558  *
559  * Since: 3.0
560  **/
561 void
562 gtk_style_properties_map_color (GtkStyleProperties *props,
563                                 const gchar        *name,
564                                 GtkSymbolicColor   *color)
565 {
566   GtkStylePropertiesPrivate *priv;
567
568   g_return_if_fail (GTK_IS_STYLE_PROPERTIES (props));
569   g_return_if_fail (name != NULL);
570   g_return_if_fail (color != NULL);
571
572   priv = props->priv;
573
574   if (G_UNLIKELY (!priv->color_map))
575     priv->color_map = g_hash_table_new_full (g_str_hash,
576                                              g_str_equal,
577                                              (GDestroyNotify) g_free,
578                                              (GDestroyNotify) gtk_symbolic_color_unref);
579
580   g_hash_table_replace (priv->color_map,
581                         g_strdup (name),
582                         gtk_symbolic_color_ref (color));
583 }
584
585 /**
586  * gtk_style_properties_lookup_color:
587  * @props: a #GtkStyleProperties
588  * @name: color name to lookup
589  *
590  * Returns the symbolic color that is mapped
591  * to @name.
592  *
593  * Returns: (transfer none): The mapped color
594  *
595  * Since: 3.0
596  **/
597 GtkSymbolicColor *
598 gtk_style_properties_lookup_color (GtkStyleProperties *props,
599                                    const gchar        *name)
600 {
601   GtkStylePropertiesPrivate *priv;
602
603   g_return_val_if_fail (GTK_IS_STYLE_PROPERTIES (props), NULL);
604   g_return_val_if_fail (name != NULL, NULL);
605
606   priv = props->priv;
607
608   if (!priv->color_map)
609     return NULL;
610
611   return g_hash_table_lookup (priv->color_map, name);
612 }
613
614 void
615 _gtk_style_properties_set_property_by_pspec (GtkStyleProperties *props,
616                                              GParamSpec         *pspec,
617                                              GtkStateFlags       state,
618                                              const GValue       *value)
619 {
620   GtkStylePropertiesPrivate *priv;
621   PropertyData *prop;
622   GType value_type;
623   GValue *val;
624
625   value_type = G_VALUE_TYPE (value);
626
627   if (pspec->value_type == GDK_TYPE_RGBA ||
628       pspec->value_type == GDK_TYPE_COLOR)
629     {
630       /* Allow GtkSymbolicColor as well */
631       g_return_if_fail (value_type == GDK_TYPE_RGBA ||
632                         value_type == GDK_TYPE_COLOR ||
633                         value_type == GTK_TYPE_SYMBOLIC_COLOR);
634     }
635   else if (pspec->value_type == CAIRO_GOBJECT_TYPE_PATTERN)
636     {
637       /* Allow GtkGradient as a substitute */
638       g_return_if_fail (value_type == CAIRO_GOBJECT_TYPE_PATTERN ||
639                         value_type == GTK_TYPE_GRADIENT);
640     }
641   else
642     g_return_if_fail (pspec->value_type == value_type);
643
644   priv = props->priv;
645   prop = g_hash_table_lookup (priv->properties, pspec);
646
647   if (!prop)
648     {
649       prop = property_data_new ();
650       g_hash_table_insert (priv->properties, pspec, prop);
651     }
652
653   val = property_data_get_value (prop, state);
654
655   if (G_VALUE_TYPE (val) == value_type)
656     g_value_reset (val);
657   else
658     {
659       if (G_IS_VALUE (val))
660         g_value_unset (val);
661
662       g_value_init (val, value_type);
663     }
664
665   g_value_copy (value, val);
666 }
667
668 /**
669  * gtk_style_properties_set_property:
670  * @props: a #GtkStyleProperties
671  * @property: styling property to set
672  * @state: state to set the value for
673  * @value: new value for the property
674  *
675  * Sets a styling property in @props.
676  *
677  * Since: 3.0
678  **/
679 void
680 gtk_style_properties_set_property (GtkStyleProperties *props,
681                                    const gchar        *property,
682                                    GtkStateFlags       state,
683                                    const GValue       *value)
684 {
685   const GtkStyleProperty *node;
686
687   g_return_if_fail (GTK_IS_STYLE_PROPERTIES (props));
688   g_return_if_fail (property != NULL);
689   g_return_if_fail (value != NULL);
690
691   node = gtk_style_property_lookup (property);
692
693   if (!node)
694     {
695       g_warning ("Style property \"%s\" is not registered", property);
696       return;
697     }
698
699   _gtk_style_properties_set_property_by_pspec (props,
700                                                node->pspec,
701                                                state,
702                                                value);
703 }
704
705 /**
706  * gtk_style_properties_set_valist:
707  * @props: a #GtkStyleProperties
708  * @state: state to set the values for
709  * @args: va_list of property name/value pairs, followed by %NULL
710  *
711  * Sets several style properties on @props.
712  *
713  * Since: 3.0
714  **/
715 void
716 gtk_style_properties_set_valist (GtkStyleProperties *props,
717                                  GtkStateFlags       state,
718                                  va_list             args)
719 {
720   GtkStylePropertiesPrivate *priv;
721   const gchar *property_name;
722
723   g_return_if_fail (GTK_IS_STYLE_PROPERTIES (props));
724
725   priv = props->priv;
726   property_name = va_arg (args, const gchar *);
727
728   while (property_name)
729     {
730       const GtkStyleProperty *node;
731       PropertyData *prop;
732       gchar *error = NULL;
733       GValue *val;
734
735       node = gtk_style_property_lookup (property_name);
736
737       if (!node)
738         {
739           g_warning ("Style property \"%s\" is not registered", property_name);
740           break;
741         }
742
743       prop = g_hash_table_lookup (priv->properties, node->pspec);
744
745       if (!prop)
746         {
747           prop = property_data_new ();
748           g_hash_table_insert (priv->properties, node->pspec, prop);
749         }
750
751       val = property_data_get_value (prop, state);
752
753       if (G_IS_VALUE (val))
754         g_value_unset (val);
755
756       G_VALUE_COLLECT_INIT (val, node->pspec->value_type,
757                             args, 0, &error);
758       if (error)
759         {
760           g_warning ("Could not set style property \"%s\": %s", property_name, error);
761           g_value_unset (val);
762           g_free (error);
763           break;
764         }
765
766       property_name = va_arg (args, const gchar *);
767     }
768 }
769
770 /**
771  * gtk_style_properties_set:
772  * @props: a #GtkStyleProperties
773  * @state: state to set the values for
774  * @...: property name/value pairs, followed by %NULL
775  *
776  * Sets several style properties on @props.
777  *
778  * Since: 3.0
779  **/
780 void
781 gtk_style_properties_set (GtkStyleProperties *props,
782                           GtkStateFlags       state,
783                           ...)
784 {
785   va_list args;
786
787   g_return_if_fail (GTK_IS_STYLE_PROPERTIES (props));
788
789   va_start (args, state);
790   gtk_style_properties_set_valist (props, state, args);
791   va_end (args);
792 }
793
794 static gboolean
795 resolve_color (GtkStyleProperties *props,
796                GValue             *value)
797 {
798   GdkRGBA color;
799
800   /* Resolve symbolic color to GdkRGBA */
801   if (!gtk_symbolic_color_resolve (g_value_get_boxed (value), props, &color))
802     return FALSE;
803
804   /* Store it back, this is where GdkRGBA caching happens */
805   g_value_unset (value);
806   g_value_init (value, GDK_TYPE_RGBA);
807   g_value_set_boxed (value, &color);
808
809   return TRUE;
810 }
811
812 static gboolean
813 resolve_color_rgb (GtkStyleProperties *props,
814                    GValue             *value)
815 {
816   GdkColor color = { 0 };
817   GdkRGBA rgba;
818
819   if (!gtk_symbolic_color_resolve (g_value_get_boxed (value), props, &rgba))
820     return FALSE;
821
822   color.red = rgba.red * 65535. + 0.5;
823   color.green = rgba.green * 65535. + 0.5;
824   color.blue = rgba.blue * 65535. + 0.5;
825
826   g_value_unset (value);
827   g_value_init (value, GDK_TYPE_COLOR);
828   g_value_set_boxed (value, &color);
829
830   return TRUE;
831 }
832
833 static gboolean
834 resolve_gradient (GtkStyleProperties *props,
835                   GValue             *value)
836 {
837   cairo_pattern_t *gradient;
838
839   if (!gtk_gradient_resolve (g_value_get_boxed (value), props, &gradient))
840     return FALSE;
841
842   /* Store it back, this is where cairo_pattern_t caching happens */
843   g_value_unset (value);
844   g_value_init (value, CAIRO_GOBJECT_TYPE_PATTERN);
845   g_value_take_boxed (value, gradient);
846
847   return TRUE;
848 }
849
850 static gboolean
851 resolve_shadow (GtkStyleProperties *props,
852                 GValue *value)
853 {
854   GtkShadow *resolved, *base;
855
856   base = g_value_get_boxed (value);
857
858   if (base == NULL)
859     return TRUE;
860   
861   if (_gtk_shadow_get_resolved (base))
862     return TRUE;
863
864   resolved = _gtk_shadow_resolve (base, props);
865   if (resolved == NULL)
866     return FALSE;
867
868   g_value_take_boxed (value, resolved);
869
870   return TRUE;
871 }
872
873 static gboolean
874 style_properties_resolve_type (GtkStyleProperties     *props,
875                                const GtkStyleProperty *node,
876                                GValue                 *val)
877 {
878   if (val && G_VALUE_TYPE (val) == GTK_TYPE_SYMBOLIC_COLOR)
879     {
880       if (node->pspec->value_type == GDK_TYPE_RGBA)
881         {
882           if (!resolve_color (props, val))
883             return FALSE;
884         }
885       else if (node->pspec->value_type == GDK_TYPE_COLOR)
886         {
887           if (!resolve_color_rgb (props, val))
888             return FALSE;
889         }
890       else
891         return FALSE;
892     }
893   else if (val && G_VALUE_TYPE (val) == GTK_TYPE_GRADIENT)
894     {
895       g_return_val_if_fail (node->pspec->value_type == CAIRO_GOBJECT_TYPE_PATTERN, FALSE);
896
897       if (!resolve_gradient (props, val))
898         return FALSE;
899     }
900   else if (val && G_VALUE_TYPE (val) == GTK_TYPE_SHADOW)
901     {
902       if (!resolve_shadow (props, val))
903         return FALSE;
904     }
905
906   return TRUE;
907 }
908
909 static void
910 lookup_default_value (const GtkStyleProperty *node,
911                       GValue                 *value)
912 {
913   if (node->pspec->value_type == GTK_TYPE_THEMING_ENGINE)
914     g_value_set_object (value, gtk_theming_engine_load (NULL));
915   else if (node->pspec->value_type == PANGO_TYPE_FONT_DESCRIPTION)
916     g_value_take_boxed (value, pango_font_description_from_string ("Sans 10"));
917   else if (node->pspec->value_type == GDK_TYPE_RGBA)
918     {
919       GdkRGBA color;
920       gdk_rgba_parse (&color, "pink");
921       g_value_set_boxed (value, &color);
922     }
923   else if (node->pspec->value_type == GTK_TYPE_BORDER)
924     {
925       g_value_take_boxed (value, gtk_border_new ());
926     }
927   else
928     g_param_value_set_default (node->pspec, value);
929 }
930
931 const GValue *
932 _gtk_style_properties_peek_property (GtkStyleProperties *props,
933                                      const gchar        *prop_name,
934                                      GtkStateFlags       state)
935 {
936   GtkStylePropertiesPrivate *priv;
937   const GtkStyleProperty *node;
938   PropertyData *prop;
939   GValue *val;
940
941   g_return_val_if_fail (GTK_IS_STYLE_PROPERTIES (props), NULL);
942   g_return_val_if_fail (prop_name != NULL, NULL);
943
944   node = gtk_style_property_lookup (prop_name);
945
946   if (!node)
947     {
948       g_warning ("Style property \"%s\" is not registered", prop_name);
949       return NULL;
950     }
951
952   priv = props->priv;
953   prop = g_hash_table_lookup (priv->properties, node->pspec);
954
955   if (!prop)
956     return NULL;
957
958   val = property_data_match_state (prop, state);
959
960   if (val &&
961       !style_properties_resolve_type (props, node, val))
962     return NULL;
963
964   return val;
965 }
966
967 /**
968  * gtk_style_properties_get_property:
969  * @props: a #GtkStyleProperties
970  * @property: style property name
971  * @state: state to retrieve the property value for
972  * @value: (out) (transfer full):  return location for the style property value.
973  *
974  * Gets a style property from @props for the given state. When done with @value,
975  * g_value_unset() needs to be called to free any allocated memory.
976  *
977  * Returns: %TRUE if the property exists in @props, %FALSE otherwise
978  *
979  * Since: 3.0
980  **/
981 gboolean
982 gtk_style_properties_get_property (GtkStyleProperties *props,
983                                    const gchar        *property,
984                                    GtkStateFlags       state,
985                                    GValue             *value)
986 {
987   GtkStylePropertiesPrivate *priv;
988   const GtkStyleProperty *node;
989   PropertyData *prop;
990   GValue *val;
991
992   g_return_val_if_fail (GTK_IS_STYLE_PROPERTIES (props), FALSE);
993   g_return_val_if_fail (property != NULL, FALSE);
994   g_return_val_if_fail (value != NULL, FALSE);
995
996   node = gtk_style_property_lookup (property);
997
998   if (!node)
999     {
1000       g_warning ("Style property \"%s\" is not registered", property);
1001       return FALSE;
1002     }
1003
1004   priv = props->priv;
1005   prop = g_hash_table_lookup (priv->properties, node->pspec);
1006
1007   if (!prop)
1008     return FALSE;
1009
1010   g_value_init (value, node->pspec->value_type);
1011   val = property_data_match_state (prop, state);
1012
1013   if (val &&
1014       !style_properties_resolve_type (props, node, val))
1015     return FALSE;
1016
1017   if (val)
1018     {
1019       g_param_value_validate (node->pspec, val);
1020       g_value_copy (val, value);
1021     }
1022   else
1023     lookup_default_value (node, value);
1024
1025   return TRUE;
1026 }
1027
1028 /**
1029  * gtk_style_properties_get_valist:
1030  * @props: a #GtkStyleProperties
1031  * @state: state to retrieve the property values for
1032  * @args: va_list of property name/return location pairs, followed by %NULL
1033  *
1034  * Retrieves several style property values from @props for a given state.
1035  *
1036  * Since: 3.0
1037  **/
1038 void
1039 gtk_style_properties_get_valist (GtkStyleProperties *props,
1040                                  GtkStateFlags       state,
1041                                  va_list             args)
1042 {
1043   GtkStylePropertiesPrivate *priv;
1044   const gchar *property_name;
1045
1046   g_return_if_fail (GTK_IS_STYLE_PROPERTIES (props));
1047
1048   priv = props->priv;
1049   property_name = va_arg (args, const gchar *);
1050
1051   while (property_name)
1052     {
1053       const GtkStyleProperty *node;
1054       PropertyData *prop;
1055       gchar *error = NULL;
1056       GValue *val = NULL;
1057
1058       node = gtk_style_property_lookup (property_name);
1059
1060       if (!node)
1061         {
1062           g_warning ("Style property \"%s\" is not registered", property_name);
1063           break;
1064         }
1065
1066       prop = g_hash_table_lookup (priv->properties, node->pspec);
1067
1068       if (prop)
1069         val = property_data_match_state (prop, state);
1070
1071       if (val &&
1072           !style_properties_resolve_type (props, node, val))
1073         val = NULL;
1074
1075       if (val)
1076         {
1077           g_param_value_validate (node->pspec, val);
1078           G_VALUE_LCOPY (val, args, 0, &error);
1079         }
1080       else
1081         {
1082           GValue default_value = { 0 };
1083
1084           g_value_init (&default_value, node->pspec->value_type);
1085           lookup_default_value (node, &default_value);
1086           G_VALUE_LCOPY (&default_value, args, 0, &error);
1087           g_value_unset (&default_value);
1088         }
1089
1090       if (error)
1091         {
1092           g_warning ("Could not get style property \"%s\": %s", property_name, error);
1093           g_free (error);
1094           break;
1095         }
1096
1097       property_name = va_arg (args, const gchar *);
1098     }
1099 }
1100
1101 /**
1102  * gtk_style_properties_get:
1103  * @props: a #GtkStyleProperties
1104  * @state: state to retrieve the property values for
1105  * @...: property name /return value pairs, followed by %NULL
1106  *
1107  * Retrieves several style property values from @props for a
1108  * given state.
1109  *
1110  * Since: 3.0
1111  **/
1112 void
1113 gtk_style_properties_get (GtkStyleProperties *props,
1114                           GtkStateFlags       state,
1115                           ...)
1116 {
1117   va_list args;
1118
1119   g_return_if_fail (GTK_IS_STYLE_PROPERTIES (props));
1120
1121   va_start (args, state);
1122   gtk_style_properties_get_valist (props, state, args);
1123   va_end (args);
1124 }
1125
1126 /**
1127  * gtk_style_properties_unset_property:
1128  * @props: a #GtkStyleProperties
1129  * @property: property to unset
1130  * @state: state to unset
1131  *
1132  * Unsets a style property in @props.
1133  *
1134  * Since: 3.0
1135  **/
1136 void
1137 gtk_style_properties_unset_property (GtkStyleProperties *props,
1138                                      const gchar        *property,
1139                                      GtkStateFlags       state)
1140 {
1141   GtkStylePropertiesPrivate *priv;
1142   const GtkStyleProperty *node;
1143   PropertyData *prop;
1144   guint pos;
1145
1146   g_return_if_fail (GTK_IS_STYLE_PROPERTIES (props));
1147   g_return_if_fail (property != NULL);
1148
1149   node = gtk_style_property_lookup (property);
1150
1151   if (!node)
1152     {
1153       g_warning ("Style property \"%s\" is not registered", property);
1154       return;
1155     }
1156
1157   priv = props->priv;
1158   prop = g_hash_table_lookup (priv->properties, node->pspec);
1159
1160   if (!prop)
1161     return;
1162
1163   if (property_data_find_position (prop, state, &pos))
1164     {
1165       ValueData *data;
1166
1167       data = &g_array_index (prop->values, ValueData, pos);
1168
1169       if (G_IS_VALUE (&data->value))
1170         g_value_unset (&data->value);
1171
1172       g_array_remove_index (prop->values, pos);
1173     }
1174 }
1175
1176 /**
1177  * gtk_style_properties_clear:
1178  * @props: a #GtkStyleProperties
1179  *
1180  * Clears all style information from @props.
1181  **/
1182 void
1183 gtk_style_properties_clear (GtkStyleProperties *props)
1184 {
1185   GtkStylePropertiesPrivate *priv;
1186
1187   g_return_if_fail (GTK_IS_STYLE_PROPERTIES (props));
1188
1189   priv = props->priv;
1190   g_hash_table_remove_all (priv->properties);
1191 }
1192
1193 /**
1194  * gtk_style_properties_merge:
1195  * @props: a #GtkStyleProperties
1196  * @props_to_merge: a second #GtkStyleProperties
1197  * @replace: whether to replace values or not
1198  *
1199  * Merges into @props all the style information contained
1200  * in @props_to_merge. If @replace is %TRUE, the values
1201  * will be overwritten, if it is %FALSE, the older values
1202  * will prevail.
1203  *
1204  * Since: 3.0
1205  **/
1206 void
1207 gtk_style_properties_merge (GtkStyleProperties       *props,
1208                             const GtkStyleProperties *props_to_merge,
1209                             gboolean                  replace)
1210 {
1211   GtkStylePropertiesPrivate *priv, *priv_to_merge;
1212   GHashTableIter iter;
1213   gpointer key, value;
1214
1215   g_return_if_fail (GTK_IS_STYLE_PROPERTIES (props));
1216   g_return_if_fail (GTK_IS_STYLE_PROPERTIES (props_to_merge));
1217
1218   priv = props->priv;
1219   priv_to_merge = props_to_merge->priv;
1220
1221   /* Merge symbolic color map */
1222   if (priv_to_merge->color_map)
1223     {
1224       g_hash_table_iter_init (&iter, priv_to_merge->color_map);
1225
1226       while (g_hash_table_iter_next (&iter, &key, &value))
1227         {
1228           const gchar *name;
1229           GtkSymbolicColor *color;
1230
1231           name = key;
1232           color = value;
1233
1234           if (!replace &&
1235               g_hash_table_lookup (priv->color_map, name))
1236             continue;
1237
1238           gtk_style_properties_map_color (props, name, color);
1239         }
1240     }
1241
1242   /* Merge symbolic style properties */
1243   g_hash_table_iter_init (&iter, priv_to_merge->properties);
1244
1245   while (g_hash_table_iter_next (&iter, &key, &value))
1246     {
1247       PropertyData *prop_to_merge = value;
1248       PropertyData *prop;
1249       guint i;
1250
1251       prop = g_hash_table_lookup (priv->properties, key);
1252
1253       if (!prop)
1254         {
1255           prop = property_data_new ();
1256           g_hash_table_insert (priv->properties, key, prop);
1257         }
1258
1259       for (i = 0; i < prop_to_merge->values->len; i++)
1260         {
1261           ValueData *data;
1262           GValue *value;
1263
1264           data = &g_array_index (prop_to_merge->values, ValueData, i);
1265
1266           if (replace && data->state == GTK_STATE_FLAG_NORMAL &&
1267               G_VALUE_TYPE (&data->value) != PANGO_TYPE_FONT_DESCRIPTION)
1268             {
1269               /* Let normal state override all states
1270                * previously set in the original set
1271                */
1272               property_data_remove_values (prop);
1273             }
1274
1275           value = property_data_get_value (prop, data->state);
1276
1277           if (G_VALUE_TYPE (&data->value) == PANGO_TYPE_FONT_DESCRIPTION &&
1278               G_IS_VALUE (value))
1279             {
1280               PangoFontDescription *font_desc;
1281               PangoFontDescription *font_desc_to_merge;
1282
1283               /* Handle merging of font descriptions */
1284               font_desc = g_value_get_boxed (value);
1285               font_desc_to_merge = g_value_get_boxed (&data->value);
1286
1287               pango_font_description_merge (font_desc, font_desc_to_merge, replace);
1288             }
1289           else if (G_VALUE_TYPE (&data->value) == G_TYPE_PTR_ARRAY &&
1290                    G_IS_VALUE (value))
1291             {
1292               GPtrArray *array, *array_to_merge;
1293               gint i;
1294
1295               /* Append the array, mainly thought
1296                * for the gtk-key-bindings property
1297                */
1298               array = g_value_get_boxed (value);
1299               array_to_merge = g_value_get_boxed (&data->value);
1300
1301               for (i = 0; i < array_to_merge->len; i++)
1302                 g_ptr_array_add (array, g_ptr_array_index (array_to_merge, i));
1303             }
1304           else if (replace || !G_IS_VALUE (value))
1305             {
1306               if (!G_IS_VALUE (value))
1307                 g_value_init (value, G_VALUE_TYPE (&data->value));
1308               else if (G_VALUE_TYPE (value) != G_VALUE_TYPE (&data->value))
1309                 {
1310                   g_value_unset (value);
1311                   g_value_init (value, G_VALUE_TYPE (&data->value));
1312                 }
1313
1314               g_value_copy (&data->value, value);
1315             }
1316         }
1317     }
1318 }