selection-chemistry.cpp revision a05187e45953c309c431d23ca734d62be88269e7
546N/A#ifdef HAVE_CONFIG_H
546N/A#include "inkscape.h"
919N/A#include "desktop-style.h"
919N/A#include "selection.h"
546N/A#include "tools-switch.h"
919N/A#include "desktop-handles.h"
919N/A#include "message-stack.h"
919N/A#include "sp-item-transform.h"
919N/A#include "sp-textpath.h"
919N/A#include "sp-tspan.h"
546N/A#include "sp-flowtext.h"
546N/A#include "sp-flowregion.h"
546N/A#include "text-editing.h"
546N/A#include "text-context.h"
546N/A#include "connector-context.h"
546N/A#include "sp-conn-end.h"
546N/A#include "dropper-context.h"
546N/A#include "libnr/nr-matrix-rotate-ops.h"
546N/A#include "libnr/nr-matrix-translate-ops.h"
546N/A#include "libnr/nr-rotate-fns.h"
546N/A#include "libnr/nr-scale-ops.h"
546N/A#include "libnr/nr-scale-translate-ops.h"
546N/A#include "libnr/nr-translate-matrix-ops.h"
546N/A#include "libnr/nr-translate-scale-ops.h"
546N/A#include "document-private.h"
546N/A#include "sp-gradient.h"
546N/A#include "sp-gradient-reference.h"
1513N/A#include "sp-linear-gradient-fns.h"
546N/A#include "sp-pattern.h"
546N/A#include "sp-radial-gradient-fns.h"
546N/A#include "sp-namedview.h"
#include "prefs-utils.h"
#include "sp-offset.h"
#include "sp-clippath.h"
#include "sp-mask.h"
#include "file.h"
#include "helper/png-write.h"
#include "layer-fns.h"
#include "context-fns.h"
#include <map>
#include "sp-item.h"
#include "box3d.h"
#include "unit-constants.h"
#include "xml/simple-document.h"
#include "sp-filter-reference.h"
#include "gradient-drag.h"
#include "uri-references.h"
#include "live_effects/lpeobject.h"
#include "sp-rect.h"
using NR::X;
using NR::Y;
#include "selection-chemistry.h"
static void sp_copy_stuff_used_by_item(GSList **defs_clip, SPItem *item, GSList const *items, Inkscape::XML::Document* xml_doc);
void sp_selection_copy_one (Inkscape::XML::Node *repr, NR::Matrix full_t, GSList **clip, Inkscape::XML::Document* xml_doc)
void sp_selection_copy_impl (GSList const *items, GSList **clip, GSList **defs_clip, SPCSSAttr **style_clip, Inkscape::XML::Document* xml_doc)
if (defs_clip) {
if (style_clip) {
if (clip) {
sp_selection_copy_one (SP_OBJECT_REPR (i->data), sp_item_i2doc_affine(SP_ITEM (i->data)), clip, xml_doc);
if (!defs_clip)
GSList *sp_selection_paste_impl (SPDocument *doc, SPObject *parent, GSList **clip, GSList **defs_clip)
if (t_str)
// (we're dealing with unattached repr, so we write to its attr instead of using sp_item_set_transform)
return copied;
void sp_selection_delete_impl(GSList const *items, bool propagate = true, bool propagate_descendants = true)
void sp_selection_delete()
void sp_selection_duplicate()
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to duplicate."));
// sorting items from different parents sorts each parent's subset without possibly mixing them, just what we need
while (reprs) {
void sp_edit_clear_all()
if (!dt)
while (items) {
GSList *
get_all_items (GSList *list, SPObject *from, SPDesktop *desktop, bool onlyvisible, bool onlysensitive, GSList const *exclude)
for (SPObject *child = sp_object_first_child(SP_OBJECT(from)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
return list;
if (!dt)
PrefsSelectionContext inlayer = (PrefsSelectionContext)prefs_get_int_attribute ("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) {
void sp_edit_select_all ()
sp_edit_select_all_full (false, false);
void sp_edit_select_all_in_all_layers ()
sp_edit_select_all_full (true, false);
void sp_edit_invert ()
sp_edit_select_all_full (false, true);
void sp_edit_invert_in_all_layers ()
sp_edit_select_all_full (true, true);
void sp_selection_group()
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>some objects</b> to group."));
// 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)
GSList *copied = sp_selection_paste_impl (doc, doc->getObjectByRepr(topmost_parent), &temp_clip, NULL);
void sp_selection_ungroup()
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."));
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) {
void sp_selection_raise_to_top()
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)
void sp_selection_lower_to_bottom()
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;
void sp_selection_cut()
while (ref) {
while (ref) {
// items in the pattern may also use gradients and other patterns, so we need to recurse here as well
for (SPObject *child = sp_object_first_child(SP_OBJECT(ref)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
void sp_copy_textpath_path (GSList **defs_clip, SPTextPath *tp, GSList const *items, Inkscape::XML::Document* xml_doc)
if (!path)
if (items && g_slist_find ((GSList *) items, path)) // do not copy it to defs if it is already in the list of items copied
void sp_copy_stuff_used_by_item (GSList **defs_clip, SPItem *item, GSList const *items, Inkscape::XML::Document* xml_doc)
for (int i = 0 ; i < SP_MARKER_LOC_QTY ; i++) {
sp_copy_textpath_path (defs_clip, SP_TEXTPATH(sp_object_first_child(SP_OBJECT(item))), items, xml_doc);
if (SP_IS_ITEM(o))
if (SP_IS_ITEM(o))
if (style_clipboard) {
return NULL;
for (SPObject *last_element = item->lastChild(); last_element != NULL; last_element = SP_OBJECT_PREV (last_element)) {
if (temp) {
return css;
void sp_selection_copy()
if (!clipboard_document) {
if (this_text) {
texts++;
while (defs_clipboard) {
if (style_clipboard) {
while (clipboard) {
if (tools_isactive (desktop, TOOLS_TEXT)) { // take style from cursor/text selection, overwriting the style just set by copy_impl
static bool pastedPicFromClipboard()
if ( pic == 0 )
const char* path;
if ( pastedPicFromClipboard() )
GSList *copied = sp_selection_paste_impl(document, desktop->currentLayer(), &clipboard, &defs_clipboard);
if (!in_place) {
if (sel_bbox) {
void sp_selection_paste_style()
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to paste style to."));
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to paste live path effect to."));
if (!effecturi) {
SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Clipboard does not contain a live path effect."));
for ( GSList const *itemlist = selection->itemList(); itemlist != NULL; itemlist = g_slist_next(itemlist) ) {
if (!size_clipboard) {
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to paste size to."));
if ( !size_clipboard ) {
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to paste size to."));
void sp_selection_to_next_layer ()
dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to move to the layer above."));
if (next) {
sp_selection_copy_impl (items, &temp_clip, NULL, NULL, sp_document_repr_doc(dt->doc())); // we're in the same doc, so no need to copy defs
next=Inkscape::next_layer(dt->currentRoot(), dt->currentLayer()); // Fixes bug 1482973: crash while moving layers
if(next) {
no_more = true;
no_more = true;
if (no_more) {
void sp_selection_to_prev_layer ()
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, NULL, NULL, 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;
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, NR::Matrix const &affine, bool set_i2d)
bool transform_textpath_with_path = (SP_IS_TEXT_TEXTPATH(item) && selection->includes( sp_textpath_get_path_item (SP_TEXTPATH(sp_object_first_child(SP_OBJECT(item)))) ));
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_get_int_attribute("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
NR::Matrix t = parent_transform * matrix_to_desktop (matrix_from_desktop (affine, item), item) * parent_transform.inverse();
NR::Matrix t_inv =parent_transform * matrix_to_desktop (matrix_from_desktop (affine.inverse(), item), item) * parent_transform.inverse();
if (prefs_parallel) {
} else if (prefs_unmoved) {
if (set_i2d) {
while (l != NULL) {
l = l->next;
void sp_selection_scale_relative(Inkscape::Selection *selection, NR::Point const &align, NR::scale const &scale)
// 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, NR::Point const ¢er, gdouble const angle_degrees)
sp_selection_skew_relative(Inkscape::Selection *selection, NR::Point const &align, double dx, double dy)
void sp_selection_rotate_90_cw()
void sp_selection_rotate_90_ccw()
if (!center) {
( ( angle_degrees > 0 )
\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) {
sp_document_maybe_done(sp_desktop_document(desktop), "selector:move:vertical", SP_VERB_CONTEXT_SELECT,
} else if (dy == 0) {
sp_document_maybe_done(sp_desktop_document(desktop), "selector:move:horizontal", SP_VERB_CONTEXT_SELECT,
if (dx == 0) {
sp_document_maybe_done(sp_desktop_document(desktop), "selector:move:vertical", SP_VERB_CONTEXT_SELECT,
} else if (dy == 0) {
sp_document_maybe_done(sp_desktop_document(desktop), "selector:move:horizontal", SP_VERB_CONTEXT_SELECT,
struct Forward {
struct Reverse {
g_slist_free(i);
return list;
sp_selection_item_next(void)
PrefsSelectionContext inlayer = (PrefsSelectionContext)prefs_get_int_attribute ("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) {
sp_selection_item_prev(void)
PrefsSelectionContext inlayer = (PrefsSelectionContext)prefs_get_int_attribute ("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) {
sp_repr_set_attr(clone, "inkscape:transform-center-x", sel_repr->attribute("inkscape:transform-center-x"));
sp_repr_set_attr(clone, "inkscape:transform-center-y", sel_repr->attribute("inkscape:transform-center-y"));
// For more details, see http://developer.gnome.org/doc/API/2.0/glib/glib-I18N.html#Q-:CAPS
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) {
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to convert to marker."));
if ( !r || r->isEmpty() ) {
NR::Point move_p = NR::Point(0, sp_document_height(doc)) - (r->min() + NR::Point ((r->extent(NR::X))/2, (r->extent(NR::Y))/2));
if (apply) {
// See ActorAlign::on_button_click() in src/ui/dialog/align-and-distribute.cpp
int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
(void)mark_id;
void sp_selection_to_guides()
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."));
if ( !r || r->isEmpty() ) {
NR::Point move_p = NR::Point(0, sp_document_height(doc)) - (r->min() + NR::Point (0, r->extent(NR::Y)));
if (apply) {
// See ActorAlign::on_button_click() in src/ui/dialog/align-and-distribute.cpp
int saved_compensation = prefs_get_int_attribute("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_first_child(SP_OBJECT(pattern)) ; 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
gchar *filename = g_strdup_printf ("%s-%s-%u.png", document->name, SP_OBJECT_REPR(items->data)->attribute("id"), current);
filename = g_strcanon (filename, "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) {
if (filter) {
if (param1) {
// 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) {
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) {
for (SPObject *child = sp_object_first_child(obj) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
if (apply_clip_path)
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to fit canvas to."));
if (!dt) return;
if (layer_only) {
return clipboard;