Port our imported parts of Mx to ShellTheme

ShellTheme replaces both StStyle and ccss_stylesheet_t.

The interface StStylable is replaced by usage of ShellThemeNode.
A concrete node class allows some significant optimizations of property
inheritance that would have been much more difficult to achieve with
the highly abstract pair of StStylable and ccss_node_t.

Some operations that were previously on StStylable (like the
::style-changed signal) are directly on NtkWidget.

Custom properties are no longer registered as param-specs; instead you
call directly into shell theme node to look up a length or color:

shell_theme_node_get_length (theme_node, "border-spacing", FALSE, &spacing);

The dependency on libccss is dropped, while preserving all existing
functionality and adding proper parsing and inheritance of font properties
and proper inheritance for the 'color' property.

Some more javascript tests for CSS functionality are added; workarounds for
a CSS bug where *.some-class was needed instead of .some-class are removed.

https://bugzilla.gnome.org/show_bug.cgi?id=595990
This commit is contained in:
Owen W. Taylor
2009-09-19 21:10:15 -04:00
parent e91e8e993d
commit a9fd350396
24 changed files with 473 additions and 2554 deletions

View File

@ -32,19 +32,16 @@
#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-theme-context.h"
#include "st-tooltip.h"
typedef ccss_border_image_t StBorderImage;
/*
* Forward declaration for sake of StWidgetChild
*/
@ -53,13 +50,14 @@ struct _StWidgetPrivate
StPadding border;
StPadding padding;
StStyle *style;
StTheme *theme;
StThemeNode *theme_node;
gchar *pseudo_class;
gchar *style_class;
ClutterActor *border_image;
ClutterActor *background_image;
ClutterColor *bg_color;
ClutterColor bg_color;
gboolean is_stylable : 1;
gboolean has_tooltip : 1;
@ -83,7 +81,7 @@ enum
{
PROP_0,
PROP_STYLE,
PROP_THEME,
PROP_PSEUDO_CLASS,
PROP_STYLE_CLASS,
@ -93,12 +91,16 @@ enum
PROP_TOOLTIP_TEXT
};
static void st_stylable_iface_init (StStylableIface *iface);
enum
{
STYLE_CHANGED,
LAST_SIGNAL
};
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (StWidget, st_widget, CLUTTER_TYPE_ACTOR,
G_IMPLEMENT_INTERFACE (ST_TYPE_STYLABLE,
st_stylable_iface_init));
static guint signals[LAST_SIGNAL] = { 0, };
G_DEFINE_ABSTRACT_TYPE (StWidget, st_widget, CLUTTER_TYPE_ACTOR);
#define ST_WIDGET_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_WIDGET, StWidgetPrivate))
@ -112,9 +114,8 @@ st_widget_set_property (GObject *gobject,
switch (prop_id)
{
case PROP_STYLE:
st_stylable_set_style (ST_STYLABLE (actor),
g_value_get_object (value));
case PROP_THEME:
st_widget_set_theme (actor, g_value_get_object (value));
break;
case PROP_PSEUDO_CLASS:
@ -158,8 +159,8 @@ st_widget_get_property (GObject *gobject,
switch (prop_id)
{
case PROP_STYLE:
g_value_set_object (value, priv->style);
case PROP_THEME:
g_value_set_object (value, priv->theme);
break;
case PROP_PSEUDO_CLASS:
@ -194,10 +195,10 @@ st_widget_dispose (GObject *gobject)
StWidget *actor = ST_WIDGET (gobject);
StWidgetPrivate *priv = ST_WIDGET (actor)->priv;
if (priv->style)
if (priv->theme)
{
g_object_unref (priv->style);
priv->style = NULL;
g_object_unref (priv->theme);
priv->theme = NULL;
}
if (priv->border_image)
@ -206,12 +207,6 @@ st_widget_dispose (GObject *gobject)
priv->border_image = NULL;
}
if (priv->bg_color)
{
clutter_color_free (priv->bg_color);
priv->bg_color = NULL;
}
if (priv->tooltip)
{
ClutterContainer *parent;
@ -387,7 +382,7 @@ st_widget_paint (ClutterActor *self)
klass->draw_background (ST_WIDGET (self),
priv->border_image,
priv->bg_color);
&priv->bg_color);
if (priv->background_image != NULL)
clutter_actor_paint (priv->background_image);
@ -408,7 +403,7 @@ st_widget_parent_set (ClutterActor *widget,
/* don't send the style changed signal if we no longer have a parent actor */
if (new_parent)
st_stylable_changed ((StStylable*) widget);
st_widget_style_changed (ST_WIDGET (widget));
}
static void
@ -447,69 +442,67 @@ st_widget_unmap (ClutterActor *actor)
clutter_actor_unmap ((ClutterActor *) priv->tooltip);
}
static void notify_children_of_style_change (ClutterContainer *container);
static void
st_widget_style_changed (StStylable *self)
notify_children_of_style_change_foreach (ClutterActor *actor,
gpointer user_data)
{
if (ST_IS_WIDGET (actor))
st_widget_style_changed (ST_WIDGET (actor));
else if (CLUTTER_IS_CONTAINER (actor))
notify_children_of_style_change ((ClutterContainer *)actor);
}
static void
notify_children_of_style_change (ClutterContainer *container)
{
/* notify our children that their parent stylable has changed */
clutter_container_foreach (container,
notify_children_of_style_change_foreach,
NULL);
}
static void
st_widget_real_style_changed (StWidget *self)
{
StWidgetPrivate *priv = ST_WIDGET (self)->priv;
StBorderImage *border_image = NULL;
StThemeNode *theme_node;
StThemeImage *theme_image;
StTextureCache *texture_cache;
ClutterTexture *texture;
gchar *bg_file = NULL;
StPadding *padding = NULL;
const char *bg_file = NULL;
gboolean relayout_needed = FALSE;
gboolean has_changed = FALSE;
ClutterColor *color;
StPadding padding;
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);
theme_node = st_widget_get_theme_node (self);
if (color)
st_theme_node_get_background_color (theme_node, &color);
if (!clutter_color_equal (&color, &priv->bg_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;
priv->bg_color = color;
has_changed = TRUE;
}
padding.top = st_theme_node_get_padding (theme_node, ST_SIDE_TOP);
padding.right = st_theme_node_get_padding (theme_node, ST_SIDE_RIGHT);
padding.bottom = st_theme_node_get_padding (theme_node, ST_SIDE_BOTTOM);
padding.left = st_theme_node_get_padding (theme_node, ST_SIDE_LEFT);
if (padding)
if (priv->padding.top != padding.top ||
priv->padding.left != padding.left ||
priv->padding.right != padding.right ||
priv->padding.bottom != padding.bottom)
{
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);
priv->padding = padding;
has_changed = TRUE;
relayout_needed = TRUE;
}
if (priv->border_image)
@ -526,25 +519,26 @@ st_widget_style_changed (StStylable *self)
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)
theme_image = st_theme_node_get_background_theme_image (theme_node);
if (theme_image)
{
const char *filename;
gint border_left, border_right, border_top, border_bottom;
gint width, height;
filename = st_theme_image_get_filename (theme_image);
/* `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);
filename);
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);
st_theme_image_get_borders (theme_image,
&border_left, &border_right, &border_top, &border_bottom);
priv->border_image = st_texture_frame_new (texture,
border_top,
@ -552,14 +546,13 @@ st_widget_style_changed (StStylable *self)
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"))
bg_file = st_theme_node_get_background_image (theme_node);
if (bg_file != NULL)
{
texture = st_texture_cache_get_texture (texture_cache, bg_file);
priv->background_image = (ClutterActor*) texture;
@ -575,7 +568,6 @@ st_widget_style_changed (StStylable *self)
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.
@ -588,37 +580,89 @@ st_widget_style_changed (StStylable *self)
clutter_actor_queue_redraw ((ClutterActor *) self);
}
priv->is_style_dirty = FALSE;
if (CLUTTER_IS_CONTAINER (self))
notify_children_of_style_change ((ClutterContainer *)self);
}
static void
st_widget_stylable_child_notify (ClutterActor *actor,
gpointer user_data)
void
st_widget_style_changed (StWidget *widget)
{
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;
widget->priv->is_style_dirty = TRUE;
if (widget->priv->theme_node)
{
g_object_unref (widget->priv->theme_node);
widget->priv->theme_node = NULL;
}
/* update the style only if we are mapped */
if (!CLUTTER_ACTOR_IS_MAPPED ((ClutterActor *) stylable))
if (!CLUTTER_ACTOR_IS_MAPPED (CLUTTER_ACTOR (widget)))
return;
g_signal_emit_by_name (stylable, "style-changed", 0);
st_widget_ensure_style (widget);
}
static void
on_theme_context_changed (StThemeContext *context,
ClutterStage *stage)
{
notify_children_of_style_change (CLUTTER_CONTAINER (stage));
}
if (CLUTTER_IS_CONTAINER (stylable))
static StThemeNode *
get_root_theme_node (ClutterStage *stage)
{
StThemeContext *context = st_theme_context_get_for_stage (stage);
if (!g_object_get_data (G_OBJECT (context), "st-theme-initialized"))
{
/* notify our children that their parent stylable has changed */
clutter_container_foreach ((ClutterContainer *) stylable,
st_widget_stylable_child_notify,
NULL);
g_object_set_data (G_OBJECT (context), "st-theme-initialized", GUINT_TO_POINTER (1));
g_signal_connect (G_OBJECT (context), "changed",
G_CALLBACK (on_theme_context_changed), stage);
}
return st_theme_context_get_root_node (context);
}
StThemeNode *
st_widget_get_theme_node (StWidget *widget)
{
StWidgetPrivate *priv = widget->priv;
if (priv->theme_node == NULL)
{
StThemeNode *parent_node = NULL;
ClutterStage *stage = NULL;
ClutterActor *parent;
parent = clutter_actor_get_parent (CLUTTER_ACTOR (widget));
while (parent != NULL)
{
if (parent_node == NULL && ST_IS_WIDGET (parent))
parent_node = st_widget_get_theme_node (ST_WIDGET (parent));
else if (CLUTTER_IS_STAGE (parent))
stage = CLUTTER_STAGE (parent);
parent = clutter_actor_get_parent (parent);
}
if (stage == NULL)
{
g_warning ("st_widget_get_theme_node called on a widget not in a stage");
stage = CLUTTER_STAGE (clutter_stage_get_default ());
}
if (parent_node == NULL)
parent_node = get_root_theme_node (CLUTTER_STAGE (stage));
priv->theme_node = st_theme_node_new (st_theme_context_get_for_stage (stage),
parent_node, priv->theme,
G_OBJECT_TYPE (widget),
clutter_actor_get_name (CLUTTER_ACTOR (widget)),
priv->style_class,
priv->pseudo_class);
}
return priv->theme_node;
}
static gboolean
@ -691,6 +735,7 @@ st_widget_class_init (StWidgetClass *klass)
actor_class->hide = st_widget_hide;
klass->draw_background = st_widget_real_draw_background;
klass->style_changed = st_widget_real_style_changed;
/**
* StWidget:pseudo-class:
@ -718,7 +763,19 @@ st_widget_class_init (StWidgetClass *klass)
"",
ST_PARAM_READWRITE));
g_object_class_override_property (gobject_class, PROP_STYLE, "style");
/**
* StWidget:theme
*
* A theme set on this actor overriding the global theming for this actor
* and its descendants
*/
g_object_class_install_property (gobject_class,
PROP_THEME,
g_param_spec_object ("theme",
"Theme",
"Theme override",
ST_TYPE_THEME,
ST_PARAM_READWRITE));
/**
* StWidget:stylable:
@ -762,108 +819,66 @@ st_widget_class_init (StWidgetClass *klass)
ST_PARAM_READWRITE);
g_object_class_install_property (gobject_class, PROP_TOOLTIP_TEXT, pspec);
/**
* StWidget::style-changed:
*
* Emitted when the style information that the widget derives from the
* theme changes
*/
signals[STYLE_CHANGED] =
g_signal_new ("style-changed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (StWidgetClass, style_changed),
NULL, NULL,
_st_marshal_VOID__VOID,
G_TYPE_NONE, 0);
}
static StStyle *
st_widget_get_style (StStylable *stylable)
/**
* st_widget_set_theme:
* @actor: a #StWidget
* @theme: a new style class string
*
* Overrides the theme that would be inherited from the actor's parent
* or the stage with an entirely new theme (set of stylesheets).
*/
void
st_widget_set_theme (StWidget *actor,
StTheme *theme)
{
StWidgetPrivate *priv = ST_WIDGET (stylable)->priv;
StWidgetPrivate *priv = actor->priv;
return priv->style;
g_return_if_fail (ST_IS_WIDGET (actor));
priv = actor->priv;
if (theme !=priv->theme)
{
if (priv->theme)
g_object_unref (priv->theme);
priv->theme = g_object_ref (priv->theme);
st_widget_style_changed (actor);
g_object_notify (G_OBJECT (actor), "theme");
}
}
static void
st_style_changed_cb (StStyle *style,
StStylable *stylable)
/**
* st_widget_get_theme:
* @actor: a #StWidget
*
* Gets the overriding theme set on the actor. See st_widget_set_theme()
*
* Return value: (transfer none): the overriding theme, or %NULL
*/
StTheme *
st_widget_get_theme (StWidget *actor)
{
st_stylable_changed (stylable);
}
g_return_val_if_fail (ST_IS_WIDGET (actor), NULL);
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;
return actor->priv->theme;
}
/**
@ -879,7 +894,7 @@ st_widget_set_style_class_name (StWidget *actor,
{
StWidgetPrivate *priv = actor->priv;
g_return_if_fail (ST_WIDGET (actor));
g_return_if_fail (ST_IS_WIDGET (actor));
priv = actor->priv;
@ -888,7 +903,7 @@ st_widget_set_style_class_name (StWidget *actor,
g_free (priv->style_class);
priv->style_class = g_strdup (style_class);
st_stylable_changed ((StStylable*) actor);
st_widget_style_changed (actor);
g_object_notify (G_OBJECT (actor), "style-class");
}
@ -907,7 +922,7 @@ st_widget_set_style_class_name (StWidget *actor,
const gchar*
st_widget_get_style_class_name (StWidget *actor)
{
g_return_val_if_fail (ST_WIDGET (actor), NULL);
g_return_val_if_fail (ST_IS_WIDGET (actor), NULL);
return actor->priv->style_class;
}
@ -924,7 +939,7 @@ st_widget_get_style_class_name (StWidget *actor)
const gchar*
st_widget_get_style_pseudo_class (StWidget *actor)
{
g_return_val_if_fail (ST_WIDGET (actor), NULL);
g_return_val_if_fail (ST_IS_WIDGET (actor), NULL);
return actor->priv->pseudo_class;
}
@ -942,7 +957,7 @@ st_widget_set_style_pseudo_class (StWidget *actor,
{
StWidgetPrivate *priv;
g_return_if_fail (ST_WIDGET (actor));
g_return_if_fail (ST_IS_WIDGET (actor));
priv = actor->priv;
@ -951,99 +966,18 @@ st_widget_set_style_pseudo_class (StWidget *actor,
g_free (priv->pseudo_class);
priv->pseudo_class = g_strdup (pseudo_class);
st_stylable_changed ((StStylable*) actor);
st_widget_style_changed (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);
st_widget_style_changed (widget);
}
static void
@ -1056,44 +990,6 @@ st_widget_init (StWidget *actor)
/* 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;
}
/**
@ -1110,11 +1006,11 @@ st_widget_ensure_style (StWidget *widget)
if (widget->priv->is_style_dirty)
{
g_signal_emit_by_name (widget, "style-changed", 0);
g_signal_emit (widget, signals[STYLE_CHANGED], 0);
widget->priv->is_style_dirty = FALSE;
}
}
/**
* st_widget_get_border_image:
* @actor: A #StWidget
@ -1353,6 +1249,5 @@ st_widget_draw_background (StWidget *self)
klass = ST_WIDGET_GET_CLASS (self);
klass->draw_background (ST_WIDGET (self),
priv->border_image,
priv->bg_color);
&priv->bg_color);
}