2772 lines
82 KiB
C
2772 lines
82 KiB
C
|
/* 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 <https://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
#include "config.h"
|
||
|
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
|
||
|
#include <gdk-pixbuf/gdk-pixbuf.h>
|
||
|
#include <gegl.h>
|
||
|
|
||
|
#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;
|
||
|
}
|