1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * Miscellaneous operations on selected items.
6 * Lauris Kaplinski <lauris@kaplinski.com>
7 * Frank Felfe <innerspace@iname.com>
8 * MenTaLguY <mental@rydia.net>
9 * bulia byak <buliabyak@users.sf.net>
10 * Andrius R. <knutux@gmail.com>
11 * Jon A. Cruz <jon@joncruz.org>
12 * Martin Sucha <martin.sucha-inkscape@jts-sro.sk>
14 * Kris De Gussem <Kris.DeGussem@gmail.com>
15 * Tavmjong Bah <tavmjong@free.fr> (Symbol additions)
19 * Copyright (C) 1999-2016 authors
20 * Copyright (C) 2001-2002 Ximian, Inc.
22 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
25 #include "selection-chemistry.h"
27 #include <boost/range/adaptor/reversed.hpp>
29 #include <glibmm/i18n.h>
33 #include "display/control/canvas-item-drawing.h"
34 #include "actions/actions-tools.h" // Switching tools
35 #include "context-fns.h"
36 #include "desktop-style.h"
38 #include "display/cairo-utils.h"
39 #include "display/control/canvas-item-bpath.h"
40 #include "display/curve.h"
41 #include "display/drawing.h"
42 #include "document-undo.h"
44 #include "filter-chemistry.h"
45 #include "gradient-drag.h"
46 #include "helper/pixbuf-ops.h"
47 #include "layer-manager.h"
48 #include "live_effects/effect.h"
49 #include "live_effects/lpeobject.h"
50 #include "message-stack.h"
51 #include "object/box3d.h"
52 #include "object/object-set.h"
53 #include "object/persp3d.h"
54 #include "object/sp-clippath.h"
55 #include "object/sp-conn-end.h"
56 #include "object/sp-defs.h"
57 #include "object/sp-ellipse.h"
58 #include "object/sp-flowregion.h"
59 #include "object/sp-flowtext.h"
60 #include "object/sp-image.h"
61 #include "object/sp-item-transform.h"
62 #include "object/sp-item.h"
63 #include "object/sp-line.h"
64 #include "object/sp-linear-gradient.h"
65 #include "object/sp-marker.h"
66 #include "object/sp-mask.h"
67 #include "object/sp-namedview.h"
68 #include "object/sp-offset.h"
69 #include "object/sp-path.h"
70 #include "object/sp-pattern.h"
71 #include "object/sp-polyline.h"
72 #include "object/sp-radial-gradient.h"
73 #include "object/sp-rect.h"
74 #include "object/sp-root.h"
75 #include "object/sp-spiral.h"
76 #include "object/sp-star.h"
77 #include "object/sp-symbol.h"
78 #include "object/sp-textpath.h"
79 #include "object/sp-tref.h"
80 #include "object/sp-tspan.h"
81 #include "object/sp-use.h"
82 #include "path-chemistry.h"
83 #include "selection.h"
86 #include "text-chemistry.h"
87 #include "text-editing.h"
88 #include "ui/clipboard.h"
89 #include "ui/icon-names.h"
90 #include "ui/tool/control-point-selection.h"
91 #include "ui/tool/multi-path-manipulator.h"
92 #include "ui/tools/connector-tool.h"
93 #include "ui/tools/dropper-tool.h"
94 #include "ui/tools/gradient-tool.h"
95 #include "ui/tools/node-tool.h"
96 #include "ui/tools/text-tool.h"
97 #include "ui/widget/canvas.h" // is_dragging()
98 #include "xml/href-attribute-helper.h"
99 #include "xml/rebase-hrefs.h"
101 // TODO FIXME: This should be moved into preference repr
102 SPCycleType SP_CYCLING
= SP_CYCLE_FOCUS
;
104 using Inkscape::DocumentUndo
;
107 using Inkscape::UI::Tools::GradientTool
;
108 using Inkscape::UI::Tools::NodeTool
;
109 using Inkscape::UI::Tools::TextTool
;
110 using namespace Inkscape
;
112 /* The clipboard handling is in ui/clipboard.cpp now. There are some legacy functions left here,
113 because the layer manipulation code uses them. It should be rewritten specifically
117 // helper for printing error messages, regardless of whether we have a GUI or not
118 // If desktop == NULL, errors will be shown on stderr
120 selection_display_message(SPDesktop
*desktop
, Inkscape::MessageType msgType
, Glib::ustring
const &msg
)
123 desktop
->messageStack()->flash(msgType
, msg
);
125 if (msgType
== Inkscape::IMMEDIATE_MESSAGE
||
126 msgType
== Inkscape::WARNING_MESSAGE
||
127 msgType
== Inkscape::ERROR_MESSAGE
) {
128 g_printerr("%s\n", msg
.c_str());
135 void SelectionHelper::selectAll(SPDesktop
*dt
)
137 NodeTool
*nt
= dynamic_cast<NodeTool
*>(dt
->getTool());
139 if (!nt
->_multipath
->empty()) {
140 nt
->_multipath
->selectSubpaths();
144 sp_edit_select_all(dt
);
147 void SelectionHelper::selectAllInAll(SPDesktop
*dt
)
149 NodeTool
*nt
= dynamic_cast<NodeTool
*>(dt
->getTool());
151 nt
->_selected_nodes
->selectAll();
153 sp_edit_select_all_in_all_layers(dt
);
157 void SelectionHelper::selectNone(SPDesktop
*dt
)
159 NodeTool
*nt
= dynamic_cast<NodeTool
*>(dt
->getTool());
160 if (nt
&& !nt
->_selected_nodes
->empty()) {
161 nt
->_selected_nodes
->clear();
162 } else if (!dt
->getSelection()->isEmpty()) {
163 dt
->getSelection()->clear();
165 // If nothing selected switch to selection tool
166 set_active_tool(dt
, "Select");
170 void SelectionHelper::selectSameFillStroke(SPDesktop
*dt
)
172 sp_select_same_fill_stroke_style(dt
, true, true, true);
175 void SelectionHelper::selectSameFillColor(SPDesktop
*dt
)
177 sp_select_same_fill_stroke_style(dt
, true, false, false);
180 void SelectionHelper::selectSameStrokeColor(SPDesktop
*dt
)
182 sp_select_same_fill_stroke_style(dt
, false, true, false);
185 void SelectionHelper::selectSameStrokeStyle(SPDesktop
*dt
)
187 sp_select_same_fill_stroke_style(dt
, false, false, true);
190 void SelectionHelper::selectSameObjectType(SPDesktop
*dt
)
192 sp_select_same_object_type(dt
);
195 void SelectionHelper::invert(SPDesktop
*dt
)
197 NodeTool
*nt
= dynamic_cast<NodeTool
*>(dt
->getTool());
199 nt
->_multipath
->invertSelectionInSubpaths();
205 void SelectionHelper::invertAllInAll(SPDesktop
*dt
)
207 NodeTool
*nt
= dynamic_cast<NodeTool
*>(dt
->getTool());
209 nt
->_selected_nodes
->invertSelection();
211 sp_edit_invert_in_all_layers(dt
);
215 void SelectionHelper::reverse(SPDesktop
*dt
)
217 // TODO make this a virtual method of event context!
218 NodeTool
*nt
= dynamic_cast<NodeTool
*>(dt
->getTool());
220 nt
->_multipath
->reverseSubpaths();
222 dt
->getSelection()->pathReverse();
227 * Fixes the current selection, removing locked objects from it
229 void SelectionHelper::fixSelection(SPDesktop
*dt
)
235 Inkscape::Selection
*selection
= dt
->getSelection();
237 std::vector
<SPItem
*> items
;
239 auto selList
= selection
->items();
241 for (auto item
: selList
| boost::adaptors::reversed
) {
243 !dt
->layerManager().isLayer(item
) &&
246 items
.push_back(item
);
250 selection
->setList(items
);
253 } // namespace Inkscape
256 * Copies repr and its inherited css style elements, along with the accumulated transform 'full_t',
257 * then prepends the copy to 'clip'.
259 static void sp_selection_copy_one(Inkscape::XML::Node
*repr
, Geom::Affine full_t
, std::vector
<Inkscape::XML::Node
*> &clip
, Inkscape::XML::Document
* xml_doc
)
261 Inkscape::XML::Node
*copy
= repr
->duplicate(xml_doc
);
263 // copy complete inherited style
264 SPCSSAttr
*css
= sp_repr_css_attr_inherited(repr
, "style");
265 sp_repr_css_set(copy
, css
, "style");
266 sp_repr_css_attr_unref(css
);
268 // write the complete accumulated transform passed to us
269 // (we're dealing with unattached repr, so we write to its attr
270 // instead of using sp_item_set_transform)
271 copy
->setAttributeOrRemoveIfEmpty("transform", sp_svg_transform_write(full_t
));
273 clip
.insert(clip
.begin(),copy
);
276 static void sp_selection_copy_impl(std::vector
<SPItem
*> const &items
, std::vector
<Inkscape::XML::Node
*> &clip
, Inkscape::XML::Document
* xml_doc
)
279 std::vector
<SPItem
*> sorted_items(items
);
280 sort(sorted_items
.begin(),sorted_items
.end(),sp_object_compare_position_bool
);
283 for (auto item
: sorted_items
) {
285 sp_selection_copy_one(item
->getRepr(), item
->i2doc_affine(), clip
, xml_doc
);
287 g_assert_not_reached();
290 reverse(clip
.begin(),clip
.end());
293 // TODO check if parent parameter should be changed to SPItem, of if the code should handle non-items.
294 static std::vector
<Inkscape::XML::Node
*> sp_selection_paste_impl(SPDocument
*doc
, SPObject
*parent
,
295 std::vector
<Inkscape::XML::Node
*> &clip
,
296 Inkscape::XML::Node
*after
= nullptr)
298 assert(!after
|| after
->parent() == parent
->getRepr());
299 assert(!parent
->cloned
);
301 Inkscape::XML::Document
*xml_doc
= doc
->getReprDoc();
303 auto parentItem
= cast
<SPItem
>(parent
);
304 g_assert(parentItem
);
306 std::vector
<Inkscape::XML::Node
*> copied
;
307 // add objects to document
308 for (auto repr
: clip
) {
309 Inkscape::XML::Node
*copy
= repr
->duplicate(xml_doc
);
311 // premultiply the item transform by the accumulated parent transform in the paste layer
312 auto const local
= parentItem
->i2doc_affine();
313 if (!local
.isIdentity()) {
314 char const *t_str
= copy
->attribute("transform");
317 sp_svg_transform_read(t_str
, &item_t
);
319 item_t
*= local
.inverse();
320 // (we're dealing with unattached repr, so we write to its attr instead of using sp_item_set_transform)
321 copy
->setAttributeOrRemoveIfEmpty("transform", sp_svg_transform_write(item_t
));
322 if (copy
->attribute("inkscape:original-d")) {
323 copy
->setAttribute("d", copy
->attribute("inkscape:original-d"));
327 parent
->getRepr()->addChild(copy
, after
);
330 copied
.push_back(copy
);
331 Inkscape::GC::release(copy
);
336 static void sp_selection_delete_impl(std::vector
<SPItem
*> const &items
, bool propagate
= true, bool propagate_descendants
= true)
338 for (auto item
: items
) {
339 sp_object_ref(item
, nullptr);
341 for (auto item
: items
) {
342 item
->deleteObject(propagate
, propagate_descendants
);
343 sp_object_unref(item
, nullptr);
347 void ObjectSet::deleteItems(bool skip_undo
)
349 if (isEmpty() && !skip_undo
) {
350 selection_display_message(desktop(),Inkscape::WARNING_MESSAGE
, _("<b>Nothing</b> was deleted."));
354 std::vector
<SPItem
*> selected(items().begin(), items().end());
356 sp_selection_delete_impl(selected
);
362 if (SPDesktop
*dt
= desktop()) {
363 dt
->layerManager().currentLayer()->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG
);
365 /* A tool may have set up private information in its selection context
366 * that depends on desktop items. I think the only sane way to deal with
367 * this currently is to reset the tool which will reset its
368 * associated selection context. For example: deleting an object
369 * while moving it around the canvas.
370 * We copy the string so we donʼt pass reference to member of the tool that is being reset.
372 dt
->setTool(std::string
{dt
->getTool()->getPrefsPath()});
376 DocumentUndo::done(document(), _("Delete"), INKSCAPE_ICON("edit-delete"));
382 static void add_ids_recursive(std::vector
<const gchar
*> &ids
, SPObject
*obj
)
385 ids
.push_back(obj
->getId());
387 if (is
<SPGroup
>(obj
)) {
388 for (auto& child
: obj
->children
) {
389 add_ids_recursive(ids
, &child
);
395 void ObjectSet::duplicate(bool suppressDone
, bool duplicateLayer
)
397 if(duplicateLayer
&& !desktop() ){
398 //TODO: understand why layer management is tied to desktop and not to document.
402 SPDocument
*doc
= document();
407 Inkscape::XML::Document
* xml_doc
= doc
->getReprDoc();
409 // check if something is selected
410 if (isEmpty() && !duplicateLayer
) {
411 selection_display_message(desktop(),Inkscape::WARNING_MESSAGE
, _("Select <b>object(s)</b> to duplicate."));
414 std::vector
<Inkscape::XML::Node
*> reprs(xmlNodes().begin(), xmlNodes().end());
418 reprs
.push_back(desktop()->layerManager().currentLayer()->getRepr());
423 std::vector
<SPItem
*> items
;
424 for(auto old_repr
: reprs
) {
425 auto item
= cast
<SPItem
>(doc
->getObjectByRepr(old_repr
));
427 items
.push_back(item
);
428 auto lpeitem
= cast
<SPLPEItem
>(item
);
430 for (auto satellite
: lpeitem
->get_satellites(false, true, true)) {
432 auto item2
= cast
<SPItem
>(satellite
);
433 if (item2
&& std::find(items
.begin(), items
.end(), item2
) == items
.end()) {
434 items
.push_back(item2
);
441 for(auto item
: items
) {
442 if (std::find(reprs
.begin(), reprs
.end(), item
->getRepr()) == reprs
.end()) {
443 reprs
.push_back(item
->getRepr());
446 // sorting items from different parents sorts each parent's subset without possibly mixing
447 // them, just what we need
448 sort(reprs
.begin(),reprs
.end(),sp_repr_compare_position_bool
);
450 std::vector
<const gchar
*> old_ids
;
451 std::vector
<const gchar
*> new_ids
;
452 Inkscape::Preferences
*prefs
= Inkscape::Preferences::get();
453 bool relink_clones
= prefs
->getBool("/options/relinkclonesonduplicate/value");
454 const bool fork_livepatheffects
= prefs
->getBool("/options/forklpeonduplicate/value", true);
456 // check ref-d shapes, split in defs|internal|external
457 // add external & defs to reprs
458 auto text_refs
= text_categorize_refs(doc
, reprs
.begin(), reprs
.end(),
459 static_cast<text_ref_t
>(TEXT_REF_DEF
| TEXT_REF_EXTERNAL
| TEXT_REF_INTERNAL
));
460 for (auto const &ref
: text_refs
) {
461 if (ref
.second
== TEXT_REF_DEF
|| ref
.second
== TEXT_REF_EXTERNAL
) {
462 reprs
.push_back(doc
->getObjectById(ref
.first
)->getRepr());
466 std::vector
<Inkscape::XML::Node
*> copies
;
467 for(auto old_repr
: reprs
) {
468 Inkscape::XML::Node
*parent
= old_repr
->parent();
469 Inkscape::XML::Node
*copy
= old_repr
->duplicate(xml_doc
);
471 if (!duplicateLayer
|| sp_repr_is_def(old_repr
)) {
472 parent
->appendChild(copy
);
473 } else if (sp_repr_is_layer(old_repr
)) {
474 parent
->addChild(copy
, old_repr
);
476 // duplicateLayer, non-layer, non-def
477 // external nodes -- append to new layer
478 // text_relink will ignore extra nodes in layer children
479 copies
[0]->appendChild(copy
);
481 SPObject
*old_obj
= doc
->getObjectByRepr(old_repr
);
482 SPObject
*new_obj
= doc
->getObjectByRepr(copy
);
483 if (old_obj
&& new_obj
) {
484 old_obj
->setTmpSuccessor(new_obj
);
487 add_ids_recursive(old_ids
, old_obj
);
488 add_ids_recursive(new_ids
, new_obj
);
491 copies
.push_back(copy
);
492 Inkscape::GC::release(copy
);
495 // Relink copied text nodes to copied reference shapes
496 text_relink_refs(text_refs
, reprs
.begin(), reprs
.end(), copies
.begin());
498 // copies contains def nodes, we don't want that in our selection
499 std::vector
<Inkscape::XML::Node
*> newsel
;
500 if (!duplicateLayer
) {
501 // compute newsel, by removing def nodes from copies
502 for (auto node
: copies
) {
503 // hide on duple this is done to dont show autoselected hidden LPE items satellites
504 // is only a make up if at any point we think is better keep selected items reselected on duple
505 // please roll back or make some more loops to handle well, keep as it for speed
507 auto itm
= cast
<SPItem
>(doc
->getObjectByRepr(node
));
508 if (!sp_repr_is_def(node
) && (!itm
|| !itm
->isHidden())) {
509 newsel
.push_back(node
);
516 g_assert(old_ids
.size() == new_ids
.size());
518 for (unsigned int i
= 0; i
< old_ids
.size(); i
++) {
519 const gchar
*id
= old_ids
[i
];
520 SPObject
*old_clone
= doc
->getObjectById(id
);
521 auto use
= cast
<SPUse
>(old_clone
);
522 auto offset
= cast
<SPOffset
>(old_clone
);
523 auto text
= cast
<SPText
>(old_clone
);
524 auto path
= cast
<SPPath
>(old_clone
);
526 SPItem
*orig
= use
->get_original();
527 if (!orig
) // orphaned
529 for (unsigned int j
= 0; j
< old_ids
.size(); j
++) {
530 if (!strcmp(orig
->getId(), old_ids
[j
])) {
531 // we have both orig and clone in selection, relink
532 // std::cout << id << " old, its ori: " << orig->getId() << "; will relink:" << new_ids[i] << " to " << new_ids[j] << "\n";
533 SPObject
*new_clone
= doc
->getObjectById(new_ids
[i
]);
534 new_clone
->setAttribute("xlink:href", Glib::ustring("#") + new_ids
[j
]);
535 new_clone
->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG
);
539 gchar
*source_href
= offset
->sourceHref
;
540 for (guint j
= 0; j
< old_ids
.size(); j
++) {
541 if (source_href
&& source_href
[0]=='#' && !strcmp(source_href
+1, old_ids
[j
])) {
542 doc
->getObjectById(new_ids
[i
])->setAttribute("xlink:href", Glib::ustring("#") + new_ids
[j
]);
546 auto textpath
= cast
<SPTextPath
>(text
->firstChild());
547 if (!textpath
) continue;
548 const gchar
*source_href
= sp_textpath_get_path_item(textpath
)->getId();
549 for (guint j
= 0; j
< old_ids
.size(); j
++) {
550 if (!strcmp(source_href
, old_ids
[j
])) {
551 doc
->getObjectById(new_ids
[i
])->firstChild()->setAttribute("xlink:href", Glib::ustring("#") + new_ids
[j
]);
555 if (old_clone
->getAttribute("inkscape:connection-start") != nullptr) {
556 const char *old_start
= old_clone
->getAttribute("inkscape:connection-start");
557 const char *old_end
= old_clone
->getAttribute("inkscape:connection-end");
558 SPObject
*new_clone
= doc
->getObjectById(new_ids
[i
]);
559 for (guint j
= 0; j
< old_ids
.size(); j
++) {
560 if(old_start
== Glib::ustring("#") + old_ids
[j
]) {
561 new_clone
->setAttribute("inkscape:connection-start", Glib::ustring("#") + new_ids
[j
]);
563 if(old_end
== Glib::ustring("#") + old_ids
[j
]) {
564 new_clone
->setAttribute("inkscape:connection-end", Glib::ustring("#") + new_ids
[j
]);
571 for (auto node
: copies
) {
572 if (fork_livepatheffects
) {
573 SPObject
*new_obj
= doc
->getObjectByRepr(node
);
574 auto newLPEObj
= cast
<SPLPEItem
>(new_obj
);
577 newLPEObj
->forkPathEffectsIfNecessary(1, true, true);
578 sp_lpe_item_update_patheffect(newLPEObj
, false, true, true);
582 for(auto old_repr
: reprs
) {
583 SPObject
*old_obj
= doc
->getObjectByRepr(old_repr
);
585 old_obj
->fixTmpSuccessors();
586 old_obj
->unsetTmpSuccessor();
590 if (!duplicateLayer
) {
592 if ( !suppressDone
) {
593 DocumentUndo::done(document(), _("Duplicate"), INKSCAPE_ICON("edit-duplicate"));
596 if ( !suppressDone
) {
597 DocumentUndo::done(document(), _("Duplicate"), INKSCAPE_ICON("edit-duplicate"));
599 SPObject
* new_layer
= doc
->getObjectByRepr(copies
[0]);
601 if (auto label
= new_layer
->label()) {
602 if (std::string(label
).find("copy") == std::string::npos
) {
603 gchar
* name
= g_strdup_printf(_("%s copy"), label
);
604 desktop()->layerManager().renameLayer(new_layer
, name
, true);
612 void sp_edit_clear_all(Inkscape::Selection
*selection
)
617 auto desktop
= selection
->desktop();
618 SPDocument
*doc
= desktop
->getDocument();
621 auto group
= desktop
->layerManager().currentLayer();
622 g_return_if_fail(group
);
623 std::vector
<SPItem
*> items
= group
->item_list();
625 for(auto & item
: items
){
626 item
->deleteObject();
629 DocumentUndo::done(doc
, _("Delete all"), "");
633 * Return a list of SPItems that are the children of 'list'
635 * list - source list of items to search in
636 * desktop - desktop associated with the source list
637 * exclude - list of items to exclude from result
638 * onlyvisible - TRUE includes only items visible on canvas
639 * onlysensitive - TRUE includes only non-locked items
640 * ingroups - TRUE to recursively get grouped items children
642 static void get_all_items_recursive(std::vector
<SPItem
*> &list
, SPObject
*from
, SPDesktop
*desktop
, bool onlyvisible
, bool onlysensitive
, bool ingroups
, std::vector
<SPItem
*> const &exclude
)
644 for (auto &child
: from
->children
) {
645 auto item
= cast
<SPItem
>(&child
);
647 !desktop
->layerManager().isLayer(item
) &&
648 (!onlysensitive
|| !item
->isLocked()) &&
649 (!onlyvisible
|| !desktop
->itemIsHidden(item
)) &&
650 (exclude
.empty() || std::find(exclude
.begin(), exclude
.end(), &child
) == exclude
.end()))
652 list
.emplace_back(item
);
655 if (ingroups
|| (item
&& desktop
->layerManager().isLayer(item
))) {
656 get_all_items_recursive(list
, &child
, desktop
, onlyvisible
, onlysensitive
, ingroups
, exclude
);
661 std::vector
<SPItem
*> get_all_items(SPObject
*from
, SPDesktop
*desktop
, bool onlyvisible
, bool onlysensitive
, bool ingroups
, std::vector
<SPItem
*> const &exclude
)
663 std::vector
<SPItem
*> list
;
664 get_all_items_recursive(list
, from
, desktop
, onlyvisible
, onlysensitive
, ingroups
, exclude
);
665 std::reverse(list
.begin(), list
.end()); // Todo: For compatibility; is it necessary?
669 static void sp_edit_select_all_full(SPDesktop
*dt
, bool force_all_layers
, bool invert
)
674 Inkscape::Selection
*selection
= dt
->getSelection();
676 auto layer
= dt
->layerManager().currentLayer();
677 g_return_if_fail(layer
);
679 Inkscape::Preferences
*prefs
= Inkscape::Preferences::get();
680 PrefsSelectionContext inlayer
= (PrefsSelectionContext
) prefs
->getInt("/options/kbselection/inlayer", PREFS_SELECTION_LAYER
);
681 bool onlyvisible
= prefs
->getBool("/options/kbselection/onlyvisible", true);
682 bool onlysensitive
= prefs
->getBool("/options/kbselection/onlysensitive", true);
684 std::vector
<SPItem
*> items
;
686 std::vector
<SPItem
*> exclude
;
688 exclude
.insert(exclude
.end(), selection
->items().begin(), selection
->items().end());
691 if (force_all_layers
)
692 inlayer
= PREFS_SELECTION_ALL
;
695 case PREFS_SELECTION_LAYER
: {
696 if ((onlysensitive
&& layer
->isLocked()) || (onlyvisible
&& dt
->itemIsHidden(layer
))) {
700 for (auto item
: layer
->item_list() | boost::adaptors::reversed
) {
701 if (item
&& (!onlysensitive
|| !item
->isLocked())) {
702 if (!onlyvisible
|| !dt
->itemIsHidden(item
)) {
703 if (!dt
->layerManager().isLayer(item
)) {
704 if (!invert
|| exclude
.end() == std::find(exclude
.begin(),exclude
.end(),item
)) {
705 items
.push_back(item
); // leave it in the list
714 case PREFS_SELECTION_LAYER_RECURSIVE
: {
715 items
= get_all_items(dt
->layerManager().currentLayer(), dt
, onlyvisible
, onlysensitive
, FALSE
, exclude
);
719 items
= get_all_items(dt
->layerManager().currentRoot(), dt
, onlyvisible
, onlysensitive
, FALSE
, exclude
);
724 selection
->setList(items
);
727 void sp_edit_select_all(SPDesktop
*desktop
)
729 sp_edit_select_all_full(desktop
, false, false);
732 void sp_edit_select_all_in_all_layers(SPDesktop
*desktop
)
734 sp_edit_select_all_full(desktop
, true, false);
737 void sp_edit_invert(SPDesktop
*desktop
)
739 sp_edit_select_all_full(desktop
, false, true);
742 void sp_edit_invert_in_all_layers(SPDesktop
*desktop
)
744 sp_edit_select_all_full(desktop
, true, true);
747 Inkscape::XML::Node
* ObjectSet::group(bool is_anchor
) {
748 SPDocument
*doc
= document();
752 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE
, _("Select <b>some objects</b> to group."));
755 Inkscape::XML::Document
*xml_doc
= doc
->getReprDoc();
756 Inkscape::XML::Node
*group
= xml_doc
->createElement(is_anchor
? "svg:a" : "svg:g");
758 std::vector
<Inkscape::XML::Node
*> p(xmlNodes().begin(), xmlNodes().end());
759 std::sort(p
.begin(), p
.end(), sp_repr_compare_position_bool
);
762 // Remember the position and parent of the topmost object.
763 Inkscape::XML::Node
*topmost
= p
.back();
764 Inkscape::XML::Node
*topmost_parent
= topmost
->parent();
766 // Find the topmost object first
767 for(auto current
: p
){
768 if (current
->parent() == topmost_parent
) {
769 if (current
->position() > topmost
->position()) {
774 // Add as close to the top as we can get it
775 topmost_parent
->addChild(group
, topmost
);
777 for(auto current
: p
){
778 if (current
->parent() == topmost_parent
) {
780 Inkscape::XML::Node
*spnew
= current
->duplicate(xml_doc
);
781 sp_repr_unparent(current
);
782 group
->appendChild(spnew
);
783 Inkscape::GC::release(spnew
);
785 } else { // move it to topmost_parent first
786 std::vector
<Inkscape::XML::Node
*> temp_clip
;
788 // At this point, current may already have no item, due to its being a clone whose original is already moved away
789 // So we copy it artificially calculating the transform from its repr->attr("transform") and the parent transform
790 gchar
const *t_str
= current
->attribute("transform");
791 Geom::Affine
item_t(Geom::identity());
793 sp_svg_transform_read(t_str
, &item_t
);
794 auto parent_item
= cast
<SPItem
>(doc
->getObjectByRepr(current
->parent()));
796 item_t
*= parent_item
->i2doc_affine();
797 // FIXME: when moving both clone and original from a transformed group (either by
798 // grouping into another parent, or by cut/paste) the transform from the original's
799 // parent becomes embedded into original itself, and this affects its clones. Fix
800 // this by remembering the transform diffs we write to each item into an array and
801 // then, if this is clone, looking up its original in that array and pre-multiplying
802 // it by the inverse of that original's transform diff.
804 sp_selection_copy_one(current
, item_t
, temp_clip
, xml_doc
);
805 sp_repr_unparent(current
);
807 // paste into topmost_parent (temporarily)
808 std::vector
<Inkscape::XML::Node
*> copied
= sp_selection_paste_impl(doc
, doc
->getObjectByRepr(topmost_parent
), temp_clip
);
809 if (!temp_clip
.empty())temp_clip
.clear() ;
810 if (!copied
.empty()) { // if success,
811 // take pasted object (now in topmost_parent)
812 Inkscape::XML::Node
*in_topmost
= copied
.back();
814 Inkscape::XML::Node
*spnew
= in_topmost
->duplicate(xml_doc
);
816 sp_repr_unparent(in_topmost
);
817 // put its copy into group
818 group
->appendChild(spnew
);
819 Inkscape::GC::release(spnew
);
825 set(doc
->getObjectByRepr(group
));
830 void ObjectSet::popFromGroup(){
832 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE
, _("<b>No objects selected</b> to pop out of group."));
836 std::set
<SPObject
*> grandparents
;
838 for (auto *obj
: items()) {
839 auto parent_group
= cast
<SPGroup
>(obj
->parent
);
840 if (!parent_group
|| !parent_group
->parent
|| SP_IS_LAYER(parent_group
)) {
841 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE
, _("Selection <b>not in a group</b>."));
844 grandparents
.insert(parent_group
->parent
);
847 assert(!grandparents
.empty());
849 if (grandparents
.size() > 1) {
850 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE
,
851 _("Objects in selection must have the same grandparents."));
855 toLayer(*grandparents
.begin());
858 DocumentUndo::done(document(), _("Pop selection from group"), INKSCAPE_ICON("object-ungroup-pop-selection"));
863 * Finds the first clone in `objects` which references an item in `groups`.
864 * The search is recursive, the children of `objects` are searched as well.
865 * Return NULL if no such clone is found.
867 template <typename Objects
>
868 static SPUse
*find_clone_to_group(Objects
const &objects
, std::set
<SPGroup
*> const &groups
)
870 assert(!groups
.count(nullptr));
872 for (auto *obj
: objects
) {
873 if (auto *use
= cast
<SPUse
>(obj
)) {
874 if (auto root
= use
->root()) {
875 if (groups
.count(cast_unsafe
<SPGroup
>(root
->clone_original
))) {
881 if (auto *use
= find_clone_to_group(obj
->childList(false), groups
)) {
890 * Ungroup all groups in an object set.
892 * Clones of ungrouped groups will be unlinked.
894 * Children of groups will not be ungrouped (operation is not recursive).
896 * Unlinked clones and children of ungrouped groups will be added to the object set.
898 static void ungroup_impl(ObjectSet
*set
)
900 std::set
<SPGroup
*> const groups(set
->groups().begin(), set
->groups().end());
902 while (auto *use
= find_clone_to_group(set
->items(), groups
)) {
903 bool const readd
= set
->includes(use
);
904 auto const unlinked
= use
->unlink();
906 set
->add(unlinked
, true);
910 std::vector
<SPItem
*> children
;
912 for (auto *group
: groups
) {
913 sp_item_group_ungroup(group
, children
);
916 set
->addList(children
);
919 void ObjectSet::ungroup(bool skip_undo
)
923 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE
, _("Select a <b>group</b> to ungroup."));
927 if (boost::distance(groups()) == 0) {
929 selection_display_message(desktop(), Inkscape::ERROR_MESSAGE
, _("<b>No groups</b> to ungroup in the selection."));
934 if(document() && !skip_undo
)
935 DocumentUndo::done(document(), _("Ungroup"), INKSCAPE_ICON("object-ungroup"));
939 * Keep ungrouping until there are no more groups.
941 void ObjectSet::ungroup_all(bool skip_undo
)
943 std::size_t last
= 0;
944 while (size() != last
) {
950 /** If items in the list have a common parent, return it, otherwise return NULL */
952 sp_item_list_common_parent_group(const SPItemRange
&items
)
957 SPObject
*parent
= items
.front()->parent
;
958 // Strictly speaking this CAN happen, if user selects <svg> from Inkscape::XML editor
959 if (!is
<SPGroup
>(parent
)) {
962 for (auto item
: items
) {
963 if (item
== items
.front()) {
966 if (item
->parent
!= parent
) {
971 return cast
<SPGroup
>(parent
);
974 /** Finds out the minimum common bbox of the selected items. */
976 enclose_items(std::vector
<SPItem
*> const &items
)
978 g_assert(!items
.empty());
981 for (auto item
: items
) {
982 r
.unionWith(item
->documentVisualBounds());
987 // TODO determine if this is intentionally different from SPObject::getPrev()
988 static SPObject
*prev_sibling(SPObject
*child
)
990 SPObject
*prev
= nullptr;
991 if ( child
&& cast
<SPGroup
>(child
->parent
) ) {
992 prev
= child
->getPrev();
997 void ObjectSet::raise(bool skip_undo
){
1000 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE
, _("Select <b>object(s)</b> to raise."));
1004 SPGroup
const *group
= sp_item_list_common_parent_group(items());
1007 selection_display_message(desktop(), Inkscape::ERROR_MESSAGE
, _("You cannot raise/lower objects from <b>different groups</b> or <b>layers</b>."));
1011 std::vector
<SPItem
*> items_copy(items().begin(), items().end());
1012 Inkscape::XML::Node
*grepr
= const_cast<Inkscape::XML::Node
*>(items_copy
.front()->parent
->getRepr());
1014 /* Construct reverse-ordered list of selected children. */
1015 std::vector
<SPItem
*> rev(items_copy
);
1016 sort(rev
.begin(),rev
.end(),sp_item_repr_compare_position_bool
);
1018 // Determine the common bbox of the selected items.
1019 Geom::OptRect selected
= enclose_items(items_copy
);
1021 // Iterate over all objects in the selection (starting from top).
1023 for (auto child
: rev
) {
1024 // for each selected object, find the next sibling
1025 for (SPObject
*newref
= child
->getNext(); newref
; newref
= newref
->getNext()) {
1026 // if the sibling is an item AND overlaps our selection,
1027 auto newItem
= cast
<SPItem
>(newref
);
1029 Geom::OptRect newref_bbox
= newItem
->documentVisualBounds();
1030 if ( newref_bbox
&& selected
->intersects(*newref_bbox
) ) {
1031 // AND if it's not one of our selected objects,
1032 if ( std::find(items_copy
.begin(),items_copy
.end(),newref
)==items_copy
.end()) {
1033 // move the selected object after that sibling
1034 grepr
->changeOrder(child
->getRepr(), newref
->getRepr());
1042 if (document() && !skip_undo
) {
1043 DocumentUndo::done(document(), C_("Undo action", "Raise"), INKSCAPE_ICON("selection-raise"));
1048 void ObjectSet::raiseToTop(bool skip_undo
) {
1050 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE
, _("Select <b>object(s)</b> to raise."));
1054 SPGroup
const *group
= sp_item_list_common_parent_group(items());
1056 selection_display_message(desktop(), Inkscape::ERROR_MESSAGE
, _("You cannot raise/lower objects from <b>different groups</b> or <b>layers</b>."));
1060 std::vector
<Inkscape::XML::Node
*> rl(xmlNodes().begin(), xmlNodes().end());
1061 sort(rl
.begin(),rl
.end(),sp_repr_compare_position_bool
);
1063 for (auto repr
: rl
) {
1064 repr
->setPosition(-1);
1066 if (document() && !skip_undo
) {
1067 DocumentUndo::done(document(), _("Raise to top"), INKSCAPE_ICON("selection-top"));
1071 void ObjectSet::lower(bool skip_undo
)
1074 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE
, _("Select <b>object(s)</b> to lower."));
1078 SPGroup
const *group
= sp_item_list_common_parent_group(items());
1080 selection_display_message(desktop(), Inkscape::ERROR_MESSAGE
, _("You cannot raise/lower objects from <b>different groups</b> or <b>layers</b>."));
1084 std::vector
<SPItem
*> items_copy(items().begin(), items().end());
1085 Inkscape::XML::Node
*grepr
= const_cast<Inkscape::XML::Node
*>(items_copy
.front()->parent
->getRepr());
1087 // Determine the common bbox of the selected items.
1088 Geom::OptRect selected
= enclose_items(items_copy
);
1090 /* Construct direct-ordered list of selected children. */
1091 std::vector
<SPItem
*> rev(items_copy
);
1092 sort(rev
.begin(),rev
.end(),sp_item_repr_compare_position_bool
);
1094 // Iterate over all objects in the selection (starting from top).
1096 for (auto child
: rev
| boost::adaptors::reversed
) {
1097 // for each selected object, find the prev sibling
1098 for (SPObject
*newref
= prev_sibling(child
); newref
; newref
= prev_sibling(newref
)) {
1099 // if the sibling is an item AND overlaps our selection,
1100 auto newItem
= cast
<SPItem
>(newref
);
1102 Geom::OptRect ref_bbox
= newItem
->documentVisualBounds();
1103 if ( ref_bbox
&& selected
->intersects(*ref_bbox
) ) {
1104 // AND if it's not one of our selected objects,
1105 if (std::find(items_copy
.begin(), items_copy
.end(), newref
) == items_copy
.end()) {
1106 // move the selected object before that sibling
1107 if (auto put_after
= prev_sibling(newref
))
1108 grepr
->changeOrder(child
->getRepr(), put_after
->getRepr());
1110 child
->getRepr()->setPosition(0);
1118 if(document() && !skip_undo
)
1119 DocumentUndo::done(document(), C_("Undo action", "Lower"), INKSCAPE_ICON("selection-lower"));
1123 void ObjectSet::lowerToBottom(bool skip_undo
)
1128 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE
, _("Select <b>object(s)</b> to lower to bottom."));
1132 SPGroup
const *group
= sp_item_list_common_parent_group(items());
1134 selection_display_message(desktop(), Inkscape::ERROR_MESSAGE
, _("You cannot raise/lower objects from <b>different groups</b> or <b>layers</b>."));
1138 std::vector
<Inkscape::XML::Node
*> rl(xmlNodes().begin(), xmlNodes().end());
1139 sort(rl
.begin(),rl
.end(),sp_repr_compare_position_bool
);
1141 for (auto const repr
: rl
| boost::adaptors::reversed
) {
1143 auto const pp
= document()->getObjectByRepr(repr
->parent());
1144 g_assert(is
<SPGroup
>(pp
));
1145 for (auto &pc
: pp
->children
) {
1146 if (is
<SPItem
>(&pc
)) {
1151 repr
->setPosition(minpos
);
1153 if (document() && !skip_undo
) {
1154 DocumentUndo::done(document(), _("Lower to bottom"), INKSCAPE_ICON("selection-bottom"));
1158 void ObjectSet::stackUp(bool skip_undo
) {
1160 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE
, _("Select <b>object(s)</b> to stack up."));
1164 std::vector
<SPItem
*> selection(items().begin(), items().end());
1165 sort(selection
.begin(), selection
.end(), sp_item_repr_compare_position_bool
);
1167 for (auto item
: selection
| boost::adaptors::reversed
) {
1168 if (!item
->raiseOne()) { // stop if top was reached
1169 if(document() && !skip_undo
)
1170 DocumentUndo::cancel(document());
1171 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE
, _("We hit top."));
1176 if(document() && !skip_undo
)
1177 DocumentUndo::done(document(), C_("Undo action", "stack up"), INKSCAPE_ICON("layer-raise"));
1180 void ObjectSet::stackDown(bool skip_undo
) {
1182 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE
, _("Select <b>object(s)</b> to stack down."));
1186 std::vector
<SPItem
*> selection(items().begin(), items().end());
1187 sort(selection
.begin(), selection
.end(), sp_item_repr_compare_position_bool
);
1189 for (auto item
: selection
) {
1190 if (!item
->lowerOne()) { // stop if bottom was reached
1191 if(document() && !skip_undo
)
1192 DocumentUndo::cancel(document());
1193 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE
, _("We hit bottom."));
1198 if (document() && !skip_undo
) {
1199 DocumentUndo::done(document(), C_("Undo action", "stack down"), INKSCAPE_ICON("layer-lower"));
1203 void ObjectSet::cut()
1207 // Text and Node tools have their own CUT responses instead of deleteItems
1208 if (auto text_tool
= dynamic_cast<TextTool
*>(_desktop
->getTool())) {
1209 if (text_tool
->deleteSelection()) {
1210 DocumentUndo::done(desktop()->getDocument(), _("Cut text"), INKSCAPE_ICON("draw-text"));
1215 auto node_tool
= dynamic_cast<Inkscape::UI::Tools::NodeTool
*>(desktop()->getTool());
1216 if (node_tool
&& node_tool
->_selected_nodes
) {
1217 auto prefs
= Preferences::get();
1218 // This takes care of undo internally
1219 node_tool
->_multipath
->deleteNodes(prefs
->getBool("/tools/nodes/delete_preserves_shape", true));
1230 take_style_from_item(SPObject
*object
)
1233 // This function should only take SPItems, but currently SPString is not an Item.
1235 // write the complete cascaded style, context-free
1236 SPCSSAttr
*css
= sp_css_attr_from_object(object
, SP_STYLE_FLAG_ALWAYS
);
1240 if ((is
<SPGroup
>(object
) && object
->firstChild()) ||
1241 (is
<SPText
>(object
) && object
->firstChild() && !object
->firstChild()->getNext())) {
1242 // if this is a text with exactly one tspan child, merge the style of that tspan as well
1243 // If this is a group, merge the style of its topmost (last) child with style
1244 auto list
= object
->children
| boost::adaptors::reversed
;
1245 for (auto& element
: list
) {
1246 if (element
.style
) {
1247 SPCSSAttr
*temp
= sp_css_attr_from_object(&element
, SP_STYLE_FLAG_IFSET
);
1249 sp_repr_css_merge(css
, temp
);
1250 sp_repr_css_attr_unref(temp
);
1257 // Remove black-listed properties (those that should not be used in a default style)
1258 css
= sp_css_attr_unset_blacklist(css
);
1260 if (!(is
<SPText
>(object
) || is
<SPTSpan
>(object
) || is
<SPTRef
>(object
) || is
<SPString
>(object
))) {
1261 // do not copy text properties from non-text objects, it's confusing
1262 css
= sp_css_attr_unset_text(css
);
1266 auto item
= cast
<SPItem
>(object
);
1268 // FIXME: also transform gradient/pattern fills, by forking? NO, this must be nondestructive
1269 double ex
= item
->i2doc_affine().descrim();
1271 css
= sp_css_attr_scale(css
, ex
);
1278 void ObjectSet::copy()
1280 Inkscape::UI::ClipboardManager
*cm
= Inkscape::UI::ClipboardManager::get();
1284 void sp_selection_paste(SPDesktop
*desktop
, bool in_place
, bool on_page
)
1286 Inkscape::UI::ClipboardManager
*cm
= Inkscape::UI::ClipboardManager::get();
1287 if (cm
->paste(desktop
, in_place
, on_page
)) {
1288 DocumentUndo::done(desktop
->getDocument(), _("Paste"), INKSCAPE_ICON("edit-paste"));
1292 void ObjectSet::pasteStyle()
1294 Inkscape::UI::ClipboardManager
*cm
= Inkscape::UI::ClipboardManager::get();
1295 if (cm
->pasteStyle(this)) {
1296 DocumentUndo::done(document(), _("Paste style"), INKSCAPE_ICON("edit-paste-style"));
1300 void ObjectSet::pastePathEffect()
1302 Inkscape::UI::ClipboardManager
*cm
= Inkscape::UI::ClipboardManager::get();
1303 if (cm
->pastePathEffect(this)) {
1304 DocumentUndo::done(document(), _("Paste live path effect"), "");
1309 static void sp_selection_remove_livepatheffect_impl(SPItem
*item
)
1311 if ( auto lpeitem
= cast
<SPLPEItem
>(item
) ) {
1312 if ( lpeitem
->hasPathEffect() ) {
1313 lpeitem
->removeAllPathEffects(false);
1318 void ObjectSet::removeLPE()
1321 // check if something is selected
1324 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE
, _("Select <b>object(s)</b> to remove live path effects from."));
1328 for (auto itemlist
=list
.begin();itemlist
!=list
.end();++itemlist
) {
1329 SPItem
*item
= *itemlist
;
1331 sp_selection_remove_livepatheffect_impl(item
);
1336 DocumentUndo::done(document(), _("Remove live path effect"), "");
1340 void ObjectSet::removeFilter()
1342 // check if something is selected
1345 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE
, _("Select <b>object(s)</b> to remove filters from."));
1349 SPCSSAttr
*css
= sp_repr_css_attr_new();
1350 sp_repr_css_unset_property(css
, "filter");
1351 if (SPDesktop
*d
= desktop()) {
1352 sp_desktop_set_style(this, desktop(), css
);
1353 // Refreshing the current tool (by switching to same tool)
1354 // will refresh tool's private information in it's selection context that
1355 // depends on desktop items.
1356 set_active_tool (d
, get_active_tool(d
));
1358 auto list
= items();
1359 for (auto itemlist
=list
.begin();itemlist
!=list
.end();++itemlist
) {
1360 sp_desktop_apply_css_recursive(*itemlist
, css
, true);
1363 sp_repr_css_attr_unref(css
);
1365 DocumentUndo::done(document(), _("Remove filter"), "");
1370 void ObjectSet::pasteSize(bool apply_x
, bool apply_y
)
1372 Inkscape::UI::ClipboardManager
*cm
= Inkscape::UI::ClipboardManager::get();
1373 if (cm
->pasteSize(this, false, apply_x
, apply_y
)) {
1374 DocumentUndo::done(document(), _("Paste size"), INKSCAPE_ICON("edit-paste-size"));
1378 void ObjectSet::pasteSizeSeparately(bool apply_x
, bool apply_y
)
1380 Inkscape::UI::ClipboardManager
*cm
= Inkscape::UI::ClipboardManager::get();
1381 if (cm
->pasteSize(this, true, apply_x
, apply_y
)) {
1382 DocumentUndo::done(document(), _("Paste size separately"), INKSCAPE_ICON("edit-paste-size-separately"));
1387 * Ensures that the clones of objects are not modified when moving objects between layers.
1388 * Calls the same function as ungroup
1390 void sp_selection_change_layer_maintain_clones(std::vector
<SPItem
*> const &items
,SPObject
*where
)
1392 for (auto item
: items
) {
1394 auto oldparent
= cast
<SPItem
>(item
->parent
);
1395 auto newparent
= cast
<SPItem
>(where
);
1396 sp_item_group_ungroup_handle_clones(item
,
1397 (oldparent
->i2doc_affine())
1398 *((newparent
->i2doc_affine()).inverse()));
1403 void ObjectSet::toNextLayer(bool skip_undo
)
1408 SPDesktop
*dt
=desktop(); //TODO make it desktop-independent
1410 // check if something is selected
1412 dt
->messageStack()->flash(Inkscape::WARNING_MESSAGE
, _("Select <b>object(s)</b> to move to the layer above."));
1416 std::vector
<SPItem
*> items_copy(items().begin(), items().end());
1418 bool no_more
= false; // Set to true, if no more layers above
1419 SPObject
*next
=Inkscape::next_layer(dt
->layerManager().currentRoot(), dt
->layerManager().currentLayer());
1422 sp_selection_change_layer_maintain_clones(items_copy
,next
);
1423 std::vector
<Inkscape::XML::Node
*> temp_clip
;
1424 sp_selection_copy_impl(items_copy
, temp_clip
, dt
->doc()->getReprDoc());
1425 sp_selection_delete_impl(items_copy
, false, false);
1426 next
=Inkscape::next_layer(dt
->layerManager().currentRoot(), dt
->layerManager().currentLayer()); // Fixes bug 1482973: crash while moving layers
1427 std::vector
<Inkscape::XML::Node
*> copied
;
1429 copied
= sp_selection_paste_impl(dt
->getDocument(), next
, temp_clip
);
1431 copied
= sp_selection_paste_impl(dt
->getDocument(), dt
->layerManager().currentLayer(), temp_clip
);
1434 setReprList(copied
);
1435 if (next
) dt
->layerManager().setCurrentLayer(next
);
1437 DocumentUndo::done(dt
->getDocument(), _("Raise to next layer"), INKSCAPE_ICON("selection-move-to-layer-above"));
1444 dt
->messageStack()->flash(Inkscape::WARNING_MESSAGE
, _("No more layers above."));
1449 void ObjectSet::toPrevLayer(bool skip_undo
)
1454 SPDesktop
*dt
=desktop(); //TODO make it desktop-independent
1456 // check if something is selected
1458 dt
->messageStack()->flash(Inkscape::WARNING_MESSAGE
, _("Select <b>object(s)</b> to move to the layer below."));
1462 std::vector
<SPItem
*> items_copy(items().begin(), items().end());
1464 bool no_more
= false; // Set to true, if no more layers below
1465 SPObject
*next
=Inkscape::previous_layer(dt
->layerManager().currentRoot(), dt
->layerManager().currentLayer());
1468 sp_selection_change_layer_maintain_clones(items_copy
,next
);
1469 std::vector
<Inkscape::XML::Node
*> temp_clip
;
1470 sp_selection_copy_impl(items_copy
, temp_clip
, dt
->doc()->getReprDoc()); // we're in the same doc, so no need to copy defs
1471 sp_selection_delete_impl(items_copy
, false, false);
1472 next
=Inkscape::previous_layer(dt
->layerManager().currentRoot(), dt
->layerManager().currentLayer()); // Fixes bug 1482973: crash while moving layers
1473 std::vector
<Inkscape::XML::Node
*> copied
;
1475 copied
= sp_selection_paste_impl(dt
->getDocument(), next
, temp_clip
);
1477 copied
= sp_selection_paste_impl(dt
->getDocument(), dt
->layerManager().currentLayer(), temp_clip
);
1480 setReprList( copied
);
1481 if (next
) dt
->layerManager().setCurrentLayer(next
);
1483 DocumentUndo::done(dt
->getDocument(), _("Lower to previous layer"), INKSCAPE_ICON("selection-move-to-layer-below"));
1490 dt
->messageStack()->flash(Inkscape::WARNING_MESSAGE
, _("No more layers below."));
1495 * Move selection to group `moveto`, after the last child of `moveto` (if it has any children).
1497 * @param moveto Layer to move to
1498 * @param skip_undo Don't call DocumentUndo::done
1500 * @pre moveto is of type SPItem (or even SPGroup?)
1502 void ObjectSet::toLayer(SPObject
*moveto
)
1507 if (!moveto
|| !moveto
->getRepr()) {
1508 g_warning("%s moveto is NULL", __func__
);
1509 g_assert_not_reached();
1513 toLayer(moveto
, moveto
->getRepr()->lastChild());
1517 * Move selection to group `moveto`, after child `after`.
1519 void ObjectSet::toLayer(SPObject
*moveto
, Inkscape::XML::Node
*after
)
1522 assert(!after
|| after
->parent() == moveto
->getRepr());
1525 SPDesktop
*dt
= desktop();
1527 // check if something is selected
1530 dt
->messageStack()->flash(Inkscape::WARNING_MESSAGE
, _("Select <b>object(s)</b> to move."));
1534 /* Make sure after is not in the selected group.
1535 * Iterate after's siblings backwards, finding the nearest that
1536 * isn't selected. This is important for positioning in the layer.
1538 while (after
&& includes(after
)) {
1539 after
= after
->prev();
1542 std::vector
<SPItem
*> items_copy(items().begin(), items().end());
1546 sp_selection_change_layer_maintain_clones(items_copy
,moveto
);
1547 std::vector
<Inkscape::XML::Node
*> temp_clip
;
1548 sp_selection_copy_impl(items_copy
, temp_clip
, document()->getReprDoc()); // we're in the same doc, so no need to copy defs
1549 sp_selection_delete_impl(items_copy
, false, false);
1550 std::vector
<Inkscape::XML::Node
*> copied
= sp_selection_paste_impl(document(), moveto
, temp_clip
, after
);
1552 setReprList(copied
);
1553 if (!temp_clip
.empty()) temp_clip
.clear();
1554 if (moveto
&& dt
) dt
->layerManager().setCurrentLayer(moveto
);
1559 object_set_contains_original(SPItem
*item
, ObjectSet
*set
)
1561 bool contains_original
= false;
1563 SPItem
*item_use
= item
;
1564 SPItem
*item_use_first
= item
;
1565 auto use
= cast
<SPUse
>(item_use
);
1566 while (use
&& item_use
&& !contains_original
)
1568 item_use
= use
->get_original();
1569 use
= cast
<SPUse
>(item_use
);
1570 contains_original
|= set
->includes(item_use
);
1571 if (item_use
== item_use_first
)
1575 // If it's a tref, check whether the object containing the character
1576 // data is part of the selection
1577 auto tref
= cast
<SPTRef
>(item
);
1578 if (!contains_original
&& tref
) {
1579 contains_original
= set
->includes(tref
->getObjectReferredTo());
1582 return contains_original
;
1587 object_set_contains_both_clone_and_original(ObjectSet
*set
)
1589 bool clone_with_original
= false;
1590 auto items
= set
->items();
1591 for (auto l
=items
.begin();l
!=items
.end() ;++l
) {
1594 clone_with_original
|= object_set_contains_original(item
, set
);
1595 if (clone_with_original
)
1599 return clone_with_original
;
1603 * Reapply the same transform again.
1605 void ObjectSet::reapplyAffine()
1607 auto cached
= _last_affine
;
1608 applyAffine(_last_affine
);
1609 _last_affine
= cached
;
1612 void ObjectSet::clearLastAffine()
1614 _last_affine
= Geom::identity(); // Clear last affine
1617 /** Apply matrix to the selection. \a set_i2d is normally true, which means objects are in the
1618 original transform, synced with their reprs, and need to jump to the new transform in one go. A
1619 value of set_i2d==false is only used by seltrans when it's dragging objects live (not outlines); in
1620 that case, items are already in the new position, but the repr is in the old, and this function
1621 then simply updates the repr from item->transform.
1624 void ObjectSet::applyAffine(Geom::Affine
const &affine
, bool set_i2d
, bool compensate
,
1625 bool adjust_transf_center
)
1630 _last_affine
= affine
;
1632 // For each perspective with a box in selection, check whether all boxes are selected and
1633 // unlink all non-selected boxes.
1635 Persp3D
*transf_persp
;
1636 std::list
<Persp3D
*> plist
= perspList();
1637 for (auto & i
: plist
) {
1638 persp
= (Persp3D
*) i
;
1641 if (!persp
->has_all_boxes_in_selection (this)) {
1642 // create a new perspective as a copy of the current one
1643 transf_persp
= Persp3D::create_xml_element (persp
->document
);
1645 std::list
<SPBox3D
*> selboxes
= box3DList(persp
);
1647 for (auto & selboxe
: selboxes
) {
1648 selboxe
->switch_perspectives(persp
, transf_persp
);
1651 transf_persp
= persp
;
1654 transf_persp
->apply_affine_transformation(affine
);
1657 auto items_copy
= items();
1658 std::vector
<SPItem
*> ordered_items
;
1659 for (auto l
=items_copy
.begin();l
!=items_copy
.end() ;++l
) {
1661 auto clonelpe
= cast
<SPLPEItem
>(item
);
1662 if (clonelpe
&& clonelpe
->hasPathEffectOfType(Inkscape::LivePathEffect::CLONE_ORIGINAL
)) {
1663 ordered_items
.insert(ordered_items
.begin(), item
);
1665 ordered_items
.push_back(item
);
1668 for (auto item
: ordered_items
) {
1669 if (is
<SPRoot
>(item
) ) {
1670 // An SVG element cannot have a transform. We could change 'x' and 'y' in response
1671 // to a translation... but leave that for another day.
1673 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE
, _("Cannot transform an embedded SVG."));
1677 Geom::Point
old_center(0,0);
1678 if (set_i2d
&& item
->isCenterSet())
1679 old_center
= item
->getCenter();
1681 // If we're moving a connector, we want to detach it
1682 // from shapes that aren't part of the selection, but
1683 // leave it attached if they are
1684 if (Inkscape::UI::Tools::cc_item_is_connector(item
)) {
1685 auto path
= cast
<SPPath
>(item
);
1687 SPItem
*attItem
[2] = {nullptr, nullptr};
1688 path
->connEndPair
.getAttachedItems(attItem
);
1689 for (int n
= 0; n
< 2; ++n
) {
1690 if (!includes(attItem
[n
])) {
1691 sp_conn_end_detach(item
, n
);
1695 g_assert_not_reached();
1699 // "clones are unmoved when original is moved" preference
1700 Inkscape::Preferences
*prefs
= Inkscape::Preferences::get();
1701 int compensation
= prefs
->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED
);
1702 bool prefs_unmoved
= (compensation
== SP_CLONE_COMPENSATION_UNMOVED
);
1703 bool prefs_parallel
= (compensation
== SP_CLONE_COMPENSATION_PARALLEL
);
1705 SiblingState sibling_state
= getSiblingState(item
);
1707 /* If this is a clone and it's selected along with its original, do not move it;
1708 * it will feel the transform of its original and respond to it itself.
1709 * Without this, a clone is doubly transformed, very unintuitive.
1711 * Same for textpath if we are also doing ANY transform to its path: do not touch textpath,
1712 * letters cannot be squeezed or rotated anyway, they only refill the changed path.
1713 * Same for linked offset if we are also moving its source: do not move it. */
1714 if (sibling_state
== SiblingState::SIBLING_TEXT_PATH
) {
1715 // Restore item->transform field from the repr, in case it was changed by seltrans.
1716 item
->readAttr(SPAttr::TRANSFORM
);
1717 } else if (sibling_state
== SiblingState::SIBLING_TEXT_FLOW_FRAME
) {
1718 // apply the inverse of the region's transform to the <use> so that the flow remains
1719 // the same (even though the output itself gets transformed)
1720 for (auto& region
: item
->children
) {
1721 if (is
<SPFlowregion
>(®ion
) || is
<SPFlowregionExclude
>(®ion
)) {
1722 for (auto& itm
: region
.children
) {
1723 auto use
= cast
<SPUse
>(&itm
);
1725 use
->doWriteTransform(item
->transform
.inverse(), nullptr, compensate
);
1730 } else if (sibling_state
== SiblingState::SIBLING_CLONE_ORIGINAL
|| sibling_state
== SiblingState::SIBLING_OFFSET_SOURCE
) {
1731 // We are transforming a clone along with its original. The below matrix juggling is
1732 // necessary to ensure that they transform as a whole, i.e. the clone's induced
1733 // transform and its move compensation are both cancelled out.
1735 // restore item->transform field from the repr, in case it was changed by seltrans
1736 item
->readAttr(SPAttr::TRANSFORM
);
1738 // calculate the matrix we need to apply to the clone to cancel its induced transform from its original
1739 Geom::Affine parent2dt
;
1741 auto parentItem
= cast
<SPItem
>(item
->parent
);
1743 parent2dt
= parentItem
->i2dt_affine();
1745 g_assert_not_reached();
1748 Geom::Affine t
= parent2dt
* affine
* parent2dt
.inverse();
1749 Geom::Affine t_inv
= t
.inverse();
1750 Geom::Affine result
= t_inv
* item
->transform
* t
;
1752 if (sibling_state
== SiblingState::SIBLING_CLONE_ORIGINAL
&& (prefs_parallel
|| prefs_unmoved
) && affine
.isTranslation()) {
1753 // we need to cancel out the move compensation, too
1755 // find out the clone move, same as in sp_use_move_compensate
1756 Geom::Affine parent
;
1758 auto use
= cast
<SPUse
>(item
);
1760 parent
= use
->get_parent_transform();
1762 g_assert_not_reached();
1765 Geom::Affine clone_move
= parent
.inverse() * t
* parent
;
1767 if (prefs_parallel
) {
1768 Geom::Affine move
= result
* clone_move
* t_inv
;
1769 item
->doWriteTransform(move
, &move
, compensate
);
1771 } else if (prefs_unmoved
) {
1772 //if (is<SPUse>(sp_use_get_original(cast<SPUse>(item))))
1773 // clone_move = Geom::identity();
1774 Geom::Affine move
= result
* clone_move
;
1775 item
->doWriteTransform(move
, &t
, compensate
);
1778 } else if (sibling_state
== SiblingState::SIBLING_OFFSET_SOURCE
&& (prefs_parallel
|| prefs_unmoved
) && affine
.isTranslation()){
1779 Geom::Affine parent
= item
->transform
;
1780 Geom::Affine offset_move
= parent
.inverse() * t
* parent
;
1782 if (prefs_parallel
) {
1783 Geom::Affine move
= result
* offset_move
* t_inv
;
1784 item
->doWriteTransform(move
, &move
, compensate
);
1786 } else if (prefs_unmoved
) {
1787 Geom::Affine move
= result
* offset_move
;
1788 item
->doWriteTransform(move
, &t
, compensate
);
1792 // just apply the result
1793 item
->doWriteTransform(result
, &t
, compensate
);
1795 } else if (sibling_state
== SiblingState::SIBLING_TEXT_SHAPE_INSIDE
) {
1796 item
->readAttr(SPAttr::TRANSFORM
);
1800 item
->set_i2d_affine(item
->i2dt_affine() * (Geom::Affine
)affine
);
1802 item
->doWriteTransform(item
->transform
, nullptr, compensate
);
1805 if (adjust_transf_center
) { // The transformation center should not be touched in case of pasting or importing, which is allowed by this if clause
1806 // if we're moving the actual object, not just updating the repr, we can transform the
1807 // center by the same matrix (only necessary for non-translations)
1808 if (set_i2d
&& item
->isCenterSet() && !(affine
.isTranslation() || affine
.isIdentity())) {
1809 item
->setCenter(old_center
* affine
);
1816 void ObjectSet::removeTransform()
1818 auto items
= xmlNodes();
1819 for (auto l
=items
.begin();l
!=items
.end() ;++l
) {
1820 (*l
)->removeAttribute("transform");
1824 DocumentUndo::done(document(), _("Remove transform"), "");
1828 void ObjectSet::setScaleAbsolute(double x0
, double x1
,double y0
, double y1
)
1833 Geom::OptRect bbox
= visualBounds();
1838 Geom::Translate
const p2o(-bbox
->min());
1840 Geom::Scale
const newSize(x1
- x0
,
1842 Geom::Scale
const scale( newSize
* Geom::Scale(bbox
->dimensions()).inverse() );
1843 Geom::Translate
const o2n(x0
, y0
);
1844 Geom::Affine
const final( p2o
* scale
* o2n
);
1849 void ObjectSet::scaleRelative(Geom::Point
const &align
, Geom::Scale
const &scale
)
1854 Geom::OptRect bbox
= visualBounds();
1860 // FIXME: ARBITRARY LIMIT: don't try to scale above 1 Mpx, it won't display properly and will crash sooner or later anyway
1861 if ( bbox
->dimensions()[Geom::X
] * scale
[Geom::X
] > 1e6
||
1862 bbox
->dimensions()[Geom::Y
] * scale
[Geom::Y
] > 1e6
)
1867 Geom::Translate
const n2d(-align
);
1868 Geom::Translate
const d2n(align
);
1869 Geom::Affine
const final( n2d
* scale
* d2n
);
1873 void ObjectSet::rotateRelative(Geom::Point
const ¢er
, double angle_degrees
)
1875 Geom::Translate
const d2n(center
);
1876 Geom::Translate
const n2d(-center
);
1877 Geom::Rotate
const rotate(Geom::Rotate::from_degrees(angle_degrees
));
1878 Geom::Affine
const final( Geom::Affine(n2d
) * rotate
* d2n
);
1882 void ObjectSet::skewRelative(Geom::Point
const &align
, double dx
, double dy
)
1884 Geom::Translate
const d2n(align
);
1885 Geom::Translate
const n2d(-align
);
1886 Geom::Affine
const skew(1, dy
,
1889 Geom::Affine
const final( n2d
* skew
* d2n
);
1893 void ObjectSet::moveRelative(Geom::Point
const &move
, bool compensate
)
1895 applyAffine(Geom::Affine(Geom::Translate(move
)), true, compensate
);
1898 void ObjectSet::moveRelative(double dx
, double dy
)
1900 applyAffine(Geom::Affine(Geom::Translate(dx
, dy
)));
1904 * Selects all the visible items with the same fill and/or stroke color/style as the items in the current selection
1907 * desktop - set the selection on this desktop
1908 * fill - select objects matching fill
1909 * stroke - select objects matching stroke
1911 void sp_select_same_fill_stroke_style(SPDesktop
*desktop
, gboolean fill
, gboolean stroke
, gboolean style
)
1917 if (!fill
&& !stroke
&& !style
) {
1921 Inkscape::Selection
*selection
= desktop
->getSelection();
1923 Inkscape::Preferences
*prefs
= Inkscape::Preferences::get();
1924 bool inlayersame
= prefs
->getBool("/options/selection/samelikeall", false);
1925 bool onlyvisible
= prefs
->getBool("/options/kbselection/onlyvisible", true);
1926 bool onlysensitive
= prefs
->getBool("/options/kbselection/onlysensitive", true);
1928 SPObject
*root
= desktop
->layerManager().currentRoot();
1929 bool ingroup
= true;
1931 // Apply the same layer logic to select same as used for select all.
1933 PrefsSelectionContext inlayer
= (PrefsSelectionContext
)prefs
->getInt("/options/kbselection/inlayer", PREFS_SELECTION_LAYER
);
1934 if (PREFS_SELECTION_ALL
!= inlayer
) {
1935 root
= selection
->activeContext();
1936 ingroup
= (inlayer
== PREFS_SELECTION_LAYER_RECURSIVE
);
1940 std::vector
<SPItem
*> all_list
= get_all_items(root
, desktop
, onlyvisible
, onlysensitive
, ingroup
);
1941 std::vector
<SPItem
*> all_matches
;
1943 auto items
= selection
->items();
1945 std::vector
<SPItem
*> tmp
;
1946 for (auto iter
: all_list
) {
1947 if(!is
<SPGroup
>(iter
)){
1948 tmp
.push_back(iter
);
1953 for (auto sel_iter
=items
.begin();sel_iter
!=items
.end();++sel_iter
) {
1954 SPItem
*sel
= *sel_iter
;
1955 std::vector
<SPItem
*> matches
= all_list
;
1956 if (fill
&& stroke
&& style
) {
1957 matches
= sp_get_same_style(sel
, matches
);
1960 matches
= sp_get_same_style(sel
, matches
, SP_FILL_COLOR
);
1963 matches
= sp_get_same_style(sel
, matches
, SP_STROKE_COLOR
);
1966 matches
= sp_get_same_style(sel
, matches
,SP_STROKE_STYLE_ALL
);
1968 all_matches
.insert(all_matches
.end(), matches
.begin(),matches
.end());
1972 selection
->setList(all_matches
);
1978 * Selects all the visible items with the same object type as the items in the current selection
1981 * desktop - set the selection on this desktop
1983 void sp_select_same_object_type(SPDesktop
*desktop
)
1990 Inkscape::Preferences
*prefs
= Inkscape::Preferences::get();
1991 bool onlyvisible
= prefs
->getBool("/options/kbselection/onlyvisible", true);
1992 bool onlysensitive
= prefs
->getBool("/options/kbselection/onlysensitive", true);
1993 bool ingroups
= TRUE
;
1994 auto matches
= get_all_items(desktop
->layerManager().currentRoot(), desktop
, onlyvisible
, onlysensitive
, ingroups
);
1996 Inkscape::Selection
*selection
= desktop
->getSelection();
1998 auto items
= selection
->items();
1999 for (auto sel_iter
=items
.begin();sel_iter
!=items
.end();++sel_iter
) {
2000 SPItem
*sel
= *sel_iter
;
2002 matches
= sp_get_same_object_type(sel
, matches
);
2004 g_assert_not_reached();
2009 selection
->setList(matches
);
2016 * Find all items in src list that have the same fill or stroke style as sel
2017 * Return the list of matching items
2019 std::vector
<SPItem
*> sp_get_same_fill_or_stroke_color(SPItem
*sel
, std::vector
<SPItem
*> &src
, SPSelectStrokeStyleType type
)
2021 std::vector
<SPItem
*> matches
;
2022 gboolean match
= false;
2024 SPIPaint
*sel_paint
= sel
->style
->getFillOrStroke(type
== SP_FILL_COLOR
);
2026 for (std::vector
<SPItem
*>::const_reverse_iterator i
=src
.rbegin();i
!=src
.rend();++i
) {
2029 SPIPaint
*iter_paint
= iter
->style
->getFillOrStroke(type
== SP_FILL_COLOR
);
2031 if (sel_paint
->isColor() && iter_paint
->isColor()
2032 && (sel_paint
->getColor().isSimilar(iter_paint
->getColor()))) {
2034 } else if (sel_paint
->isPaintserver() && iter_paint
->isPaintserver()) {
2036 SPPaintServer
*sel_server
=
2037 (type
== SP_FILL_COLOR
) ? sel
->style
->getFillPaintServer() : sel
->style
->getStrokePaintServer();
2038 SPPaintServer
*iter_server
=
2039 (type
== SP_FILL_COLOR
) ? iter
->style
->getFillPaintServer() : iter
->style
->getStrokePaintServer();
2041 auto check_gradient
= [] (SPGradient
const *g
) {
2042 return is
<SPLinearGradient
>(g
) || is
<SPRadialGradient
>(g
) || g
->getVector()->isSwatch();
2045 SPGradient
*sel_gradient
, *iter_gradient
;
2046 SPPattern
*sel_pattern
, *iter_pattern
;
2048 if ((sel_gradient
= cast
<SPGradient
>(sel_server
)) &&
2049 (iter_gradient
= cast
<SPGradient
>(iter_server
)) &&
2050 check_gradient(sel_gradient
) &&
2051 check_gradient(iter_gradient
))
2053 SPGradient
*sel_vector
= sel_gradient
->getVector();
2054 SPGradient
*iter_vector
= iter_gradient
->getVector();
2055 if (sel_vector
== iter_vector
) {
2059 } else if ((sel_pattern
= cast
<SPPattern
>(sel_server
)) &&
2060 (iter_pattern
= cast
<SPPattern
>(iter_server
))) {
2061 SPPattern
*sel_pat
= sel_pattern
->rootPattern();
2062 SPPattern
*iter_pat
= iter_pattern
->rootPattern();
2063 if (sel_pat
== iter_pat
) {
2067 } else if (sel_paint
->isNone() && iter_paint
->isNone()) {
2069 } else if (sel_paint
->isNoneSet() && iter_paint
->isNoneSet()) {
2074 matches
.push_back(iter
);
2077 g_assert_not_reached();
2084 static bool item_type_match (SPItem
*i
, SPItem
*j
)
2086 if (is
<SPRect
>(i
)) {
2087 return ( is
<SPRect
>(j
) );
2089 } else if (is
<SPGenericEllipse
>(i
)) {
2090 return (is
<SPGenericEllipse
>(j
));
2092 } else if (is
<SPStar
>(i
) || is
<SPPolygon
>(i
)) {
2093 return (is
<SPStar
>(j
) || is
<SPPolygon
>(j
)) ;
2095 } else if (is
<SPSpiral
>(i
)) {
2096 return (is
<SPSpiral
>(j
));
2098 } else if (is
<SPPath
>(i
) || is
<SPLine
>(i
) || is
<SPPolyLine
>(i
)) {
2099 return (is
<SPPath
>(j
) || is
<SPLine
>(j
) || is
<SPPolyLine
>(j
));
2101 } else if (is
<SPText
>(i
) || is
<SPFlowtext
>(i
) || is
<SPTSpan
>(i
) || is
<SPTRef
>(i
)) {
2102 return (is
<SPText
>(j
) || is
<SPFlowtext
>(j
) || is
<SPTSpan
>(j
) || is
<SPTRef
>(j
));
2104 } else if (is
<SPUse
>(i
)) {
2105 return (is
<SPUse
>(j
)) ;
2107 } else if (is
<SPImage
>(i
)) {
2108 return (is
<SPImage
>(j
));
2110 } else if (is
<SPOffset
>(i
) && cast_unsafe
<SPOffset
>(i
)->sourceHref
) { // Linked offset
2111 return (is
<SPOffset
>(j
) && cast_unsafe
<SPOffset
>(j
)->sourceHref
);
2113 } else if (is
<SPOffset
>(i
) && !cast_unsafe
<SPOffset
>(i
)->sourceHref
) { // Dynamic offset
2114 return is
<SPOffset
>(j
) && !cast_unsafe
<SPOffset
>(j
)->sourceHref
;
2122 * Find all items in src list that have the same object type as sel by type
2123 * Return the list of matching items
2125 std::vector
<SPItem
*> sp_get_same_object_type(SPItem
*sel
, std::vector
<SPItem
*> &src
)
2127 std::vector
<SPItem
*> matches
;
2129 for (std::vector
<SPItem
*>::const_reverse_iterator i
=src
.rbegin();i
!=src
.rend();++i
) {
2131 if (item
&& item_type_match(sel
, item
) && !item
->cloned
) {
2132 matches
.push_back(item
);
2139 * Find all items in src list that have the same stroke style as sel by type
2140 * Return the list of matching items
2142 std::vector
<SPItem
*> sp_get_same_style(SPItem
*sel
, std::vector
<SPItem
*> &src
, SPSelectStrokeStyleType type
)
2144 std::vector
<SPItem
*> matches
;
2147 SPStyle
*sel_style
= sel
->style
;
2149 if (type
== SP_FILL_COLOR
|| type
== SP_STYLE_ALL
) {
2150 src
= sp_get_same_fill_or_stroke_color(sel
, src
, SP_FILL_COLOR
);
2152 if (type
== SP_STROKE_COLOR
|| type
== SP_STYLE_ALL
) {
2153 src
= sp_get_same_fill_or_stroke_color(sel
, src
, SP_STROKE_COLOR
);
2157 * Stroke width needs to handle transformations, so call this function
2158 * to get the transformed stroke width
2160 std::vector
<SPItem
*> objects
;
2161 SPStyle
*sel_style_for_width
= nullptr;
2162 if (type
== SP_STROKE_STYLE_WIDTH
|| type
== SP_STROKE_STYLE_ALL
|| type
==SP_STYLE_ALL
) {
2163 objects
.push_back(sel
);
2164 sel_style_for_width
= new SPStyle(SP_ACTIVE_DOCUMENT
);
2165 objects_query_strokewidth (objects
, sel_style_for_width
);
2168 for (auto iter
: src
) {
2171 SPStyle
*iter_style
= iter
->style
;
2174 if (type
== SP_STROKE_STYLE_WIDTH
|| type
== SP_STROKE_STYLE_ALL
|| type
==SP_STYLE_ALL
) {
2175 match
= (sel_style
->stroke_width
.set
== iter_style
->stroke_width
.set
);
2176 if (sel_style
->stroke_width
.set
&& iter_style
->stroke_width
.set
) {
2177 std::vector
<SPItem
*> objects
;
2178 objects
.insert(objects
.begin(),iter
);
2179 SPStyle
tmp_style(SP_ACTIVE_DOCUMENT
);
2180 objects_query_strokewidth (objects
, &tmp_style
);
2182 if (sel_style_for_width
) {
2183 match
= (sel_style_for_width
->stroke_width
.computed
== tmp_style
.stroke_width
.computed
);
2187 match_g
= match_g
&& match
;
2188 if (type
== SP_STROKE_STYLE_DASHES
|| type
== SP_STROKE_STYLE_ALL
|| type
==SP_STYLE_ALL
) {
2189 match
= (sel_style
->stroke_dasharray
.set
== iter_style
->stroke_dasharray
.set
);
2190 if (sel_style
->stroke_dasharray
.set
&& iter_style
->stroke_dasharray
.set
) {
2191 match
= (sel_style
->stroke_dasharray
== iter_style
->stroke_dasharray
);
2194 match_g
= match_g
&& match
;
2195 if (type
== SP_STROKE_STYLE_MARKERS
|| type
== SP_STROKE_STYLE_ALL
|| type
==SP_STYLE_ALL
) {
2197 int len
= sizeof(sel_style
->marker
)/sizeof(SPIString
);
2198 for (int i
= 0; i
< len
; i
++) {
2199 if (g_strcmp0(sel_style
->marker_ptrs
[i
]->value(),
2200 iter_style
->marker_ptrs
[i
]->value())) {
2206 match_g
= match_g
&& match
;
2208 while (iter
->cloned
) iter
=cast
<SPItem
>(iter
->parent
);
2209 matches
.insert(matches
.begin(),iter
);
2212 g_assert_not_reached();
2216 if( sel_style_for_width
!= nullptr ) delete sel_style_for_width
;
2220 void ObjectSet::move(double dx
, double dy
)
2226 moveRelative(dx
, dy
);
2230 DocumentUndo::maybeDone(document(), "selector:move:vertical", _("Move vertically"), INKSCAPE_ICON("tool-pointer"));
2231 } else if (dy
== 0) {
2232 DocumentUndo::maybeDone(document(), "selector:move:horizontal", _("Move horizontally"), INKSCAPE_ICON("tool-pointer"));
2234 DocumentUndo::done(document(), _("Move"), INKSCAPE_ICON("tool-pointer"));
2239 void ObjectSet::move(double dx
, double dy
, bool rotated
)
2242 double const rotation
= desktop()->current_rotation().angle();
2243 double const rdx
= std::cos(rotation
) * dx
+ std::sin(rotation
) * dy
;
2244 double const rdy
= -std::sin(rotation
) * dx
+ std::cos(rotation
) * dy
;
2252 void ObjectSet::move(double dx
, double dy
, bool rotated
, bool screen
)
2255 moveScreen(dx
, dy
, rotated
);
2257 move(dx
, dy
, rotated
);
2261 void ObjectSet::moveScreen(double dx
, double dy
)
2263 if (isEmpty() || !desktop()) {
2267 // same as ObjectSet::move but divide deltas by zoom factor
2268 gdouble
const zoom
= desktop()->current_zoom();
2269 gdouble
const zdx
= dx
/ zoom
;
2270 gdouble
const zdy
= dy
/ zoom
;
2271 moveRelative(zdx
, zdy
);
2273 SPDocument
*doc
= document();
2275 DocumentUndo::maybeDone(doc
, "selector:move:vertical", _("Move vertically by pixels"), INKSCAPE_ICON("tool-pointer"));
2276 } else if (dy
== 0) {
2277 DocumentUndo::maybeDone(doc
, "selector:move:horizontal", _("Move horizontally by pixels"), INKSCAPE_ICON("tool-pointer"));
2279 DocumentUndo::done(doc
, _("Move"), INKSCAPE_ICON("tool-pointer"));
2283 void ObjectSet::moveScreen(double dx
, double dy
, bool rotated
)
2286 double const rotation
= desktop()->current_rotation().angle();
2287 double const rdx
= std::cos(rotation
) * dx
+ std::sin(rotation
) * dy
;
2288 double const rdy
= -std::sin(rotation
) * dx
+ std::cos(rotation
) * dy
;
2290 moveScreen(rdx
, rdy
);
2297 typedef SPObject
*Iterator
;
2299 static Iterator
children(SPObject
*o
) { return o
->firstChild(); }
2300 static Iterator
siblings_after(SPObject
*o
) { return o
->getNext(); }
2301 static void dispose(Iterator i
) {}
2303 static SPObject
*object(Iterator i
) { return i
; }
2304 static Iterator
next(Iterator i
) { return i
->getNext(); }
2305 static bool isNull(Iterator i
) {return (!i
);}
2308 struct ListReverse
{
2309 typedef std::list
<SPObject
*> *Iterator
;
2311 static Iterator
children(SPObject
*o
) {
2312 return make_list(o
, nullptr);
2314 static Iterator
siblings_after(SPObject
*o
) {
2315 return make_list(o
->parent
, o
);
2317 static void dispose(Iterator i
) {
2321 static SPObject
*object(Iterator i
) {
2322 return *(i
->begin());
2324 static Iterator
next(Iterator i
) { i
->pop_front(); return i
; }
2326 static bool isNull(Iterator i
) {return i
->empty();}
2329 static std::list
<SPObject
*> *make_list(SPObject
*object
, SPObject
*limit
) {
2330 auto list
= new std::list
<SPObject
*>;
2331 for (auto &child
: object
->children
) {
2332 if (&child
== limit
) {
2335 list
->push_front(&child
);
2343 template <typename D
>
2344 SPItem
*next_item(SPDesktop
*desktop
, std::vector
<SPObject
*> &path
, SPObject
*root
,
2345 bool only_in_viewport
, PrefsSelectionContext inlayer
, bool onlyvisible
, bool onlysensitive
)
2347 typename
D::Iterator children
;
2348 typename
D::Iterator iter
;
2350 SPItem
*found
=nullptr;
2352 if (!path
.empty()) {
2353 SPObject
*object
=path
.back();
2355 g_assert(object
->parent
== root
);
2356 if (desktop
->layerManager().isLayer(object
)) {
2357 found
= next_item
<D
>(desktop
, path
, object
, only_in_viewport
, inlayer
, onlyvisible
, onlysensitive
);
2359 iter
= children
= D::siblings_after(object
);
2361 iter
= children
= D::children(root
);
2364 while ( !D::isNull(iter
) && !found
) {
2365 SPObject
*object
=D::object(iter
);
2366 if (desktop
->layerManager().isLayer(object
)) {
2367 if (PREFS_SELECTION_LAYER
!= inlayer
) { // recurse into sublayers
2368 std::vector
<SPObject
*> empt
;
2369 found
= next_item
<D
>(desktop
, empt
, object
, only_in_viewport
, inlayer
, onlyvisible
, onlysensitive
);
2372 auto item
= cast
<SPItem
>(object
);
2374 ( !only_in_viewport
|| desktop
->isWithinViewport(item
) ) &&
2375 ( !onlyvisible
|| !desktop
->itemIsHidden(item
)) &&
2376 ( !onlysensitive
|| !item
->isLocked()) &&
2377 !desktop
->layerManager().isLayer(item
) )
2382 iter
= D::next(iter
);
2385 D::dispose(children
);
2391 template <typename D
>
2392 SPItem
*next_item_from_list(SPDesktop
*desktop
, std::vector
<SPItem
*> const &items
,
2393 SPObject
*root
, bool only_in_viewport
, PrefsSelectionContext inlayer
, bool onlyvisible
, bool onlysensitive
)
2395 SPObject
*current
=root
;
2396 for(auto item
: items
) {
2397 if ( root
->isAncestorOf(item
) &&
2398 ( !only_in_viewport
|| desktop
->isWithinViewport(item
) ) )
2405 std::vector
<SPObject
*> path
;
2406 while ( current
!= root
) {
2407 path
.push_back(current
);
2408 current
= current
->parent
;
2412 // first, try from the current object
2413 next
= next_item
<D
>(desktop
, path
, root
, only_in_viewport
, inlayer
, onlyvisible
, onlysensitive
);
2415 if (!next
) { // if we ran out of objects, start over at the root
2416 std::vector
<SPObject
*> empt
;
2417 next
= next_item
<D
>(desktop
, empt
, root
, only_in_viewport
, inlayer
, onlyvisible
, onlysensitive
);
2424 sp_selection_item_next(SPDesktop
*desktop
)
2426 g_return_if_fail(desktop
!= nullptr);
2427 Inkscape::Selection
*selection
= desktop
->getSelection();
2429 Inkscape::Preferences
*prefs
= Inkscape::Preferences::get();
2430 PrefsSelectionContext inlayer
= (PrefsSelectionContext
)prefs
->getInt("/options/kbselection/inlayer", PREFS_SELECTION_LAYER
);
2431 bool onlyvisible
= prefs
->getBool("/options/kbselection/onlyvisible", true);
2432 bool onlysensitive
= prefs
->getBool("/options/kbselection/onlysensitive", true);
2435 if (PREFS_SELECTION_ALL
!= inlayer
) {
2436 root
= selection
->activeContext();
2438 root
= desktop
->layerManager().currentRoot();
2441 std::vector
<SPItem
*> vec(selection
->items().begin(), selection
->items().end());
2442 SPItem
*item
=next_item_from_list
<Forward
>(desktop
, vec
, root
, SP_CYCLING
== SP_CYCLE_VISIBLE
, inlayer
, onlyvisible
, onlysensitive
);
2445 selection
->set(item
, PREFS_SELECTION_LAYER_RECURSIVE
== inlayer
);
2446 if ( SP_CYCLING
== SP_CYCLE_FOCUS
) {
2447 scroll_to_show_item(desktop
, item
);
2453 sp_selection_item_prev(SPDesktop
*desktop
)
2455 SPDocument
*document
= desktop
->getDocument();
2456 g_return_if_fail(document
!= nullptr);
2457 g_return_if_fail(desktop
!= nullptr);
2458 Inkscape::Selection
*selection
= desktop
->getSelection();
2460 Inkscape::Preferences
*prefs
= Inkscape::Preferences::get();
2461 PrefsSelectionContext inlayer
= (PrefsSelectionContext
) prefs
->getInt("/options/kbselection/inlayer", PREFS_SELECTION_LAYER
);
2462 bool onlyvisible
= prefs
->getBool("/options/kbselection/onlyvisible", true);
2463 bool onlysensitive
= prefs
->getBool("/options/kbselection/onlysensitive", true);
2466 if (PREFS_SELECTION_ALL
!= inlayer
) {
2467 root
= selection
->activeContext();
2469 root
= desktop
->layerManager().currentRoot();
2472 std::vector
<SPItem
*> vec(selection
->items().begin(), selection
->items().end());
2473 SPItem
*item
=next_item_from_list
<ListReverse
>(desktop
, vec
, root
, SP_CYCLING
== SP_CYCLE_VISIBLE
, inlayer
, onlyvisible
, onlysensitive
);
2476 selection
->set(item
, PREFS_SELECTION_LAYER_RECURSIVE
== inlayer
);
2477 if ( SP_CYCLING
== SP_CYCLE_FOCUS
) {
2478 scroll_to_show_item(desktop
, item
);
2483 void sp_selection_next_patheffect_param(SPDesktop
* dt
)
2487 Inkscape::Selection
*selection
= dt
->getSelection();
2488 if ( selection
&& !selection
->isEmpty() ) {
2489 SPItem
*item
= selection
->singleItem();
2490 if ( auto lpeitem
= cast
<SPLPEItem
>(item
) ) {
2491 if (lpeitem
->hasPathEffect()) {
2492 lpeitem
->editNextParamOncanvas(dt
);
2494 dt
->messageStack()->flash(Inkscape::WARNING_MESSAGE
, _("The selection has no applied path effect."));
2500 void ObjectSet::editMask(bool /*clip*/)
2509 * If \a item is not entirely visible then adjust visible area to centre on the centre on of
2512 void scroll_to_show_item(SPDesktop
*desktop
, SPItem
*item
)
2514 auto dbox
= desktop
->get_display_area();
2515 Geom::OptRect sbox
= item
->desktopVisualBounds();
2517 if ( sbox
&& dbox
.contains(*sbox
) == false ) {
2518 Geom::Point
const s_dt
= sbox
->midpoint();
2519 Geom::Point
const s_w
= desktop
->d2w(s_dt
);
2520 Geom::Point
const d_dt
= dbox
.midpoint();
2521 Geom::Point
const d_w
= desktop
->d2w(d_dt
);
2522 Geom::Point
const moved_w( d_w
- s_w
);
2523 desktop
->scroll_relative(moved_w
);
2527 void ObjectSet::clone(bool skip_undo
)
2529 if (document() == nullptr) {
2533 Inkscape::XML::Document
*xml_doc
= document()->getReprDoc();
2535 // check if something is selected
2538 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE
, _("Select an <b>object</b> to clone."));
2542 // Assign IDs to selected objects that don't have an ID attribute
2545 std::vector
<Inkscape::XML::Node
*> reprs(xmlNodes().begin(), xmlNodes().end());
2549 // sorting items from different parents sorts each parent's subset without possibly mixing them, just what we need
2550 sort(reprs
.begin(),reprs
.end(),sp_repr_compare_position_bool
);
2552 std::vector
<Inkscape::XML::Node
*> newsel
;
2554 for(auto sel_repr
: reprs
){
2555 Inkscape::XML::Node
*parent
= sel_repr
->parent();
2557 Inkscape::XML::Node
*clone
= xml_doc
->createElement("svg:use");
2558 clone
->setAttribute("x", "0");
2559 clone
->setAttribute("y", "0");
2560 gchar
*href_str
= g_strdup_printf("#%s", sel_repr
->attribute("id"));
2561 clone
->setAttribute("xlink:href", href_str
);
2564 clone
->setAttribute("inkscape:transform-center-x", sel_repr
->attribute("inkscape:transform-center-x"));
2565 clone
->setAttribute("inkscape:transform-center-y", sel_repr
->attribute("inkscape:transform-center-y"));
2567 // add the new clone to the top of the original's parent
2568 parent
->appendChild(clone
);
2570 newsel
.push_back(clone
);
2571 Inkscape::GC::release(clone
);
2574 DocumentUndo::done(document(), C_("Action", "Clone"), INKSCAPE_ICON("edit-clone"));
2577 setReprList(newsel
);
2580 void ObjectSet::relink()
2584 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE
, _("Select <b>clones</b> to relink."));
2588 Inkscape::UI::ClipboardManager
*cm
= Inkscape::UI::ClipboardManager::get();
2589 auto newid
= cm
->getFirstObjectID();
2590 if (newid
.empty()) {
2592 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE
, _("Copy an <b>object</b> to clipboard to relink clones to."));
2595 auto newrefAttribute
= "#" + newid
;
2597 // Get a copy of current selection.
2598 bool relinked
= false;
2599 auto items_
= items();
2600 for (auto i
=items_
.begin();i
!=items_
.end();++i
){
2603 if (auto use
= cast
<SPUse
>(item
)) {
2604 // Get original referenced item, relink, then get new reference
2605 SPItem
*ref
= use
->get_original();
2606 use
->setAttribute(Inkscape::getHrefAttribute(*use
->getRepr()).first
, newrefAttribute
);
2607 SPItem
*newref
= use
->get_original();
2609 if (ref
&& newref
) {
2610 // Compensate for position of new reference if requested.
2611 // Default behavior is to move according to transform, so not
2612 // handled explicitly.
2613 Inkscape::Preferences
*prefs
= Inkscape::Preferences::get();
2614 int compensation
= prefs
->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED
);
2616 if (compensation
== SP_CLONE_COMPENSATION_UNMOVED
|| compensation
== SP_CLONE_COMPENSATION_PARALLEL
) {
2617 auto center
= ref
->getCenter();
2618 auto newcenter
= newref
->getCenter();
2619 Geom::Affine translation
= Geom::Translate(newcenter
- center
);
2621 // Transform of clone. Necessary to apply the offset
2622 // translation from the reference prior to applying clone-
2623 // specific transformations.
2624 Geom::Affine t
= item
->transform
;
2626 // To make the clone appear unmoved, simply invert the
2627 // translation. To make the clone move in parallel, add the
2628 // translation back in, but make sure that the translation
2629 // is applied to a shape that isn't transformed in any other way
2630 Geom::Affine m
= t
.inverse() * translation
.inverse() * t
;
2631 if (compensation
== SP_CLONE_COMPENSATION_PARALLEL
) {
2632 m
*= m
.withoutTranslation().inverse() * translation
* m
.withoutTranslation();
2635 // Compensation must be applied for each clone indivudally
2636 // in case we are re-linking many clones that originally had
2637 // different references.
2638 auto s
= ObjectSet(document());
2644 item
->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG
);
2651 desktop()->messageStack()->flash(Inkscape::ERROR_MESSAGE
, _("<b>No clones to relink</b> in the selection."));
2653 DocumentUndo::done(document(), _("Relink clone"), INKSCAPE_ICON("edit-clone-unlink"));
2658 bool ObjectSet::unlink(const bool skip_undo
, const bool silent
)
2661 if(desktop() && !silent
)
2662 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE
, _("Select <b>clones</b> to unlink."));
2666 // Get a copy of current selection.
2667 std::vector
<SPItem
*> new_select
;
2668 bool unlinked
= false;
2669 std::vector
<SPItem
*> items_(items().begin(), items().end());
2671 for (auto i
=items_
.rbegin();i
!=items_
.rend();++i
){
2674 ObjectSet
tmp_set(document());
2676 auto *clip_obj
= item
->getClipObject();
2677 auto *mask_obj
= item
->getMaskObject();
2679 // The following always-false check was added in 5bfbeb4a.
2680 // Cannot tell if necessary since neither commit/MR say what bug it fixes.
2681 // Keeping the if (false) explicit to minimize likelihood of regressions
2682 // if (is<SPUse>(clip_obj)) {
2684 tmp_set
.unsetMask(true, true, true);
2685 unlinked
= tmp_set
.unlink(true) || unlinked
;
2686 tmp_set
.setMask(true, false, true);
2688 new_select
.push_back(tmp_set
.singleItem());
2689 } else if (mask_obj
) {
2690 // if (is<SPUse>(mask_obj)) {
2692 tmp_set
.unsetMask(false, true, true);
2693 unlinked
= tmp_set
.unlink(true) || unlinked
;
2694 tmp_set
.setMask(false, false, true);
2696 new_select
.push_back(tmp_set
.singleItem());
2698 if (is
<SPText
>(item
)) {
2699 SPObject
*tspan
= sp_tref_convert_to_tspan(item
);
2702 item
->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG
);
2705 // Set unlink to true, and fall into the next if which
2706 // will include this text item in the new selection
2710 if (!(is
<SPUse
>(item
) || is
<SPTRef
>(item
))) {
2711 // keep the non-use item in the new selection
2712 new_select
.push_back(item
);
2716 SPItem
*unlink
= nullptr;
2717 auto use
= cast
<SPUse
>(item
);
2719 unlink
= use
->unlink();
2720 // Unable to unlink use (external or invalid href?)
2722 new_select
.push_back(item
);
2725 } else /*if (is<SPTRef>(use))*/ {
2726 unlink
= cast
<SPItem
>(sp_tref_convert_to_tspan(item
));
2727 g_assert(unlink
!= nullptr);
2731 // Add ungrouped items to the new selection.
2732 new_select
.push_back(unlink
);
2736 if (!new_select
.empty()) { // set new selection
2738 setList(new_select
);
2741 if(desktop() && !silent
)
2742 desktop()->messageStack()->flash(Inkscape::ERROR_MESSAGE
, _("<b>No clones to unlink</b> in the selection."));
2746 DocumentUndo::done(document(), _("Unlink clone"), INKSCAPE_ICON("edit-clone-unlink"));
2751 bool ObjectSet::unlinkRecursive(const bool skip_undo
, const bool force
, const bool silent
) {
2753 if (desktop() && !silent
)
2754 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE
, _("Select <b>clones</b> to unlink."));
2757 Inkscape::Preferences
*prefs
= Inkscape::Preferences::get();
2758 bool pathoperationsunlink
= prefs
->getBool("/options/pathoperationsunlink/value", true);
2759 if (!force
&& !pathoperationsunlink
) {
2760 if (desktop() && !pathoperationsunlink
&& !silent
) {
2761 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE
, _("Unable to unlink. Check the setting for 'Unlinking Clones' in your preferences."));
2765 bool unlinked
= false;
2766 ObjectSet
tmp_set(document());
2767 std::vector
<SPItem
*> items_(items().begin(), items().end());
2768 for (auto& it
:items_
) {
2770 unlinked
= tmp_set
.unlink(true, silent
) || unlinked
;
2771 it
= tmp_set
.singleItem();
2772 if (is
<SPGroup
>(it
)) {
2773 std::vector
<SPObject
*> c
= it
->childList(false);
2775 unlinked
= tmp_set
.unlinkRecursive(skip_undo
, force
, silent
) || unlinked
;
2779 if(desktop() && !silent
)
2780 desktop()->messageStack()->flash(Inkscape::ERROR_MESSAGE
, _("<b>No clones to unlink</b> in the selection."));
2783 DocumentUndo::done(document(), _("Unlink clone recursively"), INKSCAPE_ICON("edit-clone-unlink"));
2789 void ObjectSet::removeLPESRecursive(bool keep_paths
) {
2794 ObjectSet
tmp_set(document());
2795 std::vector
<SPItem
*> items_(items().begin(), items().end());
2796 std::vector
<SPItem
*> itemsdone_
;
2797 for (auto& it
:items_
) {
2798 auto splpeitem
= cast
<SPLPEItem
>(it
);
2799 auto spgroup
= cast
<SPGroup
>(it
);
2801 std::vector
<SPObject
*> c
= spgroup
->childList(false);
2803 tmp_set
.removeLPESRecursive(keep_paths
);
2806 // Maybe the item is changed from SPShape to SPPath invalidating selection
2807 // fix issue Inkscape#2321
2808 char const *id
= splpeitem
->getAttribute("id");
2809 SPDocument
*document
= splpeitem
->document
;
2810 splpeitem
->removeAllPathEffects(keep_paths
);
2811 auto upditem
= cast
<SPItem
>(document
->getObjectById(id
));
2813 itemsdone_
.push_back(upditem
);
2816 itemsdone_
.push_back(it
);
2820 setList(itemsdone_
);
2823 void ObjectSet::cloneOriginal()
2825 SPItem
*item
= singleItem();
2827 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.");
2829 // Check if other than two objects are selected
2831 auto items_
= items();
2832 if (boost::distance(items_
) != 1 || !item
) {
2834 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE
, error
);
2838 SPItem
*original
= nullptr;
2839 auto use
= cast
<SPUse
>(item
);
2841 original
= use
->get_original();
2842 } else if (auto offset
= cast
<SPOffset
>(item
)) {
2843 original
= sp_offset_get_source(offset
);
2844 } else if (auto text
= cast
<SPText
>(item
)) {
2845 original
= text
->get_first_shape_dependency();
2846 } else if (auto flowtext
= cast
<SPFlowtext
>(item
)) {
2847 original
= flowtext
->get_frame(nullptr); // first frame only
2850 if (original
== nullptr) { // it's an object that we don't know what to do with
2852 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE
, error
);
2858 desktop()->messageStack()->flash(Inkscape::ERROR_MESSAGE
, _("<b>Cannot find</b> the object to select (orphaned clone, offset, textpath, flowed text?)"));
2862 for (SPObject
*o
= original
; o
&& !is
<SPRoot
>(o
); o
= o
->parent
) {
2863 if (is
<SPDefs
>(o
)) {
2865 desktop()->messageStack()->flash(Inkscape::ERROR_MESSAGE
, _("The object you're trying to select is <b>not visible</b> (it is in <defs>)"));
2871 Inkscape::Preferences
*prefs
= Inkscape::Preferences::get();
2872 bool highlight
= prefs
->getBool("/options/highlightoriginal/value");
2874 Geom::OptRect a
= item
->desktopVisualBounds();
2875 Geom::OptRect b
= original
->desktopVisualBounds();
2876 if ( a
&& b
&& desktop()) {
2877 // draw a flashing line between the objects
2879 curve
.moveto(a
->midpoint());
2880 curve
.lineto(b
->midpoint());
2882 // We use a bpath as it supports dashes.
2883 auto canvas_item_bpath
= new Inkscape::CanvasItemBpath(desktop()->getCanvasTemp(), curve
.get_pathvector());
2884 canvas_item_bpath
->set_stroke(0x0000ddff);
2885 canvas_item_bpath
->set_dashes({5.0, 3.0});
2886 canvas_item_bpath
->set_visible(true);
2887 desktop()->add_temporary_canvasitem(canvas_item_bpath
, 1000);
2893 if (SP_CYCLING
== SP_CYCLE_FOCUS
&& desktop()) {
2894 scroll_to_show_item(desktop(), original
);
2900 * This applies the Fill Between Many LPE, and has it refer to the selection.
2902 void ObjectSet::cloneOriginalPathLPE(bool allow_transforms
, bool sync
, bool skip_undo
)
2905 Inkscape::SVGOStringStream os
;
2906 SPObject
* firstItem
= nullptr;
2907 auto items_
= items();
2908 bool multiple
= false;
2909 for (auto *item
: items_
) {
2910 if (is
<SPShape
>(item
) || is
<SPText
>(item
) || is
<SPGroup
>(item
)) {
2917 os
<< '#' << item
->getId() << ",0,1";
2921 Inkscape::XML::Document
*xml_doc
= document()->getReprDoc();
2922 SPObject
*parent
= firstItem
->parent
;
2924 Inkscape::XML::Node
*lpe_repr
= xml_doc
->createElement("inkscape:path-effect");
2926 lpe_repr
->setAttribute("effect", "fill_between_many");
2927 lpe_repr
->setAttributeOrRemoveIfEmpty("linkedpaths", os
.str());
2929 lpe_repr
->setAttribute("effect", "clone_original");
2930 lpe_repr
->setAttribute("css_properties", "");
2931 lpe_repr
->setAttribute("attributes", "");
2932 lpe_repr
->setAttribute("linkeditem", ((Glib::ustring
)"#" + (Glib::ustring
)firstItem
->getId()));
2934 lpe_repr
->setAttribute("is_visible", "true");
2935 gchar
const *method_str
= allow_transforms
? "d" : "bsplinespiro";
2936 lpe_repr
->setAttribute("method", method_str
);
2937 gchar
const *allow_transforms_str
= allow_transforms
? "true" : "false";
2938 lpe_repr
->setAttribute("allow_transforms", allow_transforms_str
);
2939 document()->getDefs()->getRepr()->addChild(lpe_repr
, nullptr); // adds to <defs> and assigns the 'id' attribute
2940 std::string lpe_id_href
= std::string("#") + lpe_repr
->attribute("id");
2941 Inkscape::GC::release(lpe_repr
);
2942 Inkscape::XML::Node
* clone
= nullptr;
2943 auto firstgroup
= cast
<SPGroup
>(firstItem
);
2944 auto shape
= cast
<SPShape
>(firstItem
);
2945 auto path
= cast
<SPPath
>(firstItem
);
2948 clone
= firstgroup
->getRepr()->duplicate(xml_doc
);
2951 // create the new path
2952 clone
= xml_doc
->createElement("svg:path");
2953 if (sync
&& !multiple
&& shape
) {
2954 std::optional
<SPCurve
> c
= SPCurve::ptr_to_opt(shape
->curveForEdit());
2957 clone
->setAttribute("original-d", sp_svg_write_path(c
->get_pathvector()));
2959 clone
->setAttribute("d", sp_svg_write_path(c
->get_pathvector()));
2961 clone
->setAttribute("d", "M 0 0");
2964 clone
->setAttribute("d", "M 0 0");
2969 // add the new clone to the top of the original's parent
2970 parent
->appendChildRepr(clone
);
2971 // select the new object:
2973 Inkscape::GC::release(clone
);
2974 SPObject
*clone_obj
= document()->getObjectById(clone
->attribute("id"));
2975 auto clone_lpeitem
= cast
<SPLPEItem
>(clone_obj
);
2976 if (clone_lpeitem
) {
2977 if (sync
&& !multiple
) {
2978 lpe_repr
->setAttribute("attributes", "style,clip-path,mask");
2980 lpe_repr
->setAttribute("is_visible", "true");
2981 clone_lpeitem
->addPathEffect(lpe_id_href
, false);
2985 DocumentUndo::done(document(), _("Fill between many"), INKSCAPE_ICON("edit-clone-link-lpe"));
2987 DocumentUndo::done(document(), _("Clone original"), INKSCAPE_ICON("edit-clone-link-lpe"));
2993 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE
, _("Select path(s) to fill."));
2997 void ObjectSet::toMarker(bool apply
)
2999 // sp_selection_tile has similar code
3001 SPDocument
*doc
= document();
3002 Inkscape::XML::Document
*xml_doc
= doc
->getReprDoc();
3004 // check if something is selected
3007 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE
,
3008 _("Select <b>object(s)</b> to convert to marker."));
3012 doc
->ensureUpToDate();
3013 Geom::OptRect r
= visualBounds();
3018 std::vector
<SPItem
*> items_(items().begin(), items().end());
3019 sort(items_
.begin(), items_
.end(), sp_item_repr_compare_position_bool
);
3021 // bottommost object, after sorting
3022 SPObject
*parent
= items_
.front()->parent
;
3024 Geom::Affine parent_transform
;
3026 auto parentItem
= cast
<SPItem
>(parent
);
3028 parent_transform
= parentItem
->i2doc_affine();
3030 g_assert_not_reached();
3034 // Create a list of duplicates, to be pasted inside marker element.
3035 std::vector
<Inkscape::XML::Node
*> repr_copies
;
3036 for (auto *item
: items_
) {
3037 auto *dup
= item
->getRepr()->duplicate(xml_doc
);
3038 repr_copies
.push_back(dup
);
3041 Geom::Rect
bbox(r
->min() * doc
->dt2doc(), r
->max() * doc
->dt2doc());
3043 // calculate the transform to be applied to objects to move them to 0,0
3044 // (alternative would be to define viewBox or set overflow:visible)
3045 Geom::Affine
const move
= Geom::Translate(-bbox
.min());
3046 Geom::Point
const center
= bbox
.dimensions() * 0.5;
3049 // Delete objects so that their clones don't get alerted;
3050 // the objects will be restored inside the marker element.
3051 for (auto item
: items_
){
3052 item
->deleteObject(false);
3056 // Hack: Temporarily set clone compensation to unmoved, so that we can move clone-originals
3057 // without disturbing clones.
3058 // See ActorAlign::on_button_click() in src/ui/dialog/align-and-distribute.cpp
3059 Inkscape::Preferences
*prefs
= Inkscape::Preferences::get();
3060 int saved_compensation
= prefs
->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED
);
3061 prefs
->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED
);
3063 gchar
const *mark_id
= generate_marker(repr_copies
, bbox
, doc
, center
, parent_transform
* move
);
3066 // restore compensation setting
3067 prefs
->setInt("/options/clonecompensation/value", saved_compensation
);
3071 DocumentUndo::done(doc
, _("Objects to marker"), "");
3074 static void sp_selection_to_guides_recursive(SPItem
*item
, bool wholegroups
) {
3075 auto group
= cast
<SPGroup
>(item
);
3076 if (group
&& !is
<SPBox3D
>(item
) && !wholegroups
) {
3077 std::vector
<SPItem
*> items
=group
->item_list();
3078 for (auto item
: items
){
3079 sp_selection_to_guides_recursive(item
, wholegroups
);
3082 item
->convert_to_guides();
3086 void ObjectSet::toGuides()
3088 SPDocument
*doc
= document();
3089 // we need to copy the list because it gets reset when objects are deleted
3090 std::vector
<SPItem
*> items_(items().begin(), items().end());
3094 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE
, _("Select <b>object(s)</b> to convert to guides."));
3098 Inkscape::Preferences
*prefs
= Inkscape::Preferences::get();
3099 bool deleteitems
= !prefs
->getBool("/tools/cvg_keep_objects", false);
3100 bool wholegroups
= prefs
->getBool("/tools/cvg_convert_whole_groups", false);
3102 // If an object is earlier in the selection list than its clone, and it is deleted, then the clone will have changed
3103 // and its entry in the selection list is invalid (crash).
3104 // Therefore: first convert all, then delete all.
3106 for (auto item
: items_
){
3107 sp_selection_to_guides_recursive(item
, wholegroups
);
3112 sp_selection_delete_impl(items_
);
3115 DocumentUndo::done(doc
, _("Objects to guides"), "");
3119 * Convert objects to <symbol>. How that happens depends on what is selected:
3121 * 1) A random selection of objects will be embedded into a single <symbol> element.
3123 * 2) Except, a single <g> will have its content directly embedded into a <symbol>; the 'id' and
3124 * 'style' of the <g> are transferred to the <symbol>.
3126 * 3) Except, a single <g> with a transform that isn't a translation will keep the group when
3127 * embedded into a <symbol> (with 'id' and 'style' transferred to <symbol>). This is because a
3128 * <symbol> cannot have a transform. (If the transform is a pure translation, the translation
3129 * is moved to the referencing <use> element that is created.)
3131 * Possible improvements:
3133 * Move objects inside symbol so bbox corner at 0,0 (see marker/pattern)
3135 * For SVG2, set 'refX' 'refY' to object center (with compensating shift in <use>
3138 void ObjectSet::toSymbol()
3141 SPDocument
*doc
= document();
3142 Inkscape::XML::Document
*xml_doc
= doc
->getReprDoc();
3143 // Check if something is selected.
3146 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE
, _("Select <b>objects</b> to convert to symbol."));
3150 doc
->ensureUpToDate();
3152 std::vector
<SPObject
*> items_(objects().begin(), objects().end());
3153 sort(items_
.begin(),items_
.end(),sp_object_compare_position_bool
);
3155 // Keep track of parent, this is where <use> will be inserted.
3156 Inkscape::XML::Node
*the_first_repr
= items_
[0]->getRepr();
3157 Inkscape::XML::Node
*the_parent_repr
= the_first_repr
->parent();
3159 // Find out if we have a single group
3160 bool single_group
= false;
3161 SPGroup
*the_group
= nullptr;
3162 Geom::Affine transform
;
3163 if( items_
.size() == 1 ) {
3164 SPObject
*object
= items_
[0];
3165 the_group
= cast
<SPGroup
>(object
);
3167 single_group
= true;
3169 if( !sp_svg_transform_read( object
->getAttribute("transform"), &transform
))
3170 transform
= Geom::identity();
3172 if( transform
.isTranslation() ) {
3174 // Create new list from group children.
3175 items_
= object
->childList(false);
3177 // Hack: Temporarily set clone compensation to unmoved, so that we can move clone-originals
3178 // without disturbing clones.
3179 // See ActorAlign::on_button_click() in src/ui/dialog/align-and-distribute.cpp
3180 Inkscape::Preferences
*prefs
= Inkscape::Preferences::get();
3181 int saved_compensation
= prefs
->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED
);
3182 prefs
->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED
);
3184 // Remove transform on group, updating clones.
3185 the_group
->doWriteTransform(Geom::identity());
3187 // restore compensation setting
3188 prefs
->setInt("/options/clonecompensation/value", saved_compensation
);
3193 // Create new <symbol>
3194 Inkscape::XML::Node
*defsrepr
= doc
->getDefs()->getRepr();
3195 Inkscape::XML::Node
*symbol_repr
= xml_doc
->createElement("svg:symbol");
3197 defsrepr
->appendChild(symbol_repr
);
3198 bool settitle
= false;
3199 // For a single group, copy relevant attributes.
3200 if( single_group
) {
3201 Glib::ustring id
= the_group
->getAttribute("id");
3202 symbol_repr
->setAttribute("style", the_group
->getAttribute("style"));
3203 symbol_repr
->setAttribute("class", the_group
->getAttribute("class"));
3204 the_group
->setAttribute("id", id
+ "_transform");
3205 symbol_repr
->setAttribute("id", id
);
3207 // This should eventually be replaced by 'refX' and 'refY' once SVG WG approves it.
3208 // It is done here for round-tripping
3209 symbol_repr
->setAttribute("inkscape:transform-center-x",
3210 the_group
->getAttribute("inkscape:transform-center-x"));
3211 symbol_repr
->setAttribute("inkscape:transform-center-y",
3212 the_group
->getAttribute("inkscape:transform-center-y"));
3214 the_group
->removeAttribute("style");
3218 // Move selected items to new <symbol>
3219 for (std::vector
<SPObject
*>::const_reverse_iterator i
=items_
.rbegin();i
!=items_
.rend();++i
){
3220 gchar
* title
= (*i
)->title();
3221 if (!single_group
&& !settitle
&& title
) {
3222 Inkscape::XML::Node
*title_repr
= xml_doc
->createElement("svg:title");
3223 symbol_repr
->addChildAtPos(title_repr
, 0);
3224 title_repr
->appendChild(xml_doc
->createTextNode(title
));
3225 Inkscape::GC::release(title_repr
);
3226 gchar
* desc
= (*i
)->desc();
3228 Inkscape::XML::Node
*desc_repr
= xml_doc
->createElement("svg:desc");
3229 desc_repr
->appendChild(xml_doc
->createTextNode(desc
));
3230 symbol_repr
->addChildAtPos(desc_repr
, 1);
3231 Inkscape::GC::release(desc_repr
);
3237 Inkscape::XML::Node
*repr
= (*i
)->getRepr();
3238 repr
->parent()->removeChild(repr
);
3239 symbol_repr
->addChild(repr
, nullptr);
3242 if( single_group
&& transform
.isTranslation() ) {
3243 the_group
->deleteObject(true);
3246 // Create <use> pointing to new symbol (to replace the moved objects).
3247 Inkscape::XML::Node
*clone
= xml_doc
->createElement("svg:use");
3249 clone
->setAttribute("xlink:href", Glib::ustring("#")+symbol_repr
->attribute("id"));
3251 the_parent_repr
->appendChild(clone
);
3253 if( single_group
&& transform
.isTranslation() ) {
3254 clone
->setAttributeOrRemoveIfEmpty("transform", sp_svg_transform_write(transform
));
3257 // Change selection to new <use> element.
3261 Inkscape::GC::release(symbol_repr
);
3263 DocumentUndo::done(doc
, _("Group to symbol"), "");
3267 * Takes selected <use> that reference a symbol, and unSymbol those symbols
3269 void ObjectSet::unSymbol()
3271 for (const auto obj
: items()) {
3272 auto use
= cast
<SPUse
>(obj
);
3274 auto sym
= cast
<SPSymbol
>(use
->root());
3280 DocumentUndo::done(document(), _("unSymbol all selected symbols"), "");
3283 void ObjectSet::tile(bool apply
)
3285 // toMarker has similar code
3287 SPDocument
*doc
= document();
3288 Inkscape::XML::Document
*xml_doc
= doc
->getReprDoc();
3290 // check if something is selected
3293 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE
,
3294 _("Select <b>object(s)</b> to convert to pattern."));
3298 doc
->ensureUpToDate();
3299 Geom::OptRect r
= visualBounds();
3304 std::vector
<SPItem
*> items_(items().begin(), items().end());
3306 sort(items_
.begin(),items_
.end(),sp_object_compare_position_bool
);
3308 // bottommost object, after sorting
3309 SPObject
*parent
= items_
[0]->parent
;
3312 Geom::Affine parent_transform
;
3314 auto parentItem
= cast
<SPItem
>(parent
);
3316 parent_transform
= parentItem
->i2doc_affine();
3318 g_assert_not_reached();
3322 // remember the position of the first item
3323 gint pos
= items_
[0]->getRepr()->position();
3325 // create a list of duplicates
3326 std::vector
<Inkscape::XML::Node
*> repr_copies
;
3327 for (auto item
: items_
){
3328 Inkscape::XML::Node
*dup
= item
->getRepr()->duplicate(xml_doc
);
3329 repr_copies
.push_back(dup
);
3332 Geom::Rect
bbox(r
->min() * doc
->dt2doc(), r
->max() * doc
->dt2doc());
3335 // delete objects so that their clones don't get alerted; this object will be restored shortly
3336 for (auto item
: items_
){
3337 item
->deleteObject(false);
3341 // Hack: Temporarily set clone compensation to unmoved, so that we can move clone-originals
3342 // without disturbing clones.
3343 // See ActorAlign::on_button_click() in src/ui/dialog/align-and-distribute.cpp
3344 Inkscape::Preferences
*prefs
= Inkscape::Preferences::get();
3345 int saved_compensation
= prefs
->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED
);
3346 prefs
->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED
);
3348 Geom::Affine move
= Geom::Translate(- bbox
.min());
3349 gchar
const *pat_id
= SPPattern::produce(repr_copies
, bbox
, doc
,
3350 move
.inverse() /* patternTransform */,
3351 parent_transform
* move
);
3353 // restore compensation setting
3354 prefs
->setInt("/options/clonecompensation/value", saved_compensation
);
3357 Inkscape::XML::Node
*rect
= xml_doc
->createElement("svg:rect");
3358 gchar
*style_str
= g_strdup_printf("stroke:none;fill:url(#%s)", pat_id
);
3359 rect
->setAttribute("style", style_str
);
3362 rect
->setAttributeOrRemoveIfEmpty("transform", sp_svg_transform_write(parent_transform
.inverse()));
3364 rect
->setAttributeSvgDouble("width", bbox
.width());
3365 rect
->setAttributeSvgDouble("height", bbox
.height());
3366 rect
->setAttributeSvgDouble("x", bbox
.left());
3367 rect
->setAttributeSvgDouble("y", bbox
.top());
3369 // restore parent and position
3370 parent
->getRepr()->addChildAtPos(rect
, pos
);
3371 SPItem
*rectangle
= static_cast<SPItem
*>(document()->getObjectByRepr(rect
));
3373 Inkscape::GC::release(rect
);
3380 DocumentUndo::done(doc
, _("Objects to pattern"), "");
3383 void ObjectSet::untile()
3386 SPDocument
*doc
= document();
3387 Inkscape::XML::Document
*xml_doc
= doc
->getReprDoc();
3389 // check if something is selected
3392 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE
, _("Select an <b>object with pattern fill</b> to extract objects from."));
3396 std::vector
<SPItem
*> new_select
;
3400 std::vector
<SPItem
*> items_(items().begin(), items().end());
3401 for (std::vector
<SPItem
*>::const_reverse_iterator i
=items_
.rbegin();i
!=items_
.rend();++i
){
3404 SPStyle
*style
= item
->style
;
3406 if (!style
|| !style
->fill
.isPaintserver())
3409 SPPaintServer
*server
= item
->style
->getFillPaintServer();
3411 auto basePat
= cast
<SPPattern
>(server
);
3418 SPPattern
*pattern
= basePat
->rootPattern();
3420 Geom::Affine pat_transform
= basePat
->getTransform();
3421 pat_transform
*= item
->transform
;
3423 for (auto& child
: pattern
->children
) {
3424 if (is
<SPItem
>(&child
)) {
3425 Inkscape::XML::Node
*copy
= child
.getRepr()->duplicate(xml_doc
);
3426 auto i
= cast
<SPItem
>(item
->parent
->appendChildRepr(copy
));
3428 // FIXME: relink clones to the new canvas objects
3429 // use SPObject::setid when mental finishes it to steal ids of
3431 // this is needed to make sure the new item has curve (simply requestDisplayUpdate does not work)
3432 doc
->ensureUpToDate();
3435 Geom::Affine
transform( i
->transform
* pat_transform
);
3436 i
->doWriteTransform(transform
);
3438 new_select
.push_back(i
);
3440 g_assert_not_reached();
3445 SPCSSAttr
*css
= sp_repr_css_attr_new();
3446 sp_repr_css_set_property(css
, "fill", "none");
3447 sp_repr_css_change(item
->getRepr(), css
, "style");
3452 desktop()->messageStack()->flash(Inkscape::ERROR_MESSAGE
, _("<b>No pattern fills</b> in the selection."));
3454 DocumentUndo::done(document(), _("Pattern to objects"), "");
3455 setList(new_select
);
3459 void ObjectSet::createBitmapCopy()
3462 SPDocument
*doc
= document();
3463 Inkscape::XML::Document
*xml_doc
= doc
->getReprDoc();
3465 // check if something is selected
3468 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE
, _("Select <b>object(s)</b> to make a bitmap copy."));
3473 desktop()->messageStack()->flash(Inkscape::IMMEDIATE_MESSAGE
, _("Rendering bitmap..."));
3474 // set "busy" cursor
3475 desktop()->setWaitingCursor();
3478 // Get the bounding box of the selection
3479 doc
->ensureUpToDate();
3480 Geom::OptRect bbox
= documentBounds(SPItem::VISUAL_BBOX
);
3483 desktop()->clearWaitingCursor();
3484 return; // exceptional situation, so not bother with a translatable error message, just quit quietly
3487 // List of the items to show; all others will be hidden
3488 std::vector
<SPItem
const *> items_
{items().begin(), items().end()};
3490 // Sort items so that the topmost comes last
3491 sort(items_
.begin(), items_
.end(), sp_item_repr_compare_position_bool
);
3493 // Remember parent and z-order of the topmost one
3494 gint pos
= items_
.back()->getRepr()->position();
3495 SPObject
*parent_object
= items_
.back()->parent
;
3496 Inkscape::XML::Node
*parent
= parent_object
->getRepr();
3498 // Calculate resolution
3499 Inkscape::Preferences
*prefs
= Inkscape::Preferences::get();
3501 int const prefs_res
= prefs
->getInt("/options/createbitmap/resolution", 0);
3502 int const prefs_min
= prefs
->getInt("/options/createbitmap/minsize", 0);
3503 if (0 < prefs_res
) {
3504 // If it's given explicitly in prefs, take it
3506 } else if (0 < prefs_min
) {
3507 // If minsize is given, look up minimum bitmap size (default 250 pixels) and calculate resolution from it
3508 res
= Inkscape::Util::Quantity::convert(prefs_min
, "in", "px") / MIN(bbox
->width(), bbox
->height());
3511 // Get export DPI from the first item available
3512 auto dpi
= Geom::Point(0, 0);
3513 for (auto &item
: items_
) {
3514 dpi
= item
->getExportDpi();
3518 dpi
= doc
->getRoot()->getExportDpi();
3523 // if all else fails, take the default 96 dpi
3524 res
= Inkscape::Util::Quantity::convert(1, "in", "px");
3528 if (res
== Inkscape::Util::Quantity::convert(1, "in", "px")) { // for default 96 dpi, snap it to pixel grid
3529 bbox
= bbox
->roundOutwards();
3532 // anti-aliasing override
3533 std::optional
<Antialiasing
> antialias
;
3534 if (auto nv
= doc
->getNamedView()) {
3535 // if off, then disable antialiasing; if on, then let SVG dictate what it is
3536 if (!nv
->antialias_rendering
) {
3537 antialias
= Antialiasing::None
;
3541 Inkscape::Pixbuf
*pb
= sp_generate_internal_bitmap(doc
, *bbox
, res
, items_
, false, nullptr, 1, antialias
);
3544 // Create the repr for the image
3545 Inkscape::XML::Node
* repr
= xml_doc
->createElement("svg:image");
3546 sp_embed_image(repr
, pb
);
3547 repr
->setAttributeSvgDouble("width", bbox
->width());
3548 repr
->setAttributeSvgDouble("height", bbox
->height());
3550 // Calculate the matrix that will be applied to the image so that it exactly overlaps the source objects
3551 auto parentItem
= cast
<SPItem
>(parent_object
);
3552 Geom::Affine affine
= Geom::Translate(bbox
->left(), bbox
->top()) * parentItem
->i2doc_affine().inverse();
3555 repr
->setAttributeOrRemoveIfEmpty("transform", sp_svg_transform_write(affine
));
3557 // add the new repr to the parent
3558 parent
->addChildAtPos(repr
, pos
+ 1);
3560 // Set selection to the new image
3565 Inkscape::GC::release(repr
);
3568 // Complete undoable transaction
3569 DocumentUndo::done(doc
, _("Create bitmap"), INKSCAPE_ICON("selection-make-bitmap-copy"));
3573 desktop()->clearWaitingCursor();
3577 /* Creates a mask or clipPath from selection.
3578 * What is a clip group?
3579 * A clip group is a tangled mess of XML that allows an object inside a group
3580 * to clip the entire group using a few <use>s and generally irritating me.
3583 void ObjectSet::setClipGroup()
3585 SPDocument
* doc
= document();
3586 Inkscape::XML::Document
*xml_doc
= doc
->getReprDoc();
3590 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE
, _("Select <b>object(s)</b> to create clippath or mask from."));
3594 std::vector
<Inkscape::XML::Node
*> p(xmlNodes().begin(), xmlNodes().end());
3596 sort(p
.begin(),p
.end(),sp_repr_compare_position_bool
);
3600 int topmost
= (p
.back())->position();
3601 Inkscape::XML::Node
*topmost_parent
= (p
.back())->parent();
3603 Inkscape::XML::Node
*inner
= xml_doc
->createElement("svg:g");
3604 inner
->setAttribute("inkscape:label", "Clip");
3606 for(auto current
: p
){
3607 if (current
->parent() == topmost_parent
) {
3608 Inkscape::XML::Node
*spnew
= current
->duplicate(xml_doc
);
3609 sp_repr_unparent(current
);
3610 inner
->appendChild(spnew
);
3611 Inkscape::GC::release(spnew
);
3612 topmost
--; // only reduce count for those items deleted from topmost_parent
3613 } else { // move it to topmost_parent first
3614 std::vector
<Inkscape::XML::Node
*> temp_clip
;
3616 // At this point, current may already have no item, due to its being a clone whose original is already moved away
3617 // So we copy it artificially calculating the transform from its repr->attr("transform") and the parent transform
3618 gchar
const *t_str
= current
->attribute("transform");
3619 Geom::Affine
item_t(Geom::identity());
3621 sp_svg_transform_read(t_str
, &item_t
);
3622 item_t
*= cast
<SPItem
>(doc
->getObjectByRepr(current
->parent()))->i2doc_affine();
3623 // FIXME: when moving both clone and original from a transformed group (either by
3624 // grouping into another parent, or by cut/paste) the transform from the original's
3625 // parent becomes embedded into original itself, and this affects its clones. Fix
3626 // this by remembering the transform diffs we write to each item into an array and
3627 // then, if this is clone, looking up its original in that array and pre-multiplying
3628 // it by the inverse of that original's transform diff.
3630 sp_selection_copy_one(current
, item_t
, temp_clip
, xml_doc
);
3631 sp_repr_unparent(current
);
3633 // paste into topmost_parent (temporarily)
3634 std::vector
<Inkscape::XML::Node
*> copied
= sp_selection_paste_impl(doc
, doc
->getObjectByRepr(topmost_parent
), temp_clip
);
3635 if (!copied
.empty()) { // if success,
3636 // take pasted object (now in topmost_parent)
3637 Inkscape::XML::Node
*in_topmost
= copied
.back();
3639 Inkscape::XML::Node
*spnew
= in_topmost
->duplicate(xml_doc
);
3641 sp_repr_unparent(in_topmost
);
3642 // put its copy into group
3643 inner
->appendChild(spnew
);
3644 Inkscape::GC::release(spnew
);
3649 Inkscape::XML::Node
*outer
= xml_doc
->createElement("svg:g");
3650 outer
->appendChild(inner
);
3651 topmost_parent
->addChildAtPos(outer
, topmost
+ 1);
3653 Inkscape::XML::Node
*clone
= xml_doc
->createElement("svg:use");
3654 clone
->setAttribute("x", "0");
3655 clone
->setAttribute("y", "0");
3656 clone
->setAttribute("xlink:href", g_strdup_printf("#%s", inner
->attribute("id")));
3658 clone
->setAttribute("inkscape:transform-center-x", inner
->attribute("inkscape:transform-center-x"));
3659 clone
->setAttribute("inkscape:transform-center-y", inner
->attribute("inkscape:transform-center-y"));
3661 std::vector
<Inkscape::XML::Node
*> templist
;
3662 templist
.push_back(clone
);
3663 // add the new clone to the top of the original's parent
3664 gchar
const *mask_id
= SPClipPath::create(templist
, doc
);
3666 char* tmp
= g_strdup_printf("url(#%s)", mask_id
);
3667 outer
->setAttribute("clip-path", tmp
);
3670 Inkscape::GC::release(clone
);
3673 DocumentUndo::done(doc
, _("Create Clip Group"), "");
3676 void ObjectSet::chameleonFill()
3678 auto doc
= document();
3679 doc
->ensureUpToDate();
3681 // Build a new drawing so our manipulations don't corupt to canvas
3683 unsigned dkey
= SPItem::display_key_new(1);
3684 auto root
= doc
->getRoot()->invoke_show(drawing
, dkey
, SP_ITEM_SHOW_DISPLAY
);
3685 drawing
.setRoot(root
);
3687 // Hide all *SELECTED* objects from the drawing
3688 for (auto&& item
: items()) {
3689 item
->invoke_hide(dkey
);
3694 // Now in a new loop, ask for a color to be set for each item.
3695 for (auto&& item
: items()) {
3696 if (auto shape
= cast
<SPShape
>(item
)) {
3697 bool evenodd
= shape
->style
->fill_rule
.computed
== SP_WIND_RULE_EVENODD
;
3698 if (auto curve
= shape
->curve()) {
3699 auto color
= drawing
.averageColor(curve
->get_pathvector() * shape
->i2dt_affine(), evenodd
);
3700 auto style
= sp_repr_css_attr_new();
3701 sp_repr_css_set_property_double(style
, "fill-opacity", color
.stealOpacity());
3702 sp_repr_css_set_property_string(style
, "fill", color
.toString(false));
3703 sp_desktop_apply_css_recursive(item
, style
, true);
3704 sp_repr_css_attr_unref(style
);
3710 doc
->getRoot()->invoke_hide(dkey
);
3712 DocumentUndo::done(doc
, _("Chameleon Fill"), "");
3716 * Creates a mask or clipPath from selection.
3717 * Two different modes:
3718 * if applyToLayer, all selection is moved to DEFS as mask/clippath
3719 * and is applied to current layer
3720 * otherwise, topmost object is used as mask for other objects
3721 * If \a apply_clip_path parameter is true, clipPath is created, otherwise mask
3724 void ObjectSet::setMask(bool apply_clip_path
, bool apply_to_layer
, bool remove_original
)
3726 if(!desktop() && apply_to_layer
)
3729 SPDocument
*doc
= document();
3730 Inkscape::XML::Document
*xml_doc
= doc
->getReprDoc();
3732 // check if something is selected
3733 bool is_empty
= isEmpty();
3734 if ( apply_to_layer
&& is_empty
) {
3736 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE
, _("Select <b>object(s)</b> to create clippath or mask from."));
3738 } else if (!apply_to_layer
&& ( is_empty
|| boost::distance(items())==1 )) {
3740 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE
, _("Select mask object and <b>object(s)</b> to apply clippath or mask to."));
3744 // FIXME: temporary patch to prevent crash!
3745 // Remove this when bboxes are fixed to not blow up on an item clipped/masked with its own clone
3746 bool clone_with_original
= object_set_contains_both_clone_and_original(this);
3747 if (clone_with_original
) {
3748 g_warning("Unable to clip/mask an object with its own clone");
3749 return; // in this version, you cannot clip/mask an object with its own clone
3753 doc
->ensureUpToDate();
3755 std::vector
<SPItem
*> items_(items().begin(), items().end());
3757 sort(items_
.begin(),items_
.end(),sp_object_compare_position_bool
);
3759 // See lp bug #542004
3762 // create a list of duplicates
3763 std::vector
<std::pair
<Inkscape::XML::Node
*, Geom::Affine
>> mask_items
;
3764 std::vector
<SPItem
*> apply_to_items
;
3765 std::vector
<SPItem
*> items_to_delete
;
3766 std::vector
<SPItem
*> items_to_select
;
3768 Inkscape::Preferences
*prefs
= Inkscape::Preferences::get();
3769 bool topmost
= prefs
->getBool("/options/maskobject/topmost", true);
3770 int grouping
= prefs
->getInt("/options/maskobject/grouping", PREFS_MASKOBJECT_GROUPING_NONE
);
3772 if (apply_to_layer
) {
3773 // all selected items are used for mask, which is applied to a layer
3774 apply_to_items
.push_back(desktop()->layerManager().currentLayer());
3777 for (auto i
: items_
) {
3778 if((!topmost
&& !apply_to_layer
&& i
== items_
.front())
3779 || (topmost
&& !apply_to_layer
&& i
== items_
.back())
3782 Inkscape::XML::Node
*dup
= i
->getRepr()->duplicate(xml_doc
);
3783 mask_items
.emplace_back(dup
, i
->i2doc_affine());
3785 if (remove_original
) {
3786 items_to_delete
.push_back(i
);
3789 items_to_select
.push_back(i
);
3793 apply_to_items
.push_back(i
);
3794 items_to_select
.push_back(i
);
3800 if (grouping
== PREFS_MASKOBJECT_GROUPING_ALL
) {
3801 // group all those objects into one group
3802 // and apply mask to that
3803 auto set
= ObjectSet
{document()};
3804 set
.add(apply_to_items
.begin(), apply_to_items
.end());
3806 items_to_select
.clear();
3808 Inkscape::XML::Node
*group
= set
.group();
3809 group
->setAttribute("inkscape:groupmode", "maskhelper");
3811 // apply clip/mask only to newly created group
3812 apply_to_items
.clear();
3813 apply_to_items
.push_back(cast
<SPItem
>(doc
->getObjectByRepr(group
)));
3815 items_to_select
.push_back(cast
<SPItem
>(doc
->getObjectByRepr(group
)));
3817 Inkscape::GC::release(group
);
3819 if (grouping
== PREFS_MASKOBJECT_GROUPING_SEPARATE
) {
3820 items_to_select
.clear();
3823 gchar
const *attributeName
= apply_clip_path
? "clip-path" : "mask";
3824 for (auto item
: apply_to_items
| boost::adaptors::reversed
) {
3825 std::vector
<Inkscape::XML::Node
*> mask_items_dup
;
3826 std::map
<Inkscape::XML::Node
*, Geom::Affine
> dup_transf
;
3827 for (auto const &mask_item
: mask_items
) {
3828 Inkscape::XML::Node
*dup
= mask_item
.first
->duplicate(xml_doc
);
3829 mask_items_dup
.push_back(dup
);
3830 dup_transf
[dup
] = mask_item
.second
;
3833 Inkscape::XML::Node
*current
= item
->getRepr();
3834 // Node to apply mask to
3835 Inkscape::XML::Node
*apply_mask_to
= current
;
3837 if (grouping
== PREFS_MASKOBJECT_GROUPING_SEPARATE
) {
3838 // enclose current node in group, and apply crop/mask on that
3839 Inkscape::XML::Node
*group
= xml_doc
->createElement("svg:g");
3840 // make a note we should ungroup this when unsetting mask
3841 group
->setAttribute("inkscape:groupmode", "maskhelper");
3843 Inkscape::XML::Node
*spnew
= current
->duplicate(xml_doc
);
3844 current
->parent()->addChild(group
, current
);
3845 sp_repr_unparent(current
);
3846 group
->appendChild(spnew
);
3848 // Apply clip/mask to group instead
3849 apply_mask_to
= group
;
3850 item
= cast
<SPItem
>(doc
->getObjectByRepr(group
));
3852 items_to_select
.push_back(item
);
3853 Inkscape::GC::release(spnew
);
3854 Inkscape::GC::release(group
);
3857 char const *mask_id
= nullptr;
3858 if (apply_clip_path
) {
3859 mask_id
= SPClipPath::create(mask_items_dup
, doc
);
3861 mask_id
= SPMask::create(mask_items_dup
, doc
);
3864 // inverted object transform should be applied to a mask object,
3865 // as mask is calculated in user space (after applying transform)
3866 for (auto const &it
: mask_items_dup
) {
3867 auto clip_item
= cast
<SPItem
>(doc
->getObjectByRepr(it
));
3868 clip_item
->doWriteTransform(dup_transf
[it
]);
3869 clip_item
->doWriteTransform(clip_item
->transform
* item
->i2doc_affine().inverse());
3872 apply_mask_to
->setAttribute(attributeName
, Glib::ustring("url(#") + mask_id
+ ')');
3875 for (auto item
: items_to_delete
) {
3876 item
->deleteObject(false);
3877 items_to_select
.erase(std::remove(items_to_select
.begin(), items_to_select
.end(), item
), items_to_select
.end());
3880 addList(items_to_select
);
3883 void ObjectSet::unsetMask(const bool apply_clip_path
,
3884 const bool delete_helper_group
,
3885 const bool remove_original
)
3887 SPDocument
*doc
= document();
3888 Inkscape::XML::Document
*xml_doc
= doc
->getReprDoc();
3890 // check if something is selected
3893 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE
, _("Select <b>object(s)</b> to remove clippath or mask from."));
3897 Inkscape::Preferences
*prefs
= Inkscape::Preferences::get();
3898 bool ungroup_masked
= prefs
->getBool("/options/maskobject/ungrouping", true);
3899 doc
->ensureUpToDate();
3901 gchar
const *attributeName
= apply_clip_path
? "clip-path" : "mask";
3902 std::map
<SPObject
*,SPItem
*> referenced_objects
;
3904 std::vector
<SPItem
*> items_(items().begin(), items().end());
3907 std::vector
<SPGroup
*> items_to_ungroup
;
3908 std::vector
<SPItem
*> items_to_select(items_
);
3910 // SPObject* refers to a group containing the clipped path or mask itself,
3911 // whereas SPItem* refers to the item being clipped or masked
3912 for (auto i
: items_
){
3913 if (remove_original
) {
3914 // remember referenced mask/clippath, so orphaned masks can be moved back to document
3916 SPObject
*obj_ref
= nullptr;
3918 if (apply_clip_path
) {
3919 obj_ref
= item
->getClipObject();
3921 obj_ref
= item
->getMaskObject();
3924 // collect distinct mask object (and associate with item to apply transform)
3926 referenced_objects
[obj_ref
] = item
;
3930 i
->setAttribute(attributeName
, "none");
3932 auto group
= cast
<SPGroup
>(i
);
3933 if (ungroup_masked
&& group
&& delete_helper_group
) {
3934 // if we had previously enclosed masked object in group,
3935 // add it to list so we can ungroup it later
3937 // ungroup only groups we created when setting clip/mask
3938 if (group
->layerMode() == SPGroup::MASK_HELPER
) {
3939 items_to_ungroup
.push_back(group
);
3944 // restore mask objects into a document
3945 for (auto & referenced_object
: referenced_objects
) {
3946 SPObject
*obj
= referenced_object
.first
; // Group containing the clipped paths or masks
3947 std::vector
<Inkscape::XML::Node
*> items_to_move
;
3948 for (auto& child
: obj
->children
) {
3949 // Collect all clipped paths and masks within a single group
3950 Inkscape::XML::Node
*copy
= child
.getRepr()->duplicate(xml_doc
);
3951 if (copy
->attribute("inkscape:original-d") && copy
->attribute("inkscape:path-effect")) {
3952 copy
->setAttribute("d", copy
->attribute("inkscape:original-d"));
3953 } else if (copy
->attribute("inkscape:original-d")) {
3954 copy
->setAttribute("d", copy
->attribute("inkscape:original-d"));
3955 copy
->removeAttribute("inkscape:original-d");
3956 } else if (!copy
->attribute("inkscape:path-effect") && !is
<SPPath
>(&child
)) {
3957 copy
->removeAttribute("d");
3958 copy
->removeAttribute("inkscape:original-d");
3960 items_to_move
.push_back(copy
);
3963 if (!obj
->isReferenced()) {
3964 // delete from defs if no other object references this mask
3965 obj
->deleteObject(false);
3968 // remember parent and position of the item to which the clippath/mask was applied
3969 Inkscape::XML::Node
*parent
= (referenced_object
.second
)->getRepr()->parent();
3970 Inkscape::XML::Node
*ref_repr
= referenced_object
.second
->getRepr();
3972 // Iterate through all clipped paths / masks
3973 for (auto const repr
: items_to_move
| boost::adaptors::reversed
) {
3974 // insert into parent, restore pos
3975 parent
->addChild(repr
, ref_repr
);
3977 auto mask_item
= cast
<SPItem
>(document()->getObjectByRepr(repr
));
3981 items_to_select
.push_back(mask_item
);
3983 // transform mask, so it is moved the same spot where mask was applied
3984 mask_item
->doWriteTransform(mask_item
->transform
* referenced_object
.second
->transform
);
3988 // ungroup marked groups added when setting mask
3989 for (auto group
: items_to_ungroup
| boost::adaptors::reversed
) {
3991 items_to_select
.erase(std::remove(items_to_select
.begin(), items_to_select
.end(), group
), items_to_select
.end());
3992 std::vector
<SPItem
*> children
;
3993 sp_item_group_ungroup(group
, children
);
3994 items_to_select
.insert(items_to_select
.end(),children
.rbegin(),children
.rend());
3996 g_assert_not_reached();
4000 // rebuild selection
4001 addList(items_to_select
);
4005 * \param with_margins margins defined in the xml under <sodipodi:namedview>
4006 * "fit-margin-..." attributes. See SPDocument::fitToRect.
4007 * \return true if an undoable change should be recorded.
4009 bool ObjectSet::fitCanvas(bool with_margins
, bool skip_undo
)
4011 g_return_val_if_fail(document(), false);
4015 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE
, _("Select <b>object(s)</b> to fit canvas to."));
4018 Geom::OptRect
const bbox
= documentBounds(SPItem::VISUAL_BBOX
);
4020 document()->fitToRect(*bbox
, with_margins
);
4022 DocumentUndo::done(document(), _("Fit Page to Selection"), "");
4029 void ObjectSet::swapFillStroke()
4033 SPPaintServer
*server
;
4034 Glib::ustring _paintserver_id
;
4036 for (auto const item
: items()) {
4037 SPCSSAttr
*css
= sp_repr_css_attr_new ();
4039 _paintserver_id
.clear();
4040 paint
= &(item
->style
->fill
);
4041 if (paint
->set
&& paint
->isNone())
4042 sp_repr_css_set_property (css
, "stroke", "none");
4043 else if (paint
->set
&& paint
->isColor()) {
4044 auto color
= paint
->getColor();
4045 color
.addOpacity(item
->style
->fill_opacity
);
4046 sp_repr_css_set_property_string(css
, "stroke", color
.toString());
4048 else if (!paint
->set
)
4049 sp_repr_css_unset_property (css
, "stroke");
4050 else if (paint
->set
&& paint
->isPaintserver()) {
4051 server
= SP_STYLE_FILL_SERVER(item
->style
);
4053 Inkscape::XML::Node
*srepr
= server
->getRepr();
4054 _paintserver_id
+= "url(#";
4055 _paintserver_id
+= srepr
->attribute("id");
4056 _paintserver_id
+= ")";
4057 sp_repr_css_set_property (css
, "stroke", _paintserver_id
.c_str());
4061 _paintserver_id
.clear();
4062 paint
= &(item
->style
->stroke
);
4063 if (paint
->set
&& paint
->isNone())
4064 sp_repr_css_set_property (css
, "fill", "none");
4065 else if (paint
->set
&& paint
->isColor()) {
4066 auto color
= paint
->getColor();
4067 color
.addOpacity(item
->style
->stroke_opacity
);
4068 sp_repr_css_set_property_string(css
, "fill", color
.toString());
4070 else if (!paint
->set
)
4071 sp_repr_css_unset_property (css
, "fill");
4072 else if (paint
->set
&& paint
->isPaintserver()) {
4073 server
= SP_STYLE_STROKE_SERVER(item
->style
);
4075 Inkscape::XML::Node
*srepr
= server
->getRepr();
4076 _paintserver_id
+= "url(#";
4077 _paintserver_id
+= srepr
->attribute("id");
4078 _paintserver_id
+= ")";
4079 sp_repr_css_set_property (css
, "fill", _paintserver_id
.c_str());
4084 Inkscape::ObjectSet set
{};
4086 sp_desktop_set_style(&set
, desktop(), css
);
4088 sp_desktop_apply_css_recursive(item
, css
, true);
4091 sp_repr_css_attr_unref (css
);
4094 DocumentUndo::done(document(), _("Swap fill and stroke of an object"), "");
4098 * Creates a linked fill between all the objects in the current selection using
4099 * the "Fill Between Many" LPE. After this method completes, the linked fill
4100 * created becomes the new selection, so as to facilitate quick styling of the
4103 * All objects referred to must have an ID. If an ID does not exist, it will be
4106 * As an additional timesaver, the fill object is created below the bottommost
4107 * object in the selection.
4109 void ObjectSet::fillBetweenMany()
4113 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE
, _("Select <b>path(s)</b> to create fill between."));
4119 SPDocument
*doc
= document();
4120 SPObject
*defs
= doc
->getDefs();
4121 SPObject
*effect
= nullptr;
4123 Inkscape::XML::Node
*effectRepr
= doc
->getReprDoc()->createElement("inkscape:path-effect");
4124 Inkscape::XML::Node
*fillRepr
= doc
->getReprDoc()->createElement("svg:path");
4127 Glib::ustring pathTarget
;
4129 for (auto&& item
: items()) {
4130 // Force-assign id if there is none present
4131 if (!item
->getId()) {
4132 auto id
= item
->generate_unique_id();
4133 item
->set(SPAttr::ID
, id
.c_str());
4138 acc
+= item
->getId();
4142 effectRepr
->setAttribute("effect", "fill_between_many");
4143 effectRepr
->setAttribute("method", "originald");
4144 effectRepr
->setAttribute("linkedpaths", acc
.c_str());
4145 defs
->appendChild(effectRepr
);
4147 effect
= doc
->getObjectByRepr(effectRepr
);
4149 pathTarget
+= effect
->getId();
4151 fillRepr
->setAttribute("inkscape:original-d", "M 0,0");
4152 fillRepr
->setAttribute("inkscape:path-effect", pathTarget
.c_str());
4153 fillRepr
->setAttribute("d", "M 0,0");
4155 // Get bottommost element in selection to create fill underneath
4156 auto&& items_
= std::vector
<SPObject
*>(items().begin(), items().end());
4157 SPObject
*first
= *std::min_element(items_
.begin(), items_
.end(), sp_object_compare_position_bool
);
4158 SPObject
*prev
= first
->getPrev();
4160 first
->parent
->addChild(fillRepr
, prev
? prev
->getRepr() : nullptr);
4162 doc
->ensureUpToDate();
4167 DocumentUndo::done(doc
, _("Create linked fill object between paths"), "");
4171 * Associates the given SPItem with a SiblingState enum
4172 * Needed for handling special cases while transforming objects
4173 * Inserts the [SPItem, SiblingState] pair to ObjectSet._sibling_state map
4175 * @return the SiblingState
4178 ObjectSet::getSiblingState(SPItem
*item
) {
4179 auto offset
= cast
<SPOffset
>(item
);
4180 auto flowtext
= cast
<SPFlowtext
>(item
);
4182 auto check_item
= _sibling_state
.find(item
);
4183 if (check_item
!= _sibling_state
.end() && check_item
->second
> SiblingState::SIBLING_NONE
) {
4184 return check_item
->second
;
4187 SiblingState ret
= SiblingState::SIBLING_NONE
;
4189 // moving both a clone and its original or any ancestor
4190 if (object_set_contains_original(item
, this)) {
4191 ret
= SiblingState::SIBLING_CLONE_ORIGINAL
;
4193 // moving both a text-on-path and its path
4194 } else if (is
<SPText
>(item
) && is
<SPTextPath
>(item
->firstChild()) &&
4195 includes(sp_textpath_get_path_item(cast_unsafe
<SPTextPath
>(item
->firstChild())))) {
4196 ret
= SiblingState::SIBLING_TEXT_PATH
;
4198 // moving both a flowtext and its frame
4199 } else if (flowtext
&& includes(flowtext
->get_frame(nullptr))) {
4200 ret
= SiblingState::SIBLING_TEXT_FLOW_FRAME
;
4202 // moving both an offset and its source
4203 } else if (offset
&& offset
->sourceHref
&& includes(sp_offset_get_source(offset
))) {
4204 ret
= SiblingState::SIBLING_OFFSET_SOURCE
;
4206 // moving object containing sub object
4207 } else if (item
->style
&& item
->style
->shape_inside
.containsAnyShape(this)) {
4208 ret
= SiblingState::SIBLING_TEXT_SHAPE_INSIDE
;
4211 _sibling_state
[item
] = ret
;
4217 ObjectSet::clearSiblingStates()
4219 _sibling_state
.clear();
4223 * \param with_margins margins defined in the xml under <sodipodi:namedview>
4224 * "fit-margin-..." attributes. See SPDocument::fitToRect.
4226 * WARNING: this is a page naive and it will break multi page documents.
4229 fit_canvas_to_drawing(SPDocument
*doc
, bool with_margins
)
4231 g_return_val_if_fail(doc
, false);
4233 doc
->ensureUpToDate();
4234 SPItem
const *const root
= doc
->getRoot();
4235 Geom::OptRect bbox
= root
->documentVisualBounds();
4237 doc
->fitToRect(*bbox
, with_margins
);
4245 fit_canvas_to_drawing(SPDesktop
*desktop
)
4247 if (fit_canvas_to_drawing(desktop
->getDocument())) {
4248 DocumentUndo::done(desktop
->getDocument(), _("Fit Page to Drawing"), "");
4252 static void itemtree_map(void (*f
)(SPItem
*, SPDesktop
*), SPObject
*root
, SPDesktop
*desktop
) {
4253 // don't operate on layers
4254 if (auto item
= cast
<SPItem
>(root
)) {
4255 if (!desktop
->layerManager().isLayer(item
)) {
4259 for (auto& child
: root
->children
) {
4260 //don't recurse into locked layers
4261 auto item
= cast
<SPItem
>(&child
);
4262 if (!(item
&& desktop
->layerManager().isLayer(item
) && item
->isLocked())) {
4263 itemtree_map(f
, &child
, desktop
);
4268 static void unlock(SPItem
*item
, SPDesktop */
*desktop*/
) {
4269 if (item
->isLocked()) {
4270 item
->setLocked(false);
4274 static void unhide(SPItem
*item
, SPDesktop
*desktop
) {
4275 if (desktop
->itemIsHidden(item
)) {
4276 item
->setExplicitlyHidden(false);
4280 static void process_all(void (*f
)(SPItem
*, SPDesktop
*), SPDesktop
*dt
, bool layer_only
) {
4285 root
= dt
->layerManager().currentLayer();
4287 root
= dt
->layerManager().currentRoot();
4290 itemtree_map(f
, root
, dt
);
4293 void unlock_all(SPDesktop
*dt
) {
4294 process_all(&unlock
, dt
, true);
4297 void unlock_all_in_all_layers(SPDesktop
*dt
) {
4298 process_all(&unlock
, dt
, false);
4301 void unhide_all(SPDesktop
*dt
) {
4302 process_all(&unhide
, dt
, true);
4305 void unhide_all_in_all_layers(SPDesktop
*dt
) {
4306 process_all(&unhide
, dt
, false);
4312 c-file-style:"stroustrup"
4313 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4314 indent-tabs-mode:nil
4318 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :