1 // SPDX-License-Identifier: GPL-2.0-or-later
2 #ifndef SP_OBJECT_H_SEEN
3 #define SP_OBJECT_H_SEEN
4 
5 
6 /*
7  * Authors:
8  *   Lauris Kaplinski <lauris@kaplinski.com>
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 <glibmm/ustring.h>
20 #include "util/const_char_ptr.h"
21 /* SPObject flags */
22 
23 class SPObject;
24 
25 #define SP_OBJECT(obj) (dynamic_cast<SPObject*>((SPObject*)obj))
26 #define SP_IS_OBJECT(obj) (dynamic_cast<const SPObject*>((SPObject*)obj) != NULL)
27 
28 /* Async modification flags */
29 #define SP_OBJECT_MODIFIED_FLAG (1 << 0)
30 #define SP_OBJECT_CHILD_MODIFIED_FLAG (1 << 1)
31 #define SP_OBJECT_PARENT_MODIFIED_FLAG (1 << 2)
32 #define SP_OBJECT_STYLE_MODIFIED_FLAG (1 << 3)
33 #define SP_OBJECT_VIEWPORT_MODIFIED_FLAG (1 << 4)
34 #define SP_OBJECT_USER_MODIFIED_FLAG_A (1 << 5)
35 #define SP_OBJECT_USER_MODIFIED_FLAG_B (1 << 6)
36 #define SP_OBJECT_STYLESHEET_MODIFIED_FLAG (1 << 7)
37 
38 /* Convenience */
39 #define SP_OBJECT_FLAGS_ALL 0xff
40 
41 /* Flags that mark object as modified */
42 /* Object, Child, Style, Viewport, User */
43 #define SP_OBJECT_MODIFIED_STATE (SP_OBJECT_FLAGS_ALL & ~(SP_OBJECT_PARENT_MODIFIED_FLAG))
44 
45 /* Flags that will propagate downstreams */
46 /* Parent, Style, Viewport, User */
47 #define SP_OBJECT_MODIFIED_CASCADE (SP_OBJECT_FLAGS_ALL & ~(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))
48 
49 /* Write flags */
50 #define SP_OBJECT_WRITE_BUILD (1 << 0)
51 #define SP_OBJECT_WRITE_EXT (1 << 1)
52 #define SP_OBJECT_WRITE_ALL (1 << 2)
53 #define SP_OBJECT_WRITE_NO_CHILDREN (1 << 3)
54 
55 #include <cassert>
56 #include <cstddef>
57 #include <sigc++/connection.h>
58 #include <sigc++/functors/slot.h>
59 #include <sigc++/signal.h>
60 #include <vector>
61 #include <boost/intrusive/list.hpp>
62 #include "version.h"
63 #include "util/forward-pointer-iterator.h"
64 
65 enum class SPAttr;
66 
67 class SPCSSAttr;
68 class SPStyle;
69 
70 namespace Inkscape {
71 namespace XML {
72 class Node;
73 struct Document;
74 }
75 }
76 
77 namespace Glib {
78     class ustring;
79 }
80 
81 enum SPExceptionType {
82     SP_NO_EXCEPTION,
83     SP_INDEX_SIZE_ERR,
84     SP_DOMSTRING_SIZE_ERR,
85     SP_HIERARCHY_REQUEST_ERR,
86     SP_WRONG_DOCUMENT_ERR,
87     SP_INVALID_CHARACTER_ERR,
88     SP_NO_DATA_ALLOWED_ERR,
89     SP_NO_MODIFICATION_ALLOWED_ERR,
90     SP_NOT_FOUND_ERR,
91     SP_NOT_SUPPORTED_ERR,
92     SP_INUSE_ATTRIBUTE_ERR,
93     SP_INVALID_STATE_ERR,
94     SP_SYNTAX_ERR,
95     SP_INVALID_MODIFICATION_ERR,
96     SP_NAMESPACE_ERR,
97     SP_INVALID_ACCESS_ERR
98 };
99 
100 /// An attempt to implement exceptions, unused?
101 struct SPException {
102     SPExceptionType code;
103 };
104 
105 #define SP_EXCEPTION_INIT(ex) {(ex)->code = SP_NO_EXCEPTION;}
106 #define SP_EXCEPTION_IS_OK(ex) (!(ex) || ((ex)->code == SP_NO_EXCEPTION))
107 
108 /// Unused
109 struct SPCtx {
110     unsigned int flags;
111 };
112 
113 enum {
114     SP_XML_SPACE_DEFAULT,
115     SP_XML_SPACE_PRESERVE
116 };
117 
118 class SPDocument;
119 
120 /// Internal class consisting of two bits.
121 class SPIXmlSpace {
122 public:
SPIXmlSpace()123     SPIXmlSpace(): set(0), value(SP_XML_SPACE_DEFAULT) {};
124     unsigned int set : 1;
125     unsigned int value : 1;
126 };
127 
128 /*
129  * Refcounting
130  *
131  * Owner is here for debug reasons, you can set it to NULL safely
132  * Ref should return object, NULL is error, unref return always NULL
133  */
134 
135 /**
136  * Increase reference count of object, with possible debugging.
137  *
138  * @param owner If non-NULL, make debug log entry.
139  * @return object, NULL is error.
140  * \pre object points to real object
141  * @todo need to move this to be a member of SPObject.
142  */
143 SPObject *sp_object_ref(SPObject *object, SPObject *owner=nullptr);
144 
145 /**
146  * Decrease reference count of object, with possible debugging and
147  * finalization.
148  *
149  * @param owner If non-NULL, make debug log entry.
150  * @return always NULL
151  * \pre object points to real object
152  * @todo need to move this to be a member of SPObject.
153  */
154 SPObject *sp_object_unref(SPObject *object, SPObject *owner=nullptr);
155 
156 /**
157  * SPObject is an abstract base class of all of the document nodes at the
158  * SVG document level. Each SPObject subclass implements a certain SVG
159  * element node type, or is an abstract base class for different node
160  * types.  The SPObject layer is bound to the SPRepr layer, closely
161  * following the SPRepr mutations via callbacks.  During creation,
162  * SPObject parses and interprets all textual attributes and CSS style
163  * strings of the SPRepr, and later updates the internal state whenever
164  * it receives a signal about a change. The opposite is not true - there
165  * are methods manipulating SPObjects directly and such changes do not
166  * propagate to the SPRepr layer. This is important for implementation of
167  * the undo stack, animations and other features.
168  *
169  * SPObjects are bound to the higher-level container SPDocument, which
170  * provides document level functionality such as the undo stack,
171  * dictionary and so on. Source: doc/architecture.txt
172  */
173 class SPObject {
174 public:
175     enum CollectionPolicy {
176         COLLECT_WITH_PARENT,
177         ALWAYS_COLLECT
178     };
179 
180     SPObject();
181     virtual ~SPObject();
182 
183     unsigned int cloned : 1;
184     SPObject *clone_original;
185     unsigned int uflags : 8;
186     unsigned int mflags : 8;
187     SPIXmlSpace xml_space;
188     Glib::ustring lang;
189     unsigned int hrefcount; /* number of xlink:href references */
190     unsigned int _total_hrefcount; /* our hrefcount + total descendants */
191     SPDocument *document; /* Document we are part of */
192     SPObject *parent; /* Our parent (only one allowed) */
193 
194 private:
195     SPObject(const SPObject&);
196     SPObject& operator=(const SPObject&);
197 
198     char *id; /* Our very own unique id */
199     Inkscape::XML::Node *repr; /* Our xml representation */
200 
201 public:
202     int refCount;
203     std::list<SPObject*> hrefList;
204 
205     /**
206      * Returns the objects current ID string.
207      */
208     char const* getId() const;
209 
210     /**
211      * Returns the XML representation of tree
212      */
213 //protected:
214     Inkscape::XML::Node * getRepr();
215 
216     /**
217      * Returns the XML representation of tree
218      */
219     Inkscape::XML::Node const* getRepr() const;
220 
221 public:
222 
223     /**
224      * Cleans up an SPObject, releasing its references and
225      * requesting that references to it be released
226      */
227     void releaseReferences();
228 
229     /**
230      * Connects to the release request signal
231      *
232      * @param slot the slot to connect
233      *
234      * @return the sigc::connection formed
235      */
connectRelease(sigc::slot<void,SPObject * > slot)236     sigc::connection connectRelease(sigc::slot<void, SPObject *> slot) {
237         return _release_signal.connect(slot);
238     }
239 
240     /**
241      * Represents the style properties, whether from presentation attributes, the <tt>style</tt>
242      * attribute, or inherited.
243      *
244      * private_set() doesn't handle SPAttr::STYLE or any presentation attributes at the
245      * time of writing, so this is probably NULL for all SPObject's that aren't an SPItem.
246      *
247      * However, this gives rise to the bugs mentioned in sp_object_get_style_property.
248      * Note that some non-SPItem SPObject's, such as SPStop, do need styling information,
249      * and need to inherit properties even through other non-SPItem parents like \<defs\>.
250      */
251     SPStyle *style;
252 
253     /**
254      * Represents the style that should be used to resolve 'context-fill' and 'context-stroke'
255      */
256     SPStyle *context_style;
257 
258     /// Switch containing next() method.
259     struct ParentIteratorStrategy {
nextParentIteratorStrategy260         static SPObject const *next(SPObject const *object) {
261             return object->parent;
262         }
263     };
264 
265     typedef Inkscape::Util::ForwardPointerIterator<SPObject, ParentIteratorStrategy> ParentIterator;
266     typedef Inkscape::Util::ForwardPointerIterator<SPObject const, ParentIteratorStrategy> ConstParentIterator;
267 
isSiblingOf(SPObject const * object)268     bool isSiblingOf(SPObject const *object) const {
269         if (object == nullptr) return false;
270         return this->parent && this->parent == object->parent;
271     }
272 
273     /**
274      * True if object is non-NULL and this is some in/direct parent of object.
275      */
276     bool isAncestorOf(SPObject const *object) const;
277 
278     /**
279      * Returns youngest object being parent to this and object.
280      */
281     SPObject const *nearestCommonAncestor(SPObject const *object) const;
282 
283     /* Returns next object in sibling list or NULL. */
284     SPObject *getNext();
285 
286     /**
287      * Returns previous object in sibling list or NULL.
288      */
289     SPObject *getPrev();
290 
hasChildren()291     bool hasChildren() const { return ( children.size() > 0 ); }
292 
firstChild()293     SPObject *firstChild() { return children.empty() ? nullptr : &children.front(); }
firstChild()294     SPObject const *firstChild() const { return children.empty() ? nullptr : &children.front(); }
295 
lastChild()296     SPObject *lastChild() { return children.empty() ? nullptr : &children.back(); }
lastChild()297     SPObject const *lastChild() const { return children.empty() ? nullptr : &children.back(); }
298 
299     SPObject *nthChild(unsigned index);
300     SPObject const *nthChild(unsigned index) const;
301 
302     enum Action { ActionGeneral, ActionBBox, ActionUpdate, ActionShow };
303 
304     /**
305      * Retrieves the children as a std vector object, optionally ref'ing the children
306      * in the process, if add_ref is specified.
307      */
308     std::vector<SPObject*> childList(bool add_ref, Action action = ActionGeneral);
309 
310     /**
311      * Append repr as child of this object.
312      * \pre this is not a cloned object
313      */
314     SPObject *appendChildRepr(Inkscape::XML::Node *repr);
315 
316     /**
317      * Gets the author-visible label property for the object or a default if
318      * no label is defined.
319      */
320     char const *label() const;
321 
322     /**
323      * Returns a default label property for this object.
324      */
325     char const *defaultLabel() const;
326 
327     /**
328      * Sets the author-visible label for this object.
329      *
330      * @param label the new label.
331      */
332     void setLabel(char const *label);
333 
334     /**
335      * Returns the title of this object, or NULL if there is none.
336      * The caller must free the returned string using g_free() - see comment
337      * for getTitleOrDesc() below.
338      */
339     char *title() const;
340 
341     /**
342      * Sets the title of this object.
343      * A NULL first argument is interpreted as meaning that the existing title
344      * (if any) should be deleted.
345      * The second argument is optional - @see setTitleOrDesc() below for details.
346      */
347     bool setTitle(char const *title, bool verbatim = false);
348 
349     /**
350      * Returns the description of this object, or NULL if there is none.
351      * The caller must free the returned string using g_free() - see comment
352      * for getTitleOrDesc() below.
353      */
354     char *desc() const;
355 
356     /**
357      * Sets the description of this object.
358      * A NULL first argument is interpreted as meaning that the existing
359      * description (if any) should be deleted.
360      * The second argument is optional - @see setTitleOrDesc() below for details.
361      */
362     bool setDesc(char const *desc, bool verbatim=false);
363 
364     /**
365      * Set the policy under which this object will be orphan-collected.
366      *
367      * Orphan-collection is the process of deleting all objects which no longer have
368      * hyper-references pointing to them.  The policy determines when this happens.  Many objects
369      * should not be deleted simply because they are no longer referred to; other objects (like
370      * "intermediate" gradients) are more or less throw-away and should always be collected when no
371      * longer in use.
372      *
373      * Along these lines, there are currently two orphan-collection policies:
374      *
375      *  COLLECT_WITH_PARENT - don't worry about the object's hrefcount;
376      *                        if its parent is collected, this object
377      *                        will be too
378      *
379      *  COLLECT_ALWAYS - always collect the object as soon as its
380      *                   hrefcount reaches zero
381      *
382      * @return the current collection policy in effect for this object
383      */
collectionPolicy()384     CollectionPolicy collectionPolicy() const { return _collection_policy; }
385 
386     /**
387      * Sets the orphan-collection policy in effect for this object.
388      *
389      * @param policy the new policy to adopt
390      *
391      * @see SPObject::collectionPolicy
392      */
setCollectionPolicy(CollectionPolicy policy)393     void setCollectionPolicy(CollectionPolicy policy) {
394         _collection_policy = policy;
395     }
396 
397     /**
398      * Requests a later automatic call to collectOrphan().
399      *
400      * This method requests that collectOrphan() be called during the document update cycle,
401      * deleting the object if it is no longer used.
402      *
403      * If the current collection policy is COLLECT_WITH_PARENT, this function has no effect.
404      *
405      * @see SPObject::collectOrphan
406      */
407     void requestOrphanCollection();
408 
409     /**
410      * Unconditionally delete the object if it is not referenced.
411      *
412      * Unconditionally delete the object if there are no outstanding hyper-references to it.
413      * Observers are not notified of the object's deletion (at the SPObject level; XML tree
414      * notifications still fire).
415      *
416      * @see SPObject::deleteObject
417      */
collectOrphan()418     void collectOrphan() {
419         if ( _total_hrefcount == 0 ) {
420             deleteObject(false);
421         }
422     }
423 
424     /**
425      * Increase weak refcount.
426      *
427      * Hrefcount is used for weak references, for example, to
428      * determine whether any graphical element references a certain gradient
429      * node.
430      * It keeps a list of "owners".
431      * @param owner Used to track who uses this object.
432      */
433     void hrefObject(SPObject* owner = nullptr);
434 
435     /**
436      * Decrease weak refcount.
437      *
438      * Hrefcount is used for weak references, for example, to determine whether
439      * any graphical element references a certain gradient node.
440      * @param owner Used to track who uses this object.
441      * \pre hrefcount>0
442      */
443     void unhrefObject(SPObject* owner = nullptr);
444 
445     /**
446      * Check if object is referenced by any other object.
447      */
isReferenced()448     bool isReferenced() { return ( _total_hrefcount > 0 ); }
449 
450     /**
451      * Deletes an object, unparenting it from its parent.
452      *
453      * Detaches the object's repr, and optionally sends notification that the object has been
454      * deleted.
455      *
456      * @param propagate If it is set to true, it emits a delete signal.
457      *
458      * @param propagate_descendants If it is true, it recursively sends the delete signal to children.
459      */
460     void deleteObject(bool propagate, bool propagate_descendants);
461 
462     /**
463      * Deletes on object.
464      *
465      * @param propagate Notify observers of this object and its children that they have been
466      *                  deleted?
467      */
468     void deleteObject(bool propagate = true)
469     {
470         deleteObject(propagate, propagate);
471     }
472 
473     /**
474      * Removes all children except for the given object, it's children and it's ancesstors.
475      */
476      void cropToObject(SPObject *except);
477 
478     /**
479      * Connects a slot to be called when an object is deleted.
480      *
481      * This connects a slot to an object's internal delete signal, which is invoked when the object
482      * is deleted
483      *
484      * The signal is mainly useful for e.g. knowing when to break hrefs or dissociate clones.
485      *
486      * @param slot the slot to connect
487      *
488      * @see SPObject::deleteObject
489      */
connectDelete(sigc::slot<void,SPObject * > slot)490     sigc::connection connectDelete(sigc::slot<void, SPObject *> slot) {
491         return _delete_signal.connect(slot);
492     }
493 
connectPositionChanged(sigc::slot<void,SPObject * > slot)494     sigc::connection connectPositionChanged(sigc::slot<void, SPObject *> slot) {
495         return _position_changed_signal.connect(slot);
496     }
497 
498     /**
499      * Returns the object which supercedes this one (if any).
500      *
501      * This is mainly useful for ensuring we can correctly perform a series of moves or deletes,
502      * even if the objects in question have been replaced in the middle of the sequence.
503      */
successor()504     SPObject *successor() { return _successor; }
505 
506     /**
507      * Indicates that another object supercedes this one.
508      */
setSuccessor(SPObject * successor)509     void setSuccessor(SPObject *successor) {
510         assert(successor != NULL);
511         assert(_successor == NULL);
512         assert(successor->_successor == NULL);
513         sp_object_ref(successor, nullptr);
514         _successor = successor;
515     }
516 
517     /* modifications; all three sets of methods should probably ultimately be protected, as they
518      * are not really part of its public interface.  However, other parts of the code to
519      * occasionally use them at present. */
520 
521     /* the no-argument version of updateRepr() is intended to be a bit more public, however -- it
522      * essentially just flushes any changes back to the backing store (the repr layer); maybe it
523      * should be called something else and made public at that point. */
524 
525     /**
526      * Updates the object's repr based on the object's state.
527      *
528      *  This method updates the repr attached to the object to reflect the object's current
529      *  state; see the three-argument version for details.
530      *
531      * @param flags object write flags that apply to this update
532      *
533      * @return the updated repr
534      */
535     Inkscape::XML::Node *updateRepr(unsigned int flags = SP_OBJECT_WRITE_EXT);
536 
537     /**
538      * Updates the given repr based on the object's state.
539      *
540      * Used both to create reprs in the original document, and to create reprs
541      * in another document (e.g. a temporary document used when saving as "Plain SVG".
542      *
543      *  This method updates the given repr to reflect the object's current state.  There are
544      *  several flags that affect this:
545      *
546      *   SP_OBJECT_WRITE_BUILD - create new reprs
547      *
548      *   SP_OBJECT_WRITE_EXT   - write elements and attributes
549      *                           which are not part of pure SVG
550      *                           (i.e. the Inkscape and Sodipodi
551      *                           namespaces)
552      *
553      *   SP_OBJECT_WRITE_ALL   - create all nodes and attributes,
554      *                           even those which might be redundant
555      *
556      * @param repr the repr to update
557      * @param flags object write flags that apply to this update
558      *
559      * @return the updated repr
560      */
561     Inkscape::XML::Node *updateRepr(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, unsigned int flags);
562 
563     /**
564      * Queues an deferred update of this object's display.
565      *
566      *  This method sets flags to indicate updates to be performed later, during the idle loop.
567      *
568      *  There are several flags permitted here:
569      *
570      *   SP_OBJECT_MODIFIED_FLAG - the object has been modified
571      *
572      *   SP_OBJECT_CHILD_MODIFIED_FLAG - a child of the object has been
573      *                                   modified
574      *
575      *   SP_OBJECT_STYLE_MODIFIED_FLAG - the object's style has been
576      *                                   modified
577      *
578      *  There are also some subclass-specific modified flags which are hardly ever used.
579      *
580      *  One of either MODIFIED or CHILD_MODIFIED is required.
581      *
582      * @param flags flags indicating what to update
583      */
584     void requestDisplayUpdate(unsigned int flags);
585 
586     /**
587      * Updates the object's display immediately
588      *
589      *  This method is called during the idle loop by SPDocument in order to update the object's
590      *  display.
591      *
592      *  One additional flag is legal here:
593      *
594      *   SP_OBJECT_PARENT_MODIFIED_FLAG - the parent has been
595      *                                    modified
596      *
597      * @param ctx an SPCtx which accumulates various state
598      *             during the recursive update -- beware! some
599      *             subclasses try to cast this to an SPItemCtx *
600      *
601      * @param flags flags indicating what to update (in addition
602      *               to any already set flags)
603      */
604     void updateDisplay(SPCtx *ctx, unsigned int flags);
605 
606     /**
607      * Requests that a modification notification signal
608      * be emitted later (e.g. during the idle loop)
609      *
610      * Request modified always bubbles *up* the tree, as opposed to
611      * request display update, which trickles down and relies on the
612      * flags set during this pass...
613      *
614      * @param flags flags indicating what has been modified
615      */
616     void requestModified(unsigned int flags);
617 
618     /**
619      *  Emits the MODIFIED signal with the object's flags.
620      *  The object's mflags are the original set aside during the update pass for
621      *  later delivery here.  Once emitModified() is called, those flags don't
622      *  need to be stored any longer.
623      *
624      * @param flags indicating what has been modified.
625      */
626     void emitModified(unsigned int flags);
627 
628     /**
629      * Connects to the modification notification signal
630      *
631      * @param slot the slot to connect
632      *
633      * @return the connection formed thereby
634      */
connectModified(sigc::slot<void,SPObject *,unsigned int> slot)635     sigc::connection connectModified(
636       sigc::slot<void, SPObject *, unsigned int> slot
637     ) {
638         return _modified_signal.connect(slot);
639     }
640 
641     /** Sends the delete signal to all children of this object recursively */
642     void _sendDeleteSignalRecursive();
643 
644     /**
645      * Adds increment to _total_hrefcount of object and its parents.
646      */
647     void _updateTotalHRefCount(int increment);
648 
_requireSVGVersion(unsigned major,unsigned minor)649     void _requireSVGVersion(unsigned major, unsigned minor) {
650         _requireSVGVersion(Inkscape::Version(major, minor));
651     }
652 
653     /**
654      * Lifts SVG version of all root objects to version.
655      */
656     void _requireSVGVersion(Inkscape::Version version);
657 
658     sigc::signal<void, SPObject *> _release_signal;
659     sigc::signal<void, SPObject *> _delete_signal;
660     sigc::signal<void, SPObject *> _position_changed_signal;
661     sigc::signal<void, SPObject *, unsigned int> _modified_signal;
662     SPObject *_successor;
663     CollectionPolicy _collection_policy;
664     char *_label;
665     mutable char *_default_label;
666 
667     // WARNING:
668     // Methods below should not be used outside of the SP tree,
669     // as they operate directly on the XML representation.
670     // In future, they will be made protected.
671 
672     /**
673      * Put object into object tree, under parent, and behind prev;
674      * also update object's XML space.
675      */
676     void attach(SPObject *object, SPObject *prev);
677 
678     /**
679      * In list of object's children, move object behind prev.
680      */
681     void reorder(SPObject* obj, SPObject *prev);
682 
683     /**
684      * Remove object from parent's children, release and unref it.
685      */
686     void detach(SPObject *object);
687 
688     /**
689      * Return object's child whose node pointer equals repr.
690      */
691     SPObject *get_child_by_repr(Inkscape::XML::Node *repr);
692 
693     void invoke_build(SPDocument *document, Inkscape::XML::Node *repr, unsigned int cloned);
694 
695     int getIntAttribute(char const *key, int def);
696 
697     unsigned getPosition();
698 
699     char const * getAttribute(char const *name,SPException *ex=nullptr) const;
700 
701     void appendChild(Inkscape::XML::Node *child);
702 
703     void addChild(Inkscape::XML::Node *child,Inkscape::XML::Node *prev=nullptr);
704 
705     /**
706      * Call virtual set() function of object.
707      */
708     void setKeyValue(SPAttr key, char const *value);
709 
710 
711     void setAttribute(Inkscape::Util::const_char_ptr key,
712                       Inkscape::Util::const_char_ptr value,
713                       SPException *ex=nullptr);
714 
715     void setAttributeOrRemoveIfEmpty(Inkscape::Util::const_char_ptr key,
716                                      Inkscape::Util::const_char_ptr value,
717                                      SPException *ex=nullptr) {
718         this->setAttribute(key.data(),
719                           (value.data() == nullptr || value.data()[0]=='\0') ? nullptr : value.data(), ex);
720     }
721 
722     /**
723      * Read value of key attribute from XML node into object.
724      */
725     void readAttr(char const *key);
726     void readAttr(SPAttr keyid);
727 
728     char const *getTagName(SPException *ex) const;
729 
730     void removeAttribute(char const *key, SPException *ex=nullptr);
731 
732     void setCSS(SPCSSAttr *css, char const *attr);
733 
734     void changeCSS(SPCSSAttr *css, char const *attr);
735 
736     bool storeAsDouble( char const *key, double *val ) const;
737 
738 private:
739     // Private member functions used in the definitions of setTitle(),
740     // setDesc(), title() and desc().
741 
742     /**
743      * Sets or deletes the title or description of this object.
744      * A NULL 'value' argument causes the title or description to be deleted.
745      *
746      * 'verbatim' parameter:
747      * If verbatim==true, then the title or description is set to exactly the
748      * specified value.  If verbatim==false then two exceptions are made:
749      *   (1) If the specified value is just whitespace, then the title/description
750      *       is deleted.
751      *   (2) If the specified value is the same as the current value except for
752      *       mark-up, then the current value is left unchanged.
753      * This is usually the desired behaviour, so 'verbatim' defaults to false for
754      * setTitle() and setDesc().
755      *
756      * The return value is true if a change was made to the title/description,
757      * and usually false otherwise.
758      */
759     bool setTitleOrDesc(char const *value, char const *svg_tagname, bool verbatim);
760 
761     /**
762      * Returns the title or description of this object, or NULL if there is none.
763      *
764      * The SVG spec allows 'title' and 'desc' elements to contain text marked up
765      * using elements from other namespaces.  Therefore, this function cannot
766      * in general just return a pointer to an existing string - it must instead
767      * construct a string containing the title or description without the mark-up.
768      * Consequently, the return value is a newly allocated string (or NULL), and
769      * must be freed (using g_free()) by the caller.
770      */
771     char * getTitleOrDesc(char const *svg_tagname) const;
772 
773     /**
774      * Find the first child of this object with a given tag name,
775      * and return it.  Returns NULL if there is no matching child.
776      */
777     SPObject * findFirstChild(char const *tagname) const;
778 
779     /**
780      * Return the full textual content of an element (typically all the
781      * content except the tags).
782      * Must not be used on anything except elements.
783      */
784     Glib::ustring textualContent() const;
785 
786     /* Real handlers of repr signals */
787 
788 public:
789     /**
790      * Callback for attr_changed node event.
791      */
792     static void repr_attr_changed(Inkscape::XML::Node *repr, char const *key, char const *oldval, char const *newval, bool is_interactive, void* data);
793 
794     /**
795      * Callback for content_changed node event.
796      */
797     static void repr_content_changed(Inkscape::XML::Node *repr, char const *oldcontent, char const *newcontent, void* data);
798 
799     /**
800      * Callback for child_added node event.
801      */
802     static void repr_child_added(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, void* data);
803 
804     /**
805      * Callback for remove_child node event.
806      */
807     static void repr_child_removed(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, void *data);
808 
809     /**
810      * Callback for order_changed node event.
811      *
812      * \todo fixme:
813      */
814     static void repr_order_changed(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *old, Inkscape::XML::Node *newer, void* data);
815 
816 
817     friend class SPObjectImpl;
818 
819 protected:
820 	virtual void build(SPDocument* doc, Inkscape::XML::Node* repr);
821 	virtual void release();
822 
823 	virtual void child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref);
824 	virtual void remove_child(Inkscape::XML::Node* child);
825 
826 	virtual void order_changed(Inkscape::XML::Node* child, Inkscape::XML::Node* old_repr, Inkscape::XML::Node* new_repr);
827 
828 	virtual void set(SPAttr key, const char* value);
829 
830 	virtual void update(SPCtx* ctx, unsigned int flags);
831 	virtual void modified(unsigned int flags);
832 
833 	virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, unsigned int flags);
834 
835     typedef boost::intrusive::list_member_hook<> ListHook;
836     ListHook _child_hook;
837 public:
838     typedef boost::intrusive::list<
839             SPObject,
840             boost::intrusive::member_hook<
841                     SPObject,
842                     ListHook,
843                     &SPObject::_child_hook
844             >> ChildrenList;
845     ChildrenList children;
846 	virtual void read_content();
847 
848     void recursivePrintTree(unsigned level = 0);  // For debugging
849     static unsigned indent_level;
850     void objectTrace( std::string const &, bool in=true, unsigned flags=0 );
851 };
852 
853 std::ostream &operator<<(std::ostream &out, const SPObject &o);
854 
855 /**
856  * Compares height of objects in tree.
857  *
858  * Works for different-parent objects, so long as they have a common ancestor.
859  * \return \verbatim
860  *    0    positions are equivalent
861  *    1    first object's position is greater than the second
862  *   -1    first object's position is less than the second   \endverbatim
863  */
864 int sp_object_compare_position(SPObject const *first, SPObject const *second);
865 bool sp_object_compare_position_bool(SPObject const *first, SPObject const *second);
866 gchar * sp_object_get_unique_id(SPObject    *object, gchar const *defid);
867 
868 #endif // SP_OBJECT_H_SEEN
869 
870 
871 /*
872   Local Variables:
873   mode:c++
874   c-file-style:"stroustrup"
875   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
876   indent-tabs-mode:nil
877   fill-column:99
878   End:
879 */
880 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
881