gtkcssprovider.c 115 KB
Newer Older
Carlos Garnacho's avatar
Carlos Garnacho committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
/* GTK - The GIMP Toolkit
 * Copyright (C) 2010 Carlos Garnacho <carlosg@gnome.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include "config.h"

#include <string.h>
#include <stdlib.h>
24

25
#include <gdk-pixbuf/gdk-pixbuf.h>
26
#include <cairo-gobject.h>
Carlos Garnacho's avatar
Carlos Garnacho committed
27

28 29
#include "gtkcssproviderprivate.h"

30
#include "gtkanimationdescription.h"
31
#include "gtk9slice.h"
32 33 34 35
#include "gtkgradient.h"
#include "gtkthemingengine.h"
#include "gtkstyleprovider.h"
#include "gtkstylecontextprivate.h"
36
#include "gtkbindings.h"
37
#include "gtkprivate.h"
Carlos Garnacho's avatar
Carlos Garnacho committed
38

Carlos Garnacho's avatar
Carlos Garnacho committed
39 40 41 42 43 44
/**
 * SECTION:gtkcssprovider
 * @Short_description: CSS-like styling for widgets
 * @Title: GtkCssProvider
 * @See_also: #GtkStyleContext, #GtkStyleProvider
 *
Matthias Clasen's avatar
Matthias Clasen committed
45 46 47
 * GtkCssProvider is an object implementing the #GtkStyleProvider interface.
 * It is able to parse <ulink url="http://www.w3.org/TR/CSS2">CSS</ulink>-like
 * input in order to style widgets.
Carlos Garnacho's avatar
Carlos Garnacho committed
48
 *
Matthias Clasen's avatar
Matthias Clasen committed
49 50 51 52 53 54 55
 * <refsect2 id="gtkcssprovider-files">
 * <title>Default files</title>
 * <para>
 * An application can cause GTK+ to parse a specific CSS style sheet by
 * calling gtk_css_provider_load_from_file() and adding the provider with
 * gtk_style_context_add_provider() or gtk_style_context_add_provider_for_screen().
 * In addition, certain files will be read when GTK+ is initialized. First,
56
 * the file <filename><envar>$XDG_CONFIG_HOME</envar>/gtk-3.0/gtk.css</filename>
Matthias Clasen's avatar
Matthias Clasen committed
57
 * is loaded if it exists. Then, GTK+ tries to load
58
 * <filename><envar>$HOME</envar>/.themes/<replaceable>theme-name</replaceable>/gtk-3.0/gtk.css</filename>,
Matthias Clasen's avatar
Matthias Clasen committed
59
 * falling back to
60
 * <filename><replaceable>datadir</replaceable>/share/themes/<replaceable>theme-name</replaceable>/gtk-3.0/gtk.css</filename>,
Matthias Clasen's avatar
Matthias Clasen committed
61
 * where <replaceable>theme-name</replaceable> is the name of the current theme
62
 * (see the #GtkSettings:gtk-theme-name setting) and <replaceable>datadir</replaceable>
Matthias Clasen's avatar
Matthias Clasen committed
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
 * is the prefix configured when GTK+ was compiled, unless overridden by the
 * <envar>GTK_DATA_PREFIX</envar> environment variable.
 * </para>
 * </refsect2>
 * <refsect2 id="gtkcssprovider-stylesheets">
 * <title>Style sheets</title>
 * <para>
 * The basic structure of the style sheets understood by this provider is
 * a series of statements, which are either rule sets or '@-rules', separated
 * by whitespace.
 * </para>
 * <para>
 * A rule set consists of a selector and a declaration block, which is
 * a series of declarations enclosed in curly braces ({ and }). The
 * declarations are separated by semicolons (;). Multiple selectors can
 * share the same declaration block, by putting all the separators in
 * front of the block, separated by commas.
 * </para>
 * <example><title>A rule set with two selectors</title>
 * <programlisting language="text">
 * GtkButton, GtkEntry {
 *     color: &num;ff00ea;
 *     font: Comic Sans 12
 * }
 * </programlisting>
 * </example>
 * </refsect2>
Carlos Garnacho's avatar
Carlos Garnacho committed
90
 * <refsect2 id="gtkcssprovider-selectors">
Matthias Clasen's avatar
Matthias Clasen committed
91
 * <title>Selectors</title>
Carlos Garnacho's avatar
Carlos Garnacho committed
92
 * <para>
Matthias Clasen's avatar
Matthias Clasen committed
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
 * Selectors work very similar to the way they do in CSS, with widget class
 * names taking the role of element names, and widget names taking the role
 * of IDs. When used in a selector, widget names must be prefixed with a
 * '&num;' character. The '*' character represents the so-called universal
 * selector, which matches any widget.
 * </para>
 * <para>
 * To express more complicated situations, selectors can be combined in
 * various ways:
 * <itemizedlist>
 * <listitem><para>To require that a widget satisfies several conditions,
 *   combine several selectors into one by concatenating them. E.g.
 *   <literal>GtkButton&num;button1</literal> matches a GtkButton widget
 *   with the name button1.</para></listitem>
 * <listitem><para>To only match a widget when it occurs inside some other
 *   widget, write the two selectors after each other, separated by whitespace.
 *   E.g. <literal>GtkToolBar GtkButton</literal> matches GtkButton widgets
 *   that occur inside a GtkToolBar.</para></listitem>
 * <listitem><para>In the previous example, the GtkButton is matched even
 *   if it occurs deeply nested inside the toolbar. To restrict the match
 *   to direct children of the parent widget, insert a '>' character between
 *   the two selectors. E.g. <literal>GtkNotebook > GtkLabel</literal> matches
 *   GtkLabel widgets that are direct children of a GtkNotebook.</para></listitem>
 * </itemizedlist>
Carlos Garnacho's avatar
Carlos Garnacho committed
117 118
 * </para>
 * <example>
Matthias Clasen's avatar
Matthias Clasen committed
119 120
 * <title>Widget classes and names in selectors</title>
 * <programlisting language="text">
Carlos Garnacho's avatar
Carlos Garnacho committed
121 122
 * /&ast; Theme labels that are descendants of a window &ast;/
 * GtkWindow GtkLabel {
Matthias Clasen's avatar
Matthias Clasen committed
123
 *     background-color: &num;898989
Carlos Garnacho's avatar
Carlos Garnacho committed
124 125 126 127
 * }
 *
 * /&ast; Theme notebooks, and anything that's within these &ast;/
 * GtkNotebook {
Matthias Clasen's avatar
Matthias Clasen committed
128
 *     background-color: &num;a939f0
Carlos Garnacho's avatar
Carlos Garnacho committed
129 130 131 132 133 134
 * }
 *
 * /&ast; Theme combo boxes, and entries that
 *  are direct children of a notebook &ast;/
 * GtkComboBox,
 * GtkNotebook > GtkEntry {
Matthias Clasen's avatar
Matthias Clasen committed
135 136
 *     color: @fg_color;
 *     background-color: &num;1209a2
Carlos Garnacho's avatar
Carlos Garnacho committed
137 138 139 140
 * }
 *
 * /&ast; Theme any widget within a GtkBin &ast;/
 * GtkBin * {
Matthias Clasen's avatar
Matthias Clasen committed
141
 *     font-name: Sans 20
Carlos Garnacho's avatar
Carlos Garnacho committed
142
 * }
Matthias Clasen's avatar
Matthias Clasen committed
143
 *
Carlos Garnacho's avatar
Carlos Garnacho committed
144 145
 * /&ast; Theme a label named title-label &ast;/
 * GtkLabel&num;title-label {
Matthias Clasen's avatar
Matthias Clasen committed
146
 *     font-name: Sans 15
Carlos Garnacho's avatar
Carlos Garnacho committed
147 148 149 150
 * }
 *
 * /&ast; Theme any widget named main-entry &ast;/
 * &num;main-entry {
Matthias Clasen's avatar
Matthias Clasen committed
151
 *     background-color: &num;f0a810
Carlos Garnacho's avatar
Carlos Garnacho committed
152 153 154 155
 * }
 * </programlisting>
 * </example>
 * <para>
Matthias Clasen's avatar
Matthias Clasen committed
156 157 158 159 160 161
 * Widgets may also define style classes, which can be used for matching.
 * When used in a selector, style classes must be prefixed with a '.'
 * character.
 * </para>
 * <para>
 * Refer to the documentation of individual widgets to learn which
Matthias Clasen's avatar
Matthias Clasen committed
162 163
 * style classes they define and see <xref linkend="gtkstylecontext-classes"/>
 * for a list of all style classes used by GTK+ widgets.
Carlos Garnacho's avatar
Carlos Garnacho committed
164
 * </para>
165 166 167 168 169 170
 * <para>
 * Note that there is some ambiguity in the selector syntax when it comes
 * to differentiation widget class names from regions. GTK+ currently treats
 * a string as a widget class name if it contains any uppercase characters
 * (which should work for more widgets with names like GtkLabel).
 * </para>
Carlos Garnacho's avatar
Carlos Garnacho committed
171
 * <example>
Matthias Clasen's avatar
Matthias Clasen committed
172 173
 * <title>Style classes in selectors</title>
 * <programlisting language="text">
Carlos Garnacho's avatar
Carlos Garnacho committed
174 175
 * /&ast; Theme all widgets defining the class entry &ast;/
 * .entry {
Carlos Garnacho's avatar
Carlos Garnacho committed
176
 *     color: &num;39f1f9;
Carlos Garnacho's avatar
Carlos Garnacho committed
177 178 179 180
 * }
 *
 * /&ast; Theme spinbuttons' entry &ast;/
 * GtkSpinButton.entry {
Matthias Clasen's avatar
Matthias Clasen committed
181
 *     color: &num;900185
Carlos Garnacho's avatar
Carlos Garnacho committed
182 183 184 185
 * }
 * </programlisting>
 * </example>
 * <para>
Matthias Clasen's avatar
Matthias Clasen committed
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
 * In complicated widgets like e.g. a GtkNotebook, it may be desirable
 * to style different parts of the widget differently. To make this
 * possible, container widgets may define regions, whose names
 * may be used for matching in selectors.
 * </para>
 * <para>
 * Some containers allow to further differentiate between regions by
 * applying so-called pseudo-classes to the region. For example, the
 * tab region in GtkNotebook allows to single out the first or last
 * tab by using the :first-child or :last-child pseudo-class.
 * When used in selectors, pseudo-classes must be prefixed with a
 * ':' character.
 * </para>
 * <para>
 * Refer to the documentation of individual widgets to learn which
201 202 203
 * regions and pseudo-classes they define and see
 * <xref linkend="gtkstylecontext-classes"/> for a list of all regions
 * used by GTK+ widgets.
Carlos Garnacho's avatar
Carlos Garnacho committed
204 205
 * </para>
 * <example>
Matthias Clasen's avatar
Matthias Clasen committed
206 207
 * <title>Regions in selectors</title>
 * <programlisting language="text">
Carlos Garnacho's avatar
Carlos Garnacho committed
208 209
 * /&ast; Theme any label within a notebook &ast;/
 * GtkNotebook GtkLabel {
Carlos Garnacho's avatar
Carlos Garnacho committed
210
 *     color: &num;f90192;
Carlos Garnacho's avatar
Carlos Garnacho committed
211 212 213
 * }
 *
 * /&ast; Theme labels within notebook tabs &ast;/
Matthias Clasen's avatar
Matthias Clasen committed
214
 * GtkNotebook tab GtkLabel {
Carlos Garnacho's avatar
Carlos Garnacho committed
215
 *     color: &num;703910;
Carlos Garnacho's avatar
Carlos Garnacho committed
216 217 218 219 220 221
 * }
 *
 * /&ast; Theme labels in the any first notebook
 *  tab, both selectors are equivalent &ast;/
 * GtkNotebook tab:nth-child(first) GtkLabel,
 * GtkNotebook tab:first-child GtkLabel {
Carlos Garnacho's avatar
Carlos Garnacho committed
222
 *     color: &num;89d012;
Carlos Garnacho's avatar
Carlos Garnacho committed
223 224 225 226
 * }
 * </programlisting>
 * </example>
 * <para>
Matthias Clasen's avatar
Matthias Clasen committed
227 228 229 230 231
 * Another use of pseudo-classes is to match widgets depending on their
 * state. This is conceptually similar to the :hover, :active or :focus
 * pseudo-classes in CSS. The available pseudo-classes for widget states
 * are :active, :prelight (or :hover), :insensitive, :selected, :focused
 * and :inconsistent.
Carlos Garnacho's avatar
Carlos Garnacho committed
232 233 234
 * </para>
 * <example>
 * <title>Styling specific widget states</title>
Matthias Clasen's avatar
Matthias Clasen committed
235
 * <programlisting language="text">
Carlos Garnacho's avatar
Carlos Garnacho committed
236 237 238 239 240
 * /&ast; Theme active (pressed) buttons &ast;/
 * GtkButton:active {
 *     background-color: &num;0274d9;
 * }
 *
Matthias Clasen's avatar
Matthias Clasen committed
241 242
 * /&ast; Theme buttons with the mouse pointer on it,
 *    both are equivalent &ast;/
Carlos Garnacho's avatar
Carlos Garnacho committed
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
 * GtkButton:hover,
 * GtkButton:prelight {
 *     background-color: &num;3085a9;
 * }
 *
 * /&ast; Theme insensitive widgets, both are equivalent &ast;/
 * :insensitive,
 * *:insensitive {
 *     background-color: &num;320a91;
 * }
 *
 * /&ast; Theme selection colors in entries &ast;/
 * GtkEntry:selected {
 *     background-color: &num;56f9a0;
 * }
 *
 * /&ast; Theme focused labels &ast;/
 * GtkLabel:focused {
 *     background-color: &num;b4940f;
 * }
 *
 * /&ast; Theme inconsistent checkbuttons &ast;/
 * GtkCheckButton:inconsistent {
 *     background-color: &num;20395a;
 * }
 * </programlisting>
 * </example>
 * <para>
Matthias Clasen's avatar
Matthias Clasen committed
271 272
 * Widget state pseudoclasses may only apply to the last element
 * in a selector.
Carlos Garnacho's avatar
Carlos Garnacho committed
273 274
 * </para>
 * <para>
Matthias Clasen's avatar
Matthias Clasen committed
275 276 277
 * To determine the effective style for a widget, all the matching rule
 * sets are merged. As in CSS, rules apply by specificity, so the rules
 * whose selectors more closely match a widget path will take precedence
Carlos Garnacho's avatar
Carlos Garnacho committed
278 279 280 281
 * over the others.
 * </para>
 * </refsect2>
 * <refsect2 id="gtkcssprovider-rules">
Matthias Clasen's avatar
Matthias Clasen committed
282
 * <title>&commat; Rules</title>
Carlos Garnacho's avatar
Carlos Garnacho committed
283
 * <para>
Matthias Clasen's avatar
Matthias Clasen committed
284 285
 * GTK+'s CSS supports the &commat;import rule, in order to load another
 * CSS style sheet in addition to the currently parsed one.
Carlos Garnacho's avatar
Carlos Garnacho committed
286 287 288
 * </para>
 * <example>
 * <title>Using the &commat;import rule</title>
Matthias Clasen's avatar
Matthias Clasen committed
289
 * <programlisting language="text">
Matthias Clasen's avatar
Matthias Clasen committed
290
 * &commat;import url ("path/to/common.css");
Carlos Garnacho's avatar
Carlos Garnacho committed
291 292 293
 * </programlisting>
 * </example>
 * <para>
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
 * In order to extend key bindings affecting different widgets, GTK+
 * supports the &commat;binding-set rule to parse a set of bind/unbind
 * directives, see #GtkBindingSet for the syntax supported
 * </para>
 * <example>
 * <title>Using the &commat;binding rule</title>
 * <programlisting language="text">
 * &commat;binding-set binding-set1 {
 *   bind "&lt;alt&gt;Left" { "move-cursor" (visual-positions, -3, 0) };
 *   unbind "End";
 * };
 *
 * &commat;binding-set binding-set1 {
 *   bind "&lt;alt&gt;Right" { "move-cursor" (visual-positions, 3, 0) };
 * };
 *
 * GtkEntry {
 *   gtk-binding-set: binding-set1, binding-set2;
 * }
 * </programlisting>
 * </example>
 * <para>
Matthias Clasen's avatar
Matthias Clasen committed
316 317 318 319
 * GTK+ also supports an additional &commat;define-color rule, in order
 * to define a color name which may be used instead of color numeric
 * representations. Also see the #GtkSettings:gtk-color-scheme setting
 * for a way to override the values of these named colors.
Carlos Garnacho's avatar
Carlos Garnacho committed
320 321 322
 * </para>
 * <example>
 * <title>Defining colors</title>
Matthias Clasen's avatar
Matthias Clasen committed
323
 * <programlisting language="text">
Carlos Garnacho's avatar
Carlos Garnacho committed
324 325 326 327 328 329 330 331 332 333 334
 * &commat;define-color bg_color &num;f9a039;
 *
 * &ast; {
 *     background-color: &commat;bg_color;
 * }
 * </programlisting>
 * </example>
 * </refsect2>
 * <refsect2 id="gtkcssprovider-symbolic-colors">
 * <title>Symbolic colors</title>
 * <para>
Matthias Clasen's avatar
Matthias Clasen committed
335 336 337 338
 * Besides being able to define color names, the CSS parser is also able
 * to read different color expressions, which can also be nested, providing
 * a rich language to define colors which are derived from a set of base
 * colors.
Carlos Garnacho's avatar
Carlos Garnacho committed
339 340 341
 * </para>
 * <example>
 * <title>Using symbolic colors</title>
Matthias Clasen's avatar
Matthias Clasen committed
342
 * <programlisting language="text">
Carlos Garnacho's avatar
Carlos Garnacho committed
343 344 345 346 347 348 349 350 351 352 353 354 355
 * &commat;define-color entry-color shade (&commat;bg_color, 0.7);
 *
 * GtkEntry {
 *     background-color: @entry-color;
 * }
 *
 * GtkEntry:focused {
 *     background-color: mix (&commat;entry-color,
 *                            shade (&num;fff, 0.5),
 *                            0.8);
 * }
 * </programlisting>
 * </example>
Matthias Clasen's avatar
Matthias Clasen committed
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370
 * <para>
 *   The various ways to express colors in GTK+ CSS are:
 * </para>
 * <informaltable>
 *   <tgroup cols="3">
 *     <thead>
 *       <row>
 *         <entry>Syntax</entry>
 *         <entry>Explanation</entry>
 *         <entry>Examples</entry>
 *       </row>
 *     </thead>
 *     <tbody>
 *       <row>
 *         <entry>rgb(@r, @g, @b)</entry>
Matthias Clasen's avatar
Matthias Clasen committed
371 372 373 374
 *         <entry>An opaque color; @r, @g, @b can be either integers between
 *                0 and 255 or percentages</entry>
 *         <entry><literallayout>rgb(128, 10, 54)
 * rgb(20%, 30%, 0%)</literallayout></entry>
Matthias Clasen's avatar
Matthias Clasen committed
375 376 377
 *       </row>
 *       <row>
 *         <entry>rgba(@r, @g, @b, @a)</entry>
Matthias Clasen's avatar
Matthias Clasen committed
378 379 380
 *         <entry>A translucent color; @r, @g, @b are as in the previous row,
 *                @a is a floating point number between 0 and 1</entry>
 *         <entry><literallayout>rgba(255, 255, 0, 0.5)</literallayout></entry>
Matthias Clasen's avatar
Matthias Clasen committed
381 382 383
 *       </row>
 *       <row>
 *         <entry>&num;@xxyyzz</entry>
Matthias Clasen's avatar
Matthias Clasen committed
384 385 386 387 388
 *         <entry>An opaque color; @xx, @yy, @zz are hexadecimal numbers
 *                specifying @r, @g, @b variants with between 1 and 4
 *                hexadecimal digits per component are allowed</entry>
 *         <entry><literallayout>&num;ff12ab
 * &num;f0c</literallayout></entry>
Matthias Clasen's avatar
Matthias Clasen committed
389 390 391
 *       </row>
 *       <row>
 *         <entry>&commat;name</entry>
Matthias Clasen's avatar
Matthias Clasen committed
392 393 394
 *         <entry>Reference to a color that has been defined with
 *                &commat;define-color
 *         </entry>
Matthias Clasen's avatar
Matthias Clasen committed
395 396 397 398
 *         <entry>&commat;bg_color</entry>
 *       </row>
 *       <row>
 *         <entry>mix(@color1, @color2, @f)</entry>
Matthias Clasen's avatar
Matthias Clasen committed
399 400 401
 *         <entry>A linear combination of @color1 and @color2. @f is a
 *                floating point number between 0 and 1.</entry>
 *         <entry><literallayout>mix(&num;ff1e0a, &commat;bg_color, 0.8)</literallayout></entry>
Matthias Clasen's avatar
Matthias Clasen committed
402 403 404
 *       </row>
 *       <row>
 *         <entry>shade(@color, @f)</entry>
Matthias Clasen's avatar
Matthias Clasen committed
405 406 407
 *         <entry>A lighter or darker variant of @color. @f is a
 *                floating point number.
 *         </entry>
Matthias Clasen's avatar
Matthias Clasen committed
408 409 410 411 412 413 414 415 416 417 418 419 420
 *         <entry>shade(&commat;fg_color, 0.5)</entry>
 *       </row>
 *       <row>
 *         <entry>lighter(@color)</entry>
 *         <entry>A lighter variant of @color</entry>
 *       </row>
 *       <row>
 *         <entry>darker(@color)</entry>
 *         <entry>A darker variant of @color</entry>
 *       </row>
 *     </tbody>
 *   </tgroup>
 * </informaltable>
Carlos Garnacho's avatar
Carlos Garnacho committed
421
 * </refsect2>
Matthias Clasen's avatar
Matthias Clasen committed
422 423 424 425 426 427
 * <refsect2 id="gtkcssprovider-gradients">
 * <title>Gradients</title>
 * <para>
 * Linear or radial Gradients can be used as background images.
 * </para>
 * <para>
428 429
 * A linear gradient along the line from (@start_x, @start_y) to
 * (@end_x, @end_y) is specified using the syntax
Matthias Clasen's avatar
Matthias Clasen committed
430
 * <literallayout>-gtk-gradient (linear,
431
 *               @start_x @start_y, @end_x @end_y,
Matthias Clasen's avatar
Matthias Clasen committed
432 433
 *               color-stop (@position, @color),
 *               ...)</literallayout>
434 435 436
 * where @start_x and @end_x can be either a floating point number between
 * 0 and 1 or one of the special values 'left', 'right' or 'center', @start_y
 * and @end_y can be either a floating point number between 0 and 1 or one
Matthias Clasen's avatar
Matthias Clasen committed
437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461
 * of the special values 'top', 'bottom' or 'center', @position is a floating
 * point number between 0 and 1 and @color is a color expression (see above).
 * The color-stop can be repeated multiple times to add more than one color
 * stop. 'from (@color)' and 'to (@color)' can be used as abbreviations for
 * color stops with position 0 and 1, respectively.
 * </para>
 * <example>
 * <title>A linear gradient</title>
 * <inlinegraphic fileref="gradient1.png" format="PNG"/>
 * <para>This gradient was specified with
 * <literallayout>-gtk-gradient (linear,
 *                left top, right bottom,
 *                from(&commat;yellow), to(&commat;blue))</literallayout></para>
 * </example>
 * <example>
 * <title>Another linear gradient</title>
 * <inlinegraphic fileref="gradient2.png" format="PNG"/>
 * <para>This gradient was specified with
 * <literallayout>-gtk-gradient (linear,
 *                0 0, 0 1,
 *                color-stop(0, &commat;yellow),
 *                color-stop(0.2, &commat;blue),
 *                color-stop(1, &num;0f0))</literallayout></para>
 * </example>
 * <para>
462 463
 * A radial gradient along the two circles defined by (@start_x, @start_y,
 * @start_radius) and (@end_x, @end_y, @end_radius) is specified using the
Matthias Clasen's avatar
Matthias Clasen committed
464 465
 * syntax
 * <literallayout>-gtk-gradient (radial,
466 467
 *                @start_x @start_y, @start_radius,
 *                @end_x @end_y, @end_radius,
Matthias Clasen's avatar
Matthias Clasen committed
468 469
 *                color-stop (@position, @color),
 *                ...)</literallayout>
470
 * where @start_radius and @end_radius are floating point numbers and
Matthias Clasen's avatar
Matthias Clasen committed
471 472 473 474 475 476 477 478 479 480 481 482 483 484 485
 * the other parameters are as before.
 * </para>
 * <example>
 * <title>A radial gradient</title>
 * <inlinegraphic fileref="gradient3.png" format="PNG"/>
 * <para>This gradient was specified with
 * <literallayout>-gtk-gradient (radial,
 *                center center, 0,
 *                center center, 1,
 *                from(&commat;yellow), to(&commat;green))</literallayout></para>
 * </example>
 * <example>
 * <title>Another radial gradient</title>
 * <inlinegraphic fileref="gradient4.png" format="PNG"/>
 * <para>This gradient was specified with
486
 * <literallayout>-gtk-gradient (radial,
Matthias Clasen's avatar
Matthias Clasen committed
487 488 489 490 491 492 493 494
 *                0.4 0.4, 0.1,
 *                0.6 0.6, 0.7,
 *                color-stop (0, &num;f00),
 *                color-stop (0.1, &num;a0f),
 *                color-stop (0.2, &commat;yellow),
 *                color-stop (1, &commat;green))</literallayout></para>
 * </example>
 * </refsect2>
495
 * <refsect2 id="gtkcssprovider-slices">
Matthias Clasen's avatar
Matthias Clasen committed
496
 * <title>Border images</title>
497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515
 * <para>
 * Images can be used in 'slices' for the purpose of creating scalable
 * borders.
 * </para>
 * <inlinegraphic fileref="slices.png" format="PNG"/>
 * <para>
 * The syntax for specifying border images of this kind is:
 * <literallayout>url(@path) @top @right @bottom @left [repeat|stretch]? [repeat|stretch]?</literallayout>
 * The sizes of the 'cut off' portions are specified
 * with the @top, @right, @bottom and @left parameters.
 * The 'middle' sections can be repeated or stretched to create
 * the desired effect, by adding the 'repeat' or 'stretch' options after
 * the dimensions. If two options are specified, the first one affects
 * the horizontal behaviour and the second one the vertical behaviour.
 * If only one option is specified, it affects both.
 * </para>
 * <example>
 * <title>A border image</title>
 * <inlinegraphic fileref="border1.png" format="PNG"/>
516 517 518
 * <para>This border image was specified with
 * <literallayout>url("gradient1.png") 10 10 10 10</literallayout>
 * </para>
519 520 521 522
 * </example>
 * <example>
 * <title>A repeating border image</title>
 * <inlinegraphic fileref="border2.png" format="PNG"/>
523 524 525
 * <para>This border image was specified with
 * <literallayout>url("gradient1.png") 10 10 10 10 repeat</literallayout>
 * </para>
526 527 528 529
 * </example>
 * <example>
 * <title>A stretched border image</title>
 * <inlinegraphic fileref="border3.png" format="PNG"/>
530 531 532
 * <para>This border image was specified with
 * <literallayout>url("gradient1.png") 10 10 10 10 stretch</literallayout>
 * </para>
533 534
 * </example>
 * </refsect2>
535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561
 * <refsect2 id="gtkcssprovider-transitions">
 * <para>Styles can specify transitions that will be used to create a gradual
 * change in the appearance when a widget state changes. The following
 * syntax is used to specify transitions:
 * <literallayout>@duration [s|ms] [linear|ease|ease-in|ease-out|ease-in-out] [loop]?</literallayout>
 * The @duration is the amount of time that the animation will take for
 * a complete cycle from start to end. If the loop option is given, the
 * animation will be repated until the state changes again.
 * The option after the duration determines the transition function from a
 * small set of predefined functions.
 * <figure><title>Linear transition</title>
 * <graphic fileref="linear.png" format="PNG"/>
 * </figure>
 * <figure><title>Ease transition</title>
 * <graphic fileref="ease.png" format="PNG"/>
 * </figure>
 * <figure><title>Ease-in-out transition</title>
 * <graphic fileref="ease-in-out.png" format="PNG"/>
 * </figure>
 * <figure><title>Ease-in transition</title>
 * <graphic fileref="ease-in.png" format="PNG"/>
 * </figure>
 * <figure><title>Ease-out transition</title>
 * <graphic fileref="ease-out.png" format="PNG"/>
 * </figure>
 * </para>
 * </refsect2>
Carlos Garnacho's avatar
Carlos Garnacho committed
562 563 564 565 566 567 568 569 570 571 572 573 574
 * <refsect2 id="gtkcssprovider-properties">
 * <title>Supported properties</title>
 * <para>
 * Properties are the part that differ the most to common CSS,
 * not all properties are supported (some are planned to be
 * supported eventually, some others are meaningless or don't
 * map intuitively in a widget based environment).
 * </para>
 * <para>
 * There is also a difference in shorthand properties, for
 * example in common CSS it is fine to define a font through
 * the different @font-family, @font-style, @font-size
 * properties, meanwhile in GTK+'s CSS only the canonical
Matthias Clasen's avatar
Matthias Clasen committed
575
 * @font property is supported.
Carlos Garnacho's avatar
Carlos Garnacho committed
576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592
 * </para>
 * <para>
 * The currently supported properties are:
 * </para>
 * <informaltable>
 *   <tgroup cols="4">
 *     <thead>
 *       <row>
 *         <entry>Property name</entry>
 *         <entry>Syntax</entry>
 *         <entry>Maps to</entry>
 *         <entry>Examples</entry>
 *       </row>
 *     </thead>
 *     <tbody>
 *       <row>
 *         <entry>engine</entry>
Matthias Clasen's avatar
Matthias Clasen committed
593
 *         <entry>engine-name</entry>
Carlos Garnacho's avatar
Carlos Garnacho committed
594
 *         <entry>#GtkThemingEngine</entry>
595 596
 *         <entry>engine: clearlooks;
 *  engine: none; /&ast; use the default (i.e. builtin) engine) &ast;/ </entry>
Carlos Garnacho's avatar
Carlos Garnacho committed
597 598 599
 *       </row>
 *       <row>
 *         <entry>background-color</entry>
Matthias Clasen's avatar
Matthias Clasen committed
600 601 602
 *         <entry morerows="2">color (see above)</entry>
 *         <entry morerows="2">#GdkRGBA</entry>
 *         <entry morerows="2"><literallayout>background-color: &num;fff;
603 604 605
 * color: &amp;color1;
 * background-color: shade (&amp;color1, 0.5);
 * color: mix (&amp;color1, &num;f0f, 0.8);</literallayout>
Carlos Garnacho's avatar
Carlos Garnacho committed
606 607 608
 *         </entry>
 *       </row>
 *       <row>
Carlos Garnacho's avatar
Carlos Garnacho committed
609
 *         <entry>color</entry>
Carlos Garnacho's avatar
Carlos Garnacho committed
610 611
 *       </row>
 *       <row>
Matthias Clasen's avatar
Matthias Clasen committed
612 613 614
 *         <entry>border-color</entry>
 *       </row>
 *       <row>
Carlos Garnacho's avatar
Carlos Garnacho committed
615
 *         <entry>font</entry>
616
 *         <entry>@family [@style] [@size]</entry>
Carlos Garnacho's avatar
Carlos Garnacho committed
617
 *         <entry>#PangoFontDescription</entry>
Matthias Clasen's avatar
Matthias Clasen committed
618
 *         <entry>font: Sans 15;</entry>
Carlos Garnacho's avatar
Carlos Garnacho committed
619 620 621
 *       </row>
 *       <row>
 *         <entry>margin</entry>
622
 *         <entry morerows="1"><literallayout>@width
623 624 625
 * @vertical_width @horizontal_width
 * @top_width @horizontal_width @bottom_width
 * @top_width @right_width @bottom_width @left_width</literallayout>
Carlos Garnacho's avatar
Carlos Garnacho committed
626 627
 *         </entry>
 *         <entry morerows="1">#GtkBorder</entry>
Matthias Clasen's avatar
Matthias Clasen committed
628
 *         <entry morerows="1"><literallayout>margin: 5;
Carlos Garnacho's avatar
Carlos Garnacho committed
629 630
 * margin: 5 10;
 * margin: 5 10 3;
Matthias Clasen's avatar
Matthias Clasen committed
631
 * margin: 5 10 3 5;</literallayout>
Carlos Garnacho's avatar
Carlos Garnacho committed
632 633 634 635 636 637 638
 *         </entry>
 *       </row>
 *       <row>
 *         <entry>padding</entry>
 *       </row>
 *       <row>
 *         <entry>background-image</entry>
639 640
 *         <entry><literallayout>gradient (see above) or
 * url(@path)</literallayout></entry>
Carlos Garnacho's avatar
Carlos Garnacho committed
641
 *         <entry>#cairo_pattern_t</entry>
Matthias Clasen's avatar
Matthias Clasen committed
642
 *         <entry><literallayout>-gtk-gradient (linear,
643
 *                left top, right top,
Carlos Garnacho's avatar
Carlos Garnacho committed
644 645 646 647 648 649 650 651 652
 *                from (&num;fff), to (&num;000));
 * -gtk-gradient (linear, 0.0 0.5, 0.5 1.0,
 *                from (&num;fff),
 *                color-stop (0.5, &num;f00),
 *                to (&num;000));
 * -gtk-gradient (radial,
 *                center center, 0.2,
 *                center center, 0.8,
 *                color-stop (0.0, &num;fff),
653 654
 *                color-stop (1.0, &num;000));
 * url ('background.png');</literallayout>
Carlos Garnacho's avatar
Carlos Garnacho committed
655 656 657
 *         </entry>
 *       </row>
 *       <row>
Matthias Clasen's avatar
Matthias Clasen committed
658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675
 *         <entry>border-width</entry>
 *         <entry>integer</entry>
 *         <entry>#gint</entry>
 *         <entry>border-width: 5;</entry>
 *       </row>
 *       <row>
 *         <entry>border-radius</entry>
 *         <entry>integer</entry>
 *         <entry>#gint</entry>
 *         <entry>border-radius: 5;</entry>
 *       </row>
 *       <row>
 *         <entry>border-style</entry>
 *         <entry>[none|solid|inset|outset]</entry>
 *         <entry>#GtkBorderStyle</entry>
 *         <entry>border-style: solid;</entry>
 *       </row>
 *       <row>
Carlos Garnacho's avatar
Carlos Garnacho committed
676
 *         <entry>border-image</entry>
677
 *         <entry><literallayout>border image (see above)</literallayout></entry>
678
 *         <entry>internal use only</entry>
Matthias Clasen's avatar
Matthias Clasen committed
679 680
 *         <entry><literallayout>border-image: url("/path/to/image.png") 3 4 3 4 stretch;
 * border-image: url("/path/to/image.png") 3 4 4 3 repeat stretch;</literallayout>
Carlos Garnacho's avatar
Carlos Garnacho committed
681 682 683 684
 *         </entry>
 *       </row>
 *       <row>
 *         <entry>transition</entry>
685
 *         <entry>transition (see above)</entry>
686
 *         <entry>internal use only</entry>
Matthias Clasen's avatar
Matthias Clasen committed
687 688
 *         <entry><literallayout>transition: 150ms ease-in-out;
 * transition: 1s linear loop;</literallayout>
Carlos Garnacho's avatar
Carlos Garnacho committed
689 690
 *         </entry>
 *       </row>
691 692 693 694 695 696 697
 *       <row>
 *         <entry>gtk-key-bindings</entry>
 *         <entry>binding set name list</entry>
 *         <entry>internal use only</entry>
 *         <entry><literallayout>gtk-bindings: binding1, binding2, ...;</literallayout>
 *         </entry>
 *       </row>
Carlos Garnacho's avatar
Carlos Garnacho committed
698 699 700
 *     </tbody>
 *   </tgroup>
 * </informaltable>
701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721
 * <para>
 * GtkThemingEngines can register their own, engine-specific style properties
 * with the function gtk_theming_engine_register_property(). These properties
 * can be set in CSS like other properties, using a name of the form
 * <literallayout>-<replaceable>namespace</replaceable>-<replaceable>name</replaceable></literallayout>, where <replaceable>namespace</replaceable> is typically
 * the name of the theming engine, and <replaceable>name</replaceable> is the
 * name of the property. Style properties that have been registered by widgets
 * using gtk_widget_class_install_style_property() can also be set in this
 * way, using the widget class name for <replaceable>namespace</replaceable>.
 * </para>
 * <example>
 * <title>Using engine-specific style properties</title>
 * <programlisting>
 * * {
 *     engine: clearlooks;
 *     border-radius: 4;
 *     -GtkPaned-handle-size: 6;
 *     -clearlooks-colorize-scrollbar: false;
 * }
 * </programlisting>
 * </example>
Carlos Garnacho's avatar
Carlos Garnacho committed
722 723 724
 * </refsect2>
 */

