[gnome-shell/nbtk-introduction] Import stylesheet code from hippo-canvas
- From: Owen Taylor <otaylor src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [gnome-shell/nbtk-introduction] Import stylesheet code from hippo-canvas
- Date: Tue, 22 Sep 2009 18:43:15 +0000 (UTC)
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]