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