Carlos Garnacho's avatar
Carlos Garnacho committed
725 726 727 728 729
typedef struct GtkCssProviderPrivate GtkCssProviderPrivate;
typedef struct SelectorElement SelectorElement;
typedef struct SelectorPath SelectorPath;
typedef struct SelectorStyleInfo SelectorStyleInfo;
typedef enum SelectorElementType SelectorElementType;
730
typedef enum CombinatorType CombinatorType;
Carlos Garnacho's avatar
Carlos Garnacho committed
731
typedef enum ParserScope ParserScope;
732
typedef enum ParserSymbol ParserSymbol;
Carlos Garnacho's avatar
Carlos Garnacho committed
733 734 735 736

enum SelectorElementType {
  SELECTOR_TYPE_NAME,
  SELECTOR_NAME,
737
  SELECTOR_GTYPE,
738
  SELECTOR_REGION,
739
  SELECTOR_CLASS,
740
  SELECTOR_GLOB
Carlos Garnacho's avatar
Carlos Garnacho committed
741 742
};

743 744 745 746 747
enum CombinatorType {
  COMBINATOR_DESCENDANT, /* No direct relation needed */
  COMBINATOR_CHILD       /* Direct child */
};

Carlos Garnacho's avatar
Carlos Garnacho committed
748 749 750
struct SelectorElement
{
  SelectorElementType elem_type;
751
  CombinatorType combinator;
Carlos Garnacho's avatar
Carlos Garnacho committed
752 753 754 755 756

