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