/* * GooCanvas. Copyright (C) 2005 Damon Chaplin. * Released under the GNU LGPL license. See COPYING for details. * * goocanvasitem.c - interface for canvas items & groups. */ /** * SECTION:goocanvasitem * @Title: GooCanvasItem * @Short_Description: the interface for canvas items. * * #GooCanvasItem defines the interface that canvas items must implement, * and contains methods for operating on canvas items. */ #include #include #include #include #include "goocanvasprivate.h" #include "goocanvasitem.h" #include "goocanvasutils.h" #include "goocanvasmarshal.h" static const char *animation_key = "GooCanvasItemAnimation"; enum { CHILD_ADDED, CHILD_MOVED, CHILD_REMOVED, CHANGED, LAST_SIGNAL }; static guint canvas_item_signals[LAST_SIGNAL] = { 0 }; static void goo_canvas_item_base_init (gpointer g_class); GType goo_canvas_item_get_type (void) { static GType canvas_item_type = 0; if (!canvas_item_type) { static const GTypeInfo canvas_item_info = { sizeof (GooCanvasItemIface), /* class_size */ goo_canvas_item_base_init, /* base_init */ NULL, /* base_finalize */ }; canvas_item_type = g_type_register_static (G_TYPE_INTERFACE, "GooCanvasItem", &canvas_item_info, 0); g_type_interface_add_prerequisite (canvas_item_type, G_TYPE_OBJECT); } return canvas_item_type; } static void goo_canvas_item_base_init (gpointer g_iface) { static gboolean initialized = FALSE; if (!initialized) { GType iface_type = G_TYPE_FROM_INTERFACE (g_iface); /** * GooCanvasItem::child-added * @item: the item that received the signal. * @child_num: the index of the new child. * * Emitted when a child has been added to the container item. */ canvas_item_signals[CHILD_ADDED] = g_signal_new ("child-added", iface_type, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GooCanvasItemIface, child_added), NULL, NULL, goo_canvas_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT); /** * GooCanvasItem::child-moved * @item: the item that received the signal. * @old_child_num: the old index of the child. * @new_child_num: the new index of the child. * * Emitted when a child has been moved in the stacking order of a * container item. */ canvas_item_signals[CHILD_MOVED] = g_signal_new ("child-moved", iface_type, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GooCanvasItemIface, child_moved), NULL, NULL, goo_canvas_marshal_VOID__INT_INT, G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT); /** * GooCanvasItem::child-removed * @item: the item that received the signal. * @child_num: the index of the child that was removed. * * Emitted when a child has been removed from the container item. */ canvas_item_signals[CHILD_REMOVED] = g_signal_new ("child-removed", iface_type, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GooCanvasItemIface, child_removed), NULL, NULL, goo_canvas_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT); /** * GooCanvasItem::changed * @item: the item that received the signal. * @recompute_bounds: if the bounds of the item need to be recomputed. * * Emitted when the item has been changed. */ canvas_item_signals[CHANGED] = g_signal_new ("changed", iface_type, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GooCanvasItemIface, changed), NULL, NULL, goo_canvas_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); g_object_interface_install_property (g_iface, g_param_spec_enum ("visibility", _("Visibility"), _("When the canvas item is visible"), GOO_TYPE_CANVAS_ITEM_VISIBILITY, GOO_CANVAS_ITEM_VISIBLE, G_PARAM_READWRITE)); g_object_interface_install_property (g_iface, g_param_spec_double ("visibility-threshold", _("Visibility Threshold"), _("The scale threshold at which the item becomes visible"), 0.0, G_MAXDOUBLE, 0.0, G_PARAM_READWRITE)); g_object_interface_install_property (g_iface, g_param_spec_boxed ("transform", _("Transform"), _("The transformation matrix of the item"), GOO_TYPE_CAIRO_MATRIX, G_PARAM_READWRITE)); g_object_interface_install_property (g_iface, g_param_spec_flags ("pointer-events", _("Pointer Events"), _("Specifies when the item receives pointer events"), GOO_TYPE_CANVAS_POINTER_EVENTS, GOO_CANVAS_EVENTS_VISIBLE_PAINTED, G_PARAM_READWRITE)); g_object_interface_install_property (g_iface, g_param_spec_string ("title", _("Title"), _("A short context-rich description of the item for use by assistive technologies"), NULL, G_PARAM_READWRITE)); g_object_interface_install_property (g_iface, g_param_spec_string ("description", _("Description"), _("A description of the item for use by assistive technologies"), NULL, G_PARAM_READWRITE)); initialized = TRUE; } } /** * goo_canvas_item_add_child: * @group: the container to add the item to. * @item: the item to add. * @position: the position of the item, or -1 to place it last (at the top of * the stacking order). * * Adds a child item to a container item at the given stack position. **/ void goo_canvas_item_add_child (GooCanvasItem *group, GooCanvasItem *item, gint position) { GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (group); g_return_if_fail (iface->add_child != NULL); iface->add_child (group, item, position); } /** * goo_canvas_item_move_child: * @group: a container item. * @old_position: the current position of the child item. * @new_position: the new position of the child item. * * Moves a child item to a new stack position within the container. **/ void goo_canvas_item_move_child (GooCanvasItem *group, gint old_position, gint new_position) { GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (group); g_return_if_fail (iface->move_child != NULL); iface->move_child (group, old_position, new_position); } /** * goo_canvas_item_remove_child: * @group: a container item. * @child_num: the position of the child item to remove. * * Removes the child item at the given position. **/ void goo_canvas_item_remove_child (GooCanvasItem *group, gint child_num) { GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (group); g_return_if_fail (iface->remove_child != NULL); iface->remove_child (group, child_num); } /** * goo_canvas_item_find_child: * @group: a container item. * @child: the child item to find. * * Attempts to find the given child item with the container's stack. * * Returns: the position of the given @child item, or -1 if it isn't found. **/ gint goo_canvas_item_find_child (GooCanvasItem *group, GooCanvasItem *child) { GooCanvasItem *item; int n_children, i; /* Find the current position of item and above. */ n_children = goo_canvas_item_get_n_children (group); for (i = 0; i < n_children; i++) { item = goo_canvas_item_get_child (group, i); if (child == item) return i; } return -1; } /** * goo_canvas_item_is_container: * @item: an item. * * Tests to see if the given item is a container. * * Returns: %TRUE if the item is a container. **/ gboolean goo_canvas_item_is_container (GooCanvasItem *item) { GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item); return iface->get_n_children ? TRUE : FALSE; } /** * goo_canvas_item_get_n_children: * @group: a container item. * * Gets the number of children of the container. * * Returns: the number of children. **/ gint goo_canvas_item_get_n_children (GooCanvasItem *group) { GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (group); return iface->get_n_children ? iface->get_n_children (group) : 0; } /** * goo_canvas_item_get_child: * @group: a container item. * @child_num: the position of a child in the container's stack. * * Gets the child item at the given stack position. * * Returns: the child item at the given stack position. **/ GooCanvasItem* goo_canvas_item_get_child (GooCanvasItem *group, gint child_num) { GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (group); return iface->get_child ? iface->get_child (group, child_num) : NULL; } /** * goo_canvas_item_get_model: * @item: an item. * * Gets the canvas model containing the given item. * * Returns: the canvas model, or %NULL of the item isn't in a model. **/ GooCanvasModel* goo_canvas_item_get_model (GooCanvasItem *item) { GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item); /* If the item has no get_model() method, we try it on the parent item instead. This means only group items need to implement this method. */ if (iface->get_model) return iface->get_model (item); else return goo_canvas_item_get_model (iface->get_parent (item)); } /** * goo_canvas_item_get_parent: * @item: an item. * * Gets the parent of the given item. * * Returns: the parent item, or %NULL if the item has no parent. **/ GooCanvasItem* goo_canvas_item_get_parent (GooCanvasItem *item) { return GOO_CANVAS_ITEM_GET_IFACE (item)->get_parent (item); } /** * goo_canvas_item_set_parent: * @item: an item. * @parent: the new parent item. * * Sets the parent of the item. **/ void goo_canvas_item_set_parent (GooCanvasItem *item, GooCanvasItem *parent) { GOO_CANVAS_ITEM_GET_IFACE (item)->set_parent (item, parent); } /** * goo_canvas_item_raise: * @item: an item. * @above: the item to raise @item above, or %NULL to raise @item to the top * of the stack. * * Raises an item in the stacking order. **/ void goo_canvas_item_raise (GooCanvasItem *item, GooCanvasItem *above) { GooCanvasItem *parent, *child; int n_children, i, item_pos = -1, above_pos = -1; parent = goo_canvas_item_get_parent (item); if (!parent || item == above) return; /* Find the current position of item and above. */ n_children = goo_canvas_item_get_n_children (parent); for (i = 0; i < n_children; i++) { child = goo_canvas_item_get_child (parent, i); if (child == item) item_pos = i; if (child == above) above_pos = i; } /* If above is NULL we raise the item to the top of the stack. */ if (!above) above_pos = n_children - 1; g_return_if_fail (item_pos != -1); g_return_if_fail (above_pos != -1); /* Only move the item if the new position is higher in the stack. */ if (above_pos > item_pos) goo_canvas_item_move_child (parent, item_pos, above_pos); } /** * goo_canvas_item_lower: * @item: an item. * @below: the item to lower @item below, or %NULL to lower @item to the * bottom of the stack. * * Lowers an item in the stacking order. **/ void goo_canvas_item_lower (GooCanvasItem *item, GooCanvasItem *below) { GooCanvasItem *parent, *child; int n_children, i, item_pos = -1, below_pos = -1; parent = goo_canvas_item_get_parent (item); if (!parent || item == below) return; /* Find the current position of item and below. */ n_children = goo_canvas_item_get_n_children (parent); for (i = 0; i < n_children; i++) { child = goo_canvas_item_get_child (parent, i); if (child == item) item_pos = i; if (child == below) below_pos = i; } /* If below is NULL we lower the item to the bottom of the stack. */ if (!below) below_pos = 0; g_return_if_fail (item_pos != -1); g_return_if_fail (below_pos != -1); /* Only move the item if the new position is lower in the stack. */ if (below_pos < item_pos) goo_canvas_item_move_child (parent, item_pos, below_pos); } /** * goo_canvas_item_get_transform: * @item: an item. * * Gets the transformation matrix of an item. * * Returns: the item's transformation matrix. **/ cairo_matrix_t* goo_canvas_item_get_transform (GooCanvasItem *item) { GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item); return iface->get_transform ? iface->get_transform (item) : NULL; } /** * goo_canvas_item_set_transform: * @item: an item. * @matrix: the new transformation matrix, or %NULL to reset the * transformation to the identity matrix. * * Sets the transformation matrix of an item. **/ void goo_canvas_item_set_transform (GooCanvasItem *item, cairo_matrix_t *matrix) { GOO_CANVAS_ITEM_GET_IFACE (item)->set_transform (item, matrix); } /** * goo_canvas_item_translate: * @item: an item. * @tx: the amount to move the origin in the horizontal direction. * @ty: the amount to move the origin in the vertical direction. * * Translates the origin of the item's coordinate system by the given amounts. **/ void goo_canvas_item_translate (GooCanvasItem *item, double tx, double ty) { GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item); cairo_matrix_t *matrix, new_matrix = { 1, 0, 0, 1, 0, 0 }; matrix = iface->get_transform (item); if (matrix) new_matrix = *matrix; cairo_matrix_translate (&new_matrix, tx, ty); iface->set_transform (item, &new_matrix); } /** * goo_canvas_item_scale: * @item: an item. * @sx: the amount to scale the horizontal axis. * @sy: the amount to scale the vertical axis. * * Scales the item's coordinate system by the given amounts. **/ void goo_canvas_item_scale (GooCanvasItem *item, double sx, double sy) { GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item); cairo_matrix_t *matrix, new_matrix = { 1, 0, 0, 1, 0, 0 }; matrix = iface->get_transform (item); if (matrix) new_matrix = *matrix; cairo_matrix_scale (&new_matrix, sx, sy); iface->set_transform (item, &new_matrix); } /** * goo_canvas_item_rotate: * @item: an item. * @degrees: the clockwise angle of rotation. * @cx: the x coordinate of the origin of the rotation. * @cy: the y coordinate of the origin of the rotation. * * Rotates the item's coordinate system by the given amount, about the given * origin. **/ void goo_canvas_item_rotate (GooCanvasItem *item, double degrees, double cx, double cy) { GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item); cairo_matrix_t *matrix, new_matrix = { 1, 0, 0, 1, 0, 0 }; double radians = degrees * (M_PI / 180); matrix = iface->get_transform (item); if (matrix) new_matrix = *matrix; cairo_matrix_translate (&new_matrix, cx, cy); cairo_matrix_rotate (&new_matrix, radians); cairo_matrix_translate (&new_matrix, -cx, -cy); iface->set_transform (item, &new_matrix); } /** * goo_canvas_item_skew_x: * @item: an item. * @degrees: the skew angle. * @cx: the x coordinate of the origin of the skew transform. * @cy: the y coordinate of the origin of the skew transform. * * Skews the item's coordinate system along the x axis by the given amount, * about the given origin. **/ void goo_canvas_item_skew_x (GooCanvasItem *item, double degrees, double cx, double cy) { GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item); cairo_matrix_t *matrix, tmp, new_matrix = { 1, 0, 0, 1, 0, 0 }; double radians = degrees * (M_PI / 180); matrix = iface->get_transform (item); if (matrix) new_matrix = *matrix; cairo_matrix_translate (&new_matrix, cx, cy); cairo_matrix_init (&tmp, 1, 0, tan (radians), 1, 0, 0); cairo_matrix_multiply (&new_matrix, &tmp, &new_matrix); cairo_matrix_translate (&new_matrix, -cx, -cy); iface->set_transform (item, &new_matrix); } /** * goo_canvas_item_skew_y: * @item: an item. * @degrees: the skew angle. * @cx: the x coordinate of the origin of the skew transform. * @cy: the y coordinate of the origin of the skew transform. * * Skews the item's coordinate system along the y axis by the given amount, * about the given origin. **/ void goo_canvas_item_skew_y (GooCanvasItem *item, double degrees, double cx, double cy) { GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item); cairo_matrix_t *matrix, tmp, new_matrix = { 1, 0, 0, 1, 0, 0 }; double radians = degrees * (M_PI / 180); matrix = iface->get_transform (item); if (matrix) new_matrix = *matrix; cairo_matrix_translate (&new_matrix, cx, cy); cairo_matrix_init (&tmp, 1, tan (radians), 0, 1, 0, 0); cairo_matrix_multiply (&new_matrix, &tmp, &new_matrix); cairo_matrix_translate (&new_matrix, -cx, -cy); iface->set_transform (item, &new_matrix); } typedef struct _GooCanvasItemAnimation GooCanvasItemAnimation; struct _GooCanvasItemAnimation { GooCanvasAnimateType type; GooCanvasItem *item; int num_steps_left, total_steps; cairo_matrix_t start, step; gboolean forward; guint timeout_id; }; static void goo_canvas_item_free_animation (GooCanvasItemAnimation *anim) { if (anim->timeout_id) { g_source_remove (anim->timeout_id); anim->timeout_id = 0; } g_free (anim); } static gboolean goo_canvas_item_animate_cb (GooCanvasItemAnimation *anim) { GooCanvasItemIface *iface; cairo_matrix_t *matrix, new_matrix = { 1, 0, 0, 1, 0, 0 }; gboolean keep_source = TRUE; GDK_THREADS_ENTER (); iface = GOO_CANVAS_ITEM_GET_IFACE (anim->item); matrix = iface->get_transform (anim->item); if (matrix) new_matrix = *matrix; new_matrix.xx += anim->step.xx; new_matrix.yx += anim->step.yx; new_matrix.xy += anim->step.xy; new_matrix.yy += anim->step.yy; new_matrix.x0 += anim->step.x0; new_matrix.y0 += anim->step.y0; iface->set_transform (anim->item, &new_matrix); if (--anim->num_steps_left == 0) { switch (anim->type) { case GOO_CANVAS_ANIMATE_RESET: /* Reset the transform to the initial value. */ /* FIXME: Need to wait one cycle at finish position? */ iface->set_transform (anim->item, &anim->start); /* Fall through.. */ case GOO_CANVAS_ANIMATE_FREEZE: keep_source = FALSE; anim->timeout_id = 0; /* This will result in a call to goo_canvas_item_free_animation() above. We've set the timeout_id to 0 so it isn't removed twice. */ g_object_set_data (G_OBJECT (anim->item), animation_key, NULL); break; case GOO_CANVAS_ANIMATE_RESTART: iface->set_transform (anim->item, &anim->start); break; case GOO_CANVAS_ANIMATE_BOUNCE: /* Switch all the step values around. */ anim->step.xx = -anim->step.xx; anim->step.yx = -anim->step.yx; anim->step.xy = -anim->step.xy; anim->step.yy = -anim->step.yy; anim->step.x0 = -anim->step.x0; anim->step.y0 = -anim->step.y0; /* FIXME: don't need this? Might be wise to reset to the initial transform each time we restart, to avoid little errors. */ anim->forward = !anim->forward; break; } anim->num_steps_left = anim->total_steps; } GDK_THREADS_LEAVE (); /* Return FALSE to remove the timeout handler when we are finished. */ return keep_source; } /** * goo_canvas_item_animate: * @item: an item. * @x: the final x offset from the current position. * @y: the final y offset from the current position. * @scale: the final scale of the item. * @degrees: the final rotation of the item. * @duration: the duration of the animation, in milliseconds (1/1000ths of a * second). * @step_time: the time between each animation step, in milliseconds. * @type: specifies what happens when the animation finishes. * * Animates an item from its current position to the given offsets, scale * and rotation. **/ void goo_canvas_item_animate (GooCanvasItem *item, double x, double y, double scale, double degrees, int duration, int step_time, GooCanvasAnimateType type) { GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item); cairo_matrix_t *matrix, identity_matrix = { 1, 0, 0, 1, 0, 0 }; cairo_matrix_t new_matrix = { 1, 0, 0, 1, 0, 0 }; GooCanvasItemAnimation *anim; double radians = degrees * (M_PI / 180); matrix = iface->get_transform (item); if (!matrix) matrix = &identity_matrix; cairo_matrix_translate (&new_matrix, x, y); cairo_matrix_scale (&new_matrix, scale, scale); cairo_matrix_rotate (&new_matrix, radians); anim = g_new (GooCanvasItemAnimation, 1); anim->type = type; anim->item = item; anim->total_steps = anim->num_steps_left = duration / step_time; anim->start = *matrix; anim->step.xx = (new_matrix.xx - matrix->xx) / anim->total_steps; anim->step.yx = (new_matrix.yx - matrix->yx) / anim->total_steps; anim->step.xy = (new_matrix.xy - matrix->xy) / anim->total_steps; anim->step.yy = (new_matrix.yy - matrix->yy) / anim->total_steps; anim->step.x0 = (new_matrix.x0 - matrix->x0) / anim->total_steps; anim->step.y0 = (new_matrix.y0 - matrix->y0) / anim->total_steps; anim->forward = TRUE; /* Store a pointer to the new animation in the item. This will automatically stop any current animation and free it. */ g_object_set_data_full (G_OBJECT (item), animation_key, anim, (GDestroyNotify) goo_canvas_item_free_animation); anim->timeout_id = g_timeout_add (step_time, (GSourceFunc) goo_canvas_item_animate_cb, anim); } /** * goo_canvas_item_stop_animation: * @item: an item. * * Stops any current animation for the given item, leaving it at its current * position. **/ void goo_canvas_item_stop_animation (GooCanvasItem *item) { /* This will result in a call to goo_canvas_item_free_animation() above. */ g_object_set_data (G_OBJECT (item), animation_key, NULL); } /** * goo_canvas_item_new: * @parent: the parent item, or %NULL. If a parent is specified, it will assume * ownership of the item, and the item will automatically be freed when it is * removed from the parent. Otherwise call g_object_unref() to free it. * @type: the type of the item to create. * @first_property: the name of the first property to set, or %NULL. * @...: the remaining property names and values to set, terminated with a * %NULL. * * Creates a new canvas item. * * Returns: a new canvas item. **/ GooCanvasItem * goo_canvas_item_new (GooCanvasItem *parent, GType type, const gchar *first_property, ...) { GooCanvasItem *item; va_list args; /* We need to use g_object_new_valist() rather than g_object_new() followed by g_object_set_valist() so that subclasses can have construct-only properties if desired. */ va_start (args, first_property); item = (GooCanvasItem*) g_object_new_valist (type, first_property, args); va_end (args); if (parent) { goo_canvas_item_add_child (parent, item, -1); g_object_unref (item); } return item; }