  union
  {
    GQuark name;
    GType type;
757 758 759 760

    struct
    {
      GQuark name;
761
      GtkRegionFlags flags;
762
    } region;
Carlos Garnacho's avatar
Carlos Garnacho committed
763 764 765 766 767 768
  };
};

struct SelectorPath
{
  GSList *elements;
769
  GtkStateFlags state;
Carlos Garnacho's avatar
Carlos Garnacho committed
770 771 772 773 774 775 776 777 778 779 780 781
  guint ref_count;
};

struct SelectorStyleInfo
{
  SelectorPath *path;
  GHashTable *style;
};

struct GtkCssProviderPrivate
{
  GScanner *scanner;
782
  gchar *filename;
783

784 785 786
  const gchar *buffer;
  const gchar *value_pos;

787 788
  GHashTable *symbolic_colors;

Carlos Garnacho's avatar
Carlos Garnacho committed
789 790 791 792 793 794 795 796 797 798
  GPtrArray *selectors_info;

  /* Current parser state */
  GSList *state;
  GSList *cur_selectors;
  GHashTable *cur_properties;
};

enum ParserScope {
  SCOPE_SELECTOR,
799
  SCOPE_PSEUDO_CLASS,
800
  SCOPE_NTH_CHILD,
Carlos Garnacho's avatar
Carlos Garnacho committed
801 802 803 804
  SCOPE_DECLARATION,
  SCOPE_VALUE
};

805 806 807 808 809
/* Extend GtkStateType, since these
 * values are also used as symbols
 */
enum ParserSymbol {
  /* Scope: pseudo-class */
Carlos Garnacho's avatar
Carlos Garnacho committed
810
  SYMBOL_NTH_CHILD = GTK_STATE_FOCUSED + 1,
811 812
  SYMBOL_FIRST_CHILD,
  SYMBOL_LAST_CHILD,
813
  SYMBOL_SORTED_CHILD,
814 815 816 817 818 819 820 821

  /* Scope: nth-child */
  SYMBOL_NTH_CHILD_EVEN,
  SYMBOL_NTH_CHILD_ODD,
  SYMBOL_NTH_CHILD_FIRST,
  SYMBOL_NTH_CHILD_LAST
};

Carlos Garnacho's avatar
Carlos Garnacho committed
822 823 824
static void gtk_css_provider_finalize (GObject *object);
static void gtk_css_style_provider_iface_init (GtkStyleProviderIface *iface);

825 826
static void scanner_apply_scope (GScanner    *scanner,
                                 ParserScope  scope);
827 828 829 830
static gboolean css_provider_parse_value (GtkCssProvider  *css_provider,
                                          const gchar     *value_str,
                                          GValue          *value,
                                          GError         **error);
