Code

prevent inkscape from crashing in case of malformed SVG, still need method to inform...
[inkscape.git] / src / sp-object.cpp
1 #define __SP_OBJECT_C__
2 /** \file
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  *
10  * Copyright (C) 1999-2008 authors
11  * Copyright (C) 2001-2002 Ximian, Inc.
12  *
13  * Released under GNU GPL, read the file 'COPYING' for more information
14  */
16 /** \class SPObject
17  *
18  * SPObject is an abstract base class of all of the document nodes at the
19  * SVG document level. Each SPObject subclass implements a certain SVG
20  * element node type, or is an abstract base class for different node
21  * types.  The SPObject layer is bound to the SPRepr layer, closely
22  * following the SPRepr mutations via callbacks.  During creation,
23  * SPObject parses and interprets all textual attributes and CSS style
24  * strings of the SPRepr, and later updates the internal state whenever
25  * it receives a signal about a change. The opposite is not true - there
26  * are methods manipulating SPObjects directly and such changes do not
27  * propagate to the SPRepr layer. This is important for implementation of
28  * the undo stack, animations and other features.
29  *
30  * SPObjects are bound to the higher-level container SPDocument, which
31  * provides document level functionality such as the undo stack,
32  * dictionary and so on. Source: doc/architecture.txt
33  */
35 #include <cstring>
36 #include <string>
38 #include "helper/sp-marshal.h"
39 #include "xml/node-event-vector.h"
40 #include "attributes.h"
41 #include "document.h"
42 #include "style.h"
43 #include "sp-object-repr.h"
44 #include "sp-root.h"
45 #include "streq.h"
46 #include "strneq.h"
47 #include "xml/repr.h"
48 #include "xml/node-fns.h"
49 #include "debug/event-tracker.h"
50 #include "debug/simple-event.h"
51 #include "debug/demangle.h"
52 #include "util/share.h"
53 #include "util/format.h"
55 #include "algorithms/longest-common-suffix.h"
56 using std::memcpy;
57 using std::strchr;
58 using std::strcmp;
59 using std::strlen;
60 using std::strstr;
62 #define noSP_OBJECT_DEBUG_CASCADE
64 #define noSP_OBJECT_DEBUG
66 #ifdef SP_OBJECT_DEBUG
67 # define debug(f, a...) { g_print("%s(%d) %s:", \
68                                   __FILE__,__LINE__,__FUNCTION__); \
69                           g_print(f, ## a); \
70                           g_print("\n"); \
71                         }
72 #else
73 # define debug(f, a...) /**/
74 #endif
76 static void sp_object_class_init(SPObjectClass *klass);
77 static void sp_object_init(SPObject *object);
78 static void sp_object_finalize(GObject *object);
80 static void sp_object_child_added(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref);
81 static void sp_object_remove_child(SPObject *object, Inkscape::XML::Node *child);
82 static void sp_object_order_changed(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *old_ref, Inkscape::XML::Node *new_ref);
84 static void sp_object_release(SPObject *object);
85 static void sp_object_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
87 static void sp_object_private_set(SPObject *object, unsigned int key, gchar const *value);
88 static Inkscape::XML::Node *sp_object_private_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags);
90 /* Real handlers of repr signals */
92 static void sp_object_repr_attr_changed(Inkscape::XML::Node *repr, gchar const *key, gchar const *oldval, gchar const *newval, bool is_interactive, gpointer data);
94 static void sp_object_repr_content_changed(Inkscape::XML::Node *repr, gchar const *oldcontent, gchar const *newcontent, gpointer data);
96 static void sp_object_repr_child_added(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, gpointer data);
97 static void sp_object_repr_child_removed(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, void *data);
99 static void sp_object_repr_order_changed(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *old, Inkscape::XML::Node *newer, gpointer data);
101 static gchar *sp_object_get_unique_id(SPObject *object, gchar const *defid);
103 guint update_in_progress = 0; // guard against update-during-update
105 Inkscape::XML::NodeEventVector object_event_vector = {
106     sp_object_repr_child_added,
107     sp_object_repr_child_removed,
108     sp_object_repr_attr_changed,
109     sp_object_repr_content_changed,
110     sp_object_repr_order_changed
111 };
113 static GObjectClass *parent_class;
115 /**
116  * Registers the SPObject class with Gdk and returns its type number.
117  */
118 GType
119 sp_object_get_type(void)
121     static GType type = 0;
122     if (!type) {
123         GTypeInfo info = {
124             sizeof(SPObjectClass),
125             NULL, NULL,
126             (GClassInitFunc) sp_object_class_init,
127             NULL, NULL,
128             sizeof(SPObject),
129             16,
130             (GInstanceInitFunc) sp_object_init,
131             NULL
132         };
133         type = g_type_register_static(G_TYPE_OBJECT, "SPObject", &info, (GTypeFlags)0);
134     }
135     return type;
138 /**
139  * Initializes the SPObject vtable.
140  */
141 static void
142 sp_object_class_init(SPObjectClass *klass)
144     GObjectClass *object_class;
146     object_class = (GObjectClass *) klass;
148     parent_class = (GObjectClass *) g_type_class_ref(G_TYPE_OBJECT);
150     object_class->finalize = sp_object_finalize;
152     klass->child_added = sp_object_child_added;
153     klass->remove_child = sp_object_remove_child;
154     klass->order_changed = sp_object_order_changed;
156     klass->release = sp_object_release;
158     klass->build = sp_object_build;
160     klass->set = sp_object_private_set;
161     klass->write = sp_object_private_write;
164 /**
165  * Callback to initialize the SPObject object.
166  */
167 static void
168 sp_object_init(SPObject *object)
170     debug("id=%x, typename=%s",object, g_type_name_from_instance((GTypeInstance*)object));
172     object->hrefcount = 0;
173     object->_total_hrefcount = 0;
174     object->document = NULL;
175     object->children = object->_last_child = NULL;
176     object->parent = object->next = NULL;
177     object->repr = NULL;
178     object->id = NULL;
180     object->_collection_policy = SPObject::COLLECT_WITH_PARENT;
182     new (&object->_release_signal) sigc::signal<void, SPObject *>();
183     new (&object->_modified_signal) sigc::signal<void, SPObject *, unsigned int>();
184     new (&object->_delete_signal) sigc::signal<void, SPObject *>();
185     new (&object->_position_changed_signal) sigc::signal<void, SPObject *>();
186     object->_successor = NULL;
188     // FIXME: now we create style for all objects, but per SVG, only the following can have style attribute:
189     // vg, g, defs, desc, title, symbol, use, image, switch, path, rect, circle, ellipse, line, polyline, 
190     // polygon, text, tspan, tref, textPath, altGlyph, glyphRef, marker, linearGradient, radialGradient, 
191     // stop, pattern, clipPath, mask, filter, feImage, a, font, glyph, missing-glyph, foreignObject
192     object->style = sp_style_new_from_object(object);
194     object->_label = NULL;
195     object->_default_label = NULL;
198 /**
199  * Callback to destroy all members and connections of object and itself.
200  */
201 static void
202 sp_object_finalize(GObject *object)
204     SPObject *spobject = (SPObject *)object;
206     g_free(spobject->_label);
207     g_free(spobject->_default_label);
208     spobject->_label = NULL;
209     spobject->_default_label = NULL;
211     if (spobject->_successor) {
212         sp_object_unref(spobject->_successor, NULL);
213         spobject->_successor = NULL;
214     }
216     if (((GObjectClass *) (parent_class))->finalize) {
217         (* ((GObjectClass *) (parent_class))->finalize)(object);
218     }
220     spobject->_release_signal.~signal();
221     spobject->_modified_signal.~signal();
222     spobject->_delete_signal.~signal();
223     spobject->_position_changed_signal.~signal();
226 namespace {
228 namespace Debug = Inkscape::Debug;
229 namespace Util = Inkscape::Util;
231 typedef Debug::SimpleEvent<Debug::Event::REFCOUNT> BaseRefCountEvent;
233 class RefCountEvent : public BaseRefCountEvent {
234 public:
235     RefCountEvent(SPObject *object, int bias, Util::ptr_shared<char> name)
236     : BaseRefCountEvent(name)
237     {
238         _addProperty("object", Util::format("%p", object));
239         _addProperty("class", Debug::demangle(g_type_name(G_TYPE_FROM_INSTANCE(object))));
240         _addProperty("new-refcount", Util::format("%d", G_OBJECT(object)->ref_count + bias));
241     }
242 };
244 class RefEvent : public RefCountEvent {
245 public:
246     RefEvent(SPObject *object)
247     : RefCountEvent(object, 1, Util::share_static_string("sp-object-ref"))
248     {}
249 };
251 class UnrefEvent : public RefCountEvent {
252 public:
253     UnrefEvent(SPObject *object)
254     : RefCountEvent(object, -1, Util::share_static_string("sp-object-unref"))
255     {}
256 };
260 /**
261  * Increase reference count of object, with possible debugging.
262  *
263  * \param owner If non-NULL, make debug log entry.
264  * \return object, NULL is error.
265  * \pre object points to real object
266  */
267 SPObject *
268 sp_object_ref(SPObject *object, SPObject *owner)
270     g_return_val_if_fail(object != NULL, NULL);
271     g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
272     g_return_val_if_fail(!owner || SP_IS_OBJECT(owner), NULL);
274     Inkscape::Debug::EventTracker<RefEvent> tracker(object);
275     g_object_ref(G_OBJECT(object));
276     return object;
279 /**
280  * Decrease reference count of object, with possible debugging and
281  * finalization.
282  *
283  * \param owner If non-NULL, make debug log entry.
284  * \return always NULL
285  * \pre object points to real object
286  */
287 SPObject *
288 sp_object_unref(SPObject *object, SPObject *owner)
290     g_return_val_if_fail(object != NULL, NULL);
291     g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
292     g_return_val_if_fail(!owner || SP_IS_OBJECT(owner), NULL);
294     Inkscape::Debug::EventTracker<UnrefEvent> tracker(object);
295     g_object_unref(G_OBJECT(object));
296     return NULL;
299 /**
300  * Increase weak refcount.
301  *
302  * Hrefcount is used for weak references, for example, to
303  * determine whether any graphical element references a certain gradient
304  * node.
305  * \param owner Ignored.
306  * \return object, NULL is error
307  * \pre object points to real object
308  */
309 SPObject *
310 sp_object_href(SPObject *object, gpointer /*owner*/)
312     g_return_val_if_fail(object != NULL, NULL);
313     g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
315     object->hrefcount++;
316     object->_updateTotalHRefCount(1);
318     return object;
321 /**
322  * Decrease weak refcount.
323  *
324  * Hrefcount is used for weak references, for example, to determine whether
325  * any graphical element references a certain gradient node.
326  * \param owner Ignored.
327  * \return always NULL
328  * \pre object points to real object and hrefcount>0
329  */
330 SPObject *
331 sp_object_hunref(SPObject *object, gpointer /*owner*/)
333     g_return_val_if_fail(object != NULL, NULL);
334     g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
335     g_return_val_if_fail(object->hrefcount > 0, NULL);
337     object->hrefcount--;
338     object->_updateTotalHRefCount(-1);
340     return NULL;
343 /**
344  * Adds increment to _total_hrefcount of object and its parents.
345  */
346 void
347 SPObject::_updateTotalHRefCount(int increment) {
348     SPObject *topmost_collectable = NULL;
349     for ( SPObject *iter = this ; iter ; iter = SP_OBJECT_PARENT(iter) ) {
350         iter->_total_hrefcount += increment;
351         if ( iter->_total_hrefcount < iter->hrefcount ) {
352             g_critical("HRefs overcounted");
353         }
354         if ( iter->_total_hrefcount == 0 &&
355              iter->_collection_policy != COLLECT_WITH_PARENT )
356         {
357             topmost_collectable = iter;
358         }
359     }
360     if (topmost_collectable) {
361         topmost_collectable->requestOrphanCollection();
362     }
365 /**
366  * True if object is non-NULL and this is some in/direct parent of object.
367  */
368 bool
369 SPObject::isAncestorOf(SPObject const *object) const {
370     g_return_val_if_fail(object != NULL, false);
371     object = SP_OBJECT_PARENT(object);
372     while (object) {
373         if ( object == this ) {
374             return true;
375         }
376         object = SP_OBJECT_PARENT(object);
377     }
378     return false;
381 namespace {
383 bool same_objects(SPObject const &a, SPObject const &b) {
384     return &a == &b;
389 /**
390  * Returns youngest object being parent to this and object.
391  */
392 SPObject const *
393 SPObject::nearestCommonAncestor(SPObject const *object) const {
394     g_return_val_if_fail(object != NULL, NULL);
396     using Inkscape::Algorithms::longest_common_suffix;
397     return longest_common_suffix<SPObject::ConstParentIterator>(this, object, NULL, &same_objects);
400 SPObject const *AncestorSon(SPObject const *obj, SPObject const *ancestor) {
401     if (obj == NULL || ancestor == NULL)
402         return NULL;
403     if (SP_OBJECT_PARENT(obj) == ancestor)
404         return obj;
405     return AncestorSon(SP_OBJECT_PARENT(obj), ancestor);
408 /**
409  * Compares height of objects in tree.
410  *
411  * Works for different-parent objects, so long as they have a common ancestor.
412  * \return \verbatim
413  *    0    positions are equivalent
414  *    1    first object's position is greater than the second
415  *   -1    first object's position is less than the second   \endverbatim
416  */
417 int
418 sp_object_compare_position(SPObject const *first, SPObject const *second)
420     if (first == second) return 0;
422     SPObject const *ancestor = first->nearestCommonAncestor(second);
423     if (ancestor == NULL) return 0; // cannot compare, no common ancestor!
425     // we have an object and its ancestor (should not happen when sorting selection)
426     if (ancestor == first)
427         return 1;
428     if (ancestor == second)
429         return -1;
431     SPObject const *to_first = AncestorSon(first, ancestor);
432     SPObject const *to_second = AncestorSon(second, ancestor);
434     g_assert(SP_OBJECT_PARENT(to_second) == SP_OBJECT_PARENT(to_first));
436     return sp_repr_compare_position(SP_OBJECT_REPR(to_first), SP_OBJECT_REPR(to_second));
440 /**
441  * Append repr as child of this object.
442  * \pre this is not a cloned object
443  */
444 SPObject *
445 SPObject::appendChildRepr(Inkscape::XML::Node *repr) {
446     if (!SP_OBJECT_IS_CLONED(this)) {
447         SP_OBJECT_REPR(this)->appendChild(repr);
448         return SP_OBJECT_DOCUMENT(this)->getObjectByRepr(repr);
449     } else {
450         g_critical("Attempt to append repr as child of cloned object");
451         return NULL;
452     }
455 /**
456  * Retrieves the children as a GSList object, optionally ref'ing the children
457  * in the process, if add_ref is specified.
458  */
459 GSList *SPObject::childList(bool add_ref, Action) {
460     GSList *l = NULL;
461     for (SPObject *child = sp_object_first_child(this) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
462         if (add_ref)
463             g_object_ref (G_OBJECT (child));
465         l = g_slist_prepend (l, child);
466     }
467     return l;
471 /** Gets the label property for the object or a default if no label
472  *  is defined.
473  */
474 gchar const *
475 SPObject::label() const {
476     return _label;
479 /** Returns a default label property for the object. */
480 gchar const *
481 SPObject::defaultLabel() const {
482     if (_label) {
483         return _label;
484     } else {
485         if (!_default_label) {
486             gchar const *id=SP_OBJECT_ID(this);
487             if (id) {
488                 _default_label = g_strdup_printf("#%s", id);
489             } else {
490                 _default_label = g_strdup_printf("<%s>", SP_OBJECT_REPR(this)->name());
491             }
492         }
493         return _default_label;
494     }
497 /** Sets the label property for the object */
498 void
499 SPObject::setLabel(gchar const *label) {
500     SP_OBJECT_REPR(this)->setAttribute("inkscape:label", label, false);
504 /** Queues the object for orphan collection */
505 void
506 SPObject::requestOrphanCollection() {
507     g_return_if_fail(document != NULL);
508     document->queueForOrphanCollection(this);
510     /** \todo
511      * This is a temporary hack added to make fill&stroke rebuild its
512      * gradient list when the defs are vacuumed.  gradient-vector.cpp
513      * listens to the modified signal on defs, and now we give it that
514      * signal.  Mental says that this should be made automatic by
515      * merging SPObjectGroup with SPObject; SPObjectGroup would issue
516      * this signal automatically. Or maybe just derive SPDefs from
517      * SPObjectGroup?
518      */
520     this->requestModified(SP_OBJECT_CHILD_MODIFIED_FLAG);
523 /** Sends the delete signal to all children of this object recursively */
524 void
525 SPObject::_sendDeleteSignalRecursive() {
526     for (SPObject *child = sp_object_first_child(this); child; child = SP_OBJECT_NEXT(child)) {
527         child->_delete_signal.emit(child);
528         child->_sendDeleteSignalRecursive();
529     }
532 /**
533  * Deletes the object reference, unparenting it from its parent.
534  *
535  * If the \a propagate parameter is set to true, it emits a delete
536  * signal.  If the \a propagate_descendants parameter is true, it
537  * recursively sends the delete signal to children.
538  */
539 void
540 SPObject::deleteObject(bool propagate, bool propagate_descendants)
542     sp_object_ref(this, NULL);
543     if (propagate) {
544         _delete_signal.emit(this);
545     }
546     if (propagate_descendants) {
547         this->_sendDeleteSignalRecursive();
548     }
550     Inkscape::XML::Node *repr=SP_OBJECT_REPR(this);
551     if (repr && sp_repr_parent(repr)) {
552         sp_repr_unparent(repr);
553     }
555     if (_successor) {
556         _successor->deleteObject(propagate, propagate_descendants);
557     }
558     sp_object_unref(this, NULL);
561 /**
562  * Put object into object tree, under parent, and behind prev;
563  * also update object's XML space.
564  */
565 void
566 sp_object_attach(SPObject *parent, SPObject *object, SPObject *prev)
568     g_return_if_fail(parent != NULL);
569     g_return_if_fail(SP_IS_OBJECT(parent));
570     g_return_if_fail(object != NULL);
571     g_return_if_fail(SP_IS_OBJECT(object));
572     g_return_if_fail(!prev || SP_IS_OBJECT(prev));
573     g_return_if_fail(!prev || prev->parent == parent);
574     g_return_if_fail(!object->parent);
576     sp_object_ref(object, parent);
577     object->parent = parent;
578     parent->_updateTotalHRefCount(object->_total_hrefcount);
580     SPObject *next;
581     if (prev) {
582         next = prev->next;
583         prev->next = object;
584     } else {
585         next = parent->children;
586         parent->children = object;
587     }
588     object->next = next;
589     if (!next) {
590         parent->_last_child = object;
591     }
592     if (!object->xml_space.set)
593         object->xml_space.value = parent->xml_space.value;
596 /**
597  * In list of object's siblings, move object behind prev.
598  */
599 void
600 sp_object_reorder(SPObject *object, SPObject *prev) {
601     g_return_if_fail(object != NULL);
602     g_return_if_fail(SP_IS_OBJECT(object));
603     g_return_if_fail(object->parent != NULL);
604     g_return_if_fail(object != prev);
605     g_return_if_fail(!prev || SP_IS_OBJECT(prev));
606     g_return_if_fail(!prev || prev->parent == object->parent);
608     SPObject *const parent=object->parent;
610     SPObject *old_prev=NULL;
611     for ( SPObject *child = parent->children ; child && child != object ;
612           child = child->next )
613     {
614         old_prev = child;
615     }
617     SPObject *next=object->next;
618     if (old_prev) {
619         old_prev->next = next;
620     } else {
621         parent->children = next;
622     }
623     if (!next) {
624         parent->_last_child = old_prev;
625     }
626     if (prev) {
627         next = prev->next;
628         prev->next = object;
629     } else {
630         next = parent->children;
631         parent->children = object;
632     }
633     object->next = next;
634     if (!next) {
635         parent->_last_child = object;
636     }
639 /**
640  * Remove object from parent's children, release and unref it.
641  */
642 void
643 sp_object_detach(SPObject *parent, SPObject *object) {
644     g_return_if_fail(parent != NULL);
645     g_return_if_fail(SP_IS_OBJECT(parent));
646     g_return_if_fail(object != NULL);
647     g_return_if_fail(SP_IS_OBJECT(object));
648     g_return_if_fail(object->parent == parent);
650     object->releaseReferences();
652     SPObject *prev=NULL;
653     for ( SPObject *child = parent->children ; child && child != object ;
654           child = child->next )
655     {
656         prev = child;
657     }
659     SPObject *next=object->next;
660     if (prev) {
661         prev->next = next;
662     } else {
663         parent->children = next;
664     }
665     if (!next) {
666         parent->_last_child = prev;
667     }
669     object->next = NULL;
670     object->parent = NULL;
672     parent->_updateTotalHRefCount(-object->_total_hrefcount);
673     sp_object_unref(object, parent);
676 /**
677  * Return object's child whose node pointer equals repr.
678  */
679 SPObject *
680 sp_object_get_child_by_repr(SPObject *object, Inkscape::XML::Node *repr)
682     g_return_val_if_fail(object != NULL, NULL);
683     g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
684     g_return_val_if_fail(repr != NULL, NULL);
686     if (object->_last_child && SP_OBJECT_REPR(object->_last_child) == repr)
687         return object->_last_child;   // optimization for common scenario
688     for ( SPObject *child = object->children ; child ; child = child->next ) {
689         if ( SP_OBJECT_REPR(child) == repr ) {
690             return child;
691         }
692     }
694     return NULL;
697 /**
698  * Callback for child_added event.
699  * Invoked whenever the given mutation event happens in the XML tree.
700  */
701 static void
702 sp_object_child_added(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref)
704     GType type = sp_repr_type_lookup(child);
705     if (!type) {
706         return;
707     }
708     SPObject *ochild = SP_OBJECT(g_object_new(type, 0));
709     SPObject *prev = ref ? sp_object_get_child_by_repr(object, ref) : NULL;
710     sp_object_attach(object, ochild, prev);
711     sp_object_unref(ochild, NULL);
713     sp_object_invoke_build(ochild, object->document, child, SP_OBJECT_IS_CLONED(object));
716 /**
717  * Removes, releases and unrefs all children of object.
718  *
719  * This is the opposite of build. It has to be invoked as soon as the
720  * object is removed from the tree, even if it is still alive according
721  * to reference count. The frontend unregisters the object from the
722  * document and releases the SPRepr bindings; implementations should free
723  * state data and release all child objects.  Invoking release on
724  * SPRoot destroys the whole document tree.
725  * \see sp_object_build()
726  */
727 static void sp_object_release(SPObject *object)
729     debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object));
730     while (object->children) {
731         sp_object_detach(object, object->children);
732     }
735 /**
736  * Remove object's child whose node equals repr, release and
737  * unref it.
738  *
739  * Invoked whenever the given mutation event happens in the XML
740  * tree, BEFORE removal from the XML tree happens, so grouping
741  * objects can safely release the child data.
742  */
743 static void
744 sp_object_remove_child(SPObject *object, Inkscape::XML::Node *child)
746     debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object));
747     SPObject *ochild = sp_object_get_child_by_repr(object, child);
748     g_return_if_fail (ochild != NULL || !strcmp("comment", child->name())); // comments have no objects
749     if (ochild)
750         sp_object_detach(object, ochild);
753 /**
754  * Move object corresponding to child after sibling object corresponding
755  * to new_ref.
756  * Invoked whenever the given mutation event happens in the XML tree.
757  * \param old_ref Ignored
758  */
759 static void sp_object_order_changed(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node */*old_ref*/,
760                                     Inkscape::XML::Node *new_ref)
762     SPObject *ochild = sp_object_get_child_by_repr(object, child);
763     g_return_if_fail(ochild != NULL);
764     SPObject *prev = new_ref ? sp_object_get_child_by_repr(object, new_ref) : NULL;
765     sp_object_reorder(ochild, prev);
766     ochild->_position_changed_signal.emit(ochild);
769 /**
770  * Virtual build callback.
771  *
772  * This has to be invoked immediately after creation of an SPObject. The
773  * frontend method ensures that the new object is properly attached to
774  * the document and repr; implementation then will parse all of the attributes,
775  * generate the children objects and so on.  Invoking build on the SPRoot
776  * object results in creation of the whole document tree (this is, what
777  * SPDocument does after the creation of the XML tree).
778  * \see sp_object_release()
779  */
780 static void
781 sp_object_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
783     /* Nothing specific here */
784     debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object));
786     sp_object_read_attr(object, "xml:space");
787     sp_object_read_attr(object, "inkscape:label");
788     sp_object_read_attr(object, "inkscape:collect");
790     for (Inkscape::XML::Node *rchild = repr->firstChild() ; rchild != NULL; rchild = rchild->next()) {
791         GType type = sp_repr_type_lookup(rchild);
792         if (!type) {
793             continue;
794         }
795         SPObject *child = SP_OBJECT(g_object_new(type, 0));
796         sp_object_attach(object, child, object->lastChild());
797         sp_object_unref(child, NULL);
798         sp_object_invoke_build(child, document, rchild, SP_OBJECT_IS_CLONED(object));
799     }
802 void
803 sp_object_invoke_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr, unsigned int cloned)
805     debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object));
807     g_assert(object != NULL);
808     g_assert(SP_IS_OBJECT(object));
809     g_assert(document != NULL);
810     g_assert(repr != NULL);
812     g_assert(object->document == NULL);
813     g_assert(object->repr == NULL);
814     g_assert(object->id == NULL);
816     /* Bookkeeping */
818     object->document = document;
819     object->repr = repr;
820     if (!cloned)
821         Inkscape::GC::anchor(repr);
822     object->cloned = cloned;
824     if (!SP_OBJECT_IS_CLONED(object)) {
825         object->document->bindObjectToRepr(object->repr, object);
827         if (Inkscape::XML::id_permitted(object->repr)) {
828             /* If we are not cloned, and not seeking, force unique id */
829             gchar const *id = object->repr->attribute("id");
830             if (!document->isSeeking()) {
831                 gchar *realid = sp_object_get_unique_id(object, id);
832                 g_assert(realid != NULL);
834                 object->document->bindObjectToId(realid, object);
835                 object->id = realid;
837                 /* Redefine ID, if required */
838                 if ((id == NULL) || (strcmp(id, realid) != 0)) {
839                     object->repr->setAttribute("id", realid);
840                 }
841             } else if (id) {
842                 // bind if id, but no conflict -- otherwise, we can expect
843                 // a subsequent setting of the id attribute
844                 if (!object->document->getObjectById(id)) {
845                     object->document->bindObjectToId(id, object);
846                     object->id = g_strdup(id);
847                 }
848             }
849         }
850     } else {
851         g_assert(object->id == NULL);
852     }
854     /* Invoke derived methods, if any */
855     if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->build) {
856         (*((SPObjectClass *) G_OBJECT_GET_CLASS(object))->build)(object, document, repr);
857     }
859     /* Signalling (should be connected AFTER processing derived methods */
860     sp_repr_add_listener(repr, &object_event_vector, object);
863 void SPObject::releaseReferences() {
864     g_assert(this->document);
865     g_assert(this->repr);
867     sp_repr_remove_listener_by_data(this->repr, this);
869     this->_release_signal.emit(this);
870     SPObjectClass *klass=(SPObjectClass *)G_OBJECT_GET_CLASS(this);
871     if (klass->release) {
872         klass->release(this);
873     }
875     /* all hrefs should be released by the "release" handlers */
876     g_assert(this->hrefcount == 0);
878     if (!SP_OBJECT_IS_CLONED(this)) {
879         if (this->id) {
880             this->document->bindObjectToId(this->id, NULL);
881         }
882         g_free(this->id);
883         this->id = NULL;
885         g_free(this->_default_label);
886         this->_default_label = NULL;
888         this->document->bindObjectToRepr(this->repr, NULL);
890         Inkscape::GC::release(this->repr);
891     } else {
892         g_assert(!this->id);
893     }
895     if (this->style) {
896         this->style = sp_style_unref(this->style);
897     }
899     this->document = NULL;
900     this->repr = NULL;
903 /**
904  * Callback for child_added node event.
905  */
906 static void
907 sp_object_repr_child_added(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, gpointer data)
909     SPObject *object = SP_OBJECT(data);
911     if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->child_added)
912         (*((SPObjectClass *)G_OBJECT_GET_CLASS(object))->child_added)(object, child, ref);
915 /**
916  * Callback for remove_child node event.
917  */
918 static void
919 sp_object_repr_child_removed(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node */*ref*/, gpointer data)
921     SPObject *object = SP_OBJECT(data);
923     if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->remove_child) {
924         (* ((SPObjectClass *)G_OBJECT_GET_CLASS(object))->remove_child)(object, child);
925     }
928 /**
929  * Callback for order_changed node event.
930  *
931  * \todo fixme:
932  */
933 static void
934 sp_object_repr_order_changed(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node *old, Inkscape::XML::Node *newer, gpointer data)
936     SPObject *object = SP_OBJECT(data);
938     if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->order_changed) {
939         (* ((SPObjectClass *)G_OBJECT_GET_CLASS(object))->order_changed)(object, child, old, newer);
940     }
943 /**
944  * Callback for set event.
945  */
946 static void
947 sp_object_private_set(SPObject *object, unsigned int key, gchar const *value)
949     g_assert(key != SP_ATTR_INVALID);
951     switch (key) {
952         case SP_ATTR_ID:
953             if ( !SP_OBJECT_IS_CLONED(object) && object->repr->type() == Inkscape::XML::ELEMENT_NODE ) {
954                 SPDocument *document=object->document;
955                 SPObject *conflict=NULL;
957                 gchar const *new_id = value;
959                 if (new_id) {
960                     conflict = document->getObjectById((char const *)new_id);
961                 }
963                 if ( conflict && conflict != object ) {
964                     if (!document->isSeeking()) {
965                         sp_object_ref(conflict, NULL);
966                         // give the conflicting object a new ID
967                         gchar *new_conflict_id = sp_object_get_unique_id(conflict, NULL);
968                         SP_OBJECT_REPR(conflict)->setAttribute("id", new_conflict_id);
969                         g_free(new_conflict_id);
970                         sp_object_unref(conflict, NULL);
971                     } else {
972                         new_id = NULL;
973                     }
974                 }
976                 if (object->id) {
977                     document->bindObjectToId(object->id, NULL);
978                     g_free(object->id);
979                 }
981                 if (new_id) {
982                     object->id = g_strdup((char const*)new_id);
983                     document->bindObjectToId(object->id, object);
984                 } else {
985                     object->id = NULL;
986                 }
988                 g_free(object->_default_label);
989                 object->_default_label = NULL;
990             }
991             break;
992         case SP_ATTR_INKSCAPE_LABEL:
993             g_free(object->_label);
994             if (value) {
995                 object->_label = g_strdup(value);
996             } else {
997                 object->_label = NULL;
998             }
999             g_free(object->_default_label);
1000             object->_default_label = NULL;
1001             break;
1002         case SP_ATTR_INKSCAPE_COLLECT:
1003             if ( value && !strcmp(value, "always") ) {
1004                 object->setCollectionPolicy(SPObject::ALWAYS_COLLECT);
1005             } else {
1006                 object->setCollectionPolicy(SPObject::COLLECT_WITH_PARENT);
1007             }
1008             break;
1009         case SP_ATTR_XML_SPACE:
1010             if (value && !strcmp(value, "preserve")) {
1011                 object->xml_space.value = SP_XML_SPACE_PRESERVE;
1012                 object->xml_space.set = TRUE;
1013             } else if (value && !strcmp(value, "default")) {
1014                 object->xml_space.value = SP_XML_SPACE_DEFAULT;
1015                 object->xml_space.set = TRUE;
1016             } else if (object->parent) {
1017                 SPObject *parent;
1018                 parent = object->parent;
1019                 object->xml_space.value = parent->xml_space.value;
1020             }
1021             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1022             break;
1023         case SP_ATTR_STYLE:
1024             sp_style_read_from_object(object->style, object);
1025             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1026             break;
1027         default:
1028             break;
1029     }
1032 /**
1033  * Call virtual set() function of object.
1034  */
1035 void
1036 sp_object_set(SPObject *object, unsigned int key, gchar const *value)
1038     g_assert(object != NULL);
1039     g_assert(SP_IS_OBJECT(object));
1041     if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->set) {
1042         ((SPObjectClass *) G_OBJECT_GET_CLASS(object))->set(object, key, value);
1043     }
1046 /**
1047  * Read value of key attribute from XML node into object.
1048  */
1049 void
1050 sp_object_read_attr(SPObject *object, gchar const *key)
1052     g_assert(object != NULL);
1053     g_assert(SP_IS_OBJECT(object));
1054     g_assert(key != NULL);
1056     g_assert(object->repr != NULL);
1058     unsigned int keyid = sp_attribute_lookup(key);
1059     if (keyid != SP_ATTR_INVALID) {
1060         /* Retrieve the 'key' attribute from the object's XML representation */
1061         gchar const *value = object->repr->attribute(key);
1063         sp_object_set(object, keyid, value);
1064     }
1067 /**
1068  * Callback for attr_changed node event.
1069  */
1070 static void
1071 sp_object_repr_attr_changed(Inkscape::XML::Node */*repr*/, gchar const *key, gchar const */*oldval*/, gchar const */*newval*/, bool is_interactive, gpointer data)
1073     SPObject *object = SP_OBJECT(data);
1075     sp_object_read_attr(object, key);
1077     // manual changes to extension attributes require the normal
1078     // attributes, which depend on them, to be updated immediately
1079     if (is_interactive) {
1080         object->updateRepr(0);
1081     }
1084 /**
1085  * Callback for content_changed node event.
1086  */
1087 static void
1088 sp_object_repr_content_changed(Inkscape::XML::Node */*repr*/, gchar const */*oldcontent*/, gchar const */*newcontent*/, gpointer data)
1090     SPObject *object = SP_OBJECT(data);
1092     if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->read_content)
1093         (*((SPObjectClass *) G_OBJECT_GET_CLASS(object))->read_content)(object);
1096 /**
1097  * Return string representation of space value.
1098  */
1099 static gchar const*
1100 sp_xml_get_space_string(unsigned int space)
1102     switch (space) {
1103         case SP_XML_SPACE_DEFAULT:
1104             return "default";
1105         case SP_XML_SPACE_PRESERVE:
1106             return "preserve";
1107         default:
1108             return NULL;
1109     }
1112 /**
1113  * Callback for write event.
1114  */
1115 static Inkscape::XML::Node *
1116 sp_object_private_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags)
1118     if (!repr && (flags & SP_OBJECT_WRITE_BUILD)) {
1119         repr = SP_OBJECT_REPR(object)->duplicate(doc);
1120         if (!( flags & SP_OBJECT_WRITE_EXT )) {
1121             repr->setAttribute("inkscape:collect", NULL);
1122         }
1123     } else {
1124         repr->setAttribute("id", object->id);
1126         if (object->xml_space.set) {
1127             char const *xml_space;
1128             xml_space = sp_xml_get_space_string(object->xml_space.value);
1129             repr->setAttribute("xml:space", xml_space);
1130         }
1132         if ( flags & SP_OBJECT_WRITE_EXT &&
1133              object->collectionPolicy() == SPObject::ALWAYS_COLLECT )
1134         {
1135             repr->setAttribute("inkscape:collect", "always");
1136         } else {
1137             repr->setAttribute("inkscape:collect", NULL);
1138         }
1139  
1140         SPStyle const *const obj_style = SP_OBJECT_STYLE(object);
1141         if (obj_style) {
1142             gchar *s = sp_style_write_string(obj_style, SP_STYLE_FLAG_IFSET);
1143             repr->setAttribute("style", ( *s ? s : NULL ));
1144             g_free(s);
1145         } else {
1146             /** \todo I'm not sure what to do in this case.  Bug #1165868
1147              * suggests that it can arise, but the submitter doesn't know
1148              * how to do so reliably.  The main two options are either
1149              * leave repr's style attribute unchanged, or explicitly clear it.
1150              * Must also consider what to do with property attributes for
1151              * the element; see below.
1152              */
1153             char const *style_str = repr->attribute("style");
1154             if (!style_str) {
1155                 style_str = "NULL";
1156             }
1157             g_warning("Item's style is NULL; repr style attribute is %s", style_str);
1158         }
1160         /** \note We treat object->style as authoritative.  Its effects have
1161          * been written to the style attribute above; any properties that are
1162          * unset we take to be deliberately unset (e.g. so that clones can
1163          * override the property).
1164          *
1165          * Note that the below has an undesirable consequence of changing the
1166          * appearance on renderers that lack CSS support (e.g. SVG tiny);
1167          * possibly we should write property attributes instead of a style
1168          * attribute.
1169          */
1170         sp_style_unset_property_attrs (object);
1171     }
1173     return repr;
1176 /**
1177  * Update this object's XML node with flags value.
1178  */
1179 Inkscape::XML::Node *
1180 SPObject::updateRepr(unsigned int flags) {
1181     if (!SP_OBJECT_IS_CLONED(this)) {
1182         Inkscape::XML::Node *repr=SP_OBJECT_REPR(this);
1183         if (repr) {
1184             return updateRepr(repr->document(), repr, flags);
1185         } else {
1186             g_critical("Attempt to update non-existent repr");
1187             return NULL;
1188         }
1189     } else {
1190         /* cloned objects have no repr */
1191         return NULL;
1192     }
1195 /** Used both to create reprs in the original document, and to create 
1196  *  reprs in another document (e.g. a temporary document used when
1197  *  saving as "Plain SVG"
1198  */
1199 Inkscape::XML::Node *
1200 SPObject::updateRepr(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, unsigned int flags) {
1201     g_assert(doc != NULL);
1203     if (SP_OBJECT_IS_CLONED(this)) {
1204         /* cloned objects have no repr */
1205         return NULL;
1206     }
1207     if (((SPObjectClass *) G_OBJECT_GET_CLASS(this))->write) {
1208         if (!(flags & SP_OBJECT_WRITE_BUILD) && !repr) {
1209             repr = SP_OBJECT_REPR(this);
1210         }
1211         return ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->write(this, doc, repr, flags);
1212     } else {
1213         g_warning("Class %s does not implement ::write", G_OBJECT_TYPE_NAME(this));
1214         if (!repr) {
1215             if (flags & SP_OBJECT_WRITE_BUILD) {
1216                 repr = SP_OBJECT_REPR(this)->duplicate(doc);
1217             }
1218             /// \todo FIXME: else probably error (Lauris) */
1219         } else {
1220             repr->mergeFrom(SP_OBJECT_REPR(this), "id");
1221         }
1222         return repr;
1223     }
1226 /* Modification */
1228 /**
1229  * Add \a flags to \a object's as dirtiness flags, and
1230  * recursively add CHILD_MODIFIED flag to
1231  * parent and ancestors (as far up as necessary).
1232  */
1233 void
1234 SPObject::requestDisplayUpdate(unsigned int flags)
1236     g_return_if_fail( this->document != NULL );
1238     if (update_in_progress) {
1239         g_print("WARNING: Requested update while update in progress, counter = %d\n", update_in_progress);
1240     }
1242     /* requestModified must be used only to set one of SP_OBJECT_MODIFIED_FLAG or
1243      * SP_OBJECT_CHILD_MODIFIED_FLAG */
1244     g_return_if_fail(!(flags & SP_OBJECT_PARENT_MODIFIED_FLAG));
1245     g_return_if_fail((flags & SP_OBJECT_MODIFIED_FLAG) || (flags & SP_OBJECT_CHILD_MODIFIED_FLAG));
1246     g_return_if_fail(!((flags & SP_OBJECT_MODIFIED_FLAG) && (flags & SP_OBJECT_CHILD_MODIFIED_FLAG)));
1248     bool already_propagated = (!(this->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG)));
1250     this->uflags |= flags;
1252     /* If requestModified has already been called on this object or one of its children, then we
1253      * don't need to set CHILD_MODIFIED on our ancestors because it's already been done.
1254      */
1255     if (already_propagated) {
1256         SPObject *parent = SP_OBJECT_PARENT(this);
1257         if (parent) {
1258             parent->requestDisplayUpdate(SP_OBJECT_CHILD_MODIFIED_FLAG);
1259         } else {
1260             sp_document_request_modified(SP_OBJECT_DOCUMENT(this));
1261         }
1262     }
1265 /**
1266  * Update views
1267  */
1268 void
1269 SPObject::updateDisplay(SPCtx *ctx, unsigned int flags)
1271     g_return_if_fail(!(flags & ~SP_OBJECT_MODIFIED_CASCADE));
1273     update_in_progress ++;
1275 #ifdef SP_OBJECT_DEBUG_CASCADE
1276     g_print("Update %s:%s %x %x %x\n", g_type_name_from_instance((GTypeInstance *) this), SP_OBJECT_ID(this), flags, this->uflags, this->mflags);
1277 #endif
1279     /* Get this flags */
1280     flags |= this->uflags;
1281     /* Copy flags to modified cascade for later processing */
1282     this->mflags |= this->uflags;
1283     /* We have to clear flags here to allow rescheduling update */
1284     this->uflags = 0;
1286     // Merge style if we have good reasons to think that parent style is changed */
1287     /** \todo
1288      * I am not sure whether we should check only propagated
1289      * flag. We are currently assuming that style parsing is
1290      * done immediately. I think this is correct (Lauris).
1291      */
1292     if ((flags & SP_OBJECT_STYLE_MODIFIED_FLAG) && (flags & SP_OBJECT_PARENT_MODIFIED_FLAG)) {
1293         if (this->style && this->parent) {
1294             sp_style_merge_from_parent(this->style, this->parent->style);
1295         }
1296     }
1298     try
1299     {
1300         if (((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update)
1301             ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update(this, ctx, flags);
1302     }
1303     catch(...)
1304     {
1305         /** \todo 
1306         * in case of catching an exception we need to inform the user somehow that the document is corrupted
1307         * maybe by implementing an document flag documentOk
1308         * or by a modal error dialog
1309         */
1310         g_warning("SPObject::updateDisplay(SPCtx *ctx, unsigned int flags) : throw in ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update(this, ctx, flags);");
1311     }
1313     update_in_progress --;
1316 /**
1317  * Request modified always bubbles *up* the tree, as opposed to 
1318  * request display update, which trickles down and relies on the 
1319  * flags set during this pass...
1320  */
1321 void
1322 SPObject::requestModified(unsigned int flags)
1324     g_return_if_fail( this->document != NULL );
1326     /* requestModified must be used only to set one of SP_OBJECT_MODIFIED_FLAG or
1327      * SP_OBJECT_CHILD_MODIFIED_FLAG */
1328     g_return_if_fail(!(flags & SP_OBJECT_PARENT_MODIFIED_FLAG));
1329     g_return_if_fail((flags & SP_OBJECT_MODIFIED_FLAG) || (flags & SP_OBJECT_CHILD_MODIFIED_FLAG));
1330     g_return_if_fail(!((flags & SP_OBJECT_MODIFIED_FLAG) && (flags & SP_OBJECT_CHILD_MODIFIED_FLAG)));
1332     bool already_propagated = (!(this->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG)));
1334     this->mflags |= flags;
1336     /* If requestModified has already been called on this object or one of its children, then we
1337      * don't need to set CHILD_MODIFIED on our ancestors because it's already been done.
1338      */
1339     if (already_propagated) {
1340         SPObject *parent=SP_OBJECT_PARENT(this);
1341         if (parent) {
1342             parent->requestModified(SP_OBJECT_CHILD_MODIFIED_FLAG);
1343         } else {
1344             sp_document_request_modified(SP_OBJECT_DOCUMENT(this));
1345         }
1346     }
1349 /** 
1350  *  Emits the MODIFIED signal with the object's flags.
1351  *  The object's mflags are the original set aside during the update pass for 
1352  *  later delivery here.  Once emitModified() is called, those flags don't
1353  *  need to be stored any longer.
1354  */
1355 void
1356 SPObject::emitModified(unsigned int flags)
1358     /* only the MODIFIED_CASCADE flag is legal here */
1359     g_return_if_fail(!(flags & ~SP_OBJECT_MODIFIED_CASCADE));
1361 #ifdef SP_OBJECT_DEBUG_CASCADE
1362     g_print("Modified %s:%s %x %x %x\n", g_type_name_from_instance((GTypeInstance *) this), SP_OBJECT_ID(this), flags, this->uflags, this->mflags);
1363 #endif
1365     flags |= this->mflags;
1366     /* We have to clear mflags beforehand, as signal handlers may
1367      * make changes and therefore queue new modification notifications
1368      * themselves. */
1369     this->mflags = 0;
1371     g_object_ref(G_OBJECT(this));
1372     SPObjectClass *klass=(SPObjectClass *)G_OBJECT_GET_CLASS(this);
1373     if (klass->modified) {
1374         klass->modified(this, flags);
1375     }
1376     _modified_signal.emit(this, flags);
1377     g_object_unref(G_OBJECT(this));
1380 gchar const *
1381 sp_object_tagName_get(SPObject const *object, SPException *ex)
1383     /* If exception is not clear, return */
1384     if (!SP_EXCEPTION_IS_OK(ex)) {
1385         return NULL;
1386     }
1388     /// \todo fixme: Exception if object is NULL? */
1389     return object->repr->name();
1392 gchar const *
1393 sp_object_getAttribute(SPObject const *object, gchar const *key, SPException *ex)
1395     /* If exception is not clear, return */
1396     if (!SP_EXCEPTION_IS_OK(ex)) {
1397         return NULL;
1398     }
1400     /// \todo fixme: Exception if object is NULL? */
1401     return (gchar const *) object->repr->attribute(key);
1404 void
1405 sp_object_setAttribute(SPObject *object, gchar const *key, gchar const *value, SPException *ex)
1407     /* If exception is not clear, return */
1408     g_return_if_fail(SP_EXCEPTION_IS_OK(ex));
1410     /// \todo fixme: Exception if object is NULL? */
1411     object->repr->setAttribute(key, value, false);
1414 void
1415 sp_object_removeAttribute(SPObject *object, gchar const *key, SPException *ex)
1417     /* If exception is not clear, return */
1418     g_return_if_fail(SP_EXCEPTION_IS_OK(ex));
1420     /// \todo fixme: Exception if object is NULL? */
1421     object->repr->setAttribute(key, NULL, false);
1424 /* Helper */
1426 static gchar *
1427 sp_object_get_unique_id(SPObject *object, gchar const *id)
1429     static unsigned long count = 0;
1431     g_assert(SP_IS_OBJECT(object));
1433     count++;
1435     gchar const *name = object->repr->name();
1436     g_assert(name != NULL);
1438     gchar const *local = strchr(name, ':');
1439     if (local) {
1440         name = local + 1;
1441     }
1443     if (id != NULL) {
1444         if (object->document->getObjectById(id) == NULL) {
1445             return g_strdup(id);
1446         }
1447     }
1449     size_t const name_len = strlen(name);
1450     size_t const buflen = name_len + (sizeof(count) * 10 / 4) + 1;
1451     gchar *const buf = (gchar *) g_malloc(buflen);
1452     memcpy(buf, name, name_len);
1453     gchar *const count_buf = buf + name_len;
1454     size_t const count_buflen = buflen - name_len;
1455     do {
1456         ++count;
1457         g_snprintf(count_buf, count_buflen, "%lu", count);
1458     } while ( object->document->getObjectById(buf) != NULL );
1459     return buf;
1462 /* Style */
1464 /**
1465  * Returns an object style property.
1466  *
1467  * \todo
1468  * fixme: Use proper CSS parsing.  The current version is buggy
1469  * in a number of situations where key is a substring of the
1470  * style string other than as a property name (including
1471  * where key is a substring of a property name), and is also
1472  * buggy in its handling of inheritance for properties that
1473  * aren't inherited by default.  It also doesn't allow for
1474  * the case where the property is specified but with an invalid
1475  * value (in which case I believe the CSS2 error-handling
1476  * behaviour applies, viz. behave as if the property hadn't
1477  * been specified).  Also, the current code doesn't use CRSelEng
1478  * stuff to take a value from stylesheets.  Also, we aren't
1479  * setting any hooks to force an update for changes in any of
1480  * the inputs (i.e., in any of the elements that this function
1481  * queries).
1482  *
1483  * \par
1484  * Given that the default value for a property depends on what
1485  * property it is (e.g., whether to inherit or not), and given
1486  * the above comment about ignoring invalid values, and that the
1487  * repr parent isn't necessarily the right element to inherit
1488  * from (e.g., maybe we need to inherit from the referencing
1489  * <use> element instead), we should probably make the caller
1490  * responsible for ascending the repr tree as necessary.
1491  */
1492 gchar const *
1493 sp_object_get_style_property(SPObject const *object, gchar const *key, gchar const *def)
1495     g_return_val_if_fail(object != NULL, NULL);
1496     g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
1497     g_return_val_if_fail(key != NULL, NULL);
1499     gchar const *style = object->repr->attribute("style");
1500     if (style) {
1501         size_t const len = strlen(key);
1502         char const *p;
1503         while ( (p = strstr(style, key))
1504                 != NULL )
1505         {
1506             p += len;
1507             while ((*p <= ' ') && *p) p++;
1508             if (*p++ != ':') break;
1509             while ((*p <= ' ') && *p) p++;
1510             size_t const inherit_len = sizeof("inherit") - 1;
1511             if (*p
1512                 && !(strneq(p, "inherit", inherit_len)
1513                      && (p[inherit_len] == '\0'
1514                          || p[inherit_len] == ';'
1515                          || g_ascii_isspace(p[inherit_len])))) {
1516                 return p;
1517             }
1518         }
1519     }
1520     gchar const *val = object->repr->attribute(key);
1521     if (val && !streq(val, "inherit")) {
1522         return val;
1523     }
1524     if (object->parent) {
1525         return sp_object_get_style_property(object->parent, key, def);
1526     }
1528     return def;
1531 /**
1532  * Lifts SVG version of all root objects to version.
1533  */
1534 void
1535 SPObject::_requireSVGVersion(Inkscape::Version version) {
1536     for ( SPObject::ParentIterator iter=this ; iter ; ++iter ) {
1537         SPObject *object=iter;
1538         if (SP_IS_ROOT(object)) {
1539             SPRoot *root=SP_ROOT(object);
1540             if ( root->version.svg < version ) {
1541                 root->version.svg = version;
1542             }
1543         }
1544     }
1547 /**
1548  * Return sodipodi version of first root ancestor or (0,0).
1549  */
1550 Inkscape::Version
1551 sp_object_get_sodipodi_version(SPObject *object)
1553     static Inkscape::Version const zero_version(0, 0);
1555     while (object) {
1556         if (SP_IS_ROOT(object)) {
1557             return SP_ROOT(object)->version.sodipodi;
1558         }
1559         object = SP_OBJECT_PARENT(object);
1560     }
1562     return zero_version;
1565 /**
1566  * Returns previous object in sibling list or NULL.
1567  */
1568 SPObject *
1569 sp_object_prev(SPObject *child)
1571     SPObject *parent = SP_OBJECT_PARENT(child);
1572     for ( SPObject *i = sp_object_first_child(parent); i; i = SP_OBJECT_NEXT(i) ) {
1573         if (SP_OBJECT_NEXT(i) == child)
1574             return i;
1575     }
1576     return NULL;
1579 /* Titles and descriptions */
1581 /* Note:
1582    Titles and descriptions are stored in 'title' and 'desc' child elements
1583    (see section 5.4 of the SVG 1.0 and 1.1 specifications).  The spec allows
1584    an element to have more than one 'title' child element, but strongly
1585    recommends against this and requires using the first one if a choice must
1586    be made.  The same applies to 'desc' elements.  Therefore, these functions
1587    ignore all but the first 'title' child element and first 'desc' child
1588    element, except when deleting a title or description.
1589 */
1591 /**
1592  * Returns the title of this object, or NULL if there is none.
1593  * The caller must free the returned string using g_free() - see comment
1594  * for getTitleOrDesc() below.
1595  */
1596 gchar *
1597 SPObject::title() const
1599     return getTitleOrDesc("svg:title");
1602 /**
1603  * Sets the title of this object
1604  * A NULL first argument is interpreted as meaning that the existing title
1605  * (if any) should be deleted.
1606  * The second argument is optional - see setTitleOrDesc() below for details.
1607  */
1608 bool
1609 SPObject::setTitle(gchar const *title, bool verbatim)
1611     return setTitleOrDesc(title, "svg:title", verbatim);
1614 /**
1615  * Returns the description of this object, or NULL if there is none.
1616  * The caller must free the returned string using g_free() - see comment
1617  * for getTitleOrDesc() below.
1618  */
1619 gchar *
1620 SPObject::desc() const
1622     return getTitleOrDesc("svg:desc");
1625 /**
1626  * Sets the description of this object.
1627  * A NULL first argument is interpreted as meaning that the existing
1628  * description (if any) should be deleted.
1629  * The second argument is optional - see setTitleOrDesc() below for details.
1630  */
1631 bool
1632 SPObject::setDesc(gchar const *desc, bool verbatim)
1634     return setTitleOrDesc(desc, "svg:desc", verbatim);
1637 /**
1638  * Returns the title or description of this object, or NULL if there is none.
1639  *
1640  * The SVG spec allows 'title' and 'desc' elements to contain text marked up
1641  * using elements from other namespaces.  Therefore, this function cannot
1642  * in general just return a pointer to an existing string - it must instead
1643  * construct a string containing the title or description without the mark-up.
1644  * Consequently, the return value is a newly allocated string (or NULL), and
1645  * must be freed (using g_free()) by the caller.
1646  */
1647 gchar *
1648 SPObject::getTitleOrDesc(gchar const *svg_tagname) const
1650     SPObject *elem = findFirstChild(svg_tagname);
1651     if (elem == NULL) return NULL;
1652     return g_string_free(elem->textualContent(), FALSE);
1655 /**
1656  * Sets or deletes the title or description of this object.
1657  * A NULL 'value' argument causes the title or description to be deleted.
1658  *
1659  * 'verbatim' parameter:
1660  * If verbatim==true, then the title or description is set to exactly the
1661  * specified value.  If verbatim==false then two exceptions are made:
1662  *   (1) If the specified value is just whitespace, then the title/description
1663  *       is deleted.
1664  *   (2) If the specified value is the same as the current value except for
1665  *       mark-up, then the current value is left unchanged.
1666  * This is usually the desired behaviour, so 'verbatim' defaults to false for
1667  * setTitle() and setDesc().
1668  *
1669  * The return value is true if a change was made to the title/description,
1670  * and usually false otherwise.
1671  */
1672 bool
1673 SPObject::setTitleOrDesc(gchar const *value, gchar const *svg_tagname, bool verbatim)
1675     if (!verbatim) {
1676         // If the new title/description is just whitespace,
1677         // treat it as though it were NULL.
1678         if (value) {
1679             bool just_whitespace = true;
1680             for (const gchar *cp = value; *cp; ++cp) {
1681                 if (!std::strchr("\r\n \t", *cp)) {
1682                     just_whitespace = false;
1683                     break;
1684                 }
1685             }
1686             if (just_whitespace) value = NULL;
1687         }
1688         // Don't stomp on mark-up if there is no real change.
1689         if (value) {
1690             gchar *current_value = getTitleOrDesc(svg_tagname);
1691             if (current_value) {
1692                 bool different = std::strcmp(current_value, value);
1693                 g_free(current_value);
1694                 if (!different) return false;
1695             }
1696         }
1697     }
1699     SPObject *elem = findFirstChild(svg_tagname);
1701     if (value == NULL) {
1702         if (elem == NULL) return false;
1703         // delete the title/description(s)
1704         while (elem) {
1705             elem->deleteObject();
1706             elem = findFirstChild(svg_tagname);
1707         }
1708         return true;
1709     }
1711     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
1713     if (elem == NULL) {
1714         // create a new 'title' or 'desc' element, putting it at the
1715         // beginning (in accordance with the spec's recommendations)
1716         Inkscape::XML::Node *xml_elem = xml_doc->createElement(svg_tagname);
1717         repr->addChild(xml_elem, NULL);
1718         elem = document->getObjectByRepr(xml_elem);
1719         Inkscape::GC::release(xml_elem);
1720     }
1721     else {
1722         // remove the current content of the 'text' or 'desc' element
1723         SPObject *child;
1724         while (NULL != (child = elem->firstChild())) child->deleteObject();
1725     }
1727     // add the new content
1728     elem->appendChildRepr(xml_doc->createTextNode(value));
1729     return true;
1732 /**
1733  * Find the first child of this object with a given tag name,
1734  * and return it.  Returns NULL if there is no matching child.
1735  */
1736 SPObject *
1737 SPObject::findFirstChild(gchar const *tagname) const
1739     for (SPObject *child = children; child; child = child->next)
1740     {
1741         if (child->repr->type() == Inkscape::XML::ELEMENT_NODE &&
1742             !strcmp(child->repr->name(), tagname)) return child;
1743     }
1744     return NULL;
1747 /**
1748  * Return the full textual content of an element (typically all the
1749  * content except the tags).
1750  * Must not be used on anything except elements.
1751  */
1752 GString*
1753 SPObject::textualContent() const
1755     GString* text = g_string_new("");
1757     for (const SPObject *child = firstChild(); child; child = child->next)
1758     {
1759         Inkscape::XML::NodeType child_type = child->repr->type();
1760         
1761         if (child_type == Inkscape::XML::ELEMENT_NODE) {
1762             GString * new_text = child->textualContent();
1763             g_string_append(text, new_text->str);
1764             g_string_free(new_text, TRUE);
1765         }
1766         else if (child_type == Inkscape::XML::TEXT_NODE) {
1767             g_string_append(text, child->repr->content());
1768         }
1769     }
1770     return text;
1773 /*
1774   Local Variables:
1775   mode:c++
1776   c-file-style:"stroustrup"
1777   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1778   indent-tabs-mode:nil
1779   fill-column:99
1780   End:
1781 */
1782 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :