selection-chemistry.cpp revision ef610fdbfb424e7ad204ac9de142c05bb986e1dc
919N/A#ifdef HAVE_CONFIG_H
919N/A#include "selection-chemistry.h"
469N/A#include "desktop-style.h"
469N/A#include "dir-util.h"
1068N/A#include "selection.h"
469N/A#include "tools-switch.h"
469N/A#include "desktop-handles.h"
469N/A#include "message-stack.h"
469N/A#include "sp-item-transform.h"
469N/A#include "sp-textpath.h"
469N/A#include "sp-tspan.h"
469N/A#include "sp-flowtext.h"
469N/A#include "sp-flowregion.h"
469N/A#include "sp-image.h"
469N/A#include "text-editing.h"
469N/A#include "text-context.h"
469N/A#include "connector-context.h"
469N/A#include "sp-conn-end.h"
469N/A#include "dropper-context.h"
469N/A#include "libnr/nr-matrix-rotate-ops.h"
469N/A#include "libnr/nr-matrix-translate-ops.h"
469N/A#include "libnr/nr-scale-ops.h"
469N/A#include "xml/rebase-hrefs.h"
469N/A#include "document-private.h"
469N/A#include "sp-gradient.h"
469N/A#include "sp-gradient-reference.h"
469N/A#include "sp-linear-gradient-fns.h"
469N/A#include "sp-pattern.h"
469N/A#include "sp-radial-gradient-fns.h"
469N/A#include "gradient-context.h"
469N/A#include "sp-namedview.h"
469N/A#include "preferences.h"
469N/A#include "sp-offset.h"
469N/A#include "sp-clippath.h"
469N/A#include "helper/png-write.h"
469N/A#include "layer-fns.h"
469N/A#include "context-fns.h"
469N/A#include "unit-constants.h"
469N/A#include "xml/simple-document.h"
469N/A#include "sp-filter-reference.h"
469N/A#include "gradient-drag.h"
469N/A#include "uri-references.h"
469N/A#include "libnr/nr-convert2geom.h"
469N/A#include "display/canvas-bpath.h"
469N/A#include "inkscape-private.h"
469N/A#include "path-chemistry.h"
469N/A#include "ui/tool/control-point-selection.h"
469N/A#include "ui/tool/multi-path-manipulator.h"
469N/A#include "sp-item-group.h"
469N/A#include "tools-switch.h"
469N/A#include "ui/tool/node-tool.h"
469N/A#include "ui/clipboard.h"
469N/A/* The clipboard handling is in ui/clipboard.cpp now. There are some legacy functions left here,
469N/A * Copies repr and its inherited css style elements, along with the accumulated transform 'full_t',
469N/Avoid sp_selection_copy_one(Inkscape::XML::Node *repr, Geom::Matrix full_t, GSList **clip, Inkscape::XML::Document* xml_doc)
469N/Avoid sp_selection_copy_impl(GSList const *items, GSList **clip, Inkscape::XML::Document* xml_doc)
469N/A sorted_items = g_slist_sort((GSList *) sorted_items, (GCompareFunc) sp_object_compare_position);
469N/A sp_selection_copy_one(SP_OBJECT_REPR(i->data), SP_ITEM(i->data)->i2doc_affine(), clip, xml_doc);
469N/A // (we're dealing with unattached repr, so we write to its attr instead of using sp_item_set_transform)
469N/Avoid sp_selection_delete_impl(GSList const *items, bool propagate = true, bool propagate_descendants = true)
469N/A desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to duplicate."));
469N/A if (relink_clones) {
469N/A if (relink_clones) {
469N/A // std::cout << id << " old, its ori: " << SP_OBJECT_ID(orig) << "; will relink:" << new_ids[i] << " to " << new_ids[j] << "\n";
469N/A if ( !suppressDone ) {
469N/Aget_all_items(GSList *list, SPObject *from, SPDesktop *desktop, bool onlyvisible, bool onlysensitive, GSList const *exclude)
469N/A for (SPObject *child = SP_OBJECT(from)->first_child() ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
469N/A PrefsSelectionContext inlayer = (PrefsSelectionContext) prefs->getInt("/options/kbselection/inlayer", PREFS_SELECTION_LAYER);
469N/A if (force_all_layers)
469N/A case PREFS_SELECTION_LAYER: {
469N/Avoid sp_selection_group_impl(GSList *p, Inkscape::XML::Node *group, Inkscape::XML::Document *xml_doc, SPDocument *doc) {
469N/A Inkscape::XML::Node *topmost_parent = ((Inkscape::XML::Node *) g_slist_last(p)->data)->parent();
469N/A // At this point, current may already have no item, due to its being a clone whose original is already moved away
469N/A // So we copy it artificially calculating the transform from its repr->attr("transform") and the parent transform
469N/A GSList *copied = sp_selection_paste_impl(doc, doc->getObjectByRepr(topmost_parent), &temp_clip);
469N/A // Move to the position of the topmost, reduced by the number of items deleted from topmost_parent
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
if (strcmp(SP_OBJECT_REPR(group)->name(), "svg:g") && strcmp(SP_OBJECT_REPR(group)->name(), "svg:switch")) {
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;
SPObject *
return NULL;
return NULL;
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) {
// For more details, see http://developer.gnome.org/doc/API/2.0/glib/glib-I18N.html#Q-:CAPS
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 = SP_OBJECT_PREV(last_element)) {
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, sp_document_repr_doc(dt->doc())); // 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) {
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::Matrix 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(SP_OBJECT(item)->first_child())) ));
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);
} else if (transform_flowtext_with_frame) {
} else if (transform_clone_with_original) {
// 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) {
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)
* @brief 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 )
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) {
SPDocumentUndo::maybe_done(sp_desktop_document(desktop), "selector:move:vertical", SP_VERB_CONTEXT_SELECT,
} else if (dy == 0) {
SPDocumentUndo::maybe_done(sp_desktop_document(desktop), "selector:move:horizontal", SP_VERB_CONTEXT_SELECT,
if (dx == 0) {
SPDocumentUndo::maybe_done(sp_desktop_document(desktop), "selector:move:vertical", SP_VERB_CONTEXT_SELECT,
} else if (dy == 0) {
SPDocumentUndo::maybe_done(sp_desktop_document(desktop), "selector:move:horizontal", SP_VERB_CONTEXT_SELECT,
struct Forward {
struct Reverse {
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<Reverse>(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);
// For more details, see http://developer.gnome.org/doc/API/2.0/glib/glib-I18N.html#Q-:CAPS
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;
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.");
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->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 <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;
for (SPObject *child = SP_OBJECT(pattern)->first_child() ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
if (!did) {
desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No pattern fills</b> in the selection."));
sp_selection_get_export_hints(Inkscape::Selection *selection, char const **filename, float *xdpi, float *ydpi)
xdpi_search &&
if (filename_search) {
if (xdpi_search) {
if (ydpi_search) {
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to make a bitmap copy."));
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
char const *hint_filename;
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
items);
if (run) {
int retval;
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) {