/* * GooCanvas. Copyright (C) 2005 Damon Chaplin. * Released under the GNU LGPL license. See COPYING for details. * * goocanvasgroupview.c - view for group item. */ /** * SECTION:goocanvasgroupview * @Title: GooCanvasGroupView * @Short_Description: a view for a group #GooCanvasItem. * * #GooCanvasGroupView represents a view of a group #GooCanvasItem (typically * a #GooCanvasGroup). * * It implements the #GooCanvasItemView interface, so you can use the * #GooCanvasItemView functions such as goo_canvas_item_view_get_item() * and goo_canvas_item_view_get_bounds(). * * Applications do not normally need to create item views themselves, as * they are created automatically by #GooCanvasView when needed. * * To respond to events such as mouse clicks in the ellipse view you can * connect to one of the #GooCanvasItemView signals such as * #GooCanvasItemView::button-press-event. You can connect to these signals * when the view is created. (See goo_canvas_view_get_item_view() and * #GooCanvasView::item-view-created.) */ #include #include #include "goocanvasprivate.h" #include "goocanvasgroupview.h" #include "goocanvasgroup.h" #include "goocanvasview.h" #include "goocanvasutils.h" #include "goocanvasatk.h" enum { PROP_0, PROP_CAN_FOCUS }; static void canvas_item_view_interface_init (GooCanvasItemViewIface *iface); static void goo_canvas_group_view_finalize (GObject *object); static void goo_canvas_group_view_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void goo_canvas_group_view_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void add_child_views_recursively (GooCanvasGroupView *group_view); static void connect_group_signals (GooCanvasGroupView *group_view); static void on_group_changed (GooCanvasItem *group, gboolean recompute_bounds, GooCanvasGroupView *view); G_DEFINE_TYPE_WITH_CODE (GooCanvasGroupView, goo_canvas_group_view, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM_VIEW, canvas_item_view_interface_init)) static void goo_canvas_group_view_class_init (GooCanvasGroupViewClass *klass) { GObjectClass *gobject_class = (GObjectClass*) klass; gobject_class->finalize = goo_canvas_group_view_finalize; gobject_class->get_property = goo_canvas_group_view_get_property; gobject_class->set_property = goo_canvas_group_view_set_property; atk_registry_set_factory_type (atk_get_default_registry (), GOO_TYPE_CANVAS_GROUP_VIEW, goo_canvas_item_view_accessible_factory_get_type ()); g_object_class_override_property (gobject_class, PROP_CAN_FOCUS, "can-focus"); } static void goo_canvas_group_view_init (GooCanvasGroupView *group_view) { group_view->item_views = g_ptr_array_sized_new (8); group_view->flags = GOO_CANVAS_ITEM_VIEW_NEED_UPDATE | GOO_CANVAS_ITEM_VIEW_NEED_ENTIRE_SUBTREE_UPDATE; } static void goo_canvas_group_view_title_changed (GooCanvasItem *group, GParamSpec *pspec, GooCanvasGroupView *group_view) { AtkObject *accessible; gchar *title; accessible = atk_gobject_accessible_for_object (G_OBJECT (group_view)); g_object_get (group, "title", &title, NULL); atk_object_set_name (accessible, title); g_free (title); } static void goo_canvas_group_view_description_changed (GooCanvasItem *group, GParamSpec *pspec, GooCanvasGroupView *group_view) { AtkObject *accessible; gchar *description; accessible = atk_gobject_accessible_for_object (G_OBJECT (group_view)); g_object_get (group, "description", &description, NULL); atk_object_set_description (accessible, description); g_free (description); } /** * goo_canvas_group_view_set_group: * @group_view: a #GooCanvasGroupView. * @group: a #GooCanvasItem. * * This function is only intended to be used by subclasses during construction * of the item views. * * It sets the underlying group item, creates any necessary child views, and * sets up signal handlers to update child views as the underlying items * are changed. **/ void goo_canvas_group_view_set_group (GooCanvasGroupView *group_view, GooCanvasItem *group) { AtkObject *accessible; gchar *title, *description; if (group_view->group) { g_signal_handlers_disconnect_matched (group_view->group, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, group_view); g_object_unref (group_view->group); } group_view->group = g_object_ref (group); g_object_get (group, "title", &title, "description", &description, NULL); accessible = atk_gobject_accessible_for_object (G_OBJECT (group_view)); if (title) atk_object_set_name (accessible, title); if (description) atk_object_set_description (accessible, description); g_free (title); g_free (description); g_signal_connect (group, "notify::title", G_CALLBACK (goo_canvas_group_view_title_changed), group_view); g_signal_connect (group, "notify::description", G_CALLBACK (goo_canvas_group_view_description_changed), group_view); add_child_views_recursively (group_view); connect_group_signals (group_view); g_signal_connect (group, "changed", G_CALLBACK (on_group_changed), group_view); } /** * goo_canvas_group_view_new: * @canvas_view: the canvas view. * @parent_view: the parent view. * @group: the group item. * * Creates a new #GooCanvasGroupView for the given #GooCanvasItem. * * This is not normally used by application code, as the views are created * automatically by #GooCanvasView. * * Returns: a new #GooCanvasGroupView. **/ GooCanvasItemView* goo_canvas_group_view_new (GooCanvasView *canvas_view, GooCanvasItemView *parent_view, GooCanvasItem *group) { GooCanvasGroupView *group_view; group_view = g_object_new (GOO_TYPE_CANVAS_GROUP_VIEW, NULL); group_view->canvas_view = canvas_view; group_view->parent_view = parent_view; goo_canvas_group_view_set_group (group_view, group); return GOO_CANVAS_ITEM_VIEW (group_view); } static void goo_canvas_group_view_finalize (GObject *object) { GooCanvasGroupView *group_view = (GooCanvasGroupView*) object; gint i; /* Unref all the items in the group. */ for (i = 0; i < group_view->item_views->len; i++) { GooCanvasItemView *item_view = group_view->item_views->pdata[i]; goo_canvas_item_view_set_parent_view (item_view, NULL); g_object_unref (item_view); } g_ptr_array_free (group_view->item_views, TRUE); /* Remove the view from the GooCanvasView hash table. */ goo_canvas_view_unregister_item_view (group_view->canvas_view, group_view->group); /* Unref the group. */ g_object_unref (group_view->group); group_view->group = NULL; G_OBJECT_CLASS (goo_canvas_group_view_parent_class)->finalize (object); } static void goo_canvas_group_view_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GooCanvasGroupView *group_view = (GooCanvasGroupView*) object; switch (prop_id) { case PROP_CAN_FOCUS: g_value_set_boolean (value, group_view->flags & GOO_CANVAS_ITEM_VIEW_CAN_FOCUS); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void goo_canvas_group_view_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GooCanvasGroupView *group_view = (GooCanvasGroupView*) object; switch (prop_id) { case PROP_CAN_FOCUS: if (g_value_get_boolean (value)) group_view->flags |= GOO_CANVAS_ITEM_VIEW_CAN_FOCUS; else group_view->flags &= ~GOO_CANVAS_ITEM_VIEW_CAN_FOCUS; break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void on_child_item_added (GooCanvasItem *group, gint position, GooCanvasGroupView *group_view) { GooCanvasItem *item; GooCanvasItemView *view; /* Get a view for the item. */ item = goo_canvas_item_get_child (group, position); view = goo_canvas_view_create_item_view (group_view->canvas_view, item, (GooCanvasItemView*) group_view); if (position >= 0) goo_canvas_util_ptr_array_insert (group_view->item_views, view, position); else g_ptr_array_add (group_view->item_views, view); goo_canvas_item_view_request_update ((GooCanvasItemView*) group_view); } static void on_child_item_moved (GooCanvasItem *group, gint old_position, gint new_position, GooCanvasGroupView *group_view) { GooCanvasItemView *view; GooCanvasBounds bounds; /* Request a redraw of the item's bounds. */ view = group_view->item_views->pdata[old_position]; goo_canvas_item_view_get_bounds (view, &bounds); goo_canvas_view_request_redraw (group_view->canvas_view, &bounds); goo_canvas_util_ptr_array_move (group_view->item_views, old_position, new_position); goo_canvas_item_view_request_update ((GooCanvasItemView*) group_view); } static void on_child_item_removed (GooCanvasItem *group, gint item_num, GooCanvasGroupView *group_view) { GooCanvasItemView *view; GooCanvasBounds bounds; view = group_view->item_views->pdata[item_num]; /* Request a redraw of the item's bounds. */ goo_canvas_item_view_get_bounds (view, &bounds); goo_canvas_view_request_redraw (group_view->canvas_view, &bounds); /* Reset the item's parent to NULL, in case it is kept around. */ goo_canvas_item_view_set_parent_view (view, NULL); g_object_unref (view); g_ptr_array_remove_index (group_view->item_views, item_num); goo_canvas_item_view_request_update ((GooCanvasItemView*) group_view); } static void add_child_views_recursively (GooCanvasGroupView *group_view) { gint n_children, i; n_children = goo_canvas_item_get_n_children (group_view->group); for (i = 0; i < n_children; i++) { on_child_item_added (group_view->group, i, group_view); } } static void connect_group_signals (GooCanvasGroupView *group_view) { GooCanvasItem *group = group_view->group; g_signal_connect (group, "child-added", G_CALLBACK (on_child_item_added), group_view); g_signal_connect (group, "child-moved", G_CALLBACK (on_child_item_moved), group_view); g_signal_connect (group, "child-removed", G_CALLBACK (on_child_item_removed), group_view); } static GooCanvasView* goo_canvas_group_view_get_canvas_view (GooCanvasItemView *view) { GooCanvasGroupView *group_view = (GooCanvasGroupView*) view; return group_view->canvas_view; } static gint goo_canvas_group_view_get_n_children (GooCanvasItemView *view) { GooCanvasGroupView *group_view = (GooCanvasGroupView*) view; return group_view->item_views->len; } static GooCanvasItemView* goo_canvas_group_view_get_child (GooCanvasItemView *view, gint child_num) { GooCanvasGroupView *group_view = (GooCanvasGroupView*) view; return group_view->item_views->pdata[child_num]; } static void goo_canvas_group_view_request_update (GooCanvasItemView *view) { GooCanvasGroupView *group_view = (GooCanvasGroupView*) view; if (!(group_view->flags & GOO_CANVAS_ITEM_VIEW_NEED_UPDATE)) { group_view->flags |= GOO_CANVAS_ITEM_VIEW_NEED_UPDATE; if (group_view->parent_view) goo_canvas_item_view_request_update (group_view->parent_view); else goo_canvas_view_request_update (group_view->canvas_view); } } static void goo_canvas_group_view_update (GooCanvasItemView *view, gboolean entire_tree, cairo_t *cr, GooCanvasBounds *bounds) { GooCanvasGroupView *group_view = (GooCanvasGroupView*) view; GooCanvasBounds child_bounds; cairo_matrix_t transform; gint i; if (entire_tree || (group_view->flags & GOO_CANVAS_ITEM_VIEW_NEED_UPDATE)) { if (group_view->flags & GOO_CANVAS_ITEM_VIEW_NEED_ENTIRE_SUBTREE_UPDATE) entire_tree = TRUE; group_view->flags &= ~GOO_CANVAS_ITEM_VIEW_NEED_UPDATE; group_view->flags &= ~GOO_CANVAS_ITEM_VIEW_NEED_ENTIRE_SUBTREE_UPDATE; group_view->bounds.x1 = group_view->bounds.y1 = 0.0; group_view->bounds.x2 = group_view->bounds.y2 = 0.0; cairo_save (cr); if (goo_canvas_item_view_get_combined_transform (view, &transform)) cairo_transform (cr, &transform); for (i = 0; i < group_view->item_views->len; i++) { GooCanvasItemView *child_view = group_view->item_views->pdata[i]; goo_canvas_item_view_update (child_view, entire_tree, cr, &child_bounds); if (i == 0) { group_view->bounds.x1 = child_bounds.x1; group_view->bounds.y1 = child_bounds.y1; group_view->bounds.x2 = child_bounds.x2; group_view->bounds.y2 = child_bounds.y2; } else { group_view->bounds.x1 = MIN (group_view->bounds.x1, child_bounds.x1); group_view->bounds.y1 = MIN (group_view->bounds.y1, child_bounds.y1); group_view->bounds.x2 = MAX (group_view->bounds.x2, child_bounds.x2); group_view->bounds.y2 = MAX (group_view->bounds.y2, child_bounds.y2); } } cairo_restore (cr); } *bounds = group_view->bounds; } static GooCanvasItemView* goo_canvas_group_view_get_parent_view (GooCanvasItemView *view) { GooCanvasGroupView *group_view = (GooCanvasGroupView*) view; return group_view->parent_view; } static void goo_canvas_group_view_set_parent_view (GooCanvasItemView *view, GooCanvasItemView *parent_view) { GooCanvasGroupView *group_view = (GooCanvasGroupView*) view; group_view->parent_view = parent_view; } static GooCanvasItem* goo_canvas_group_view_get_item (GooCanvasItemView *view) { GooCanvasGroupView *group_view = (GooCanvasGroupView*) view; return (GooCanvasItem*) group_view->group; } static void goo_canvas_group_view_get_bounds (GooCanvasItemView *view, GooCanvasBounds *bounds) { GooCanvasGroupView *group_view = (GooCanvasGroupView*) view; if (group_view->flags & GOO_CANVAS_ITEM_VIEW_NEED_UPDATE) goo_canvas_item_view_ensure_updated (view); *bounds = group_view->bounds; } static GooCanvasItemView* goo_canvas_group_view_get_item_view_at (GooCanvasItemView *view, gdouble x, gdouble y, cairo_t *cr, gboolean is_pointer_event, gboolean parent_visible) { GooCanvasGroupView *group_view = (GooCanvasGroupView*) view; GooCanvasBounds child_bounds; GooCanvasItemView *found_item = NULL; GooCanvasItemVisibility visibility; gboolean visible = parent_visible; cairo_matrix_t transform; int i; if (group_view->flags & GOO_CANVAS_ITEM_VIEW_NEED_UPDATE) goo_canvas_item_view_ensure_updated (view); g_object_get (group_view->group, "visibility", &visibility, NULL); if (visibility == GOO_CANVAS_ITEM_INVISIBLE) { visible = FALSE; } else if (visibility == GOO_CANVAS_ITEM_VISIBLE_ABOVE_THRESHOLD) { gdouble visibility_threshold; g_object_get (group_view->group, "visibility-threshold", &visibility_threshold, NULL); if (group_view->canvas_view->scale < visibility_threshold) visible = FALSE; } /* Check if the group should receive events. */ if (is_pointer_event) { GooCanvasPointerEvents pointer_events; g_object_get (group_view->group, "pointer-events", &pointer_events, NULL); if (pointer_events == GOO_CANVAS_EVENTS_NONE || ((pointer_events & GOO_CANVAS_EVENTS_VISIBLE_MASK) && !visible)) return NULL; } /* Step down from the top item to the bottom in the stack/layer, and return the first item found that contains the given point. */ cairo_save (cr); if (goo_canvas_item_view_get_combined_transform (view, &transform)) cairo_transform (cr, &transform); for (i = group_view->item_views->len - 1; i >= 0; i--) { GooCanvasItemView *child_view = group_view->item_views->pdata[i]; goo_canvas_item_view_get_bounds (child_view, &child_bounds); /* Skip the item if the bounds don't contain the point. */ if (child_bounds.x1 > x || child_bounds.x2 < x || child_bounds.y1 > y || child_bounds.y2 < y) continue; found_item = goo_canvas_item_view_get_item_view_at (child_view, x, y, cr, is_pointer_event, visible); if (found_item) break; } cairo_restore (cr); return found_item; } static gboolean goo_canvas_group_view_is_visible (GooCanvasItemView *view) { GooCanvasGroupView *group_view = (GooCanvasGroupView*) view; GooCanvasItemVisibility visibility; g_object_get (group_view->group, "visibility", &visibility, NULL); if (visibility == GOO_CANVAS_ITEM_INVISIBLE) return FALSE; if (visibility == GOO_CANVAS_ITEM_VISIBLE_ABOVE_THRESHOLD) { gdouble visibility_threshold; g_object_get (group_view->group, "visibility-threshold", &visibility_threshold, NULL); if (group_view->canvas_view->scale < visibility_threshold) return FALSE; } if (group_view->parent_view) return goo_canvas_item_view_is_visible (group_view->parent_view); return TRUE; } static void goo_canvas_group_view_paint (GooCanvasItemView *view, cairo_t *cr, GooCanvasBounds *bounds, gdouble scale) { GooCanvasGroupView *group_view = (GooCanvasGroupView*) view; GooCanvasBounds child_bounds; GooCanvasItemVisibility visibility; cairo_matrix_t transform; gint i; /* Check if the item should be visible. */ g_object_get (group_view->group, "visibility", &visibility, NULL); if (visibility == GOO_CANVAS_ITEM_INVISIBLE) return; if (visibility == GOO_CANVAS_ITEM_VISIBLE_ABOVE_THRESHOLD) { gdouble visibility_threshold; g_object_get (group_view->group, "visibility-threshold", &visibility_threshold, NULL); if (group_view->canvas_view->scale < visibility_threshold) return; } /* Paint all the items in the group. */ cairo_save (cr); if (goo_canvas_item_view_get_combined_transform (view, &transform)) cairo_transform (cr, &transform); for (i = 0; i < group_view->item_views->len; i++) { GooCanvasItemView *child_view = group_view->item_views->pdata[i]; goo_canvas_item_view_get_bounds (child_view, &child_bounds); /* Skip the item if the bounds don't intersect the expose rectangle. */ if (child_bounds.x1 > bounds->x2 || child_bounds.x2 < bounds->x1 || child_bounds.y1 > bounds->y2 || child_bounds.y2 < bounds->y1) continue; goo_canvas_item_view_paint (child_view, cr, bounds, scale); } cairo_restore (cr); } static void canvas_item_view_interface_init (GooCanvasItemViewIface *iface) { iface->get_canvas_view = goo_canvas_group_view_get_canvas_view; iface->get_n_children = goo_canvas_group_view_get_n_children; iface->get_child = goo_canvas_group_view_get_child; iface->request_update = goo_canvas_group_view_request_update; iface->get_parent_view = goo_canvas_group_view_get_parent_view; iface->set_parent_view = goo_canvas_group_view_set_parent_view; iface->get_item = goo_canvas_group_view_get_item; iface->get_bounds = goo_canvas_group_view_get_bounds; iface->get_item_view_at = goo_canvas_group_view_get_item_view_at; iface->is_visible = goo_canvas_group_view_is_visible; iface->update = goo_canvas_group_view_update; iface->paint = goo_canvas_group_view_paint; } static void on_group_changed (GooCanvasItem *group, gboolean recompute_bounds, GooCanvasGroupView *view) { view->flags |= GOO_CANVAS_ITEM_VIEW_NEED_ENTIRE_SUBTREE_UPDATE; goo_canvas_item_view_request_update ((GooCanvasItemView*) view); }