/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* * st-scroll-view.h: Container with scroll-bars * * Copyright 2008 OpenedHand * Copyright 2009 Intel Corporation. * Copyright 2009, 2010 Red Hat, Inc. * Copyright 2010 Maxim Ermilov * * 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, see . */ /** * SECTION:st-scroll-view * @short_description: a container for scrollable children * * #StScrollView is a single child container for actors that implement * #StScrollable. It provides scrollbars around the edge of the child to * allow the user to move around the scrollable area. */ /* TODO: The code here currently only deals with height-for-width * allocation; width-for-height allocation would need a second set of * code paths through get_preferred_height()/get_preferred_width()/allocate() * that reverse the roles of the horizontal and vertical scrollbars. * * TODO: The multiple layout passes with and without scrollbars when * using the automatic policy causes considerable inefficiency because * it breaks request caching; we should saved the last size passed * into allocate() and if it's the same as previous size not repeat * the determination of scrollbar visibility. This requires overriding * queue_relayout() so we know when to discard the saved value. * * The size negotiation between the #StScrollView and the child is * described in the documentation for #StScrollable; the significant * part to note there is that reported minimum sizes for a scrolled * child are the minimum sizes when no scrollbar is needed. This allows * us to determine what scrollbars are visible without a need to look * inside the #StAdjustment. * * The second simplification that we make that allows us to implement * a straighforward height-for-width negotiation without multiple * allocate passes is that when the vertical scrollbar policy is * AUTO, we always reserve space for the vertical scrollbar in the * reported minimum and natural size. * * See https://bugzilla.gnome.org/show_bug.cgi?id=611740 for a more * detailed description of the considerations involved. */ #include "st-scroll-view.h" #include "st-marshal.h" #include "st-scroll-bar.h" #include "st-scrollable.h" #include #include static void clutter_container_iface_init (ClutterContainerIface *iface); static ClutterContainerIface *st_scroll_view_parent_iface = NULL; G_DEFINE_TYPE_WITH_CODE (StScrollView, st_scroll_view, ST_TYPE_BIN, G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTAINER, clutter_container_iface_init)) #define SCROLL_VIEW_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \ ST_TYPE_SCROLL_VIEW, \ StScrollViewPrivate)) struct _StScrollViewPrivate { /* a pointer to the child; this is actually stored * inside StBin:child, but we keep it to avoid * calling st_bin_get_child() every time we need it */ ClutterActor *child; StAdjustment *hadjustment; ClutterActor *hscroll; StAdjustment *vadjustment; ClutterActor *vscroll; GtkPolicyType hscrollbar_policy; GtkPolicyType vscrollbar_policy; ClutterActor *top_shadow; ClutterActor *bottom_shadow; gfloat row_size; gfloat column_size; gboolean vshadows; gboolean row_size_set : 1; gboolean column_size_set : 1; guint mouse_scroll : 1; guint hscrollbar_visible : 1; guint vscrollbar_visible : 1; guint top_shadow_visible : 1; guint bottom_shadow_visible : 1; }; enum { PROP_0, PROP_HSCROLL, PROP_VSCROLL, PROP_HSCROLLBAR_POLICY, PROP_VSCROLLBAR_POLICY, PROP_MOUSE_SCROLL, PROP_VSHADOWS }; static void st_scroll_view_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { StScrollViewPrivate *priv = ((StScrollView *) object)->priv; switch (property_id) { case PROP_HSCROLL: g_value_set_object (value, priv->hscroll); break; case PROP_VSCROLL: g_value_set_object (value, priv->vscroll); break; case PROP_HSCROLLBAR_POLICY: g_value_set_enum (value, priv->hscrollbar_policy); break; case PROP_VSCROLLBAR_POLICY: g_value_set_enum (value, priv->vscrollbar_policy); break; case PROP_MOUSE_SCROLL: g_value_set_boolean (value, priv->mouse_scroll); break; case PROP_VSHADOWS: g_value_set_boolean (value, priv->vshadows); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void update_shadow_visibility (StScrollView *scroll) { StScrollViewPrivate *priv = scroll->priv; if (priv->vshadows) { gdouble value, lower, upper, page_size; st_adjustment_get_values (priv->vadjustment, &value, &lower, &upper, NULL, NULL, &page_size); priv->top_shadow_visible = value > lower + 0.1; priv->bottom_shadow_visible = value < upper - page_size - 0.1; } else { priv->top_shadow_visible = FALSE; priv->bottom_shadow_visible = FALSE; } } /** * st_scroll_view_set_vshadows: * @self: a #StScrollView * @vshadows: Whether to enable vertical shadows * * Sets whether to show shadows at the top and bottom of the area. Shadows * are omitted when fully scrolled to that edge. */ void st_scroll_view_set_vshadows (StScrollView *self, gboolean vshadows) { StScrollViewPrivate *priv = ST_SCROLL_VIEW (self)->priv; vshadows = vshadows != FALSE; if (priv->vshadows == vshadows) return; priv->vshadows = vshadows; if (vshadows) { if (priv->top_shadow) { clutter_actor_show (priv->top_shadow); clutter_actor_show (priv->bottom_shadow); } else { priv->top_shadow = g_object_new (ST_TYPE_BIN, "style-class", "top-shadow", NULL); priv->bottom_shadow = g_object_new (ST_TYPE_BIN, "style-class", "bottom-shadow", NULL); clutter_actor_set_parent (priv->bottom_shadow, CLUTTER_ACTOR (self)); clutter_actor_set_parent (priv->top_shadow, CLUTTER_ACTOR (self)); } } else { clutter_actor_hide (priv->top_shadow); clutter_actor_hide (priv->bottom_shadow); } update_shadow_visibility (self); g_object_notify (G_OBJECT (self), "vshadows"); } static void st_scroll_view_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { StScrollView *self = ST_SCROLL_VIEW (object); StScrollViewPrivate *priv = self->priv; switch (property_id) { case PROP_VSHADOWS: st_scroll_view_set_vshadows (self, g_value_get_boolean (value)); break; case PROP_MOUSE_SCROLL: st_scroll_view_set_mouse_scrolling (self, g_value_get_boolean (value)); break; case PROP_HSCROLLBAR_POLICY: st_scroll_view_set_policy (self, g_value_get_enum (value), priv->vscrollbar_policy); break; case PROP_VSCROLLBAR_POLICY: st_scroll_view_set_policy (self, priv->hscrollbar_policy, g_value_get_enum (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void st_scroll_view_dispose (GObject *object) { StScrollViewPrivate *priv = ST_SCROLL_VIEW (object)->priv; if (priv->vscroll) clutter_actor_destroy (priv->vscroll); if (priv->hscroll) clutter_actor_destroy (priv->hscroll); /* For most reliable freeing of memory, an object with signals * like StAdjustment should be explicitly disposed. Since we own * the adjustments, we take care of that. This also disconnects * the signal handlers that we established on creation. */ if (priv->hadjustment) { g_object_run_dispose (G_OBJECT (priv->hadjustment)); g_object_unref (priv->hadjustment); priv->hadjustment = NULL; } if (priv->vadjustment) { g_object_run_dispose (G_OBJECT (priv->vadjustment)); g_object_unref (priv->vadjustment); priv->vadjustment = NULL; } /* since it's impossible to get a handle to these actors, we can * just directly unparent them and not go through destroy/remove */ if (priv->top_shadow) { clutter_actor_unparent (priv->top_shadow); priv->top_shadow = NULL; } if (priv->bottom_shadow) { clutter_actor_unparent (priv->bottom_shadow); priv->bottom_shadow = NULL; } G_OBJECT_CLASS (st_scroll_view_parent_class)->dispose (object); } static void st_scroll_view_paint (ClutterActor *actor) { StScrollViewPrivate *priv = ST_SCROLL_VIEW (actor)->priv; /* StBin will paint the child */ CLUTTER_ACTOR_CLASS (st_scroll_view_parent_class)->paint (actor); /* paint our custom children */ if (priv->hscrollbar_visible && CLUTTER_ACTOR_IS_VISIBLE (priv->hscroll)) clutter_actor_paint (priv->hscroll); if (priv->vscrollbar_visible && CLUTTER_ACTOR_IS_VISIBLE (priv->vscroll)) clutter_actor_paint (priv->vscroll); if (priv->top_shadow_visible) clutter_actor_paint (priv->top_shadow); if (priv->bottom_shadow_visible) clutter_actor_paint (priv->bottom_shadow); } static void st_scroll_view_pick (ClutterActor *actor, const ClutterColor *color) { StScrollViewPrivate *priv = ST_SCROLL_VIEW (actor)->priv; /* Chain up so we get a bounding box pained (if we are reactive) */ CLUTTER_ACTOR_CLASS (st_scroll_view_parent_class)->pick (actor, color); /* paint our custom children */ if (priv->hscrollbar_visible && CLUTTER_ACTOR_IS_VISIBLE (priv->hscroll)) clutter_actor_paint (priv->hscroll); if (priv->vscrollbar_visible && CLUTTER_ACTOR_IS_VISIBLE (priv->vscroll)) clutter_actor_paint (priv->vscroll); } static double get_scrollbar_width (StScrollView *scroll, gfloat for_height) { StScrollViewPrivate *priv = scroll->priv; if (CLUTTER_ACTOR_IS_VISIBLE (priv->vscroll)) { gfloat min_size; clutter_actor_get_preferred_width (CLUTTER_ACTOR (priv->vscroll), for_height, &min_size, NULL); return min_size; } else return 0; } static double get_scrollbar_height (StScrollView *scroll, gfloat for_width) { StScrollViewPrivate *priv = scroll->priv; if (CLUTTER_ACTOR_IS_VISIBLE (priv->hscroll)) { gfloat min_size; clutter_actor_get_preferred_height (CLUTTER_ACTOR (priv->hscroll), for_width, &min_size, NULL); return min_size; } else return 0; } static void st_scroll_view_get_preferred_width (ClutterActor *actor, gfloat for_height, gfloat *min_width_p, gfloat *natural_width_p) { StScrollViewPrivate *priv = ST_SCROLL_VIEW (actor)->priv; StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor)); gboolean account_for_vscrollbar = FALSE; gfloat min_width = 0, natural_width; gfloat child_min_width, child_natural_width; if (!priv->child) return; st_theme_node_adjust_for_height (theme_node, &for_height); clutter_actor_get_preferred_width (priv->child, -1, &child_min_width, &child_natural_width); natural_width = child_natural_width; switch (priv->hscrollbar_policy) { case GTK_POLICY_NEVER: min_width = child_min_width; break; case GTK_POLICY_ALWAYS: case GTK_POLICY_AUTOMATIC: /* Should theoretically use the min width of the hscrollbar, * but that's not cleanly defined at the moment */ min_width = 0; break; } switch (priv->vscrollbar_policy) { case GTK_POLICY_NEVER: account_for_vscrollbar = FALSE; break; case GTK_POLICY_ALWAYS: account_for_vscrollbar = TRUE; break; case GTK_POLICY_AUTOMATIC: /* For automatic scrollbars, we always request space for the vertical * scrollbar; we won't know whether we actually need one until our * height is assigned in allocate(). */ account_for_vscrollbar = TRUE; break; } account_for_vscrollbar = priv->vscrollbar_policy != GTK_POLICY_NEVER; if (account_for_vscrollbar) { float sb_width = get_scrollbar_width (ST_SCROLL_VIEW (actor), for_height); min_width += sb_width; natural_width += sb_width; } if (min_width_p) *min_width_p = min_width; if (natural_width_p) *natural_width_p = natural_width; st_theme_node_adjust_preferred_width (theme_node, min_width_p, natural_width_p); } static void st_scroll_view_get_preferred_height (ClutterActor *actor, gfloat for_width, gfloat *min_height_p, gfloat *natural_height_p) { StScrollViewPrivate *priv = ST_SCROLL_VIEW (actor)->priv; StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor)); gboolean account_for_hscrollbar = FALSE; gfloat min_height = 0, natural_height; gfloat child_min_height, child_natural_height; gfloat child_min_width; gfloat sb_width; if (!priv->child) return; st_theme_node_adjust_for_width (theme_node, &for_width); clutter_actor_get_preferred_width (priv->child, -1, &child_min_width, NULL); natural_height = child_natural_height; if (min_height_p) *min_height_p = 0; sb_width = get_scrollbar_width (ST_SCROLL_VIEW (actor), -1); switch (priv->vscrollbar_policy) { case GTK_POLICY_NEVER: break; case GTK_POLICY_ALWAYS: case GTK_POLICY_AUTOMATIC: /* We've requested space for the scrollbar, subtract it back out */ for_width -= sb_width; break; } switch (priv->hscrollbar_policy) { case GTK_POLICY_NEVER: account_for_hscrollbar = FALSE; break; case GTK_POLICY_ALWAYS: account_for_hscrollbar = TRUE; break; case GTK_POLICY_AUTOMATIC: account_for_hscrollbar = for_width < child_min_width; break; } clutter_actor_get_preferred_height (priv->child, for_width, &child_min_height, &child_natural_height); natural_height = child_natural_height; switch (priv->vscrollbar_policy) { case GTK_POLICY_NEVER: min_height = child_min_height; break; case GTK_POLICY_ALWAYS: case GTK_POLICY_AUTOMATIC: /* Should theoretically use the min height of the vscrollbar, * but that's not cleanly defined at the moment */ min_height = 0; break; } if (account_for_hscrollbar) { float sb_height = get_scrollbar_height (ST_SCROLL_VIEW (actor), for_width); min_height += sb_height; natural_height += sb_height; } if (min_height_p) *min_height_p = min_height; if (natural_height_p) *natural_height_p = natural_height; st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p); } static gfloat get_shadow_height (ClutterActor *shadow) { gfloat natural_height; /* The shadows are empty StBin and have no height-for-width behavior */ clutter_actor_get_preferred_height (shadow, -1, NULL, &natural_height); return natural_height; } static void st_scroll_view_allocate (ClutterActor *actor, const ClutterActorBox *box, ClutterAllocationFlags flags) { ClutterActorBox content_box, child_box; ClutterActorClass *parent_parent_class; gfloat avail_width, avail_height, sb_width, sb_height; gboolean hscrollbar_visible, vscrollbar_visible; StScrollViewPrivate *priv = ST_SCROLL_VIEW (actor)->priv; StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor)); /* Chain up to the parent's parent class * * We do this because we do not want StBin to allocate the child, as we * give it a different allocation later, depending on whether the scrollbars * are visible */ parent_parent_class = g_type_class_peek_parent (st_scroll_view_parent_class); CLUTTER_ACTOR_CLASS (parent_parent_class)-> allocate (actor, box, flags); st_theme_node_get_content_box (theme_node, box, &content_box); avail_width = content_box.x2 - content_box.x1; avail_height = content_box.y2 - content_box.y1; if (clutter_actor_get_request_mode (actor) == CLUTTER_REQUEST_HEIGHT_FOR_WIDTH) { sb_width = get_scrollbar_width (ST_SCROLL_VIEW (actor), -1); sb_height = get_scrollbar_height (ST_SCROLL_VIEW (actor), sb_width); } else { sb_height = get_scrollbar_height (ST_SCROLL_VIEW (actor), -1); sb_width = get_scrollbar_width (ST_SCROLL_VIEW (actor), sb_height); } /* Determine what scrollbars are visible. The basic idea of the * handling of an automatic scrollbars is that we start off with the * assumption that we don't need any scrollbars, see if that works, * and if not add horizontal and vertical scrollbars until we are no * longer overflowing. */ if (priv->child) { gfloat child_min_width; gfloat child_min_height; clutter_actor_get_preferred_width (priv->child, -1, &child_min_width, NULL); if (priv->vscrollbar_policy == GTK_POLICY_AUTOMATIC) { if (priv->hscrollbar_policy == GTK_POLICY_AUTOMATIC) { /* Pass one, try without a vertical scrollbar */ clutter_actor_get_preferred_height (priv->child, avail_width, &child_min_height, NULL); vscrollbar_visible = child_min_height > avail_height; hscrollbar_visible = child_min_width > avail_width - (vscrollbar_visible ? sb_width : 0); vscrollbar_visible = child_min_height > avail_height - (hscrollbar_visible ? sb_height : 0); /* Pass two - if we needed a vertical scrollbar, get a new preferred height */ if (vscrollbar_visible) { clutter_actor_get_preferred_height (priv->child, MAX (avail_width - sb_width, 0), &child_min_height, NULL); hscrollbar_visible = child_min_width > avail_width - sb_width; } } else { hscrollbar_visible = priv->hscrollbar_policy != GTK_POLICY_NEVER; /* try without a vertical scrollbar */ clutter_actor_get_preferred_height (priv->child, avail_width, &child_min_height, NULL); vscrollbar_visible = child_min_height > avail_height - (hscrollbar_visible ? sb_height : 0); } } else { vscrollbar_visible = priv->vscrollbar_policy != GTK_POLICY_NEVER; if (priv->hscrollbar_policy == GTK_POLICY_AUTOMATIC) hscrollbar_visible = child_min_width > avail_height - (vscrollbar_visible ? 0 : sb_width); else hscrollbar_visible = priv->hscrollbar_policy != GTK_POLICY_NEVER; } } else { hscrollbar_visible = priv->hscrollbar_policy != GTK_POLICY_NEVER; vscrollbar_visible = priv->vscrollbar_policy != GTK_POLICY_NEVER; } /* Whether or not we show the scrollbars, if the scrollbars are visible * actors, we need to give them some allocation, so we unconditionally * give them the "right" allocation; that might overlap the child when * the scrollbars are not visible, but it doesn't matter because we * don't include them in pick or paint. */ /* Vertical scrollbar */ if (CLUTTER_ACTOR_IS_VISIBLE (priv->vscroll)) { child_box.x1 = content_box.x2 - sb_width; child_box.y1 = content_box.y1; child_box.x2 = content_box.x2; child_box.y2 = content_box.y2 - (hscrollbar_visible ? sb_height : 0); clutter_actor_allocate (priv->vscroll, &child_box, flags); } /* Horizontal scrollbar */ if (CLUTTER_ACTOR_IS_VISIBLE (priv->hscroll)) { child_box.x1 = content_box.x1; child_box.y1 = content_box.y2 - sb_height; child_box.x2 = content_box.x2 - (vscrollbar_visible ? sb_width : 0); child_box.y2 = content_box.y2; clutter_actor_allocate (priv->hscroll, &child_box, flags); } /* Now fold visibility into the scrollbar sizes to simplify the rest * of the computations. */ if (!hscrollbar_visible) sb_height = 0; if (!vscrollbar_visible) sb_width = 0; /* Child */ child_box.x1 = content_box.x1; child_box.y1 = content_box.y1; child_box.x2 = content_box.x2 - sb_width; child_box.y2 = content_box.y2 - sb_height; if (priv->child) clutter_actor_allocate (priv->child, &child_box, flags); /* Shadows */ if (priv->top_shadow && CLUTTER_ACTOR_IS_VISIBLE (priv->top_shadow)) { child_box.x1 = content_box.x1; child_box.y1 = content_box.y1; child_box.x2 = MAX (child_box.x1, content_box.x2 - sb_width); child_box.y2 = content_box.y1 + get_shadow_height (priv->top_shadow); clutter_actor_allocate (priv->top_shadow, &child_box, flags); } if (priv->bottom_shadow && CLUTTER_ACTOR_IS_VISIBLE (priv->bottom_shadow)) { child_box.x1 = content_box.x1; child_box.y1 = content_box.y2 - sb_height - get_shadow_height (priv->bottom_shadow); child_box.x2 = MAX (content_box.x1, content_box.x2 - sb_width); child_box.y2 = content_box.y2 - sb_height; clutter_actor_allocate (priv->bottom_shadow, &child_box, flags); } priv->hscrollbar_visible = hscrollbar_visible; priv->vscrollbar_visible = vscrollbar_visible; } static void st_scroll_view_style_changed (StWidget *widget) { StScrollViewPrivate *priv = ST_SCROLL_VIEW (widget)->priv; st_widget_style_changed (ST_WIDGET (priv->hscroll)); st_widget_style_changed (ST_WIDGET (priv->vscroll)); if (priv->top_shadow) { st_widget_style_changed (ST_WIDGET (priv->top_shadow)); st_widget_style_changed (ST_WIDGET (priv->bottom_shadow)); } ST_WIDGET_CLASS (st_scroll_view_parent_class)->style_changed (widget); } static gboolean st_scroll_view_scroll_event (ClutterActor *self, ClutterScrollEvent *event) { StScrollViewPrivate *priv = ST_SCROLL_VIEW (self)->priv; gdouble lower, value, upper, step; /* don't handle scroll events if requested not to */ if (!priv->mouse_scroll) return FALSE; switch (event->direction) { case CLUTTER_SCROLL_UP: case CLUTTER_SCROLL_DOWN: g_object_get (priv->vadjustment, "lower", &lower, "step-increment", &step, "value", &value, "upper", &upper, NULL); break; case CLUTTER_SCROLL_LEFT: case CLUTTER_SCROLL_RIGHT: g_object_get (priv->hadjustment, "lower", &lower, "step-increment", &step, "value", &value, "upper", &upper, NULL); break; } switch (event->direction) { case CLUTTER_SCROLL_UP: if (value == lower) return FALSE; else st_adjustment_set_value (priv->vadjustment, value - step); break; case CLUTTER_SCROLL_DOWN: if (value == upper) return FALSE; else st_adjustment_set_value (priv->vadjustment, value + step); break; case CLUTTER_SCROLL_LEFT: if (value == lower) return FALSE; else st_adjustment_set_value (priv->hadjustment, value - step); break; case CLUTTER_SCROLL_RIGHT: if (value == upper) return FALSE; else st_adjustment_set_value (priv->hadjustment, value + step); break; } return TRUE; } static void st_scroll_view_class_init (StScrollViewClass *klass) { GParamSpec *pspec; GObjectClass *object_class = G_OBJECT_CLASS (klass); ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); StWidgetClass *widget_class = ST_WIDGET_CLASS (klass); g_type_class_add_private (klass, sizeof (StScrollViewPrivate)); object_class->get_property = st_scroll_view_get_property; object_class->set_property = st_scroll_view_set_property; object_class->dispose = st_scroll_view_dispose; actor_class->paint = st_scroll_view_paint; actor_class->pick = st_scroll_view_pick; actor_class->get_preferred_width = st_scroll_view_get_preferred_width; actor_class->get_preferred_height = st_scroll_view_get_preferred_height; actor_class->allocate = st_scroll_view_allocate; actor_class->scroll_event = st_scroll_view_scroll_event; widget_class->style_changed = st_scroll_view_style_changed; g_object_class_install_property (object_class, PROP_HSCROLL, g_param_spec_object ("hscroll", "StScrollBar", "Horizontal scroll indicator", ST_TYPE_SCROLL_BAR, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_VSCROLL, g_param_spec_object ("vscroll", "StScrollBar", "Vertical scroll indicator", ST_TYPE_SCROLL_BAR, G_PARAM_READABLE)); pspec = g_param_spec_enum ("vscrollbar-policy", "Vertical Scrollbar Policy", "When the vertical scrollbar is displayed", GTK_TYPE_POLICY_TYPE, GTK_POLICY_AUTOMATIC, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_VSCROLLBAR_POLICY, pspec); pspec = g_param_spec_enum ("hscrollbar-policy", "Horizontal Scrollbar Policy", "When the horizontal scrollbar is displayed", GTK_TYPE_POLICY_TYPE, GTK_POLICY_AUTOMATIC, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_HSCROLLBAR_POLICY, pspec); pspec = g_param_spec_boolean ("enable-mouse-scrolling", "Enable Mouse Scrolling", "Enable automatic mouse wheel scrolling", TRUE, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_MOUSE_SCROLL, pspec); pspec = g_param_spec_boolean ("vshadows", "Vertical Shadows", "Show shadows at the top and and bottom of the area unless fully scrolled to that edge", FALSE, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_VSHADOWS, pspec); } static void child_adjustment_changed_cb (StAdjustment *adjustment, StScrollView *scroll) { update_shadow_visibility (scroll); } static void child_adjustment_notify_value (GObject *gobject, GParamSpec *pspec, StScrollView *scroll) { update_shadow_visibility (scroll); } static void st_scroll_view_init (StScrollView *self) { StScrollViewPrivate *priv = self->priv = SCROLL_VIEW_PRIVATE (self); priv->hscrollbar_policy = GTK_POLICY_AUTOMATIC; priv->vscrollbar_policy = GTK_POLICY_AUTOMATIC; priv->hadjustment = g_object_new (ST_TYPE_ADJUSTMENT, NULL); priv->hscroll = g_object_new (ST_TYPE_SCROLL_BAR, "adjustment", priv->hadjustment, "vertical", FALSE, NULL); priv->vadjustment = g_object_new (ST_TYPE_ADJUSTMENT, NULL); g_signal_connect (priv->vadjustment, "changed", G_CALLBACK (child_adjustment_changed_cb), self); g_signal_connect (priv->vadjustment, "notify::value", G_CALLBACK (child_adjustment_notify_value), self); priv->vscroll = g_object_new (ST_TYPE_SCROLL_BAR, "adjustment", priv->vadjustment, "vertical", TRUE, NULL); clutter_actor_set_parent (priv->hscroll, CLUTTER_ACTOR (self)); clutter_actor_set_parent (priv->vscroll, CLUTTER_ACTOR (self)); /* mouse scroll is enabled by default, so we also need to be reactive */ priv->mouse_scroll = TRUE; g_object_set (G_OBJECT (self), "reactive", TRUE, NULL); } static void st_scroll_view_add (ClutterContainer *container, ClutterActor *actor) { StScrollView *self = ST_SCROLL_VIEW (container); StScrollViewPrivate *priv = self->priv; if (ST_IS_SCROLLABLE (actor)) { priv->child = actor; /* chain up to StBin::add() */ st_scroll_view_parent_iface->add (container, actor); st_scrollable_set_adjustments (ST_SCROLLABLE (actor), priv->hadjustment, priv->vadjustment); } else { g_warning ("Attempting to add an actor of type %s to " "a StScrollView, but the actor does " "not implement StScrollable.", g_type_name (G_OBJECT_TYPE (actor))); } } static void st_scroll_view_remove (ClutterContainer *container, ClutterActor *actor) { StScrollView *self = ST_SCROLL_VIEW (container); StScrollViewPrivate *priv = self->priv; if (actor == priv->child) { g_object_ref (priv->child); /* chain up to StBin::remove() */ st_scroll_view_parent_iface->remove (container, actor); st_scrollable_set_adjustments (ST_SCROLLABLE (priv->child), NULL, NULL); g_object_unref (priv->child); priv->child = NULL; } else { if (actor == priv->vscroll) priv->vscroll = NULL; else if (actor == priv->hscroll) priv->hscroll = NULL; else g_assert ("Unknown child removed from StScrollView"); clutter_actor_unparent (actor); } } static void st_scroll_view_foreach_with_internals (ClutterContainer *container, ClutterCallback callback, gpointer user_data) { StScrollViewPrivate *priv = ST_SCROLL_VIEW (container)->priv; if (priv->child != NULL) callback (priv->child, user_data); if (priv->hscroll != NULL) callback (priv->hscroll, user_data); if (priv->vscroll != NULL) callback (priv->vscroll, user_data); if (priv->top_shadow) { callback (priv->top_shadow, user_data); callback (priv->bottom_shadow, user_data); } } static void clutter_container_iface_init (ClutterContainerIface *iface) { /* store a pointer to the StBin implementation of * ClutterContainer so that we can chain up when * overriding the methods */ st_scroll_view_parent_iface = g_type_interface_peek_parent (iface); iface->add = st_scroll_view_add; iface->remove = st_scroll_view_remove; iface->foreach_with_internals = st_scroll_view_foreach_with_internals; } StWidget * st_scroll_view_new (void) { return g_object_new (ST_TYPE_SCROLL_VIEW, NULL); } /** * st_scroll_view_get_hscroll_bar: * @scroll: a #StScrollView * * Gets the horizontal scrollbar of the scrollbiew * * Return value: (transfer none): the horizontal #StScrollbar */ ClutterActor * st_scroll_view_get_hscroll_bar (StScrollView *scroll) { g_return_val_if_fail (ST_IS_SCROLL_VIEW (scroll), NULL); return scroll->priv->hscroll; } /** * st_scroll_view_get_vscroll_bar: * @scroll: a #StScrollView * * Gets the vertical scrollbar of the scrollbiew * * Return value: (transfer none): the vertical #StScrollbar */ ClutterActor * st_scroll_view_get_vscroll_bar (StScrollView *scroll) { g_return_val_if_fail (ST_IS_SCROLL_VIEW (scroll), NULL); return scroll->priv->vscroll; } gfloat st_scroll_view_get_column_size (StScrollView *scroll) { gdouble column_size; g_return_val_if_fail (scroll, 0); g_object_get (scroll->priv->hadjustment, "step-increment", &column_size, NULL); return column_size; } void st_scroll_view_set_column_size (StScrollView *scroll, gfloat column_size) { g_return_if_fail (scroll); if (column_size < 0) { scroll->priv->column_size_set = FALSE; scroll->priv->column_size = -1; } else { scroll->priv->column_size_set = TRUE; scroll->priv->column_size = column_size; g_object_set (scroll->priv->hadjustment, "step-increment", (gdouble) scroll->priv->column_size, NULL); } } gfloat st_scroll_view_get_row_size (StScrollView *scroll) { gdouble row_size; g_return_val_if_fail (scroll, 0); g_object_get (scroll->priv->vadjustment, "step-increment", &row_size, NULL); return row_size; } void st_scroll_view_set_row_size (StScrollView *scroll, gfloat row_size) { g_return_if_fail (scroll); if (row_size < 0) { scroll->priv->row_size_set = FALSE; scroll->priv->row_size = -1; } else { scroll->priv->row_size_set = TRUE; scroll->priv->row_size = row_size; g_object_set (scroll->priv->vadjustment, "step-increment", (gdouble) scroll->priv->row_size, NULL); } } void st_scroll_view_set_mouse_scrolling (StScrollView *scroll, gboolean enabled) { StScrollViewPrivate *priv; g_return_if_fail (ST_IS_SCROLL_VIEW (scroll)); priv = ST_SCROLL_VIEW (scroll)->priv; if (priv->mouse_scroll != enabled) { priv->mouse_scroll = enabled; /* make sure we can receive mouse wheel events */ if (enabled) clutter_actor_set_reactive ((ClutterActor *) scroll, TRUE); } } gboolean st_scroll_view_get_mouse_scrolling (StScrollView *scroll) { StScrollViewPrivate *priv; g_return_val_if_fail (ST_IS_SCROLL_VIEW (scroll), FALSE); priv = ST_SCROLL_VIEW (scroll)->priv; return priv->mouse_scroll; } /** * st_scroll_view_set_policy: * @scroll: A #StScrollView * @hscroll: Whether to enable horizontal scrolling * @vscroll: Whether to enable vertical scrolling * * Set the scroll policy. */ void st_scroll_view_set_policy (StScrollView *scroll, GtkPolicyType hscroll, GtkPolicyType vscroll) { StScrollViewPrivate *priv; g_return_if_fail (ST_IS_SCROLL_VIEW (scroll)); priv = ST_SCROLL_VIEW (scroll)->priv; if (priv->hscrollbar_policy == hscroll && priv->vscrollbar_policy == vscroll) return; g_object_freeze_notify ((GObject *) scroll); if (priv->hscrollbar_policy != hscroll) { priv->hscrollbar_policy = hscroll; g_object_notify ((GObject *) scroll, "hscrollbar-policy"); } if (priv->vscrollbar_policy != vscroll) { priv->vscrollbar_policy = vscroll; g_object_notify ((GObject *) scroll, "vscrollbar-policy"); } clutter_actor_queue_relayout (CLUTTER_ACTOR (scroll)); g_object_thaw_notify ((GObject *) scroll); }