[gnome-shell] Import Mx core as ST



commit d291e568fde82c685c94781d559bc5552ac9bd0d
Author: Owen W. Taylor <otaylor fishsoup net>
Date:   Tue Sep 8 15:47:30 2009 -0400

    Import Mx core as ST
    
    Import the core MxWidget/MxBin and their dependencies; we use the
    namespace "St" (Shell Toolkit) because it is the same length as Mx
    so enabling easy sharing of code, but makes it clear that this is
    a friendly fork and not a literal import.
    
    Based on a patch by Colin Walters <walters verbum org>
    
    https://bugzilla.gnome.org/show_bug.cgi?id=591245

 configure.ac              |    1 +
 src/Makefile-st.am        |  102 ++++
 src/Makefile.am           |   37 +-
 src/st/st-bin.c           |  791 ++++++++++++++++++++++++++
 src/st/st-bin.h           |   92 +++
 src/st/st-enum-types.c.in |   30 +
 src/st/st-enum-types.h.in |   29 +
 src/st/st-marshal.list    |   12 +
 src/st/st-private.c       |  111 ++++
 src/st/st-private.h       |   57 ++
 src/st/st-stylable.c      |  849 ++++++++++++++++++++++++++++
 src/st/st-stylable.h      |  124 ++++
 src/st/st-style.c         |  780 ++++++++++++++++++++++++++
 src/st/st-style.h         |   94 ++++
 src/st/st-subtexture.c    |  575 +++++++++++++++++++
 src/st/st-subtexture.h    |   96 ++++
 src/st/st-texture-cache.c |  450 +++++++++++++++
 src/st/st-texture-cache.h |   95 ++++
 src/st/st-texture-frame.c |  620 +++++++++++++++++++++
 src/st/st-texture-frame.h |   93 +++
 src/st/st-tooltip.c       |  710 ++++++++++++++++++++++++
 src/st/st-tooltip.h       |   80 +++
 src/st/st-types.h         |   73 +++
 src/st/st-widget.c        | 1357 +++++++++++++++++++++++++++++++++++++++++++++
 src/st/st-widget.h        |  108 ++++
 25 files changed, 7360 insertions(+), 6 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index d9d2617..5c7efed 100644
--- a/configure.ac
+++ b/configure.ac
@@ -57,6 +57,7 @@ PKG_CHECK_MODULES(MUTTER_PLUGIN, gio-unix-2.0 gtk+-2.0 dbus-glib-1 mutter-plugin
                                  gnome-desktop-2.0 >= 2.26 libstartup-notification-1.0
                                  gobject-introspection-1.0 >= 0.6.5)
 PKG_CHECK_MODULES(TIDY, clutter-1.0)
