#include <gdk-pixbuf/gdk-pixbuf.h>
#include <cairo-gobject.h>
-#include "gtkanimationdescription.h"
-#include "gtk9slice.h"
-#include "gtkgradient.h"
-#include "gtkthemingengine.h"
+#include "gtkcssproviderprivate.h"
+
+#include "gtkcssstringfuncsprivate.h"
+#include "gtksymboliccolor.h"
#include "gtkstyleprovider.h"
-#include "gtkcssprovider.h"
#include "gtkstylecontextprivate.h"
+#include "gtkbindings.h"
+#include "gtkmarshalers.h"
#include "gtkprivate.h"
+#include "gtkintl.h"
/**
* SECTION:gtkcssprovider
* @import url ("path/to/common.css");
* </programlisting>
* </example>
+ * <para id="css-binding-set">
+ * In order to extend key bindings affecting different widgets, GTK+
+ * supports the @binding-set rule to parse a set of bind/unbind
+ * directives, see #GtkBindingSet for the supported syntax. Note that
+ * the binding sets defined in this way must be associated with rule sets
+ * by setting the gtk-key-bindings style property.
+ * </para>
+ * <para>
+ * Customized key bindings are typically defined in a separate
+ * <filename>gtk-keys.css</filename> CSS file and GTK+ loads this file
+ * according to the current key theme, which is defined by the
+ * #GtkSettings:gtk-key-theme-name setting.
+ * </para>
+ * <example>
+ * <title>Using the @binding rule</title>
+ * <programlisting language="text">
+ * @binding-set binding-set1 {
+ * bind "<alt>Left" { "move-cursor" (visual-positions, -3, 0) };
+ * unbind "End";
+ * };
+ *
+ * @binding-set binding-set2 {
+ * bind "<alt>Right" { "move-cursor" (visual-positions, 3, 0) };
+ * bind "<alt>KP_space" { "delete-from-cursor" (whitespace, 1)
+ * "insert-at-cursor" (" ") };
+ * };
+ *
+ * GtkEntry {
+ * gtk-key-bindings: binding-set1, binding-set2;
+ * }
+ * </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
* transition: 1s linear loop;</literallayout>
* </entry>
* </row>
+ * <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>
* </tbody>
* </tgroup>
* </informaltable>
* </refsect2>
*/
-typedef struct GtkCssProviderPrivate GtkCssProviderPrivate;
typedef struct SelectorElement SelectorElement;
typedef struct SelectorPath SelectorPath;
typedef struct SelectorStyleInfo SelectorStyleInfo;
+typedef struct _GtkCssScannerPrivate GtkCssScannerPrivate;
typedef enum SelectorElementType SelectorElementType;
typedef enum CombinatorType CombinatorType;
typedef enum ParserScope ParserScope;
GHashTable *style;
};
-struct GtkCssProviderPrivate
+struct _GtkCssScannerPrivate
{
- GScanner *scanner;
- gchar *filename;
+ GFile *file;
+ GSList *state;
+ GSList *cur_selectors;
+ GHashTable *cur_properties;
+};
- const gchar *buffer;
- const gchar *value_pos;
+struct _GtkCssProviderPrivate
+{
+ GScanner *scanner;
GHashTable *symbolic_colors;
GPtrArray *selectors_info;
-
- /* Current parser state */
- GSList *state;
- GSList *cur_selectors;
- GHashTable *cur_properties;
};
enum ParserScope {
SCOPE_PSEUDO_CLASS,
SCOPE_NTH_CHILD,
SCOPE_DECLARATION,
- SCOPE_VALUE
+ SCOPE_VALUE,
+ SCOPE_BINDING_SET
};
/* Extend GtkStateType, since these
SYMBOL_NTH_CHILD_LAST
};
+enum {
+ PARSING_ERROR,
+ LAST_SIGNAL
+};
+
+static guint css_provider_signals[LAST_SIGNAL] = { 0 };
+
static void gtk_css_provider_finalize (GObject *object);
static void gtk_css_style_provider_iface_init (GtkStyleProviderIface *iface);
static void scanner_apply_scope (GScanner *scanner,
ParserScope scope);
-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);
+static void gtk_css_provider_take_error (GtkCssProvider *provider,
+ GScanner *scanner,
+ GError *error);
GQuark
gtk_css_provider_error_quark (void)
G_IMPLEMENT_INTERFACE (GTK_TYPE_STYLE_PROVIDER,
gtk_css_style_provider_iface_init));
+static void
+gtk_css_provider_parsing_error (GtkCssProvider *provider,
+ const gchar *path,
+ guint line,
+ guint position,
+ const GError * error)
+{
+ /* Only emit a warning when we have no error handlers. This is our
+ * default handlers. And in this case erroneous CSS files are a bug
+ * and should be fixed.
+ * Note that these warnings can also be triggered by a broken theme
+ * that people installed from some weird location on the internets.
+ */
+ if (!g_signal_has_handler_pending (provider,
+ css_provider_signals[PARSING_ERROR],
+ 0,
+ TRUE))
+ {
+ g_warning ("Theme parsing error: %s:%u:%u: %s", path ? path : "<unknown>", line, position, error->message);
+ }
+}
+
static void
gtk_css_provider_class_init (GtkCssProviderClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ /**
+ * GtkCssProvider::parsing-error:
+ * @provider: the provider that had a parsing error
+ * @path: path to the parsed file or %NULL if the file cannot be
+ * identified or the data was not loaded from a file
+ * @line: line in the file or data or 0 if unknown
+ * @position: offset into the current line or 0 if unknown or the
+ * whole line is affected
+ * @error: The parsing error
+ *
+ * Signals that a parsing error occured. the @path, @line and @position
+ * describe the actual location of the error as accurately as possible.
+ *
+ * Parsing errors are never fatal, so the parsing will resume after
+ * the error. Errors may however cause parts of the given
+ * data or even all of it to not be parsed at all. So it is a useful idea
+ * to check that the parsing succeeds by connecting to this signal.
+ *
+ * Note that this signal may be emitted at any time as the css provider
+ * may opt to defer parsing parts or all of the input to a later time
+ * than when a loading function was called.
+ */
+ css_provider_signals[PARSING_ERROR] =
+ g_signal_new (I_("parsing-error"),
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GtkCssProviderClass, parsing_error),
+ NULL, NULL,
+ _gtk_marshal_VOID__STRING_UINT_UINT_BOXED,
+ G_TYPE_NONE, 4,
+ G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_ERROR);
+
object_class->finalize = gtk_css_provider_finalize;
+ klass->parsing_error = gtk_css_provider_parsing_error;
+
g_type_class_add_private (object_class, sizeof (GtkCssProviderPrivate));
}
info->style = NULL;
}
+static void
+property_value_free (GValue *value)
+{
+ if (G_IS_VALUE (value))
+ g_value_unset (value);
+
+ g_slice_free (GValue, value);
+}
+
+static void
+gtk_css_scanner_reset (GScanner *scanner)
+{
+ GtkCssScannerPrivate *priv = scanner->user_data;
+
+ g_slist_free (priv->state);
+ priv->state = NULL;
+
+ g_slist_foreach (priv->cur_selectors, (GFunc) selector_path_unref, NULL);
+ g_slist_free (priv->cur_selectors);
+ priv->cur_selectors = NULL;
+
+ if (priv->cur_properties)
+ g_hash_table_unref (priv->cur_properties);
+
+ priv->cur_properties = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) property_value_free);
+
+ scanner_apply_scope (scanner, SCOPE_SELECTOR);
+}
+
+static void
+gtk_css_scanner_destroy (GScanner *scanner)
+{
+ GtkCssScannerPrivate *priv = scanner->user_data;
+
+ gtk_css_scanner_reset (scanner);
+
+ if (priv->file)
+ g_object_unref (priv->file);
+ g_hash_table_destroy (priv->cur_properties);
+ g_slice_free (GtkCssScannerPrivate, priv);
+
+ g_scanner_destroy (scanner);
+}
+
static GScanner *
-create_scanner (void)
+gtk_css_scanner_new (GFile *file,
+ const gchar *data,
+ gsize length)
{
+ GtkCssScannerPrivate *priv;
GScanner *scanner;
scanner = g_scanner_new (NULL);
+ priv = scanner->user_data = g_slice_new0 (GtkCssScannerPrivate);
+
+ if (file)
+ priv->file = g_object_ref (file);
+
+ priv->cur_properties = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) property_value_free);
+
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));
scanner_apply_scope (scanner, SCOPE_SELECTOR);
+ if (length > G_MAXUINT32)
+ g_warning ("CSS file too large, truncating");
+
+ g_scanner_input_text (scanner, data, length);
+
return scanner;
}
GtkCssProviderPrivate);
priv->selectors_info = g_ptr_array_new_with_free_func ((GDestroyNotify) selector_style_info_free);
- priv->scanner = create_scanner ();
priv->symbolic_colors = g_hash_table_new_full (g_str_hash, g_str_equal,
(GDestroyNotify) g_free,
*score |= 0xF;
else
{
- GType parent = type;
-
- *score = 0xE;
+ guint diff = g_type_depth (type) - g_type_depth (elem->type);
- while ((parent = g_type_parent (parent)) != G_TYPE_INVALID)
+ if (G_UNLIKELY (diff > 0xE))
{
- if (parent == elem->type)
- break;
-
- *score -= 1;
-
- if (*score == 1)
- {
- g_warning ("Hierarchy is higher than expected.");
- break;
- }
+ g_warning ("Hierarchy is higher than expected.");
+ diff = 0xE;
}
+
+ *score = 0XF - diff;
}
return TRUE;
((info->state & state) != 0 &&
(info->state & ~(state)) == 0)))
{
- const gchar *val_str;
+ GError *error = NULL;
- val_str = g_value_get_string (val);
- found = TRUE;
+ found = _gtk_css_value_from_string (value,
+ NULL,
+ g_value_get_string (val),
+ &error);
- css_provider_parse_value (GTK_CSS_PROVIDER (provider), val_str, value, NULL);
- break;
+ if (found)
+ break;
+
+ gtk_css_provider_take_error (GTK_CSS_PROVIDER (provider), NULL, error);
}
}
css_provider = GTK_CSS_PROVIDER (object);
priv = css_provider->priv;
- g_scanner_destroy (priv->scanner);
- g_free (priv->filename);
-
g_ptr_array_free (priv->selectors_info, TRUE);
- g_slist_foreach (priv->cur_selectors, (GFunc) selector_path_unref, NULL);
- g_slist_free (priv->cur_selectors);
-
- if (priv->cur_properties)
- g_hash_table_unref (priv->cur_properties);
if (priv->symbolic_colors)
g_hash_table_destroy (priv->symbolic_colors);
}
static void
-property_value_free (GValue *value)
+gtk_css_provider_take_error (GtkCssProvider *provider,
+ GScanner *scanner,
+ GError *error)
{
- if (G_IS_VALUE (value))
- g_value_unset (value);
+ char *filename;
+ guint line, position;
- g_slice_free (GValue, value);
+ if (scanner)
+ {
+ GtkCssScannerPrivate *priv = scanner->user_data;
+ if (priv->file)
+ filename = g_file_get_path (priv->file);
+ else
+ filename = NULL;
+ line = scanner->line;
+ position = scanner->position;
+ }
+ else
+ {
+ filename = NULL;
+ line = 0;
+ position = 0;
+ }
+
+ g_signal_emit (provider, css_provider_signals[PARSING_ERROR], 0,
+ filename, line, position, error);
+
+ g_free (filename);
+ g_error_free (error);
+}
+
+static void
+gtk_css_provider_error_literal (GtkCssProvider *provider,
+ GScanner *scanner,
+ GQuark domain,
+ gint code,
+ const char *message)
+{
+ gtk_css_provider_take_error (provider,
+ scanner,
+ g_error_new_literal (domain, code, message));
+}
+
+static void
+gtk_css_provider_error (GtkCssProvider *provider,
+ GScanner *scanner,
+ GQuark domain,
+ gint code,
+ const char *format,
+ ...) G_GNUC_PRINTF (5, 6);
+static void
+gtk_css_provider_error (GtkCssProvider *provider,
+ GScanner *scanner,
+ GQuark domain,
+ gint code,
+ const char *format,
+ ...)
+{
+ GError *error;
+ va_list args;
+
+ va_start (args, format);
+ error = g_error_new_valist (domain, code, format, args);
+ va_end (args);
+
+ gtk_css_provider_take_error (provider, scanner, error);
+}
+
+static void
+gtk_css_provider_invalid_token (GtkCssProvider *provider,
+ GScanner *scanner,
+ const char *expected)
+{
+ gtk_css_provider_error (provider,
+ scanner,
+ GTK_CSS_PROVIDER_ERROR,
+ GTK_CSS_PROVIDER_ERROR_SYNTAX,
+ "expected a valid %s", expected);
}
static void
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_BINDING_SET)
+ {
+ 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 "*@";
}
static void
-css_provider_push_scope (GtkCssProvider *css_provider,
- ParserScope scope)
+gtk_css_scanner_push_scope (GScanner *scanner,
+ ParserScope scope)
{
- GtkCssProviderPrivate *priv;
+ GtkCssScannerPrivate *priv;
- priv = css_provider->priv;
+ priv = scanner->user_data;
priv->state = g_slist_prepend (priv->state, GUINT_TO_POINTER (scope));
- scanner_apply_scope (priv->scanner, scope);
+ scanner_apply_scope (scanner, scope);
}
-static ParserScope
-css_provider_pop_scope (GtkCssProvider *css_provider)
+static void
+gtk_css_scanner_pop_scope (GScanner *scanner)
{
- GtkCssProviderPrivate *priv;
+ GtkCssScannerPrivate *priv;
ParserScope scope = SCOPE_SELECTOR;
- priv = css_provider->priv;
+ priv = scanner->user_data;
if (!priv->state)
{
g_warning ("Push/pop calls to parser scope aren't paired");
- scanner_apply_scope (priv->scanner, SCOPE_SELECTOR);
- return SCOPE_SELECTOR;
+ scanner_apply_scope (scanner, SCOPE_SELECTOR);
+ return;
}
priv->state = g_slist_delete_link (priv->state, priv->state);
if (priv->state)
scope = GPOINTER_TO_INT (priv->state->data);
- scanner_apply_scope (priv->scanner, scope);
-
- return scope;
+ scanner_apply_scope (scanner, scope);
}
static void
-css_provider_reset_parser (GtkCssProvider *css_provider)
+css_provider_commit (GtkCssProvider *css_provider,
+ GScanner *scanner)
{
+ GtkCssScannerPrivate *scanner_priv;
GtkCssProviderPrivate *priv;
+ GSList *l;
priv = css_provider->priv;
+ scanner_priv = scanner->user_data;
- g_slist_free (priv->state);
- 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);
- priv->cur_selectors = NULL;
-
- if (priv->cur_properties)
- g_hash_table_unref (priv->cur_properties);
-
- priv->cur_properties = g_hash_table_new_full (g_str_hash,
- g_str_equal,
- (GDestroyNotify) g_free,
- (GDestroyNotify) property_value_free);
-}
-
-static void
-css_provider_commit (GtkCssProvider *css_provider)
-{
- GtkCssProviderPrivate *priv;
- GSList *l;
+ l = scanner_priv->cur_selectors;
- priv = css_provider->priv;
- l = priv->cur_selectors;
+ if (g_hash_table_size (scanner_priv->cur_properties) == 0)
+ return;
while (l)
{
SelectorStyleInfo *info;
info = selector_style_info_new (path);
- selector_style_info_set_style (info, priv->cur_properties);
+ selector_style_info_set_style (info, scanner_priv->cur_properties);
g_ptr_array_add (priv->selectors_info, info);
l = l->next;
if (scanner->token != G_TOKEN_LEFT_PAREN)
return G_TOKEN_LEFT_PAREN;
- css_provider_push_scope (css_provider, SCOPE_NTH_CHILD);
+ gtk_css_scanner_push_scope (scanner, SCOPE_NTH_CHILD);
g_scanner_get_next_token (scanner);
if (scanner->token != G_TOKEN_SYMBOL)
if (scanner->token != G_TOKEN_RIGHT_PAREN)
return G_TOKEN_RIGHT_PAREN;
- css_provider_pop_scope (css_provider);
+ gtk_css_scanner_pop_scope (scanner);
}
else if (symbol == SYMBOL_FIRST_CHILD)
*flags = GTK_REGION_FIRST;
ParserSymbol symbol;
g_scanner_get_next_token (scanner);
- css_provider_push_scope (css_provider, SCOPE_PSEUDO_CLASS);
+ gtk_css_scanner_push_scope (scanner, SCOPE_PSEUDO_CLASS);
/* Check for the next token being nth-child, parse in that
* case, and fallback into common state parsing if not.
if ((token = parse_nth_child (css_provider, scanner, &flags)) != G_TOKEN_NONE)
return token;
- css_provider_pop_scope (css_provider);
+ gtk_css_scanner_pop_scope (scanner);
}
else
{
- css_provider_pop_scope (css_provider);
+ gtk_css_scanner_pop_scope (scanner);
selector_path_prepend_region (path, region_name, 0);
g_free (region_name);
break;
if (selector_path_depth (path) == 0)
selector_path_prepend_glob (path);
- css_provider_push_scope (css_provider, SCOPE_PSEUDO_CLASS);
+ gtk_css_scanner_push_scope (scanner, SCOPE_PSEUDO_CLASS);
while (scanner->token == ':')
{
g_scanner_get_next_token (scanner);
}
- css_provider_pop_scope (css_provider);
+ gtk_css_scanner_pop_scope (scanner);
}
return G_TOKEN_NONE;
}
-#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)
+static void
+resolve_binding_sets (const gchar *value_str,
+ GValue *value)
{
- GtkSymbolicColor *symbolic_color = NULL;
- gchar *str;
+ GPtrArray *array;
+ gchar **bindings, **str;
- str = (gchar *) string;
- *end_ptr = str;
+ bindings = g_strsplit (value_str, ",", -1);
+ array = g_ptr_array_new ();
- if (str[0] == '@')
+ for (str = bindings; *str; str++)
{
- const gchar *end;
- gchar *name;
-
- str++;
- end = str;
+ GtkBindingSet *binding_set;
- while (*end == '-' || *end == '_' || g_ascii_isalpha (*end))
- end++;
+ binding_set = gtk_binding_set_find (g_strstrip (*str));
- name = g_strndup (str, end - str);
- symbolic_color = gtk_symbolic_color_new_name (name);
- g_free (name);
+ if (!binding_set)
+ continue;
- *end_ptr = (gchar *) end;
+ g_ptr_array_add (array, binding_set);
}
- else if (g_str_has_prefix (str, "lighter") ||
- g_str_has_prefix (str, "darker"))
- {
- GtkSymbolicColor *param_color;
- gboolean is_lighter = FALSE;
- is_lighter = g_str_has_prefix (str, "lighter");
-
- if (is_lighter)
- str += strlen ("lighter");
- else
- str += strlen ("darker");
+ g_value_take_boxed (value, array);
+ g_strfreev (bindings);
+}
- SKIP_SPACES (str);
+static GTokenType
+parse_rule (GtkCssProvider *css_provider,
+ GScanner *scanner)
+{
+ GtkCssScannerPrivate *priv;
+ GTokenType expected_token;
+ SelectorPath *selector;
- if (*str != '(')
- {
- *end_ptr = (gchar *) str;
- return NULL;
- }
+ priv = scanner->user_data;
- str++;
- SKIP_SPACES (str);
- param_color = symbolic_color_parse_str (str, end_ptr);
+ gtk_css_scanner_push_scope (scanner, SCOPE_SELECTOR);
- if (!param_color)
- return NULL;
+ /* Handle directives */
+ if (scanner->token == G_TOKEN_IDENTIFIER &&
+ scanner->value.v_identifier[0] == '@')
+ {
+ gchar *directive;
- str = *end_ptr;
- SKIP_SPACES (str);
- *end_ptr = (gchar *) str;
+ directive = &scanner->value.v_identifier[1];
- if (*str != ')')
+ if (strcmp (directive, "define-color") == 0)
{
- gtk_symbolic_color_unref (param_color);
- return NULL;
- }
-
- if (is_lighter)
- symbolic_color = gtk_symbolic_color_new_shade (param_color, 1.3);
- else
- symbolic_color = gtk_symbolic_color_new_shade (param_color, 0.7);
+ GtkSymbolicColor *color;
+ gchar *color_name, *color_str;
+ GError *error = NULL;
- gtk_symbolic_color_unref (param_color);
- (*end_ptr)++;
- }
- else if (g_str_has_prefix (str, "shade") ||
- g_str_has_prefix (str, "alpha"))
- {
- GtkSymbolicColor *param_color;
- gboolean is_shade = FALSE;
- gdouble factor;
+ /* Directive is a color mapping */
+ g_scanner_get_next_token (scanner);
- is_shade = g_str_has_prefix (str, "shade");
+ if (scanner->token != G_TOKEN_IDENTIFIER)
+ {
+ gtk_css_provider_invalid_token (css_provider, scanner, "Color name");
+ return G_TOKEN_IDENTIFIER;
+ }
- if (is_shade)
- str += strlen ("shade");
- else
- str += strlen ("alpha");
+ color_name = g_strdup (scanner->value.v_identifier);
+ gtk_css_scanner_push_scope (scanner, SCOPE_VALUE);
+ g_scanner_get_next_token (scanner);
- SKIP_SPACES (str);
+ if (scanner->token != G_TOKEN_IDENTIFIER)
+ {
+ gtk_css_provider_invalid_token (css_provider, scanner, "Color definition");
+ return G_TOKEN_IDENTIFIER;
+ }
- if (*str != '(')
- {
- *end_ptr = (gchar *) str;
- return NULL;
- }
+ color_str = g_strstrip (scanner->value.v_identifier);
+ color = _gtk_css_parse_symbolic_color (color_str, &error);
+ if (!color)
+ {
+ gtk_css_provider_take_error (css_provider, scanner, error);
+ return G_TOKEN_IDENTIFIER;
+ }
- str++;
- SKIP_SPACES (str);
- param_color = symbolic_color_parse_str (str, end_ptr);
+ g_hash_table_insert (css_provider->priv->symbolic_colors, color_name, color);
- if (!param_color)
- return NULL;
+ gtk_css_scanner_pop_scope (scanner);
+ g_scanner_get_next_token (scanner);
- str = *end_ptr;
- SKIP_SPACES (str);
+ if (scanner->token != ';')
+ return ';';
- if (str[0] != ',')
- {
- gtk_symbolic_color_unref (param_color);
- *end_ptr = (gchar *) str;
- return NULL;
+ return G_TOKEN_NONE;
}
+ else if (strcmp (directive, "import") == 0)
+ {
+ gboolean loaded;
+ gchar *path = NULL;
+ GFile *base, *actual;
+ char *dirname;
+ GError *error = NULL;
- str++;
- SKIP_SPACES (str);
- factor = g_ascii_strtod (str, end_ptr);
+ gtk_css_scanner_push_scope (scanner, SCOPE_VALUE);
+ g_scanner_get_next_token (scanner);
- str = *end_ptr;
- SKIP_SPACES (str);
- *end_ptr = (gchar *) str;
+ if (scanner->token == G_TOKEN_IDENTIFIER &&
+ g_str_has_prefix (scanner->value.v_identifier, "url"))
+ path = g_strstrip (scanner->value.v_identifier);
+ else if (scanner->token == G_TOKEN_STRING)
+ path = g_strstrip (scanner->value.v_string);
+ else
+ {
+ gtk_css_provider_invalid_token (css_provider, scanner, "File URL");
+ return G_TOKEN_IDENTIFIER;
+ }
- if (str[0] != ')')
- {
- gtk_symbolic_color_unref (param_color);
- return NULL;
- }
+ if (scanner->input_name)
+ dirname = g_path_get_dirname (scanner->input_name);
+ else
+ dirname = g_get_current_dir ();
- if (is_shade)
- symbolic_color = gtk_symbolic_color_new_shade (param_color, factor);
- else
- symbolic_color = gtk_symbolic_color_new_alpha (param_color, factor);
+ base = g_file_new_for_path (dirname);
+ g_free (dirname);
- gtk_symbolic_color_unref (param_color);
- (*end_ptr)++;
- }
- else if (g_str_has_prefix (str, "mix"))
- {
- GtkSymbolicColor *color1, *color2;
- gdouble factor;
+ actual = _gtk_css_parse_url (base, path, NULL, &error);
+ g_object_unref (base);
- str += strlen ("mix");
- SKIP_SPACES (str);
+ if (actual == NULL)
+ {
+ gtk_css_provider_take_error (css_provider, scanner, error);
+ return G_TOKEN_IDENTIFIER;
+ }
- if (*str != '(')
- {
- *end_ptr = (gchar *) str;
- return NULL;
- }
+ gtk_css_scanner_pop_scope (scanner);
+ g_scanner_get_next_token (scanner);
- str++;
- SKIP_SPACES (str);
- color1 = symbolic_color_parse_str (str, end_ptr);
+ if (scanner->token != ';')
+ {
+ g_object_unref (actual);
+ return ';';
+ }
- if (!color1)
- return NULL;
+ path = g_file_get_path (actual);
+ g_object_unref (actual);
- str = *end_ptr;
- SKIP_SPACES (str);
+ /* FIXME: Avoid recursive importing */
+ loaded = gtk_css_provider_load_from_path_internal (css_provider, path,
+ FALSE, NULL);
- if (str[0] != ',')
- {
- gtk_symbolic_color_unref (color1);
- *end_ptr = (gchar *) str;
- return NULL;
- }
+ /* Restore previous state */
+ g_free (path);
- str++;
- SKIP_SPACES (str);
- color2 = symbolic_color_parse_str (str, end_ptr);
-
- if (!color2 || *end_ptr[0] != ',')
- {
- gtk_symbolic_color_unref (color1);
- return NULL;
- }
-
- str = *end_ptr;
- SKIP_SPACES (str);
-
- if (str[0] != ',')
- {
- gtk_symbolic_color_unref (color1);
- gtk_symbolic_color_unref (color2);
- *end_ptr = (gchar *) str;
- return NULL;
- }
-
- str++;
- SKIP_SPACES (str);
- factor = g_ascii_strtod (str, end_ptr);
-
- str = *end_ptr;
- SKIP_SPACES (str);
- *end_ptr = (gchar *) str;
-
- if (str[0] != ')')
- {
- gtk_symbolic_color_unref (color1);
- gtk_symbolic_color_unref (color2);
- return NULL;
- }
-
- symbolic_color = gtk_symbolic_color_new_mix (color1, color2, factor);
- gtk_symbolic_color_unref (color1);
- gtk_symbolic_color_unref (color2);
- (*end_ptr)++;
- }
- else
- {
- GdkRGBA color;
- gchar *color_str;
- const gchar *end;
-
- end = str + 1;
-
- if (str[0] == '#')
- {
- /* Color in hex format */
- while (g_ascii_isxdigit (*end))
- end++;
- }
- else if (g_str_has_prefix (str, "rgb"))
- {
- /* color in rgb/rgba format */
- while (*end != ')' && *end != '\0')
- end++;
-
- if (*end == ')')
- end++;
- }
- else
- {
- /* Color name */
- while (*end != '\0' &&
- (g_ascii_isalnum (*end) || *end == ' '))
- end++;
- }
-
- color_str = g_strndup (str, end - str);
- *end_ptr = (gchar *) end;
-
- if (!gdk_rgba_parse (&color, color_str))
- {
- g_free (color_str);
- return NULL;
- }
-
- symbolic_color = gtk_symbolic_color_new_literal (&color);
- g_free (color_str);
- }
-
- return symbolic_color;
-}
-
-static GtkSymbolicColor *
-symbolic_color_parse (const gchar *str,
- GError **error)
-{
- GtkSymbolicColor *color;
- gchar *end;
-
- color = symbolic_color_parse_str (str, &end);
-
- if (*end != '\0')
- {
- g_set_error_literal (error,
- GTK_CSS_PROVIDER_ERROR,
- GTK_CSS_PROVIDER_ERROR_FAILED,
- "Could not parse symbolic color");
-
- if (color)
- {
- gtk_symbolic_color_unref (color);
- color = NULL;
- }
- }
-
- return color;
-}
-
-static GtkGradient *
-gradient_parse_str (const gchar *str,
- gchar **end_ptr)
-{
- GtkGradient *gradient = NULL;
- gdouble coords[6];
- gchar *end;
- guint i;
-
- if (g_str_has_prefix (str, "-gtk-gradient"))
- {
- cairo_pattern_type_t type;
-
- str += strlen ("-gtk-gradient");
- SKIP_SPACES (str);
-
- if (*str != '(')
- {
- *end_ptr = (gchar *) str;
- return NULL;
- }
-
- str++;
- SKIP_SPACES (str);
-
- /* Parse gradient type */
- if (g_str_has_prefix (str, "linear"))
- {
- type = CAIRO_PATTERN_TYPE_LINEAR;
- str += strlen ("linear");
- }
- else if (g_str_has_prefix (str, "radial"))
- {
- type = CAIRO_PATTERN_TYPE_RADIAL;
- str += strlen ("radial");
- }
- else
- {
- *end_ptr = (gchar *) str;
- return NULL;
- }
-
- SKIP_SPACES (str);
-
- /* Parse start/stop position parameters */
- for (i = 0; i < 2; i++)
- {
- if (*str != ',')
- {
- *end_ptr = (gchar *) str;
- return NULL;
- }
-
- str++;
- SKIP_SPACES (str);
-
- if (strncmp (str, "left", 4) == 0)
- {
- coords[i * 3] = 0;
- str += strlen ("left");
- }
- else if (strncmp (str, "right", 5) == 0)
- {
- coords[i * 3] = 1;
- str += strlen ("right");
- }
- else if (strncmp (str, "center", 6) == 0)
- {
- coords[i * 3] = 0.5;
- str += strlen ("center");
- }
- else
- {
- coords[i * 3] = g_ascii_strtod (str, &end);
-
- if (str == end)
- {
- *end_ptr = (gchar *) str;
- return NULL;
- }
-
- str = end;
- }
-
- SKIP_SPACES (str);
-
- if (strncmp (str, "top", 3) == 0)
- {
- coords[(i * 3) + 1] = 0;
- str += strlen ("top");
- }
- else if (strncmp (str, "bottom", 6) == 0)
- {
- coords[(i * 3) + 1] = 1;
- str += strlen ("bottom");
- }
- else if (strncmp (str, "center", 6) == 0)
- {
- coords[(i * 3) + 1] = 0.5;
- str += strlen ("center");
- }
- else
- {
- coords[(i * 3) + 1] = g_ascii_strtod (str, &end);
-
- if (str == end)
- {
- *end_ptr = (gchar *) str;
- return NULL;
- }
-
- str = end;
- }
-
- SKIP_SPACES (str);
-
- if (type == CAIRO_PATTERN_TYPE_RADIAL)
- {
- /* Parse radius */
- if (*str != ',')
- {
- *end_ptr = (gchar *) str;
- return NULL;
- }
-
- str++;
- SKIP_SPACES (str);
-
- coords[(i * 3) + 2] = g_ascii_strtod (str, &end);
- str = end;
-
- SKIP_SPACES (str);
- }
- }
-
- if (type == CAIRO_PATTERN_TYPE_LINEAR)
- gradient = gtk_gradient_new_linear (coords[0], coords[1], coords[3], coords[4]);
- else
- gradient = gtk_gradient_new_radial (coords[0], coords[1], coords[2],
- coords[3], coords[4], coords[5]);
-
- while (*str == ',')
- {
- GtkSymbolicColor *color;
- gdouble position;
-
- if (*str != ',')
- {
- *end_ptr = (gchar *) str;
- return gradient;
- }
-
- str++;
- SKIP_SPACES (str);
-
- if (g_str_has_prefix (str, "from"))
- {
- position = 0;
- str += strlen ("from");
- SKIP_SPACES (str);
-
- if (*str != '(')
- {
- *end_ptr = (gchar *) str;
- return gradient;
- }
- }
- else if (g_str_has_prefix (str, "to"))
- {
- position = 1;
- str += strlen ("to");
- SKIP_SPACES (str);
-
- if (*str != '(')
- {
- *end_ptr = (gchar *) str;
- return gradient;
- }
- }
- else if (g_str_has_prefix (str, "color-stop"))
- {
- str += strlen ("color-stop");
- SKIP_SPACES (str);
-
- if (*str != '(')
- {
- *end_ptr = (gchar *) str;
- return gradient;
- }
-
- str++;
- SKIP_SPACES (str);
-
- position = g_ascii_strtod (str, &end);
-
- str = end;
- SKIP_SPACES (str);
-
- if (*str != ',')
- {
- *end_ptr = (gchar *) str;
- return gradient;
- }
- }
- else
- {
- *end_ptr = (gchar *) str;
- return gradient;
- }
-
- str++;
- SKIP_SPACES (str);
-
- color = symbolic_color_parse_str (str, &end);
-
- str = end;
- SKIP_SPACES (str);
-
- if (*str != ')')
- {
- *end_ptr = (gchar *) str;
- return gradient;
- }
-
- str++;
- SKIP_SPACES (str);
-
- if (color)
- {
- gtk_gradient_add_color_stop (gradient, position, color);
- gtk_symbolic_color_unref (color);
- }
- }
-
- if (*str != ')')
- {
- *end_ptr = (gchar *) str;
- return gradient;
- }
-
- str++;
- }
-
- *end_ptr = (gchar *) str;
-
- return gradient;
-}
-
-static gchar *
-path_parse_str (GtkCssProvider *css_provider,
- const gchar *str,
- gchar **end_ptr,
- GError **error)
-{
- gchar *path, *chr;
- const gchar *start, *end;
- start = str;
-
- if (g_str_has_prefix (str, "url"))
- {
- str += strlen ("url");
- SKIP_SPACES (str);
-
- if (*str != '(')
- {
- *end_ptr = (gchar *) str;
- return NULL;
- }
-
- chr = strchr (str, ')');
- if (!chr)
- {
- *end_ptr = (gchar *) str;
- return NULL;
- }
-
- end = chr + 1;
-
- str++;
- SKIP_SPACES (str);
-
- if (*str == '"' || *str == '\'')
- {
- const gchar *p;
- p = str;
- str++;
-
- chr--;
- SKIP_SPACES_BACK (chr);
-
- if (*chr != *p || chr == p)
- {
- *end_ptr = (gchar *)str;
- return NULL;
- }
- }
- else
- {
- *end_ptr = (gchar *)str;
- return NULL;
- }
-
- path = g_strndup (str, chr - str);
- g_strstrip (path);
-
- *end_ptr = (gchar *)end;
- }
- else
- {
- path = g_strdup (str);
- *end_ptr = (gchar *)str + strlen (str);
- }
-
- /* Always return an absolute path */
- if (!g_path_is_absolute (path))
- {
- GtkCssProviderPrivate *priv;
- gchar *dirname, *full_path;
-
- priv = css_provider->priv;
-
- /* Use relative path to the current CSS file path, if any */
- 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);
- g_free (dirname);
-
- path = full_path;
- }
-
- if (!g_file_test (path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))
- {
- 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,
- GError **error)
-{
- gchar *path;
- gchar *end;
-
- path = path_parse_str (css_provider, str, &end, error);
-
- if (!path)
- return NULL;
-
- if (*end != '\0')
- {
- 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,
- GError **error)
-{
- gdouble distance_top, distance_bottom;
- gdouble distance_left, distance_right;
- GtkSliceSideModifier mods[2];
- GdkPixbuf *pixbuf;
- Gtk9Slice *slice;
- gchar *path;
- gint i = 0;
-
- SKIP_SPACES (str);
-
- /* Parse image url */
- path = path_parse_str (css_provider, str, end_ptr, error);
-
- if (!path)
- return NULL;
-
- str = *end_ptr;
- SKIP_SPACES (str);
-
- /* Parse top/left/bottom/right distances */
- distance_top = g_ascii_strtod (str, end_ptr);
-
- str = *end_ptr;
- SKIP_SPACES (str);
-
- distance_right = g_ascii_strtod (str, end_ptr);
-
- str = *end_ptr;
- SKIP_SPACES (str);
-
- distance_bottom = g_ascii_strtod (str, end_ptr);
-
- str = *end_ptr;
- SKIP_SPACES (str);
-
- distance_left = g_ascii_strtod (str, end_ptr);
-
- str = *end_ptr;
- SKIP_SPACES (str);
-
- while (*str && i < 2)
- {
- if (g_str_has_prefix (str, "stretch"))
- {
- str += strlen ("stretch");
- mods[i] = GTK_SLICE_STRETCH;
- }
- else if (g_str_has_prefix (str, "repeat"))
- {
- str += strlen ("repeat");
- mods[i] = GTK_SLICE_REPEAT;
- }
- else
- {
- g_free (path);
- *end_ptr = (gchar *) str;
- return NULL;
- }
-
- SKIP_SPACES (str);
- i++;
- }
-
- *end_ptr = (gchar *) str;
-
- if (*str != '\0')
- {
- g_free (path);
- return NULL;
- }
-
- if (i != 2)
- {
- /* Fill in second modifier, same as the first */
- mods[1] = mods[0];
- }
-
- pixbuf = gdk_pixbuf_new_from_file (path, error);
- g_free (path);
-
- if (!pixbuf)
- {
- *end_ptr = (gchar *) str;
- return NULL;
- }
-
- slice = _gtk_9slice_new (pixbuf,
- distance_top, distance_bottom,
- distance_left, distance_right,
- mods[0], mods[1]);
- g_object_unref (pixbuf);
-
- return slice;
-}
-
-static gdouble
-unit_parse_str (const gchar *str,
- gchar **end_str)
-{
- gdouble unit;
-
- SKIP_SPACES (str);
- unit = g_ascii_strtod (str, end_str);
- str = *end_str;
-
- /* Now parse the unit type, if any. We
- * don't admit spaces between these.
- */
- if (*str != ' ' && *str != '\0')
- {
- while (**end_str != ' ' && **end_str != '\0')
- (*end_str)++;
-
- /* Only handle pixels at the moment */
- if (strncmp (str, "px", 2) != 0)
- {
- gchar *type;
-
- type = g_strndup (str, *end_str - str);
- g_warning ("Unknown unit '%s', only pixel units are "
- "currently supported in CSS style", type);
- g_free (type);
- }
- }
-
- return unit;
-}
-
-static GtkBorder *
-border_parse_str (const gchar *str,
- gchar **end_str)
-{
- gdouble first, second, third, fourth;
- GtkBorder *border;
-
- border = gtk_border_new ();
-
- SKIP_SPACES (str);
- if (!g_ascii_isdigit (*str) && *str != '-')
- return border;
-
- first = unit_parse_str (str, end_str);
- str = *end_str;
- SKIP_SPACES (str);
-
- if (!g_ascii_isdigit (*str) && *str != '-')
- {
- border->left = border->right = border->top = border->bottom = (gint) first;
- *end_str = (gchar *) str;
- return border;
- }
-
- second = unit_parse_str (str, end_str);
- str = *end_str;
- SKIP_SPACES (str);
-
- if (!g_ascii_isdigit (*str) && *str != '-')
- {
- border->top = border->bottom = (gint) first;
- border->left = border->right = (gint) second;
- *end_str = (gchar *) str;
- return border;
- }
-
- third = unit_parse_str (str, end_str);
- str = *end_str;
- SKIP_SPACES (str);
-
- if (!g_ascii_isdigit (*str) && *str != '-')
- {
- border->top = (gint) first;
- border->left = border->right = (gint) second;
- border->bottom = (gint) third;
- *end_str = (gchar *) str;
- return border;
- }
-
- fourth = unit_parse_str (str, end_str);
-
- border->top = (gint) first;
- border->right = (gint) second;
- border->bottom = (gint) third;
- border->left = (gint) fourth;
-
- return border;
-}
-
-static gboolean
-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 rgba;
- GdkColor color;
-
- if (type == GDK_TYPE_RGBA &&
- gdk_rgba_parse (&rgba, value_str))
- g_value_set_boxed (value, &rgba);
- else if (type == GDK_TYPE_COLOR &&
- gdk_color_parse (value_str, &color))
- g_value_set_boxed (value, &color);
- else
- {
- GtkSymbolicColor *symbolic_color;
-
- symbolic_color = symbolic_color_parse_str (value_str, &end);
-
- 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)
- {
- PangoFontDescription *font_desc;
-
- font_desc = pango_font_description_from_string (value_str);
- g_value_take_boxed (value, font_desc);
- }
- else if (type == G_TYPE_BOOLEAN)
- {
- if (value_str[0] == '1' ||
- g_ascii_strcasecmp (value_str, "true") == 0)
- g_value_set_boolean (value, TRUE);
- else
- g_value_set_boolean (value, FALSE);
- }
- 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);
- 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);
-
- if (desc)
- g_value_take_boxed (value, desc);
- else
- parsed = FALSE;
- }
- else if (type == GTK_TYPE_BORDER)
- {
- GtkBorder *border;
-
- 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_str (value_str, &end);
-
- if (gradient)
- {
- g_value_unset (value);
- g_value_init (value, GTK_TYPE_GRADIENT);
- g_value_take_boxed (value, gradient);
- }
- else
- {
- 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))
- {
- GEnumClass *enum_class;
- GEnumValue *enum_value;
-
- enum_class = g_type_class_ref (type);
- enum_value = g_enum_get_value_by_nick (enum_class, value_str);
-
- if (!enum_value)
- {
- 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
- g_value_set_enum (value, enum_value->value);
-
- g_type_class_unref (enum_class);
- }
- else if (G_TYPE_IS_FLAGS (type))
- {
- GFlagsClass *flags_class;
- GFlagsValue *flag_value;
- guint flags = 0;
- gchar *ptr;
-
- flags_class = g_type_class_ref (type);
-
- /* Parse comma separated values */
- ptr = strchr (value_str, ',');
-
- while (ptr && parsed)
- {
- gchar *flag_str;
-
- *ptr = '\0';
- ptr++;
-
- flag_str = (gchar *) value_str;
- flag_value = g_flags_get_value_by_nick (flags_class,
- g_strstrip (flag_str));
-
- if (!flag_value)
- {
- 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;
- }
+ if (!loaded)
+ return G_TOKEN_IDENTIFIER;
else
- flags |= flag_value->value;
-
- value_str = ptr;
- ptr = strchr (value_str, ',');
- }
-
- /* Store last/only value */
- flag_value = g_flags_get_value_by_nick (flags_class, value_str);
-
- if (!flag_value)
- {
- 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;
+ return G_TOKEN_NONE;
}
- else
- flags |= flag_value->value;
-
- if (parsed)
- g_value_set_enum (value, flags);
-
- g_type_class_unref (flags_class);
- }
- else if (type == GTK_TYPE_9SLICE)
- {
- Gtk9Slice *slice;
-
- slice = slice_parse_str (css_provider, value_str, &end, error);
-
- if (slice)
- g_value_take_boxed (value, slice);
- else
- parsed = FALSE;
- }
- else
- {
- 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
+ else if (strcmp (directive, "binding-set") == 0)
{
- 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);
-}
+ GtkBindingSet *binding_set;
+ gchar *binding_set_name;
-static GTokenType
-parse_rule (GtkCssProvider *css_provider,
- GScanner *scanner,
- GError **error)
-{
- GtkCssProviderPrivate *priv;
- GTokenType expected_token;
- SelectorPath *selector;
-
- priv = css_provider->priv;
-
- css_provider_push_scope (css_provider, SCOPE_SELECTOR);
-
- /* Handle directives */
- if (scanner->token == G_TOKEN_IDENTIFIER &&
- scanner->value.v_identifier[0] == '@')
- {
- gchar *directive;
-
- directive = &scanner->value.v_identifier[1];
-
- if (strcmp (directive, "define-color") == 0)
- {
- GtkSymbolicColor *color;
- gchar *color_name, *color_str;
-
- /* Directive is a color mapping */
g_scanner_get_next_token (scanner);
if (scanner->token != G_TOKEN_IDENTIFIER)
{
- scanner->user_data = "Color name";
+ gtk_css_provider_invalid_token (css_provider, scanner, "Binding 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);
+ binding_set_name = scanner->value.v_identifier;
+ binding_set = gtk_binding_set_find (binding_set_name);
- if (scanner->token != G_TOKEN_IDENTIFIER)
+ if (!binding_set)
{
- scanner->user_data = "Color definition";
- return G_TOKEN_IDENTIFIER;
+ binding_set = gtk_binding_set_new (binding_set_name);
+ binding_set->parsed = TRUE;
}
- color_str = g_strstrip (scanner->value.v_identifier);
- color = symbolic_color_parse (color_str, error);
-
- if (!color)
- {
- scanner->user_data = "Color definition";
- return G_TOKEN_IDENTIFIER;
- }
-
- g_hash_table_insert (priv->symbolic_colors, color_name, color);
-
- css_provider_pop_scope (css_provider);
g_scanner_get_next_token (scanner);
- if (scanner->token != ';')
- return ';';
-
- return G_TOKEN_NONE;
- }
- else if (strcmp (directive, "import") == 0)
- {
- GScanner *scanner_backup;
- GSList *state_backup;
- gboolean loaded;
- gchar *path = NULL;
+ if (scanner->token != G_TOKEN_LEFT_CURLY)
+ return G_TOKEN_LEFT_CURLY;
- css_provider_push_scope (css_provider, SCOPE_VALUE);
+ gtk_css_scanner_push_scope (scanner, SCOPE_BINDING_SET);
g_scanner_get_next_token (scanner);
- 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)
+ do
{
- scanner->user_data = "File URL";
- return G_TOKEN_IDENTIFIER;
- }
-
- css_provider_pop_scope (css_provider);
- g_scanner_get_next_token (scanner);
+ GTokenType ret;
- if (scanner->token != ';')
- {
- g_free (path);
- return ';';
- }
+ if (scanner->token != G_TOKEN_IDENTIFIER)
+ {
+ gtk_css_provider_invalid_token (css_provider, scanner, "Binding definition");
+ return G_TOKEN_IDENTIFIER;
+ }
- /* Snapshot current parser state and scanner in order to restore after importing */
- state_backup = priv->state;
- scanner_backup = priv->scanner;
+ ret = gtk_binding_entry_add_signal_from_string (binding_set,
+ scanner->value.v_identifier);
+ if (ret != G_TOKEN_NONE)
+ {
+ gtk_css_provider_invalid_token (css_provider, scanner, "Binding definition");
+ return ret;
+ }
- priv->state = NULL;
- priv->scanner = create_scanner ();
+ g_scanner_get_next_token (scanner);
- /* FIXME: Avoid recursive importing */
- loaded = gtk_css_provider_load_from_path_internal (css_provider, path,
- FALSE, error);
+ if (scanner->token != ';')
+ return ';';
- /* Restore previous state */
- css_provider_reset_parser (css_provider);
- priv->state = state_backup;
- g_scanner_destroy (priv->scanner);
- priv->scanner = scanner_backup;
+ g_scanner_get_next_token (scanner);
+ }
+ while (scanner->token != G_TOKEN_RIGHT_CURLY);
- g_free (path);
+ gtk_css_scanner_pop_scope (scanner);
+ g_scanner_get_next_token (scanner);
- if (!loaded)
- {
- scanner->user_data = "File URL";
- return G_TOKEN_IDENTIFIER;
- }
- else
- return G_TOKEN_NONE;
+ return G_TOKEN_NONE;
}
else
{
- scanner->user_data = "Directive";
+ gtk_css_provider_invalid_token (css_provider, scanner, "Directive");
return G_TOKEN_IDENTIFIER;
}
}
if (expected_token != G_TOKEN_NONE)
{
selector_path_unref (selector);
- scanner->user_data = "Selector";
+ gtk_css_provider_invalid_token (css_provider, scanner, "Selector");
return expected_token;
}
if (expected_token != G_TOKEN_NONE)
{
selector_path_unref (selector);
- scanner->user_data = "Selector";
+ gtk_css_provider_invalid_token (css_provider, scanner, "Selector");
return expected_token;
}
priv->cur_selectors = g_slist_prepend (priv->cur_selectors, selector);
}
- css_provider_pop_scope (css_provider);
+ gtk_css_scanner_pop_scope (scanner);
if (scanner->token != G_TOKEN_LEFT_CURLY)
return G_TOKEN_LEFT_CURLY;
/* Declarations parsing */
- css_provider_push_scope (css_provider, SCOPE_DECLARATION);
- g_scanner_get_next_token (scanner);
+ gtk_css_scanner_push_scope (scanner, SCOPE_DECLARATION);
- while (scanner->token == G_TOKEN_IDENTIFIER)
+ g_scanner_get_next_token (scanner);
+
+ while (scanner->token != G_TOKEN_RIGHT_CURLY &&
+ !g_scanner_eof (scanner))
{
gchar *value_str = NULL;
GtkStylePropertyParser parse_func = NULL;
- GParamSpec *pspec;
+ GParamSpec *pspec = NULL;;
gchar *prop;
+ if (scanner->token == ';')
+ {
+ g_scanner_get_next_token (scanner);
+ continue;
+ }
+
+ if (scanner->token != G_TOKEN_IDENTIFIER)
+ {
+ gtk_css_provider_error_literal (css_provider,
+ scanner,
+ GTK_CSS_PROVIDER_ERROR,
+ GTK_CSS_PROVIDER_ERROR_PROPERTY_NAME,
+ "Expected a valid property name");
+ goto find_end_of_declaration;
+ }
+
prop = g_strdup (scanner->value.v_identifier);
+
+ if (!gtk_style_properties_lookup_property (prop, &parse_func, &pspec) &&
+ prop[0] != '-')
+ {
+ gtk_css_provider_error (css_provider,
+ scanner,
+ GTK_CSS_PROVIDER_ERROR,
+ GTK_CSS_PROVIDER_ERROR_PROPERTY_NAME,
+ "'%s' is not a valid property name",
+ prop);
+ g_free (prop);
+ goto find_end_of_declaration;
+ }
+
g_scanner_get_next_token (scanner);
if (scanner->token != ':')
{
g_free (prop);
- return ':';
+ gtk_css_provider_invalid_token (css_provider, scanner, "':'");
+ goto find_end_of_declaration;
}
- priv->value_pos = priv->scanner->text;
-
- css_provider_push_scope (css_provider, SCOPE_VALUE);
+ gtk_css_scanner_push_scope (scanner, 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;
+ /* the error value here is hacky. But strings should be used for
+ * strings really, so a string is not a syntax error but a broken
+ * value for everything that we support. */
+ gtk_css_provider_error (css_provider,
+ scanner,
+ GTK_CSS_PROVIDER_ERROR,
+ scanner->token == G_TOKEN_STRING ? GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE
+ : GTK_CSS_PROVIDER_ERROR_SYNTAX,
+ "Not a property value");
+ gtk_css_scanner_pop_scope (scanner);
+ goto find_end_of_declaration;
}
value_str = scanner->value.v_identifier;
- SKIP_SPACES (value_str);
g_strchomp (value_str);
- if (gtk_style_properties_lookup_property (prop, &parse_func, &pspec))
+ gtk_css_scanner_pop_scope (scanner);
+ g_scanner_peek_next_token (scanner);
+
+ if (scanner->next_token != ';' &&
+ scanner->next_token != G_TOKEN_RIGHT_CURLY &&
+ scanner->next_token != G_TOKEN_EOF)
+ {
+ gtk_css_provider_invalid_token (css_provider, scanner, "';'");
+ g_free (prop);
+ goto find_end_of_declaration;
+ }
+
+ if (pspec)
{
GValue *val;
g_param_value_set_default (pspec, val);
g_hash_table_insert (priv->cur_properties, prop, val);
}
+ else if (strcmp (prop, "gtk-key-bindings") == 0)
+ {
+ /* Private property holding the binding sets */
+ resolve_binding_sets (value_str, 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, error)))
- g_hash_table_insert (priv->cur_properties, prop, val);
+ else if (parse_func)
+ {
+ GError *error = NULL;
+
+ if ((*parse_func) (value_str, val, &error))
+ g_hash_table_insert (priv->cur_properties, prop, val);
+ else
+ gtk_css_provider_take_error (css_provider, scanner, error);
+ }
else
{
- g_value_unset (val);
- g_slice_free (GValue, val);
- g_free (prop);
+ GError *error = NULL;
+ GFile *base;
+ char *dirname;
+ gboolean success;
- scanner->user_data = "Property value";
- return G_TOKEN_IDENTIFIER;
+ if (scanner->input_name)
+ dirname = g_path_get_dirname (scanner->input_name);
+ else
+ dirname = g_get_current_dir ();
+
+ base = g_file_new_for_path (dirname);
+ g_free (dirname);
+
+ success = _gtk_css_value_from_string (val,
+ base,
+ value_str,
+ &error);
+
+ g_object_unref (base);
+
+ if (success)
+ {
+ g_hash_table_insert (priv->cur_properties, prop, val);
+ }
+ else
+ {
+ g_value_unset (val);
+ g_slice_free (GValue, val);
+ g_free (prop);
+
+ gtk_css_provider_take_error (css_provider, scanner, error);
+ }
}
}
else if (prop[0] == '-')
else
g_free (prop);
- css_provider_pop_scope (css_provider);
g_scanner_get_next_token (scanner);
- if (scanner->token != ';')
- break;
+ if (g_scanner_eof (scanner))
+ {
+ gtk_css_provider_invalid_token (css_provider, scanner, "}");
+ break;
+ }
- g_scanner_get_next_token (scanner);
+find_end_of_declaration:
+ while (scanner->token != ';' &&
+ scanner->token != G_TOKEN_RIGHT_CURLY &&
+ !g_scanner_eof (scanner))
+ g_scanner_get_next_token (scanner);
}
- if (scanner->token != G_TOKEN_RIGHT_CURLY)
- return G_TOKEN_RIGHT_CURLY;
-
- css_provider_pop_scope (css_provider);
+ gtk_css_scanner_pop_scope (scanner);
return G_TOKEN_NONE;
}
+static void
+gtk_css_provider_reset (GtkCssProvider *css_provider)
+{
+ GtkCssProviderPrivate *priv;
+
+ priv = css_provider->priv;
+
+ if (priv->selectors_info->len > 0)
+ g_ptr_array_remove_range (priv->selectors_info, 0, priv->selectors_info->len);
+}
+
+static void
+gtk_css_provider_propagate_error (GtkCssProvider *provider,
+ const gchar *path,
+ guint line,
+ guint position,
+ const GError *error,
+ GError **propagate_to)
+{
+ /* we already set an error. And we'd like to keep the first one */
+ if (*propagate_to)
+ return;
+
+ *propagate_to = g_error_copy (error);
+ g_prefix_error (propagate_to, "%s:%u:%u: ", path ? path : "<unknown>", line, position);
+}
+
static gboolean
parse_stylesheet (GtkCssProvider *css_provider,
+ GScanner *scanner,
GError **error)
{
- GtkCssProviderPrivate *priv;
- gboolean result;
+ gulong error_handler;
- result = TRUE;
+ if (error)
+ error_handler = g_signal_connect (css_provider,
+ "parsing-error",
+ G_CALLBACK (gtk_css_provider_propagate_error),
+ error);
+ else
+ error_handler = 0; /* silence gcc */
- priv = css_provider->priv;
- g_scanner_get_next_token (priv->scanner);
+ g_scanner_get_next_token (scanner);
- while (!g_scanner_eof (priv->scanner))
+ while (!g_scanner_eof (scanner))
{
GTokenType expected_token;
- GError *err = NULL;
- css_provider_reset_parser (css_provider);
- expected_token = parse_rule (css_provider, priv->scanner, &err);
+ expected_token = parse_rule (css_provider, scanner);
if (expected_token != G_TOKEN_NONE)
{
- /* 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);
+ while (!g_scanner_eof (scanner) &&
+ scanner->token != G_TOKEN_RIGHT_CURLY)
+ g_scanner_get_next_token (scanner);
}
else
- css_provider_commit (css_provider);
+ css_provider_commit (css_provider, scanner);
- g_scanner_get_next_token (priv->scanner);
+ g_scanner_get_next_token (scanner);
+
+ gtk_css_scanner_reset (scanner);
}
- return result;
+ if (error)
+ {
+ g_signal_handler_disconnect (css_provider, error_handler);
+
+ if (*error)
+ {
+ /* We clear all contents from the provider for backwards compat reasons */
+ gtk_css_provider_reset (css_provider);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
}
/**
gssize length,
GError **error)
{
- GtkCssProviderPrivate *priv;
+ GScanner *scanner;
+ gboolean result;
g_return_val_if_fail (GTK_IS_CSS_PROVIDER (css_provider), FALSE);
g_return_val_if_fail (data != NULL, FALSE);
- priv = css_provider->priv;
-
if (length < 0)
length = strlen (data);
- if (priv->selectors_info->len > 0)
- g_ptr_array_remove_range (priv->selectors_info, 0, priv->selectors_info->len);
+ gtk_css_provider_reset (css_provider);
- priv->scanner->input_name = "-";
- priv->buffer = data;
- g_scanner_input_text (priv->scanner, data, (guint) length);
+ scanner = gtk_css_scanner_new (NULL, data, length);
- g_free (priv->filename);
- priv->filename = NULL;
- priv->buffer = NULL;
+ result = parse_stylesheet (css_provider, scanner, error);
- return parse_stylesheet (css_provider, error);
+ gtk_css_scanner_destroy (scanner);
+
+ return result;
}
/**
GFile *file,
GError **error)
{
- GtkCssProviderPrivate *priv;
GError *internal_error = NULL;
+ GScanner *scanner;
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 = css_provider->priv;
-
if (!g_file_load_contents (file, NULL,
&data, &length,
NULL, &internal_error))
return FALSE;
}
- if (priv->selectors_info->len > 0)
- g_ptr_array_remove_range (priv->selectors_info, 0, priv->selectors_info->len);
+ gtk_css_provider_reset (css_provider);
- g_free (priv->filename);
- priv->filename = g_file_get_path (file);
+ scanner = gtk_css_scanner_new (file, data, length);
- priv->scanner->input_name = priv->filename;
- priv->buffer = data;
- g_scanner_input_text (priv->scanner, data, (guint) length);
+ ret = parse_stylesheet (css_provider, scanner, error);
- ret = parse_stylesheet (css_provider, error);
-
- priv->buffer = NULL;
g_free (data);
+ gtk_css_scanner_destroy (scanner);
return ret;
}
GtkCssProviderPrivate *priv;
GError *internal_error = NULL;
GMappedFile *mapped_file;
+ GScanner *scanner;
const gchar *data;
gsize length;
+ GFile *file;
gboolean ret;
priv = css_provider->priv;
data = "";
if (reset)
- {
- if (priv->selectors_info->len > 0)
- g_ptr_array_remove_range (priv->selectors_info, 0, priv->selectors_info->len);
+ gtk_css_provider_reset (css_provider);
- g_free (priv->filename);
- priv->filename = g_strdup (path);
- }
+ file = g_file_new_for_path (path);
+ scanner = gtk_css_scanner_new (file, data, length);
+ g_object_unref (file);
- priv->scanner->input_name = priv->filename;
- priv->buffer = data;
- g_scanner_input_text (priv->scanner, data, (guint) length);
+ ret = parse_stylesheet (css_provider, scanner, error);
- ret = parse_stylesheet (css_provider, error);
+ gtk_css_scanner_destroy (scanner);
- priv->buffer = NULL;
g_mapped_file_unref (mapped_file);
return ret;
"@define-color selected_fg_color #fff; \n"
"@define-color tooltip_bg_color #eee1b3; \n"
"@define-color tooltip_fg_color #000; \n"
+ "@define-color placeholder_text_color #808080; \n"
"\n"
"@define-color info_fg_color rgb (181, 171, 156);\n"
"@define-color info_bg_color rgb (252, 252, 189);\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"
+ "* {\n"
" background-color: @bg_color;\n"
" color: @fg_color;\n"
" border-color: shade (@bg_color, 0.6);\n"
" color: @selected_fg_color;\n"
"}\n"
"\n"
- ".expander, .view.expander {\n"
+ ".expander, GtkTreeView.view.expander {\n"
" color: #fff;\n"
"}\n"
"\n"
- ".expander:prelight {\n"
+ ".expander:prelight,\n"
+ "GtkTreeView.view.expander:selected:prelight {\n"
" color: @text_color;\n"
"}\n"
"\n"
".expander:active {\n"
- " transition: 300ms linear;\n"
+ " transition: 200ms linear;\n"
"}\n"
"\n"
"*:insensitive {\n"
" color: shade (@bg_color, 0.7);\n"
"}\n"
"\n"
- "GtkTreeView, GtkIconView {\n"
- " background-color: @base_color;\n"
- " color: @text_color;\n"
- "}\n"
- "\n"
".view {\n"
+ " border-width: 0;\n"
+ " border-radius: 0;\n"
" background-color: @base_color;\n"
" color: @text_color;\n"
"}\n"
" color: @selected_fg_color;\n"
"}\n"
"\n"
- "GtkTreeView > row {\n"
+ ".view column:sorted row,\n"
+ ".view column:sorted row:prelight {\n"
+ " background-color: shade (@bg_color, 0.85);\n"
+ "}\n"
+ "\n"
+ ".view column:sorted row:nth-child(odd),\n"
+ ".view column:sorted row:nth-child(odd):prelight {\n"
+ " background-color: shade (@bg_color, 0.8);\n"
+ "}\n"
+ "\n"
+ ".view row,\n"
+ ".view row:prelight {\n"
" background-color: @base_color;\n"
" color: @text_color;\n"
- " border-width: 0;\n"
"}\n"
"\n"
- "GtkTreeView > row:nth-child(odd) { \n"
+ ".view row:nth-child(odd),\n"
+ ".view row:nth-child(odd):prelight {\n"
" background-color: shade (@base_color, 0.93); \n"
"}\n"
"\n"
+ ".view row:selected:focused {\n"
+ " background-color: @selected_bg_color;\n"
+ "}\n"
+ "\n"
+ ".view row:selected {\n"
+ " background-color: darker (@bg_color);\n"
+ " color: @selected_fg_color;\n"
+ "}\n"
+ "\n"
+ ".view.cell.trough,\n"
+ ".view.cell.trough:hover,\n"
+ ".view.cell.trough:selected,\n"
+ ".view.cell.trough:selected:focused {\n"
+ " background-color: @bg_color;\n"
+ " color: @fg_color;\n"
+ "}\n"
+ "\n"
+ ".view.cell.progressbar,\n"
+ ".view.cell.progressbar:hover,\n"
+ ".view.cell.progressbar:selected,\n"
+ ".view.cell.progressbar:selected:focused {\n"
+ " background-color: @selected_bg_color;\n"
+ " color: @selected_fg_color;\n"
+ "}\n"
+ "\n"
+ ".rubberband {\n"
+ " background-color: alpha (@fg_color, 0.25);\n"
+ " border-color: @fg_color;\n"
+ " border-style: solid;\n"
+ " border-width: 1;\n"
+ "}\n"
+ "\n"
".tooltip {\n"
" background-color: @tooltip_bg_color; \n"
" color: @tooltip_fg_color; \n"
"}\n"
"\n"
".trough {\n"
+ " background-color: darker (@bg_color);\n"
" border-style: inset;\n"
" border-width: 1;\n"
" padding: 0;\n"
"}\n"
"\n"
".progressbar,\n"
- ".entry.progressbar {\n"
+ ".entry.progressbar, \n"
+ ".cell.progressbar {\n"
" background-color: @selected_bg_color;\n"
" border-color: shade (@selected_bg_color, 0.7);\n"
" color: @selected_fg_color;\n"
" background-color: shade (@bg_color, 1.05);\n"
"}\n"
"\n"
- ".check, .radio {\n"
+ ".check, .radio,"
+ ".cell.check, .cell.radio,\n"
+ ".cell.check:hover, .cell.radio:hover {\n"
" border-style: solid;\n"
" border-width: 1;\n"
" background-color: @base_color;\n"
"}\n"
"\n"
".check:selected, .radio:selected {\n"
- " background-color: @selected_bg_color;\n"
+ " background-color: darker (@bg_color);\n"
" color: @selected_fg_color;\n"
+ " border-color: @selected_fg_color;\n"
+ "}\n"
+ "\n"
+ ".check:selected:focused, .radio:selected:focused {\n"
+ " background-color: @selected_bg_color;\n"
"}\n"
"\n"
".menu.check, .menu.radio {\n"
" border-color: shade (@bg_color, 0.8);\n"
"}\n"
"\n"
+ "GtkSwitch.trough:active {\n"
+ " background-color: @selected_bg_color;\n"
+ " color: @selected_fg_color;\n"
+ "}\n"
+ "\n"
"GtkToggleButton.button:inconsistent {\n"
" border-style: outset;\n"
" border-width: 1px;\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"
" color: @fg_color;\n"
"}\n"
"\n"
- ".menu {\n"
- " border-width: 1;\n"
- " padding: 0;\n"
- "}\n"
- "\n"
".menu * {\n"
" border-width: 0;\n"
" padding: 2;\n"
return provider;
}
-static gchar *
-css_provider_get_theme_dir (void)
+gchar *
+_gtk_css_provider_get_theme_dir (void)
{
const gchar *var;
gchar *path;
/**
* gtk_css_provider_get_named:
* @name: A theme name
- * @variant: variant to load, for example, "dark", or %NULL for the default
+ * @variant: (allow-none): variant to load, for example, "dark", or
+ * %NULL for the default
*
* Loads a theme from the usual theme paths
*
* Returns: (transfer none): a #GtkCssProvider with the theme loaded.
- * This memory is owned by GTK+, and you must not free it.
- **/
+ * This memory is owned by GTK+, and you must not free it.
+ */
GtkCssProvider *
gtk_css_provider_get_named (const gchar *name,
const gchar *variant)
{
static GHashTable *themes = NULL;
GtkCssProvider *provider;
+ gchar *key;
if (G_UNLIKELY (!themes))
themes = g_hash_table_new (g_str_hash, g_str_equal);
- provider = g_hash_table_lookup (themes, name);
+ if (variant == NULL)
+ key = (gchar *)name;
+ else
+ key = g_strconcat (name, "-", variant, NULL);
+
+ provider = g_hash_table_lookup (themes, key);
if (!provider)
{
if (!path)
{
- gchar *theme_dir = css_provider_get_theme_dir ();
+ gchar *theme_dir;
+
+ theme_dir = _gtk_css_provider_get_theme_dir ();
path = g_build_filename (theme_dir, name, subpath, NULL);
g_free (theme_dir);
if (path)
{
- GError *error = NULL;
+ GError *error;
provider = gtk_css_provider_new ();
- gtk_css_provider_load_from_path (provider, path, &error);
-
- if (error)
+ error = NULL;
+ if (!gtk_css_provider_load_from_path (provider, path, &error))
{
g_warning ("Could not load named theme \"%s\": %s", name, error->message);
g_error_free (error);
provider = NULL;
}
else
- g_hash_table_insert (themes, g_strdup (name), provider);
+ g_hash_table_insert (themes, g_strdup (key), provider);
+
+ g_free (path);
}
}
+ if (key != name)
+ g_free (key);
+
return provider;
}
+
+static void
+selector_path_print (const SelectorPath *path,
+ GString * str)
+{
+ GSList *walk, *reverse;
+
+ reverse = g_slist_copy (path->elements);
+ reverse = g_slist_reverse (reverse);
+
+ for (walk = reverse; walk; walk = walk->next)
+ {
+ SelectorElement *elem = walk->data;
+
+ switch (elem->elem_type)
+ {
+ case SELECTOR_TYPE_NAME:
+ case SELECTOR_NAME:
+ g_string_append (str, g_quark_to_string (elem->name));
+ break;
+ case SELECTOR_GTYPE:
+ g_string_append (str, g_type_name (elem->type));
+ break;
+ case SELECTOR_REGION:
+ g_string_append (str, g_quark_to_string (elem->region.name));
+ if (elem->region.flags)
+ {
+ char * flag_names[] = {
+ "nth-child(even)",
+ "nth-child(odd)",
+ "first-child",
+ "last-child",
+ "sorted"
+ };
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (flag_names); i++)
+ {
+ if (elem->region.flags & (1 << i))
+ {
+ g_string_append_c (str, ':');
+ g_string_append (str, flag_names[i]);
+ }
+ }
+ }
+ break;
+ case SELECTOR_CLASS:
+ g_string_append_c (str, '.');
+ g_string_append (str, g_quark_to_string (elem->name));
+ break;
+ case SELECTOR_GLOB:
+ if (walk->next == NULL ||
+ elem->combinator != COMBINATOR_CHILD ||
+ ((SelectorElement *) walk->next->data)->elem_type != SELECTOR_CLASS)
+ g_string_append (str, "*");
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ if (walk->next)
+ {
+ switch (elem->combinator)
+ {
+ case COMBINATOR_DESCENDANT:
+ if (elem->elem_type != SELECTOR_CLASS ||
+ ((SelectorElement *) walk->next->data)->elem_type != SELECTOR_CLASS)
+ g_string_append_c (str, ' ');
+ break;
+ case COMBINATOR_CHILD:
+ if (((SelectorElement *) walk->next->data)->elem_type != SELECTOR_CLASS)
+ g_string_append (str, " > ");
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+ }
+
+ }
+
+ if (path->state)
+ {
+ char * state_names[] = {
+ "active",
+ "hover",
+ "selected",
+ "insensitive",
+ "inconsistent",
+ "focus"
+ };
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (state_names); i++)
+ {
+ if (path->state & (1 << i))
+ {
+ g_string_append_c (str, ':');
+ g_string_append (str, state_names[i]);
+ }
+ }
+ }
+
+ g_slist_free (reverse);
+}
+
+static void
+selector_style_info_print (const SelectorStyleInfo *info,
+ GString *str)
+{
+ GList *keys, *walk;
+ char *s;
+
+ selector_path_print (info->path, str);
+
+ g_string_append (str, " {\n");
+
+ keys = g_hash_table_get_keys (info->style);
+ /* so the output is identical for identical selector styles */
+ keys = g_list_sort (keys, (GCompareFunc) strcmp);
+
+ for (walk = keys; walk; walk = walk->next)
+ {
+ const char *name = walk->data;
+ const GValue *value = g_hash_table_lookup (info->style, (gpointer) name);
+
+ g_string_append (str, " ");
+ g_string_append (str, name);
+ g_string_append (str, ": ");
+ s = _gtk_css_value_to_string (value);
+ g_string_append (str, s);
+ g_free (s);
+ g_string_append (str, ";\n");
+ }
+
+ g_list_free (keys);
+
+ g_string_append (str, "}\n");
+}
+
+static void
+gtk_css_provider_print_colors (GHashTable *colors,
+ GString *str)
+{
+ GList *keys, *walk;
+ char *s;
+
+ keys = g_hash_table_get_keys (colors);
+ /* so the output is identical for identical styles */
+ keys = g_list_sort (keys, (GCompareFunc) strcmp);
+
+ for (walk = keys; walk; walk = walk->next)
+ {
+ const char *name = walk->data;
+ GtkSymbolicColor *symbolic = g_hash_table_lookup (colors, (gpointer) name);
+
+ g_string_append (str, "@define-color ");
+ g_string_append (str, name);
+ g_string_append (str, " ");
+ s = gtk_symbolic_color_to_string (symbolic);
+ g_string_append (str, s);
+ g_free (s);
+ g_string_append (str, ";\n");
+ }
+
+ g_list_free (keys);
+}
+
+/**
+ * gtk_css_provider_to_string:
+ * @provider: the provider to write to a string
+ *
+ * Convertes the @provider into a string representation in CSS
+ * format.
+ *
+ * Using gtk_css_provider_load_from_data() with the return value
+ * from this function on a new provider created with
+ * gtk_css_provider_new() will basicallu create a duplicate of
+ * this @provider.
+ *
+ * Returns: a new string representing the @provider.
+ **/
+char *
+gtk_css_provider_to_string (GtkCssProvider *provider)
+{
+ GtkCssProviderPrivate *priv;
+ GString *str;
+ guint i;
+
+ g_return_val_if_fail (GTK_IS_CSS_PROVIDER (provider), NULL);
+
+ priv = provider->priv;
+
+ str = g_string_new ("");
+
+ gtk_css_provider_print_colors (priv->symbolic_colors, str);
+
+ for (i = 0; i < priv->selectors_info->len; i++)
+ {
+ if (i > 0)
+ g_string_append (str, "\n");
+ selector_style_info_print (g_ptr_array_index (priv->selectors_info, i),
+ str);
+ }
+
+ return g_string_free (str, FALSE);
+}
+