[gnome-shell/nbtk-introduction] Import stylesheet code from hippo-canvas



commit 98215f497d8aa22d600beaeedf6e3c8446209d76
Author: Owen W. Taylor <otaylor fishsoup net>
Date:   Sat Sep 19 20:43:49 2009 -0400

    Import stylesheet code from hippo-canvas
    
    Import:
    
      HippoCanvasTheme      => ShellTheme
      HippoCanvasThemeImage => ShellThemeImage
      HippoCanvasStyle      => ShellThemeNode
    
    ShellThemeContext is a new class managing the theme for a stage and
    global properties like resolution.
    
    test-theme.c is a newly written test program to do verification of the
    style matching and property handling rules.
    
    Various changes are made in the import:
    
     - Comprehensive reindentation
     - guint32 pixels replaced with ClutterColor
     - General pseudo-class support added
     - Old-fashioned (non-bordered) background image support added, though
       with no support for repeat, etc.
     - Bug fixes for problems revealed by test program

 .gitignore                        |    1 +
 configure.ac                      |    1 +
 src/Makefile-toolkit.am           |   36 +
 src/Makefile.am                   |    9 +-
 src/toolkit/shell-theme-context.c |  218 +++++
 src/toolkit/shell-theme-context.h |   40 +
 src/toolkit/shell-theme-image.c   |   92 ++
 src/toolkit/shell-theme-image.h   |   38 +
 src/toolkit/shell-theme-node.c    | 1777 +++++++++++++++++++++++++++++++++++++
 src/toolkit/shell-theme-node.h    |  115 +++
 src/toolkit/shell-theme-private.h |   22 +
 src/toolkit/shell-theme.c         | 1032 +++++++++++++++++++++
 src/toolkit/shell-theme.h         |   28 +
 src/toolkit/test-theme.c          |  292 ++++++
 src/toolkit/test-theme.css        |   68 ++
 15 files changed, 3766 insertions(+), 3 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index ba962a7..d39cc63 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,6 +38,7 @@ src/gnomeshell-taskpanel
 src/gnome-shell
 src/test-recorder
 src/test-recorder.ogg
+src/test-theme
 stamp-h1
 tests/run-test.sh
 xmldocs.make
diff --git a/configure.ac b/configure.ac
index 70882e2..ad6b87a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -60,6 +60,7 @@ PKG_CHECK_MODULES(TIDY, clutter-1.0)
 PKG_CHECK_MODULES(NBTK, clutter-1.0 gtk+-2.0 libccss-1 >= 0.3.1 clutter-imcontext-0.1)
 PKG_CHECK_MODULES(BIG, clutter-1.0 gtk+-2.0 librsvg-2.0)
 PKG_CHECK_MODULES(GDMUSER, dbus-glib-1 gtk+-2.0)
+PKG_CHECK_MODULES(TOOLKIT, clutter-1.0 libcroco-0.6)
 PKG_CHECK_MODULES(TRAY, gtk+-2.0)
 
 MUTTER_BIN_DIR=`$PKG_CONFIG --variable=exec_prefix mutter-plugins`/bin
diff --git a/src/Makefile-toolkit.am b/src/Makefile-toolkit.am
new file mode 100644
index 0000000..67c4852
--- /dev/null
+++ b/src/Makefile-toolkit.am
@@ -0,0 +1,36 @@
+toolkit_cflags =				\
+    -I$(top_srcdir)/src				\
+    -DPREFIX=\""$(prefix)"\"			\
+    -DLIBDIR=\""$(libdir)"\"			\
+    -DG_DISABLE_DEPRECATED			\
+    -DG_LOG_DOMAIN=\"Shell\"			\
+    $(TOOLKIT_CFLAGS)
+
+# please, keep this sorted alphabetically
+toolkit_sources =				\
+	toolkit/shell-theme.c			\
+	toolkit/shell-theme.h			\
+	toolkit/shell-theme-context.c		\
+	toolkit/shell-theme-context.h		\
+	toolkit/shell-theme-image.c		\
+	toolkit/shell-theme-image.h		\
+	toolkit/shell-theme-node.c		\
+	toolkit/shell-theme-node.h		\
+	toolkit/shell-theme-private.h
+
+non_gir_toolkit_sources =			\
+	toolkit/shell-theme-private.h
+
+noinst_LTLIBRARIES += libshell-toolkit.la
+
+libshell_toolkit_la_LIBADD = $(TOOLKIT_LIBS)
+libshell_toolkit_la_SOURCES = $(toolkit_sources) $(toolkit_built_sources)
+libshell_toolkit_la_CPPFLAGS = $(toolkit_cflags)
+libshell_toolkit_la_LDFLAGS = $(LDADD)
+
+noinst_PROGRAMS += test-theme
+
+test_theme_CPPFLAGS = $(toolkit_cflags)
+test_theme_LDADD = libshell-toolkit.la
+
+test_theme_SOURCES = toolkit/test-theme.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 83837df..dfa827c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -4,6 +4,7 @@ CLEANFILES =
 EXTRA_DIST =
 libexec_PROGRAMS =
 noinst_LTLIBRARIES =
+noinst_PROGRAMS =
 
 .AUTOPARALLEL:
 
@@ -25,6 +26,7 @@ EXTRA_DIST += gnome-shell.in
 include Makefile-big.am
 include Makefile-gdmuser.am
 include Makefile-nbtk.am
+include Makefile-toolkit.am
 include Makefile-tray.am
 
 gnome_shell_cflags =				\
@@ -97,7 +99,7 @@ libgnome_shell_la_SOURCES =			\
 	shell-wm.c				\
 	shell-wm.h
 
-non_gir_sources =						\
+non_gir_sources =				\
 	shell-embedded-window-private.h
 
 shell_recorder_sources =        \
@@ -115,7 +117,7 @@ if BUILD_RECORDER
 libgnome_shell_la_SOURCES += $(shell_recorder_sources)
 non_gir_sources += $(shell_recorder_non_gir_sources)
 
-noinst_PROGRAMS = test-recorder
+noinst_PROGRAMS += test-recorder
 
 test_recorder_CPPFLAGS = $(TEST_SHELL_RECORDER_CFLAGS)
 test_recorder_LDADD = $(TEST_SHELL_RECORDER_LIBS)
@@ -126,7 +128,7 @@ test_recorder_SOURCES =     \
 endif BUILD_RECORDER
 
 libgnome_shell_la_gir_sources = \
-	$(filter-out $(non_gir_sources), $(libgnome_shell_la_SOURCES))
+	$(filter-out $(non_gir_sources) $(non_gir_toolkit_sources), $(libgnome_shell_la_SOURCES) $(toolkit_sources))
 
 shell-marshal.h: stamp-shell-marshal.h
 	@true
@@ -155,6 +157,7 @@ libgnome_shell_la_LIBADD =	\
 	libbig-1.0.la		\
 	libnbtk-1.0.la       	\
 	libgdmuser-1.0.la	\
+	libshell-toolkit.la	\
 	libtray.la
 libgnome_shell_la_CPPFLAGS = $(gnome_shell_cflags)
 
