From 0eca3efcb072ef2088453b575e6dd2dd000fb8ab Mon Sep 17 00:00:00 2001 From: "Owen W. Taylor" Date: Sat, 19 Sep 2009 22:56:09 -0400 Subject: [PATCH] Add support for inline styles Add support for passing an inline-style string when creating a ShellThemeNode. Hook this up to a new 'style' property of NbtkWidget. Add a test case that demonstrates using this to update font sizes on the fly. --- src/nbtk/nbtk-widget.c | 74 ++++++++++++++++++++++++++++++- src/nbtk/nbtk-widget.h | 3 ++ src/toolkit/shell-theme-context.c | 2 +- src/toolkit/shell-theme-node.c | 39 ++++++++++++++-- src/toolkit/shell-theme-node.h | 3 +- src/toolkit/shell-theme-private.h | 6 +-- src/toolkit/shell-theme.c | 13 +++--- src/toolkit/test-theme.c | 28 ++++++++---- tests/interactive/inline-style.js | 47 ++++++++++++++++++++ 9 files changed, 189 insertions(+), 26 deletions(-) create mode 100644 tests/interactive/inline-style.js diff --git a/src/nbtk/nbtk-widget.c b/src/nbtk/nbtk-widget.c index 4cc6c42e9..7295afe6d 100644 --- a/src/nbtk/nbtk-widget.c +++ b/src/nbtk/nbtk-widget.c @@ -54,6 +54,7 @@ struct _NbtkWidgetPrivate ShellThemeNode *theme_node; gchar *pseudo_class; gchar *style_class; + gchar *inline_style; ClutterActor *border_image; ClutterActor *background_image; @@ -84,6 +85,7 @@ enum PROP_THEME, PROP_PSEUDO_CLASS, PROP_STYLE_CLASS, + PROP_STYLE, PROP_STYLABLE, @@ -126,6 +128,10 @@ nbtk_widget_set_property (GObject *gobject, nbtk_widget_set_style_class_name (actor, g_value_get_string (value)); break; + case PROP_STYLE: + nbtk_widget_set_style (actor, g_value_get_string (value)); + break; + case PROP_STYLABLE: if (actor->priv->is_stylable != g_value_get_boolean (value)) { @@ -171,6 +177,10 @@ nbtk_widget_get_property (GObject *gobject, g_value_set_string (value, priv->style_class); break; + case PROP_STYLE: + g_value_set_string (value, priv->inline_style); + break; + case PROP_STYLABLE: g_value_set_boolean (value, priv->is_stylable); break; @@ -662,7 +672,8 @@ nbtk_widget_get_theme_node (NbtkWidget *widget) G_OBJECT_TYPE (widget), clutter_actor_get_name (CLUTTER_ACTOR (widget)), priv->style_class, - priv->pseudo_class); + priv->pseudo_class, + priv->inline_style); } return priv->theme_node; @@ -766,6 +777,20 @@ nbtk_widget_class_init (NbtkWidgetClass *klass) "", NBTK_PARAM_READWRITE)); + /** + * NbtkWidget:style: + * + * Inline style information for the actor as a ';'-separated list of + * CSS properties. + */ + g_object_class_install_property (gobject_class, + PROP_STYLE, + g_param_spec_string ("style", + "Style", + "Inline style string", + "", + NBTK_PARAM_READWRITE)); + /** * NbtkWidget:theme * @@ -975,6 +1000,53 @@ nbtk_widget_set_style_pseudo_class (NbtkWidget *actor, } } +/** + * nbtk_widget_set_style: + * @actor: a #NbtkWidget + * @style_class: (allow-none): a inline style string, or %NULL + * + * Set the inline style string for this widget. The inline style string is an + * optional ';'-separated list of CSS properties that override the style as + * determined from the stylesheets of the current theme. + */ +void +nbtk_widget_set_style (NbtkWidget *actor, + const gchar *style) +{ + NbtkWidgetPrivate *priv = actor->priv; + + g_return_if_fail (NBTK_IS_WIDGET (actor)); + + priv = actor->priv; + + if (g_strcmp0 (style, priv->inline_style)) + { + g_free (priv->inline_style); + priv->inline_style = g_strdup (style); + + nbtk_widget_style_changed (actor); + + g_object_notify (G_OBJECT (actor), "style"); + } +} + +/** + * nbtk_widget_get_style: + * @actor: a #NbtkWidget + * + * Get the current inline style string. See nbtk_widget_set_style(). + * + * Returns: The inline style string, or %NULL. The string is owned by the + * #NbtkWidget and should not be modified or freed. + */ +const gchar* +nbtk_widget_get_style (NbtkWidget *actor) +{ + g_return_val_if_fail (NBTK_IS_WIDGET (actor), NULL); + + return actor->priv->inline_style; +} + static void nbtk_widget_name_notify (NbtkWidget *widget, GParamSpec *pspec, diff --git a/src/nbtk/nbtk-widget.h b/src/nbtk/nbtk-widget.h index 8d367b0fa..cc303ad2a 100644 --- a/src/nbtk/nbtk-widget.h +++ b/src/nbtk/nbtk-widget.h @@ -85,6 +85,9 @@ G_CONST_RETURN gchar *nbtk_widget_get_style_pseudo_class (NbtkWidget *actor); void nbtk_widget_set_style_class_name (NbtkWidget *actor, const gchar *style_class); G_CONST_RETURN gchar *nbtk_widget_get_style_class_name (NbtkWidget *actor); +void nbtk_widget_set_style (NbtkWidget *actor, + const gchar *style); +G_CONST_RETURN gchar *nbtk_widget_get_style (NbtkWidget *actor); void nbtk_widget_set_theme (NbtkWidget *actor, ShellTheme *theme); diff --git a/src/toolkit/shell-theme-context.c b/src/toolkit/shell-theme-context.c index 62ad27691..57b7e26a8 100644 --- a/src/toolkit/shell-theme-context.c +++ b/src/toolkit/shell-theme-context.c @@ -212,7 +212,7 @@ shell_theme_context_get_root_node (ShellThemeContext *context) { if (context->root_node == NULL) context->root_node = shell_theme_node_new (context, NULL, context->theme, - G_TYPE_NONE, NULL, NULL, NULL); + G_TYPE_NONE, NULL, NULL, NULL, NULL); return context->root_node; } diff --git a/src/toolkit/shell-theme-node.c b/src/toolkit/shell-theme-node.c index 474e3510d..fcf2dcc21 100644 --- a/src/toolkit/shell-theme-node.c +++ b/src/toolkit/shell-theme-node.c @@ -43,10 +43,15 @@ struct _ShellThemeNode { char *element_id; char *element_class; char *pseudo_class; + char *inline_style; CRDeclaration **properties; int n_properties; + /* We hold onto these separately so we can unref them; the alternative + * would be to ref everything in ->properties */ + CRDeclaration *inline_properties; + guint properties_computed : 1; guint borders_computed : 1; guint background_computed : 1; @@ -91,10 +96,12 @@ static void shell_theme_node_finalize (GObject *object) { ShellThemeNode *node = SHELL_THEME_NODE (object); + CRDeclaration *cur_decl; g_free (node->element_id); g_free (node->element_class); g_free (node->pseudo_class); + g_free (node->inline_style); if (node->properties) { @@ -103,6 +110,9 @@ shell_theme_node_finalize (GObject *object) node->n_properties = 0; } + for (cur_decl = node->inline_properties; cur_decl; cur_decl = cur_decl->next) + cr_declaration_unref (cur_decl); + if (node->font_desc) { pango_font_description_free (node->font_desc); @@ -128,7 +138,8 @@ shell_theme_node_new (ShellThemeContext *context, GType element_type, const char *element_id, const char *element_class, - const char *pseudo_class) + const char *pseudo_class, + const char *inline_style) { ShellThemeNode *node; @@ -153,6 +164,7 @@ shell_theme_node_new (ShellThemeContext *context, node->element_id = g_strdup (element_id); node->element_class = g_strdup (element_class); node->pseudo_class = g_strdup (pseudo_class); + node->inline_style = g_strdup (inline_style); return node; } @@ -227,11 +239,32 @@ ensure_properties (ShellThemeNode *node) { if (!node->properties_computed) { + GPtrArray *properties = NULL; + node->properties_computed = TRUE; if (node->theme) - _shell_theme_get_matched_properties (node->theme, node, - &node->properties, &node->n_properties); + properties = _shell_theme_get_matched_properties (node->theme, node); + + if (node->inline_style) + { + CRDeclaration *cur_decl; + + if (!properties) + properties = g_ptr_array_new (); + + node->inline_properties = cr_declaration_parse_list_from_buf ((const guchar *)node->inline_style, + CR_UTF_8); + + for (cur_decl = node->inline_properties; cur_decl; cur_decl = cur_decl->next) + g_ptr_array_add (properties, cur_decl); + } + + if (properties) + { + node->n_properties = properties->len; + node->properties = (CRDeclaration **)g_ptr_array_free (properties, FALSE); + } } } diff --git a/src/toolkit/shell-theme-node.h b/src/toolkit/shell-theme-node.h index b98b03754..489295e68 100644 --- a/src/toolkit/shell-theme-node.h +++ b/src/toolkit/shell-theme-node.h @@ -46,7 +46,8 @@ ShellThemeNode *shell_theme_node_new (ShellThemeContext *context, GType element_type, const char *element_id, const char *element_class, - const char *pseudo_class); + const char *pseudo_class, + const char *inline_style); ShellThemeNode *shell_theme_node_get_parent (ShellThemeNode *node); diff --git a/src/toolkit/shell-theme-private.h b/src/toolkit/shell-theme-private.h index 214e94dcc..18c975782 100644 --- a/src/toolkit/shell-theme-private.h +++ b/src/toolkit/shell-theme-private.h @@ -7,10 +7,8 @@ G_BEGIN_DECLS -void _shell_theme_get_matched_properties (ShellTheme *theme, - ShellThemeNode *node, - CRDeclaration ***properties, - int *n_properties); +GPtrArray *_shell_theme_get_matched_properties (ShellTheme *theme, + ShellThemeNode *node); /* Resolve an URL from the stylesheet to a filename */ char *_shell_theme_resolve_url (ShellTheme *theme, diff --git a/src/toolkit/shell-theme.c b/src/toolkit/shell-theme.c index b64e15dff..e1ab3f721 100644 --- a/src/toolkit/shell-theme.c +++ b/src/toolkit/shell-theme.c @@ -940,18 +940,16 @@ compare_declarations (gconstpointer a, return 0; } -void +GPtrArray * _shell_theme_get_matched_properties (ShellTheme *theme, - ShellThemeNode *node, - CRDeclaration ***properties, - int *n_properties) + ShellThemeNode *node) { enum CRStyleOrigin origin = 0; CRStyleSheet *sheet = NULL; GPtrArray *props = g_ptr_array_new (); - g_return_if_fail (SHELL_IS_THEME (theme)); - g_return_if_fail (SHELL_IS_THEME_NODE (node)); + g_return_val_if_fail (SHELL_IS_THEME (theme), NULL); + g_return_val_if_fail (SHELL_IS_THEME_NODE (node), NULL); for (origin = ORIGIN_UA; origin < NB_ORIGINS; origin++) { @@ -966,8 +964,7 @@ _shell_theme_get_matched_properties (ShellTheme *theme, * after earlier declarations */ g_ptr_array_sort (props, compare_declarations); - *n_properties = props->len; - *properties = (CRDeclaration **) g_ptr_array_free (props, FALSE); + return props; } /* Resolve an url from an url() reference in a stylesheet into an absolute diff --git a/src/toolkit/test-theme.c b/src/toolkit/test-theme.c index f2bf3fcb1..d16334406 100644 --- a/src/toolkit/test-theme.c +++ b/src/toolkit/test-theme.c @@ -244,6 +244,16 @@ test_pseudo_class (void) assert_text_decoration (group3, "group3", 0); } +static void +test_inline_style (void) +{ + test = "inline_style"; + /* These properties come from the inline-style specified when creating the node */ + assert_foreground_color (text3, "text3", 0x00000ffff); + assert_length ("text3", "padding-bottom", 12., + shell_theme_node_get_padding (text3, SHELL_SIDE_BOTTOM)); +} + int main (int argc, char **argv) { @@ -263,21 +273,22 @@ main (int argc, char **argv) root = shell_theme_context_get_root_node (context); group1 = shell_theme_node_new (context, root, NULL, - CLUTTER_TYPE_GROUP, "group1", NULL, NULL); + CLUTTER_TYPE_GROUP, "group1", NULL, NULL, NULL); text1 = shell_theme_node_new (context, group1, NULL, - CLUTTER_TYPE_TEXT, "text1", "special-text", NULL); + CLUTTER_TYPE_TEXT, "text1", "special-text", NULL, NULL); text2 = shell_theme_node_new (context, group1, NULL, - CLUTTER_TYPE_TEXT, "text2", NULL, NULL); + CLUTTER_TYPE_TEXT, "text2", NULL, NULL, NULL); group2 = shell_theme_node_new (context, root, NULL, - CLUTTER_TYPE_GROUP, "group2", NULL, NULL); + CLUTTER_TYPE_GROUP, "group2", NULL, NULL, NULL); text3 = shell_theme_node_new (context, group2, NULL, - CLUTTER_TYPE_TEXT, "text3", NULL, NULL); + CLUTTER_TYPE_TEXT, "text3", NULL, NULL, + "color: #0000ff; padding-bottom: 12px;"); text4 = shell_theme_node_new (context, group2, NULL, - CLUTTER_TYPE_TEXT, "text4", NULL, "visited hover"); + CLUTTER_TYPE_TEXT, "text4", NULL, "visited hover", NULL); group3 = shell_theme_node_new (context, group2, NULL, - CLUTTER_TYPE_GROUP, "group3", NULL, "hover"); + CLUTTER_TYPE_GROUP, "group3", NULL, "hover", NULL); cairo_texture = shell_theme_node_new (context, root, NULL, - CLUTTER_TYPE_CAIRO_TEXTURE, "cairoTexture", NULL, NULL); + CLUTTER_TYPE_CAIRO_TEXTURE, "cairoTexture", NULL, NULL, NULL); test_defaults (); test_lengths (); @@ -287,6 +298,7 @@ main (int argc, char **argv) test_background (); test_font (); test_pseudo_class (); + test_inline_style (); return fail ? 1 : 0; } diff --git a/tests/interactive/inline-style.js b/tests/interactive/inline-style.js new file mode 100644 index 000000000..366db2b1a --- /dev/null +++ b/tests/interactive/inline-style.js @@ -0,0 +1,47 @@ +/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ + +const Clutter = imports.gi.Clutter; +const Nbtk = imports.gi.Nbtk; + +const UI = imports.testcommon.ui; + +UI.init(); +let stage = Clutter.Stage.get_default(); + +let vbox = new Nbtk.BoxLayout({ vertical: true, + width: stage.width, + height: stage.height }); +stage.add_actor(vbox); + +let hbox = new Nbtk.BoxLayout({ spacing: 12 }); +vbox.add(hbox); + +let text = new Nbtk.Label({ text: "Styled Text" }); +vbox.add (text); + +let size = 24; +function update_size() { + text.style = 'font-size: ' + size + 'pt'; +} +update_size(); + +let button = new Nbtk.Button ({ label: 'Smaller', + style: 'padding: 4px; background: #eeddcc' }); +hbox.add (button); +button.connect('clicked', function() { + size /= 1.2; + update_size (); + }); + +let button = new Nbtk.Button ({ label: 'Bigger', + style: 'padding: 4px; background: #eeddcc' }); +hbox.add (button); +button.connect('clicked', function() { + size *= 1.2; + update_size (); + }); + +stage.show(); +Clutter.main(); +stage.destroy(); +