Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
selection-chemistry.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
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
18 *
19 * Copyright (C) 1999-2016 authors
20 * Copyright (C) 2001-2002 Ximian, Inc.
21 *
22 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
23 */
24
25#include "selection-chemistry.h"
26
27#include <boost/range/adaptor/reversed.hpp>
28#include <cstring>
29#include <glibmm/i18n.h>
30#include <map>
31#include <string>
32
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"
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"
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"
59#include "object/sp-flowtext.h"
60#include "object/sp-image.h"
62#include "object/sp-item.h"
63#include "object/sp-line.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"
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"
96#include "ui/tools/node-tool.h"
97#include "ui/tools/text-tool.h"
98#include "ui/widget/canvas.h" // is_dragging()
100#include "xml/rebase-hrefs.h"
101
102// TODO FIXME: This should be moved into preference repr
104
106using Geom::X;
107using Geom::Y;
111using namespace Inkscape;
112
113/* The clipboard handling is in ui/clipboard.cpp now. There are some legacy functions left here,
114because the layer manipulation code uses them. It should be rewritten specifically
115for that purpose. */
116
117
118// helper for printing error messages, regardless of whether we have a GUI or not
119// If desktop == NULL, errors will be shown on stderr
120static void
122{
123 if (desktop) {
124 desktop->messageStack()->flash(msgType, msg);
125 } else {
126 if (msgType == Inkscape::IMMEDIATE_MESSAGE ||
127 msgType == Inkscape::WARNING_MESSAGE ||
128 msgType == Inkscape::ERROR_MESSAGE) {
129 g_printerr("%s\n", msg.c_str());
130 }
131 }
132}
133
134namespace Inkscape {
135
137{
138 NodeTool *nt = dynamic_cast<NodeTool*>(dt->getTool());
139 if (nt) {
140 if (!nt->_multipath->empty()) {
142 return;
143 }
144 }
146}
147
149{
150 NodeTool *nt = dynamic_cast<NodeTool*>(dt->getTool());
151 if (nt) {
153 } else {
155 }
156}
157
159{
160 NodeTool *nt = dynamic_cast<NodeTool*>(dt->getTool());
161 if (nt && !nt->_selected_nodes->empty()) {
162 nt->_selected_nodes->clear();
163 } else if (!dt->getSelection()->isEmpty()) {
164 dt->getSelection()->clear();
165 } else {
166 // If nothing selected switch to selection tool
167 set_active_tool(dt, "Select");
168 }
169}
170
175
180
185
190
195
197{
198 NodeTool *nt = dynamic_cast<NodeTool*>(dt->getTool());
199 if (nt) {
201 } else {
202 sp_edit_invert(dt);
203 }
204}
205
207{
208 NodeTool *nt = dynamic_cast<NodeTool*>(dt->getTool());
209 if (nt) {
211 } else {
213 }
214}
215
217{
218 // TODO make this a virtual method of event context!
219 NodeTool *nt = dynamic_cast<NodeTool*>(dt->getTool());
220 if (nt) {
222 } else {
223 dt->getSelection()->pathReverse();
224 }
225}
226
227/*
228 * Fixes the current selection, removing locked objects from it
229 */
231{
232 if (!dt) {
233 return;
234 }
235
236 Inkscape::Selection *selection = dt->getSelection();
237
238 std::vector<SPItem*> items;
239
240 auto selList = selection->items();
241
242 for (auto item : selList | boost::adaptors::reversed) {
243 if (item &&
244 !dt->layerManager().isLayer(item) &&
245 !item->isLocked())
246 {
247 items.push_back(item);
248 }
249 }
250
251 selection->setList(items);
252}
253
254} // namespace Inkscape
255
260static 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{
262 Inkscape::XML::Node *copy = repr->duplicate(xml_doc);
263
264 // copy complete inherited style
265 SPCSSAttr *css = sp_repr_css_attr_inherited(repr, "style");
266 sp_repr_css_set(copy, css, "style");
268
269 // write the complete accumulated transform passed to us
270 // (we're dealing with unattached repr, so we write to its attr
271 // instead of using sp_item_set_transform)
272 copy->setAttributeOrRemoveIfEmpty("transform", sp_svg_transform_write(full_t));
273
274 clip.insert(clip.begin(),copy);
275}
276
277static void sp_selection_copy_impl(std::vector<SPItem*> const &items, std::vector<Inkscape::XML::Node*> &clip, Inkscape::XML::Document* xml_doc)
278{
279 // Sort items:
280 std::vector<SPItem*> sorted_items(items);
281 sort(sorted_items.begin(),sorted_items.end(),sp_object_compare_position_bool);
282
283 // Copy item reprs:
284 for (auto item : sorted_items) {
285 if (item) {
287 } else {
288 g_assert_not_reached();
289 }
290 }
291 reverse(clip.begin(),clip.end());
292}
293
294// TODO check if parent parameter should be changed to SPItem, of if the code should handle non-items.
295static std::vector<Inkscape::XML::Node *> sp_selection_paste_impl(SPDocument *doc, SPObject *parent,
296 std::vector<Inkscape::XML::Node *> &clip,
297 Inkscape::XML::Node *after = nullptr)
298{
299 assert(!after || after->parent() == parent->getRepr());
300 assert(!parent->cloned);
301
302 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
303
304 auto parentItem = cast<SPItem>(parent);
305 g_assert(parentItem);
306
307 std::vector<Inkscape::XML::Node*> copied;
308 // add objects to document
309 for (auto repr : clip) {
310 Inkscape::XML::Node *copy = repr->duplicate(xml_doc);
311
312 // premultiply the item transform by the accumulated parent transform in the paste layer
313 auto const local = parentItem->i2doc_affine();
314 if (!local.isIdentity()) {
315 char const *t_str = copy->attribute("transform");
316 Geom::Affine item_t;
317 if (t_str) {
318 sp_svg_transform_read(t_str, &item_t);
319 }
320 item_t *= local.inverse();
321 // (we're dealing with unattached repr, so we write to its attr instead of using sp_item_set_transform)
322 copy->setAttributeOrRemoveIfEmpty("transform", sp_svg_transform_write(item_t));
323 if (copy->attribute("inkscape:original-d")) {
324 copy->setAttribute("d", copy->attribute("inkscape:original-d"));
325 }
326 }
327
328 parent->getRepr()->addChild(copy, after);
329 after = copy;
330
331 copied.push_back(copy);
333 }
334 return copied;
335}
336
337static void sp_selection_delete_impl(std::vector<SPItem*> const &items, bool propagate = true, bool propagate_descendants = true)
338{
339 for (auto item : items) {
340 sp_object_ref(item, nullptr);
341 }
342 for (auto item : items) {
343 item->deleteObject(propagate, propagate_descendants);
344 sp_object_unref(item, nullptr);
345 }
346}
347
348void ObjectSet::deleteItems(bool skip_undo)
349{
350 if (isEmpty() && !skip_undo) {
351 selection_display_message(desktop(),Inkscape::WARNING_MESSAGE, _("<b>Nothing</b> was deleted."));
352 return;
353 }
354
355 std::vector<SPItem*> selected(items().begin(), items().end());
356 clear();
357 sp_selection_delete_impl(selected);
358
359 if (skip_undo) {
360 return;
361 }
362
363 if (SPDesktop *dt = desktop()) {
364 dt->layerManager().currentLayer()->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
365
366 /* A tool may have set up private information in its selection context
367 * that depends on desktop items. I think the only sane way to deal with
368 * this currently is to reset the tool which will reset its
369 * associated selection context. For example: deleting an object
370 * while moving it around the canvas.
371 * We copy the string so we donʼt pass reference to member of the tool that is being reset.
372 */
373 dt->setTool(std::string{dt->getTool()->getPrefsPath()});
374 }
375
376 if(document()) {
377 DocumentUndo::done(document(), _("Delete"), INKSCAPE_ICON("edit-delete"));
378 }
379}
380
381
382
383static void add_ids_recursive(std::vector<const gchar *> &ids, SPObject *obj)
384{
385 if (obj) {
386 ids.push_back(obj->getId());
387
388 if (is<SPGroup>(obj)) {
389 for (auto& child: obj->children) {
391 }
392 }
393 }
394}
395
396void ObjectSet::duplicate(bool suppressDone, bool duplicateLayer)
397{
398 if(duplicateLayer && !desktop() ){
399 //TODO: understand why layer management is tied to desktop and not to document.
400 return;
401 }
402
403 SPDocument *doc = document();
404
405 if(!doc)
406 return;
407
408 Inkscape::XML::Document* xml_doc = doc->getReprDoc();
409
410 // check if something is selected
411 if (isEmpty() && !duplicateLayer) {
412 selection_display_message(desktop(),Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to duplicate."));
413 return;
414 }
415 std::vector<Inkscape::XML::Node*> reprs(xmlNodes().begin(), xmlNodes().end());
416
417 if(duplicateLayer){
418 reprs.clear();
419 reprs.push_back(desktop()->layerManager().currentLayer()->getRepr());
420 }
421
422 clear();
423
424 std::vector<SPItem *> items;
425 for(auto old_repr : reprs) {
426 auto item = cast<SPItem>(doc->getObjectByRepr(old_repr));
427 if (item) {
428 items.push_back(item);
429 auto lpeitem = cast<SPLPEItem>(item);
430 if (lpeitem) {
431 for (auto satellite : lpeitem->get_satellites(false, true, true)) {
432 if (satellite) {
433 auto item2 = cast<SPItem>(satellite);
434 if (item2 && std::find(items.begin(), items.end(), item2) == items.end()) {
435 items.push_back(item2);
436 }
437 }
438 }
439 }
440 }
441 }
442 for(auto item : items) {
443 if (std::find(reprs.begin(), reprs.end(), item->getRepr()) == reprs.end()) {
444 reprs.push_back(item->getRepr());
445 }
446 }
447 // sorting items from different parents sorts each parent's subset without possibly mixing
448 // them, just what we need
449 sort(reprs.begin(),reprs.end(),sp_repr_compare_position_bool);
450
451 std::vector<const gchar *> old_ids;
452 std::vector<const gchar *> new_ids;
454 bool relink_clones = prefs->getBool("/options/relinkclonesonduplicate/value");
455 const bool fork_livepatheffects = prefs->getBool("/options/forklpeonduplicate/value", true);
456
457 // check ref-d shapes, split in defs|internal|external
458 // add external & defs to reprs
459 auto text_refs = text_categorize_refs(doc, reprs.begin(), reprs.end(),
461 for (auto const &ref : text_refs) {
462 if (ref.second == TEXT_REF_DEF || ref.second == TEXT_REF_EXTERNAL) {
463 reprs.push_back(doc->getObjectById(ref.first)->getRepr());
464 }
465 }
466
467 std::vector<Inkscape::XML::Node*> copies;
468 for(auto old_repr : reprs) {
469 Inkscape::XML::Node *parent = old_repr->parent();
470 Inkscape::XML::Node *copy = old_repr->duplicate(xml_doc);
471
472 if (!duplicateLayer || sp_repr_is_def(old_repr)) {
474 } else if (sp_repr_is_layer(old_repr)) {
475 parent->addChild(copy, old_repr);
476 } else {
477 // duplicateLayer, non-layer, non-def
478 // external nodes -- append to new layer
479 // text_relink will ignore extra nodes in layer children
480 copies[0]->appendChild(copy);
481 }
482 SPObject *old_obj = doc->getObjectByRepr(old_repr);
483 SPObject *new_obj = doc->getObjectByRepr(copy);
484 if (old_obj && new_obj) {
485 old_obj->setTmpSuccessor(new_obj);
486 }
487 if (relink_clones) {
488 add_ids_recursive(old_ids, old_obj);
489 add_ids_recursive(new_ids, new_obj);
490 }
491
492 copies.push_back(copy);
494 }
495
496 // Relink copied text nodes to copied reference shapes
497 text_relink_refs(text_refs, reprs.begin(), reprs.end(), copies.begin());
498
499 // copies contains def nodes, we don't want that in our selection
500 std::vector<Inkscape::XML::Node*> newsel;
501 if (!duplicateLayer) {
502 // compute newsel, by removing def nodes from copies
503 for (auto node : copies) {
504 // hide on duple this is done to dont show autoselected hidden LPE items satellites
505 // is only a make up if at any point we think is better keep selected items reselected on duple
506 // please roll back or make some more loops to handle well, keep as it for speed
507 // and simplicity
508 auto itm = cast<SPItem>(doc->getObjectByRepr(node));
509 if (!sp_repr_is_def(node) && (!itm || !itm->isHidden())) {
510 newsel.push_back(node);
511 }
512 }
513 }
514
515 if (relink_clones) {
516
517 g_assert(old_ids.size() == new_ids.size());
518
519 for (unsigned int i = 0; i < old_ids.size(); i++) {
520 const gchar *id = old_ids[i];
521 SPObject *old_clone = doc->getObjectById(id);
522 auto use = cast<SPUse>(old_clone);
523 auto offset = cast<SPOffset>(old_clone);
524 auto text = cast<SPText>(old_clone);
525 auto path = cast<SPPath>(old_clone);
526 if (use) {
527 SPItem *orig = use->get_original();
528 if (!orig) // orphaned
529 continue;
530 for (unsigned int j = 0; j < old_ids.size(); j++) {
531 if (!strcmp(orig->getId(), old_ids[j])) {
532 // we have both orig and clone in selection, relink
533 // std::cout << id << " old, its ori: " << orig->getId() << "; will relink:" << new_ids[i] << " to " << new_ids[j] << "\n";
534 SPObject *new_clone = doc->getObjectById(new_ids[i]);
535 new_clone->setAttribute("xlink:href", Glib::ustring("#") + new_ids[j]);
536 new_clone->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
537 }
538 }
539 } else if (offset) {
540 gchar *source_href = offset->sourceHref;
541 for (guint j = 0; j < old_ids.size(); j++) {
542 if (source_href && source_href[0]=='#' && !strcmp(source_href+1, old_ids[j])) {
543 doc->getObjectById(new_ids[i])->setAttribute("xlink:href", Glib::ustring("#") + new_ids[j]);
544 }
545 }
546 } else if (text) {
547 auto textpath = cast<SPTextPath>(text->firstChild());
548 if (!textpath) continue;
549 const gchar *source_href = sp_textpath_get_path_item(textpath)->getId();
550 for (guint j = 0; j < old_ids.size(); j++) {
551 if (!strcmp(source_href, old_ids[j])) {
552 doc->getObjectById(new_ids[i])->firstChild()->setAttribute("xlink:href", Glib::ustring("#") + new_ids[j]);
553 }
554 }
555 } else if (path) {
556 if (old_clone->getAttribute("inkscape:connection-start") != nullptr) {
557 const char *old_start = old_clone->getAttribute("inkscape:connection-start");
558 const char *old_end = old_clone->getAttribute("inkscape:connection-end");
559 SPObject *new_clone = doc->getObjectById(new_ids[i]);
560 for (guint j = 0; j < old_ids.size(); j++) {
561 if(old_start == Glib::ustring("#") + old_ids[j]) {
562 new_clone->setAttribute("inkscape:connection-start", Glib::ustring("#") + new_ids[j]);
563 }
564 if(old_end == Glib::ustring("#") + old_ids[j]) {
565 new_clone->setAttribute("inkscape:connection-end", Glib::ustring("#") + new_ids[j]);
566 }
567 }
568 }
569 }
570 }
571 }
572 for (auto node : copies) {
573 if (fork_livepatheffects) {
574 SPObject *new_obj = doc->getObjectByRepr(node);
575 auto newLPEObj = cast<SPLPEItem>(new_obj);
576 if (newLPEObj) {
577 // force always fork
578 newLPEObj->forkPathEffectsIfNecessary(1, true, true);
579 sp_lpe_item_update_patheffect(newLPEObj, false, true, true);
580 }
581 }
582 }
583 for(auto old_repr : reprs) {
584 SPObject *old_obj = doc->getObjectByRepr(old_repr);
585 if (old_obj) {
586 old_obj->fixTmpSuccessors();
587 old_obj->unsetTmpSuccessor();
588 }
589 }
590
591 if (!duplicateLayer) {
592 setReprList(newsel);
593 if ( !suppressDone ) {
594 DocumentUndo::done(document(), _("Duplicate"), INKSCAPE_ICON("edit-duplicate"));
595 }
596 } else {
597 if ( !suppressDone ) {
598 DocumentUndo::done(document(), _("Duplicate"), INKSCAPE_ICON("edit-duplicate"));
599 }
600 SPObject* new_layer = doc->getObjectByRepr(copies[0]);
601
602 if (auto label = new_layer->label()) {
603 if (std::string(label).find("copy") == std::string::npos) {
604 gchar* name = g_strdup_printf(_("%s copy"), label);
605 desktop()->layerManager().renameLayer(new_layer, name, true);
606 g_free(name);
607 }
608 }
609 }
610
611}
612
614{
615 if (!selection)
616 return;
617
618 auto desktop = selection->desktop();
620 selection->clear();
621
622 auto group = desktop->layerManager().currentLayer();
623 g_return_if_fail(group);
624 std::vector<SPItem*> items = group->item_list();
625
626 for(auto & item : items){
628 }
629
630 DocumentUndo::done(doc, _("Delete all"), "");
631}
632
633/*
634 * Return a list of SPItems that are the children of 'list'
635 *
636 * list - source list of items to search in
637 * desktop - desktop associated with the source list
638 * exclude - list of items to exclude from result
639 * onlyvisible - TRUE includes only items visible on canvas
640 * onlysensitive - TRUE includes only non-locked items
641 * ingroups - TRUE to recursively get grouped items children
642 */
643static 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{
645 for (auto &child : from->children) {
646 auto item = cast<SPItem>(&child);
647 if (item &&
649 (!onlysensitive || !item->isLocked()) &&
650 (!onlyvisible || !desktop->itemIsHidden(item)) &&
651 (exclude.empty() || std::find(exclude.begin(), exclude.end(), &child) == exclude.end()))
652 {
653 list.emplace_back(item);
654 }
655
656 if (ingroups || (item && desktop->layerManager().isLayer(item))) {
657 get_all_items_recursive(list, &child, desktop, onlyvisible, onlysensitive, ingroups, exclude);
658 }
659 }
660}
661
662std::vector<SPItem*> get_all_items(SPObject *from, SPDesktop *desktop, bool onlyvisible, bool onlysensitive, bool ingroups, std::vector<SPItem*> const &exclude)
663{
664 std::vector<SPItem*> list;
665 get_all_items_recursive(list, from, desktop, onlyvisible, onlysensitive, ingroups, exclude);
666 std::reverse(list.begin(), list.end()); // Todo: For compatibility; is it necessary?
667 return list;
668}
669
670static void sp_edit_select_all_full(SPDesktop *dt, bool force_all_layers, bool invert)
671{
672 if (!dt)
673 return;
674
675 Inkscape::Selection *selection = dt->getSelection();
676
677 auto layer = dt->layerManager().currentLayer();
678 g_return_if_fail(layer);
679
681 PrefsSelectionContext inlayer = (PrefsSelectionContext) prefs->getInt("/options/kbselection/inlayer", PREFS_SELECTION_LAYER);
682 bool onlyvisible = prefs->getBool("/options/kbselection/onlyvisible", true);
683 bool onlysensitive = prefs->getBool("/options/kbselection/onlysensitive", true);
684
685 std::vector<SPItem*> items ;
686
687 std::vector<SPItem*> exclude;
688 if (invert) {
689 exclude.insert(exclude.end(), selection->items().begin(), selection->items().end());
690 }
691
692 if (force_all_layers)
693 inlayer = PREFS_SELECTION_ALL;
694
695 switch (inlayer) {
697 if ((onlysensitive && layer->isLocked()) || (onlyvisible && dt->itemIsHidden(layer))) {
698 return;
699 }
700
701 auto const item_list = layer->item_list();
702 for (auto item : item_list | boost::adaptors::reversed) {
703 if (item && (!onlysensitive || !item->isLocked())) {
704 if (!onlyvisible || !dt->itemIsHidden(item)) {
705 if (!dt->layerManager().isLayer(item)) {
706 if (!invert || exclude.end() == std::find(exclude.begin(),exclude.end(),item)) {
707 items.push_back(item); // leave it in the list
708 }
709 }
710 }
711 }
712 }
713
714 break;
715 }
717 items = get_all_items(dt->layerManager().currentLayer(), dt, onlyvisible, onlysensitive, FALSE, exclude);
718 break;
719 }
720 default: {
721 items = get_all_items(dt->layerManager().currentRoot(), dt, onlyvisible, onlysensitive, FALSE, exclude);
722 break;
723 }
724 }
725
726 selection->setList(items);
727}
728
733
738
743
748
750 SPDocument *doc = document();
751 if(!doc)
752 return nullptr;
753 if (isEmpty()) {
754 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE, _("Select <b>some objects</b> to group."));
755 return nullptr;
756 }
757 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
758 Inkscape::XML::Node *group = xml_doc->createElement(is_anchor ? "svg:a" : "svg:g");
759
760 std::vector<Inkscape::XML::Node*> p(xmlNodes().begin(), xmlNodes().end());
761 std::sort(p.begin(), p.end(), sp_repr_compare_position_bool);
762 this->clear();
763
764 // Remember the position and parent of the topmost object.
765 Inkscape::XML::Node *topmost = p.back();
766 Inkscape::XML::Node *topmost_parent = topmost->parent();
767
768 // Find the topmost object first
769 for(auto current : p){
770 if (current->parent() == topmost_parent) {
771 if (current->position() > topmost->position()) {
772 topmost = current;
773 }
774 }
775 }
776 // Add as close to the top as we can get it
777 topmost_parent->addChild(group, topmost);
778
779 for(auto current : p){
780 if (current->parent() == topmost_parent) {
781
782 Inkscape::XML::Node *spnew = current->duplicate(xml_doc);
784 group->appendChild(spnew);
786
787 } else { // move it to topmost_parent first
788 std::vector<Inkscape::XML::Node*> temp_clip;
789
790 // At this point, current may already have no item, due to its being a clone whose original is already moved away
791 // So we copy it artificially calculating the transform from its repr->attr("transform") and the parent transform
792 gchar const *t_str = current->attribute("transform");
794 if (t_str)
795 sp_svg_transform_read(t_str, &item_t);
796 auto parent_item = cast<SPItem>(doc->getObjectByRepr(current->parent()));
797 assert(parent_item);
798 item_t *= parent_item->i2doc_affine();
799 // FIXME: when moving both clone and original from a transformed group (either by
800 // grouping into another parent, or by cut/paste) the transform from the original's
801 // parent becomes embedded into original itself, and this affects its clones. Fix
802 // this by remembering the transform diffs we write to each item into an array and
803 // then, if this is clone, looking up its original in that array and pre-multiplying
804 // it by the inverse of that original's transform diff.
805
806 sp_selection_copy_one(current, item_t, temp_clip, xml_doc);
808
809 // paste into topmost_parent (temporarily)
810 std::vector<Inkscape::XML::Node*> copied = sp_selection_paste_impl(doc, doc->getObjectByRepr(topmost_parent), temp_clip);
811 if (!temp_clip.empty())temp_clip.clear() ;
812 if (!copied.empty()) { // if success,
813 // take pasted object (now in topmost_parent)
814 Inkscape::XML::Node *in_topmost = copied.back();
815 // make a copy
816 Inkscape::XML::Node *spnew = in_topmost->duplicate(xml_doc);
817 // remove pasted
818 sp_repr_unparent(in_topmost);
819 // put its copy into group
820 group->appendChild(spnew);
822 copied.clear();
823 }
824 }
825 }
826
828
829 return group;
830}
831
833 if (isEmpty()) {
834 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE, _("<b>No objects selected</b> to pop out of group."));
835 return;
836 }
837
838 std::set<SPObject*> grandparents;
839
840 for (auto *obj : items()) {
841 auto parent_group = cast<SPGroup>(obj->parent);
842 if (!parent_group || !parent_group->parent || SP_IS_LAYER(parent_group)) {
843 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE, _("Selection <b>not in a group</b>."));
844 return;
845 }
846 grandparents.insert(parent_group->parent);
847 }
848
849 assert(!grandparents.empty());
850
851 if (grandparents.size() > 1) {
853 _("Objects in selection must have the same grandparents."));
854 return;
855 }
856
857 toLayer(*grandparents.begin());
858
859 if(document())
860 DocumentUndo::done(document(), _("Pop selection from group"), INKSCAPE_ICON("object-ungroup-pop-selection"));
861
862}
863
869template <typename Objects>
870static SPUse *find_clone_to_group(Objects const &objects, std::set<SPGroup *> const &groups)
871{
872 assert(!groups.count(nullptr));
873
874 for (auto *obj : objects) {
875 if (auto *use = cast<SPUse>(obj)) {
876 if (auto root = use->root()) {
877 if (groups.count(cast_unsafe<SPGroup>(root->clone_original))) {
878 return use;
879 }
880 }
881 }
882
883 if (auto *use = find_clone_to_group(obj->childList(false), groups)) {
884 return use;
885 }
886 }
887
888 return nullptr;
889}
890
901{
902 std::set<SPGroup *> const groups(set->groups().begin(), set->groups().end());
903
904 while (auto *use = find_clone_to_group(set->items(), groups)) {
905 bool const readd = set->includes(use);
906 auto const unlinked = use->unlink();
907 if (readd) {
908 set->add(unlinked, true);
909 }
910 }
911
912 std::vector<SPItem *> children;
913
914 for (auto *group : groups) {
915 sp_item_group_ungroup(group, children);
916 }
917
918 set->addList(children);
919}
920
921void ObjectSet::ungroup(bool skip_undo)
922{
923 if (isEmpty()) {
924 if(desktop())
925 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE, _("Select a <b>group</b> to ungroup."));
926 return;
927 }
928
929 if (boost::distance(groups()) == 0) {
930 if(desktop())
931 selection_display_message(desktop(), Inkscape::ERROR_MESSAGE, _("<b>No groups</b> to ungroup in the selection."));
932 return;
933 }
934
935 ungroup_impl(this);
936 if(document() && !skip_undo)
937 DocumentUndo::done(document(), _("Ungroup"), INKSCAPE_ICON("object-ungroup"));
938}
939
943void ObjectSet::ungroup_all(bool skip_undo)
944{
945 std::size_t last = 0;
946 while (size() != last) {
947 last = size();
948 ungroup(skip_undo);
949 }
950}
951
953static SPGroup *
955{
956 if (items.empty()) {
957 return nullptr;
958 }
959 SPObject *parent = items.front()->parent;
960 // Strictly speaking this CAN happen, if user selects <svg> from Inkscape::XML editor
961 if (!is<SPGroup>(parent)) {
962 return nullptr;
963 }
964 for (auto item : items) {
965 if (item == items.front()) {
966 continue;
967 }
968 if (item->parent != parent) {
969 return nullptr;
970 }
971 }
972
973 return cast<SPGroup>(parent);
974}
975
977static Geom::OptRect
978enclose_items(std::vector<SPItem*> const &items)
979{
980 g_assert(!items.empty());
981
983 for (auto item : items) {
985 }
986 return r;
987}
988
989// TODO determine if this is intentionally different from SPObject::getPrev()
991{
992 SPObject *prev = nullptr;
993 if ( child && cast<SPGroup>(child->parent) ) {
994 prev = child->getPrev();
995 }
996 return prev;
997}
998
999void ObjectSet::raise(bool skip_undo){
1000
1001 if(isEmpty()){
1002 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to raise."));
1003 return;
1004 }
1005
1007 if (!group) {
1008 if(desktop())
1009 selection_display_message(desktop(), Inkscape::ERROR_MESSAGE, _("You cannot raise/lower objects from <b>different groups</b> or <b>layers</b>."));
1010 return;
1011 }
1012
1013 std::vector<SPItem*> items_copy(items().begin(), items().end());
1014 Inkscape::XML::Node *grepr = const_cast<Inkscape::XML::Node *>(items_copy.front()->parent->getRepr());
1015
1016 /* Construct reverse-ordered list of selected children. */
1017 std::vector<SPItem*> rev(items_copy);
1018 sort(rev.begin(),rev.end(),sp_item_repr_compare_position_bool);
1019
1020 // Determine the common bbox of the selected items.
1021 Geom::OptRect selected = enclose_items(items_copy);
1022
1023 // Iterate over all objects in the selection (starting from top).
1024 if (selected) {
1025 for (auto child : rev) {
1026 // for each selected object, find the next sibling
1027 for (SPObject *newref = child->getNext(); newref; newref = newref->getNext()) {
1028 // if the sibling is an item AND overlaps our selection,
1029 auto newItem = cast<SPItem>(newref);
1030 if (newItem) {
1031 Geom::OptRect newref_bbox = newItem->documentVisualBounds();
1032 if ( newref_bbox && selected->intersects(*newref_bbox) ) {
1033 // AND if it's not one of our selected objects,
1034 if ( std::find(items_copy.begin(),items_copy.end(),newref)==items_copy.end()) {
1035 // move the selected object after that sibling
1036 grepr->changeOrder(child->getRepr(), newref->getRepr());
1037 }
1038 break;
1039 }
1040 }
1041 }
1042 }
1043 }
1044 if (document() && !skip_undo) {
1045 DocumentUndo::done(document(), C_("Undo action", "Raise"), INKSCAPE_ICON("selection-raise"));
1046 }
1047}
1048
1049
1050void ObjectSet::raiseToTop(bool skip_undo) {
1051 if (isEmpty()) {
1052 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to raise."));
1053 return;
1054 }
1055
1057 if (!group) {
1058 selection_display_message(desktop(), Inkscape::ERROR_MESSAGE, _("You cannot raise/lower objects from <b>different groups</b> or <b>layers</b>."));
1059 return;
1060 }
1061
1062 std::vector<Inkscape::XML::Node*> rl(xmlNodes().begin(), xmlNodes().end());
1063 sort(rl.begin(),rl.end(),sp_repr_compare_position_bool);
1064
1065 for (auto repr : rl) {
1066 repr->setPosition(-1);
1067 }
1068 if (document() && !skip_undo) {
1069 DocumentUndo::done(document(), _("Raise to top"), INKSCAPE_ICON("selection-top"));
1070 }
1071}
1072
1073void ObjectSet::lower(bool skip_undo)
1074{
1075 if(isEmpty()){
1076 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to lower."));
1077 return;
1078 }
1079
1081 if (!group) {
1082 selection_display_message(desktop(), Inkscape::ERROR_MESSAGE, _("You cannot raise/lower objects from <b>different groups</b> or <b>layers</b>."));
1083 return;
1084 }
1085
1086 std::vector<SPItem*> items_copy(items().begin(), items().end());
1087 Inkscape::XML::Node *grepr = const_cast<Inkscape::XML::Node *>(items_copy.front()->parent->getRepr());
1088
1089 // Determine the common bbox of the selected items.
1090 Geom::OptRect selected = enclose_items(items_copy);
1091
1092 /* Construct direct-ordered list of selected children. */
1093 std::vector<SPItem*> rev(items_copy);
1094 sort(rev.begin(),rev.end(),sp_item_repr_compare_position_bool);
1095
1096 // Iterate over all objects in the selection (starting from top).
1097 if (selected) {
1098 for (auto child : rev | boost::adaptors::reversed) {
1099 // for each selected object, find the prev sibling
1100 for (SPObject *newref = prev_sibling(child); newref; newref = prev_sibling(newref)) {
1101 // if the sibling is an item AND overlaps our selection,
1102 auto newItem = cast<SPItem>(newref);
1103 if (newItem) {
1104 Geom::OptRect ref_bbox = newItem->documentVisualBounds();
1105 if ( ref_bbox && selected->intersects(*ref_bbox) ) {
1106 // AND if it's not one of our selected objects,
1107 if (std::find(items_copy.begin(), items_copy.end(), newref) == items_copy.end()) {
1108 // move the selected object before that sibling
1109 if (auto put_after = prev_sibling(newref))
1110 grepr->changeOrder(child->getRepr(), put_after->getRepr());
1111 else
1112 child->getRepr()->setPosition(0);
1113 }
1114 break;
1115 }
1116 }
1117 }
1118 }
1119 }
1120 if(document() && !skip_undo)
1121 DocumentUndo::done(document(), C_("Undo action", "Lower"), INKSCAPE_ICON("selection-lower"));
1122}
1123
1124
1125void ObjectSet::lowerToBottom(bool skip_undo)
1126{
1127 if(!document())
1128 return;
1129 if (isEmpty()) {
1130 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to lower to bottom."));
1131 return;
1132 }
1133
1135 if (!group) {
1136 selection_display_message(desktop(), Inkscape::ERROR_MESSAGE, _("You cannot raise/lower objects from <b>different groups</b> or <b>layers</b>."));
1137 return;
1138 }
1139
1140 std::vector<Inkscape::XML::Node*> rl(xmlNodes().begin(), xmlNodes().end());
1141 sort(rl.begin(),rl.end(),sp_repr_compare_position_bool);
1142
1143 for (auto const repr : rl | boost::adaptors::reversed) {
1144 int minpos = 0;
1145 auto const pp = document()->getObjectByRepr(repr->parent());
1146 g_assert(is<SPGroup>(pp));
1147 for (auto &pc : pp->children) {
1148 if (is<SPItem>(&pc)) {
1149 break;
1150 }
1151 minpos++;
1152 }
1153 repr->setPosition(minpos);
1154 }
1155 if (document() && !skip_undo) {
1156 DocumentUndo::done(document(), _("Lower to bottom"), INKSCAPE_ICON("selection-bottom"));
1157 }
1158}
1159
1160void ObjectSet::stackUp(bool skip_undo) {
1161 if (isEmpty()) {
1162 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to stack up."));
1163 return;
1164 }
1165
1166 std::vector<SPItem*> selection(items().begin(), items().end());
1167 sort(selection.begin(), selection.end(), sp_item_repr_compare_position_bool);
1168
1169 for (auto item : selection | boost::adaptors::reversed) {
1170 if (!item->raiseOne()) { // stop if top was reached
1171 if(document() && !skip_undo)
1174 return;
1175 }
1176 }
1177
1178 if(document() && !skip_undo)
1179 DocumentUndo::done(document(), C_("Undo action", "stack up"), INKSCAPE_ICON("layer-raise"));
1180}
1181
1182void ObjectSet::stackDown(bool skip_undo) {
1183 if (isEmpty()) {
1184 selection_display_message(desktop(), Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to stack down."));
1185 return;
1186 }
1187
1188 std::vector<SPItem*> selection(items().begin(), items().end());
1189 sort(selection.begin(), selection.end(), sp_item_repr_compare_position_bool);
1190
1191 for (auto item: selection) {
1192 if (!item->lowerOne()) { // stop if bottom was reached
1193 if(document() && !skip_undo)
1196 return;
1197 }
1198 }
1199
1200 if (document() && !skip_undo) {
1201 DocumentUndo::done(document(), C_("Undo action", "stack down"), INKSCAPE_ICON("layer-lower"));
1202 }
1203}
1204
1206{
1207 copy();
1208
1209 // Text and Node tools have their own CUT responses instead of deleteItems
1210 if (auto text_tool = dynamic_cast<TextTool*>(_desktop->getTool())) {
1211 if (text_tool->deleteSelection()) {
1212 DocumentUndo::done(desktop()->getDocument(), _("Cut text"), INKSCAPE_ICON("draw-text"));
1213 return;
1214 }
1215 }
1216
1217 auto node_tool = dynamic_cast<Inkscape::UI::Tools::NodeTool *>(desktop()->getTool());
1218 if (node_tool && node_tool->_selected_nodes) {
1219 auto prefs = Preferences::get();
1220 // This takes care of undo internally
1221 node_tool->_multipath->deleteNodes((Inkscape::UI::NodeDeleteMode)prefs->getInt("/tools/node/delete-mode-cut", (int)Inkscape::UI::NodeDeleteMode::gap_lines));
1222 return;
1223 }
1224
1225 deleteItems();
1226}
1227
1231SPCSSAttr *
1233{
1234 // CPPIFY:
1235 // This function should only take SPItems, but currently SPString is not an Item.
1236
1237 // write the complete cascaded style, context-free
1239 if (!css) {
1240 return nullptr;
1241 }
1242
1243 if ((is<SPGroup>(object) && object->firstChild()) ||
1244 (is<SPText>(object) && object->firstChild() && !object->firstChild()->getNext())) {
1245 // if this is a text with exactly one tspan child, merge the style of that tspan as well
1246 // If this is a group, merge the style of its topmost (last) child with style
1247 for (auto &element : object->children | boost::adaptors::reversed) {
1248 if (element.style) {
1249 if (auto temp = sp_css_attr_from_object(&element, SP_STYLE_FLAG_IFSET)) {
1250 sp_repr_css_merge(css, temp);
1252 }
1253 break;
1254 }
1255 }
1256 }
1257
1258 // Remove black-listed properties (those that should not be used in a default style)
1260
1261 if (!(is<SPText>(object) || is<SPTSpan>(object) || is<SPTRef>(object) || is<SPString>(object))) {
1262 // do not copy text properties from non-text objects, it's confusing
1264 }
1265
1266
1267 auto item = cast<SPItem>(object);
1268 if (item) {
1269 // FIXME: also transform gradient/pattern fills, by forking? NO, this must be nondestructive
1270 double ex = item->i2doc_affine().descrim();
1271 if (ex != 1.0) {
1272 css = sp_css_attr_scale(css, ex);
1273 }
1274 }
1275
1276 return css;
1277}
1278
1284
1285void sp_selection_paste(SPDesktop *desktop, bool in_place, bool on_page)
1286{
1288 if (cm->paste(desktop, in_place, on_page)) {
1289 DocumentUndo::done(desktop->getDocument(), _("Paste"), INKSCAPE_ICON("edit-paste"));
1290 }
1291}
1292
1294{
1296 if (cm->pasteStyle(this)) {
1297 DocumentUndo::done(document(), _("Paste style"), INKSCAPE_ICON("edit-paste-style"));
1298 }
1299}
1300
1302{
1304 if (cm->pastePathEffect(this)) {
1305 DocumentUndo::done(document(), _("Paste live path effect"), "");
1306 }
1307}
1308
1309
1311{
1312 if ( auto lpeitem = cast<SPLPEItem>(item) ) {
1313 if ( lpeitem->hasPathEffect() ) {
1314 lpeitem->removeAllPathEffects(false);
1315 }
1316 }
1317}
1318
1320{
1321
1322 // check if something is selected
1323 if (isEmpty()) {
1324 if(desktop())
1325 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to remove live path effects from."));
1326 return;
1327 }
1328 auto list= items();
1329 for (auto itemlist=list.begin();itemlist!=list.end();++itemlist) {
1330 SPItem *item = *itemlist;
1331
1333
1334 }
1335
1336 if (document()) {
1337 DocumentUndo::done(document(), _("Remove live path effect"), "");
1338 }
1339}
1340
1342{
1343 // check if something is selected
1344 if (isEmpty()) {
1345 if(desktop())
1346 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to remove filters from."));
1347 return;
1348 }
1349
1352 if (SPDesktop *d = desktop()) {
1354 // Refreshing the current tool (by switching to same tool)
1355 // will refresh tool's private information in it's selection context that
1356 // depends on desktop items.
1358 } else {
1359 auto list = items();
1360 for (auto itemlist=list.begin();itemlist!=list.end();++itemlist) {
1361 sp_desktop_apply_css_recursive(*itemlist, css, true);
1362 }
1363 }
1365 if (document()) {
1366 DocumentUndo::done(document(), _("Remove filter"), "");
1367 }
1368}
1369
1370
1371void ObjectSet::pasteSize(bool apply_x, bool apply_y)
1372{
1374 if (cm->pasteSize(this, false, apply_x, apply_y)) {
1375 DocumentUndo::done(document(), _("Paste size"), INKSCAPE_ICON("edit-paste-size"));
1376 }
1377}
1378
1379void ObjectSet::pasteSizeSeparately(bool apply_x, bool apply_y)
1380{
1382 if (cm->pasteSize(this, true, apply_x, apply_y)) {
1383 DocumentUndo::done(document(), _("Paste size separately"), INKSCAPE_ICON("edit-paste-size-separately"));
1384 }
1385}
1386
1391void sp_selection_change_layer_maintain_clones(std::vector<SPItem*> const &items,SPObject *where)
1392{
1393 for (auto item : items) {
1394 if (item) {
1395 auto oldparent = cast<SPItem>(item->parent);
1396 auto newparent = cast<SPItem>(where);
1398 (oldparent->i2doc_affine())
1399 *((newparent->i2doc_affine()).inverse()));
1400 }
1401 }
1402}
1403
1404void ObjectSet::toNextLayer(bool skip_undo)
1405{
1406 if (!desktop()) {
1407 return;
1408 }
1409 SPDesktop *dt=desktop(); //TODO make it desktop-independent
1410
1411 // check if something is selected
1412 if (isEmpty()) {
1413 dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to move to the layer above."));
1414 return;
1415 }
1416
1417 std::vector<SPItem*> items_copy(items().begin(), items().end());
1418
1419 bool no_more = false; // Set to true, if no more layers above
1421 if (next) {
1422 clear();
1424 std::vector<Inkscape::XML::Node*> temp_clip;
1425 sp_selection_copy_impl(items_copy, temp_clip, dt->doc()->getReprDoc());
1426 sp_selection_delete_impl(items_copy, false, false);
1427 next=Inkscape::next_layer(dt->layerManager().currentRoot(), dt->layerManager().currentLayer()); // Fixes bug 1482973: crash while moving layers
1428 std::vector<Inkscape::XML::Node*> copied;
1429 if (next) {
1430 copied = sp_selection_paste_impl(dt->getDocument(), next, temp_clip);
1431 } else {
1432 copied = sp_selection_paste_impl(dt->getDocument(), dt->layerManager().currentLayer(), temp_clip);
1433 no_more = true;
1434 }
1435 setReprList(copied);
1436 if (next) dt->layerManager().setCurrentLayer(next);
1437 if ( !skip_undo ) {
1438 DocumentUndo::done(dt->getDocument(), _("Raise to next layer"), INKSCAPE_ICON("selection-move-to-layer-above"));
1439 }
1440 } else {
1441 no_more = true;
1442 }
1443
1444 if (no_more) {
1445 dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No more layers above."));
1446 }
1447
1448}
1449
1450void ObjectSet::toPrevLayer(bool skip_undo)
1451{
1452 if (!desktop()) {
1453 return;
1454 }
1455 SPDesktop *dt=desktop(); //TODO make it desktop-independent
1456
1457 // check if something is selected
1458 if (isEmpty()) {
1459 dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to move to the layer below."));
1460 return;
1461 }
1462
1463 std::vector<SPItem*> items_copy(items().begin(), items().end());
1464
1465 bool no_more = false; // Set to true, if no more layers below
1467 if (next) {
1468 clear();
1470 std::vector<Inkscape::XML::Node*> temp_clip;
1471 sp_selection_copy_impl(items_copy, temp_clip, dt->doc()->getReprDoc()); // we're in the same doc, so no need to copy defs
1472 sp_selection_delete_impl(items_copy, false, false);
1473 next=Inkscape::previous_layer(dt->layerManager().currentRoot(), dt->layerManager().currentLayer()); // Fixes bug 1482973: crash while moving layers
1474 std::vector<Inkscape::XML::Node*> copied;
1475 if (next) {
1476 copied = sp_selection_paste_impl(dt->getDocument(), next, temp_clip);
1477 } else {
1478 copied = sp_selection_paste_impl(dt->getDocument(), dt->layerManager().currentLayer(), temp_clip);
1479 no_more = true;
1480 }
1481 setReprList( copied);
1482 if (next) dt->layerManager().setCurrentLayer(next);
1483 if ( !skip_undo ) {
1484 DocumentUndo::done(dt->getDocument(), _("Lower to previous layer"), INKSCAPE_ICON("selection-move-to-layer-below"));
1485 }
1486 } else {
1487 no_more = true;
1488 }
1489
1490 if (no_more) {
1491 dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No more layers below."));
1492 }
1493}
1494
1504{
1505 if(!document())
1506 return;
1507
1508 if (!moveto || !moveto->getRepr()) {
1509 g_warning("%s moveto is NULL", __func__);
1510 g_assert_not_reached();
1511 return;
1512 }
1513
1514 toLayer(moveto, moveto->getRepr()->lastChild());
1515}
1516
1521{
1522 assert(moveto);
1523 assert(!after || after->parent() == moveto->getRepr());
1524 assert(document());
1525
1526 SPDesktop *dt = desktop();
1527
1528 // check if something is selected
1529 if (isEmpty()) {
1530 if(dt)
1531 dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to move."));
1532 return;
1533 }
1534
1535 /* Make sure after is not in the selected group.
1536 * Iterate after's siblings backwards, finding the nearest that
1537 * isn't selected. This is important for positioning in the layer.
1538 */
1539 while (after && includes(after)) {
1540 after = after->prev();
1541 }
1542
1543 std::vector<SPItem*> items_copy(items().begin(), items().end());
1544
1545 if (moveto) {
1546 clear();
1548 std::vector<Inkscape::XML::Node*> temp_clip;
1549 sp_selection_copy_impl(items_copy, temp_clip, document()->getReprDoc()); // we're in the same doc, so no need to copy defs
1550 sp_selection_delete_impl(items_copy, false, false);
1551 std::vector<Inkscape::XML::Node*> copied = sp_selection_paste_impl(document(), moveto, temp_clip, after);
1552
1553 setReprList(copied);
1554 if (!temp_clip.empty()) temp_clip.clear();
1555 if (moveto && dt) dt->layerManager().setCurrentLayer(moveto);
1556 }
1557}
1558
1559static bool
1561{
1562 bool contains_original = false;
1563
1564 SPItem *item_use = item;
1565 SPItem *item_use_first = item;
1566 auto use = cast<SPUse>(item_use);
1567 while (use && item_use && !contains_original)
1568 {
1569 item_use = use->get_original();
1570 use = cast<SPUse>(item_use);
1571 contains_original |= set->includes(item_use);
1572 if (item_use == item_use_first)
1573 break;
1574 }
1575
1576 // If it's a tref, check whether the object containing the character
1577 // data is part of the selection
1578 auto tref = cast<SPTRef>(item);
1579 if (!contains_original && tref) {
1580 contains_original = set->includes(tref->getObjectReferredTo());
1581 }
1582
1583 return contains_original;
1584}
1585
1586
1587static bool
1589{
1590 bool clone_with_original = false;
1591 auto items = set->items();
1592 for (auto l=items.begin();l!=items.end() ;++l) {
1593 SPItem *item = *l;
1594 if (item) {
1595 clone_with_original |= object_set_contains_original(item, set);
1596 if (clone_with_original)
1597 break;
1598 }
1599 }
1600 return clone_with_original;
1601}
1602
1607{
1608 auto cached = _last_affine;
1610 _last_affine = cached;
1611}
1612
1614{
1615 _last_affine = Geom::identity(); // Clear last affine
1616}
1617
1625void ObjectSet::applyAffine(Geom::Affine const &affine, bool set_i2d, bool compensate,
1626 bool adjust_transf_center)
1627{
1628 if (isEmpty())
1629 return;
1630
1631 _last_affine = affine;
1632
1633 // For each perspective with a box in selection, check whether all boxes are selected and
1634 // unlink all non-selected boxes.
1635 Persp3D *persp;
1636 Persp3D *transf_persp;
1637 std::list<Persp3D *> plist = perspList();
1638 for (auto & i : plist) {
1639 persp = (Persp3D *) i;
1640
1641 if (persp) {
1642 if (!persp->has_all_boxes_in_selection (this)) {
1643 // create a new perspective as a copy of the current one
1644 transf_persp = Persp3D::create_xml_element (persp->document);
1645
1646 std::list<SPBox3D *> selboxes = box3DList(persp);
1647
1648 for (auto & selboxe : selboxes) {
1649 selboxe->switch_perspectives(persp, transf_persp);
1650 }
1651 } else {
1652 transf_persp = persp;
1653 }
1654
1655 transf_persp->apply_affine_transformation(affine);
1656 }
1657 }
1658 auto items_copy = items();
1659 std::vector<SPItem *> ordered_items;
1660 for (auto l=items_copy.begin();l!=items_copy.end() ;++l) {
1661 SPItem *item = *l;
1662 auto clonelpe = cast<SPLPEItem>(item);
1663 if (clonelpe && clonelpe->hasPathEffectOfType(Inkscape::LivePathEffect::CLONE_ORIGINAL)) {
1664 ordered_items.insert(ordered_items.begin(), item);
1665 } else {
1666 ordered_items.push_back(item);
1667 }
1668 }
1669 for (auto item : ordered_items) {
1670 if (is<SPRoot>(item) ) {
1671 // An SVG element cannot have a transform. We could change 'x' and 'y' in response
1672 // to a translation... but leave that for another day.
1673 if(desktop())
1674 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Cannot transform an embedded SVG."));
1675 break;
1676 }
1677
1678 Geom::Point old_center(0,0);
1679 if (set_i2d && item->isCenterSet())
1680 old_center = item->getCenter();
1681
1682 // If we're moving a connector, we want to detach it
1683 // from shapes that aren't part of the selection, but
1684 // leave it attached if they are
1686 auto path = cast<SPPath>(item);
1687 if (path) {
1688 SPItem *attItem[2] = {nullptr, nullptr};
1689 path->connEndPair.getAttachedItems(attItem);
1690 for (int n = 0; n < 2; ++n) {
1691 if (!includes(attItem[n])) {
1693 }
1694 }
1695 } else {
1696 g_assert_not_reached();
1697 }
1698 }
1699
1700 // "clones are unmoved when original is moved" preference
1702 int compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
1703 bool prefs_unmoved = (compensation == SP_CLONE_COMPENSATION_UNMOVED);
1704 bool prefs_parallel = (compensation == SP_CLONE_COMPENSATION_PARALLEL);
1705
1706 SiblingState sibling_state = getSiblingState(item);
1707
1708 /* If this is a clone and it's selected along with its original, do not move it;
1709 * it will feel the transform of its original and respond to it itself.
1710 * Without this, a clone is doubly transformed, very unintuitive.
1711 *
1712 * Same for textpath if we are also doing ANY transform to its path: do not touch textpath,
1713 * letters cannot be squeezed or rotated anyway, they only refill the changed path.
1714 * Same for linked offset if we are also moving its source: do not move it. */
1715 if (sibling_state == SiblingState::SIBLING_TEXT_PATH) {
1716 // Restore item->transform field from the repr, in case it was changed by seltrans.
1718 } else if (sibling_state == SiblingState::SIBLING_TEXT_FLOW_FRAME) {
1719 // apply the inverse of the region's transform to the <use> so that the flow remains
1720 // the same (even though the output itself gets transformed)
1721 for (auto& region: item->children) {
1722 if (is<SPFlowregion>(&region) || is<SPFlowregionExclude>(&region)) {
1723 for (auto& itm: region.children) {
1724 auto use = cast<SPUse>(&itm);
1725 if ( use ) {
1726 use->doWriteTransform(item->transform.inverse(), nullptr, compensate);
1727 }
1728 }
1729 }
1730 }
1731 } else if (sibling_state == SiblingState::SIBLING_CLONE_ORIGINAL || sibling_state == SiblingState::SIBLING_OFFSET_SOURCE) {
1732 // We are transforming a clone along with its original. The below matrix juggling is
1733 // necessary to ensure that they transform as a whole, i.e. the clone's induced
1734 // transform and its move compensation are both cancelled out.
1735
1736 // restore item->transform field from the repr, in case it was changed by seltrans
1738
1739 // calculate the matrix we need to apply to the clone to cancel its induced transform from its original
1740 Geom::Affine parent2dt;
1741 {
1742 auto parentItem = cast<SPItem>(item->parent);
1743 if (parentItem) {
1744 parent2dt = parentItem->i2dt_affine();
1745 } else {
1746 g_assert_not_reached();
1747 }
1748 }
1749 Geom::Affine t = parent2dt * affine * parent2dt.inverse();
1750 Geom::Affine t_inv = t.inverse();
1751 Geom::Affine result = t_inv * item->transform * t;
1752
1753 if (sibling_state == SiblingState::SIBLING_CLONE_ORIGINAL && (prefs_parallel || prefs_unmoved) && affine.isTranslation()) {
1754 // we need to cancel out the move compensation, too
1755
1756 // find out the clone move, same as in sp_use_move_compensate
1758 {
1759 auto use = cast<SPUse>(item);
1760 if (use) {
1761 parent = use->get_parent_transform();
1762 } else {
1763 g_assert_not_reached();
1764 }
1765 }
1766 Geom::Affine clone_move = parent.inverse() * t * parent;
1767
1768 if (prefs_parallel) {
1769 Geom::Affine move = result * clone_move * t_inv;
1770 item->doWriteTransform(move, &move, compensate);
1771
1772 } else if (prefs_unmoved) {
1773 //if (is<SPUse>(sp_use_get_original(cast<SPUse>(item))))
1774 // clone_move = Geom::identity();
1775 Geom::Affine move = result * clone_move;
1776 item->doWriteTransform(move, &t, compensate);
1777 }
1778
1779 } else if (sibling_state == SiblingState::SIBLING_OFFSET_SOURCE && (prefs_parallel || prefs_unmoved) && affine.isTranslation()){
1781 Geom::Affine offset_move = parent.inverse() * t * parent;
1782
1783 if (prefs_parallel) {
1784 Geom::Affine move = result * offset_move * t_inv;
1785 item->doWriteTransform(move, &move, compensate);
1786
1787 } else if (prefs_unmoved) {
1788 Geom::Affine move = result * offset_move;
1789 item->doWriteTransform(move, &t, compensate);
1790 }
1791
1792 } else {
1793 // just apply the result
1794 item->doWriteTransform(result, &t, compensate);
1795 }
1796 } else if (sibling_state == SiblingState::SIBLING_TEXT_SHAPE_INSIDE) {
1798
1799 } else {
1800 if (set_i2d) {
1802 }
1803 item->doWriteTransform(item->transform, nullptr, compensate);
1804 }
1805
1806 if (adjust_transf_center) { // The transformation center should not be touched in case of pasting or importing, which is allowed by this if clause
1807 // if we're moving the actual object, not just updating the repr, we can transform the
1808 // center by the same matrix (only necessary for non-translations)
1809 if (set_i2d && item->isCenterSet() && !(affine.isTranslation() || affine.isIdentity())) {
1810 item->setCenter(old_center * affine);
1811 item->updateRepr();
1812 }
1813 }
1814 }
1815}
1816
1818{
1819 auto items = xmlNodes();
1820 for (auto l=items.begin();l!=items.end() ;++l) {
1821 (*l)->removeAttribute("transform");
1822 }
1823
1824 if (document()) {
1825 DocumentUndo::done(document(), _("Remove transform"), "");
1826 }
1827}
1828
1829void ObjectSet::setScaleAbsolute(double x0, double x1,double y0, double y1)
1830{
1831 if (isEmpty())
1832 return;
1833
1834 Geom::OptRect bbox = visualBounds();
1835 if ( !bbox ) {
1836 return;
1837 }
1838
1839 Geom::Translate const p2o(-bbox->min());
1840
1841 Geom::Scale const newSize(x1 - x0,
1842 y1 - y0);
1843 Geom::Scale const scale( newSize * Geom::Scale(bbox->dimensions()).inverse() );
1844 Geom::Translate const o2n(x0, y0);
1845 Geom::Affine const final( p2o * scale * o2n );
1846
1847 applyAffine(final);
1848}
1849
1850void ObjectSet::scaleRelative(Geom::Point const &align, Geom::Scale const &scale)
1851{
1852 if (isEmpty())
1853 return;
1854
1855 Geom::OptRect bbox = visualBounds();
1856
1857 if ( !bbox ) {
1858 return;
1859 }
1860
1861 // FIXME: ARBITRARY LIMIT: don't try to scale above 1 Mpx, it won't display properly and will crash sooner or later anyway
1862 if ( bbox->dimensions()[Geom::X] * scale[Geom::X] > 1e6 ||
1863 bbox->dimensions()[Geom::Y] * scale[Geom::Y] > 1e6 )
1864 {
1865 return;
1866 }
1867
1868 Geom::Translate const n2d(-align);
1869 Geom::Translate const d2n(align);
1870 Geom::Affine const final( n2d * scale * d2n );
1871 applyAffine(final);
1872}
1873
1874void ObjectSet::rotateRelative(Geom::Point const &center, double angle_degrees)
1875{
1876 Geom::Translate const d2n(center);
1877 Geom::Translate const n2d(-center);
1878 Geom::Rotate const rotate(Geom::Rotate::from_degrees(angle_degrees));
1879 Geom::Affine const final( Geom::Affine(n2d) * rotate * d2n );
1880 applyAffine(final);
1881}
1882
1883void ObjectSet::skewRelative(Geom::Point const &align, double dx, double dy)
1884{
1885 Geom::Translate const d2n(align);
1886 Geom::Translate const n2d(-align);
1887 Geom::Affine const skew(1, dy,
1888 dx, 1,
1889 0, 0);
1890 Geom::Affine const final( n2d * skew * d2n );
1891 applyAffine(final);
1892}
1893
1894void ObjectSet::moveRelative(Geom::Point const &move, bool compensate)
1895{
1896 applyAffine(Geom::Affine(Geom::Translate(move)), true, compensate);
1897}
1898
1899void ObjectSet::moveRelative(double dx, double dy)
1900{
1902}
1903
1904/*
1905 * Selects all the visible items with the same fill and/or stroke color/style as the items in the current selection
1906 *
1907 * Params:
1908 * desktop - set the selection on this desktop
1909 * fill - select objects matching fill
1910 * stroke - select objects matching stroke
1911 */
1912void sp_select_same_fill_stroke_style(SPDesktop *desktop, gboolean fill, gboolean stroke, gboolean style)
1913{
1914 if (!desktop) {
1915 return;
1916 }
1917
1918 if (!fill && !stroke && !style) {
1919 return;
1920 }
1921
1923
1925 bool inlayersame = prefs->getBool("/options/selection/samelikeall", false);
1926 bool onlyvisible = prefs->getBool("/options/kbselection/onlyvisible", true);
1927 bool onlysensitive = prefs->getBool("/options/kbselection/onlysensitive", true);
1928
1930 bool ingroup = true;
1931
1932 // Apply the same layer logic to select same as used for select all.
1933 if (inlayersame) {
1934 PrefsSelectionContext inlayer = (PrefsSelectionContext)prefs->getInt("/options/kbselection/inlayer", PREFS_SELECTION_LAYER);
1935 if (PREFS_SELECTION_ALL != inlayer) {
1936 root = selection->activeContext();
1937 ingroup = (inlayer == PREFS_SELECTION_LAYER_RECURSIVE);
1938 }
1939 }
1940
1941 std::vector<SPItem*> all_list = get_all_items(root, desktop, onlyvisible, onlysensitive, ingroup);
1942 std::vector<SPItem*> all_matches;
1943
1944 auto items = selection->items();
1945
1946 std::vector<SPItem*> tmp;
1947 for (auto iter : all_list) {
1948 if(!is<SPGroup>(iter)){
1949 tmp.push_back(iter);
1950 }
1951 }
1952 all_list=tmp;
1953
1954 for (auto sel_iter=items.begin();sel_iter!=items.end();++sel_iter) {
1955 SPItem *sel = *sel_iter;
1956 std::vector<SPItem*> matches = all_list;
1957 if (fill && stroke && style) {
1958 matches = sp_get_same_style(sel, matches);
1959 }
1960 else if (fill) {
1961 matches = sp_get_same_style(sel, matches, SP_FILL_COLOR);
1962 }
1963 else if (stroke) {
1964 matches = sp_get_same_style(sel, matches, SP_STROKE_COLOR);
1965 }
1966 else if (style) {
1967 matches = sp_get_same_style(sel, matches,SP_STROKE_STYLE_ALL);
1968 }
1969 all_matches.insert(all_matches.end(), matches.begin(),matches.end());
1970 }
1971
1972 selection->clear();
1973 selection->setList(all_matches);
1974
1975}
1976
1977
1978/*
1979 * Selects all the visible items with the same object type as the items in the current selection
1980 *
1981 * Params:
1982 * desktop - set the selection on this desktop
1983 */
1985{
1986 if (!desktop) {
1987 return;
1988 }
1989
1990
1992 bool onlyvisible = prefs->getBool("/options/kbselection/onlyvisible", true);
1993 bool onlysensitive = prefs->getBool("/options/kbselection/onlysensitive", true);
1994 bool ingroups = TRUE;
1995 auto matches = get_all_items(desktop->layerManager().currentRoot(), desktop, onlyvisible, onlysensitive, ingroups);
1996
1998
1999 auto items= selection->items();
2000 for (auto sel_iter=items.begin();sel_iter!=items.end();++sel_iter) {
2001 SPItem *sel = *sel_iter;
2002 if (sel) {
2003 matches = sp_get_same_object_type(sel, matches);
2004 } else {
2005 g_assert_not_reached();
2006 }
2007 }
2008
2009 selection->clear();
2010 selection->setList(matches);
2011
2012}
2013
2014
2015
2016/*
2017 * Find all items in src list that have the same fill or stroke style as sel
2018 * Return the list of matching items
2019 */
2020std::vector<SPItem*> sp_get_same_fill_or_stroke_color(SPItem *sel, std::vector<SPItem*> &src, SPSelectStrokeStyleType type)
2021{
2022 std::vector<SPItem*> matches ;
2023 gboolean match = false;
2024
2025 SPIPaint *sel_paint = sel->style->getFillOrStroke(type == SP_FILL_COLOR);
2026
2027 for (std::vector<SPItem*>::const_reverse_iterator i=src.rbegin();i!=src.rend();++i) {
2028 SPItem *iter = *i;
2029 if (iter) {
2030 SPIPaint *iter_paint = iter->style->getFillOrStroke(type == SP_FILL_COLOR);
2031 match = false;
2032 if (sel_paint->isColor() && iter_paint->isColor()
2033 && (sel_paint->getColor().isSimilar(iter_paint->getColor()))) {
2034 match = true;
2035 } else if (sel_paint->isPaintserver() && iter_paint->isPaintserver()) {
2036
2037 SPPaintServer *sel_server =
2038 (type == SP_FILL_COLOR) ? sel->style->getFillPaintServer() : sel->style->getStrokePaintServer();
2039 SPPaintServer *iter_server =
2040 (type == SP_FILL_COLOR) ? iter->style->getFillPaintServer() : iter->style->getStrokePaintServer();
2041
2042 auto check_gradient = [] (SPGradient const *g) {
2043 return is<SPLinearGradient>(g) || is<SPRadialGradient>(g) || g->getVector()->isSwatch();
2044 };
2045
2046 SPGradient *sel_gradient, *iter_gradient;
2047 SPPattern *sel_pattern, *iter_pattern;
2048
2049 if ((sel_gradient = cast<SPGradient>(sel_server)) &&
2050 (iter_gradient = cast<SPGradient>(iter_server)) &&
2051 check_gradient(sel_gradient) &&
2052 check_gradient(iter_gradient))
2053 {
2054 SPGradient *sel_vector = sel_gradient->getVector();
2055 SPGradient *iter_vector = iter_gradient->getVector();
2056 if (sel_vector == iter_vector) {
2057 match = true;
2058 }
2059
2060 } else if ((sel_pattern = cast<SPPattern>(sel_server)) &&
2061 (iter_pattern = cast<SPPattern>(iter_server))) {
2062 SPPattern *sel_pat = sel_pattern->rootPattern();
2063 SPPattern *iter_pat = iter_pattern->rootPattern();
2064 if (sel_pat == iter_pat) {
2065 match = true;
2066 }
2067 }
2068 } else if (sel_paint->isNone() && iter_paint->isNone()) {
2069 match = true;
2070 } else if (sel_paint->isNoneSet() && iter_paint->isNoneSet()) {
2071 match = true;
2072 }
2073
2074 if (match) {
2075 matches.push_back(iter);
2076 }
2077 } else {
2078 g_assert_not_reached();
2079 }
2080 }
2081
2082 return matches;
2083}
2084
2085static bool item_type_match (SPItem *i, SPItem *j)
2086{
2087 if (is<SPRect>(i)) {
2088 return ( is<SPRect>(j) );
2089
2090 } else if (is<SPGenericEllipse>(i)) {
2091 return (is<SPGenericEllipse>(j));
2092
2093 } else if (is<SPStar>(i) || is<SPPolygon>(i)) {
2094 return (is<SPStar>(j) || is<SPPolygon>(j)) ;
2095
2096 } else if (is<SPSpiral>(i)) {
2097 return (is<SPSpiral>(j));
2098
2099 } else if (is<SPPath>(i) || is<SPLine>(i) || is<SPPolyLine>(i)) {
2100 return (is<SPPath>(j) || is<SPLine>(j) || is<SPPolyLine>(j));
2101
2102 } else if (is<SPText>(i) || is<SPFlowtext>(i) || is<SPTSpan>(i) || is<SPTRef>(i)) {
2103 return (is<SPText>(j) || is<SPFlowtext>(j) || is<SPTSpan>(j) || is<SPTRef>(j));
2104
2105 } else if (is<SPUse>(i)) {
2106 return (is<SPUse>(j)) ;
2107
2108 } else if (is<SPImage>(i)) {
2109 return (is<SPImage>(j));
2110
2111 } else if (is<SPOffset>(i) && cast_unsafe<SPOffset>(i)->sourceHref) { // Linked offset
2112 return (is<SPOffset>(j) && cast_unsafe<SPOffset>(j)->sourceHref);
2113
2114 } else if (is<SPOffset>(i) && !cast_unsafe<SPOffset>(i)->sourceHref) { // Dynamic offset
2115 return is<SPOffset>(j) && !cast_unsafe<SPOffset>(j)->sourceHref;
2116
2117 }
2118
2119 return false;
2120}
2121
2122/*
2123 * Find all items in src list that have the same object type as sel by type
2124 * Return the list of matching items
2125 */
2126std::vector<SPItem*> sp_get_same_object_type(SPItem *sel, std::vector<SPItem*> &src)
2127{
2128 std::vector<SPItem*> matches;
2129
2130 for (std::vector<SPItem*>::const_reverse_iterator i=src.rbegin();i!=src.rend();++i) {
2131 SPItem *item = *i;
2132 if (item && item_type_match(sel, item) && !item->cloned) {
2133 matches.push_back(item);
2134 }
2135 }
2136 return matches;
2137}
2138
2139/*
2140 * Find all items in src list that have the same stroke style as sel by type
2141 * Return the list of matching items
2142 */
2143std::vector<SPItem*> sp_get_same_style(SPItem *sel, std::vector<SPItem*> &src, SPSelectStrokeStyleType type)
2144{
2145 std::vector<SPItem*> matches;
2146 bool match = false;
2147
2148 SPStyle *sel_style = sel->style;
2149
2150 if (type == SP_FILL_COLOR || type == SP_STYLE_ALL) {
2152 }
2153 if (type == SP_STROKE_COLOR || type == SP_STYLE_ALL) {
2155 }
2156
2157 /*
2158 * Stroke width needs to handle transformations, so call this function
2159 * to get the transformed stroke width
2160 */
2161 std::vector<SPItem*> objects;
2162 SPStyle *sel_style_for_width = nullptr;
2163 if (type == SP_STROKE_STYLE_WIDTH || type == SP_STROKE_STYLE_ALL || type==SP_STYLE_ALL ) {
2164 objects.push_back(sel);
2165 sel_style_for_width = new SPStyle(SP_ACTIVE_DOCUMENT);
2166 objects_query_strokewidth (objects, sel_style_for_width);
2167 }
2168 bool match_g;
2169 for (auto iter : src) {
2170 if (iter) {
2171 match_g=true;
2172 SPStyle *iter_style = iter->style;
2173 match = true;
2174
2175 if (type == SP_STROKE_STYLE_WIDTH|| type == SP_STROKE_STYLE_ALL|| type==SP_STYLE_ALL) {
2176 match = (sel_style->stroke_width.set == iter_style->stroke_width.set);
2177 if (sel_style->stroke_width.set && iter_style->stroke_width.set) {
2178 std::vector<SPItem*> objects;
2179 objects.insert(objects.begin(),iter);
2180 SPStyle tmp_style(SP_ACTIVE_DOCUMENT);
2181 objects_query_strokewidth (objects, &tmp_style);
2182
2183 if (sel_style_for_width) {
2184 match = (sel_style_for_width->stroke_width.computed == tmp_style.stroke_width.computed);
2185 }
2186 }
2187 }
2188 match_g = match_g && match;
2189 if (type == SP_STROKE_STYLE_DASHES|| type == SP_STROKE_STYLE_ALL || type==SP_STYLE_ALL) {
2190 match = (sel_style->stroke_dasharray.set == iter_style->stroke_dasharray.set);
2191 if (sel_style->stroke_dasharray.set && iter_style->stroke_dasharray.set) {
2192 match = (sel_style->stroke_dasharray == iter_style->stroke_dasharray);
2193 }
2194 }
2195 match_g = match_g && match;
2196 if (type == SP_STROKE_STYLE_MARKERS|| type == SP_STROKE_STYLE_ALL|| type==SP_STYLE_ALL) {
2197 match = true;
2198 int len = sizeof(sel_style->marker)/sizeof(SPIString);
2199 for (int i = 0; i < len; i++) {
2200 if (g_strcmp0(sel_style->marker_ptrs[i]->value(),
2201 iter_style->marker_ptrs[i]->value())) {
2202 match = false;
2203 break;
2204 }
2205 }
2206 }
2207 match_g = match_g && match;
2208 if (match_g) {
2209 while (iter->cloned) iter=cast<SPItem>(iter->parent);
2210 matches.insert(matches.begin(),iter);
2211 }
2212 } else {
2213 g_assert_not_reached();
2214 }
2215 }
2216
2217 if( sel_style_for_width != nullptr ) delete sel_style_for_width;
2218 return matches;
2219}
2220
2221void ObjectSet::move(double dx, double dy)
2222{
2223 if (isEmpty()) {
2224 return;
2225 }
2226
2227 moveRelative(dx, dy);
2228
2229 if (document()) {
2230 if (dx == 0) {
2231 DocumentUndo::maybeDone(document(), "selector:move:vertical", _("Move vertically"), INKSCAPE_ICON("tool-pointer"));
2232 } else if (dy == 0) {
2233 DocumentUndo::maybeDone(document(), "selector:move:horizontal", _("Move horizontally"), INKSCAPE_ICON("tool-pointer"));
2234 } else {
2235 DocumentUndo::done(document(), _("Move"), INKSCAPE_ICON("tool-pointer"));
2236 }
2237 }
2238}
2239
2240void ObjectSet::move(double dx, double dy, bool rotated)
2241{
2242 if (rotated) {
2243 double const rotation = desktop()->current_rotation().angle();
2244 double const rdx = std::cos(rotation) * dx + std::sin(rotation) * dy;
2245 double const rdy = -std::sin(rotation) * dx + std::cos(rotation) * dy;
2246
2247 move(rdx, rdy);
2248 } else {
2249 move(dx, dy);
2250 }
2251}
2252
2253void ObjectSet::move(double dx, double dy, bool rotated, bool screen)
2254{
2255 if (screen) {
2256 moveScreen(dx, dy, rotated);
2257 } else {
2258 move(dx, dy, rotated);
2259 }
2260}
2261
2262void ObjectSet::moveScreen(double dx, double dy)
2263{
2264 if (isEmpty() || !desktop()) {
2265 return;
2266 }
2267
2268 // same as ObjectSet::move but divide deltas by zoom factor
2269 gdouble const zoom = desktop()->current_zoom();
2270 gdouble const zdx = dx / zoom;
2271 gdouble const zdy = dy / zoom;
2272 moveRelative(zdx, zdy);
2273
2274 SPDocument *doc = document();
2275 if (dx == 0) {
2276 DocumentUndo::maybeDone(doc, "selector:move:vertical", _("Move vertically by pixels"), INKSCAPE_ICON("tool-pointer"));
2277 } else if (dy == 0) {
2278 DocumentUndo::maybeDone(doc, "selector:move:horizontal", _("Move horizontally by pixels"), INKSCAPE_ICON("tool-pointer"));
2279 } else {
2280 DocumentUndo::done(doc, _("Move"), INKSCAPE_ICON("tool-pointer"));
2281 }
2282}
2283
2284void ObjectSet::moveScreen(double dx, double dy, bool rotated)
2285{
2286 if (rotated) {
2287 double const rotation = desktop()->current_rotation().angle();
2288 double const rdx = std::cos(rotation) * dx + std::sin(rotation) * dy;
2289 double const rdy = -std::sin(rotation) * dx + std::cos(rotation) * dy;
2290
2291 moveScreen(rdx, rdy);
2292 } else {
2293 moveScreen(dx, dy);
2294 }
2295}
2296
2297struct Forward {
2298 typedef SPObject *Iterator;
2299
2300 static Iterator children(SPObject *o) { return o->firstChild(); }
2301 static Iterator siblings_after(SPObject *o) { return o->getNext(); }
2302 static void dispose(Iterator i) {}
2303
2304 static SPObject *object(Iterator i) { return i; }
2305 static Iterator next(Iterator i) { return i->getNext(); }
2306 static bool isNull(Iterator i) {return (!i);}
2307};
2308
2309struct ListReverse {
2310 typedef std::list<SPObject *> *Iterator;
2311
2312 static Iterator children(SPObject *o) {
2313 return make_list(o, nullptr);
2314 }
2315 static Iterator siblings_after(SPObject *o) {
2316 return make_list(o->parent, o);
2317 }
2318 static void dispose(Iterator i) {
2319 delete i;
2320 }
2321
2322 static SPObject *object(Iterator i) {
2323 return *(i->begin());
2324 }
2325 static Iterator next(Iterator i) { i->pop_front(); return i; }
2326
2327 static bool isNull(Iterator i) {return i->empty();}
2328
2329private:
2330 static std::list<SPObject *> *make_list(SPObject *object, SPObject *limit) {
2331 auto list = new std::list<SPObject *>;
2332 for (auto &child: object->children) {
2333 if (&child == limit) {
2334 break;
2335 }
2336 list->push_front(&child);
2337 }
2338 return list;
2339 }
2340};
2341
2342
2343
2344template <typename D>
2345SPItem *next_item(SPDesktop *desktop, std::vector<SPObject *> &path, SPObject *root,
2346 bool only_in_viewport, PrefsSelectionContext inlayer, bool onlyvisible, bool onlysensitive)
2347{
2348 typename D::Iterator children;
2349 typename D::Iterator iter;
2350
2351 SPItem *found=nullptr;
2352
2353 if (!path.empty()) {
2354 SPObject *object=path.back();
2355 path.pop_back();
2356 g_assert(object->parent == root);
2357 if (desktop->layerManager().isLayer(object)) {
2358 found = next_item<D>(desktop, path, object, only_in_viewport, inlayer, onlyvisible, onlysensitive);
2359 }
2360 iter = children = D::siblings_after(object);
2361 } else {
2362 iter = children = D::children(root);
2363 }
2364
2365 while ( !D::isNull(iter) && !found ) {
2366 SPObject *object=D::object(iter);
2367 if (desktop->layerManager().isLayer(object)) {
2368 if (PREFS_SELECTION_LAYER != inlayer) { // recurse into sublayers
2369 std::vector<SPObject *> empt;
2370 found = next_item<D>(desktop, empt, object, only_in_viewport, inlayer, onlyvisible, onlysensitive);
2371 }
2372 } else {
2373 auto item = cast<SPItem>(object);
2374 if ( item &&
2375 ( !only_in_viewport || desktop->isWithinViewport(item) ) &&
2376 ( !onlyvisible || !desktop->itemIsHidden(item)) &&
2377 ( !onlysensitive || !item->isLocked()) &&
2379 {
2380 found = item;
2381 }
2382 }
2383 iter = D::next(iter);
2384 }
2385
2386 D::dispose(children);
2387
2388 return found;
2389}
2390
2391
2392template <typename D>
2393SPItem *next_item_from_list(SPDesktop *desktop, std::vector<SPItem*> const &items,
2394 SPObject *root, bool only_in_viewport, PrefsSelectionContext inlayer, bool onlyvisible, bool onlysensitive)
2395{
2397 for(auto item : items) {
2398 if ( root->isAncestorOf(item) &&
2399 ( !only_in_viewport || desktop->isWithinViewport(item) ) )
2400 {
2401 current = item;
2402 break;
2403 }
2404 }
2405
2406 std::vector<SPObject *> path;
2407 while ( current != root ) {
2408 path.push_back(current);
2410 }
2411
2412 SPItem *next;
2413 // first, try from the current object
2414 next = next_item<D>(desktop, path, root, only_in_viewport, inlayer, onlyvisible, onlysensitive);
2415
2416 if (!next) { // if we ran out of objects, start over at the root
2417 std::vector<SPObject *> empt;
2418 next = next_item<D>(desktop, empt, root, only_in_viewport, inlayer, onlyvisible, onlysensitive);
2419 }
2420
2421 return next;
2422}
2423
2424void
2426{
2427 g_return_if_fail(desktop != nullptr);
2429
2431 PrefsSelectionContext inlayer = (PrefsSelectionContext)prefs->getInt("/options/kbselection/inlayer", PREFS_SELECTION_LAYER);
2432 bool onlyvisible = prefs->getBool("/options/kbselection/onlyvisible", true);
2433 bool onlysensitive = prefs->getBool("/options/kbselection/onlysensitive", true);
2434
2435 SPObject *root;
2436 if (PREFS_SELECTION_ALL != inlayer) {
2437 root = selection->activeContext();
2438 } else {
2440 }
2441
2442 std::vector<SPItem *> vec(selection->items().begin(), selection->items().end());
2443 SPItem *item=next_item_from_list<Forward>(desktop, vec, root, SP_CYCLING == SP_CYCLE_VISIBLE, inlayer, onlyvisible, onlysensitive);
2444
2445 if (item) {
2446 selection->set(item, PREFS_SELECTION_LAYER_RECURSIVE == inlayer);
2447 if ( SP_CYCLING == SP_CYCLE_FOCUS ) {
2449 }
2450 }
2451}
2452
2453void
2455{
2456 SPDocument *document = desktop->getDocument();
2457 g_return_if_fail(document != nullptr);
2458 g_return_if_fail(desktop != nullptr);
2460
2462 PrefsSelectionContext inlayer = (PrefsSelectionContext) prefs->getInt("/options/kbselection/inlayer", PREFS_SELECTION_LAYER);
2463 bool onlyvisible = prefs->getBool("/options/kbselection/onlyvisible", true);
2464 bool onlysensitive = prefs->getBool("/options/kbselection/onlysensitive", true);
2465
2466 SPObject *root;
2467 if (PREFS_SELECTION_ALL != inlayer) {
2468 root = selection->activeContext();
2469 } else {
2471 }
2472
2473 std::vector<SPItem *> vec(selection->items().begin(), selection->items().end());
2474 SPItem *item=next_item_from_list<ListReverse>(desktop, vec, root, SP_CYCLING == SP_CYCLE_VISIBLE, inlayer, onlyvisible, onlysensitive);
2475
2476 if (item) {
2477 selection->set(item, PREFS_SELECTION_LAYER_RECURSIVE == inlayer);
2478 if ( SP_CYCLING == SP_CYCLE_FOCUS ) {
2480 }
2481 }
2482}
2483
2485{
2486 if (!dt) return;
2487
2488 Inkscape::Selection *selection = dt->getSelection();
2489 if ( selection && !selection->isEmpty() ) {
2490 SPItem *item = selection->singleItem();
2491 if ( auto lpeitem = cast<SPLPEItem>(item) ) {
2492 if (lpeitem->hasPathEffect()) {
2493 lpeitem->editNextParamOncanvas(dt);
2494 } else {
2495 dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("The selection has no applied path effect."));
2496 }
2497 }
2498 }
2499}
2500
2501void ObjectSet::editMask(bool /*clip*/)
2502{
2503 return;
2504}
2505
2506
2507
2508
2514{
2515 auto dbox = desktop->get_display_area();
2517
2518 if ( sbox && dbox.contains(*sbox) == false ) {
2519 Geom::Point const s_dt = sbox->midpoint();
2520 Geom::Point const s_w = desktop->d2w(s_dt);
2521 Geom::Point const d_dt = dbox.midpoint();
2522 Geom::Point const d_w = desktop->d2w(d_dt);
2523 Geom::Point const moved_w( d_w - s_w );
2524 desktop->scroll_relative(moved_w);
2525 }
2526}
2527
2528void ObjectSet::clone(bool skip_undo)
2529{
2530 if (document() == nullptr) {
2531 return;
2532 }
2533
2535
2536 // check if something is selected
2537 if (isEmpty()) {
2538 if(desktop())
2539 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select an <b>object</b> to clone."));
2540 return;
2541 }
2542
2543 // Assign IDs to selected objects that don't have an ID attribute
2544 enforceIds();
2545
2546 std::vector<Inkscape::XML::Node*> reprs(xmlNodes().begin(), xmlNodes().end());
2547
2548 clear();
2549
2550 // sorting items from different parents sorts each parent's subset without possibly mixing them, just what we need
2551 sort(reprs.begin(),reprs.end(),sp_repr_compare_position_bool);
2552
2553 std::vector<Inkscape::XML::Node*> newsel;
2554
2555 for(auto sel_repr : reprs){
2556 Inkscape::XML::Node *parent = sel_repr->parent();
2557
2558 Inkscape::XML::Node *clone = xml_doc->createElement("svg:use");
2559 clone->setAttribute("x", "0");
2560 clone->setAttribute("y", "0");
2561 gchar *href_str = g_strdup_printf("#%s", sel_repr->attribute("id"));
2562 clone->setAttribute("xlink:href", href_str);
2563 g_free(href_str);
2564
2565 clone->setAttribute("inkscape:transform-center-x", sel_repr->attribute("inkscape:transform-center-x"));
2566 clone->setAttribute("inkscape:transform-center-y", sel_repr->attribute("inkscape:transform-center-y"));
2567
2568 // add the new clone to the top of the original's parent
2569 parent->appendChild(clone);
2570
2571 newsel.push_back(clone);
2573 }
2574 if (!skip_undo) {
2575 DocumentUndo::done(document(), C_("Action", "Clone"), INKSCAPE_ICON("edit-clone"));
2576 }
2577
2578 setReprList(newsel);
2579}
2580
2582{
2583 if (isEmpty()) {
2584 if(desktop())
2585 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>clones</b> to relink."));
2586 return;
2587 }
2588
2590 auto newid = cm->getFirstObjectID();
2591 if (newid.empty()) {
2592 if(desktop())
2593 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Copy an <b>object</b> to clipboard to relink clones to."));
2594 return;
2595 }
2596 auto newrefAttribute = "#" + newid;
2597
2598 // Get a copy of current selection.
2599 bool relinked = false;
2600 auto items_= items();
2601 for (auto i=items_.begin();i!=items_.end();++i){
2602 SPItem *item = *i;
2603
2604 if (auto use = cast<SPUse>(item)) {
2605 // Get original referenced item, relink, then get new reference
2606 SPItem *ref = use->get_original();
2607 use->setAttribute(Inkscape::getHrefAttribute(*use->getRepr()).first, newrefAttribute);
2608 SPItem *newref = use->get_original();
2609
2610 if (ref && newref) {
2611 // Compensate for position of new reference if requested.
2612 // Default behavior is to move according to transform, so not
2613 // handled explicitly.
2615 int compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
2616
2617 if (compensation == SP_CLONE_COMPENSATION_UNMOVED || compensation == SP_CLONE_COMPENSATION_PARALLEL) {
2618 auto center = ref->getCenter();
2619 auto newcenter = newref->getCenter();
2620 Geom::Affine translation = Geom::Translate(newcenter - center);
2621
2622 // Transform of clone. Necessary to apply the offset
2623 // translation from the reference prior to applying clone-
2624 // specific transformations.
2626
2627 // To make the clone appear unmoved, simply invert the
2628 // translation. To make the clone move in parallel, add the
2629 // translation back in, but make sure that the translation
2630 // is applied to a shape that isn't transformed in any other way
2631 Geom::Affine m = t.inverse() * translation.inverse() * t;
2632 if (compensation == SP_CLONE_COMPENSATION_PARALLEL) {
2633 m *= m.withoutTranslation().inverse() * translation * m.withoutTranslation();
2634 }
2635
2636 // Compensation must be applied for each clone indivudally
2637 // in case we are re-linking many clones that originally had
2638 // different references.
2639 auto s = ObjectSet(document());
2640 s.add(item);
2641 s.applyAffine(m);
2642 }
2643 }
2644
2645 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
2646 relinked = true;
2647 }
2648 }
2649
2650 if (!relinked) {
2651 if(desktop())
2652 desktop()->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No clones to relink</b> in the selection."));
2653 } else {
2654 DocumentUndo::done(document(), _("Relink clone"), INKSCAPE_ICON("edit-clone-unlink"));
2655 }
2656}
2657
2658
2659bool ObjectSet::unlink(const bool skip_undo, const bool silent)
2660{
2661 if (isEmpty()) {
2662 if(desktop() && !silent)
2663 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>clones</b> to unlink."));
2664 return false;
2665 }
2666
2667 // Get a copy of current selection.
2668 std::vector<SPItem*> new_select;
2669 bool unlinked = false;
2670 std::vector<SPItem *> items_(items().begin(), items().end());
2671
2672 for (auto i=items_.rbegin();i!=items_.rend();++i){
2673 SPItem *item = *i;
2674
2675 ObjectSet tmp_set(document());
2676 tmp_set.set(item);
2677 auto *clip_obj = item->getClipObject();
2678 auto *mask_obj = item->getMaskObject();
2679 if (clip_obj) {
2680 // The following always-false check was added in 5bfbeb4a.
2681 // Cannot tell if necessary since neither commit/MR say what bug it fixes.
2682 // Keeping the if (false) explicit to minimize likelihood of regressions
2683 // if (is<SPUse>(clip_obj)) {
2684 if (false) {
2685 tmp_set.unsetMask(true, true, true);
2686 unlinked = tmp_set.unlink(true) || unlinked;
2687 tmp_set.setMask(true, false, true);
2688 }
2689 new_select.push_back(tmp_set.singleItem());
2690 } else if (mask_obj) {
2691 // if (is<SPUse>(mask_obj)) {
2692 if (false) {
2693 tmp_set.unsetMask(false, true, true);
2694 unlinked = tmp_set.unlink(true) || unlinked;
2695 tmp_set.setMask(false, false, true);
2696 }
2697 new_select.push_back(tmp_set.singleItem());
2698 } else {
2699 if (is<SPText>(item)) {
2701
2702 if (tspan) {
2703 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
2704 }
2705
2706 // Set unlink to true, and fall into the next if which
2707 // will include this text item in the new selection
2708 unlinked = true;
2709 }
2710
2711 if (!(is<SPUse>(item) || is<SPTRef>(item))) {
2712 // keep the non-use item in the new selection
2713 new_select.push_back(item);
2714 continue;
2715 }
2716
2717 SPItem *unlink = nullptr;
2718 auto use = cast<SPUse>(item);
2719 if (use) {
2720 unlink = use->unlink();
2721 // Unable to unlink use (external or invalid href?)
2722 if (!unlink) {
2723 new_select.push_back(item);
2724 continue;
2725 }
2726 } else /*if (is<SPTRef>(use))*/ {
2727 unlink = cast<SPItem>(sp_tref_convert_to_tspan(item));
2728 g_assert(unlink != nullptr);
2729 }
2730
2731 unlinked = true;
2732 // Add ungrouped items to the new selection.
2733 new_select.push_back(unlink);
2734 }
2735 }
2736
2737 if (!new_select.empty()) { // set new selection
2738 clear();
2739 setList(new_select);
2740 }
2741 if (!unlinked) {
2742 if(desktop() && !silent)
2743 desktop()->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No clones to unlink</b> in the selection."));
2744 }
2745
2746 if (!skip_undo) {
2747 DocumentUndo::done(document(), _("Unlink clone"), INKSCAPE_ICON("edit-clone-unlink"));
2748 }
2749 return unlinked;
2750}
2751
2752bool ObjectSet::unlinkRecursive(const bool skip_undo, const bool force, const bool silent) {
2753 if (isEmpty()){
2754 if (desktop() && !silent)
2755 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>clones</b> to unlink."));
2756 return false;
2757 }
2759 bool pathoperationsunlink = prefs->getBool("/options/pathoperationsunlink/value", true);
2760 if (!force && !pathoperationsunlink) {
2761 if (desktop() && !pathoperationsunlink && !silent) {
2762 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Unable to unlink. Check the setting for 'Unlinking Clones' in your preferences."));
2763 }
2764 return false;
2765 }
2766 bool unlinked = false;
2767 ObjectSet tmp_set(document());
2768 std::vector<SPItem*> items_(items().begin(), items().end());
2769 for (auto& it:items_) {
2770 tmp_set.set(it);
2771 unlinked = tmp_set.unlink(true, silent) || unlinked;
2772 it = tmp_set.singleItem();
2773 if (is<SPGroup>(it)) {
2774 std::vector<SPObject*> c = it->childList(false);
2775 tmp_set.setList(c);
2776 unlinked = tmp_set.unlinkRecursive(skip_undo, force, silent) || unlinked;
2777 }
2778 }
2779 if (!unlinked) {
2780 if(desktop() && !silent)
2781 desktop()->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No clones to unlink</b> in the selection."));
2782 }
2783 if (!skip_undo) {
2784 DocumentUndo::done(document(), _("Unlink clone recursively"), INKSCAPE_ICON("edit-clone-unlink"));
2785 }
2786 setList(items_);
2787 return unlinked;
2788}
2789
2790void ObjectSet::removeLPESRecursive(bool keep_paths) {
2791 if (isEmpty()){
2792 return;
2793 }
2794
2795 ObjectSet tmp_set(document());
2796 std::vector<SPItem *> items_(items().begin(), items().end());
2797 std::vector<SPItem *> itemsdone_;
2798 for (auto& it:items_) {
2799 auto splpeitem = cast<SPLPEItem>(it);
2800 auto spgroup = cast<SPGroup>(it);
2801 if (spgroup) {
2802 std::vector<SPObject*> c = spgroup->childList(false);
2803 tmp_set.setList(c);
2804 tmp_set.removeLPESRecursive(keep_paths);
2805 }
2806 if (splpeitem) {
2807 // Maybe the item is changed from SPShape to SPPath invalidating selection
2808 // fix issue Inkscape#2321
2809 char const *id = splpeitem->getAttribute("id");
2810 SPDocument *document = splpeitem->document;
2811 splpeitem->removeAllPathEffects(keep_paths);
2812 auto upditem = cast<SPItem>(document->getObjectById(id));
2813 if (upditem) {
2814 itemsdone_.push_back(upditem);
2815 }
2816 } else {
2817 itemsdone_.push_back(it);
2818 }
2819
2820 }
2821 setList(itemsdone_);
2822}
2823
2825{
2826 SPItem *item = singleItem();
2827
2828 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
2830 // Check if other than two objects are selected
2831
2832 auto items_= items();
2833 if (boost::distance(items_) != 1 || !item) {
2834 if(desktop())
2836 return;
2837 }
2838
2839 SPItem *original = nullptr;
2840 auto use = cast<SPUse>(item);
2841 if (use) {
2842 original = use->get_original();
2843 } else if (auto offset = cast<SPOffset>(item)) {
2845 } else if (auto text = cast<SPText>(item)) {
2846 original = text->get_first_shape_dependency();
2847 } else if (auto flowtext = cast<SPFlowtext>(item)) {
2848 original = flowtext->get_frame(nullptr); // first frame only
2849 }
2850
2851 if (original == nullptr) { // it's an object that we don't know what to do with
2852 if(desktop())
2854 return;
2855 }
2856
2857 if (!original) {
2858 if(desktop())
2859 desktop()->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>Cannot find</b> the object to select (orphaned clone, offset, textpath, flowed text?)"));
2860 return;
2861 }
2862
2863 for (SPObject *o = original; o && !is<SPRoot>(o); o = o->parent) {
2864 if (is<SPDefs>(o)) {
2865 if(desktop())
2866 desktop()->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The object you're trying to select is <b>not visible</b> (it is in &lt;defs&gt;)"));
2867 return;
2868 }
2869 }
2870
2871 if (original) {
2873 bool highlight = prefs->getBool("/options/highlightoriginal/value");
2874 if (highlight) {
2876 Geom::OptRect b = original->desktopVisualBounds();
2877 if ( a && b && desktop()) {
2878 // draw a flashing line between the objects
2879 SPCurve curve;
2880 curve.moveto(a->midpoint());
2881 curve.lineto(b->midpoint());
2882
2883 // We use a bpath as it supports dashes.
2884 auto canvas_item_bpath = new Inkscape::CanvasItemBpath(desktop()->getCanvasTemp(), curve.get_pathvector());
2885 canvas_item_bpath->set_stroke(0x0000ddff);
2886 canvas_item_bpath->set_dashes({5.0, 3.0});
2887 canvas_item_bpath->set_visible(true);
2888 desktop()->add_temporary_canvasitem(canvas_item_bpath, 1000);
2889 }
2890 }
2891
2892 clear();
2893 set(original);
2894 if (SP_CYCLING == SP_CYCLE_FOCUS && desktop()) {
2896 }
2897 }
2898}
2899
2903void ObjectSet::cloneOriginalPathLPE(bool allow_transforms, bool sync, bool skip_undo)
2904{
2905
2907 SPObject * firstItem = nullptr;
2908 auto items_= items();
2909 bool multiple = false;
2910 for (auto *item : items_) {
2911 if (is<SPShape>(item) || is<SPText>(item) || is<SPGroup>(item)) {
2912 if (firstItem) {
2913 os << "|";
2914 multiple = true;
2915 } else {
2916 firstItem = item;
2917 }
2918 os << '#' << item->getId() << ",0,1";
2919 }
2920 }
2921 if (firstItem) {
2924 // create the LPE
2925 Inkscape::XML::Node *lpe_repr = xml_doc->createElement("inkscape:path-effect");
2926 if (multiple) {
2927 lpe_repr->setAttribute("effect", "fill_between_many");
2928 lpe_repr->setAttributeOrRemoveIfEmpty("linkedpaths", os.str());
2929 } else {
2930 lpe_repr->setAttribute("effect", "clone_original");
2931 lpe_repr->setAttribute("css_properties", "");
2932 lpe_repr->setAttribute("attributes", "");
2933 lpe_repr->setAttribute("linkeditem", ((Glib::ustring)"#" + (Glib::ustring)firstItem->getId()));
2934 }
2935 lpe_repr->setAttribute("is_visible", "true");
2936 gchar const *method_str = allow_transforms ? "d" : "bsplinespiro";
2937 lpe_repr->setAttribute("method", method_str);
2938 gchar const *allow_transforms_str = allow_transforms ? "true" : "false";
2939 lpe_repr->setAttribute("allow_transforms", allow_transforms_str);
2940 document()->getDefs()->getRepr()->addChild(lpe_repr, nullptr); // adds to <defs> and assigns the 'id' attribute
2941 std::string lpe_id_href = std::string("#") + lpe_repr->attribute("id");
2942 Inkscape::GC::release(lpe_repr);
2943 Inkscape::XML::Node* clone = nullptr;
2944 auto firstgroup = cast<SPGroup>(firstItem);
2945 auto shape = cast<SPShape>(firstItem);
2946 auto path = cast<SPPath>(firstItem);
2947 if (firstgroup) {
2948 if (!multiple) {
2949 clone = firstgroup->getRepr()->duplicate(xml_doc);
2950 }
2951 } else {
2952 // create the new path
2953 clone = xml_doc->createElement("svg:path");
2954 if (sync && !multiple && shape) {
2955 std::optional<SPCurve> c = SPCurve::ptr_to_opt(shape->curveForEdit());
2956 if (c) {
2957 if (path) {
2958 clone->setAttribute("original-d", sp_svg_write_path(c->get_pathvector()));
2959 }
2960 clone->setAttribute("d", sp_svg_write_path(c->get_pathvector()));
2961 } else {
2962 clone->setAttribute("d", "M 0 0");
2963 }
2964 } else {
2965 clone->setAttribute("d", "M 0 0");
2966 }
2967
2968 }
2969 if (clone) {
2970 // add the new clone to the top of the original's parent
2971 parent->appendChildRepr(clone);
2972 // select the new object:
2973 set(clone);
2975 SPObject *clone_obj = document()->getObjectById(clone->attribute("id"));
2976 auto clone_lpeitem = cast<SPLPEItem>(clone_obj);
2977 if (clone_lpeitem) {
2978 if (sync && !multiple) {
2979 lpe_repr->setAttribute("attributes", "style,clip-path,mask");
2980 }
2981 lpe_repr->setAttribute("is_visible", "true");
2982 clone_lpeitem->addPathEffect(lpe_id_href, false);
2983 }
2984 if (!skip_undo) {
2985 if (multiple) {
2986 DocumentUndo::done(document(), _("Fill between many"), INKSCAPE_ICON("edit-clone-link-lpe"));
2987 } else {
2988 DocumentUndo::done(document(), _("Clone original"), INKSCAPE_ICON("edit-clone-link-lpe"));
2989 }
2990 }
2991 }
2992 } else {
2993 if(desktop())
2994 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select path(s) to fill."));
2995 }
2996}
2997
2998void ObjectSet::toMarker(bool apply)
2999{
3000 // sp_selection_tile has similar code
3001
3002 SPDocument *doc = document();
3003 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
3004
3005 // check if something is selected
3006 if (isEmpty()) {
3007 if (desktop())
3009 _("Select <b>object(s)</b> to convert to marker."));
3010 return;
3011 }
3012
3013 doc->ensureUpToDate();
3015 if (!r) {
3016 return;
3017 }
3018
3019 std::vector<SPItem*> items_(items().begin(), items().end());
3020 sort(items_.begin(), items_.end(), sp_item_repr_compare_position_bool);
3021
3022 // bottommost object, after sorting
3023 SPObject *parent = items_.front()->parent;
3024
3025 Geom::Affine parent_transform;
3026 {
3027 auto parentItem = cast<SPItem>(parent);
3028 if (parentItem) {
3029 parent_transform = parentItem->i2doc_affine();
3030 } else {
3031 g_assert_not_reached();
3032 }
3033 }
3034
3035 // Create a list of duplicates, to be pasted inside marker element.
3036 std::vector<Inkscape::XML::Node*> repr_copies;
3037 for (auto *item : items_) {
3038 auto *dup = item->getRepr()->duplicate(xml_doc);
3039 repr_copies.push_back(dup);
3040 }
3041
3042 Geom::Rect bbox(r->min() * doc->dt2doc(), r->max() * doc->dt2doc());
3043
3044 // calculate the transform to be applied to objects to move them to 0,0
3045 // (alternative would be to define viewBox or set overflow:visible)
3046 Geom::Affine const move = Geom::Translate(-bbox.min());
3047 Geom::Point const center = bbox.dimensions() * 0.5;
3048
3049 if (apply) {
3050 // Delete objects so that their clones don't get alerted;
3051 // the objects will be restored inside the marker element.
3052 for (auto item : items_){
3053 item->deleteObject(false);
3054 }
3055 }
3056
3057 // Hack: Temporarily set clone compensation to unmoved, so that we can move clone-originals
3058 // without disturbing clones.
3059 // See ActorAlign::on_button_click() in src/ui/dialog/align-and-distribute.cpp
3061 int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
3062 prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
3063
3064 gchar const *mark_id = generate_marker(repr_copies, bbox, doc, center, parent_transform * move);
3065 (void)mark_id;
3066
3067 // restore compensation setting
3068 prefs->setInt("/options/clonecompensation/value", saved_compensation);
3069
3070
3071
3072 DocumentUndo::done(doc, _("Objects to marker"), "");
3073}
3074
3075static void sp_selection_to_guides_recursive(SPItem *item, bool wholegroups) {
3076 auto group = cast<SPGroup>(item);
3077 if (group && !is<SPBox3D>(item) && !wholegroups) {
3078 std::vector<SPItem*> items=group->item_list();
3079 for (auto item : items){
3081 }
3082 } else {
3084 }
3085}
3086
3088{
3089 SPDocument *doc = document();
3090 // we need to copy the list because it gets reset when objects are deleted
3091 std::vector<SPItem*> items_(items().begin(), items().end());
3092
3093 if (isEmpty()) {
3094 if(desktop())
3095 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to convert to guides."));
3096 return;
3097 }
3098
3100 bool deleteitems = !prefs->getBool("/tools/cvg_keep_objects", false);
3101 bool wholegroups = prefs->getBool("/tools/cvg_convert_whole_groups", false);
3102
3103 // If an object is earlier in the selection list than its clone, and it is deleted, then the clone will have changed
3104 // and its entry in the selection list is invalid (crash).
3105 // Therefore: first convert all, then delete all.
3106
3107 for (auto item : items_){
3109 }
3110
3111 if (deleteitems) {
3112 clear();
3114 }
3115
3116 DocumentUndo::done(doc, _("Objects to guides"), "");
3117}
3118
3119/*
3120 * Convert objects to <symbol>. How that happens depends on what is selected:
3121 *
3122 * 1) A random selection of objects will be embedded into a single <symbol> element.
3123 *
3124 * 2) Except, a single <g> will have its content directly embedded into a <symbol>; the 'id' and
3125 * 'style' of the <g> are transferred to the <symbol>.
3126 *
3127 * 3) Except, a single <g> with a transform that isn't a translation will keep the group when
3128 * embedded into a <symbol> (with 'id' and 'style' transferred to <symbol>). This is because a
3129 * <symbol> cannot have a transform. (If the transform is a pure translation, the translation
3130 * is moved to the referencing <use> element that is created.)
3131 *
3132 * Possible improvements:
3133 *
3134 * Move objects inside symbol so bbox corner at 0,0 (see marker/pattern)
3135 *
3136 * For SVG2, set 'refX' 'refY' to object center (with compensating shift in <use>
3137 * transformation).
3138 */
3140{
3141
3142 SPDocument *doc = document();
3143 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
3144 // Check if something is selected.
3145 if (isEmpty()) {
3146 if (desktop())
3147 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>objects</b> to convert to symbol."));
3148 return;
3149 }
3150
3151 doc->ensureUpToDate();
3152
3153 std::vector<SPObject*> items_(objects().begin(), objects().end());
3154 sort(items_.begin(),items_.end(),sp_object_compare_position_bool);
3155
3156 // Keep track of parent, this is where <use> will be inserted.
3157 Inkscape::XML::Node *the_first_repr = items_[0]->getRepr();
3158 Inkscape::XML::Node *the_parent_repr = the_first_repr->parent();
3159
3160 // Find out if we have a single group
3161 bool single_group = false;
3162 SPGroup *the_group = nullptr;
3163 Geom::Affine transform;
3164 if( items_.size() == 1 ) {
3165 SPObject *object = items_[0];
3166 the_group = cast<SPGroup>(object);
3167 if ( the_group ) {
3168 single_group = true;
3169
3170 if( !sp_svg_transform_read( object->getAttribute("transform"), &transform ))
3171 transform = Geom::identity();
3172
3173 if( transform.isTranslation() ) {
3174
3175 // Create new list from group children.
3176 items_ = object->childList(false);
3177
3178 // Hack: Temporarily set clone compensation to unmoved, so that we can move clone-originals
3179 // without disturbing clones.
3180 // See ActorAlign::on_button_click() in src/ui/dialog/align-and-distribute.cpp
3182 int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
3183 prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
3184
3185 // Remove transform on group, updating clones.
3186 the_group->doWriteTransform(Geom::identity());
3187
3188 // restore compensation setting
3189 prefs->setInt("/options/clonecompensation/value", saved_compensation);
3190 }
3191 }
3192 }
3193
3194 // Create new <symbol>
3195 Inkscape::XML::Node *defsrepr = doc->getDefs()->getRepr();
3196 Inkscape::XML::Node *symbol_repr = xml_doc->createElement("svg:symbol");
3197
3198 defsrepr->appendChild(symbol_repr);
3199 bool settitle = false;
3200 // For a single group, copy relevant attributes.
3201 if( single_group ) {
3202 Glib::ustring id = the_group->getAttribute("id");
3203 symbol_repr->setAttribute("style", the_group->getAttribute("style"));
3204 symbol_repr->setAttribute("class", the_group->getAttribute("class"));
3205 the_group->setAttribute("id", id + "_transform");
3206 symbol_repr->setAttribute("id", id);
3207
3208 // This should eventually be replaced by 'refX' and 'refY' once SVG WG approves it.
3209 // It is done here for round-tripping
3210 symbol_repr->setAttribute("inkscape:transform-center-x",
3211 the_group->getAttribute("inkscape:transform-center-x"));
3212 symbol_repr->setAttribute("inkscape:transform-center-y",
3213 the_group->getAttribute("inkscape:transform-center-y"));
3214
3215 the_group->removeAttribute("style");
3216
3217 }
3218
3219 // Move selected items to new <symbol>
3220 for (std::vector<SPObject*>::const_reverse_iterator i=items_.rbegin();i!=items_.rend();++i){
3221 gchar* title = (*i)->title();
3222 if (!single_group && !settitle && title) {
3223 Inkscape::XML::Node *title_repr = xml_doc->createElement("svg:title");
3224 symbol_repr->addChildAtPos(title_repr, 0);
3225 title_repr->appendChild(xml_doc->createTextNode(title));
3226 Inkscape::GC::release(title_repr);
3227 gchar * desc = (*i)->desc();
3228 if (desc) {
3229 Inkscape::XML::Node *desc_repr = xml_doc->createElement("svg:desc");
3230 desc_repr->appendChild(xml_doc->createTextNode(desc));
3231 symbol_repr->addChildAtPos(desc_repr, 1);
3232 Inkscape::GC::release(desc_repr);
3233 }
3234 g_free(desc);
3235 settitle = true;
3236 }
3237 g_free(title);
3238 Inkscape::XML::Node *repr = (*i)->getRepr();
3239 repr->parent()->removeChild(repr);
3240 symbol_repr->addChild(repr, nullptr);
3241 }
3242
3243 if( single_group && transform.isTranslation() ) {
3244 the_group->deleteObject(true);
3245 }
3246
3247 // Create <use> pointing to new symbol (to replace the moved objects).
3248 Inkscape::XML::Node *clone = xml_doc->createElement("svg:use");
3249
3250 clone->setAttribute("xlink:href", Glib::ustring("#")+symbol_repr->attribute("id"));
3251
3252 the_parent_repr->appendChild(clone);
3253
3254 if( single_group && transform.isTranslation() ) {
3255 clone->setAttributeOrRemoveIfEmpty("transform", sp_svg_transform_write(transform));
3256 }
3257
3258 // Change selection to new <use> element.
3259 set(clone);
3260
3261 // Clean up
3262 Inkscape::GC::release(symbol_repr);
3263
3264 DocumentUndo::done(doc, _("Group to symbol"), "");
3265}
3266
3267/*
3268 * Takes selected <use> that reference a symbol, and unSymbol those symbols
3269 */
3271{
3272 for (const auto obj: items()) {
3273 auto use = cast<SPUse>(obj);
3274 if (use) {
3275 auto sym = cast<SPSymbol>(use->root());
3276 if (sym) {
3277 sym->unSymbol();
3278 }
3279 }
3280 }
3281 DocumentUndo::done(document(), _("unSymbol all selected symbols"), "");
3282}
3283
3284void ObjectSet::tile(bool apply)
3285{
3286 // toMarker has similar code
3287
3288 SPDocument *doc = document();
3289 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
3290
3291 // check if something is selected
3292 if (isEmpty()) {
3293 if (desktop())
3295 _("Select <b>object(s)</b> to convert to pattern."));
3296 return;
3297 }
3298
3299 doc->ensureUpToDate();
3301 if ( !r ) {
3302 return;
3303 }
3304
3305 std::vector<SPItem*> items_(items().begin(), items().end());
3306
3307 sort(items_.begin(),items_.end(),sp_object_compare_position_bool);
3308
3309 // bottommost object, after sorting
3310 SPObject *parent = items_[0]->parent;
3311
3312
3313 Geom::Affine parent_transform;
3314 {
3315 auto parentItem = cast<SPItem>(parent);
3316 if (parentItem) {
3317 parent_transform = parentItem->i2doc_affine();
3318 } else {
3319 g_assert_not_reached();
3320 }
3321 }
3322
3323 // remember the position of the first item
3324 gint pos = items_[0]->getRepr()->position();
3325
3326 // create a list of duplicates
3327 std::vector<Inkscape::XML::Node*> repr_copies;
3328 for (auto item : items_){
3329 Inkscape::XML::Node *dup = item->getRepr()->duplicate(xml_doc);
3330 repr_copies.push_back(dup);
3331 }
3332
3333 Geom::Rect bbox(r->min() * doc->dt2doc(), r->max() * doc->dt2doc());
3334
3335 if (apply) {
3336 // delete objects so that their clones don't get alerted; this object will be restored shortly
3337 for (auto item : items_){
3338 item->deleteObject(false);
3339 }
3340 }
3341
3342 // Hack: Temporarily set clone compensation to unmoved, so that we can move clone-originals
3343 // without disturbing clones.
3344 // See ActorAlign::on_button_click() in src/ui/dialog/align-and-distribute.cpp
3346 int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
3347 prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
3348
3350 gchar const *pat_id = SPPattern::produce(repr_copies, bbox, doc,
3351 move.inverse() /* patternTransform */,
3352 parent_transform * move);
3353
3354 // restore compensation setting
3355 prefs->setInt("/options/clonecompensation/value", saved_compensation);
3356
3357 if (apply) {
3358 Inkscape::XML::Node *rect = xml_doc->createElement("svg:rect");
3359 gchar *style_str = g_strdup_printf("stroke:none;fill:url(#%s)", pat_id);
3360 rect->setAttribute("style", style_str);
3361 g_free(style_str);
3362
3363 rect->setAttributeOrRemoveIfEmpty("transform", sp_svg_transform_write(parent_transform.inverse()));
3364
3365 rect->setAttributeSvgDouble("width", bbox.width());
3366 rect->setAttributeSvgDouble("height", bbox.height());
3367 rect->setAttributeSvgDouble("x", bbox.left());
3368 rect->setAttributeSvgDouble("y", bbox.top());
3369
3370 // restore parent and position
3371 parent->getRepr()->addChildAtPos(rect, pos);
3372 SPItem *rectangle = static_cast<SPItem *>(document()->getObjectByRepr(rect));
3373
3375
3376 clear();
3377 set(rectangle);
3378 }
3379
3380
3381 DocumentUndo::done(doc, _("Objects to pattern"), "");
3382}
3383
3385{
3386
3387 SPDocument *doc = document();
3388 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
3389
3390 // check if something is selected
3391 if (isEmpty()) {
3392 if(desktop())
3393 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select an <b>object with pattern fill</b> to extract objects from."));
3394 return;
3395 }
3396
3397 std::vector<SPItem*> new_select;
3398
3399 bool did = false;
3400
3401 std::vector<SPItem*> items_(items().begin(), items().end());
3402 for (std::vector<SPItem*>::const_reverse_iterator i=items_.rbegin();i!=items_.rend();++i){
3403 SPItem *item = *i;
3404
3405 SPStyle *style = item->style;
3406
3407 if (!style || !style->fill.isPaintserver())
3408 continue;
3409
3411
3412 auto basePat = cast<SPPattern>(server);
3413 if (!basePat) {
3414 continue;
3415 }
3416
3417 did = true;
3418
3419 SPPattern *pattern = basePat->rootPattern();
3420
3421 Geom::Affine pat_transform = basePat->getTransform();
3422 pat_transform *= item->transform;
3423
3424 for (auto& child: pattern->children) {
3425 if (is<SPItem>(&child)) {
3426 Inkscape::XML::Node *copy = child.getRepr()->duplicate(xml_doc);
3427 auto i = cast<SPItem>(item->parent->appendChildRepr(copy));
3428
3429 // FIXME: relink clones to the new canvas objects
3430 // use SPObject::setid when mental finishes it to steal ids of
3431
3432 // this is needed to make sure the new item has curve (simply requestDisplayUpdate does not work)
3433 doc->ensureUpToDate();
3434
3435 if (i) {
3436 Geom::Affine transform( i->transform * pat_transform );
3437 i->doWriteTransform(transform);
3438
3439 new_select.push_back(i);
3440 } else {
3441 g_assert_not_reached();
3442 }
3443 }
3444 }
3445
3447 sp_repr_css_set_property(css, "fill", "none");
3448 sp_repr_css_change(item->getRepr(), css, "style");
3449 }
3450
3451 if (!did) {
3452 if(desktop())
3453 desktop()->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No pattern fills</b> in the selection."));
3454 } else {
3455 DocumentUndo::done(document(), _("Pattern to objects"), "");
3456 setList(new_select);
3457 }
3458}
3459
3461{
3462
3463 SPDocument *doc = document();
3464 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
3465
3466 // check if something is selected
3467 if (isEmpty()) {
3468 if(desktop())
3469 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to make a bitmap copy."));
3470 return;
3471 }
3472
3473 if (desktop()) {
3474 desktop()->messageStack()->flash(Inkscape::IMMEDIATE_MESSAGE, _("Rendering bitmap..."));
3475 // set "busy" cursor
3477 }
3478
3479 // Get the bounding box of the selection
3480 doc->ensureUpToDate();
3482 if (!bbox) {
3483 if(desktop())
3485 return; // exceptional situation, so not bother with a translatable error message, just quit quietly
3486 }
3487
3488 // List of the items to show; all others will be hidden
3489 std::vector<SPItem const *> items_{items().begin(), items().end()};
3490
3491 // Sort items so that the topmost comes last
3492 sort(items_.begin(), items_.end(), sp_item_repr_compare_position_bool);
3493
3494 // Remember parent and z-order of the topmost one
3495 gint pos = items_.back()->getRepr()->position();
3496 SPObject *parent_object = items_.back()->parent;
3497 Inkscape::XML::Node *parent = parent_object->getRepr();
3498
3499 // Calculate resolution
3501 double res;
3502 int const prefs_res = prefs->getInt("/options/createbitmap/resolution", 0);
3503 int const prefs_min = prefs->getInt("/options/createbitmap/minsize", 0);
3504 if (0 < prefs_res) {
3505 // If it's given explicitly in prefs, take it
3506 res = prefs_res;
3507 } else if (0 < prefs_min) {
3508 // If minsize is given, look up minimum bitmap size (default 250 pixels) and calculate resolution from it
3509 res = Inkscape::Util::Quantity::convert(prefs_min, "in", "px") / MIN(bbox->width(), bbox->height());
3510 } else {
3511
3512 // Get export DPI from the first item available
3513 auto dpi = Geom::Point(0, 0);
3514 for (auto &item : items_) {
3515 dpi = item->getExportDpi();
3516 if (dpi.x()) break;
3517 }
3518 if (!dpi.x()) {
3519 dpi = doc->getRoot()->getExportDpi();
3520 }
3521 if (dpi.x()) {
3522 res = dpi.x();
3523 } else {
3524 // if all else fails, take the default 96 dpi
3525 res = Inkscape::Util::Quantity::convert(1, "in", "px");
3526 }
3527 }
3528
3529 if (res == Inkscape::Util::Quantity::convert(1, "in", "px")) { // for default 96 dpi, snap it to pixel grid
3530 bbox = bbox->roundOutwards();
3531 }
3532
3533 // anti-aliasing override
3534 std::optional<Antialiasing> antialias;
3535 if (auto nv = doc->getNamedView()) {
3536 // if off, then disable antialiasing; if on, then let SVG dictate what it is
3537 if (!nv->antialias_rendering) {
3538 antialias = Antialiasing::None;
3539 }
3540 }
3541
3542 Inkscape::Pixbuf *pb = sp_generate_internal_bitmap(doc, *bbox, res, items_, false, nullptr, 1, antialias);
3543
3544 if (pb) {
3545 // Create the repr for the image
3546 Inkscape::XML::Node * repr = xml_doc->createElement("svg:image");
3547 sp_embed_image(repr, pb);
3548 repr->setAttributeSvgDouble("width", bbox->width());
3549 repr->setAttributeSvgDouble("height", bbox->height());
3550
3551 // Calculate the matrix that will be applied to the image so that it exactly overlaps the source objects
3552 auto parentItem = cast<SPItem>(parent_object);
3553 Geom::Affine affine = Geom::Translate(bbox->left(), bbox->top()) * parentItem->i2doc_affine().inverse();
3554
3555 // Write transform
3556 repr->setAttributeOrRemoveIfEmpty("transform", sp_svg_transform_write(affine));
3557
3558 // add the new repr to the parent
3559 parent->addChildAtPos(repr, pos + 1);
3560
3561 // Set selection to the new image
3562 clear();
3563 add(repr);
3564
3565 // Clean up
3567 delete pb;
3568
3569 // Complete undoable transaction
3570 DocumentUndo::done(doc, _("Create bitmap"), INKSCAPE_ICON("selection-make-bitmap-copy"));
3571 }
3572
3573 if(desktop()) {
3575 }
3576}
3577
3578/* Creates a mask or clipPath from selection.
3579 * What is a clip group?
3580 * A clip group is a tangled mess of XML that allows an object inside a group
3581 * to clip the entire group using a few <use>s and generally irritating me.
3582 */
3583
3585{
3586 SPDocument* doc = document();
3587 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
3588
3589 if (isEmpty()) {
3590 if(desktop())
3591 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to create clippath or mask from."));
3592 return;
3593 }
3594
3595 std::vector<Inkscape::XML::Node*> p(xmlNodes().begin(), xmlNodes().end());
3596
3597 sort(p.begin(),p.end(),sp_repr_compare_position_bool);
3598
3599 clear();
3600
3601 int topmost = (p.back())->position();
3602 Inkscape::XML::Node *topmost_parent = (p.back())->parent();
3603
3604 Inkscape::XML::Node *inner = xml_doc->createElement("svg:g");
3605 inner->setAttribute("inkscape:label", "Clip");
3606
3607 for(auto current : p){
3608 if (current->parent() == topmost_parent) {
3609 Inkscape::XML::Node *spnew = current->duplicate(xml_doc);
3611 inner->appendChild(spnew);
3612 Inkscape::GC::release(spnew);
3613 topmost --; // only reduce count for those items deleted from topmost_parent
3614 } else { // move it to topmost_parent first
3615 std::vector<Inkscape::XML::Node*> temp_clip;
3616
3617 // At this point, current may already have no item, due to its being a clone whose original is already moved away
3618 // So we copy it artificially calculating the transform from its repr->attr("transform") and the parent transform
3619 gchar const *t_str = current->attribute("transform");
3620 Geom::Affine item_t(Geom::identity());
3621 if (t_str)
3622 sp_svg_transform_read(t_str, &item_t);
3623 item_t *= cast<SPItem>(doc->getObjectByRepr(current->parent()))->i2doc_affine();
3624 // FIXME: when moving both clone and original from a transformed group (either by
3625 // grouping into another parent, or by cut/paste) the transform from the original's
3626 // parent becomes embedded into original itself, and this affects its clones. Fix
3627 // this by remembering the transform diffs we write to each item into an array and
3628 // then, if this is clone, looking up its original in that array and pre-multiplying
3629 // it by the inverse of that original's transform diff.
3630
3631 sp_selection_copy_one(current, item_t, temp_clip, xml_doc);
3633
3634 // paste into topmost_parent (temporarily)
3635 std::vector<Inkscape::XML::Node*> copied = sp_selection_paste_impl(doc, doc->getObjectByRepr(topmost_parent), temp_clip);
3636 if (!copied.empty()) { // if success,
3637 // take pasted object (now in topmost_parent)
3638 Inkscape::XML::Node *in_topmost = copied.back();
3639 // make a copy
3640 Inkscape::XML::Node *spnew = in_topmost->duplicate(xml_doc);
3641 // remove pasted
3642 sp_repr_unparent(in_topmost);
3643 // put its copy into group
3644 inner->appendChild(spnew);
3645 Inkscape::GC::release(spnew);
3646 }
3647 }
3648 }
3649
3650 Inkscape::XML::Node *outer = xml_doc->createElement("svg:g");
3651 outer->appendChild(inner);
3652 topmost_parent->addChildAtPos(outer, topmost + 1);
3653
3654 Inkscape::XML::Node *clone = xml_doc->createElement("svg:use");
3655 clone->setAttribute("x", "0");
3656 clone->setAttribute("y", "0");
3657 clone->setAttribute("xlink:href", g_strdup_printf("#%s", inner->attribute("id")));
3658
3659 clone->setAttribute("inkscape:transform-center-x", inner->attribute("inkscape:transform-center-x"));
3660 clone->setAttribute("inkscape:transform-center-y", inner->attribute("inkscape:transform-center-y"));
3661
3662 std::vector<Inkscape::XML::Node*> templist;
3663 templist.push_back(clone);
3664 // add the new clone to the top of the original's parent
3665 gchar const *mask_id = SPClipPath::create(templist, doc);
3666
3667 char* tmp = g_strdup_printf("url(#%s)", mask_id);
3668 outer->setAttribute("clip-path", tmp);
3669 g_free(tmp);
3670
3672
3673 set(outer);
3674 DocumentUndo::done(doc, _("Create Clip Group"), "");
3675}
3676
3678{
3679 auto doc = document();
3680 doc->ensureUpToDate();
3681
3682 // Build a new drawing so our manipulations don't corupt to canvas
3683 Drawing drawing;
3684 unsigned dkey = SPItem::display_key_new(1);
3685 auto root = doc->getRoot()->invoke_show(drawing, dkey, SP_ITEM_SHOW_DISPLAY);
3686 drawing.setRoot(root);
3687
3688 // Hide all *SELECTED* objects from the drawing
3689 for (auto&& item : items()) {
3690 item->invoke_hide(dkey);
3691 }
3692
3693 drawing.update();
3694
3695 // Now in a new loop, ask for a color to be set for each item.
3696 for (auto&& item : items()) {
3697 if (auto shape = cast<SPShape>(item)) {
3698 bool evenodd = shape->style->fill_rule.computed == SP_WIND_RULE_EVENODD;
3699 if (auto curve = shape->curve()) {
3700 auto color = drawing.averageColor(curve->get_pathvector() * shape->i2dt_affine(), evenodd);
3701 auto style = sp_repr_css_attr_new();
3702 sp_repr_css_set_property_double(style, "fill-opacity", color.stealOpacity());
3703 sp_repr_css_set_property_string(style, "fill", color.toString(false));
3706
3707 }
3708 }
3709 }
3710
3711 doc->getRoot()->invoke_hide(dkey);
3712
3713 DocumentUndo::done(doc, _("Chameleon Fill"), "");
3714}
3715
3725 void ObjectSet::setMask(bool apply_clip_path, bool apply_to_layer, bool remove_original)
3726{
3727 if(!desktop() && apply_to_layer)
3728 return;
3729
3730 SPDocument *doc = document();
3731 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
3732
3733 // check if something is selected
3734 bool is_empty = isEmpty();
3735 if ( apply_to_layer && is_empty) {
3736 if(desktop())
3737 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to create clippath or mask from."));
3738 return;
3739 } else if (!apply_to_layer && ( is_empty || boost::distance(items())==1 )) {
3740 if(desktop())
3741 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select mask object and <b>object(s)</b> to apply clippath or mask to."));
3742 return;
3743 }
3744
3745 // FIXME: temporary patch to prevent crash!
3746 // Remove this when bboxes are fixed to not blow up on an item clipped/masked with its own clone
3747 bool clone_with_original = object_set_contains_both_clone_and_original(this);
3748 if (clone_with_original) {
3749 g_warning("Unable to clip/mask an object with its own clone");
3750 return; // in this version, you cannot clip/mask an object with its own clone
3751 }
3752 // /END FIXME
3753
3754 doc->ensureUpToDate();
3755
3756 std::vector<SPItem*> items_(items().begin(), items().end());
3757
3758 sort(items_.begin(),items_.end(),sp_object_compare_position_bool);
3759
3760 // See lp bug #542004
3761 clear();
3762
3763 // create a list of duplicates
3764 std::vector<std::pair<Inkscape::XML::Node*, Geom::Affine>> mask_items;
3765 std::vector<SPItem*> apply_to_items;
3766 std::vector<SPItem*> items_to_delete;
3767 std::vector<SPItem*> items_to_select;
3768
3770 bool topmost = prefs->getBool("/options/maskobject/topmost", true);
3771 int grouping = prefs->getInt("/options/maskobject/grouping", PREFS_MASKOBJECT_GROUPING_NONE);
3772
3773 if (apply_to_layer) {
3774 // all selected items are used for mask, which is applied to a layer
3775 apply_to_items.push_back(desktop()->layerManager().currentLayer());
3776 }
3777
3778 for (auto i : items_) {
3779 if((!topmost && !apply_to_layer && i == items_.front())
3780 || (topmost && !apply_to_layer && i == items_.back())
3781 || apply_to_layer){
3782
3783 Inkscape::XML::Node *dup = i->getRepr()->duplicate(xml_doc);
3784 mask_items.emplace_back(dup, i->i2doc_affine());
3785
3786 if (remove_original) {
3787 items_to_delete.push_back(i);
3788 }
3789 else {
3790 items_to_select.push_back(i);
3791 }
3792 continue;
3793 }else{
3794 apply_to_items.push_back(i);
3795 items_to_select.push_back(i);
3796 }
3797 }
3798
3799 items_.clear();
3800
3801 if (grouping == PREFS_MASKOBJECT_GROUPING_ALL) {
3802 // group all those objects into one group
3803 // and apply mask to that
3804 auto set = ObjectSet{document()};
3805 set.add(apply_to_items.begin(), apply_to_items.end());
3806
3807 items_to_select.clear();
3808
3809 Inkscape::XML::Node *group = set.group();
3810 group->setAttribute("inkscape:groupmode", "maskhelper");
3811
3812 // apply clip/mask only to newly created group
3813 apply_to_items.clear();
3814 apply_to_items.push_back(cast<SPItem>(doc->getObjectByRepr(group)));
3815
3816 items_to_select.push_back(cast<SPItem>(doc->getObjectByRepr(group)));
3817
3819 }
3820 if (grouping == PREFS_MASKOBJECT_GROUPING_SEPARATE) {
3821 items_to_select.clear();
3822 }
3823
3824 gchar const *attributeName = apply_clip_path ? "clip-path" : "mask";
3825 for (auto item : apply_to_items | boost::adaptors::reversed) {
3826 std::vector<Inkscape::XML::Node*> mask_items_dup;
3827 std::map<Inkscape::XML::Node*, Geom::Affine> dup_transf;
3828 for (auto const &mask_item : mask_items) {
3829 Inkscape::XML::Node *dup = mask_item.first->duplicate(xml_doc);
3830 mask_items_dup.push_back(dup);
3831 dup_transf[dup] = mask_item.second;
3832 }
3833
3835 // Node to apply mask to
3836 Inkscape::XML::Node *apply_mask_to = current;
3837
3838 if (grouping == PREFS_MASKOBJECT_GROUPING_SEPARATE) {
3839 // enclose current node in group, and apply crop/mask on that
3840 Inkscape::XML::Node *group = xml_doc->createElement("svg:g");
3841 // make a note we should ungroup this when unsetting mask
3842 group->setAttribute("inkscape:groupmode", "maskhelper");
3843
3844 Inkscape::XML::Node *spnew = current->duplicate(xml_doc);
3847 group->appendChild(spnew);
3848
3849 // Apply clip/mask to group instead
3850 apply_mask_to = group;
3851 item = cast<SPItem>(doc->getObjectByRepr(group));
3852
3853 items_to_select.push_back(item);
3854 Inkscape::GC::release(spnew);
3856 }
3857
3858 char const *mask_id = nullptr;
3859 if (apply_clip_path) {
3860 mask_id = SPClipPath::create(mask_items_dup, doc);
3861 } else {
3862 mask_id = SPMask::create(mask_items_dup, doc);
3863 }
3864
3865 // inverted object transform should be applied to a mask object,
3866 // as mask is calculated in user space (after applying transform)
3867 for (auto const &it : mask_items_dup) {
3868 auto clip_item = cast<SPItem>(doc->getObjectByRepr(it));
3869 clip_item->doWriteTransform(dup_transf[it]);
3870 clip_item->doWriteTransform(clip_item->transform * item->i2doc_affine().inverse());
3871 }
3872
3873 apply_mask_to->setAttribute(attributeName, Glib::ustring("url(#") + mask_id + ')');
3874 }
3875
3876 for (auto item : items_to_delete) {
3877 item->deleteObject(false);
3878 items_to_select.erase(std::remove(items_to_select.begin(), items_to_select.end(), item), items_to_select.end());
3879 }
3880
3881 addList(items_to_select);
3882}
3883
3884void ObjectSet::unsetMask(const bool apply_clip_path,
3885 const bool delete_helper_group,
3886 const bool remove_original)
3887{
3888 SPDocument *doc = document();
3889 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
3890
3891 // check if something is selected
3892 if (isEmpty()) {
3893 if(desktop())
3894 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to remove clippath or mask from."));
3895 return;
3896 }
3897
3899 bool ungroup_masked = prefs->getBool("/options/maskobject/ungrouping", true);
3900 doc->ensureUpToDate();
3901
3902 gchar const *attributeName = apply_clip_path ? "clip-path" : "mask";
3903 std::map<SPObject*,SPItem*> referenced_objects;
3904
3905 std::vector<SPItem*> items_(items().begin(), items().end());
3906 clear();
3907
3908 std::vector<SPGroup *> items_to_ungroup;
3909 std::vector<SPItem*> items_to_select(items_);
3910
3911 // SPObject* refers to a group containing the clipped path or mask itself,
3912 // whereas SPItem* refers to the item being clipped or masked
3913 for (auto i : items_){
3914 if (remove_original) {
3915 // remember referenced mask/clippath, so orphaned masks can be moved back to document
3916 SPItem *item = i;
3917 SPObject *obj_ref = nullptr;
3918
3919 if (apply_clip_path) {
3920 obj_ref = item->getClipObject();
3921 } else {
3922 obj_ref = item->getMaskObject();
3923 }
3924
3925 // collect distinct mask object (and associate with item to apply transform)
3926 if (obj_ref) {
3927 referenced_objects[obj_ref] = item;
3928 }
3929 }
3930
3931 i->setAttribute(attributeName, "none");
3932
3933 auto group = cast<SPGroup>(i);
3934 if (ungroup_masked && group && delete_helper_group) {
3935 // if we had previously enclosed masked object in group,
3936 // add it to list so we can ungroup it later
3937
3938 // ungroup only groups we created when setting clip/mask
3939 if (group->layerMode() == SPGroup::MASK_HELPER) {
3940 items_to_ungroup.push_back(group);
3941 }
3942 }
3943 }
3944
3945 // restore mask objects into a document
3946 for (auto & referenced_object : referenced_objects) {
3947 SPObject *obj = referenced_object.first; // Group containing the clipped paths or masks
3948 std::vector<Inkscape::XML::Node *> items_to_move;
3949 for (auto& child: obj->children) {
3950 // Collect all clipped paths and masks within a single group
3951 Inkscape::XML::Node *copy = child.getRepr()->duplicate(xml_doc);
3952 if (copy->attribute("inkscape:original-d") && copy->attribute("inkscape:path-effect")) {
3953 copy->setAttribute("d", copy->attribute("inkscape:original-d"));
3954 } else if (copy->attribute("inkscape:original-d")) {
3955 copy->setAttribute("d", copy->attribute("inkscape:original-d"));
3956 copy->removeAttribute("inkscape:original-d");
3957 } else if (!copy->attribute("inkscape:path-effect") && !is<SPPath>(&child)) {
3958 copy->removeAttribute("d");
3959 copy->removeAttribute("inkscape:original-d");
3960 }
3961 items_to_move.push_back(copy);
3962 }
3963
3964 if (!obj->isReferenced()) {
3965 // delete from defs if no other object references this mask
3966 obj->deleteObject(false);
3967 }
3968
3969 // remember parent and position of the item to which the clippath/mask was applied
3970 Inkscape::XML::Node *parent = (referenced_object.second)->getRepr()->parent();
3971 Inkscape::XML::Node *ref_repr = referenced_object.second->getRepr();
3972
3973 // Iterate through all clipped paths / masks
3974 for (auto const repr : items_to_move | boost::adaptors::reversed) {
3975 // insert into parent, restore pos
3976 parent->addChild(repr, ref_repr);
3977
3978 auto mask_item = cast<SPItem>(document()->getObjectByRepr(repr));
3979 if (!mask_item) {
3980 continue;
3981 }
3982 items_to_select.push_back(mask_item);
3983
3984 // transform mask, so it is moved the same spot where mask was applied
3985 mask_item->doWriteTransform(mask_item->transform * referenced_object.second->transform);
3986 }
3987 }
3988
3989 // ungroup marked groups added when setting mask
3990 for (auto group : items_to_ungroup | boost::adaptors::reversed) {
3991 if (group) {
3992 items_to_select.erase(std::remove(items_to_select.begin(), items_to_select.end(), group), items_to_select.end());
3993 std::vector<SPItem*> children;
3994 sp_item_group_ungroup(group, children);
3995 items_to_select.insert(items_to_select.end(),children.rbegin(),children.rend());
3996 } else {
3997 g_assert_not_reached();
3998 }
3999 }
4000
4001 // rebuild selection
4002 addList(items_to_select);
4003}
4004
4010bool ObjectSet::fitCanvas(bool with_margins, bool skip_undo)
4011{
4012 g_return_val_if_fail(document(), false);
4013
4014 if (isEmpty()) {
4015 if(desktop())
4016 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to fit canvas to."));
4017 return false;
4018 }
4020 if (bbox) {
4021 document()->fitToRect(*bbox, with_margins);
4022 if(!skip_undo)
4023 DocumentUndo::done(document(), _("Fit Page to Selection"), "");
4024 return true;
4025 } else {
4026 return false;
4027 }
4028}
4029
4031{
4032
4033 SPIPaint *paint;
4034 SPPaintServer *server;
4035 Glib::ustring _paintserver_id;
4036
4037 for (auto const item : items()) {
4039
4040 _paintserver_id.clear();
4041 paint = &(item->style->fill);
4042 if (paint->set && paint->isNone())
4043 sp_repr_css_set_property (css, "stroke", "none");
4044 else if (paint->set && paint->isColor()) {
4045 auto color = paint->getColor();
4047 sp_repr_css_set_property_string(css, "stroke", color.toString());
4048 }
4049 else if (!paint->set)
4050 sp_repr_css_unset_property (css, "stroke");
4051 else if (paint->set && paint->isPaintserver()) {
4052 server = SP_STYLE_FILL_SERVER(item->style);
4053 if (server) {
4054 Inkscape::XML::Node *srepr = server->getRepr();
4055 _paintserver_id += "url(#";
4056 _paintserver_id += srepr->attribute("id");
4057 _paintserver_id += ")";
4058 sp_repr_css_set_property (css, "stroke", _paintserver_id.c_str());
4059 }
4060 }
4061
4062 _paintserver_id.clear();
4063 paint = &(item->style->stroke);
4064 if (paint->set && paint->isNone())
4065 sp_repr_css_set_property (css, "fill", "none");
4066 else if (paint->set && paint->isColor()) {
4067 auto color = paint->getColor();
4069 sp_repr_css_set_property_string(css, "fill", color.toString());
4070 }
4071 else if (!paint->set)
4073 else if (paint->set && paint->isPaintserver()) {
4074 server = SP_STYLE_STROKE_SERVER(item->style);
4075 if (server) {
4076 Inkscape::XML::Node *srepr = server->getRepr();
4077 _paintserver_id += "url(#";
4078 _paintserver_id += srepr->attribute("id");
4079 _paintserver_id += ")";
4080 sp_repr_css_set_property (css, "fill", _paintserver_id.c_str());
4081 }
4082 }
4083
4084 if (desktop()) {
4086 set.add(item);
4088 } else {
4090 }
4091
4093 }
4094
4095 DocumentUndo::done(document(), _("Swap fill and stroke of an object"), "");
4096}
4097
4111{
4112 if (isEmpty()) {
4113 if (desktop()) {
4114 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>path(s)</b> to create fill between."));
4115 }
4116
4117 return;
4118 }
4119
4120 SPDocument *doc = document();
4121 SPObject *defs = doc->getDefs();
4122 SPObject *effect = nullptr;
4123
4124 Inkscape::XML::Node *effectRepr = doc->getReprDoc()->createElement("inkscape:path-effect");
4125 Inkscape::XML::Node *fillRepr = doc->getReprDoc()->createElement("svg:path");
4126
4127 Glib::ustring acc;
4128 Glib::ustring pathTarget;
4129
4130 for (auto&& item : items()) {
4131 // Force-assign id if there is none present
4132 if (!item->getId()) {
4133 auto id = item->generate_unique_id();
4134 item->set(SPAttr::ID, id.c_str());
4135 item->updateRepr();
4136 }
4137
4138 acc += "#";
4139 acc += item->getId();
4140 acc += ",0,1|";
4141 }
4142
4143 effectRepr->setAttribute("effect", "fill_between_many");
4144 effectRepr->setAttribute("method", "originald");
4145 effectRepr->setAttribute("linkedpaths", acc.c_str());
4146 defs->appendChild(effectRepr);
4147
4148 effect = doc->getObjectByRepr(effectRepr);
4149 pathTarget += "#";
4150 pathTarget += effect->getId();
4151
4152 fillRepr->setAttribute("inkscape:original-d", "M 0,0");
4153 fillRepr->setAttribute("inkscape:path-effect", pathTarget.c_str());
4154 fillRepr->setAttribute("d", "M 0,0");
4155
4156 // Get bottommost element in selection to create fill underneath
4157 auto&& items_ = std::vector<SPObject*>(items().begin(), items().end());
4158 SPObject *first = *std::min_element(items_.begin(), items_.end(), sp_object_compare_position_bool);
4159 SPObject *prev = first->getPrev();
4160
4161 first->parent->addChild(fillRepr, prev ? prev->getRepr() : nullptr);
4162
4163 doc->ensureUpToDate();
4164
4165 clear();
4166 add(fillRepr);
4167
4168 DocumentUndo::done(doc, _("Create linked fill object between paths"), "");
4169}
4170
4180 auto offset = cast<SPOffset>(item);
4181 auto flowtext = cast<SPFlowtext>(item);
4182
4183 auto check_item = _sibling_state.find(item);
4184 if (check_item != _sibling_state.end() && check_item->second > SiblingState::SIBLING_NONE) {
4185 return check_item->second;
4186 }
4187
4189
4190 // moving both a clone and its original or any ancestor
4193
4194 // moving both a text-on-path and its path
4195 } else if (is<SPText>(item) && is<SPTextPath>(item->firstChild()) &&
4196 includes(sp_textpath_get_path_item(cast_unsafe<SPTextPath>(item->firstChild())))) {
4198
4199 // moving both a flowtext and its frame
4200 } else if (flowtext && includes(flowtext->get_frame(nullptr))) {
4202
4203 // moving both an offset and its source
4204 } else if (offset && offset->sourceHref && includes(sp_offset_get_source(offset))) {
4206
4207 // moving object containing sub object
4208 } else if (item->style && item->style->shape_inside.containsAnyShape(this)) {
4210 }
4211
4212 _sibling_state[item] = ret;
4213
4214 return ret;
4215}
4216
4217void
4222
4229bool
4230fit_canvas_to_drawing(SPDocument *doc, bool with_margins)
4231{
4232 g_return_val_if_fail(doc, false);
4233
4234 doc->ensureUpToDate();
4235 SPItem const *const root = doc->getRoot();
4236 Geom::OptRect bbox = root->documentVisualBounds();
4237 if (bbox) {
4238 doc->fitToRect(*bbox, with_margins);
4239 return true;
4240 } else {
4241 return false;
4242 }
4243}
4244
4245void
4247{
4249 DocumentUndo::done(desktop->getDocument(), _("Fit Page to Drawing"), "");
4250 }
4251}
4252
4253static void itemtree_map(void (*f)(SPItem *, SPDesktop *), SPObject *root, SPDesktop *desktop) {
4254 // don't operate on layers
4255 if (auto item = cast<SPItem>(root)) {
4256 if (!desktop->layerManager().isLayer(item)) {
4257 f(item, desktop);
4258 }
4259 }
4260 for (auto& child: root->children) {
4261 //don't recurse into locked layers
4262 auto item = cast<SPItem>(&child);
4263 if (!(item && desktop->layerManager().isLayer(item) && item->isLocked())) {
4265 }
4266 }
4267}
4268
4269static void unlock(SPItem *item, SPDesktop */*desktop*/) {
4270 if (item->isLocked()) {
4271 item->setLocked(false);
4272 }
4273}
4274
4276 if (desktop->itemIsHidden(item)) {
4277 item->setExplicitlyHidden(false);
4278 }
4279}
4280
4281static void process_all(void (*f)(SPItem *, SPDesktop *), SPDesktop *dt, bool layer_only) {
4282 if (!dt) return;
4283
4284 SPObject *root;
4285 if (layer_only) {
4286 root = dt->layerManager().currentLayer();
4287 } else {
4288 root = dt->layerManager().currentRoot();
4289 }
4290
4291 itemtree_map(f, root, dt);
4292}
4293
4295 process_all(&unlock, dt, true);
4296}
4297
4299 process_all(&unlock, dt, false);
4300}
4301
4303 process_all(&unhide, dt, true);
4304}
4305
4307 process_all(&unhide, dt, false);
4308}
4309
4310/*
4311 Local Variables:
4312 mode:c++
4313 c-file-style:"stroustrup"
4314 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4315 indent-tabs-mode:nil
4316 fill-column:99
4317 End:
4318*/
4319// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
double scale
Definition aa.cpp:228
void get_all_items_recursive(std::vector< SPObject * > &objects, SPObject *object, Glib::ustring &condition)
Glib::ustring get_active_tool(InkscapeWindow *win)
void set_active_tool(InkscapeWindow *win, Glib::ustring const &tool)
Cairo integration helpers.
Inkscape canvas widget.
3x3 matrix representing an affine transformation.
Definition affine.h:70
Coord descrim() const
Calculate the descriminant.
Definition affine.cpp:434
bool isIdentity(Coord eps=EPSILON) const
Check whether this matrix is an identity matrix.
Definition affine.cpp:109
bool isTranslation(Coord eps=EPSILON) const
Check whether this matrix represents a pure translation.
Definition affine.cpp:123
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
Affine withoutTranslation() const
Definition affine.h:169
bool contains(CRect const &r) const
Check whether the rectangle includes all points in the given rectangle.
void unionWith(CRect const &b)
Enlarge the rectangle to contain the argument.
bool intersects(CRect const &r) const
Check whether the rectangles have any common points.
C top() const
Return top coordinate of the rectangle (+Y is downwards).
C left() const
Return leftmost coordinate of the rectangle (+X is to the right).
C height() const
Get the vertical extent of the rectangle.
C width() const
Get the horizontal extent of the rectangle.
CPoint min() const
Get the corner of the rectangle with smallest coordinate values.
CPoint dimensions() const
Get rectangle's width and height as a point.
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Two-dimensional point that doubles as a vector.
Definition point.h:66
constexpr Coord x() const noexcept
Definition point.h:104
Axis aligned, non-empty rectangle.
Definition rect.h:92
Rotation around the origin.
Definition transforms.h:187
static Rotate from_degrees(Coord deg)
Construct a rotation from its angle in degrees.
Definition transforms.h:218
Coord angle() const
Definition transforms.h:204
Scaling from the origin.
Definition transforms.h:150
Scale inverse() const
Definition transforms.h:172
Translation by a vector.
Definition transforms.h:115
Translate inverse() const
Get the inverse translation.
Definition transforms.h:133
bool addOpacity(double opacity=1.0)
Definition color.h:56
bool isSimilar(Color const &other, double epsilon=EPSILON) const
Find out if a color is similar to another color, converting it first if it's a different type.
Definition color.cpp:599
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
static void maybeDone(SPDocument *document, const gchar *keyconst, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
static void cancel(SPDocument *document)
void setRoot(DrawingItem *root)
Definition drawing.cpp:64
Colors::Color averageColor(Geom::IntRect const &area) const
Definition drawing.cpp:374
void update(Geom::IntRect const &area=Geom::IntRect::infinite(), Geom::Affine const &affine=Geom::identity(), unsigned flags=DrawingItem::STATE_ALL, unsigned reset=0)
Definition drawing.cpp:228
void renameLayer(SPObject *obj, char const *label, bool uniquify)
void setCurrentLayer(SPObject *object, bool clear=false)
Sets the current layer of the desktop.
SPGroup * currentLayer() const
Returns current top layer.
SPGroup * currentRoot() const
Returns current root (=bottom) layer.
bool isLayer(SPObject *object) const
True if object is a layer.
MessageId flash(MessageType type, char const *message)
Temporarily pushes a message onto the stack.
void enforceIds()
Assign IDs to selected objects that don't have an ID attribute Checks if the object's id attribute is...
void moveScreen(double dx, double dy)
void pasteSize(bool apply_x, bool apply_y)
SPGroupRange groups()
Returns a range of selected groups.
Definition object-set.h:267
Inkscape::XML::Node * group(bool is_anchor=false)
void stackDown(bool skip_undo=false)
void pasteSizeSeparately(bool apply_x, bool apply_y)
SPDesktop * desktop()
Returns the desktop the selection is bound to.
Definition object-set.h:390
void toNextLayer(bool skip_undo=false)
bool unlink(const bool skip_undo=false, const bool silent=false)
Unlink all directly selected clones.
void move(double dx, double dy)
SPItemRange items()
Returns a range of selected SPItems.
Definition object-set.h:255
void removeLPESRecursive(bool keep_paths)
void unsetMask(const bool apply_clip_path, const bool delete_helper_group, bool remove_original)
void cloneOriginalPathLPE(bool allow_transforms=false, bool sync=false, bool skip_undo=false)
This applies the Fill Between Many LPE, and has it refer to the selection.
void rotateRelative(const Geom::Point &, double)
bool add(SPObject *object, bool nosignal=false)
Add an SPObject to the set of selected objects.
boost::enable_if< boost::is_base_of< SPObject, T >, void >::type setList(const std::vector< T * > &objs)
Selects exactly the specified objects.
Definition object-set.h:299
XMLNodeRange xmlNodes()
Returns a range of the xml nodes of all selected objects.
Definition object-set.h:274
void raiseToTop(bool skip_undo=false)
void stackUp(bool skip_undo=false)
void setReprList(std::vector< XML::Node * > const &list)
Selects the objects with the same IDs as those in list.
void applyAffine(Geom::Affine const &affine, bool set_i2d=true, bool compensate=true, bool adjust_transf_center=true)
Apply matrix to the selection.
void moveRelative(const Geom::Point &move, bool compensate=true)
Geom::Affine _last_affine
Definition object-set.h:542
void duplicate(bool suppressDone=false, bool duplicateLayer=false)
void toMarker(bool apply=true)
void toPrevLayer(bool skip_undo=false)
void reapplyAffine()
Reapply the same transform again.
std::optional< Geom::Point > center() const
Returns the rotation/skew center of the selection.
void clear()
Unselects all selected objects.
std::list< SPBox3D * > const box3DList(Persp3D *persp=nullptr)
Returns a list of all 3D boxes in the current selection which are associated to persp.
void ungroup_all(bool skip_undo=false)
Keep ungrouping until there are no more groups.
boost::enable_if< boost::is_base_of< SPObject, T >, void >::type addList(const std::vector< T * > &objs)
Adds the specified objects to selection, without deselecting first.
Definition object-set.h:326
void deleteItems(bool skip_undo=false)
void ungroup(bool skip_undo=false)
void skewRelative(const Geom::Point &, double, double)
bool isEmpty()
Returns true if no items are selected.
void tile(bool apply=true)
std::map< SPObject *, SiblingState > _sibling_state
Definition object-set.h:540
void set(SPObject *object, bool persist_selection_context=false)
Set the selection to a single specific object.
Geom::OptRect documentBounds(SPItem::BBoxType type) const
int size()
Returns size of the selection.
SPDesktop * _desktop
Definition object-set.h:530
void scaleRelative(const Geom::Point &, const Geom::Scale &)
SPDocument * document()
Returns the document the selection is bound to.
Definition object-set.h:397
void raise(bool skip_undo=false)
SPItem * firstItem() const
Returns the first selected item, returns nullptr if no items selected.
void lowerToBottom(bool skip_undo=false)
void lower(bool skip_undo=false)
bool includes(SPObject *object, bool anyAncestor=false)
Returns true if the given object is selected.
SPItem * singleItem()
Returns a single selected item.
void setScaleAbsolute(double, double, double, double)
bool unlinkRecursive(const bool skip_undo=false, const bool force=false, const bool silent=false)
Recursively unlink any clones present in the current selection, including clones which are used to cl...
SPObjectRange objects()
Returns the list of selected objects.
void clone(bool skip_undo=false)
void toLayer(SPObject *layer)
Move selection to group moveto, after the last child of moveto (if it has any children).
void setMask(bool apply_clip_path, bool apply_to_layer, bool remove_original)
Creates a mask or clipPath from selection.
bool fitCanvas(bool with_margins, bool skip_undo=false)
Geom::OptRect visualBounds() const
std::list< Persp3D * > const perspList()
Returns a list of all perspectives which have a 3D box in the current selection.
void fillBetweenMany()
Creates a linked fill between all the objects in the current selection using the "Fill Between Many" ...
SiblingState getSiblingState(SPItem *item)
Associates the given SPItem with a SiblingState enum Needed for handling special cases while transfor...
Class to hold image data for raster images.
Definition cairo-utils.h:31
Preference storage class.
Definition preferences.h:66
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
static Preferences * get()
Access the singleton Preferences object.
int getInt(Glib::ustring const &pref_path, int def=0)
Retrieve an integer.
void setInt(Glib::ustring const &pref_path, int value)
Set an integer value.
std::string str() const
The set of selected SPObjects for a given document and layer model.
Definition selection.h:80
SPObject * activeContext()
Returns active layer for selection (currentLayer or its parent).
void set(XML::Node *repr)
Set the selection to an XML node's SPObject.
Definition selection.h:118
System-wide clipboard manager.
Definition clipboard.h:44
static ClipboardManager * get()
virtual bool pasteSize(ObjectSet *set, bool separately, bool apply_x, bool apply_y)=0
virtual bool pastePathEffect(ObjectSet *set)=0
virtual void copy(ObjectSet *set)=0
virtual Glib::ustring getFirstObjectID()=0
virtual bool paste(SPDesktop *desktop, bool in_place=false, bool on_page=false)=0
virtual bool pasteStyle(ObjectSet *set)=0
void invertSelection()
Unselect all selected points and select all unselected points.
void clear()
Remove all points from the selection, making it empty.
void selectAll()
Select all points that this selection can contain.
Inkscape::UI::ControlPointSelection * _selected_nodes
Definition node-tool.h:50
Inkscape::UI::MultiPathManipulator * _multipath
Definition node-tool.h:51
static double convert(double from_dist, Unit const *from, Unit const *to)
Convert distances.
Definition units.cpp:525
Interface for refcounted XML nodes.
Definition node.h:80
virtual Node * parent()=0
Get the parent of this node.
virtual Node * prev()=0
virtual void addChild(Node *child, Node *after)=0
Insert another node as a child of this node.
virtual void changeOrder(Node *child, Node *after)=0
Move a given node in this node's child order.
virtual void appendChild(Node *child)=0
Append a node as the last child of this node.
void setAttributeOrRemoveIfEmpty(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:167
void addChildAtPos(Node *child, unsigned pos)
Insert another node as a child of this node.
Definition node.h:430
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
virtual Node * duplicate(Document *doc) const =0
Create a duplicate of this node.
virtual unsigned position() const =0
Get the index of this node in parent's child order.
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
virtual void removeChild(Node *child)=0
Remove a child of this node.
bool setAttributeSvgDouble(Util::const_char_ptr key, double val)
For attributes where an exponent is allowed.
Definition node.cpp:111
virtual Node * lastChild()=0
Get the last child of this node.
static Persp3D * create_xml_element(SPDocument *document)
Definition persp3d.cpp:203
void apply_affine_transformation(Geom::Affine const &xform)
Definition persp3d.cpp:390
bool has_all_boxes_in_selection(Inkscape::ObjectSet *set) const
Definition persp3d.cpp:503
static char const * create(std::vector< Inkscape::XML::Node * > &reprs, SPDocument *document)
Wrapper around a Geom::PathVector object.
Definition curve.h:26
static std::optional< SPCurve > ptr_to_opt(T const &p)
Definition curve.h:83
To do: update description of desktop.
Definition desktop.h:149
double current_zoom() const
Definition desktop.h:335
Geom::Parallelogram get_display_area() const
Return canvas viewbox in desktop coordinates.
Definition desktop.cpp:521
SPDocument * getDocument() const
Definition desktop.h:189
Inkscape::Display::TemporaryItem * add_temporary_canvasitem(Inkscape::CanvasItem *item, int lifetime_msecs, bool move_to_bottom=true)
One should not keep a reference to the SPCanvasItem, the temporary item code will delete the object f...
Definition desktop.cpp:229
bool itemIsHidden(SPItem const *item) const
Definition desktop.cpp:264
Inkscape::MessageStack * messageStack() const
Definition desktop.h:160
Geom::Affine const & d2w() const
Transformation from desktop to window coordinates.
Definition desktop.h:419
bool isWithinViewport(SPItem const *item) const
True if desktop viewport intersects item's bbox.
Definition desktop.cpp:253
void scroll_relative(Geom::Point const &delta)
Scroll canvas by specific coordinate amount (window coordinates).
Definition desktop.cpp:872
Inkscape::Selection * getSelection() const
Definition desktop.h:188
Geom::Rotate const & current_rotation() const
Definition desktop.h:366
Inkscape::UI::Tools::ToolBase * getTool() const
Definition desktop.h:187
SPDocument * doc() const
Definition desktop.h:159
void clearWaitingCursor()
Definition desktop.cpp:1166
Inkscape::LayerManager & layerManager()
Definition desktop.h:287
void setWaitingCursor()
Definition desktop.cpp:1155
Typed SVG document implementation.
Definition document.h:103
SPRoot * getRoot()
Returns our SPRoot.
Definition document.h:202
const Geom::Affine & dt2doc() const
Desktop to document coordinate transformation.
Definition document.h:270
SPObject * getObjectById(std::string const &id) const
void fitToRect(Geom::Rect const &rect, bool with_margins=false)
Given a Geom::Rect that may, for example, correspond to the bbox of an object, this function fits the...
SPDefs * getDefs()
Return the main defs object for the document.
Definition document.cpp:245
Inkscape::XML::Document * getReprDoc()
Our Inkscape::XML::Document.
Definition document.h:213
int ensureUpToDate(unsigned int object_modified_tag=0)
Repeatedly works on getting the document updated, since sometimes it takes more than one pass to get ...
SPObject * getObjectByRepr(Inkscape::XML::Node *repr) const
SPNamedView * getNamedView()
Get the namedview for this document, creates it if it's not found.
Definition document.cpp:233
Gradient.
Definition sp-gradient.h:86
SPGradient * getVector(bool force_private=false)
Returns private vector of given gradient (the gradient at the end of the href chain which has stops),...
Paint type internal to SPStyle.
bool isPaintserver() const
bool isColor() const
bool isNone() const
bool isNoneSet() const
Colors::Color const & getColor() const
String type internal to SPStyle.
char const * value() const
Get value if set, or inherited value, or default value (may be NULL)
Base class for visual SVG elements.
Definition sp-item.h:109
bool raiseOne()
Definition sp-item.cpp:429
void set_i2d_affine(Geom::Affine const &transform)
Definition sp-item.cpp:1827
Geom::Affine i2dt_affine() const
Returns the transformation from item to desktop coords.
Definition sp-item.cpp:1821
Geom::OptRect documentVisualBounds() const
Get item's visual bbox in document coordinate system.
Definition sp-item.cpp:1018
void set(SPAttr key, char const *value) override
Definition sp-item.cpp:559
Geom::Point getCenter(bool ensure_uptodate=true) const
Definition sp-item.cpp:377
SPMask * getMaskObject() const
Definition sp-item.cpp:177
bool lowerOne()
Definition sp-item.cpp:439
Geom::Affine transform
Definition sp-item.h:138
void invoke_hide(unsigned int key)
Definition sp-item.cpp:1320
bool isLocked() const
Definition sp-item.cpp:218
Geom::OptRect desktopVisualBounds() const
Get item's visual bbox in desktop coordinate system.
Definition sp-item.cpp:1049
@ VISUAL_BBOX
Definition sp-item.h:118
Geom::Affine i2doc_affine() const
Returns the accumulated transformation of the item and all its ancestors, including root's viewport.
Definition sp-item.cpp:1816
bool isCenterSet() const
Definition sp-item.cpp:372
static unsigned int display_key_new(unsigned numkeys)
Allocates unique integer keys.
Definition sp-item.cpp:1246
virtual void convert_to_guides() const
Definition sp-item.cpp:1880
void doWriteTransform(Geom::Affine const &transform, Geom::Affine const *adv=nullptr, bool compensate=true)
Set a new transform on an object.
Definition sp-item.cpp:1658
void setLocked(bool lock)
Definition sp-item.cpp:228
void setExplicitlyHidden(bool val)
Sets the display CSS property to ‘hidden’ if val is true, otherwise makes it unset.
Definition sp-item.cpp:324
void setCenter(Geom::Point const &object_centre)
Sets the transform_center_x and transform_center_y properties to retain the rotation center.
Definition sp-item.cpp:331
SPClipPath * getClipObject() const
Definition sp-item.cpp:102
static char const * create(std::vector< Inkscape::XML::Node * > &reprs, SPDocument *document)
Definition sp-mask.cpp:193
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
char const * label() const
Gets the author-visible label property for the object or a default if no label is defined.
SPObject * getNext()
void appendChild(Inkscape::XML::Node *child)
void setAttribute(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value)
void removeAttribute(char const *key)
Geom::Point getExportDpi() const
Get and set the exported DPI for this objet, if available.
std::vector< SPObject * > childList(bool add_ref, Action action=ActionGeneral)
Retrieves the children as a std vector object, optionally ref'ing the children in the process,...
void setTmpSuccessor(SPObject *tmpsuccessor)
Indicates that another object supercedes temporaty this one.
SPDocument * document
Definition sp-object.h:188
bool isReferenced()
Check if object is referenced by any other object.
Definition sp-object.h:489
SPObject * firstChild()
Definition sp-object.h:315
char const * getId() const
Returns the objects current ID string.
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
Definition sp-object.h:248
SPObject * getPrev()
Returns previous object in sibling list or NULL.
SPObject * parent
Definition sp-object.h:189
Inkscape::XML::Node * updateRepr(unsigned int flags=SP_OBJECT_WRITE_EXT)
Updates the object's repr based on the object's state.
void readAttr(char const *key)
Read value of key attribute from XML node into object.
std::string generate_unique_id(char const *default_id=nullptr) const
Generate a document-wide unique id for this object.
void deleteObject(bool propagate, bool propagate_descendants)
Deletes an object, unparenting it from its parent.
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
char const * getAttribute(char const *name) const
void fixTmpSuccessors()
Fix temporary successors in duple stamp.
SPObject * appendChildRepr(Inkscape::XML::Node *repr)
Append repr as child of this object.
unsigned int cloned
Definition sp-object.h:180
void addChild(Inkscape::XML::Node *child, Inkscape::XML::Node *prev=nullptr)
void unsetTmpSuccessor()
Unset object supercedes.
ChildrenList children
Definition sp-object.h:907
void requestDisplayUpdate(unsigned int flags)
Queues an deferred update of this object's display.
bool isSwatch() const
static char const * produce(std::vector< Inkscape::XML::Node * > const &reprs, Geom::Rect const &bounds, SPDocument *document, Geom::Affine const &transform, Geom::Affine const &move)
create a new pattern in XML tree
SPPattern const * rootPattern() const
An SVG style object.
Definition style.h:45
SPPaintServer * getFillPaintServer()
Definition style.h:339
T< SPAttr::FILL, SPIPaint > fill
fill
Definition style.h:240
T< SPAttr::STROKE_DASHARRAY, SPIDashArray > stroke_dasharray
stroke-dasharray
Definition style.h:257
T< SPAttr::STROKE, SPIPaint > stroke
stroke
Definition style.h:247
SPPaintServer * getStrokePaintServer()
Definition style.h:343
T< SPAttr::STROKE_WIDTH, SPILength > stroke_width
stroke-width
Definition style.h:249
T< SPAttr::FILL_OPACITY, SPIScale24 > fill_opacity
fill-opacity
Definition style.h:242
SPIPaint * getFillOrStroke(bool fill_)
Get either the fill or the stroke property.
Definition style.h:355
T< SPAttr::STROKE_OPACITY, SPIScale24 > stroke_opacity
stroke-opacity
Definition style.h:261
T< SPAttr::MARKER, SPIString > marker
Marker list.
Definition style.h:266
T< SPAttr::SHAPE_INSIDE, SPIShapes > shape_inside
SVG2 Text Wrapping.
Definition style.h:179
SPIString * marker_ptrs[SP_MARKER_LOC_QTY]
Definition style.h:270
Definition sp-use.h:25
System-wide clipboard management - class declaration.
double inner(valarray< double > const &x, valarray< double > const &y)
RootCluster root
Control point selection - stores a set of control points and applies transformations to them.
std::shared_ptr< Css const > css
Css & result
Glib::ustring msg
double c[8][4]
Geom::Point orig
int objects_query_strokewidth(const std::vector< SPItem * > &objects, SPStyle *style_res)
Write to style_res the average stroke width of a list of objects.
void sp_desktop_apply_css_recursive(SPObject *o, SPCSSAttr *css, bool skip_lines)
Apply style on object and children, recursively.
void sp_desktop_set_style(SPDesktop *desktop, SPCSSAttr *css, bool change, bool write_current, bool switch_style)
Apply style on selection on desktop.
Editable view implementation.
static char const *const current
Definition dir-util.cpp:71
static char const *const parent
Definition dir-util.cpp:70
TODO: insert short description here.
SVG drawing for display.
@ PREFS_MASKOBJECT_GROUPING_ALL
Definition enums.h:113
@ PREFS_MASKOBJECT_GROUPING_SEPARATE
Definition enums.h:112
@ PREFS_MASKOBJECT_GROUPING_NONE
Definition enums.h:111
@ SP_CLONE_COMPENSATION_UNMOVED
Definition enums.h:90
@ SP_CLONE_COMPENSATION_PARALLEL
Definition enums.h:89
PrefsSelectionContext
Definition enums.h:102
@ PREFS_SELECTION_LAYER
Definition enums.h:104
@ PREFS_SELECTION_LAYER_RECURSIVE
Definition enums.h:105
@ PREFS_SELECTION_ALL
Definition enums.h:103
Gradient drawing and editing tool.
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Macro for icon names used in Inkscape.
SPItem * item
std::string original
Inkscape::XML::Node * node
double offset
Glib::ustring label
Geom::Point end
Raw stack of active status messages.
Multi path manipulator - a tool component that edits multiple paths at once.
Affine identity()
Create an identity matrix.
Definition affine.h:210
static R & release(R &r)
Decrements the reference count of a anchored object.
void invert(SPDesktop *desktop)
void selectSameFillStroke(SPDesktop *dt)
void selectSameStrokeColor(SPDesktop *dt)
void selectNone(SPDesktop *desktop)
void invertAllInAll(SPDesktop *desktop)
void selectAll(SPDesktop *desktop)
void selectSameObjectType(SPDesktop *dt)
void fixSelection(SPDesktop *desktop)
void selectAllInAll(SPDesktop *desktop)
void selectSameStrokeStyle(SPDesktop *dt)
void selectSameFillColor(SPDesktop *dt)
bool cc_item_is_connector(SPItem *item)
Helper class to stream background task notifications as a series of messages.
SPObject * next_layer(SPObject *root, SPObject *layer)
Finds the next layer under root, relative to layer in depth-first order.
ObjectSet::SPItemRange SPItemRange
Definition object-set.h:545
SPObject * previous_layer(SPObject *root, SPObject *layer)
Finds the previous layer under root, relative to layer in depth-first order.
std::pair< char const *, char const * > getHrefAttribute(XML::Node const &node)
Get the 'href' or 'xlink:href' (fallback) attribute from an XML node.
MessageType
A hint about the meaning of a message; is it an ordinary message, a message advising the user of some...
Definition message.h:25
@ ERROR_MESSAGE
Definition message.h:29
@ IMMEDIATE_MESSAGE
Definition message.h:27
@ WARNING_MESSAGE
Definition message.h:28
New node tool with support for multiple path editing.
static T clip(T const &v, T const &a, T const &b)
SiblingState
SiblingState enums are used to associate the current state while grabbing objects.
Definition object-set.h:45
@ SIBLING_CLONE_ORIGINAL
@ SIBLING_TEXT_SHAPE_INSIDE
@ SIBLING_TEXT_FLOW_FRAME
Path manipulator - a component that edits a single path on-canvas.
Inkscape::Pixbuf * sp_generate_internal_bitmap(SPDocument *document, Geom::Rect const &area, double dpi, std::vector< SPItem const * > items, bool opaque, uint32_t const *checkerboard_color, double device_scale, std::optional< Antialiasing > antialias)
Generates a bitmap from given items.
GLsync sync
Ocnode * child[8]
Definition quantize.cpp:33
Ocnode ** ref
Definition quantize.cpp:32
TODO: insert short description here.
SPCSSAttr * sp_repr_css_attr_new()
Creates an empty SPCSSAttr (a class for manipulating CSS style properties).
Definition repr-css.cpp:67
void sp_repr_css_merge(SPCSSAttr *dst, SPCSSAttr *src)
Merges two SPCSSAttr's.
Definition repr-css.cpp:299
SPCSSAttr * sp_repr_css_attr_inherited(Node const *repr, gchar const *attr)
Creates a new SPCSSAttr with one attribute whose value is determined by cascading.
Definition repr-css.cpp:116
void sp_repr_css_change(Node *repr, SPCSSAttr *css, gchar const *attr)
Creates a new SPCSAttr with the values filled from a repr, merges in properties from the given SPCSAt...
Definition repr-css.cpp:358
void sp_repr_css_set(Node *repr, SPCSSAttr *css, gchar const *attr)
Sets an attribute (e.g.
Definition repr-css.cpp:265
void sp_repr_css_set_property_double(SPCSSAttr *css, gchar const *name, double value)
Set a style property to a new float value (e.g.
Definition repr-css.cpp:224
void sp_repr_css_attr_unref(SPCSSAttr *css)
Unreferences an SPCSSAttr (will be garbage collected if no references remain).
Definition repr-css.cpp:76
void sp_repr_css_set_property_string(SPCSSAttr *css, char const *name, std::string const &value)
Set a style property to a standard string.
Definition repr-css.cpp:235
void sp_repr_css_unset_property(SPCSSAttr *css, gchar const *name)
Set a style property to "inkscape:unset".
Definition repr-css.cpp:202
void sp_repr_css_set_property(SPCSSAttr *css, gchar const *name, gchar const *value)
Set a style property to a new value (e.g.
Definition repr-css.cpp:191
bool sp_repr_compare_position_bool(Inkscape::XML::Node const *first, Inkscape::XML::Node const *second)
bool sp_repr_is_def(Inkscape::XML::Node const *node)
Definition repr.h:173
void sp_repr_unparent(Inkscape::XML::Node *repr)
Remove repr from children of its parent node.
Definition repr.h:107
bool sp_repr_is_layer(Inkscape::XML::Node const *node)
Definition repr.h:179
GList * items
auto len
Definition safe-printf.h:21
void sp_edit_invert_in_all_layers(SPDesktop *desktop)
std::vector< SPItem * > sp_get_same_object_type(SPItem *sel, std::vector< SPItem * > &src)
static void sp_selection_to_guides_recursive(SPItem *item, bool wholegroups)
SPItem * next_item(SPDesktop *desktop, std::vector< SPObject * > &path, SPObject *root, bool only_in_viewport, PrefsSelectionContext inlayer, bool onlyvisible, bool onlysensitive)
static void unhide(SPItem *item, SPDesktop *desktop)
std::vector< SPItem * > sp_get_same_style(SPItem *sel, std::vector< SPItem * > &src, SPSelectStrokeStyleType type)
static void sp_selection_copy_impl(std::vector< SPItem * > const &items, std::vector< Inkscape::XML::Node * > &clip, Inkscape::XML::Document *xml_doc)
static SPObject * prev_sibling(SPObject *child)
SPCycleType SP_CYCLING
void sp_selection_change_layer_maintain_clones(std::vector< SPItem * > const &items, SPObject *where)
Ensures that the clones of objects are not modified when moving objects between layers.
void sp_edit_invert(SPDesktop *desktop)
static void sp_selection_delete_impl(std::vector< SPItem * > const &items, bool propagate=true, bool propagate_descendants=true)
static bool item_type_match(SPItem *i, SPItem *j)
static void unlock(SPItem *item, SPDesktop *)
void sp_selection_paste(SPDesktop *desktop, bool in_place, bool on_page)
static bool object_set_contains_both_clone_and_original(ObjectSet *set)
static void sp_edit_select_all_full(SPDesktop *dt, bool force_all_layers, bool invert)
static void selection_display_message(SPDesktop *desktop, Inkscape::MessageType msgType, Glib::ustring const &msg)
static Geom::OptRect enclose_items(std::vector< SPItem * > const &items)
Finds out the minimum common bbox of the selected items.
std::vector< SPItem * > sp_get_same_fill_or_stroke_color(SPItem *sel, std::vector< SPItem * > &src, SPSelectStrokeStyleType type)
std::vector< SPItem * > get_all_items(SPObject *from, SPDesktop *desktop, bool onlyvisible, bool onlysensitive, bool ingroups, std::vector< SPItem * > const &exclude)
void sp_selection_item_next(SPDesktop *desktop)
void scroll_to_show_item(SPDesktop *desktop, SPItem *item)
If item is not entirely visible then adjust visible area to centre on the centre on of item.
void sp_edit_select_all_in_all_layers(SPDesktop *desktop)
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)
Copies repr and its inherited css style elements, along with the accumulated transform 'full_t',...
void sp_edit_clear_all(Inkscape::Selection *selection)
SPItem * next_item_from_list(SPDesktop *desktop, std::vector< SPItem * > const &items, SPObject *root, bool only_in_viewport, PrefsSelectionContext inlayer, bool onlyvisible, bool onlysensitive)
SPCSSAttr * take_style_from_item(SPObject *object)
void sp_selection_item_prev(SPDesktop *desktop)
void unlock_all(SPDesktop *dt)
static void add_ids_recursive(std::vector< const gchar * > &ids, SPObject *obj)
void unlock_all_in_all_layers(SPDesktop *dt)
void sp_edit_select_all(SPDesktop *desktop)
void sp_selection_next_patheffect_param(SPDesktop *dt)
void unhide_all_in_all_layers(SPDesktop *dt)
static SPGroup * sp_item_list_common_parent_group(const SPItemRange &items)
If items in the list have a common parent, return it, otherwise return NULL.
static void ungroup_impl(ObjectSet *set)
Ungroup all groups in an object set.
bool fit_canvas_to_drawing(SPDocument *doc, bool with_margins)
static bool object_set_contains_original(SPItem *item, ObjectSet *set)
void unhide_all(SPDesktop *dt)
static void process_all(void(*f)(SPItem *, SPDesktop *), SPDesktop *dt, bool layer_only)
void sp_select_same_object_type(SPDesktop *desktop)
static void sp_selection_remove_livepatheffect_impl(SPItem *item)
static SPUse * find_clone_to_group(Objects const &objects, std::set< SPGroup * > const &groups)
Finds the first clone in objects which references an item in groups.
void sp_select_same_fill_stroke_style(SPDesktop *desktop, gboolean fill, gboolean stroke, gboolean style)
static std::vector< Inkscape::XML::Node * > sp_selection_paste_impl(SPDocument *doc, SPObject *parent, std::vector< Inkscape::XML::Node * > &clip, Inkscape::XML::Node *after=nullptr)
static void itemtree_map(void(*f)(SPItem *, SPDesktop *), SPObject *root, SPDesktop *desktop)
@ SP_CYCLE_VISIBLE
@ SP_CYCLE_FOCUS
SPSelectStrokeStyleType
@ SP_STROKE_STYLE_MARKERS
@ SP_STROKE_STYLE_DASHES
@ SP_STROKE_STYLE_WIDTH
@ SP_STROKE_COLOR
@ SP_STROKE_STYLE_ALL
@ SP_FILL_COLOR
@ SP_STYLE_ALL
void sp_conn_end_detach(SPObject *const owner, unsigned const handle_ix)
TODO: insert short description here.
TODO: insert short description here.
TODO: insert short description here.
void sp_embed_image(Inkscape::XML::Node *image_node, Inkscape::Pixbuf *pb)
Definition sp-image.cpp:716
SVG <image> implementation.
void sp_item_group_ungroup_handle_clones(SPItem *parent, Geom::Affine const g)
finds clones of a child of the group going out of the group; and inverse the group transform on its c...
void sp_item_group_ungroup(SPGroup *group, std::vector< SPItem * > &children)
bool SP_IS_LAYER(SPObject const *obj)
TODO: insert short description here.
Some things pertinent to all visible shapes: SPItem, SPItemView, SPItemCtx.
bool sp_item_repr_compare_position_bool(SPObject const *first, SPObject const *second)
Definition sp-item.h:473
TODO: insert short description here.
void sp_lpe_item_update_patheffect(SPLPEItem *lpeitem, bool wholetree, bool write, bool with_satellites)
Calls any registered handlers for the update_patheffect action.
const gchar * generate_marker(std::vector< Inkscape::XML::Node * > &reprs, Geom::Rect bounds, SPDocument *document, Geom::Point center, Geom::Affine move)
void invert(const double v[16], double alpha[16])
bool sp_object_compare_position_bool(SPObject const *first, SPObject const *second)
SPObject * sp_object_unref(SPObject *object, SPObject *owner)
Decrease reference count of object, with possible debugging and finalization.
SPObject * sp_object_ref(SPObject *object, SPObject *owner)
Increase reference count of object, with possible debugging.
SPItem * sp_offset_get_source(SPOffset *offset)
SVG <pattern> implementation.
TODO: insert short description here.
TODO: insert short description here.
SPRoot: SVG <svg> implementation.
TODO: insert short description here.
SPItem * sp_textpath_get_path_item(SPTextPath const *tp)
Definition sp-tspan.cpp:463
SPObject * sp_tref_convert_to_tspan(SPObject *obj)
This function will create a new tspan element with the same attributes as the tref had and add the sa...
Definition sp-tref.cpp:433
SVG <tref> implementation, see sp-tref.cpp.
TODO: insert short description here.
Interface for XML documents.
Definition document.h:43
virtual Node * createTextNode(char const *content)=0
virtual Node * createElement(char const *name)=0
Definition curve.h:24
@ SP_WIND_RULE_EVENODD
Definition style-enums.h:26
static const unsigned SP_STYLE_FLAG_IFSET(1<< 0)
SPCSSAttr * sp_css_attr_unset_blacklist(SPCSSAttr *css)
Unset properties that should not be set for default tool style.
Definition style.cpp:1498
SPCSSAttr * sp_css_attr_from_object(SPObject *object, guint const flags)
Definition style.cpp:1427
SPCSSAttr * sp_css_attr_unset_text(SPCSSAttr *css)
Unset any text-related properties.
Definition style.cpp:1444
SPCSSAttr * sp_css_attr_scale(SPCSSAttr *css, double ex)
Scale any properties that may hold <length> by ex.
Definition style.cpp:1625
SPStyle - a style object for SPItem objects.
bool sp_svg_transform_read(gchar const *str, Geom::Affine *transform)
std::string sp_svg_transform_write(Geom::Affine const &transform)
static void sp_svg_write_path(Inkscape::SVG::PathString &str, Geom::Path const &p, bool normalize=false)
Definition svg-path.cpp:109
SPDesktop * desktop
void text_relink_refs(text_refs_t const &refs, InIterOrig origBegin, InIterOrig origEnd, InIterCopy copyBegin)
text_refs_t text_categorize_refs(SPDocument *doc, InIter begin, InIter end, text_ref_t which)
text_ref_t
@ TEXT_REF_DEF
@ TEXT_REF_EXTERNAL
@ TEXT_REF_INTERNAL
TextTool.
Glib::ustring name
Definition toolbars.cpp:55