diff --git a/src/toolkit/shell-theme-context.c b/src/toolkit/shell-theme-context.c
new file mode 100644
index 0000000..62ad276
--- /dev/null
+++ b/src/toolkit/shell-theme-context.c
@@ -0,0 +1,218 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include <config.h>
+
+#include "shell-theme.h"
+#include "shell-theme-context.h"
+
+struct _ShellThemeContext {
+  GObject parent;
+
+  double resolution;
+  PangoFontDescription *font;
+  ShellThemeNode *root_node;
+  ShellTheme *theme;
+};
+
+struct _ShellThemeContextClass {
+  GObjectClass parent_class;
+};
+
+enum
+{
+  CHANGED,
+
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE (ShellThemeContext, shell_theme_context, G_TYPE_OBJECT)
+
+static void
+shell_theme_context_finalize (GObject *object)
+{
+  ShellThemeContext *context = SHELL_THEME_CONTEXT (object);
+
+  if (context->root_node)
+    g_object_unref (context->root_node);
+  if (context->theme)
+    g_object_unref (context->theme);
+  
+  pango_font_description_free (context->font);
+
+  G_OBJECT_CLASS (shell_theme_context_parent_class)->finalize (object);
+}
+
+static void
+shell_theme_context_class_init (ShellThemeContextClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = shell_theme_context_finalize;
+
+  signals[CHANGED] =
+    g_signal_new ("changed",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  0, /* no default handler slot */
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+}
+
+static void
+shell_theme_context_init (ShellThemeContext *context)
+{
+  context->resolution = 96.;
+  context->font = pango_font_description_from_string ("sans-serif 10");
+}
+
+ShellThemeContext *
+shell_theme_context_new (void)
+{
+  ShellThemeContext *context;
+
+  context = g_object_new (SHELL_TYPE_THEME_CONTEXT, NULL);
+
+  return context;
+}
+
+static void
+on_stage_destroy (ClutterStage *stage)
+{
+  ShellThemeContext *context = shell_theme_context_get_for_stage (stage);
+
+  g_object_set_data (G_OBJECT (stage), "shell-theme-context", NULL);
+  g_object_unref (context);
+}
+
+/**
+ * shell_theme_context_get_for_stage:
+ * @stage: a #ClutterStage
+ *
+ * Gets a singleton theme context associated with the stage.
+ *
+ * Return value: (transfer none): the singleton theme context for the stage
+ */
+ShellThemeContext *
+shell_theme_context_get_for_stage (ClutterStage *stage)
+{
+  ShellThemeContext *context;
+
+  g_return_val_if_fail (CLUTTER_IS_STAGE (stage), NULL);
+
+  context = g_object_get_data (G_OBJECT (stage), "shell-theme-context");
+  if (context)
+    return context;
+
+  context = shell_theme_context_new ();
+  g_object_set_data (G_OBJECT (stage), "shell-theme-context", context);
+  g_signal_connect (stage, "destroy",
+                    G_CALLBACK (on_stage_destroy), NULL);
+
+  return context;
+}
+
+/**
+ * shell_theme_context_set_theme:
+ * @context: a #ShellThemeContext
+ *
+ * Sets the default set of theme stylesheets for the context. This theme will
+ * be used for the root node and for nodes descending from it, unless some other
+ * style is explicitely specified.
+ */
+void
+shell_theme_context_set_theme (ShellThemeContext          *context,
+                               ShellTheme                 *theme)
+{
+  g_return_if_fail (SHELL_IS_THEME_CONTEXT (context));
+  g_return_if_fail (theme == NULL || SHELL_IS_THEME (theme));
+
+  if (context->theme != theme)
+    {
+      if (context->theme)
+        g_object_unref (context->theme);
+
+      context->theme = theme;
+
+      if (context->theme)
+        g_object_ref (context->theme);
+
+      g_signal_emit (context, signals[CHANGED], 0);
+    }
+}
+
+/**
+ * shell_theme_context_get_theme:
+ * @context: a #ShellThemeContext
+ *
+ * Gets the default theme for the context. See shell_theme_context_set_theme()
+ *
+ * Return value: (transfer none): the default theme for the context
+ */
+ShellTheme *
+shell_theme_context_get_theme (ShellThemeContext *context)
+{
+  g_return_val_if_fail (SHELL_IS_THEME_CONTEXT (context), NULL);
+
+  return context->theme;
+}
+
+void
+shell_theme_context_set_resolution (ShellThemeContext *context,
+                                    double             resolution)
+{
+  g_return_if_fail (SHELL_IS_THEME_CONTEXT (context));
+
+  context->resolution = resolution;
+}
+
+double
+shell_theme_context_get_resolution (ShellThemeContext *context)
+{
+  g_return_val_if_fail (SHELL_IS_THEME_CONTEXT (context), 96.);
+
+  return context->resolution;
+}
+
+void
+shell_theme_context_set_font (ShellThemeContext          *context,
+                              const PangoFontDescription *font)
+{
+  g_return_if_fail (SHELL_IS_THEME_CONTEXT (context));
+
+  if (context->font == font)
+    return;
+
+  pango_font_description_free (context->font);
+  context->font = pango_font_description_copy (font);
+}
+
+const PangoFontDescription *
+shell_theme_context_get_font (ShellThemeContext *context)
+{
+  g_return_val_if_fail (SHELL_IS_THEME_CONTEXT (context), NULL);
+
+  return context->font;
+}
+
+/**
+ * shell_theme_context_get_root_node:
+ * @context: a #ShellThemeContext
+ *
+ * Gets the root node of the tree of theme style nodes that associated with this
+ * context. For the node tree associated with a stage, this node represents
+ * styles applied to the stage itself.
+ *
+ * Return value: (transfer none): the root node of the context's style tree
+ */
+ShellThemeNode *
+shell_theme_context_get_root_node (ShellThemeContext *context)
+{
+  if (context->root_node == NULL)
+    context->root_node = shell_theme_node_new (context, NULL, context->theme,
+                                               G_TYPE_NONE, NULL, NULL, NULL);
+
+  return context->root_node;
+}
diff --git a/src/toolkit/shell-theme-context.h b/src/toolkit/shell-theme-context.h
new file mode 100644
index 0000000..c74b1db
--- /dev/null
+++ b/src/toolkit/shell-theme-context.h
@@ -0,0 +1,40 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_THEME_CONTEXT_H__
+#define __SHELL_THEME_CONTEXT_H__
+
+#include <clutter/clutter.h>
+#include <pango/pango.h>
+#include "shell-theme-node.h"
+
+G_BEGIN_DECLS
+
+typedef struct _ShellThemeContextClass ShellThemeContextClass;
+
+#define SHELL_TYPE_THEME_CONTEXT             (shell_theme_context_get_type ())
+#define SHELL_THEME_CONTEXT(object)          (G_TYPE_CHECK_INSTANCE_CAST ((object), SHELL_TYPE_THEME_CONTEXT, ShellThemeContext))
+#define SHELL_THEME_CONTEXT_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_THEME_CONTEXT, ShellThemeContextClass))
+#define SHELL_IS_THEME_CONTEXT(object)       (G_TYPE_CHECK_INSTANCE_TYPE ((object), SHELL_TYPE_THEME_CONTEXT))
+#define SHELL_IS_THEME_CONTEXT_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_THEME_CONTEXT))
+#define SHELL_THEME_CONTEXT_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_THEME_CONTEXT, ShellThemeContextClass))
+
+GType shell_theme_context_get_type (void) G_GNUC_CONST;
+
+ShellThemeContext *shell_theme_context_new           (void);
+ShellThemeContext *shell_theme_context_get_for_stage (ClutterStage *stage);
+
+void                        shell_theme_context_set_theme      (ShellThemeContext          *context,
+                                                                ShellTheme                 *theme);
+ShellTheme *                shell_theme_context_get_theme      (ShellThemeContext          *context);
+
+void                        shell_theme_context_set_resolution (ShellThemeContext          *context,
+                                                                gdouble                     resolution);
+double                      shell_theme_context_get_resolution (ShellThemeContext          *context);
+void                        shell_theme_context_set_font       (ShellThemeContext          *context,
+                                                                const PangoFontDescription *font);
+const PangoFontDescription *shell_theme_context_get_font       (ShellThemeContext          *context);
+
+ShellThemeNode *            shell_theme_context_get_root_node  (ShellThemeContext          *context);
+
+G_END_DECLS
+
+#endif /* __SHELL_THEME_CONTEXT_H__ */
diff --git a/src/toolkit/shell-theme-image.c b/src/toolkit/shell-theme-image.c
new file mode 100644
index 0000000..e07306f
--- /dev/null
+++ b/src/toolkit/shell-theme-image.c
@@ -0,0 +1,92 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include <config.h>
+
+#include "shell-theme-image.h"
+
+struct _ShellThemeImage {
+  GObject parent;
+
+  char *filename;
+  int border_top;
+  int border_right;
+  int border_bottom;
+  int border_left;
+};
+
+struct _ShellThemeImageClass {
+  GObjectClass parent_class;
+
+};
+
+G_DEFINE_TYPE (ShellThemeImage, shell_theme_image, G_TYPE_OBJECT)
+
+static void
+shell_theme_image_finalize (GObject *object)
+{
+  ShellThemeImage *image = SHELL_THEME_IMAGE (object);
+
+  g_free (image->filename);
+
+  G_OBJECT_CLASS (shell_theme_image_parent_class)->finalize (object);
+}
+
+static void
+shell_theme_image_class_init (ShellThemeImageClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = shell_theme_image_finalize;
+}
+
+static void
+shell_theme_image_init (ShellThemeImage *image)
+{
+}
+
+ShellThemeImage *
+shell_theme_image_new (const char *filename,
+                       int         border_top,
+                       int         border_right,
+                       int         border_bottom,
+                       int         border_left)
+{
+  ShellThemeImage *image;
+
+  image = g_object_new (SHELL_TYPE_THEME_IMAGE, NULL);
+
+  image->filename = g_strdup (filename);
+  image->border_top = border_top;
+  image->border_right = border_right;
+  image->border_bottom = border_bottom;
+  image->border_left = border_left;
+
+  return image;
+}
+
+const char *
+shell_theme_image_get_filename (ShellThemeImage *image)
+{
+  g_return_val_if_fail (SHELL_IS_THEME_IMAGE (image), NULL);
+
+  return image->filename;
+}
+
+void
+shell_theme_image_get_borders (ShellThemeImage *image,
+                               int             *border_top,
+                               int             *border_right,
+                               int             *border_bottom,
+                               int             *border_left)
+{
+  g_return_if_fail (SHELL_IS_THEME_IMAGE (image));
+
+  if (border_top)
+    *border_top = image->border_top;
+  if (border_right)
+    *border_right = image->border_right;
+  if (border_bottom)
+    *border_bottom = image->border_bottom;
+  if (border_left)
+    *border_left = image->border_left;
+}
diff --git a/src/toolkit/shell-theme-image.h b/src/toolkit/shell-theme-image.h
new file mode 100644
index 0000000..e6b1a95
--- /dev/null
+++ b/src/toolkit/shell-theme-image.h
@@ -0,0 +1,38 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_THEME_IMAGE_H__
+#define __SHELL_THEME_IMAGE_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/* A ShellThemeImage encapsulates an image with specified unscaled borders on each edge.
+ */
+typedef struct _ShellThemeImage      ShellThemeImage;
+typedef struct _ShellThemeImageClass ShellThemeImageClass;
+
+#define SHELL_TYPE_THEME_IMAGE             (shell_theme_image_get_type ())
+#define SHELL_THEME_IMAGE(object)          (G_TYPE_CHECK_INSTANCE_CAST ((object), SHELL_TYPE_THEME_IMAGE, ShellThemeImage))
+#define SHELL_THEME_IMAGE_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_THEME_IMAGE, ShellThemeImageClass))
+#define SHELL_IS_THEME_IMAGE(object)       (G_TYPE_CHECK_INSTANCE_TYPE ((object), SHELL_TYPE_THEME_IMAGE))
+#define SHELL_IS_THEME_IMAGE_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_THEME_IMAGE))
+#define SHELL_THEME_IMAGE_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_THEME_IMAGE, ShellThemeImageClass))
+
+GType             shell_theme_image_get_type          (void) G_GNUC_CONST;
+
+ShellThemeImage *shell_theme_image_new (const char *filename,
+                                        int         border_top,
+                                        int         border_right,
+                                        int         border_bottom,
+                                        int         border_left);
+
+const char *shell_theme_image_get_filename (ShellThemeImage *image);
+void        shell_theme_image_get_borders  (ShellThemeImage *image,
+                                            int             *border_top,
+                                            int             *border_right,
+                                            int             *border_bottom,
+                                            int             *border_left);
+
+G_END_DECLS
+
+#endif /* __SHELL_THEME_IMAGE_H__ */
diff --git a/src/toolkit/shell-theme-node.c b/src/toolkit/shell-theme-node.c
new file mode 100644
index 0000000..474e351
--- /dev/null
+++ b/src/toolkit/shell-theme-node.c
@@ -0,0 +1,1777 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "shell-theme-private.h"
+#include "shell-theme-context.h"
+#include "shell-theme-node.h"
+
+static void shell_theme_node_init               (ShellThemeNode          *node);
+static void shell_theme_node_class_init         (ShellThemeNodeClass     *klass);
+static void shell_theme_node_dispose            (GObject                 *object);
+static void shell_theme_node_finalize           (GObject                 *object);
+
+
+#if 0
+enum {
+  LAST_SIGNAL
+};
+
+static int signals[LAST_SIGNAL];
+#endif
+
+struct _ShellThemeNode {
+  GObject parent;
+
+  ShellThemeContext *context;
+  ShellThemeNode *parent_node;
+  ShellTheme *theme;
+
+  PangoFontDescription *font_desc;
+
+  ClutterColor background_color;
+  ClutterColor foreground_color;
+  ClutterColor border_color[4];
+  double border_width[4];
+  guint padding[4];
+
+  char *background_image;
+  ShellThemeImage *background_theme_image;
+
+  GType element_type;
+  char *element_id;
+  char *element_class;
+  char *pseudo_class;
+
+  CRDeclaration **properties;
+  int n_properties;
+
+  guint properties_computed : 1;
+  guint borders_computed : 1;
+  guint background_computed : 1;
+  guint foreground_computed : 1;
+  guint background_theme_image_computed : 1;
+  guint link_type : 2;
+};
+
+struct _ShellThemeNodeClass {
+  GObjectClass parent_class;
+
+};
+
+static const ClutterColor BLACK_COLOR = { 0, 0, 0, 0xff };
+static const ClutterColor TRANSPARENT_COLOR = { 0, 0, 0, 0 };
+
+G_DEFINE_TYPE (ShellThemeNode, shell_theme_node, G_TYPE_OBJECT)
+
+static void
+shell_theme_node_init (ShellThemeNode *node)
+{
+}
+
+static void
+shell_theme_node_class_init (ShellThemeNodeClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = shell_theme_node_dispose;
+  object_class->finalize = shell_theme_node_finalize;
+}
+
+static void
+shell_theme_node_dispose (GObject *object)
+{
+  /* ShellThemeNode *node = SHELL_THEME_NODE (object); */
+
+  G_OBJECT_CLASS (shell_theme_node_parent_class)->dispose (object);
+}
+
+static void
+shell_theme_node_finalize (GObject *object)
+{
+  ShellThemeNode *node = SHELL_THEME_NODE (object);
+
+  g_free (node->element_id);
+  g_free (node->element_class);
+  g_free (node->pseudo_class);
+
+  if (node->properties)
+    {
+      g_free (node->properties);
+      node->properties = NULL;
+      node->n_properties = 0;
+    }
+
+  if (node->font_desc)
+    {
+      pango_font_description_free (node->font_desc);
+      node->font_desc = NULL;
+    }
+
+  if (node->background_theme_image)
+    {
+      g_object_unref (node->background_theme_image);
+      node->background_theme_image = NULL;
+    }
+
+  if (node->background_image)
+    g_free (node->background_image);
+
+  G_OBJECT_CLASS (shell_theme_node_parent_class)->finalize (object);
+}
+
+ShellThemeNode *
+shell_theme_node_new (ShellThemeContext    *context,
+                      ShellThemeNode       *parent_node,
+                      ShellTheme           *theme,
+                      GType                 element_type,
+                      const char           *element_id,
+                      const char           *element_class,
+                      const char           *pseudo_class)
+{
+  ShellThemeNode *node;
+
+  g_return_val_if_fail (SHELL_IS_THEME_CONTEXT (context), NULL);
+  g_return_val_if_fail (parent_node == NULL || SHELL_IS_THEME_NODE (parent_node), NULL);
+
+  node = g_object_new (SHELL_TYPE_THEME_NODE, NULL);
+
+  node->context = g_object_ref (context);
+  if (parent_node != NULL)
+    node->parent_node = g_object_ref (parent_node);
+  else
+    node->parent_node = NULL;
+
+  if (theme == NULL && parent_node != NULL)
+    theme = parent_node->theme;
+
+  if (theme != NULL)
+    node->theme = g_object_ref (theme);
+
+  node->element_type = element_type;
+  node->element_id = g_strdup (element_id);
+  node->element_class = g_strdup (element_class);
+  node->pseudo_class = g_strdup (pseudo_class);
+
+  return node;
+}
+
+/**
+ * shell_theme_node_get_parent:
+ * @node: a #ShellThemeNode
+ *
+ * Gets the parent themed element node.
+ *
+ * Return value: (transfer none): the parent #ShellThemeNode, or %NULL if this
+ *  is the root node of the tree of theme elements.
+ */
+ShellThemeNode *
+shell_theme_node_get_parent (ShellThemeNode *node)
+{
+  g_return_val_if_fail (SHELL_IS_THEME_NODE (node), NULL);
+
+  return node->parent_node;
+}
+
+/**
+ * shell_theme_node_get_theme:
+ * @node: a #ShellThemeNode
+ *
+ * Gets the theme stylesheet set that styles this node
+ *
+ * Return value: (transfer none): the theme stylesheet set
+ */
+ShellTheme *
+shell_theme_node_get_theme (ShellThemeNode *node)
+{
+  g_return_val_if_fail (SHELL_IS_THEME_NODE (node), NULL);
+
+  return node->theme;
+}
+
+GType
+shell_theme_node_get_element_type (ShellThemeNode *node)
+{
+  g_return_val_if_fail (SHELL_IS_THEME_NODE (node), G_TYPE_NONE);
+
+  return node->element_type;
+}
+
+const char *
+shell_theme_node_get_element_id (ShellThemeNode *node)
+{
+  g_return_val_if_fail (SHELL_IS_THEME_NODE (node), NULL);
+
+  return node->element_id;
+}
+
+const char *
+shell_theme_node_get_element_class (ShellThemeNode *node)
+{
+  g_return_val_if_fail (SHELL_IS_THEME_NODE (node), NULL);
+
+  return node->element_class;
+}
+
+const char *
+shell_theme_node_get_pseudo_class (ShellThemeNode *node)
+{
+  g_return_val_if_fail (SHELL_IS_THEME_NODE (node), NULL);
+
+  return node->pseudo_class;
+}
+
+static void
+ensure_properties (ShellThemeNode *node)
+{
+  if (!node->properties_computed)
+    {
+      node->properties_computed = TRUE;
+
+      if (node->theme)
+        _shell_theme_get_matched_properties (node->theme, node,
+                                             &node->properties, &node->n_properties);
+    }
+}
+
+typedef enum {
+  VALUE_FOUND,
+  VALUE_NOT_FOUND,
+  VALUE_INHERIT
+} GetFromTermResult;
+
+static gboolean
+term_is_inherit (CRTerm *term)
+{
+  return (term->type == TERM_IDENT &&
+          strcmp (term->content.str->stryng->str, "inherit") == 0);
+}
+
+static gboolean
+term_is_none (CRTerm *term)
+{
+  return (term->type == TERM_IDENT &&
+          strcmp (term->content.str->stryng->str, "none") == 0);
+}
+
+static gboolean
+term_is_transparent (CRTerm *term)
+{
+  return (term->type == TERM_IDENT &&
+          strcmp (term->content.str->stryng->str, "transparent") == 0);
+}
+
+static int
+color_component_from_double (double component)
+{
+  /* http://people.redhat.com/otaylor/pixel-converting.html */
+  if (component >= 1.0)
+    return 255;
+  else
+    return (int)(component * 256);
+}
+
+static GetFromTermResult
+get_color_from_rgba_term (CRTerm       *term,
+                          ClutterColor *color)
+{
+  CRTerm *arg = term->ext_content.func_param;
+  CRNum *num;
+  double r = 0, g = 0, b = 0, a = 0;
+  int i;
+
+  for (i = 0; i < 4; i++)
+    {
+      double value;
+
+      if (arg == NULL)
+        return VALUE_NOT_FOUND;
+
+      if ((i == 0 && arg->the_operator != NO_OP) ||
+          (i > 0 && arg->the_operator != COMMA))
+        return VALUE_NOT_FOUND;
+
+      if (arg->type != TERM_NUMBER)
+        return VALUE_NOT_FOUND;
+
+      num = arg->content.num;
+
+      /* For simplicity, we convert a,r,g,b to [0,1.0] floats and then
+       * convert them back below. Then when we set them on a cairo content
+       * we convert them back to floats, and then cairo converts them
+       * back to integers to pass them to X, and so forth...
+       */
+      if (i < 3)
+        {
+          if (num->type == NUM_PERCENTAGE)
+            {
+              value = num->val / 100;
+            }
+          else if (num->type == NUM_GENERIC)
+            {
+              value = num->val / 255;
+            }
+          else
+            {
+              return VALUE_NOT_FOUND;
+            }
+        }
+      else
+        {
+          if (num->type != NUM_GENERIC)
+            return VALUE_NOT_FOUND;
+
+          value = num->val;
+        }
+
+      value = CLAMP (value, 0, 1);
+
+      switch (i)
+        {
+        case 0:
+          r = value;
+          break;
+        case 1:
+          g = value;
+          break;
+        case 2:
+          b = value;
+          break;
+        case 3:
+          a = value;
+          break;
+        }
+
+      arg = arg->next;
+    }
+
+  color->red = color_component_from_double (r);
+  color->green = color_component_from_double (g);
+  color->blue = color_component_from_double (b);
+  color->alpha = color_component_from_double (a);
+
+  return VALUE_FOUND;
+}
+
+static GetFromTermResult
+get_color_from_term (ShellThemeNode *node,
+                     CRTerm         *term,
+                     ClutterColor   *color)
+{
+  CRRgb rgb;
+  enum CRStatus status;
+
+  /* Since libcroco doesn't know about rgba colors, it can't handle
+   * the transparent keyword
+   */
+  if (term_is_transparent (term))
+    {
+      *color = TRANSPARENT_COLOR;
+      return VALUE_FOUND;
+    }
+  /* rgba () colors - a CSS3 addition, are not supported by libcroco,
+   * but they are parsed as a "function", so we can emulate the
+   * functionality.
+   */
+  else if (term->type == TERM_FUNCTION &&
+           term->content.str &&
+           term->content.str->stryng &&
+           term->content.str->stryng->str &&
+           strcmp (term->content.str->stryng->str, "rgba") == 0)
+    {
+      return get_color_from_rgba_term (term, color);
+    }
+
+  status = cr_rgb_set_from_term (&rgb, term);
+  if (status != CR_OK)
+    return VALUE_NOT_FOUND;
+
+  if (rgb.inherit)
+    return VALUE_INHERIT;
+
+  if (rgb.is_percentage)
+    cr_rgb_compute_from_percentage (&rgb);
+
+  color->red = rgb.red;
+  color->green = rgb.green;
+  color->blue = rgb.blue;
+  color->alpha = 0xff;
+
+  return VALUE_FOUND;
+}
+
+gboolean
+shell_theme_node_get_color (ShellThemeNode *node,
+                            const char     *property_name,
+                            gboolean        inherit,
+                            ClutterColor   *color)
+{
+
+  int i;
+
+  ensure_properties (node);
+
+  for (i = node->n_properties - 1; i >= 0; i--)
+    {
+      CRDeclaration *decl = node->properties[i];
+
+      if (strcmp (decl->property->stryng->str, property_name) == 0)
+        {
+          GetFromTermResult result = get_color_from_term (node, decl->value, color);
+          if (result == VALUE_FOUND)
+            {
+              return TRUE;
+            }
+          else if (result == VALUE_INHERIT)
+            {
+              if (node->parent_node)
+                return shell_theme_node_get_color (node->parent_node, property_name, inherit, color);
+              else
+                break;
+            }
+        }
+    }
+
+  return FALSE;
+}
+
+gboolean
+shell_theme_node_get_double (ShellThemeNode *node,
+                             const char     *property_name,
+                             gboolean        inherit,
+                             double         *value)
+{
+  gboolean result = FALSE;
+  int i;
+
+  ensure_properties (node);
+
+  for (i = node->n_properties - 1; i >= 0; i--)
+    {
+      CRDeclaration *decl = node->properties[i];
+
+      if (strcmp (decl->property->stryng->str, property_name) == 0)
+        {
+          CRTerm *term = decl->value;
+
+          if (term->type != TERM_NUMBER || term->content.num->type != NUM_GENERIC)
+            continue;
+
+          *value = term->content.num->val;
+          result = TRUE;
+          break;
+        }
+    }
+
+  if (!result && inherit && node->parent_node)
+    result = shell_theme_node_get_double (node->parent_node, property_name, inherit, value);
+
+  return result;
+}
+
+static const PangoFontDescription *
+get_parent_font (ShellThemeNode *node)
+{
+  if (node->parent_node)
+    return shell_theme_node_get_font (node->parent_node);
+  else
+    return shell_theme_context_get_font (node->context);
+}
+
+static GetFromTermResult
+get_length_from_term (ShellThemeNode *node,
+                      CRTerm         *term,
+                      gboolean        use_parent_font,
+                      gdouble        *length)
+{
+  CRNum *num;
+
+  enum {
+    ABSOLUTE,
+    POINTS,
+    FONT_RELATIVE,
+  } type = ABSOLUTE;
+
+  double multiplier = 1.0;
+
+  if (term->type != TERM_NUMBER)
+    {
+      g_warning ("Ignoring length property that isn't a number");
+      return FALSE;
+    }
+
+  num = term->content.num;
+
+  switch (num->type)
+    {
+    case NUM_LENGTH_PX:
+      type = ABSOLUTE;
+      multiplier = 1;
+      break;
+    case NUM_LENGTH_PT:
+      type = POINTS;
+      multiplier = 1;
+      break;
+    case NUM_LENGTH_IN:
+      type = POINTS;
+      multiplier = 72;
+      break;
+    case NUM_LENGTH_CM:
+      type = POINTS;
+      multiplier = 72. / 2.54;
+      break;
+    case NUM_LENGTH_MM:
+      type = POINTS;
+      multiplier = 72. / 25.4;
+      break;
+    case NUM_LENGTH_PC:
+      type = POINTS;
+      multiplier = 12. / 25.4;
+      break;
+    case NUM_LENGTH_EM:
+      {
+        type = FONT_RELATIVE;
+        multiplier = 1;
+        break;
+      }
+    case NUM_LENGTH_EX:
+      {
+        /* Doing better would require actually resolving the font description
+         * to a specific font, and Pango doesn't have an ex metric anyways,
+         * so we'd have to try and synthesize it by complicated means.
+         *
+         * The 0.5em is the CSS spec suggested thing to use when nothing
+         * better is available.
+         */
+        type = FONT_RELATIVE;
+        multiplier = 0.5;
+        break;
+      }
+
+    case NUM_INHERIT:
+      return VALUE_INHERIT;
+
+    case NUM_AUTO:
+      g_warning ("'auto' not supported for lengths");
+      return VALUE_NOT_FOUND;
+
+    case NUM_GENERIC:
+      g_warning ("length values must specify a unit");
+      return VALUE_NOT_FOUND;
+
+    case NUM_PERCENTAGE:
+      g_warning ("percentage lengths not currently supported");
+      return VALUE_NOT_FOUND;
+
+    case NUM_ANGLE_DEG:
+    case NUM_ANGLE_RAD:
+    case NUM_ANGLE_GRAD:
+    case NUM_TIME_MS:
+    case NUM_TIME_S:
+    case NUM_FREQ_HZ:
+    case NUM_FREQ_KHZ:
+    case NUM_UNKNOWN_TYPE:
+    case NB_NUM_TYPE:
+      g_warning ("Ignoring invalid type of number of length property");
+      return VALUE_NOT_FOUND;
+    }
+
+  switch (type)
+    {
+    case ABSOLUTE:
+      *length = num->val * multiplier;
+      break;
+    case POINTS:
+      {
+        double resolution = shell_theme_context_get_resolution (node->context);
+        *length = num->val * multiplier * (resolution / 72.);
+      }
+      break;
+    case FONT_RELATIVE:
+      {
+        const PangoFontDescription *desc;
+        double font_size;
+
+        if (use_parent_font)
+          desc = get_parent_font (node);
+        else
+          desc = shell_theme_node_get_font (node);
+
+        font_size = (double)pango_font_description_get_size (desc) / PANGO_SCALE;
+
+        if (pango_font_description_get_size_is_absolute (desc))
+          {
+            *length = num->val * multiplier * font_size;
+          }
+        else
+          {
+            double resolution = shell_theme_context_get_resolution (node->context);
+            *length = num->val * multiplier * (resolution / 72.) * font_size;
+          }
+      }
+      break;
+    default:
+      g_assert_not_reached ();
+    }
+
+  return VALUE_FOUND;
+}
+
+static GetFromTermResult
+get_length_internal (ShellThemeNode *node,
+                     const char     *property_name,
+                     const char     *suffixed,
+                     gdouble        *length)
+{
+  int i;
+
+  ensure_properties (node);
+
+  for (i = node->n_properties - 1; i >= 0; i--)
+    {
+      CRDeclaration *decl = node->properties[i];
+
+      if (strcmp (decl->property->stryng->str, property_name) == 0 ||
+          (suffixed != NULL && strcmp (decl->property->stryng->str, suffixed) == 0))
+        {
+          GetFromTermResult result = get_length_from_term (node, decl->value, FALSE, length);
+          if (result != VALUE_NOT_FOUND)
+            return result;
+        }
+    }
+
+  return VALUE_NOT_FOUND;
+}
+
+gboolean
+shell_theme_node_get_length (ShellThemeNode *node,
+                             const char     *property_name,
+                             gboolean        inherit,
+                             gdouble        *length)
+{
+  GetFromTermResult result = get_length_internal (node, property_name, NULL, length);
+  if (result == VALUE_FOUND)
+    return TRUE;
+  else if (result == VALUE_INHERIT)
+    inherit = TRUE;
+
+  if (inherit && node->parent_node &&
+      shell_theme_node_get_length (node->parent_node, property_name, inherit, length))
+    return TRUE;
+  else
+    return FALSE;
+}
+
+static void
+do_border_property (ShellThemeNode *node,
+                    CRDeclaration  *decl)
+{
+  const char *property_name = decl->property->stryng->str + 6; /* Skip 'border' */
+  ShellSide side = (ShellSide)-1;
+  ClutterColor color;
+  gboolean color_set = FALSE;
+  double width;
+  gboolean width_set = FALSE;
+  int j;
+
+  if (g_str_has_prefix (property_name, "-left"))
+    {
+      side = SHELL_SIDE_LEFT;
+      property_name += 5;
+    }
+  else if (g_str_has_prefix (property_name, "-right"))
+    {
+      side = SHELL_SIDE_RIGHT;
+      property_name += 6;
+    }
+  else if (g_str_has_prefix (property_name, "-top"))
+    {
+      side = SHELL_SIDE_TOP;
+      property_name += 4;
+    }
+  else if (g_str_has_prefix (property_name, "-bottom"))
+    {
+      side = SHELL_SIDE_BOTTOM;
+      property_name += 7;
+    }
+
+  if (strcmp (property_name, "") == 0)
+    {
+      /* Set value for width/color/node in any order */
+      CRTerm *term;
+
+      for (term = decl->value; term; term = term->next)
+        {
+          GetFromTermResult result;
+
+          if (term->type == TERM_IDENT)
+            {
+              const char *ident = term->content.str->stryng->str;
+              if (strcmp (ident, "none") == 0 || strcmp (ident, "hidden") == 0)
+                {
+                  width = 0.;
+                  continue;
+                }
+              else if (strcmp (ident, "solid") == 0)
+                {
+                  /* The only thing we support */
+                  continue;
+                }
+              else if (strcmp (ident, "dotted") == 0 ||
+                       strcmp (ident, "dashed") == 0 ||
+                       strcmp (ident, "solid") == 0 ||
+                       strcmp (ident, "double") == 0 ||
+                       strcmp (ident, "groove") == 0 ||
+                       strcmp (ident, "ridge") == 0 ||
+                       strcmp (ident, "inset") == 0 ||
+                       strcmp (ident, "outset") == 0)
+                {
+                  /* Treat the same as solid */
+                  continue;
+                }
+
+              /* Presumably a color, fall through */
+            }
+
+          if (term->type == TERM_NUMBER)
+            {
+              result = get_length_from_term (node, term, FALSE, &width);
+              if (result != VALUE_NOT_FOUND)
+                {
+                  width_set = result == VALUE_FOUND;
+                  continue;
+                }
+            }
+
+          result = get_color_from_term (node, term, &color);
+          if (result != VALUE_NOT_FOUND)
+            {
+              color_set = result == VALUE_FOUND;
+              continue;
+            }
+        }
+
+    }
+  else if (strcmp (property_name, "-color") == 0)
+    {
+      if (decl->value == NULL || decl->value->next != NULL)
+        return;
+
+      if (get_color_from_term (node, decl->value, &color) == VALUE_FOUND)
+        { /* Ignore inherit */
+          color_set = TRUE;
+        }
+    }
+  else if (strcmp (property_name, "-width") == 0)
+    {
+      if (decl->value == NULL || decl->value->next != NULL)
+        return;
+
+      if (get_length_from_term (node, decl->value, FALSE, &width) == VALUE_FOUND)
+        { /* Ignore inherit */
+          width_set = TRUE;
+        }
+    }
+
+  if (side == (ShellSide)-1)
+    {
+      for (j = 0; j < 4; j++)
+        {
+          if (color_set)
+            node->border_color[j] = color;
+          if (width_set)
+            node->border_width[j] = width;
+        }
+    }
+  else
+    {
+      if (color_set)
+        node->border_color[side] = color;
+      if (width_set)
+        node->border_width[side] = width;
+    }
+}
+
+static void
+do_padding_property_term (ShellThemeNode *node,
+                          CRTerm         *term,
+                          gboolean        left,
+                          gboolean        right,
+                          gboolean        top,
+                          gboolean        bottom)
+{
+  gdouble value;
+
+  if (get_length_from_term (node, term, FALSE, &value) != VALUE_FOUND)
+    return;
+
+  if (left)
+    node->padding[SHELL_SIDE_LEFT] = value;
+  if (right)
+    node->padding[SHELL_SIDE_RIGHT] = value;
+  if (top)
+    node->padding[SHELL_SIDE_TOP] = value;
+  if (bottom)
+    node->padding[SHELL_SIDE_BOTTOM] = value;
+}
+
+static void
+do_padding_property (ShellThemeNode *node,
+                     CRDeclaration  *decl)
+{
+  const char *property_name = decl->property->stryng->str + 7; /* Skip 'padding' */
+
+  if (strcmp (property_name, "") == 0)
+    {
+      /* Slight deviation ... if we don't understand some of the terms and understand others,
+       * then we set the ones we understand and ignore the others instead of ignoring the
+       * whole thing
+       */
+      if (decl->value == NULL) /* 0 values */
+        return;
+      else if (decl->value->next == NULL)
+        { /* 1 value */
+          do_padding_property_term (node, decl->value, TRUE, TRUE, TRUE, TRUE); /* left/right/top/bottom */
+          return;
+        }  else if (decl->value->next->next == NULL)
+        { /* 2 values */
+          do_padding_property_term (node, decl->value,       FALSE, FALSE, TRUE,  TRUE);  /* top/bottom */
+          do_padding_property_term (node, decl->value->next, TRUE, TRUE,   FALSE, FALSE); /* left/right */
+        }  else if (decl->value->next->next->next == NULL)
+        { /* 3 values */
+          do_padding_property_term (node, decl->value,             FALSE, FALSE, TRUE,  FALSE); /* top */
+          do_padding_property_term (node, decl->value->next,       TRUE,  TRUE,  FALSE, FALSE); /* left/right */
+          do_padding_property_term (node, decl->value->next->next, FALSE, FALSE, FALSE, TRUE);  /* bottom */
+        }
+      else  if (decl->value->next->next->next == NULL)
+        { /* 4 values */
+          do_padding_property_term (node, decl->value,                   FALSE, FALSE, TRUE,  FALSE);
+          do_padding_property_term (node, decl->value->next,             FALSE, TRUE, FALSE, FALSE); /* left */
+          do_padding_property_term (node, decl->value->next->next,       FALSE, FALSE, FALSE, TRUE);
+          do_padding_property_term (node, decl->value->next->next->next, TRUE,  FALSE, FALSE, TRUE); /* left */
+        }
+      else
+        {
+          g_warning ("Too many values for padding property");
+          return;
+        }
+    }
+  else
+    {
+      if (decl->value == NULL || decl->value->next != NULL)
+        return;
+
+      if (strcmp (property_name, "-left") == 0)
+        {
+          do_padding_property_term (node, decl->value, TRUE,  FALSE, FALSE, FALSE);
+        }
+      else if (strcmp (property_name, "-right") == 0)
+        {
+          do_padding_property_term (node, decl->value, FALSE, TRUE,  FALSE, FALSE);
+        }
+      else if (strcmp (property_name, "-top") == 0)
+        {
+          do_padding_property_term (node, decl->value, FALSE, FALSE, TRUE,  FALSE);
+        }
+      else if (strcmp (property_name, "-bottom") == 0)
+        {
+          do_padding_property_term (node, decl->value, FALSE, FALSE, FALSE, TRUE);
+        }
+    }
+}
+
+static void
+ensure_borders (ShellThemeNode *node)
+{
+  int i, j;
+
+  if (node->borders_computed)
+    return;
+
+  node->borders_computed = TRUE;
+
+  ensure_properties (node);
+
+  for (j = 0; j < 4; j++)
+    {
+      node->border_width[j] = 0;
+      node->border_color[j] = TRANSPARENT_COLOR;
+    }
+
+  for (i = 0; i < node->n_properties; i++)
+    {
+      CRDeclaration *decl = node->properties[i];
+      const char *property_name = decl->property->stryng->str;
+
+      if (g_str_has_prefix (property_name, "border"))
+        {
+          do_border_property (node, decl);
+        }
+      else if (g_str_has_prefix (property_name, "padding"))
+        {
+          do_padding_property (node, decl);
+        }
+    }
+}
+
+double
+shell_theme_node_get_border_width (ShellThemeNode *node,
+                                   ShellSide       side)
+{
+  g_return_val_if_fail (SHELL_IS_THEME_NODE (node), 0.);
+  g_return_val_if_fail (side >= SHELL_SIDE_LEFT && side <= SHELL_SIDE_BOTTOM, 0.);
+
+  ensure_borders (node);
+
+  return node->border_width[side];
+}
+
+static GetFromTermResult
+get_background_color_from_term (ShellThemeNode *node,
+                                CRTerm         *term,
+                                ClutterColor   *color)
+{
+  GetFromTermResult result = get_color_from_term (node, term, color);
+  if (result == VALUE_NOT_FOUND)
+    {
+      if (term_is_transparent (term))
+        {
+          *color = TRANSPARENT_COLOR;
+          return VALUE_FOUND;
+        }
+    }
+
+  return result;
+}
+
+static void
+ensure_background (ShellThemeNode *node)
+{
+  int i;
+
+  if (node->background_computed)
+    return;
+
+  node->background_computed = TRUE;
+  node->background_color = TRANSPARENT_COLOR;
+
+  ensure_properties (node);
+
+  for (i = 0; i < node->n_properties; i++)
+    {
+      CRDeclaration *decl = node->properties[i];
+      const char *property_name = decl->property->stryng->str;
+
+      if (g_str_has_prefix (property_name, "background"))
+        property_name += 10;
+      else
+        continue;
+
+      if (strcmp (property_name, "") == 0)
+        {
+          /* We're very liberal here ... if we recognize any term in the expression we take it, and
+           * we ignore the rest. The actual specification is:
+           *
+           * background: [<'background-color'> || <'background-image'> || <'background-repeat'> || <'background-attachment'> || <'background-position'>] | inherit
+           */
+
+          CRTerm *term;
+          /* background: property sets all terms to specified or default values */
+          node->background_color = TRANSPARENT_COLOR;
+          g_free (node->background_image);
+          node->background_image = NULL;
+
+          for (term = decl->value; term; term = term->next)
+            {
+              GetFromTermResult result = get_background_color_from_term (node, term, &node->background_color);
+              if (result == VALUE_FOUND)
+                {
+                }
+              else if (result == VALUE_INHERIT)
+                {
+                  if (node->parent_node)
+                    {
+                      shell_theme_node_get_background_color (node->parent_node, &node->background_color);
+                      node->background_image = g_strdup (shell_theme_node_get_background_image (node->parent_node));
+                    }
+                }
+              else if (term_is_none (term))
+                {
+                }
+              else if (term->type == TERM_URI)
+                {
+                  node->background_image = _shell_theme_resolve_url (node->theme,
+                                                                     decl->parent_statement->parent_sheet,
+                                                                     term->content.str->stryng->str);
+                }
+            }
+        }
+      else if (strcmp (property_name, "-color") == 0)
+        {
+          GetFromTermResult result;
+
+          if (decl->value == NULL || decl->value->next != NULL)
+            continue;
+
+          result = get_background_color_from_term (node, decl->value, &node->background_color);
+          if (result == VALUE_FOUND)
+            {
+            }
+          else if (result == VALUE_INHERIT)
+            {
+              if (node->parent_node)
+                shell_theme_node_get_background_color (node->parent_node, &node->background_color);
+            }
+        }
+      else if (strcmp (property_name, "-image") == 0)
+        {
+          if (decl->value == NULL || decl->value->next != NULL)
+            continue;
+
+          if (decl->value->type == TERM_URI)
+            {
+              g_free (node->background_image);
+              node->background_image = _shell_theme_resolve_url (node->theme,
+                                                                 decl->parent_statement->parent_sheet,
+                                                                 decl->value->content.str->stryng->str);
+            }
+          else if (term_is_inherit (decl->value))
+            {
+              g_free (node->background_image);
+              node->background_image = g_strdup (shell_theme_node_get_background_image (node->parent_node));
+            }
+          else if (term_is_none (decl->value))
+            {
+              g_free (node->background_image);
+              node->background_image = NULL;
+            }
+        }
+    }
+}
+
+void
+shell_theme_node_get_background_color (ShellThemeNode *node,
+                                       ClutterColor   *color)
+{
+  g_return_if_fail (SHELL_IS_THEME_NODE (node));
+
+  ensure_background (node);
+
+  *color = node->background_color;
+}
+
+const char *
+shell_theme_node_get_background_image (ShellThemeNode *node)
+{
+  g_return_val_if_fail (SHELL_IS_THEME_NODE (node), NULL);
+
+  ensure_background (node);
+
+  return node->background_image;
+}
+
+void
+shell_theme_node_get_foreground_color (ShellThemeNode *node,
+                                       ClutterColor   *color)
+{
+  g_return_if_fail (SHELL_IS_THEME_NODE (node));
+
+  if (!node->foreground_computed)
+    {
+      int i;
+
+      node->foreground_computed = TRUE;
+
+      ensure_properties (node);
+
+      for (i = node->n_properties - 1; i >= 0; i--)
+        {
+          CRDeclaration *decl = node->properties[i];
+
+          if (strcmp (decl->property->stryng->str, "color") == 0)
+            {
+              GetFromTermResult result = get_color_from_term (node, decl->value, &node->foreground_color);
+              if (result == VALUE_FOUND)
+                goto out;
+              else if (result == VALUE_INHERIT)
+                break;
+            }
+        }
+
+      if (node->parent_node)
+        shell_theme_node_get_foreground_color (node->parent_node, &node->foreground_color);
+      else
+        node->foreground_color = BLACK_COLOR; /* default to black */
+    }
+
+ out:
+  *color = node->foreground_color;
+}
+
+void
+shell_theme_node_get_border_color (ShellThemeNode *node,
+                                   ShellSide       side,
+                                   ClutterColor   *color)
+{
+  g_return_if_fail (SHELL_IS_THEME_NODE (node));
+  g_return_if_fail (side >= SHELL_SIDE_LEFT && side <= SHELL_SIDE_BOTTOM);
+
+  ensure_borders (node);
+
+  *color = node->border_color[side];
+}
+
+double
+shell_theme_node_get_padding (ShellThemeNode *node,
+                              ShellSide      side)
+{
+  g_return_val_if_fail (SHELL_IS_THEME_NODE (node), 0.);
+  g_return_val_if_fail (side >= SHELL_SIDE_LEFT && side <= SHELL_SIDE_BOTTOM, 0.);
+
+  ensure_borders (node);
+
+  return node->padding[side];
+}
+
+ShellTextDecoration
+shell_theme_node_get_text_decoration (ShellThemeNode *node)
+{
+  int i;
+
+  ensure_properties (node);
+
+  for (i = node->n_properties - 1; i >= 0; i--)
+    {
+      CRDeclaration *decl = node->properties[i];
+
+      if (strcmp (decl->property->stryng->str, "text-decoration") == 0)
+        {
+          CRTerm *term = decl->value;
+          ShellTextDecoration decoration = 0;
+
+          /* Specification is none | [ underline || overline || line-through || blink ] | inherit
+           *
+           * We're a bit more liberal, and for example treat 'underline none' as the same as
+           * none.
+           */
+          for (; term; term = term->next)
+            {
+              if (term->type != TERM_IDENT)
+                goto next_decl;
+
+              if (strcmp (term->content.str->stryng->str, "none") == 0)
+                {
+                  return 0;
+                }
+              else if (strcmp (term->content.str->stryng->str, "inherit") == 0)
+                {
+                  if (node->parent_node)
+                    return shell_theme_node_get_text_decoration (node->parent_node);
+                }
+              else if (strcmp (term->content.str->stryng->str, "underline") == 0)
+                {
+                  decoration |= SHELL_TEXT_DECORATION_UNDERLINE;
+                }
+              else if (strcmp (term->content.str->stryng->str, "overline") == 0)
+                {
+                  decoration |= SHELL_TEXT_DECORATION_OVERLINE;
+                }
+              else if (strcmp (term->content.str->stryng->str, "line-through") == 0)
+                {
+                  decoration |= SHELL_TEXT_DECORATION_LINE_THROUGH;
+                }
+              else if (strcmp (term->content.str->stryng->str, "blink") == 0)
+                {
+                  decoration |= SHELL_TEXT_DECORATION_BLINK;
+                }
+              else
+                {
+                  goto next_decl;
+                }
+            }
+
+          return decoration;
+        }
+
+    next_decl:
+      ;
+    }
+
+  return 0;
+}
+
+static gboolean
+font_family_from_terms (CRTerm *term,
+                        char  **family)
+{
+  GString *family_string;
+  gboolean result = FALSE;
+  gboolean last_was_quoted = FALSE;
+
+  if (!term)
+    return FALSE;
+
+  family_string = g_string_new (NULL);
+
+  while (term)
+    {
+      if (term->type != TERM_STRING && term->type != TERM_IDENT)
+        {
+          goto out;
+        }
+
+      if (family_string->len > 0)
+        {
+          if (term->the_operator != COMMA && term->the_operator != NO_OP)
+            goto out;
+          /* Can concetenate to bare words, but not two quoted strings */
+          if ((term->the_operator == NO_OP && last_was_quoted) || term->type == TERM_STRING)
+            goto out;
+
+          if (term->the_operator == NO_OP)
+            {
+              g_string_append (family_string, " ");
+            }
+          else
+            {
+              g_string_append (family_string, ", ");
+            }
+        }
+      else
+        {
+          if (term->the_operator != NO_OP)
+            goto out;
+        }
+
+      g_string_append (family_string, term->content.str->stryng->str);
+
+      term = term->next;
+    }
+
+  result = TRUE;
+
+ out:
+  if (result)
+    {
+      *family = g_string_free (family_string, FALSE);
+      return TRUE;
+    }
+  else
+    {
+      *family = g_string_free (family_string, TRUE);
+      return FALSE;
+    }
+}
+
+/* In points */
+static int font_sizes[] = {
+  6 * 1024,   /* xx-small */
+  8 * 1024,   /* x-small */
+  10 * 1024,  /* small */
+  12 * 1024,  /* medium */
+  16 * 1024,  /* large */
+  20 * 1024,  /* x-large */
+  24 * 1024,  /* xx-large */
+};
+
+static gboolean
+font_size_from_term (ShellThemeNode *node,
+                     CRTerm         *term,
+                     double         *size)
+{
+  if (term->type == TERM_IDENT)
+    {
+      double resolution = shell_theme_context_get_resolution (node->context);
+      /* We work in integers to avoid double comparisons when converting back
+       * from a size in pixels to a logical size.
+       */
+      int size_points = (int)(0.5 + *size * (72. / resolution));
+
+      if (strcmp (term->content.str->stryng->str, "xx-small") == 0)
+        {
+          size_points = font_sizes[0];
+        }
+      else if (strcmp (term->content.str->stryng->str, "x-small") == 0)
+        {
+          size_points = font_sizes[1];
+        }
+      else if (strcmp (term->content.str->stryng->str, "small") == 0)
+        {
+          size_points = font_sizes[2];
+        }
+      else if (strcmp (term->content.str->stryng->str, "medium") == 0)
+        {
+          size_points = font_sizes[3];
+        }
+      else if (strcmp (term->content.str->stryng->str, "large") == 0)
+        {
+          size_points = font_sizes[4];
+        }
+      else if (strcmp (term->content.str->stryng->str, "x-large") == 0)
+        {
+          size_points = font_sizes[5];
+        }
+      else if (strcmp (term->content.str->stryng->str, "xx-large") == 0)
+        {
+          size_points = font_sizes[6];
+        }
+      else if (strcmp (term->content.str->stryng->str, "smaller") == 0)
+        {
+          /* Find the standard size equal to or smaller than the current size */
+          int i = 0;
+
+          while (i <= 6 && font_sizes[i] < size_points)
+            i++;
+
+          if (i > 6)
+            { /* original size greater than any standard size */
+              size_points = (int)(0.5 + size_points / 1.2);
+            }
+          else
+            {
+              /* Go one smaller than that, if possible */
+              if (i > 0)
+                i--;
+
+              size_points = font_sizes[i];
+            }
+        }
+      else if (strcmp (term->content.str->stryng->str, "larger") == 0)
+        {
+          /* Find the standard size equal to or larger than the current size */
+          int i = 6;
+
+          while (i >= 0 && font_sizes[i] > size_points)
+            i--;
+
+          if (i < 0) /* original size smaller than any standard size */
+            i = 0;
+
+          /* Go one larger than that, if possible */
+          if (i < 6)
+            i++;
+
+          size_points = font_sizes[i];
+        }
+      else
+        {
+          return FALSE;
+        }
+
+      *size = size_points * (resolution / 72.);
+      return TRUE;
+
+    }
+  else if (term->type == TERM_NUMBER && term->content.num->type == NUM_PERCENTAGE)
+    {
+      *size *= term->content.num->val / 100.;
+      return TRUE;
+    }
+  else if (get_length_from_term (node, term, TRUE, size) == VALUE_FOUND)
+    {
+      /* Convert from pixels to Pango units */
+      *size *= 1024;
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+font_weight_from_term (CRTerm      *term,
+                       PangoWeight *weight,
+                       gboolean    *weight_absolute)
+{
+  if (term->type == TERM_NUMBER)
+    {
+      int weight_int;
+
+      /* The spec only allows numeric weights from 100-900, though Pango
+       * will handle any number. We just let anything through.
+       */
+      if (term->content.num->type != NUM_GENERIC)
+        return FALSE;
+
+      weight_int = (int)(term->content.num->val + 0.5);
+
+      *weight = weight_int;
+      *weight_absolute = TRUE;
+
+    }
+  else if (term->type == TERM_IDENT)
+    {
+      /* FIXME: handle INHERIT */
+
+      if (strcmp (term->content.str->stryng->str, "bold") == 0)
+        {
+          *weight = PANGO_WEIGHT_BOLD;
+          *weight_absolute = TRUE;
+        }
+      else if (strcmp (term->content.str->stryng->str, "normal") == 0)
+        {
+          *weight = PANGO_WEIGHT_NORMAL;
+          *weight_absolute = TRUE;
+        }
+      else if (strcmp (term->content.str->stryng->str, "bolder") == 0)
+        {
+          *weight = PANGO_WEIGHT_BOLD;
+          *weight_absolute = FALSE;
+        }
+      else if (strcmp (term->content.str->stryng->str, "lighter") == 0)
+        {
+          *weight = PANGO_WEIGHT_LIGHT;
+          *weight_absolute = FALSE;
+        }
+      else
+        {
+          return FALSE;
+        }
+
+    }
+  else
+    {
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+font_style_from_term (CRTerm     *term,
+                     PangoStyle *style)
+{
+  if (term->type != TERM_IDENT)
+    return FALSE;
+
+  /* FIXME: handle INHERIT */
+
+  if (strcmp (term->content.str->stryng->str, "normal") == 0)
+    *style = PANGO_STYLE_NORMAL;
+  else if (strcmp (term->content.str->stryng->str, "oblique") == 0)
+    *style = PANGO_STYLE_OBLIQUE;
+  else if (strcmp (term->content.str->stryng->str, "italic") == 0)
+    *style = PANGO_STYLE_ITALIC;
+  else
+    return FALSE;
+
+  return TRUE;
+}
+
+static gboolean
+font_variant_from_term (CRTerm       *term,
+                        PangoVariant *variant)
+{
+  if (term->type != TERM_IDENT)
+    return FALSE;
+
+  /* FIXME: handle INHERIT */
+
+  if (strcmp (term->content.str->stryng->str, "normal") == 0)
+    *variant = PANGO_VARIANT_NORMAL;
+  else if (strcmp (term->content.str->stryng->str, "small-caps") == 0)
+    *variant = PANGO_VARIANT_SMALL_CAPS;
+  else
+    return FALSE;
+
+  return TRUE;
+}
+
+const PangoFontDescription *
+shell_theme_node_get_font (ShellThemeNode *node)
+{
+  PangoStyle font_style;
+  gboolean font_style_set = FALSE;
+  PangoVariant variant;
+  gboolean variant_set = FALSE;
+  PangoWeight weight;
+  gboolean weight_absolute;
+  gboolean weight_set = FALSE;
+  double parent_size;
+  double size = 0.; /* Suppress warning */
+  gboolean size_set = FALSE;
+  char *family = NULL;
+  int i;
+
+  if (node->font_desc)
+    return node->font_desc;
+
+  node->font_desc = pango_font_description_copy (get_parent_font (node));
+  parent_size = pango_font_description_get_size (node->font_desc);
+  if (!pango_font_description_get_size_is_absolute (node->font_desc))
+    {
+      double resolution = shell_theme_context_get_resolution (node->context);
+      parent_size *= (resolution / 72.);
+    }
+
+  ensure_properties (node);
+
+  for (i = 0; i < node->n_properties; i++)
+    {
+      CRDeclaration *decl = node->properties[i];
+
+      if (strcmp (decl->property->stryng->str, "font") == 0)
+        {
+          PangoStyle tmp_style = PANGO_STYLE_NORMAL;
+          PangoVariant tmp_variant = PANGO_VARIANT_NORMAL;
+          PangoWeight tmp_weight = PANGO_WEIGHT_NORMAL;
+          gboolean tmp_weight_absolute = TRUE;
+          double tmp_size;
+          CRTerm *term = decl->value;
+
+          /* A font specification starts with node/variant/weight
+           * in any order. Each is allowed to be specified only once,
+           * but we don't enforce that.
+           */
+          for (; term; term = term->next)
+            {
+              if (font_style_from_term (term, &tmp_style))
+                ;
+              else if (font_variant_from_term (term, &tmp_variant))
+                ;
+              else if (font_weight_from_term (term, &tmp_weight, &tmp_weight_absolute))
+                ;
+              else
+                break;
+            }
+
+          /* The size is mandatory */
+
+          if (term == NULL || term->type != TERM_NUMBER)
+            {
+              g_warning ("Size missing from font property");
+              continue;
+            }
+
+          tmp_size = parent_size;
+          if (!font_size_from_term (node, term, &tmp_size))
+            {
+              g_warning ("Couldn't parse size in font property");
+              continue;
+            }
+
+          term = term->next;
+
+          if (term != NULL && term->type && TERM_NUMBER && term->the_operator == DIVIDE)
+            {
+              /* Ignore line-height specification */
+              term = term->next;
+            }
+
+          /* the font family is mandatory - it is a comma-separated list of
+           * names.
+           */
+          if (!font_family_from_terms (term, &family))
+            {
+              g_warning ("Couldn't parse family in font property");
+              continue;
+            }
+
+          font_style = tmp_style;
+          font_style_set = TRUE;
+          weight = tmp_weight;
+          weight_absolute = tmp_weight_absolute;
+          weight_set = TRUE;
+          variant = tmp_variant;
+          variant_set = TRUE;
+
+          size = tmp_size;
+          size_set = TRUE;
+
+        }
+      else if (strcmp (decl->property->stryng->str, "font-family") == 0)
+        {
+          if (!font_family_from_terms (decl->value, &family))
+            {
+              g_warning ("Couldn't parse family in font property");
+              continue;
+            }
+        }
+      else if (strcmp (decl->property->stryng->str, "font-weight") == 0)
+        {
+          if (decl->value == NULL || decl->value->next != NULL)
+            continue;
+
+          if (font_weight_from_term (decl->value, &weight, &weight_absolute))
+            weight_set = TRUE;
+        }
+      else if (strcmp (decl->property->stryng->str, "font-style") == 0)
+        {
+          if (decl->value == NULL || decl->value->next != NULL)
+            continue;
+
+          if (font_style_from_term (decl->value, &font_style))
+            font_style_set = TRUE;
+        }
+      else if (strcmp (decl->property->stryng->str, "font-variant") == 0)
+        {
+          if (decl->value == NULL || decl->value->next != NULL)
+            continue;
+
+          if (font_variant_from_term (decl->value, &variant))
+            variant_set = TRUE;
+        }
+      else if (strcmp (decl->property->stryng->str, "font-size") == 0)
+        {
+          gdouble tmp_size;
+          if (decl->value == NULL || decl->value->next != NULL)
+            continue;
+
+          tmp_size = parent_size;
+          if (font_size_from_term (node, decl->value, &tmp_size))
+            {
+              size = tmp_size;
+              size_set = TRUE;
+            }
+        }
+    }
+
+  if (family)
+    pango_font_description_set_family (node->font_desc, family);
+
+  if (size_set)
+    pango_font_description_set_absolute_size (node->font_desc, size);
+
+  if (weight_set)
+    {
+      if (!weight_absolute)
+        {
+          /* bolder/lighter are supposed to switch between available styles, but with
+           * font substitution, that gets to be a pretty fuzzy concept. So we use
+           * a fixed step of 200. (The spec says 100, but that might not take us from
+           * normal to bold.
+           */
+
+          PangoWeight old_weight = pango_font_description_get_weight (node->font_desc);
+          if (weight == PANGO_WEIGHT_BOLD)
+            weight = old_weight + 200;
+          else
+            weight = old_weight - 200;
+
+          if (weight < 100)
+            weight = 100;
+          if (weight > 900)
+            weight = 900;
+        }
+
+      pango_font_description_set_weight (node->font_desc, weight);
+    }
+
+  if (font_style_set)
+    pango_font_description_set_style (node->font_desc, font_style);
+  if (variant_set)
+    pango_font_description_set_variant (node->font_desc, variant);
+
+  return node->font_desc;
+}
+
+/**
+ * shell_theme_node_get_background_theme_image:
+ * @node: a #ShellThemeNode
+ *
+ * Gets the value for the -shell-background-image style property
+ *
+ * Return value: (transfer none): the background image, or %NULL
+ *   if there is no background theme image.
+ */
+ShellThemeImage *
+shell_theme_node_get_background_theme_image (ShellThemeNode *node)
+{
+  int i;
+
+  if (node->background_theme_image_computed)
+    return node->background_theme_image;
+
+  node->background_theme_image = NULL;
+  node->background_theme_image_computed = TRUE;
+
+  ensure_properties (node);
+
+  for (i = node->n_properties - 1; i >= 0; i--)
+    {
+      CRDeclaration *decl = node->properties[i];
+
+      if (strcmp (decl->property->stryng->str, "-shell-background-image") == 0)
+        {
+          CRTerm *term = decl->value;
+          int lengths[4];
+          int n_lengths = 0;
+          int i;
+
+          const char *url;
+          int border_top;
+          int border_right;
+          int border_bottom;
+          int border_left;
+
+          char *filename;
+
+          /* First term must be the URL to the image */
+          if (term->type != TERM_URI)
+            goto next_property;
+
+          url = term->content.str->stryng->str;
+
+          term = term->next;
+
+          /* Followed by 0 to 4 lengths */
+          for (i = 0; i < 4; i++)
+            {
+              double value;
+
+              if (term == NULL)
+                break;
+
+              if (get_length_from_term (node, term, FALSE, &value) != VALUE_FOUND)
+                goto next_property;
+
+              lengths[n_lengths] = (int)(0.5 + value);
+              n_lengths++;
+
+              term = term->next;
+            }
+
+          switch (n_lengths)
+            {
+            case 0:
+              border_top = border_right = border_bottom = border_left = 0;
+              break;
+            case 1:
+              border_top = border_right = border_bottom = border_left = lengths[0];
+              break;
+            case 2:
+              border_top = border_bottom = lengths[0];
+              border_left = border_right = lengths[1];
+              break;
+            case 3:
+              border_top = lengths[0];
+              border_left = border_right = lengths[1];
+              border_bottom = lengths[2];
+              break;
+            case 4:
+            default:
+              border_top = lengths[0];
+              border_right = lengths[1];
+              border_bottom = lengths[2];
+              border_left = lengths[3];
+              break;
+            }
+
+          filename = _shell_theme_resolve_url (node->theme, decl->parent_statement->parent_sheet, url);
+          if (filename == NULL)
+            goto next_property;
+
+          node->background_theme_image = shell_theme_image_new (filename,
+                                                                border_top, border_right, border_bottom, border_left);
+
+          g_free (filename);
+
+          return node->background_theme_image;
+        }
+
+    next_property:
+      ;
+    }
+
+  return NULL;
+}
diff --git a/src/toolkit/shell-theme-node.h b/src/toolkit/shell-theme-node.h
new file mode 100644
index 0000000..b98b037
--- /dev/null
+++ b/src/toolkit/shell-theme-node.h
@@ -0,0 +1,115 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_THEME_NODE_H__
+#define __SHELL_THEME_NODE_H__
+
+#include <clutter/clutter.h>
+#include "shell-theme-image.h"
+
+G_BEGIN_DECLS
+
+typedef struct _ShellTheme          ShellTheme;
+typedef struct _ShellThemeContext   ShellThemeContext;
+
+typedef struct _ShellThemeNode      ShellThemeNode;
+typedef struct _ShellThemeNodeClass ShellThemeNodeClass;
+
+#define SHELL_TYPE_THEME_NODE              (shell_theme_node_get_type ())
+#define SHELL_THEME_NODE(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), SHELL_TYPE_THEME_NODE, ShellThemeNode))
+#define SHELL_THEME_NODE_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass),     SHELL_TYPE_THEME_NODE, ShellThemeNodeClass))
+#define SHELL_IS_THEME_NODE(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), SHELL_TYPE_THEME_NODE))
+#define SHELL_IS_THEME_NODE_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass),     SHELL_TYPE_THEME_NODE))
+#define SHELL_THEME_NODE_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj),     SHELL_TYPE_THEME_NODE, ShellThemeNodeClass))
+
+typedef enum {
+    SHELL_SIDE_LEFT,
+    SHELL_SIDE_RIGHT,
+    SHELL_SIDE_TOP,
+    SHELL_SIDE_BOTTOM
+} ShellSide;
+
+/* These are the CSS values; that doesn't mean we have to implement blink... */
+typedef enum {
+    SHELL_TEXT_DECORATION_UNDERLINE    = 1 << 0,
+    SHELL_TEXT_DECORATION_OVERLINE     = 1 << 1,
+    SHELL_TEXT_DECORATION_LINE_THROUGH = 1 << 2,
+    SHELL_TEXT_DECORATION_BLINK        = 1 << 3
+} ShellTextDecoration;
+
+GType shell_theme_node_get_type (void) G_GNUC_CONST;
+
+/* An element_type of G_TYPE_NONE means this style was created for the stage
+ * actor and matches a selector element name of 'stage'
+ */
+ShellThemeNode *shell_theme_node_new (ShellThemeContext *context,
+                                      ShellThemeNode    *parent_node,   /* can be null */
+                                      ShellTheme        *theme,         /* can be null */
+                                      GType              element_type,
+                                      const char        *element_id,
+                                      const char        *element_class,
+                                      const char        *pseudo_class);
+
+ShellThemeNode *shell_theme_node_get_parent (ShellThemeNode *node);
+
+ShellTheme *shell_theme_node_get_theme (ShellThemeNode *node);
+
+GType       shell_theme_node_get_element_type  (ShellThemeNode *node);
+const char *shell_theme_node_get_element_id    (ShellThemeNode *node);
+const char *shell_theme_node_get_element_class (ShellThemeNode *node);
+const char *shell_theme_node_get_pseudo_class  (ShellThemeNode *node);
+
+/* Generic getters ... these are not cached so are less efficient. The other
+ * reason for adding the more specific version is that we can handle the
+ * details of the actual CSS rules, which can be complicated, especially
+ * for fonts
+ */
+gboolean shell_theme_node_get_color (ShellThemeNode *node,
+                                     const char     *property_name,
+                                     gboolean        inherit,
+                                     ClutterColor   *color);
+
+gboolean shell_theme_node_get_double (ShellThemeNode *node,
+                                      const char     *property_name,
+                                      gboolean        inherit,
+                                      double         *value);
+
+/* The length here is already resolved to pixels
+ */
+gboolean shell_theme_node_get_length (ShellThemeNode *node,
+                                      const char     *property_name,
+                                      gboolean        inherit,
+                                      gdouble        *length);
+
+/* Specific getters for particular properties: cached
+ */
+void shell_theme_node_get_background_color (ShellThemeNode *node,
+                                            ClutterColor   *color);
+void shell_theme_node_get_foreground_color (ShellThemeNode *node,
+                                            ClutterColor   *color);
+
+const char *shell_theme_node_get_background_image (ShellThemeNode *node);
+
+double  shell_theme_node_get_border_width (ShellThemeNode *node,
+                                           ShellSide       side);
+void    shell_theme_node_get_border_color (ShellThemeNode *node,
+                                           ShellSide       side,
+                                           ClutterColor   *color);
+double  shell_theme_node_get_padding      (ShellThemeNode *node,
+                                           ShellSide       side);
+
+ShellTextDecoration shell_theme_node_get_text_decoration (ShellThemeNode *node);
+
+/* Font rule processing is pretty complicated, so we just hardcode it
+ * under the standard font/font-family/font-size/etc names. This means
+ * you can't have multiple separate styled fonts for a single item,
+ * but that should be OK.
+ */
+const PangoFontDescription *shell_theme_node_get_font (ShellThemeNode *node);
+
+/* This is the getter for -shell-background-image, which is different from
+ * background-image in having provisions for unscaled borders.
+ */
+ShellThemeImage *shell_theme_node_get_background_theme_image (ShellThemeNode *node);
+
+G_END_DECLS
+
+#endif /* __SHELL_THEME_NODE_H__ */
diff --git a/src/toolkit/shell-theme-private.h b/src/toolkit/shell-theme-private.h
new file mode 100644
index 0000000..214e94d
--- /dev/null
+++ b/src/toolkit/shell-theme-private.h
@@ -0,0 +1,22 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_THEME_PRIVATE_H__
+#define __SHELL_THEME_PRIVATE_H__
+
+#include <libcroco/libcroco.h>
+#include "shell-theme.h"
+
+G_BEGIN_DECLS
+
+void _shell_theme_get_matched_properties (ShellTheme       *theme,
+                                          ShellThemeNode   *node,
+                                          CRDeclaration  ***properties,
+                                          int              *n_properties);
+
+/* Resolve an URL from the stylesheet to a filename */
+char *_shell_theme_resolve_url (ShellTheme   *theme,
+                                CRStyleSheet *base_stylesheet,
+                                const char   *url);
+
+G_END_DECLS
+
+#endif /* __SHELL_THEME_PRIVATE_H__ */
diff --git a/src/toolkit/shell-theme.c b/src/toolkit/shell-theme.c
new file mode 100644
index 0000000..b64e15d
--- /dev/null
+++ b/src/toolkit/shell-theme.c
@@ -0,0 +1,1032 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* This file started as a cut-and-paste of cr-sel-eng.c from libcroco.
+ *
+ * In moving it to hippo-canvas:
+ * - Reformatted and otherwise edited to match our coding style
+ * - Switched from handling xmlNode to handling HippoStyle
+ * - Simplified by removing things that we don't need or that don't
+ *   make sense in our context.
+ * - The code to get a list of matching properties works quite differently;
+ *   we order things in priority order, but we don't actually try to
+ *   coelesce properties with the same name.
+ *
+ * In moving it to gnome-shell:
+ *  - Renamed again to ShellTheme
+ *  - Reformatted to match the gnome-shell coding style
+ *  - Removed notion of "theme engine" from hippo-canvas
+ *  - pseudo-class matching changed from link enum to strings
+ *  - Some code simplication
+ */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 
+ * General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Copyright (C) 2003-2004 Dodji Seketeli.  All Rights Reserved.
+ */
+
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "shell-theme-node.h"
+#include "shell-theme-private.h"
+
+static GObject *shell_theme_constructor (GType                  type,
+                                         guint                  n_construct_properties,
+                                         GObjectConstructParam *construct_properties);
+
+static void shell_theme_dispose      (GObject      *object);
+static void shell_theme_finalize     (GObject      *object);
+static void shell_theme_set_property (GObject      *object,
+                                      guint         prop_id,
+                                      const GValue *value,
+                                      GParamSpec   *pspec);
+static void shell_theme_get_property (GObject      *object,
+                                      guint         prop_id,
+                                      GValue       *value,
+                                      GParamSpec   *pspec);
+
+#if 0
+enum
+{
+  LAST_SIGNAL
+};
+
+static int signals[LAST_SIGNAL];
+#endif
+
+struct _ShellTheme
+{
+  GObject parent;
+
+  char *application_stylesheet;
+  char *default_stylesheet;
+  char *theme_stylesheet;
+
+  GHashTable *stylesheets_by_filename;
+  GHashTable *filenames_by_stylesheet;
+
+  CRCascade *cascade;
+};
+
+struct _ShellThemeClass
+{
+  GObjectClass parent_class;
+};
+
+enum
+{
+  PROP_0,
+  PROP_APPLICATION_STYLESHEET,
+  PROP_THEME_STYLESHEET,
+  PROP_DEFAULT_STYLESHEET
+};
+
+G_DEFINE_TYPE (ShellTheme, shell_theme, G_TYPE_OBJECT)
+
+/* Quick strcmp.  Test only for == 0 or != 0, not < 0 or > 0.  */
+#define strqcmp(str,lit,lit_len) \
+  (strlen (str) != (lit_len) || memcmp (str, lit, lit_len))
+
+static void shell_theme_init (ShellTheme * theme)
+{
+  theme->stylesheets_by_filename = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                          (GDestroyNotify)g_free, (GDestroyNotify)cr_stylesheet_unref);
+  theme->filenames_by_stylesheet = g_hash_table_new (g_direct_hash, g_direct_equal);
+}
+
+static void
+shell_theme_class_init (ShellThemeClass * klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructor = shell_theme_constructor;
+  object_class->dispose = shell_theme_dispose;
+  object_class->finalize = shell_theme_finalize;
+  object_class->set_property = shell_theme_set_property;
+  object_class->get_property = shell_theme_get_property;
+
+  /**
+   * ShellTheme:application-stylesheet
+   *
+   * The highest priority stylesheet, representing application-specific
+   * styling; this is associated with the CSS "author" stylesheet.
+   */
+  g_object_class_install_property (object_class,
+				   PROP_APPLICATION_STYLESHEET,
+				   g_param_spec_string ("application-stylesheet",
+                                                        "Application Stylesheet",
+                                                        "Stylesheet with application-specific styling",
+                                                        NULL,
+                                                        G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+
+  /**
+   * ShellTheme:theme-stylesheet
+   *
+   * The second priority stylesheet, representing theme-specific styling;
+   * this is associated with the CSS "user" stylesheet.
+   */
+  g_object_class_install_property (object_class,
+				   PROP_THEME_STYLESHEET,
+				   g_param_spec_string ("theme-stylesheet",
+							"Theme Stylesheet",
+							"Stylesheet with theme-specific styling",
+							NULL,
+							G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+
+  /**
+   * ShellTheme:default-stylesheet
+   *
+   * The lowest priority stylesheet, representing global default
+   * styling; this is associated with the CSS "user agent" stylesheet.
+   */
+  g_object_class_install_property (object_class,
+				   PROP_DEFAULT_STYLESHEET,
+				   g_param_spec_string ("default-stylesheet",
+							"Default Stylesheet",
+							"Stylesheet with global default styling",
+							NULL,
+							G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+
+}
+
+static CRStyleSheet *
+parse_stylesheet (const char *filename)
+{
+  enum CRStatus status;
+  CRStyleSheet *stylesheet;
+
+  if (filename == NULL)
+    return NULL;
+
+  status = cr_om_parser_simply_parse_file ((const guchar *) filename,
+					   CR_UTF_8,
+                                           &stylesheet);
+
+  if (status != CR_OK)
+    {
+      g_warning ("Error parsing stylesheet '%s'", filename);
+      return NULL;
+    }
+
+  return stylesheet;
+}
+
+static void
+insert_stylesheet (ShellTheme   *theme,
+		   const char   *filename,
+                   CRStyleSheet *stylesheet)
+{
+  char *filename_copy;
+
+  if (stylesheet == NULL)
+    return;
+
+  filename_copy = g_strdup(filename);
+  cr_stylesheet_ref (stylesheet);
+
+  g_hash_table_insert (theme->stylesheets_by_filename, filename_copy, stylesheet);
+  g_hash_table_insert (theme->filenames_by_stylesheet, stylesheet, filename_copy);
+}
+
+static GObject *
+shell_theme_constructor (GType                  type,
+			 guint                  n_construct_properties,
+			 GObjectConstructParam *construct_properties)
+{
+  GObject *object;
+  ShellTheme *theme;
+  CRStyleSheet *application_stylesheet;
+  CRStyleSheet *theme_stylesheet;
+  CRStyleSheet *default_stylesheet;
+
+  object = (*G_OBJECT_CLASS (shell_theme_parent_class)->constructor) (type,
+								      n_construct_properties,
+								      construct_properties);
+  theme = SHELL_THEME (object);
+
+  application_stylesheet = parse_stylesheet (theme->application_stylesheet);
+  theme_stylesheet = parse_stylesheet (theme->theme_stylesheet);
+  default_stylesheet = parse_stylesheet (theme->default_stylesheet);
+
+  theme->cascade = cr_cascade_new (application_stylesheet,
+				   theme_stylesheet,
+                                   default_stylesheet);
+
+  if (theme->cascade == NULL)
+    g_error ("Out of memory when creating cascade object");
+
+  insert_stylesheet (theme, theme->application_stylesheet, application_stylesheet);
+  insert_stylesheet (theme, theme->theme_stylesheet, theme_stylesheet);
+  insert_stylesheet (theme, theme->default_stylesheet, default_stylesheet);
+
+  return object;
+}
+
+static void
+shell_theme_dispose (GObject * object)
+{
+  /* ShellTheme *theme = SHELL_THEME(object); */
+
+  G_OBJECT_CLASS (shell_theme_parent_class)->dispose (object);
+}
+
+static void
+shell_theme_finalize (GObject * object)
+{
+  ShellTheme *theme = SHELL_THEME (object);
+
+  g_hash_table_destroy (theme->stylesheets_by_filename);
+  g_hash_table_destroy (theme->filenames_by_stylesheet);
+
+  g_free (theme->application_stylesheet);
+  g_free (theme->theme_stylesheet);
+  g_free (theme->default_stylesheet);
+
+  if (theme->cascade)
+    {
+      cr_cascade_unref (theme->cascade);
+      theme->cascade = NULL;
+    }
+
+  G_OBJECT_CLASS (shell_theme_parent_class)->finalize (object);
+}
+
+static void
+shell_theme_set_property (GObject      *object,
+			  guint         prop_id,
+			  const GValue *value,
+                          GParamSpec   *pspec)
+{
+  ShellTheme *theme = SHELL_THEME (object);
+
+  switch (prop_id)
+    {
+    case PROP_APPLICATION_STYLESHEET:
+      {
+	const char *path = g_value_get_string (value);
+
+	if (path != theme->application_stylesheet)
+	  {
+	    g_free (theme->application_stylesheet);
+	    theme->application_stylesheet = g_strdup (path);
+	  }
+
+	break;
+      }
+    case PROP_THEME_STYLESHEET:
+      {
+	const char *path = g_value_get_string (value);
+
+	if (path != theme->theme_stylesheet)
+	  {
+	    g_free (theme->theme_stylesheet);
+	    theme->theme_stylesheet = g_strdup (path);
+	  }
+
+	break;
+      }
+    case PROP_DEFAULT_STYLESHEET:
+      {
+	const char *path = g_value_get_string (value);
+
+	if (path != theme->default_stylesheet)
+	  {
+	    g_free (theme->default_stylesheet);
+	    theme->default_stylesheet = g_strdup (path);
+	  }
+
+	break;
+      }
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+shell_theme_get_property (GObject    *object,
+			  guint       prop_id,
+                          GValue     *value,
+                          GParamSpec *pspec)
+{
+  ShellTheme *theme = SHELL_THEME (object);
+
+  switch (prop_id)
+    {
+    case PROP_APPLICATION_STYLESHEET:
+      g_value_set_string (value, theme->application_stylesheet);
+      break;
+    case PROP_THEME_STYLESHEET:
+      g_value_set_string (value, theme->theme_stylesheet);
+      break;
+    case PROP_DEFAULT_STYLESHEET:
+      g_value_set_string (value, theme->default_stylesheet);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+/**
+ * shell_theme_new:
+ * @application_stylesheet: The highest priority stylesheet, representing application-specific
+ *   styling; this is associated with the CSS "author" stylesheet, may be %NULL
+ * @theme_stylesheet: The second priority stylesheet, representing theme-specific styling ;
+ *   this is associated with the CSS "user" stylesheet, may be %NULL
+ * @default_stylesheet: The lowest priority stylesheet, representing global default styling;
+ *   this is associated with the CSS "user agent" stylesheet, may be %NULL
+ *
+ * Return value: the newly created theme object
+ **/
+ShellTheme *
+shell_theme_new (const char       *application_stylesheet,
+		 const char       *theme_stylesheet,
+                 const char       *default_stylesheet)
+{
+  ShellTheme *theme = g_object_new (SHELL_TYPE_THEME,
+				    "application-stylesheet", application_stylesheet,
+				    "theme-stylesheet", theme_stylesheet,
+				    "default-stylesheet", default_stylesheet,
+				    NULL);
+
+  return theme;
+}
+
+static gboolean
+string_in_list (GString    *stryng,
+                const char *list)
+{
+  const char *cur;
+
+  for (cur = list; *cur;)
+    {
+      while (*cur && cr_utils_is_white_space (*cur))
+        cur++;
+
+      if (strncmp (cur, stryng->str, stryng->len) == 0)
+        {
+          cur +=  stryng->len;
+          if ((!*cur) || cr_utils_is_white_space (*cur))
+            return TRUE;
+        }
+
+      /*  skip to next whitespace character  */
+      while (*cur && !cr_utils_is_white_space (*cur))
+        cur++;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+pseudo_class_add_sel_matches_style (ShellTheme      *a_this,
+				    CRAdditionalSel *a_add_sel,
+				    ShellThemeNode  *a_node)
+{
+  const char *node_pseudo_class;
+
+  g_return_val_if_fail (a_this
+			&& a_add_sel
+			&& a_add_sel->content.pseudo
+			&& a_add_sel->content.pseudo->name
+			&& a_add_sel->content.pseudo->name->stryng
+			&& a_add_sel->content.pseudo->name->stryng->str
+			&& a_node, FALSE);
+
+  node_pseudo_class = shell_theme_node_get_pseudo_class (a_node);
+
+  if (node_pseudo_class == NULL)
+    return FALSE;
+
+  return string_in_list (a_add_sel->content.pseudo->name->stryng, node_pseudo_class);
+}
+
+/**
+ * param a_add_sel the class additional selector to consider.
+ * param a_node the style node to consider.
+ * return TRUE if the class additional selector matches
+ *the style node given in argument, FALSE otherwise.
+ */
+static gboolean
+class_add_sel_matches_style (CRAdditionalSel *a_add_sel,
+			     ShellThemeNode  *a_node)
+{
+  const char *element_class;
+
+  g_return_val_if_fail (a_add_sel
+			&& a_add_sel->type == CLASS_ADD_SELECTOR
+			&& a_add_sel->content.class_name
+			&& a_add_sel->content.class_name->stryng
+			&& a_add_sel->content.class_name->stryng->str
+			&& a_node, FALSE);
+
+  element_class = shell_theme_node_get_element_class (a_node);
+  if (element_class == NULL)
+    return FALSE;
+
+  return string_in_list (a_add_sel->content.class_name->stryng, element_class);
+}
+
+/**
+ * return TRUE if the additional attribute selector matches
+ *the current style node given in argument, FALSE otherwise.
+ * param a_add_sel the additional attribute selector to consider.
+ * param a_node the style node to consider.
+ */
+static gboolean
+id_add_sel_matches_style (CRAdditionalSel *a_add_sel,
+			  ShellThemeNode  *a_node)
+{
+  gboolean result = FALSE;
+  const char *id;
+
+  g_return_val_if_fail (a_add_sel
+			&& a_add_sel->type == ID_ADD_SELECTOR
+			&& a_add_sel->content.id_name
+			&& a_add_sel->content.id_name->stryng
+			&& a_add_sel->content.id_name->stryng->str
+			&& a_node, FALSE);
+  g_return_val_if_fail (a_add_sel
+			&& a_add_sel->type == ID_ADD_SELECTOR
+			&& a_node, FALSE);
+
+  id = shell_theme_node_get_element_id (a_node);
+
+  if (id != NULL)
+    {
+      if (!strqcmp (id, a_add_sel->content.id_name->stryng->str,
+		    a_add_sel->content.id_name->stryng->len))
+	{
+	  result = TRUE;
+	}
+    }
+
+  return result;
+}
+
+/**
+ *Evaluates if a given additional selector matches an style node.
+ * param a_add_sel the additional selector to consider.
+ * param a_node the style node to consider.
+ * return TRUE is a_add_sel matches a_node, FALSE otherwise.
+ */
+static gboolean
+additional_selector_matches_style (ShellTheme      *a_this,
+				   CRAdditionalSel *a_add_sel,
+				   ShellThemeNode  *a_node)
+{
+  CRAdditionalSel *cur_add_sel = NULL;
+
+  g_return_val_if_fail (a_add_sel, FALSE);
+
+  for (cur_add_sel = a_add_sel; cur_add_sel; cur_add_sel = cur_add_sel->next)
+    {
+      switch (cur_add_sel->type)
+        {
+        case NO_ADD_SELECTOR:
+	  return FALSE;
+        case CLASS_ADD_SELECTOR:
+	  if (!class_add_sel_matches_style (cur_add_sel, a_node))
+            return FALSE;
+          break;
+        case ID_ADD_SELECTOR:
+	  if (!id_add_sel_matches_style (cur_add_sel, a_node))
+            return FALSE;
+          break;
+        case ATTRIBUTE_ADD_SELECTOR:
+	  g_warning ("Attribute selectors not supported");
+	  return FALSE;
+        case  PSEUDO_CLASS_ADD_SELECTOR:
+	  if (!pseudo_class_add_sel_matches_style (a_this, cur_add_sel, a_node))
+            return FALSE;
+          break;
+        }
+    }
+
+  return TRUE;
+}
+
+/* This is a workaround for:
+ *  - Python encodes '.' as '+' in type names
+ *  - libcroco doesn't correctly parse the \+ escape in types
+ *  - it's far too ugly to write foo\2b\bar\2bWidget...
+ *
+ * So we consider an element name of foo-bar-Widget to match a type
+ * of foo+bar+Widget
+ */
+static gboolean
+element_name_matches_type_name (const char *element_name,
+				const char *type_name)
+{
+  const char *p = element_name;
+  const char *q = type_name;
+
+  while (*p || *q)
+    {
+      if (*p == '-')
+	{
+	  if (*q != '-' && *q != '+')
+	    return FALSE;
+	}
+      else
+	{
+	  if (*p != *q)
+	    return FALSE;
+	}
+
+      p++;
+      q++;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+element_name_matches_type (const char *element_name,
+                           GType       element_type)
+{
+  /* This is not efficient, but the number of selectors with an element_name
+   * is probably fairly small.
+   */
+  if (element_type == G_TYPE_NONE)
+    {
+      return strcmp (element_name, "stage") == 0;
+    }
+  else
+    {
+      while (TRUE)
+	{
+	  if (element_name_matches_type_name (element_name, g_type_name (element_type)))
+	    return TRUE;
+
+	  if (element_type == G_TYPE_OBJECT)
+	    return FALSE;
+
+	  element_type = g_type_parent (element_type);
+	}
+    }
+}
+
+/**
+ *Evaluate a selector (a simple selectors list) and says
+ *if it matches the style node given in parameter.
+ *The algorithm used here is the following:
+ *Walk the combinator separated list of simple selectors backward, starting
+ *from the end of the list. For each simple selector, looks if
+ *if matches the current style.
+ *
+ * param a_this the selection engine.
+ * param a_sel the simple selection list.
+ * param a_node the style node.
+ * param a_result out parameter. Set to true if the
+ *selector matches the style node, FALSE otherwise.
+ * param a_recurse if set to TRUE, the function will walk to
+ *the next simple selector (after the evaluation of the current one)
+ *and recursively evaluate it. Must be usually set to TRUE unless you
+ *know what you are doing.
+ */
+static enum CRStatus
+sel_matches_style_real (ShellTheme     *a_this,
+			CRSimpleSel    *a_sel,
+			ShellThemeNode *a_node,
+			gboolean       *a_result,
+			gboolean        a_eval_sel_list_from_end,
+                        gboolean        a_recurse)
+{
+  CRSimpleSel *cur_sel = NULL;
+  ShellThemeNode *cur_node = NULL;
+  GType cur_type;
+
+  *a_result = FALSE;
+
+  if (a_eval_sel_list_from_end)
+    {
+      /*go and get the last simple selector of the list */
+      for (cur_sel = a_sel; cur_sel && cur_sel->next; cur_sel = cur_sel->next)
+        ;
+    }
+  else
+    {
+      cur_sel = a_sel;
+    }
+
+  cur_node = a_node;
+  cur_type = shell_theme_node_get_element_type (cur_node);
+
+  while (cur_sel)
+    {
+      if (((cur_sel->type_mask & TYPE_SELECTOR)
+	   && (cur_sel->name
+	       && cur_sel->name->stryng
+	       && cur_sel->name->stryng->str)
+	   &&
+	   (element_name_matches_type (cur_sel->name->stryng->str, cur_type)))
+	  || (cur_sel->type_mask & UNIVERSAL_SELECTOR))
+	{
+	  /*
+	   *this simple selector
+	   *matches the current style node
+	   *Let's see if the preceding
+	   *simple selectors also match
+	   *their style node counterpart.
+	   */
+	  if (cur_sel->add_sel)
+	    {
+	      if (additional_selector_matches_style (a_this, cur_sel->add_sel, cur_node))
+                goto walk_a_step_in_expr;
+	      else
+                goto done;
+	    }
+	  else
+            goto walk_a_step_in_expr;
+	}
+      if (!(cur_sel->type_mask & TYPE_SELECTOR)
+	  && !(cur_sel->type_mask & UNIVERSAL_SELECTOR))
+	{
+	  if (!cur_sel->add_sel)
+            goto done;
+	  if (additional_selector_matches_style (a_this, cur_sel->add_sel, cur_node))
+            goto walk_a_step_in_expr;
+	  else
+            goto done;
+	}
+      else
+	{
+	  goto done;
+	}
+
+    walk_a_step_in_expr:
+      if (a_recurse == FALSE)
+	{
+	  *a_result = TRUE;
+	  goto done;
+	}
+
+      /*
+       *here, depending on the combinator of cur_sel
+       *choose the axis of the element tree traversal
+       *and walk one step in the element tree.
+       */
+      if (!cur_sel->prev)
+	break;
+
+      switch (cur_sel->combinator)
+	{
+	case NO_COMBINATOR:
+	  break;
+
+	case COMB_WS:		/*descendant selector */
+	  {
+	    ShellThemeNode *n = NULL;
+
+	    /*
+	     *walk the element tree upward looking for a parent
+	     *style that matches the preceding selector.
+	     */
+	    for (n = shell_theme_node_get_parent (a_node); n; n = shell_theme_node_get_parent (n))
+	      {
+		enum CRStatus status;
+		gboolean matches = FALSE;
+
+		status = sel_matches_style_real (a_this, cur_sel->prev, n, &matches, FALSE, TRUE);
+
+		if (status != CR_OK)
+		  goto done;
+
+		if (matches)
+		  {
+		    cur_node = n;
+		    cur_type = shell_theme_node_get_element_type (cur_node);
+		    break;
+		  }
+	      }
+
+	    if (!n)
+	      {
+		/*
+		 *didn't find any ancestor that matches
+		 *the previous simple selector.
+		 */
+		goto done;
+	      }
+	    /*
+	     *in this case, the preceding simple sel
+	     *will have been interpreted twice, which
+	     *is a cpu and mem waste ... I need to find
+	     *another way to do this. Anyway, this is
+	     *my first attempt to write this function and
+	     *I am a bit clueless.
+	     */
+	    break;
+	  }
+
+	case COMB_PLUS:
+	  g_warning ("+ combinators are not supported");
+	  goto done;
+
+	case COMB_GT:
+	  cur_node = shell_theme_node_get_parent (cur_node);
+	  if (!cur_node)
+	    goto done;
+	  cur_type = shell_theme_node_get_element_type (cur_node);
+	  break;
+
+	default:
+	  goto done;
+	}
+
+      cur_sel = cur_sel->prev;
+    }
+
+  /*
+   *if we reached this point, it means the selector matches
+   *the style node.
+   */
+  *a_result = TRUE;
+
+done:
+  return CR_OK;
+}
+
+static void
+add_matched_properties (ShellTheme     *a_this,
+			CRStyleSheet   *a_nodesheet,
+			ShellThemeNode *a_node,
+                        GPtrArray      *props)
+{
+  CRStatement *cur_stmt = NULL;
+  CRSelector *sel_list = NULL;
+  CRSelector *cur_sel = NULL;
+  gboolean matches = FALSE;
+  enum CRStatus status = CR_OK;
+
+  /*
+   *walk through the list of statements and,
+   *get the selectors list inside the statements that
+   *contain some, and try to match our style node in these
+   *selectors lists.
+   */
+  for (cur_stmt = a_nodesheet->statements; cur_stmt; cur_stmt = cur_stmt->next)
+    {
+      /*
+       *initialyze the selector list in which we will
+       *really perform the search.
+       */
+      sel_list = NULL;
+
+      /*
+       *get the the damn selector list in 
+       *which we have to look
+       */
+      switch (cur_stmt->type)
+	{
+	case RULESET_STMT:
+	  if (cur_stmt->kind.ruleset && cur_stmt->kind.ruleset->sel_list)
+	    {
+	      sel_list = cur_stmt->kind.ruleset->sel_list;
+	    }
+	  break;
+
+	case AT_MEDIA_RULE_STMT:
+	  if (cur_stmt->kind.media_rule
+	      && cur_stmt->kind.media_rule->rulesets
+	      && cur_stmt->kind.media_rule->rulesets->kind.ruleset
+	      && cur_stmt->kind.media_rule->rulesets->kind.ruleset->sel_list)
+	    {
+	      sel_list = cur_stmt->kind.media_rule->rulesets->kind.ruleset->sel_list;
+	    }
+	  break;
+
+	case AT_IMPORT_RULE_STMT:
+	  {
+	    CRAtImportRule *import_rule = cur_stmt->kind.import_rule;
+
+	    if (import_rule->sheet == NULL)
+	      {
+		char *filename = NULL;
+
+		if (import_rule->url->stryng && import_rule->url->stryng->str)
+		  filename = _shell_theme_resolve_url (a_this,
+						       a_nodesheet,
+						       import_rule->url->stryng->str);
+
+		if (filename)
+		  import_rule->sheet = parse_stylesheet (filename);
+
+		if (import_rule->sheet)
+		  {
+		    insert_stylesheet (a_this, filename, import_rule->sheet);
+		    /* refcount of stylesheets starts off at zero, so we don't need to unref! */
+		  }
+		else
+                  {
+                    /* Set a marker to avoid repeatedly trying to parse a non-existent or
+                     * broken stylesheet
+                     */
+                    import_rule->sheet = (CRStyleSheet *) - 1;
+                  }
+
+                if (filename)
+                  g_free (filename);
+              }
+
+            if (import_rule->sheet != (CRStyleSheet *) - 1)
+              {
+                add_matched_properties (a_this, import_rule->sheet,
+                                        a_node, props);
+              }
+          }
+          break;
+        default:
+          break;
+        }
+
+      if (!sel_list)
+        continue;
+
+      /*
+       *now, we have a comma separated selector list to look in.
+       *let's walk it and try to match the style node
+       *on each item of the list.
+       */
+      for (cur_sel = sel_list; cur_sel; cur_sel = cur_sel->next)
+        {
+          if (!cur_sel->simple_sel)
+            continue;
+
+          status = sel_matches_style_real (a_this, cur_sel->simple_sel, a_node, &matches, TRUE, TRUE);
+
+          if (status == CR_OK && matches)
+            {
+              CRDeclaration *cur_decl = NULL;
+
+              /* In order to sort the matching properties, we need to compute the
+               * specificity of the selector that actually matched this
+               * element. In a non-thread-safe fashion, we store it in the
+               * ruleset. (Fixing this would mean cut-and-pasting
+               * cr_simple_sel_compute_specificity(), and have no need for
+               * thread-safety anyways.)
+               *
+               * Once we've sorted the properties, the specificity no longer
+               * matters and it can be safely overriden.
+               */
+              cr_simple_sel_compute_specificity (cur_sel->simple_sel);
+
+              cur_stmt->specificity = cur_sel->simple_sel->specificity;
+
+              for (cur_decl = cur_stmt->kind.ruleset->decl_list; cur_decl; cur_decl = cur_decl->next)
+                g_ptr_array_add (props, cur_decl);
+            }
+        }
+    }
+}
+
+#define ORIGIN_AUTHOR_IMPORTANT (ORIGIN_AUTHOR + 1)
+#define ORIGIN_USER_IMPORTANT   (ORIGIN_AUTHOR + 2)
+
+static inline int
+get_origin (const CRDeclaration * decl)
+{
+  enum CRStyleOrigin origin = decl->parent_statement->parent_sheet->origin;
+
+  if (decl->important)
+    {
+      if (origin == ORIGIN_AUTHOR)
+	return ORIGIN_AUTHOR_IMPORTANT;
+      else if (origin == ORIGIN_USER)
+	return ORIGIN_USER_IMPORTANT;
+    }
+
+  return origin;
+}
+
+/* Order of comparison is so that higher priority statements compare after
+ * lower priority statements */
+static int
+compare_declarations (gconstpointer a,
+                      gconstpointer b)
+{
+  /* g_ptr_array_sort() is broooken */
+  CRDeclaration *decl_a = *(CRDeclaration **) a;
+  CRDeclaration *decl_b = *(CRDeclaration **) b;
+
+  int origin_a = get_origin (decl_a);
+  int origin_b = get_origin (decl_b);
+
+  if (origin_a != origin_b)
+    return origin_a - origin_b;
+
+  if (decl_a->parent_statement->specificity != decl_b->parent_statement->specificity)
+    return decl_a->parent_statement->specificity - decl_b->parent_statement->specificity;
+
+  return 0;
+}
+
+void
+_shell_theme_get_matched_properties (ShellTheme      *theme,
+				     ShellThemeNode  *node,
+				     CRDeclaration ***properties,
+				     int             *n_properties)
+{
+  enum CRStyleOrigin origin = 0;
+  CRStyleSheet *sheet = NULL;
+  GPtrArray *props = g_ptr_array_new ();
+
+  g_return_if_fail (SHELL_IS_THEME (theme));
+  g_return_if_fail (SHELL_IS_THEME_NODE (node));
+
+  for (origin = ORIGIN_UA; origin < NB_ORIGINS; origin++)
+    {
+      sheet = cr_cascade_get_sheet (theme->cascade, origin);
+      if (!sheet)
+	continue;
+
+      add_matched_properties (theme, sheet, node, props);
+    }
+
+  /* We count on a stable sort here so that later declarations come
+   * after earlier declarations */
+  g_ptr_array_sort (props, compare_declarations);
+
+  *n_properties = props->len;
+  *properties = (CRDeclaration **) g_ptr_array_free (props, FALSE);
+}
+
+/* Resolve an url from an url() reference in a stylesheet into an absolute
+ * local filename, if possible. The resolution here is distinctly lame and
+ * will fail on many examples.
+ */
+char *
+_shell_theme_resolve_url (ShellTheme   *theme,
+			  CRStyleSheet *base_stylesheet,
+                          const char   *url)
+{
+  const char *base_filename = NULL;
+  char *dirname;
+  char *filename;
+
+  /* Handle absolute file:/ URLs */
+  if (g_str_has_prefix (url, "file:") ||
+      g_str_has_prefix (url, "File:") ||
+      g_str_has_prefix (url, "FILE:"))
+    {
+      GError *error = NULL;
+      char *filename;
+
+      filename = g_filename_from_uri (url, NULL, &error);
+      if (filename == NULL)
+	{
+	  g_warning ("%s", error->message);
+	  g_error_free (error);
+	}
+
+      return NULL;
+    }
+
+  /* Guard against http:/ URLs */
+
+  if (g_str_has_prefix (url, "http:") ||
+      g_str_has_prefix (url, "Http:") ||
+      g_str_has_prefix (url, "HTTP:"))
+    {
+      g_warning ("Http URL '%s' in theme stylesheet is not supported", url);
+      return NULL;
+    }
+
+  /* Assume anything else is a relative URL, and "resolve" it
+   */
+  if (url[0] == '/')
+    return g_strdup (url);
+
+  base_filename = g_hash_table_lookup (theme->filenames_by_stylesheet, base_stylesheet);
+
+  if (base_filename == NULL)
+    {
+      g_warning ("Can't get base to resolve url '%s'", url);
+      return NULL;
+    }
+
+  dirname = g_path_get_dirname (base_filename);
+  filename = g_build_filename (dirname, url, NULL);
+  g_free (dirname);
+
+  return filename;
+}
diff --git a/src/toolkit/shell-theme.h b/src/toolkit/shell-theme.h
new file mode 100644
index 0000000..e6f2897
--- /dev/null
+++ b/src/toolkit/shell-theme.h
@@ -0,0 +1,28 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_THEME_H__
+#define __SHELL_THEME_H__
+
+#include <glib-object.h>
+
+#include "shell-theme-node.h"
+
+G_BEGIN_DECLS
+
+typedef struct _ShellThemeClass ShellThemeClass;
+
+#define SHELL_TYPE_THEME              (shell_theme_get_type ())
+#define SHELL_THEME(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), SHELL_TYPE_THEME, ShellTheme))
+#define SHELL_THEME_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_THEME, ShellThemeClass))
+#define SHELL_IS_THEME(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), SHELL_TYPE_THEME))
+#define SHELL_IS_THEME_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_THEME))
+#define SHELL_THEME_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_THEME, ShellThemeClass))
+
+GType  shell_theme_get_type (void) G_GNUC_CONST;
+
+ShellTheme *shell_theme_new (const char *application_stylesheet,
+                             const char *theme_stylesheet,
+                             const char *default_stylesheet);
+
+G_END_DECLS
+
+#endif /* __SHELL_THEME_H__ */
diff --git a/src/toolkit/test-theme.c b/src/toolkit/test-theme.c
new file mode 100644
index 0000000..f2bf3fc
--- /dev/null
+++ b/src/toolkit/test-theme.c
@@ -0,0 +1,292 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include <clutter/clutter.h>
+#include "shell-theme.h"
+#include "shell-theme-context.h"
+#include <math.h>
+#include <string.h>
+
+static ShellThemeNode *root;
+static ShellThemeNode *group1;
+static ShellThemeNode *text1;
+static ShellThemeNode *text2;
+static ShellThemeNode *group2;
+static ShellThemeNode *text3;
+static ShellThemeNode *text4;
+static ShellThemeNode *group3;
+static ShellThemeNode *cairo_texture;
+static gboolean fail;
+
+static const char *test;
+
+static void
+assert_font (ShellThemeNode *node,
+	     const char     *node_description,
+	     const char     *expected)
+{
+  char *value = pango_font_description_to_string (shell_theme_node_get_font (node));
+
+  if (strcmp (expected, value) != 0)
+    {
+      g_print ("%s: %s.font: expected: %s, got: %s\n",
+	       test, node_description, expected, value);
+      fail = TRUE;
+    }
+
+  g_free (value);
+}
+
+static char *
+text_decoration_to_string (ShellTextDecoration decoration)
+{
+  GString *result = g_string_new (NULL);
+
+  if (decoration & SHELL_TEXT_DECORATION_UNDERLINE)
+    g_string_append(result, " underline");
+  if (decoration & SHELL_TEXT_DECORATION_OVERLINE)
+    g_string_append(result, " overline");
+  if (decoration & SHELL_TEXT_DECORATION_LINE_THROUGH)
+    g_string_append(result, " line_through");
+  if (decoration & SHELL_TEXT_DECORATION_BLINK)
+    g_string_append(result, " blink");
+
+  if (result->len > 0)
+    g_string_erase (result, 0, 1);
+  else
+    g_string_append(result, "none");
+
+  return g_string_free (result, FALSE);
+}
+
+static void
+assert_text_decoration (ShellThemeNode     *node,
+			const char         *node_description,
+			ShellTextDecoration expected)
+{
+  ShellTextDecoration value = shell_theme_node_get_text_decoration (node);
+  if (expected != value)
+    {
+      char *es = text_decoration_to_string (expected);
+      char *vs = text_decoration_to_string (value);
+
+      g_print ("%s: %s.text-decoration: expected: %s, got: %s\n",
+	       test, node_description, es, vs);
+      fail = TRUE;
+
+      g_free (es);
+      g_free (vs);
+    }
+}
+
+static void
+assert_foreground_color (ShellThemeNode *node,
+			 const char     *node_description,
+			 guint32         expected)
+{
+  ClutterColor color;
+  shell_theme_node_get_foreground_color (node, &color);
+  guint32 value = clutter_color_to_pixel (&color);
+
+  if (expected != value)
+    {
+      g_print ("%s: %s.color: expected: #%08x, got: #%08x\n",
+	       test, node_description, expected, value);
+      fail = TRUE;
+    }
+}
+
+static void
+assert_background_color (ShellThemeNode *node,
+			 const char     *node_description,
+			 guint32         expected)
+{
+  ClutterColor color;
+  shell_theme_node_get_background_color (node, &color);
+  guint32 value = clutter_color_to_pixel (&color);
+
+  if (expected != value)
+    {
+      g_print ("%s: %s.background-color: expected: #%08x, got: #%08x\n",
+	       test, node_description, expected, value);
+      fail = TRUE;
+    }
+}
+
+static void
+assert_background_image (ShellThemeNode *node,
+			 const char     *node_description,
+			 const char     *expected)
+{
+  const char *value = shell_theme_node_get_background_image (node);
+  if (expected == NULL)
+    expected = "(null)";
+  if (value == NULL)
+    value = "(null)";
+
+  if (strcmp (expected, value) != 0)
+    {
+      g_print ("%s: %s.background-image: expected: %s, got: %s\n",
+	       test, node_description, expected, value);
+      fail = TRUE;
+    }
+}
+
+#define LENGTH_EPSILON 0.001
+
+static void
+assert_length (const char     *node_description,
+	       const char     *property_description,
+	       double          expected,
+	       double          value)
+{
+  if (fabs (expected - value) > LENGTH_EPSILON)
+    {
+      g_print ("%s %s.%s: expected: %3f, got: %3f\n",
+	       test, node_description, property_description, expected, value);
+      fail = TRUE;
+    }
+}
+
+static void
+test_defaults (void)
+{
+  test = "defaults";
+  /* font comes from context */
+  assert_font (root, "stage", "sans-serif 12");
+  /* black is the default foreground color */
+  assert_foreground_color (root, "stage", 0x00000ff);
+}
+
+static void
+test_lengths (void)
+{
+  test = "lengths";
+  /* 12pt == 16px at 96dpi */
+  assert_length ("group1", "padding-top", 16.,
+		 shell_theme_node_get_padding (group1, SHELL_SIDE_TOP));
+  /* 12px == 12px */
+  assert_length ("group1", "padding-right", 12.,
+		 shell_theme_node_get_padding (group1, SHELL_SIDE_RIGHT));
+  /* 2em == 32px (with a 12pt font) */
+  assert_length ("group1", "padding-bottom", 32.,
+		 shell_theme_node_get_padding (group1, SHELL_SIDE_BOTTOM));
+  /* 1in == 72pt == 96px, at 96dpi */
+  assert_length ("group1", "padding-left", 96.,
+		 shell_theme_node_get_padding (group1, SHELL_SIDE_LEFT));
+}
+
+static void
+test_classes (void)
+{
+  test = "classes";
+  /* .special-text class overrides size and style;
+   * the ClutterTexture.special-text selector doesn't match */
+  assert_font (text1, "text1", "sans-serif Italic 32px");
+}
+
+static void
+test_type_inheritance (void)
+{
+  test = "type_inheritance";
+  /* From ClutterTexture element selector */
+  assert_length ("cairoTexture", "padding-top", 10.,
+		 shell_theme_node_get_padding (cairo_texture, SHELL_SIDE_TOP));
+  /* From ClutterCairoTexture element selector */
+  assert_length ("cairoTexture", "padding-right", 20.,
+		 shell_theme_node_get_padding (cairo_texture, SHELL_SIDE_RIGHT));
+}
+
+static void
+test_adjacent_selector (void)
+{
+  test = "adjacent_selector";
+  /* #group1 > #text1 matches text1 */
+  assert_foreground_color (text1, "text1", 0x00ff00ff);
+  /* stage > #text2 doesn't match text2 */
+  assert_foreground_color (text2, "text2", 0x000000ff);
+}
+
+static void
+test_background (void)
+{
+  test = "background";
+  /* group1 has a background: shortcut property setting color and image */
+  assert_background_color (group1, "group1", 0xff0000ff);
+  assert_background_image (group1, "group1", "toolkit/some-background.png");
+  /* text1 inherits the background image but not the color */
+  assert_background_color (text1,  "text1",  0x00000000);
+  assert_background_image (text1,  "text1",  "toolkit/some-background.png");
+  /* text1 inherits inherits both, but then background: none overrides both */
+  assert_background_color (text2,  "text2",  0x00000000);
+  assert_background_image (text2,  "text2",  NULL);
+  /* background-image property */
+  assert_background_image (group2, "group2", "toolkit/other-background.png");
+}
+
+static void
+test_font (void)
+{
+  test = "font";
+  /* font specified with font: */
+  assert_font (group2, "group2", "serif Italic 12px");
+  /* text3 inherits and overrides individually properties */
+  assert_font (text3,  "text3",  "serif Bold Oblique Small-Caps 24px");
+}
+
+static void
+test_pseudo_class (void)
+{
+  test = "pseudo_class";
+  /* text4 has :visited and :hover pseudo-classes, so should pick up both of these */
+  assert_foreground_color (text4,   "text4",  0x888888ff);
+  assert_text_decoration  (text4,   "text4",  SHELL_TEXT_DECORATION_UNDERLINE);
+  /* :hover pseudo-class matches, but class doesn't match */
+  assert_text_decoration  (group3,  "group3", 0);
+}
+
+int
+main (int argc, char **argv)
+{
+  ShellTheme *theme;
+  ShellThemeContext *context;
+
+  clutter_init (&argc, &argv);
+
+  theme = shell_theme_new ("toolkit/test-theme.css",
+			   NULL, NULL);
+
+  context = shell_theme_context_new ();
+  shell_theme_context_set_theme (context, theme);
+  shell_theme_context_set_resolution (context, 96.);
+  shell_theme_context_set_font (context,
+				pango_font_description_from_string ("sans-serif 12"));
+
+  root = shell_theme_context_get_root_node (context);
+  group1 = shell_theme_node_new (context, root, NULL,
+				 CLUTTER_TYPE_GROUP, "group1", NULL, NULL);
+  text1 = shell_theme_node_new  (context, group1, NULL,
+				 CLUTTER_TYPE_TEXT, "text1", "special-text", NULL);
+  text2 = shell_theme_node_new  (context, group1, NULL,
+				 CLUTTER_TYPE_TEXT, "text2", NULL, NULL);
+  group2 = shell_theme_node_new (context, root, NULL,
+				 CLUTTER_TYPE_GROUP, "group2", NULL, NULL);
+  text3 = shell_theme_node_new  (context, group2, NULL,
+				 CLUTTER_TYPE_TEXT, "text3", NULL, NULL);
+  text4 = shell_theme_node_new  (context, group2, NULL,
+				 CLUTTER_TYPE_TEXT, "text4", NULL, "visited hover");
+  group3 = shell_theme_node_new (context, group2, NULL,
+				 CLUTTER_TYPE_GROUP, "group3", NULL, "hover");
+  cairo_texture = shell_theme_node_new (context, root, NULL,
+					CLUTTER_TYPE_CAIRO_TEXTURE, "cairoTexture", NULL, NULL);
+
+  test_defaults ();
+  test_lengths ();
+  test_classes ();
+  test_type_inheritance ();
+  test_adjacent_selector ();
+  test_background ();
+  test_font ();
+  test_pseudo_class ();
+
+  return fail ? 1 : 0;
+}
diff --git a/src/toolkit/test-theme.css b/src/toolkit/test-theme.css
new file mode 100644
index 0000000..5776e0f
--- /dev/null
+++ b/src/toolkit/test-theme.css
@@ -0,0 +1,68 @@
+stage {
+}
+
+#group1 {
+    padding: 12pt;
+    padding-right: 12px;
+    padding-bottom: 2em;
+    padding-left: 1in;
+
+    background: #ff0000 url('some-background.png');
+}
+
+#text1 {
+    background-image: inherit;
+}
+
+.special-text {
+    font-size: 24pt;
+    font-style: italic;
+}
+
+ClutterTexture.special-text {
+    font-weight: bold;
+}
+
+#text2 {
+    background: inherit;
+    background: none; /* also overrides the color */
+}
+
+#group2 {
+    font: italic 12px serif;
+}
+
+#text3 {
+    font-variant: small-caps;
+    font-weight: bold;
+    font-style: oblique;
+    font-size: 200%;
+}
+
+ClutterTexture {
+    padding: 10px;
+}
+
+ClutterCairoTexture {
+    padding-right: 20px;
+}
+
+#group1 > #text1 {
+    color: #00ff00;
+}
+
+stage > #text2 {
+    color: #ff0000;
+}
+
+#group2 {
+    background-image: url('other-background.png');
+}
+
+ClutterText:hover {
+    text-decoration: underline;
+}
+
+ClutterText:visited {
+    color: #888888;
+}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]