831 832 833 834 835
static gboolean gtk_css_provider_load_from_path_internal (GtkCssProvider  *css_provider,
                                                          const gchar     *path,
                                                          gboolean         reset,
                                                          GError         **error);

Matthias Clasen's avatar
Matthias Clasen committed
836 837 838 839 840 841
GQuark
gtk_css_provider_error_quark (void)
{
  return g_quark_from_static_string ("gtk-css-provider-error-quark");
}

Carlos Garnacho's avatar
Carlos Garnacho committed
842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883
G_DEFINE_TYPE_EXTENDED (GtkCssProvider, gtk_css_provider, G_TYPE_OBJECT, 0,
                        G_IMPLEMENT_INTERFACE (GTK_TYPE_STYLE_PROVIDER,
                                               gtk_css_style_provider_iface_init));

static void
gtk_css_provider_class_init (GtkCssProviderClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->finalize = gtk_css_provider_finalize;

  g_type_class_add_private (object_class, sizeof (GtkCssProviderPrivate));
}

static SelectorPath *
selector_path_new (void)
{
  SelectorPath *path;

  path = g_slice_new0 (SelectorPath);
  path->ref_count = 1;

  return path;
}

static SelectorPath *
selector_path_ref (SelectorPath *path)
{
  path->ref_count++;
  return path;
}

