1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * SPObject implementation.
4  *
5  * Authors:
6  *   Lauris Kaplinski <lauris@kaplinski.com>
7  *   bulia byak <buliabyak@users.sf.net>
8  *   Stephen Silver <sasilver@users.sourceforge.net>
9  *   Jon A. Cruz <jon@joncruz.org>
10  *   Abhishek Sharma
11  *   Adrian Boguszewski
12  *
13  * Copyright (C) 1999-2016 authors
14  * Copyright (C) 2001-2002 Ximian, Inc.
15  *
16  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
17  */
18 
19 #include <cstring>
20 #include <string>
21 #include <vector>
22 
23 #include <boost/range/adaptor/transformed.hpp>
24 
25 #include "helper/sp-marshal.h"
26 #include "xml/node-event-vector.h"
27 #include "attributes.h"
28 #include "attribute-rel-util.h"
29 #include "color-profile.h"
30 #include "document.h"
31 #include "preferences.h"
32 #include "style.h"
33 #include "live_effects/lpeobject.h"
34 #include "sp-factory.h"
35 #include "sp-paint-server.h"
36 #include "sp-root.h"
37 #include "sp-style-elem.h"
38 #include "sp-script.h"
39 #include "streq.h"
40 #include "strneq.h"
41 #include "xml/node-fns.h"
42 #include "debug/event-tracker.h"
43 #include "debug/simple-event.h"
44 #include "debug/demangle.h"
45 #include "util/format.h"
46 #include "util/longest-common-suffix.h"
47 
48 #define noSP_OBJECT_DEBUG_CASCADE
49 
50 #define noSP_OBJECT_DEBUG
51 
52 #ifdef SP_OBJECT_DEBUG
53 # define debug(f, a...) { g_print("%s(%d) %s:", \
54                                   __FILE__,__LINE__,__FUNCTION__); \
55                           g_print(f, ## a); \
56                           g_print("\n"); \
57                         }
58 #else
59 # define debug(f, a...) /* */
60 #endif
61 
62 // Define to enable indented tracing of SPObject.
63 //#define OBJECT_TRACE
64 unsigned SPObject::indent_level = 0;
65 
66 Inkscape::XML::NodeEventVector object_event_vector = {
67     SPObject::repr_child_added,
68     SPObject::repr_child_removed,
69     SPObject::repr_attr_changed,
70     SPObject::repr_content_changed,
71     SPObject::repr_order_changed
72 };
73 
74 /**
75  * A friend class used to set internal members on SPObject so as to not expose settors in SPObject's public API
76  */
77 class SPObjectImpl
78 {
79 public:
80 
81 /**
82  * Null's the id member of an SPObject without attempting to free prior contents.
83  *
84  * @param[inout] obj Pointer to the object which's id shall be nulled.
85  */
setIdNull(SPObject * obj)86     static void setIdNull( SPObject* obj ) {
87         if (obj) {
88             obj->id = nullptr;
89         }
90     }
91 
92 /**
93  * Sets the id member of an object, freeing any prior content.
94  *
95  * @param[inout] obj Pointer to the object which's id shall be set.
96  * @param[in] id New id
97  */
setId(SPObject * obj,gchar const * id)98     static void setId( SPObject* obj, gchar const* id ) {
99         if (obj && (id != obj->id) ) {
100             if (obj->id) {
101                 g_free(obj->id);
102                 obj->id = nullptr;
103             }
104             if (id) {
105                 obj->id = g_strdup(id);
106             }
107         }
108     }
109 };
110 
111 /**
112  * Constructor, sets all attributes to default values.
113  */
SPObject()114 SPObject::SPObject()
115     : cloned(0), clone_original(nullptr), uflags(0), mflags(0), hrefcount(0), _total_hrefcount(0),
116       document(nullptr), parent(nullptr), id(nullptr), repr(nullptr), refCount(1), hrefList(std::list<SPObject*>()),
117       _successor(nullptr), _collection_policy(SPObject::COLLECT_WITH_PARENT),
118       _label(nullptr), _default_label(nullptr)
119 {
120     debug("id=%p, typename=%s",this, g_type_name_from_instance((GTypeInstance*)this));
121 
122     //used XML Tree here.
123     this->getRepr(); // TODO check why this call is made
124 
125     SPObjectImpl::setIdNull(this);
126 
127     // FIXME: now we create style for all objects, but per SVG, only the following can have style attribute:
128     // vg, g, defs, desc, title, symbol, use, image, switch, path, rect, circle, ellipse, line, polyline,
129     // polygon, text, tspan, tref, textPath, altGlyph, glyphRef, marker, linearGradient, radialGradient,
130     // stop, pattern, clipPath, mask, filter, feImage, a, font, glyph, missing-glyph, foreignObject
131     this->style = new SPStyle( nullptr, this ); // Is it necessary to call with "this"?
132     this->context_style = nullptr;
133 }
134 
135 /**
136  * Destructor, frees the used memory and unreferences a potential successor of the object.
137  */
~SPObject()138 SPObject::~SPObject() {
139     g_free(this->_label);
140     g_free(this->_default_label);
141 
142     this->_label = nullptr;
143     this->_default_label = nullptr;
144 
145     if (this->_successor) {
146         sp_object_unref(this->_successor, nullptr);
147         this->_successor = nullptr;
148     }
149     if (parent) {
150         parent->children.erase(parent->children.iterator_to(*this));
151     }
152 
153     if( style == nullptr ) {
154         // style pointer could be NULL if unreffed too many times.
155         // Conjecture: style pointer is never NULL.
156         std::cerr << "SPObject::~SPObject(): style pointer is NULL" << std::endl;
157     } else if( style->refCount() > 1 ) {
158         // Conjecture: style pointer should be unreffed by other classes before reaching here.
159         // Conjecture is false for SPTSpan where ref is held by InputStreamTextSource.
160         // As an additional note:
161         //   The outer tspan of a nested tspan will result in a ref count of five: one for the
162         //   TSpan itself, one for the InputStreamTextSource instance before the inner tspan and
163         //   one for the one after, along with one for each corresponding DrawingText instance.
164         // std::cerr << "SPObject::~SPObject(): someone else still holding ref to style" << std::endl;
165         //
166         sp_style_unref( this->style );
167     } else {
168         delete this->style;
169     }
170 }
171 
172 // CPPIFY: make pure virtual
read_content()173 void SPObject::read_content() {
174     //throw;
175 }
176 
update(SPCtx *,unsigned int)177 void SPObject::update(SPCtx* /*ctx*/, unsigned int /*flags*/) {
178     //throw;
179 }
180 
modified(unsigned int)181 void SPObject::modified(unsigned int /*flags*/) {
182 #ifdef OBJECT_TRACE
183     objectTrace( "SPObject::modified  (default) (empty function)" );
184     objectTrace( "SPObject::modified  (default)", false );
185 #endif
186     //throw;
187 }
188 
189 namespace {
190 
191 namespace Debug = Inkscape::Debug;
192 namespace Util = Inkscape::Util;
193 
194 typedef Debug::SimpleEvent<Debug::Event::REFCOUNT> BaseRefCountEvent;
195 
196 class RefCountEvent : public BaseRefCountEvent {
197 public:
RefCountEvent(SPObject * object,int bias,char const * name)198     RefCountEvent(SPObject *object, int bias, char const *name)
199     : BaseRefCountEvent(name)
200     {
201         _addProperty("object", Util::format("%p", object).pointer());
202         _addProperty("class", Debug::demangle(typeid(*object).name()));
203         _addProperty("new-refcount", Util::format("%d", object->refCount + bias).pointer());
204     }
205 };
206 
207 class RefEvent : public RefCountEvent {
208 public:
RefEvent(SPObject * object)209     RefEvent(SPObject *object)
210     : RefCountEvent(object, 1, "sp-object-ref")
211     {}
212 };
213 
214 class UnrefEvent : public RefCountEvent {
215 public:
UnrefEvent(SPObject * object)216     UnrefEvent(SPObject *object)
217     : RefCountEvent(object, -1, "sp-object-unref")
218     {}
219 };
220 
221 }
222 
getId() const223 gchar const* SPObject::getId() const {
224     return id;
225 }
226 
getRepr()227 Inkscape::XML::Node * SPObject::getRepr() {
228     return repr;
229 }
230 
getRepr() const231 Inkscape::XML::Node const* SPObject::getRepr() const{
232     return repr;
233 }
234 
235 
sp_object_ref(SPObject * object,SPObject * owner)236 SPObject *sp_object_ref(SPObject *object, SPObject *owner)
237 {
238     g_return_val_if_fail(object != nullptr, NULL);
239     g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
240     g_return_val_if_fail(!owner || SP_IS_OBJECT(owner), NULL);
241 
242     Inkscape::Debug::EventTracker<RefEvent> tracker(object);
243 
244     object->refCount++;
245 
246     return object;
247 }
248 
sp_object_unref(SPObject * object,SPObject * owner)249 SPObject *sp_object_unref(SPObject *object, SPObject *owner)
250 {
251     g_return_val_if_fail(object != nullptr, NULL);
252     g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
253     g_return_val_if_fail(!owner || SP_IS_OBJECT(owner), NULL);
254 
255     Inkscape::Debug::EventTracker<UnrefEvent> tracker(object);
256 
257     object->refCount--;
258 
259     if (object->refCount <= 0) {
260         delete object;
261     }
262 
263     return nullptr;
264 }
265 
hrefObject(SPObject * owner)266 void SPObject::hrefObject(SPObject* owner)
267 {
268     // if (owner) std::cout << "  owner: " << *owner << std::endl;
269 
270     // If owner is a clone, do not increase hrefcount, it's already href'ed by original.
271     if (!owner || !owner->cloned) {
272         hrefcount++;
273         _updateTotalHRefCount(1);
274     }
275 
276     if(owner)
277         hrefList.push_front(owner);
278 }
279 
unhrefObject(SPObject * owner)280 void SPObject::unhrefObject(SPObject* owner)
281 {
282     g_return_if_fail(hrefcount > 0);
283 
284     if (!owner || !owner->cloned) {
285         hrefcount--;
286     }
287 
288     _updateTotalHRefCount(-1);
289 
290     if(owner)
291         hrefList.remove(owner);
292 }
293 
_updateTotalHRefCount(int increment)294 void SPObject::_updateTotalHRefCount(int increment) {
295     SPObject *topmost_collectable = nullptr;
296     for ( SPObject *iter = this ; iter ; iter = iter->parent ) {
297         iter->_total_hrefcount += increment;
298         if ( iter->_total_hrefcount < iter->hrefcount ) {
299             g_critical("HRefs overcounted");
300         }
301         if ( iter->_total_hrefcount == 0 &&
302              iter->_collection_policy != COLLECT_WITH_PARENT )
303         {
304             topmost_collectable = iter;
305         }
306     }
307     if (topmost_collectable) {
308         topmost_collectable->requestOrphanCollection();
309     }
310 }
311 
isAncestorOf(SPObject const * object) const312 bool SPObject::isAncestorOf(SPObject const *object) const {
313     g_return_val_if_fail(object != nullptr, false);
314     object = object->parent;
315     while (object) {
316         if ( object == this ) {
317             return true;
318         }
319         object = object->parent;
320     }
321     return false;
322 }
323 
324 namespace {
325 
same_objects(SPObject const & a,SPObject const & b)326 bool same_objects(SPObject const &a, SPObject const &b) {
327     return &a == &b;
328 }
329 
330 }
331 
nearestCommonAncestor(SPObject const * object) const332 SPObject const *SPObject::nearestCommonAncestor(SPObject const *object) const {
333     g_return_val_if_fail(object != nullptr, NULL);
334 
335     using Inkscape::Algorithms::longest_common_suffix;
336     return longest_common_suffix<SPObject::ConstParentIterator>(this, object, nullptr, &same_objects);
337 }
338 
AncestorSon(SPObject const * obj,SPObject const * ancestor)339 static SPObject const *AncestorSon(SPObject const *obj, SPObject const *ancestor) {
340     SPObject const *result = nullptr;
341     if ( obj && ancestor ) {
342         if (obj->parent == ancestor) {
343             result = obj;
344         } else {
345             result = AncestorSon(obj->parent, ancestor);
346         }
347     }
348     return result;
349 }
350 
sp_object_compare_position(SPObject const * first,SPObject const * second)351 int sp_object_compare_position(SPObject const *first, SPObject const *second)
352 {
353     int result = 0;
354     if (first != second) {
355         SPObject const *ancestor = first->nearestCommonAncestor(second);
356         // Need a common ancestor to be able to compare
357         if ( ancestor ) {
358             // we have an object and its ancestor (should not happen when sorting selection)
359             if (ancestor == first) {
360                 result = 1;
361             } else if (ancestor == second) {
362                 result = -1;
363             } else {
364                 SPObject const *to_first = AncestorSon(first, ancestor);
365                 SPObject const *to_second = AncestorSon(second, ancestor);
366 
367                 g_assert(to_second->parent == to_first->parent);
368 
369                 result = sp_repr_compare_position(to_first->getRepr(), to_second->getRepr());
370             }
371         }
372     }
373     return result;
374 }
375 
sp_object_compare_position_bool(SPObject const * first,SPObject const * second)376 bool sp_object_compare_position_bool(SPObject const *first, SPObject const *second){
377     return sp_object_compare_position(first,second)<0;
378 }
379 
380 
appendChildRepr(Inkscape::XML::Node * repr)381 SPObject *SPObject::appendChildRepr(Inkscape::XML::Node *repr) {
382     if ( !cloned ) {
383         getRepr()->appendChild(repr);
384         return document->getObjectByRepr(repr);
385     } else {
386         g_critical("Attempt to append repr as child of cloned object");
387         return nullptr;
388     }
389 }
390 
setCSS(SPCSSAttr * css,gchar const * attr)391 void SPObject::setCSS(SPCSSAttr *css, gchar const *attr)
392 {
393     g_assert(this->getRepr() != nullptr);
394     sp_repr_css_set(this->getRepr(), css, attr);
395 }
396 
changeCSS(SPCSSAttr * css,gchar const * attr)397 void SPObject::changeCSS(SPCSSAttr *css, gchar const *attr)
398 {
399     g_assert(this->getRepr() != nullptr);
400     sp_repr_css_change(this->getRepr(), css, attr);
401 }
402 
childList(bool add_ref,Action)403 std::vector<SPObject*> SPObject::childList(bool add_ref, Action) {
404     std::vector<SPObject*> l;
405     for (auto& child: children) {
406         if (add_ref) {
407             sp_object_ref(&child);
408         }
409         l.push_back(&child);
410     }
411     return l;
412 }
413 
label() const414 gchar const *SPObject::label() const {
415     return _label;
416 }
417 
defaultLabel() const418 gchar const *SPObject::defaultLabel() const {
419     if (_label) {
420         return _label;
421     } else {
422         if (!_default_label) {
423             if (getId()) {
424                 _default_label = g_strdup_printf("#%s", getId());
425             } else if (getRepr()) {
426                 _default_label = g_strdup_printf("<%s>", getRepr()->name());
427             } else {
428                 _default_label = g_strdup("Default label");
429             }
430         }
431         return _default_label;
432     }
433 }
434 
setLabel(gchar const * label)435 void SPObject::setLabel(gchar const *label)
436 {
437     getRepr()->setAttribute("inkscape:label", label);
438 }
439 
440 
requestOrphanCollection()441 void SPObject::requestOrphanCollection() {
442     g_return_if_fail(document != nullptr);
443     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
444 
445     // do not remove style or script elements (Bug #276244)
446     if (dynamic_cast<SPStyleElem *>(this)) {
447         // leave it
448     } else if (dynamic_cast<SPScript *>(this)) {
449         // leave it
450     } else if ((! prefs->getBool("/options/cleanupswatches/value", false)) && SP_IS_PAINT_SERVER(this) && static_cast<SPPaintServer*>(this)->isSwatch() ) {
451         // leave it
452     } else if (IS_COLORPROFILE(this)) {
453         // leave it
454     } else if (dynamic_cast<LivePathEffectObject *>(this)) {
455         document->queueForOrphanCollection(this);
456     } else {
457         document->queueForOrphanCollection(this);
458 
459         /** \todo
460          * This is a temporary hack added to make fill&stroke rebuild its
461          * gradient list when the defs are vacuumed.  gradient-vector.cpp
462          * listens to the modified signal on defs, and now we give it that
463          * signal.  Mental says that this should be made automatic by
464          * merging SPObjectGroup with SPObject; SPObjectGroup would issue
465          * this signal automatically. Or maybe just derive SPDefs from
466          * SPObjectGroup?
467          */
468 
469         this->requestModified(SP_OBJECT_CHILD_MODIFIED_FLAG);
470     }
471 }
472 
_sendDeleteSignalRecursive()473 void SPObject::_sendDeleteSignalRecursive() {
474     for (auto& child: children) {
475         child._delete_signal.emit(&child);
476         child._sendDeleteSignalRecursive();
477     }
478 }
479 
deleteObject(bool propagate,bool propagate_descendants)480 void SPObject::deleteObject(bool propagate, bool propagate_descendants)
481 {
482     sp_object_ref(this, nullptr);
483     if ( SP_IS_LPE_ITEM(this) && SP_LPE_ITEM(this)->hasPathEffect()) {
484         SP_LPE_ITEM(this)->removeAllPathEffects(false);
485     }
486     if (propagate) {
487         _delete_signal.emit(this);
488     }
489     if (propagate_descendants) {
490         this->_sendDeleteSignalRecursive();
491     }
492 
493     Inkscape::XML::Node *repr = getRepr();
494     if (repr && repr->parent()) {
495         sp_repr_unparent(repr);
496     }
497 
498     if (_successor) {
499         _successor->deleteObject(propagate, propagate_descendants);
500     }
501     sp_object_unref(this, nullptr);
502 }
503 
cropToObject(SPObject * except)504 void SPObject::cropToObject(SPObject *except)
505 {
506     std::vector<SPObject*> toDelete;
507     for (auto& child: children) {
508         if (SP_IS_ITEM(&child)) {
509             if (child.isAncestorOf(except)) {
510                 child.cropToObject(except);
511             } else if(&child != except) {
512                 sp_object_ref(&child, nullptr);
513                 toDelete.push_back(&child);
514             }
515         }
516     }
517     for (auto & i : toDelete) {
518         i->deleteObject(true, true);
519         sp_object_unref(i, nullptr);
520     }
521 }
522 
attach(SPObject * object,SPObject * prev)523 void SPObject::attach(SPObject *object, SPObject *prev)
524 {
525     //g_return_if_fail(parent != NULL);
526     //g_return_if_fail(SP_IS_OBJECT(parent));
527     g_return_if_fail(object != nullptr);
528     g_return_if_fail(SP_IS_OBJECT(object));
529     g_return_if_fail(!prev || SP_IS_OBJECT(prev));
530     g_return_if_fail(!prev || prev->parent == this);
531     g_return_if_fail(!object->parent);
532 
533     sp_object_ref(object, this);
534     object->parent = this;
535     this->_updateTotalHRefCount(object->_total_hrefcount);
536 
537     auto it = children.begin();
538     if (prev != nullptr) {
539         it = ++children.iterator_to(*prev);
540     }
541     children.insert(it, *object);
542 
543     if (!object->xml_space.set)
544         object->xml_space.value = this->xml_space.value;
545 }
546 
reorder(SPObject * obj,SPObject * prev)547 void SPObject::reorder(SPObject* obj, SPObject* prev) {
548     g_return_if_fail(obj != nullptr);
549     g_return_if_fail(obj->parent);
550     g_return_if_fail(obj->parent == this);
551     g_return_if_fail(obj != prev);
552     g_return_if_fail(!prev || prev->parent == obj->parent);
553 
554     auto it = children.begin();
555     if (prev != nullptr) {
556         it = ++children.iterator_to(*prev);
557     }
558 
559     children.splice(it, children, children.iterator_to(*obj));
560 }
561 
detach(SPObject * object)562 void SPObject::detach(SPObject *object)
563 {
564     //g_return_if_fail(parent != NULL);
565     //g_return_if_fail(SP_IS_OBJECT(parent));
566     g_return_if_fail(object != nullptr);
567     g_return_if_fail(SP_IS_OBJECT(object));
568     g_return_if_fail(object->parent == this);
569 
570     children.erase(children.iterator_to(*object));
571     object->releaseReferences();
572 
573     object->parent = nullptr;
574 
575     this->_updateTotalHRefCount(-object->_total_hrefcount);
576     sp_object_unref(object, this);
577 }
578 
get_child_by_repr(Inkscape::XML::Node * repr)579 SPObject *SPObject::get_child_by_repr(Inkscape::XML::Node *repr)
580 {
581     g_return_val_if_fail(repr != nullptr, NULL);
582     SPObject *result = nullptr;
583 
584     if (children.size() > 0 && children.back().getRepr() == repr) {
585         result = &children.back();   // optimization for common scenario
586     } else {
587         for (auto& child: children) {
588             if (child.getRepr() == repr) {
589                 result = &child;
590                 break;
591             }
592         }
593     }
594     return result;
595 }
596 
597 /**
598  * Get closest child to a reference representation. May traverse backwards
599  * until it finds a child SPObject node.
600  *
601  * @param obj Parent object
602  * @param ref Refernece node, may be NULL
603  * @return Child, or NULL if not found
604  */
get_closest_child_by_repr(SPObject & obj,Inkscape::XML::Node * ref)605 static SPObject *get_closest_child_by_repr(SPObject &obj, Inkscape::XML::Node *ref)
606 {
607     for (; ref; ref = ref->prev()) {
608         // The most likely situation is that `ref` is indeed a child of `obj`,
609         // so try that first, before checking getObjectByRepr.
610         if (auto result = obj.get_child_by_repr(ref)) {
611             return result;
612         }
613 
614         // Only continue if `ref` is not an SPObject, but e.g. an XML comment
615         if (obj.document->getObjectByRepr(ref)) {
616             break;
617         }
618     }
619 
620     return nullptr;
621 }
622 
child_added(Inkscape::XML::Node * child,Inkscape::XML::Node * ref)623 void SPObject::child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *ref) {
624     SPObject* object = this;
625 
626     const std::string type_string = NodeTraits::get_type_string(*child);
627 
628     SPObject* ochild = SPFactory::createObject(type_string);
629     if (ochild == nullptr) {
630         // Currently, there are many node types that do not have
631         // corresponding classes in the SPObject tree.
632         // (rdf:RDF, inkscape:clipboard, ...)
633         // Thus, simply ignore this case for now.
634         return;
635     }
636 
637     SPObject *prev = get_closest_child_by_repr(*object, ref);
638     object->attach(ochild, prev);
639     sp_object_unref(ochild, nullptr);
640 
641     ochild->invoke_build(object->document, child, object->cloned);
642 }
643 
release()644 void SPObject::release() {
645     SPObject* object = this;
646     debug("id=%p, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object));
647     auto tmp = children | boost::adaptors::transformed([](SPObject& obj){return &obj;});
648     std::vector<SPObject *> toRelease(tmp.begin(), tmp.end());
649 
650     for (auto& p: toRelease) {
651         object->detach(p);
652     }
653 }
654 
remove_child(Inkscape::XML::Node * child)655 void SPObject::remove_child(Inkscape::XML::Node* child) {
656     debug("id=%p, typename=%s", this, g_type_name_from_instance((GTypeInstance*)this));
657 
658     SPObject *ochild = this->get_child_by_repr(child);
659 
660     // If the xml node has got a corresponding child in the object tree
661     if (ochild) {
662         this->detach(ochild);
663     }
664 }
665 
order_changed(Inkscape::XML::Node * child,Inkscape::XML::Node *,Inkscape::XML::Node * new_ref)666 void SPObject::order_changed(Inkscape::XML::Node *child, Inkscape::XML::Node * /*old_ref*/, Inkscape::XML::Node *new_ref) {
667     SPObject* object = this;
668 
669     SPObject *ochild = object->get_child_by_repr(child);
670     g_return_if_fail(ochild != nullptr);
671     SPObject *prev = get_closest_child_by_repr(*object, new_ref);
672     object->reorder(ochild, prev);
673     ochild->_position_changed_signal.emit(ochild);
674 }
675 
build(SPDocument * document,Inkscape::XML::Node * repr)676 void SPObject::build(SPDocument *document, Inkscape::XML::Node *repr) {
677 
678 #ifdef OBJECT_TRACE
679     objectTrace( "SPObject::build" );
680 #endif
681     SPObject* object = this;
682 
683     /* Nothing specific here */
684     debug("id=%p, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object));
685 
686     object->readAttr(SPAttr::XML_SPACE);
687     object->readAttr(SPAttr::LANG);
688     object->readAttr(SPAttr::XML_LANG);   // "xml:lang" overrides "lang" per spec, read it last.
689     object->readAttr(SPAttr::INKSCAPE_LABEL);
690     object->readAttr(SPAttr::INKSCAPE_COLLECT);
691 
692     // Inherit if not set
693     if (lang.empty() && object->parent) {
694         lang = object->parent->lang;
695     }
696 
697     if(object->cloned && (repr->attribute("id")) ) // The cases where this happens are when the "original" has no id. This happens
698                                                    // if it is a SPString (a TextNode, e.g. in a <title>), or when importing
699                                                    // stuff externally modified to have no id.
700         object->clone_original = document->getObjectById(repr->attribute("id"));
701 
702     for (Inkscape::XML::Node *rchild = repr->firstChild() ; rchild != nullptr; rchild = rchild->next()) {
703         const std::string typeString = NodeTraits::get_type_string(*rchild);
704 
705         SPObject* child = SPFactory::createObject(typeString);
706         if (child == nullptr) {
707             // Currently, there are many node types that do not have
708             // corresponding classes in the SPObject tree.
709             // (rdf:RDF, inkscape:clipboard, ...)
710             // Thus, simply ignore this case for now.
711             continue;
712         }
713 
714         object->attach(child, object->lastChild());
715         sp_object_unref(child, nullptr);
716         child->invoke_build(document, rchild, object->cloned);
717     }
718 
719 #ifdef OBJECT_TRACE
720     objectTrace( "SPObject::build", false );
721 #endif
722 }
723 
invoke_build(SPDocument * document,Inkscape::XML::Node * repr,unsigned int cloned)724 void SPObject::invoke_build(SPDocument *document, Inkscape::XML::Node *repr, unsigned int cloned)
725 {
726 #ifdef OBJECT_TRACE
727     objectTrace( "SPObject::invoke_build" );
728 #endif
729     debug("id=%p, typename=%s", this, g_type_name_from_instance((GTypeInstance*)this));
730 
731     //g_assert(object != NULL);
732     //g_assert(SP_IS_OBJECT(object));
733     g_assert(document != nullptr);
734     g_assert(repr != nullptr);
735 
736     g_assert(this->document == nullptr);
737     g_assert(this->repr == nullptr);
738     g_assert(this->getId() == nullptr);
739 
740     /* Bookkeeping */
741 
742     this->document = document;
743     this->repr = repr;
744     if (!cloned) {
745         Inkscape::GC::anchor(repr);
746     }
747     this->cloned = cloned;
748 
749     /* Invoke derived methods, if any */
750     this->build(document, repr);
751 
752     if ( !cloned ) {
753         this->document->bindObjectToRepr(this->repr, this);
754 
755         if (Inkscape::XML::id_permitted(this->repr)) {
756             /* If we are not cloned, and not seeking, force unique id */
757             gchar const *id = this->repr->attribute("id");
758             if (!document->isSeeking()) {
759                 {
760                     gchar *realid = sp_object_get_unique_id(this, id);
761                     g_assert(realid != nullptr);
762 
763                     this->document->bindObjectToId(realid, this);
764                     SPObjectImpl::setId(this, realid);
765                     g_free(realid);
766                 }
767 
768                 /* Redefine ID, if required */
769                 if ((id == nullptr) || (std::strcmp(id, this->getId()) != 0)) {
770                     this->repr->setAttribute("id", this->getId());
771                 }
772             } else if (id) {
773                 // bind if id, but no conflict -- otherwise, we can expect
774                 // a subsequent setting of the id attribute
775                 if (!this->document->getObjectById(id)) {
776                     this->document->bindObjectToId(id, this);
777                     SPObjectImpl::setId(this, id);
778                 }
779             }
780         }
781     } else {
782         g_assert(this->getId() == nullptr);
783     }
784 
785 
786     /* Signalling (should be connected AFTER processing derived methods */
787     sp_repr_add_listener(repr, &object_event_vector, this);
788 
789 #ifdef OBJECT_TRACE
790     objectTrace( "SPObject::invoke_build", false );
791 #endif
792 }
793 
getIntAttribute(char const * key,int def)794 int SPObject::getIntAttribute(char const *key, int def)
795 {
796     sp_repr_get_int(getRepr(),key,&def);
797     return def;
798 }
799 
getPosition()800 unsigned SPObject::getPosition(){
801     g_assert(this->repr);
802 
803     return repr->position();
804 }
805 
appendChild(Inkscape::XML::Node * child)806 void SPObject::appendChild(Inkscape::XML::Node *child) {
807     g_assert(this->repr);
808 
809     repr->appendChild(child);
810 }
811 
nthChild(unsigned index)812 SPObject* SPObject::nthChild(unsigned index) {
813     g_assert(this->repr);
814     if (hasChildren()) {
815         std::vector<SPObject*> l;
816         unsigned counter = 0;
817         for (auto& child: children) {
818             if (counter == index) {
819                 return &child;
820             }
821             counter++;
822         }
823     }
824     return nullptr;
825 }
826 
addChild(Inkscape::XML::Node * child,Inkscape::XML::Node * prev)827 void SPObject::addChild(Inkscape::XML::Node *child, Inkscape::XML::Node * prev)
828 {
829     g_assert(this->repr);
830 
831     repr->addChild(child,prev);
832 }
833 
releaseReferences()834 void SPObject::releaseReferences() {
835     g_assert(this->document);
836     g_assert(this->repr);
837 
838     sp_repr_remove_listener_by_data(this->repr, this);
839 
840     this->_release_signal.emit(this);
841 
842     this->release();
843 
844     /* all hrefs should be released by the "release" handlers */
845     g_assert(this->hrefcount == 0);
846 
847     if (!cloned) {
848         if (this->id) {
849             this->document->bindObjectToId(this->id, nullptr);
850         }
851         g_free(this->id);
852         this->id = nullptr;
853 
854         g_free(this->_default_label);
855         this->_default_label = nullptr;
856 
857         this->document->bindObjectToRepr(this->repr, nullptr);
858 
859         Inkscape::GC::release(this->repr);
860     } else {
861         g_assert(!this->id);
862     }
863 
864     // style belongs to SPObject, we should not need to unref here.
865     // if (this->style) {
866     //     this->style = sp_style_unref(this->style);
867     // }
868 
869     this->document = nullptr;
870     this->repr = nullptr;
871 }
872 
873 
getPrev()874 SPObject *SPObject::getPrev()
875 {
876     SPObject *prev = nullptr;
877     if (parent && !parent->children.empty() && &parent->children.front() != this) {
878         prev = &*(--parent->children.iterator_to(*this));
879     }
880     return prev;
881 }
882 
getNext()883 SPObject* SPObject::getNext()
884 {
885     SPObject *next = nullptr;
886     if (parent && !parent->children.empty() && &parent->children.back() != this) {
887         next = &*(++parent->children.iterator_to(*this));
888     }
889     return next;
890 }
891 
repr_child_added(Inkscape::XML::Node *,Inkscape::XML::Node * child,Inkscape::XML::Node * ref,gpointer data)892 void SPObject::repr_child_added(Inkscape::XML::Node * /*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, gpointer data)
893 {
894     SPObject *object = SP_OBJECT(data);
895 
896     object->child_added(child, ref);
897 }
898 
repr_child_removed(Inkscape::XML::Node *,Inkscape::XML::Node * child,Inkscape::XML::Node *,gpointer data)899 void SPObject::repr_child_removed(Inkscape::XML::Node * /*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node * /*ref*/, gpointer data)
900 {
901     SPObject *object = SP_OBJECT(data);
902 
903     object->remove_child(child);
904 }
905 
repr_order_changed(Inkscape::XML::Node *,Inkscape::XML::Node * child,Inkscape::XML::Node * old,Inkscape::XML::Node * newer,gpointer data)906 void SPObject::repr_order_changed(Inkscape::XML::Node * /*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node *old, Inkscape::XML::Node *newer, gpointer data)
907 {
908     SPObject *object = SP_OBJECT(data);
909 
910     object->order_changed(child, old, newer);
911 }
912 
set(SPAttr key,gchar const * value)913 void SPObject::set(SPAttr key, gchar const* value) {
914 
915 #ifdef OBJECT_TRACE
916     std::stringstream temp;
917     temp << "SPObject::set: " << key  << " " << (value?value:"null");
918     objectTrace( temp.str() );
919 #endif
920 
921     g_assert(key != SPAttr::INVALID);
922 
923     SPObject* object = this;
924 
925     switch (key) {
926 
927         case SPAttr::ID:
928 
929             //XML Tree being used here.
930             if ( !object->cloned && object->getRepr()->type() == Inkscape::XML::NodeType::ELEMENT_NODE ) {
931                 SPDocument *document=object->document;
932                 SPObject *conflict=nullptr;
933 
934                 gchar const *new_id = value;
935 
936                 if (new_id) {
937                     conflict = document->getObjectById((char const *)new_id);
938                 }
939 
940                 if ( conflict && conflict != object ) {
941                     if (!document->isSeeking()) {
942                         sp_object_ref(conflict, nullptr);
943                         // give the conflicting object a new ID
944                         gchar *new_conflict_id = sp_object_get_unique_id(conflict, nullptr);
945                         conflict->setAttribute("id", new_conflict_id);
946                         g_free(new_conflict_id);
947                         sp_object_unref(conflict, nullptr);
948                     } else {
949                         new_id = nullptr;
950                     }
951                 }
952 
953                 if (object->getId()) {
954                     document->bindObjectToId(object->getId(), nullptr);
955                     SPObjectImpl::setId(object, nullptr);
956                 }
957 
958                 if (new_id) {
959                     SPObjectImpl::setId(object, new_id);
960                     document->bindObjectToId(object->getId(), object);
961                 }
962 
963                 g_free(object->_default_label);
964                 object->_default_label = nullptr;
965             }
966             break;
967 
968         case SPAttr::INKSCAPE_LABEL:
969             g_free(object->_label);
970             if (value) {
971                 object->_label = g_strdup(value);
972             } else {
973                 object->_label = nullptr;
974             }
975             g_free(object->_default_label);
976             object->_default_label = nullptr;
977             break;
978 
979         case SPAttr::INKSCAPE_COLLECT:
980             if ( value && !std::strcmp(value, "always") ) {
981                 object->setCollectionPolicy(SPObject::ALWAYS_COLLECT);
982             } else {
983                 object->setCollectionPolicy(SPObject::COLLECT_WITH_PARENT);
984             }
985             break;
986 
987         case SPAttr::XML_SPACE:
988             if (value && !std::strcmp(value, "preserve")) {
989                 object->xml_space.value = SP_XML_SPACE_PRESERVE;
990                 object->xml_space.set = TRUE;
991             } else if (value && !std::strcmp(value, "default")) {
992                 object->xml_space.value = SP_XML_SPACE_DEFAULT;
993                 object->xml_space.set = TRUE;
994             } else if (object->parent) {
995                 SPObject *parent;
996                 parent = object->parent;
997                 object->xml_space.value = parent->xml_space.value;
998             }
999             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1000             break;
1001 
1002         case SPAttr::LANG:
1003             if (value) {
1004                 lang = value;
1005                 // To do: sanity check
1006             }
1007             break;
1008 
1009         case SPAttr::XML_LANG:
1010             if (value) {
1011                 lang = value;
1012                 // To do: sanity check
1013             }
1014             break;
1015 
1016         case SPAttr::STYLE:
1017             object->style->readFromObject( object );
1018             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1019             break;
1020 
1021         default:
1022             break;
1023     }
1024 #ifdef OBJECT_TRACE
1025     objectTrace( "SPObject::set", false );
1026 #endif
1027 }
1028 
setKeyValue(SPAttr key,gchar const * value)1029 void SPObject::setKeyValue(SPAttr key, gchar const *value)
1030 {
1031     //g_assert(object != NULL);
1032     //g_assert(SP_IS_OBJECT(object));
1033 
1034     this->set(key, value);
1035 }
1036 
readAttr(SPAttr keyid)1037 void SPObject::readAttr(SPAttr keyid)
1038 {
1039     char const *key = sp_attribute_name(keyid);
1040 
1041     assert(key != nullptr);
1042     assert(getRepr() != nullptr);
1043 
1044     char const *value = getRepr()->attribute(key);
1045 
1046     setKeyValue(keyid, value);
1047 }
1048 
readAttr(gchar const * key)1049 void SPObject::readAttr(gchar const *key)
1050 {
1051     //g_assert(object != NULL);
1052     //g_assert(SP_IS_OBJECT(object));
1053     g_assert(key != nullptr);
1054 
1055     //XML Tree being used here.
1056     g_assert(this->getRepr() != nullptr);
1057 
1058     auto keyid = sp_attribute_lookup(key);
1059     if (keyid != SPAttr::INVALID) {
1060         /* Retrieve the 'key' attribute from the object's XML representation */
1061         gchar const *value = getRepr()->attribute(key);
1062 
1063         setKeyValue(keyid, value);
1064     }
1065 }
1066 
repr_attr_changed(Inkscape::XML::Node *,gchar const * key,gchar const *,gchar const *,bool is_interactive,gpointer data)1067 void SPObject::repr_attr_changed(Inkscape::XML::Node * /*repr*/, gchar const *key, gchar const * /*oldval*/, gchar const * /*newval*/, bool is_interactive, gpointer data)
1068 {
1069     SPObject *object = SP_OBJECT(data);
1070 
1071     object->readAttr(key);
1072 
1073     // manual changes to extension attributes require the normal
1074     // attributes, which depend on them, to be updated immediately
1075     if (is_interactive) {
1076         object->updateRepr(0);
1077     }
1078 }
1079 
repr_content_changed(Inkscape::XML::Node *,gchar const *,gchar const *,gpointer data)1080 void SPObject::repr_content_changed(Inkscape::XML::Node * /*repr*/, gchar const * /*oldcontent*/, gchar const * /*newcontent*/, gpointer data)
1081 {
1082     SPObject *object = SP_OBJECT(data);
1083 
1084     object->read_content();
1085 }
1086 
1087 /**
1088  * Return string representation of space value.
1089  */
sp_xml_get_space_string(unsigned int space)1090 static gchar const *sp_xml_get_space_string(unsigned int space)
1091 {
1092     switch (space) {
1093         case SP_XML_SPACE_DEFAULT:
1094             return "default";
1095         case SP_XML_SPACE_PRESERVE:
1096             return "preserve";
1097         default:
1098             return nullptr;
1099     }
1100 }
1101 
write(Inkscape::XML::Document * doc,Inkscape::XML::Node * repr,guint flags)1102 Inkscape::XML::Node* SPObject::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) {
1103 #ifdef OBJECT_TRACE
1104     objectTrace( "SPObject::write" );
1105 #endif
1106 
1107     if (!repr && (flags & SP_OBJECT_WRITE_BUILD)) {
1108         repr = this->getRepr()->duplicate(doc);
1109         if (!( flags & SP_OBJECT_WRITE_EXT )) {
1110             repr->removeAttribute("inkscape:collect");
1111         }
1112     } else if (repr) {
1113         repr->setAttribute("id", this->getId());
1114 
1115         if (this->xml_space.set) {
1116             char const *xml_space;
1117             xml_space = sp_xml_get_space_string(this->xml_space.value);
1118             repr->setAttribute("xml:space", xml_space);
1119         }
1120 
1121         if ( flags & SP_OBJECT_WRITE_EXT &&
1122              this->collectionPolicy() == SPObject::ALWAYS_COLLECT )
1123         {
1124             repr->setAttribute("inkscape:collect", "always");
1125         } else {
1126             repr->removeAttribute("inkscape:collect");
1127         }
1128 
1129         if (style) {
1130             // Write if property set by style attribute in this object
1131             Glib::ustring s =
1132                 style->write(SPStyleSrc::STYLE_PROP);
1133 
1134             // Write style attributes (SPStyleSrc::ATTRIBUTE) back to xml object
1135             bool any_written = false;
1136             auto properties = style->properties();
1137             for (auto * prop : properties) {
1138                 if(prop->shall_write(SP_STYLE_FLAG_IFSET | SP_STYLE_FLAG_IFSRC, SPStyleSrc::ATTRIBUTE)) {
1139                     // WARNING: We don't know for sure if the css names are the same as the attribute names
1140                     repr->setAttributeOrRemoveIfEmpty(prop->name(), prop->get_value());
1141                     any_written = true;
1142                 }
1143             }
1144             if(any_written) {
1145                 // We need to ask the object to update the style and keep things in sync
1146                 // see `case SPAttr::STYLE` above for how the style attr itself does this.
1147                 style->readFromObject(this);
1148                 requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1149             }
1150 
1151             // Check for valid attributes. This may be time consuming.
1152             // It is useful, though, for debugging Inkscape code.
1153             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1154             if( prefs->getBool("/options/svgoutput/check_on_editing") ) {
1155 
1156                 unsigned int flags = sp_attribute_clean_get_prefs();
1157                 Glib::ustring s_cleaned = sp_attribute_clean_style( repr, s.c_str(), flags );
1158             }
1159 
1160             repr->setAttributeOrRemoveIfEmpty("style", s);
1161         } else {
1162             /** \todo I'm not sure what to do in this case.  Bug #1165868
1163              * suggests that it can arise, but the submitter doesn't know
1164              * how to do so reliably.  The main two options are either
1165              * leave repr's style attribute unchanged, or explicitly clear it.
1166              * Must also consider what to do with property attributes for
1167              * the element; see below.
1168              */
1169             char const *style_str = repr->attribute("style");
1170             if (!style_str) {
1171                 style_str = "NULL";
1172             }
1173             g_warning("Item's style is NULL; repr style attribute is %s", style_str);
1174         }
1175     }
1176 
1177 #ifdef OBJECT_TRACE
1178     objectTrace( "SPObject::write", false );
1179 #endif
1180     return repr;
1181 }
1182 
updateRepr(unsigned int flags)1183 Inkscape::XML::Node * SPObject::updateRepr(unsigned int flags)
1184 {
1185 #ifdef OBJECT_TRACE
1186     objectTrace( "SPObject::updateRepr 1" );
1187 #endif
1188 
1189     if ( !cloned ) {
1190         Inkscape::XML::Node *repr = getRepr();
1191         if (repr) {
1192 #ifdef OBJECT_TRACE
1193             objectTrace( "SPObject::updateRepr 1", false );
1194 #endif
1195             return updateRepr(repr->document(), repr, flags);
1196         } else {
1197             g_critical("Attempt to update non-existent repr");
1198 #ifdef OBJECT_TRACE
1199             objectTrace( "SPObject::updateRepr 1", false );
1200 #endif
1201             return nullptr;
1202         }
1203     } else {
1204         /* cloned objects have no repr */
1205 #ifdef OBJECT_TRACE
1206         objectTrace( "SPObject::updateRepr 1", false );
1207 #endif
1208         return nullptr;
1209     }
1210 }
1211 
updateRepr(Inkscape::XML::Document * doc,Inkscape::XML::Node * repr,unsigned int flags)1212 Inkscape::XML::Node * SPObject::updateRepr(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, unsigned int flags)
1213 {
1214 #ifdef OBJECT_TRACE
1215     objectTrace( "SPObject::updateRepr 2" );
1216 #endif
1217 
1218     g_assert(doc != nullptr);
1219 
1220     if (cloned) {
1221         /* cloned objects have no repr */
1222 #ifdef OBJECT_TRACE
1223         objectTrace( "SPObject::updateRepr 2", false );
1224 #endif
1225         return nullptr;
1226     }
1227 
1228     if (!(flags & SP_OBJECT_WRITE_BUILD) && !repr) {
1229         repr = getRepr();
1230     }
1231 
1232 #ifdef OBJECT_TRACE
1233     Inkscape::XML::Node *node = write(doc, repr, flags);
1234     objectTrace( "SPObject::updateRepr 2", false );
1235     return node;
1236 #else
1237     return this->write(doc, repr, flags);
1238 #endif
1239 
1240 }
1241 
1242 /* Modification */
1243 
requestDisplayUpdate(unsigned int flags)1244 void SPObject::requestDisplayUpdate(unsigned int flags)
1245 {
1246     g_return_if_fail( this->document != nullptr );
1247 
1248 #ifndef NDEBUG
1249     // expect no nested update calls
1250     if (document->update_in_progress) {
1251         // observed with LPE on <rect>
1252         g_print("WARNING: Requested update while update in progress, counter = %d\n", document->update_in_progress);
1253     }
1254 #endif
1255 
1256     /* requestModified must be used only to set one of SP_OBJECT_MODIFIED_FLAG or
1257      * SP_OBJECT_CHILD_MODIFIED_FLAG */
1258     g_return_if_fail(!(flags & SP_OBJECT_PARENT_MODIFIED_FLAG));
1259     g_return_if_fail((flags & SP_OBJECT_MODIFIED_FLAG) || (flags & SP_OBJECT_CHILD_MODIFIED_FLAG));
1260     g_return_if_fail(!((flags & SP_OBJECT_MODIFIED_FLAG) && (flags & SP_OBJECT_CHILD_MODIFIED_FLAG)));
1261 
1262 #ifdef OBJECT_TRACE
1263     objectTrace( "SPObject::requestDisplayUpdate" );
1264 #endif
1265 
1266     bool already_propagated = (!(this->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG)));
1267     //https://stackoverflow.com/a/7841333
1268     if ((this->uflags & flags) !=  flags ) {
1269         this->uflags |= flags;
1270     }
1271     /* If requestModified has already been called on this object or one of its children, then we
1272      * don't need to set CHILD_MODIFIED on our ancestors because it's already been done.
1273      */
1274     if (already_propagated) {
1275         if(this->document) {
1276             if (parent) {
1277                 parent->requestDisplayUpdate(SP_OBJECT_CHILD_MODIFIED_FLAG);
1278             } else {
1279                 this->document->requestModified();
1280             }
1281         }
1282     }
1283 
1284 #ifdef OBJECT_TRACE
1285     objectTrace( "SPObject::requestDisplayUpdate", false );
1286 #endif
1287 
1288 }
1289 
updateDisplay(SPCtx * ctx,unsigned int flags)1290 void SPObject::updateDisplay(SPCtx *ctx, unsigned int flags)
1291 {
1292     g_return_if_fail(!(flags & ~SP_OBJECT_MODIFIED_CASCADE));
1293 
1294 #ifdef OBJECT_TRACE
1295     objectTrace( "SPObject::updateDisplay" );
1296 #endif
1297 
1298     assert(++(document->update_in_progress));
1299 
1300 #ifdef SP_OBJECT_DEBUG_CASCADE
1301     g_print("Update %s:%s %x %x %x\n", g_type_name_from_instance((GTypeInstance *) this), getId(), flags, this->uflags, this->mflags);
1302 #endif
1303 
1304     /* Get this flags */
1305     flags |= this->uflags;
1306     /* Copy flags to modified cascade for later processing */
1307     this->mflags |= this->uflags;
1308     /* We have to clear flags here to allow rescheduling update */
1309     this->uflags = 0;
1310 
1311     // Merge style if we have good reasons to think that parent style is changed */
1312     /** \todo
1313      * I am not sure whether we should check only propagated
1314      * flag. We are currently assuming that style parsing is
1315      * done immediately. I think this is correct (Lauris).
1316      */
1317     if (style) {
1318         if ((flags & SP_OBJECT_STYLESHEET_MODIFIED_FLAG)) {
1319             style->readFromObject(this);
1320         } else if (parent && (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) && (flags & SP_OBJECT_PARENT_MODIFIED_FLAG)) {
1321             style->cascade( this->parent->style );
1322         }
1323     }
1324 
1325     try
1326     {
1327         this->update(ctx, flags);
1328     }
1329     catch(...)
1330     {
1331         /** \todo
1332         * in case of catching an exception we need to inform the user somehow that the document is corrupted
1333         * maybe by implementing an document flag documentOk
1334         * or by a modal error dialog
1335         */
1336         g_warning("SPObject::updateDisplay(SPCtx *ctx, unsigned int flags) : throw in ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update(this, ctx, flags);");
1337     }
1338 
1339     assert((document->update_in_progress)--);
1340 
1341 #ifdef OBJECT_TRACE
1342     objectTrace( "SPObject::updateDisplay", false );
1343 #endif
1344 }
1345 
requestModified(unsigned int flags)1346 void SPObject::requestModified(unsigned int flags)
1347 {
1348     g_return_if_fail( this->document != nullptr );
1349 
1350     /* requestModified must be used only to set one of SP_OBJECT_MODIFIED_FLAG or
1351      * SP_OBJECT_CHILD_MODIFIED_FLAG */
1352     g_return_if_fail(!(flags & SP_OBJECT_PARENT_MODIFIED_FLAG));
1353     g_return_if_fail((flags & SP_OBJECT_MODIFIED_FLAG) || (flags & SP_OBJECT_CHILD_MODIFIED_FLAG));
1354     g_return_if_fail(!((flags & SP_OBJECT_MODIFIED_FLAG) && (flags & SP_OBJECT_CHILD_MODIFIED_FLAG)));
1355 
1356 #ifdef OBJECT_TRACE
1357     objectTrace( "SPObject::requestModified" );
1358 #endif
1359 
1360     bool already_propagated = (!(this->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG)));
1361 
1362     this->mflags |= flags;
1363 
1364     /* If requestModified has already been called on this object or one of its children, then we
1365      * don't need to set CHILD_MODIFIED on our ancestors because it's already been done.
1366      */
1367     if (already_propagated) {
1368         if (parent) {
1369             parent->requestModified(SP_OBJECT_CHILD_MODIFIED_FLAG);
1370         } else {
1371             document->requestModified();
1372         }
1373     }
1374 #ifdef OBJECT_TRACE
1375     objectTrace( "SPObject::requestModified", false );
1376 #endif
1377 }
1378 
emitModified(unsigned int flags)1379 void SPObject::emitModified(unsigned int flags)
1380 {
1381     /* only the MODIFIED_CASCADE flag is legal here */
1382     g_return_if_fail(!(flags & ~SP_OBJECT_MODIFIED_CASCADE));
1383 
1384 #ifdef OBJECT_TRACE
1385     objectTrace( "SPObject::emitModified", true, flags );
1386 #endif
1387 
1388 #ifdef SP_OBJECT_DEBUG_CASCADE
1389     g_print("Modified %s:%s %x %x %x\n", g_type_name_from_instance((GTypeInstance *) this), getId(), flags, this->uflags, this->mflags);
1390 #endif
1391 
1392     flags |= this->mflags;
1393     /* We have to clear mflags beforehand, as signal handlers may
1394      * make changes and therefore queue new modification notifications
1395      * themselves. */
1396     this->mflags = 0;
1397 
1398     sp_object_ref(this);
1399 
1400     this->modified(flags);
1401 
1402     _modified_signal.emit(this, flags);
1403     sp_object_unref(this);
1404 
1405 #ifdef OBJECT_TRACE
1406     objectTrace( "SPObject::emitModified", false );
1407 #endif
1408 }
1409 
getTagName(SPException * ex) const1410 gchar const *SPObject::getTagName(SPException *ex) const
1411 {
1412     g_assert(repr != nullptr);
1413     /* If exception is not clear, return */
1414     if (!SP_EXCEPTION_IS_OK(ex)) {
1415         return nullptr;
1416     }
1417 
1418     /// \todo fixme: Exception if object is NULL? */
1419     //XML Tree being used here.
1420     return getRepr()->name();
1421 }
1422 
getAttribute(gchar const * key,SPException * ex) const1423 gchar const *SPObject::getAttribute(gchar const *key, SPException *ex) const
1424 {
1425     g_assert(this->repr != nullptr);
1426     /* If exception is not clear, return */
1427     if (!SP_EXCEPTION_IS_OK(ex)) {
1428         return nullptr;
1429     }
1430 
1431     /// \todo fixme: Exception if object is NULL? */
1432     //XML Tree being used here.
1433     return (gchar const *) getRepr()->attribute(key);
1434 }
1435 
setAttribute(Inkscape::Util::const_char_ptr key,Inkscape::Util::const_char_ptr value,SPException * ex)1436 void SPObject::setAttribute(Inkscape::Util::const_char_ptr key,
1437                             Inkscape::Util::const_char_ptr value, SPException *ex)
1438 {
1439     g_assert(this->repr != nullptr);
1440     /* If exception is not clear, return */
1441     g_return_if_fail(SP_EXCEPTION_IS_OK(ex));
1442 
1443     /// \todo fixme: Exception if object is NULL? */
1444     //XML Tree being used here.
1445     getRepr()->setAttribute(key, value);
1446 }
1447 
1448 
removeAttribute(gchar const * key,SPException * ex)1449 void SPObject::removeAttribute(gchar const *key, SPException *ex)
1450 {
1451     /* If exception is not clear, return */
1452     g_return_if_fail(SP_EXCEPTION_IS_OK(ex));
1453 
1454     /// \todo fixme: Exception if object is NULL? */
1455     //XML Tree being used here.
1456     getRepr()->removeAttribute(key);
1457 }
1458 
storeAsDouble(gchar const * key,double * val) const1459 bool SPObject::storeAsDouble( gchar const *key, double *val ) const
1460 {
1461     g_assert(this->getRepr()!= nullptr);
1462     return sp_repr_get_double(((Inkscape::XML::Node *)(this->getRepr())),key,val);
1463 }
1464 
1465 /** Helper */
1466 gchar *
sp_object_get_unique_id(SPObject * object,gchar const * id)1467 sp_object_get_unique_id(SPObject    *object,
1468                         gchar const *id)
1469 {
1470     static unsigned long count = 0;
1471 
1472     g_assert(SP_IS_OBJECT(object));
1473 
1474     count++;
1475 
1476     //XML Tree being used here.
1477     gchar const *name = object->getRepr()->name();
1478     g_assert(name != nullptr);
1479 
1480     gchar const *local = std::strchr(name, ':');
1481     if (local) {
1482         name = local + 1;
1483     }
1484 
1485     if (id != nullptr) {
1486         if (object->document->getObjectById(id) == nullptr) {
1487             return g_strdup(id);
1488         }
1489     }
1490 
1491     size_t const name_len = std::strlen(name);
1492     size_t const buflen = name_len + (sizeof(count) * 10 / 4) + 1;
1493     gchar *const buf = (gchar *) g_malloc(buflen);
1494     std::memcpy(buf, name, name_len);
1495     gchar *const count_buf = buf + name_len;
1496     size_t const count_buflen = buflen - name_len;
1497     do {
1498         ++count;
1499         g_snprintf(count_buf, count_buflen, "%lu", count);
1500     } while ( object->document->getObjectById(buf) != nullptr );
1501     return buf;
1502 }
1503 
_requireSVGVersion(Inkscape::Version version)1504 void SPObject::_requireSVGVersion(Inkscape::Version version) {
1505     for ( SPObject::ParentIterator iter=this ; iter ; ++iter ) {
1506         SPObject *object = iter;
1507         if (SP_IS_ROOT(object)) {
1508             SPRoot *root = SP_ROOT(object);
1509             if ( root->version.svg < version ) {
1510                 root->version.svg = version;
1511             }
1512         }
1513     }
1514 }
1515 
1516 // Titles and descriptions
1517 
1518 /* Note:
1519    Titles and descriptions are stored in 'title' and 'desc' child elements
1520    (see section 5.4 of the SVG 1.0 and 1.1 specifications).  The spec allows
1521    an element to have more than one 'title' child element, but strongly
1522    recommends against this and requires using the first one if a choice must
1523    be made.  The same applies to 'desc' elements.  Therefore, these functions
1524    ignore all but the first 'title' child element and first 'desc' child
1525    element, except when deleting a title or description.
1526 
1527    This will change in SVG 2, where multiple 'title' and 'desc' elements will
1528    be allowed with different localized strings.
1529 */
1530 
title() const1531 gchar * SPObject::title() const
1532 {
1533     return getTitleOrDesc("svg:title");
1534 }
1535 
setTitle(gchar const * title,bool verbatim)1536 bool SPObject::setTitle(gchar const *title, bool verbatim)
1537 {
1538     return setTitleOrDesc(title, "svg:title", verbatim);
1539 }
1540 
desc() const1541 gchar * SPObject::desc() const
1542 {
1543     return getTitleOrDesc("svg:desc");
1544 }
1545 
setDesc(gchar const * desc,bool verbatim)1546 bool SPObject::setDesc(gchar const *desc, bool verbatim)
1547 {
1548     return setTitleOrDesc(desc, "svg:desc", verbatim);
1549 }
1550 
getTitleOrDesc(gchar const * svg_tagname) const1551 char * SPObject::getTitleOrDesc(gchar const *svg_tagname) const
1552 {
1553     char *result = nullptr;
1554     SPObject *elem = findFirstChild(svg_tagname);
1555     if ( elem ) {
1556         //This string copy could be avoided by changing
1557         //the return type of SPObject::getTitleOrDesc
1558         //to std::unique_ptr<Glib::ustring>
1559         result = g_strdup(elem->textualContent().c_str());
1560     }
1561     return result;
1562 }
1563 
setTitleOrDesc(gchar const * value,gchar const * svg_tagname,bool verbatim)1564 bool SPObject::setTitleOrDesc(gchar const *value, gchar const *svg_tagname, bool verbatim)
1565 {
1566     if (!verbatim) {
1567         // If the new title/description is just whitespace,
1568         // treat it as though it were NULL.
1569         if (value) {
1570             bool just_whitespace = true;
1571             for (const gchar *cp = value; *cp; ++cp) {
1572                 if (!std::strchr("\r\n \t", *cp)) {
1573                     just_whitespace = false;
1574                     break;
1575                 }
1576             }
1577             if (just_whitespace) {
1578                 value = nullptr;
1579             }
1580         }
1581         // Don't stomp on mark-up if there is no real change.
1582         if (value) {
1583             gchar *current_value = getTitleOrDesc(svg_tagname);
1584             if (current_value) {
1585                 bool different = std::strcmp(current_value, value);
1586                 g_free(current_value);
1587                 if (!different) {
1588                     return false;
1589                 }
1590             }
1591         }
1592     }
1593 
1594     SPObject *elem = findFirstChild(svg_tagname);
1595 
1596     if (value == nullptr) {
1597         if (elem == nullptr) {
1598             return false;
1599         }
1600         // delete the title/description(s)
1601         while (elem) {
1602             elem->deleteObject();
1603             elem = findFirstChild(svg_tagname);
1604         }
1605         return true;
1606     }
1607 
1608     Inkscape::XML::Document *xml_doc = document->getReprDoc();
1609 
1610     if (elem == nullptr) {
1611         // create a new 'title' or 'desc' element, putting it at the
1612         // beginning (in accordance with the spec's recommendations)
1613         Inkscape::XML::Node *xml_elem = xml_doc->createElement(svg_tagname);
1614         repr->addChild(xml_elem, nullptr);
1615         elem = document->getObjectByRepr(xml_elem);
1616         Inkscape::GC::release(xml_elem);
1617     }
1618     else {
1619         // remove the current content of the 'text' or 'desc' element
1620         auto tmp = elem->children | boost::adaptors::transformed([](SPObject& obj) { return &obj; });
1621         std::vector<SPObject*> vec(tmp.begin(), tmp.end());
1622         for (auto &child: vec) {
1623             child->deleteObject();
1624         }
1625     }
1626 
1627     // add the new content
1628     elem->appendChildRepr(xml_doc->createTextNode(value));
1629     return true;
1630 }
1631 
findFirstChild(gchar const * tagname) const1632 SPObject* SPObject::findFirstChild(gchar const *tagname) const
1633 {
1634     for (auto& child: const_cast<SPObject*>(this)->children)
1635     {
1636         if (child.repr->type() == Inkscape::XML::NodeType::ELEMENT_NODE &&
1637             !std::strcmp(child.repr->name(), tagname)) {
1638             return &child;
1639         }
1640     }
1641     return nullptr;
1642 }
1643 
textualContent() const1644 Glib::ustring SPObject::textualContent() const
1645 {
1646     Glib::ustring text;
1647 
1648     for (auto& child: children)
1649     {
1650         Inkscape::XML::NodeType child_type = child.repr->type();
1651 
1652         if (child_type == Inkscape::XML::NodeType::ELEMENT_NODE) {
1653             text += child.textualContent();
1654         }
1655         else if (child_type == Inkscape::XML::NodeType::TEXT_NODE) {
1656             text += child.repr->content();
1657         }
1658     }
1659     return text;
1660 }
1661 
1662 // For debugging: Print SP tree structure.
recursivePrintTree(unsigned level)1663 void SPObject::recursivePrintTree( unsigned level )
1664 {
1665     if (level == 0) {
1666         std::cout << "SP Object Tree" << std::endl;
1667     }
1668     std::cout << "SP: ";
1669     for (unsigned i = 0; i < level; ++i) {
1670         std::cout << "  ";
1671     }
1672     std::cout << (getId()?getId():"No object id")
1673               << " clone: " << std::boolalpha << (bool)cloned
1674               << " hrefcount: " << hrefcount << std::endl;
1675     for (auto& child: children) {
1676         child.recursivePrintTree(level + 1);
1677     }
1678 }
1679 
1680 // Function to allow tracing of program flow through SPObject and derived classes.
1681 // To trace function, add at entrance ('in' = true) and exit of function ('in' = false).
objectTrace(std::string const & text,bool in,unsigned flags)1682 void SPObject::objectTrace( std::string const &text, bool in, unsigned flags ) {
1683     if( in ) {
1684         for (unsigned i = 0; i < indent_level; ++i) {
1685             std::cout << "  ";
1686         }
1687         std::cout << text << ":"
1688                   << " entrance: "
1689                   << (id?id:"null")
1690                   // << " uflags: " << uflags
1691                   // << " mflags: " << mflags
1692                   // << " flags: " << flags
1693                   << std::endl;
1694         ++indent_level;
1695     } else {
1696         --indent_level;
1697         for (unsigned i = 0; i < indent_level; ++i) {
1698             std::cout << "  ";
1699         }
1700         std::cout << text << ":"
1701                   << " exit:     "
1702                   << (id?id:"null")
1703                   // << " uflags: " << uflags
1704                   // << " mflags: " << mflags
1705                   // << " flags: " << flags
1706                   << std::endl;
1707     }
1708 }
1709 
operator <<(std::ostream & out,const SPObject & o)1710 std::ostream &operator<<(std::ostream &out, const SPObject &o)
1711 {
1712     out << (o.getId()?o.getId():"No ID")
1713         << " cloned: " << std::boolalpha << (bool)o.cloned
1714         << " ref: " << o.refCount
1715         << " href: " << o.hrefcount
1716         << " total href: " << o._total_hrefcount;
1717     return out;
1718 }
1719 /*
1720   Local Variables:
1721   mode:c++
1722   c-file-style:"stroustrup"
1723   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1724   indent-tabs-mode:nil
1725   fill-column:99
1726   End:
1727 */
1728 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
1729