1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * SPDocument manipulation
4 *
5 * Authors:
6 * Lauris Kaplinski <lauris@kaplinski.com>
7 * MenTaLguY <mental@rydia.net>
8 * bulia byak <buliabyak@users.sf.net>
9 * Jon A. Cruz <jon@joncruz.org>
10 * Abhishek Sharma
11 * Tavmjong Bah <tavmjong@free.fr>
12 *
13 * Copyright (C) 2004-2005 MenTaLguY
14 * Copyright (C) 1999-2002 Lauris Kaplinski
15 * Copyright (C) 2000-2001 Ximian, Inc.
16 * Copyright (C) 2012 Tavmjong Bah
17 *
18 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
19 */
20
21 /** \class SPDocument
22 * SPDocument serves as the container of both model trees (agnostic XML
23 * and typed object tree), and implements all of the document-level
24 * functionality used by the program. Many document level operations, like
25 * load, save, print, export and so on, use SPDocument as their basic datatype.
26 *
27 * SPDocument implements undo and redo stacks and an id-based object
28 * dictionary. Thanks to unique id attributes, the latter can be used to
29 * map from the XML tree back to the object tree.
30 *
31 * SPDocument performs the basic operations needed for asynchronous
32 * update notification (SPObject ::modified virtual method), and implements
33 * the 'modified' signal, as well.
34 */
35
36
37 #define noSP_DOCUMENT_DEBUG_IDLE
38 #define noSP_DOCUMENT_DEBUG_UNDO
39
40 #include <vector>
41 #include <string>
42 #include <cstring>
43
44 #include <2geom/transforms.h>
45
46 #include "desktop.h"
47 #include "io/dir-util.h"
48 #include "document-undo.h"
49 #include "file.h"
50 #include "id-clash.h"
51 #include "inkscape-version.h"
52 #include "inkscape.h"
53 #include "inkscape-window.h"
54 #include "profile-manager.h"
55 #include "rdf.h"
56
57 #include "actions/actions-canvas-snapping.h"
58
59 #include "display/drawing.h"
60
61 #include "3rdparty/adaptagrams/libavoid/router.h"
62
63 #include "3rdparty/libcroco/cr-parser.h"
64 #include "3rdparty/libcroco/cr-sel-eng.h"
65 #include "3rdparty/libcroco/cr-selector.h"
66
67 #include "live_effects/lpeobject.h"
68 #include "object/persp3d.h"
69 #include "object/sp-defs.h"
70 #include "object/sp-factory.h"
71 #include "object/sp-root.h"
72 #include "object/sp-symbol.h"
73
74 #include "widgets/desktop-widget.h"
75
76 #include "xml/croco-node-iface.h"
77 #include "xml/rebase-hrefs.h"
78 #include "xml/simple-document.h"
79
80 using Inkscape::DocumentUndo;
81 using Inkscape::Util::unit_table;
82
83 // Higher number means lower priority.
84 #define SP_DOCUMENT_UPDATE_PRIORITY (G_PRIORITY_HIGH_IDLE - 2)
85
86 // Should have a lower priority than SP_DOCUMENT_UPDATE_PRIORITY,
87 // since we want it to happen when there are no more updates.
88 #define SP_DOCUMENT_REROUTING_PRIORITY (G_PRIORITY_HIGH_IDLE - 1)
89
90 bool sp_no_convert_text_baseline_spacing = false;
91
92 //gboolean sp_document_resource_list_free(gpointer key, gpointer value, gpointer data);
93
94 static gint doc_count = 0;
95 static gint doc_mem_count = 0;
96
97 static unsigned long next_serial = 0;
98
SPDocument()99 SPDocument::SPDocument() :
100 keepalive(false),
101 virgin(true),
102 rdoc(nullptr),
103 rroot(nullptr),
104 root(nullptr),
105 style_cascade(cr_cascade_new(nullptr, nullptr, nullptr)),
106 document_uri(nullptr),
107 document_base(nullptr),
108 document_name(nullptr),
109 actionkey(),
110 profileManager(nullptr), // deferred until after other initialization
111 router(new Avoid::Router(Avoid::PolyLineRouting|Avoid::OrthogonalRouting)),
112 oldSignalsConnected(false),
113 current_persp3d(nullptr),
114 current_persp3d_impl(nullptr),
115 _parent_document(nullptr),
116 _node_cache_valid(false),
117 _activexmltree(nullptr)
118 {
119 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
120
121 if (!prefs->getBool("/options/yaxisdown", true)) {
122 _doc2dt[3] = -1;
123 }
124
125 // Penalise libavoid for choosing paths with needless extra segments.
126 // This results in much better looking orthogonal connector paths.
127 router->setRoutingPenalty(Avoid::segmentPenalty);
128
129 _serial = next_serial++;
130
131 sensitive = false;
132 partial = nullptr;
133 history_size = 0;
134 seeking = false;
135
136 // Once things are set, hook in the manager
137 profileManager = new Inkscape::ProfileManager(this);
138
139 // XXX only for testing!
140 undoStackObservers.add(console_output_undo_observer);
141 _node_cache = std::deque<SPItem*>();
142
143 // Actions
144 action_group = Gio::SimpleActionGroup::create();
145 add_actions_canvas_snapping(this);
146 }
147
~SPDocument()148 SPDocument::~SPDocument() {
149 destroySignal.emit();
150
151 // kill/unhook this first
152 if ( profileManager ) {
153 delete profileManager;
154 profileManager = nullptr;
155 }
156
157 if (router) {
158 delete router;
159 router = nullptr;
160 }
161
162 if (oldSignalsConnected) {
163 selChangeConnection.disconnect();
164 desktopActivatedConnection.disconnect();
165 } else {
166 _selection_changed_connection.disconnect();
167 _desktop_activated_connection.disconnect();
168 }
169
170 if (partial) {
171 sp_repr_free_log(partial);
172 partial = nullptr;
173 }
174
175 DocumentUndo::clearRedo(this);
176 DocumentUndo::clearUndo(this);
177
178 if (root) {
179 root->releaseReferences();
180 sp_object_unref(root);
181 root = nullptr;
182 }
183
184 if (rdoc) Inkscape::GC::release(rdoc);
185
186 /* Free resources */
187 resources.clear();
188
189 // This also destroys all attached stylesheets
190 cr_cascade_unref(style_cascade);
191 style_cascade = nullptr;
192
193 if (document_name) {
194 g_free(document_name);
195 document_name = nullptr;
196 }
197 if (document_base) {
198 g_free(document_base);
199 document_base = nullptr;
200 }
201 if (document_uri) {
202 g_free(document_uri);
203 document_uri = nullptr;
204 }
205
206 modified_connection.disconnect();
207 rerouting_connection.disconnect();
208
209 if (keepalive) {
210 inkscape_unref(INKSCAPE);
211 keepalive = false;
212 }
213
214 if (this->current_persp3d_impl)
215 delete this->current_persp3d_impl;
216 this->current_persp3d_impl = nullptr;
217
218 // This is at the end of the destructor, because preceding code adds new orphans to the queue
219 collectOrphans();
220 }
221
getReprNamedView()222 Inkscape::XML::Node *SPDocument::getReprNamedView()
223 {
224 return sp_repr_lookup_name (rroot, "sodipodi:namedview");
225 }
226
227 /**
228 * Get the namedview for this document, creates it if it's not found.
229 *
230 * @returns SPNamedView object, existing or created.
231 */
getNamedView()232 SPNamedView *SPDocument::getNamedView()
233 {
234 auto xml = getReprNamedView();
235 if (!xml) {
236 xml = rdoc->createElement("sodipodi:namedview");
237 rroot->addChildAtPos(xml, 0);
238 }
239 return dynamic_cast<SPNamedView *> (getObjectByRepr(xml));
240 }
241
getDefs()242 SPDefs *SPDocument::getDefs()
243 {
244 if (!root) {
245 return nullptr;
246 }
247 return root->defs;
248 }
249
getCurrentPersp3D()250 Persp3D *SPDocument::getCurrentPersp3D() {
251 // Check if current_persp3d is still valid
252 std::vector<Persp3D*> plist;
253 getPerspectivesInDefs(plist);
254 for (auto & i : plist) {
255 if (current_persp3d == i)
256 return current_persp3d;
257 }
258
259 // If not, return the first perspective in defs (which may be NULL of none exists)
260 current_persp3d = Persp3D::document_first_persp (this);
261
262 return current_persp3d;
263 }
264
setCurrentPersp3D(Persp3D * const persp)265 void SPDocument::setCurrentPersp3D(Persp3D * const persp) {
266 current_persp3d = persp;
267 //current_persp3d_impl = persp->perspective_impl;
268 }
269
getPerspectivesInDefs(std::vector<Persp3D * > & list) const270 void SPDocument::getPerspectivesInDefs(std::vector<Persp3D*> &list) const
271 {
272 for (auto& i: root->defs->children) {
273 if (SP_IS_PERSP3D(&i)) {
274 list.push_back(SP_PERSP3D(&i));
275 }
276 }
277 }
278
279 /**
280 void SPDocument::initialize_current_persp3d()
281 {
282 this->current_persp3d = Persp3D::document_first_persp(this);
283 if (!this->current_persp3d) {
284 this->current_persp3d = Persp3D::create_xml_element(this);
285 }
286 }
287 **/
288
queueForOrphanCollection(SPObject * object)289 void SPDocument::queueForOrphanCollection(SPObject *object) {
290 g_return_if_fail(object != nullptr);
291 g_return_if_fail(object->document == this);
292
293 sp_object_ref(object, nullptr);
294 _collection_queue.push_back(object);
295 }
296
collectOrphans()297 void SPDocument::collectOrphans() {
298 while (!_collection_queue.empty()) {
299 std::vector<SPObject *> objects(_collection_queue);
300 _collection_queue.clear();
301 for (auto object : objects) {
302 object->collectOrphan();
303 sp_object_unref(object, nullptr);
304 }
305 }
306 }
307
createDoc(Inkscape::XML::Document * rdoc,gchar const * document_uri,gchar const * document_base,gchar const * document_name,bool keepalive,SPDocument * parent)308 SPDocument *SPDocument::createDoc(Inkscape::XML::Document *rdoc,
309 gchar const *document_uri,
310 gchar const *document_base,
311 gchar const *document_name,
312 bool keepalive,
313 SPDocument *parent)
314 {
315 SPDocument *document = new SPDocument();
316
317 Inkscape::XML::Node *rroot = rdoc->root();
318
319 document->keepalive = keepalive;
320
321 document->rdoc = rdoc;
322 document->rroot = rroot;
323 if (parent) {
324 document->_parent_document = parent;
325 parent->_child_documents.push_back(document);
326 }
327
328 if (document->document_uri){
329 g_free(document->document_uri);
330 document->document_uri = nullptr;
331 }
332 if (document->document_base){
333 g_free(document->document_base);
334 document->document_base = nullptr;
335 }
336 if (document->document_name){
337 g_free(document->document_name);
338 document->document_name = nullptr;
339 }
340 #ifndef _WIN32
341 document->document_uri = prepend_current_dir_if_relative(document_uri);
342 #else
343 // FIXME: it may be that prepend_current_dir_if_relative works OK on windows too, test!
344 document->document_uri = document_uri? g_strdup(document_uri) : NULL;
345 #endif
346
347 // base is simply the part of the path before filename; e.g. when running "inkscape ../file.svg" the base is "../"
348 // which is why we use g_get_current_dir() in calculating the abs path above
349 //This is NULL for a new document
350 if (document_base) {
351 document->document_base = g_strdup(document_base);
352 } else {
353 document->document_base = nullptr;
354 }
355 document->document_name = g_strdup(document_name);
356
357 // Create SPRoot element
358 const std::string typeString = NodeTraits::get_type_string(*rroot);
359 SPObject* rootObj = SPFactory::createObject(typeString);
360 document->root = dynamic_cast<SPRoot*>(rootObj);
361
362 if (document->root == nullptr) {
363 // Node is not a valid root element
364 delete rootObj;
365
366 // fixme: what to do here?
367 throw;
368 }
369
370 // Recursively build object tree
371 document->root->invoke_build(document, rroot, false);
372
373 /* Eliminate obsolete sodipodi:docbase, for privacy reasons */
374 rroot->removeAttribute("sodipodi:docbase");
375
376 /* Eliminate any claim to adhere to a profile, as we don't try to */
377 rroot->removeAttribute("baseProfile");
378
379 // loading or creating namedview.
380 auto nv = document->getNamedView();
381
382 // Set each of the defaults in new or existing namedview (allows for per-attr overriding)
383 nv->setDefaultAttribute("pagecolor", "/template/base/pagecolor", "");
384 nv->setDefaultAttribute("bordercolor", "/template/base/bordercolor", "");
385 nv->setDefaultAttribute("borderopacity", "/template/base/borderopacity", "");
386 nv->setDefaultAttribute("inkscape:pageshadow", "/template/base/pageshadow", "2");
387 nv->setDefaultAttribute("inkscape:pageopacity", "/template/base/pageopacity", "0.0");
388 nv->setDefaultAttribute("inkscape:pagecheckerboard", "/template/base/pagecheckerboard", "0");
389
390 // If no units are set in the document, try and guess them from the width/height
391 if (document->root->width.isAbsolute()) {
392 nv->setDefaultAttribute("inkscape:document-units", "", document->root->width.getUnit());
393 } else if (document->root->height.isAbsolute()) {
394 nv->setDefaultAttribute("inkscape:document-units", "", document->root->height.getUnit());
395 }
396
397 // Defs
398 if (!document->root->defs) {
399 Inkscape::XML::Node *r = rdoc->createElement("svg:defs");
400 rroot->addChild(r, nullptr);
401 Inkscape::GC::release(r);
402 g_assert(document->root->defs);
403 }
404
405 /* Default RDF */
406 rdf_set_defaults( document );
407
408 if (keepalive) {
409 inkscape_ref(INKSCAPE);
410 }
411
412 // Check if the document already has a perspective (e.g., when opening an existing
413 // document). If not, create a new one and set it as the current perspective.
414 document->setCurrentPersp3D(Persp3D::document_first_persp(document));
415 if (!document->getCurrentPersp3D()) {
416 //document->setCurrentPersp3D(Persp3D::create_xml_element (document));
417 Persp3DImpl *persp_impl = new Persp3DImpl();
418 document->setCurrentPersp3DImpl(persp_impl);
419 }
420
421 DocumentUndo::setUndoSensitive(document, true);
422
423 // reset undo key when selection changes, so that same-key actions on different objects are not coalesced
424 document->selChangeConnection = INKSCAPE.signal_selection_changed.connect(
425 sigc::hide(sigc::bind(
426 sigc::ptr_fun(&DocumentUndo::resetKey), document)
427 ));
428 document->desktopActivatedConnection = INKSCAPE.signal_activate_desktop.connect(
429 sigc::hide(sigc::bind(
430 sigc::ptr_fun(&DocumentUndo::resetKey), document)
431 ));
432 document->oldSignalsConnected = true;
433
434
435 // ************* Fix Document **************
436 // Move to separate function?
437
438 /** Fix OSB **/
439 sp_file_fix_osb(document->getRoot());
440
441
442 /** Fix baseline spacing (pre-92 files) **/
443 if ( (!sp_no_convert_text_baseline_spacing)
444 && sp_version_inside_range( document->root->version.inkscape, 0, 1, 0, 92 ) ) {
445 sp_file_convert_text_baseline_spacing(document);
446 }
447
448 /** Fix font names in legacy documents (pre-92 files) **/
449 if ( sp_version_inside_range( document->root->version.inkscape, 0, 1, 0, 92 ) ) {
450 sp_file_convert_font_name(document);
451 }
452
453 /** Fix first line spacing in legacy documents (pre-1.0 files) **/
454 if (sp_version_inside_range(document->root->version.inkscape, 0, 1, 1, 0)) {
455 sp_file_fix_empty_lines(document);
456 }
457
458 /** Fix dpi (pre-92 files). With GUI fixed in Inkscape::Application::fix_document. **/
459 if ( !(INKSCAPE.use_gui()) && sp_version_inside_range( document->root->version.inkscape, 0, 1, 0, 92 ) ) {
460 sp_file_convert_dpi(document);
461 }
462
463 // Update LPE's See: Related bug:#1769679 #18
464 SPDefs * defs = document->getDefs();
465 if (defs) {
466 defs->emitModified(SP_OBJECT_MODIFIED_CASCADE);
467 }
468
469 // Update document level action settings
470 set_actions_canvas_snapping(document);
471
472 return document;
473 }
474
475 /**
476 * Create a copy of the document, useful for modifying during save & export.
477 */
copy() const478 std::unique_ptr<SPDocument> SPDocument::copy() const
479 {
480 // New SimpleDocument object where we will put all the same data
481 Inkscape::XML::Document *new_rdoc = new Inkscape::XML::SimpleDocument();
482
483 // Duplicate the svg root node AND any PI and COMMENT nodes, this should be put
484 // into xml/simple-document.h at some point to fix it's duplicate implementation.
485 for (Inkscape::XML::Node *child = rdoc->firstChild(); child; child = child->next()) {
486 if (child) {
487 // Get a new xml repr for the svg root node
488 Inkscape::XML::Node *new_child = child->duplicate(new_rdoc);
489
490 // Add the duplicated svg node as the document's rdoc
491 new_rdoc->appendChild(new_child);
492 Inkscape::GC::release(new_child);
493 }
494 }
495
496 auto doc = createDoc(new_rdoc, document_uri, document_base, document_name, keepalive, nullptr);
497 doc->_original_document = this->doRef();
498 Inkscape::GC::release(new_rdoc);
499
500 return std::unique_ptr<SPDocument>(doc);
501 }
502
503 /**
504 * Fetches a document and attaches it to the current document as a child href
505 */
createChildDoc(std::string const & document_uri)506 SPDocument *SPDocument::createChildDoc(std::string const &document_uri)
507 {
508 SPDocument *parent = this;
509 SPDocument *document = nullptr;
510
511 while(parent != nullptr && parent->getDocumentURI() != nullptr && document == nullptr) {
512 // Check myself and any parents in the chain
513 if(document_uri == parent->getDocumentURI()) {
514 document = parent;
515 break;
516 }
517 // Then check children of those.
518 boost::ptr_list<SPDocument>::iterator iter;
519 for (iter = parent->_child_documents.begin();
520 iter != parent->_child_documents.end(); ++iter) {
521 if(document_uri == iter->getDocumentURI()) {
522 document = &*iter;
523 break;
524 }
525 }
526 parent = parent->_parent_document;
527 }
528
529 // Load a fresh document from the svg source.
530 if(!document) {
531 std::string path;
532 if (Glib::path_is_absolute(document_uri)) {
533 path = document_uri;
534 } else {
535 path = this->getDocumentBase() + document_uri;
536 }
537 document = createNewDoc(path.c_str(), false, false, this);
538 }
539 return document;
540 }
541 /**
542 * Fetches document from URI, or creates new, if NULL; public document
543 * appears in document list.
544 */
createNewDoc(gchar const * document_uri,bool keepalive,bool make_new,SPDocument * parent)545 SPDocument *SPDocument::createNewDoc(gchar const *document_uri, bool keepalive, bool make_new, SPDocument *parent)
546 {
547 Inkscape::XML::Document *rdoc = nullptr;
548 gchar *document_base = nullptr;
549 gchar *document_name = nullptr;
550
551 if (document_uri) {
552 Inkscape::XML::Node *rroot;
553 /* Try to fetch repr from file */
554 rdoc = sp_repr_read_file(document_uri, SP_SVG_NS_URI);
555 /* If file cannot be loaded, return NULL without warning */
556 if (rdoc == nullptr) return nullptr;
557 rroot = rdoc->root();
558 /* If xml file is not svg, return NULL without warning */
559 /* fixme: destroy document */
560 if (strcmp(rroot->name(), "svg:svg") != 0) return nullptr;
561
562 // Opening a template that points to a sister file should still work
563 // this also includes tutorials which point to png files.
564 document_base = g_path_get_dirname(document_uri);
565
566 if (make_new) {
567 document_uri = nullptr;
568 document_name = g_strdup_printf(_("New document %d"), ++doc_count);
569 } else {
570 document_name = g_path_get_basename(document_uri);
571 if (strcmp(document_base, ".") == 0) {
572 g_free(document_base);
573 document_base = nullptr;
574 }
575 }
576 } else {
577 if (make_new) {
578 document_name = g_strdup_printf(_("Memory document %d"), ++doc_mem_count);
579 }
580
581 rdoc = sp_repr_document_new("svg:svg");
582 }
583
584 //# These should be set by now
585 g_assert(document_name);
586
587 SPDocument *doc = createDoc(rdoc, document_uri, document_base, document_name, keepalive, parent);
588
589 g_free(document_base);
590 g_free(document_name);
591
592 return doc;
593 }
594
createNewDocFromMem(gchar const * buffer,gint length,bool keepalive)595 SPDocument *SPDocument::createNewDocFromMem(gchar const *buffer, gint length, bool keepalive)
596 {
597 SPDocument *doc = nullptr;
598
599 Inkscape::XML::Document *rdoc = sp_repr_read_mem(buffer, length, SP_SVG_NS_URI);
600 if ( rdoc ) {
601 // Only continue to create a non-null doc if it could be loaded
602 Inkscape::XML::Node *rroot = rdoc->root();
603 if ( strcmp(rroot->name(), "svg:svg") != 0 ) {
604 // If xml file is not svg, return NULL without warning
605 // TODO fixme: destroy document
606 } else {
607 Glib::ustring document_name = Glib::ustring::compose( _("Memory document %1"), ++doc_mem_count );
608 doc = createDoc(rdoc, nullptr, nullptr, document_name.c_str(), keepalive, nullptr);
609 }
610 }
611
612 return doc;
613 }
614
doRef()615 std::unique_ptr<SPDocument> SPDocument::doRef()
616 {
617 Inkscape::GC::anchor(this);
618 return std::unique_ptr<SPDocument>(this);
619 }
doRef() const620 std::unique_ptr<SPDocument const> SPDocument::doRef() const
621 {
622 return const_cast<SPDocument*>(this)->doRef();
623 }
624
625 /// guaranteed not to return nullptr
getDisplayUnit() const626 Inkscape::Util::Unit const* SPDocument::getDisplayUnit() const
627 {
628 // The document may not have a namedview (cleaned) so don't crash if it doesn't exist.
629 auto nv_repr = sp_repr_lookup_name (rroot, "sodipodi:namedview");
630 if (!nv_repr) {
631 return unit_table.getUnit("px");
632 }
633 auto nv = dynamic_cast<SPNamedView *> (getObjectByRepr(nv_repr));
634 return nv ? nv->getDisplayUnit() : unit_table.getUnit("px");
635 }
636
637 /// Sets document scale (by changing viewBox)
setDocumentScale(double scaleX,double scaleY)638 void SPDocument::setDocumentScale( double scaleX, double scaleY ) {
639
640 root->viewBox = Geom::Rect::from_xywh(
641 root->viewBox.left(),
642 root->viewBox.top(),
643 root->width.computed * scaleX,
644 root->height.computed * scaleY );
645 root->viewBox_set = true;
646 root->updateRepr();
647 }
648
649 /// Sets document scale (by changing viewBox, x and y scaling equal)
setDocumentScale(double scale)650 void SPDocument::setDocumentScale( double scale ) {
651 setDocumentScale( scale, scale );
652 }
653
654 /// Returns document scale as defined by width/height (in pixels) and viewBox (real world to
655 /// user-units).
getDocumentScale() const656 Geom::Scale SPDocument::getDocumentScale() const
657 {
658 Geom::Scale scale;
659 if( root->viewBox_set ) {
660 double scale_x = 1.0;
661 double scale_y = 1.0;
662 if( root->viewBox.width() > 0.0 ) {
663 scale_x = root->width.computed / root->viewBox.width();
664 }
665 if( root->viewBox.height() > 0.0 ) {
666 scale_y = root->height.computed / root->viewBox.height();
667 }
668 scale = Geom::Scale(scale_x, scale_y);
669 }
670 // std::cout << "SPDocument::getDocumentScale():\n" << scale << std::endl;
671 return scale;
672 }
673
674 // Avoid calling root->updateRepr() twice by combining setting width and height.
675 // (As done on every delete as clipboard calls this via fitToRect(). Also called in page-sizer.cpp)
setWidthAndHeight(const Inkscape::Util::Quantity & width,const Inkscape::Util::Quantity & height,bool changeSize)676 void SPDocument::setWidthAndHeight(const Inkscape::Util::Quantity &width, const Inkscape::Util::Quantity &height, bool changeSize)
677 {
678 Inkscape::Util::Unit const *old_width_units = unit_table.getUnit("px");
679 if (root->width.unit)
680 old_width_units = unit_table.getUnit(root->width.unit);
681 gdouble old_width_converted; // old width converted to new units
682 if (root->width.unit == SVGLength::PERCENT)
683 old_width_converted = Inkscape::Util::Quantity::convert(root->width.computed, "px", width.unit);
684 else
685 old_width_converted = Inkscape::Util::Quantity::convert(root->width.value, old_width_units, width.unit);
686
687 root->width.computed = width.value("px");
688 root->width.value = width.quantity;
689 root->width.unit = (SVGLength::Unit) width.unit->svgUnit();
690
691 Inkscape::Util::Unit const *old_height_units = unit_table.getUnit("px");
692 if (root->height.unit)
693 old_height_units = unit_table.getUnit(root->height.unit);
694 gdouble old_height_converted; // old height converted to new units
695 if (root->height.unit == SVGLength::PERCENT)
696 old_height_converted = Inkscape::Util::Quantity::convert(root->height.computed, "px", height.unit);
697 else
698 old_height_converted = Inkscape::Util::Quantity::convert(root->height.value, old_height_units, height.unit);
699
700 root->height.computed = height.value("px");
701 root->height.value = height.quantity;
702 root->height.unit = (SVGLength::Unit) height.unit->svgUnit();
703
704 // viewBox scaled by relative change in page size (maintains document scale).
705 if (root->viewBox_set && changeSize) {
706 root->viewBox.setMax(Geom::Point(
707 root->viewBox.left() + (root->width.value / old_width_converted ) * root->viewBox.width(),
708 root->viewBox.top() + (root->height.value / old_height_converted) * root->viewBox.height()));
709 }
710 root->updateRepr();
711 }
712
getWidth() const713 Inkscape::Util::Quantity SPDocument::getWidth() const
714 {
715 g_return_val_if_fail(this->root != nullptr, Inkscape::Util::Quantity(0.0, unit_table.getUnit("")));
716
717 gdouble result = root->width.value;
718 SVGLength::Unit u = root->width.unit;
719 if (root->width.unit == SVGLength::PERCENT && root->viewBox_set) {
720 result = root->viewBox.width();
721 u = SVGLength::PX;
722 }
723 if (u == SVGLength::NONE) {
724 u = SVGLength::PX;
725 }
726 return Inkscape::Util::Quantity(result, unit_table.getUnit(u));
727 }
728
setWidth(const Inkscape::Util::Quantity & width,bool changeSize)729 void SPDocument::setWidth(const Inkscape::Util::Quantity &width, bool changeSize)
730 {
731 Inkscape::Util::Unit const *old_width_units = unit_table.getUnit("px");
732 if (root->width.unit)
733 old_width_units = unit_table.getUnit(root->width.unit);
734 gdouble old_width_converted; // old width converted to new units
735 if (root->width.unit == SVGLength::PERCENT)
736 old_width_converted = Inkscape::Util::Quantity::convert(root->width.computed, "px", width.unit);
737 else
738 old_width_converted = Inkscape::Util::Quantity::convert(root->width.value, old_width_units, width.unit);
739
740 root->width.computed = width.value("px");
741 root->width.value = width.quantity;
742 root->width.unit = (SVGLength::Unit) width.unit->svgUnit();
743
744 if (root->viewBox_set && changeSize)
745 root->viewBox.setMax(Geom::Point(root->viewBox.left() + (root->width.value / old_width_converted) * root->viewBox.width(), root->viewBox.bottom()));
746
747 root->updateRepr();
748 }
749
750
getHeight() const751 Inkscape::Util::Quantity SPDocument::getHeight() const
752 {
753 g_return_val_if_fail(this->root != nullptr, Inkscape::Util::Quantity(0.0, unit_table.getUnit("")));
754
755 gdouble result = root->height.value;
756 SVGLength::Unit u = root->height.unit;
757 if (root->height.unit == SVGLength::PERCENT && root->viewBox_set) {
758 result = root->viewBox.height();
759 u = SVGLength::PX;
760 }
761 if (u == SVGLength::NONE) {
762 u = SVGLength::PX;
763 }
764 return Inkscape::Util::Quantity(result, unit_table.getUnit(u));
765 }
766
setHeight(const Inkscape::Util::Quantity & height,bool changeSize)767 void SPDocument::setHeight(const Inkscape::Util::Quantity &height, bool changeSize)
768 {
769 Inkscape::Util::Unit const *old_height_units = unit_table.getUnit("px");
770 if (root->height.unit)
771 old_height_units = unit_table.getUnit(root->height.unit);
772 gdouble old_height_converted; // old height converted to new units
773 if (root->height.unit == SVGLength::PERCENT)
774 old_height_converted = Inkscape::Util::Quantity::convert(root->height.computed, "px", height.unit);
775 else
776 old_height_converted = Inkscape::Util::Quantity::convert(root->height.value, old_height_units, height.unit);
777
778 root->height.computed = height.value("px");
779 root->height.value = height.quantity;
780 root->height.unit = (SVGLength::Unit) height.unit->svgUnit();
781
782 if (root->viewBox_set && changeSize)
783 root->viewBox.setMax(Geom::Point(root->viewBox.right(), root->viewBox.top() + (root->height.value / old_height_converted) * root->viewBox.height()));
784
785 root->updateRepr();
786 }
787
doc2dt() const788 const Geom::Affine &SPDocument::doc2dt() const
789 {
790 if (root && !is_yaxisdown()) {
791 _doc2dt[5] = root->height.computed;
792 }
793
794 return _doc2dt;
795 }
796
getViewBox() const797 Geom::Rect SPDocument::getViewBox() const
798 {
799 Geom::Rect viewBox;
800 if (root->viewBox_set) {
801 viewBox = root->viewBox;
802 } else {
803 viewBox = Geom::Rect::from_xywh( 0, 0, getWidth().value("px"), getHeight().value("px"));
804 }
805 return viewBox;
806 }
807
808 /**
809 * Set default viewbox calculated from document properties.
810 */
setViewBox()811 void SPDocument::setViewBox()
812 {
813 setViewBox(Geom::Rect(0,
814 0,
815 getWidth().value(getDisplayUnit()),
816 getHeight().value(getDisplayUnit())));
817 }
818
setViewBox(const Geom::Rect & viewBox)819 void SPDocument::setViewBox(const Geom::Rect &viewBox)
820 {
821 root->viewBox_set = true;
822 root->viewBox = viewBox;
823 root->updateRepr();
824 }
825
getDimensions() const826 Geom::Point SPDocument::getDimensions() const
827 {
828 return Geom::Point(getWidth().value("px"), getHeight().value("px"));
829 }
830
preferredBounds() const831 Geom::OptRect SPDocument::preferredBounds() const
832 {
833 return Geom::OptRect( Geom::Point(0, 0), getDimensions() );
834 }
835
836 /**
837 * Given a Geom::Rect that may, for example, correspond to the bbox of an object,
838 * this function fits the canvas to that rect by resizing the canvas
839 * and translating the document root into position.
840 * \param rect fit document size to this, in document coordinates
841 * \param with_margins add margins to rect, by taking margins from this
842 * document's namedview (<sodipodi:namedview> "fit-margin-..."
843 * attributes, and "units")
844 */
fitToRect(Geom::Rect const & rect,bool with_margins)845 void SPDocument::fitToRect(Geom::Rect const &rect, bool with_margins)
846 {
847 double const w = rect.width();
848 double const h = rect.height();
849
850 Inkscape::Util::Unit const *nv_units = unit_table.getUnit("px");
851 if (root->height.unit && (root->height.unit != SVGLength::PERCENT))
852 nv_units = unit_table.getUnit(root->height.unit);
853 SPNamedView *nv = sp_document_namedview(this, nullptr);
854
855 /* in px */
856 double margin_top = 0.0;
857 double margin_left = 0.0;
858 double margin_right = 0.0;
859 double margin_bottom = 0.0;
860
861 if (with_margins && nv) {
862 if (nv != nullptr) {
863 margin_top = nv->getMarginLength("fit-margin-top", nv_units, unit_table.getUnit("px"), w, h, false);
864 margin_left = nv->getMarginLength("fit-margin-left", nv_units, unit_table.getUnit("px"), w, h, true);
865 margin_right = nv->getMarginLength("fit-margin-right", nv_units, unit_table.getUnit("px"), w, h, true);
866 margin_bottom = nv->getMarginLength("fit-margin-bottom", nv_units, unit_table.getUnit("px"), w, h, false);
867 margin_top = Inkscape::Util::Quantity::convert(margin_top, nv_units, "px");
868 margin_left = Inkscape::Util::Quantity::convert(margin_left, nv_units, "px");
869 margin_right = Inkscape::Util::Quantity::convert(margin_right, nv_units, "px");
870 margin_bottom = Inkscape::Util::Quantity::convert(margin_bottom, nv_units, "px");
871 }
872 }
873
874 double y_dir = yaxisdir();
875
876 Geom::Rect const rect_with_margins(
877 rect.min() - Geom::Point(margin_left, margin_top),
878 rect.max() + Geom::Point(margin_right, margin_bottom));
879
880 // rect in desktop coordinates before changing document dimensions
881 auto rect_with_margins_dt_old = rect_with_margins * doc2dt();
882
883 setWidthAndHeight(
884 Inkscape::Util::Quantity(Inkscape::Util::Quantity::convert(rect_with_margins.width(), "px", nv_units), nv_units),
885 Inkscape::Util::Quantity(Inkscape::Util::Quantity::convert(rect_with_margins.height(), "px", nv_units), nv_units)
886 );
887
888 // rect in desktop coordinates after changing document dimensions
889 auto rect_with_margins_dt_new = rect_with_margins * doc2dt();
890
891 Geom::Translate const tr(-rect_with_margins_dt_new.min());
892 root->translateChildItems(tr);
893
894 if(nv) {
895 Geom::Translate tr2(-rect_with_margins_dt_old.min());
896 nv->translateGuides(tr2);
897 nv->translateGrids(tr2);
898
899 // update the viewport so the drawing appears to stay where it was
900 nv->scrollAllDesktops(-tr2[0], -tr2[1] * y_dir, false);
901 }
902 }
903
setDocumentBase(gchar const * document_base)904 void SPDocument::setDocumentBase( gchar const* document_base )
905 {
906 if (this->document_base) {
907 g_free(this->document_base);
908 this->document_base = nullptr;
909 }
910 if (document_base) {
911 this->document_base = g_strdup(document_base);
912 }
913 }
914
do_change_uri(gchar const * const filename,bool const rebase)915 void SPDocument::do_change_uri(gchar const *const filename, bool const rebase)
916 {
917 gchar *new_document_base = nullptr;
918 gchar *new_document_name = nullptr;
919 gchar *new_document_uri = nullptr;
920 if (filename) {
921
922 #ifndef _WIN32
923 new_document_uri = prepend_current_dir_if_relative(filename);
924 #else
925 // FIXME: it may be that prepend_current_dir_if_relative works OK on windows too, test!
926 new_document_uri = g_strdup(filename);
927 #endif
928
929 new_document_base = g_path_get_dirname(new_document_uri);
930 new_document_name = g_path_get_basename(new_document_uri);
931 } else {
932 new_document_uri = g_strdup_printf(_("Unnamed document %d"), ++doc_count);
933 new_document_base = nullptr;
934 new_document_name = g_strdup(this->document_uri);
935 }
936
937 // Update saveable repr attributes.
938 Inkscape::XML::Node *repr = getReprRoot();
939
940 // Changing uri in the document repr must not be not undoable.
941 bool const saved = DocumentUndo::getUndoSensitive(this);
942 DocumentUndo::setUndoSensitive(this, false);
943
944 if (rebase) {
945 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
946 bool use_sodipodi_absref = prefs->getBool("/options/svgoutput/usesodipodiabsref", false);
947 Inkscape::XML::rebase_hrefs(this, new_document_base, use_sodipodi_absref);
948 }
949
950 if (strncmp(new_document_name, "ink_ext_XXXXXX", 14)) // do not use temporary filenames
951 repr->setAttribute("sodipodi:docname", new_document_name);
952 DocumentUndo::setUndoSensitive(this, saved);
953
954
955 g_free(this->document_name);
956 g_free(this->document_base);
957 g_free(this->document_uri);
958 this->document_name = new_document_name;
959 this->document_base = new_document_base;
960 this->document_uri = new_document_uri;
961
962 this->uri_set_signal.emit(this->document_uri);
963 }
964
965 /**
966 * Sets base, name and uri members of \a document. Doesn't update
967 * any relative hrefs in the document: thus, this is primarily for
968 * newly-created documents.
969 *
970 * \see sp_document_change_uri_and_hrefs
971 */
setDocumentUri(gchar const * filename)972 void SPDocument::setDocumentUri(gchar const *filename)
973 {
974 do_change_uri(filename, false);
975 }
976
977 /**
978 * Changes the base, name and uri members of \a document, and updates any
979 * relative hrefs in the document to be relative to the new base.
980 *
981 * \see sp_document_set_uri
982 */
changeUriAndHrefs(gchar const * filename)983 void SPDocument::changeUriAndHrefs(gchar const *filename)
984 {
985 do_change_uri(filename, true);
986 }
987
bindObjectToId(gchar const * id,SPObject * object)988 void SPDocument::bindObjectToId(gchar const *id, SPObject *object) {
989 GQuark idq = g_quark_from_string(id);
990
991 if (object) {
992 if(object->getId())
993 iddef.erase(object->getId());
994 g_assert(iddef.find(id)==iddef.end());
995 iddef[id] = object;
996 } else {
997 g_assert(iddef.find(id)!=iddef.end());
998 iddef.erase(id);
999 }
1000
1001 SPDocument::IDChangedSignalMap::iterator pos;
1002
1003 pos = id_changed_signals.find(idq);
1004 if ( pos != id_changed_signals.end() ) {
1005 if (!(*pos).second.empty()) {
1006 (*pos).second.emit(object);
1007 } else { // discard unused signal
1008 id_changed_signals.erase(pos);
1009 }
1010 }
1011 }
1012
1013 /**
1014 * Assign IDs to selected objects that don't have an ID attribute
1015 * Checks if the object's id attribute is NULL. If it is, assign it a unique ID
1016 */
enforceObjectIds()1017 void SPDocument::enforceObjectIds()
1018 {
1019 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1020 Inkscape::Selection *selection = desktop->getSelection();
1021 bool showInfoDialog = false;
1022 Glib::ustring msg = _("Selected objects require IDs.\nThe following IDs have been assigned:\n");
1023 auto items = selection->items();
1024 for (auto iter = items.begin(); iter != items.end(); ++iter) {
1025 SPItem *item = *iter;
1026 if(!item->getId())
1027 {
1028 // Selected object does not have an ID, so assign it a unique ID
1029 gchar *id = sp_object_get_unique_id(item, nullptr);
1030 item->setAttribute("id", id);
1031 item->updateRepr();
1032 msg += Glib::ustring::compose(_(" %1\n"), id);
1033 g_free(id);
1034 showInfoDialog = true;
1035 }
1036 }
1037 if(showInfoDialog) {
1038 desktop->showInfoDialog(msg);
1039 setModifiedSinceSave(true);
1040 }
1041 return;
1042 }
1043
getObjectById(Glib::ustring const & id) const1044 SPObject *SPDocument::getObjectById(Glib::ustring const &id) const
1045 {
1046 if (iddef.empty()) {
1047 return nullptr;
1048 }
1049
1050 std::map<std::string, SPObject *>::const_iterator rv = iddef.find(id);
1051 if (rv != iddef.end()) {
1052 return (rv->second);
1053 } else {
1054 return nullptr;
1055 }
1056 }
1057
getObjectById(gchar const * id) const1058 SPObject *SPDocument::getObjectById(gchar const *id) const
1059 {
1060 if (id == nullptr) {
1061 return nullptr;
1062 }
1063
1064 return getObjectById(Glib::ustring(id));
1065 }
1066
_getObjectsByClassRecursive(Glib::ustring const & klass,SPObject * parent,std::vector<SPObject * > & objects)1067 void _getObjectsByClassRecursive(Glib::ustring const &klass, SPObject *parent, std::vector<SPObject *> &objects)
1068 {
1069 if (parent) {
1070 char const *temp = parent->getAttribute("class");
1071 if (temp) {
1072 std::istringstream classes(temp);
1073 Glib::ustring token;
1074 while (classes >> token) {
1075 // we can have multiple class
1076 if (classes.str() == " ") {
1077 token = "";
1078 continue;
1079 }
1080 if (token == klass) {
1081 objects.push_back(parent);
1082 break;
1083 }
1084 }
1085 }
1086
1087 // Check children
1088 for (auto& child : parent->children) {
1089 _getObjectsByClassRecursive( klass, &child, objects );
1090 }
1091 }
1092 }
1093
getObjectsByClass(Glib::ustring const & klass) const1094 std::vector<SPObject *> SPDocument::getObjectsByClass(Glib::ustring const &klass) const
1095 {
1096 std::vector<SPObject *> objects;
1097 g_return_val_if_fail(!klass.empty(), objects);
1098
1099 _getObjectsByClassRecursive(klass, root, objects);
1100 return objects;
1101 }
1102
_getObjectsByElementRecursive(Glib::ustring const & element,SPObject * parent,std::vector<SPObject * > & objects)1103 void _getObjectsByElementRecursive(Glib::ustring const &element, SPObject *parent,
1104 std::vector<SPObject *> &objects)
1105 {
1106 if (parent) {
1107 Glib::ustring prefixed = "svg:" + element;
1108 if (parent->getRepr()->name() == prefixed) {
1109 objects.push_back(parent);
1110 }
1111
1112 // Check children
1113 for (auto& child : parent->children) {
1114 _getObjectsByElementRecursive(element, &child, objects);
1115 }
1116 }
1117 }
1118
getObjectsByElement(Glib::ustring const & element) const1119 std::vector<SPObject *> SPDocument::getObjectsByElement(Glib::ustring const &element) const
1120 {
1121 std::vector<SPObject *> objects;
1122 g_return_val_if_fail(!element.empty(), objects);
1123
1124 _getObjectsByElementRecursive(element, root, objects);
1125 return objects;
1126 }
1127
_getObjectsBySelectorRecursive(SPObject * parent,CRSelEng * sel_eng,CRSimpleSel * simple_sel,std::vector<SPObject * > & objects)1128 void _getObjectsBySelectorRecursive(SPObject *parent,
1129 CRSelEng *sel_eng, CRSimpleSel *simple_sel,
1130 std::vector<SPObject *> &objects)
1131 {
1132 if (parent) {
1133 gboolean result = false;
1134 cr_sel_eng_matches_node( sel_eng, simple_sel, parent->getRepr(), &result );
1135 if (result) {
1136 objects.push_back(parent);
1137 }
1138
1139 // Check children
1140 for (auto& child : parent->children) {
1141 _getObjectsBySelectorRecursive(&child, sel_eng, simple_sel, objects);
1142 }
1143 }
1144 }
1145
getObjectsBySelector(Glib::ustring const & selector) const1146 std::vector<SPObject *> SPDocument::getObjectsBySelector(Glib::ustring const &selector) const
1147 {
1148 // std::cout << "\nSPDocument::getObjectsBySelector: " << selector << std::endl;
1149
1150 std::vector<SPObject *> objects;
1151 g_return_val_if_fail(!selector.empty(), objects);
1152
1153 static CRSelEng *sel_eng = nullptr;
1154 if (!sel_eng) {
1155 sel_eng = cr_sel_eng_new(&Inkscape::XML::croco_node_iface);
1156 }
1157
1158 Glib::ustring my_selector = selector + " {"; // Parsing fails sometimes without '{'. Fix me
1159 CRSelector *cr_selector = cr_selector_parse_from_buf ((guchar*)my_selector.c_str(), CR_UTF_8);
1160 // char * cr_string = (char*)cr_selector_to_string( cr_selector );
1161 // std::cout << " selector: |" << (cr_string?cr_string:"Empty") << "|" << std::endl;
1162 CRSelector const *cur = nullptr;
1163 for (cur = cr_selector; cur; cur = cur->next) {
1164 if (cur->simple_sel ) {
1165 _getObjectsBySelectorRecursive(root, sel_eng, cur->simple_sel, objects);
1166 }
1167 }
1168 return objects;
1169 }
1170
bindObjectToRepr(Inkscape::XML::Node * repr,SPObject * object)1171 void SPDocument::bindObjectToRepr(Inkscape::XML::Node *repr, SPObject *object)
1172 {
1173 if (object) {
1174 g_assert(reprdef.find(repr)==reprdef.end());
1175 reprdef[repr] = object;
1176 } else {
1177 g_assert(reprdef.find(repr)!=reprdef.end());
1178 reprdef.erase(repr);
1179 }
1180 }
1181
getObjectByRepr(Inkscape::XML::Node * repr) const1182 SPObject *SPDocument::getObjectByRepr(Inkscape::XML::Node *repr) const
1183 {
1184 g_return_val_if_fail(repr != nullptr, NULL);
1185 std::map<Inkscape::XML::Node *, SPObject *>::const_iterator rv = reprdef.find(repr);
1186 if(rv != reprdef.end())
1187 return (rv->second);
1188 else
1189 return nullptr;
1190 }
1191
1192 /** Returns preferred document languages (from most to least preferred)
1193 *
1194 * This currently includes (in order):
1195 * - language set in RDF metadata
1196 * - languages suitable for system locale (influenced by Inkscape GUI locale preference)
1197 */
getLanguages() const1198 std::vector<Glib::ustring> SPDocument::getLanguages() const
1199 {
1200 std::vector<Glib::ustring> document_languages;
1201
1202 // get language from RDF
1203 gchar const *rdf_language = rdf_get_work_entity(this, rdf_find_entity("language"));
1204 if (rdf_language) {
1205 gchar *rdf_language_stripped = g_strstrip(g_strdup(rdf_language));
1206 if (strcmp(rdf_language_stripped, "") != 0) {
1207 document_languages.emplace_back(rdf_language_stripped);
1208 }
1209 g_free(rdf_language_stripped);
1210 }
1211
1212 // get language from system locale (will also match the interface language preference as we set LANG accordingly)
1213 // TODO: This includes locales with encodings like "de_DE.UTF-8" - is this useful or should we skip these?
1214 // TODO: This includes the default "C" locale - is this useful or should we skip it?
1215 const gchar * const * names = g_get_language_names();
1216 for (int i=0; names[i]; i++) {
1217 document_languages.emplace_back(names[i]);
1218 }
1219
1220 return document_languages;
1221 }
1222
1223 /* Object modification root handler */
1224
requestModified()1225 void SPDocument::requestModified()
1226 {
1227 if (modified_connection.empty()) {
1228 modified_connection =
1229 Glib::signal_idle().connect(sigc::mem_fun(*this, &SPDocument::idle_handler),
1230 SP_DOCUMENT_UPDATE_PRIORITY);
1231 }
1232
1233 if (rerouting_connection.empty()) {
1234 rerouting_connection =
1235 Glib::signal_idle().connect(sigc::mem_fun(*this, &SPDocument::rerouting_handler),
1236 SP_DOCUMENT_REROUTING_PRIORITY);
1237 }
1238 }
1239
setupViewport(SPItemCtx * ctx)1240 void SPDocument::setupViewport(SPItemCtx *ctx)
1241 {
1242 ctx->flags = 0;
1243 ctx->i2doc = Geom::identity();
1244 // Set up viewport in case svg has it defined as percentages
1245 if (root->viewBox_set) { // if set, take from viewBox
1246 ctx->viewport = root->viewBox;
1247 } else { // as a last resort, set size to A4
1248 ctx->viewport = Geom::Rect::from_xywh(0, 0, Inkscape::Util::Quantity::convert(210, "mm", "px"), Inkscape::Util::Quantity::convert(297, "mm", "px"));
1249 }
1250 ctx->i2vp = Geom::identity();
1251 }
1252
1253 /**
1254 * Tries to update the document state based on the modified and
1255 * "update required" flags, and return true if the document has
1256 * been brought fully up to date.
1257 */
1258 bool
_updateDocument(int update_flags)1259 SPDocument::_updateDocument(int update_flags)
1260 {
1261 /* Process updates */
1262 if (this->root->uflags || this->root->mflags) {
1263 if (this->root->uflags) {
1264 SPItemCtx ctx;
1265 setupViewport(&ctx);
1266
1267 DocumentUndo::ScopedInsensitive _no_undo(this);
1268
1269 this->root->updateDisplay((SPCtx *)&ctx, update_flags);
1270 }
1271 this->_emitModified();
1272 }
1273
1274 return !(this->root->uflags || this->root->mflags);
1275 }
1276
1277
1278 /**
1279 * Repeatedly works on getting the document updated, since sometimes
1280 * it takes more than one pass to get the document updated. But it
1281 * usually should not take more than a few loops, and certainly never
1282 * more than 32 iterations. So we bail out if we hit 32 iterations,
1283 * since this typically indicates we're stuck in an update loop.
1284 */
ensureUpToDate()1285 gint SPDocument::ensureUpToDate()
1286 {
1287 // Bring the document up-to-date, specifically via the following:
1288 // 1a) Process all document updates.
1289 // 1b) When completed, process connector routing changes.
1290 // 2a) Process any updates resulting from connector reroutings.
1291 int counter = 32;
1292 for (unsigned int pass = 1; pass <= 2; ++pass) {
1293 // Process document updates.
1294 while (!_updateDocument(0)) {
1295 if (counter == 0) {
1296 g_warning("More than 32 iteration while updating document '%s'", document_uri);
1297 break;
1298 }
1299 counter--;
1300 }
1301 if (counter == 0)
1302 {
1303 break;
1304 }
1305
1306 // After updates on the first pass we get libavoid to process all the
1307 // changed objects and provide new routings. This may cause some objects
1308 // to be modified, hence the second update pass.
1309 if (pass == 1) {
1310 router->processTransaction();
1311 }
1312 }
1313
1314 // Remove handlers
1315 modified_connection.disconnect();
1316 rerouting_connection.disconnect();
1317
1318 return (counter > 0);
1319 }
1320
1321 /**
1322 * An idle handler to update the document. Returns true if
1323 * the document needs further updates.
1324 */
1325 bool
idle_handler()1326 SPDocument::idle_handler()
1327 {
1328 bool status = !_updateDocument(0); // method TRUE if it does NOT need further modification, so invert
1329 if (!status) {
1330 modified_connection.disconnect();
1331 }
1332 return status;
1333 }
1334
1335 /**
1336 * An idle handler to reroute connectors in the document.
1337 */
1338 bool
rerouting_handler()1339 SPDocument::rerouting_handler()
1340 {
1341 // Process any queued movement actions and determine new routings for
1342 // object-avoiding connectors. Callbacks will be used to update and
1343 // redraw affected connectors.
1344 router->processTransaction();
1345
1346 // We don't need to handle rerouting again until there are further
1347 // diagram updates.
1348 return false;
1349 }
1350
is_within(Geom::Rect const & area,Geom::Rect const & box)1351 static bool is_within(Geom::Rect const &area, Geom::Rect const &box)
1352 {
1353 return area.contains(box);
1354 }
1355
overlaps(Geom::Rect const & area,Geom::Rect const & box)1356 static bool overlaps(Geom::Rect const &area, Geom::Rect const &box)
1357 {
1358 return area.intersects(box);
1359 }
1360
1361 /**
1362 * @param area Area in document coordinates
1363 */
find_items_in_area(std::vector<SPItem * > & s,SPGroup * group,unsigned int dkey,Geom::Rect const & area,bool (* test)(Geom::Rect const &,Geom::Rect const &),bool take_hidden=false,bool take_insensitive=false,bool take_groups=true,bool enter_groups=false)1364 static std::vector<SPItem*> &find_items_in_area(std::vector<SPItem*> &s,
1365 SPGroup *group, unsigned int dkey,
1366 Geom::Rect const &area,
1367 bool (*test)(Geom::Rect const &, Geom::Rect const &),
1368 bool take_hidden = false,
1369 bool take_insensitive = false,
1370 bool take_groups = true,
1371 bool enter_groups = false)
1372 {
1373 g_return_val_if_fail(SP_IS_GROUP(group), s);
1374
1375 for (auto& o: group->children) {
1376 if (SPItem *item = dynamic_cast<SPItem *>(&o)) {
1377 if (!take_insensitive && item->isLocked()) {
1378 continue;
1379 }
1380
1381 if (!take_hidden && item->isHidden()) {
1382 continue;
1383 }
1384
1385 if (SPGroup * childgroup = dynamic_cast<SPGroup *>(item)) {
1386 bool is_layer = childgroup->effectiveLayerMode(dkey) == SPGroup::LAYER;
1387 if (is_layer || (enter_groups)) {
1388 s = find_items_in_area(s, childgroup, dkey, area, test, take_hidden, take_insensitive, take_groups, enter_groups);
1389 }
1390 if (!take_groups || is_layer) {
1391 continue;
1392 }
1393 }
1394 Geom::OptRect box = item->documentVisualBounds();
1395 if (box && test(area, *box)) {
1396 s.push_back(item);
1397 }
1398 }
1399 }
1400 return s;
1401 }
1402
getItemFromListAtPointBottom(unsigned int dkey,SPGroup * group,std::vector<SPItem * > const & list,Geom::Point const & p,bool take_insensitive)1403 SPItem *SPDocument::getItemFromListAtPointBottom(unsigned int dkey, SPGroup *group, std::vector<SPItem*> const &list,Geom::Point const &p, bool take_insensitive)
1404 {
1405 g_return_val_if_fail(group, NULL);
1406 SPItem *bottomMost = nullptr;
1407
1408 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1409 gdouble delta = prefs->getDouble("/options/cursortolerance/value", 1.0);
1410
1411 for (auto& o: group->children) {
1412 if (bottomMost) {
1413 break;
1414 }
1415 if (SP_IS_ITEM(&o)) {
1416 SPItem *item = SP_ITEM(&o);
1417 Inkscape::DrawingItem *arenaitem = item->get_arenaitem(dkey);
1418
1419 if (arenaitem) {
1420 arenaitem->drawing().update();
1421 }
1422
1423 if (arenaitem && arenaitem->pick(p, delta, 1) != nullptr
1424 && (take_insensitive || item->isVisibleAndUnlocked(dkey))) {
1425 if (find(list.begin(), list.end(), item) != list.end()) {
1426 bottomMost = item;
1427 }
1428 }
1429
1430 if (!bottomMost && SP_IS_GROUP(&o)) {
1431 // return null if not found:
1432 bottomMost = getItemFromListAtPointBottom(dkey, SP_GROUP(&o), list, p, take_insensitive);
1433 }
1434 }
1435 }
1436 return bottomMost;
1437 }
1438
1439 /**
1440 Turn the SVG DOM into a flat list of nodes that can be searched from top-down.
1441 The list can be persisted, which improves "find at multiple points" speed.
1442 */
1443 // TODO: study add `gboolean with_groups = false` as parameter.
build_flat_item_list(unsigned int dkey,SPGroup * group,gboolean into_groups) const1444 void SPDocument::build_flat_item_list(unsigned int dkey, SPGroup *group, gboolean into_groups) const
1445 {
1446 for (auto& o: group->children) {
1447 if (!SP_IS_ITEM(&o)) {
1448 continue;
1449 }
1450
1451 if (SP_IS_GROUP(&o) && (SP_GROUP(&o)->effectiveLayerMode(dkey) == SPGroup::LAYER || into_groups)) {
1452 build_flat_item_list(dkey, SP_GROUP(&o), into_groups);
1453 } else {
1454 SPItem *child = SP_ITEM(&o);
1455 if (child->isVisibleAndUnlocked(dkey)) {
1456 _node_cache.push_front(child);
1457 }
1458 }
1459 }
1460 }
1461
1462 /**
1463 Returns the topmost (in z-order) item from the descendants of group (recursively) which
1464 is at the point p, or NULL if none. Honors into_groups on whether to recurse into
1465 non-layer groups or not. Honors take_insensitive on whether to return insensitive
1466 items. If upto != NULL, then if item upto is encountered (at any level), stops searching
1467 upwards in z-order and returns what it has found so far (i.e. the found item is
1468 guaranteed to be lower than upto). Requires a list of nodes built by
1469 build_flat_item_list.
1470 */
find_item_at_point(std::deque<SPItem * > * nodes,unsigned int dkey,Geom::Point const & p,SPItem * upto=nullptr)1471 static SPItem *find_item_at_point(std::deque<SPItem*> *nodes, unsigned int dkey, Geom::Point const &p, SPItem* upto=nullptr)
1472 {
1473 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1474 gdouble delta = prefs->getDouble("/options/cursortolerance/value", 1.0);
1475
1476 SPItem *seen = nullptr;
1477 SPItem *child;
1478 bool seen_upto = (!upto);
1479 for (auto node : *nodes) {
1480 child = node;
1481 if (!seen_upto){
1482 if(child == upto)
1483 seen_upto = true;
1484 continue;
1485 }
1486 Inkscape::DrawingItem *arenaitem = child->get_arenaitem(dkey);
1487 if (arenaitem) {
1488 arenaitem->drawing().update();
1489 if (arenaitem->pick(p, delta, 1) != nullptr) {
1490 seen = child;
1491 break;
1492 }
1493 }
1494 }
1495
1496 return seen;
1497 }
1498
1499 /**
1500 Returns the topmost non-layer group from the descendants of group which is at point
1501 p, or NULL if none. Recurses into layers but not into groups.
1502 */
find_group_at_point(unsigned int dkey,SPGroup * group,Geom::Point const & p)1503 static SPItem *find_group_at_point(unsigned int dkey, SPGroup *group, Geom::Point const &p)
1504 {
1505 SPItem *seen = nullptr;
1506 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1507 gdouble delta = prefs->getDouble("/options/cursortolerance/value", 1.0);
1508
1509 for (auto& o: group->children) {
1510 if (!SP_IS_ITEM(&o)) {
1511 continue;
1512 }
1513 if (SP_IS_GROUP(&o) && SP_GROUP(&o)->effectiveLayerMode(dkey) == SPGroup::LAYER) {
1514 SPItem *newseen = find_group_at_point(dkey, SP_GROUP(&o), p);
1515 if (newseen) {
1516 seen = newseen;
1517 }
1518 }
1519 if (SP_IS_GROUP(&o) && SP_GROUP(&o)->effectiveLayerMode(dkey) != SPGroup::LAYER ) {
1520 SPItem *child = SP_ITEM(&o);
1521 Inkscape::DrawingItem *arenaitem = child->get_arenaitem(dkey);
1522 if (arenaitem) {
1523 arenaitem->drawing().update();
1524 }
1525
1526 // seen remembers the last (topmost) of groups pickable at this point
1527 if (arenaitem && arenaitem->pick(p, delta, 1) != nullptr) {
1528 seen = child;
1529 }
1530 }
1531 }
1532 return seen;
1533 }
1534
1535
1536 /**
1537 * Return list of items, contained in box
1538 *
1539 * @param box area to find items, in document coordinates
1540 */
1541
getItemsInBox(unsigned int dkey,Geom::Rect const & box,bool take_hidden,bool take_insensitive,bool take_groups,bool enter_groups) const1542 std::vector<SPItem*> SPDocument::getItemsInBox(unsigned int dkey, Geom::Rect const &box, bool take_hidden, bool take_insensitive, bool take_groups, bool enter_groups) const
1543 {
1544 std::vector<SPItem*> x;
1545 return find_items_in_area(x, SP_GROUP(this->root), dkey, box, is_within, take_hidden, take_insensitive, take_groups, enter_groups);
1546 }
1547
1548 /**
1549 * Get items whose bounding box overlaps with given area.
1550 * @param dkey desktop view in use
1551 * @param box area to find items, in document coordinates
1552 * @param take_hidden get hidden items
1553 * @param take_insensitive get insensitive items
1554 * @param take_groups get also the groups
1555 * @param enter_groups get items inside groups
1556 * @return Return list of items, that the parts of the item contained in box
1557 */
1558
getItemsPartiallyInBox(unsigned int dkey,Geom::Rect const & box,bool take_hidden,bool take_insensitive,bool take_groups,bool enter_groups) const1559 std::vector<SPItem*> SPDocument::getItemsPartiallyInBox(unsigned int dkey, Geom::Rect const &box, bool take_hidden, bool take_insensitive, bool take_groups, bool enter_groups) const
1560 {
1561 std::vector<SPItem*> x;
1562 return find_items_in_area(x, SP_GROUP(this->root), dkey, box, overlaps, take_hidden, take_insensitive, take_groups, enter_groups);
1563 }
1564
getItemsAtPoints(unsigned const key,std::vector<Geom::Point> points,bool all_layers,size_t limit) const1565 std::vector<SPItem*> SPDocument::getItemsAtPoints(unsigned const key, std::vector<Geom::Point> points, bool all_layers, size_t limit) const
1566 {
1567 std::vector<SPItem*> items;
1568 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1569
1570 // When picking along the path, we don't want small objects close together
1571 // (such as hatching strokes) to obscure each other by their deltas,
1572 // so we temporarily set delta to a small value
1573 gdouble saved_delta = prefs->getDouble("/options/cursortolerance/value", 1.0);
1574 prefs->setDouble("/options/cursortolerance/value", 0.25);
1575
1576 // Cache a flattened SVG DOM to speed up selection.
1577 if(!_node_cache_valid){
1578 _node_cache.clear();
1579 build_flat_item_list(key, SP_GROUP(this->root), true);
1580 _node_cache_valid=true;
1581 }
1582 SPObject *current_layer = nullptr;
1583 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1584 Inkscape::LayerModel *layer_model = nullptr;
1585 if(desktop){
1586 current_layer = desktop->currentLayer();
1587 layer_model = desktop->layers;
1588 }
1589 size_t item_counter = 0;
1590 for(int i = points.size()-1;i>=0; i--) {
1591 SPItem *item = find_item_at_point(&_node_cache, key, points[i]);
1592 if (item && items.end()==find(items.begin(),items.end(), item))
1593 if(all_layers || (layer_model && layer_model->layerForObject(item) == current_layer)){
1594 items.push_back(item);
1595 item_counter++;
1596 //limit 0 = no limit
1597 if(item_counter == limit){
1598 prefs->setDouble("/options/cursortolerance/value", saved_delta);
1599 return items;
1600 }
1601 }
1602 }
1603
1604 // and now we restore it back
1605 prefs->setDouble("/options/cursortolerance/value", saved_delta);
1606
1607 return items;
1608 }
1609
getItemAtPoint(unsigned const key,Geom::Point const & p,bool const into_groups,SPItem * upto) const1610 SPItem *SPDocument::getItemAtPoint( unsigned const key, Geom::Point const &p,
1611 bool const into_groups, SPItem *upto) const
1612 {
1613 // Build a flattened SVG DOM for find_item_at_point.
1614 std::deque<SPItem*> bak(_node_cache);
1615 if(!into_groups){
1616 _node_cache.clear();
1617 build_flat_item_list(key, SP_GROUP(this->root), into_groups);
1618 }
1619 if(!_node_cache_valid && into_groups){
1620 _node_cache.clear();
1621 build_flat_item_list(key, SP_GROUP(this->root), true);
1622 _node_cache_valid=true;
1623 }
1624
1625 SPItem *res = find_item_at_point(&_node_cache, key, p, upto);
1626 if(!into_groups)
1627 _node_cache = bak;
1628 return res;
1629 }
1630
getGroupAtPoint(unsigned int key,Geom::Point const & p) const1631 SPItem *SPDocument::getGroupAtPoint(unsigned int key, Geom::Point const &p) const
1632 {
1633 return find_group_at_point(key, SP_GROUP(this->root), p);
1634 }
1635
1636 // Resource management
1637
addResource(gchar const * key,SPObject * object)1638 bool SPDocument::addResource(gchar const *key, SPObject *object)
1639 {
1640 g_return_val_if_fail(key != nullptr, false);
1641 g_return_val_if_fail(*key != '\0', false);
1642 g_return_val_if_fail(object != nullptr, false);
1643 g_return_val_if_fail(SP_IS_OBJECT(object), false);
1644
1645 bool result = false;
1646
1647 if ( !object->cloned ) {
1648 std::vector<SPObject *> rlist = resources[key];
1649 g_return_val_if_fail(std::find(rlist.begin(),rlist.end(),object) == rlist.end(), false);
1650 resources[key].insert(resources[key].begin(),object);
1651
1652 GQuark q = g_quark_from_string(key);
1653
1654 /*in general, do not send signal if the object has no id (yet),
1655 it means the object is not completely built.
1656 (happens when pasting swatches across documents, cf bug 1495106)
1657 [this check should be more generally presend on emit() calls since
1658 the backtrace is unusable with crashed from this cause]
1659 */
1660 if(object->getId() || dynamic_cast<SPGroup*>(object) )
1661 resources_changed_signals[q].emit();
1662
1663 result = true;
1664 }
1665
1666 return result;
1667 }
1668
removeResource(gchar const * key,SPObject * object)1669 bool SPDocument::removeResource(gchar const *key, SPObject *object)
1670 {
1671 g_return_val_if_fail(key != nullptr, false);
1672 g_return_val_if_fail(*key != '\0', false);
1673 g_return_val_if_fail(object != nullptr, false);
1674 g_return_val_if_fail(SP_IS_OBJECT(object), false);
1675
1676 bool result = false;
1677
1678 if ( !object->cloned ) {
1679 std::vector<SPObject *> rlist = resources[key];
1680 g_return_val_if_fail(!rlist.empty(), false);
1681 std::vector<SPObject*>::iterator it = std::find(resources[key].begin(),resources[key].end(),object);
1682 g_return_val_if_fail(it != rlist.end(), false);
1683 resources[key].erase(it);
1684
1685 GQuark q = g_quark_from_string(key);
1686 resources_changed_signals[q].emit();
1687
1688 result = true;
1689 }
1690
1691 return result;
1692 }
1693
getResourceList(gchar const * key)1694 std::vector<SPObject *> const SPDocument::getResourceList(gchar const *key)
1695 {
1696 std::vector<SPObject *> emptyset;
1697 g_return_val_if_fail(key != nullptr, emptyset);
1698 g_return_val_if_fail(*key != '\0', emptyset);
1699
1700 return resources[key];
1701 }
1702
1703 /* Helpers */
1704
count_objects_recursive(SPObject * obj,unsigned int count)1705 static unsigned int count_objects_recursive(SPObject *obj, unsigned int count)
1706 {
1707 count++; // obj itself
1708
1709 for (auto& i: obj->children) {
1710 count = count_objects_recursive(&i, count);
1711 }
1712
1713 return count;
1714 }
1715
1716 /**
1717 * Count the number of objects in a given document recursively using the count_objects_recursive helper function
1718 *
1719 * @param[in] document Pointer to the document for counting objects
1720 * @return Number of objects in the document
1721 */
objects_in_document(SPDocument * document)1722 static unsigned int objects_in_document(SPDocument *document)
1723 {
1724 return count_objects_recursive(document->getRoot(), 0);
1725 }
1726
1727 /**
1728 * Remove unused definitions etc. recursively from an object and its siblings
1729 *
1730 * @param[inout] obj Object which shall be "cleaned"
1731 */
vacuum_document_recursive(SPObject * obj)1732 static void vacuum_document_recursive(SPObject *obj)
1733 {
1734 if (SP_IS_DEFS(obj)) {
1735 for (auto& def: obj->children) {
1736 // fixme: some inkscape-internal nodes in the future might not be collectable
1737 def.requestOrphanCollection();
1738 }
1739 } else {
1740 for (auto& i: obj->children) {
1741 vacuum_document_recursive(&i);
1742 }
1743 }
1744 }
1745
1746 /**
1747 * Remove unused definitions etc. recursively from an entire document.
1748 *
1749 * @return Number of removed objects
1750 */
vacuumDocument()1751 unsigned int SPDocument::vacuumDocument()
1752 {
1753 unsigned int start = objects_in_document(this);
1754 unsigned int end;
1755 unsigned int newend = start;
1756
1757 unsigned int iterations = 0;
1758
1759 do {
1760 end = newend;
1761
1762 vacuum_document_recursive(root);
1763 this->collectOrphans();
1764 iterations++;
1765
1766 newend = objects_in_document(this);
1767
1768 } while (iterations < 100 && newend < end);
1769 // We stop if vacuum_document_recursive doesn't remove any more objects or after 100 iterations, whichever occurs first.
1770
1771 return start - newend;
1772 }
1773
1774 /**
1775 * Indicate to the user if the document has been modified since the last save by displaying a "*" in front of the name of the file in the window title.
1776 *
1777 * @param[in] modified True if the document has been modified.
1778 */
setModifiedSinceSave(bool modified)1779 void SPDocument::setModifiedSinceSave(bool modified) {
1780 this->modified_since_save = modified;
1781 this->modified_since_autosave = modified;
1782 if (SP_ACTIVE_DESKTOP) {
1783 InkscapeWindow *window = SP_ACTIVE_DESKTOP->getInkscapeWindow();
1784 if (window) { // during load, SP_ACTIVE_DESKTOP may be !nullptr, but parent might still be nullptr
1785 SPDesktopWidget *dtw = window->get_desktop_widget();
1786 dtw->updateTitle( this->getDocumentName() );
1787 }
1788 }
1789 }
1790
1791
1792 /**
1793 * Paste SVG defs from the document retrieved from the clipboard or imported document into the active document.
1794 * @param clipdoc The document to paste.
1795 * @pre @c clipdoc != NULL and pasting into the active document is possible.
1796 */
importDefs(SPDocument * source)1797 void SPDocument::importDefs(SPDocument *source)
1798 {
1799 Inkscape::XML::Node *root = source->getReprRoot();
1800 Inkscape::XML::Node *target_defs = this->getDefs()->getRepr();
1801 std::vector<Inkscape::XML::Node const *> defsNodes = sp_repr_lookup_name_many(root, "svg:defs");
1802
1803 prevent_id_clashes(source, this);
1804
1805 for (auto & defsNode : defsNodes) {
1806 _importDefsNode(source, const_cast<Inkscape::XML::Node *>(defsNode), target_defs);
1807 }
1808 }
1809
_importDefsNode(SPDocument * source,Inkscape::XML::Node * defs,Inkscape::XML::Node * target_defs)1810 void SPDocument::_importDefsNode(SPDocument *source, Inkscape::XML::Node *defs, Inkscape::XML::Node *target_defs)
1811 {
1812 int stagger=0;
1813
1814 /* Note, "clipboard" throughout the comments means "the document that is either the clipboard
1815 or an imported document", as importDefs is called in both contexts.
1816
1817 The order of the records in the clipboard is unpredictable and there may be both
1818 forward and backwards references to other records within it. There may be definitions in
1819 the clipboard that duplicate definitions in the present document OR that duplicate other
1820 definitions in the clipboard. (Inkscape will not have created these, but they may be read
1821 in from other SVG sources.)
1822
1823 There are 3 passes to clean this up:
1824
1825 In the first find and mark definitions in the clipboard that are duplicates of those in the
1826 present document. Change the ID to "RESERVED_FOR_INKSCAPE_DUPLICATE_DEF_XXXXXXXXX".
1827 (Inkscape will not reuse an ID, and the XXXXXXXXX keeps it from automatically creating new ones.)
1828 References in the clipboard to the old clipboard name are converted to the name used
1829 in the current document.
1830
1831 In the second find and mark definitions in the clipboard that are duplicates of earlier
1832 definitions in the clipbard. Unfortunately this is O(n^2) and could be very slow for a large
1833 SVG with thousands of definitions. As before, references are adjusted to reflect the name
1834 going forward.
1835
1836 In the final cycle copy over those records not marked with that ID.
1837
1838 If an SVG file uses the special ID it will cause problems!
1839
1840 If this function is called because of the paste of a true clipboard the caller will have passed in a
1841 COPY of the clipboard items. That is good, because this routine modifies that document. If the calling
1842 behavior ever changes, so that the same document is passed in on multiple pastes, this routine will break
1843 as in the following example:
1844 1. Paste clipboard containing B same as A into document containing A. Result, B is dropped
1845 and all references to it will point to A.
1846 2. Paste same clipboard into a new document. It will not contain A, so there will be unsatisfied
1847 references in that window.
1848 */
1849
1850 std::string DuplicateDefString = "RESERVED_FOR_INKSCAPE_DUPLICATE_DEF";
1851
1852 /* First pass: remove duplicates in clipboard of definitions in document */
1853 for (Inkscape::XML::Node *def = defs->firstChild() ; def ; def = def->next()) {
1854 if(def->type() != Inkscape::XML::NodeType::ELEMENT_NODE)continue;
1855 /* If this clipboard has been pasted into one document, and is now being pasted into another,
1856 or pasted again into the same, it will already have been processed. If we detect that then
1857 skip the rest of this pass. */
1858 Glib::ustring defid = def->attribute("id");
1859 if( defid.find( DuplicateDefString ) != Glib::ustring::npos )break;
1860
1861 SPObject *src = source->getObjectByRepr(def);
1862
1863 // Prevent duplicates of solid swatches by checking if equivalent swatch already exists
1864 SPGradient *s_gr = dynamic_cast<SPGradient *>(src);
1865 LivePathEffectObject *s_lpeobj = dynamic_cast<LivePathEffectObject *>(src);
1866 if (src && (s_gr || s_lpeobj)) {
1867 for (auto& trg: getDefs()->children) {
1868 SPGradient *t_gr = dynamic_cast<SPGradient *>(&trg);
1869 if (src != &trg && s_gr && t_gr) {
1870 if (s_gr->isEquivalent(t_gr)) {
1871 // Change object references to the existing equivalent gradient
1872 Glib::ustring newid = trg.getId();
1873 if (newid != defid) { // id could be the same if it is a second paste into the same document
1874 change_def_references(src, &trg);
1875 }
1876 gchar *longid = g_strdup_printf("%s_%9.9d", DuplicateDefString.c_str(), stagger++);
1877 def->setAttribute("id", longid);
1878 g_free(longid);
1879 // do NOT break here, there could be more than 1 duplicate!
1880 }
1881 }
1882 LivePathEffectObject *t_lpeobj = dynamic_cast<LivePathEffectObject *>(&trg);
1883 if (src != &trg && s_lpeobj && t_lpeobj) {
1884 if (t_lpeobj->is_similar(s_lpeobj)) {
1885 // Change object references to the existing equivalent gradient
1886 Glib::ustring newid = trg.getId();
1887 if (newid != defid) { // id could be the same if it is a second paste into the same document
1888 change_def_references(src, &trg);
1889 }
1890 gchar *longid = g_strdup_printf("%s_%9.9d", DuplicateDefString.c_str(), stagger++);
1891 def->setAttribute("id", longid);
1892 g_free(longid);
1893 // do NOT break here, there could be more than 1 duplicate!
1894 }
1895 }
1896 }
1897 }
1898 }
1899
1900 /* Second pass: remove duplicates in clipboard of earlier definitions in clipboard */
1901 for (Inkscape::XML::Node *def = defs->firstChild() ; def ; def = def->next()) {
1902 if(def->type() != Inkscape::XML::NodeType::ELEMENT_NODE)continue;
1903 Glib::ustring defid = def->attribute("id");
1904 if( defid.find( DuplicateDefString ) != Glib::ustring::npos )continue; // this one already handled
1905 SPObject *src = source->getObjectByRepr(def);
1906 LivePathEffectObject *s_lpeobj = dynamic_cast<LivePathEffectObject *>(src);
1907 SPGradient *s_gr = dynamic_cast<SPGradient *>(src);
1908 if (src && (s_gr || s_lpeobj)) {
1909 for (Inkscape::XML::Node *laterDef = def->next() ; laterDef ; laterDef = laterDef->next()) {
1910 SPObject *trg = source->getObjectByRepr(laterDef);
1911 SPGradient *t_gr = dynamic_cast<SPGradient *>(trg);
1912 if (trg && (src != trg) && s_gr && t_gr) {
1913 Glib::ustring newid = trg->getId();
1914 if (newid.find(DuplicateDefString) != Glib::ustring::npos)
1915 continue; // this one already handled
1916 if (t_gr && s_gr->isEquivalent(t_gr)) {
1917 // Change object references to the existing equivalent gradient
1918 // two id's in the clipboard should never be the same, so always change references
1919 change_def_references(trg, src);
1920 gchar *longid = g_strdup_printf("%s_%9.9d", DuplicateDefString.c_str(), stagger++);
1921 laterDef->setAttribute("id", longid);
1922 g_free(longid);
1923 // do NOT break here, there could be more than 1 duplicate!
1924 }
1925 }
1926 LivePathEffectObject *t_lpeobj = dynamic_cast<LivePathEffectObject *>(trg);
1927 if (trg && (src != trg) && s_lpeobj && t_lpeobj) {
1928 Glib::ustring newid = trg->getId();
1929 if (newid.find(DuplicateDefString) != Glib::ustring::npos)
1930 continue; // this one already handled
1931 if (t_lpeobj->is_similar(s_lpeobj)) {
1932 // Change object references to the existing equivalent gradient
1933 // two id's in the clipboard should never be the same, so always change references
1934 change_def_references(trg, src);
1935 gchar *longid = g_strdup_printf("%s_%9.9d", DuplicateDefString.c_str(), stagger++);
1936 laterDef->setAttribute("id", longid);
1937 g_free(longid);
1938 // do NOT break here, there could be more than 1 duplicate!
1939 }
1940 }
1941 }
1942 }
1943 }
1944
1945 /* Final pass: copy over those parts which are not duplicates */
1946 for (Inkscape::XML::Node *def = defs->firstChild() ; def ; def = def->next()) {
1947 if(def->type() != Inkscape::XML::NodeType::ELEMENT_NODE)continue;
1948
1949 /* Ignore duplicate defs marked in the first pass */
1950 Glib::ustring defid = def->attribute("id");
1951 if( defid.find( DuplicateDefString ) != Glib::ustring::npos )continue;
1952
1953 bool duplicate = false;
1954 SPObject *src = source->getObjectByRepr(def);
1955
1956 // Prevent duplication of symbols... could be more clever.
1957 // The tag "_inkscape_duplicate" is added to "id" by ClipboardManagerImpl::copySymbol().
1958 // We assume that symbols are in defs section (not required by SVG spec).
1959 if (src && SP_IS_SYMBOL(src)) {
1960
1961 Glib::ustring id = src->getRepr()->attribute("id");
1962 size_t pos = id.find( "_inkscape_duplicate" );
1963 if( pos != Glib::ustring::npos ) {
1964
1965 // This is our symbol, now get rid of tag
1966 id.erase( pos );
1967
1968 // Check that it really is a duplicate
1969 for (auto& trg: getDefs()->children) {
1970 if (SP_IS_SYMBOL(&trg) && src != &trg) {
1971 std::string id2 = trg.getRepr()->attribute("id");
1972
1973 if( !id.compare( id2 ) ) {
1974 duplicate = true;
1975 break;
1976 }
1977 }
1978 }
1979 if ( !duplicate ) {
1980 src->setAttribute("id", id);
1981 }
1982 }
1983 }
1984
1985 if (!duplicate) {
1986 Inkscape::XML::Node * dup = def->duplicate(this->getReprDoc());
1987 target_defs->appendChild(dup);
1988 Inkscape::GC::release(dup);
1989 }
1990 }
1991 }
1992
1993 // Signals ------------------------------
1994
1995 void
addUndoObserver(Inkscape::UndoStackObserver & observer)1996 SPDocument::addUndoObserver(Inkscape::UndoStackObserver& observer)
1997 {
1998 this->undoStackObservers.add(observer);
1999 }
2000
2001 void
removeUndoObserver(Inkscape::UndoStackObserver & observer)2002 SPDocument::removeUndoObserver(Inkscape::UndoStackObserver& observer)
2003 {
2004 this->undoStackObservers.remove(observer);
2005 }
2006
connectDestroy(sigc::signal<void>::slot_type slot)2007 sigc::connection SPDocument::connectDestroy(sigc::signal<void>::slot_type slot)
2008 {
2009 return destroySignal.connect(slot);
2010 }
2011
connectModified(SPDocument::ModifiedSignal::slot_type slot)2012 sigc::connection SPDocument::connectModified(SPDocument::ModifiedSignal::slot_type slot)
2013 {
2014 return modified_signal.connect(slot);
2015 }
2016
connectURISet(SPDocument::URISetSignal::slot_type slot)2017 sigc::connection SPDocument::connectURISet(SPDocument::URISetSignal::slot_type slot)
2018 {
2019 return uri_set_signal.connect(slot);
2020 }
2021
connectResized(SPDocument::ResizedSignal::slot_type slot)2022 sigc::connection SPDocument::connectResized(SPDocument::ResizedSignal::slot_type slot)
2023 {
2024 return resized_signal.connect(slot);
2025 }
2026
connectCommit(SPDocument::CommitSignal::slot_type slot)2027 sigc::connection SPDocument::connectCommit(SPDocument::CommitSignal::slot_type slot)
2028 {
2029 return commit_signal.connect(slot);
2030 }
2031
connectIdChanged(gchar const * id,SPDocument::IDChangedSignal::slot_type slot)2032 sigc::connection SPDocument::connectIdChanged(gchar const *id,
2033 SPDocument::IDChangedSignal::slot_type slot)
2034 {
2035 return id_changed_signals[g_quark_from_string(id)].connect(slot);
2036 }
2037
connectResourcesChanged(gchar const * key,SPDocument::ResourcesChangedSignal::slot_type slot)2038 sigc::connection SPDocument::connectResourcesChanged(gchar const *key,
2039 SPDocument::ResourcesChangedSignal::slot_type slot)
2040 {
2041 GQuark q = g_quark_from_string(key);
2042 return resources_changed_signals[q].connect(slot);
2043 }
2044
2045 sigc::connection
connectReconstructionStart(SPDocument::ReconstructionStart::slot_type slot)2046 SPDocument::connectReconstructionStart(SPDocument::ReconstructionStart::slot_type slot)
2047 {
2048 return _reconstruction_start_signal.connect(slot);
2049 }
2050
2051 sigc::connection
connectReconstructionFinish(SPDocument::ReconstructionFinish::slot_type slot)2052 SPDocument::connectReconstructionFinish(SPDocument::ReconstructionFinish::slot_type slot)
2053 {
2054 return _reconstruction_finish_signal.connect(slot);
2055 }
2056
_emitModified()2057 void SPDocument::_emitModified() {
2058 static guint const flags = SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG;
2059 root->emitModified(0);
2060 modified_signal.emit(flags);
2061 _node_cache_valid=false;
2062 }
2063
2064 void
emitReconstructionStart()2065 SPDocument::emitReconstructionStart()
2066 {
2067 // printf("Starting Reconstruction\n");
2068 _reconstruction_start_signal.emit();
2069 }
2070
2071 void
emitReconstructionFinish()2072 SPDocument::emitReconstructionFinish()
2073 {
2074 // printf("Finishing Reconstruction\n");
2075 _reconstruction_finish_signal.emit();
2076 // indicates that gradients are reloaded (to rebuild the Auto palette)
2077 resources_changed_signals[g_quark_from_string("gradient")].emit();
2078 resources_changed_signals[g_quark_from_string("filter")].emit();
2079
2080 /**
2081 // Reference to the old persp3d object is invalid after reconstruction.
2082 initialize_current_persp3d();
2083 **/
2084 }
2085
emitResizedSignal(gdouble width,gdouble height)2086 void SPDocument::emitResizedSignal(gdouble width, gdouble height)
2087 {
2088 this->resized_signal.emit(width, height);
2089 }
2090
2091
2092 /*
2093 Local Variables:
2094 mode:c++
2095 c-file-style:"stroustrup"
2096 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2097 indent-tabs-mode:nil
2098 fill-column:99
2099 End:
2100 */
2101 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
2102