static void
selector_path_unref (SelectorPath *path)
{
  path->ref_count--;

  if (path->ref_count > 0)
    return;

  while (path->elements)
    {
884
      g_slice_free (SelectorElement, path->elements->data);
Carlos Garnacho's avatar
Carlos Garnacho committed
885 886 887 888 889 890 891 892 893 894 895 896 897 898
      path->elements = g_slist_delete_link (path->elements, path->elements);
    }

  g_slice_free (SelectorPath, path);
}

static void
selector_path_prepend_type (SelectorPath *path,
                            const gchar  *type_name)
{
  SelectorElement *elem;
  GType type;

  elem = g_slice_new (SelectorElement);
899
  elem->combinator = COMBINATOR_DESCENDANT;
Carlos Garnacho's avatar
Carlos Garnacho committed
900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915
  type = g_type_from_name (type_name);

  if (type == G_TYPE_INVALID)
    {
      elem->elem_type = SELECTOR_TYPE_NAME;
      elem->name = g_quark_from_string (type_name);
    }
  else
    {
      elem->elem_type = SELECTOR_GTYPE;
      elem->type = type;
    }

  path->elements = g_slist_prepend (path->elements, elem);
}

916 917 918 919 920 921 922
static void
selector_path_prepend_glob (SelectorPath *path)
{
  SelectorElement *elem;

  elem = g_slice_new (SelectorElement);
  elem->elem_type = SELECTOR_GLOB;
923
  elem->combinator = COMBINATOR_DESCENDANT;
924 925 926 927

  path->elements = g_slist_prepend (path->elements, elem);
}

