/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * 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 . */ #include "config.h" #include #include #include #include #include "libpikabase/pikabase.h" #include "libpikamath/pikamath.h" #include "core-types.h" #include "pika.h" #include "pika-parasites.h" #include "pikachannel.h" #include "pikacontainer.h" #include "pikaidtable.h" #include "pikaimage.h" #include "pikaimage-undo.h" #include "pikaimage-undo-push.h" #include "pikaitem.h" #include "pikaitem-preview.h" #include "pikaitemtree.h" #include "pikalist.h" #include "pikaparasitelist.h" #include "pikaprogress.h" #include "pikastrokeoptions.h" #include "paint/pikapaintoptions.h" #include "pika-intl.h" enum { REMOVED, VISIBILITY_CHANGED, COLOR_TAG_CHANGED, LOCK_CONTENT_CHANGED, LOCK_POSITION_CHANGED, LOCK_VISIBILITY_CHANGED, LAST_SIGNAL }; enum { PROP_0, PROP_IMAGE, PROP_ID, PROP_WIDTH, PROP_HEIGHT, PROP_OFFSET_X, PROP_OFFSET_Y, PROP_VISIBLE, PROP_COLOR_TAG, PROP_LOCK_CONTENT, PROP_LOCK_POSITION, PROP_LOCK_VISIBILITY, N_PROPS }; typedef struct _PikaItemPrivate PikaItemPrivate; struct _PikaItemPrivate { gint ID; /* provides a unique ID */ guint32 tattoo; /* provides a permanent ID */ PikaImage *image; /* item owner */ PikaParasiteList *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 lock_content : 1; /* content editability */ guint lock_position : 1; /* content movability */ guint lock_visibility : 1; /* automatic visibility change */ guint removed : 1; /* removed from the image? */ PikaColorTag color_tag; /* color tag */ GList *offset_nodes; /* offset nodes to manage */ }; #define GET_PRIVATE(item) ((PikaItemPrivate *) pika_item_get_instance_private ((PikaItem *) (item))) /* local function prototypes */ static void pika_item_constructed (GObject *object); static void pika_item_finalize (GObject *object); static void pika_item_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void pika_item_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static gint64 pika_item_get_memsize (PikaObject *object, gint64 *gui_size); static gboolean pika_item_real_is_content_locked (PikaItem *item, PikaItem **locked_item); static gboolean pika_item_real_is_position_locked (PikaItem *item, PikaItem **locked_item, gboolean check_children); static gboolean pika_item_real_is_visibility_locked (PikaItem *item, PikaItem **locked_item); static gboolean pika_item_real_bounds (PikaItem *item, gdouble *x, gdouble *y, gdouble *width, gdouble *height); static PikaItem * pika_item_real_duplicate (PikaItem *item, GType new_type); static void pika_item_real_convert (PikaItem *item, PikaImage *dest_image, GType old_type); static gboolean pika_item_real_rename (PikaItem *item, const gchar *new_name, const gchar *undo_desc, GError **error); static void pika_item_real_start_transform (PikaItem *item, gboolean push_undo); static void pika_item_real_end_transform (PikaItem *item, gboolean push_undo); static void pika_item_real_translate (PikaItem *item, gdouble offset_x, gdouble offset_y, gboolean push_undo); static void pika_item_real_scale (PikaItem *item, gint new_width, gint new_height, gint new_offset_x, gint new_offset_y, PikaInterpolationType interpolation, PikaProgress *progress); static void pika_item_real_resize (PikaItem *item, PikaContext *context, PikaFillType fill_type, gint new_width, gint new_height, gint offset_x, gint offset_y); static PikaTransformResize pika_item_real_get_clip (PikaItem *item, PikaTransformResize clip_result); G_DEFINE_TYPE_WITH_PRIVATE (PikaItem, pika_item, PIKA_TYPE_FILTER) #define parent_class pika_item_parent_class static guint pika_item_signals[LAST_SIGNAL] = { 0 }; static GParamSpec *pika_item_props[N_PROPS] = { NULL, }; static void pika_item_class_init (PikaItemClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PikaObjectClass *pika_object_class = PIKA_OBJECT_CLASS (klass); PikaViewableClass *viewable_class = PIKA_VIEWABLE_CLASS (klass); pika_item_signals[REMOVED] = g_signal_new ("removed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaItemClass, removed), NULL, NULL, NULL, G_TYPE_NONE, 0); pika_item_signals[VISIBILITY_CHANGED] = g_signal_new ("visibility-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaItemClass, visibility_changed), NULL, NULL, NULL, G_TYPE_NONE, 0); pika_item_signals[COLOR_TAG_CHANGED] = g_signal_new ("color-tag-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaItemClass, color_tag_changed), NULL, NULL, NULL, G_TYPE_NONE, 0); pika_item_signals[LOCK_CONTENT_CHANGED] = g_signal_new ("lock-content-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaItemClass, lock_content_changed), NULL, NULL, NULL, G_TYPE_NONE, 0); pika_item_signals[LOCK_POSITION_CHANGED] = g_signal_new ("lock-position-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaItemClass, lock_position_changed), NULL, NULL, NULL, G_TYPE_NONE, 0); pika_item_signals[LOCK_VISIBILITY_CHANGED] = g_signal_new ("lock-visibility-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaItemClass, lock_visibility_changed), NULL, NULL, NULL, G_TYPE_NONE, 0); object_class->constructed = pika_item_constructed; object_class->finalize = pika_item_finalize; object_class->set_property = pika_item_set_property; object_class->get_property = pika_item_get_property; pika_object_class->get_memsize = pika_item_get_memsize; viewable_class->name_editable = TRUE; viewable_class->get_preview_size = pika_item_get_preview_size; viewable_class->get_popup_size = pika_item_get_popup_size; klass->removed = NULL; klass->visibility_changed = NULL; klass->color_tag_changed = NULL; klass->lock_content_changed = NULL; klass->lock_position_changed = NULL; klass->lock_visibility_changed = NULL; klass->unset_removed = NULL; klass->is_attached = NULL; klass->is_content_locked = pika_item_real_is_content_locked; klass->is_position_locked = pika_item_real_is_position_locked; klass->is_visibility_locked = pika_item_real_is_visibility_locked; klass->get_tree = NULL; klass->bounds = pika_item_real_bounds; klass->duplicate = pika_item_real_duplicate; klass->convert = pika_item_real_convert; klass->rename = pika_item_real_rename; klass->start_move = NULL; klass->end_move = NULL; klass->start_transform = pika_item_real_start_transform; klass->end_transform = pika_item_real_end_transform; klass->translate = pika_item_real_translate; klass->scale = pika_item_real_scale; klass->resize = pika_item_real_resize; klass->flip = NULL; klass->rotate = NULL; klass->transform = NULL; klass->get_clip = pika_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; pika_item_props[PROP_IMAGE] = g_param_spec_object ("image", NULL, NULL, PIKA_TYPE_IMAGE, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT); pika_item_props[PROP_ID] = g_param_spec_int ("id", NULL, NULL, 0, G_MAXINT, 0, PIKA_PARAM_READABLE); pika_item_props[PROP_WIDTH] = g_param_spec_int ("width", NULL, NULL, 1, PIKA_MAX_IMAGE_SIZE, 1, PIKA_PARAM_READABLE); pika_item_props[PROP_HEIGHT] = g_param_spec_int ("height", NULL, NULL, 1, PIKA_MAX_IMAGE_SIZE, 1, PIKA_PARAM_READABLE); pika_item_props[PROP_OFFSET_X] = g_param_spec_int ("offset-x", NULL, NULL, -PIKA_MAX_IMAGE_SIZE, PIKA_MAX_IMAGE_SIZE, 0, PIKA_PARAM_READABLE); pika_item_props[PROP_OFFSET_Y] = g_param_spec_int ("offset-y", NULL, NULL, -PIKA_MAX_IMAGE_SIZE, PIKA_MAX_IMAGE_SIZE, 0, PIKA_PARAM_READABLE); pika_item_props[PROP_VISIBLE] = g_param_spec_boolean ("visible", NULL, NULL, TRUE, PIKA_PARAM_READABLE); pika_item_props[PROP_COLOR_TAG] = g_param_spec_enum ("color-tag", NULL, NULL, PIKA_TYPE_COLOR_TAG, PIKA_COLOR_TAG_NONE, PIKA_PARAM_READABLE); pika_item_props[PROP_LOCK_CONTENT] = g_param_spec_boolean ("lock-content", NULL, NULL, FALSE, PIKA_PARAM_READABLE); pika_item_props[PROP_LOCK_POSITION] = g_param_spec_boolean ("lock-position", NULL, NULL, FALSE, PIKA_PARAM_READABLE); pika_item_props[PROP_LOCK_VISIBILITY] = g_param_spec_boolean ("lock-visibility", NULL, NULL, FALSE, PIKA_PARAM_READABLE); g_object_class_install_properties (object_class, N_PROPS, pika_item_props); } static void pika_item_init (PikaItem *item) { PikaItemPrivate *private = GET_PRIVATE (item); g_object_force_floating (G_OBJECT (item)); private->parasites = pika_parasite_list_new (); private->visible = TRUE; private->bind_visible_to_active = TRUE; } static void pika_item_constructed (GObject *object) { PikaItemPrivate *private = GET_PRIVATE (object); G_OBJECT_CLASS (parent_class)->constructed (object); pika_assert (PIKA_IS_IMAGE (private->image)); pika_assert (private->ID != 0); } static void pika_item_finalize (GObject *object) { PikaItemPrivate *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->pika) { pika_id_table_remove (private->image->pika->item_table, private->ID); private->image = NULL; } g_clear_object (&private->parasites); G_OBJECT_CLASS (parent_class)->finalize (object); } static void pika_item_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { PikaItem *item = PIKA_ITEM (object); switch (property_id) { case PROP_IMAGE: pika_item_set_image (item, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void pika_item_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PikaItemPrivate *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_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; case PROP_LOCK_VISIBILITY: g_value_set_boolean (value, private->lock_visibility); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static gint64 pika_item_get_memsize (PikaObject *object, gint64 *gui_size) { PikaItemPrivate *private = GET_PRIVATE (object); gint64 memsize = 0; memsize += pika_object_get_memsize (PIKA_OBJECT (private->parasites), gui_size); return memsize + PIKA_OBJECT_CLASS (parent_class)->get_memsize (object, gui_size); } static gboolean pika_item_real_is_content_locked (PikaItem *item, PikaItem **locked_item) { PikaItem *parent = pika_item_get_parent (item); if (GET_PRIVATE (item)->lock_content) { if (locked_item) *locked_item = item; } else if (parent && pika_item_is_content_locked (parent, locked_item)) { return TRUE; } return GET_PRIVATE (item)->lock_content; } static gboolean pika_item_real_is_position_locked (PikaItem *item, PikaItem **locked_item, gboolean check_children) { PikaItem *parent = pika_item_get_parent (item); if (GET_PRIVATE (item)->lock_position) { if (locked_item) *locked_item = item; } else if (parent && PIKA_ITEM_GET_CLASS (item)->is_position_locked (parent, locked_item, FALSE)) { return TRUE; } return GET_PRIVATE (item)->lock_position; } static gboolean pika_item_real_is_visibility_locked (PikaItem *item, PikaItem **locked_item) { /* Unlike other locks, the visibility lock does not propagate from * parents or children. */ if (GET_PRIVATE (item)->lock_visibility && locked_item) *locked_item = item; return GET_PRIVATE (item)->lock_visibility; } static gboolean pika_item_real_bounds (PikaItem *item, gdouble *x, gdouble *y, gdouble *width, gdouble *height) { PikaItemPrivate *private = GET_PRIVATE (item); *x = 0; *y = 0; *width = private->width; *height = private->height; return TRUE; } static PikaItem * pika_item_real_duplicate (PikaItem *item, GType new_type) { PikaItemPrivate *private; PikaItem *new_item; gchar *new_name; g_return_val_if_fail (PIKA_IS_ITEM (item), NULL); private = GET_PRIVATE (item); g_return_val_if_fail (PIKA_IS_IMAGE (private->image), NULL); g_return_val_if_fail (g_type_is_a (new_type, PIKA_TYPE_ITEM), NULL); /* formulate the new name */ { const gchar *name; gint len; name = pika_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 = pika_item_new (new_type, pika_item_get_image (item), new_name, private->offset_x, private->offset_y, pika_item_get_width (item), pika_item_get_height (item)); g_free (new_name); pika_viewable_set_expanded (PIKA_VIEWABLE (new_item), pika_viewable_get_expanded (PIKA_VIEWABLE (item))); g_object_unref (GET_PRIVATE (new_item)->parasites); GET_PRIVATE (new_item)->parasites = pika_parasite_list_copy (private->parasites); pika_item_set_visible (new_item, pika_item_get_visible (item), FALSE); pika_item_set_color_tag (new_item, pika_item_get_color_tag (item), FALSE); if (pika_item_can_lock_content (new_item)) pika_item_set_lock_content (new_item, pika_item_get_lock_content (item), FALSE); if (pika_item_can_lock_position (new_item)) pika_item_set_lock_position (new_item, pika_item_get_lock_position (item), FALSE); if (pika_item_can_lock_visibility (new_item)) pika_item_set_lock_visibility (new_item, pika_item_get_lock_visibility (item), FALSE); return new_item; } static void pika_item_real_convert (PikaItem *item, PikaImage *dest_image, GType old_type) { pika_item_set_image (item, dest_image); } static gboolean pika_item_real_rename (PikaItem *item, const gchar *new_name, const gchar *undo_desc, GError **error) { if (pika_item_is_attached (item)) pika_item_tree_rename_item (pika_item_get_tree (item), item, new_name, TRUE, undo_desc); else pika_object_set_name (PIKA_OBJECT (item), new_name); return TRUE; } static void pika_item_real_translate (PikaItem *item, gdouble offset_x, gdouble offset_y, gboolean push_undo) { PikaItemPrivate *private = GET_PRIVATE (item); pika_item_set_offset (item, private->offset_x + SIGNED_ROUND (offset_x), private->offset_y + SIGNED_ROUND (offset_y)); } static void pika_item_real_start_transform (PikaItem *item, gboolean push_undo) { pika_item_start_move (item, push_undo); } static void pika_item_real_end_transform (PikaItem *item, gboolean push_undo) { pika_item_end_move (item, push_undo); } static void pika_item_real_scale (PikaItem *item, gint new_width, gint new_height, gint new_offset_x, gint new_offset_y, PikaInterpolationType interpolation, PikaProgress *progress) { PikaItemPrivate *private = GET_PRIVATE (item); if (private->width != new_width) { private->width = new_width; g_object_notify_by_pspec (G_OBJECT (item), pika_item_props[PROP_WIDTH]); } if (private->height != new_height) { private->height = new_height; g_object_notify_by_pspec (G_OBJECT (item), pika_item_props[PROP_HEIGHT]); } pika_item_set_offset (item, new_offset_x, new_offset_y); } static void pika_item_real_resize (PikaItem *item, PikaContext *context, PikaFillType fill_type, gint new_width, gint new_height, gint offset_x, gint offset_y) { PikaItemPrivate *private = GET_PRIVATE (item); if (private->width != new_width) { private->width = new_width; g_object_notify_by_pspec (G_OBJECT (item), pika_item_props[PROP_WIDTH]); } if (private->height != new_height) { private->height = new_height; g_object_notify_by_pspec (G_OBJECT (item), pika_item_props[PROP_HEIGHT]); } pika_item_set_offset (item, private->offset_x - offset_x, private->offset_y - offset_y); } static PikaTransformResize pika_item_real_get_clip (PikaItem *item, PikaTransformResize clip_result) { if (pika_item_get_lock_position (item)) return PIKA_TRANSFORM_RESIZE_CLIP; else return clip_result; } /* public functions */ /** * pika_item_new: * @type: The new item's type. * @image: The new item's #PikaImage. * @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. * * Returns: The newly created item. */ PikaItem * pika_item_new (GType type, PikaImage *image, const gchar *name, gint offset_x, gint offset_y, gint width, gint height) { PikaItem *item; PikaItemPrivate *private; g_return_val_if_fail (g_type_is_a (type, PIKA_TYPE_ITEM), NULL); g_return_val_if_fail (PIKA_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; pika_item_set_offset (item, offset_x, offset_y); if (name && strlen (name)) pika_object_set_name (PIKA_OBJECT (item), name); else pika_object_set_static_name (PIKA_OBJECT (item), PIKA_ITEM_GET_CLASS (item)->default_name); return item; } /** * pika_item_remove: * @item: the #PikaItem to remove. * * This function sets the 'removed' flag on @item to %TRUE, and emits * a 'removed' signal on the item. */ void pika_item_removed (PikaItem *item) { PikaContainer *children; g_return_if_fail (PIKA_IS_ITEM (item)); GET_PRIVATE (item)->removed = TRUE; children = pika_viewable_get_children (PIKA_VIEWABLE (item)); if (children) pika_container_foreach (children, (GFunc) pika_item_removed, NULL); g_signal_emit (item, pika_item_signals[REMOVED], 0); } /** * pika_item_is_removed: * @item: the #PikaItem to check. * * Returns: %TRUE if the 'removed' flag is set for @item, %FALSE otherwise. */ gboolean pika_item_is_removed (PikaItem *item) { g_return_val_if_fail (PIKA_IS_ITEM (item), FALSE); return GET_PRIVATE (item)->removed; } /** * pika_item_unset_removed: * @item: a #PikaItem 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 pika_item_unset_removed (PikaItem *item) { PikaContainer *children; g_return_if_fail (PIKA_IS_ITEM (item)); g_return_if_fail (pika_item_is_removed (item)); GET_PRIVATE (item)->removed = FALSE; children = pika_viewable_get_children (PIKA_VIEWABLE (item)); if (children) pika_container_foreach (children, (GFunc) pika_item_unset_removed, NULL); if (PIKA_ITEM_GET_CLASS (item)->unset_removed) PIKA_ITEM_GET_CLASS (item)->unset_removed (item); } /** * pika_item_is_attached: * @item: The #PikaItem to check. * * Returns: %TRUE if the item is attached to an image, %FALSE otherwise. */ gboolean pika_item_is_attached (PikaItem *item) { PikaImage *image; PikaItem *parent; g_return_val_if_fail (PIKA_IS_ITEM (item), FALSE); image = pika_item_get_image (item); if (image != NULL && pika_image_is_hidden_item (image, item)) return TRUE; parent = pika_item_get_parent (item); if (parent) return pika_item_is_attached (parent); return PIKA_ITEM_GET_CLASS (item)->is_attached (item); } PikaItem * pika_item_get_parent (PikaItem *item) { g_return_val_if_fail (PIKA_IS_ITEM (item), NULL); return PIKA_ITEM (pika_viewable_get_parent (PIKA_VIEWABLE (item))); } PikaItemTree * pika_item_get_tree (PikaItem *item) { g_return_val_if_fail (PIKA_IS_ITEM (item), NULL); if (PIKA_ITEM_GET_CLASS (item)->get_tree) return PIKA_ITEM_GET_CLASS (item)->get_tree (item); return NULL; } PikaContainer * pika_item_get_container (PikaItem *item) { PikaItem *parent; PikaItemTree *tree; g_return_val_if_fail (PIKA_IS_ITEM (item), NULL); parent = pika_item_get_parent (item); if (parent) return pika_viewable_get_children (PIKA_VIEWABLE (parent)); tree = pika_item_get_tree (item); if (tree) return tree->container; return NULL; } GList * pika_item_get_container_iter (PikaItem *item) { PikaContainer *container; g_return_val_if_fail (PIKA_IS_ITEM (item), NULL); container = pika_item_get_container (item); if (container) return PIKA_LIST (container)->queue->head; return NULL; } gint pika_item_get_index (PikaItem *item) { PikaContainer *container; g_return_val_if_fail (PIKA_IS_ITEM (item), -1); container = pika_item_get_container (item); if (container) return pika_container_get_child_index (container, PIKA_OBJECT (item)); return -1; } GList * pika_item_get_path (PikaItem *item) { PikaContainer *container; GList *path = NULL; g_return_val_if_fail (PIKA_IS_ITEM (item), NULL); g_return_val_if_fail (pika_item_is_attached (item), NULL); container = pika_item_get_container (item); while (container) { guint32 index = pika_container_get_child_index (container, PIKA_OBJECT (item)); path = g_list_prepend (path, GUINT_TO_POINTER (index)); item = pika_item_get_parent (item); if (item) container = pika_item_get_container (item); else container = NULL; } return path; } gboolean pika_item_bounds (PikaItem *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 (PIKA_IS_ITEM (item), FALSE); retval = PIKA_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 pika_item_bounds_f (PikaItem *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 (PIKA_IS_ITEM (item), FALSE); retval = PIKA_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; } /** * pika_item_duplicate: * @item: The #PikaItem to duplicate. * @new_type: The type to make the new item. * * Returns: the newly created item. */ PikaItem * pika_item_duplicate (PikaItem *item, GType new_type) { PikaItemPrivate *private; g_return_val_if_fail (PIKA_IS_ITEM (item), NULL); private = GET_PRIVATE (item); g_return_val_if_fail (PIKA_IS_IMAGE (private->image), NULL); g_return_val_if_fail (g_type_is_a (new_type, PIKA_TYPE_ITEM), NULL); return PIKA_ITEM_GET_CLASS (item)->duplicate (item, new_type); } /** * pika_item_convert: * @item: The #PikaItem to convert. * @dest_image: The #PikaImage 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. */ PikaItem * pika_item_convert (PikaItem *item, PikaImage *dest_image, GType new_type) { PikaItem *new_item; GType old_type; g_return_val_if_fail (PIKA_IS_ITEM (item), NULL); g_return_val_if_fail (PIKA_IS_IMAGE (GET_PRIVATE (item)->image), NULL); g_return_val_if_fail (PIKA_IS_IMAGE (dest_image), NULL); g_return_val_if_fail (g_type_is_a (new_type, PIKA_TYPE_ITEM), NULL); old_type = G_TYPE_FROM_INSTANCE (item); new_item = pika_item_duplicate (item, new_type); if (new_item) PIKA_ITEM_GET_CLASS (new_item)->convert (new_item, dest_image, old_type); return new_item; } /** * pika_item_rename: * @item: The #PikaItem 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 PikaObject::name-changed signal is emitted for the item. * * Returns: %TRUE if the @item could be renamed, %FALSE otherwise. */ gboolean pika_item_rename (PikaItem *item, const gchar *new_name, GError **error) { PikaItemClass *item_class; g_return_val_if_fail (PIKA_IS_ITEM (item), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); item_class = PIKA_ITEM_GET_CLASS (item); if (! new_name || ! *new_name) new_name = item_class->default_name; if (strcmp (new_name, pika_object_get_name (item))) return item_class->rename (item, new_name, item_class->rename_desc, error); return TRUE; } /** * pika_item_get_width: * @item: The #PikaItem to check. * * Returns: The width of the item. */ gint pika_item_get_width (PikaItem *item) { g_return_val_if_fail (PIKA_IS_ITEM (item), -1); return GET_PRIVATE (item)->width; } /** * pika_item_get_height: * @item: The #PikaItem to check. * * Returns: The height of the item. */ gint pika_item_get_height (PikaItem *item) { g_return_val_if_fail (PIKA_IS_ITEM (item), -1); return GET_PRIVATE (item)->height; } void pika_item_set_size (PikaItem *item, gint width, gint height) { PikaItemPrivate *private; g_return_if_fail (PIKA_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_by_pspec (G_OBJECT (item), pika_item_props[PROP_WIDTH]); } if (private->height != height) { private->height = height; g_object_notify_by_pspec (G_OBJECT (item), pika_item_props[PROP_HEIGHT]); } g_object_thaw_notify (G_OBJECT (item)); pika_viewable_size_changed (PIKA_VIEWABLE (item)); } } /** * pika_item_get_offset: * @item: The #PikaItem to check. * @offset_x: (out) (optional): Return location for the item's X offset. * @offset_y: (out) (optional): Return location for the item's Y offset. * * Reveals the X and Y offsets of the item. */ void pika_item_get_offset (PikaItem *item, gint *offset_x, gint *offset_y) { PikaItemPrivate *private; g_return_if_fail (PIKA_IS_ITEM (item)); private = GET_PRIVATE (item); if (offset_x) *offset_x = private->offset_x; if (offset_y) *offset_y = private->offset_y; } void pika_item_set_offset (PikaItem *item, gint offset_x, gint offset_y) { PikaItemPrivate *private; GList *list; g_return_if_fail (PIKA_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_by_pspec (G_OBJECT (item), pika_item_props[PROP_OFFSET_X]); } if (private->offset_y != offset_y) { private->offset_y = offset_y; g_object_notify_by_pspec (G_OBJECT (item), pika_item_props[PROP_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 pika_item_get_offset_x (PikaItem *item) { g_return_val_if_fail (PIKA_IS_ITEM (item), 0); return GET_PRIVATE (item)->offset_x; } gint pika_item_get_offset_y (PikaItem *item) { g_return_val_if_fail (PIKA_IS_ITEM (item), 0); return GET_PRIVATE (item)->offset_y; } void pika_item_start_move (PikaItem *item, gboolean push_undo) { g_return_if_fail (PIKA_IS_ITEM (item)); if (PIKA_ITEM_GET_CLASS (item)->start_move) PIKA_ITEM_GET_CLASS (item)->start_move (item, push_undo); } void pika_item_end_move (PikaItem *item, gboolean push_undo) { g_return_if_fail (PIKA_IS_ITEM (item)); if (PIKA_ITEM_GET_CLASS (item)->end_move) PIKA_ITEM_GET_CLASS (item)->end_move (item, push_undo); } void pika_item_start_transform (PikaItem *item, gboolean push_undo) { g_return_if_fail (PIKA_IS_ITEM (item)); if (PIKA_ITEM_GET_CLASS (item)->start_transform) PIKA_ITEM_GET_CLASS (item)->start_transform (item, push_undo); } void pika_item_end_transform (PikaItem *item, gboolean push_undo) { g_return_if_fail (PIKA_IS_ITEM (item)); if (PIKA_ITEM_GET_CLASS (item)->end_transform) PIKA_ITEM_GET_CLASS (item)->end_transform (item, push_undo); } /** * pika_item_translate: * @item: The #PikaItem 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 pika_item_translate (PikaItem *item, gdouble offset_x, gdouble offset_y, gboolean push_undo) { PikaItemClass *item_class; PikaImage *image; g_return_if_fail (PIKA_IS_ITEM (item)); item_class = PIKA_ITEM_GET_CLASS (item); image = pika_item_get_image (item); if (! pika_item_is_attached (item)) push_undo = FALSE; if (push_undo) pika_image_undo_group_start (image, PIKA_UNDO_GROUP_ITEM_DISPLACE, item_class->translate_desc); pika_item_start_transform (item, push_undo); item_class->translate (item, offset_x, offset_y, push_undo); pika_item_end_transform (item, push_undo); if (push_undo) pika_image_undo_group_end (image); } /** * pika_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 pika_item_check_scaling (PikaItem *item, gint new_width, gint new_height) { PikaItemPrivate *private; PikaImage *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 (PIKA_IS_ITEM (item), FALSE); private = GET_PRIVATE (item); image = pika_item_get_image (item); img_scale_w = ((gdouble) new_width / (gdouble) pika_image_get_width (image)); img_scale_h = ((gdouble) new_height / (gdouble) pika_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 + pika_item_get_width (item))) - new_item_offset_x; new_item_height = SIGNED_ROUND (img_scale_h * (private->offset_y + pika_item_get_height (item))) - new_item_offset_y; return (new_item_width > 0 && new_item_height > 0); } void pika_item_scale (PikaItem *item, gint new_width, gint new_height, gint new_offset_x, gint new_offset_y, PikaInterpolationType interpolation, PikaProgress *progress) { PikaItemClass *item_class; PikaImage *image; gboolean push_undo; g_return_if_fail (PIKA_IS_ITEM (item)); g_return_if_fail (progress == NULL || PIKA_IS_PROGRESS (progress)); if (new_width < 1 || new_height < 1) return; item_class = PIKA_ITEM_GET_CLASS (item); image = pika_item_get_image (item); push_undo = pika_item_is_attached (item); if (push_undo) pika_image_undo_group_start (image, PIKA_UNDO_GROUP_ITEM_SCALE, item_class->scale_desc); pika_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)); pika_item_end_transform (item, push_undo); if (push_undo) pika_image_undo_group_end (image); } /** * pika_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 pika_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 pika_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 pika_item_scale_by_factors (PikaItem *item, gdouble w_factor, gdouble h_factor, PikaInterpolationType interpolation, PikaProgress *progress) { return pika_item_scale_by_factors_with_origin (item, w_factor, h_factor, 0, 0, 0, 0, interpolation, progress); } /** * pika_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 pika_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 pika_item_scale_by_factors_with_origin (PikaItem *item, gdouble w_factor, gdouble h_factor, gint origin_x, gint origin_y, gint new_origin_x, gint new_origin_y, PikaInterpolationType interpolation, PikaProgress *progress) { PikaItemPrivate *private; PikaContainer *children; gint new_width, new_height; gint new_offset_x, new_offset_y; g_return_val_if_fail (PIKA_IS_ITEM (item), FALSE); g_return_val_if_fail (progress == NULL || PIKA_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 = pika_viewable_get_children (PIKA_VIEWABLE (item)); /* avoid discarding empty layer groups */ if (children && pika_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 + pika_item_get_width (item))) - new_offset_x; new_height = SIGNED_ROUND (h_factor * (private->offset_y - origin_y + pika_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) { pika_item_scale (item, new_width, new_height, new_offset_x, new_offset_y, interpolation, progress); return TRUE; } return FALSE; } /** * pika_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 [-PikaItem::offset_x, -PikaItem::->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 * pika_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 pika_item_scale_by_origin (PikaItem *item, gint new_width, gint new_height, PikaInterpolationType interpolation, PikaProgress *progress, gboolean local_origin) { PikaItemPrivate *private; gint new_offset_x, new_offset_y; g_return_if_fail (PIKA_IS_ITEM (item)); g_return_if_fail (progress == NULL || PIKA_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 + ((pika_item_get_width (item) - new_width) / 2.0)); new_offset_y = (private->offset_y + ((pika_item_get_height (item) - new_height) / 2.0)); } else { new_offset_x = (gint) (((gdouble) new_width * (gdouble) private->offset_x / (gdouble) pika_item_get_width (item))); new_offset_y = (gint) (((gdouble) new_height * (gdouble) private->offset_y / (gdouble) pika_item_get_height (item))); } pika_item_scale (item, new_width, new_height, new_offset_x, new_offset_y, interpolation, progress); } void pika_item_resize (PikaItem *item, PikaContext *context, PikaFillType fill_type, gint new_width, gint new_height, gint offset_x, gint offset_y) { PikaItemClass *item_class; PikaImage *image; gboolean push_undo; g_return_if_fail (PIKA_IS_ITEM (item)); g_return_if_fail (PIKA_IS_CONTEXT (context)); if (new_width < 1 || new_height < 1) return; item_class = PIKA_ITEM_GET_CLASS (item); image = pika_item_get_image (item); push_undo = pika_item_is_attached (item); if (push_undo) pika_image_undo_group_start (image, PIKA_UNDO_GROUP_ITEM_RESIZE, item_class->resize_desc); /* note that we call pika_item_start_move(), and not * pika_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 * pika_item_{start,end}_transform() to suspend mask resizing in group * layers, which should not happen when reisizing a group, hence the call to * pika_item_start_move(). * * see the comment in pika_group_layer_resize() for more information. */ pika_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)); pika_item_end_move (item, push_undo); if (push_undo) pika_image_undo_group_end (image); } void pika_item_flip (PikaItem *item, PikaContext *context, PikaOrientationType flip_type, gdouble axis, gboolean clip_result) { PikaItemClass *item_class; PikaImage *image; gboolean push_undo; g_return_if_fail (PIKA_IS_ITEM (item)); g_return_if_fail (pika_item_is_attached (item)); g_return_if_fail (PIKA_IS_CONTEXT (context)); item_class = PIKA_ITEM_GET_CLASS (item); image = pika_item_get_image (item); push_undo = pika_item_is_attached (item); if (push_undo) pika_image_undo_group_start (image, PIKA_UNDO_GROUP_TRANSFORM, item_class->flip_desc); pika_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)); pika_item_end_transform (item, push_undo); if (push_undo) pika_image_undo_group_end (image); } void pika_item_rotate (PikaItem *item, PikaContext *context, PikaRotationType rotate_type, gdouble center_x, gdouble center_y, gboolean clip_result) { PikaItemClass *item_class; PikaImage *image; gboolean push_undo; g_return_if_fail (PIKA_IS_ITEM (item)); g_return_if_fail (pika_item_is_attached (item)); g_return_if_fail (PIKA_IS_CONTEXT (context)); item_class = PIKA_ITEM_GET_CLASS (item); image = pika_item_get_image (item); push_undo = pika_item_is_attached (item); if (push_undo) pika_image_undo_group_start (image, PIKA_UNDO_GROUP_TRANSFORM, item_class->rotate_desc); pika_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)); pika_item_end_transform (item, push_undo); if (push_undo) pika_image_undo_group_end (image); } void pika_item_transform (PikaItem *item, PikaContext *context, const PikaMatrix3 *matrix, PikaTransformDirection direction, PikaInterpolationType interpolation, PikaTransformResize clip_result, PikaProgress *progress) { PikaItemClass *item_class; PikaImage *image; gboolean push_undo; g_return_if_fail (PIKA_IS_ITEM (item)); g_return_if_fail (pika_item_is_attached (item)); g_return_if_fail (PIKA_IS_CONTEXT (context)); g_return_if_fail (matrix != NULL); g_return_if_fail (progress == NULL || PIKA_IS_PROGRESS (progress)); item_class = PIKA_ITEM_GET_CLASS (item); image = pika_item_get_image (item); push_undo = pika_item_is_attached (item); if (push_undo) pika_image_undo_group_start (image, PIKA_UNDO_GROUP_TRANSFORM, item_class->transform_desc); pika_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)); pika_item_end_transform (item, push_undo); if (push_undo) pika_image_undo_group_end (image); } PikaTransformResize pika_item_get_clip (PikaItem *item, PikaTransformResize clip_result) { g_return_val_if_fail (PIKA_IS_ITEM (item), PIKA_TRANSFORM_RESIZE_ADJUST); return PIKA_ITEM_GET_CLASS (item)->get_clip (item, clip_result); } gboolean pika_item_fill (PikaItem *item, GList *drawables, PikaFillOptions *fill_options, gboolean push_undo, PikaProgress *progress, GError **error) { PikaItemClass *item_class; GList *iter; gboolean retval = FALSE; g_return_val_if_fail (PIKA_IS_ITEM (item), FALSE); g_return_val_if_fail (pika_item_is_attached (item), FALSE); g_return_val_if_fail (PIKA_IS_FILL_OPTIONS (fill_options), FALSE); g_return_val_if_fail (progress == NULL || PIKA_IS_PROGRESS (progress), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); item_class = PIKA_ITEM_GET_CLASS (item); for (iter = drawables; iter; iter = iter->next) { g_return_val_if_fail (PIKA_IS_DRAWABLE (iter->data), FALSE); g_return_val_if_fail (pika_item_is_attached (PIKA_ITEM (iter->data)), FALSE); } if (item_class->fill) { PikaImage *image = pika_item_get_image (item); if (push_undo) pika_image_undo_group_start (image, PIKA_UNDO_GROUP_PAINT, item_class->fill_desc); for (iter = drawables; iter; iter = iter->next) { retval = item_class->fill (item, iter->data, fill_options, push_undo, progress, error); if (! retval) break; } if (push_undo) pika_image_undo_group_end (image); } return retval; } gboolean pika_item_stroke (PikaItem *item, GList *drawables, PikaContext *context, PikaStrokeOptions *stroke_options, PikaPaintOptions *paint_options, gboolean push_undo, PikaProgress *progress, GError **error) { PikaItemClass *item_class; GList *iter; gboolean retval = FALSE; g_return_val_if_fail (PIKA_IS_ITEM (item), FALSE); g_return_val_if_fail (pika_item_is_attached (item), FALSE); g_return_val_if_fail (PIKA_IS_CONTEXT (context), FALSE); g_return_val_if_fail (PIKA_IS_STROKE_OPTIONS (stroke_options), FALSE); g_return_val_if_fail (paint_options == NULL || PIKA_IS_PAINT_OPTIONS (paint_options), FALSE); g_return_val_if_fail (progress == NULL || PIKA_IS_PROGRESS (progress), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); item_class = PIKA_ITEM_GET_CLASS (item); for (iter = drawables; iter; iter = iter->next) { g_return_val_if_fail (PIKA_IS_DRAWABLE (iter->data), FALSE); g_return_val_if_fail (pika_item_is_attached (PIKA_ITEM (iter->data)), FALSE); } if (item_class->stroke) { PikaImage *image = pika_item_get_image (item); pika_stroke_options_prepare (stroke_options, context, paint_options); if (push_undo) pika_image_undo_group_start (image, PIKA_UNDO_GROUP_PAINT, item_class->stroke_desc); for (iter = drawables; iter; iter = iter->next) { retval = item_class->stroke (item, iter->data, stroke_options, push_undo, progress, error); if (! retval) break; } if (push_undo) pika_image_undo_group_end (image); pika_stroke_options_finish (stroke_options); } return retval; } void pika_item_to_selection (PikaItem *item, PikaChannelOps op, gboolean antialias, gboolean feather, gdouble feather_radius_x, gdouble feather_radius_y) { PikaItemClass *item_class; g_return_if_fail (PIKA_IS_ITEM (item)); g_return_if_fail (pika_item_is_attached (item)); item_class = PIKA_ITEM_GET_CLASS (item); if (item_class->to_selection) item_class->to_selection (item, op, antialias, feather, feather_radius_x, feather_radius_y); } void pika_item_add_offset_node (PikaItem *item, GeglNode *node) { PikaItemPrivate *private; g_return_if_fail (PIKA_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 pika_item_remove_offset_node (PikaItem *item, GeglNode *node) { PikaItemPrivate *private; g_return_if_fail (PIKA_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 pika_item_get_id (PikaItem *item) { g_return_val_if_fail (PIKA_IS_ITEM (item), -1); return GET_PRIVATE (item)->ID; } PikaItem * pika_item_get_by_id (Pika *pika, gint item_id) { g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL); if (pika->item_table == NULL) return NULL; return (PikaItem *) pika_id_table_lookup (pika->item_table, item_id); } PikaTattoo pika_item_get_tattoo (PikaItem *item) { g_return_val_if_fail (PIKA_IS_ITEM (item), 0); return GET_PRIVATE (item)->tattoo; } void pika_item_set_tattoo (PikaItem *item, PikaTattoo tattoo) { g_return_if_fail (PIKA_IS_ITEM (item)); GET_PRIVATE (item)->tattoo = tattoo; } PikaImage * pika_item_get_image (PikaItem *item) { g_return_val_if_fail (PIKA_IS_ITEM (item), NULL); return GET_PRIVATE (item)->image; } void pika_item_set_image (PikaItem *item, PikaImage *image) { PikaItemPrivate *private; g_return_if_fail (PIKA_IS_ITEM (item)); g_return_if_fail (! pika_item_is_attached (item)); g_return_if_fail (! pika_item_is_removed (item)); g_return_if_fail (PIKA_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 = pika_id_table_insert (image->pika->item_table, item); g_object_notify_by_pspec (G_OBJECT (item), pika_item_props[PROP_ID]); } if (private->tattoo == 0 || private->image != image) { private->tattoo = pika_image_get_new_tattoo (image); } private->image = image; g_object_notify_by_pspec (G_OBJECT (item), pika_item_props[PROP_IMAGE]); g_object_thaw_notify (G_OBJECT (item)); } /** * pika_item_replace_item: * @item: a newly allocated #PikaItem * @replace: the #PikaItem 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 #PikaItem * 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 pika_item_replace_item (PikaItem *item, PikaItem *replace) { PikaItemPrivate *private; gint offset_x; gint offset_y; g_return_if_fail (PIKA_IS_ITEM (item)); g_return_if_fail (! pika_item_is_attached (item)); g_return_if_fail (! pika_item_is_removed (item)); g_return_if_fail (PIKA_IS_ITEM (replace)); private = GET_PRIVATE (item); pika_object_set_name (PIKA_OBJECT (item), pika_object_get_name (replace)); if (private->ID) pika_id_table_remove (pika_item_get_image (item)->pika->item_table, pika_item_get_id (item)); private->ID = pika_item_get_id (replace); pika_id_table_replace (pika_item_get_image (item)->pika->item_table, pika_item_get_id (item), item); /* Set image before tattoo so that the explicitly set tattoo overrides * the one implicitly set when setting the image */ pika_item_set_image (item, pika_item_get_image (replace)); GET_PRIVATE (replace)->image = NULL; pika_item_set_tattoo (item, pika_item_get_tattoo (replace)); pika_item_set_tattoo (replace, 0); g_object_unref (private->parasites); private->parasites = GET_PRIVATE (replace)->parasites; GET_PRIVATE (replace)->parasites = NULL; pika_item_get_offset (replace, &offset_x, &offset_y); pika_item_set_offset (item, offset_x, offset_y); pika_item_set_size (item, pika_item_get_width (replace), pika_item_get_height (replace)); pika_item_set_visible (item, pika_item_get_visible (replace), FALSE); pika_item_set_color_tag (item, pika_item_get_color_tag (replace), FALSE); pika_item_set_lock_content (item, pika_item_get_lock_content (replace), FALSE); pika_item_set_lock_position (item, pika_item_get_lock_position (replace), FALSE); pika_item_set_lock_visibility (item, pika_item_get_lock_visibility (replace), FALSE); } /** * pika_item_set_parasites: * @item: a #PikaItem * @parasites: a #PikaParasiteList * * Set an @item's #PikaParasiteList. 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 pika_item_set_parasites (PikaItem *item, PikaParasiteList *parasites) { PikaItemPrivate *private; g_return_if_fail (PIKA_IS_ITEM (item)); g_return_if_fail (PIKA_IS_PARASITE_LIST (parasites)); private = GET_PRIVATE (item); g_set_object (&private->parasites, parasites); } /** * pika_item_get_parasites: * @item: a #PikaItem * * Get an @item's #PikaParasiteList. 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. * * Returns: The @item's #PikaParasiteList. **/ PikaParasiteList * pika_item_get_parasites (PikaItem *item) { g_return_val_if_fail (PIKA_IS_ITEM (item), NULL); return GET_PRIVATE (item)->parasites; } gboolean pika_item_parasite_validate (PikaItem *item, const PikaParasite *parasite, GError **error) { g_return_val_if_fail (PIKA_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 pika_item_parasite_attach (PikaItem *item, const PikaParasite *parasite, gboolean push_undo) { PikaItemPrivate *private; PikaParasite copy; g_return_if_fail (PIKA_IS_ITEM (item)); g_return_if_fail (parasite != NULL); private = GET_PRIVATE (item); /* make a temporary copy of the PikaParasite struct because * pika_parasite_shift_parent() changes it */ copy = *parasite; if (! pika_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 (pika_parasite_is_undoable (©)) { /* do a group in case we have attach_parent set */ pika_image_undo_group_start (private->image, PIKA_UNDO_GROUP_PARASITE_ATTACH, C_("undo-type", "Attach Parasite")); pika_image_undo_push_item_parasite (private->image, NULL, item, ©); } else if (pika_parasite_is_persistent (©) && ! pika_parasite_compare (©, pika_item_parasite_find (item, pika_parasite_get_name (©)))) { pika_image_undo_push_cantundo (private->image, C_("undo-type", "Attach Parasite to Item")); } } pika_parasite_list_add (private->parasites, ©); if (pika_parasite_has_flag (©, PIKA_PARASITE_ATTACH_PARENT)) { pika_parasite_shift_parent (©); pika_image_parasite_attach (private->image, ©, TRUE); } else if (pika_parasite_has_flag (©, PIKA_PARASITE_ATTACH_GRANDPARENT)) { pika_parasite_shift_parent (©); pika_parasite_shift_parent (©); pika_parasite_attach (private->image->pika, ©); } if (pika_item_is_attached (item) && pika_parasite_is_undoable (©)) { pika_image_undo_group_end (private->image); } } void pika_item_parasite_detach (PikaItem *item, const gchar *name, gboolean push_undo) { PikaItemPrivate *private; const PikaParasite *parasite; g_return_if_fail (PIKA_IS_ITEM (item)); g_return_if_fail (name != NULL); private = GET_PRIVATE (item); parasite = pika_parasite_list_find (private->parasites, name); if (! parasite) return; if (! pika_item_is_attached (item)) push_undo = FALSE; if (push_undo) { if (pika_parasite_is_undoable (parasite)) { pika_image_undo_push_item_parasite_remove (private->image, C_("undo-type", "Remove Parasite from Item"), item, pika_parasite_get_name (parasite)); } else if (pika_parasite_is_persistent (parasite)) { pika_image_undo_push_cantundo (private->image, C_("undo-type", "Remove Parasite from Item")); } } pika_parasite_list_remove (private->parasites, name); } const PikaParasite * pika_item_parasite_find (PikaItem *item, const gchar *name) { g_return_val_if_fail (PIKA_IS_ITEM (item), NULL); return pika_parasite_list_find (GET_PRIVATE (item)->parasites, name); } static void pika_item_parasite_list_foreach_func (gchar *name, PikaParasite *parasite, gchar ***cur) { *(*cur)++ = (gchar *) g_strdup (name); } gchar ** pika_item_parasite_list (PikaItem *item) { PikaItemPrivate *private; gint count; gchar **list; gchar **cur; g_return_val_if_fail (PIKA_IS_ITEM (item), NULL); private = GET_PRIVATE (item); count = pika_parasite_list_length (private->parasites); cur = list = g_new0 (gchar *, count + 1); pika_parasite_list_foreach (private->parasites, (GHFunc) pika_item_parasite_list_foreach_func, &cur); return list; } gboolean pika_item_set_visible (PikaItem *item, gboolean visible, gboolean push_undo) { g_return_val_if_fail (PIKA_IS_ITEM (item), FALSE); visible = visible ? TRUE : FALSE; if (pika_item_get_visible (item) != visible) { if (pika_item_is_visibility_locked (item, NULL)) { return FALSE; } if (push_undo && pika_item_is_attached (item)) { PikaImage *image = pika_item_get_image (item); if (image) pika_image_undo_push_item_visibility (image, NULL, item); } GET_PRIVATE (item)->visible = visible; if (GET_PRIVATE (item)->bind_visible_to_active) pika_filter_set_active (PIKA_FILTER (item), visible); g_signal_emit (item, pika_item_signals[VISIBILITY_CHANGED], 0); g_object_notify_by_pspec (G_OBJECT (item), pika_item_props[PROP_VISIBLE]); } return TRUE; } gboolean pika_item_get_visible (PikaItem *item) { g_return_val_if_fail (PIKA_IS_ITEM (item), FALSE); return GET_PRIVATE (item)->visible; } gboolean pika_item_is_visible (PikaItem *item) { g_return_val_if_fail (PIKA_IS_ITEM (item), FALSE); if (pika_item_get_visible (item)) { PikaItem *parent; parent = PIKA_ITEM (pika_viewable_get_parent (PIKA_VIEWABLE (item))); if (parent) return pika_item_is_visible (parent); return TRUE; } return FALSE; } void pika_item_bind_visible_to_active (PikaItem *item, gboolean bind) { g_return_if_fail (PIKA_IS_ITEM (item)); GET_PRIVATE (item)->bind_visible_to_active = bind; if (bind) pika_filter_set_active (PIKA_FILTER (item), pika_item_get_visible (item)); } void pika_item_set_color_tag (PikaItem *item, PikaColorTag color_tag, gboolean push_undo) { g_return_if_fail (PIKA_IS_ITEM (item)); if (pika_item_get_color_tag (item) != color_tag) { if (push_undo && pika_item_is_attached (item)) { PikaImage *image = pika_item_get_image (item); if (image) pika_image_undo_push_item_color_tag (image, NULL, item); } GET_PRIVATE (item)->color_tag = color_tag; g_signal_emit (item, pika_item_signals[COLOR_TAG_CHANGED], 0); g_object_notify_by_pspec (G_OBJECT (item), pika_item_props[PROP_COLOR_TAG]); } } PikaColorTag pika_item_get_color_tag (PikaItem *item) { g_return_val_if_fail (PIKA_IS_ITEM (item), PIKA_COLOR_TAG_NONE); return GET_PRIVATE (item)->color_tag; } PikaColorTag pika_item_get_merged_color_tag (PikaItem *item) { g_return_val_if_fail (PIKA_IS_ITEM (item), PIKA_COLOR_TAG_NONE); if (pika_item_get_color_tag (item) == PIKA_COLOR_TAG_NONE) { PikaItem *parent; parent = PIKA_ITEM (pika_viewable_get_parent (PIKA_VIEWABLE (item))); if (parent) return pika_item_get_merged_color_tag (parent); } return pika_item_get_color_tag (item); } void pika_item_set_lock_content (PikaItem *item, gboolean lock_content, gboolean push_undo) { g_return_if_fail (PIKA_IS_ITEM (item)); g_return_if_fail (pika_item_can_lock_content (item)); lock_content = lock_content ? TRUE : FALSE; if (pika_item_get_lock_content (item) != lock_content) { if (push_undo && pika_item_is_attached (item)) { /* Right now I don't think this should be pushed. */ #if 0 PikaImage *image = pika_item_get_image (item); pika_image_undo_push_item_lock_content (image, NULL, item); #endif } GET_PRIVATE (item)->lock_content = lock_content; g_signal_emit (item, pika_item_signals[LOCK_CONTENT_CHANGED], 0); g_object_notify_by_pspec (G_OBJECT (item), pika_item_props[PROP_LOCK_CONTENT]); } } gboolean pika_item_get_lock_content (PikaItem *item) { g_return_val_if_fail (PIKA_IS_ITEM (item), FALSE); return GET_PRIVATE (item)->lock_content; } gboolean pika_item_can_lock_content (PikaItem *item) { g_return_val_if_fail (PIKA_IS_ITEM (item), FALSE); return TRUE; } gboolean pika_item_is_content_locked (PikaItem *item, PikaItem **locked_item) { g_return_val_if_fail (PIKA_IS_ITEM (item), FALSE); return PIKA_ITEM_GET_CLASS (item)->is_content_locked (item, locked_item); } void pika_item_set_lock_position (PikaItem *item, gboolean lock_position, gboolean push_undo) { g_return_if_fail (PIKA_IS_ITEM (item)); g_return_if_fail (pika_item_can_lock_position (item)); lock_position = lock_position ? TRUE : FALSE; if (pika_item_get_lock_position (item) != lock_position) { if (push_undo && pika_item_is_attached (item)) { PikaImage *image = pika_item_get_image (item); pika_image_undo_push_item_lock_position (image, NULL, item); } GET_PRIVATE (item)->lock_position = lock_position; g_signal_emit (item, pika_item_signals[LOCK_POSITION_CHANGED], 0); g_object_notify_by_pspec (G_OBJECT (item), pika_item_props[PROP_LOCK_POSITION]); } } gboolean pika_item_get_lock_position (PikaItem *item) { g_return_val_if_fail (PIKA_IS_ITEM (item), FALSE); return GET_PRIVATE (item)->lock_position; } gboolean pika_item_can_lock_position (PikaItem *item) { g_return_val_if_fail (PIKA_IS_ITEM (item), FALSE); return TRUE; } gboolean pika_item_is_position_locked (PikaItem *item, PikaItem **locked_item) { g_return_val_if_fail (PIKA_IS_ITEM (item), FALSE); return PIKA_ITEM_GET_CLASS (item)->is_position_locked (item, locked_item, TRUE); } void pika_item_set_lock_visibility (PikaItem *item, gboolean lock_visibility, gboolean push_undo) { g_return_if_fail (PIKA_IS_ITEM (item)); g_return_if_fail (pika_item_can_lock_visibility (item)); lock_visibility = lock_visibility ? TRUE : FALSE; if (pika_item_get_lock_visibility (item) != lock_visibility) { if (push_undo && pika_item_is_attached (item)) { PikaImage *image = pika_item_get_image (item); pika_image_undo_push_item_lock_visibility (image, NULL, item); } GET_PRIVATE (item)->lock_visibility = lock_visibility; g_signal_emit (item, pika_item_signals[LOCK_VISIBILITY_CHANGED], 0); g_object_notify (G_OBJECT (item), "lock-visibility"); } } gboolean pika_item_get_lock_visibility (PikaItem *item) { g_return_val_if_fail (PIKA_IS_ITEM (item), FALSE); return GET_PRIVATE (item)->lock_visibility; } gboolean pika_item_can_lock_visibility (PikaItem *item) { g_return_val_if_fail (PIKA_IS_ITEM (item), FALSE); return TRUE; } gboolean pika_item_is_visibility_locked (PikaItem *item, PikaItem **locked_item) { g_return_val_if_fail (PIKA_IS_ITEM (item), FALSE); return PIKA_ITEM_GET_CLASS (item)->is_visibility_locked (item, locked_item); } gboolean pika_item_mask_bounds (PikaItem *item, gint *x1, gint *y1, gint *x2, gint *y2) { PikaImage *image; PikaChannel *selection; gint x, y, width, height; gboolean retval; g_return_val_if_fail (PIKA_IS_ITEM (item), FALSE); g_return_val_if_fail (pika_item_is_attached (item), FALSE); image = pika_item_get_image (item); selection = pika_image_get_mask (image); /* check for is_empty() before intersecting so we ignore the * selection if it is suspended (like when stroking) */ if (PIKA_ITEM (selection) != item && ! pika_channel_is_empty (selection) && pika_item_bounds (PIKA_ITEM (selection), &x, &y, &width, &height)) { gint off_x, off_y; gint x2, y2; pika_item_get_offset (item, &off_x, &off_y); x2 = x + width; y2 = y + height; x = CLAMP (x - off_x, 0, pika_item_get_width (item)); y = CLAMP (y - off_y, 0, pika_item_get_height (item)); x2 = CLAMP (x2 - off_x, 0, pika_item_get_width (item)); y2 = CLAMP (y2 - off_y, 0, pika_item_get_height (item)); width = x2 - x; height = y2 - y; retval = TRUE; } else { x = 0; y = 0; width = pika_item_get_width (item); height = pika_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; } /** * pika_item_mask_intersect: * @item: a #PikaItem * @x: (out) (optional): return location for x * @y: (out) (optional): return location for y * @width: (out) (optional): return location for the width * @height: (out) (optional): 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 the selection within the * item. */ gboolean pika_item_mask_intersect (PikaItem *item, gint *x, gint *y, gint *width, gint *height) { PikaImage *image; PikaChannel *selection; gint tmp_x, tmp_y; gint tmp_width, tmp_height; gboolean retval; g_return_val_if_fail (PIKA_IS_ITEM (item), FALSE); g_return_val_if_fail (pika_item_is_attached (item), FALSE); image = pika_item_get_image (item); selection = pika_image_get_mask (image); /* check for is_empty() before intersecting so we ignore the * selection if it is suspended (like when stroking) */ if (PIKA_ITEM (selection) != item && ! pika_channel_is_empty (selection) && pika_item_bounds (PIKA_ITEM (selection), &tmp_x, &tmp_y, &tmp_width, &tmp_height)) { gint off_x, off_y; pika_item_get_offset (item, &off_x, &off_y); retval = pika_rectangle_intersect (tmp_x - off_x, tmp_y - off_y, tmp_width, tmp_height, 0, 0, pika_item_get_width (item), pika_item_get_height (item), &tmp_x, &tmp_y, &tmp_width, &tmp_height); } else { tmp_x = 0; tmp_y = 0; tmp_width = pika_item_get_width (item); tmp_height = pika_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 pika_item_is_in_set (PikaItem *item, PikaItemSet set) { PikaItemPrivate *private; g_return_val_if_fail (PIKA_IS_ITEM (item), FALSE); private = GET_PRIVATE (item); switch (set) { case PIKA_ITEM_SET_NONE: return FALSE; case PIKA_ITEM_SET_ALL: return TRUE; case PIKA_ITEM_SET_IMAGE_SIZED: return (pika_item_get_width (item) == pika_image_get_width (private->image) && pika_item_get_height (item) == pika_image_get_height (private->image)); case PIKA_ITEM_SET_VISIBLE: return pika_item_get_visible (item); } return FALSE; }