#include "gtkanimationdescription.h"
#include "gtk9slice.h"
#include "gtkcssprovider.h"
+#include "gtkprivate.h"
/**
* SECTION:gtkcssprovider
* @Title: GtkCssProvider
* @See_also: #GtkStyleContext, #GtkStyleProvider
*
- * #GtkCssProvider is an object implementing #GtkStyleProvider, it is able
- * to parse CSS-like input in order to style widgets.
+ * 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.
*
+ * <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,
+ * the file <filename><envar>$XDG_CONFIG_HOME</envar>/gtk-3.0/gtk.css</filename>
+ * is loaded if it exists. Then, GTK+ tries to load
+ * <filename><envar>$HOME</envar>/.themes/<replaceable>theme-name</replaceable>/gtk-3.0/gtk.css</filename>,
+ * falling back to
+ * <filename><replaceable>datadir</replaceable>/share/themes/<replaceable>theme-name</replaceable>/gtk-3.0/gtk.css</filename>,
+ * where <replaceable>theme-name</replaceable> is the name of the current theme
+ * (see the #GtkSettings:gtk-theme-name setting) and <replaceable>datadir</replaceable>
+ * 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: #ff00ea;
+ * font: Comic Sans 12
+ * }
+ * </programlisting>
+ * </example>
+ * </refsect2>
* <refsect2 id="gtkcssprovider-selectors">
- * <title>Widget selectors</title>
+ * <title>Selectors</title>
+ * <para>
+ * 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
+ * '#' character. The '*' character represents the so-called universal
+ * selector, which matches any widget.
+ * </para>
* <para>
- * Selectors work in a really similar way than in common CSS, widget object
- * names act as HTML tags:
+ * 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#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>
* </para>
* <example>
- * <title>Widgets in selectors</title>
- * <programlisting>
+ * <title>Widget classes and names in selectors</title>
+ * <programlisting language="text">
* /* Theme labels that are descendants of a window */
* GtkWindow GtkLabel {
- * background-color: #898989;
+ * background-color: #898989
* }
*
* /* Theme notebooks, and anything that's within these */
* GtkNotebook {
- * background-color: #a939f0;
+ * background-color: #a939f0
* }
*
* /* Theme combo boxes, and entries that
* are direct children of a notebook */
* GtkComboBox,
* GtkNotebook > GtkEntry {
- * background-color: #1209a2;
+ * color: @fg_color;
+ * background-color: #1209a2
* }
*
* /* Theme any widget within a GtkBin */
* GtkBin * {
- * font-name: Sans 20;
+ * font-name: Sans 20
* }
- * </programlisting>
- * </example>
- * <para>
- * Widget names may be matched in CSS as well:
- * </para>
- * <example>
- * <title>Widget names in selectors</title>
- * <programlisting>
+ *
* /* Theme a label named title-label */
* GtkLabel#title-label {
- * font-name: Sans 15;
+ * font-name: Sans 15
* }
*
* /* Theme any widget named main-entry */
* #main-entry {
- * background-color: #f0a810;
+ * background-color: #f0a810
* }
* </programlisting>
* </example>
* <para>
- * Widgets may also define different classes, so these can be matched
- * in CSS:
+ * 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
+ * style classes they define and see <xref linkend="gtkstylecontext-classes"/>
+ * for a list of all style classes used by GTK+ widgets.
* </para>
* <example>
- * <title>Widget names in selectors</title>
- * <programlisting>
+ * <title>Style classes in selectors</title>
+ * <programlisting language="text">
* /* Theme all widgets defining the class entry */
* .entry {
* color: #39f1f9;
*
* /* Theme spinbuttons' entry */
* GtkSpinButton.entry {
- * color: #900185;
+ * color: #900185
* }
* </programlisting>
* </example>
* <para>
- * Container widgets may define regions, these region names may be
- * referenced in CSS, it's also possible to apply :nth-child
- * pseudo-class information if the widget implementation provides
- * such data.
+ * 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
+ * regions and pseudo-classes they define and see
+ * <xref linkend="gtkstylecontext-classes"/> for a list of all regions
+ * used by GTK+ widgets.
* </para>
* <example>
- * <title>Region names in containers</title>
- * <programlisting>
+ * <title>Regions in selectors</title>
+ * <programlisting language="text">
* /* Theme any label within a notebook */
* GtkNotebook GtkLabel {
* color: #f90192;
* }
*
* /* Theme labels within notebook tabs */
- * GtkNotebook tab:nth-child GtkLabel {
+ * GtkNotebook tab GtkLabel {
* color: #703910;
* }
*
* </programlisting>
* </example>
* <para>
- * Widget states may be matched as pseudoclasses.
- * Given the needed widget states differ from the
- * offered pseudoclasses in CSS, some are unsupported,
- * and some custom ones have been added.
+ * 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.
* </para>
* <example>
* <title>Styling specific widget states</title>
- * <programlisting>
+ * <programlisting language="text">
* /* Theme active (pressed) buttons */
* GtkButton:active {
* background-color: #0274d9;
* }
*
- * /* Theme buttons with the mouse pointer on it */
+ * /* Theme buttons with the mouse pointer on it,
+ * both are equivalent */
* GtkButton:hover,
* GtkButton:prelight {
* background-color: #3085a9;
* </programlisting>
* </example>
* <para>
- * Widget state pseudoclasses may only apply to the
- * last element in a selector.
+ * Widget state pseudoclasses may only apply to the last element
+ * in a selector.
* </para>
* <para>
- * All the mentioned elements may be combined to create
- * complex selectors that match specific widget paths.
- * As in CSS, rules apply by specificity, so the selectors
- * describing the best a widget path will take precedence
+ * 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
* over the others.
* </para>
* </refsect2>
* <refsect2 id="gtkcssprovider-rules">
- * <title>@ rules</title>
+ * <title>@ Rules</title>
* <para>
- * GTK+'s CSS supports the @import rule, in order
- * to load another CSS file in addition to the currently
- * parsed one.
+ * GTK+'s CSS supports the @import rule, in order to load another
+ * CSS style sheet in addition to the currently parsed one.
* </para>
* <example>
* <title>Using the @import rule</title>
- * <programlisting>
- * @import url (path/to/common.css)
+ * <programlisting language="text">
+ * @import url ("path/to/common.css");
* </programlisting>
* </example>
* <para>
- * GTK+ also supports an additional @define-color
- * rule, in order to define a color name which may be used
- * instead of color numeric representations.
+ * GTK+ also supports an additional @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.
* </para>
* <example>
* <title>Defining colors</title>
- * <programlisting>
+ * <programlisting language="text">
* @define-color bg_color #f9a039;
*
* * {
* <refsect2 id="gtkcssprovider-symbolic-colors">
* <title>Symbolic colors</title>
* <para>
- * Besides being able to define color names, the CSS
- * parser is also able to read different color modifiers,
- * which can also be nested, providing a rich language
- * to define colors starting from other colors.
+ * 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.
* </para>
* <example>
* <title>Using symbolic colors</title>
- * <programlisting>
+ * <programlisting language="text">
* @define-color entry-color shade (@bg_color, 0.7);
*
* GtkEntry {
* }
* </programlisting>
* </example>
+ * <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>
+ * <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>
+ * </row>
+ * <row>
+ * <entry>rgba(@r, @g, @b, @a)</entry>
+ * <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>
+ * </row>
+ * <row>
+ * <entry>#@xxyyzz</entry>
+ * <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>#ff12ab
+ * #f0c</literallayout></entry>
+ * </row>
+ * <row>
+ * <entry>@name</entry>
+ * <entry>Reference to a color that has been defined with
+ * @define-color
+ * </entry>
+ * <entry>@bg_color</entry>
+ * </row>
+ * <row>
+ * <entry>mix(@color1, @color2, @f)</entry>
+ * <entry>A linear combination of @color1 and @color2. @f is a
+ * floating point number between 0 and 1.</entry>
+ * <entry><literallayout>mix(#ff1e0a, @bg_color, 0.8)</literallayout></entry>
+ * </row>
+ * <row>
+ * <entry>shade(@color, @f)</entry>
+ * <entry>A lighter or darker variant of @color. @f is a
+ * floating point number.
+ * </entry>
+ * <entry>shade(@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>
+ * </refsect2>
+ * <refsect2 id="gtkcssprovider-gradients">
+ * <title>Gradients</title>
+ * <para>
+ * Linear or radial Gradients can be used as background images.
+ * </para>
+ * <para>
+ * A linear gradient along the line from (@start_x, @start_y) to
+ * (@end_x, @end_y) is specified using the syntax
+ * <literallayout>-gtk-gradient (linear,
+ * @start_x @start_y, @end_x @end_y,
+ * color-stop (@position, @color),
+ * ...)</literallayout>
+ * 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
+ * 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(@yellow), to(@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, @yellow),
+ * color-stop(0.2, @blue),
+ * color-stop(1, #0f0))</literallayout></para>
+ * </example>
+ * <para>
+ * 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
+ * syntax
+ * <literallayout>-gtk-gradient (radial,
+ * @start_x @start_y, @start_radius,
+ * @end_x @end_y, @end_radius,
+ * color-stop (@position, @color),
+ * ...)</literallayout>
+ * where @start_radius and @end_radius are floating point numbers and
+ * 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(@yellow), to(@green))</literallayout></para>
+ * </example>
+ * <example>
+ * <title>Another radial gradient</title>
+ * <inlinegraphic fileref="gradient4.png" format="PNG"/>
+ * <para>This gradient was specified with
+ * <literallayout>-gtk-gradient (radial,
+ * 0.4 0.4, 0.1,
+ * 0.6 0.6, 0.7,
+ * color-stop (0, #f00),
+ * color-stop (0.1, #a0f),
+ * color-stop (0.2, @yellow),
+ * color-stop (1, @green))</literallayout></para>
+ * </example>
+ * </refsect2>
+ * <refsect2 id="gtkcssprovider-slices">
+ * <title>Border images</title>
+ * <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"/>
+ * <para>This border image was specified with
+ * <literallayout>url("gradient1.png") 10 10 10 10</literallayout>
+ * </para>
+ * </example>
+ * <example>
+ * <title>A repeating border image</title>
+ * <inlinegraphic fileref="border2.png" format="PNG"/>
+ * <para>This border image was specified with
+ * <literallayout>url("gradient1.png") 10 10 10 10 repeat</literallayout>
+ * </para>
+ * </example>
+ * <example>
+ * <title>A stretched border image</title>
+ * <inlinegraphic fileref="border3.png" format="PNG"/>
+ * <para>This border image was specified with
+ * <literallayout>url("gradient1.png") 10 10 10 10 stretch</literallayout>
+ * </para>
+ * </example>
+ * </refsect2>
+ * <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>
* <refsect2 id="gtkcssprovider-properties">
* <title>Supported properties</title>
* 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
- * @font property would be supported.
+ * @font property is supported.
* </para>
* <para>
* The currently supported properties are:
* <tbody>
* <row>
* <entry>engine</entry>
- * <entry><programlisting>engine-name</programlisting></entry>
+ * <entry>engine-name</entry>
* <entry>#GtkThemingEngine</entry>
- * <entry><programlisting>engine: clearlooks;</programlisting></entry>
+ * <entry>engine: clearlooks;</entry>
* </row>
* <row>
* <entry>background-color</entry>
- * <entry morerows="1"><programlisting>color</programlisting></entry>
- * <entry morerows="1">#GdkRGBA</entry>
- * <entry morerows="1">
- * <programlisting>
- * background-color: #fff;
- * color: @color-name;
- * background-color: shade (@color-name, 0.5);
- * color: mix (@color-name, #f0f, 0.8);</programlisting>
+ * <entry morerows="2">color (see above)</entry>
+ * <entry morerows="2">#GdkRGBA</entry>
+ * <entry morerows="2"><literallayout>background-color: #fff;
+ * color: &color1;
+ * background-color: shade (&color1, 0.5);
+ * color: mix (&color1, #f0f, 0.8);</literallayout>
* </entry>
* </row>
* <row>
* <entry>color</entry>
* </row>
* <row>
+ * <entry>border-color</entry>
+ * </row>
+ * <row>
* <entry>font</entry>
- * <entry><programlisting>family [style] [size]</programlisting></entry>
+ * <entry>@family [@style] [@size]</entry>
* <entry>#PangoFontDescription</entry>
- * <entry><programlisting>font: Sans 15;</programlisting></entry>
+ * <entry>font: Sans 15;</entry>
* </row>
* <row>
* <entry>margin</entry>
- * <entry morerows="1">
- * <programlisting>
- * width
- * vertical-width horizontal-width
- * top-width horizontal-width bottom-width
- * top-width right-width bottom-width left-width
- * </programlisting>
+ * <entry morerows="1"><literallayout>@width
+ * @vertical_width @horizontal_width
+ * @top_width @horizontal_width @bottom_width
+ * @top_width @right_width @bottom_width @left_width</literallayout>
* </entry>
* <entry morerows="1">#GtkBorder</entry>
- * <entry morerows="1">
- * <programlisting>
- * margin: 5;
+ * <entry morerows="1"><literallayout>margin: 5;
* margin: 5 10;
* margin: 5 10 3;
- * margin: 5 10 3 5;</programlisting>
+ * margin: 5 10 3 5;</literallayout>
* </entry>
* </row>
* <row>
* </row>
* <row>
* <entry>background-image</entry>
- * <entry>
- * <programlisting>
- * -gtk-gradient (linear,
- * starting-x-position starting-y-position,
- * ending-x-position ending-y-position,
- * [ [from|to] (color) |
- * color-stop (percentage, color) ] )
- *
- * -gtk-gradient (radial,
- * starting-x-position starting-y-position, starting-radius,
- * ending-x-position ending-y-position, ending-radius,
- * [ [from|to] (color) |
- * color-stop (percentage, color) ]* )</programlisting>
- * </entry>
+ * <entry><literallayout>gradient (see above) or
+ * url(@path)</literallayout></entry>
* <entry>#cairo_pattern_t</entry>
- * <entry>
- * <programlisting>
- * -gtk-gradient (linear,
+ * <entry><literallayout>-gtk-gradient (linear,
* left top, right top,
* from (#fff), to (#000));
* -gtk-gradient (linear, 0.0 0.5, 0.5 1.0,
* center center, 0.2,
* center center, 0.8,
* color-stop (0.0, #fff),
- * color-stop (1.0, #000));</programlisting>
+ * color-stop (1.0, #000));
+ * url ('background.png');</literallayout>
* </entry>
* </row>
* <row>
+ * <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>
* <entry>border-image</entry>
- * <entry><programlisting>url([path]) top-distance right-distance bottom-distance left-distance horizontal-option vertical-option</programlisting></entry>
- * <entry></entry>
- * <entry>
- * <programlisting>
- * 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;</programlisting>
+ * <entry><literallayout>border image (see above)</literallayout></entry>
+ * <entry>internal use only</entry>
+ * <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>
* </entry>
* </row>
* <row>
* <entry>transition</entry>
- * <entry><programlisting>duration [s|ms] [linear|ease|ease-in|ease-out|ease-in-out] [loop]?</programlisting></entry>
- * <entry></entry>
- * <entry>
- * <programlisting>
- * transition: 150ms ease-in-out;
- * transition: 1s linear loop;</programlisting>
+ * <entry>transition (see above)</entry>
+ * <entry>internal use only</entry>
+ * <entry><literallayout>transition: 150ms ease-in-out;
+ * transition: 1s linear loop;</literallayout>
* </entry>
* </row>
* </tbody>
* </tgroup>
* </informaltable>
+ * <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>
* </refsect2>
*/
GScanner *scanner;
gchar *filename;
+ const gchar *buffer;
+ const gchar *value_pos;
+
GHashTable *symbolic_colors;
GPtrArray *selectors_info;
static void scanner_apply_scope (GScanner *scanner,
ParserScope scope);
-static gboolean css_provider_parse_value (GtkCssProvider *css_provider,
- const gchar *value_str,
- GValue *value);
+static gboolean css_provider_parse_value (GtkCssProvider *css_provider,
+ const gchar *value_str,
+ GValue *value,
+ GError **error);
static gboolean gtk_css_provider_load_from_path_internal (GtkCssProvider *css_provider,
const gchar *path,
gboolean reset,
GError **error);
+GQuark
+gtk_css_provider_error_quark (void)
+{
+ return g_quark_from_static_string ("gtk-css-provider-error-quark");
+}
G_DEFINE_TYPE_EXTENDED (GtkCssProvider, gtk_css_provider, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (GTK_TYPE_STYLE_PROVIDER,
g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "insensitive", GUINT_TO_POINTER (GTK_STATE_INSENSITIVE));
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));
+ g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "focus", GUINT_TO_POINTER (GTK_STATE_FOCUSED));
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));
{
GType type;
- type = gtk_widget_path_iter_get_widget_type (path, index);
+ type = gtk_widget_path_iter_get_object_type (path, index);
if (!g_type_is_a (type, elem->type))
return FALSE;
SelectorPath *selector)
{
GSList *elements = selector->elements;
- gboolean match = TRUE;
+ gboolean match = TRUE, first = TRUE, first_match = FALSE;
guint64 score = 0;
gint i;
match = compare_selector_element (path, i, elem, &elem_score);
+ if (match && first)
+ first_match = TRUE;
+
/* 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
score <<= 4;
score |= elem_score;
}
+
+ first = FALSE;
}
/* If there are pending selector
if (!match)
score = 0;
+ else if (first_match)
+ {
+ /* Assign more weight to these selectors
+ * that matched right from the first element.
+ */
+ score <<= 4;
+ }
return score;
}
static gboolean
gtk_css_provider_get_style_property (GtkStyleProvider *provider,
GtkWidgetPath *path,
- const gchar *property_name,
+ GtkStateFlags state,
+ GParamSpec *pspec,
GValue *value)
{
GArray *priority_info;
gboolean found = FALSE;
gchar *prop_name;
- GType path_type;
gint i;
- path_type = gtk_widget_path_get_widget_type (path);
-
prop_name = g_strdup_printf ("-%s-%s",
- g_type_name (path_type),
- property_name);
+ g_type_name (pspec->owner_type),
+ pspec->name);
priority_info = css_provider_get_selectors (GTK_CSS_PROVIDER (provider), path);
info = &g_array_index (priority_info, StylePriorityInfo, i);
val = g_hash_table_lookup (info->style, prop_name);
- if (val)
+ if (val &&
+ (info->state == 0 ||
+ info->state == state ||
+ ((info->state & state) != 0 &&
+ (info->state & ~(state)) == 0)))
{
const gchar *val_str;
val_str = g_value_get_string (val);
found = TRUE;
- css_provider_parse_value (GTK_CSS_PROVIDER (provider), val_str, value);
+ css_provider_parse_value (GTK_CSS_PROVIDER (provider), val_str, value, NULL);
break;
}
}
g_slist_foreach (priv->cur_selectors, (GFunc) selector_path_unref, NULL);
g_slist_free (priv->cur_selectors);
- g_hash_table_unref (priv->cur_properties);
- g_hash_table_destroy (priv->symbolic_colors);
+ if (priv->cur_properties)
+ g_hash_table_unref (priv->cur_properties);
+ if (priv->symbolic_colors)
+ g_hash_table_destroy (priv->symbolic_colors);
G_OBJECT_CLASS (gtk_css_provider_parent_class)->finalize (object);
}
if (scope == SCOPE_VALUE)
{
- scanner->config->cset_identifier_first = G_CSET_a_2_z "@#-_0123456789" G_CSET_A_2_Z;
- scanner->config->cset_identifier_nth = G_CSET_a_2_z "@#-_ 0123456789(),.%\t\n" G_CSET_A_2_Z;
+ scanner->config->cset_identifier_first = G_CSET_a_2_z G_CSET_A_2_Z G_CSET_DIGITS "@#-_";
+ scanner->config->cset_identifier_nth = G_CSET_a_2_z G_CSET_A_2_Z G_CSET_DIGITS "@#-_ +(),.%\t\n'/\"";
scanner->config->scan_identifier_1char = TRUE;
}
else if (scope == SCOPE_SELECTOR)
{
scanner->config->cset_identifier_first = G_CSET_a_2_z G_CSET_A_2_Z "*@";
- scanner->config->cset_identifier_nth = G_CSET_a_2_z "-_#." G_CSET_A_2_Z;
+ scanner->config->cset_identifier_nth = G_CSET_a_2_z G_CSET_A_2_Z G_CSET_DIGITS "-_#.";
scanner->config->scan_identifier_1char = TRUE;
}
else if (scope == SCOPE_PSEUDO_CLASS ||
scope == SCOPE_NTH_CHILD ||
scope == SCOPE_DECLARATION)
{
- scanner->config->cset_identifier_first = G_CSET_a_2_z "-" G_CSET_A_2_Z;
- scanner->config->cset_identifier_nth = G_CSET_a_2_z "-" G_CSET_A_2_Z;
+ scanner->config->cset_identifier_first = G_CSET_a_2_z G_CSET_A_2_Z "-_";
+ scanner->config->cset_identifier_nth = G_CSET_a_2_z G_CSET_A_2_Z G_CSET_DIGITS "-_";
scanner->config->scan_identifier_1char = FALSE;
}
else
priv->state = NULL;
scanner_apply_scope (priv->scanner, SCOPE_SELECTOR);
+ priv->scanner->user_data = NULL;
+ priv->value_pos = NULL;
g_slist_foreach (priv->cur_selectors, (GFunc) selector_path_unref, NULL);
g_slist_free (priv->cur_selectors);
}
#define SKIP_SPACES(s) while (s[0] == ' ' || s[0] == '\t' || s[0] == '\n') s++;
+#define SKIP_SPACES_BACK(s) while (s[0] == ' ' || s[0] == '\t' || s[0] == '\n') s--;
static GtkSymbolicColor *
symbolic_color_parse_str (const gchar *string,
- gchar **end_ptr)
+ gchar **end_ptr)
{
GtkSymbolicColor *symbolic_color = NULL;
gchar *str;
str++;
end = str;
- while (*end == '-' || *end == '_' ||
- g_ascii_isalpha (*end))
+ while (*end == '-' || *end == '_' || g_ascii_isalpha (*end))
end++;
name = g_strndup (str, end - str);
*end_ptr = (gchar *) str;
if (str[0] != ')')
- {
+ {
gtk_symbolic_color_unref (color1);
gtk_symbolic_color_unref (color2);
return NULL;
}
else
{
- /* color name? parse until first whitespace */
- while (*end != ' ' && *end != '\0')
+ /* Color name */
+ while (*end != '\0' &&
+ (g_ascii_isalnum (*end) || *end == ' '))
end++;
}
}
static GtkSymbolicColor *
-symbolic_color_parse (const gchar *str)
+symbolic_color_parse (const gchar *str,
+ GError **error)
{
GtkSymbolicColor *color;
gchar *end;
if (*end != '\0')
{
- g_warning ("Error parsing symbolic color \"%s\", stopped at char %ld : '%c'",
- str, end - str, *end);
+ g_set_error_literal (error,
+ GTK_CSS_PROVIDER_ERROR,
+ GTK_CSS_PROVIDER_ERROR_FAILED,
+ "Could not parse symbolic color");
if (color)
{
else
{
coords[i * 3] = g_ascii_strtod (str, &end);
+
+ if (str == end)
+ {
+ *end_ptr = (gchar *) str;
+ return NULL;
+ }
+
str = end;
}
else
{
coords[(i * 3) + 1] = g_ascii_strtod (str, &end);
+
+ if (str == end)
+ {
+ *end_ptr = (gchar *) str;
+ return NULL;
+ }
+
str = end;
}
str++;
SKIP_SPACES (str);
- position = g_strtod (str, &end);
+ position = g_ascii_strtod (str, &end);
str = end;
SKIP_SPACES (str);
return gradient;
}
-static GtkGradient *
-gradient_parse (const gchar *str)
+static gchar *
+path_parse_str (GtkCssProvider *css_provider,
+ const gchar *str,
+ gchar **end_ptr,
+ GError **error)
{
- GtkGradient *gradient;
- gchar *end;
-
- gradient = gradient_parse_str (str, &end);
+ gchar *path, *chr;
+ const gchar *start, *end;
+ start = str;
- if (*end != '\0')
+ if (g_str_has_prefix (str, "url"))
{
- g_warning ("Error parsing pattern \"%s\", stopped at char %ld : '%c'",
- str, end - str, *end);
+ str += strlen ("url");
+ SKIP_SPACES (str);
- if (gradient)
+ if (*str != '(')
{
- gtk_gradient_unref (gradient);
- gradient = NULL;
+ *end_ptr = (gchar *) str;
+ return NULL;
}
- }
- return gradient;
-}
+ chr = strchr (str, ')');
+ if (!chr)
+ {
+ *end_ptr = (gchar *) str;
+ return NULL;
+ }
-static gchar *
-path_parse_str (GtkCssProvider *css_provider,
- const gchar *str,
- gchar **end_ptr)
-{
- gchar *path, *chr;
+ end = chr + 1;
- if (!g_str_has_prefix (str, "url"))
- {
- *end_ptr = (gchar *) str;
- return NULL;
- }
+ str++;
+ SKIP_SPACES (str);
- str += strlen ("url");
- SKIP_SPACES (str);
+ if (*str == '"' || *str == '\'')
+ {
+ const gchar *p;
+ p = str;
+ str++;
- if (*str != '(')
- {
- *end_ptr = (gchar *) str;
- return NULL;
- }
+ chr--;
+ SKIP_SPACES_BACK (chr);
+
+ if (*chr != *p || chr == p)
+ {
+ *end_ptr = (gchar *)str;
+ return NULL;
+ }
+ }
+ else
+ {
+ *end_ptr = (gchar *)str;
+ return NULL;
+ }
- chr = strchr (str, ')');
+ path = g_strndup (str, chr - str);
+ g_strstrip (path);
- if (!chr)
+ *end_ptr = (gchar *)end;
+ }
+ else
{
- *end_ptr = (gchar *) str;
- return NULL;
+ path = g_strdup (str);
+ *end_ptr = (gchar *)str + strlen (str);
}
- str++;
- SKIP_SPACES (str);
-
- path = g_strndup (str, chr - str);
- g_strstrip (path);
-
- *end_ptr = chr + 1;
-
/* Always return an absolute path */
if (!g_path_is_absolute (path))
{
priv = css_provider->priv;
/* Use relative path to the current CSS file path, if any */
- dirname = g_path_get_dirname (priv->filename);
+ if (priv->filename)
+ dirname = g_path_get_dirname (priv->filename);
+ else
+ dirname = g_get_current_dir ();
full_path = g_build_filename (dirname, path, NULL);
g_free (path);
if (!g_file_test (path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))
{
- g_warning ("File doesn't exist: %s\n", path);
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_EXIST,
+ "File doesn't exist: %s", path);
g_free (path);
path = NULL;
+ *end_ptr = (gchar *)start;
}
return path;
}
static gchar *
-path_parse (GtkCssProvider *css_provider,
- const gchar *str)
+path_parse (GtkCssProvider *css_provider,
+ const gchar *str,
+ GError **error)
{
- gchar *path, *end;
+ gchar *path;
+ gchar *end;
+
+ path = path_parse_str (css_provider, str, &end, error);
- path = path_parse_str (css_provider, str, &end);
+ if (!path)
+ return NULL;
if (*end != '\0')
{
- g_warning ("Error parsing file path \"%s\", stopped at char %ld : '%c'",
- str, end - str, *end);
-
- if (path)
- {
- g_free (path);
- path = NULL;
- }
+ g_set_error_literal (error,
+ GTK_CSS_PROVIDER_ERROR,
+ GTK_CSS_PROVIDER_ERROR_FAILED,
+ "Error parsing path");
+ g_free (path);
+ path = NULL;
}
return path;
static Gtk9Slice *
slice_parse_str (GtkCssProvider *css_provider,
const gchar *str,
- gchar **end_ptr)
+ gchar **end_ptr,
+ GError **error)
{
gdouble distance_top, distance_bottom;
gdouble distance_left, distance_right;
GtkSliceSideModifier mods[2];
- GError *error = NULL;
GdkPixbuf *pixbuf;
Gtk9Slice *slice;
gchar *path;
SKIP_SPACES (str);
/* Parse image url */
- path = path_parse_str (css_provider, str, end_ptr);
+ path = path_parse_str (css_provider, str, end_ptr, error);
if (!path)
return NULL;
SKIP_SPACES (str);
/* Parse top/left/bottom/right distances */
- distance_top = g_strtod (str, end_ptr);
+ distance_top = g_ascii_strtod (str, end_ptr);
str = *end_ptr;
SKIP_SPACES (str);
- distance_right = g_strtod (str, end_ptr);
+ distance_right = g_ascii_strtod (str, end_ptr);
str = *end_ptr;
SKIP_SPACES (str);
- distance_bottom = g_strtod (str, end_ptr);
+ distance_bottom = g_ascii_strtod (str, end_ptr);
str = *end_ptr;
SKIP_SPACES (str);
- distance_left = g_strtod (str, end_ptr);
+ distance_left = g_ascii_strtod (str, end_ptr);
str = *end_ptr;
SKIP_SPACES (str);
mods[1] = mods[0];
}
- pixbuf = gdk_pixbuf_new_from_file (path, &error);
+ pixbuf = gdk_pixbuf_new_from_file (path, error);
g_free (path);
- if (error)
+ if (!pixbuf)
{
- g_warning ("Pixbuf could not be loaded: %s\n", error->message);
- g_error_free (error);
*end_ptr = (gchar *) str;
return NULL;
}
- slice = gtk_9slice_new (pixbuf,
- distance_top, distance_bottom,
- distance_left, distance_right,
- mods[0], mods[1]);
+ slice = _gtk_9slice_new (pixbuf,
+ distance_top, distance_bottom,
+ distance_left, distance_right,
+ mods[0], mods[1]);
g_object_unref (pixbuf);
return slice;
}
-static Gtk9Slice *
-slice_parse (GtkCssProvider *css_provider,
- const gchar *str)
-{
- Gtk9Slice *slice;
- gchar *end;
-
- slice = slice_parse_str (css_provider, str, &end);
-
- if (*end != '\0')
- {
- g_warning ("Error parsing sliced image \"%s\", stopped at char %ld : '%c'",
- str, end - str, *end);
-
- if (slice)
- {
- gtk_9slice_unref (slice);
- slice = NULL;
- }
- }
-
- return slice;
-}
-
static gdouble
unit_parse_str (const gchar *str,
gchar **end_str)
gdouble unit;
SKIP_SPACES (str);
- unit = g_strtod (str, end_str);
+ unit = g_ascii_strtod (str, end_str);
str = *end_str;
/* Now parse the unit type, if any. We
return border;
}
-static GtkBorder *
-border_parse (const gchar *str)
-{
- GtkBorder *border;
- gchar *end;
-
- border = border_parse_str (str, &end);
-
- if (*end != '\0')
- {
- g_warning ("Error parsing border \"%s\", stopped at char %ld : '%c'",
- str, end - str, *end);
-
- if (border)
- gtk_border_free (border);
-
- return NULL;
- }
-
- return border;
-}
-
static gboolean
-css_provider_parse_value (GtkCssProvider *css_provider,
- const gchar *value_str,
- GValue *value)
+css_provider_parse_value (GtkCssProvider *css_provider,
+ const gchar *value_str,
+ GValue *value,
+ GError **error)
{
+ GtkCssProviderPrivate *priv;
GType type;
gboolean parsed = TRUE;
+ gchar *end = NULL;
+ priv = css_provider->priv;
type = G_VALUE_TYPE (value);
if (type == GDK_TYPE_RGBA ||
type == GDK_TYPE_COLOR)
{
- GdkRGBA color;
- GdkColor rgb;
+ GdkRGBA rgba;
+ GdkColor color;
if (type == GDK_TYPE_RGBA &&
- gdk_rgba_parse (&color, value_str))
- g_value_set_boxed (value, &color);
+ gdk_rgba_parse (&rgba, value_str))
+ g_value_set_boxed (value, &rgba);
else if (type == GDK_TYPE_COLOR &&
- gdk_color_parse (value_str, &rgb))
- g_value_set_boxed (value, &rgb);
+ gdk_color_parse (value_str, &color))
+ g_value_set_boxed (value, &color);
else
{
GtkSymbolicColor *symbolic_color;
- symbolic_color = symbolic_color_parse (value_str);
-
- if (!symbolic_color)
- return FALSE;
+ symbolic_color = symbolic_color_parse_str (value_str, &end);
- g_value_unset (value);
- g_value_init (value, GTK_TYPE_SYMBOLIC_COLOR);
- g_value_take_boxed (value, symbolic_color);
+ if (symbolic_color)
+ {
+ g_value_unset (value);
+ g_value_init (value, GTK_TYPE_SYMBOLIC_COLOR);
+ g_value_take_boxed (value, symbolic_color);
+ }
+ else
+ parsed = FALSE;
}
}
else if (type == PANGO_TYPE_FONT_DESCRIPTION)
}
else if (type == G_TYPE_INT)
g_value_set_int (value, atoi (value_str));
+ else if (type == G_TYPE_UINT)
+ g_value_set_uint (value, (guint) atoi (value_str));
else if (type == G_TYPE_DOUBLE)
g_value_set_double (value, g_ascii_strtod (value_str, NULL));
+ else if (type == G_TYPE_FLOAT)
+ g_value_set_float (value, (gfloat) g_ascii_strtod (value_str, NULL));
else if (type == GTK_TYPE_THEMING_ENGINE)
{
GtkThemingEngine *engine;
engine = gtk_theming_engine_load (value_str);
- g_value_set_object (value, engine);
+ if (engine)
+ g_value_set_object (value, engine);
+ else
+ parsed = FALSE;
}
else if (type == GTK_TYPE_ANIMATION_DESCRIPTION)
{
GtkAnimationDescription *desc;
- desc = gtk_animation_description_from_string (value_str);
+ desc = _gtk_animation_description_from_string (value_str);
if (desc)
g_value_take_boxed (value, desc);
{
GtkBorder *border;
- border = border_parse (value_str);
+ border = border_parse_str (value_str, &end);
g_value_take_boxed (value, border);
}
else if (type == CAIRO_GOBJECT_TYPE_PATTERN)
{
GtkGradient *gradient;
- gradient = gradient_parse (value_str);
+ gradient = gradient_parse_str (value_str, &end);
if (gradient)
{
g_value_take_boxed (value, gradient);
}
else
- parsed = FALSE;
+ {
+ gchar *path;
+ GdkPixbuf *pixbuf;
+
+ g_clear_error (error);
+ path = path_parse_str (css_provider, value_str, &end, error);
+
+ if (path)
+ {
+ pixbuf = gdk_pixbuf_new_from_file (path, NULL);
+ g_free (path);
+
+ if (pixbuf)
+ {
+ cairo_surface_t *surface;
+ cairo_pattern_t *pattern;
+ cairo_t *cr;
+ cairo_matrix_t matrix;
+
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+ gdk_pixbuf_get_width (pixbuf),
+ gdk_pixbuf_get_height (pixbuf));
+ cr = cairo_create (surface);
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
+ cairo_paint (cr);
+ pattern = cairo_pattern_create_for_surface (surface);
+
+ cairo_matrix_init_scale (&matrix,
+ gdk_pixbuf_get_width (pixbuf),
+ gdk_pixbuf_get_height (pixbuf));
+ cairo_pattern_set_matrix (pattern, &matrix);
+
+ cairo_surface_destroy (surface);
+ cairo_destroy (cr);
+ g_object_unref (pixbuf);
+
+ g_value_take_boxed (value, pattern);
+ }
+ else
+ parsed = FALSE;
+ }
+ else
+ parsed = FALSE;
+ }
}
else if (G_TYPE_IS_ENUM (type))
{
if (!enum_value)
{
- g_warning ("Unknown value '%s' for enum type '%s'",
- value_str, g_type_name (type));
+ g_set_error (error,
+ GTK_CSS_PROVIDER_ERROR,
+ GTK_CSS_PROVIDER_ERROR_FAILED,
+ "Unknown value '%s' for enum type '%s'",
+ value_str, g_type_name (type));
parsed = FALSE;
}
else
if (!flag_value)
{
- g_warning ("Unknown flag '%s' for type '%s'",
- value_str, g_type_name (type));
+ g_set_error (error,
+ GTK_CSS_PROVIDER_ERROR,
+ GTK_CSS_PROVIDER_ERROR_FAILED,
+ "Unknown flag '%s' for type '%s'",
+ value_str, g_type_name (type));
parsed = FALSE;
}
else
if (!flag_value)
{
- g_warning ("Unknown flag '%s' for type '%s'",
- value_str, g_type_name (type));
+ g_set_error (error,
+ GTK_CSS_PROVIDER_ERROR,
+ GTK_CSS_PROVIDER_ERROR_FAILED,
+ "Unknown flag '%s' for type '%s'",
+ value_str, g_type_name (type));
parsed = FALSE;
}
else
{
Gtk9Slice *slice;
- slice = slice_parse (css_provider, value_str);
+ slice = slice_parse_str (css_provider, value_str, &end, error);
if (slice)
g_value_take_boxed (value, slice);
}
else
{
- g_warning ("Cannot parse string '%s' for type %s", value_str, g_type_name (type));
+ g_set_error (error,
+ GTK_CSS_PROVIDER_ERROR,
+ GTK_CSS_PROVIDER_ERROR_FAILED,
+ "Cannot parse string '%s' for type %s",
+ value_str, g_type_name (type));
parsed = FALSE;
}
+ if (end && *end)
+ {
+ /* Set error position in the scanner
+ * according to what we've parsed so far
+ */
+ priv->value_pos += (end - value_str);
+
+ if (error && !*error)
+ g_set_error_literal (error,
+ GTK_CSS_PROVIDER_ERROR,
+ GTK_CSS_PROVIDER_ERROR_FAILED,
+ "Failed to parse value");
+ }
+
return parsed;
}
+static void
+scanner_report_warning (GtkCssProvider *css_provider,
+ GTokenType expected_token,
+ GError *error)
+{
+ GtkCssProviderPrivate *priv;
+ const gchar *line_end, *line_start;
+ const gchar *expected_str;
+ gchar buf[2], *line, *str;
+ guint pos;
+
+ priv = css_provider->priv;
+
+ if (error)
+ str = g_strdup (error->message);
+ else
+ {
+ if (priv->scanner->user_data)
+ expected_str = priv->scanner->user_data;
+ else
+ {
+ switch (expected_token)
+ {
+ case G_TOKEN_SYMBOL:
+ expected_str = "Symbol";
+ case G_TOKEN_IDENTIFIER:
+ expected_str = "Identifier";
+ default:
+ buf[0] = expected_token;
+ buf[1] = '\0';
+ expected_str = buf;
+ }
+ }
+
+ str = g_strdup_printf ("Parse error, expecting a %s '%s'",
+ (expected_str != buf) ? "valid" : "",
+ expected_str);
+ }
+
+ if (priv->value_pos)
+ line_start = priv->value_pos - 1;
+ else
+ line_start = priv->scanner->text - 1;
+
+ while (*line_start != '\n' &&
+ line_start != priv->buffer)
+ line_start--;
+
+ if (*line_start == '\n')
+ line_start++;
+
+ if (priv->value_pos)
+ pos = priv->value_pos - line_start + 1;
+ else
+ pos = priv->scanner->text - line_start - 1;
+
+ line_end = strchr (line_start, '\n');
+
+ if (line_end)
+ line = g_strndup (line_start, (line_end - line_start));
+ else
+ line = g_strdup (line_start);
+
+ g_message ("CSS: %s\n"
+ "%s, line %d, char %d:\n"
+ "%*c %s\n"
+ "%*c ^",
+ str, priv->scanner->input_name,
+ priv->scanner->line, priv->scanner->position,
+ 3, ' ', line,
+ 3 + pos, ' ');
+
+ g_free (line);
+ g_free (str);
+}
+
static GTokenType
-parse_rule (GtkCssProvider *css_provider,
- GScanner *scanner)
+parse_rule (GtkCssProvider *css_provider,
+ GScanner *scanner,
+ GError **error)
{
GtkCssProviderPrivate *priv;
GTokenType expected_token;
g_scanner_get_next_token (scanner);
if (scanner->token != G_TOKEN_IDENTIFIER)
- return G_TOKEN_IDENTIFIER;
+ {
+ scanner->user_data = "Color name";
+ return G_TOKEN_IDENTIFIER;
+ }
color_name = g_strdup (scanner->value.v_identifier);
css_provider_push_scope (css_provider, SCOPE_VALUE);
g_scanner_get_next_token (scanner);
if (scanner->token != G_TOKEN_IDENTIFIER)
- return G_TOKEN_IDENTIFIER;
+ {
+ scanner->user_data = "Color definition";
+ return G_TOKEN_IDENTIFIER;
+ }
color_str = g_strstrip (scanner->value.v_identifier);
- color = symbolic_color_parse (color_str);
+ color = symbolic_color_parse (color_str, error);
if (!color)
- return G_TOKEN_IDENTIFIER;
+ {
+ scanner->user_data = "Color definition";
+ return G_TOKEN_IDENTIFIER;
+ }
g_hash_table_insert (priv->symbolic_colors, color_name, color);
{
GScanner *scanner_backup;
GSList *state_backup;
- GError *error = NULL;
gboolean loaded;
- gchar *path;
+ gchar *path = NULL;
css_provider_push_scope (css_provider, SCOPE_VALUE);
g_scanner_get_next_token (scanner);
- if (scanner->token != G_TOKEN_IDENTIFIER)
- return G_TOKEN_IDENTIFIER;
-
- path = path_parse (css_provider,
- g_strstrip (scanner->value.v_identifier));
-
- if (!path)
- return G_TOKEN_IDENTIFIER;
+ if (scanner->token == G_TOKEN_IDENTIFIER &&
+ g_str_has_prefix (scanner->value.v_identifier, "url"))
+ path = path_parse (css_provider,
+ g_strstrip (scanner->value.v_identifier),
+ error);
+ else if (scanner->token == G_TOKEN_STRING)
+ path = path_parse (css_provider,
+ g_strstrip (scanner->value.v_string),
+ error);
+
+ if (path == NULL)
+ {
+ scanner->user_data = "File URL";
+ return G_TOKEN_IDENTIFIER;
+ }
css_provider_pop_scope (css_provider);
g_scanner_get_next_token (scanner);
/* FIXME: Avoid recursive importing */
loaded = gtk_css_provider_load_from_path_internal (css_provider, path,
- FALSE, &error);
+ FALSE, error);
/* Restore previous state */
css_provider_reset_parser (css_provider);
if (!loaded)
{
- g_warning ("Error loading imported file \"%s\": %s",
- path, (error) ? error->message : "");
- g_error_free (error);
+ scanner->user_data = "File URL";
return G_TOKEN_IDENTIFIER;
}
else
return G_TOKEN_NONE;
}
else
- return G_TOKEN_IDENTIFIER;
+ {
+ scanner->user_data = "Directive";
+ return G_TOKEN_IDENTIFIER;
+ }
}
expected_token = parse_selector (css_provider, scanner, &selector);
if (expected_token != G_TOKEN_NONE)
{
selector_path_unref (selector);
+ scanner->user_data = "Selector";
return expected_token;
}
if (expected_token != G_TOKEN_NONE)
{
selector_path_unref (selector);
+ scanner->user_data = "Selector";
return expected_token;
}
while (scanner->token == G_TOKEN_IDENTIFIER)
{
- const gchar *value_str = NULL;
+ gchar *value_str = NULL;
GtkStylePropertyParser parse_func = NULL;
GParamSpec *pspec;
- GError *error = NULL;
gchar *prop;
prop = g_strdup (scanner->value.v_identifier);
return ':';
}
+ priv->value_pos = priv->scanner->text;
+
css_provider_push_scope (css_provider, SCOPE_VALUE);
g_scanner_get_next_token (scanner);
if (scanner->token != G_TOKEN_IDENTIFIER)
{
g_free (prop);
+ scanner->user_data = "Property value";
return G_TOKEN_IDENTIFIER;
}
- value_str = g_strstrip (scanner->value.v_identifier);
+ value_str = scanner->value.v_identifier;
+ SKIP_SPACES (value_str);
+ g_strchomp (value_str);
if (gtk_style_properties_lookup_property (prop, &parse_func, &pspec))
{
if (strcmp (value_str, "none") == 0)
{
- /* Remove/unset the current value */
- g_hash_table_remove (priv->cur_properties, prop);
+ /* Insert the default value, so it has an opportunity
+ * to override other style providers when merged
+ */
+ g_param_value_set_default (pspec, val);
+ g_hash_table_insert (priv->cur_properties, prop, val);
}
else if (pspec->value_type == G_TYPE_STRING)
{
g_value_set_string (val, value_str);
g_hash_table_insert (priv->cur_properties, prop, val);
}
- else if ((parse_func && (parse_func) (value_str, val, &error)) ||
- (!parse_func && css_provider_parse_value (css_provider, value_str, val)))
+ else if ((parse_func && (parse_func) (value_str, val, error)) ||
+ (!parse_func && css_provider_parse_value (css_provider, value_str, val, error)))
g_hash_table_insert (priv->cur_properties, prop, val);
else
{
- if (error)
- {
- g_warning ("Error parsing property value: %s\n", error->message);
- g_error_free (error);
- }
-
g_value_unset (val);
g_slice_free (GValue, val);
g_free (prop);
+ scanner->user_data = "Property value";
return G_TOKEN_IDENTIFIER;
}
}
g_scanner_get_next_token (scanner);
if (scanner->token != ';')
- return ';';
+ break;
g_scanner_get_next_token (scanner);
}
}
static gboolean
-parse_stylesheet (GtkCssProvider *css_provider)
+parse_stylesheet (GtkCssProvider *css_provider,
+ GError **error)
{
GtkCssProviderPrivate *priv;
+ gboolean result;
+
+ result = TRUE;
priv = css_provider->priv;
g_scanner_get_next_token (priv->scanner);
while (!g_scanner_eof (priv->scanner))
{
GTokenType expected_token;
+ GError *err = NULL;
css_provider_reset_parser (css_provider);
- expected_token = parse_rule (css_provider, priv->scanner);
+ expected_token = parse_rule (css_provider, priv->scanner, &err);
if (expected_token != G_TOKEN_NONE)
{
- g_scanner_unexp_token (priv->scanner, expected_token,
- NULL, NULL, NULL,
- "Error parsing style resource", FALSE);
+ /* If a GError was passed in, propagate the error and bail out,
+ * else report a warning and keep going
+ */
+ if (error != NULL)
+ {
+ result = FALSE;
+ if (err)
+ g_propagate_error (error, err);
+ else
+ g_set_error_literal (error,
+ GTK_CSS_PROVIDER_ERROR,
+ GTK_CSS_PROVIDER_ERROR_FAILED,
+ "Error parsing stylesheet");
+ break;
+ }
+ else
+ {
+ scanner_report_warning (css_provider, expected_token, err);
+ g_clear_error (&err);
+ }
while (!g_scanner_eof (priv->scanner) &&
priv->scanner->token != G_TOKEN_RIGHT_CURLY)
g_scanner_get_next_token (priv->scanner);
}
- return TRUE;
+ return result;
}
/**
* Returns: %TRUE if the data could be loaded.
**/
gboolean
-gtk_css_provider_load_from_data (GtkCssProvider *css_provider,
- const gchar *data,
- gssize length,
- GError *error)
+gtk_css_provider_load_from_data (GtkCssProvider *css_provider,
+ const gchar *data,
+ gssize length,
+ GError **error)
{
GtkCssProviderPrivate *priv;
g_ptr_array_remove_range (priv->selectors_info, 0, priv->selectors_info->len);
priv->scanner->input_name = "-";
+ priv->buffer = data;
g_scanner_input_text (priv->scanner, data, (guint) length);
g_free (priv->filename);
priv->filename = NULL;
+ priv->buffer = NULL;
- parse_stylesheet (css_provider);
-
- return TRUE;
+ return parse_stylesheet (css_provider, error);
}
/**
GError *internal_error = NULL;
gchar *data;
gsize length;
+ gboolean ret;
g_return_val_if_fail (GTK_IS_CSS_PROVIDER (css_provider), FALSE);
g_return_val_if_fail (G_IS_FILE (file), FALSE);
priv->filename = g_file_get_path (file);
priv->scanner->input_name = priv->filename;
+ priv->buffer = data;
g_scanner_input_text (priv->scanner, data, (guint) length);
- parse_stylesheet (css_provider);
+ ret = parse_stylesheet (css_provider, error);
+ priv->buffer = NULL;
g_free (data);
- return TRUE;
+ return ret;
}
static gboolean
GMappedFile *mapped_file;
const gchar *data;
gsize length;
+ gboolean ret;
priv = css_provider->priv;
length = g_mapped_file_get_length (mapped_file);
data = g_mapped_file_get_contents (mapped_file);
- /* FIXME: Set error */
if (!data)
- return FALSE;
+ data = "";
if (reset)
{
}
priv->scanner->input_name = priv->filename;
+ priv->buffer = data;
g_scanner_input_text (priv->scanner, data, (guint) length);
- parse_stylesheet (css_provider);
+ ret = parse_stylesheet (css_provider, error);
+ priv->buffer = NULL;
g_mapped_file_unref (mapped_file);
- return TRUE;
+ return ret;
}
/**
"@define-color tooltip_bg_color #eee1b3; \n"
"@define-color tooltip_fg_color #000; \n"
"\n"
+ "@define-color info_fg_color rgb (181, 171, 156);\n"
+ "@define-color info_bg_color rgb (252, 252, 189);\n"
+ "@define-color warning_fg_color rgb (173, 120, 41);\n"
+ "@define-color warning_bg_color rgb (250, 173, 61);\n"
+ "@define-color question_fg_color rgb (97, 122, 214);\n"
+ "@define-color question_bg_color rgb (138, 173, 212);\n"
+ "@define-color error_fg_color rgb (166, 38, 38);\n"
+ "@define-color error_bg_color rgb (237, 54, 54);\n"
+ "\n"
"*,\n"
"GtkTreeView > GtkButton {\n"
" background-color: @bg_color;\n"
" color: @fg_color;\n"
- " border-color: shade (@bg_color, 0.7);\n"
- " padding: 2 2; \n"
+ " border-color: shade (@bg_color, 0.6);\n"
+ " padding: 2;\n"
+ " border-width: 0;\n"
"}\n"
"\n"
"*:prelight {\n"
- " background-color: shade (@bg_color, 2.0);\n"
+ " background-color: shade (@bg_color, 1.05);\n"
" color: shade (@fg_color, 1.3);\n"
"}\n"
"\n"
" color: @selected_fg_color;\n"
"}\n"
"\n"
+ ".expander {\n"
+ " color: #fff;\n"
+ "}\n"
+ "\n"
+ ".expander:prelight {\n"
+ " color: @text_color;\n"
+ "}\n"
+ "\n"
+ ".expander:active {\n"
+ " transition: 300ms linear;\n"
+ "}\n"
+ "\n"
"*:insensitive {\n"
- " background-color: shade (@bg_color, 0.7);\n"
- " color: shade (@fg_color, 0.7);\n"
+ " border-color: shade (@bg_color, 0.7);\n"
+ " background-color: shade (@bg_color, 0.9);\n"
+ " color: shade (@bg_color, 0.7);\n"
"}\n"
"\n"
"GtkTreeView, GtkIconView, GtkTextView {\n"
".tooltip {\n"
" background-color: @tooltip_bg_color; \n"
" color: @tooltip_fg_color; \n"
+ " border-color: @tooltip_fg_color; \n"
+ " border-width: 1;\n"
+ " border-style: solid;\n"
"}\n"
"\n"
".button,\n"
" color: @text_color;\n"
"}\n"
"\n"
+ ".entry:insensitive {\n"
+ " background-color: shade (@base_color, 0.9);\n"
+ " color: shade (@base_color, 0.7);\n"
+ "}\n"
+ ".entry:active {\n"
+ " background-color: #c4c2bd;\n"
+ " color: #000;\n"
+ "}\n"
+ "\n"
".progressbar,\n"
".entry.progressbar {\n"
" background-color: @selected_bg_color;\n"
" border-color: shade (@selected_bg_color, 0.7);\n"
+ " color: @selected_fg_color;\n"
+ "}\n"
+ "\n"
+ "GtkCheckButton:hover,\n"
+ "GtkCheckButton:selected,\n"
+ "GtkRadioButton:hover,\n"
+ "GtkRadioButton:selected {\n"
+ " background-color: shade (@bg_color, 1.05);\n"
"}\n"
"\n"
- ".check, .radio {\n"
+ ".check, .radio,\n"
+ ".check:active, .radio:active,\n"
+ ".check:hover, .radio:hover {\n"
" background-color: @base_color;\n"
+ " border-color: @fg_color;\n"
" color: @text_color;\n"
+ " border-style: solid;\n"
+ " border-width: 1;\n"
+ "}\n"
+ "\n"
+ ".check:selected, .radio:selected {\n"
+ " background-color: @selected_bg_color;\n"
+ " color: @selected_fg_color;\n"
"}\n"
"\n"
- ".menu.check,\n"
- ".menu.radio {\n"
+ ".menu.check, .menu.radio {\n"
" color: @fg_color;\n"
"}\n"
"\n"
" border-style: none;\n"
"}\n"
"\n"
+ ".popup {\n"
+ " border-style: outset;\n"
+ " border-width: 1;\n"
+ "}\n"
+ "\n"
".viewport {\n"
" border-style: inset;\n"
" border-width: 2;\n"
" border-style: outset;\n"
" border-width: 1;\n"
"}\n"
+ "\n"
+ ".frame {\n"
+ " border-style: inset;\n"
+ " border-width: 1;\n"
+ "}\n"
+ "\n"
+ ".menu,\n"
+ ".menubar,\n"
+ ".toolbar {\n"
+ " border-style: outset;\n"
+ " border-width: 1;\n"
+ "}\n"
+ "\n"
+ ".menu:hover,\n"
+ ".menubar:hover {\n"
+ " background-color: @selected_bg_color;\n"
+ " color: @selected_fg_color;\n"
+ "}\n"
+ "\n"
+ ".menu .check,\n"
+ ".menu .radio,\n"
+ ".menu .check:active,\n"
+ ".menu .radio:active {\n"
+ " border-style: none;\n"
+ "}\n"
+ "\n"
+ "GtkSpinButton.button {\n"
+ " border-width: 1;\n"
+ "}\n"
+ "\n"
+ ".scale.slider:hover,\n"
+ "GtkSpinButton.button:hover {\n"
+ " background-color: shade (@bg_color, 1.05);\n"
+ " border-color: shade (@bg_color, 0.8);\n"
+ "}\n"
+ "\n"
+ "GtkToggleButton.button:inconsistent {\n"
+ " border-style: outset;\n"
+ " border-width: 1px;\n"
+ " background-color: shade (@bg_color, 0.9);\n"
+ " border-color: shade (@bg_color, 0.7);\n"
+ "}\n"
+ "\n"
+ "GtkLabel:selected {\n"
+ " background-color: shade (@bg_color, 0.9);\n"
+ " color: @fg_color;\n"
+ "}\n"
+ "\n"
+ "GtkLabel:selected:focused {\n"
+ " background-color: @selected_bg_color;\n"
+ " color: @selected_fg_color;\n"
+ "}\n"
+ "\n"
+ ".spinner:active {\n"
+ " transition: 750ms linear loop;\n"
+ "}\n"
+ "\n"
+ ".info {\n"
+ " background-color: @info_bg_color;\n"
+ " color: @info_fg_color;\n"
+ "}\n"
+ "\n"
+ ".warning {\n"
+ " background-color: @warning_bg_color;\n"
+ " color: @warning_fg_color;\n"
+ "}\n"
+ "\n"
+ ".question {\n"
+ " background-color: @question_bg_color;\n"
+ " color: @question_fg_color;\n"
+ "}\n"
+ "\n"
+ ".error {\n"
+ " background-color: @error_bg_color;\n"
+ " color: @error_fg_color;\n"
+ "}\n"
+ "\n"
+ ".highlight {\n"
+ " background-color: @selected_bg_color;\n"
+ " color: @selected_fg_color;\n"
+ "}\n"
"\n";
provider = gtk_css_provider_new ();
- gtk_css_provider_load_from_data (provider, str, -1, NULL);
+ if (!gtk_css_provider_load_from_data (provider, str, -1, NULL))
+ {
+ g_error ("Failed to load the internal default CSS.");
+ }
}
return provider;
}
}
+ g_free (subpath);
+
if (path)
{
GError *error = NULL;