928
static void
929 930 931
selector_path_prepend_region (SelectorPath   *path,
                              const gchar    *name,
                              GtkRegionFlags  flags)
932 933 934 935 936 937 938 939 940 941 942 943 944
{
  SelectorElement *elem;

  elem = g_slice_new (SelectorElement);
  elem->combinator = COMBINATOR_DESCENDANT;
  elem->elem_type = SELECTOR_REGION;

  elem->region.name = g_quark_from_string (name);
  elem->region.flags = flags;

  path->elements = g_slist_prepend (path->elements, elem);
}

945 946 947 948 949 950 951 952 953 954 955 956 957 958 959
static void
selector_path_prepend_name (SelectorPath *path,
                            const gchar  *name)
{
  SelectorElement *elem;

  elem = g_slice_new (SelectorElement);
  elem->combinator = COMBINATOR_DESCENDANT;
  elem->elem_type = SELECTOR_NAME;

  elem->name = g_quark_from_string (name);

  path->elements = g_slist_prepend (path->elements, elem);
}

960 961 962 963 964 965 966 967 968 969 970 971 972 973 974
static void
selector_path_prepend_class (SelectorPath *path,
                             const gchar  *name)
{
  SelectorElement *elem;

  elem = g_slice_new (SelectorElement);
  elem->combinator = COMBINATOR_DESCENDANT;
  elem->elem_type = SELECTOR_CLASS;

  elem->name = g_quark_from_string (name);

  path->elements = g_slist_prepend (path->elements, elem);
}

975 976 977 978 979 980 981 982 983 984 985 986 987
static void
selector_path_prepend_combinator (SelectorPath   *path,
                                  CombinatorType  combinator)
{
  SelectorElement *elem;

  g_assert (path->elements != NULL);

  /* It is actually stored in the last element */
  elem = path->elements->data;
  elem->combinator = combinator;
}

988 989 990 991 992 993
static gint
selector_path_depth (SelectorPath *path)
{
  return g_slist_length (path->elements);
}

Carlos Garnacho's avatar
Carlos Garnacho committed
994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012
static SelectorStyleInfo *
selector_style_info_new (SelectorPath *path)
{
  SelectorStyleInfo *info;

  info = g_slice_new0 (SelectorStyleInfo);
  info->path = selector_path_ref (path);

  return info;
}

static void
selector_style_info_free (SelectorStyleInfo *info)
{
  if (info->style)
    g_hash_table_unref (info->style);

  if (info->path)
    selector_path_unref (info->path);
1013 1014

  g_slice_free (SelectorStyleInfo, info);
Carlos Garnacho's avatar
Carlos Garnacho committed
1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029
}

static void
selector_style_info_set_style (SelectorStyleInfo *info,
                               GHashTable        *style)
{
  if (info->style)
    g_hash_table_unref (info->style);

  if (style)
    info->style = g_hash_table_ref (style);
  else
    info->style = NULL;
}

1030 1031
static GScanner *
create_scanner (void)
Carlos Garnacho's avatar
Carlos Garnacho committed
1032 1033 1034 1035 1036
{
  GScanner *scanner;

  scanner = g_scanner_new (NULL);

1037 1038 1039 1040 1041
  g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "active", GUINT_TO_POINTER (GTK_STATE_ACTIVE));
  g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "prelight", GUINT_TO_POINTER (GTK_STATE_PRELIGHT));
  g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "hover", GUINT_TO_POINTER (GTK_STATE_PRELIGHT));
  g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "selected", GUINT_TO_POINTER (GTK_STATE_SELECTED));
  g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "insensitive", GUINT_TO_POINTER (GTK_STATE_INSENSITIVE));
1042 1043
  g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "inconsistent", GUINT_TO_POINTER (GTK_STATE_INCONSISTENT));
  g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "focused", GUINT_TO_POINTER (GTK_STATE_FOCUSED));
Matthias Clasen's avatar
Matthias Clasen committed
1044
  g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "focus", GUINT_TO_POINTER (GTK_STATE_FOCUSED));
1045

1046 1047 1048
  g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "nth-child", GUINT_TO_POINTER (SYMBOL_NTH_CHILD));
  g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "first-child", GUINT_TO_POINTER (SYMBOL_FIRST_CHILD));
  g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "last-child", GUINT_TO_POINTER (SYMBOL_LAST_CHILD));
1049
  g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "sorted", GUINT_TO_POINTER (SYMBOL_SORTED_CHILD));
1050 1051 1052 1053 1054 1055

  g_scanner_scope_add_symbol (scanner, SCOPE_NTH_CHILD, "even", GUINT_TO_POINTER (SYMBOL_NTH_CHILD_EVEN));
  g_scanner_scope_add_symbol (scanner, SCOPE_NTH_CHILD, "odd", GUINT_TO_POINTER (SYMBOL_NTH_CHILD_ODD));
  g_scanner_scope_add_symbol (scanner, SCOPE_NTH_CHILD, "first", GUINT_TO_POINTER (SYMBOL_NTH_CHILD_FIRST));
  g_scanner_scope_add_symbol (scanner, SCOPE_NTH_CHILD, "last", GUINT_TO_POINTER (SYMBOL_NTH_CHILD_LAST));

1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071
  scanner_apply_scope (scanner, SCOPE_SELECTOR);

  return scanner;
}

static void
gtk_css_provider_init (GtkCssProvider *css_provider)
{
  GtkCssProviderPrivate *priv;

  priv = css_provider->priv = G_TYPE_INSTANCE_GET_PRIVATE (css_provider,
                                                           GTK_TYPE_CSS_PROVIDER,
                                                           GtkCssProviderPrivate);

  priv->selectors_info = g_ptr_array_new_with_free_func ((GDestroyNotify) selector_style_info_free);
  priv->scanner = create_scanner ();
1072 1073 1074 1075

  priv->symbolic_colors = g_hash_table_new_full (g_str_hash, g_str_equal,
                                                 (GDestroyNotify) g_free,
                                                 (GDestroyNotify) gtk_symbolic_color_unref);
Carlos Garnacho's avatar
Carlos Garnacho committed
1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087
}

typedef struct ComparePathData ComparePathData;

struct ComparePathData
{
  guint64 score;
  SelectorPath *path;
  GSList *iter;
};

static gboolean
Carlos Garnacho's avatar
Carlos Garnacho committed
1088 1089 1090 1091
compare_selector_element (GtkWidgetPath   *path,
                          guint            index,
                          SelectorElement *elem,
                          guint8          *score)
