summaryrefslogtreecommitdiffstats
path: root/src/object/sp-item-group.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:29:01 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:29:01 +0000
commit35a96bde514a8897f6f0fcc41c5833bf63df2e2a (patch)
tree657d15a03cc46bd099fc2c6546a7a4ad43815d9f /src/object/sp-item-group.cpp
parentInitial commit. (diff)
downloadinkscape-35a96bde514a8897f6f0fcc41c5833bf63df2e2a.tar.xz
inkscape-35a96bde514a8897f6f0fcc41c5833bf63df2e2a.zip
Adding upstream version 1.0.2.upstream/1.0.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/object/sp-item-group.cpp990
1 files changed, 990 insertions, 0 deletions
diff --git a/src/object/sp-item-group.cpp b/src/object/sp-item-group.cpp
new file mode 100644
index 0000000..e64503c
--- /dev/null
+++ b/src/object/sp-item-group.cpp
@@ -0,0 +1,990 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * SVG <g> implementation
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 1999-2006 authors
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glibmm/i18n.h>
+#include <cstring>
+#include <string>
+
+#include "attributes.h"
+#include "document.h"
+#include "document-undo.h"
+#include "selection-chemistry.h"
+#include "verbs.h"
+
+#include "display/drawing-group.h"
+#include "display/curve.h"
+#include "live_effects/effect.h"
+#include "live_effects/lpeobject.h"
+#include "live_effects/lpeobject-reference.h"
+#include "svg/svg.h"
+#include "svg/css-ostringstream.h"
+#include "xml/repr.h"
+#include "xml/sp-css-attr.h"
+
+#include "box3d.h"
+#include "persp3d.h"
+#include "sp-defs.h"
+#include "sp-item-transform.h"
+#include "sp-root.h"
+#include "sp-rect.h"
+#include "sp-offset.h"
+#include "sp-clippath.h"
+#include "sp-mask.h"
+#include "sp-path.h"
+#include "sp-use.h"
+#include "sp-title.h"
+#include "sp-desc.h"
+#include "sp-switch.h"
+#include "sp-textpath.h"
+#include "sp-flowtext.h"
+#include "style.h"
+
+using Inkscape::DocumentUndo;
+
+static void sp_group_perform_patheffect(SPGroup *group, SPGroup *top_group, Inkscape::LivePathEffect::Effect *lpe, bool write);
+
+SPGroup::SPGroup() : SPLPEItem(),
+ _expanded(false),
+ _insert_bottom(false),
+ _layer_mode(SPGroup::GROUP)
+{
+}
+
+SPGroup::~SPGroup() = default;
+
+void SPGroup::build(SPDocument *document, Inkscape::XML::Node *repr) {
+ this->readAttr( "inkscape:groupmode" );
+
+ SPLPEItem::build(document, repr);
+}
+
+void SPGroup::release() {
+ if (this->_layer_mode == SPGroup::LAYER) {
+ this->document->removeResource("layer", this);
+ }
+
+ SPLPEItem::release();
+}
+
+void SPGroup::child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref) {
+ SPLPEItem::child_added(child, ref);
+
+ SPObject *last_child = this->lastChild();
+ if (last_child && last_child->getRepr() == child) {
+ // optimization for the common special case where the child is being added at the end
+ SPItem *item = dynamic_cast<SPItem *>(last_child);
+ if ( item ) {
+ /* TODO: this should be moved into SPItem somehow */
+ SPItemView *v;
+
+ for (v = this->display; v != nullptr; v = v->next) {
+ Inkscape::DrawingItem *ac = item->invoke_show (v->arenaitem->drawing(), v->key, v->flags);
+
+ if (ac) {
+ v->arenaitem->appendChild(ac);
+ }
+ }
+ }
+ } else { // general case
+ SPItem *item = dynamic_cast<SPItem *>(get_child_by_repr(child));
+ if ( item ) {
+ /* TODO: this should be moved into SPItem somehow */
+ SPItemView *v;
+ unsigned position = item->pos_in_parent();
+
+ for (v = this->display; v != nullptr; v = v->next) {
+ Inkscape::DrawingItem *ac = item->invoke_show (v->arenaitem->drawing(), v->key, v->flags);
+
+ if (ac) {
+ v->arenaitem->prependChild(ac);
+ ac->setZOrder(position);
+ }
+ }
+ }
+ }
+ this->requestModified(SP_OBJECT_MODIFIED_FLAG);
+}
+
+/* fixme: hide (Lauris) */
+
+void SPGroup::remove_child(Inkscape::XML::Node *child) {
+ SPLPEItem::remove_child(child);
+
+ this->requestModified(SP_OBJECT_MODIFIED_FLAG);
+}
+
+void SPGroup::order_changed (Inkscape::XML::Node *child, Inkscape::XML::Node *old_ref, Inkscape::XML::Node *new_ref)
+{
+ SPLPEItem::order_changed(child, old_ref, new_ref);
+
+ SPItem *item = dynamic_cast<SPItem *>(get_child_by_repr(child));
+ if ( item ) {
+ /* TODO: this should be moved into SPItem somehow */
+ SPItemView *v;
+ unsigned position = item->pos_in_parent();
+ for ( v = item->display ; v != nullptr ; v = v->next ) {
+ v->arenaitem->setZOrder(position);
+ }
+ }
+
+ this->requestModified(SP_OBJECT_MODIFIED_FLAG);
+}
+
+void SPGroup::update(SPCtx *ctx, unsigned int flags) {
+ // std::cout << "SPGroup::update(): " << (getId()?getId():"null") << std::endl;
+ SPItemCtx *ictx, cctx;
+
+ ictx = (SPItemCtx *) ctx;
+ cctx = *ictx;
+
+ unsigned childflags = flags;
+
+ if (flags & SP_OBJECT_MODIFIED_FLAG) {
+ childflags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
+ }
+ childflags &= SP_OBJECT_MODIFIED_CASCADE;
+ std::vector<SPObject*> l=this->childList(true, SPObject::ActionUpdate);
+ for(auto child : l){
+ if (childflags || (child->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) {
+ SPItem *item = dynamic_cast<SPItem *>(child);
+ if (item) {
+ cctx.i2doc = item->transform * ictx->i2doc;
+ cctx.i2vp = item->transform * ictx->i2vp;
+ child->updateDisplay((SPCtx *)&cctx, childflags);
+ } else {
+ child->updateDisplay(ctx, childflags);
+ }
+ }
+
+ sp_object_unref(child);
+ }
+
+ // For a group, we need to update ourselves *after* updating children.
+ // this is because the group might contain shapes such as rect or ellipse,
+ // which recompute their equivalent path (a.k.a curve) in the update callback,
+ // and this is in turn used when computing bbox.
+ SPLPEItem::update(ctx, flags);
+
+ if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) {
+ for (SPItemView *v = this->display; v != nullptr; v = v->next) {
+ Inkscape::DrawingGroup *group = dynamic_cast<Inkscape::DrawingGroup *>(v->arenaitem);
+ if( this->parent ) {
+ this->context_style = this->parent->context_style;
+ }
+ group->setStyle(this->style, this->context_style);
+ }
+ }
+}
+
+void SPGroup::modified(guint flags) {
+ //std::cout << "SPGroup::modified(): " << (getId()?getId():"null") << std::endl;
+ SPLPEItem::modified(flags);
+ if (flags & SP_OBJECT_MODIFIED_FLAG) {
+ flags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
+ }
+
+ flags &= SP_OBJECT_MODIFIED_CASCADE;
+
+ if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) {
+ for (SPItemView *v = this->display; v != nullptr; v = v->next) {
+ Inkscape::DrawingGroup *group = dynamic_cast<Inkscape::DrawingGroup *>(v->arenaitem);
+ group->setStyle(this->style);
+ }
+ }
+
+ std::vector<SPObject*> l=this->childList(true);
+ for(auto child : l){
+ if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) {
+ child->emitModified(flags);
+ }
+
+ sp_object_unref(child);
+ }
+}
+
+Inkscape::XML::Node* SPGroup::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) {
+ if (flags & SP_OBJECT_WRITE_BUILD) {
+ std::vector<Inkscape::XML::Node *> l;
+
+ if (!repr) {
+ if (dynamic_cast<SPSwitch *>(this)) {
+ repr = xml_doc->createElement("svg:switch");
+ } else {
+ repr = xml_doc->createElement("svg:g");
+ }
+ }
+
+ for (auto& child: children) {
+ if ( !dynamic_cast<SPTitle *>(&child) && !dynamic_cast<SPDesc *>(&child) ) {
+ Inkscape::XML::Node *crepr = child.updateRepr(xml_doc, nullptr, flags);
+
+ if (crepr) {
+ l.push_back(crepr);
+ }
+ }
+ }
+ for (auto i=l.rbegin();i!=l.rend();++i) {
+ repr->addChild(*i, nullptr);
+ Inkscape::GC::release(*i);
+ }
+ } else {
+ for (auto& child: children) {
+ if ( !dynamic_cast<SPTitle *>(&child) && !dynamic_cast<SPDesc *>(&child) ) {
+ child.updateRepr(flags);
+ }
+ }
+ }
+
+ if ( flags & SP_OBJECT_WRITE_EXT ) {
+ const char *value;
+ if ( _layer_mode == SPGroup::LAYER ) {
+ value = "layer";
+ } else if ( _layer_mode == SPGroup::MASK_HELPER ) {
+ value = "maskhelper";
+ } else if ( flags & SP_OBJECT_WRITE_ALL ) {
+ value = "group";
+ } else {
+ value = nullptr;
+ }
+
+ repr->setAttribute("inkscape:groupmode", value);
+ }
+
+ SPLPEItem::write(xml_doc, repr, flags);
+
+ return repr;
+}
+
+Geom::OptRect SPGroup::bbox(Geom::Affine const &transform, SPItem::BBoxType bboxtype) const
+{
+ Geom::OptRect bbox;
+
+ // TODO CPPIFY: replace this const_cast later
+ std::vector<SPObject*> l = const_cast<SPGroup*>(this)->childList(false, SPObject::ActionBBox);
+ for(auto o : l){
+ SPItem *item = dynamic_cast<SPItem *>(o);
+ if (item && !item->isHidden()) {
+ Geom::Affine const ct(item->transform * transform);
+ bbox |= item->bounds(bboxtype, ct);
+ }
+ }
+
+ return bbox;
+}
+
+void SPGroup::print(SPPrintContext *ctx) {
+ for(auto& child: children){
+ SPObject *o = &child;
+ SPItem *item = dynamic_cast<SPItem *>(o);
+ if (item) {
+ item->invoke_print(ctx);
+ }
+ }
+}
+
+const char *SPGroup::displayName() const {
+ return _("Group");
+}
+
+gchar *SPGroup::description() const {
+ gint len = this->getItemCount();
+ return g_strdup_printf(
+ ngettext(_("of <b>%d</b> object"), _("of <b>%d</b> objects"), len), len);
+}
+
+void SPGroup::set(SPAttributeEnum key, gchar const* value) {
+ switch (key) {
+ case SP_ATTR_INKSCAPE_GROUPMODE:
+ if ( value && !strcmp(value, "layer") ) {
+ this->setLayerMode(SPGroup::LAYER);
+ } else if ( value && !strcmp(value, "maskhelper") ) {
+ this->setLayerMode(SPGroup::MASK_HELPER);
+ } else {
+ this->setLayerMode(SPGroup::GROUP);
+ }
+ break;
+
+ default:
+ SPLPEItem::set(key, value);
+ break;
+ }
+}
+
+Inkscape::DrawingItem *SPGroup::show (Inkscape::Drawing &drawing, unsigned int key, unsigned int flags) {
+ // std::cout << "SPGroup::show(): " << (getId()?getId():"null") << std::endl;
+ Inkscape::DrawingGroup *ai;
+
+ ai = new Inkscape::DrawingGroup(drawing);
+ ai->setPickChildren(this->effectiveLayerMode(key) == SPGroup::LAYER);
+ if( this->parent ) {
+ this->context_style = this->parent->context_style;
+ }
+ ai->setStyle(this->style, this->context_style);
+
+ this->_showChildren(drawing, ai, key, flags);
+ return ai;
+}
+
+void SPGroup::hide (unsigned int key) {
+ std::vector<SPObject*> l=this->childList(false, SPObject::ActionShow);
+ for(auto o : l){
+ SPItem *item = dynamic_cast<SPItem *>(o);
+ if (item) {
+ item->invoke_hide(key);
+ }
+ }
+
+// SPLPEItem::onHide(key);
+}
+
+
+void SPGroup::snappoints(std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs) const {
+ for (auto& o: children)
+ {
+ SPItem const *item = dynamic_cast<SPItem const *>(&o);
+ if (item) {
+ item->getSnappoints(p, snapprefs);
+ }
+ }
+}
+
+void sp_item_group_ungroup_handle_clones(SPItem *parent, Geom::Affine const g)
+{
+ for(std::list<SPObject*>::const_iterator refd=parent->hrefList.begin();refd!=parent->hrefList.end();++refd){
+ SPItem *citem = dynamic_cast<SPItem *>(*refd);
+ if (citem && !citem->cloned) {
+ SPUse *useitem = dynamic_cast<SPUse *>(citem);
+ if (useitem && useitem->get_original() == parent) {
+ Geom::Affine ctrans;
+ ctrans = g.inverse() * citem->transform;
+ gchar *affinestr = sp_svg_transform_write(ctrans);
+ citem->setAttribute("transform", affinestr);
+ g_free(affinestr);
+ }
+ }
+ }
+}
+
+void
+sp_recursive_scale_text_size(Inkscape::XML::Node *repr, double scale){
+ for (Inkscape::XML::Node *child = repr->firstChild() ; child; child = child->next() ){
+ if ( child) {
+ sp_recursive_scale_text_size(child, scale);
+ }
+ }
+ SPCSSAttr * css = sp_repr_css_attr(repr,"style");
+ Glib::ustring element = g_quark_to_string(repr->code());
+ if ((css && element == "svg:text") || element == "svg:tspan") {
+ gchar const *w = sp_repr_css_property(css, "font-size", nullptr);
+ if (w) {
+ gchar *units = nullptr;
+ double wd = g_ascii_strtod(w, &units);
+ wd *= scale;
+ if (w != units) {
+ Inkscape::CSSOStringStream os;
+ os << wd << units; // reattach units
+ sp_repr_css_set_property(css, "font-size", os.str().c_str());
+ Glib::ustring css_str;
+ sp_repr_css_write_string(css,css_str);
+ repr->setAttributeOrRemoveIfEmpty("style", css_str);
+ }
+ }
+ w = nullptr;
+ w = sp_repr_css_property(css, "letter-spacing", nullptr);
+ if (w) {
+ gchar *units = nullptr;
+ double wd = g_ascii_strtod(w, &units);
+ wd *= scale;
+ if (w != units) {
+ Inkscape::CSSOStringStream os;
+ os << wd << units; // reattach units
+ sp_repr_css_set_property(css, "letter-spacing", os.str().c_str());
+ Glib::ustring css_str;
+ sp_repr_css_write_string(css,css_str);
+ repr->setAttributeOrRemoveIfEmpty("style", css_str);
+ }
+ }
+ w = nullptr;
+ w = sp_repr_css_property(css, "word-spacing", nullptr);
+ if (w) {
+ gchar *units = nullptr;
+ double wd = g_ascii_strtod(w, &units);
+ wd *= scale;
+ if (w != units) {
+ Inkscape::CSSOStringStream os;
+ os << wd << units; // reattach units
+ sp_repr_css_set_property(css, "word-spacing", os.str().c_str());
+ Glib::ustring css_str;
+ sp_repr_css_write_string(css,css_str);
+ repr->setAttributeOrRemoveIfEmpty("style", css_str);
+ }
+ }
+ gchar const *dx = repr->attribute("dx");
+ if (dx) {
+ gchar ** dxarray = g_strsplit(dx, " ", 0);
+ Inkscape::SVGOStringStream dx_data;
+ while (*dxarray != nullptr) {
+ double pos;
+ sp_svg_number_read_d(*dxarray, &pos);
+ pos *= scale;
+ dx_data << pos << " ";
+ dxarray++;
+ }
+ repr->setAttribute("dx", dx_data.str());
+ }
+ gchar const *dy = repr->attribute("dy");
+ if (dy) {
+ gchar ** dyarray = g_strsplit(dy, " ", 0);
+ Inkscape::SVGOStringStream dy_data;
+ while (*dyarray != nullptr) {
+ double pos;
+ sp_svg_number_read_d(*dyarray, &pos);
+ pos *= scale;
+ dy_data << pos << " ";
+ dyarray++;
+ }
+ repr->setAttribute("dy", dy_data.str());
+ }
+ }
+}
+
+void
+sp_item_group_ungroup (SPGroup *group, std::vector<SPItem*> &children, bool do_done)
+{
+ g_return_if_fail (group != nullptr);
+
+ SPDocument *doc = group->document;
+ SPRoot *root = doc->getRoot();
+ SPObject *defs = root->defs;
+
+ Inkscape::XML::Node *grepr = group->getRepr();
+
+ g_return_if_fail (!strcmp (grepr->name(), "svg:g")
+ || !strcmp (grepr->name(), "svg:a")
+ || !strcmp (grepr->name(), "svg:switch")
+ || !strcmp (grepr->name(), "svg:svg"));
+
+ // this converts the gradient/pattern fill/stroke on the group, if any, to userSpaceOnUse
+ group->adjust_paint_recursive(Geom::identity(), Geom::identity());
+
+ SPItem *pitem = dynamic_cast<SPItem *>(group->parent);
+ g_assert(pitem);
+ Inkscape::XML::Node *prepr = pitem->getRepr();
+
+ {
+ SPBox3D *box = dynamic_cast<SPBox3D *>(group);
+ if (box) {
+ group = box3d_convert_to_group(box);
+ }
+ }
+
+ group->removeAllPathEffects(false);
+
+ /* Step 1 - generate lists of children objects */
+ std::vector<Inkscape::XML::Node *> items;
+ std::vector<Inkscape::XML::Node *> objects;
+ Geom::Affine const g(group->transform);
+
+ for (auto& child: group->children) {
+ if (SPItem *citem = dynamic_cast<SPItem *>(&child)) {
+ sp_item_group_ungroup_handle_clones(citem, g);
+ }
+ }
+
+ for (auto& child: group->children) {
+ SPItem *citem = dynamic_cast<SPItem *>(&child);
+ if (citem) {
+ /* Merging of style */
+ // this converts the gradient/pattern fill/stroke, if any, to userSpaceOnUse; we need to do
+ // it here _before_ the new transform is set, so as to use the pre-transform bbox
+ citem->adjust_paint_recursive(Geom::identity(), Geom::identity());
+
+ child.style->merge( group->style );
+ /*
+ * fixme: We currently make no allowance for the case where child is cloned
+ * and the group has any style settings.
+ *
+ * (This should never occur with documents created solely with the current
+ * version of inkscape without using the XML editor: we usually apply group
+ * style changes to children rather than to the group itself.)
+ *
+ * If the group has no style settings, then style->merge() should be a no-op. Otherwise
+ * (i.e. if we change the child's style to compensate for its parent going away)
+ * then those changes will typically be reflected in any clones of child,
+ * whereas we'd prefer for Ungroup not to affect the visual appearance.
+ *
+ * The only way of preserving styling appearance in general is for child to
+ * be put into a new group -- a somewhat surprising response to an Ungroup
+ * command. We could add a new groupmode:transparent that would mostly
+ * hide the existence of such groups from the user (i.e. editing behaves as
+ * if the transparent group's children weren't in a group), though that's
+ * extra complication & maintenance burden and this case is rare.
+ */
+
+ child.updateRepr();
+
+ Inkscape::XML::Node *nrepr = child.getRepr()->duplicate(prepr->document());
+
+ // Merging transform
+ Geom::Affine ctrans = citem->transform * g;
+ // We should not apply the group's transformation to both a linked offset AND to its source
+ if (dynamic_cast<SPOffset *>(citem)) { // Do we have an offset at hand (whether it's dynamic or linked)?
+ SPItem *source = sp_offset_get_source(dynamic_cast<SPOffset *>(citem));
+ // When dealing with a chain of linked offsets, the transformation of an offset will be
+ // tied to the transformation of the top-most source, not to any of the intermediate
+ // offsets. So let's find the top-most source
+ while (source != nullptr && dynamic_cast<SPOffset *>(source)) {
+ source = sp_offset_get_source(dynamic_cast<SPOffset *>(source));
+ }
+ if (source != nullptr && // If true then we must be dealing with a linked offset ...
+ group->isAncestorOf(source) ) { // ... of which the source is in the same group
+ ctrans = citem->transform; // then we should apply the transformation of the group to the offset
+ }
+ }
+
+ // FIXME: constructing a transform that would fully preserve the appearance of a
+ // textpath if it is ungrouped with its path seems to be impossible in general
+ // case. E.g. if the group was squeezed, to keep the ungrouped textpath squeezed
+ // as well, we'll need to relink it to some "virtual" path which is inversely
+ // stretched relative to the actual path, and then squeeze the textpath back so it
+ // would both fit the actual path _and_ be squeezed as before. It's a bummer.
+
+ // This is just a way to temporarily remember the transform in repr. When repr is
+ // reattached outside of the group, the transform will be written more properly
+ // (i.e. optimized into the object if the corresponding preference is set)
+ gchar *affinestr=sp_svg_transform_write(ctrans);
+ SPText * text = dynamic_cast<SPText *>(citem);
+ if (text) {
+ //this causes a change in text-on-path appearance when there is a non-conformal transform, see bug #1594565
+ SPTextPath * text_path = dynamic_cast<SPTextPath *>(text->firstChild());
+ if (!text_path) {
+ nrepr->setAttribute("transform", affinestr);
+ } else {
+ // The following breaks roundtripping group -> ungroup
+ // double scale = (ctrans.expansionX() + ctrans.expansionY()) / 2.0;
+ // sp_recursive_scale_text_size(nrepr, scale);
+ Geom::Affine ttrans = ctrans.inverse() * SP_ITEM(text)->transform * ctrans;
+ gchar *affinestr = sp_svg_transform_write(ttrans);
+ nrepr->setAttribute("transform", affinestr);
+ g_free(affinestr);
+ }
+ } else {
+ nrepr->setAttribute("transform", affinestr);
+ }
+ g_free(affinestr);
+
+ items.push_back(nrepr);
+
+ } else {
+ Inkscape::XML::Node *nrepr = child.getRepr()->duplicate(prepr->document());
+ objects.push_back(nrepr);
+ }
+ }
+
+ /* Step 2 - clear group */
+ // remember the position of the group
+ gint pos = group->getRepr()->position();
+
+ // the group is leaving forever, no heir, clones should take note; its children however are going to reemerge
+ group->deleteObject(true, false);
+
+ /* Step 3 - add nonitems */
+ if (!objects.empty()) {
+ Inkscape::XML::Node *last_def = defs->getRepr()->lastChild();
+ for (auto i=objects.rbegin();i!=objects.rend();++i) {
+ Inkscape::XML::Node *repr = *i;
+ if (!sp_repr_is_meta_element(repr)) {
+ defs->getRepr()->addChild(repr, last_def);
+ }
+ Inkscape::GC::release(repr);
+ }
+ }
+
+ /* Step 4 - add items */
+ for (auto i=items.rbegin();i!=items.rend();++i) {
+ Inkscape::XML::Node *repr = *i;
+ // add item
+ // restore position; since the items list was prepended (i.e. reverse), we now add
+ // all children at the same pos, which inverts the order once again
+ prepr->addChildAtPos(repr, pos);
+
+ // fill in the children list if non-null
+ SPItem *item = static_cast<SPItem *>(doc->getObjectByRepr(repr));
+
+ if (item) {
+ item->doWriteTransform(item->transform, nullptr, false);
+ children.insert(children.begin(),item);
+ item->requestModified(SP_OBJECT_MODIFIED_FLAG);
+ } else {
+ g_assert_not_reached();
+ }
+
+ Inkscape::GC::release(repr);
+ }
+ if (do_done) {
+ DocumentUndo::done(doc, SP_VERB_NONE, _("Ungroup"));
+ }
+}
+
+/*
+ * some API for list aspect of SPGroup
+ */
+
+std::vector<SPItem*> sp_item_group_item_list(SPGroup * group)
+{
+ std::vector<SPItem*> s;
+ g_return_val_if_fail(group != nullptr, s);
+
+ for (auto& o: group->children) {
+ if ( dynamic_cast<SPItem *>(&o) ) {
+ s.push_back((SPItem*)&o);
+ }
+ }
+ return s;
+}
+
+SPObject *sp_item_group_get_child_by_name(SPGroup *group, SPObject *ref, const gchar *name)
+{
+ SPObject *child = (ref) ? ref->getNext() : group->firstChild();
+ while ( child && strcmp(child->getRepr()->name(), name) ) {
+ child = child->getNext();
+ }
+ return child;
+}
+
+void SPGroup::setLayerMode(LayerMode mode) {
+ if ( _layer_mode != mode ) {
+ if ( mode == LAYER ) {
+ this->document->addResource("layer", this);
+ } else if ( _layer_mode == LAYER ) {
+ this->document->removeResource("layer", this);
+ }
+ _layer_mode = mode;
+ _updateLayerMode();
+ }
+}
+
+SPGroup::LayerMode SPGroup::layerDisplayMode(unsigned int dkey) const {
+ std::map<unsigned int, LayerMode>::const_iterator iter;
+ iter = _display_modes.find(dkey);
+ if ( iter != _display_modes.end() ) {
+ return (*iter).second;
+ } else {
+ return GROUP;
+ }
+}
+
+void SPGroup::setExpanded(bool isexpanded) {
+ if ( _expanded != isexpanded ){
+ _expanded = isexpanded;
+ }
+}
+
+void SPGroup::setInsertBottom(bool insertbottom) {
+ if ( _insert_bottom != insertbottom) {
+ _insert_bottom = insertbottom;
+ }
+}
+
+void SPGroup::setLayerDisplayMode(unsigned int dkey, SPGroup::LayerMode mode) {
+ if ( layerDisplayMode(dkey) != mode ) {
+ _display_modes[dkey] = mode;
+ _updateLayerMode(dkey);
+ }
+}
+
+void SPGroup::_updateLayerMode(unsigned int display_key) {
+ SPItemView *view;
+ for ( view = this->display ; view ; view = view->next ) {
+ if ( !display_key || view->key == display_key ) {
+ Inkscape::DrawingGroup *g = dynamic_cast<Inkscape::DrawingGroup *>(view->arenaitem);
+ if (g) {
+ g->setPickChildren(effectiveLayerMode(view->key) == SPGroup::LAYER);
+ }
+ }
+ }
+}
+
+void SPGroup::translateChildItems(Geom::Translate const &tr)
+{
+ if ( hasChildren() ) {
+ for (auto& o: children) {
+ SPItem *item = dynamic_cast<SPItem *>(&o);
+ if ( item ) {
+ item->move_rel(tr);
+ }
+ }
+ }
+}
+
+// Recursively (or not) scale child items around a point
+void SPGroup::scaleChildItemsRec(Geom::Scale const &sc, Geom::Point const &p, bool noRecurse)
+{
+ if ( hasChildren() ) {
+ for (auto& o: children) {
+ if ( SPDefs *defs = dynamic_cast<SPDefs *>(&o) ) { // select symbols from defs, ignore clips, masks, patterns
+ for (auto& defschild: defs->children) {
+ SPGroup *defsgroup = dynamic_cast<SPGroup *>(&defschild);
+ if (defsgroup)
+ defsgroup->scaleChildItemsRec(sc, p, false);
+ }
+ } else if ( SPItem *item = dynamic_cast<SPItem *>(&o) ) {
+ SPGroup *group = dynamic_cast<SPGroup *>(item);
+ if (group && !dynamic_cast<SPBox3D *>(item)) {
+ /* Using recursion breaks clipping because transforms are applied
+ in coordinates for draws but nothing in defs is changed
+ instead change the transform on the entire group, and the transform
+ is applied after any references to clipping paths. However NOT using
+ recursion apparently breaks as of r13544 other parts of Inkscape
+ involved with showing/modifying units. So offer both for use
+ in different contexts.
+ */
+ if(noRecurse) {
+ // used for EMF import
+ Geom::Translate const s(p);
+ Geom::Affine final = s.inverse() * sc * s;
+ Geom::Affine tAff = item->i2dt_affine() * final;
+ item->set_i2d_affine(tAff);
+ tAff = item->transform;
+ // Eliminate common rounding error affecting EMF/WMF input.
+ // When the rounding error persists it converts the simple
+ // transform=scale() to transform=matrix().
+ if(std::abs(tAff[4]) < 1.0e-5 && std::abs(tAff[5]) < 1.0e-5){
+ tAff[4] = 0.0;
+ tAff[5] = 0.0;
+ }
+ item->doWriteTransform(tAff, nullptr, true);
+ } else {
+ // used for other import
+ SPItem *sub_item = nullptr;
+ if (item->getClipObject()) {
+ sub_item = dynamic_cast<SPItem *>(item->getClipObject()->firstChild());
+ }
+ if (sub_item != nullptr) {
+ sub_item->doWriteTransform(sub_item->transform*sc, nullptr, true);
+ }
+ sub_item = nullptr;
+ if (item->getMaskObject()) {
+ sub_item = dynamic_cast<SPItem *>(item->getMaskObject()->firstChild());
+ }
+ if (sub_item != nullptr) {
+ sub_item->doWriteTransform(sub_item->transform*sc, nullptr, true);
+ }
+ item->doWriteTransform(sc.inverse()*item->transform*sc, nullptr, true);
+ group->scaleChildItemsRec(sc, p, false);
+ }
+ } else {
+// Geom::OptRect bbox = item->desktopVisualBounds();
+// if (bbox) { // test not needed, this was causing a failure to scale <circle> and <rect> in the clipboard, see LP Bug 1365451
+ // Scale item
+ Geom::Translate const s(p);
+ Geom::Affine final = s.inverse() * sc * s;
+
+ gchar const *conn_type = nullptr;
+ SPText *text_item = dynamic_cast<SPText *>(item);
+ bool is_text_path = text_item && text_item->firstChild() && dynamic_cast<SPTextPath *>(text_item->firstChild());
+ if (is_text_path) {
+ text_item->optimizeTextpathText();
+ } else {
+ SPFlowtext *flowText = dynamic_cast<SPFlowtext *>(item);
+ if (flowText) {
+ flowText->optimizeScaledText();
+ } else {
+ SPBox3D *box = dynamic_cast<SPBox3D *>(item);
+ if (box) {
+ // Force recalculation from perspective
+ box3d_position_set(box);
+ } else if (item->getAttribute("inkscape:connector-type") != nullptr
+ && (item->getAttribute("inkscape:connection-start") == nullptr
+ || item->getAttribute("inkscape:connection-end") == nullptr)) {
+ // Remove and store connector type for transform if disconnected
+ conn_type = item->getAttribute("inkscape:connector-type");
+ item->removeAttribute("inkscape:connector-type");
+ }
+ }
+ }
+
+ Persp3D *persp = dynamic_cast<Persp3D *>(item);
+ if (persp) {
+ persp3d_apply_affine_transformation(persp, final);
+ } else if (is_text_path && !item->transform.isIdentity()) {
+ // Save and reset current transform
+ Geom::Affine tmp(item->transform);
+ item->transform = Geom::Affine();
+ // Apply scale
+ item->set_i2d_affine(item->i2dt_affine() * sc);
+ item->doWriteTransform(item->transform, nullptr, true);
+ // Scale translation and restore original transform
+ tmp[4] *= sc[0];
+ tmp[5] *= sc[1];
+ item->doWriteTransform(tmp, nullptr, true);
+ } else if (dynamic_cast<SPUse *>(item)) {
+ // calculate the matrix we need to apply to the clone
+ // to cancel its induced transform from its original
+ Geom::Affine move = final.inverse() * item->transform * final;
+ item->doWriteTransform(move, &move, true);
+ } else {
+ item->doWriteTransform(item->transform*sc, nullptr, true);
+ }
+
+ if (conn_type != nullptr) {
+ item->setAttribute("inkscape:connector-type", conn_type);
+ }
+
+ if (item->isCenterSet() && !(final.isTranslation() || final.isIdentity())) {
+ item->scaleCenter(sc); // All coordinates have been scaled, so also the center must be scaled
+ item->updateRepr();
+ }
+// }
+ }
+ }
+ }
+ }
+}
+
+gint SPGroup::getItemCount() const {
+ gint len = 0;
+ for (auto& child: children) {
+ if (dynamic_cast<SPItem const *>(&child)) {
+ len++;
+ }
+ }
+
+ return len;
+}
+
+void SPGroup::_showChildren (Inkscape::Drawing &drawing, Inkscape::DrawingItem *ai, unsigned int key, unsigned int flags) {
+ Inkscape::DrawingItem *ac = nullptr;
+ std::vector<SPObject*> l=this->childList(false, SPObject::ActionShow);
+ for(auto o : l){
+ SPItem * child = dynamic_cast<SPItem *>(o);
+ if (child) {
+ ac = child->invoke_show (drawing, key, flags);
+ if (ac) {
+ ai->appendChild(ac);
+ }
+ }
+ }
+}
+
+void SPGroup::update_patheffect(bool write) {
+#ifdef GROUP_VERBOSE
+ g_message("sp_group_update_patheffect: %p\n", lpeitem);
+#endif
+ std::vector<SPItem*> const item_list = sp_item_group_item_list(this);
+
+ for (auto sub_item : item_list) {
+ if (sub_item) {
+ SPLPEItem *lpe_item = dynamic_cast<SPLPEItem *>(sub_item);
+ if (lpe_item) {
+ lpe_item->update_patheffect(write);
+ }
+ }
+ }
+
+ this->resetClipPathAndMaskLPE();
+ if (hasPathEffect() && pathEffectsEnabled()) {
+ PathEffectList path_effect_list(*this->path_effect_list);
+ for (auto &lperef : path_effect_list) {
+ LivePathEffectObject *lpeobj = lperef->lpeobject;
+ if (lpeobj) {
+ Inkscape::LivePathEffect::Effect *lpe = lpeobj->get_lpe();
+ if (lpe) {
+ lpeobj->get_lpe()->doBeforeEffect_impl(this);
+ sp_group_perform_patheffect(this, this, lpe, write);
+ lpeobj->get_lpe()->doAfterEffect_impl(this);
+ }
+ }
+ }
+ }
+}
+
+static void
+sp_group_perform_patheffect(SPGroup *group, SPGroup *top_group, Inkscape::LivePathEffect::Effect *lpe, bool write)
+{
+ std::vector<SPItem*> const item_list = sp_item_group_item_list(group);
+ for (auto sub_item : item_list) {
+ SPGroup *sub_group = dynamic_cast<SPGroup *>(sub_item);
+ if (sub_group) {
+ sp_group_perform_patheffect(sub_group, top_group, lpe, write);
+ } else {
+ SPShape* sub_shape = dynamic_cast<SPShape *>(sub_item);
+ //SPPath* sub_path = dynamic_cast<SPPath *>(sub_item);
+ SPItem* clipmaskto = dynamic_cast<SPItem *>(sub_item);
+ if (clipmaskto) {
+ top_group->applyToClipPath(clipmaskto, lpe);
+ top_group->applyToMask(clipmaskto, lpe);
+ }
+ if (sub_shape) {
+ SPCurve * c = sub_shape->getCurve();
+ bool success = false;
+ // only run LPEs when the shape has a curve defined
+ if (c) {
+ lpe->pathvector_before_effect = c->get_pathvector();
+ c->transform(i2anc_affine(sub_shape, top_group));
+ sub_shape->setCurveInsync(c);
+ if (lpe->lpeversion.param_getSVGValue() != "0") { // we are on 1 or up
+ sub_shape->bbox_vis_cache_is_valid = false;
+ sub_shape->bbox_geom_cache_is_valid = false;
+ }
+ success = top_group->performOnePathEffect(c, sub_shape, lpe);
+ c->transform(i2anc_affine(sub_shape, top_group).inverse());
+ Inkscape::XML::Node *repr = sub_item->getRepr();
+ if (c && success) {
+ sub_shape->setCurveInsync(c);
+ lpe->pathvector_after_effect = c->get_pathvector();
+ if (write) {
+ gchar *str = sp_svg_write_path(lpe->pathvector_after_effect);
+ repr->setAttribute("d", str);
+#ifdef GROUP_VERBOSE
+ g_message("sp_group_perform_patheffect writes 'd' attribute");
+#endif
+ g_free(str);
+ }
+ c->unref();
+ } else {
+ // LPE was unsuccessful or doeffect stack return null. Read the old 'd'-attribute.
+ if (gchar const * value = repr->attribute("d")) {
+ Geom::PathVector pv = sp_svg_read_pathv(value);
+ SPCurve *oldcurve = new (std::nothrow) SPCurve(pv);
+ if (oldcurve) {
+ sub_shape->setCurve(oldcurve);
+ oldcurve->unref();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ SPItem* clipmaskto = dynamic_cast<SPItem *>(group);
+ if (clipmaskto) {
+ top_group->applyToClipPath(clipmaskto, lpe);
+ top_group->applyToMask(clipmaskto, lpe);
+ }
+}
+
+/*
+ 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 :