diff options
Diffstat (limited to 'app/core/gimpitem.c')
-rw-r--r-- | app/core/gimpitem.c | 2689 |
1 files changed, 2689 insertions, 0 deletions
diff --git a/app/core/gimpitem.c b/app/core/gimpitem.c new file mode 100644 index 0000000..c0f732c --- /dev/null +++ b/app/core/gimpitem.c @@ -0,0 +1,2689 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <stdlib.h> +#include <string.h> + +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <gegl.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "core-types.h" + +#include "gimp.h" +#include "gimp-parasites.h" +#include "gimpchannel.h" +#include "gimpcontainer.h" +#include "gimpidtable.h" +#include "gimpimage.h" +#include "gimpimage-undo.h" +#include "gimpimage-undo-push.h" +#include "gimpitem.h" +#include "gimpitem-linked.h" +#include "gimpitem-preview.h" +#include "gimpitemtree.h" +#include "gimplist.h" +#include "gimpmarshal.h" +#include "gimpparasitelist.h" +#include "gimpprogress.h" +#include "gimpstrokeoptions.h" + +#include "paint/gimppaintoptions.h" + +#include "gimp-intl.h" + + +enum +{ + REMOVED, + VISIBILITY_CHANGED, + LINKED_CHANGED, + COLOR_TAG_CHANGED, + LOCK_CONTENT_CHANGED, + LOCK_POSITION_CHANGED, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_IMAGE, + PROP_ID, + PROP_WIDTH, + PROP_HEIGHT, + PROP_OFFSET_X, + PROP_OFFSET_Y, + PROP_VISIBLE, + PROP_LINKED, + PROP_COLOR_TAG, + PROP_LOCK_CONTENT, + PROP_LOCK_POSITION +}; + + +typedef struct _GimpItemPrivate GimpItemPrivate; + +struct _GimpItemPrivate +{ + gint ID; /* provides a unique ID */ + guint32 tattoo; /* provides a permanent ID */ + + GimpImage *image; /* item owner */ + + GimpParasiteList *parasites; /* Plug-in parasite data */ + + gint width, height; /* size in pixels */ + gint offset_x, offset_y; /* pixel offset in image */ + + guint visible : 1; /* item visibility */ + guint bind_visible_to_active : 1; /* visibility bound to active */ + + guint linked : 1; /* control linkage */ + guint lock_content : 1; /* content editability */ + guint lock_position : 1; /* content movability */ + + guint removed : 1; /* removed from the image? */ + + GimpColorTag color_tag; /* color tag */ + + GList *offset_nodes; /* offset nodes to manage */ +}; + +#define GET_PRIVATE(item) ((GimpItemPrivate *) gimp_item_get_instance_private ((GimpItem *) (item))) + + +/* local function prototypes */ + +static void gimp_item_constructed (GObject *object); +static void gimp_item_finalize (GObject *object); +static void gimp_item_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_item_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gint64 gimp_item_get_memsize (GimpObject *object, + gint64 *gui_size); + +static gboolean gimp_item_real_is_content_locked (GimpItem *item); +static gboolean gimp_item_real_is_position_locked (GimpItem *item); +static gboolean gimp_item_real_bounds (GimpItem *item, + gdouble *x, + gdouble *y, + gdouble *width, + gdouble *height); +static GimpItem * gimp_item_real_duplicate (GimpItem *item, + GType new_type); +static void gimp_item_real_convert (GimpItem *item, + GimpImage *dest_image, + GType old_type); +static gboolean gimp_item_real_rename (GimpItem *item, + const gchar *new_name, + const gchar *undo_desc, + GError **error); +static void gimp_item_real_start_transform (GimpItem *item, + gboolean push_undo); +static void gimp_item_real_end_transform (GimpItem *item, + gboolean push_undo); +static void gimp_item_real_translate (GimpItem *item, + gdouble offset_x, + gdouble offset_y, + gboolean push_undo); +static void gimp_item_real_scale (GimpItem *item, + gint new_width, + gint new_height, + gint new_offset_x, + gint new_offset_y, + GimpInterpolationType interpolation, + GimpProgress *progress); +static void gimp_item_real_resize (GimpItem *item, + GimpContext *context, + GimpFillType fill_type, + gint new_width, + gint new_height, + gint offset_x, + gint offset_y); +static GimpTransformResize + gimp_item_real_get_clip (GimpItem *item, + GimpTransformResize clip_result); + + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpItem, gimp_item, GIMP_TYPE_FILTER) + +#define parent_class gimp_item_parent_class + +static guint gimp_item_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_item_class_init (GimpItemClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass); + GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass); + + gimp_item_signals[REMOVED] = + g_signal_new ("removed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpItemClass, removed), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + gimp_item_signals[VISIBILITY_CHANGED] = + g_signal_new ("visibility-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpItemClass, visibility_changed), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + gimp_item_signals[LINKED_CHANGED] = + g_signal_new ("linked-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpItemClass, linked_changed), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + gimp_item_signals[COLOR_TAG_CHANGED] = + g_signal_new ("color-tag-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpItemClass, color_tag_changed), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + gimp_item_signals[LOCK_CONTENT_CHANGED] = + g_signal_new ("lock-content-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpItemClass, lock_content_changed), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + gimp_item_signals[LOCK_POSITION_CHANGED] = + g_signal_new ("lock-position-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpItemClass, lock_position_changed), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + object_class->constructed = gimp_item_constructed; + object_class->finalize = gimp_item_finalize; + object_class->set_property = gimp_item_set_property; + object_class->get_property = gimp_item_get_property; + + gimp_object_class->get_memsize = gimp_item_get_memsize; + + viewable_class->name_editable = TRUE; + viewable_class->get_preview_size = gimp_item_get_preview_size; + viewable_class->get_popup_size = gimp_item_get_popup_size; + + klass->removed = NULL; + klass->visibility_changed = NULL; + klass->linked_changed = NULL; + klass->color_tag_changed = NULL; + klass->lock_content_changed = NULL; + klass->lock_position_changed = NULL; + + klass->unset_removed = NULL; + klass->is_attached = NULL; + klass->is_content_locked = gimp_item_real_is_content_locked; + klass->is_position_locked = gimp_item_real_is_position_locked; + klass->get_tree = NULL; + klass->bounds = gimp_item_real_bounds; + klass->duplicate = gimp_item_real_duplicate; + klass->convert = gimp_item_real_convert; + klass->rename = gimp_item_real_rename; + klass->start_move = NULL; + klass->end_move = NULL; + klass->start_transform = gimp_item_real_start_transform; + klass->end_transform = gimp_item_real_end_transform; + klass->translate = gimp_item_real_translate; + klass->scale = gimp_item_real_scale; + klass->resize = gimp_item_real_resize; + klass->flip = NULL; + klass->rotate = NULL; + klass->transform = NULL; + klass->get_clip = gimp_item_real_get_clip; + klass->fill = NULL; + klass->stroke = NULL; + klass->to_selection = NULL; + + klass->default_name = NULL; + klass->rename_desc = NULL; + klass->translate_desc = NULL; + klass->scale_desc = NULL; + klass->resize_desc = NULL; + klass->flip_desc = NULL; + klass->rotate_desc = NULL; + klass->transform_desc = NULL; + klass->fill_desc = NULL; + klass->stroke_desc = NULL; + + g_object_class_install_property (object_class, PROP_IMAGE, + g_param_spec_object ("image", NULL, NULL, + GIMP_TYPE_IMAGE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, PROP_ID, + g_param_spec_int ("id", NULL, NULL, + 0, G_MAXINT, 0, + GIMP_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_WIDTH, + g_param_spec_int ("width", NULL, NULL, + 1, GIMP_MAX_IMAGE_SIZE, 1, + GIMP_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_HEIGHT, + g_param_spec_int ("height", NULL, NULL, + 1, GIMP_MAX_IMAGE_SIZE, 1, + GIMP_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_OFFSET_X, + g_param_spec_int ("offset-x", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_OFFSET_Y, + g_param_spec_int ("offset-y", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_VISIBLE, + g_param_spec_boolean ("visible", NULL, NULL, + TRUE, + GIMP_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_LINKED, + g_param_spec_boolean ("linked", NULL, NULL, + FALSE, + GIMP_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_COLOR_TAG, + g_param_spec_enum ("color-tag", NULL, NULL, + GIMP_TYPE_COLOR_TAG, + GIMP_COLOR_TAG_NONE, + GIMP_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_LOCK_CONTENT, + g_param_spec_boolean ("lock-content", + NULL, NULL, + FALSE, + GIMP_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_LOCK_POSITION, + g_param_spec_boolean ("lock-position", + NULL, NULL, + FALSE, + GIMP_PARAM_READABLE)); +} + +static void +gimp_item_init (GimpItem *item) +{ + GimpItemPrivate *private = GET_PRIVATE (item); + + g_object_force_floating (G_OBJECT (item)); + + private->parasites = gimp_parasite_list_new (); + private->visible = TRUE; + private->bind_visible_to_active = TRUE; +} + +static void +gimp_item_constructed (GObject *object) +{ + GimpItemPrivate *private = GET_PRIVATE (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (GIMP_IS_IMAGE (private->image)); + gimp_assert (private->ID != 0); +} + +static void +gimp_item_finalize (GObject *object) +{ + GimpItemPrivate *private = GET_PRIVATE (object); + + if (private->offset_nodes) + { + g_list_free_full (private->offset_nodes, + (GDestroyNotify) g_object_unref); + private->offset_nodes = NULL; + } + + if (private->image && private->image->gimp) + { + gimp_id_table_remove (private->image->gimp->item_table, private->ID); + private->image = NULL; + } + + g_clear_object (&private->parasites); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_item_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpItem *item = GIMP_ITEM (object); + + switch (property_id) + { + case PROP_IMAGE: + gimp_item_set_image (item, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_item_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpItemPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_IMAGE: + g_value_set_object (value, private->image); + break; + case PROP_ID: + g_value_set_int (value, private->ID); + break; + case PROP_WIDTH: + g_value_set_int (value, private->width); + break; + case PROP_HEIGHT: + g_value_set_int (value, private->height); + break; + case PROP_OFFSET_X: + g_value_set_int (value, private->offset_x); + break; + case PROP_OFFSET_Y: + g_value_set_int (value, private->offset_y); + break; + case PROP_VISIBLE: + g_value_set_boolean (value, private->visible); + break; + case PROP_LINKED: + g_value_set_boolean (value, private->linked); + break; + case PROP_COLOR_TAG: + g_value_set_enum (value, private->color_tag); + break; + case PROP_LOCK_CONTENT: + g_value_set_boolean (value, private->lock_content); + break; + case PROP_LOCK_POSITION: + g_value_set_boolean (value, private->lock_position); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gint64 +gimp_item_get_memsize (GimpObject *object, + gint64 *gui_size) +{ + GimpItemPrivate *private = GET_PRIVATE (object); + gint64 memsize = 0; + + memsize += gimp_object_get_memsize (GIMP_OBJECT (private->parasites), + gui_size); + + return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object, + gui_size); +} + +static gboolean +gimp_item_real_is_content_locked (GimpItem *item) +{ + GimpItem *parent = gimp_item_get_parent (item); + + if (parent && gimp_item_is_content_locked (parent)) + return TRUE; + + return GET_PRIVATE (item)->lock_content; +} + +static gboolean +gimp_item_real_is_position_locked (GimpItem *item) +{ + if (gimp_item_get_linked (item)) + if (gimp_item_linked_is_locked (item)) + return TRUE; + + return GET_PRIVATE (item)->lock_position; +} + +static gboolean +gimp_item_real_bounds (GimpItem *item, + gdouble *x, + gdouble *y, + gdouble *width, + gdouble *height) +{ + GimpItemPrivate *private = GET_PRIVATE (item); + + *x = 0; + *y = 0; + *width = private->width; + *height = private->height; + + return TRUE; +} + +static GimpItem * +gimp_item_real_duplicate (GimpItem *item, + GType new_type) +{ + GimpItemPrivate *private; + GimpItem *new_item; + gchar *new_name; + + g_return_val_if_fail (GIMP_IS_ITEM (item), NULL); + + private = GET_PRIVATE (item); + + g_return_val_if_fail (GIMP_IS_IMAGE (private->image), NULL); + g_return_val_if_fail (g_type_is_a (new_type, GIMP_TYPE_ITEM), NULL); + + /* formulate the new name */ + { + const gchar *name; + gint len; + + name = gimp_object_get_name (item); + + g_return_val_if_fail (name != NULL, NULL); + + len = strlen (_("copy")); + + if ((strlen (name) >= len && + strcmp (&name[strlen (name) - len], _("copy")) == 0) || + g_regex_match_simple ("#([0-9]+)\\s*$", name, 0, 0)) + { + /* don't have redundant "copy"s */ + new_name = g_strdup (name); + } + else + { + new_name = g_strdup_printf (_("%s copy"), name); + } + } + + new_item = gimp_item_new (new_type, + gimp_item_get_image (item), new_name, + private->offset_x, private->offset_y, + gimp_item_get_width (item), + gimp_item_get_height (item)); + + g_free (new_name); + + gimp_viewable_set_expanded (GIMP_VIEWABLE (new_item), + gimp_viewable_get_expanded (GIMP_VIEWABLE (item))); + + g_object_unref (GET_PRIVATE (new_item)->parasites); + GET_PRIVATE (new_item)->parasites = gimp_parasite_list_copy (private->parasites); + + gimp_item_set_visible (new_item, gimp_item_get_visible (item), FALSE); + gimp_item_set_linked (new_item, gimp_item_get_linked (item), FALSE); + gimp_item_set_color_tag (new_item, gimp_item_get_color_tag (item), FALSE); + + if (gimp_item_can_lock_content (new_item)) + gimp_item_set_lock_content (new_item, gimp_item_get_lock_content (item), + FALSE); + + if (gimp_item_can_lock_position (new_item)) + gimp_item_set_lock_position (new_item, gimp_item_get_lock_position (item), + FALSE); + + return new_item; +} + +static void +gimp_item_real_convert (GimpItem *item, + GimpImage *dest_image, + GType old_type) +{ + gimp_item_set_image (item, dest_image); +} + +static gboolean +gimp_item_real_rename (GimpItem *item, + const gchar *new_name, + const gchar *undo_desc, + GError **error) +{ + if (gimp_item_is_attached (item)) + gimp_item_tree_rename_item (gimp_item_get_tree (item), item, + new_name, TRUE, undo_desc); + else + gimp_object_set_name (GIMP_OBJECT (item), new_name); + + return TRUE; +} + +static void +gimp_item_real_translate (GimpItem *item, + gdouble offset_x, + gdouble offset_y, + gboolean push_undo) +{ + GimpItemPrivate *private = GET_PRIVATE (item); + + gimp_item_set_offset (item, + private->offset_x + SIGNED_ROUND (offset_x), + private->offset_y + SIGNED_ROUND (offset_y)); +} + +static void +gimp_item_real_start_transform (GimpItem *item, + gboolean push_undo) +{ + gimp_item_start_move (item, push_undo); +} + +static void +gimp_item_real_end_transform (GimpItem *item, + gboolean push_undo) +{ + gimp_item_end_move (item, push_undo); +} + +static void +gimp_item_real_scale (GimpItem *item, + gint new_width, + gint new_height, + gint new_offset_x, + gint new_offset_y, + GimpInterpolationType interpolation, + GimpProgress *progress) +{ + GimpItemPrivate *private = GET_PRIVATE (item); + + if (private->width != new_width) + { + private->width = new_width; + g_object_notify (G_OBJECT (item), "width"); + } + + if (private->height != new_height) + { + private->height = new_height; + g_object_notify (G_OBJECT (item), "height"); + } + + gimp_item_set_offset (item, new_offset_x, new_offset_y); +} + +static void +gimp_item_real_resize (GimpItem *item, + GimpContext *context, + GimpFillType fill_type, + gint new_width, + gint new_height, + gint offset_x, + gint offset_y) +{ + GimpItemPrivate *private = GET_PRIVATE (item); + + if (private->width != new_width) + { + private->width = new_width; + g_object_notify (G_OBJECT (item), "width"); + } + + if (private->height != new_height) + { + private->height = new_height; + g_object_notify (G_OBJECT (item), "height"); + } + + gimp_item_set_offset (item, + private->offset_x - offset_x, + private->offset_y - offset_y); +} + +static GimpTransformResize +gimp_item_real_get_clip (GimpItem *item, + GimpTransformResize clip_result) +{ + if (gimp_item_get_lock_position (item)) + return GIMP_TRANSFORM_RESIZE_CLIP; + else + return clip_result; +} + + +/* public functions */ + +/** + * gimp_item_new: + * @type: The new item's type. + * @image: The new item's #GimpImage. + * @name: The name to assign the item. + * @offset_x: The X offset to assign the item. + * @offset_y: The Y offset to assign the item. + * @width: The width to assign the item. + * @height: The height to assign the item. + * + * Return value: The newly created item. + */ +GimpItem * +gimp_item_new (GType type, + GimpImage *image, + const gchar *name, + gint offset_x, + gint offset_y, + gint width, + gint height) +{ + GimpItem *item; + GimpItemPrivate *private; + + g_return_val_if_fail (g_type_is_a (type, GIMP_TYPE_ITEM), NULL); + g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL); + g_return_val_if_fail (width > 0 && height > 0, NULL); + + item = g_object_new (type, + "image", image, + NULL); + + private = GET_PRIVATE (item); + + private->width = width; + private->height = height; + gimp_item_set_offset (item, offset_x, offset_y); + + if (name && strlen (name)) + gimp_object_set_name (GIMP_OBJECT (item), name); + else + gimp_object_set_static_name (GIMP_OBJECT (item), + GIMP_ITEM_GET_CLASS (item)->default_name); + + return item; +} + +/** + * gimp_item_remove: + * @item: the #GimpItem to remove. + * + * This function sets the 'removed' flag on @item to #TRUE, and emits + * a 'removed' signal on the item. + */ +void +gimp_item_removed (GimpItem *item) +{ + GimpContainer *children; + + g_return_if_fail (GIMP_IS_ITEM (item)); + + GET_PRIVATE (item)->removed = TRUE; + + children = gimp_viewable_get_children (GIMP_VIEWABLE (item)); + + if (children) + gimp_container_foreach (children, (GFunc) gimp_item_removed, NULL); + + g_signal_emit (item, gimp_item_signals[REMOVED], 0); +} + +/** + * gimp_item_is_removed: + * @item: the #GimpItem to check. + * + * Returns: %TRUE if the 'removed' flag is set for @item, %FALSE otherwise. + */ +gboolean +gimp_item_is_removed (GimpItem *item) +{ + g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE); + + return GET_PRIVATE (item)->removed; +} + +/** + * gimp_item_unset_removed: + * @item: a #GimpItem which was on the undo stack + * + * Unsets an item's "removed" state. This function is called when an + * item was on the undo stack and is added back to its parent + * container during and undo or redo. It must never be called from + * anywhere else. + **/ +void +gimp_item_unset_removed (GimpItem *item) +{ + GimpContainer *children; + + g_return_if_fail (GIMP_IS_ITEM (item)); + g_return_if_fail (gimp_item_is_removed (item)); + + GET_PRIVATE (item)->removed = FALSE; + + children = gimp_viewable_get_children (GIMP_VIEWABLE (item)); + + if (children) + gimp_container_foreach (children, (GFunc) gimp_item_unset_removed, NULL); + + if (GIMP_ITEM_GET_CLASS (item)->unset_removed) + GIMP_ITEM_GET_CLASS (item)->unset_removed (item); +} + +/** + * gimp_item_is_attached: + * @item: The #GimpItem to check. + * + * Returns: %TRUE if the item is attached to an image, %FALSE otherwise. + */ +gboolean +gimp_item_is_attached (GimpItem *item) +{ + GimpItem *parent; + + g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE); + + parent = gimp_item_get_parent (item); + + if (parent) + return gimp_item_is_attached (parent); + + return GIMP_ITEM_GET_CLASS (item)->is_attached (item); +} + +GimpItem * +gimp_item_get_parent (GimpItem *item) +{ + g_return_val_if_fail (GIMP_IS_ITEM (item), NULL); + + return GIMP_ITEM (gimp_viewable_get_parent (GIMP_VIEWABLE (item))); +} + +GimpItemTree * +gimp_item_get_tree (GimpItem *item) +{ + g_return_val_if_fail (GIMP_IS_ITEM (item), NULL); + + if (GIMP_ITEM_GET_CLASS (item)->get_tree) + return GIMP_ITEM_GET_CLASS (item)->get_tree (item); + + return NULL; +} + +GimpContainer * +gimp_item_get_container (GimpItem *item) +{ + GimpItem *parent; + GimpItemTree *tree; + + g_return_val_if_fail (GIMP_IS_ITEM (item), NULL); + + parent = gimp_item_get_parent (item); + + if (parent) + return gimp_viewable_get_children (GIMP_VIEWABLE (parent)); + + tree = gimp_item_get_tree (item); + + if (tree) + return tree->container; + + return NULL; +} + +GList * +gimp_item_get_container_iter (GimpItem *item) +{ + GimpContainer *container; + + g_return_val_if_fail (GIMP_IS_ITEM (item), NULL); + + container = gimp_item_get_container (item); + + if (container) + return GIMP_LIST (container)->queue->head; + + return NULL; +} + +gint +gimp_item_get_index (GimpItem *item) +{ + GimpContainer *container; + + g_return_val_if_fail (GIMP_IS_ITEM (item), -1); + + container = gimp_item_get_container (item); + + if (container) + return gimp_container_get_child_index (container, GIMP_OBJECT (item)); + + return -1; +} + +GList * +gimp_item_get_path (GimpItem *item) +{ + GimpContainer *container; + GList *path = NULL; + + g_return_val_if_fail (GIMP_IS_ITEM (item), NULL); + g_return_val_if_fail (gimp_item_is_attached (item), NULL); + + container = gimp_item_get_container (item); + + while (container) + { + guint32 index = gimp_container_get_child_index (container, + GIMP_OBJECT (item)); + + path = g_list_prepend (path, GUINT_TO_POINTER (index)); + + item = gimp_item_get_parent (item); + + if (item) + container = gimp_item_get_container (item); + else + container = NULL; + } + + return path; +} + +gboolean +gimp_item_bounds (GimpItem *item, + gint *x, + gint *y, + gint *width, + gint *height) +{ + gdouble tmp_x, tmp_y, tmp_width, tmp_height; + gboolean retval; + + g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE); + + retval = GIMP_ITEM_GET_CLASS (item)->bounds (item, + &tmp_x, &tmp_y, + &tmp_width, &tmp_height); + + if (x) *x = floor (tmp_x); + if (y) *y = floor (tmp_y); + if (width) *width = ceil (tmp_x + tmp_width) - floor (tmp_x); + if (height) *height = ceil (tmp_y + tmp_height) - floor (tmp_y); + + return retval; +} + +gboolean +gimp_item_bounds_f (GimpItem *item, + gdouble *x, + gdouble *y, + gdouble *width, + gdouble *height) +{ + gdouble tmp_x, tmp_y, tmp_width, tmp_height; + gboolean retval; + + g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE); + + retval = GIMP_ITEM_GET_CLASS (item)->bounds (item, + &tmp_x, &tmp_y, + &tmp_width, &tmp_height); + + if (x) *x = tmp_x; + if (y) *y = tmp_y; + if (width) *width = tmp_width; + if (height) *height = tmp_height; + + return retval; +} + +/** + * gimp_item_duplicate: + * @item: The #GimpItem to duplicate. + * @new_type: The type to make the new item. + * + * Returns: the newly created item. + */ +GimpItem * +gimp_item_duplicate (GimpItem *item, + GType new_type) +{ + GimpItemPrivate *private; + + g_return_val_if_fail (GIMP_IS_ITEM (item), NULL); + + private = GET_PRIVATE (item); + + g_return_val_if_fail (GIMP_IS_IMAGE (private->image), NULL); + g_return_val_if_fail (g_type_is_a (new_type, GIMP_TYPE_ITEM), NULL); + + return GIMP_ITEM_GET_CLASS (item)->duplicate (item, new_type); +} + +/** + * gimp_item_convert: + * @item: The #GimpItem to convert. + * @dest_image: The #GimpImage in which to place the converted item. + * @new_type: The type to convert the item to. + * + * Returns: the new item that results from the conversion. + */ +GimpItem * +gimp_item_convert (GimpItem *item, + GimpImage *dest_image, + GType new_type) +{ + GimpItem *new_item; + GType old_type; + + g_return_val_if_fail (GIMP_IS_ITEM (item), NULL); + g_return_val_if_fail (GIMP_IS_IMAGE (GET_PRIVATE (item)->image), NULL); + g_return_val_if_fail (GIMP_IS_IMAGE (dest_image), NULL); + g_return_val_if_fail (g_type_is_a (new_type, GIMP_TYPE_ITEM), NULL); + + old_type = G_TYPE_FROM_INSTANCE (item); + + new_item = gimp_item_duplicate (item, new_type); + + if (new_item) + GIMP_ITEM_GET_CLASS (new_item)->convert (new_item, dest_image, old_type); + + return new_item; +} + +/** + * gimp_item_rename: + * @item: The #GimpItem to rename. + * @new_name: The new name to give the item. + * @error: Return location for error message. + * + * This function assigns a new name to the item, if the desired name is + * different from the name it already has, and pushes an entry onto the + * undo stack for the item's image. If @new_name is NULL or empty, the + * default name for the item's class is used. If the name is changed, + * the GimpObject::name-changed signal is emitted for the item. + * + * Returns: %TRUE if the @item could be renamed, %FALSE otherwise. + */ +gboolean +gimp_item_rename (GimpItem *item, + const gchar *new_name, + GError **error) +{ + GimpItemClass *item_class; + + g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + item_class = GIMP_ITEM_GET_CLASS (item); + + if (! new_name || ! *new_name) + new_name = item_class->default_name; + + if (strcmp (new_name, gimp_object_get_name (item))) + return item_class->rename (item, new_name, item_class->rename_desc, error); + + return TRUE; +} + +/** + * gimp_item_get_width: + * @item: The #GimpItem to check. + * + * Returns: The width of the item. + */ +gint +gimp_item_get_width (GimpItem *item) +{ + g_return_val_if_fail (GIMP_IS_ITEM (item), -1); + + return GET_PRIVATE (item)->width; +} + +/** + * gimp_item_get_height: + * @item: The #GimpItem to check. + * + * Returns: The height of the item. + */ +gint +gimp_item_get_height (GimpItem *item) +{ + g_return_val_if_fail (GIMP_IS_ITEM (item), -1); + + return GET_PRIVATE (item)->height; +} + +void +gimp_item_set_size (GimpItem *item, + gint width, + gint height) +{ + GimpItemPrivate *private; + + g_return_if_fail (GIMP_IS_ITEM (item)); + + private = GET_PRIVATE (item); + + if (private->width != width || + private->height != height) + { + g_object_freeze_notify (G_OBJECT (item)); + + if (private->width != width) + { + private->width = width; + g_object_notify (G_OBJECT (item), "width"); + } + + if (private->height != height) + { + private->height = height; + g_object_notify (G_OBJECT (item), "height"); + } + + g_object_thaw_notify (G_OBJECT (item)); + + gimp_viewable_size_changed (GIMP_VIEWABLE (item)); + } +} + +/** + * gimp_item_get_offset: + * @item: The #GimpItem to check. + * @offset_x: Return location for the item's X offset. + * @offset_y: Return location for the item's Y offset. + * + * Reveals the X and Y offsets of the item. + */ +void +gimp_item_get_offset (GimpItem *item, + gint *offset_x, + gint *offset_y) +{ + GimpItemPrivate *private; + + g_return_if_fail (GIMP_IS_ITEM (item)); + + private = GET_PRIVATE (item); + + if (offset_x) *offset_x = private->offset_x; + if (offset_y) *offset_y = private->offset_y; +} + +void +gimp_item_set_offset (GimpItem *item, + gint offset_x, + gint offset_y) +{ + GimpItemPrivate *private; + GList *list; + + g_return_if_fail (GIMP_IS_ITEM (item)); + + private = GET_PRIVATE (item); + + g_object_freeze_notify (G_OBJECT (item)); + + if (private->offset_x != offset_x) + { + private->offset_x = offset_x; + g_object_notify (G_OBJECT (item), "offset-x"); + } + + if (private->offset_y != offset_y) + { + private->offset_y = offset_y; + g_object_notify (G_OBJECT (item), "offset-y"); + } + + for (list = private->offset_nodes; list; list = g_list_next (list)) + { + GeglNode *node = list->data; + + gegl_node_set (node, + "x", (gdouble) private->offset_x, + "y", (gdouble) private->offset_y, + NULL); + } + + g_object_thaw_notify (G_OBJECT (item)); +} + +gint +gimp_item_get_offset_x (GimpItem *item) +{ + g_return_val_if_fail (GIMP_IS_ITEM (item), 0); + + return GET_PRIVATE (item)->offset_x; +} + +gint +gimp_item_get_offset_y (GimpItem *item) +{ + g_return_val_if_fail (GIMP_IS_ITEM (item), 0); + + return GET_PRIVATE (item)->offset_y; +} + +void +gimp_item_start_move (GimpItem *item, + gboolean push_undo) +{ + g_return_if_fail (GIMP_IS_ITEM (item)); + + if (GIMP_ITEM_GET_CLASS (item)->start_move) + GIMP_ITEM_GET_CLASS (item)->start_move (item, push_undo); +} + +void +gimp_item_end_move (GimpItem *item, + gboolean push_undo) +{ + g_return_if_fail (GIMP_IS_ITEM (item)); + + if (GIMP_ITEM_GET_CLASS (item)->end_move) + GIMP_ITEM_GET_CLASS (item)->end_move (item, push_undo); +} + +void +gimp_item_start_transform (GimpItem *item, + gboolean push_undo) +{ + g_return_if_fail (GIMP_IS_ITEM (item)); + + if (GIMP_ITEM_GET_CLASS (item)->start_transform) + GIMP_ITEM_GET_CLASS (item)->start_transform (item, push_undo); +} + +void +gimp_item_end_transform (GimpItem *item, + gboolean push_undo) +{ + g_return_if_fail (GIMP_IS_ITEM (item)); + + if (GIMP_ITEM_GET_CLASS (item)->end_transform) + GIMP_ITEM_GET_CLASS (item)->end_transform (item, push_undo); +} + +/** + * gimp_item_translate: + * @item: The #GimpItem to move. + * @offset_x: Increment to the X offset of the item. + * @offset_y: Increment to the Y offset of the item. + * @push_undo: If #TRUE, create an entry in the image's undo stack + * for this action. + * + * Adds the specified increments to the X and Y offsets for the item. + */ +void +gimp_item_translate (GimpItem *item, + gdouble offset_x, + gdouble offset_y, + gboolean push_undo) +{ + GimpItemClass *item_class; + GimpImage *image; + + g_return_if_fail (GIMP_IS_ITEM (item)); + + item_class = GIMP_ITEM_GET_CLASS (item); + image = gimp_item_get_image (item); + + if (! gimp_item_is_attached (item)) + push_undo = FALSE; + + if (push_undo) + gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_ITEM_DISPLACE, + item_class->translate_desc); + + gimp_item_start_transform (item, push_undo); + + item_class->translate (item, offset_x, offset_y, push_undo); + + gimp_item_end_transform (item, push_undo); + + if (push_undo) + gimp_image_undo_group_end (image); +} + +/** + * gimp_item_check_scaling: + * @item: Item to check + * @new_width: proposed width of item, in pixels + * @new_height: proposed height of item, in pixels + * + * Scales item dimensions, then snaps them to pixel centers + * + * Returns: #FALSE if any dimension reduces to zero as a result + * of this; otherwise, returns #TRUE. + **/ +gboolean +gimp_item_check_scaling (GimpItem *item, + gint new_width, + gint new_height) +{ + GimpItemPrivate *private; + GimpImage *image; + gdouble img_scale_w; + gdouble img_scale_h; + gint new_item_offset_x; + gint new_item_offset_y; + gint new_item_width; + gint new_item_height; + + g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE); + + private = GET_PRIVATE (item); + image = gimp_item_get_image (item); + + img_scale_w = ((gdouble) new_width / + (gdouble) gimp_image_get_width (image)); + img_scale_h = ((gdouble) new_height / + (gdouble) gimp_image_get_height (image)); + new_item_offset_x = SIGNED_ROUND (img_scale_w * private->offset_x); + new_item_offset_y = SIGNED_ROUND (img_scale_h * private->offset_y); + new_item_width = SIGNED_ROUND (img_scale_w * (private->offset_x + + gimp_item_get_width (item))) - + new_item_offset_x; + new_item_height = SIGNED_ROUND (img_scale_h * (private->offset_y + + gimp_item_get_height (item))) - + new_item_offset_y; + + return (new_item_width > 0 && new_item_height > 0); +} + +void +gimp_item_scale (GimpItem *item, + gint new_width, + gint new_height, + gint new_offset_x, + gint new_offset_y, + GimpInterpolationType interpolation, + GimpProgress *progress) +{ + GimpItemClass *item_class; + GimpImage *image; + gboolean push_undo; + + g_return_if_fail (GIMP_IS_ITEM (item)); + g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress)); + + if (new_width < 1 || new_height < 1) + return; + + item_class = GIMP_ITEM_GET_CLASS (item); + image = gimp_item_get_image (item); + + push_undo = gimp_item_is_attached (item); + + if (push_undo) + gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_ITEM_SCALE, + item_class->scale_desc); + + gimp_item_start_transform (item, push_undo); + + g_object_freeze_notify (G_OBJECT (item)); + + item_class->scale (item, new_width, new_height, new_offset_x, new_offset_y, + interpolation, progress); + + g_object_thaw_notify (G_OBJECT (item)); + + gimp_item_end_transform (item, push_undo); + + if (push_undo) + gimp_image_undo_group_end (image); +} + +/** + * gimp_item_scale_by_factors: + * @item: Item to be transformed by explicit width and height factors. + * @w_factor: scale factor to apply to width and horizontal offset + * @h_factor: scale factor to apply to height and vertical offset + * @interpolation: + * @progress: + * + * Scales item dimensions and offsets by uniform width and + * height factors. + * + * Use gimp_item_scale_by_factors() in circumstances when the same + * width and height scaling factors are to be uniformly applied to a + * set of items. In this context, the item's dimensions and offsets + * from the sides of the containing image all change by these + * predetermined factors. By fiat, the fixed point of the transform is + * the upper left hand corner of the image. Returns #FALSE if a + * requested scale factor is zero or if a scaling zero's out a item + * dimension; returns #TRUE otherwise. + * + * Use gimp_item_scale() in circumstances where new item width + * and height dimensions are predetermined instead. + * + * Side effects: Undo set created for item. Old item imagery + * scaled & painted to new item tiles. + * + * Returns: #TRUE, if the scaled item has positive dimensions + * #FALSE if the scaled item has at least one zero dimension + **/ +gboolean +gimp_item_scale_by_factors (GimpItem *item, + gdouble w_factor, + gdouble h_factor, + GimpInterpolationType interpolation, + GimpProgress *progress) +{ + return gimp_item_scale_by_factors_with_origin (item, + w_factor, h_factor, + 0, 0, 0, 0, + interpolation, progress); +} + +/** + * gimp_item_scale_by_factors: + * @item: Item to be transformed by explicit width and height factors. + * @w_factor: scale factor to apply to width and horizontal offset + * @h_factor: scale factor to apply to height and vertical offset + * @origin_x: x-coordinate of the transformation input origin + * @origin_y: y-coordinate of the transformation input origin + * @new_origin_x: x-coordinate of the transformation output origin + * @new_origin_y: y-coordinate of the transformation output origin + * @interpolation: + * @progress: + * + * Same as gimp_item_scale_by_factors(), but with the option to specify + * custom input and output points of origin for the transformation. + * + * Returns: #TRUE, if the scaled item has positive dimensions + * #FALSE if the scaled item has at least one zero dimension + **/ +gboolean +gimp_item_scale_by_factors_with_origin (GimpItem *item, + gdouble w_factor, + gdouble h_factor, + gint origin_x, + gint origin_y, + gint new_origin_x, + gint new_origin_y, + GimpInterpolationType interpolation, + GimpProgress *progress) +{ + GimpItemPrivate *private; + GimpContainer *children; + gint new_width, new_height; + gint new_offset_x, new_offset_y; + + g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE); + g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), FALSE); + + private = GET_PRIVATE (item); + + if (w_factor <= 0.0 || h_factor <= 0.0) + { + g_warning ("%s: requested width or height scale is non-positive", + G_STRFUNC); + return FALSE; + } + + children = gimp_viewable_get_children (GIMP_VIEWABLE (item)); + + /* avoid discarding empty layer groups */ + if (children && gimp_container_is_empty (children)) + return TRUE; + + new_offset_x = SIGNED_ROUND (w_factor * (private->offset_x - origin_x)); + new_offset_y = SIGNED_ROUND (h_factor * (private->offset_y - origin_y)); + new_width = SIGNED_ROUND (w_factor * (private->offset_x - origin_x + + gimp_item_get_width (item))) - + new_offset_x; + new_height = SIGNED_ROUND (h_factor * (private->offset_y - origin_y + + gimp_item_get_height (item))) - + new_offset_y; + + new_offset_x += new_origin_x; + new_offset_y += new_origin_y; + + if (new_width > 0 && new_height > 0) + { + gimp_item_scale (item, + new_width, new_height, + new_offset_x, new_offset_y, + interpolation, progress); + return TRUE; + } + + return FALSE; +} + +/** + * gimp_item_scale_by_origin: + * @item: The item to be transformed by width & height scale factors + * @new_width: The width that item will acquire + * @new_height: The height that the item will acquire + * @interpolation: + * @progress: + * @local_origin: sets fixed point of the scaling transform. See below. + * + * Sets item dimensions to new_width and + * new_height. Derives vertical and horizontal scaling + * transforms from new width and height. If local_origin is + * #TRUE, the fixed point of the scaling transform coincides + * with the item's center point. Otherwise, the fixed + * point is taken to be [-GimpItem::offset_x, -GimpItem::->offset_y]. + * + * Since this function derives scale factors from new and + * current item dimensions, these factors will vary from + * item to item because of aliasing artifacts; factor + * variations among items can be quite large where item + * dimensions approach pixel dimensions. Use + * gimp_item_scale_by_factors() where constant scales are to + * be uniformly applied to a number of items. + * + * Side effects: undo set created for item. + * Old item imagery scaled + * & painted to new item tiles + **/ +void +gimp_item_scale_by_origin (GimpItem *item, + gint new_width, + gint new_height, + GimpInterpolationType interpolation, + GimpProgress *progress, + gboolean local_origin) +{ + GimpItemPrivate *private; + gint new_offset_x, new_offset_y; + + g_return_if_fail (GIMP_IS_ITEM (item)); + g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress)); + + private = GET_PRIVATE (item); + + if (new_width == 0 || new_height == 0) + { + g_warning ("%s: requested width or height equals zero", G_STRFUNC); + return; + } + + if (local_origin) + { + new_offset_x = (private->offset_x + + ((gimp_item_get_width (item) - new_width) / 2.0)); + new_offset_y = (private->offset_y + + ((gimp_item_get_height (item) - new_height) / 2.0)); + } + else + { + new_offset_x = (gint) (((gdouble) new_width * + (gdouble) private->offset_x / + (gdouble) gimp_item_get_width (item))); + + new_offset_y = (gint) (((gdouble) new_height * + (gdouble) private->offset_y / + (gdouble) gimp_item_get_height (item))); + } + + gimp_item_scale (item, + new_width, new_height, + new_offset_x, new_offset_y, + interpolation, progress); +} + +void +gimp_item_resize (GimpItem *item, + GimpContext *context, + GimpFillType fill_type, + gint new_width, + gint new_height, + gint offset_x, + gint offset_y) +{ + GimpItemClass *item_class; + GimpImage *image; + gboolean push_undo; + + g_return_if_fail (GIMP_IS_ITEM (item)); + g_return_if_fail (GIMP_IS_CONTEXT (context)); + + if (new_width < 1 || new_height < 1) + return; + + item_class = GIMP_ITEM_GET_CLASS (item); + image = gimp_item_get_image (item); + + push_undo = gimp_item_is_attached (item); + + if (push_undo) + gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_ITEM_RESIZE, + item_class->resize_desc); + + /* note that we call gimp_item_start_move(), and not + * gimp_item_start_transform(). whether or not a resize operation should be + * considered a transform operation, or a move operation, depends on the + * intended use of these functions by subclasses. atm, we only use + * gimp_item_{start,end}_transform() to suspend mask resizing in group + * layers, which should not happen when reisizing a group, hence the call to + * gimp_item_start_move(). + * + * see the comment in gimp_group_layer_resize() for more information. + */ + gimp_item_start_move (item, push_undo); + + g_object_freeze_notify (G_OBJECT (item)); + + item_class->resize (item, context, fill_type, + new_width, new_height, offset_x, offset_y); + + g_object_thaw_notify (G_OBJECT (item)); + + gimp_item_end_move (item, push_undo); + + if (push_undo) + gimp_image_undo_group_end (image); +} + +void +gimp_item_flip (GimpItem *item, + GimpContext *context, + GimpOrientationType flip_type, + gdouble axis, + gboolean clip_result) +{ + GimpItemClass *item_class; + GimpImage *image; + gboolean push_undo; + + g_return_if_fail (GIMP_IS_ITEM (item)); + g_return_if_fail (gimp_item_is_attached (item)); + g_return_if_fail (GIMP_IS_CONTEXT (context)); + + item_class = GIMP_ITEM_GET_CLASS (item); + image = gimp_item_get_image (item); + + push_undo = gimp_item_is_attached (item); + + if (push_undo) + gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_TRANSFORM, + item_class->flip_desc); + + gimp_item_start_transform (item, push_undo); + + g_object_freeze_notify (G_OBJECT (item)); + + item_class->flip (item, context, flip_type, axis, clip_result); + + g_object_thaw_notify (G_OBJECT (item)); + + gimp_item_end_transform (item, push_undo); + + if (push_undo) + gimp_image_undo_group_end (image); +} + +void +gimp_item_rotate (GimpItem *item, + GimpContext *context, + GimpRotationType rotate_type, + gdouble center_x, + gdouble center_y, + gboolean clip_result) +{ + GimpItemClass *item_class; + GimpImage *image; + gboolean push_undo; + + g_return_if_fail (GIMP_IS_ITEM (item)); + g_return_if_fail (gimp_item_is_attached (item)); + g_return_if_fail (GIMP_IS_CONTEXT (context)); + + item_class = GIMP_ITEM_GET_CLASS (item); + image = gimp_item_get_image (item); + + push_undo = gimp_item_is_attached (item); + + if (push_undo) + gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_TRANSFORM, + item_class->rotate_desc); + + gimp_item_start_transform (item, push_undo); + + g_object_freeze_notify (G_OBJECT (item)); + + item_class->rotate (item, context, rotate_type, center_x, center_y, + clip_result); + + g_object_thaw_notify (G_OBJECT (item)); + + gimp_item_end_transform (item, push_undo); + + if (push_undo) + gimp_image_undo_group_end (image); +} + +void +gimp_item_transform (GimpItem *item, + GimpContext *context, + const GimpMatrix3 *matrix, + GimpTransformDirection direction, + GimpInterpolationType interpolation, + GimpTransformResize clip_result, + GimpProgress *progress) +{ + GimpItemClass *item_class; + GimpImage *image; + gboolean push_undo; + + g_return_if_fail (GIMP_IS_ITEM (item)); + g_return_if_fail (gimp_item_is_attached (item)); + g_return_if_fail (GIMP_IS_CONTEXT (context)); + g_return_if_fail (matrix != NULL); + g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress)); + + item_class = GIMP_ITEM_GET_CLASS (item); + image = gimp_item_get_image (item); + + push_undo = gimp_item_is_attached (item); + + if (push_undo) + gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_TRANSFORM, + item_class->transform_desc); + + gimp_item_start_transform (item, push_undo); + + g_object_freeze_notify (G_OBJECT (item)); + + item_class->transform (item, context, matrix, direction, interpolation, + clip_result, progress); + + g_object_thaw_notify (G_OBJECT (item)); + + gimp_item_end_transform (item, push_undo); + + if (push_undo) + gimp_image_undo_group_end (image); +} + +GimpTransformResize +gimp_item_get_clip (GimpItem *item, + GimpTransformResize clip_result) +{ + g_return_val_if_fail (GIMP_IS_ITEM (item), GIMP_TRANSFORM_RESIZE_ADJUST); + + return GIMP_ITEM_GET_CLASS (item)->get_clip (item, clip_result); +} + +gboolean +gimp_item_fill (GimpItem *item, + GimpDrawable *drawable, + GimpFillOptions *fill_options, + gboolean push_undo, + GimpProgress *progress, + GError **error) +{ + GimpItemClass *item_class; + gboolean retval = FALSE; + + g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE); + g_return_val_if_fail (gimp_item_is_attached (item), FALSE); + g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE); + g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), FALSE); + g_return_val_if_fail (GIMP_IS_FILL_OPTIONS (fill_options), FALSE); + g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + item_class = GIMP_ITEM_GET_CLASS (item); + + if (item_class->fill) + { + GimpImage *image = gimp_item_get_image (item); + + if (push_undo) + gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_PAINT, + item_class->fill_desc); + + retval = item_class->fill (item, drawable, fill_options, push_undo, + progress, error); + + if (push_undo) + gimp_image_undo_group_end (image); + } + + return retval; +} + +gboolean +gimp_item_stroke (GimpItem *item, + GimpDrawable *drawable, + GimpContext *context, + GimpStrokeOptions *stroke_options, + GimpPaintOptions *paint_options, + gboolean push_undo, + GimpProgress *progress, + GError **error) +{ + GimpItemClass *item_class; + gboolean retval = FALSE; + + g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE); + g_return_val_if_fail (gimp_item_is_attached (item), FALSE); + g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE); + g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), FALSE); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), FALSE); + g_return_val_if_fail (GIMP_IS_STROKE_OPTIONS (stroke_options), FALSE); + g_return_val_if_fail (paint_options == NULL || + GIMP_IS_PAINT_OPTIONS (paint_options), FALSE); + g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + item_class = GIMP_ITEM_GET_CLASS (item); + + if (item_class->stroke) + { + GimpImage *image = gimp_item_get_image (item); + + gimp_stroke_options_prepare (stroke_options, context, paint_options); + + if (push_undo) + gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_PAINT, + item_class->stroke_desc); + + retval = item_class->stroke (item, drawable, stroke_options, push_undo, + progress, error); + + if (push_undo) + gimp_image_undo_group_end (image); + + gimp_stroke_options_finish (stroke_options); + } + + return retval; +} + +void +gimp_item_to_selection (GimpItem *item, + GimpChannelOps op, + gboolean antialias, + gboolean feather, + gdouble feather_radius_x, + gdouble feather_radius_y) +{ + GimpItemClass *item_class; + + g_return_if_fail (GIMP_IS_ITEM (item)); + g_return_if_fail (gimp_item_is_attached (item)); + + item_class = GIMP_ITEM_GET_CLASS (item); + + if (item_class->to_selection) + item_class->to_selection (item, op, antialias, + feather, feather_radius_x, feather_radius_y); +} + +void +gimp_item_add_offset_node (GimpItem *item, + GeglNode *node) +{ + GimpItemPrivate *private; + + g_return_if_fail (GIMP_IS_ITEM (item)); + g_return_if_fail (GEGL_IS_NODE (node)); + + private = GET_PRIVATE (item); + + g_return_if_fail (g_list_find (private->offset_nodes, node) == NULL); + + gegl_node_set (node, + "x", (gdouble) private->offset_x, + "y", (gdouble) private->offset_y, + NULL); + + private->offset_nodes = g_list_append (private->offset_nodes, + g_object_ref (node)); +} + +void +gimp_item_remove_offset_node (GimpItem *item, + GeglNode *node) +{ + GimpItemPrivate *private; + + g_return_if_fail (GIMP_IS_ITEM (item)); + g_return_if_fail (GEGL_IS_NODE (node)); + + private = GET_PRIVATE (item); + + g_return_if_fail (g_list_find (private->offset_nodes, node) != NULL); + + private->offset_nodes = g_list_remove (private->offset_nodes, node); + g_object_unref (node); +} + +gint +gimp_item_get_ID (GimpItem *item) +{ + g_return_val_if_fail (GIMP_IS_ITEM (item), -1); + + return GET_PRIVATE (item)->ID; +} + +GimpItem * +gimp_item_get_by_ID (Gimp *gimp, + gint item_id) +{ + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + + if (gimp->item_table == NULL) + return NULL; + + return (GimpItem *) gimp_id_table_lookup (gimp->item_table, item_id); +} + +GimpTattoo +gimp_item_get_tattoo (GimpItem *item) +{ + g_return_val_if_fail (GIMP_IS_ITEM (item), 0); + + return GET_PRIVATE (item)->tattoo; +} + +void +gimp_item_set_tattoo (GimpItem *item, + GimpTattoo tattoo) +{ + g_return_if_fail (GIMP_IS_ITEM (item)); + + GET_PRIVATE (item)->tattoo = tattoo; +} + +GimpImage * +gimp_item_get_image (GimpItem *item) +{ + g_return_val_if_fail (GIMP_IS_ITEM (item), NULL); + + return GET_PRIVATE (item)->image; +} + +void +gimp_item_set_image (GimpItem *item, + GimpImage *image) +{ + GimpItemPrivate *private; + + g_return_if_fail (GIMP_IS_ITEM (item)); + g_return_if_fail (! gimp_item_is_attached (item)); + g_return_if_fail (! gimp_item_is_removed (item)); + g_return_if_fail (GIMP_IS_IMAGE (image)); + + private = GET_PRIVATE (item); + + if (image == private->image) + return; + + g_object_freeze_notify (G_OBJECT (item)); + + if (private->ID == 0) + { + private->ID = gimp_id_table_insert (image->gimp->item_table, item); + + g_object_notify (G_OBJECT (item), "id"); + } + + if (private->tattoo == 0 || private->image != image) + { + private->tattoo = gimp_image_get_new_tattoo (image); + } + + private->image = image; + g_object_notify (G_OBJECT (item), "image"); + + g_object_thaw_notify (G_OBJECT (item)); +} + +/** + * gimp_item_replace_item: + * @item: a newly allocated #GimpItem + * @replace: the #GimpItem to be replaced by @item + * + * This function shouly only be called right after @item has been + * newly allocated. + * + * Replaces @replace by @item, as far as possible within the #GimpItem + * class. The new @item takes over @replace's ID, tattoo, offset, size + * etc. and all these properties are set to %NULL on @replace. + * + * This function *only* exists to allow subclasses to do evil hacks + * like in XCF text layer loading. Don't ever use this function if you + * are not sure. + * + * After this function returns, @replace has become completely + * unusable, should only be used to steal everything it has (like its + * drawable properties if it's a drawable), and then be destroyed. + **/ +void +gimp_item_replace_item (GimpItem *item, + GimpItem *replace) +{ + GimpItemPrivate *private; + gint offset_x; + gint offset_y; + + g_return_if_fail (GIMP_IS_ITEM (item)); + g_return_if_fail (! gimp_item_is_attached (item)); + g_return_if_fail (! gimp_item_is_removed (item)); + g_return_if_fail (GIMP_IS_ITEM (replace)); + + private = GET_PRIVATE (item); + + gimp_object_set_name (GIMP_OBJECT (item), gimp_object_get_name (replace)); + + if (private->ID) + gimp_id_table_remove (gimp_item_get_image (item)->gimp->item_table, + gimp_item_get_ID (item)); + + private->ID = gimp_item_get_ID (replace); + gimp_id_table_replace (gimp_item_get_image (item)->gimp->item_table, + gimp_item_get_ID (item), + item); + + /* Set image before tattoo so that the explicitly set tattoo overrides + * the one implicitly set when setting the image + */ + gimp_item_set_image (item, gimp_item_get_image (replace)); + GET_PRIVATE (replace)->image = NULL; + + gimp_item_set_tattoo (item, gimp_item_get_tattoo (replace)); + gimp_item_set_tattoo (replace, 0); + + g_object_unref (private->parasites); + private->parasites = GET_PRIVATE (replace)->parasites; + GET_PRIVATE (replace)->parasites = NULL; + + gimp_item_get_offset (replace, &offset_x, &offset_y); + gimp_item_set_offset (item, offset_x, offset_y); + + gimp_item_set_size (item, + gimp_item_get_width (replace), + gimp_item_get_height (replace)); + + gimp_item_set_visible (item, gimp_item_get_visible (replace), FALSE); + gimp_item_set_linked (item, gimp_item_get_linked (replace), FALSE); + gimp_item_set_color_tag (item, gimp_item_get_color_tag (replace), FALSE); + gimp_item_set_lock_content (item, gimp_item_get_lock_content (replace), FALSE); + gimp_item_set_lock_position (item, gimp_item_get_lock_position (replace), FALSE); +} + +/** + * gimp_item_set_parasites: + * @item: a #GimpItem + * @parasites: a #GimpParasiteList + * + * Set an @item's #GimpParasiteList. It's usually never needed to + * fiddle with an item's parasite list directly. This function exists + * for special purposes only, like when creating items from unusual + * sources. + **/ +void +gimp_item_set_parasites (GimpItem *item, + GimpParasiteList *parasites) +{ + GimpItemPrivate *private; + + g_return_if_fail (GIMP_IS_ITEM (item)); + g_return_if_fail (GIMP_IS_PARASITE_LIST (parasites)); + + private = GET_PRIVATE (item); + + g_set_object (&private->parasites, parasites); +} + +/** + * gimp_item_get_parasites: + * @item: a #GimpItem + * + * Get an @item's #GimpParasiteList. It's usually never needed to + * fiddle with an item's parasite list directly. This function exists + * for special purposes only, like when saving an item to XCF. + * + * Return value: The @item's #GimpParasiteList. + **/ +GimpParasiteList * +gimp_item_get_parasites (GimpItem *item) +{ + g_return_val_if_fail (GIMP_IS_ITEM (item), NULL); + + return GET_PRIVATE (item)->parasites; +} + +gboolean +gimp_item_parasite_validate (GimpItem *item, + const GimpParasite *parasite, + GError **error) +{ + g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE); + g_return_val_if_fail (parasite != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + return TRUE; +} + +void +gimp_item_parasite_attach (GimpItem *item, + const GimpParasite *parasite, + gboolean push_undo) +{ + GimpItemPrivate *private; + GimpParasite copy; + + g_return_if_fail (GIMP_IS_ITEM (item)); + g_return_if_fail (parasite != NULL); + + private = GET_PRIVATE (item); + + /* make a temporary copy of the GimpParasite struct because + * gimp_parasite_shift_parent() changes it + */ + copy = *parasite; + + if (! gimp_item_is_attached (item)) + push_undo = FALSE; + + if (push_undo) + { + /* only set the dirty bit manually if we can be saved and the new + * parasite differs from the current one and we aren't undoable + */ + if (gimp_parasite_is_undoable (©)) + { + /* do a group in case we have attach_parent set */ + gimp_image_undo_group_start (private->image, + GIMP_UNDO_GROUP_PARASITE_ATTACH, + C_("undo-type", "Attach Parasite")); + + gimp_image_undo_push_item_parasite (private->image, NULL, item, ©); + } + else if (gimp_parasite_is_persistent (©) && + ! gimp_parasite_compare (©, + gimp_item_parasite_find + (item, gimp_parasite_name (©)))) + { + gimp_image_undo_push_cantundo (private->image, + C_("undo-type", "Attach Parasite to Item")); + } + } + + gimp_parasite_list_add (private->parasites, ©); + + if (gimp_parasite_has_flag (©, GIMP_PARASITE_ATTACH_PARENT)) + { + gimp_parasite_shift_parent (©); + gimp_image_parasite_attach (private->image, ©, TRUE); + } + else if (gimp_parasite_has_flag (©, GIMP_PARASITE_ATTACH_GRANDPARENT)) + { + gimp_parasite_shift_parent (©); + gimp_parasite_shift_parent (©); + gimp_parasite_attach (private->image->gimp, ©); + } + + if (gimp_item_is_attached (item) && + gimp_parasite_is_undoable (©)) + { + gimp_image_undo_group_end (private->image); + } +} + +void +gimp_item_parasite_detach (GimpItem *item, + const gchar *name, + gboolean push_undo) +{ + GimpItemPrivate *private; + const GimpParasite *parasite; + + g_return_if_fail (GIMP_IS_ITEM (item)); + g_return_if_fail (name != NULL); + + private = GET_PRIVATE (item); + + parasite = gimp_parasite_list_find (private->parasites, name); + + if (! parasite) + return; + + if (! gimp_item_is_attached (item)) + push_undo = FALSE; + + if (push_undo) + { + if (gimp_parasite_is_undoable (parasite)) + { + gimp_image_undo_push_item_parasite_remove (private->image, + C_("undo-type", "Remove Parasite from Item"), + item, + gimp_parasite_name (parasite)); + } + else if (gimp_parasite_is_persistent (parasite)) + { + gimp_image_undo_push_cantundo (private->image, + C_("undo-type", "Remove Parasite from Item")); + } + } + + gimp_parasite_list_remove (private->parasites, name); +} + +const GimpParasite * +gimp_item_parasite_find (GimpItem *item, + const gchar *name) +{ + g_return_val_if_fail (GIMP_IS_ITEM (item), NULL); + + return gimp_parasite_list_find (GET_PRIVATE (item)->parasites, name); +} + +static void +gimp_item_parasite_list_foreach_func (gchar *name, + GimpParasite *parasite, + gchar ***cur) +{ + *(*cur)++ = (gchar *) g_strdup (name); +} + +gchar ** +gimp_item_parasite_list (GimpItem *item, + gint *count) +{ + GimpItemPrivate *private; + gchar **list; + gchar **cur; + + g_return_val_if_fail (GIMP_IS_ITEM (item), NULL); + g_return_val_if_fail (count != NULL, NULL); + + private = GET_PRIVATE (item); + + *count = gimp_parasite_list_length (private->parasites); + + cur = list = g_new (gchar *, *count); + + gimp_parasite_list_foreach (private->parasites, + (GHFunc) gimp_item_parasite_list_foreach_func, + &cur); + + return list; +} + +void +gimp_item_set_visible (GimpItem *item, + gboolean visible, + gboolean push_undo) +{ + g_return_if_fail (GIMP_IS_ITEM (item)); + + visible = visible ? TRUE : FALSE; + + if (gimp_item_get_visible (item) != visible) + { + if (push_undo && gimp_item_is_attached (item)) + { + GimpImage *image = gimp_item_get_image (item); + + if (image) + gimp_image_undo_push_item_visibility (image, NULL, item); + } + + GET_PRIVATE (item)->visible = visible; + + if (GET_PRIVATE (item)->bind_visible_to_active) + gimp_filter_set_active (GIMP_FILTER (item), visible); + + g_signal_emit (item, gimp_item_signals[VISIBILITY_CHANGED], 0); + + g_object_notify (G_OBJECT (item), "visible"); + } +} + +gboolean +gimp_item_get_visible (GimpItem *item) +{ + g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE); + + return GET_PRIVATE (item)->visible; +} + +gboolean +gimp_item_is_visible (GimpItem *item) +{ + g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE); + + if (gimp_item_get_visible (item)) + { + GimpItem *parent; + + parent = GIMP_ITEM (gimp_viewable_get_parent (GIMP_VIEWABLE (item))); + + if (parent) + return gimp_item_is_visible (parent); + + return TRUE; + } + + return FALSE; +} + +void +gimp_item_bind_visible_to_active (GimpItem *item, + gboolean bind) +{ + g_return_if_fail (GIMP_IS_ITEM (item)); + + GET_PRIVATE (item)->bind_visible_to_active = bind; + + if (bind) + gimp_filter_set_active (GIMP_FILTER (item), gimp_item_get_visible (item)); +} + +void +gimp_item_set_linked (GimpItem *item, + gboolean linked, + gboolean push_undo) +{ + g_return_if_fail (GIMP_IS_ITEM (item)); + + linked = linked ? TRUE : FALSE; + + if (gimp_item_get_linked (item) != linked) + { + GimpImage *image = gimp_item_get_image (item); + gboolean is_attached = gimp_item_is_attached (item); + + if (push_undo && is_attached && image) + gimp_image_undo_push_item_linked (image, NULL, item); + + GET_PRIVATE (item)->linked = linked; + + g_signal_emit (item, gimp_item_signals[LINKED_CHANGED], 0); + + if (is_attached && image) + gimp_image_linked_items_changed (image); + + g_object_notify (G_OBJECT (item), "linked"); + } +} + +gboolean +gimp_item_get_linked (GimpItem *item) +{ + g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE); + + return GET_PRIVATE (item)->linked; +} + +void +gimp_item_set_color_tag (GimpItem *item, + GimpColorTag color_tag, + gboolean push_undo) +{ + g_return_if_fail (GIMP_IS_ITEM (item)); + + if (gimp_item_get_color_tag (item) != color_tag) + { + if (push_undo && gimp_item_is_attached (item)) + { + GimpImage *image = gimp_item_get_image (item); + + if (image) + gimp_image_undo_push_item_color_tag (image, NULL, item); + } + + GET_PRIVATE (item)->color_tag = color_tag; + + g_signal_emit (item, gimp_item_signals[COLOR_TAG_CHANGED], 0); + + g_object_notify (G_OBJECT (item), "color-tag"); + } +} + +GimpColorTag +gimp_item_get_color_tag (GimpItem *item) +{ + g_return_val_if_fail (GIMP_IS_ITEM (item), GIMP_COLOR_TAG_NONE); + + return GET_PRIVATE (item)->color_tag; +} + +GimpColorTag +gimp_item_get_merged_color_tag (GimpItem *item) +{ + g_return_val_if_fail (GIMP_IS_ITEM (item), GIMP_COLOR_TAG_NONE); + + if (gimp_item_get_color_tag (item) == GIMP_COLOR_TAG_NONE) + { + GimpItem *parent; + + parent = GIMP_ITEM (gimp_viewable_get_parent (GIMP_VIEWABLE (item))); + + if (parent) + return gimp_item_get_merged_color_tag (parent); + } + + return gimp_item_get_color_tag (item); +} + +void +gimp_item_set_lock_content (GimpItem *item, + gboolean lock_content, + gboolean push_undo) +{ + g_return_if_fail (GIMP_IS_ITEM (item)); + g_return_if_fail (gimp_item_can_lock_content (item)); + + lock_content = lock_content ? TRUE : FALSE; + + if (gimp_item_get_lock_content (item) != lock_content) + { + if (push_undo && gimp_item_is_attached (item)) + { + /* Right now I don't think this should be pushed. */ +#if 0 + GimpImage *image = gimp_item_get_image (item); + + gimp_image_undo_push_item_lock_content (image, NULL, item); +#endif + } + + GET_PRIVATE (item)->lock_content = lock_content; + + g_signal_emit (item, gimp_item_signals[LOCK_CONTENT_CHANGED], 0); + + g_object_notify (G_OBJECT (item), "lock-content"); + } +} + +gboolean +gimp_item_get_lock_content (GimpItem *item) +{ + g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE); + + return GET_PRIVATE (item)->lock_content; +} + +gboolean +gimp_item_can_lock_content (GimpItem *item) +{ + g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE); + + return TRUE; +} + +gboolean +gimp_item_is_content_locked (GimpItem *item) +{ + g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE); + + return GIMP_ITEM_GET_CLASS (item)->is_content_locked (item); +} + +void +gimp_item_set_lock_position (GimpItem *item, + gboolean lock_position, + gboolean push_undo) +{ + g_return_if_fail (GIMP_IS_ITEM (item)); + g_return_if_fail (gimp_item_can_lock_position (item)); + + lock_position = lock_position ? TRUE : FALSE; + + if (gimp_item_get_lock_position (item) != lock_position) + { + if (push_undo && gimp_item_is_attached (item)) + { + GimpImage *image = gimp_item_get_image (item); + + gimp_image_undo_push_item_lock_position (image, NULL, item); + } + + GET_PRIVATE (item)->lock_position = lock_position; + + g_signal_emit (item, gimp_item_signals[LOCK_POSITION_CHANGED], 0); + + g_object_notify (G_OBJECT (item), "lock-position"); + } +} + +gboolean +gimp_item_get_lock_position (GimpItem *item) +{ + g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE); + + return GET_PRIVATE (item)->lock_position; +} + +gboolean +gimp_item_can_lock_position (GimpItem *item) +{ + g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE); + + if (gimp_viewable_get_children (GIMP_VIEWABLE (item))) + return FALSE; + + return TRUE; +} + +gboolean +gimp_item_is_position_locked (GimpItem *item) +{ + g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE); + + return GIMP_ITEM_GET_CLASS (item)->is_position_locked (item); +} + +gboolean +gimp_item_mask_bounds (GimpItem *item, + gint *x1, + gint *y1, + gint *x2, + gint *y2) +{ + GimpImage *image; + GimpChannel *selection; + gint x, y, width, height; + gboolean retval; + + g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE); + g_return_val_if_fail (gimp_item_is_attached (item), FALSE); + + image = gimp_item_get_image (item); + selection = gimp_image_get_mask (image); + + /* check for is_empty() before intersecting so we ignore the + * selection if it is suspended (like when stroking) + */ + if (GIMP_ITEM (selection) != item && + ! gimp_channel_is_empty (selection) && + gimp_item_bounds (GIMP_ITEM (selection), &x, &y, &width, &height)) + { + gint off_x, off_y; + gint x2, y2; + + gimp_item_get_offset (item, &off_x, &off_y); + + x2 = x + width; + y2 = y + height; + + x = CLAMP (x - off_x, 0, gimp_item_get_width (item)); + y = CLAMP (y - off_y, 0, gimp_item_get_height (item)); + x2 = CLAMP (x2 - off_x, 0, gimp_item_get_width (item)); + y2 = CLAMP (y2 - off_y, 0, gimp_item_get_height (item)); + + width = x2 - x; + height = y2 - y; + + retval = TRUE; + } + else + { + x = 0; + y = 0; + width = gimp_item_get_width (item); + height = gimp_item_get_height (item); + + retval = FALSE; + } + + if (x1) *x1 = x; + if (y1) *y1 = y; + if (x2) *x2 = x + width; + if (y2) *y2 = y + height; + + return retval; +} + +/** + * gimp_item_mask_intersect: + * @item: a #GimpItem + * @x: return location for x + * @y: return location for y + * @width: return location for the width + * @height: return location for the height + * + * Intersect the area of the @item and its image's selection mask. + * The computed area is the bounding box of he selection within the + * item. + **/ +gboolean +gimp_item_mask_intersect (GimpItem *item, + gint *x, + gint *y, + gint *width, + gint *height) +{ + GimpImage *image; + GimpChannel *selection; + gint tmp_x, tmp_y; + gint tmp_width, tmp_height; + gboolean retval; + + g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE); + g_return_val_if_fail (gimp_item_is_attached (item), FALSE); + + image = gimp_item_get_image (item); + selection = gimp_image_get_mask (image); + + /* check for is_empty() before intersecting so we ignore the + * selection if it is suspended (like when stroking) + */ + if (GIMP_ITEM (selection) != item && + ! gimp_channel_is_empty (selection) && + gimp_item_bounds (GIMP_ITEM (selection), + &tmp_x, &tmp_y, &tmp_width, &tmp_height)) + { + gint off_x, off_y; + + gimp_item_get_offset (item, &off_x, &off_y); + + retval = gimp_rectangle_intersect (tmp_x - off_x, tmp_y - off_y, + tmp_width, tmp_height, + 0, 0, + gimp_item_get_width (item), + gimp_item_get_height (item), + &tmp_x, &tmp_y, + &tmp_width, &tmp_height); + } + else + { + tmp_x = 0; + tmp_y = 0; + tmp_width = gimp_item_get_width (item); + tmp_height = gimp_item_get_height (item); + + retval = TRUE; + } + + if (x) *x = tmp_x; + if (y) *y = tmp_y; + if (width) *width = tmp_width; + if (height) *height = tmp_height; + + return retval; +} + +gboolean +gimp_item_is_in_set (GimpItem *item, + GimpItemSet set) +{ + GimpItemPrivate *private; + + g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE); + + private = GET_PRIVATE (item); + + switch (set) + { + case GIMP_ITEM_SET_NONE: + return FALSE; + + case GIMP_ITEM_SET_ALL: + return TRUE; + + case GIMP_ITEM_SET_IMAGE_SIZED: + return (gimp_item_get_width (item) == gimp_image_get_width (private->image) && + gimp_item_get_height (item) == gimp_image_get_height (private->image)); + + case GIMP_ITEM_SET_VISIBLE: + return gimp_item_get_visible (item); + + case GIMP_ITEM_SET_LINKED: + return gimp_item_get_linked (item); + } + + return FALSE; +} |