selection-chemistry.cpp revision d7943e935ff00bba1ca84e964ac46778ec16bb87
919N/A#ifdef HAVE_CONFIG_H
256N/A#include "selection-chemistry.h"
256N/A#include "desktop-style.h"
256N/A#include "dir-util.h"
256N/A#include "selection.h"
822N/A#include "tools-switch.h"
1088N/A#include "desktop-handles.h"
1088N/A#include "message-stack.h"
256N/A#include "sp-item-transform.h"
256N/A#include "sp-textpath.h"
256N/A#include "sp-tspan.h"
256N/A#include "sp-flowtext.h"
256N/A#include "sp-flowregion.h"
256N/A#include "sp-image.h"
256N/A#include "sp-ellipse.h"
822N/A#include "sp-spiral.h"
256N/A#include "sp-polyline.h"
256N/A#include "text-editing.h"
256N/A#include "text-context.h"
256N/A#include "connector-context.h"
256N/A#include "sp-conn-end.h"
256N/A#include "dropper-context.h"
256N/A#include "xml/rebase-hrefs.h"
256N/A#include "document-private.h"
256N/A#include "document-undo.h"
256N/A#include "sp-gradient.h"
256N/A#include "sp-gradient-reference.h"
256N/A#include "sp-linear-gradient-fns.h"
256N/A#include "sp-pattern.h"
256N/A#include "sp-symbol.h"
256N/A#include "sp-radial-gradient-fns.h"
256N/A#include "gradient-context.h"
256N/A#include "sp-namedview.h"
256N/A#include "preferences.h"
256N/A#include "sp-offset.h"
256N/A#include "sp-clippath.h"
981N/A#include "helper/png-write.h"
256N/A#include "layer-fns.h"
256N/A#include "context-fns.h"
851N/A#include "unit-constants.h"
256N/A#include "xml/simple-document.h"
1088N/A#include "sp-filter-reference.h"
1088N/A#include "gradient-drag.h"
256N/A#include "uri-references.h"
256N/A#include "display/canvas-bpath.h"
256N/A#include "inkscape-private.h"
256N/A#include "path-chemistry.h"
256N/A#include "ui/tool/control-point-selection.h"
256N/A#include "ui/tool/multi-path-manipulator.h"
256N/A#include "sp-lpe-item.h"
256N/A#include "live_effects/effect.h"
256N/A#include "live_effects/effect-enum.h"
256N/A#include "live_effects/parameter/originalpath.h"
1584N/A#include "sp-item-group.h"
256N/A#include "tools-switch.h"
256N/A#include "ui/tool/node-tool.h"
256N/A#include "ui/clipboard.h"
256N/A/* The clipboard handling is in ui/clipboard.cpp now. There are some legacy functions left here,
static void sp_selection_copy_one(Inkscape::XML::Node *repr, Geom::Affine full_t, GSList **clip, Inkscape::XML::Document* xml_doc)
static void sp_selection_copy_impl(GSList const *items, GSList **clip, Inkscape::XML::Document* xml_doc)
sorted_items = g_slist_sort(static_cast<GSList *>(sorted_items), (GCompareFunc) sp_object_compare_position);
sp_selection_copy_one(SP_OBJECT(i->data)->getRepr(), SP_ITEM(i->data)->i2doc_affine(), clip, xml_doc);
if (t_str)
// (we're dealing with unattached repr, so we write to its attr instead of using sp_item_set_transform)
return copied;
static void sp_selection_delete_impl(GSList const *items, bool propagate = true, bool propagate_descendants = true)
if (obj) {
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to duplicate."));
while (reprs) {
if (relink_clones) {
if (fork_livepatheffects) {
if (relink_clones) {
// std::cout << id << " old, its ori: " << orig->getId() << "; will relink:" << new_ids[i] << " to " << new_ids[j] << "\n";
if ( !suppressDone ) {
if (!dt)
while (items) {
GSList *get_all_items(GSList *list, SPObject *from, SPDesktop *desktop, bool onlyvisible, bool onlysensitive, bool ingroups, GSList const *exclude)
return list;
if (!dt)
PrefsSelectionContext inlayer = (PrefsSelectionContext) prefs->getInt("/options/kbselection/inlayer", PREFS_SELECTION_LAYER);
if (invert) {
if (force_all_layers)
switch (inlayer) {
case PREFS_SELECTION_LAYER: {
case PREFS_SELECTION_LAYER_RECURSIVE: {
if (items) {
static void sp_selection_group_impl(GSList *p, Inkscape::XML::Node *group, Inkscape::XML::Document *xml_doc, SPDocument *doc) {
Inkscape::XML::Node *topmost_parent = (static_cast<Inkscape::XML::Node *>(g_slist_last(p)->data))->parent();
// At this point, current may already have no item, due to its being a clone whose original is already moved away
// So we copy it artificially calculating the transform from its repr->attr("transform") and the parent transform
if (t_str)
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>some objects</b> to group."));
bool ungrouped = false;
i != NULL;
i = i->next)
// when ungrouping cloned groups with their originals, some objects that were selected may no more exist due to unlinking
ungrouped = true;
if (!ungrouped) {
desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No groups</b> to ungroup in the selection."));
/** Replace all groups in the list with their member objects, recursively; returns a new list, frees old */
GSList *
bool has_groups = false;
has_groups = true;
return out;
static SPGroup *
if (!items) {
return NULL;
return NULL;
return NULL;
return prev;
if (!desktop)
if (!items) {
if (!group) {
desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("You cannot raise/lower objects from <b>different groups</b> or <b>layers</b>."));
if (selected) {
while (rev) {
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to raise to top."));
if (!group) {
desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("You cannot raise/lower objects from <b>different groups</b> or <b>layers</b>."));
if (!items) {
if (!group) {
desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("You cannot raise/lower objects from <b>different groups</b> or <b>layers</b>."));
if (selected) {
while (rev) {
if (put_after)
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to lower to bottom."));
if (!group) {
desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("You cannot raise/lower objects from <b>different groups</b> or <b>layers</b>."));
minpos = 0;
return NULL;
for (SPObject *last_element = item->lastChild(); last_element != NULL; last_element = last_element->getPrev()) {
if (temp) {
return css;
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to remove live path effects from."));
for ( GSList const *itemlist = selection->itemList(); itemlist != NULL; itemlist = g_slist_next(itemlist) ) {
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to remove filters from."));
dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to move to the layer above."));
if (next) {
next=Inkscape::next_layer(dt->currentRoot(), dt->currentLayer()); // Fixes bug 1482973: crash while moving layers
if (next) {
no_more = true;
if ( !suppressDone ) {
no_more = true;
if (no_more) {
dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to move to the layer below."));
if (next) {
sp_selection_copy_impl(items, &temp_clip, dt->doc()->getReprDoc()); // we're in the same doc, so no need to copy defs
next=Inkscape::previous_layer(dt->currentRoot(), dt->currentLayer()); // Fixes bug 1482973: crash while moving layers
if (next) {
no_more = true;
if ( !suppressDone ) {
no_more = true;
if (no_more) {
if (moveto) {
sp_selection_copy_impl(items, &temp_clip, dt->doc()->getReprDoc()); // we're in the same doc, so no need to copy defs
if ( !suppressDone ) {
bool contains_original = false;
return contains_original;
bool clone_with_original = false;
if (clone_with_original)
return clone_with_original;
void sp_selection_apply_affine(Inkscape::Selection *selection, Geom::Affine const &affine, bool set_i2d, bool compensate)
bool transform_textpath_with_path = (SP_IS_TEXT_TEXTPATH(item) && selection->includes( sp_textpath_get_path_item(SP_TEXTPATH(item->firstChild())) ));
bool transform_flowtext_with_frame = (SP_IS_FLOWTEXT(item) && selection->includes( SP_FLOWTEXT(item)->get_frame(NULL))); // (only the first frame is checked so far)
bool transform_offset_with_source = (SP_IS_OFFSET(item) && SP_OFFSET(item)->sourceHref) && selection->includes( sp_offset_get_source(SP_OFFSET(item)) );
int compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
if (transform_textpath_with_path) {
} else if (transform_flowtext_with_frame) {
// calculate the matrix we need to apply to the clone to cancel its induced transform from its original
if (prefs_parallel) {
} else if (prefs_unmoved) {
} else if (transform_offset_with_source && (prefs_parallel || prefs_unmoved) && affine.isTranslation()){
if (prefs_parallel) {
} else if (prefs_unmoved) {
if (set_i2d) {
while (l != NULL) {
l = l->next;
if ( !bbox ) {
void sp_selection_scale_relative(Inkscape::Selection *selection, Geom::Point const &align, Geom::Scale const &scale)
if ( !bbox ) {
// FIXME: ARBITRARY LIMIT: don't try to scale above 1 Mpx, it won't display properly and will crash sooner or later anyway
sp_selection_rotate_relative(Inkscape::Selection *selection, Geom::Point const ¢er, gdouble const angle_degrees)
sp_selection_skew_relative(Inkscape::Selection *selection, Geom::Point const &align, double dx, double dy)
void sp_selection_move_relative(Inkscape::Selection *selection, Geom::Point const &move, bool compensate)
* Rotates selected objects 90 degrees, either clock-wise or counter-clockwise, depending on the value of ccw.
Geom::Rotate const rot_90(Geom::Point(0, ccw ? 1 : -1)); // pos. or neg. rotation, depending on the value of ccw
if (!center) {
( ( angle_degrees > 0 )
* Selects all the visible items with the same fill and/or stroke color/style as the items in the current selection
void sp_select_same_fill_stroke_style(SPDesktop *desktop, gboolean fill, gboolean stroke, gboolean style)
if (!desktop) {
GSList *all_list = get_all_items(NULL, desktop->currentRoot(), desktop, onlyvisible, onlysensitive, ingroups, NULL);
if (fill) {
if (stroke) {
if (style) {
if (all_matches) {
if (all_list) {
if (!desktop) {
GSList *all_list = get_all_items(NULL, desktop->currentRoot(), desktop, onlyvisible, onlysensitive, ingroups, NULL);
if (matches) {
if (all_list) {
if (!desktop) {
GSList *all_list = get_all_items(NULL, desktop->currentRoot(), desktop, onlyvisible, onlysensitive, ingroups, NULL);
if (matches) {
if (all_list) {
match = false;
if (sel_paint->isColor() && iter_paint->isColor() // color == color comparision doesnt seem to work here.
match = true;
match = true;
match = true;
match = true;
match = true;
if (match) {
return matches;
if ( SP_IS_RECT(i)) {
return ( SP_IS_RECT(j) );
} else if (SP_IS_SPIRAL(i)) {
return (SP_IS_SPIRAL(j));
} else if (SP_IS_TEXT(i) || SP_IS_FLOWTEXT(i) || SP_IS_TSPAN(i) || SP_IS_TREF(i) || SP_IS_STRING(i)) {
} else if (SP_IS_USE(i)) {
return (SP_IS_USE(j)) ;
} else if (SP_IS_IMAGE(i)) {
return (SP_IS_IMAGE(j));
return matches;
match = false;
if (sel_style_for_width) {
match = (sel_style_for_width->stroke_width.computed == iter_style_for_width->stroke_width.computed);
match = false;
match = true;
for (int i = 0; i < len; i++) {
match = false;
if (match) {
return matches;
return r.corner(i);
\param angle the angle in "angular pixels", i.e. how many visible pixels must move the outermost point of the rotated object
( (angle > 0)
if (!bbox) {
( (grow > 0)
if (!sel_bbox) {
if (dx == 0) {
DocumentUndo::maybeDone(sp_desktop_document(desktop), "selector:move:vertical", SP_VERB_CONTEXT_SELECT,
} else if (dy == 0) {
DocumentUndo::maybeDone(sp_desktop_document(desktop), "selector:move:horizontal", SP_VERB_CONTEXT_SELECT,
if (dx == 0) {
DocumentUndo::maybeDone(sp_desktop_document(desktop), "selector:move:vertical", SP_VERB_CONTEXT_SELECT,
} else if (dy == 0) {
DocumentUndo::maybeDone(sp_desktop_document(desktop), "selector:move:horizontal", SP_VERB_CONTEXT_SELECT,
struct Forward {
struct ListReverse {
g_slist_free(i);
return list;
PrefsSelectionContext inlayer = (PrefsSelectionContext)prefs->getInt("/options/kbselection/inlayer", PREFS_SELECTION_LAYER);
SPItem *item=next_item_from_list<Forward>(desktop, selection->itemList(), root, SP_CYCLING == SP_CYCLE_VISIBLE, inlayer, onlyvisible, onlysensitive);
if (item) {
PrefsSelectionContext inlayer = (PrefsSelectionContext) prefs->getInt("/options/kbselection/inlayer", PREFS_SELECTION_LAYER);
SPItem *item=next_item_from_list<ListReverse>(desktop, selection->itemList(), root, SP_CYCLING == SP_CYCLE_VISIBLE, inlayer, onlyvisible, onlysensitive);
if (item) {
if (!dt) return;
dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("The selection has no applied path effect."));
SPObject *root, bool only_in_viewport, PrefsSelectionContext inlayer, bool onlyvisible, bool onlysensitive)
while (items) {
return next;
if (path) {
found = next_item<D>(desktop, path->next, object, only_in_viewport, inlayer, onlyvisible, onlysensitive);
return found;
// sorting items from different parents sorts each parent's subset without possibly mixing them, just what we need
while (reprs) {
clone->setAttribute("inkscape:transform-center-x", sel_repr->attribute("inkscape:transform-center-x"), false);
clone->setAttribute("inkscape:transform-center-y", sel_repr->attribute("inkscape:transform-center-y"), false);
if (!desktop)
if (!newid) {
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Copy an <b>object</b> to clipboard to relink clones to."));
bool relinked = false;
relinked = true;
if (!relinked) {
desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No clones to relink</b> in the selection."));
if (!desktop)
bool unlinked = false;
if (tspan) {
unlinked = true;
if (!unlink) {
unlinked = true;
if (!unlinked) {
desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No clones to unlink</b> in the selection."));
gchar const *error = _("Select a <b>clone</b> to go to its original. Select a <b>linked offset</b> to go to its source. Select a <b>text on path</b> to go to the path. Select a <b>flowed text</b> to go to its frame.");
Inkscape::LivePathEffect::Effect* lpe = sp_lpe_item_has_path_effect_of_type(SP_LPE_ITEM(item), Inkscape::LivePathEffect::CLONE_ORIGINAL);
if (lpe) {
if (Inkscape::LivePathEffect::OriginalPathParam *pathparam = dynamic_cast<Inkscape::LivePathEffect::OriginalPathParam *>(lpeparam)) {
if (!original) {
desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>Cannot find</b> the object to select (orphaned clone, offset, textpath, flowed text?)"));
if (SP_IS_DEFS(o)) {
desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The object you're trying to select is <b>not visible</b> (it is in <defs>)"));
if (original) {
if (highlight) {
sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), 0x0000ddff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT, 5, 3);
desktop->doc()->getDefs()->getRepr()->addChild(lpe_repr, NULL); // adds to <defs> and assigns the 'id' attribute
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to convert to marker."));
if (apply) {
// See ActorAlign::on_button_click() in src/ui/dialog/align-and-distribute.cpp
int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
(void)mark_id;
if (deleteitem) {
if (!items) {
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to convert to guides."));
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select one <b>group</b> to convert to symbol."));
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select only one <b>group</b> to convert to symbol."));
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select original (<b>Shift+D</b>) to convert to symbol."));
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Group selection first to convert to symbol."));
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select a <b>symbol</b> to extract objects from."));
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select only one <b>symbol</b> to convert to group."));
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select only one <b>symbol</b> to convert to group."));
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to convert to pattern."));
Geom::Point move_p = Geom::Point(0, doc->getHeight()) - (r->min() + Geom::Point(0, r->dimensions()[Geom::Y]));
if (apply) {
// See ActorAlign::on_button_click() in src/ui/dialog/align-and-distribute.cpp
int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
if (apply) {
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select an <b>object with pattern fill</b> to extract objects from."));
bool did = false;
did = true;
if (!did) {
desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No pattern fills</b> in the selection."));
void sp_selection_get_export_hints(Inkscape::Selection *selection, Glib::ustring &filename, float *xdpi, float *ydpi)
xdpi_search &&
if (filename_search) {
if (tmp){
if (xdpi_search) {
if (ydpi_search) {
void sp_document_get_export_hints(SPDocument *doc, Glib::ustring &filename, float *xdpi, float *ydpi)
if(tmp)
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to make a bitmap copy."));
if (!bbox) {
return; // exceptional situation, so not bother with a translatable error message, just quit quietly
current);
g_strcanon(basename, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.=+~$#@^&!?", '_');
double res;
if (0 < prefs_res) {
} else if (0 < prefs_min) {
// If minsize is given, look up minimum bitmap size (default 250 pixels) and calculate resolution from it
if (hint_xdpi != 0) {
if (hint_xdpi != 0) {
// Calculate the matrix that will be applied to the image so that it exactly overlaps the source objects
shift_y = -round(-shift_y); // this gets correct rounding despite coordinate inversion, remove the negations when the inversion is gone
t = Geom::Scale(1, -1) * Geom::Translate(shift_x, shift_y) * eek.inverse(); /// @fixme hardcoded doc2dt transform?
items);
if (run) {
if (pb) {
g_free(c);
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to create clippath or mask from."));
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select mask object and <b>object(s)</b> to apply clippath or mask to."));
if (clone_with_original) {
if (apply_to_layer) {
if (remove_original) {
} else if (!topmost) {
if (remove_original) {
if (remove_original) {
Inkscape::XML::Node *dup = reinterpret_cast<Inkscape::XML::Node *>(mask_item->data)->duplicate(xml_doc);
if (apply_clip_path) {
if (apply_clip_path) {
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to remove clippath or mask from."));
if (remove_original) {
if (apply_clip_path) {
for ( std::map<SPObject*,SPItem*>::iterator it = referenced_objects.begin() ; it != referenced_objects.end() ; ++it) {
if (apply_clip_path) {
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to fit canvas to."));
if (bbox) {
if (bbox) {
* ui/dialog/page-sizer.
if (changed) {
if (!dt) return;
if (layer_only) {