Context for the "About" label
[inkscape.git] / src / selection-chemistry.cpp
blob2323e71aa3fe1ca01af44414f1693fe83a2b30b0
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /** @file
3 * Miscellaneous operations on selected items.
4 */
5 /* Authors:
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>
13 * Abhishek Sharma
14 * Kris De Gussem <Kris.DeGussem@gmail.com>
15 * Tavmjong Bah <tavmjong@free.fr> (Symbol additions)
16 * Adrian Boguszewski
17 * Marc Jeanmougin
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>
28 #include <cstring>
29 #include <glibmm/i18n.h>
30 #include <map>
31 #include <string>
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"
37 #include "desktop.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"
43 #include "file.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"
84 #include "style.h"
85 #include "svg/svg.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;
105 using Geom::X;
106 using Geom::Y;
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
114 for that purpose. */
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
119 static void
120 selection_display_message(SPDesktop *desktop, Inkscape::MessageType msgType, Glib::ustring const &msg)
122 if (desktop) {
123 desktop->messageStack()->flash(msgType, msg);
124 } else {
125 if (msgType == Inkscape::IMMEDIATE_MESSAGE ||
126 msgType == Inkscape::WARNING_MESSAGE ||
127 msgType == Inkscape::ERROR_MESSAGE) {
128 g_printerr("%s\n", msg.c_str());
133 namespace Inkscape {
135 void SelectionHelper::selectAll(SPDesktop *dt)
137 NodeTool *nt = dynamic_cast<NodeTool*>(dt->getTool());
138 if (nt) {
139 if (!nt->_multipath->empty()) {
140 nt->_multipath->selectSubpaths();
141 return;
144 sp_edit_select_all(dt);
147 void SelectionHelper::selectAllInAll(SPDesktop *dt)
149 NodeTool *nt = dynamic_cast<NodeTool*>(dt->getTool());
150 if (nt) {
151 nt->_selected_nodes->selectAll();
152 } else {
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();
164 } else {
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());
198 if (nt) {
199 nt->_multipath->invertSelectionInSubpaths();
200 } else {
201 sp_edit_invert(dt);
205 void SelectionHelper::invertAllInAll(SPDesktop *dt)
207 NodeTool *nt = dynamic_cast<NodeTool*>(dt->getTool());
208 if (nt) {
209 nt->_selected_nodes->invertSelection();
210 } else {
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());
219 if (nt) {
220 nt->_multipath->reverseSubpaths();
221 } else {
222 dt->getSelection()->pathReverse();
227 * Fixes the current selection, removing locked objects from it
229 void SelectionHelper::fixSelection(SPDesktop *dt)
231 if (!dt) {
232 return;
235 Inkscape::Selection *selection = dt->getSelection();
237 std::vector<SPItem*> items;
239 auto selList = selection->items();
241 for (auto item : selList | boost::adaptors::reversed) {
242 if (item &&
243 !dt->layerManager().isLayer(item) &&
244 !item->isLocked())
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)
278 // Sort items:
279 std::vector<SPItem*> sorted_items(items);
280 sort(sorted_items.begin(),sorted_items.end(),sp_object_compare_position_bool);
282 // Copy item reprs:
283 for (auto item : sorted_items) {
284 if (item) {
285 sp_selection_copy_one(item->getRepr(), item->i2doc_affine(), clip, xml_doc);
286 } else {
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");
315 Geom::Affine item_t;
316 if (t_str) {
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);
328 after = copy;
330 copied.push_back(copy);
331 Inkscape::GC::release(copy);
333 return copied;
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."));
351 return;
354 std::vector<SPItem*> selected(items().begin(), items().end());
355 clear();
356 sp_selection_delete_impl(selected);
358 if (skip_undo) {
359 return;
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()});
375 if(document()) {
376 DocumentUndo::done(document(), _("Delete"), INKSCAPE_ICON("edit-delete"));
382 static void add_ids_recursive(std::vector<const gchar *> &ids, SPObject *obj)
384 if (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.
399 return;
402 SPDocument *doc = document();
404 if(!doc)
405 return;
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."));
412 return;
414 std::vector<Inkscape::XML::Node*> reprs(xmlNodes().begin(), xmlNodes().end());
416 if(duplicateLayer){
417 reprs.clear();
418 reprs.push_back(desktop()->layerManager().currentLayer()->getRepr());
421 clear();
423 std::vector<SPItem *> items;
424 for(auto old_repr : reprs) {
425 auto item = cast<SPItem>(doc->getObjectByRepr(old_repr));
426 if (item) {
427 items.push_back(item);
428 auto lpeitem = cast<SPLPEItem>(item);
429 if (lpeitem) {
430 for (auto satellite : lpeitem->get_satellites(false, true, true)) {
431 if (satellite) {
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);
475 } else {
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);
486 if (relink_clones) {
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
506 // and simplicity
507 auto itm = cast<SPItem>(doc->getObjectByRepr(node));
508 if (!sp_repr_is_def(node) && (!itm || !itm->isHidden())) {
509 newsel.push_back(node);
514 if (relink_clones) {
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);
525 if (use) {
526 SPItem *orig = use->get_original();
527 if (!orig) // orphaned
528 continue;
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);
538 } else if (offset) {
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]);
545 } else if (text) {
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]);
554 } else if (path) {
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);
575 if (newLPEObj) {
576 // force always fork
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);
584 if (old_obj) {
585 old_obj->fixTmpSuccessors();
586 old_obj->unsetTmpSuccessor();
590 if (!duplicateLayer) {
591 setReprList(newsel);
592 if ( !suppressDone ) {
593 DocumentUndo::done(document(), _("Duplicate"), INKSCAPE_ICON("edit-duplicate"));
595 } else {
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);
605 g_free(name);
612 void sp_edit_clear_all(Inkscape::Selection *selection)
614 if (!selection)
615 return;
617 auto desktop = selection->desktop();
618 SPDocument *doc = desktop->getDocument();
619 selection->clear();
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);
646 if (item &&
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?
666 return list;
669 static void sp_edit_select_all_full(SPDesktop *dt, bool force_all_layers, bool invert)
671 if (!dt)
672 return;
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;
687 if (invert) {
688 exclude.insert(exclude.end(), selection->items().begin(), selection->items().end());
691 if (force_all_layers)
692 inlayer = PREFS_SELECTION_ALL;
694 switch (inlayer) {
695 case PREFS_SELECTION_LAYER: {
696 if ((onlysensitive && layer->isLocked()) || (onlyvisible && dt->itemIsHidden(layer))) {
697 return;
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
712 break;
714 case PREFS_SELECTION_LAYER_RECURSIVE: {
715 items = get_all_items(dt->layerManager().currentLayer(), dt, onlyvisible, onlysensitive, FALSE, exclude);
716 break;
718 default: {
719 items = get_all_items(dt->layerManager().currentRoot(), dt, onlyvisible, onlysensitive, FALSE, exclude);
720 break;
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();
749 if(!doc)
750 return nullptr;
751 if (isEmpty()) {
752 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE, _("Select <b>some objects</b> to group."));
753 return nullptr;
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);
760 this->clear();
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()) {
770 topmost = current;
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());
792 if (t_str)
793 sp_svg_transform_read(t_str, &item_t);
794 auto parent_item = cast<SPItem>(doc->getObjectByRepr(current->parent()));
795 assert(parent_item);
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();
813 // make a copy
814 Inkscape::XML::Node *spnew = in_topmost->duplicate(xml_doc);
815 // remove pasted
816 sp_repr_unparent(in_topmost);
817 // put its copy into group
818 group->appendChild(spnew);
819 Inkscape::GC::release(spnew);
820 copied.clear();
825 set(doc->getObjectByRepr(group));
827 return group;
830 void ObjectSet::popFromGroup(){
831 if (isEmpty()) {
832 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE, _("<b>No objects selected</b> to pop out of group."));
833 return;
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>."));
842 return;
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."));
852 return;
855 toLayer(*grandparents.begin());
857 if(document())
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))) {
876 return use;
881 if (auto *use = find_clone_to_group(obj->childList(false), groups)) {
882 return use;
886 return nullptr;
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();
905 if (readd) {
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)
921 if (isEmpty()) {
922 if(desktop())
923 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE, _("Select a <b>group</b> to ungroup."));
924 return;
927 if (boost::distance(groups()) == 0) {
928 if(desktop())
929 selection_display_message(desktop(), Inkscape::ERROR_MESSAGE, _("<b>No groups</b> to ungroup in the selection."));
930 return;
933 ungroup_impl(this);
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) {
945 last = size();
946 ungroup(skip_undo);
950 /** If items in the list have a common parent, return it, otherwise return NULL */
951 static SPGroup *
952 sp_item_list_common_parent_group(const SPItemRange &items)
954 if (items.empty()) {
955 return nullptr;
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)) {
960 return nullptr;
962 for (auto item : items) {
963 if (item == items.front()) {
964 continue;
966 if (item->parent != parent) {
967 return nullptr;
971 return cast<SPGroup>(parent);
974 /** Finds out the minimum common bbox of the selected items. */
975 static Geom::OptRect
976 enclose_items(std::vector<SPItem*> const &items)
978 g_assert(!items.empty());
980 Geom::OptRect r;
981 for (auto item : items) {
982 r.unionWith(item->documentVisualBounds());
984 return r;
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();
994 return prev;
997 void ObjectSet::raise(bool skip_undo){
999 if(isEmpty()){
1000 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to raise."));
1001 return;
1004 SPGroup const *group = sp_item_list_common_parent_group(items());
1005 if (!group) {
1006 if(desktop())
1007 selection_display_message(desktop(), Inkscape::ERROR_MESSAGE, _("You cannot raise/lower objects from <b>different groups</b> or <b>layers</b>."));
1008 return;
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).
1022 if (selected) {
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);
1028 if (newItem) {
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());
1036 break;
1042 if (document() && !skip_undo) {
1043 DocumentUndo::done(document(), C_("Undo action", "Raise"), INKSCAPE_ICON("selection-raise"));
1048 void ObjectSet::raiseToTop(bool skip_undo) {
1049 if (isEmpty()) {
1050 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to raise."));
1051 return;
1054 SPGroup const *group = sp_item_list_common_parent_group(items());
1055 if (!group) {
1056 selection_display_message(desktop(), Inkscape::ERROR_MESSAGE, _("You cannot raise/lower objects from <b>different groups</b> or <b>layers</b>."));
1057 return;
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)
1073 if(isEmpty()){
1074 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to lower."));
1075 return;
1078 SPGroup const *group = sp_item_list_common_parent_group(items());
1079 if (!group) {
1080 selection_display_message(desktop(), Inkscape::ERROR_MESSAGE, _("You cannot raise/lower objects from <b>different groups</b> or <b>layers</b>."));
1081 return;
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).
1095 if (selected) {
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);
1101 if (newItem) {
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());
1109 else
1110 child->getRepr()->setPosition(0);
1112 break;
1118 if(document() && !skip_undo)
1119 DocumentUndo::done(document(), C_("Undo action", "Lower"), INKSCAPE_ICON("selection-lower"));
1123 void ObjectSet::lowerToBottom(bool skip_undo)
1125 if(!document())
1126 return;
1127 if (isEmpty()) {
1128 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to lower to bottom."));
1129 return;
1132 SPGroup const *group = sp_item_list_common_parent_group(items());
1133 if (!group) {
1134 selection_display_message(desktop(), Inkscape::ERROR_MESSAGE, _("You cannot raise/lower objects from <b>different groups</b> or <b>layers</b>."));
1135 return;
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) {
1142 int minpos = 0;
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)) {
1147 break;
1149 minpos++;
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) {
1159 if (isEmpty()) {
1160 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to stack up."));
1161 return;
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."));
1172 return;
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) {
1181 if (isEmpty()) {
1182 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to stack down."));
1183 return;
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."));
1194 return;
1198 if (document() && !skip_undo) {
1199 DocumentUndo::done(document(), C_("Undo action", "stack down"), INKSCAPE_ICON("layer-lower"));
1203 void ObjectSet::cut()
1205 copy();
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"));
1211 return;
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));
1220 return;
1223 deleteItems();
1227 * \pre item != NULL
1229 SPCSSAttr *
1230 take_style_from_item(SPObject *object)
1232 // CPPIFY:
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);
1237 if (css == nullptr)
1238 return nullptr;
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);
1248 if (temp) {
1249 sp_repr_css_merge(css, temp);
1250 sp_repr_css_attr_unref(temp);
1252 break;
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);
1267 if (item) {
1268 // FIXME: also transform gradient/pattern fills, by forking? NO, this must be nondestructive
1269 double ex = item->i2doc_affine().descrim();
1270 if (ex != 1.0) {
1271 css = sp_css_attr_scale(css, ex);
1275 return css;
1278 void ObjectSet::copy()
1280 Inkscape::UI::ClipboardManager *cm = Inkscape::UI::ClipboardManager::get();
1281 cm->copy(this);
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
1322 if (isEmpty()) {
1323 if(desktop())
1324 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to remove live path effects from."));
1325 return;
1327 auto list= items();
1328 for (auto itemlist=list.begin();itemlist!=list.end();++itemlist) {
1329 SPItem *item = *itemlist;
1331 sp_selection_remove_livepatheffect_impl(item);
1335 if (document()) {
1336 DocumentUndo::done(document(), _("Remove live path effect"), "");
1340 void ObjectSet::removeFilter()
1342 // check if something is selected
1343 if (isEmpty()) {
1344 if(desktop())
1345 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to remove filters from."));
1346 return;
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));
1357 } else {
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);
1364 if (document()) {
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) {
1393 if (item) {
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)
1405 if (!desktop()) {
1406 return;
1408 SPDesktop *dt=desktop(); //TODO make it desktop-independent
1410 // check if something is selected
1411 if (isEmpty()) {
1412 dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to move to the layer above."));
1413 return;
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());
1420 if (next) {
1421 clear();
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;
1428 if (next) {
1429 copied = sp_selection_paste_impl(dt->getDocument(), next, temp_clip);
1430 } else {
1431 copied = sp_selection_paste_impl(dt->getDocument(), dt->layerManager().currentLayer(), temp_clip);
1432 no_more = true;
1434 setReprList(copied);
1435 if (next) dt->layerManager().setCurrentLayer(next);
1436 if ( !skip_undo ) {
1437 DocumentUndo::done(dt->getDocument(), _("Raise to next layer"), INKSCAPE_ICON("selection-move-to-layer-above"));
1439 } else {
1440 no_more = true;
1443 if (no_more) {
1444 dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No more layers above."));
1449 void ObjectSet::toPrevLayer(bool skip_undo)
1451 if (!desktop()) {
1452 return;
1454 SPDesktop *dt=desktop(); //TODO make it desktop-independent
1456 // check if something is selected
1457 if (isEmpty()) {
1458 dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to move to the layer below."));
1459 return;
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());
1466 if (next) {
1467 clear();
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;
1474 if (next) {
1475 copied = sp_selection_paste_impl(dt->getDocument(), next, temp_clip);
1476 } else {
1477 copied = sp_selection_paste_impl(dt->getDocument(), dt->layerManager().currentLayer(), temp_clip);
1478 no_more = true;
1480 setReprList( copied);
1481 if (next) dt->layerManager().setCurrentLayer(next);
1482 if ( !skip_undo ) {
1483 DocumentUndo::done(dt->getDocument(), _("Lower to previous layer"), INKSCAPE_ICON("selection-move-to-layer-below"));
1485 } else {
1486 no_more = true;
1489 if (no_more) {
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)
1504 if(!document())
1505 return;
1507 if (!moveto || !moveto->getRepr()) {
1508 g_warning("%s moveto is NULL", __func__);
1509 g_assert_not_reached();
1510 return;
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)
1521 assert(moveto);
1522 assert(!after || after->parent() == moveto->getRepr());
1523 assert(document());
1525 SPDesktop *dt = desktop();
1527 // check if something is selected
1528 if (isEmpty()) {
1529 if(dt)
1530 dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to move."));
1531 return;
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());
1544 if (moveto) {
1545 clear();
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);
1558 static bool
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)
1572 break;
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;
1586 static bool
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) {
1592 SPItem *item = *l;
1593 if (item) {
1594 clone_with_original |= object_set_contains_original(item, set);
1595 if (clone_with_original)
1596 break;
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)
1627 if (isEmpty())
1628 return;
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.
1634 Persp3D *persp;
1635 Persp3D *transf_persp;
1636 std::list<Persp3D *> plist = perspList();
1637 for (auto & i : plist) {
1638 persp = (Persp3D *) i;
1640 if (persp) {
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);
1650 } else {
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) {
1660 SPItem *item = *l;
1661 auto clonelpe = cast<SPLPEItem>(item);
1662 if (clonelpe && clonelpe->hasPathEffectOfType(Inkscape::LivePathEffect::CLONE_ORIGINAL)) {
1663 ordered_items.insert(ordered_items.begin(), item);
1664 } else {
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.
1672 if(desktop())
1673 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Cannot transform an embedded SVG."));
1674 break;
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);
1686 if (path) {
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);
1694 } else {
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>(&region) || is<SPFlowregionExclude>(&region)) {
1722 for (auto& itm: region.children) {
1723 auto use = cast<SPUse>(&itm);
1724 if ( use ) {
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);
1742 if (parentItem) {
1743 parent2dt = parentItem->i2dt_affine();
1744 } else {
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);
1759 if (use) {
1760 parent = use->get_parent_transform();
1761 } else {
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);
1791 } else {
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);
1798 } else {
1799 if (set_i2d) {
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);
1810 item->updateRepr();
1816 void ObjectSet::removeTransform()
1818 auto items = xmlNodes();
1819 for (auto l=items.begin();l!=items.end() ;++l) {
1820 (*l)->removeAttribute("transform");
1823 if (document()) {
1824 DocumentUndo::done(document(), _("Remove transform"), "");
1828 void ObjectSet::setScaleAbsolute(double x0, double x1,double y0, double y1)
1830 if (isEmpty())
1831 return;
1833 Geom::OptRect bbox = visualBounds();
1834 if ( !bbox ) {
1835 return;
1838 Geom::Translate const p2o(-bbox->min());
1840 Geom::Scale const newSize(x1 - x0,
1841 y1 - y0);
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 );
1846 applyAffine(final);
1849 void ObjectSet::scaleRelative(Geom::Point const &align, Geom::Scale const &scale)
1851 if (isEmpty())
1852 return;
1854 Geom::OptRect bbox = visualBounds();
1856 if ( !bbox ) {
1857 return;
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 )
1864 return;
1867 Geom::Translate const n2d(-align);
1868 Geom::Translate const d2n(align);
1869 Geom::Affine const final( n2d * scale * d2n );
1870 applyAffine(final);
1873 void ObjectSet::rotateRelative(Geom::Point const &center, 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 );
1879 applyAffine(final);
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,
1887 dx, 1,
1888 0, 0);
1889 Geom::Affine const final( n2d * skew * d2n );
1890 applyAffine(final);
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
1906 * Params:
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)
1913 if (!desktop) {
1914 return;
1917 if (!fill && !stroke && !style) {
1918 return;
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.
1932 if (inlayersame) {
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);
1951 all_list=tmp;
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);
1959 else if (fill) {
1960 matches = sp_get_same_style(sel, matches, SP_FILL_COLOR);
1962 else if (stroke) {
1963 matches = sp_get_same_style(sel, matches, SP_STROKE_COLOR);
1965 else if (style) {
1966 matches = sp_get_same_style(sel, matches,SP_STROKE_STYLE_ALL);
1968 all_matches.insert(all_matches.end(), matches.begin(),matches.end());
1971 selection->clear();
1972 selection->setList(all_matches);
1978 * Selects all the visible items with the same object type as the items in the current selection
1980 * Params:
1981 * desktop - set the selection on this desktop
1983 void sp_select_same_object_type(SPDesktop *desktop)
1985 if (!desktop) {
1986 return;
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;
2001 if (sel) {
2002 matches = sp_get_same_object_type(sel, matches);
2003 } else {
2004 g_assert_not_reached();
2008 selection->clear();
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) {
2027 SPItem *iter = *i;
2028 if (iter) {
2029 SPIPaint *iter_paint = iter->style->getFillOrStroke(type == SP_FILL_COLOR);
2030 match = false;
2031 if (sel_paint->isColor() && iter_paint->isColor()
2032 && (sel_paint->getColor().isSimilar(iter_paint->getColor()))) {
2033 match = true;
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) {
2056 match = true;
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) {
2064 match = true;
2067 } else if (sel_paint->isNone() && iter_paint->isNone()) {
2068 match = true;
2069 } else if (sel_paint->isNoneSet() && iter_paint->isNoneSet()) {
2070 match = true;
2073 if (match) {
2074 matches.push_back(iter);
2076 } else {
2077 g_assert_not_reached();
2081 return matches;
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;
2118 return false;
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) {
2130 SPItem *item = *i;
2131 if (item && item_type_match(sel, item) && !item->cloned) {
2132 matches.push_back(item);
2135 return matches;
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;
2145 bool match = false;
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);
2167 bool match_g;
2168 for (auto iter : src) {
2169 if (iter) {
2170 match_g=true;
2171 SPStyle *iter_style = iter->style;
2172 match = true;
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) {
2196 match = true;
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())) {
2201 match = false;
2202 break;
2206 match_g = match_g && match;
2207 if (match_g) {
2208 while (iter->cloned) iter=cast<SPItem>(iter->parent);
2209 matches.insert(matches.begin(),iter);
2211 } else {
2212 g_assert_not_reached();
2216 if( sel_style_for_width != nullptr ) delete sel_style_for_width;
2217 return matches;
2220 void ObjectSet::move(double dx, double dy)
2222 if (isEmpty()) {
2223 return;
2226 moveRelative(dx, dy);
2228 if (document()) {
2229 if (dx == 0) {
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"));
2233 } else {
2234 DocumentUndo::done(document(), _("Move"), INKSCAPE_ICON("tool-pointer"));
2239 void ObjectSet::move(double dx, double dy, bool rotated)
2241 if (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;
2246 move(rdx, rdy);
2247 } else {
2248 move(dx, dy);
2252 void ObjectSet::move(double dx, double dy, bool rotated, bool screen)
2254 if (screen) {
2255 moveScreen(dx, dy, rotated);
2256 } else {
2257 move(dx, dy, rotated);
2261 void ObjectSet::moveScreen(double dx, double dy)
2263 if (isEmpty() || !desktop()) {
2264 return;
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();
2274 if (dx == 0) {
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"));
2278 } else {
2279 DocumentUndo::done(doc, _("Move"), INKSCAPE_ICON("tool-pointer"));
2283 void ObjectSet::moveScreen(double dx, double dy, bool rotated)
2285 if (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);
2291 } else {
2292 moveScreen(dx, dy);
2296 struct Forward {
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) {
2318 delete 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();}
2328 private:
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) {
2333 break;
2335 list->push_front(&child);
2337 return list;
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();
2354 path.pop_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);
2360 } else {
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);
2371 } else {
2372 auto item = cast<SPItem>(object);
2373 if ( item &&
2374 ( !only_in_viewport || desktop->isWithinViewport(item) ) &&
2375 ( !onlyvisible || !desktop->itemIsHidden(item)) &&
2376 ( !onlysensitive || !item->isLocked()) &&
2377 !desktop->layerManager().isLayer(item) )
2379 found = item;
2382 iter = D::next(iter);
2385 D::dispose(children);
2387 return found;
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) ) )
2400 current = item;
2401 break;
2405 std::vector<SPObject *> path;
2406 while ( current != root ) {
2407 path.push_back(current);
2408 current = current->parent;
2411 SPItem *next;
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);
2420 return next;
2423 void
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);
2434 SPObject *root;
2435 if (PREFS_SELECTION_ALL != inlayer) {
2436 root = selection->activeContext();
2437 } else {
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);
2444 if (item) {
2445 selection->set(item, PREFS_SELECTION_LAYER_RECURSIVE == inlayer);
2446 if ( SP_CYCLING == SP_CYCLE_FOCUS ) {
2447 scroll_to_show_item(desktop, item);
2452 void
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);
2465 SPObject *root;
2466 if (PREFS_SELECTION_ALL != inlayer) {
2467 root = selection->activeContext();
2468 } else {
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);
2475 if (item) {
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)
2485 if (!dt) return;
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);
2493 } else {
2494 dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("The selection has no applied path effect."));
2500 void ObjectSet::editMask(bool /*clip*/)
2502 return;
2509 * If \a item is not entirely visible then adjust visible area to centre on the centre on of
2510 * \a item.
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) {
2530 return;
2533 Inkscape::XML::Document *xml_doc = document()->getReprDoc();
2535 // check if something is selected
2536 if (isEmpty()) {
2537 if(desktop())
2538 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select an <b>object</b> to clone."));
2539 return;
2542 // Assign IDs to selected objects that don't have an ID attribute
2543 enforceIds();
2545 std::vector<Inkscape::XML::Node*> reprs(xmlNodes().begin(), xmlNodes().end());
2547 clear();
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);
2562 g_free(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);
2573 if (!skip_undo) {
2574 DocumentUndo::done(document(), C_("Action", "Clone"), INKSCAPE_ICON("edit-clone"));
2577 setReprList(newsel);
2580 void ObjectSet::relink()
2582 if (isEmpty()) {
2583 if(desktop())
2584 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>clones</b> to relink."));
2585 return;
2588 Inkscape::UI::ClipboardManager *cm = Inkscape::UI::ClipboardManager::get();
2589 auto newid = cm->getFirstObjectID();
2590 if (newid.empty()) {
2591 if(desktop())
2592 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Copy an <b>object</b> to clipboard to relink clones to."));
2593 return;
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){
2601 SPItem *item = *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());
2639 s.add(item);
2640 s.applyAffine(m);
2644 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
2645 relinked = true;
2649 if (!relinked) {
2650 if(desktop())
2651 desktop()->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No clones to relink</b> in the selection."));
2652 } else {
2653 DocumentUndo::done(document(), _("Relink clone"), INKSCAPE_ICON("edit-clone-unlink"));
2658 bool ObjectSet::unlink(const bool skip_undo, const bool silent)
2660 if (isEmpty()) {
2661 if(desktop() && !silent)
2662 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>clones</b> to unlink."));
2663 return false;
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){
2672 SPItem *item = *i;
2674 ObjectSet tmp_set(document());
2675 tmp_set.set(item);
2676 auto *clip_obj = item->getClipObject();
2677 auto *mask_obj = item->getMaskObject();
2678 if (clip_obj) {
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)) {
2683 if (false) {
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)) {
2691 if (false) {
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());
2697 } else {
2698 if (is<SPText>(item)) {
2699 SPObject *tspan = sp_tref_convert_to_tspan(item);
2701 if (tspan) {
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
2707 unlinked = true;
2710 if (!(is<SPUse>(item) || is<SPTRef>(item))) {
2711 // keep the non-use item in the new selection
2712 new_select.push_back(item);
2713 continue;
2716 SPItem *unlink = nullptr;
2717 auto use = cast<SPUse>(item);
2718 if (use) {
2719 unlink = use->unlink();
2720 // Unable to unlink use (external or invalid href?)
2721 if (!unlink) {
2722 new_select.push_back(item);
2723 continue;
2725 } else /*if (is<SPTRef>(use))*/ {
2726 unlink = cast<SPItem>(sp_tref_convert_to_tspan(item));
2727 g_assert(unlink != nullptr);
2730 unlinked = true;
2731 // Add ungrouped items to the new selection.
2732 new_select.push_back(unlink);
2736 if (!new_select.empty()) { // set new selection
2737 clear();
2738 setList(new_select);
2740 if (!unlinked) {
2741 if(desktop() && !silent)
2742 desktop()->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No clones to unlink</b> in the selection."));
2745 if (!skip_undo) {
2746 DocumentUndo::done(document(), _("Unlink clone"), INKSCAPE_ICON("edit-clone-unlink"));
2748 return unlinked;
2751 bool ObjectSet::unlinkRecursive(const bool skip_undo, const bool force, const bool silent) {
2752 if (isEmpty()){
2753 if (desktop() && !silent)
2754 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>clones</b> to unlink."));
2755 return false;
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."));
2763 return false;
2765 bool unlinked = false;
2766 ObjectSet tmp_set(document());
2767 std::vector<SPItem*> items_(items().begin(), items().end());
2768 for (auto& it:items_) {
2769 tmp_set.set(it);
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);
2774 tmp_set.setList(c);
2775 unlinked = tmp_set.unlinkRecursive(skip_undo, force, silent) || unlinked;
2778 if (!unlinked) {
2779 if(desktop() && !silent)
2780 desktop()->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No clones to unlink</b> in the selection."));
2782 if (!skip_undo) {
2783 DocumentUndo::done(document(), _("Unlink clone recursively"), INKSCAPE_ICON("edit-clone-unlink"));
2785 setList(items_);
2786 return unlinked;
2789 void ObjectSet::removeLPESRecursive(bool keep_paths) {
2790 if (isEmpty()){
2791 return;
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);
2800 if (spgroup) {
2801 std::vector<SPObject*> c = spgroup->childList(false);
2802 tmp_set.setList(c);
2803 tmp_set.removeLPESRecursive(keep_paths);
2805 if (splpeitem) {
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));
2812 if (upditem) {
2813 itemsdone_.push_back(upditem);
2815 } else {
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) {
2833 if(desktop())
2834 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, error);
2835 return;
2838 SPItem *original = nullptr;
2839 auto use = cast<SPUse>(item);
2840 if (use) {
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
2851 if(desktop())
2852 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, error);
2853 return;
2856 if (!original) {
2857 if(desktop())
2858 desktop()->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>Cannot find</b> the object to select (orphaned clone, offset, textpath, flowed text?)"));
2859 return;
2862 for (SPObject *o = original; o && !is<SPRoot>(o); o = o->parent) {
2863 if (is<SPDefs>(o)) {
2864 if(desktop())
2865 desktop()->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The object you're trying to select is <b>not visible</b> (it is in &lt;defs&gt;)"));
2866 return;
2870 if (original) {
2871 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
2872 bool highlight = prefs->getBool("/options/highlightoriginal/value");
2873 if (highlight) {
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
2878 SPCurve curve;
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);
2891 clear();
2892 set(original);
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)) {
2911 if (firstItem) {
2912 os << "|";
2913 multiple = true;
2914 } else {
2915 firstItem = item;
2917 os << '#' << item->getId() << ",0,1";
2920 if (firstItem) {
2921 Inkscape::XML::Document *xml_doc = document()->getReprDoc();
2922 SPObject *parent = firstItem->parent;
2923 // create the LPE
2924 Inkscape::XML::Node *lpe_repr = xml_doc->createElement("inkscape:path-effect");
2925 if (multiple) {
2926 lpe_repr->setAttribute("effect", "fill_between_many");
2927 lpe_repr->setAttributeOrRemoveIfEmpty("linkedpaths", os.str());
2928 } else {
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);
2946 if (firstgroup) {
2947 if (!multiple) {
2948 clone = firstgroup->getRepr()->duplicate(xml_doc);
2950 } else {
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());
2955 if (c) {
2956 if (path) {
2957 clone->setAttribute("original-d", sp_svg_write_path(c->get_pathvector()));
2959 clone->setAttribute("d", sp_svg_write_path(c->get_pathvector()));
2960 } else {
2961 clone->setAttribute("d", "M 0 0");
2963 } else {
2964 clone->setAttribute("d", "M 0 0");
2968 if (clone) {
2969 // add the new clone to the top of the original's parent
2970 parent->appendChildRepr(clone);
2971 // select the new object:
2972 set(clone);
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);
2983 if (!skip_undo) {
2984 if (multiple) {
2985 DocumentUndo::done(document(), _("Fill between many"), INKSCAPE_ICON("edit-clone-link-lpe"));
2986 } else {
2987 DocumentUndo::done(document(), _("Clone original"), INKSCAPE_ICON("edit-clone-link-lpe"));
2991 } else {
2992 if(desktop())
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
3005 if (isEmpty()) {
3006 if (desktop())
3007 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE,
3008 _("Select <b>object(s)</b> to convert to marker."));
3009 return;
3012 doc->ensureUpToDate();
3013 Geom::OptRect r = visualBounds();
3014 if (!r) {
3015 return;
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);
3027 if (parentItem) {
3028 parent_transform = parentItem->i2doc_affine();
3029 } else {
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;
3048 if (apply) {
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);
3064 (void)mark_id;
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);
3081 } else {
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());
3092 if (isEmpty()) {
3093 if(desktop())
3094 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to convert to guides."));
3095 return;
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);
3110 if (deleteitems) {
3111 clear();
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>
3136 * transformation).
3138 void ObjectSet::toSymbol()
3141 SPDocument *doc = document();
3142 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
3143 // Check if something is selected.
3144 if (isEmpty()) {
3145 if (desktop())
3146 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>objects</b> to convert to symbol."));
3147 return;
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);
3166 if ( the_group ) {
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();
3227 if (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);
3233 g_free(desc);
3234 settitle = true;
3236 g_free(title);
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.
3258 set(clone);
3260 // Clean up
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);
3273 if (use) {
3274 auto sym = cast<SPSymbol>(use->root());
3275 if (sym) {
3276 sym->unSymbol();
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
3291 if (isEmpty()) {
3292 if (desktop())
3293 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE,
3294 _("Select <b>object(s)</b> to convert to pattern."));
3295 return;
3298 doc->ensureUpToDate();
3299 Geom::OptRect r = visualBounds();
3300 if ( !r ) {
3301 return;
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);
3315 if (parentItem) {
3316 parent_transform = parentItem->i2doc_affine();
3317 } else {
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());
3334 if (apply) {
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);
3356 if (apply) {
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);
3360 g_free(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);
3375 clear();
3376 set(rectangle);
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
3390 if (isEmpty()) {
3391 if(desktop())
3392 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select an <b>object with pattern fill</b> to extract objects from."));
3393 return;
3396 std::vector<SPItem*> new_select;
3398 bool did = false;
3400 std::vector<SPItem*> items_(items().begin(), items().end());
3401 for (std::vector<SPItem*>::const_reverse_iterator i=items_.rbegin();i!=items_.rend();++i){
3402 SPItem *item = *i;
3404 SPStyle *style = item->style;
3406 if (!style || !style->fill.isPaintserver())
3407 continue;
3409 SPPaintServer *server = item->style->getFillPaintServer();
3411 auto basePat = cast<SPPattern>(server);
3412 if (!basePat) {
3413 continue;
3416 did = true;
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();
3434 if (i) {
3435 Geom::Affine transform( i->transform * pat_transform );
3436 i->doWriteTransform(transform);
3438 new_select.push_back(i);
3439 } else {
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");
3450 if (!did) {
3451 if(desktop())
3452 desktop()->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No pattern fills</b> in the selection."));
3453 } else {
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
3466 if (isEmpty()) {
3467 if(desktop())
3468 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to make a bitmap copy."));
3469 return;
3472 if (desktop()) {
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);
3481 if (!bbox) {
3482 if(desktop())
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();
3500 double res;
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
3505 res = prefs_res;
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());
3509 } else {
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();
3515 if (dpi.x()) break;
3517 if (!dpi.x()) {
3518 dpi = doc->getRoot()->getExportDpi();
3520 if (dpi.x()) {
3521 res = dpi.x();
3522 } else {
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);
3543 if (pb) {
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();
3554 // Write transform
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
3561 clear();
3562 add(repr);
3564 // Clean up
3565 Inkscape::GC::release(repr);
3566 delete pb;
3568 // Complete undoable transaction
3569 DocumentUndo::done(doc, _("Create bitmap"), INKSCAPE_ICON("selection-make-bitmap-copy"));
3572 if(desktop()) {
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();
3588 if (isEmpty()) {
3589 if(desktop())
3590 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to create clippath or mask from."));
3591 return;
3594 std::vector<Inkscape::XML::Node*> p(xmlNodes().begin(), xmlNodes().end());
3596 sort(p.begin(),p.end(),sp_repr_compare_position_bool);
3598 clear();
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());
3620 if (t_str)
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();
3638 // make a copy
3639 Inkscape::XML::Node *spnew = in_topmost->duplicate(xml_doc);
3640 // remove pasted
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);
3668 g_free(tmp);
3670 Inkscape::GC::release(clone);
3672 set(outer);
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
3682 Drawing drawing;
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);
3692 drawing.update();
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)
3727 return;
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) {
3735 if(desktop())
3736 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to create clippath or mask from."));
3737 return;
3738 } else if (!apply_to_layer && ( is_empty || boost::distance(items())==1 )) {
3739 if(desktop())
3740 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select mask object and <b>object(s)</b> to apply clippath or mask to."));
3741 return;
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
3751 // /END FIXME
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
3760 clear();
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())
3780 || apply_to_layer){
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);
3788 else {
3789 items_to_select.push_back(i);
3791 continue;
3792 }else{
3793 apply_to_items.push_back(i);
3794 items_to_select.push_back(i);
3798 items_.clear();
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);
3860 } else {
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
3891 if (isEmpty()) {
3892 if(desktop())
3893 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to remove clippath or mask from."));
3894 return;
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());
3905 clear();
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
3915 SPItem *item = i;
3916 SPObject *obj_ref = nullptr;
3918 if (apply_clip_path) {
3919 obj_ref = item->getClipObject();
3920 } else {
3921 obj_ref = item->getMaskObject();
3924 // collect distinct mask object (and associate with item to apply transform)
3925 if (obj_ref) {
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));
3978 if (!mask_item) {
3979 continue;
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) {
3990 if (group) {
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());
3995 } else {
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);
4013 if (isEmpty()) {
4014 if(desktop())
4015 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to fit canvas to."));
4016 return false;
4018 Geom::OptRect const bbox = documentBounds(SPItem::VISUAL_BBOX);
4019 if (bbox) {
4020 document()->fitToRect(*bbox, with_margins);
4021 if(!skip_undo)
4022 DocumentUndo::done(document(), _("Fit Page to Selection"), "");
4023 return true;
4024 } else {
4025 return false;
4029 void ObjectSet::swapFillStroke()
4032 SPIPaint *paint;
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);
4052 if (server) {
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);
4074 if (server) {
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());
4083 if (desktop()) {
4084 Inkscape::ObjectSet set{};
4085 set.add(item);
4086 sp_desktop_set_style(&set, desktop(), css);
4087 } else {
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
4101 * fill.
4103 * All objects referred to must have an ID. If an ID does not exist, it will be
4104 * created.
4106 * As an additional timesaver, the fill object is created below the bottommost
4107 * object in the selection.
4109 void ObjectSet::fillBetweenMany()
4111 if (isEmpty()) {
4112 if (desktop()) {
4113 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>path(s)</b> to create fill between."));
4116 return;
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");
4126 Glib::ustring acc;
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());
4134 item->updateRepr();
4137 acc += "#";
4138 acc += item->getId();
4139 acc += ",0,1|";
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);
4148 pathTarget += "#";
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();
4164 clear();
4165 add(fillRepr);
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
4174 * @param item
4175 * @return the SiblingState
4177 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;
4213 return ret;
4216 void
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.
4228 bool
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();
4236 if (bbox) {
4237 doc->fitToRect(*bbox, with_margins);
4238 return true;
4239 } else {
4240 return false;
4244 void
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)) {
4256 f(item, desktop);
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) {
4281 if (!dt) return;
4283 SPObject *root;
4284 if (layer_only) {
4285 root = dt->layerManager().currentLayer();
4286 } else {
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);
4310 Local Variables:
4311 mode:c++
4312 c-file-style:"stroustrup"
4313 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4314 indent-tabs-mode:nil
4315 fill-column:99
4316 End:
4318 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :