/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* * 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 * Thomas Wood * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "st-widget.h" #include "st-marshal.h" #include "st-private.h" #include "st-texture-cache.h" #include "st-texture-frame.h" #include "st-theme-context.h" #include "st-tooltip.h" #include /* * Forward declaration for sake of StWidgetChild */ struct _StWidgetPrivate { StTheme *theme; StThemeNode *theme_node; gchar *pseudo_class; gchar *style_class; gchar *inline_style; ClutterActor *border_image; ClutterActor *background_image; ClutterColor bg_color; StGradientType bg_gradient_type; ClutterColor bg_gradient_end; gboolean is_stylable : 1; gboolean has_tooltip : 1; gboolean is_style_dirty : 1; gboolean draw_bg_color : 1; gboolean draw_border_internal : 1; StTooltip *tooltip; StTextDirection direction; }; /** * 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_THEME, PROP_PSEUDO_CLASS, PROP_STYLE_CLASS, PROP_STYLE, PROP_STYLABLE, PROP_HAS_TOOLTIP, PROP_TOOLTIP_TEXT }; enum { STYLE_CHANGED, LAST_SIGNAL }; 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)) static void st_widget_recompute_style (StWidget *widget, StThemeNode *old_theme_node); static void st_widget_redraw_gradient (StWidget *widget); 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_THEME: st_widget_set_theme (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_STYLE: st_widget_set_style (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_THEME: g_value_set_object (value, priv->theme); 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_STYLE: g_value_set_string (value, priv->inline_style); 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->theme) { g_object_unref (priv->theme); priv->theme = NULL; } if (priv->border_image) { clutter_actor_unparent (priv->border_image); priv->border_image = 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; StThemeNode *theme_node; ClutterActorClass *klass; ClutterGeometry area; ClutterVertex in_v, out_v; theme_node = st_widget_get_theme_node ((StWidget*) actor); 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 && priv->bg_gradient_type == ST_GRADIENT_NONE) { 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); } else if (priv->bg_gradient_type != ST_GRADIENT_NONE) { float width, height; ClutterActorBox frame_box, content_box; width = box->x2 - box->x1; height = box->y2 - box->y1; frame_box.x1 = frame_box.y1 = 0; frame_box.x2 = width; frame_box.y2 = height; st_theme_node_get_content_box (theme_node, &frame_box, &content_box); if (width > 0 && height > 0) clutter_cairo_texture_set_surface_size (CLUTTER_CAIRO_TEXTURE (priv->background_image), width, height); st_widget_redraw_gradient ((StWidget*) actor); clutter_actor_allocate (CLUTTER_ACTOR (priv->background_image), &content_box, flags); } } static void st_widget_real_draw_background (StWidget *self) { StWidgetPrivate *priv = self->priv; ClutterActor *actor = CLUTTER_ACTOR (self); ClutterActorBox allocation = { 0, }; gfloat w, h; guint8 opacity; clutter_actor_get_allocation_box (actor, &allocation); w = allocation.x2 - allocation.x1; h = allocation.y2 - allocation.y1; opacity = clutter_actor_get_paint_opacity (actor); /* Default implementation just draws the background * colour and the image on top */ if (priv->draw_bg_color) { ClutterColor bg_color = priv->bg_color; bg_color.alpha = opacity * bg_color.alpha / 255; cogl_set_source_color4ub (bg_color.red, bg_color.green, bg_color.blue, bg_color.alpha); cogl_rectangle (0, 0, w, h); } if (priv->draw_border_internal) { StThemeNode *node = st_widget_get_theme_node (self); int side; double border_top, border_right, border_bottom, border_left; border_top = st_theme_node_get_border_width (node, ST_SIDE_TOP); border_right = st_theme_node_get_border_width (node, ST_SIDE_RIGHT); border_bottom = st_theme_node_get_border_width (node, ST_SIDE_BOTTOM); border_left = st_theme_node_get_border_width (node, ST_SIDE_LEFT); for (side = 0; side < 4; side++) { ClutterColor color; switch (side) { case ST_SIDE_TOP: if (border_top <= 0) continue; break; case ST_SIDE_RIGHT: if (border_right <= 0) continue; break; case ST_SIDE_BOTTOM: if (border_bottom <= 0) continue; break; case ST_SIDE_LEFT: if (border_left <= 0) continue; break; } st_theme_node_get_border_color (node, side, &color); color.alpha = (color.alpha * opacity) / 0xff; cogl_set_source_color4ub (color.red, color.green, color.blue, color.alpha); /* Note top and bottom extend to the ends, left/right * are constrained by them. See comment above about CSS * conformance. */ switch (side) { case ST_SIDE_TOP: cogl_rectangle (0, 0, w, border_top); break; case ST_SIDE_RIGHT: cogl_rectangle (w - border_right, border_top, w, h - border_bottom); break; case ST_SIDE_BOTTOM: cogl_rectangle (0, h - border_bottom, w, h); break; case ST_SIDE_LEFT: cogl_rectangle (0, border_top, border_left, h - border_bottom); break; } } } if (priv->border_image) clutter_actor_paint (priv->border_image); } 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)); 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_widget_style_changed (ST_WIDGET (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 draw_vertical_gradient (ClutterCairoTexture *texture, ClutterColor *start, ClutterColor *end) { guint width, height; cairo_t *cr; cairo_pattern_t *pattern; clutter_cairo_texture_get_surface_size (texture, &width, &height); clutter_cairo_texture_clear (texture); cr = clutter_cairo_texture_create (texture); pattern = cairo_pattern_create_linear (0, 0, 0, height); cairo_pattern_add_color_stop_rgba (pattern, 0, start->red / 255., start->green / 255., start->blue / 255., start->alpha / 255.); cairo_pattern_add_color_stop_rgba (pattern, 1, end->red / 255., end->green / 255., end->blue / 255., end->alpha / 255.); cairo_rectangle (cr, 0, 0, width, height); cairo_set_source (cr, pattern); cairo_fill (cr); cairo_pattern_destroy (pattern); cairo_destroy (cr); } static void draw_horizontal_gradient (ClutterCairoTexture *texture, ClutterColor *start, ClutterColor *end) { guint width, height; cairo_t *cr; cairo_pattern_t *pattern; clutter_cairo_texture_get_surface_size (texture, &width, &height); clutter_cairo_texture_clear (texture); cr = clutter_cairo_texture_create (texture); pattern = cairo_pattern_create_linear (0, 0, width, 0); cairo_pattern_add_color_stop_rgba (pattern, 0, start->red / 255., start->green / 255., start->blue / 255., start->alpha / 255.); cairo_pattern_add_color_stop_rgba (pattern, 1, end->red / 255., end->green / 255., end->blue / 255., end->alpha / 255.); cairo_rectangle (cr, 0, 0, width, height); cairo_set_source (cr, pattern); cairo_fill (cr); cairo_pattern_destroy (pattern); cairo_destroy (cr); } static void st_widget_redraw_gradient (StWidget *widget) { if (widget->priv->bg_gradient_type == ST_GRADIENT_VERTICAL) draw_vertical_gradient ((ClutterCairoTexture*) widget->priv->background_image, &widget->priv->bg_color, &widget->priv->bg_gradient_end); else if (widget->priv->bg_gradient_type == ST_GRADIENT_HORIZONTAL) draw_horizontal_gradient ((ClutterCairoTexture*) widget->priv->background_image, &widget->priv->bg_color, &widget->priv->bg_gradient_end); } static void notify_children_of_style_change (ClutterContainer *container); static void 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; StThemeNode *theme_node; StBorderImage *border_image; StTextureCache *texture_cache; ClutterTexture *texture; const char *bg_file = NULL; gboolean relayout_needed = FALSE; gboolean has_changed = FALSE; ClutterColor color; guint border_width = 0; guint border_radius = 0; ClutterColor border_color = { 0, }; StGradientType gradient; ClutterColor gradient_end; StSide side; StCorner corner; gboolean uniform_border_width; /* application has request this widget is not stylable */ if (!priv->is_stylable) return; theme_node = st_widget_get_theme_node (self); st_theme_node_get_background_gradient (theme_node, &gradient, &color, &gradient_end); if (gradient == ST_GRADIENT_NONE) { if (gradient != priv->bg_gradient_type) has_changed = TRUE; priv->bg_gradient_type = gradient; st_theme_node_get_background_color (theme_node, &color); if (!clutter_color_equal (&color, &priv->bg_color)) { priv->bg_color = color; priv->draw_bg_color = color.alpha != 0; has_changed = TRUE; } } else if (gradient != priv->bg_gradient_type || !clutter_color_equal (&color, &priv->bg_color) || !clutter_color_equal (&gradient_end, &priv->bg_gradient_end)) { priv->bg_gradient_type = gradient; priv->bg_color = color; priv->bg_gradient_end = gradient_end; priv->draw_bg_color = TRUE; has_changed = TRUE; } 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 (); /* Rough notes about the relationship of borders and backgrounds in CSS3; * see http://www.w3.org/TR/css3-background/ for more accurate details. * * - Things are drawn in 4 layers, from the bottom: * Background color * Background image * Border color or border image * Content * - The background color and image extend to and are clipped by the * edge of the border area, so will be rounded if the border is rounded. * (CSS3 background-clip property modifies this) * - The border image replaces what would normally be drawn by the border * - The border image is not clipped by a rounded border-radius * - The border radius rounds the background even if the border is * zero width or a border image is being used. * * Deviations from the above as implemented here: * - Nonuniform border widths combined with a non-zero border radius result * in the border radius being ignored * - The combination of border image and a non-zero border radius is * not supported; the background color will be drawn with square * corners. * - The background image is drawn above the border color or image, * not below it. * - We don't clip the background image to the (rounded) border area. * * The first two allow us always draw with no more than single border_image * and a single background image above it. */ /* Check whether all border widths are the same. Also, acquire the * first nonzero border width as well as the border color. */ uniform_border_width = TRUE; border_width = st_theme_node_get_border_width (theme_node, ST_SIDE_TOP); if (border_width > 0.5) border_width = (int)(0.5 + border_width); for (side = 0; side < 4; side++) { double width = st_theme_node_get_border_width (theme_node, side); if (width > 0.5) width = (int)(0.5 + width); if (width > 0) { border_width = width; st_theme_node_get_border_color (theme_node, side, &border_color); } if ((int)width != border_width) { uniform_border_width = FALSE; break; } } /* Pick the first nonzero border radius, but only if we have a uniform border. */ if (uniform_border_width) { for (corner = 0; corner < 4; corner++) { double radius = st_theme_node_get_border_radius (theme_node, corner); if (radius > 0.5) { border_radius = (int)(0.5 + radius); break; } } } border_image = st_theme_node_get_border_image (theme_node); if (border_image) { const char *filename; gint border_left, border_right, border_top, border_bottom; gint width, height; filename = st_border_image_get_filename (border_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, filename); clutter_texture_get_base_size (CLUTTER_TEXTURE (texture), &width, &height); st_border_image_get_borders (border_image, &border_left, &border_right, &border_top, &border_bottom); 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)); has_changed = TRUE; relayout_needed = TRUE; } else if (border_radius > 0) { priv->draw_border_internal = FALSE; priv->draw_bg_color = FALSE; priv->border_image = g_object_new (BIG_TYPE_RECTANGLE, "color", &priv->bg_color, "border-width", border_width, "border-color", &border_color, "corner-radius", border_radius, NULL); clutter_actor_set_parent (priv->border_image, CLUTTER_ACTOR (self)); has_changed = TRUE; relayout_needed = TRUE; } else if (border_width > 0 && border_color.alpha != 0) { priv->draw_bg_color = TRUE; priv->draw_border_internal = TRUE; has_changed = TRUE; relayout_needed = TRUE; } else if (priv->draw_border_internal) { priv->draw_border_internal = FALSE; has_changed = TRUE; relayout_needed = TRUE; } 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; 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; } else if (priv->bg_gradient_type != ST_GRADIENT_NONE) { texture = g_object_new (CLUTTER_TYPE_CAIRO_TEXTURE, NULL); priv->background_image = CLUTTER_ACTOR (texture); clutter_actor_set_parent (priv->background_image, CLUTTER_ACTOR (self)); has_changed = TRUE; relayout_needed = TRUE; } /* 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); } if (CLUTTER_IS_CONTAINER (self)) notify_children_of_style_change ((ClutterContainer *)self); } void st_widget_style_changed (StWidget *widget) { StThemeNode *old_theme_node = NULL; widget->priv->is_style_dirty = TRUE; if (widget->priv->theme_node) { old_theme_node = widget->priv->theme_node; widget->priv->theme_node = NULL; } /* update the style only if we are mapped */ if (CLUTTER_ACTOR_IS_MAPPED (CLUTTER_ACTOR (widget))) st_widget_recompute_style (widget, old_theme_node); if (old_theme_node) g_object_unref (old_theme_node); } static void on_theme_context_changed (StThemeContext *context, ClutterStage *stage) { notify_children_of_style_change (CLUTTER_CONTAINER (stage)); } 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")) { 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); } /** * st_widget_get_theme_node: * @widget: a #StWidget * * Gets the theme node holding style information for the widget. * The theme node is used to access standard and custom CSS * properties of the widget. * * Return value: (transfer none): the theme node for the widget. * This is owned by the widget. When attributes of the widget * or the environment that affect the styling change (for example * the style_class property of the widget), it will be recreated, * and the ::style-changed signal will be emitted on the widget. */ 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, priv->inline_style); } return priv->theme_node; } 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; klass->style_changed = st_widget_real_style_changed; /** * 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)); /** * StWidget: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", "", ST_PARAM_READWRITE)); /** * 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: * * 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); /** * 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); } /** * 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 = actor->priv; 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"); } } /** * 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) { g_return_val_if_fail (ST_IS_WIDGET (actor), NULL); return actor->priv->theme; } /** * 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_IS_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_widget_style_changed (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_IS_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_IS_WIDGET (actor), NULL); return actor->priv->pseudo_class; } /** * st_widget_set_style_pseudo_class: * @actor: a #StWidget * @pseudo_class: (allow-none): 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_IS_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_widget_style_changed (actor); g_object_notify (G_OBJECT (actor), "pseudo-class"); } } /** * st_widget_set_style: * @actor: a #StWidget * @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 st_widget_set_style (StWidget *actor, const gchar *style) { StWidgetPrivate *priv = actor->priv; g_return_if_fail (ST_IS_WIDGET (actor)); priv = actor->priv; if (g_strcmp0 (style, priv->inline_style)) { g_free (priv->inline_style); priv->inline_style = g_strdup (style); st_widget_style_changed (actor); g_object_notify (G_OBJECT (actor), "style"); } } /** * st_widget_get_style: * @actor: a #StWidget * * Get the current inline style string. See st_widget_set_style(). * * Returns: The inline style string, or %NULL. The string is owned by the * #StWidget and should not be modified or freed. */ const gchar* st_widget_get_style (StWidget *actor) { g_return_val_if_fail (ST_IS_WIDGET (actor), NULL); return actor->priv->inline_style; } static void st_widget_name_notify (StWidget *widget, GParamSpec *pspec, gpointer data) { st_widget_style_changed (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); } static void st_widget_recompute_style (StWidget *widget, StThemeNode *old_theme_node) { ClutterActorBox allocation_box; StThemeNode *new_theme_node = st_widget_get_theme_node (widget); clutter_actor_get_allocation_box ((ClutterActor *) widget, &allocation_box); if (!old_theme_node || !st_theme_node_geometry_equal (old_theme_node, new_theme_node)) clutter_actor_queue_relayout ((ClutterActor *) widget); /* Could compare gradient values here if we hit a performance issue. * Also, only redraw if we've been allocated. */ if (allocation_box.x2 - allocation_box.x1 > 0 && allocation_box.y2 - allocation_box.y1 > 0) st_widget_redraw_gradient (widget); g_signal_emit (widget, signals[STYLE_CHANGED], 0); widget->priv->is_style_dirty = FALSE; } /** * 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) st_widget_recompute_style (widget, NULL); } static StTextDirection default_direction = ST_TEXT_DIRECTION_LTR; StTextDirection st_widget_get_default_direction (void) { return default_direction; } void st_widget_set_default_direction (StTextDirection dir) { g_return_if_fail (dir != ST_TEXT_DIRECTION_NONE); default_direction = dir; } StTextDirection st_widget_get_direction (StWidget *self) { g_return_val_if_fail (ST_IS_WIDGET (self), ST_TEXT_DIRECTION_LTR); if (self->priv->direction != ST_TEXT_DIRECTION_NONE) return self->priv->direction; else return default_direction; } void st_widget_set_direction (StWidget *self, StTextDirection dir) { g_return_if_fail (ST_IS_WIDGET (self)); self->priv->direction = dir; } /** * 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: (transfer none): #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: (transfer none): a #ClutterActor */ ClutterActor * st_widget_get_background_image (StWidget *actor) { StWidgetPrivate *priv = ST_WIDGET (actor)->priv; return priv->background_image; } /** * 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)); }