Carlos Garnacho's avatar
Carlos Garnacho committed
1092
{
Carlos Garnacho's avatar
Carlos Garnacho committed
1093
  *score = 0;
Carlos Garnacho's avatar
Carlos Garnacho committed
1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108

  if (elem->elem_type == SELECTOR_TYPE_NAME)
    {
      const gchar *type_name;
      GType resolved_type;

      /* Resolve the type name */
      type_name = g_quark_to_string (elem->name);
      resolved_type = g_type_from_name (type_name);

      if (resolved_type == G_TYPE_INVALID)
        {
          /* Type couldn't be resolved, so the selector
           * clearly doesn't affect the given widget path
           */
Carlos Garnacho's avatar
Carlos Garnacho committed
1109
          return FALSE;
Carlos Garnacho's avatar
Carlos Garnacho committed
1110 1111 1112 1113 1114 1115 1116 1117
        }

      elem->elem_type = SELECTOR_GTYPE;
      elem->type = resolved_type;
    }

  if (elem->elem_type == SELECTOR_GTYPE)
    {
Carlos Garnacho's avatar
Carlos Garnacho committed
1118 1119
      GType type;

1120
      type = gtk_widget_path_iter_get_object_type (path, index);
Carlos Garnacho's avatar
Carlos Garnacho committed
1121

Carlos Garnacho's avatar
Carlos Garnacho committed
1122
      if (!g_type_is_a (type, elem->type))
Carlos Garnacho's avatar
Carlos Garnacho committed
1123 1124 1125 1126
        return FALSE;

      if (type == elem->type)
        *score |= 0xF;
Carlos Garnacho's avatar
Carlos Garnacho committed
1127 1128 1129
      else
        {
          GType parent = type;
Carlos Garnacho's avatar
Carlos Garnacho committed
1130 1131

          *score = 0xE;
Carlos Garnacho's avatar
Carlos Garnacho committed
1132 1133 1134 1135 1136 1137

          while ((parent = g_type_parent (parent)) != G_TYPE_INVALID)
            {
              if (parent == elem->type)
                break;

Carlos Garnacho's avatar
Carlos Garnacho committed
1138
              *score -= 1;
Carlos Garnacho's avatar
Carlos Garnacho committed
1139

Carlos Garnacho's avatar
Carlos Garnacho committed
1140
              if (*score == 1)
Carlos Garnacho's avatar
Carlos Garnacho committed
1141 1142 1143 1144 1145 1146
                {
                  g_warning ("Hierarchy is higher than expected.");
                  break;
                }
            }
        }
Carlos Garnacho's avatar
Carlos Garnacho committed
1147 1148

      return TRUE;
Carlos Garnacho's avatar
Carlos Garnacho committed
1149
    }
1150 1151
  else if (elem->elem_type == SELECTOR_REGION)
    {
1152
      GtkRegionFlags flags;
1153

1154 1155 1156
      if (!gtk_widget_path_iter_has_qregion (path, index,
                                             elem->region.name,
                                             &flags))
1157 1158 1159 1160 1161 1162 1163 1164 1165
        return FALSE;

      if (elem->region.flags != 0 &&
          (flags & elem->region.flags) == 0)
        return FALSE;

      *score = 0xF;
      return TRUE;
    }
1166 1167 1168
  else if (elem->elem_type == SELECTOR_GLOB)
    {
      /* Treat as lowest matching type */
Carlos Garnacho's avatar
Carlos Garnacho committed
1169 1170
      *score = 1;
      return TRUE;
1171
    }
1172 1173
  else if (elem->elem_type == SELECTOR_NAME)
    {
1174
      if (!gtk_widget_path_iter_has_qname (path, index, elem->name))
1175 1176 1177 1178 1179
        return FALSE;

      *score = 0xF;
      return TRUE;
    }
1180 1181 1182 1183 1184 1185 1186 1187
  else if (elem->elem_type == SELECTOR_CLASS)
    {
      if (!gtk_widget_path_iter_has_qclass (path, index, elem->name))
        return FALSE;

      *score = 0xF;
      return TRUE;
    }
Carlos Garnacho's avatar
Carlos Garnacho committed
1188

Carlos Garnacho's avatar
Carlos Garnacho committed
1189
  return FALSE;
Carlos Garnacho's avatar
Carlos Garnacho committed
1190 1191 1192 1193 1194 1195
}

static guint64
compare_selector (GtkWidgetPath *path,
                  SelectorPath  *selector)
{
Carlos Garnacho's avatar
Carlos Garnacho committed
1196
  GSList *elements = selector->elements;
1197
  gboolean match = TRUE, first = TRUE, first_match = FALSE;
Carlos Garnacho's avatar
Carlos Garnacho committed
1198
  guint64 score = 0;
1199
  gint i;
Carlos Garnacho's avatar
Carlos Garnacho committed
1200

1201
  i = gtk_widget_path_length (path) - 1;
1202

1203
  while (elements && match && i >= 0)
Carlos Garnacho's avatar
Carlos Garnacho committed
1204 1205 1206
    {
      SelectorElement *elem;
      guint8 elem_score;
Carlos Garnacho's avatar
Carlos Garnacho committed
1207

Carlos Garnacho's avatar
Carlos Garnacho committed
1208
      elem = elements->data;
Carlos Garnacho's avatar
Carlos Garnacho committed
1209

Carlos Garnacho's avatar
Carlos Garnacho committed
1210
      match = compare_selector_element (path, i, elem, &elem_score);
1211

1212 1213 1214
      if (match && first)
        first_match = TRUE;

1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226
      /* Only move on to the next index if there is no match
       * with the current element (whether to continue or not
       * handled right after in the combinator check), or a
       * GType or glob has just been matched.
       *
       * Region and widget names do not trigger this because
       * the next element in the selector path could also be
       * related to the same index.
       */
      if (!match ||
          (elem->elem_type == SELECTOR_GTYPE ||
           elem->elem_type == SELECTOR_GLOB))
1227
        i--;
Carlos Garnacho's avatar
Carlos Garnacho committed
1228

1229 1230 1231
      if (!match &&
          elem->elem_type != SELECTOR_TYPE_NAME &&
          elem->combinator == COMBINATOR_DESCENDANT)
Carlos Garnacho's avatar
Carlos Garnacho committed
1232 1233 1234 1235 1236 1237
        {
          /* With descendant combinators there may
           * be intermediate chidren in the hierarchy
           */
          match = TRUE;
        }
1238
      else if (match)
1239
        elements = elements->next;
Carlos Garnacho's avatar
Carlos Garnacho committed
1240 1241 1242

      if (match)
        {
1243
          /* Only 4 bits are actually used */
Carlos Garnacho's avatar
Carlos Garnacho committed
1244 1245 1246
          score <<= 4;
          score |= elem_score;
        }
1247 1248

      first = FALSE;
Carlos Garnacho's avatar
Carlos Garnacho committed
1249 1250
    }

Carlos Garnacho's avatar
Carlos Garnacho committed
1251 1252 1253 1254 1255 1256 1257 1258 1259
  /* If there are pending selector
   * elements to compare, it's not
   * a match.
   */
  if (elements)
    match = FALSE;

  if (!match)
    score = 0;
Carlos Garnacho's avatar