From 179fa413b047bede6e32109e2ce82437c5fb8d34 Mon Sep 17 00:00:00 2001 From: MenTaLguY Date: Mon, 16 Jan 2006 02:36:01 +0000 Subject: moving trunk for module inkscape (bzr r1) --- src/sp-use.cpp | 712 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 712 insertions(+) create mode 100644 src/sp-use.cpp (limited to 'src/sp-use.cpp') diff --git a/src/sp-use.cpp b/src/sp-use.cpp new file mode 100644 index 000000000..3a55331a0 --- /dev/null +++ b/src/sp-use.cpp @@ -0,0 +1,712 @@ +#define __SP_USE_C__ + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 1999-2005 authors + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include +#endif +#include +#include +#include "libnr/nr-matrix-translate-ops.h" +#include +#include "display/nr-arena-group.h" +#include "attributes.h" +#include "document.h" +#include "sp-object-repr.h" +#include "sp-flowregion.h" +#include "uri.h" +#include "xml/repr.h" +#include "prefs-utils.h" +#include "style.h" +#include "sp-symbol.h" +#include "sp-use.h" +#include "sp-use-reference.h" + +/* fixme: */ + +static void sp_use_class_init(SPUseClass *classname); +static void sp_use_init(SPUse *use); +static void sp_use_finalize(GObject *obj); + +static void sp_use_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static void sp_use_release(SPObject *object); +static void sp_use_set(SPObject *object, unsigned key, gchar const *value); +static Inkscape::XML::Node *sp_use_write(SPObject *object, Inkscape::XML::Node *repr, guint flags); +static void sp_use_update(SPObject *object, SPCtx *ctx, guint flags); +static void sp_use_modified(SPObject *object, guint flags); + +static void sp_use_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags); +static void sp_use_print(SPItem *item, SPPrintContext *ctx); +static gchar *sp_use_description(SPItem *item); +static NRArenaItem *sp_use_show(SPItem *item, NRArena *arena, unsigned key, unsigned flags); +static void sp_use_hide(SPItem *item, unsigned key); + +static void sp_use_href_changed(SPObject *old_ref, SPObject *ref, SPUse *use); + +static void sp_use_delete_self(SPObject *deleted, SPUse *self); + +static SPItemClass *parent_class; + +//void m_print(gchar *say, NR::Matrix m) +//{ g_print("%s %g %g %g %g %g %g\n", say, m[0], m[1], m[2], m[3], m[4], m[5]); } + +GType +sp_use_get_type(void) +{ + static GType use_type = 0; + if (!use_type) { + GTypeInfo use_info = { + sizeof(SPUseClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_use_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof(SPUse), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_use_init, + NULL, /* value_table */ + }; + use_type = g_type_register_static(SP_TYPE_ITEM, "SPUse", &use_info, (GTypeFlags)0); + } + return use_type; +} + +static void +sp_use_class_init(SPUseClass *classname) +{ + GObjectClass *gobject_class = (GObjectClass *) classname; + SPObjectClass *sp_object_class = (SPObjectClass *) classname; + SPItemClass *item_class = (SPItemClass *) classname; + + parent_class = (SPItemClass*) g_type_class_ref(SP_TYPE_ITEM); + + gobject_class->finalize = sp_use_finalize; + + sp_object_class->build = sp_use_build; + sp_object_class->release = sp_use_release; + sp_object_class->set = sp_use_set; + sp_object_class->write = sp_use_write; + sp_object_class->update = sp_use_update; + sp_object_class->modified = sp_use_modified; + + item_class->bbox = sp_use_bbox; + item_class->description = sp_use_description; + item_class->print = sp_use_print; + item_class->show = sp_use_show; + item_class->hide = sp_use_hide; +} + +static void +sp_use_init(SPUse *use) +{ + use->x.unset(); + use->y.unset(); + use->width.unset(SVGLength::PERCENT, 1.0, 1.0); + use->height.unset(SVGLength::PERCENT, 1.0, 1.0); + use->href = NULL; + + new (&use->_delete_connection) sigc::connection(); + new (&use->_changed_connection) sigc::connection(); + + new (&use->_transformed_connection) sigc::connection(); + + use->ref = new SPUseReference(SP_OBJECT(use)); + + use->_changed_connection = use->ref->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_use_href_changed), use)); +} + +static void +sp_use_finalize(GObject *obj) +{ + SPUse *use = (SPUse *) obj; + + delete use->ref; + + use->_delete_connection.~connection(); + use->_changed_connection.~connection(); + + use->_transformed_connection.~connection(); +} + +static void +sp_use_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + if (((SPObjectClass *) parent_class)->build) { + (* ((SPObjectClass *) parent_class)->build)(object, document, repr); + } + + sp_object_read_attr(object, "x"); + sp_object_read_attr(object, "y"); + sp_object_read_attr(object, "width"); + sp_object_read_attr(object, "height"); + sp_object_read_attr(object, "xlink:href"); + + // We don't need to create child here: + // reading xlink:href will attach ref, and that will cause the changed signal to be emitted, + // which will call sp_use_href_changed, and that will take care of the child +} + +static void +sp_use_release(SPObject *object) +{ + SPUse *use = SP_USE(object); + + use->child = NULL; + + use->_delete_connection.disconnect(); + use->_changed_connection.disconnect(); + use->_transformed_connection.disconnect(); + + g_free(use->href); + use->href = NULL; + + use->ref->detach(); + + if (((SPObjectClass *) parent_class)->release) { + ((SPObjectClass *) parent_class)->release(object); + } +} + +static void +sp_use_set(SPObject *object, unsigned key, gchar const *value) +{ + SPUse *use = SP_USE(object); + + switch (key) { + case SP_ATTR_X: + use->x.readOrUnset(value); + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_Y: + use->y.readOrUnset(value); + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_WIDTH: + use->width.readOrUnset(value, SVGLength::PERCENT, 1.0, 1.0); + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_HEIGHT: + use->height.readOrUnset(value, SVGLength::PERCENT, 1.0, 1.0); + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_XLINK_HREF: { + if ( value && use->href && ( strcmp(value, use->href) == 0 ) ) { + /* No change, do nothing. */ + } else { + g_free(use->href); + use->href = NULL; + if (value) { + // First, set the href field, because sp_use_href_changed will need it. + use->href = g_strdup(value); + + // Now do the attaching, which emits the changed signal. + try { + use->ref->attach(Inkscape::URI(value)); + } catch (Inkscape::BadURIException &e) { + g_warning("%s", e.what()); + use->ref->detach(); + } + } else { + use->ref->detach(); + } + } + break; + } + + default: + if (((SPObjectClass *) parent_class)->set) { + ((SPObjectClass *) parent_class)->set(object, key, value); + } + break; + } +} + +static Inkscape::XML::Node * +sp_use_write(SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + SPUse *use = SP_USE(object); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = sp_repr_new("svg:use"); + } + + if (((SPObjectClass *) (parent_class))->write) { + ((SPObjectClass *) (parent_class))->write(object, repr, flags); + } + + sp_repr_set_svg_double(repr, "x", use->x.computed); + sp_repr_set_svg_double(repr, "y", use->y.computed); + sp_repr_set_svg_double(repr, "width", use->width.computed); + sp_repr_set_svg_double(repr, "height", use->height.computed); + + if (use->ref->getURI()) { + gchar *uri_string = use->ref->getURI()->toString(); + repr->setAttribute("xlink:href", uri_string); + g_free(uri_string); + } + + return repr; +} + +static void +sp_use_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags) +{ + SPUse const *use = SP_USE(item); + + if (use->child && SP_IS_ITEM(use->child)) { + SPItem *child = SP_ITEM(use->child); + NR::Matrix const ct( child->transform + * NR::translate(use->x.computed, + use->y.computed) + * transform ); + sp_item_invoke_bbox_full(child, bbox, ct, flags, FALSE); + } +} + +static void +sp_use_print(SPItem *item, SPPrintContext *ctx) +{ + SPUse *use = SP_USE(item); + + if (use->child && SP_IS_ITEM(use->child)) { + sp_item_invoke_print(SP_ITEM(use->child), ctx); + } +} + +static gchar * +sp_use_description(SPItem *item) +{ + SPUse *use = SP_USE(item); + + char *ret; + if (use->child) { + static unsigned recursion_depth = 0; + if (recursion_depth >= 2) { + /* TRANSLATORS: Used for statusbar description for long chains: + * "Clone of: Clone of: ... in Layer 1". */ + return g_strdup(_("...")); + /* We could do better, e.g. chasing the href chain until we reach something other than + * a , and giving its description. */ + } + ++recursion_depth; + char *child_desc = sp_item_description(SP_ITEM(use->child)); + --recursion_depth; + + ret = g_strdup_printf(_("Clone of: %s"), child_desc); + g_free(child_desc); + return ret; + } else { + return g_strdup(_("Orphaned clone")); + } +} + +static NRArenaItem * +sp_use_show(SPItem *item, NRArena *arena, unsigned key, unsigned flags) +{ + SPUse *use = SP_USE(item); + + NRArenaItem *ai = NRArenaGroup::create(arena); + nr_arena_group_set_transparent(NR_ARENA_GROUP(ai), FALSE); + + if (use->child) { + NRArenaItem *ac = sp_item_invoke_show(SP_ITEM(use->child), arena, key, flags); + if (ac) { + nr_arena_item_add_child(ai, ac, NULL); + nr_arena_item_unref(ac); + } + NR::translate t(use->x.computed, + use->y.computed); + nr_arena_group_set_child_transform(NR_ARENA_GROUP(ai), NR::Matrix(t)); + } + + return ai; +} + +static void +sp_use_hide(SPItem *item, unsigned key) +{ + SPUse *use = SP_USE(item); + + if (use->child) { + sp_item_invoke_hide(SP_ITEM(use->child), key); + } + + if (((SPItemClass *) parent_class)->hide) { + ((SPItemClass *) parent_class)->hide(item, key); + } +} + +/** + * Returns the ultimate original of a SPUse (i.e. the first object in the chain of its originals + * which is not an SPUse). + * + * Note that the returned is the clone object, i.e. the child of an SPUse (of the argument one for + * the trivial case) and not the "true original". + */ +SPItem * +sp_use_root(SPUse *use) +{ + SPObject *orig = use->child; + while (SP_IS_USE(orig)) { + orig = SP_USE(orig)->child; + } + g_assert(SP_IS_ITEM(orig)); + return SP_ITEM(orig); +} + +/** + * Returns the effective transform that goes from the ultimate original to given SPUse, both ends + * included. + */ +NR::Matrix +sp_use_get_root_transform(SPUse *use) +{ + //track the ultimate source of a chain of uses + SPObject *orig = use->child; + GSList *chain = NULL; + chain = g_slist_prepend(chain, use); + while (SP_IS_USE(orig)) { + chain = g_slist_prepend(chain, orig); + orig = SP_USE(orig)->child; + } + chain = g_slist_prepend(chain, orig); + + + //calculate the accummulated transform, starting from the original + NR::Matrix t(NR::identity()); + for (GSList *i = chain; i != NULL; i = i->next) { + SPItem *i_tem = SP_ITEM(i->data); + + // "An additional transformation translate(x,y) is appended to the end (i.e., + // right-side) of the transform attribute on the generated 'g', where x and y + // represent the values of the x and y attributes on the 'use' element." - http://www.w3.org/TR/SVG11/struct.html#UseElement + if (SP_IS_USE(i_tem)) { + SPUse *i_use = SP_USE(i_tem); + if ((i_use->x._set && i_use->x.computed != 0) || (i_use->y._set && i_use->y.computed != 0)) { + t = t * NR::translate(i_use->x._set ? i_use->x.computed : 0, i_use->y._set ? i_use->y.computed : 0); + } + } + + t *= i_tem->transform; + } + + g_slist_free(chain); + return t; +} + +/** + * Returns the transform that leads to the use from its immediate original. + * Does not inlcude the original's transform if any. + */ +NR::Matrix +sp_use_get_parent_transform(SPUse *use) +{ + NR::Matrix t(NR::identity()); + if ((use->x._set && use->x.computed != 0) || (use->y._set && use->y.computed != 0)) { + t *= NR::translate(use->x._set ? use->x.computed : 0, + use->y._set ? use->y.computed : 0); + } + + t *= SP_ITEM(use)->transform; + return t; +} + +/** + * Sensing a movement of the original, this function attempts to compensate for it in such a way + * that the clone stays unmoved or moves in parallel (depending on user setting) regardless of the + * clone's transform. + */ +static void +sp_use_move_compensate(NR::Matrix const *mp, SPItem *original, SPUse *self) +{ + // the clone is orphaned; or this is not a real use, but a clone of another use; + // we skip it, otherwise duplicate compensation will occur + if (SP_OBJECT_IS_CLONED(self)) { + return; + } + + // never compensate uses which are used in flowtext + if (SP_OBJECT_PARENT(self) && SP_IS_FLOWREGION(SP_OBJECT_PARENT(self))) { + return; + } + + guint mode = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_PARALLEL); + // user wants no compensation + if (mode == SP_CLONE_COMPENSATION_NONE) + return; + + NR::Matrix m(*mp); + + // this is not a simple move, do not try to compensate + if (!(m.is_translation())) + return; + + // restore item->transform field from the repr, in case it was changed by seltrans + sp_object_read_attr (SP_OBJECT (self), "transform"); + + NR::Matrix t = sp_use_get_parent_transform(self); + NR::Matrix clone_move = t.inverse() * m * t; + + // calculate the compensation matrix and the advertized movement matrix + NR::Matrix advertized_move; + if (mode == SP_CLONE_COMPENSATION_PARALLEL) { + clone_move = clone_move.inverse() * m; + advertized_move = m; + } else if (mode == SP_CLONE_COMPENSATION_UNMOVED) { + clone_move = clone_move.inverse(); + advertized_move.set_identity(); + } else { + g_assert_not_reached(); + } + + // commit the compensation + SPItem *item = SP_ITEM(self); + item->transform *= clone_move; + sp_item_write_transform(item, SP_OBJECT_REPR(item), item->transform, &advertized_move); + SP_OBJECT(item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +static void +sp_use_href_changed(SPObject *old_ref, SPObject *ref, SPUse *use) +{ + SPItem *item = SP_ITEM(use); + + use->_delete_connection.disconnect(); + use->_transformed_connection.disconnect(); + + if (use->child) { + sp_object_detach(SP_OBJECT(use), use->child); + use->child = NULL; + } + + if (use->href) { + SPItem *refobj = use->ref->getObject(); + if (refobj) { + Inkscape::XML::Node *childrepr = SP_OBJECT_REPR(refobj); + GType type = sp_repr_type_lookup(childrepr); + g_return_if_fail(type > G_TYPE_NONE); + if (g_type_is_a(type, SP_TYPE_ITEM)) { + use->child = (SPObject*) g_object_new(type, 0); + sp_object_attach(SP_OBJECT(use), use->child, use->lastChild()); + sp_object_unref(use->child, SP_OBJECT(use)); + sp_object_invoke_build(use->child, SP_OBJECT(use)->document, childrepr, TRUE); + + for (SPItemView *v = item->display; v != NULL; v = v->next) { + NRArenaItem *ai; + ai = sp_item_invoke_show(SP_ITEM(use->child), NR_ARENA_ITEM_ARENA(v->arenaitem), v->key, v->flags); + if (ai) { + nr_arena_item_add_child(v->arenaitem, ai, NULL); + nr_arena_item_unref(ai); + } + } + + } + use->_delete_connection = SP_OBJECT(refobj)->connectDelete(sigc::bind(sigc::ptr_fun(&sp_use_delete_self), use)); + use->_transformed_connection = SP_ITEM(refobj)->connectTransformed(sigc::bind(sigc::ptr_fun(&sp_use_move_compensate), use)); + } + } +} + +static void +sp_use_delete_self(SPObject *deleted, SPUse *self) +{ + // always delete uses which are used in flowtext + if (SP_OBJECT_PARENT(self) && SP_IS_FLOWREGION(SP_OBJECT_PARENT(self))) { + SP_OBJECT(self)->deleteObject(); + return; + } + + guint const mode = prefs_get_int_attribute("options.cloneorphans", "value", + SP_CLONE_ORPHANS_UNLINK); + + if (mode == SP_CLONE_ORPHANS_UNLINK) { + sp_use_unlink(self); + } else if (mode == SP_CLONE_ORPHANS_DELETE) { + SP_OBJECT(self)->deleteObject(); + } +} + +static void +sp_use_update(SPObject *object, SPCtx *ctx, unsigned flags) +{ + SPItem *item = SP_ITEM(object); + SPUse *use = SP_USE(object); + SPItemCtx *ictx = (SPItemCtx *) ctx; + SPItemCtx cctx = *ictx; + + if (((SPObjectClass *) (parent_class))->update) + ((SPObjectClass *) (parent_class))->update(object, ctx, flags); + + if (flags & SP_OBJECT_MODIFIED_FLAG) flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + flags &= SP_OBJECT_MODIFIED_CASCADE; + + /* Set up child viewport */ + if (use->x.unit == SVGLength::PERCENT) { + use->x.computed = use->x.value * (ictx->vp.x1 - ictx->vp.x0); + } + if (use->y.unit == SVGLength::PERCENT) { + use->y.computed = use->y.value * (ictx->vp.y1 - ictx->vp.y0); + } + if (use->width.unit == SVGLength::PERCENT) { + use->width.computed = use->width.value * (ictx->vp.x1 - ictx->vp.x0); + } + if (use->height.unit == SVGLength::PERCENT) { + use->height.computed = use->height.value * (ictx->vp.y1 - ictx->vp.y0); + } + cctx.vp.x0 = 0.0; + cctx.vp.y0 = 0.0; + cctx.vp.x1 = use->width.computed; + cctx.vp.y1 = use->height.computed; + cctx.i2vp = NR::identity(); + flags&=~SP_OBJECT_USER_MODIFIED_FLAG_B; + + if (use->child) { + g_object_ref(G_OBJECT(use->child)); + if (flags || (use->child->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + if (SP_IS_ITEM(use->child)) { + SPItem const &chi = *SP_ITEM(use->child); + cctx.i2doc = chi.transform * ictx->i2doc; + cctx.i2vp = chi.transform * ictx->i2vp; + use->child->updateDisplay((SPCtx *)&cctx, flags); + } else { + use->child->updateDisplay(ctx, flags); + } + } + g_object_unref(G_OBJECT(use->child)); + } + + /* As last step set additional transform of arena group */ + for (SPItemView *v = item->display; v != NULL; v = v->next) { + NRMatrix t; + nr_matrix_set_translate(&t, use->x.computed, use->y.computed); + nr_arena_group_set_child_transform(NR_ARENA_GROUP(v->arenaitem), &t); + } +} + +static void +sp_use_modified(SPObject *object, guint flags) +{ + SPUse *use_obj = SP_USE(object); + + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + flags &= SP_OBJECT_MODIFIED_CASCADE; + + SPObject *child = use_obj->child; + if (child) { + g_object_ref(G_OBJECT(child)); + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->emitModified(flags); + } + g_object_unref(G_OBJECT(child)); + } +} + +SPItem * +sp_use_unlink(SPUse *use) +{ + if (!use) return NULL; + + Inkscape::XML::Node *repr = SP_OBJECT_REPR(use); + if (!repr) return NULL; + + Inkscape::XML::Node *parent = sp_repr_parent(repr); + SPDocument *document = SP_OBJECT(use)->document; + + // Track the ultimate source of a chain of uses. + SPItem *orig = sp_use_root(use); + + // Calculate the accumulated transform, starting from the original. + NR::Matrix t = sp_use_get_root_transform(use); + + Inkscape::XML::Node *copy = NULL; + if (SP_IS_SYMBOL(orig)) { // make a group, copy children + copy = sp_repr_new("svg:g"); + for (Inkscape::XML::Node *child = SP_OBJECT_REPR(orig)->firstChild() ; child != NULL; child = child->next()) { + Inkscape::XML::Node *newchild = child->duplicate(); + copy->appendChild(newchild); + } + } else { // just copy + copy = SP_OBJECT_REPR(orig)->duplicate(); + } + + // Add the duplicate repr just after the existing one. + parent->addChild(copy, repr); + + // Retrieve the SPItem of the resulting repr. + SPObject *unlinked = document->getObjectByRepr(copy); + + // Merge style from the use. + SPStyle *unli_sty = SP_OBJECT_STYLE(unlinked); + SPStyle const *use_sty = SP_OBJECT_STYLE(use); + sp_style_merge_from_dying_parent(unli_sty, use_sty); + sp_style_merge_from_parent(unli_sty, unlinked->parent->style); + + SP_OBJECT(unlinked)->updateRepr(); + + // Hold onto our SPObject and repr for now. + sp_object_ref(SP_OBJECT(use), NULL); + Inkscape::GC::anchor(repr); + + // Remove ourselves, not propagating delete events to avoid a + // chain-reaction with other elements that might reference us. + SP_OBJECT(use)->deleteObject(false); + + // Give the copy our old id and let go of our old repr. + copy->setAttribute("id", repr->attribute("id")); + Inkscape::GC::release(repr); + + // Remove tiled clone attrs. + copy->setAttribute("inkscape:tiled-clone-of", NULL); + copy->setAttribute("inkscape:tile-w", NULL); + copy->setAttribute("inkscape:tile-h", NULL); + copy->setAttribute("inkscape:tile-cx", NULL); + copy->setAttribute("inkscape:tile-cy", NULL); + + // Establish the succession and let go of our object. + SP_OBJECT(use)->setSuccessor(unlinked); + sp_object_unref(SP_OBJECT(use), NULL); + + SPItem *item = SP_ITEM(unlinked); + // Set the accummulated transform. + { + NR::Matrix nomove(NR::identity()); + NRMatrix ctrans = t.operator const NRMatrix&(); + // Advertise ourselves as not moving. + sp_item_write_transform(item, SP_OBJECT_REPR(item), &ctrans, &nomove); + } + return item; +} + +SPItem * +sp_use_get_original(SPUse *use) +{ + SPItem *ref = use->ref->getObject(); + return ref; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : -- cgit v1.2.3