+PKG_CHECK_MODULES(ST, clutter-1.0 gtk+-2.0 libccss-1 >= 0.3.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(TRAY, gtk+-2.0)
diff --git a/src/Makefile-st.am b/src/Makefile-st.am
new file mode 100644
index 0000000..24bc10c
--- /dev/null
+++ b/src/Makefile-st.am
@@ -0,0 +1,102 @@
+st_cflags =					\
+    -I$(top_srcdir)/src				\
+    -DPREFIX=\""$(prefix)"\"			\
+    -DLIBDIR=\""$(libdir)"\"			\
+    -DG_DISABLE_DEPRECATED			\
+    -DG_LOG_DOMAIN=\"St\"			\
+    -DST_COMPILATION				\
+    -DPACKAGE_DATA_DIR=\"$(pkgdatadir)\"	\
+    $(ST_CFLAGS)				\
+    $(NULL)
+
+st_built_sources =				\
+    st-enum-types.h				\
+    st-enum-types.c				\
+    st-marshal.h				\
+    st-marshal.c
+
+BUILT_SOURCES += $(st_built_sources)
+
+EXTRA_DIST +=					\
+	st/st-marshal.list			\
+	st/st-enum-types.h.in			\
+	st/st-enum-types.c.in
+
+CLEANFILES += stamp-st-marshal.h stamp-st-enum-types.h
+
+st-marshal.h: stamp-st-marshal.h
+	@true
+stamp-st-marshal.h: Makefile st/st-marshal.list
+	$(AM_V_GEN) $(GLIB_GENMARSHAL)					\
+		--prefix=_st_marshal					\
+		--header						\
+	$(srcdir)/st/st-marshal.list > $  tmp &&			\
+	(cmp -s $  tmp st-marshal.h || cp -f $  tmp st-marshal.h) &&	\
+	rm -f $  tmp &&							\
+	echo timestamp > $(@F)
+
+st-marshal.c: Makefile st/st-marshal.list
+	$(AM_V_GEN) (echo "#include \"st-marshal.h\"" ;	\
+	 $(GLIB_GENMARSHAL)				\
+		--prefix=_st_marshal			\
+		--body					\
+	 $(srcdir)/st/st-marshal.list ) > $  tmp &&	\
+	cp -f $  tmp st-marshal.c &&			\
+	rm -f $  tmp
+
+st-enum-types.h: stamp-st-enum-types.h Makefile
+	@true
+stamp-st-enum-types.h: $(source_h) st/st-enum-types.h.in
+	$(AM_V_GEN) ( cd $(srcdir) &&					\
+	  $(GLIB_MKENUMS)						\
+	    --template st/st-enum-types.h.in				\
+	  $(st_source_h) ) >> $  tmp &&					\
+	(cmp -s $  tmp st-enum-types.h || cp $  tmp st-enum-types.h) &&	\
+	rm -f $  tmp &&							\
+	echo timestamp > $(@F)
+
+st-enum-types.c: stamp-st-enum-types.h st/st-enum-types.c.in
+	$(AM_V_GEN) ( cd $(srcdir) &&		\
+	  $(GLIB_MKENUMS)			\
+	    --template st/st-enum-types.c.in	\
+	  $(st_source_h) ) >> $  tmp &&		\
+	cp $  tmp $@ &&				\
+	rm -f $  tmp
+
+# please, keep this sorted alphabetically
+st_source_h =					\
+    st/st-bin.h					\
+    st/st-private.h				\
+    st/st-stylable.h				\
+    st/st-style.h				\
+    st/st-subtexture.h				\
+    st/st-texture-cache.h			\
+    st/st-texture-frame.h			\
+    st/st-tooltip.h				\
+    st/st-types.h				\
+    st/st-widget.h				\
+    $(NULL)
+
+# please, keep this sorted alphabetically
+st_source_c =					\
+    st/st-bin.c					\
+    st/st-private.c				\
+    st/st-stylable.c				\
+    st/st-style.c				\
+    st/st-subtexture.c				\
+    st/st-texture-cache.c			\
+    st/st-texture-frame.c			\
+    st/st-tooltip.c				\
+    st/st-widget.c				\
+    $(NULL)
+
+noinst_LTLIBRARIES += libst-1.0.la
+
+libst_1_0_la_LIBADD = $(ST_LIBS)
+libst_1_0_la_SOURCES =				\
+    $(st_source_c)				\
+    $(st_source_h)				\
+    $(st_built_sources)				\
+    $(NULL)
+libst_1_0_la_CPPFLAGS = $(st_cflags)
+libst_1_0_la_LDFLAGS = $(LDADD)
diff --git a/src/Makefile.am b/src/Makefile.am
index fe81579..04255cb 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -24,6 +24,7 @@ EXTRA_DIST += gnome-shell.in
 
 include Makefile-big.am
 include Makefile-gdmuser.am
+include Makefile-st.am
 include Makefile-tray.am
 
 gnome_shell_cflags =				\
@@ -150,14 +151,15 @@ libgnome_shell_la_LIBADD =	\
 	$(MUTTER_PLUGIN_LIBS)	\
         $(LIBGNOMEUI_LIBS)      \
 	libbig-1.0.la		\
+	libst-1.0.la       	\
 	libgdmuser-1.0.la	\
 	libtray.la
 libgnome_shell_la_CPPFLAGS = $(gnome_shell_cflags)
 
 typelibdir = $(pkglibdir)
-typelib_DATA = Shell-0.1.typelib Big-1.0.typelib
+typelib_DATA = Shell-0.1.typelib Big-1.0.typelib St-1.0.typelib
 
-Shell-0.1.gir: $(mutter) $(G_IR_SCANNER) Big-1.0.gir libgnome-shell.la Makefile
+Shell-0.1.gir: $(mutter) $(G_IR_SCANNER) Big-1.0.gir St-1.0.gir libgnome-shell.la Makefile
 	$(AM_V_GEN) $(G_IR_SCANNER)			\
 		--namespace=Shell			\
 		--nsversion=0.1				\
@@ -167,6 +169,7 @@ Shell-0.1.gir: $(mutter) $(G_IR_SCANNER) Big-1.0.gir libgnome-shell.la Makefile
 		--libtool="$(LIBTOOL)"    \
 		--add-include-path=$(builddir)     \
 		--include=Big-1.0     \
+		--include=St-1.0     \
 		--program=mutter			\
 	        --program-arg=--mutter-plugins=$$(pwd)/libgnome-shell.la \
 		$(addprefix $(srcdir)/,$(libgnome_shell_la_gir_sources)) \
@@ -177,14 +180,14 @@ CLEANFILES += Shell-0.1.gir
 # The dependency on libgnome-shell.la here is because g-ir-compiler opens it
 # (not the fake library, since we've already done the rewriting)
 Shell-0.1.typelib: libgnome-shell.la Shell-0.1.gir Big-1.0.gir
-	$(AM_V_GEN) LD_LIBRARY_PATH=.$${LD_LIBRARY_PATH:+:$$LD_LIBRARY_PATH}	\
+	$(AM_V_GEN) \
 		$(G_IR_COMPILER)						\
 			--includedir=.						\
 			--includedir=$(MUTTER_LIB_DIR)/mutter/			\
 		Shell-0.1.gir -o $@
 CLEANFILES += Shell-0.1.typelib
 
-Big-1.0.gir: $(mutter) $(G_IR_SCANNER) libgnome-shell.la libbig-1.0.la $(srcdir)/big-enum-types.h Makefile
+Big-1.0.gir: $(mutter) $(G_IR_SCANNER) libgnome-shell.la libbig-1.0.la Makefile
 	$(AM_V_GEN) $(G_IR_SCANNER)			\
 		--namespace=Big				\
 		--nsversion=1.0				\
@@ -201,6 +204,28 @@ Big-1.0.gir: $(mutter) $(G_IR_SCANNER) libgnome-shell.la libbig-1.0.la $(srcdir)
 CLEANFILES += Big-1.0.gir
 
 Big-1.0.typelib: libbig-1.0.la Big-1.0.gir
-	$(AM_V_GEN) LD_LIBRARY_PATH=.$${LD_LIBRARY_PATH:+:$$LD_LIBRARY_PATH} \
-		$(G_IR_COMPILER) Big-1.0.gir -o $@
+	$(AM_V_GEN) $(G_IR_COMPILER) Big-1.0.gir -o $@
 CLEANFILES += Big-1.0.typelib
+
+St-1.0.gir: $(mutter) $(G_IR_SCANNER) libgnome-shell.la libst-1.0.la Makefile
+	$(AM_V_GEN) $(G_IR_SCANNER)						\
+	        --namespace=St							\
+	        --nsversion=1.0							\
+	        --include=Clutter-1.0						\
+		--add-include-path=$(builddir)     				\
+	        --libtool="$(LIBTOOL)"						\
+	        --program=mutter						\
+		--program-arg=--mutter-plugins=$$(pwd)/libgnome-shell.la	\
+	        -DST_COMPILATION						\
+	        $(addprefix $(srcdir)/,$(st_source_h))				\
+	        $(addprefix $(srcdir)/,$(st_source_c))				\
+	        $(srcdir)/st-enum-types.h					\
+	        $(ST_CFLAGS)							\
+	        -o $@
+CLEANFILES += St-1.0.gir
+
+St-1.0.typelib: St-1.0.gir
+	$(AM_V_GEN) $(G_IR_COMPILER)						\
+		$< -o $@
+
+CLEANFILES += St-1.0.typelib
diff --git a/src/st/st-bin.c b/src/st/st-bin.c
new file mode 100644
index 0000000..005ed6b
--- /dev/null
+++ b/src/st/st-bin.c
@@ -0,0 +1,791 @@
+/*
+ * st-bin.c: Basic container actor
+ *
+ * Copyright (c) 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Written by: Emmanuele Bassi <ebassi linux intel com>
+ *
+ */
+
+/**
+ * SECTION:st-bin
+ * @short_description: a simple container with one actor
+ *
+ * #StBin is a simple container capable of having only one
+ * #ClutterActor as a child.
+ *
+ * #StBin inherits from #StWidget, so it is fully themable.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <clutter/clutter.h>
+
+#include "st-bin.h"
+#include "st-enum-types.h"
+#include "st-private.h"
+#include "st-stylable.h"
+
+#define ST_BIN_GET_PRIVATE(obj)       (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_BIN, StBinPrivate))
+
+struct _StBinPrivate
+{
+  ClutterActor *child;
+
+  StAlign       x_align;
+  StAlign       y_align;
+
+  guint         x_fill : 1;
+  guint         y_fill : 1;
+};
+
+enum
+{
+  PROP_0,
+
+  PROP_CHILD,
+  PROP_X_ALIGN,
+  PROP_Y_ALIGN,
+  PROP_X_FILL,
+  PROP_Y_FILL
+};
+
+static void clutter_container_iface_init (ClutterContainerIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (StBin, st_bin, ST_TYPE_WIDGET,
+                         G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTAINER,
+                                                clutter_container_iface_init));
+
+void
+_st_bin_get_align_factors (StBin   *bin,
+                           gdouble *x_align,
+                           gdouble *y_align)
+{
+  StBinPrivate *priv = bin->priv;
+  gdouble factor;
+
+  switch (priv->x_align)
+    {
+    case ST_ALIGN_START:
+      factor = 0.0;
+      break;
+
+    case ST_ALIGN_MIDDLE:
+      factor = 0.5;
+      break;
+
+    case ST_ALIGN_END:
+      factor = 1.0;
+      break;
+
+    default:
+      factor = 0.0;
+      break;
+    }
+
+  if (x_align)
+    *x_align = factor;
+
+  switch (priv->y_align)
+    {
+    case ST_ALIGN_START:
+      factor = 0.0;
+      break;
+
+    case ST_ALIGN_MIDDLE:
+      factor = 0.5;
+      break;
+
+    case ST_ALIGN_END:
+      factor = 1.0;
+      break;
+
+    default:
+      factor = 0.0;
+      break;
+    }
+
+  if (y_align)
+    *y_align = factor;
+}
+
+static void
+st_bin_add (ClutterContainer *container,
+            ClutterActor     *actor)
+{
+  st_bin_set_child (ST_BIN (container), actor);
+}
+
+static void
+st_bin_remove (ClutterContainer *container,
+               ClutterActor     *actor)
+{
+  StBinPrivate *priv = ST_BIN (container)->priv;
+
+  if (priv->child == actor)
+    st_bin_set_child (ST_BIN (container), NULL);
+}
+
+static void
+st_bin_foreach (ClutterContainer *container,
+                ClutterCallback   callback,
+                gpointer          user_data)
+{
+  StBinPrivate *priv = ST_BIN (container)->priv;
+
+  if (priv->child)
+    callback (priv->child, user_data);
+}
+
+static void
+clutter_container_iface_init (ClutterContainerIface *iface)
+{
+  iface->add = st_bin_add;
+  iface->remove = st_bin_remove;
+  iface->foreach = st_bin_foreach;
+}
+
+static void
+st_bin_paint (ClutterActor *self)
+{
+  StBinPrivate *priv = ST_BIN (self)->priv;
+
+  /* allow StWidget to paint the background */
+  CLUTTER_ACTOR_CLASS (st_bin_parent_class)->paint (self);
+
+  /* the pain our child */
+  if (priv->child)
+    clutter_actor_paint (priv->child);
+}
+
+static void
+st_bin_pick (ClutterActor       *self,
+             const ClutterColor *pick_color)
+{
+  StBinPrivate *priv = ST_BIN (self)->priv;
+
+  /* get the default pick implementation */
+  CLUTTER_ACTOR_CLASS (st_bin_parent_class)->pick (self, pick_color);
+
+  if (priv->child)
+    clutter_actor_paint (priv->child);
+}
+
+static void
+st_bin_allocate (ClutterActor          *self,
+                 const ClutterActorBox *box,
+                 ClutterAllocationFlags flags)
+{
+  StBinPrivate *priv = ST_BIN (self)->priv;
+
+  CLUTTER_ACTOR_CLASS (st_bin_parent_class)->allocate (self, box,
+                                                       flags);
+
+  if (priv->child)
+    {
+      gfloat natural_width, natural_height;
+      gfloat min_width, min_height;
+      gfloat child_width, child_height;
+      gfloat available_width, available_height;
+      ClutterRequestMode request;
+      ClutterActorBox allocation = { 0, };
+      StPadding padding = { 0, };
+      gdouble x_align, y_align;
+
+      _st_bin_get_align_factors (ST_BIN (self), &x_align, &y_align);
+
+      st_widget_get_padding (ST_WIDGET (self), &padding);
+
+      available_width  = box->x2 - box->x1
+                         - padding.left - padding.right;
+      available_height = box->y2 - box->y1
+                         - padding.top - padding.bottom;
+
+      if (available_width < 0)
+        available_width = 0;
+
+      if (available_height < 0)
+        available_height = 0;
+
+      if (priv->x_fill)
+        {
+          allocation.x1 = (int) padding.left;
+          allocation.x2 = (int)(allocation.x1 + available_width);
+        }
+
+      if (priv->y_fill)
+        {
+          allocation.y1 = (int) padding.top;
+          allocation.y2 = (int)(allocation.y1 + available_height);
+        }
+
+      /* if we are filling horizontally and vertically then we're done */
+      if (priv->x_fill && priv->y_fill)
+        {
+          clutter_actor_allocate (priv->child, &allocation, flags);
+          return;
+        }
+
+      request = CLUTTER_REQUEST_HEIGHT_FOR_WIDTH;
+      g_object_get (G_OBJECT (priv->child), "request-mode", &request, NULL);
+
+      if (request == CLUTTER_REQUEST_HEIGHT_FOR_WIDTH)
+        {
+          clutter_actor_get_preferred_width (priv->child, available_height,
+                                             &min_width,
+                                             &natural_width);
+
+          child_width = CLAMP (natural_width, min_width, available_width);
+
+          clutter_actor_get_preferred_height (priv->child, child_width,
+                                              &min_height,
+                                              &natural_height);
+
+          child_height = CLAMP (natural_height, min_height, available_height);
+        }
+      else
+        {
+          clutter_actor_get_preferred_height (priv->child, available_width,
+                                              &min_height,
+                                              &natural_height);
+
+          child_height = CLAMP (natural_height, min_height, available_height);
+
+          clutter_actor_get_preferred_width (priv->child, child_height,
+                                             &min_width,
+                                             &natural_width);
+
+          child_width = CLAMP (natural_width, min_width, available_width);
+        }
+
+      if (!priv->x_fill)
+        {
+          allocation.x1 = (int)((available_width - child_width) * x_align
+                                + padding.left);
+          allocation.x2 = allocation.x1 + child_width;
+        }
+
+      if (!priv->y_fill)
+        {
+          allocation.y1 = (int)((available_height - child_height) * y_align
+                                + padding.top);
+          allocation.y2 = allocation.y1 + child_height;
+        }
+
+      clutter_actor_allocate (priv->child, &allocation, flags);
+    }
+}
+
+static void
+st_bin_get_preferred_width (ClutterActor *self,
+                            gfloat        for_height,
+                            gfloat       *min_width_p,
+                            gfloat       *natural_width_p)
+{
+  StBinPrivate *priv = ST_BIN (self)->priv;
+  gfloat min_width, natural_width;
+  StPadding padding = { 0, };
+
+  st_widget_get_padding (ST_WIDGET (self), &padding);
+
+  min_width = natural_width = padding.left + padding.right;
+
+  if (priv->child == NULL)
+    {
+      if (min_width_p)
+        *min_width_p = min_width;
+
+      if (natural_width_p)
+        *natural_width_p = natural_width;
+    }
+  else
+    {
+      clutter_actor_get_preferred_width (priv->child, for_height,
+                                         min_width_p,
+                                         natural_width_p);
+
+      if (min_width_p)
+        *min_width_p += min_width;
+
+      if (natural_width_p)
+        *natural_width_p += natural_width;
+    }
+}
+
+static void
+st_bin_get_preferred_height (ClutterActor *self,
+                             gfloat        for_width,
+                             gfloat       *min_height_p,
+                             gfloat       *natural_height_p)
+{
+  StBinPrivate *priv = ST_BIN (self)->priv;
+  gfloat min_height, natural_height;
+  StPadding padding = { 0, };
+
+  st_widget_get_padding (ST_WIDGET (self), &padding);
+
+  min_height = natural_height = padding.top + padding.bottom;
+
+  if (priv->child == NULL)
+    {
+      if (min_height_p)
+        *min_height_p = min_height;
+
+      if (natural_height_p)
+        *natural_height_p = natural_height;
+    }
+  else
+    {
+      clutter_actor_get_preferred_height (priv->child, for_width,
+                                          min_height_p,
+                                          natural_height_p);
+
+      if (min_height_p)
+        *min_height_p += min_height;
+
+      if (natural_height_p)
+        *natural_height_p += natural_height;
+    }
+}
+
+static void
+st_bin_dispose (GObject *gobject)
+{
+  StBinPrivate *priv = ST_BIN (gobject)->priv;
+
+  if (priv->child)
+    {
+      clutter_actor_unparent (priv->child);
+      priv->child = NULL;
+    }
+
+  G_OBJECT_CLASS (st_bin_parent_class)->dispose (gobject);
+}
+
+static void
+st_bin_set_property (GObject      *gobject,
+                     guint         prop_id,
+                     const GValue *value,
+                     GParamSpec   *pspec)
+{
+  StBin *bin = ST_BIN (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_CHILD:
+      st_bin_set_child (bin, g_value_get_object (value));
+      break;
+
+    case PROP_X_ALIGN:
+      st_bin_set_alignment (bin,
+                            g_value_get_enum (value),
+                            bin->priv->y_align);
+      break;
+
+    case PROP_Y_ALIGN:
+      st_bin_set_alignment (bin,
+                            bin->priv->x_align,
+                            g_value_get_enum (value));
+      break;
+
+    case PROP_X_FILL:
+      st_bin_set_fill (bin,
+                       g_value_get_boolean (value),
+                       bin->priv->y_fill);
+      break;
+
+    case PROP_Y_FILL:
+      st_bin_set_fill (bin,
+                       bin->priv->y_fill,
+                       g_value_get_boolean (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+    }
+}
+
+static void
+st_bin_get_property (GObject    *gobject,
+                     guint       prop_id,
+                     GValue     *value,
+                     GParamSpec *pspec)
+{
+  StBinPrivate *priv = ST_BIN (gobject)->priv;
+
+  switch (prop_id)
+    {
+    case PROP_CHILD:
+      g_value_set_object (value, priv->child);
+      break;
+
+    case PROP_X_FILL:
+      g_value_set_boolean (value, priv->x_fill);
+      break;
+
+    case PROP_Y_FILL:
+      g_value_set_boolean (value, priv->y_fill);
+      break;
+
+    case PROP_X_ALIGN:
+      g_value_set_enum (value, priv->x_align);
+      break;
+
+    case PROP_Y_ALIGN:
+      g_value_set_enum (value, priv->y_align);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+    }
+}
+
+static void
+st_bin_class_init (StBinClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+  GParamSpec *pspec;
+
+  g_type_class_add_private (klass, sizeof (StBinPrivate));
+
+  gobject_class->set_property = st_bin_set_property;
+  gobject_class->get_property = st_bin_get_property;
+  gobject_class->dispose = st_bin_dispose;
+
+  actor_class->get_preferred_width = st_bin_get_preferred_width;
+  actor_class->get_preferred_height = st_bin_get_preferred_height;
+  actor_class->allocate = st_bin_allocate;
+  actor_class->paint = st_bin_paint;
+  actor_class->pick = st_bin_pick;
+
+  /**
+   * StBin:child:
+   *
+   * The child #ClutterActor of the #StBin container.
+   */
+  pspec = g_param_spec_object ("child",
+                               "Child",
+                               "The child of the Bin",
+                               CLUTTER_TYPE_ACTOR,
+                               ST_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_CHILD, pspec);
+
+  /**
+   * StBin:x-align:
+   *
+   * The horizontal alignment of the #StBin child.
+   */
+  pspec = g_param_spec_enum ("x-align",
+                             "X Align",
+                             "The horizontal alignment",
+                             ST_TYPE_ALIGN,
+                             ST_ALIGN_MIDDLE,
+                             ST_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_X_ALIGN, pspec);
+
+  /**
+   * StBin:y-align:
+   *
+   * The vertical alignment of the #StBin child.
+   */
+  pspec = g_param_spec_enum ("y-align",
+                             "Y Align",
+                             "The vertical alignment",
+                             ST_TYPE_ALIGN,
+                             ST_ALIGN_MIDDLE,
+                             ST_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_Y_ALIGN, pspec);
+
+  /**
+   * StBin:x-fill:
+   *
+   * Whether the child should fill the horizontal allocation
+   */
+  pspec = g_param_spec_boolean ("x-fill",
+                                "X Fill",
+                                "Whether the child should fill the "
+                                "horizontal allocation",
+                                FALSE,
+                                ST_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_X_FILL, pspec);
+
+  /**
+   * StBin:y-fill:
+   *
+   * Whether the child should fill the vertical allocation
+   */
+  pspec = g_param_spec_boolean ("y-fill",
+                                "Y Fill",
+                                "Whether the child should fill the "
+                                "vertical allocation",
+                                FALSE,
+                                ST_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_Y_FILL, pspec);
+}
+
+static void
+st_bin_init (StBin *bin)
+{
+  bin->priv = ST_BIN_GET_PRIVATE (bin);
+
+  bin->priv->x_align = ST_ALIGN_MIDDLE;
+  bin->priv->y_align = ST_ALIGN_MIDDLE;
+}
+
+/**
+ * st_bin_new:
+ *
+ * Creates a new #StBin, a simple container for one child.
+ *
+ * Return value: the newly created #StBin actor
+ */
+StWidget *
+st_bin_new (void)
+{
+  return g_object_new (ST_TYPE_BIN, NULL);
+}
+
+/**
+ * st_bin_set_child:
+ * @bin: a #StBin
+ * @child: a #ClutterActor, or %NULL
+ *
+ * Sets @child as the child of @bin.
+ *
+ * If @bin already has a child, the previous child is removed.
+ */
+void
+st_bin_set_child (StBin        *bin,
+                  ClutterActor *child)
+{
+  StBinPrivate *priv;
+
+  g_return_if_fail (ST_IS_BIN (bin));
+  g_return_if_fail (child == NULL || CLUTTER_IS_ACTOR (child));
+
+  priv = bin->priv;
+
+  if (priv->child == child)
+    return;
+
+  if (priv->child)
+    {
+      ClutterActor *old_child = priv->child;
+
+      g_object_ref (old_child);
+
+      priv->child = NULL;
+      clutter_actor_unparent (old_child);
+
+      g_signal_emit_by_name (bin, "actor-removed", old_child);
+
+      g_object_unref (old_child);
+    }
+
+  if (child)
+    {
+      priv->child = child;
+      clutter_actor_set_parent (child, CLUTTER_ACTOR (bin));
+
+      g_signal_emit_by_name (bin, "actor-added", priv->child);
+    }
+
+  clutter_actor_queue_relayout (CLUTTER_ACTOR (bin));
+
+  g_object_notify (G_OBJECT (bin), "child");
+}
+
+/**
+ * st_bin_get_child:
+ * @bin: a #StBin
+ *
+ * Retrieves a pointer to the child of @bin.
+ *
+ * Return value: a #ClutterActor, or %NULL
+ */
+ClutterActor *
+st_bin_get_child (StBin *bin)
+{
+  g_return_val_if_fail (ST_IS_BIN (bin), NULL);
+
+  return bin->priv->child;
+}
+
+/**
+ * st_bin_set_alignment:
+ * @bin: a #StBin
+ * @x_align: horizontal alignment
+ * @y_align: vertical alignment
+ *
+ * Sets the horizontal and vertical alignment of the child
+ * inside a #StBin.
+ */
+void
+st_bin_set_alignment (StBin  *bin,
+                      StAlign x_align,
+                      StAlign y_align)
+{
+  StBinPrivate *priv;
+  gboolean changed = FALSE;
+
+  g_return_if_fail (ST_IS_BIN (bin));
+
+  priv = bin->priv;
+
+  g_object_freeze_notify (G_OBJECT (bin));
+
+  if (priv->x_align != x_align)
+    {
+      priv->x_align = x_align;
+      g_object_notify (G_OBJECT (bin), "x-align");
+      changed = TRUE;
+    }
+
+  if (priv->y_align != y_align)
+    {
+      priv->y_align = y_align;
+      g_object_notify (G_OBJECT (bin), "y-align");
+      changed = TRUE;
+    }
+
+  if (changed)
+    clutter_actor_queue_relayout (CLUTTER_ACTOR (bin));
+
+  g_object_thaw_notify (G_OBJECT (bin));
+}
+
+/**
+ * st_bin_get_alignment:
+ * @bin: a #StBin
+ * @x_align: return location for the horizontal alignment, or %NULL
+ * @y_align: return location for the vertical alignment, or %NULL
+ *
+ * Retrieves the horizontal and vertical alignment of the child
+ * inside a #StBin, as set by st_bin_set_alignment().
+ */
+void
+st_bin_get_alignment (StBin   *bin,
+                      StAlign *x_align,
+                      StAlign *y_align)
+{
+  StBinPrivate *priv;
+
+  g_return_if_fail (ST_IS_BIN (bin));
+
+  priv = bin->priv;
+
+  if (x_align)
+    *x_align = priv->x_align;
+
+  if (y_align)
+    *y_align = priv->y_align;
+}
+
+/**
+ * st_bin_set_fill:
+ * @bin: a #StBin
+ * @x_fill: %TRUE if the child should fill horizontally the @bin
+ * @y_fill: %TRUE if the child should fill vertically the @bin
+ *
+ * Sets whether the child of @bin should fill out the horizontal
+ * and/or vertical allocation of the parent
+ */
+void
+st_bin_set_fill (StBin   *bin,
+                 gboolean x_fill,
+                 gboolean y_fill)
+{
+  StBinPrivate *priv;
+  gboolean changed = FALSE;
+
+  g_return_if_fail (ST_IS_BIN (bin));
+
+  priv = bin->priv;
+
+  g_object_freeze_notify (G_OBJECT (bin));
+
+  if (priv->x_fill != x_fill)
+    {
+      priv->x_fill = x_fill;
+      changed = TRUE;
+
+      g_object_notify (G_OBJECT (bin), "x-fill");
+    }
+
+  if (priv->y_fill != y_fill)
+    {
+      priv->y_fill = y_fill;
+      changed = TRUE;
+
+      g_object_notify (G_OBJECT (bin), "y-fill");
+    }
+
+  if (changed)
+    clutter_actor_queue_relayout (CLUTTER_ACTOR (bin));
+
+  g_object_thaw_notify (G_OBJECT (bin));
+}
+
+/**
+ * st_bin_get_fill:
+ * @bin: a #StBin
+ * @x_fill: (out): return location for the horizontal fill, or %NULL
+ * @y_fill: (out): return location for the vertical fill, or %NULL
+ *
+ * Retrieves the horizontal and vertical fill settings
+ */
+void
+st_bin_get_fill (StBin    *bin,
+                 gboolean *x_fill,
+                 gboolean *y_fill)
+{
+  g_return_if_fail (ST_IS_BIN (bin));
+
+  if (x_fill)
+    *x_fill = bin->priv->x_fill;
+
+  if (y_fill)
+    *y_fill = bin->priv->y_fill;
+}
+
+static gpointer
+st_padding_copy (gpointer data)
+{
+  return g_slice_dup (StPadding, data);
+}
+
+static void
+st_padding_free (gpointer data)
+{
+  if (G_LIKELY (data))
+    g_slice_free (StPadding, data);
+}
+
+GType
+st_padding_get_type (void)
+{
+  static GType our_type = 0;
+
+  if (G_UNLIKELY (our_type == 0))
+    our_type = g_boxed_type_register_static (I_("StPadding"),
+                                             st_padding_copy,
+                                             st_padding_free);
+
+  return our_type;
+}
diff --git a/src/st/st-bin.h b/src/st/st-bin.h
new file mode 100644
index 0000000..68fed60
--- /dev/null
+++ b/src/st/st-bin.h
@@ -0,0 +1,92 @@
+/*
+ * st-bin.h: Basic container actor
+ *
+ * Copyright 2009, 2008 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ * Boston, MA 02111-1307, USA.
+ *
+ * Written by: Emmanuele Bassi <ebassi linux intel com>
+ *
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_BIN_H__
+#define __ST_BIN_H__
+
+#include <st/st-types.h>
+#include <st/st-widget.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_BIN                   (st_bin_get_type ())
+#define ST_BIN(obj)                   (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_BIN, StBin))
+#define ST_IS_BIN(obj)                (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ST_TYPE_BIN))
+#define ST_BIN_CLASS(klass)           (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_BIN, StBinClass))
+#define ST_IS_BIN_CLASS(klass)        (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_BIN))
+#define ST_BIN_GET_CLASS(obj)         (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_BIN, StBinClass))
+
+typedef struct _StBin                 StBin;
+typedef struct _StBinPrivate          StBinPrivate;
+typedef struct _StBinClass            StBinClass;
+
+/**
+ * StBin:
+ *
+ * The #StBin struct contains only private data
+ */
+struct _StBin
+{
+  /*< private >*/
+  StWidget parent_instance;
+
+  StBinPrivate *priv;
+};
+
+/**
+ * StBinClass:
+ *
+ * The #StBinClass struct contains only private data
+ */
+struct _StBinClass
+{
+  /*< private >*/
+  StWidgetClass parent_class;
+};
+
+GType st_bin_get_type (void) G_GNUC_CONST;
+
+StWidget   *  st_bin_new           (void);
+void          st_bin_set_child     (StBin        *bin,
+                                    ClutterActor *child);
+ClutterActor *st_bin_get_child     (StBin        *bin);
+void          st_bin_set_alignment (StBin        *bin,
+                                    StAlign       x_align,
+                                    StAlign       y_align);
+void          st_bin_get_alignment (StBin        *bin,
+                                    StAlign      *x_align,
+                                    StAlign      *y_align);
+void          st_bin_set_fill      (StBin        *bin,
+                                    gboolean      x_fill,
+                                    gboolean      y_fill);
+void          st_bin_get_fill      (StBin        *bin,
+                                    gboolean     *x_fill,
+                                    gboolean     *y_fill);
+
+G_END_DECLS
+
+#endif /* __ST_BIN_H__ */
diff --git a/src/st/st-enum-types.c.in b/src/st/st-enum-types.c.in
new file mode 100644
index 0000000..738ac27
--- /dev/null
+++ b/src/st/st-enum-types.c.in
@@ -0,0 +1,30 @@
+/*** BEGIN file-header ***/
+#include "st-enum-types.h"
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* enumerations from "@filename@" */
+#include "@filename@"
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GType
+ enum_name@_get_type(void) {
+  static GType enum_type_id = 0;
+  if (G_UNLIKELY (!enum_type_id))
+    {
+      static const G Type@Value values[] = {
+/*** END value-header ***/
+
+/*** BEGIN value-production ***/
+        { @VALUENAME@, "@VALUENAME@", "@valuenick@" },
+/*** END value-production ***/
+
+/*** BEGIN value-tail ***/
+        { 0, NULL, NULL }
+      };
+      enum_type_id = g_ type@_register_static (g_intern_static_string ("@EnumName@"), values);
+    }
+  return enum_type_id;
+}
+/*** END value-tail ***/
diff --git a/src/st/st-enum-types.h.in b/src/st/st-enum-types.h.in
new file mode 100644
index 0000000..b3dd0e7
--- /dev/null
+++ b/src/st/st-enum-types.h.in
@@ -0,0 +1,29 @@
+/*** BEGIN file-header ***/
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_ENUM_TYPES_H__
+#define __ST_ENUM_TYPES_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* enumerations from "@filename@" */
+/*** END file-production ***/
+
+/*** BEGIN file-tail ***/
+G_END_DECLS
+
+#endif /* !__ST_ENUM_TYPES_H__ */
+/*** END file-tail ***/
+
+/*** BEGIN value-header ***/
+GType @enum_name _get_type (void) G_GNUC_CONST;
+#define ST_TYPE_ ENUMSHORT@ (@enum_name _get_type())
+
+/*** END value-header ***/
diff --git a/src/st/st-marshal.list b/src/st/st-marshal.list
new file mode 100644
index 0000000..9b3bed6
--- /dev/null
+++ b/src/st/st-marshal.list
@@ -0,0 +1,12 @@
+VOID:OBJECT
+VOID:VOID
+VOID:PARAM
+VOID:POINTER
+VOID:UINT
+VOID:UINT,UINT
+VOID:OBJECT,OBJECT
+VOID:STRING,OBJECT
+VOID:OBJECT,OBJECT,INT,INT
+VOID:OBJECT,FLOAT,FLOAT,INT,ENUM
+VOID:FLOAT,FLOAT,INT,ENUM
+VOID:FLOAT,FLOAT
diff --git a/src/st/st-private.c b/src/st/st-private.c
new file mode 100644
index 0000000..0a35e4e
--- /dev/null
+++ b/src/st/st-private.c
@@ -0,0 +1,111 @@
+#include "st-private.h"
+
+/* Utility function to modify a child allocation box with respect to the
+ * x/y-fill child properties. Expects childbox to contain the available
+ * allocation space.
+ */
+void
+_st_allocate_fill (ClutterActor    *child,
+                   ClutterActorBox *childbox,
+                   StAlign          x_alignment,
+                   StAlign          y_alignment,
+                   gboolean         x_fill,
+                   gboolean         y_fill)
+{
+  gfloat natural_width, natural_height;
+  gfloat min_width, min_height;
+  gfloat child_width, child_height;
+  gfloat available_width, available_height;
+  ClutterRequestMode request;
+  ClutterActorBox allocation = { 0, };
+  gdouble x_align, y_align;
+
+  if (x_alignment == ST_ALIGN_START)
+    x_align = 0.0;
+  else if (x_alignment == ST_ALIGN_MIDDLE)
+    x_align = 0.5;
+  else
+    x_align = 1.0;
+
+  if (y_alignment == ST_ALIGN_START)
+    y_align = 0.0;
+  else if (y_alignment == ST_ALIGN_MIDDLE)
+    y_align = 0.5;
+  else
+    y_align = 1.0;
+
+  available_width  = childbox->x2 - childbox->x1;
+  available_height = childbox->y2 - childbox->y1;
+
+  if (available_width < 0)
+    available_width = 0;
+
+  if (available_height < 0)
+    available_height = 0;
+
+  if (x_fill)
+    {
+      allocation.x1 = childbox->x1;
+      allocation.x2 = (int)(allocation.x1 + available_width);
+    }
+
+  if (y_fill)
+    {
+      allocation.y1 = childbox->y1;
+      allocation.y2 = (int)(allocation.y1 + available_height);
+    }
+
+  /* if we are filling horizontally and vertically then we're done */
+  if (x_fill && y_fill)
+    {
+      *childbox = allocation;
+      return;
+    }
+
+  request = CLUTTER_REQUEST_HEIGHT_FOR_WIDTH;
+  g_object_get (G_OBJECT (child), "request-mode", &request, NULL);
+
+  if (request == CLUTTER_REQUEST_HEIGHT_FOR_WIDTH)
+    {
+      clutter_actor_get_preferred_width (child, available_height,
+                                         &min_width,
+                                         &natural_width);
+
+      child_width = CLAMP (natural_width, min_width, available_width);
+
+      clutter_actor_get_preferred_height (child, child_width,
+                                          &min_height,
+                                          &natural_height);
+
+      child_height = CLAMP (natural_height, min_height, available_height);
+    }
+  else
+    {
+      clutter_actor_get_preferred_height (child, available_width,
+                                          &min_height,
+                                          &natural_height);
+
+      child_height = CLAMP (natural_height, min_height, available_height);
+
+      clutter_actor_get_preferred_width (child, child_height,
+                                         &min_width,
+                                         &natural_width);
+
+      child_width = CLAMP (natural_width, min_width, available_width);
+    }
+
+  if (!x_fill)
+    {
+      allocation.x1 = childbox->x1 + (int)((available_width - child_width) * x_align);
+      allocation.x2 = allocation.x1 + (int) child_width;
+    }
+
+  if (!y_fill)
+    {
+      allocation.y1 = childbox->y1 + (int)((available_height - child_height) * y_align);
+      allocation.y2 = allocation.y1 + (int) child_height;
+    }
+
+  *childbox = allocation;
+
+}
diff --git a/src/st/st-private.h b/src/st/st-private.h
new file mode 100644
index 0000000..1e99cf6
--- /dev/null
+++ b/src/st/st-private.h
@@ -0,0 +1,57 @@
+/*
+ * st-private.h: Private declarations
+ *
+ * Copyright 2007 OpenedHand
+ * Copyright 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __ST_PRIVATE_H__
+#define __ST_PRIVATE_H__
+
+#include <glib.h>
+#include "st-widget.h"
+#include "st-bin.h"
+
+G_BEGIN_DECLS
+
+#define I_(str)         (g_intern_static_string ((str)))
+
+#define ST_PARAM_READABLE     \
+        (G_PARAM_READABLE |     \
+         G_PARAM_STATIC_NICK | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB)
+
+#define ST_PARAM_READWRITE    \
+        (G_PARAM_READABLE | G_PARAM_WRITABLE | \
+         G_PARAM_STATIC_NICK | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB)
+
+G_END_DECLS
+
+ClutterActor *_st_widget_get_dnd_clone (StWidget *widget);
+
+void _st_bin_get_align_factors (StBin   *bin,
+                                gdouble *x_align,
+                                gdouble *y_align);
+
+void _st_allocate_fill (ClutterActor    *child,
+                        ClutterActorBox *childbox,
+                        StAlign          x_align,
+                        StAlign          y_align,
+                        gboolean         x_fill,
+                        gboolean         y_fill);
+
+#endif /* __ST_PRIVATE_H__ */
diff --git a/src/st/st-stylable.c b/src/st/st-stylable.c
new file mode 100644
index 0000000..1af8788
--- /dev/null
+++ b/src/st/st-stylable.c
@@ -0,0 +1,849 @@
+/*
+ * st-stylable.c: Interface for stylable objects
+ *
+ * Copyright 2008 Intel Corporation
+ * Copyright 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Written by: Emmanuele Bassi <ebassi openedhand com>
+ *             Thomas Wood <thomas linux intel com>
+ *
+ */
+
+/**
+ * SECTION:st-stylable
+ * @short_description: Interface for stylable objects
+ *
+ * Stylable objects are classes that can have "style properties", that is
+ * properties that can be changed by attaching a #StStyle to them.
+ *
+ * Objects can choose to subclass #StWidget, and thus inherit all the
+ * #StWidget style properties; or they can subclass #StWidget and
+ * reimplement the #StStylable interface to add new style properties
+ * specific for them (and their subclasses); or, finally, they can simply
+ * subclass #GObject and implement #StStylable to install new properties.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib-object.h>
+#include <gobject/gvaluecollector.h>
+#include <gobject/gobjectnotifyqueue.c>
+
+#include "st-marshal.h"
+#include "st-private.h"
+#include "st-stylable.h"
+
+enum
+{
+  STYLE_CHANGED,
+  STYLE_NOTIFY,
+  CHANGED,
+
+  LAST_SIGNAL
+};
+
+static GObjectNotifyContext property_notify_context = { 0, };
+
+static GParamSpecPool *style_property_spec_pool = NULL;
+
+static GQuark quark_real_owner         = 0;
+static GQuark quark_style              = 0;
+
+static guint stylable_signals[LAST_SIGNAL] = { 0, };
+
+static void
+st_stylable_notify_dispatcher (GObject     *gobject,
+                               guint        n_pspecs,
+                               GParamSpec **pspecs)
+{
+  guint i;
+
+  for (i = 0; i < n_pspecs; i++)
+    g_signal_emit (gobject, stylable_signals[STYLE_NOTIFY],
+                   g_quark_from_string (pspecs[i]->name),
+                   pspecs[i]);
+}
+
+static void
+st_stylable_base_finalize (gpointer g_iface)
+{
+  GList *list, *node;
+
+  list = g_param_spec_pool_list_owned (style_property_spec_pool,
+                                       G_TYPE_FROM_INTERFACE (g_iface));
+
+  for (node = list; node; node = node->next)
+    {
+      GParamSpec *pspec = node->data;
+
+      g_param_spec_pool_remove (style_property_spec_pool, pspec);
+      g_param_spec_unref (pspec);
+    }
+
+  g_list_free (list);
+}
+
+static void
+st_stylable_base_init (gpointer g_iface)
+{
+  static gboolean initialised = FALSE;
+
+  if (G_UNLIKELY (!initialised))
+    {
+      GType iface_type = G_TYPE_FROM_INTERFACE (g_iface);
+
+      initialised = TRUE;
+
+      quark_real_owner = g_quark_from_static_string ("st-stylable-real-owner-quark");
+      quark_style = g_quark_from_static_string ("st-stylable-style-quark");
+
+      style_property_spec_pool = g_param_spec_pool_new (FALSE);
+
+      property_notify_context.quark_notify_queue = g_quark_from_static_string ("StStylable-style-property-notify-queue");
+      property_notify_context.dispatcher = st_stylable_notify_dispatcher;
+
+      /**
+       * StStylable:style:
+       *
+       * The #StStyle attached to a stylable object.
+       */
+      g_object_interface_install_property (g_iface,
+                                           g_param_spec_object ("style",
+                                                                "Style",
+                                                                "A style object",
+                                                                ST_TYPE_STYLE,
+                                                                ST_PARAM_READWRITE));
+
+      /**
+       * StStylable::style-changed:
+       * @stylable: the #StStylable that received the signal
+       * @old_style: the previously set #StStyle for @stylable
+       *
+       * The ::style-changed signal is emitted each time one of the style
+       * properties have changed.
+       */
+      stylable_signals[STYLE_CHANGED] =
+        g_signal_new (I_("style-changed"),
+                      iface_type,
+                      G_SIGNAL_RUN_FIRST,
+                      G_STRUCT_OFFSET (StStylableIface, style_changed),
+                      NULL, NULL,
+                      _st_marshal_VOID__VOID,
+                      G_TYPE_NONE, 0);
+
+      /**
+       * StStylable::stylable-changed:
+       * @actor: the actor that received the signal
+       *
+       * The ::changed signal is emitted each time any of the properties of the
+       * stylable has changed.
+       */
+      stylable_signals[CHANGED] =
+        g_signal_new (I_("stylable-changed"),
+                      iface_type,
+                      G_SIGNAL_RUN_LAST,
+                      G_STRUCT_OFFSET (StStylableIface, stylable_changed),
+                      NULL, NULL,
+                      _st_marshal_VOID__VOID,
+                      G_TYPE_NONE, 0);
+
+      stylable_signals[STYLE_NOTIFY] =
+        g_signal_new (I_("style-notify"),
+                      iface_type,
+                      G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_DETAILED | G_SIGNAL_NO_HOOKS | G_SIGNAL_ACTION,
+                      G_STRUCT_OFFSET (StStylableIface, style_notify),
+                      NULL, NULL,
+                      _st_marshal_VOID__PARAM,
+                      G_TYPE_NONE, 1,
+                      G_TYPE_PARAM);
+    }
+}
+
+GType
+st_stylable_get_type (void)
+{
+  static GType our_type = 0;
+
+  if (G_UNLIKELY (our_type == 0))
+    {
+      GTypeInfo stylable_info = {
+        sizeof (StStylableIface),
+        st_stylable_base_init,
+        st_stylable_base_finalize
+      };
+
+      our_type = g_type_register_static (G_TYPE_INTERFACE,
+                                         I_("StStylable"),
+                                         &stylable_info, 0);
+    }
+
+  return our_type;
+}
+
+void
+st_stylable_freeze_notify (StStylable *stylable)
+{
+  g_return_if_fail (ST_IS_STYLABLE (stylable));
+
+  g_object_ref (stylable);
+  g_object_notify_queue_freeze (G_OBJECT (stylable), &property_notify_context);
+  g_object_unref (stylable);
+}
+
+void
+st_stylable_thaw_notify (StStylable *stylable)
+{
+  GObjectNotifyQueue *nqueue;
+
+  g_return_if_fail (ST_IS_STYLABLE (stylable));
+
+  g_object_ref (stylable);
+
+  nqueue = g_object_notify_queue_from_object (G_OBJECT (stylable),
+                                              &property_notify_context);
+
+  if (!nqueue || !nqueue->freeze_count)
+    g_warning ("%s: property-changed notification for %s(%p) is not frozen",
+               G_STRFUNC, G_OBJECT_TYPE_NAME (stylable), stylable);
+  else
+    g_object_notify_queue_thaw (G_OBJECT (stylable), nqueue);
+
+  g_object_unref (stylable);
+}
+
+void
+st_stylable_notify (StStylable  *stylable,
+                    const gchar *property_name)
+{
+  GParamSpec *pspec;
+
+  g_return_if_fail (ST_IS_STYLABLE (stylable));
+  g_return_if_fail (property_name != NULL);
+
+  g_object_ref (stylable);
+
+  pspec = g_param_spec_pool_lookup (style_property_spec_pool,
+                                    property_name,
+                                    G_OBJECT_TYPE (stylable),
+                                    TRUE);
+
+  if (!pspec)
+    g_warning ("%s: object class `%s' has no style property named `%s'",
+               G_STRFUNC,
+               G_OBJECT_TYPE_NAME (stylable),
+               property_name);
+  else
+    {
+      GObjectNotifyQueue *nqueue;
+
+      nqueue = g_object_notify_queue_freeze (G_OBJECT (stylable),
+                                             &property_notify_context);
+      g_object_notify_queue_add (G_OBJECT (stylable), nqueue, pspec);
+      g_object_notify_queue_thaw (G_OBJECT (stylable), nqueue);
+    }
+
+  g_object_unref (stylable);
+}
+
+/**
+ * st_stylable_iface_install_property:
+ * @iface: a #StStylableIface
+ * @owner_type: #GType of the style property owner
+ * @pspec: a #GParamSpec
+ *
+ * Installs a property for @owner_type using @pspec as the property
+ * description.
+ *
+ * This function should be used inside the #StStylableIface initialization
+ * function of a class, for instance:
+ *
+ * <informalexample><programlisting>
+ * G_DEFINE_TYPE_WITH_CODE (FooActor, foo_actor, CLUTTER_TYPE_ACTOR,
+ *                          G_IMPLEMENT_INTERFACE (ST_TYPE_STYLABLE,
+ *                                                 st_stylable_init));
+ * ...
+ * static void
+ * st_stylable_init (StStylableIface *iface)
+ * {
+ *   static gboolean is_initialized = FALSE;
+ *
+ *   if (!is_initialized)
+ *     {
+ *       ...
+ *       st_stylable_iface_install_property (stylable,
+ *                                             FOO_TYPE_ACTOR,
+ *                                             g_param_spec_int ("x-spacing",
+ *                                                               "X Spacing",
+ *                                                               "Horizontal spacing",
+ *                                                               -1, G_MAXINT,
+ *                                                               2,
+ *                                                               G_PARAM_READWRITE));
+ *       ...
+ *     }
+ * }
+ * </programlisting></informalexample>
+ */
+void
+st_stylable_iface_install_property (StStylableIface *iface,
+                                    GType            owner_type,
+                                    GParamSpec      *pspec)
+{
+  g_return_if_fail (ST_IS_STYLABLE_IFACE (iface));
+  g_return_if_fail (owner_type != G_TYPE_INVALID);
+  g_return_if_fail (G_IS_PARAM_SPEC (pspec));
+  g_return_if_fail (pspec->flags & G_PARAM_READABLE);
+  g_return_if_fail (!(pspec->flags & (G_PARAM_CONSTRUCT_ONLY | G_PARAM_CONSTRUCT
+                                      )));
+
+  if (g_param_spec_pool_lookup (style_property_spec_pool, pspec->name,
+                                owner_type,
+                                FALSE))
+    {
+      g_warning ("%s: class `%s' already contains a style property named `%s'",
+                 G_STRLOC,
+                 g_type_name (owner_type),
+                 pspec->name);
+      return;
+    }
+
+  g_param_spec_ref_sink (pspec);
+  g_param_spec_set_qdata_full (pspec, quark_real_owner,
+                               g_strdup (g_type_name (owner_type)),
+                               g_free);
+
+  g_param_spec_pool_insert (style_property_spec_pool,
+                            pspec,
+                            owner_type);
+}
+
+/**
+ * st_stylable_list_properties:
+ * @stylable: a #StStylable
+ * @n_props: return location for the number of properties, or %NULL
+ *
+ * Retrieves all the #GParamSpec<!-- -->s installed by @stylable.
+ *
+ * Return value: an array of #GParamSpec<!-- -->s. Free it with
+ *   g_free() when done.
+ */
+GParamSpec **
+st_stylable_list_properties (StStylable *stylable,
+                             guint      *n_props)
+{
+  GParamSpec **pspecs = NULL;
+  guint n;
+
+  g_return_val_if_fail (ST_IS_STYLABLE (stylable), NULL);
+
+  pspecs = g_param_spec_pool_list (style_property_spec_pool,
+                                   G_OBJECT_TYPE (stylable),
+                                   &n);
+  if (n_props)
+    *n_props = n;
+
+  return pspecs;
+}
+
+/**
+ * st_stylable_find_property:
+ * @stylable: a #StStylable
+ * @property_name: the name of the property to find
+ *
+ * Finds the #GParamSpec installed by @stylable for the property
+ * with @property_name.
+ *
+ * Return value: a #GParamSpec for the given property, or %NULL if
+ *   no property with that name was found
+ */
+GParamSpec *
+st_stylable_find_property (StStylable  *stylable,
+                           const gchar *property_name)
+{
+  g_return_val_if_fail (ST_IS_STYLABLE (stylable), NULL);
+  g_return_val_if_fail (property_name != NULL, NULL);
+
+  return g_param_spec_pool_lookup (style_property_spec_pool,
+                                   property_name,
+                                   G_OBJECT_TYPE (stylable),
+                                   TRUE);
+}
+
+static inline void
+st_stylable_get_property_internal (StStylable *stylable,
+                                   GParamSpec *pspec,
+                                   GValue     *value)
+{
+  StStyle *style;
+  GValue real_value = { 0, };
+
+  style = st_stylable_get_style (stylable);
+
+  if (!style)
+    {
+      g_value_reset (value);
+      return;
+    }
+
+  st_style_get_property (style, stylable, pspec, &real_value);
+
+  g_value_copy (&real_value, value);
+  g_value_unset (&real_value);
+
+}
+
+/**
+ * st_stylable_get_property:
+ * @stylable: a #StStylable
+ * @property_name: the name of the property
+ * @value: return location for an empty #GValue
+ *
+ * Retrieves the value of @property_name for @stylable, and puts it
+ * into @value.
+ */
+void
+st_stylable_get_property (StStylable  *stylable,
+                          const gchar *property_name,
+                          GValue      *value)
+{
+  GParamSpec *pspec;
+
+  g_return_if_fail (ST_IS_STYLABLE (stylable));
+  g_return_if_fail (property_name != NULL);
+  g_return_if_fail (value != NULL);
+
+  pspec = st_stylable_find_property (stylable, property_name);
+  if (!pspec)
+    {
+      g_warning ("Stylable class `%s' doesn't have a property named `%s'",
+                 g_type_name (G_OBJECT_TYPE (stylable)),
+                 property_name);
+      return;
+    }
+
+  if (!(pspec->flags & G_PARAM_READABLE))
+    {
+      g_warning ("Style property `%s' of class `%s' is not readable",
+                 pspec->name,
+                 g_type_name (G_OBJECT_TYPE (stylable)));
+      return;
+    }
+
+  if (G_VALUE_TYPE (value) != G_PARAM_SPEC_VALUE_TYPE (pspec))
+    {
+      g_warning ("Passed value is not of the requested type `%s' for "
+                 "the style property `%s' of class `%s'",
+                 g_type_name (G_PARAM_SPEC_VALUE_TYPE (pspec)),
+                 pspec->name,
+                 g_type_name (G_OBJECT_TYPE (stylable)));
+      return;
+    }
+
+  st_stylable_get_property_internal (stylable, pspec, value);
+}
+
+/**
+ * st_stylable_get:
+ * @stylable: a #StStylable
+ * @first_property_name: name of the first property to get
+ * @Varargs: return location for the first property, followed optionally
+ *   by more name/return location pairs, followed by %NULL
+ *
+ * Gets the style properties for @stylable.
+ *
+ * In general, a copy is made of the property contents and the called
+ * is responsible for freeing the memory in the appropriate manner for
+ * the property type.
+ *
+ * <example>
+ * <title>Using st_stylable_get(<!-- -->)</title>
+ * <para>An example of using st_stylable_get() to get the contents of
+ * two style properties - one of type #G_TYPE_INT and one of type
+ * #CLUTTER_TYPE_COLOR:</para>
+ * <programlisting>
+ *   gint x_spacing;
+ *   ClutterColor *bg_color;
+ *
+ *   st_stylable_get (stylable,
+ *                      "x-spacing", &amp;x_spacing,
+ *                      "bg-color", &amp;bg_color,
+ *                      NULL);
+ *
+ *   /<!-- -->* do something with x_spacing and bg_color *<!-- -->/
+ *
+ *   clutter_color_free (bg_color);
+ * </programlisting>
+ * </example>
+ */
+void
+st_stylable_get (StStylable  *stylable,
+                 const gchar *first_property_name,
+                 ...)
+{
+  StStyle *style;
+  va_list args;
+
+  g_return_if_fail (ST_IS_STYLABLE (stylable));
+  g_return_if_fail (first_property_name != NULL);
+
+  style = st_stylable_get_style (stylable);
+
+  va_start (args, first_property_name);
+  st_style_get_valist (style, stylable, first_property_name, args);
+  va_end (args);
+}
+
+/**
+ * st_stylable_get_default_value:
+ * @stylable: a #StStylable
+ * @property_name: name of the property to query
+ * @value_out: return location for the default value
+ *
+ * Query @stylable for the default value of property @property_name and
+ * fill @value_out with the result.
+ *
+ * Returns: %TRUE if property @property_name exists and the default value has
+ * been returned.
+ */
+gboolean
+st_stylable_get_default_value (StStylable  *stylable,
+                               const gchar *property_name,
+                               GValue      *value_out)
+{
+  GParamSpec *pspec;
+
+  pspec = st_stylable_find_property (stylable, property_name);
+  if (!pspec)
+    {
+      g_warning ("%s: no style property named `%s' found for class `%s'",
+                 G_STRLOC,
+                 property_name,
+                 g_type_name (G_OBJECT_TYPE (stylable)));
+      return FALSE;
+    }
+
+  if (!(pspec->flags & G_PARAM_READABLE))
+    {
+      g_warning ("Style property `%s' of class `%s' is not readable",
+                 pspec->name,
+                 g_type_name (G_OBJECT_TYPE (stylable)));
+      return FALSE;
+    }
+
+  g_value_init (value_out, G_PARAM_SPEC_VALUE_TYPE (pspec));
+  g_param_value_set_default (pspec, value_out);
+  return TRUE;
+}
+
+/**
+ * st_stylable_get_style:
+ * @stylable: a #StStylable
+ *
+ * Retrieves the #StStyle used by @stylable. This function does not
+ * alter the reference count of the returned object.
+ *
+ * Return value: a #StStyle
+ */
+StStyle *
+st_stylable_get_style (StStylable *stylable)
+{
+  StStylableIface *iface;
+
+  g_return_val_if_fail (ST_IS_STYLABLE (stylable), NULL);
+
+  iface = ST_STYLABLE_GET_IFACE (stylable);
+  if (iface->get_style)
+    return iface->get_style (stylable);
+
+  return g_object_get_data (G_OBJECT (stylable), "st-stylable-style");
+}
+
+/**
+ * st_stylable_set_style:
+ * @stylable: a #StStylable
+ * @style: a #StStyle
+ *
+ * Sets @style as the new #StStyle to be used by @stylable.
+ *
+ * The #StStylable will take ownership of the passed #StStyle.
+ *
+ * After the #StStle has been set, the StStylable::style-set signal
+ * will be emitted.
+ */
+void
+st_stylable_set_style (StStylable *stylable,
+                       StStyle    *style)
+{
+  StStylableIface *iface;
+  StStyle *old_style;
+
+  g_return_if_fail (ST_IS_STYLABLE (stylable));
+  g_return_if_fail (ST_IS_STYLE (style));
+
+  iface = ST_STYLABLE_GET_IFACE (stylable);
+
+  old_style = st_stylable_get_style (stylable);
+  g_object_ref (old_style);
+
+  if (iface->set_style)
+    iface->set_style (stylable, style);
+  else
+    {
+      g_object_set_qdata_full (G_OBJECT (stylable),
+                               quark_style,
+                               g_object_ref_sink (style),
+                               g_object_unref);
+    }
+
+  g_signal_emit (stylable, stylable_signals[STYLE_CHANGED], 0, old_style);
+  g_object_unref (old_style);
+
+  g_object_notify (G_OBJECT (stylable), "style");
+}
+
+/**
+ * st_stylable_get_container:
+ * @stylable: a #StStylable
+ *
+ * Obtain the parent #StStylable that contains @stylable.
+ *
+ * Return value: The parent #StStylable
+ */
+StStylable*
+st_stylable_get_container (StStylable *stylable)
+{
+  StStylableIface *iface;
+
+  g_return_val_if_fail (ST_IS_STYLABLE (stylable), NULL);
+
+  iface = ST_STYLABLE_GET_IFACE (stylable);
+
+  if (iface->get_container)
+    return iface->get_container (stylable);
+  else
+    return NULL;
+}
+
+/**
+ * st_stylable_get_base_style:
+ * @stylable: a #StStylable
+ *
+ * Get the parent ancestor #StStylable of @stylable.
+ *
+ * Return value: the parent #StStylable
+ */
+StStylable*
+st_stylable_get_base_style (StStylable *stylable)
+{
+  StStylableIface *iface;
+
+  g_return_val_if_fail (ST_IS_STYLABLE (stylable), NULL);
+
+  iface = ST_STYLABLE_GET_IFACE (stylable);
+
+  if (iface->get_base_style)
+    return iface->get_base_style (stylable);
+  else
+    return NULL;
+}
+
+
+/**
+ * st_stylable_get_style_id:
+ * @stylable: a #StStylable
+ *
+ * Get the ID value of @stylable
+ *
+ * Return value: the id of @stylable
+ */
+const gchar*
+st_stylable_get_style_id (StStylable *stylable)
+{
+  StStylableIface *iface;
+
+  g_return_val_if_fail (ST_IS_STYLABLE (stylable), NULL);
+
+  iface = ST_STYLABLE_GET_IFACE (stylable);
+
+  if (iface->get_style_id)
+    return iface->get_style_id (stylable);
+  else
+    return NULL;
+}
+
+/**
+ * st_stylable_get_style_type:
+ * @stylable: a #StStylable
+ *
+ * Get the type name of @stylable
+ *
+ * Return value: the type name of @stylable
+ */
+const gchar*
+st_stylable_get_style_type (StStylable *stylable)
+{
+  StStylableIface *iface;
+
+  g_return_val_if_fail (ST_IS_STYLABLE (stylable), NULL);
+
+  iface = ST_STYLABLE_GET_IFACE (stylable);
+
+  if (iface->get_style_type)
+    return iface->get_style_type (stylable);
+  else
+    return G_OBJECT_TYPE_NAME (stylable);
+}
+
+/**
+ * st_stylable_get_style_class:
+ * @stylable: a #StStylable
+ *
+ * Get the style class name of @stylable
+ *
+ * Return value: the type name of @stylable
+ */
+const gchar*
+st_stylable_get_style_class (StStylable *stylable)
+{
+  StStylableIface *iface;
+
+  g_return_val_if_fail (ST_IS_STYLABLE (stylable), NULL);
+
+  iface = ST_STYLABLE_GET_IFACE (stylable);
+
+  if (iface->get_style_class)
+    return iface->get_style_class (stylable);
+  else
+    return NULL;
+}
+
+/**
+ * st_stylable_get_pseudo_class:
+ * @stylable: a #StStylable
+ *
+ * Get the pseudo class name of @stylable
+ *
+ * Return value: the pseudo class name of @stylable
+ */
+const gchar*
+st_stylable_get_pseudo_class (StStylable *stylable)
+{
+  StStylableIface *iface;
+
+  g_return_val_if_fail (ST_IS_STYLABLE (stylable), NULL);
+
+  iface = ST_STYLABLE_GET_IFACE (stylable);
+
+  if (iface->get_pseudo_class)
+    return iface->get_pseudo_class (stylable);
+  else
+    return NULL;
+}
+
+/**
+ * st_stylable_get_attribute:
+ * @stylable: a #StStylable
+ * @name: attribute name
+ *
+ * Get the named attribute from @stylable
+ *
+ * Return value: the value of the attribute
+ */
+gchar*
+st_stylable_get_attribute (StStylable  *stylable,
+                           const gchar *name)
+{
+  StStylableIface *iface;
+  GValue value = { 0, };
+  GValue string_value = { 0, };
+  gchar *ret;
+  GParamSpec *pspec;
+
+  g_return_val_if_fail (ST_IS_STYLABLE (stylable), NULL);
+
+  iface = ST_STYLABLE_GET_IFACE (stylable);
+
+  if (iface->get_attribute)
+    return iface->get_attribute (stylable, name);
+
+  /* look up a generic gobject property */
+  pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (stylable), name);
+
+  /* if no such property exists, return NULL */
+  if (pspec == NULL)
+    return NULL;
+
+  g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+  g_object_get_property (G_OBJECT (stylable), name, &value);
+
+  g_value_init (&string_value, G_TYPE_STRING);
+  if (g_value_transform (&value, &string_value))
+    ret = g_strdup (g_value_get_string (&string_value));
+  else
+    ret = NULL;
+
+  g_value_unset (&value);
+  g_value_unset (&string_value);
+
+  return ret;
+}
+
+/**
+ * st_stylable_get_viewport:
+ * @stylable: a #StStylable
+ * @x: location to store X coordinate
+ * @y: location to store Y coordinate
+ * @width: location to store width
+ * @height: location to store height
+ *
+ * Obtain the position and dimensions of @stylable.
+ *
+ * Return value: true if the function succeeded
+ */
+gboolean
+st_stylable_get_viewport (StStylable *stylable,
+                          gint       *x,
+                          gint       *y,
+                          gint       *width,
+                          gint       *height)
+{
+  StStylableIface *iface;
+
+  g_return_val_if_fail (ST_IS_STYLABLE (stylable), FALSE);
+
+  iface = ST_STYLABLE_GET_IFACE (stylable);
+  if (iface->get_viewport)
+    return iface->get_viewport (stylable, x, y, width, height);
+  else
+    return FALSE;
+}
+
+
+/**
+ * st_stylable_changed:
+ * @stylable: A #StStylable
+ *
+ * Emit the "stylable-changed" signal on @stylable
+ */
+void
+st_stylable_changed (StStylable *stylable)
+{
+  g_signal_emit (stylable, stylable_signals[CHANGED], 0, NULL);
+}
diff --git a/src/st/st-stylable.h b/src/st/st-stylable.h
new file mode 100644
index 0000000..b00a2ba
--- /dev/null
+++ b/src/st/st-stylable.h
@@ -0,0 +1,124 @@
+/*
+ * st-stylable.h: Interface for stylable objects
+ *
+ * Copyright 2008, 2009 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ * Boston, MA 02111-1307, USA.
+ *
+ * Written by: Emmanuele Bassi <ebassi openedhand com>
+ *             Thomas Wood <thomas linux intel com>
+ *
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_STYLABLE_H__
+#define __ST_STYLABLE_H__
+
+#include <glib-object.h>
+#include <st/st-style.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_STYLABLE              (st_stylable_get_type ())
+#define ST_STYLABLE(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_STYLABLE, StStylable))
+#define ST_IS_STYLABLE(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ST_TYPE_STYLABLE))
+#define ST_STYLABLE_IFACE(iface)      (G_TYPE_CHECK_CLASS_CAST ((iface), ST_TYPE_STYLABLE, StStylableIface))
+#define ST_IS_STYLABLE_IFACE(iface)   (G_TYPE_CHECK_CLASS_TYPE ((iface), ST_TYPE_STYLABLE))
+#define ST_STYLABLE_GET_IFACE(obj)    (G_TYPE_INSTANCE_GET_INTERFACE ((obj), ST_TYPE_STYLABLE, StStylableIface))
+
+/* StStylableIface is defined in st-style.h */
+
+struct _StStylableIface
+{
+  GTypeInterface g_iface;
+
+  /* virtual functions */
+  StStyle *  (* get_style) (StStylable *stylable);
+  void       (* set_style) (StStylable *stylable,
+                            StStyle    *style);
+
+  /* context virtual functions */
+  StStylable   *(*get_container)    (StStylable  *stylable);
+  StStylable   *(*get_base_style)   (StStylable  *stylable);
+  const gchar  *(*get_style_id)     (StStylable  *stylable);
+  const gchar  *(*get_style_type)   (StStylable  *stylable);
+  const gchar  *(*get_style_class)  (StStylable  *stylable);
+  const gchar  *(*get_pseudo_class) (StStylable  *stylable);
+  gchar        *(*get_attribute)    (StStylable  *stylable,
+                                     const gchar *name);
+  gboolean      (*get_viewport)     (StStylable *stylable,
+                                     gint        *x,
+                                     gint        *y,
+                                     gint        *width,
+                                     gint        *height);
+
+  /* signals, not vfuncs */
+  void (* style_notify)     (StStylable *stylable,
+                             GParamSpec *pspec);
+  void (* style_changed)    (StStylable *stylable);
+
+  void (* stylable_changed) (StStylable *stylable);
+};
+
+GType st_stylable_get_type (void) G_GNUC_CONST;
+
+void st_stylable_iface_install_property (StStylableIface *iface,
+                                         GType            owner_type,
+                                         GParamSpec      *pspec);
+
+void         st_stylable_freeze_notify     (StStylable  *stylable);
+void         st_stylable_notify            (StStylable  *stylable,
+                                            const gchar *property_name);
+void         st_stylable_thaw_notify       (StStylable  *stylable);
+GParamSpec **st_stylable_list_properties   (StStylable  *stylable,
+                                            guint       *n_props);
+GParamSpec * st_stylable_find_property     (StStylable  *stylable,
+                                            const gchar *property_name);
+void         st_stylable_set_style         (StStylable  *stylable,
+                                            StStyle     *style);
+StStyle *    st_stylable_get_style         (StStylable  *stylable);
+
+void         st_stylable_get               (StStylable  *stylable,
+                                            const gchar *first_property_name,
+                                            ...) G_GNUC_NULL_TERMINATED;
+void         st_stylable_get_property      (StStylable  *stylable,
+                                            const gchar *property_name,
+                                            GValue      *value);
+gboolean     st_stylable_get_default_value (StStylable  *stylable,
+                                            const gchar *property_name,
+                                            GValue      *value_out);
+
+StStylable*  st_stylable_get_container     (StStylable  *stylable);
+StStylable*  st_stylable_get_base_style    (StStylable  *stylable);
+const gchar* st_stylable_get_style_id      (StStylable  *stylable);
+const gchar* st_stylable_get_style_type    (StStylable  *stylable);
+const gchar* st_stylable_get_style_class   (StStylable  *stylable);
+const gchar* st_stylable_get_pseudo_class  (StStylable  *stylable);
+gchar*       st_stylable_get_attribute     (StStylable  *stylable,
+                                            const gchar *name);
+gboolean     st_stylable_get_viewport      (StStylable  *stylable,
+                                            gint        *x,
+                                            gint        *y,
+                                            gint        *width,
+                                            gint        *height);
+
+void st_stylable_changed (StStylable *stylable);
+
+G_END_DECLS
+
+#endif /* __ST_STYLABLE_H__ */
diff --git a/src/st/st-style.c b/src/st/st-style.c
new file mode 100644
index 0000000..15e74a6
--- /dev/null
+++ b/src/st/st-style.c
@@ -0,0 +1,780 @@
+/*
+ * Copyright 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/**
+ * SECTION:st-style
+ * @short_description: a data store for style properties
+ *
+ * #StStyle is a property data store that can read properties from a style
+ * sheet. It is queried with objects that implement the StStylable
+ * interface.
+ */
+
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib-object.h>
+#include <gobject/gvaluecollector.h>
+#include <glib/gi18n-lib.h>
+
+#include <clutter/clutter.h>
+
+#include <ccss/ccss.h>
+
+#include "st-stylable.h"
+#include "st-style.h"
+#include "st-types.h"
+#include "st-marshal.h"
+#include "st-widget.h"
+
+enum
+{
+  CHANGED,
+
+  LAST_SIGNAL
+};
+
+#define ST_STYLE_GET_PRIVATE(obj) \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_STYLE, StStylePrivate))
+
+#define ST_STYLE_ERROR g_style_error_quark ()
+
+typedef struct {
+  GType  value_type;
+  gchar *value_name;
+  GValue value;
+} StyleProperty;
+
+struct _StStylePrivate
+{
+  ccss_stylesheet_t *stylesheet;
+  GList             *image_paths;
+
+  GHashTable        *style_hash;
+  GHashTable        *node_hash;
+};
+
+typedef struct {
+  ccss_node_t      parent;
+  StStylable      *stylable;
+  StStylableIface *iface;
+} st_style_node_t;
+
+static ccss_function_t const * peek_css_functions (void);
+
+static ccss_node_class_t * peek_node_class (void);
+
+static guint style_signals[LAST_SIGNAL] = { 0, };
+
+static StStyle *default_style = NULL;
+
+G_DEFINE_TYPE (StStyle, st_style, G_TYPE_OBJECT);
+
+static GQuark
+g_style_error_quark (void)
+{
+  return g_quark_from_static_string ("st-style-error-quark");
+}
+
+static gboolean
+st_style_real_load_from_file (StStyle     *style,
+                              const gchar *filename,
+                              GError     **error,
+                              gint         priority)
+{
+  StStylePrivate *priv;
+  ccss_grammar_t *grammar;
+  GError *internal_error;
+  gchar *path;
+  GList *l;
+
+  g_return_val_if_fail (ST_IS_STYLE (style), FALSE);
+  g_return_val_if_fail (filename != NULL, FALSE);
+
+  priv = ST_STYLE (style)->priv;
+
+  if (!g_file_test (filename, G_FILE_TEST_IS_REGULAR))
+    {
+      internal_error = g_error_new (ST_STYLE_ERROR,
+                                    ST_STYLE_ERROR_INVALID_FILE,
+                                    _("Invalid theme file '%s'"), filename);
+      g_propagate_error (error, internal_error);
+      return FALSE;
+    }
+
+
+  /* add the path of the stylesheet to the search path */
+  path = g_path_get_dirname (filename);
+
+  /* make sure path is valid */
+  if (!path)
+    return TRUE;
+
+  for (l = priv->image_paths; l; l = l->next)
+    {
+      if (g_str_equal ((gchar *) l->data, path))
+        {
+          /* we have this path already */
+          g_free (path);
+          path = NULL;
+        }
+    }
+
+  /* Add the new path */
+  if (path)
+    priv->image_paths = g_list_append (priv->image_paths, path);
+
+  /* now load the stylesheet */
+  if (!priv->stylesheet)
+    {
+      grammar = ccss_grammar_create_css ();
+      ccss_grammar_add_functions (grammar, peek_css_functions ());
+      priv->stylesheet = ccss_grammar_create_stylesheet_from_file (grammar,
+                                                                   filename,
+                                                                   path);
+      ccss_grammar_destroy (grammar);
+    }
+  else
+    {
+      ccss_stylesheet_add_from_file (priv->stylesheet, filename, priority, path);
+    }
+
+  g_signal_emit (style, style_signals[CHANGED], 0, NULL);
+
+  return TRUE;
+}
+
+/**
+ * st_style_load_from_file:
+ * @style: a #StStyle
+ * @filename: filename of the style sheet to load
+ * @error: a #GError or #NULL
+ *
+ * Load style information from the specified file.
+ *
+ * returns: TRUE if the style information was loaded successfully. Returns
+ * FALSE on error.
+ */
+gboolean
+st_style_load_from_file (StStyle     *style,
+                         const gchar *filename,
+                         GError     **error)
+{
+  return st_style_real_load_from_file (style, filename, error,
+                                       CCSS_STYLESHEET_AUTHOR);
+}
+
+static void
+st_style_load (StStyle *style)
+{
+  const gchar *env_var;
+  gchar *rc_file = NULL;
+  GError *error;
+
+  env_var = g_getenv ("ST_RC_FILE");
+  if (env_var && *env_var)
+    rc_file = g_strdup (env_var);
+
+  if (!rc_file)
+    rc_file = g_build_filename (PACKAGE_DATA_DIR,
+                                "st",
+                                "style",
+                                "default.css",
+                                NULL);
+
+  error = NULL;
+
+  if (g_file_test (rc_file, G_FILE_TEST_EXISTS))
+    {
+      /* load the default theme with lowest priority */
+      if (!st_style_real_load_from_file (style, rc_file, &error, CCSS_STYLESHEET_USER_AGENT))
+        {
+          g_critical ("Unable to load resource file '%s': %s",
+                      rc_file,
+                      error->message);
+          g_error_free (error);
+        }
+    }
+
+  g_free (rc_file);
+}
+
+static void
+st_style_finalize (GObject *gobject)
+{
+  StStylePrivate *priv = ((StStyle *) gobject)->priv;
+  GList *l;
+
+  for (l = priv->image_paths; l; l = g_list_delete_link (l, l))
+    {
+      g_free (l->data);
+    }
+
+  G_OBJECT_CLASS (st_style_parent_class)->finalize (gobject);
+}
+
+static void
+st_style_class_init (StStyleClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  g_type_class_add_private (klass, sizeof (StStylePrivate));
+
+  gobject_class->finalize = st_style_finalize;
+
+  /**
+   * StStyle::changed:
+   *
+   * Indicates that the style data has changed in some way. For example, a new
+   * stylesheet may have been loaded.
+   */
+
+  style_signals[CHANGED] =
+    g_signal_new ("changed",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (StStyleClass, changed),
+                  NULL, NULL,
+                  _st_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+}
+
+/* url loader for libccss */
+static char *
+ccss_url (GSList const *args,
+          void         *user_data)
+{
+  const gchar *given_path, *filename;
+  gchar *test_path;
+
+  g_return_val_if_fail (args, NULL);
+
+  given_path = (char const *) args->data;
+
+  /* we can only deal with local paths */
+  if (!g_str_has_prefix (given_path, "file://"))
+    return NULL;
+  filename = &given_path[7];
+
+  /*
+   * Handle absolute paths correctly
+   */
+  if (*filename == '/')
+    return strdup (filename);
+
+  /* first try looking in the theme dir */
+  test_path = g_build_filename (g_get_user_config_dir (),
+                                "st",
+                                filename,
+                                NULL);
+  if (g_file_test (test_path, G_FILE_TEST_IS_REGULAR))
+    return test_path;
+  g_free (test_path);
+
+  if (user_data)
+    {
+      test_path = g_build_filename ((gchar *) user_data, filename, NULL);
+
+      if (g_file_test (test_path, G_FILE_TEST_IS_REGULAR))
+        return test_path;
+
+      g_free (test_path);
+    }
+  else
+    {
+      g_warning ("No path available css url resolver!");
+    }
+
+  /* couldn't find the image anywhere, so just return the filename */
+  return strdup (given_path);
+}
+
+static ccss_function_t const *
+peek_css_functions (void)
+{
+  static ccss_function_t const ccss_functions[] =
+  {
+    { "url", ccss_url },
+    { NULL }
+  };
+
+  return ccss_functions;
+}
+
+
+static void
+st_style_init (StStyle *style)
+{
+  StStylePrivate *priv;
+
+  style->priv = priv = ST_STYLE_GET_PRIVATE (style);
+
+  /* create a hash table to look up pointer keys and values */
+  style->priv->node_hash = g_hash_table_new_full (NULL, NULL,
+                                                  NULL, g_free);
+  style->priv->style_hash = g_hash_table_new_full (NULL, NULL,
+                                                   NULL, (GDestroyNotify) ccss_style_destroy);
+
+  st_style_load (style);
+}
+
+/**
+ * st_style_new:
+ *
+ * Creates a new #StStyle object. This must be freed using #g_object_unref
+ * when no longer required.
+ *
+ * Returns: a newly allocated #StStyle
+ */
+StStyle *
+st_style_new (void)
+{
+  return g_object_new (ST_TYPE_STYLE, NULL);
+}
+
+/**
+ * st_style_get_default:
+ *
+ * Return the default StStyle object. This includes the current theme (if
+ * any).
+ *
+ * Returns: a #StStyle object. This must not be freed or unref'd by
+ * applications
+ */
+StStyle *
+st_style_get_default (void)
+{
+  if (G_LIKELY (default_style))
+    return default_style;
+
+  default_style = g_object_new (ST_TYPE_STYLE, NULL);
+
+  return default_style;
+}
+
+/* functions for ccss */
+
+static st_style_node_t *
+get_container (st_style_node_t *node)
+{
+  st_style_node_t *container;
+  ClutterActor *parent;
+
+  g_return_val_if_fail (node, NULL);
+  g_return_val_if_fail (node->iface, NULL);
+  g_return_val_if_fail (node->stylable, NULL);
+
+  parent = clutter_actor_get_parent (CLUTTER_ACTOR (node->stylable));
+  while (parent && !ST_IS_WIDGET (parent))
+    parent = clutter_actor_get_parent (CLUTTER_ACTOR (parent));
+
+  if (!parent)
+    return NULL;
+
+  container = g_new0 (st_style_node_t, 1);
+  ccss_node_init ((ccss_node_t*) container, peek_node_class ());
+  container->iface = node->iface;
+  container->stylable = ST_STYLABLE (parent);
+
+  return container;
+}
+
+static const gchar*
+get_style_id (st_style_node_t *node)
+{
+  return st_stylable_get_style_id (node->stylable);
+}
+
+static const gchar*
+get_style_type (st_style_node_t *node)
+{
+  return st_stylable_get_style_type (node->stylable);
+}
+
+static const gchar*
+get_style_class (st_style_node_t *node)
+{
+  return st_stylable_get_style_class (node->stylable);
+}
+
+static const gchar*
+get_pseudo_class (st_style_node_t *node)
+{
+  return st_stylable_get_pseudo_class (node->stylable);
+}
+
+static const gchar*
+get_attribute (st_style_node_t *node,
+               const char      *name)
+{
+  return st_stylable_get_attribute (node->stylable, name);
+}
+
+static void
+release (st_style_node_t *node)
+{
+  g_return_if_fail (node);
+
+  g_free (node);
+}
+
+static ccss_node_class_t *
+peek_node_class (void)
+{
+  static ccss_node_class_t _node_class = {
+    .is_a             = NULL,
+    .get_container    = (ccss_node_get_container_f) get_container,
+    .get_id           = (ccss_node_get_id_f) get_style_id,
+    .get_type         = (ccss_node_get_type_f) get_style_type,
+    .get_class        = (ccss_node_get_class_f) get_style_class,
+    .get_pseudo_class = (ccss_node_get_pseudo_class_f) get_pseudo_class,
+    .get_viewport     = NULL, // (ccss_node_get_viewport_f) get_viewport,
+    .get_attribute    = (ccss_node_get_attribute_f) get_attribute,
+    .release          = (ccss_node_release_f) release
+  };
+
+  return &_node_class;
+}
+
+static void
+st_style_fetch_ccss_property (ccss_style_t *ccss_style,
+                              GParamSpec   *pspec,
+                              GValue       *value)
+{
+  gboolean value_set = FALSE;
+
+  g_value_init (value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+
+  if (G_PARAM_SPEC_VALUE_TYPE (pspec))
+    {
+      double number;
+
+      if (G_IS_PARAM_SPEC_INT (pspec))
+        {
+          if (ccss_style_get_double (ccss_style, pspec->name, &number))
+            {
+              g_value_set_int (value, (gint) number);
+              value_set = TRUE;
+            }
+        }
+      else if (G_IS_PARAM_SPEC_UINT (pspec))
+        {
+          if (ccss_style_get_double (ccss_style, pspec->name, &number))
+            {
+              g_value_set_uint (value, (guint) number);
+              value_set = TRUE;
+            }
+        }
+      else if (G_PARAM_SPEC_VALUE_TYPE (pspec) == ST_TYPE_BORDER_IMAGE &&
+               !g_strcmp0 ("border-image", pspec->name))
+        {
+          ccss_border_image_t const *border_image;
+
+          if (ccss_style_get_property (ccss_style,
+                                       "border-image",
+                                       (ccss_property_base_t const **) &border_image))
+            {
+              if (border_image &&
+                  border_image->base.state == CCSS_PROPERTY_STATE_SET)
+                {
+                  g_value_set_boxed (value, border_image);
+                  value_set = TRUE;
+                }
+            }
+        }
+      else if (ST_TYPE_PADDING == G_PARAM_SPEC_VALUE_TYPE (pspec) &&
+               0 == g_strcmp0 ("padding", pspec->name))
+        {
+          StPadding padding = { 0, };
+          gboolean padding_set = 0;
+
+          if (ccss_style_get_double (ccss_style, "padding-top", &number))
+            {
+              padding.top = number;
+              padding_set = TRUE;
+            }
+
+          if (ccss_style_get_double (ccss_style, "padding-right", &number))
+            {
+              padding.right = number;
+              padding_set = TRUE;
+            }
+
+          if (ccss_style_get_double (ccss_style, "padding-bottom", &number))
+            {
+              padding.bottom = number;
+              padding_set = TRUE;
+            }
+
+          if (ccss_style_get_double (ccss_style, "padding-left", &number))
+            {
+              padding.left = number;
+              padding_set = TRUE;
+            }
+
+          if (padding_set)
+            {
+              g_value_set_boxed (value, &padding);
+              value_set = TRUE;
+            }
+        }
+      else
+        {
+          gchar *string = NULL;
+
+          ccss_style_get_string (ccss_style, pspec->name, &string);
+
+          if (string)
+            {
+              if (CLUTTER_IS_PARAM_SPEC_COLOR (pspec))
+                {
+                  ClutterColor color = { 0, };
+
+                  clutter_color_from_string (&color, string);
+                  clutter_value_set_color (value, &color);
+
+                  value_set = TRUE;
+                }
+              else
+              if (G_IS_PARAM_SPEC_STRING (pspec))
+                {
+                  g_value_set_string (value, string);
+                  value_set = TRUE;
+                }
+              g_free (string);
+            }
+        }
+    }
+
+  /* no value was found in css, so copy in the default value */
+  if (!value_set)
+    g_param_value_set_default (pspec, value);
+}
+
+static ccss_style_t*
+st_style_get_ccss_query (StStyle    *style,
+                         StStylable *stylable)
+{
+  StStylableIface *iface = ST_STYLABLE_GET_IFACE (stylable);
+  ccss_style_t *ccss_style;
+  st_style_node_t *ccss_node;
+
+  ccss_node = g_hash_table_lookup (style->priv->node_hash, stylable);
+
+  if (!ccss_node)
+    {
+      ccss_node = g_new0 (st_style_node_t, 1);
+      ccss_node_init ((ccss_node_t*) ccss_node, peek_node_class ());
+      ccss_node->iface = iface;
+      ccss_node->stylable = stylable;
+
+      g_hash_table_insert (style->priv->node_hash, stylable, ccss_node);
+      g_signal_connect_swapped (stylable, "stylable-changed",
+                                G_CALLBACK (g_hash_table_remove),
+                                style->priv->node_hash);
+
+
+      g_object_weak_ref ((GObject*) stylable,
+                         (GWeakNotify) g_hash_table_remove, style->priv->node_hash);
+    }
+
+
+  ccss_style = g_hash_table_lookup (style->priv->style_hash, stylable);
+
+  if (!ccss_style)
+    {
+      ccss_style = ccss_stylesheet_query (style->priv->stylesheet,
+                                          (ccss_node_t *) ccss_node);
+
+      g_hash_table_insert (style->priv->style_hash, stylable, ccss_style);
+
+      /* remove the cache if the stylable changes */
+      g_signal_connect_swapped (stylable, "stylable-changed",
+                                G_CALLBACK (g_hash_table_remove),
+                                style->priv->style_hash);
+
+      g_object_weak_ref ((GObject*) stylable,
+                         (GWeakNotify) g_hash_table_remove, style->priv->style_hash);
+    }
+
+  return ccss_style;
+
+}
+
+
+/**
+ * st_style_get_property:
+ * @style: the style data store object
+ * @stylable: a stylable to retreive the data for
+ * @pspec: a #GParamSpec describing the property required
+ * @value: a #GValue to place the return value in
+ *
+ * Requests the property described in @pspec for the specified stylable
+ */
+
+void
+st_style_get_property (StStyle    *style,
+                       StStylable *stylable,
+                       GParamSpec *pspec,
+                       GValue     *value)
+{
+  StStylePrivate *priv;
+  gboolean value_set = FALSE;
+
+  g_return_if_fail (ST_IS_STYLE (style));
+  g_return_if_fail (ST_IS_STYLABLE (stylable));
+  g_return_if_fail (pspec != NULL);
+  g_return_if_fail (value != NULL);
+
+  priv = style->priv;
+
+  /* look up the property in the css */
+  if (priv->stylesheet)
+    {
+      ccss_style_t *ccss_style;
+
+      ccss_style = st_style_get_ccss_query (style, stylable);
+      if (ccss_style)
+        {
+          st_style_fetch_ccss_property (ccss_style, pspec, value);
+          value_set = TRUE;
+        }
+    }
+
+  /* no value was found in css, so copy in the default value */
+  if (!value_set)
+    {
+      g_value_init (value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+      g_param_value_set_default (pspec, value);
+    }
+}
+
+/**
+ * st_style_get_valist:
+ * @style: a #StStyle
+ * @stylable: a #StStylable
+ * @first_property_name: name of the first property to get
+ * @va_args: return location for the first property, followed optionally
+ *   by more name/return location pairs, followed by %NULL
+ *
+ * Gets the style properties for @stylable from @style.
+ *
+ * Please refer to st_style_get() for further information.
+ */
+void
+st_style_get_valist (StStyle     *style,
+                     StStylable  *stylable,
+                     const gchar *first_property_name,
+                     va_list      va_args)
+{
+  StStylePrivate *priv;
+  const gchar *name = first_property_name;
+  gboolean values_set = FALSE;
+
+  g_return_if_fail (ST_IS_STYLE (style));
+  g_return_if_fail (ST_IS_STYLABLE (stylable));
+  g_return_if_fail (style->priv != NULL);
+
+  priv = style->priv;
+
+  /* look up the property in the css */
+  if (priv->stylesheet)
+    {
+      ccss_style_t *ccss_style;
+
+      ccss_style = st_style_get_ccss_query (style, stylable);
+
+      if (ccss_style)
+        {
+          while (name)
+            {
+              GValue value = { 0, };
+              gchar *error = NULL;
+              GParamSpec *pspec = st_stylable_find_property (stylable, name);
+              st_style_fetch_ccss_property (ccss_style, pspec, &value);
+              G_VALUE_LCOPY (&value, va_args, 0, &error);
+              if (error)
+                {
+                  g_warning ("%s: %s", G_STRLOC, error);
+                  g_free (error);
+                  g_value_unset (&value);
+                  break;
+                }
+              g_value_unset (&value);
+              name = va_arg (va_args, gchar*);
+            }
+          values_set = TRUE;
+        }
+    }
+
+  if (!values_set)
+    {
+      /* Set the remaining properties to their default values
+       * even if broken out of the above loop. */
+      while (name)
+        {
+          GValue value = { 0, };
+          gchar *error = NULL;
+          st_stylable_get_default_value (stylable, name, &value);
+          G_VALUE_LCOPY (&value, va_args, 0, &error);
+          if (error)
+            {
+              g_warning ("%s: %s", G_STRLOC, error);
+              g_free (error);
+              g_value_unset (&value);
+              break;
+            }
+          g_value_unset (&value);
+          name = va_arg (va_args, gchar*);
+        }
+    }
+}
+
+/**
+ * st_style_get:
+ * @style: a #StStyle
+ * @stylable: a #StStylable
+ * @first_property_name: name of the first property to get
+ * @Varargs: return location for the first property, followed optionally
+ *   by more name/return location pairs, followed by %NULL
+ *
+ * Gets the style properties for @stylable from @style.
+ *
+ * In general, a copy is made of the property contents and the caller
+ * is responsible for freeing the memory in the appropriate manner for
+ * the property type.
+ */
+void
+st_style_get (StStyle     *style,
+              StStylable  *stylable,
+              const gchar *first_property_name,
+              ...)
+{
+  va_list va_args;
+
+  g_return_if_fail (ST_IS_STYLE (style));
+  g_return_if_fail (first_property_name != NULL);
+
+  va_start (va_args, first_property_name);
+  st_style_get_valist (style, stylable, first_property_name, va_args);
+  va_end (va_args);
+}
+
diff --git a/src/st/st-style.h b/src/st/st-style.h
new file mode 100644
index 0000000..ce63a2a
--- /dev/null
+++ b/src/st/st-style.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_STYLE_H__
+#define __ST_STYLE_H__
+
+#include <glib-object.h>
+#include <clutter/clutter.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_STYLE                 (st_style_get_type ())
+#define ST_STYLE(obj)                 (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_STYLE, StStyle))
+#define ST_IS_STYLE(obj)              (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ST_TYPE_STYLE))
+#define ST_STYLE_CLASS(klass)         (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_STYLE, StStyleClass))
+#define ST_IS_STYLE_CLASS(klass)      (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_STYLE))
+#define ST_STYLE_GET_CLASS(obj)       (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_STYLE, StStyleClass))
+
+typedef struct _StStyle               StStyle;
+typedef struct _StStylePrivate        StStylePrivate;
+typedef struct _StStyleClass          StStyleClass;
+
+/* forward declaration */
+typedef struct _StStylable            StStylable; /* dummy typedef */
+typedef struct _StStylableIface       StStylableIface;
+
+typedef enum { /*< prefix=ST_STYLE_ERROR >*/
+  ST_STYLE_ERROR_INVALID_FILE
+} StStyleError;
+
+/**
+ * StStyle:
+ *
+ * The contents of this structure is private and should only be accessed using
+ * the provided API.
+ */
+struct _StStyle
+{
+  /*< private >*/
+  GObject parent_instance;
+
+  StStylePrivate *priv;
+};
+
+struct _StStyleClass
+{
+  GObjectClass parent_class;
+
+  void (* changed) (StStyle *style);
+};
+
+GType st_style_get_type (void) G_GNUC_CONST;
+
+StStyle *st_style_get_default (void);
+StStyle *st_style_new         (void);
+
+gboolean st_style_load_from_file (StStyle      *style,
+                                  const gchar  *filename,
+                                  GError      **error);
+void     st_style_get_property   (StStyle      *style,
+                                  StStylable   *stylable,
+                                  GParamSpec   *pspec,
+                                  GValue       *value);
+void     st_style_get            (StStyle      *style,
+                                  StStylable   *stylable,
+                                  const gchar  *first_property_name,
+                                  ...) G_GNUC_NULL_TERMINATED;
+void     st_style_get_valist     (StStyle      *style,
+                                  StStylable   *stylable,
+                                  const gchar  *first_property_name,
+                                  va_list       va_args);
+
+G_END_DECLS
+
+#endif /* __ST_STYLE_H__ */
diff --git a/src/st/st-subtexture.c b/src/st/st-subtexture.c
new file mode 100644
index 0000000..7ea1fa4
--- /dev/null
+++ b/src/st/st-subtexture.c
@@ -0,0 +1,575 @@
+/*
+ * st-subtexture.h: Class to wrap a texture and "subframe" it.
+ * based on
+ * st-texture-frame.c: Expandible texture actor
+ *
+ * Copyright 2007 OpenedHand
+ * Copyright 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <cogl/cogl.h>
+#include <clutter/clutter.h>
+
+#include "st-subtexture.h"
+
+enum
+{
+  PROP_0,
+
+  PROP_PARENT_TEXTURE,
+
+  PROP_TOP,
+  PROP_LEFT,
+  PROP_WIDTH,
+  PROP_HEIGHT
+};
+
+G_DEFINE_TYPE (StSubtexture, st_subtexture, CLUTTER_TYPE_ACTOR);
+
+#define ST_SUBTEXTURE_GET_PRIVATE(obj)     (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_SUBTEXTURE, StSubtexturePrivate))
+
+struct _StSubtexturePrivate
+{
+  ClutterTexture *parent_texture;
+
+  int             left;
+  int             top;
+  int             width;
+  int             height;
+
+  CoglHandle      material;
+};
+
+static void
+st_subtexture_get_preferred_width (ClutterActor *self,
+                                   gfloat        for_height,
+                                   gfloat       *min_width_p,
+                                   gfloat       *natural_width_p)
+{
+  StSubtexturePrivate *priv = ST_SUBTEXTURE (self)->priv;
+
+  if (G_UNLIKELY (priv->parent_texture == NULL))
+    {
+      if (min_width_p)
+        *min_width_p = 0;
+
+      if (natural_width_p)
+        *natural_width_p = 0;
+    }
+  else
+    {
+      if (min_width_p)
+        *min_width_p = priv->width;
+      if (natural_width_p)
+        *natural_width_p = priv->width;
+    }
+}
+
+static void
+st_subtexture_get_preferred_height (ClutterActor *self,
+                                    gfloat        for_width,
+                                    gfloat       *min_height_p,
+                                    gfloat       *natural_height_p)
+{
+  StSubtexturePrivate *priv = ST_SUBTEXTURE (self)->priv;
+
+  if (G_UNLIKELY (priv->parent_texture == NULL))
+    {
+      if (min_height_p)
+        *min_height_p = 0;
+
+      if (natural_height_p)
+        *natural_height_p = 0;
+    }
+  else
+    {
+      if (min_height_p)
+        *min_height_p = priv->height;
+      if (natural_height_p)
+        *natural_height_p = priv->height;
+    }
+}
+
+static void
+st_subtexture_realize (ClutterActor *self)
+{
+  StSubtexturePrivate *priv = ST_SUBTEXTURE (self)->priv;
+
+  if (priv->material != COGL_INVALID_HANDLE)
+    return;
+
+  priv->material = cogl_material_new ();
+
+  CLUTTER_ACTOR_SET_FLAGS (self, CLUTTER_ACTOR_REALIZED);
+}
+
+static void
+st_subtexture_unrealize (ClutterActor *self)
+{
+  StSubtexturePrivate *priv = ST_SUBTEXTURE (self)->priv;
+
+  if (priv->material == COGL_INVALID_HANDLE)
+    return;
+
+  cogl_material_unref (priv->material);
+  priv->material = COGL_INVALID_HANDLE;
+
+  CLUTTER_ACTOR_UNSET_FLAGS (self, CLUTTER_ACTOR_REALIZED);
+}
+
+static void
+st_subtexture_paint (ClutterActor *self)
+{
+  StSubtexturePrivate *priv = ST_SUBTEXTURE (self)->priv;
+  CoglHandle cogl_texture = COGL_INVALID_HANDLE;
+  ClutterActorBox box = { 0, 0, 0, 0 };
+  gfloat tx1, ty1, tx2, ty2, tex_width, tex_height, width, height;
+  guint8 opacity;
+
+  /* no need to paint stuff if we don't have a texture */
+  if (G_UNLIKELY (priv->parent_texture == NULL))
+    return;
+
+  /* parent texture may have been hidden, so need to make sure it gets
+   * realized
+   */
+  if (!CLUTTER_ACTOR_IS_REALIZED (priv->parent_texture))
+    clutter_actor_realize (CLUTTER_ACTOR (priv->parent_texture));
+
+  cogl_texture = clutter_texture_get_cogl_texture (priv->parent_texture);
+  if (cogl_texture == COGL_INVALID_HANDLE)
+    return;
+
+  tex_width  = cogl_texture_get_width (cogl_texture);
+  tex_height = cogl_texture_get_height (cogl_texture);
+
+  clutter_actor_get_allocation_box (self, &box);
+
+  width = box.x2 - box.x1;
+  height = box.y2 - box.y1;
+
+  tx1 = 1.0 *  priv->left / tex_width;
+  ty1 = 1.0 * priv->top / tex_height;
+
+  tx2 = 1.0 * (priv->left + priv->width) / tex_width;
+  ty2 = 1.0 * (priv->top + priv->height) / tex_height;
+
+
+  opacity = clutter_actor_get_paint_opacity (self);
+
+  g_assert (priv->material != COGL_INVALID_HANDLE);
+
+  /* set the source material using the parent texture's COGL handle */
+  cogl_material_set_color4ub (priv->material, 255, 255, 255, opacity);
+  cogl_material_set_layer (priv->material, 0, cogl_texture);
+  cogl_set_source (priv->material);
+
+  cogl_rectangle_with_texture_coords (0,0, (float) width, (float) height,
+                                      tx1, ty1, tx2, ty2);
+}
+
+static inline void
+st_subtexture_set_frame_internal (StSubtexture *frame,
+                                  int           left,
+                                  int           top,
+                                  int           width,
+                                  int           height)
+{
+  StSubtexturePrivate *priv = frame->priv;
+  GObject *gobject = G_OBJECT (frame);
+  gboolean changed = FALSE;
+
+  g_object_freeze_notify (gobject);
+
+  if (priv->top != top)
+    {
+      priv->top = top;
+      g_object_notify (gobject, "top");
+      changed = TRUE;
+    }
+
+  if (priv->left != left)
+    {
+      priv->left = left;
+      g_object_notify (gobject, "left");
+      changed = TRUE;
+    }
+
+  if (priv->width != width)
+    {
+      priv->width = width;
+      g_object_notify (gobject, "width");
+      changed = TRUE;
+    }
+
+  if (priv->height != height)
+    {
+      priv->height = height;
+      g_object_notify (gobject, "height");
+      changed = TRUE;
+    }
+
+  if (changed && CLUTTER_ACTOR_IS_VISIBLE (frame))
+    clutter_actor_queue_redraw (CLUTTER_ACTOR (frame));
+
+  g_object_thaw_notify (gobject);
+}
+
+static void
+st_subtexture_set_property (GObject      *gobject,
+                            guint         prop_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  StSubtexture *frame = ST_SUBTEXTURE (gobject);
+  StSubtexturePrivate *priv = frame->priv;
+
+  switch (prop_id)
+    {
+    case PROP_PARENT_TEXTURE:
+      st_subtexture_set_parent_texture (frame,
+                                        g_value_get_object (value));
+      break;
+
+    case PROP_TOP:
+      st_subtexture_set_frame_internal (frame,
+                                        priv->left,
+                                        g_value_get_int (value),
+                                        priv->width,
+                                        priv->height);
+      break;
+
+    case PROP_LEFT:
+      st_subtexture_set_frame_internal (frame,
+                                        g_value_get_int (value),
+                                        priv->top,
+                                        priv->width,
+                                        priv->height);
+      break;
+
+    case PROP_WIDTH:
+      st_subtexture_set_frame_internal (frame,
+                                        priv->left,
+                                        priv->top,
+                                        g_value_get_int (value),
+                                        priv->height);
+      break;
+
+    case PROP_HEIGHT:
+      st_subtexture_set_frame_internal (frame,
+                                        priv->left,
+                                        priv->top,
+                                        priv->width,
+                                        g_value_get_int (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+st_subtexture_get_property (GObject    *gobject,
+                            guint       prop_id,
+                            GValue     *value,
+                            GParamSpec *pspec)
+{
+  StSubtexturePrivate *priv = ST_SUBTEXTURE (gobject)->priv;
+
+  switch (prop_id)
+    {
+    case PROP_PARENT_TEXTURE:
+      g_value_set_object (value, priv->parent_texture);
+      break;
+
+    case PROP_LEFT:
+      g_value_set_int (value, priv->left);
+      break;
+
+    case PROP_TOP:
+      g_value_set_int (value, priv->top);
+      break;
+
+    case PROP_WIDTH:
+      g_value_set_int (value, priv->width);
+      break;
+
+    case PROP_HEIGHT:
+      g_value_set_int (value, priv->height);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+st_subtexture_dispose (GObject *gobject)
+{
+  StSubtexturePrivate *priv = ST_SUBTEXTURE (gobject)->priv;
+
+  if (priv->parent_texture)
+    {
+      g_object_unref (priv->parent_texture);
+      priv->parent_texture = NULL;
+    }
+
+  if (priv->material)
+    {
+      cogl_material_unref (priv->material);
+      priv->material = COGL_INVALID_HANDLE;
+    }
+
+  G_OBJECT_CLASS (st_subtexture_parent_class)->dispose (gobject);
+}
+
+static void
+st_subtexture_class_init (StSubtextureClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+  GParamSpec *pspec;
+
+  g_type_class_add_private (gobject_class, sizeof (StSubtexturePrivate));
+
+  actor_class->get_preferred_width =
+    st_subtexture_get_preferred_width;
+  actor_class->get_preferred_height =
+    st_subtexture_get_preferred_height;
+  actor_class->realize = st_subtexture_realize;
+  actor_class->unrealize = st_subtexture_unrealize;
+  actor_class->paint = st_subtexture_paint;
+
+  gobject_class->set_property = st_subtexture_set_property;
+  gobject_class->get_property = st_subtexture_get_property;
+  gobject_class->dispose = st_subtexture_dispose;
+
+  pspec = g_param_spec_object ("parent-texture",
+                               "Parent Texture",
+                               "The parent ClutterTexture",
+                               CLUTTER_TYPE_TEXTURE,
+                               G_PARAM_READWRITE |
+                               G_PARAM_CONSTRUCT);
+  g_object_class_install_property (gobject_class, PROP_PARENT_TEXTURE, pspec);
+
+  pspec = g_param_spec_int ("left",
+                            "Left",
+                            "Left offset",
+                            0, G_MAXINT,
+                            0,
+                            G_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_LEFT, pspec);
+
+  pspec = g_param_spec_int ("top",
+                            "Top",
+                            "Top offset",
+                            0, G_MAXINT,
+                            0,
+                            G_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_TOP, pspec);
+
+  pspec = g_param_spec_int ("width",
+                            "Width",
+                            "Width",
+                            0, G_MAXINT,
+                            0,
+                            G_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_WIDTH, pspec);
+
+  pspec = g_param_spec_int ("height",
+                            "Height",
+                            "Height",
+                            0, G_MAXINT,
+                            0,
+                            G_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_HEIGHT, pspec);
+}
+
+static void
+st_subtexture_init (StSubtexture *self)
+{
+  StSubtexturePrivate *priv;
+
+  self->priv = priv = ST_SUBTEXTURE_GET_PRIVATE (self);
+
+  priv->material = COGL_INVALID_HANDLE;
+}
+
+/**
+ * st_subtexture_new:
+ * @texture: a #ClutterTexture or %NULL
+ * @left: left
+ * @top: top
+ * @width: width
+ * @height: height
+ *
+ * A #StSubtexture is a specialized texture that efficiently clones
+ * an area of the given @texture while keeping preserving portions of the
+ * same texture.
+ *
+ * A #StSubtexture can be used to make a rectangular texture fit a
+ * given size without stretching its borders.
+ *
+ * Return value: the newly created #StSubtexture
+ */
+ClutterActor*
+st_subtexture_new (ClutterTexture *texture,
+                   gint            left,
+                   gint            top,
+                   gint            width,
+                   gint            height)
+{
+  g_return_val_if_fail (texture == NULL || CLUTTER_IS_TEXTURE (texture), NULL);
+
+  return g_object_new (ST_TYPE_SUBTEXTURE,
+                       "parent-texture", texture,
+                       "top", top,
+                       "left", left,
+                       "width", width,
+                       "height", height,
+                       NULL);
+}
+
+/**
+ * st_subtexture_get_parent_texture:
+ * @frame: A #StSubtexture
+ *
+ * Return the texture used by the #StSubtexture
+ *
+ * Returns: a #ClutterTexture owned by the #StSubtexture
+ */
+ClutterTexture *
+st_subtexture_get_parent_texture (StSubtexture *frame)
+{
+  g_return_val_if_fail (ST_IS_SUBTEXTURE (frame), NULL);
+
+  return frame->priv->parent_texture;
+}
+
+/**
+ * st_subtexture_set_parent_texture:
+ * @frame: A #StSubtexture
+ * @texture: A #ClutterTexture
+ *
+ * Set the #ClutterTexture used by this #StSubtexture
+ *
+ */
+void
+st_subtexture_set_parent_texture (StSubtexture   *frame,
+                                  ClutterTexture *texture)
+{
+  StSubtexturePrivate *priv;
+  gboolean was_visible;
+
+  g_return_if_fail (ST_IS_SUBTEXTURE (frame));
+  g_return_if_fail (texture == NULL || CLUTTER_IS_TEXTURE (texture));
+
+  priv = frame->priv;
+
+  was_visible = CLUTTER_ACTOR_IS_VISIBLE (frame);
+
+  if (priv->parent_texture == texture)
+    return;
+
+  if (priv->parent_texture)
+    {
+      g_object_unref (priv->parent_texture);
+      priv->parent_texture = NULL;
+
+      if (was_visible)
+        clutter_actor_hide (CLUTTER_ACTOR (frame));
+    }
+
+  if (texture)
+    {
+      priv->parent_texture = g_object_ref (texture);
+
+      if (was_visible && CLUTTER_ACTOR_IS_VISIBLE (priv->parent_texture))
+        clutter_actor_show (CLUTTER_ACTOR (frame));
+    }
+
+  clutter_actor_queue_relayout (CLUTTER_ACTOR (frame));
+
+  g_object_notify (G_OBJECT (frame), "parent-texture");
+}
+
+/**
+ * st_subtexture_set_frame:
+ * @frame: A #StSubtexture
+ * @left: left
+ * @top: top
+ * @width: width
+ * @height: height
+ *
+ * Set the frame of the subtexture
+ *
+ */
+void
+st_subtexture_set_frame (StSubtexture *frame,
+                         gint          left,
+                         gint          top,
+                         gint          width,
+                         gint          height)
+{
+  g_return_if_fail (ST_IS_SUBTEXTURE (frame));
+
+  st_subtexture_set_frame_internal (frame, left, top, width, height);
+}
+
+/**
+ * st_subtexture_get_frame:
+ * @frame: A #StSubtexture
+ * @left: left
+ * @top: top
+ * @width: width
+ * @height: height
+ *
+ * Retrieve the current frame.
+ *
+ */
+void
+st_subtexture_get_frame (StSubtexture *frame,
+                         gint         *left,
+                         gint         *top,
+                         gint         *width,
+                         gint         *height)
+{
+  StSubtexturePrivate *priv;
+
+  g_return_if_fail (ST_IS_SUBTEXTURE (frame));
+
+  priv = frame->priv;
+
+  if (top)
+    *top = priv->top;
+
+  if (left)
+    *left = priv->left;
+
+  if (width)
+    *width = priv->width;
+
+  if (height)
+    *height = priv->height;
+}
diff --git a/src/st/st-subtexture.h b/src/st/st-subtexture.h
new file mode 100644
index 0000000..ce9c10e
--- /dev/null
+++ b/src/st/st-subtexture.h
@@ -0,0 +1,96 @@
+/*
+ * st-subtexture.h: Class to wrap a texture and "subframe" it.
+ *
+ * Based on
+ * st-texture-frame.h: Expandible texture actor
+ *
+ * Copyright 2007, 2008 OpenedHand Ltd
+ * Copyright 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_SUBTEXTURE_H__
+#define __ST_SUBTEXTURE_H__
+
+#include <clutter/clutter.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_SUBTEXTURE                 (st_subtexture_get_type ())
+#define ST_SUBTEXTURE(obj)                 (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_SUBTEXTURE, StSubtexture))
+#define ST_SUBTEXTURE_CLASS(klass)         (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_SUBTEXTURE, StSubtextureClass))
+#define ST_IS_SUBTEXTURE(obj)              (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ST_TYPE_SUBTEXTURE))
+#define ST_IS_SUBTEXTURE_CLASS(klass)      (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_SUBTEXTURE))
+#define ST_SUBTEXTURE_GET_CLASS(obj)       (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_SUBTEXTURE, StSubtextureClass))
+
+typedef struct _StSubtexture                StSubtexture;
+typedef struct _StSubtexturePrivate         StSubtexturePrivate;
+typedef struct _StSubtextureClass           StSubtextureClass;
+
+/**
+ * StSubtexture:
+ *
+ * The contents of this structure are private and should only be accessed
+ * through the public API.
+ */
+struct _StSubtexture
+{
+  /*< private >*/
+  ClutterActor parent_instance;
+  
+  StSubtexturePrivate    *priv;
+};
+
+struct _StSubtextureClass 
+{
+  ClutterActorClass parent_class;
+
+  /* padding for future expansion */
+  void (*_st_box_1) (void);
+  void (*_st_box_2) (void);
+  void (*_st_box_3) (void);
+  void (*_st_box_4) (void);
+}; 
+
+GType st_subtexture_get_type (void) G_GNUC_CONST;
+
+ClutterActor *  st_subtexture_new                (ClutterTexture *texture,
+                                                  gint            top,
+                                                  gint            left,
+                                                  gint            width,
+                                                  gint            height);
+void            st_subtexture_set_parent_texture (StSubtexture   *frame,
+                                                  ClutterTexture *texture);
+ClutterTexture *st_subtexture_get_parent_texture (StSubtexture   *frame);
+void            st_subtexture_set_frame          (StSubtexture   *frame,
+                                                  gint            top,
+                                                  gint            left,
+                                                  gint            width,
+                                                  gint            height);
+void            st_subtexture_get_frame          (StSubtexture   *frame,
+                                                  gint           *top,
+                                                  gint           *left,
+                                                  gint           *width,
+                                                  gint           *height);
+
+G_END_DECLS
+
+#endif /* __ST_SUBTEXTURE_H__ */
diff --git a/src/st/st-texture-cache.c b/src/st/st-texture-cache.c
new file mode 100644
index 0000000..0cab32c
--- /dev/null
+++ b/src/st/st-texture-cache.c
@@ -0,0 +1,450 @@
+/*
+ * st-widget.h: Base class for St actors
+ *
+ * Copyright 2007 OpenedHand
+ * Copyright 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+/**
+ * SECTION:st-texture-cache
+ * @short_description: A per-process store to cache textures
+ *
+ * #StTextureCache allows an application to re-use an previously loaded
+ * textures.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <string.h>
+
+#include "st-texture-cache.h"
+#include "st-marshal.h"
+#include "st-private.h"
+#include "st-subtexture.h"
+G_DEFINE_TYPE (StTextureCache, st_texture_cache, G_TYPE_OBJECT)
+
+#define TEXTURE_CACHE_PRIVATE(o) \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((o), ST_TYPE_TEXTURE_CACHE, StTextureCachePrivate))
+
+typedef struct _StTextureCachePrivate StTextureCachePrivate;
+
+struct _StTextureCachePrivate
+{
+  GHashTable *cache;
+};
+
+typedef struct FinalizedClosure
+{
+  gchar          *path;
+  StTextureCache *cache;
+} FinalizedClosure;
+
+enum
+{
+  PROP_0,
+};
+
+static StTextureCache* __cache_singleton = NULL;
+
+/*
+ * Convention: posX with a value of -1 indicates whole texture
+ */
+typedef struct StTextureCacheItem {
+  char          filename[256];
+  int           width, height;
+  int           posX, posY;
+  ClutterActor *ptr;
+} StTextureCacheItem;
+
+static StTextureCacheItem *
+st_texture_cache_item_new (void)
+{
+  return g_slice_new0 (StTextureCacheItem);
+}
+
+static void
+st_texture_cache_item_free (StTextureCacheItem *item)
+{
+  g_slice_free (StTextureCacheItem, item);
+}
+
+static void
+st_texture_cache_set_property (GObject      *object,
+                               guint         prop_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+{
+  switch (prop_id)
+    {
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+st_texture_cache_get_property (GObject    *object,
+                               guint       prop_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+  switch (prop_id)
+    {
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+st_texture_cache_dispose (GObject *object)
+{
+  if (G_OBJECT_CLASS (st_texture_cache_parent_class)->dispose)
+    G_OBJECT_CLASS (st_texture_cache_parent_class)->dispose (object);
+}
+
+static void
+st_texture_cache_finalize (GObject *object)
+{
+  StTextureCachePrivate *priv = TEXTURE_CACHE_PRIVATE(object);
+
+  if (priv->cache)
+    {
+      g_hash_table_unref (priv->cache);
+      priv->cache = NULL;
+    }
+
+  G_OBJECT_CLASS (st_texture_cache_parent_class)->finalize (object);
+}
+
+static void
+st_texture_cache_class_init (StTextureCacheClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  g_type_class_add_private (klass, sizeof (StTextureCachePrivate));
+
+  object_class->get_property = st_texture_cache_get_property;
+  object_class->set_property = st_texture_cache_set_property;
+  object_class->dispose = st_texture_cache_dispose;
+  object_class->finalize = st_texture_cache_finalize;
+
+}
+
+static void
+st_texture_cache_init (StTextureCache *self)
+{
+  StTextureCachePrivate *priv = TEXTURE_CACHE_PRIVATE(self);
+
+  priv->cache = g_hash_table_new_full (g_str_hash,
+                                       g_str_equal,
+                                       g_free,
+                                       NULL);
+
+}
+
+/**
+ * st_texture_cache_get_default:
+ *
+ * Returns the default texture cache. This is owned by St and should not be
+ * unreferenced or freed.
+ *
+ * Returns: a StTextureCache
+ */
+StTextureCache*
+st_texture_cache_get_default (void)
+{
+  if (G_UNLIKELY (__cache_singleton == NULL))
+    __cache_singleton = g_object_new (ST_TYPE_TEXTURE_CACHE, NULL);
+
+  return __cache_singleton;
+}
+
+#if 0
+static void
+on_texure_finalized (gpointer data,
+                     GObject *where_the_object_was)
+{
+  FinalizedClosure *closure = (FinalizedClosure *) data;
+  StTextureCachePrivate *priv = TEXTURE_CACHE_PRIVATE(closure->cache);
+
+  g_hash_table_remove (priv->cache, closure->path);
+
+  g_free(closure->path);
+  g_free(closure);
+}
+#endif
+
+/**
+ * st_texture_cache_get_size:
+ * @self: A #StTextureCache
+ *
+ * Returns the number of items in the texture cache
+ *
+ * Returns: the current size of the cache
+ */
+gint
+st_texture_cache_get_size (StTextureCache *self)
+{
+  StTextureCachePrivate *priv = TEXTURE_CACHE_PRIVATE(self);
+
+  return g_hash_table_size (priv->cache);
+}
+
+static void
+add_texture_to_cache (StTextureCache     *self,
+                      const gchar        *path,
+                      StTextureCacheItem *item)
+{
+  /*  FinalizedClosure        *closure; */
+  StTextureCachePrivate *priv = TEXTURE_CACHE_PRIVATE(self);
+
+  g_hash_table_insert (priv->cache, g_strdup (path), item);
+
+#if 0
+  /* Make sure we can remove from hash */
+  closure = g_new0 (FinalizedClosure, 1);
+  closure->path = g_strdup (path);
+  closure->cache = self;
+
+  g_object_weak_ref (G_OBJECT (res), on_texure_finalized, closure);
+#endif
+}
+
+/* NOTE: you should unref the returned texture when not needed */
+
+/**
+ * st_texture_cache_get_texture:
+ * @self: A #StTextureCache
+ * @path: A path to a image file
+ *
+ * Create a new ClutterTexture with the specified image. Adds the image to the
+ * cache if the image had not been previously loaded. Subsequent calls with
+ * the same image path will return a new ClutterTexture with the previously
+ * loaded image.
+ *
+ * Returns: a newly created ClutterTexture
+ */
+ClutterTexture*
+st_texture_cache_get_texture (StTextureCache *self,
+                              const gchar    *path)
+{
+  ClutterActor *texture;
+  CoglHandle *handle;
+  StTextureCachePrivate *priv;
+  StTextureCacheItem *item;
+
+  g_return_val_if_fail (ST_IS_TEXTURE_CACHE (self), NULL);
+  g_return_val_if_fail (path != NULL, NULL);
+
+
+  priv = TEXTURE_CACHE_PRIVATE (self);
+
+  item = g_hash_table_lookup (priv->cache, path);
+
+  if (item && item->posX != -1)
+    {
+      GError *err = NULL;
+      /*
+       * We have a cache hit, but it's for a partial texture. The only
+       * sane option is to read it from disk and just don't cache it
+       * at all.
+       */
+      return CLUTTER_TEXTURE(clutter_texture_new_from_file(path, &err));
+    }
+  if (!item)
+    {
+      GError *err = NULL;
+
+      item = st_texture_cache_item_new ();
+      item->posX = -1;
+      item->posY = -1;
+      item->ptr = clutter_texture_new_from_file (path, &err);
+      clutter_texture_get_base_size (CLUTTER_TEXTURE (item->ptr),
+                                     &item->width, &item->height);
+
+      if (!item->ptr)
+        {
+          if (err)
+            {
+              g_warning ("Error loading image: %s", err->message);
+              g_error_free (err);
+            }
+
+          return NULL;
+        }
+
+      add_texture_to_cache (self, path, item);
+    }
+
+  texture = clutter_texture_new ();
+  handle = clutter_texture_get_cogl_texture (CLUTTER_TEXTURE (item->ptr));
+  clutter_texture_set_cogl_texture ((ClutterTexture*) texture, handle);
+
+  return (ClutterTexture*) texture;
+}
+
+
+/**
+ * st_texture_cache_get_actor:
+ * @self: A #StTextureCache
+ * @path: A path to a image file
+ *
+ * Create a new ClutterSubTexture with the specified image. Adds the image to the
+ * cache if the image had not been previously loaded. Subsequent calls with
+ * the same image path will return a new ClutterTexture with the previously
+ * loaded image.
+ *
+ * Use this function if all you need is an actor for drawing.
+ *
+ * Returns: a newly created ClutterTexture
+ */
+ClutterActor*
+st_texture_cache_get_actor (StTextureCache *self,
+                            const gchar    *path)
+{
+  StTextureCachePrivate *priv;
+  StTextureCacheItem *item;
+  GError *err = NULL;
+
+  g_return_val_if_fail (ST_IS_TEXTURE_CACHE (self), NULL);
+  g_return_val_if_fail (path != NULL, NULL);
+
+  priv = TEXTURE_CACHE_PRIVATE (self);
+
+
+  item = g_hash_table_lookup (priv->cache, path);
+
+  if (item)
+    {
+      int posX = item->posX;
+      int posY = item->posY;
+      if (posX == -1)
+        posX = 0;
+      if (posY == -1)
+        posY = 0;
+      return st_subtexture_new (CLUTTER_TEXTURE (item->ptr), posX, posY,
+                                item->width, item->height);
+    }
+
+  item = st_texture_cache_item_new ();
+  item->posX = -1;
+  item->posY = -1;
+  item->ptr = clutter_texture_new_from_file (path, &err);
+  clutter_texture_get_base_size (CLUTTER_TEXTURE (item->ptr),
+                                 &item->width, &item->height);
+
+  if (!item->ptr)
+    {
+      if (err)
+        {
+          g_warning ("Error loading image: %s", err->message);
+          g_error_free (err);
+        }
+
+      return NULL;
+    }
+
+  add_texture_to_cache (self, path, item);
+
+  return st_subtexture_new (CLUTTER_TEXTURE (item->ptr), 0, 0, item->width,
+                            item->height);
+}
+
+void
+st_texture_cache_load_cache (StTextureCache *self,
+                             const gchar    *filename)
+{
+  FILE *file;
+  StTextureCacheItem *element, head;
+  int ret;
+  ClutterActor *actor;
+  GError *error = NULL;
+  StTextureCachePrivate *priv;
+
+  g_return_if_fail (ST_IS_TEXTURE_CACHE (self));
+  g_return_if_fail (filename != NULL);
+
+  priv = TEXTURE_CACHE_PRIVATE (self);
+
+  file = fopen(filename, "rm");
+  if (!file)
+    return;
+
+  ret = fread (&head, sizeof(StTextureCacheItem), 1, file);
+  if (ret < 0)
+    {
+      fclose (file);
+      return;
+    }
+
+  /* check if we already if this texture in the cache */
+  if (g_hash_table_lookup (priv->cache, head.filename))
+    {
+      /* skip it, we're done */
+      fclose (file);
+      return;
+    }
+
+  actor = clutter_texture_new_from_file (head.filename, &error);
+
+  if (error)
+    {
+      g_critical (G_STRLOC ": Error opening cache image file: %s",
+                  error->message);
+      g_clear_error (&error);
+      fclose (file);
+      return;
+    }
+
+  element = st_texture_cache_item_new ();
+  element->posX = -1;
+  element->posY = -1;
+  element->ptr = actor;
+  strncpy (element->filename, head.filename, 256);
+  clutter_texture_get_base_size (CLUTTER_TEXTURE (element->ptr),
+                                 &element->width, &element->height);
+  g_hash_table_insert (priv->cache, element->filename, element);
+
+  while (!feof (file))
+    {
+      element = st_texture_cache_item_new ();
+      ret = fread (element, sizeof (StTextureCacheItem), 1, file);
+      if (ret < 1)
+        {
+          /* end of file */
+          st_texture_cache_item_free (element);
+          break;
+        }
+
+      element->ptr = actor;
+
+      if (g_hash_table_lookup (priv->cache, element->filename))
+        {
+          /* file is already in the cache.... */
+          st_texture_cache_item_free (element);
+        } else {
+          g_hash_table_insert (priv->cache, element->filename, element);
+        }
+    }
+}
diff --git a/src/st/st-texture-cache.h b/src/st/st-texture-cache.h
new file mode 100644
index 0000000..b3da32e
--- /dev/null
+++ b/src/st/st-texture-cache.h
@@ -0,0 +1,95 @@
+/*
+ * st-texture-cache.h: Cached textures object
+ *
+ * Copyright 2007 OpenedHand
+ * Copyright 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef _ST_TEXTURE_CACHE
+#define _ST_TEXTURE_CACHE
+
+#include <glib-object.h>
+#include <clutter/clutter.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_TEXTURE_CACHE st_texture_cache_get_type()
+
+#define ST_TEXTURE_CACHE(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+  ST_TYPE_TEXTURE_CACHE, StTextureCache))
+
+#define ST_TEXTURE_CACHE_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), \
+  ST_TYPE_TEXTURE_CACHE, StTextureCacheClass))
+
+#define ST_IS_TEXTURE_CACHE(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+  ST_TYPE_TEXTURE_CACHE))
+
+#define ST_IS_TEXTURE_CACHE_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+  ST_TYPE_TEXTURE_CACHE))
+
+#define ST_TEXTURE_CACHE_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+  ST_TYPE_TEXTURE_CACHE, StTextureCacheClass))
+
+/**
+ * StTextureCache:
+ *
+ * The contents of this structure are private and should only be accessed
+ * through the public API.
+ */
+typedef struct {
+    /*< private >*/
+    GObject parent;
+} StTextureCache;
+
+typedef struct {
+  GObjectClass parent_class;
+
+  void (* loaded)        (StTextureCache *self,
+                          const gchar      *path,
+                          ClutterTexture   *texture);
+
+  void (* error_loading) (StTextureCache *self,
+                          GError           *error);
+} StTextureCacheClass;
+
+GType st_texture_cache_get_type (void);
+
+StTextureCache* st_texture_cache_get_default (void);
+
+ClutterTexture* st_texture_cache_get_texture (StTextureCache *self,
+                                              const gchar    *path);
+ClutterActor*   st_texture_cache_get_actor   (StTextureCache *self,
+                                              const gchar    *path);
+
+gint            st_texture_cache_get_size    (StTextureCache *self);
+
+void st_texture_cache_load_cache (StTextureCache *self,
+                                  const char     *filename);
+
+G_END_DECLS
+
+#endif /* _ST_TEXTURE_CACHE */
diff --git a/src/st/st-texture-frame.c b/src/st/st-texture-frame.c
new file mode 100644
index 0000000..75d3430
--- /dev/null
+++ b/src/st/st-texture-frame.c
@@ -0,0 +1,620 @@
+/*
+ * st-texture-frame.h: Expandible texture actor
+ *
+ * Copyright 2007 OpenedHand
+ * Copyright 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+/**
+ * SECTION:st-texture-frame
+ * @short_description: Stretch a texture to fit the entire allocation
+ *
+ * #StTextureFrame
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <cogl/cogl.h>
+
+#include "st-texture-frame.h"
+#include "st-private.h"
+
+enum
+{
+  PROP_0,
+
+  PROP_PARENT_TEXTURE,
+
+  PROP_TOP,
+  PROP_RIGHT,
+  PROP_BOTTOM,
+  PROP_LEFT
+};
+
+G_DEFINE_TYPE (StTextureFrame, st_texture_frame, CLUTTER_TYPE_ACTOR);
+
+#define ST_TEXTURE_FRAME_GET_PRIVATE(obj)     (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_TEXTURE_FRAME, StTextureFramePrivate))
+
+struct _StTextureFramePrivate
+{
+  ClutterTexture *parent_texture;
+
+  gfloat          top;
+  gfloat          right;
+  gfloat          bottom;
+  gfloat          left;
+};
+
+static void
+st_texture_frame_get_preferred_width (ClutterActor *self,
+                                      gfloat        for_height,
+                                      gfloat       *min_width_p,
+                                      gfloat       *natural_width_p)
+{
+  StTextureFramePrivate *priv = ST_TEXTURE_FRAME (self)->priv;
+
+  if (G_UNLIKELY (priv->parent_texture == NULL))
+    {
+      if (min_width_p)
+        *min_width_p = 0;
+
+      if (natural_width_p)
+        *natural_width_p = 0;
+    }
+  else
+    {
+      ClutterActorClass *klass;
+
+      /* by directly querying the parent texture's class implementation
+       * we are going around any override mechanism the parent texture
+       * might have in place, and we ask directly for the original
+       * preferred width
+       */
+      klass = CLUTTER_ACTOR_GET_CLASS (priv->parent_texture);
+      klass->get_preferred_width (CLUTTER_ACTOR (priv->parent_texture),
+                                  for_height,
+                                  min_width_p,
+                                  natural_width_p);
+    }
+}
+
+static void
+st_texture_frame_get_preferred_height (ClutterActor *self,
+                                       gfloat        for_width,
+                                       gfloat       *min_height_p,
+                                       gfloat       *natural_height_p)
+{
+  StTextureFramePrivate *priv = ST_TEXTURE_FRAME (self)->priv;
+
+  if (G_UNLIKELY (priv->parent_texture == NULL))
+    {
+      if (min_height_p)
+        *min_height_p = 0;
+
+      if (natural_height_p)
+        *natural_height_p = 0;
+    }
+  else
+    {
+      ClutterActorClass *klass;
+
+      /* by directly querying the parent texture's class implementation
+       * we are going around any override mechanism the parent texture
+       * might have in place, and we ask directly for the original
+       * preferred height
+       */
+      klass = CLUTTER_ACTOR_GET_CLASS (priv->parent_texture);
+      klass->get_preferred_height (CLUTTER_ACTOR (priv->parent_texture),
+                                   for_width,
+                                   min_height_p,
+                                   natural_height_p);
+    }
+}
+
+static void
+st_texture_frame_paint (ClutterActor *self)
+{
+  StTextureFramePrivate *priv = ST_TEXTURE_FRAME (self)->priv;
+  CoglHandle cogl_texture = COGL_INVALID_HANDLE;
+  CoglHandle cogl_material = COGL_INVALID_HANDLE;
+  ClutterActorBox box = { 0, };
+  gfloat width, height;
+  gfloat tex_width, tex_height;
+  gfloat ex, ey;
+  gfloat tx1, ty1, tx2, ty2;
+  guint8 opacity;
+
+  /* no need to paint stuff if we don't have a texture */
+  if (G_UNLIKELY (priv->parent_texture == NULL))
+    return;
+
+  /* parent texture may have been hidden, so need to make sure it gets
+   * realized
+   */
+  if (!CLUTTER_ACTOR_IS_REALIZED (priv->parent_texture))
+    clutter_actor_realize (CLUTTER_ACTOR (priv->parent_texture));
+
+  cogl_texture = clutter_texture_get_cogl_texture (priv->parent_texture);
+  if (cogl_texture == COGL_INVALID_HANDLE)
+    return;
+  cogl_material = clutter_texture_get_cogl_material (priv->parent_texture);
+  if (cogl_material == COGL_INVALID_HANDLE)
+    return;
+
+  tex_width  = cogl_texture_get_width (cogl_texture);
+  tex_height = cogl_texture_get_height (cogl_texture);
+
+  clutter_actor_get_allocation_box (self, &box);
+  width = box.x2 - box.x1;
+  height = box.y2 - box.y1;
+
+  tx1 = priv->left / tex_width;
+  tx2 = (tex_width - priv->right) / tex_width;
+  ty1 = priv->top / tex_height;
+  ty2 = (tex_height - priv->bottom) / tex_height;
+
+  ex = width - priv->right;
+  if (ex < 0)
+    ex = priv->right;           /* FIXME ? */
+
+  ey = height - priv->bottom;
+  if (ey < 0)
+    ey = priv->bottom;          /* FIXME ? */
+
+  opacity = clutter_actor_get_paint_opacity (self);
+
+  /* Paint using the parent texture's material. It should already have
+     the cogl texture set as the first layer */
+  /* NB: for correct blending we need set a preumultiplied color here: */
+  cogl_material_set_color4ub (cogl_material,
+                              opacity, opacity, opacity, opacity);
+  cogl_set_source (cogl_material);
+
+  {
+    GLfloat rectangles[] =
+    {
+      /* top left corner */
+      0, 0, priv->left, priv->top,
+      0.0, 0.0,
+      tx1, ty1,
+
+      /* top middle */
+      priv->left, 0, ex, priv->top,
+      tx1, 0.0,
+      tx2, ty1,
+
+      /* top right */
+      ex, 0, width, priv->top,
+      tx2, 0.0,
+      1.0, ty1,
+
+      /* mid left */
+      0, priv->top, priv->left, ey,
+      0.0, ty1,
+      tx1, ty2,
+
+      /* center */
+      priv->left, priv->top, ex, ey,
+      tx1, ty1,
+      tx2, ty2,
+
+      /* mid right */
+      ex, priv->top, width, ey,
+      tx2, ty1,
+      1.0, ty2,
+
+      /* bottom left */
+      0, ey, priv->left, height,
+      0.0, ty2,
+      tx1, 1.0,
+
+      /* bottom center */
+      priv->left, ey, ex, height,
+      tx1, ty2,
+      tx2, 1.0,
+
+      /* bottom right */
+      ex, ey, width, height,
+      tx2, ty2,
+      1.0, 1.0
+    };
+
+    cogl_rectangles_with_texture_coords (rectangles, 9);
+  }
+}
+
+static inline void
+st_texture_frame_set_frame_internal (StTextureFrame *frame,
+                                     gfloat          top,
+                                     gfloat          right,
+                                     gfloat          bottom,
+                                     gfloat          left)
+{
+  StTextureFramePrivate *priv = frame->priv;
+  GObject *gobject = G_OBJECT (frame);
+  gboolean changed = FALSE;
+
+  g_object_freeze_notify (gobject);
+
+  if (priv->top != top)
+    {
+      priv->top = top;
+      g_object_notify (gobject, "top");
+      changed = TRUE;
+    }
+
+  if (priv->right != right)
+    {
+      priv->right = right;
+      g_object_notify (gobject, "right");
+      changed = TRUE;
+    }
+
+  if (priv->bottom != bottom)
+    {
+      priv->bottom = bottom;
+      g_object_notify (gobject, "bottom");
+      changed = TRUE;
+    }
+
+  if (priv->left != left)
+    {
+      priv->left = left;
+      g_object_notify (gobject, "left");
+      changed = TRUE;
+    }
+
+  if (changed && CLUTTER_ACTOR_IS_VISIBLE (frame))
+    clutter_actor_queue_redraw (CLUTTER_ACTOR (frame));
+
+  g_object_thaw_notify (gobject);
+}
+
+static void
+st_texture_frame_set_property (GObject      *gobject,
+                               guint         prop_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+{
+  StTextureFrame *frame = ST_TEXTURE_FRAME (gobject);
+  StTextureFramePrivate *priv = frame->priv;
+
+  switch (prop_id)
+    {
+    case PROP_PARENT_TEXTURE:
+      st_texture_frame_set_parent_texture (frame,
+                                           g_value_get_object (value));
+      break;
+
+    case PROP_TOP:
+      st_texture_frame_set_frame_internal (frame,
+                                           g_value_get_float (value),
+                                           priv->right,
+                                           priv->bottom,
+                                           priv->left);
+      break;
+
+    case PROP_RIGHT:
+      st_texture_frame_set_frame_internal (frame,
+                                           priv->top,
+                                           g_value_get_float (value),
+                                           priv->bottom,
+                                           priv->left);
+      break;
+
+    case PROP_BOTTOM:
+      st_texture_frame_set_frame_internal (frame,
+                                           priv->top,
+                                           priv->right,
+                                           g_value_get_float (value),
+                                           priv->left);
+      break;
+
+    case PROP_LEFT:
+      st_texture_frame_set_frame_internal (frame,
+                                           priv->top,
+                                           priv->right,
+                                           priv->bottom,
+                                           g_value_get_float (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+st_texture_frame_get_property (GObject    *gobject,
+                               guint       prop_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+  StTextureFramePrivate *priv = ST_TEXTURE_FRAME (gobject)->priv;
+
+  switch (prop_id)
+    {
+    case PROP_PARENT_TEXTURE:
+      g_value_set_object (value, priv->parent_texture);
+      break;
+
+    case PROP_LEFT:
+      g_value_set_float (value, priv->left);
+      break;
+
+    case PROP_TOP:
+      g_value_set_float (value, priv->top);
+      break;
+
+    case PROP_RIGHT:
+      g_value_set_float (value, priv->right);
+      break;
+
+    case PROP_BOTTOM:
+      g_value_set_float (value, priv->bottom);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+st_texture_frame_dispose (GObject *gobject)
+{
+  StTextureFramePrivate *priv = ST_TEXTURE_FRAME (gobject)->priv;
+
+  if (priv->parent_texture)
+    {
+      g_object_unref (priv->parent_texture);
+      priv->parent_texture = NULL;
+    }
+
+  G_OBJECT_CLASS (st_texture_frame_parent_class)->dispose (gobject);
+}
+
+static void
+st_texture_frame_class_init (StTextureFrameClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+  GParamSpec *pspec;
+
+  g_type_class_add_private (gobject_class, sizeof (StTextureFramePrivate));
+
+  actor_class->get_preferred_width =
+    st_texture_frame_get_preferred_width;
+  actor_class->get_preferred_height =
+    st_texture_frame_get_preferred_height;
+  actor_class->paint = st_texture_frame_paint;
+
+  gobject_class->set_property = st_texture_frame_set_property;
+  gobject_class->get_property = st_texture_frame_get_property;
+  gobject_class->dispose = st_texture_frame_dispose;
+
+  pspec = g_param_spec_object ("parent-texture",
+                               "Parent Texture",
+                               "The parent ClutterTexture",
+                               CLUTTER_TYPE_TEXTURE,
+                               ST_PARAM_READWRITE |
+                               G_PARAM_CONSTRUCT);
+  g_object_class_install_property (gobject_class, PROP_PARENT_TEXTURE, pspec);
+
+  pspec = g_param_spec_float ("left",
+                              "Left",
+                              "Left offset",
+                              0, G_MAXFLOAT,
+                              0,
+                              ST_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_LEFT, pspec);
+
+  pspec = g_param_spec_float ("top",
+                              "Top",
+                              "Top offset",
+                              0, G_MAXFLOAT,
+                              0,
+                              ST_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_TOP, pspec);
+
+  pspec = g_param_spec_float ("bottom",
+                              "Bottom",
+                              "Bottom offset",
+                              0, G_MAXFLOAT,
+                              0,
+                              ST_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_BOTTOM, pspec);
+
+  pspec = g_param_spec_float ("right",
+                              "Right",
+                              "Right offset",
+                              0, G_MAXFLOAT,
+                              0,
+                              ST_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_RIGHT, pspec);
+}
+
+static void
+st_texture_frame_init (StTextureFrame *self)
+{
+  StTextureFramePrivate *priv;
+
+  self->priv = priv = ST_TEXTURE_FRAME_GET_PRIVATE (self);
+}
+
+/**
+ * st_texture_frame_new:
+ * @texture: a #ClutterTexture or %NULL
+ * @left: left margin preserving its content
+ * @top: top margin preserving its content
+ * @right: right margin preserving its content
+ * @bottom: bottom margin preserving its content
+ *
+ * A #StTextureFrame is a specialized texture that efficiently clones
+ * an area of the given @texture while keeping preserving portions of the
+ * same texture.
+ *
+ * A #StTextureFrame can be used to make a rectangular texture fit a
+ * given size without stretching its borders.
+ *
+ * Return value: the newly created #StTextureFrame
+ */
+ClutterActor*
+st_texture_frame_new (ClutterTexture *texture,
+                      gfloat          top,
+                      gfloat          right,
+                      gfloat          bottom,
+                      gfloat          left)
+{
+  g_return_val_if_fail (texture == NULL || CLUTTER_IS_TEXTURE (texture), NULL);
+
+  return g_object_new (ST_TYPE_TEXTURE_FRAME,
+                       "parent-texture", texture,
+                       "top", top,
+                       "right", right,
+                       "bottom", bottom,
+                       "left", left,
+                       NULL);
+}
+
+/**
+ * st_texture_frame_get_parent_texture:
+ * @frame: A #StTextureFrame
+ *
+ * Return the texture used by the #StTextureFrame
+ *
+ * Returns: a #ClutterTexture owned by the #StTextureFrame
+ */
+ClutterTexture *
+st_texture_frame_get_parent_texture (StTextureFrame *frame)
+{
+  g_return_val_if_fail (ST_IS_TEXTURE_FRAME (frame), NULL);
+
+  return frame->priv->parent_texture;
+}
+
+/**
+ * st_texture_frame_set_parent_texture:
+ * @frame: A #StTextureFrame
+ * @texture: A #ClutterTexture
+ *
+ * Set the #ClutterTexture used by this #StTextureFrame
+ *
+ */
+void
+st_texture_frame_set_parent_texture (StTextureFrame *frame,
+                                     ClutterTexture *texture)
+{
+  StTextureFramePrivate *priv;
+  gboolean was_visible;
+
+  g_return_if_fail (ST_IS_TEXTURE_FRAME (frame));
+  g_return_if_fail (texture == NULL || CLUTTER_IS_TEXTURE (texture));
+
+  priv = frame->priv;
+
+  was_visible = CLUTTER_ACTOR_IS_VISIBLE (frame);
+
+  if (priv->parent_texture == texture)
+    return;
+
+  if (priv->parent_texture)
+    {
+      g_object_unref (priv->parent_texture);
+      priv->parent_texture = NULL;
+
+      if (was_visible)
+        clutter_actor_hide (CLUTTER_ACTOR (frame));
+    }
+
+  if (texture)
+    {
+      priv->parent_texture = g_object_ref_sink (texture);
+
+      if (was_visible && CLUTTER_ACTOR_IS_VISIBLE (priv->parent_texture))
+        clutter_actor_show (CLUTTER_ACTOR (frame));
+    }
+
+  clutter_actor_queue_relayout (CLUTTER_ACTOR (frame));
+
+  g_object_notify (G_OBJECT (frame), "parent-texture");
+}
+
+/**
+ * st_texture_frame_set_frame:
+ * @frame: A #StTextureFrame
+ * @top: width of the top slice
+ * @right: width of the right slice
+ * @bottom: width of the bottom slice
+ * @left: width of the left slice
+ *
+ * Set the slice lines of the specified frame. The slices are calculated as
+ * widths from the edge of the frame.
+ *
+ */
+void
+st_texture_frame_set_frame (StTextureFrame *frame,
+                            gfloat          top,
+                            gfloat          right,
+                            gfloat          bottom,
+                            gfloat          left)
+{
+  g_return_if_fail (ST_IS_TEXTURE_FRAME (frame));
+
+  st_texture_frame_set_frame_internal (frame, top, right, bottom, left);
+}
+
+/**
+ * st_texture_frame_get_frame:
+ * @frame: A #StTextureFrame
+ * @top: width of the top slice
+ * @right: width of the right slice
+ * @bottom: width of the bottom slice
+ * @left: width of the left slice
+ *
+ * Retrieve the current slice lines from the specified frame.
+ *
+ */
+void
+st_texture_frame_get_frame (StTextureFrame *frame,
+                            gfloat         *top,
+                            gfloat         *right,
+                            gfloat         *bottom,
+                            gfloat         *left)
+{
+  StTextureFramePrivate *priv;
+
+  g_return_if_fail (ST_IS_TEXTURE_FRAME (frame));
+
+  priv = frame->priv;
+
+  if (top)
+    *top = priv->top;
+
+  if (right)
+    *right = priv->right;
+
+  if (bottom)
+    *bottom = priv->bottom;
+
+  if (left)
+    *left = priv->left;
+}
diff --git a/src/st/st-texture-frame.h b/src/st/st-texture-frame.h
new file mode 100644
index 0000000..240a1c0
--- /dev/null
+++ b/src/st/st-texture-frame.h
@@ -0,0 +1,93 @@
+/*
+ * st-texture-frame.h: Expandible texture actor
+ *
+ * Copyright 2007, 2008 OpenedHand Ltd
+ * Copyright 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_TEXTURE_FRAME_H__
+#define __ST_TEXTURE_FRAME_H__
+
+#include <clutter/clutter.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_TEXTURE_FRAME                 (st_texture_frame_get_type ())
+#define ST_TEXTURE_FRAME(obj)                 (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_TEXTURE_FRAME, StTextureFrame))
+#define ST_TEXTURE_FRAME_CLASS(klass)         (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_TEXTURE_FRAME, StTextureFrameClass))
+#define ST_IS_TEXTURE_FRAME(obj)              (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ST_TYPE_TEXTURE_FRAME))
+#define ST_IS_TEXTURE_FRAME_CLASS(klass)      (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_TEXTURE_FRAME))
+#define ST_TEXTURE_FRAME_GET_CLASS(obj)       (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_TEXTURE_FRAME, StTextureFrameClass))
+
+typedef struct _StTextureFrame                StTextureFrame;
+typedef struct _StTextureFramePrivate         StTextureFramePrivate;
+typedef struct _StTextureFrameClass           StTextureFrameClass;
+
+/**
+ * StTextureFrame:
+ *
+ * The contents of this structure are private and should only be accessed
+ * through the public API.
+ */
+struct _StTextureFrame
+{
+  /*< private >*/
+  ClutterActor parent_instance;
+  
+  StTextureFramePrivate    *priv;
+};
+
+struct _StTextureFrameClass 
+{
+  ClutterActorClass parent_class;
+
+  /* padding for future expansion */
+  void (*_clutter_box_1) (void);
+  void (*_clutter_box_2) (void);
+  void (*_clutter_box_3) (void);
+  void (*_clutter_box_4) (void);
+}; 
+
+GType st_texture_frame_get_type (void) G_GNUC_CONST;
+
+ClutterActor *  st_texture_frame_new                (ClutterTexture *texture,
+                                                     gfloat          top,
+                                                     gfloat          right,
+                                                     gfloat          bottom,
+                                                     gfloat          left);
+void            st_texture_frame_set_parent_texture (StTextureFrame *frame,
+                                                     ClutterTexture *texture);
+ClutterTexture *st_texture_frame_get_parent_texture (StTextureFrame *frame);
+void            st_texture_frame_set_frame          (StTextureFrame *frame,
+                                                     gfloat          top,
+                                                     gfloat          right,
+                                                     gfloat          bottom,
+                                                     gfloat          left);
+void            st_texture_frame_get_frame          (StTextureFrame *frame,
+                                                     gfloat         *top,
+                                                     gfloat         *right,
+                                                     gfloat         *bottom,
+                                                     gfloat         *left);
+
+G_END_DECLS
+
+#endif /* __ST_TEXTURE_FRAME_H__ */
diff --git a/src/st/st-tooltip.c b/src/st/st-tooltip.c
new file mode 100644
index 0000000..5070945
--- /dev/null
+++ b/src/st/st-tooltip.c
@@ -0,0 +1,710 @@
+/*
+ * st-tooltip.c: Plain tooltip actor
+ *
+ * Copyright 2008, 2009 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ * Boston, MA 02111-1307, USA.
+ *
+ * Written by: Thomas Wood <thomas linux intel com>
+ *
+ */
+
+/**
+ * SECTION:st-tooltip
+ * @short_description: A tooltip widget
+ *
+ * #StTooltip implements a single tooltip. It should not normally be created
+ * by the application but by the widget implementing tooltip capabilities, for
+ * example, #st_button_set_tooltip().
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib.h>
+
+#include <clutter/clutter.h>
+
+#include "st-tooltip.h"
+
+#include "st-widget.h"
+#include "st-stylable.h"
+#include "st-private.h"
+
+enum
+{
+  PROP_0,
+
+  PROP_LABEL,
+  PROP_TIP_AREA
+};
+
+#define ST_TOOLTIP_GET_PRIVATE(obj)    \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_TOOLTIP, StTooltipPrivate))
+
+struct _StTooltipPrivate
+{
+  ClutterActor    *label;
+
+  gfloat           arrow_offset;
+  gboolean         actor_below;
+
+  ClutterGeometry *tip_area;
+};
+
+G_DEFINE_TYPE (StTooltip, st_tooltip, ST_TYPE_WIDGET);
+
+static void
+st_tooltip_set_property (GObject      *gobject,
+                         guint         prop_id,
+                         const GValue *value,
+                         GParamSpec   *pspec)
+{
+  StTooltip *tooltip = ST_TOOLTIP (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_LABEL:
+      st_tooltip_set_label (tooltip, g_value_get_string (value));
+      break;
+
+    case PROP_TIP_AREA:
+      st_tooltip_set_tip_area (tooltip, g_value_get_boxed (value));
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+st_tooltip_get_property (GObject    *gobject,
+                         guint       prop_id,
+                         GValue     *value,
+                         GParamSpec *pspec)
+{
+  StTooltipPrivate *priv = ST_TOOLTIP (gobject)->priv;
+
+  switch (prop_id)
+    {
+    case PROP_LABEL:
+      g_value_set_string (value, clutter_text_get_text (CLUTTER_TEXT (priv->label)));
+      break;
+
+    case PROP_TIP_AREA:
+      g_value_set_boxed (value, priv->tip_area);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+st_tooltip_style_changed (StWidget *self)
+{
+  ClutterColor *color = NULL;
+  StTooltipPrivate *priv;
+  gchar *font_name;
+  gchar *font_string;
+  gint font_size;
+
+  priv = ST_TOOLTIP (self)->priv;
+
+  st_stylable_get (ST_STYLABLE (self),
+                   "color", &color,
+                   "font-family", &font_name,
+                   "font-size", &font_size,
+                   NULL);
+
+  if (color)
+    {
+      clutter_text_set_color (CLUTTER_TEXT (priv->label), color);
+      clutter_color_free (color);
+    }
+
+  if (font_name || font_size)
+    {
+      if (font_name && font_size)
+        {
+          font_string = g_strdup_printf ("%s %dpx", font_name, font_size);
+          g_free (font_name);
+        }
+      else
+      if (font_size)
+        font_string = g_strdup_printf ("%dpx", font_size);
+      else
+        font_string = font_name;
+
+      clutter_text_set_font_name (CLUTTER_TEXT (priv->label), font_string);
+
+      g_free (font_string);
+    }
+
+}
+
+static void
+st_tooltip_get_preferred_width (ClutterActor *self,
+                                gfloat        for_height,
+                                gfloat       *min_width_p,
+                                gfloat       *natural_width_p)
+{
+  StTooltipPrivate *priv = ST_TOOLTIP (self)->priv;
+  gfloat min_label_w, natural_label_w;
+  gfloat label_height, arrow_height;
+  ClutterActor *arrow_image;
+  StPadding padding;
+
+  st_widget_get_padding (ST_WIDGET (self), &padding);
+
+  arrow_image = st_widget_get_background_image (ST_WIDGET (self));
+  if (arrow_image)
+    {
+      clutter_actor_get_preferred_height (arrow_image,
+                                          -1,
+                                          NULL,
+                                          &arrow_height);
+    }
+  else
+    {
+      arrow_height = 0;
+    }
+
+  if (for_height > -1)
+    {
+      label_height = for_height - arrow_height - padding.top - padding.bottom;
+    }
+  else
+    {
+      label_height = -1;
+    }
+
+  if (priv->label)
+    {
+      clutter_actor_get_preferred_width (priv->label,
+                                         label_height,
+                                         &min_label_w,
+                                         &natural_label_w);
+    }
+  else
+    {
+      min_label_w = 0;
+      natural_label_w = 0;
+    }
+
+
+  if (min_width_p)
+    {
+      *min_width_p = padding.left + padding.right + min_label_w;
+    }
+
+  if (natural_width_p)
+    {
+      *natural_width_p = padding.left + padding.right + natural_label_w;
+    }
+}
+
+static void
+st_tooltip_get_preferred_height (ClutterActor *self,
+                                 gfloat        for_width,
+                                 gfloat       *min_height_p,
+                                 gfloat       *natural_height_p)
+{
+  StTooltipPrivate *priv = ST_TOOLTIP (self)->priv;
+  gfloat arrow_height;
+  gfloat min_label_h, natural_label_h;
+  gfloat label_width;
+  ClutterActor *arrow_image;
+  StPadding padding;
+
+  arrow_image = st_widget_get_background_image (ST_WIDGET (self));
+
+  if (arrow_image && !priv->actor_below)
+    {
+      clutter_actor_get_preferred_height (arrow_image,
+                                          -1,
+                                          NULL,
+                                          &arrow_height);
+    }
+  else
+    {
+      arrow_height = 0;
+    }
+  st_widget_get_padding (ST_WIDGET (self), &padding);
+
+  if (for_width > -1)
+    {
+      label_width = for_width - padding.left - padding.right;
+    }
+  else
+    {
+      label_width = -1;
+    }
+
+  if (priv->label)
+    {
+      clutter_actor_get_preferred_height (priv->label,
+                                          label_width,
+                                          &min_label_h,
+                                          &natural_label_h);
+    }
+  else
+    {
+      min_label_h = 0;
+      natural_label_h = 0;
+    }
+
+  if (min_height_p)
+    {
+      *min_height_p = padding.top + padding.bottom
+                      + arrow_height + min_label_h;
+    }
+
+  if (natural_height_p)
+    {
+      *natural_height_p = padding.top + padding.bottom
+                          + arrow_height + natural_label_h;
+    }
+}
+
+static void
+st_tooltip_allocate (ClutterActor          *self,
+                     const ClutterActorBox *box,
+                     ClutterAllocationFlags flags)
+{
+  StTooltipPrivate *priv = ST_TOOLTIP (self)->priv;
+  ClutterActorBox child_box, arrow_box;
+  gfloat arrow_height, arrow_width;
+  ClutterActor *border_image, *arrow_image;
+  StPadding padding;
+
+  CLUTTER_ACTOR_CLASS (st_tooltip_parent_class)->allocate (self,
+                                                           box,
+                                                           flags);
+
+  st_widget_get_padding (ST_WIDGET (self), &padding);
+
+  arrow_image = st_widget_get_background_image (ST_WIDGET (self));
+
+  if (arrow_image && !priv->actor_below)
+    {
+      clutter_actor_get_preferred_height (arrow_image, -1, NULL, &arrow_height);
+      clutter_actor_get_preferred_width (arrow_image, -1, NULL, &arrow_width);
+
+      arrow_box.x1 = (float)(priv->arrow_offset) - (int)(arrow_width / 2);
+      arrow_box.y1 = 0;
+      arrow_box.x2 = arrow_box.x1 + arrow_width;
+      arrow_box.y2 = arrow_box.y1 + arrow_height;
+
+      clutter_actor_allocate (arrow_image, &arrow_box, flags);
+    }
+  else
+    {
+      arrow_height = 0;
+      arrow_width = 0;
+    }
+
+  child_box.x1 = child_box.y1 = 0;
+  child_box.x2 = (box->x2 - box->x1);
+  child_box.y2 = (box->y2 - box->y1);
+
+  /* remove the space that is used by the arrow */
+  child_box.y1 += arrow_height;
+
+  border_image = st_widget_get_border_image (ST_WIDGET (self));
+  if (border_image)
+    clutter_actor_allocate (border_image, &child_box, flags);
+
+  if (priv->label)
+    {
+      /* now remove the padding */
+      child_box.y1 += padding.top;
+      child_box.x1 += padding.left;
+      child_box.x2 -= padding.right;
+      child_box.y2 -= padding.bottom;
+
+      clutter_actor_allocate (priv->label, &child_box, flags);
+    }
+}
+
+
+static void
+st_tooltip_paint (ClutterActor *self)
+{
+  ClutterActor *border_image, *arrow_image;
+  StTooltipPrivate *priv = ST_TOOLTIP (self)->priv;
+
+  border_image = st_widget_get_border_image (ST_WIDGET (self));
+  if (border_image)
+    clutter_actor_paint (border_image);
+
+  arrow_image = st_widget_get_background_image (ST_WIDGET (self));
+  if (arrow_image && !priv->actor_below)
+    clutter_actor_paint (arrow_image);
+
+  clutter_actor_paint (priv->label);
+}
+
+static void
+st_tooltip_map (ClutterActor *self)
+{
+  StTooltipPrivate *priv = ST_TOOLTIP (self)->priv;
+  ClutterActor *border_image, *arrow_image;
+
+  CLUTTER_ACTOR_CLASS (st_tooltip_parent_class)->map (self);
+
+  border_image = st_widget_get_border_image (ST_WIDGET (self));
+  if (border_image)
+    clutter_actor_map (border_image);
+
+  arrow_image = st_widget_get_background_image (ST_WIDGET (self));
+  if (arrow_image)
+    clutter_actor_map (arrow_image);
+
+  clutter_actor_map (priv->label);
+}
+
+static void
+st_tooltip_unmap (ClutterActor *self)
+{
+  StTooltipPrivate *priv = ST_TOOLTIP (self)->priv;
+  ClutterActor *border_image, *arrow_image;
+
+  CLUTTER_ACTOR_CLASS (st_tooltip_parent_class)->unmap (self);
+
+  border_image = st_widget_get_border_image (ST_WIDGET (self));
+  if (border_image)
+    clutter_actor_unmap (border_image);
+
+  arrow_image = st_widget_get_background_image (ST_WIDGET (self));
+  if (arrow_image)
+    clutter_actor_unmap (arrow_image);
+
+  clutter_actor_unmap (priv->label);
+}
+
+static void
+st_tooltip_class_init (StTooltipClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+  GParamSpec *pspec;
+
+  g_type_class_add_private (klass, sizeof (StTooltipPrivate));
+
+  gobject_class->set_property = st_tooltip_set_property;
+  gobject_class->get_property = st_tooltip_get_property;
+
+  actor_class->get_preferred_width = st_tooltip_get_preferred_width;
+  actor_class->get_preferred_height = st_tooltip_get_preferred_height;
+  actor_class->allocate = st_tooltip_allocate;
+  actor_class->paint = st_tooltip_paint;
+  actor_class->map = st_tooltip_map;
+  actor_class->unmap = st_tooltip_unmap;
+
+  pspec = g_param_spec_string ("label",
+                               "Label",
+                               "Label of the tooltip",
+                               NULL, G_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_LABEL, pspec);
+
+  pspec = g_param_spec_boxed ("tip-area",
+                              "Tip Area",
+                              "Area on the stage the tooltip applies to",
+                              CLUTTER_TYPE_GEOMETRY,
+                              ST_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_TIP_AREA, pspec);
+}
+
+static void
+st_tooltip_init (StTooltip *tooltip)
+{
+  tooltip->priv = ST_TOOLTIP_GET_PRIVATE (tooltip);
+
+  tooltip->priv->label = g_object_new (CLUTTER_TYPE_TEXT,
+                                       "line-alignment", PANGO_ALIGN_CENTER,
+                                       "ellipsize", PANGO_ELLIPSIZE_END,
+                                       "use-markup", TRUE,
+                                       NULL);
+
+  tooltip->priv->tip_area = NULL;
+
+  clutter_actor_set_parent (CLUTTER_ACTOR (tooltip->priv->label),
+                            CLUTTER_ACTOR (tooltip));
+
+  g_object_set (tooltip, "show-on-set-parent", FALSE, NULL);
+
+  clutter_actor_set_reactive (CLUTTER_ACTOR (tooltip), FALSE);
+
+  g_signal_connect (tooltip, "style-changed",
+                    G_CALLBACK (st_tooltip_style_changed), NULL);
+}
+
+static void
+st_tooltip_update_position (StTooltip *tooltip)
+{
+  StTooltipPrivate *priv = tooltip->priv;
+  ClutterGeometry *tip_area = tooltip->priv->tip_area;
+  gfloat tooltip_w, tooltip_h, tooltip_x, tooltip_y;
+  gfloat stage_w, stage_h;
+  ClutterActor *stage;
+
+  /* ensure the tooltip with is not fixed size */
+  clutter_actor_set_size ((ClutterActor*) tooltip, -1, -1);
+
+  /* if no area set, just position ourselves top left */
+  if (!priv->tip_area)
+    {
+      clutter_actor_set_position ((ClutterActor*) tooltip, 0, 0);
+      return;
+    }
+
+  /* we need to have a style in case there are padding values to take into
+   * account when calculating width/height */
+  st_widget_ensure_style ((StWidget *) tooltip);
+
+  /* find out the tooltip's size */
+  clutter_actor_get_size ((ClutterActor*) tooltip, &tooltip_w, &tooltip_h);
+
+  /* attempt to place the tooltip */
+  tooltip_x = (int)(tip_area->x + (tip_area->width / 2) - (tooltip_w / 2));
+  tooltip_y = (int)(tip_area->y + tip_area->height);
+
+  stage = clutter_actor_get_stage ((ClutterActor *) tooltip);
+  if (!stage)
+    {
+      return;
+    }
+  clutter_actor_get_size (stage, &stage_w, &stage_h);
+
+  /* make sure the tooltip is not off screen vertically */
+  if (tooltip_w > stage_w)
+    {
+      tooltip_x = 0;
+      clutter_actor_set_width ((ClutterActor*) tooltip, stage_w);
+    }
+  else if (tooltip_x < 0)
+    {
+      tooltip_x = 0;
+    }
+  else if (tooltip_x + tooltip_w > stage_w)
+    {
+      tooltip_x = (int)(stage_w) - tooltip_w;
+    }
+
+  /* make sure the tooltip is not off screen horizontally */
+  if (tooltip_y + tooltip_h > stage_h)
+    {
+      priv->actor_below = TRUE;
+
+      /* re-query size as may have changed */
+      clutter_actor_get_preferred_height ((ClutterActor*) tooltip,
+                                          -1, NULL, &tooltip_h);
+      tooltip_y = tip_area->y - tooltip_h;
+    }
+  else
+    {
+      priv->actor_below = FALSE;
+    }
+
+  /* calculate the arrow offset */
+  priv->arrow_offset = tip_area->x + tip_area->width / 2 - tooltip_x;
+
+  clutter_actor_set_position ((ClutterActor*) tooltip, tooltip_x, tooltip_y);
+}
+
+/**
+ * st_tooltip_get_label:
+ * @tooltip: a #StTooltip
+ *
+ * Get the text displayed on the tooltip
+ *
+ * Returns: the text for the tooltip. This must not be freed by the application
+ */
+G_CONST_RETURN gchar *
+st_tooltip_get_label (StTooltip *tooltip)
+{
+  g_return_val_if_fail (ST_IS_TOOLTIP (tooltip), NULL);
+
+  return clutter_text_get_text (CLUTTER_TEXT (tooltip->priv->label));
+}
+
+/**
+ * st_tooltip_set_label:
+ * @tooltip: a #StTooltip
+ * @text: text to set the label to
+ *
+ * Sets the text displayed on the tooltip
+ */
+void
+st_tooltip_set_label (StTooltip   *tooltip,
+                      const gchar *text)
+{
+  StTooltipPrivate *priv;
+
+  g_return_if_fail (ST_IS_TOOLTIP (tooltip));
+
+  priv = tooltip->priv;
+
+  clutter_text_set_text (CLUTTER_TEXT (priv->label), text);
+
+  g_object_notify (G_OBJECT (tooltip), "label");
+}
+
+/**
+ * st_tooltip_show:
+ * @tooltip: a #StTooltip
+ *
+ * Show the tooltip relative to the associated widget.
+ */
+void
+st_tooltip_show (StTooltip *tooltip)
+{
+  StTooltipPrivate *priv;
+  ClutterActor *parent;
+  ClutterActor *stage;
+  ClutterActor *self = CLUTTER_ACTOR (tooltip);
+  ClutterAnimation *animation;
+
+  /* make sure we're not currently already animating (e.g. hiding) */
+  animation = clutter_actor_get_animation (CLUTTER_ACTOR (tooltip));
+  if (animation)
+    clutter_animation_completed (animation);
+
+  priv = tooltip->priv;
+  parent = clutter_actor_get_parent (self);
+  stage = clutter_actor_get_stage (self);
+
+  if (!stage)
+    {
+      g_warning ("StTooltip is not on any stage.");
+      return;
+    }
+
+  /* make sure we're parented on the stage */
+  if (G_UNLIKELY (parent != stage))
+    {
+      g_object_ref (self);
+      clutter_actor_unparent (self);
+      clutter_actor_set_parent (self, stage);
+      g_object_unref (self);
+      parent = stage;
+    }
+
+  /* raise the tooltip to the top */
+  clutter_container_raise_child (CLUTTER_CONTAINER (stage),
+                                 CLUTTER_ACTOR (tooltip),
+                                 NULL);
+
+  st_tooltip_update_position (tooltip);
+
+  /* finally show the tooltip... */
+  CLUTTER_ACTOR_CLASS (st_tooltip_parent_class)->show (self);
+
+  /* and give it some bounce! */
+  g_object_set (G_OBJECT (self),
+                "scale-center-x", priv->arrow_offset,
+                "scale-center-y", (priv->actor_below) ? clutter_actor_get_height (self) : 0,
+                NULL);
+  clutter_actor_set_scale (self, 0.0, 0.0);
+  clutter_actor_animate (self, CLUTTER_EASE_OUT_ELASTIC,
+                         500,
+                         "scale-x", 1.0,
+                         "scale-y", 1.0,
+                         NULL);
+}
+
+static void
+st_tooltip_hide_complete (ClutterAnimation *animation,
+                          ClutterActor     *actor)
+{
+  CLUTTER_ACTOR_CLASS (st_tooltip_parent_class)->hide (actor);
+  g_signal_handlers_disconnect_by_func (actor,
+                                        st_tooltip_hide_complete,
+                                        actor);
+}
+
+/**
+ * st_tooltip_hide:
+ * @tooltip: a #StTooltip
+ *
+ * Hide the tooltip
+ */
+void
+st_tooltip_hide (StTooltip *tooltip)
+{
+  ClutterAnimation *animation;
+
+  g_return_if_fail (ST_TOOLTIP (tooltip));
+
+  /* make sure we're not currently already animating (e.g. hiding) */
+  animation = clutter_actor_get_animation (CLUTTER_ACTOR (tooltip));
+  if (animation)
+    clutter_animation_completed (animation);
+
+  g_object_set (G_OBJECT (tooltip),
+                "scale-center-x", tooltip->priv->arrow_offset,
+                NULL);
+  animation =
+    clutter_actor_animate (CLUTTER_ACTOR (tooltip), CLUTTER_EASE_IN_SINE,
+                           150,
+                           "scale-x", 0.0,
+                           "scale-y", 0.0,
+                           NULL);
+  g_signal_connect (animation, "completed",
+                    G_CALLBACK (st_tooltip_hide_complete), tooltip);
+}
+
+/**
+ * st_tooltip_set_tip_area:
+ * @tooltip: A #StTooltip
+ * @area: A #ClutterGeometry
+ *
+ * Set the area on the stage that the tooltip applies to.
+ */
+void
+st_tooltip_set_tip_area (StTooltip             *tooltip,
+                         const ClutterGeometry *area)
+{
+  g_return_if_fail (ST_IS_TOOLTIP (tooltip));
+
+  if (tooltip->priv->tip_area)
+    g_boxed_free (CLUTTER_TYPE_GEOMETRY, tooltip->priv->tip_area);
+  tooltip->priv->tip_area = g_boxed_copy (CLUTTER_TYPE_GEOMETRY, area);
+
+  st_tooltip_update_position (tooltip);
+}
+
+/**
+ * st_tooltip_get_tip_area:
+ * @tooltip: A #StTooltip
+ *
+ * Retrieve the area on the stage that the tooltip currently applies to
+ *
+ * Returns: the #ClutterGeometry, owned by the tooltip which must not be freed
+ * by the application.
+ */
+G_CONST_RETURN ClutterGeometry*
+st_tooltip_get_tip_area (StTooltip *tooltip)
+{
+  g_return_val_if_fail (ST_IS_TOOLTIP (tooltip), NULL);
+
+  return tooltip->priv->tip_area;
+}
diff --git a/src/st/st-tooltip.h b/src/st/st-tooltip.h
new file mode 100644
index 0000000..b2cef3e
--- /dev/null
+++ b/src/st/st-tooltip.h
@@ -0,0 +1,80 @@
+/*
+ * st-tooltip.h: Plain tooltip actor
+ *
+ * Copyright 2008, 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Written by: Thomas Wood <thomas linux intel com>
+ *
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_TOOLTIP_H__
+#define __ST_TOOLTIP_H__
+
+G_BEGIN_DECLS
+
+#include <st/st-bin.h>
+
+#define ST_TYPE_TOOLTIP                (st_tooltip_get_type ())
+#define ST_TOOLTIP(obj)                (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_TOOLTIP, StTooltip))
+#define ST_IS_TOOLTIP(obj)             (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ST_TYPE_TOOLTIP))
+#define ST_TOOLTIP_CLASS(klass)        (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_TOOLTIP, StTooltipClass))
+#define ST_IS_TOOLTIP_CLASS(klass)     (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_TOOLTIP))
+#define ST_TOOLTIP_GET_CLASS(obj)      (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_TOOLTIP, StTooltipClass))
+
+typedef struct _StTooltip              StTooltip;
+typedef struct _StTooltipPrivate       StTooltipPrivate;
+typedef struct _StTooltipClass         StTooltipClass;
+
+/**
+ * StTooltip:
+ *
+ * The contents of this structure is private and should only be accessed using
+ * the provided API.
+ */
+struct _StTooltip
+{
+  /*< private >*/
+  StBin parent_instance;
+
+  StTooltipPrivate *priv;
+};
+
+struct _StTooltipClass
+{
+  StBinClass parent_class;
+};
+
+GType st_tooltip_get_type (void) G_GNUC_CONST;
+
+G_CONST_RETURN gchar *st_tooltip_get_label (StTooltip   *tooltip);
+void                  st_tooltip_set_label (StTooltip   *tooltip,
+                                            const gchar *text);
+void                  st_tooltip_show      (StTooltip   *tooltip);
+void                  st_tooltip_hide      (StTooltip   *tooltip);
+
+void                            st_tooltip_set_tip_area (StTooltip             *tooltip,
+                                                         const ClutterGeometry *area);
+G_CONST_RETURN ClutterGeometry* st_tooltip_get_tip_area (StTooltip             *tooltip);
+
+G_END_DECLS
+
+#endif /* __ST_TOOLTIP_H__ */
diff --git a/src/st/st-types.h b/src/st/st-types.h
new file mode 100644
index 0000000..2049945
--- /dev/null
+++ b/src/st/st-types.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/**
+ * SECTION:st-types
+ * @short_description: type definitions used throughout St
+ *
+ * Common types for StWidgets.
+ */
+
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_TYPES_H__
+#define __ST_TYPES_H__
+
+#include <glib-object.h>
+#include <clutter/clutter.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_BORDER_IMAGE          (st_border_image_get_type ())
+#define ST_TYPE_PADDING               (st_padding_get_type ())
+
+typedef struct _StPadding             StPadding;
+
+GType st_border_image_get_type (void) G_GNUC_CONST;
+
+/**
+ * StPadding:
+ * @top: padding from the top
+ * @right: padding from the right
+ * @bottom: padding from the bottom
+ * @left: padding from the left
+ *
+ * The padding from the internal border of the parent container.
+ */
+struct _StPadding
+{
+  gfloat top;
+  gfloat right;
+  gfloat bottom;
+  gfloat left;
+};
+
+GType st_padding_get_type (void) G_GNUC_CONST;
+
+typedef enum {
+  ST_ALIGN_START,
+  ST_ALIGN_MIDDLE,
+  ST_ALIGN_END
+} StAlign;
+
+G_END_DECLS
+
+#endif /* __ST_TYPES_H__ */
diff --git a/src/st/st-widget.c b/src/st/st-widget.c
new file mode 100644
index 0000000..bcc4a2c
--- /dev/null
+++ b/src/st/st-widget.c
@@ -0,0 +1,1357 @@
+/*
+ * st-widget.c: Base class for St actors
+ *
+ * Copyright 2007 OpenedHand
+ * Copyright 2008, 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ * Boston, MA 02111-1307, USA.
+ *
+ * Written by: Emmanuele Bassi <ebassi openedhand com>
+ *             Thomas Wood <thomas linux intel com>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <clutter/clutter.h>
+#include <ccss/ccss.h>
+
+#include "st-widget.h"
+
+#include "st-marshal.h"
+#include "st-private.h"
+#include "st-stylable.h"
+#include "st-texture-cache.h"
+#include "st-texture-frame.h"
+#include "st-tooltip.h"
+
+typedef ccss_border_image_t StBorderImage;
+
+/*
+ * Forward declaration for sake of StWidgetChild
+ */
+struct _StWidgetPrivate
+{
+  StPadding     border;
+  StPadding     padding;
+
+  StStyle      *style;
+  gchar        *pseudo_class;
+  gchar        *style_class;
+
+  ClutterActor *border_image;
+  ClutterActor *background_image;
+  ClutterColor *bg_color;
+
+  gboolean      is_stylable : 1;
+  gboolean      has_tooltip : 1;
+  gboolean      is_style_dirty : 1;
+
+  StTooltip    *tooltip;
+};
+
+/**
+ * SECTION:st-widget
+ * @short_description: Base class for stylable actors
+ *
+ * #StWidget is a simple abstract class on top of #ClutterActor. It
+ * provides basic themeing properties.
+ *
+ * Actors in the St library should subclass #StWidget if they plan
+ * to obey to a certain #StStyle.
+ */
+
+enum
+{
+  PROP_0,
+
+  PROP_STYLE,
+  PROP_PSEUDO_CLASS,
+  PROP_STYLE_CLASS,
+
+  PROP_STYLABLE,
+
+  PROP_HAS_TOOLTIP,
+  PROP_TOOLTIP_TEXT
+};
+
+static void st_stylable_iface_init (StStylableIface *iface);
+
+
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (StWidget, st_widget, CLUTTER_TYPE_ACTOR,
+                                  G_IMPLEMENT_INTERFACE (ST_TYPE_STYLABLE,
+                                                         st_stylable_iface_init));
+
+#define ST_WIDGET_GET_PRIVATE(obj)    (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_WIDGET, StWidgetPrivate))
+
+static void
+st_widget_set_property (GObject      *gobject,
+                        guint         prop_id,
+                        const GValue *value,
+                        GParamSpec   *pspec)
+{
+  StWidget *actor = ST_WIDGET (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_STYLE:
+      st_stylable_set_style (ST_STYLABLE (actor),
+                             g_value_get_object (value));
+      break;
+
+    case PROP_PSEUDO_CLASS:
+      st_widget_set_style_pseudo_class (actor, g_value_get_string (value));
+      break;
+
+    case PROP_STYLE_CLASS:
+      st_widget_set_style_class_name (actor, g_value_get_string (value));
+      break;
+
+    case PROP_STYLABLE:
+      if (actor->priv->is_stylable != g_value_get_boolean (value))
+        {
+          actor->priv->is_stylable = g_value_get_boolean (value);
+          clutter_actor_queue_relayout ((ClutterActor *) gobject);
+        }
+      break;
+
+    case PROP_HAS_TOOLTIP:
+      st_widget_set_has_tooltip (actor, g_value_get_boolean (value));
+      break;
+
+    case PROP_TOOLTIP_TEXT:
+      st_widget_set_tooltip_text (actor, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+st_widget_get_property (GObject    *gobject,
+                        guint       prop_id,
+                        GValue     *value,
+                        GParamSpec *pspec)
+{
+  StWidget *actor = ST_WIDGET (gobject);
+  StWidgetPrivate *priv = actor->priv;
+
+  switch (prop_id)
+    {
+    case PROP_STYLE:
+      g_value_set_object (value, priv->style);
+      break;
+
+    case PROP_PSEUDO_CLASS:
+      g_value_set_string (value, priv->pseudo_class);
+      break;
+
+    case PROP_STYLE_CLASS:
+      g_value_set_string (value, priv->style_class);
+      break;
+
+    case PROP_STYLABLE:
+      g_value_set_boolean (value, priv->is_stylable);
+      break;
+
+    case PROP_HAS_TOOLTIP:
+      g_value_set_boolean (value, priv->has_tooltip);
+      break;
+
+    case PROP_TOOLTIP_TEXT:
+      g_value_set_string (value, st_widget_get_tooltip_text (actor));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+st_widget_dispose (GObject *gobject)
+{
+  StWidget *actor = ST_WIDGET (gobject);
+  StWidgetPrivate *priv = ST_WIDGET (actor)->priv;
+
+  if (priv->style)
+    {
+      g_object_unref (priv->style);
+      priv->style = NULL;
+    }
+
+  if (priv->border_image)
+    {
+      clutter_actor_unparent (priv->border_image);
+      priv->border_image = NULL;
+    }
+
+  if (priv->bg_color)
+    {
+      clutter_color_free (priv->bg_color);
+      priv->bg_color = NULL;
+    }
+
+  if (priv->tooltip)
+    {
+      ClutterContainer *parent;
+      ClutterActor *tooltip = CLUTTER_ACTOR (priv->tooltip);
+
+      /* this is just a little bit awkward because the tooltip is parented
+       * on the stage, but we still want to "own" it */
+      parent = CLUTTER_CONTAINER (clutter_actor_get_parent (tooltip));
+
+      if (parent)
+        clutter_container_remove_actor (parent, tooltip);
+
+      priv->tooltip = NULL;
+    }
+
+  G_OBJECT_CLASS (st_widget_parent_class)->dispose (gobject);
+}
+
+static void
+st_widget_finalize (GObject *gobject)
+{
+  StWidgetPrivate *priv = ST_WIDGET (gobject)->priv;
+
+  g_free (priv->style_class);
+  g_free (priv->pseudo_class);
+
+  G_OBJECT_CLASS (st_widget_parent_class)->finalize (gobject);
+}
+
+static void
+st_widget_allocate (ClutterActor          *actor,
+                    const ClutterActorBox *box,
+                    ClutterAllocationFlags flags)
+{
+  StWidgetPrivate *priv = ST_WIDGET (actor)->priv;
+  ClutterActorClass *klass;
+  ClutterGeometry area;
+  ClutterVertex in_v, out_v;
+
+  klass = CLUTTER_ACTOR_CLASS (st_widget_parent_class);
+  klass->allocate (actor, box, flags);
+
+  /* update tooltip position */
+  if (priv->tooltip)
+    {
+      in_v.x = in_v.y = in_v.z = 0;
+      clutter_actor_apply_transform_to_point (actor, &in_v, &out_v);
+      area.x = out_v.x;
+      area.y = out_v.y;
+
+      in_v.x = box->x2 - box->x1;
+      in_v.y = box->y2 - box->y1;
+      clutter_actor_apply_transform_to_point (actor, &in_v, &out_v);
+      area.width = out_v.x - area.x;
+      area.height = out_v.y - area.y;
+
+      st_tooltip_set_tip_area (priv->tooltip, &area);
+    }
+
+
+
+  if (priv->border_image)
+    {
+      ClutterActorBox frame_box = {
+        0,
+        0,
+        box->x2 - box->x1,
+        box->y2 - box->y1
+      };
+
+      clutter_actor_allocate (CLUTTER_ACTOR (priv->border_image),
+                              &frame_box,
+                              flags);
+    }
+
+  if (priv->background_image)
+    {
+      ClutterActorBox frame_box = {
+        0, 0, box->x2 - box->x1, box->y2 - box->y1
+      };
+      gfloat w, h;
+
+      clutter_actor_get_size (CLUTTER_ACTOR (priv->background_image), &w, &h);
+
+      /* scale the background into the allocated bounds */
+      if (w > frame_box.x2 || h > frame_box.y2)
+        {
+          gint new_h, new_w, offset;
+          gint box_w, box_h;
+
+          box_w = (int) frame_box.x2;
+          box_h = (int) frame_box.y2;
+
+          /* scale to fit */
+          new_h = (int)((h / w) * ((gfloat) box_w));
+          new_w = (int)((w / h) * ((gfloat) box_h));
+
+          if (new_h > box_h)
+            {
+              /* center for new width */
+              offset = ((box_w) - new_w) * 0.5;
+              frame_box.x1 = offset;
+              frame_box.x2 = offset + new_w;
+
+              frame_box.y2 = box_h;
+            }
+          else
+            {
+              /* center for new height */
+              offset = ((box_h) - new_h) * 0.5;
+              frame_box.y1 = offset;
+              frame_box.y2 = offset + new_h;
+
+              frame_box.x2 = box_w;
+            }
+
+        }
+      else
+        {
+          /* center the background on the widget */
+          frame_box.x1 = (int)(((box->x2 - box->x1) / 2) - (w / 2));
+          frame_box.y1 = (int)(((box->y2 - box->y1) / 2) - (h / 2));
+          frame_box.x2 = frame_box.x1 + w;
+          frame_box.y2 = frame_box.y1 + h;
+        }
+
+      clutter_actor_allocate (CLUTTER_ACTOR (priv->background_image),
+                              &frame_box,
+                              flags);
+    }
+}
+
+static void
+st_widget_real_draw_background (StWidget           *self,
+                                ClutterActor       *background,
+                                const ClutterColor *color)
+{
+  /* Default implementation just draws the background
+   * colour and the image on top
+   */
+  if (color && color->alpha != 0)
+    {
+      ClutterActor *actor = CLUTTER_ACTOR (self);
+      ClutterActorBox allocation = { 0, };
+      ClutterColor bg_color = *color;
+      gfloat w, h;
+
+      bg_color.alpha = clutter_actor_get_paint_opacity (actor)
+                       * bg_color.alpha
+                       / 255;
+
+      clutter_actor_get_allocation_box (actor, &allocation);
+
+      w = allocation.x2 - allocation.x1;
+      h = allocation.y2 - allocation.y1;
+
+      cogl_set_source_color4ub (bg_color.red,
+                                bg_color.green,
+                                bg_color.blue,
+                                bg_color.alpha);
+      cogl_rectangle (0, 0, w, h);
+    }
+
+  if (background)
+    clutter_actor_paint (background);
+}
+
+static void
+st_widget_paint (ClutterActor *self)
+{
+  StWidgetPrivate *priv = ST_WIDGET (self)->priv;
+  StWidgetClass *klass = ST_WIDGET_GET_CLASS (self);
+
+  klass->draw_background (ST_WIDGET (self),
+                          priv->border_image,
+                          priv->bg_color);
+
+  if (priv->background_image != NULL)
+    clutter_actor_paint (priv->background_image);
+}
+
+static void
+st_widget_parent_set (ClutterActor *widget,
+                      ClutterActor *old_parent)
+{
+  ClutterActorClass *parent_class;
+  ClutterActor *new_parent;
+
+  parent_class = CLUTTER_ACTOR_CLASS (st_widget_parent_class);
+  if (parent_class->parent_set)
+    parent_class->parent_set (widget, old_parent);
+
+  new_parent = clutter_actor_get_parent (widget);
+
+  /* don't send the style changed signal if we no longer have a parent actor */
+  if (new_parent)
+    st_stylable_changed ((StStylable*) widget);
+}
+
+static void
+st_widget_map (ClutterActor *actor)
+{
+  StWidgetPrivate *priv = ST_WIDGET (actor)->priv;
+
+  CLUTTER_ACTOR_CLASS (st_widget_parent_class)->map (actor);
+
+  st_widget_ensure_style ((StWidget*) actor);
+
+  if (priv->border_image)
+    clutter_actor_map (priv->border_image);
+
+  if (priv->background_image)
+    clutter_actor_map (priv->background_image);
+
+  if (priv->tooltip)
+    clutter_actor_map ((ClutterActor *) priv->tooltip);
+}
+
+static void
+st_widget_unmap (ClutterActor *actor)
+{
+  StWidgetPrivate *priv = ST_WIDGET (actor)->priv;
+
+  CLUTTER_ACTOR_CLASS (st_widget_parent_class)->unmap (actor);
+
+  if (priv->border_image)
+    clutter_actor_unmap (priv->border_image);
+
+  if (priv->background_image)
+    clutter_actor_unmap (priv->background_image);
+
+  if (priv->tooltip)
+    clutter_actor_unmap ((ClutterActor *) priv->tooltip);
+}
+
+static void
+st_widget_style_changed (StStylable *self)
+{
+  StWidgetPrivate *priv = ST_WIDGET (self)->priv;
+  StBorderImage *border_image = NULL;
+  StTextureCache *texture_cache;
+  ClutterTexture *texture;
+  gchar *bg_file = NULL;
+  StPadding *padding = NULL;
+  gboolean relayout_needed = FALSE;
+  gboolean has_changed = FALSE;
+  ClutterColor *color;
+
+  /* application has request this widget is not stylable */
+  if (!priv->is_stylable)
+    return;
+
+  /* cache these values for use in the paint function */
+  st_stylable_get (self,
+                   "background-color", &color,
+                   "background-image", &bg_file,
+                   "border-image", &border_image,
+                   "padding", &padding,
+                   NULL);
+
+  if (color)
+    {
+      if (priv->bg_color && clutter_color_equal (color, priv->bg_color))
+        {
+          /* color is the same ... */
+          clutter_color_free (color);
+        }
+      else
+        {
+          clutter_color_free (priv->bg_color);
+          priv->bg_color = color;
+          has_changed = TRUE;
+        }
+    }
+  else
+  if (priv->bg_color)
+    {
+      clutter_color_free (priv->bg_color);
+      priv->bg_color = NULL;
+      has_changed = TRUE;
+    }
+
+
+
+  if (padding)
+    {
+      if (priv->padding.top != padding->top ||
+          priv->padding.left != padding->left ||
+          priv->padding.right != padding->right ||
+          priv->padding.bottom != padding->bottom)
+        {
+          /* Padding changed. Need to relayout. */
+          has_changed = TRUE;
+          relayout_needed = TRUE;
+        }
+
+      priv->padding = *padding;
+      g_boxed_free (ST_TYPE_PADDING, padding);
+    }
+
+  if (priv->border_image)
+    {
+      clutter_actor_unparent (priv->border_image);
+      priv->border_image = NULL;
+    }
+
+  if (priv->background_image)
+    {
+      clutter_actor_unparent (priv->background_image);
+      priv->background_image = NULL;
+    }
+
+  texture_cache = st_texture_cache_get_default ();
+
+  /* Check if the URL is actually present, not garbage in the property */
+  if (border_image && border_image->uri)
+    {
+      gint border_left, border_right, border_top, border_bottom;
+      gint width, height;
+
+      /* `border-image' takes precedence over `background-image'.
+       * Firefox lets the background-image shine thru when border-image has
+       * alpha an channel, maybe that would be an option for the future. */
+      texture = st_texture_cache_get_texture (texture_cache,
+                                              border_image->uri);
+
+      clutter_texture_get_base_size (CLUTTER_TEXTURE (texture),
+                                     &width, &height);
+
+      border_left = ccss_position_get_size (&border_image->left, width);
+      border_top = ccss_position_get_size (&border_image->top, height);
+      border_right = ccss_position_get_size (&border_image->right, width);
+      border_bottom = ccss_position_get_size (&border_image->bottom, height);
+
+      priv->border_image = st_texture_frame_new (texture,
+                                                 border_top,
+                                                 border_right,
+                                                 border_bottom,
+                                                 border_left);
+      clutter_actor_set_parent (priv->border_image, CLUTTER_ACTOR (self));
+      g_boxed_free (ST_TYPE_BORDER_IMAGE, border_image);
+
+      has_changed = TRUE;
+      relayout_needed = TRUE;
+    }
+
+  if (bg_file != NULL &&
+      strcmp (bg_file, "none"))
+    {
+      texture = st_texture_cache_get_texture (texture_cache, bg_file);
+      priv->background_image = (ClutterActor*) texture;
+
+      if (priv->background_image != NULL)
+        {
+          clutter_actor_set_parent (priv->background_image,
+                                    CLUTTER_ACTOR (self));
+        }
+      else
+        g_warning ("Could not load %s", bg_file);
+
+      has_changed = TRUE;
+      relayout_needed = TRUE;
+    }
+  g_free (bg_file);
+
+  /* If there are any properties above that need to cause a relayout thay
+   * should set this flag.
+   */
+  if (has_changed)
+    {
+      if (relayout_needed)
+        clutter_actor_queue_relayout ((ClutterActor *) self);
+      else
+        clutter_actor_queue_redraw ((ClutterActor *) self);
+    }
+
+  priv->is_style_dirty = FALSE;
+}
+
+static void
+st_widget_stylable_child_notify (ClutterActor *actor,
+                                 gpointer      user_data)
+{
+  if (ST_IS_STYLABLE (actor))
+    st_stylable_changed ((StStylable*) actor);
+}
+
+static void
+st_widget_stylable_changed (StStylable *stylable)
+{
+
+  ST_WIDGET (stylable)->priv->is_style_dirty = TRUE;
+
+  /* update the style only if we are mapped */
+  if (!CLUTTER_ACTOR_IS_MAPPED ((ClutterActor *) stylable))
+    return;
+
+  g_signal_emit_by_name (stylable, "style-changed", 0);
+
+
+  if (CLUTTER_IS_CONTAINER (stylable))
+    {
+      /* notify our children that their parent stylable has changed */
+      clutter_container_foreach ((ClutterContainer *) stylable,
+                                 st_widget_stylable_child_notify,
+                                 NULL);
+    }
+}
+
+static gboolean
+st_widget_enter (ClutterActor         *actor,
+                 ClutterCrossingEvent *event)
+{
+  StWidgetPrivate *priv = ST_WIDGET (actor)->priv;
+
+
+  if (priv->has_tooltip)
+    st_widget_show_tooltip ((StWidget*) actor);
+
+  if (CLUTTER_ACTOR_CLASS (st_widget_parent_class)->enter_event)
+    return CLUTTER_ACTOR_CLASS (st_widget_parent_class)->enter_event (actor, event);
+  else
+    return FALSE;
+}
+
+static gboolean
+st_widget_leave (ClutterActor         *actor,
+                 ClutterCrossingEvent *event)
+{
+  StWidgetPrivate *priv = ST_WIDGET (actor)->priv;
+
+  if (priv->has_tooltip)
+    st_tooltip_hide (priv->tooltip);
+
+  if (CLUTTER_ACTOR_CLASS (st_widget_parent_class)->leave_event)
+    return CLUTTER_ACTOR_CLASS (st_widget_parent_class)->leave_event (actor, event);
+  else
+    return FALSE;
+}
+
+static void
+st_widget_hide (ClutterActor *actor)
+{
+  StWidget *widget = (StWidget *) actor;
+
+  /* hide the tooltip, if there is one */
+  if (widget->priv->tooltip)
+    st_tooltip_hide (ST_TOOLTIP (widget->priv->tooltip));
+
+  CLUTTER_ACTOR_CLASS (st_widget_parent_class)->hide (actor);
+}
+
+
+
+static void
+st_widget_class_init (StWidgetClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+  GParamSpec *pspec;
+
+  g_type_class_add_private (klass, sizeof (StWidgetPrivate));
+
+  gobject_class->set_property = st_widget_set_property;
+  gobject_class->get_property = st_widget_get_property;
+  gobject_class->dispose = st_widget_dispose;
+  gobject_class->finalize = st_widget_finalize;
+
+  actor_class->allocate = st_widget_allocate;
+  actor_class->paint = st_widget_paint;
+  actor_class->parent_set = st_widget_parent_set;
+  actor_class->map = st_widget_map;
+  actor_class->unmap = st_widget_unmap;
+
+  actor_class->enter_event = st_widget_enter;
+  actor_class->leave_event = st_widget_leave;
+  actor_class->hide = st_widget_hide;
+
+  klass->draw_background = st_widget_real_draw_background;
+
+  /**
+   * StWidget:pseudo-class:
+   *
+   * The pseudo-class of the actor. Typical values include "hover", "active",
+   * "focus".
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_PSEUDO_CLASS,
+                                   g_param_spec_string ("pseudo-class",
+                                                        "Pseudo Class",
+                                                        "Pseudo class for styling",
+                                                        "",
+                                                        ST_PARAM_READWRITE));
+  /**
+   * StWidget:style-class:
+   *
+   * The style-class of the actor for use in styling.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_STYLE_CLASS,
+                                   g_param_spec_string ("style-class",
+                                                        "Style Class",
+                                                        "Style class for styling",
+                                                        "",
+                                                        ST_PARAM_READWRITE));
+
+  g_object_class_override_property (gobject_class, PROP_STYLE, "style");
+
+  /**
+   * StWidget:stylable:
+   *
+   * Enable or disable styling of the widget
+   */
+  pspec = g_param_spec_boolean ("stylable",
+                                "Stylable",
+                                "Whether the table should be styled",
+                                TRUE,
+                                ST_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class,
+                                   PROP_STYLABLE,
+                                   pspec);
+
+  /**
+   * StWidget:has-tooltip:
+   *
+   * Determines whether the widget has a tooltip. If set to TRUE, causes the
+   * widget to monitor enter and leave events (i.e. sets the widget reactive).
+   */
+  pspec = g_param_spec_boolean ("has-tooltip",
+                                "Has Tooltip",
+                                "Determines whether the widget has a tooltip",
+                                FALSE,
+                                ST_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class,
+                                   PROP_HAS_TOOLTIP,
+                                   pspec);
+
+
+  /**
+   * StWidget:tooltip-text:
+   *
+   * text displayed on the tooltip
+   */
+  pspec = g_param_spec_string ("tooltip-text",
+                               "Tooltip Text",
+                               "Text displayed on the tooltip",
+                               "",
+                               ST_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_TOOLTIP_TEXT, pspec);
+
+}
+
+static StStyle *
+st_widget_get_style (StStylable *stylable)
+{
+  StWidgetPrivate *priv = ST_WIDGET (stylable)->priv;
+
+  return priv->style;
+}
+
+static void
+st_style_changed_cb (StStyle    *style,
+                     StStylable *stylable)
+{
+  st_stylable_changed (stylable);
+}
+
+
+static void
+st_widget_set_style (StStylable *stylable,
+                     StStyle    *style)
+{
+  StWidgetPrivate *priv = ST_WIDGET (stylable)->priv;
+
+  if (priv->style)
+    g_object_unref (priv->style);
+
+  priv->style = g_object_ref_sink (style);
+
+  g_signal_connect (priv->style,
+                    "changed",
+                    G_CALLBACK (st_style_changed_cb),
+                    stylable);
+}
+
+static StStylable*
+st_widget_get_container (StStylable *stylable)
+{
+  ClutterActor *parent;
+
+  g_return_val_if_fail (ST_IS_WIDGET (stylable), NULL);
+
+  parent = clutter_actor_get_parent (CLUTTER_ACTOR (stylable));
+
+  if (ST_IS_STYLABLE (parent))
+    return ST_STYLABLE (parent);
+  else
+    return NULL;
+}
+
+static StStylable*
+st_widget_get_base_style (StStylable *stylable)
+{
+  return NULL;
+}
+
+static const gchar*
+st_widget_get_style_id (StStylable *stylable)
+{
+  g_return_val_if_fail (ST_IS_WIDGET (stylable), NULL);
+
+  return clutter_actor_get_name (CLUTTER_ACTOR (stylable));
+}
+
+static const gchar*
+st_widget_get_style_type (StStylable *stylable)
+{
+  return G_OBJECT_TYPE_NAME (stylable);
+}
+
+static const gchar*
+st_widget_get_style_class (StStylable *stylable)
+{
+  g_return_val_if_fail (ST_IS_WIDGET (stylable), NULL);
+
+  return ST_WIDGET (stylable)->priv->style_class;
+}
+
+static const gchar*
+st_widget_get_pseudo_class (StStylable *stylable)
+{
+  g_return_val_if_fail (ST_IS_WIDGET (stylable), NULL);
+
+  return ST_WIDGET (stylable)->priv->pseudo_class;
+}
+
+static gboolean
+st_widget_get_viewport (StStylable *stylable,
+                        gint       *x,
+                        gint       *y,
+                        gint       *width,
+                        gint       *height)
+{
+  g_return_val_if_fail (ST_IS_WIDGET (stylable), FALSE);
+
+  *x = 0;
+  *y = 0;
+
+  *width = clutter_actor_get_width (CLUTTER_ACTOR (stylable));
+  *height = clutter_actor_get_height (CLUTTER_ACTOR (stylable));
+
+  return TRUE;
+}
+
+/**
+ * st_widget_set_style_class_name:
+ * @actor: a #StWidget
+ * @style_class: a new style class string
+ *
+ * Set the style class name
+ */
+void
+st_widget_set_style_class_name (StWidget    *actor,
+                                const gchar *style_class)
+{
+  StWidgetPrivate *priv = actor->priv;
+
+  g_return_if_fail (ST_WIDGET (actor));
+
+  priv = actor->priv;
+
+  if (g_strcmp0 (style_class, priv->style_class))
+    {
+      g_free (priv->style_class);
+      priv->style_class = g_strdup (style_class);
+
+      st_stylable_changed ((StStylable*) actor);
+
+      g_object_notify (G_OBJECT (actor), "style-class");
+    }
+}
+
+
+/**
+ * st_widget_get_style_class_name:
+ * @actor: a #StWidget
+ *
+ * Get the current style class name
+ *
+ * Returns: the class name string. The string is owned by the #StWidget and
+ * should not be modified or freed.
+ */
+const gchar*
+st_widget_get_style_class_name (StWidget *actor)
+{
+  g_return_val_if_fail (ST_WIDGET (actor), NULL);
+
+  return actor->priv->style_class;
+}
+
+/**
+ * st_widget_get_style_pseudo_class:
+ * @actor: a #StWidget
+ *
+ * Get the current style pseudo class
+ *
+ * Returns: the pseudo class string. The string is owned by the #StWidget and
+ * should not be modified or freed.
+ */
+const gchar*
+st_widget_get_style_pseudo_class (StWidget *actor)
+{
+  g_return_val_if_fail (ST_WIDGET (actor), NULL);
+
+  return actor->priv->pseudo_class;
+}
+
+/**
+ * st_widget_set_style_pseudo_class:
+ * @actor: a #StWidget
+ * @pseudo_class: a new pseudo class string
+ *
+ * Set the style pseudo class
+ */
+void
+st_widget_set_style_pseudo_class (StWidget    *actor,
+                                  const gchar *pseudo_class)
+{
+  StWidgetPrivate *priv;
+
+  g_return_if_fail (ST_WIDGET (actor));
+
+  priv = actor->priv;
+
+  if (g_strcmp0 (pseudo_class, priv->pseudo_class))
+    {
+      g_free (priv->pseudo_class);
+      priv->pseudo_class = g_strdup (pseudo_class);
+
+      st_stylable_changed ((StStylable*) actor);
+
+      g_object_notify (G_OBJECT (actor), "pseudo-class");
+    }
+}
+
+
+static void
+st_stylable_iface_init (StStylableIface *iface)
+{
+  static gboolean is_initialized = FALSE;
+
+  if (!is_initialized)
+    {
+      GParamSpec *pspec;
+      ClutterColor color = { 0x00, 0x00, 0x00, 0xff };
+      ClutterColor bg_color = { 0xff, 0xff, 0xff, 0x00 };
+
+      is_initialized = TRUE;
+
+      pspec = clutter_param_spec_color ("background-color",
+                                        "Background Color",
+                                        "The background color of an actor",
+                                        &bg_color,
+                                        G_PARAM_READWRITE);
+      st_stylable_iface_install_property (iface, ST_TYPE_WIDGET, pspec);
+
+      pspec = clutter_param_spec_color ("color",
+                                        "Text Color",
+                                        "The color of the text of an actor",
+                                        &color,
+                                        G_PARAM_READWRITE);
+      st_stylable_iface_install_property (iface, ST_TYPE_WIDGET, pspec);
+
+      pspec = g_param_spec_string ("background-image",
+                                   "Background Image",
+                                   "Background image filename",
+                                   NULL,
+                                   G_PARAM_READWRITE);
+      st_stylable_iface_install_property (iface, ST_TYPE_WIDGET, pspec);
+
+      pspec = g_param_spec_string ("font-family",
+                                   "Font Family",
+                                   "Name of the font to use",
+                                   "Sans",
+                                   G_PARAM_READWRITE);
+      st_stylable_iface_install_property (iface, ST_TYPE_WIDGET, pspec);
+
+      pspec = g_param_spec_int ("font-size",
+                                "Font Size",
+                                "Size of the font to use in pixels",
+                                0, G_MAXINT, 12,
+                                G_PARAM_READWRITE);
+      st_stylable_iface_install_property (iface, ST_TYPE_WIDGET, pspec);
+
+      pspec = g_param_spec_boxed ("border-image",
+                                  "Border image",
+                                  "9-slice image to use for drawing borders and background",
+                                  ST_TYPE_BORDER_IMAGE,
+                                  G_PARAM_READWRITE);
+      st_stylable_iface_install_property (iface, ST_TYPE_WIDGET, pspec);
+
+      pspec = g_param_spec_boxed ("padding",
+                                  "Padding",
+                                  "Padding between the widget's borders "
+                                  "and its content",
+                                  ST_TYPE_PADDING,
+                                  G_PARAM_READWRITE);
+      st_stylable_iface_install_property (iface, ST_TYPE_WIDGET, pspec);
+
+      iface->style_changed = st_widget_style_changed;
+      iface->stylable_changed = st_widget_stylable_changed;
+
+      iface->get_style = st_widget_get_style;
+      iface->set_style = st_widget_set_style;
+      iface->get_base_style = st_widget_get_base_style;
+      iface->get_container = st_widget_get_container;
+      iface->get_style_id = st_widget_get_style_id;
+      iface->get_style_type = st_widget_get_style_type;
+      iface->get_style_class = st_widget_get_style_class;
+      iface->get_pseudo_class = st_widget_get_pseudo_class;
+      /* iface->get_attribute = st_widget_get_attribute; */
+      iface->get_viewport = st_widget_get_viewport;
+    }
+}
+
+
+static void
+st_widget_name_notify (StWidget   *widget,
+                       GParamSpec *pspec,
+                       gpointer    data)
+{
+  st_stylable_changed ((StStylable*) widget);
+}
+
+static void
+st_widget_init (StWidget *actor)
+{
+  StWidgetPrivate *priv;
+
+  actor->priv = priv = ST_WIDGET_GET_PRIVATE (actor);
+  priv->is_stylable = TRUE;
+
+  /* connect style changed */
+  g_signal_connect (actor, "notify::name", G_CALLBACK (st_widget_name_notify), NULL);
+
+  /* set the default style */
+  st_widget_set_style (ST_STYLABLE (actor), st_style_get_default ());
+
+}
+
+static StBorderImage *
+st_border_image_copy (const StBorderImage *border_image)
+{
+  StBorderImage *copy;
+
+  g_return_val_if_fail (border_image != NULL, NULL);
+
+  copy = g_slice_new (StBorderImage);
+  *copy = *border_image;
+
+  return copy;
+}
+
+static void
+st_border_image_free (StBorderImage *border_image)
+{
+  if (G_LIKELY (border_image))
+    g_slice_free (StBorderImage, border_image);
+}
+
+GType
+st_border_image_get_type (void)
+{
+  static GType our_type = 0;
+
+  if (G_UNLIKELY (our_type == 0))
+    our_type =
+      g_boxed_type_register_static (I_("StBorderImage"),
+                                    (GBoxedCopyFunc) st_border_image_copy,
+                                    (GBoxedFreeFunc) st_border_image_free);
+
+  return our_type;
+}
+
+/**
+ * st_widget_ensure_style:
+ * @widget: A #StWidget
+ *
+ * Ensures that @widget has read its style information.
+ *
+ */
+void
+st_widget_ensure_style (StWidget *widget)
+{
+  g_return_if_fail (ST_IS_WIDGET (widget));
+
+  if (widget->priv->is_style_dirty)
+    {
+      g_signal_emit_by_name (widget, "style-changed", 0);
+    }
+}
+
+
+/**
+ * st_widget_get_border_image:
+ * @actor: A #StWidget
+ *
+ * Get the texture used as the border image. This is set using the
+ * "border-image" CSS property. This function should normally only be used
+ * by subclasses.
+ *
+ * Returns: #ClutterActor
+ */
+ClutterActor *
+st_widget_get_border_image (StWidget *actor)
+{
+  StWidgetPrivate *priv = ST_WIDGET (actor)->priv;
+  return priv->border_image;
+}
+
+/**
+ * st_widget_get_background_image:
+ * @actor: A #StWidget
+ *
+ * Get the texture used as the background image. This is set using the
+ * "background-image" CSS property. This function should normally only be used
+ * by subclasses.
+ *
+ * Returns: a #ClutterActor
+ */
+ClutterActor *
+st_widget_get_background_image (StWidget *actor)
+{
+  StWidgetPrivate *priv = ST_WIDGET (actor)->priv;
+  return priv->background_image;
+}
+
+/**
+ * st_widget_get_padding:
+ * @widget: A #StWidget
+ * @padding: A pointer to an #StPadding to fill
+ *
+ * Gets the padding of the widget, set using the "padding" CSS property. This
+ * function should normally only be used by subclasses.
+ *
+ */
+void
+st_widget_get_padding (StWidget  *widget,
+                       StPadding *padding)
+{
+  g_return_if_fail (ST_IS_WIDGET (widget));
+  g_return_if_fail (padding != NULL);
+
+  *padding = widget->priv->padding;
+}
+
+/**
+ * st_widget_set_has_tooltip:
+ * @widget: A #StWidget
+ * @has_tooltip: #TRUE if the widget should display a tooltip
+ *
+ * Enables tooltip support on the #StWidget.
+ *
+ * Note that setting has-tooltip to #TRUE will cause the widget to be set
+ * reactive. If you no longer need tooltip support and do not need the widget
+ * to be reactive, you need to set ClutterActor::reactive to FALSE.
+ *
+ */
+void
+st_widget_set_has_tooltip (StWidget *widget,
+                           gboolean  has_tooltip)
+{
+  StWidgetPrivate *priv;
+
+  g_return_if_fail (ST_IS_WIDGET (widget));
+
+  priv = widget->priv;
+
+  priv->has_tooltip = has_tooltip;
+
+  if (has_tooltip)
+    {
+      clutter_actor_set_reactive ((ClutterActor*) widget, TRUE);
+
+      if (!priv->tooltip)
+        {
+          priv->tooltip = g_object_new (ST_TYPE_TOOLTIP, NULL);
+          clutter_actor_set_parent ((ClutterActor *) priv->tooltip,
+                                    (ClutterActor *) widget);
+        }
+    }
+  else
+    {
+      if (priv->tooltip)
+        {
+          clutter_actor_unparent (CLUTTER_ACTOR (priv->tooltip));
+          priv->tooltip = NULL;
+        }
+    }
+}
+
+/**
+ * st_widget_get_has_tooltip:
+ * @widget: A #StWidget
+ *
+ * Returns the current value of the has-tooltip property. See
+ * st_tooltip_set_has_tooltip() for more information.
+ *
+ * Returns: current value of has-tooltip on @widget
+ */
+gboolean
+st_widget_get_has_tooltip (StWidget *widget)
+{
+  g_return_val_if_fail (ST_IS_WIDGET (widget), FALSE);
+
+  return widget->priv->has_tooltip;
+}
+
+/**
+ * st_widget_set_tooltip_text:
+ * @widget: A #StWidget
+ * @text: text to set as the tooltip
+ *
+ * Set the tooltip text of the widget. This will set StWidget::has-tooltip to
+ * #TRUE. A value of #NULL will unset the tooltip and set has-tooltip to #FALSE.
+ *
+ */
+void
+st_widget_set_tooltip_text (StWidget    *widget,
+                            const gchar *text)
+{
+  StWidgetPrivate *priv;
+
+  g_return_if_fail (ST_IS_WIDGET (widget));
+
+  priv = widget->priv;
+
+  if (text == NULL)
+    st_widget_set_has_tooltip (widget, FALSE);
+  else
+    st_widget_set_has_tooltip (widget, TRUE);
+
+  st_tooltip_set_label (priv->tooltip, text);
+}
+
+/**
+ * st_widget_get_tooltip_text:
+ * @widget: A #StWidget
+ *
+ * Get the current tooltip string
+ *
+ * Returns: The current tooltip string, owned by the #StWidget
+ */
+const gchar*
+st_widget_get_tooltip_text (StWidget *widget)
+{
+  StWidgetPrivate *priv;
+
+  g_return_val_if_fail (ST_IS_WIDGET (widget), NULL);
+  priv = widget->priv;
+
+  if (!priv->has_tooltip)
+    return NULL;
+
+  return st_tooltip_get_label (widget->priv->tooltip);
+}
+
+/**
+ * st_widget_show_tooltip:
+ * @widget: A #StWidget
+ *
+ * Show the tooltip for @widget
+ *
+ */
+void
+st_widget_show_tooltip (StWidget *widget)
+{
+  gfloat x, y, width, height;
+  ClutterGeometry area;
+
+  g_return_if_fail (ST_IS_WIDGET (widget));
+
+  /* XXX not necceary, but first allocate transform is wrong */
+
+  clutter_actor_get_transformed_position ((ClutterActor*) widget,
+                                          &x, &y);
+
+  clutter_actor_get_size ((ClutterActor*) widget, &width, &height);
+
+  area.x = x;
+  area.y = y;
+  area.width = width;
+  area.height = height;
+
+
+  if (widget->priv->tooltip)
+    {
+      st_tooltip_set_tip_area (widget->priv->tooltip, &area);
+      st_tooltip_show (widget->priv->tooltip);
+    }
+}
+
+/**
+ * st_widget_hide_tooltip:
+ * @widget: A #StWidget
+ *
+ * Hide the tooltip for @widget
+ *
+ */
+void
+st_widget_hide_tooltip (StWidget *widget)
+{
+  g_return_if_fail (ST_IS_WIDGET (widget));
+
+  if (widget->priv->tooltip)
+    st_tooltip_hide (widget->priv->tooltip);
+}
+
+/**
+ * st_widget_draw_background:
+ * @widget: a #StWidget
+ *
+ * Invokes #StWidget::draw_background() using the default background
+ * image and/or color from the @widget style
+ *
+ * This function should be used by subclasses of #StWidget that override
+ * the paint() virtual function and cannot chain up
+ */
+void
+st_widget_draw_background (StWidget *self)
+{
+  StWidgetPrivate *priv;
+  StWidgetClass *klass;
+
+  g_return_if_fail (ST_IS_WIDGET (self));
+
+  priv = self->priv;
+
+  klass = ST_WIDGET_GET_CLASS (self);
+  klass->draw_background (ST_WIDGET (self),
+                          priv->border_image,
+                          priv->bg_color);
+
+}
diff --git a/src/st/st-widget.h b/src/st/st-widget.h
new file mode 100644
index 0000000..1c5ee36
--- /dev/null
+++ b/src/st/st-widget.h
@@ -0,0 +1,108 @@
+/*
+ * st-widget.h: Base class for St actors
+ *
+ * Copyright 2007 OpenedHand
+ * Copyright 2008, 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_WIDGET_H__
+#define __ST_WIDGET_H__
+
+#include <clutter/clutter.h>
+#include <st/st-types.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_WIDGET                 (st_widget_get_type ())
+#define ST_WIDGET(obj)                 (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_WIDGET, StWidget))
+#define ST_IS_WIDGET(obj)              (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ST_TYPE_WIDGET))
+#define ST_WIDGET_CLASS(klass)         (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_WIDGET, StWidgetClass))
+#define ST_IS_WIDGET_CLASS(klass)      (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_WIDGET))
+#define ST_WIDGET_GET_CLASS(obj)       (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_WIDGET, StWidgetClass))
+
+typedef struct _StWidget               StWidget;
+typedef struct _StWidgetPrivate        StWidgetPrivate;
+typedef struct _StWidgetClass          StWidgetClass;
+
+/**
+ * StWidget:
+ *
+ * Base class for stylable actors. The contents of the #StWidget
+ * structure are private and should only be accessed through the
+ * public API.
+ */
+struct _StWidget
+{
+  /*< private >*/
+  ClutterActor parent_instance;
+
+  StWidgetPrivate *priv;
+};
+
+/**
+ * StWidgetClass:
+ *
+ * Base class for stylable actors.
+ */
+struct _StWidgetClass
+{
+  /*< private >*/
+  ClutterActorClass parent_class;
+
+  /* vfuncs */
+  void (* draw_background) (StWidget           *self,
+                            ClutterActor       *background,
+                            const ClutterColor *color);
+};
+
+GType st_widget_get_type (void) G_GNUC_CONST;
+
+void                  st_widget_set_style_pseudo_class (StWidget    *actor,
+                                                        const gchar *pseudo_class);
+G_CONST_RETURN gchar *st_widget_get_style_pseudo_class (StWidget    *actor);
+void                  st_widget_set_style_class_name   (StWidget    *actor,
+                                                        const gchar *style_class);
+G_CONST_RETURN gchar *st_widget_get_style_class_name   (StWidget    *actor);
+
+void         st_widget_set_has_tooltip  (StWidget    *widget,
+                                         gboolean     has_tooltip);
+gboolean     st_widget_get_has_tooltip  (StWidget    *widget);
+void         st_widget_set_tooltip_text (StWidget    *widget,
+                                         const gchar *text);
+const gchar* st_widget_get_tooltip_text (StWidget    *widget);
+
+void st_widget_show_tooltip (StWidget *widget);
+void st_widget_hide_tooltip (StWidget *widget);
+
+void st_widget_ensure_style (StWidget *widget);
+
+
+/* Only to be used by sub-classes of StWidget */
+ClutterActor *st_widget_get_background_image (StWidget  *actor);
+ClutterActor *st_widget_get_border_image     (StWidget  *actor);
+void          st_widget_get_padding          (StWidget  *widget,
+                                              StPadding *padding);
+void          st_widget_draw_background      (StWidget  *widget);
+
+G_END_DECLS
+
+#endif /* __ST_WIDGET_H__ */



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