void LPEInterpolate::resetDefaults(SPItem const* item) { Effect::resetDefaults(item); if (!SP_IS_PATH(item)) return; SPCurve const *crv = SP_PATH(item)->get_curve_reference(); Geom::PathVector const &pathv = crv->get_pathvector(); if ( (pathv.size() < 2) ) return; Geom::OptRect bounds_A = pathv[0].boundsExact(); Geom::OptRect bounds_B = pathv[1].boundsExact(); if (bounds_A && bounds_B) { Geom::PathVector traj_pathv; traj_pathv.push_back( Geom::Path() ); traj_pathv[0].start( bounds_A->midpoint() ); traj_pathv[0].appendNew<Geom::LineSegment>( bounds_B->midpoint() ); trajectory_path.set_new_value( traj_pathv, true ); } else { trajectory_path.param_set_and_write_default(); } }
static gchar * sp_path_description(SPItem * item) { int count = SP_PATH(item)->nodesInPath(); if (SP_IS_LPE_ITEM(item) && sp_lpe_item_has_path_effect(SP_LPE_ITEM(item))) { Glib::ustring s; PathEffectList effect_list = sp_lpe_item_get_effect_list(SP_LPE_ITEM(item)); for (PathEffectList::iterator it = effect_list.begin(); it != effect_list.end(); ++it) { LivePathEffectObject *lpeobj = (*it)->lpeobject; if (!lpeobj || !lpeobj->get_lpe()) break; if (s.empty()) s = lpeobj->get_lpe()->getName(); else s = s + ", " + lpeobj->get_lpe()->getName(); } return g_strdup_printf(ngettext("<b>Path</b> (%i node, path effect: %s)", "<b>Path</b> (%i nodes, path effect: %s)",count), count, s.c_str()); } else { return g_strdup_printf(ngettext("<b>Path</b> (%i node)", "<b>Path</b> (%i nodes)",count), count); } }
static void sp_path_convert_to_guides(SPItem *item) { SPPath *path = SP_PATH(item); if (!path->_curve) { return; } std::list<std::pair<Geom::Point, Geom::Point> > pts; Geom::Affine const i2dt(path->i2dt_affine()); Geom::PathVector const & pv = path->_curve->get_pathvector(); for(Geom::PathVector::const_iterator pit = pv.begin(); pit != pv.end(); ++pit) { for(Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_default(); ++cit) { // only add curves for straight line segments if( is_straight_curve(*cit) ) { pts.push_back(std::make_pair(cit->initialPoint() * i2dt, cit->finalPoint() * i2dt)); } } } sp_guide_pt_pairs_to_guides(item->document, pts); }
static void avoid_conn_move(Geom::Matrix const */*mp*/, SPItem *moved_item) { // Reroute connector SPPath *path = SP_PATH(moved_item); path->connEndPair.makePathInvalid(); sp_conn_adjust_invalid_path(path); }
static void avoid_conn_transformed(Geom::Matrix const */*mp*/, SPItem *moved_item) { SPPath *path = SP_PATH(moved_item); if (path->connEndPair.isAutoRoutingConn()) { path->connEndPair.tellLibavoidNewEndpoints(); } }
static void redrawConnectorCallback(void *ptr) { SPPath *path = SP_PATH(ptr); if (path->document == NULL) { // This can happen when the document is being destroyed. return; } sp_conn_redraw_path(path); }
void sp_selected_path_reverse(SPDesktop *desktop) { Inkscape::Selection *selection = sp_desktop_selection(desktop); GSList *items = (GSList *) selection->itemList(); if (!items) { sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>path(s)</b> to reverse.")); return; } // set "busy" cursor desktop->setWaitingCursor(); bool did = false; desktop->messageStack()->flash(Inkscape::IMMEDIATE_MESSAGE, _("Reversing paths...")); for (GSList *i = items; i != NULL; i = i->next) { if (!SP_IS_PATH(i->data)) continue; did = true; SPPath *path = SP_PATH(i->data); SPCurve *rcurve = sp_path_get_curve_reference(path)->create_reverse(); gchar *str = sp_svg_write_path(rcurve->get_pathvector()); if ( sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(path)) ) { SP_OBJECT_REPR(path)->setAttribute("inkscape:original-d", str); } else { SP_OBJECT_REPR(path)->setAttribute("d", str); } g_free(str); rcurve->unref(); // reverse nodetypes order (Bug #179866) gchar *nodetypes = g_strdup(SP_OBJECT_REPR(path)->attribute("sodipodi:nodetypes")); if ( nodetypes ) { SP_OBJECT_REPR(path)->setAttribute("sodipodi:nodetypes", g_strreverse(nodetypes)); g_free(nodetypes); } } desktop->clearWaitingCursor(); if (did) { sp_document_done(sp_desktop_document(desktop), SP_VERB_SELECTION_REVERSE, _("Reverse path")); } else { sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("<b>No paths</b> to reverse in the selection.")); } }
static void sp_path_release(SPObject *object) { SPPath *path = SP_PATH(object); path->connEndPair.release(); if (((SPObjectClass *) parent_class)->release) { ((SPObjectClass *) parent_class)->release(object); } }
static void emitPathInvalidationNotification(void *ptr) { // We emit a signal here rather than just calling the reroute function // since this allows all the movement action computation to happen, // then all connectors (that require it) will be rerouted. Otherwise, // one connector could get rerouted several times as a result of // dragging a couple of shapes. SPPath *path = SP_PATH(ptr); path->connEndPair._invalid_path_signal.emit(path); }
void SPLPEItem::apply_to_clippath(SPItem *item) { SPClipPath *clipPath = item->clip_ref->getObject(); if(clipPath) { SPObject * clip_data = clipPath->firstChild(); SPCurve * clip_curve = NULL; if (SP_IS_PATH(clip_data)) { clip_curve = SP_PATH(clip_data)->get_original_curve(); } else if(SP_IS_SHAPE(clip_data)) { clip_curve = SP_SHAPE(clip_data)->getCurve(); } else if(SP_IS_GROUP(clip_data)) { apply_to_clip_or_mask_group(SP_ITEM(clip_data), item); return; } if(clip_curve) { bool success = false; if(SP_IS_GROUP(this)){ clip_curve->transform(i2anc_affine(SP_GROUP(item), SP_GROUP(this))); success = this->performPathEffect(clip_curve); clip_curve->transform(i2anc_affine(SP_GROUP(item), SP_GROUP(this)).inverse()); } else { success = this->performPathEffect(clip_curve); } Inkscape::XML::Node *reprClip = clip_data->getRepr(); if (success) { gchar *str = sp_svg_write_path(clip_curve->get_pathvector()); reprClip->setAttribute("d", str); g_free(str); } else { // LPE was unsuccesfull. Read the old 'd'-attribute. if (gchar const * value = reprClip->attribute("d")) { Geom::PathVector pv = sp_svg_read_pathv(value); SPCurve *oldcurve = new SPCurve(pv); if (oldcurve) { SP_SHAPE(clip_data)->setCurve(oldcurve, TRUE); oldcurve->unref(); } } } clip_curve->unref(); } } if(SP_IS_GROUP(item)){ GSList const *item_list = sp_item_group_item_list(SP_GROUP(item)); for ( GSList const *iter = item_list; iter; iter = iter->next ) { SPObject *subitem = static_cast<SPObject *>(iter->data); apply_to_clippath(SP_ITEM(subitem)); } } }
static void sp_path_update(SPObject *object, SPCtx *ctx, guint flags) { if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; // since we change the description, it's not a "just translation" anymore } if (((SPObjectClass *) parent_class)->update) { ((SPObjectClass *) parent_class)->update(object, ctx, flags); } SPPath *path = SP_PATH(object); path->connEndPair.update(); }
/** * Writes the given transform into the repr for the given item. */ static Geom::Affine sp_path_set_transform(SPItem *item, Geom::Affine const &xform) { if (!SP_IS_PATH(item)) { return Geom::identity(); } SPPath *path = SP_PATH(item); if (!path->_curve) { // 0 nodes, nothing to transform return Geom::identity(); } // Transform the original-d path if this is a valid LPE item, other else the (ordinary) path if (path->_curve_before_lpe && sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(item))) { if (sp_lpe_item_has_path_effect_of_type(SP_LPE_ITEM(item), Inkscape::LivePathEffect::CLONE_ORIGINAL)) { // if path has the CLONE_ORIGINAL LPE applied, don't write the transform to the pathdata, but write it 'unoptimized' return xform; } else { path->_curve_before_lpe->transform(xform); } } else { path->_curve->transform(xform); } // Adjust stroke item->adjust_stroke(xform.descrim()); // Adjust pattern fill item->adjust_pattern(xform); // Adjust gradient fill item->adjust_gradient(xform); // Adjust LPE item->adjust_livepatheffect(xform); item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); // nothing remains - we've written all of the transform, so return identity return Geom::identity(); }
void Inkscape::ObjectSnapper::freeSnap(IntermSnapResults &isr, SnapCandidatePoint const &p, Geom::OptRect const &bbox_to_snap, std::vector<SPItem const *> const *it, std::vector<SnapCandidatePoint> *unselected_nodes) const { if (_snap_enabled == false || _snapmanager->snapprefs.isSourceSnappable(p.getSourceType()) == false || ThisSnapperMightSnap() == false) { return; } /* Get a list of all the SPItems that we will try to snap to */ if (p.getSourceNum() <= 0) { Geom::Rect const local_bbox_to_snap = bbox_to_snap ? *bbox_to_snap : Geom::Rect(p.getPoint(), p.getPoint()); _findCandidates(_snapmanager->getDocument()->getRoot(), it, p.getSourceNum() <= 0, local_bbox_to_snap, false, Geom::identity()); } _snapNodes(isr, p, unselected_nodes); if (_snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_PATH, SNAPTARGET_PATH_INTERSECTION, SNAPTARGET_BBOX_EDGE, SNAPTARGET_PAGE_BORDER, SNAPTARGET_TEXT_BASELINE)) { unsigned n = (unselected_nodes == NULL) ? 0 : unselected_nodes->size(); if (n > 0) { /* While editing a path in the node tool, findCandidates must ignore that path because * of the node snapping requirements (i.e. only unselected nodes must be snapable). * That path must not be ignored however when snapping to the paths, so we add it here * manually when applicable */ SPPath *path = NULL; if (it != NULL) { if (it->size() == 1 && SP_IS_PATH(*it->begin())) { path = SP_PATH(*it->begin()); } // else: *it->begin() might be a SPGroup, e.g. when editing a LPE of text that has been converted to a group of paths // as reported in bug #356743. In that case we can just ignore it, i.e. not snap to this item } _snapPaths(isr, p, unselected_nodes, path); } else { _snapPaths(isr, p, NULL, NULL); } } }
/** * * Writes the path object into a Inkscape::XML::Node */ static Inkscape::XML::Node * sp_path_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { SPShape *shape = (SPShape *) object; if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { repr = xml_doc->createElement("svg:path"); } #ifdef PATH_VERBOSE g_message("sp_path_write writes 'd' attribute"); #endif if ( shape->_curve != NULL ) { gchar *str = sp_svg_write_path(shape->_curve->get_pathvector()); repr->setAttribute("d", str); g_free(str); } else { repr->setAttribute("d", NULL); } if (flags & SP_OBJECT_WRITE_EXT) { if ( shape->_curve_before_lpe != NULL ) { gchar *str = sp_svg_write_path(shape->_curve_before_lpe->get_pathvector()); repr->setAttribute("inkscape:original-d", str); g_free(str); } else { repr->setAttribute("inkscape:original-d", NULL); } } SP_PATH(shape)->connEndPair.writeRepr(repr); if (((SPObjectClass *)(parent_class))->write) { ((SPObjectClass *)(parent_class))->write(object, xml_doc, repr, flags); } return repr; }
/// @todo investigate why Geom::Point p is passed in but ignored. void Inkscape::ObjectSnapper::_collectPaths(Geom::Point /*p*/, SnapSourceType const source_type, bool const &first_point) const { // Now, let's first collect all paths to snap to. If we have a whole bunch of points to snap, // e.g. when translating an item using the selector tool, then we will only do this for the // first point and store the collection for later use. This significantly improves the performance if (first_point) { _clear_paths(); // Determine the type of bounding box we should snap to SPItem::BBoxType bbox_type = SPItem::GEOMETRIC_BBOX; bool p_is_a_node = source_type & SNAPSOURCE_NODE_CATEGORY; bool p_is_a_bbox = source_type & SNAPSOURCE_BBOX_CATEGORY; bool p_is_other = (source_type & SNAPSOURCE_OTHERS_CATEGORY) || (source_type & SNAPSOURCE_DATUMS_CATEGORY); if (_snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_BBOX_EDGE)) { Preferences *prefs = Preferences::get(); int prefs_bbox = prefs->getBool("/tools/bounding_box", 0); bbox_type = !prefs_bbox ? SPItem::VISUAL_BBOX : SPItem::GEOMETRIC_BBOX; } // Consider the page border for snapping if (_snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_PAGE_BORDER) && _snapmanager->snapprefs.isAnyCategorySnappable()) { Geom::PathVector *border_path = _getBorderPathv(); if (border_path != NULL) { _paths_to_snap_to->push_back(SnapCandidatePath(border_path, SNAPTARGET_PAGE_BORDER, Geom::OptRect())); } } for (std::vector<SnapCandidateItem>::const_iterator i = _candidates->begin(); i != _candidates->end(); ++i) { /* Transform the requested snap point to this item's coordinates */ Geom::Affine i2doc(Geom::identity()); SPItem *root_item = NULL; /* We might have a clone at hand, so make sure we get the root item */ if (SP_IS_USE((*i).item)) { i2doc = SP_USE((*i).item)->get_root_transform(); root_item = SP_USE((*i).item)->root(); g_return_if_fail(root_item); } else { i2doc = (*i).item->i2doc_affine(); root_item = (*i).item; } //Build a list of all paths considered for snapping to //Add the item's path to snap to if (_snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_PATH, SNAPTARGET_PATH_INTERSECTION, SNAPTARGET_TEXT_BASELINE)) { if (p_is_other || p_is_a_node || (!_snapmanager->snapprefs.getStrictSnapping() && p_is_a_bbox)) { if (SP_IS_TEXT(root_item) || SP_IS_FLOWTEXT(root_item)) { if (_snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_TEXT_BASELINE)) { // Snap to the text baseline Text::Layout const *layout = te_get_layout(static_cast<SPItem *>(root_item)); if (layout != NULL && layout->outputExists()) { Geom::PathVector *pv = new Geom::PathVector(); pv->push_back(layout->baseline() * root_item->i2dt_affine() * (*i).additional_affine * _snapmanager->getDesktop()->doc2dt()); _paths_to_snap_to->push_back(SnapCandidatePath(pv, SNAPTARGET_TEXT_BASELINE, Geom::OptRect())); } } } else { // Snapping for example to a traced bitmap is very stressing for // the CPU, so we'll only snap to paths having no more than 500 nodes // This also leads to a lag of approx. 500 msec (in my lousy test set-up). bool very_complex_path = false; if (SP_IS_PATH(root_item)) { very_complex_path = SP_PATH(root_item)->nodesInPath() > 500; } if (!very_complex_path && root_item && _snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_PATH, SNAPTARGET_PATH_INTERSECTION)) { SPCurve *curve = NULL; if (SP_IS_SHAPE(root_item)) { curve = SP_SHAPE(root_item)->getCurve(); }/* else if (SP_IS_TEXT(root_item) || SP_IS_FLOWTEXT(root_item)) { curve = te_get_layout(root_item)->convertToCurves(); }*/ if (curve) { // We will get our own copy of the pathvector, which must be freed at some point // Geom::PathVector *pv = pathvector_for_curve(root_item, curve, true, true, Geom::identity(), (*i).additional_affine); Geom::PathVector *pv = new Geom::PathVector(curve->get_pathvector()); (*pv) *= root_item->i2dt_affine() * (*i).additional_affine * _snapmanager->getDesktop()->doc2dt(); // (_edit_transform * _i2d_transform); _paths_to_snap_to->push_back(SnapCandidatePath(pv, SNAPTARGET_PATH, Geom::OptRect())); // Perhaps for speed, get a reference to the Geom::pathvector, and store the transformation besides it. curve->unref(); } } } } } //Add the item's bounding box to snap to if (_snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_BBOX_EDGE)) { if (p_is_other || p_is_a_bbox || (!_snapmanager->snapprefs.getStrictSnapping() && p_is_a_node)) { // Discard the bbox of a clipped path / mask, because we don't want to snap to both the bbox // of the item AND the bbox of the clipping path at the same time if (!(*i).clip_or_mask) { Geom::OptRect rect = root_item->bounds(bbox_type, i2doc); if (rect) { Geom::PathVector *path = _getPathvFromRect(*rect); rect = root_item->desktopBounds(bbox_type); _paths_to_snap_to->push_back(SnapCandidatePath(path, SNAPTARGET_BBOX_EDGE, rect)); } } } } } } }
void sp_selected_path_break_apart(SPDesktop *desktop) { Inkscape::Selection *selection = sp_desktop_selection(desktop); if (selection->isEmpty()) { sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>path(s)</b> to break apart.")); return; } desktop->messageStack()->flash(Inkscape::IMMEDIATE_MESSAGE, _("Breaking apart paths...")); // set "busy" cursor desktop->setWaitingCursor(); bool did = false; for (GSList *items = g_slist_copy((GSList *) selection->itemList()); items != NULL; items = items->next) { SPItem *item = (SPItem *) items->data; if (!SP_IS_PATH(item)) continue; SPPath *path = SP_PATH(item); SPCurve *curve = sp_path_get_curve_for_edit(SP_PATH(path)); if (curve == NULL) continue; did = true; Inkscape::XML::Node *parent = SP_OBJECT_REPR(item)->parent(); gint pos = SP_OBJECT_REPR(item)->position(); char const *id = SP_OBJECT_REPR(item)->attribute("id"); gchar *style = g_strdup(SP_OBJECT(item)->repr->attribute("style")); gchar *path_effect = g_strdup(SP_OBJECT(item)->repr->attribute("inkscape:path-effect")); Geom::PathVector apv = curve->get_pathvector() * SP_ITEM(path)->transform; curve->unref(); // it's going to resurrect as one of the pieces, so we delete without advertisement SP_OBJECT(item)->deleteObject(false); curve = new SPCurve(apv); g_assert(curve != NULL); GSList *list = curve->split(); curve->unref(); GSList *reprs = NULL; for (GSList *l = list; l != NULL; l = l->next) { curve = (SPCurve *) l->data; Inkscape::XML::Node *repr = parent->document()->createElement("svg:path"); repr->setAttribute("style", style); repr->setAttribute("inkscape:path-effect", path_effect); gchar *str = sp_svg_write_path(curve->get_pathvector()); if (path_effect) repr->setAttribute("inkscape:original-d", str); else repr->setAttribute("d", str); g_free(str); // add the new repr to the parent parent->appendChild(repr); // move to the saved position repr->setPosition(pos > 0 ? pos : 0); // if it's the first one, restore id if (l == list) repr->setAttribute("id", id); reprs = g_slist_prepend (reprs, repr); Inkscape::GC::release(repr); } selection->setReprList(reprs); g_slist_free(reprs); g_slist_free(list); g_free(style); g_free(path_effect); } desktop->clearWaitingCursor(); if (did) { sp_document_done(sp_desktop_document(desktop), SP_VERB_SELECTION_BREAK_APART, _("Break apart")); } else { sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("<b>No path(s)</b> to break apart in the selection.")); } }
/** * Given a repr, this sets the data items in the path object such as * fill & style attributes, markers, and CSS properties. */ static void sp_path_build (SPObject *object, SPDocument *document, SPRepr *repr) { SPPath *path; SPVersion version; const gchar* marker_value; path = SP_PATH (object); version = sp_object_get_sodipodi_version (object); /* Fixes old Sodipodi nodetype to namespaced parameter */ if (sp_version_inside_range (version, 0, 0, 0, 25)) { const gchar *str; str = sp_repr_attr (repr, "INKSCAPE-PATH-NODE-TYPES"); sp_repr_set_attr (repr, "sodipodi:nodetypes", str); sp_repr_set_attr (repr, "INKSCAPE-PATH-NODE-TYPES", NULL); } sp_object_read_attr (object, "d"); /* Are these calls actually necessary? */ sp_object_read_attr (object, "marker"); sp_object_read_attr (object, "marker-start"); sp_object_read_attr (object, "marker-mid"); sp_object_read_attr (object, "marker-end"); if (sp_version_inside_range (version, 0, 0, 0, 25)) { SPShape *shape; SPCSSAttr *css; const gchar *val; gboolean changed; gboolean open; shape = (SPShape *) path; /* Remove fill from open paths for compatibility with inkscape < 0.25 */ /* And set fill-rule of closed paths to evenodd */ /* We force style rewrite at moment (Lauris) */ changed = TRUE; open = FALSE; if (shape->curve && shape->curve->bpath) { ArtBpath *bp; for (bp = shape->curve->bpath; bp->code != ART_END; bp++) { if (bp->code == ART_MOVETO_OPEN) { open = TRUE; break; } } } css = sp_repr_css_attr (repr, "style"); if (open) { val = sp_repr_css_property (css, "fill", NULL); if (val && strcmp (val, "none")) { sp_repr_css_set_property (css, "fill", "none"); changed = TRUE; } } else { val = sp_repr_css_property (css, "fill-rule", NULL); if (!val) { sp_repr_css_set_property (css, "fill-rule", "evenodd"); changed = TRUE; } } if (changed) { sp_repr_css_set (repr, css, "style"); } sp_repr_css_attr_unref (css); } if (((SPObjectClass *) parent_class)->build) { ((SPObjectClass *) parent_class)->build (object, document, repr); } }
bool sp_item_list_to_curves(const GSList *items, GSList **selected, GSList **to_select, bool skip_all_lpeitems) { bool did = false; for (; items != NULL; items = items->next) { SPItem *item = SP_ITEM(items->data); SPDocument *document = item->document; if ( skip_all_lpeitems && SP_IS_LPE_ITEM(item) && !SP_IS_GROUP(item) ) // also convert objects in an SPGroup when skip_all_lpeitems is set. { continue; } if (SP_IS_PATH(item) && !SP_PATH(item)->original_curve) { continue; // already a path, and no path effect } if (SP_IS_BOX3D(item)) { // convert 3D box to ordinary group of paths; replace the old element in 'selected' with the new group Inkscape::XML::Node *repr = SP_OBJECT_REPR(box3d_convert_to_group(SP_BOX3D(item))); if (repr) { *to_select = g_slist_prepend (*to_select, repr); did = true; *selected = g_slist_remove (*selected, item); } continue; } if (SP_IS_GROUP(item)) { sp_lpe_item_remove_all_path_effects(SP_LPE_ITEM(item), true); GSList *item_list = sp_item_group_item_list(SP_GROUP(item)); GSList *item_to_select = NULL; GSList *item_selected = NULL; if (sp_item_list_to_curves(item_list, &item_selected, &item_to_select)) did = true; g_slist_free(item_list); g_slist_free(item_to_select); g_slist_free(item_selected); continue; } Inkscape::XML::Node *repr = sp_selected_item_to_curved_repr(item, 0); if (!repr) continue; did = true; *selected = g_slist_remove (*selected, item); // remember the position of the item gint pos = SP_OBJECT_REPR(item)->position(); // remember parent Inkscape::XML::Node *parent = SP_OBJECT_REPR(item)->parent(); // remember id char const *id = SP_OBJECT_REPR(item)->attribute("id"); // remember title gchar *title = item->title(); // remember description gchar *desc = item->desc(); // It's going to resurrect, so we delete without notifying listeners. SP_OBJECT(item)->deleteObject(false); // restore id repr->setAttribute("id", id); // add the new repr to the parent parent->appendChild(repr); SPObject* newObj = document->getObjectByRepr(repr); if (title && newObj) { newObj->setTitle(title); g_free(title); } if (desc && newObj) { newObj->setDesc(desc); g_free(desc); } // move to the saved position repr->setPosition(pos > 0 ? pos : 0); /* Buglet: We don't re-add the (new version of the) object to the selection of any other * desktops where it was previously selected. */ *to_select = g_slist_prepend (*to_select, repr); Inkscape::GC::release(repr); } return did; }
void sp_selected_path_combine(SPDesktop *desktop) { Inkscape::Selection *selection = sp_desktop_selection(desktop); SPDocument *doc = sp_desktop_document(desktop); if (g_slist_length((GSList *) selection->itemList()) < 1) { sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to combine.")); return; } desktop->messageStack()->flash(Inkscape::IMMEDIATE_MESSAGE, _("Combining paths...")); // set "busy" cursor desktop->setWaitingCursor(); GSList *items = g_slist_copy((GSList *) selection->itemList()); items = sp_degroup_list (items); // descend into any groups in selection GSList *to_paths = NULL; for (GSList *i = items; i != NULL; i = i->next) { SPItem *item = (SPItem *) i->data; if (!SP_IS_PATH(item) && !SP_IS_GROUP(item)) to_paths = g_slist_prepend(to_paths, item); } GSList *converted = NULL; bool did = sp_item_list_to_curves(to_paths, &items, &converted); g_slist_free(to_paths); for (GSList *i = converted; i != NULL; i = i->next) items = g_slist_prepend(items, doc->getObjectByRepr((Inkscape::XML::Node*)(i->data))); items = sp_degroup_list (items); // converting to path may have added more groups, descend again items = g_slist_sort(items, (GCompareFunc) sp_item_repr_compare_position); items = g_slist_reverse(items); // remember the position, id, transform and style of the topmost path, they will be assigned to the combined one gint position = 0; char const *id = NULL; char const *transform = NULL; gchar *style = NULL; gchar *path_effect = NULL; SPCurve* curve = 0; SPItem *first = NULL; Inkscape::XML::Node *parent = NULL; if (did) { selection->clear(); } for (GSList *i = items; i != NULL; i = i->next) { // going from top to bottom SPItem *item = (SPItem *) i->data; if (!SP_IS_PATH(item)) continue; if (!did) { selection->clear(); did = true; } SPCurve *c = sp_path_get_curve_for_edit(SP_PATH(item)); if (first == NULL) { // this is the topmost path first = item; parent = SP_OBJECT_REPR(first)->parent(); position = SP_OBJECT_REPR(first)->position(); id = SP_OBJECT_REPR(first)->attribute("id"); transform = SP_OBJECT_REPR(first)->attribute("transform"); // FIXME: merge styles of combined objects instead of using the first one's style style = g_strdup(SP_OBJECT_REPR(first)->attribute("style")); path_effect = g_strdup(SP_OBJECT_REPR(first)->attribute("inkscape:path-effect")); //c->transform(item->transform); curve = c; } else { c->transform(item->getRelativeTransform(SP_OBJECT(first))); curve->append(c, false); c->unref(); } // unless this is the topmost object, if (item != first) { // reduce position only if the same parent if (SP_OBJECT_REPR(item)->parent() == parent) position--; // delete the object for real, so that its clones can take appropriate action SP_OBJECT(item)->deleteObject(); } } g_slist_free(items); if (did) { SP_OBJECT(first)->deleteObject(false); // delete the topmost. Inkscape::XML::Document *xml_doc = sp_document_repr_doc(desktop->doc()); Inkscape::XML::Node *repr = xml_doc->createElement("svg:path"); // restore id, transform, path effect, and style repr->setAttribute("id", id); if (transform) repr->setAttribute("transform", transform); repr->setAttribute("style", style); g_free(style); repr->setAttribute("inkscape:path-effect", path_effect); g_free(path_effect); // set path data corresponding to new curve gchar *dstring = sp_svg_write_path(curve->get_pathvector()); curve->unref(); if (path_effect) repr->setAttribute("inkscape:original-d", dstring); else repr->setAttribute("d", dstring); g_free(dstring); // add the new group to the parent of the topmost parent->appendChild(repr); // move to the position of the topmost, reduced by the number of deleted items repr->setPosition(position > 0 ? position : 0); sp_document_done(sp_desktop_document(desktop), SP_VERB_SELECTION_COMBINE, _("Combine")); selection->set(repr); Inkscape::GC::release(repr); } else { sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("<b>No path(s)</b> to combine in the selection.")); } desktop->clearWaitingCursor(); }