Code

Connector tool: make connectors avoid the convex hull of shapes.
[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 "sp-style-elem.h"
46 #include "sp-script.h"
47 #include "streq.h"
48 #include "strneq.h"
49 #include "xml/repr.h"
50 #include "xml/node-fns.h"
51 #include "debug/event-tracker.h"
52 #include "debug/simple-event.h"
53 #include "debug/demangle.h"
54 #include "util/share.h"
55 #include "util/format.h"
57 #include "algorithms/longest-common-suffix.h"
58 using std::memcpy;
59 using std::strchr;
60 using std::strcmp;
61 using std::strlen;
62 using std::strstr;
64 #define noSP_OBJECT_DEBUG_CASCADE
66 #define noSP_OBJECT_DEBUG
68 #ifdef SP_OBJECT_DEBUG
69 # define debug(f, a...) { g_print("%s(%d) %s:", \
70                                   __FILE__,__LINE__,__FUNCTION__); \
71                           g_print(f, ## a); \
72                           g_print("\n"); \
73                         }
74 #else
75 # define debug(f, a...) /**/
76 #endif
78 static void sp_object_class_init(SPObjectClass *klass);
79 static void sp_object_init(SPObject *object);
80 static void sp_object_finalize(GObject *object);
82 static void sp_object_child_added(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref);
83 static void sp_object_remove_child(SPObject *object, Inkscape::XML::Node *child);
84 static void sp_object_order_changed(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *old_ref, Inkscape::XML::Node *new_ref);
86 static void sp_object_release(SPObject *object);
87 static void sp_object_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
89 static void sp_object_private_set(SPObject *object, unsigned int key, gchar const *value);
90 static Inkscape::XML::Node *sp_object_private_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags);
92 /* Real handlers of repr signals */
94 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);
96 static void sp_object_repr_content_changed(Inkscape::XML::Node *repr, gchar const *oldcontent, gchar const *newcontent, gpointer data);
98 static void sp_object_repr_child_added(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, gpointer data);
99 static void sp_object_repr_child_removed(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, void *data);
101 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);
103 static gchar *sp_object_get_unique_id(SPObject *object, gchar const *defid);
105 guint update_in_progress = 0; // guard against update-during-update
107 Inkscape::XML::NodeEventVector object_event_vector = {
108     sp_object_repr_child_added,
109     sp_object_repr_child_removed,
110     sp_object_repr_attr_changed,
111     sp_object_repr_content_changed,
112     sp_object_repr_order_changed
113 };
115 static GObjectClass *parent_class;
117 /**
118  * Registers the SPObject class with Gdk and returns its type number.
119  */
120 GType
121 sp_object_get_type(void)
123     static GType type = 0;
124     if (!type) {
125         GTypeInfo info = {
126             sizeof(SPObjectClass),
127             NULL, NULL,
128             (GClassInitFunc) sp_object_class_init,
129             NULL, NULL,
130             sizeof(SPObject),
131             16,
132             (GInstanceInitFunc) sp_object_init,
133             NULL
134         };
135         type = g_type_register_static(G_TYPE_OBJECT, "SPObject", &info, (GTypeFlags)0);
136     }
137     return type;
140 /**
141  * Initializes the SPObject vtable.
142  */
143 static void
144 sp_object_class_init(SPObjectClass *klass)
146     GObjectClass *object_class;
148     object_class = (GObjectClass *) klass;
150     parent_class = (GObjectClass *) g_type_class_ref(G_TYPE_OBJECT);
152     object_class->finalize = sp_object_finalize;
154     klass->child_added = sp_object_child_added;
155     klass->remove_child = sp_object_remove_child;
156     klass->order_changed = sp_object_order_changed;
158     klass->release = sp_object_release;
160     klass->build = sp_object_build;
162     klass->set = sp_object_private_set;
163     klass->write = sp_object_private_write;
166 /**
167  * Callback to initialize the SPObject object.
168  */
169 static void
170 sp_object_init(SPObject *object)
172     debug("id=%x, typename=%s",object, g_type_name_from_instance((GTypeInstance*)object));
174     object->hrefcount = 0;
175     object->_total_hrefcount = 0;
176     object->document = NULL;
177     object->children = object->_last_child = NULL;
178     object->parent = object->next = NULL;
179     object->repr = NULL;
180     object->id = NULL;
182     object->_collection_policy = SPObject::COLLECT_WITH_PARENT;
184     new (&object->_release_signal) sigc::signal<void, SPObject *>();
185     new (&object->_modified_signal) sigc::signal<void, SPObject *, unsigned int>();
186     new (&object->_delete_signal) sigc::signal<void, SPObject *>();
187     new (&object->_position_changed_signal) sigc::signal<void, SPObject *>();
188     object->_successor = NULL;
190     // FIXME: now we create style for all objects, but per SVG, only the following can have style attribute:
191     // vg, g, defs, desc, title, symbol, use, image, switch, path, rect, circle, ellipse, line, polyline, 
192     // polygon, text, tspan, tref, textPath, altGlyph, glyphRef, marker, linearGradient, radialGradient, 
193     // stop, pattern, clipPath, mask, filter, feImage, a, font, glyph, missing-glyph, foreignObject
194     object->style = sp_style_new_from_object(object);
196     object->_label = NULL;
197     object->_default_label = NULL;
200 /**
201  * Callback to destroy all members and connections of object and itself.
202  */
203 static void
204 sp_object_finalize(GObject *object)
206     SPObject *spobject = (SPObject *)object;
208     g_free(spobject->_label);
209     g_free(spobject->_default_label);
210     spobject->_label = NULL;
211     spobject->_default_label = NULL;
213     if (spobject->_successor) {
214         sp_object_unref(spobject->_successor, NULL);
215         spobject->_successor = NULL;
216     }
218     if (((GObjectClass *) (parent_class))->finalize) {
219         (* ((GObjectClass *) (parent_class))->finalize)(object);
220     }
222     spobject->_release_signal.~signal();
223     spobject->_modified_signal.~signal();
224     spobject->_delete_signal.~signal();
225     spobject->_position_changed_signal.~signal();
228 namespace {
230 namespace Debug = Inkscape::Debug;
231 namespace Util = Inkscape::Util;
233 typedef Debug::SimpleEvent<Debug::Event::REFCOUNT> BaseRefCountEvent;
235 class RefCountEvent : public BaseRefCountEvent {
236 public:
237     RefCountEvent(SPObject *object, int bias, Util::ptr_shared<char> name)
238     : BaseRefCountEvent(name)
239     {
240         _addProperty("object", Util::format("%p", object));
241         _addProperty("class", Debug::demangle(g_type_name(G_TYPE_FROM_INSTANCE(object))));
242         _addProperty("new-refcount", Util::format("%d", G_OBJECT(object)->ref_count + bias));
243     }
244 };
246 class RefEvent : public RefCountEvent {
247 public:
248     RefEvent(SPObject *object)
249     : RefCountEvent(object, 1, Util::share_static_string("sp-object-ref"))
250     {}
251 };
253 class UnrefEvent : public RefCountEvent {
254 public:
255     UnrefEvent(SPObject *object)
256     : RefCountEvent(object, -1, Util::share_static_string("sp-object-unref"))
257     {}
258 };
262 /**
263  * Increase reference count of object, with possible debugging.
264  *
265  * \param owner If non-NULL, make debug log entry.
266  * \return object, NULL is error.
267  * \pre object points to real object
268  */
269 SPObject *
270 sp_object_ref(SPObject *object, SPObject *owner)
272     g_return_val_if_fail(object != NULL, NULL);
273     g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
274     g_return_val_if_fail(!owner || SP_IS_OBJECT(owner), NULL);
276     Inkscape::Debug::EventTracker<RefEvent> tracker(object);
277     g_object_ref(G_OBJECT(object));
278     return object;
281 /**
282  * Decrease reference count of object, with possible debugging and
283  * finalization.
284  *
285  * \param owner If non-NULL, make debug log entry.
286  * \return always NULL
287  * \pre object points to real object
288  */
289 SPObject *
290 sp_object_unref(SPObject *object, SPObject *owner)
292     g_return_val_if_fail(object != NULL, NULL);
293     g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
294     g_return_val_if_fail(!owner || SP_IS_OBJECT(owner), NULL);
296     Inkscape::Debug::EventTracker<UnrefEvent> tracker(object);
297     g_object_unref(G_OBJECT(object));
298     return NULL;
301 /**
302  * Increase weak refcount.
303  *
304  * Hrefcount is used for weak references, for example, to
305  * determine whether any graphical element references a certain gradient
306  * node.
307  * \param owner Ignored.
308  * \return object, NULL is error
309  * \pre object points to real object
310  */
311 SPObject *
312 sp_object_href(SPObject *object, gpointer /*owner*/)
314     g_return_val_if_fail(object != NULL, NULL);
315     g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
317     object->hrefcount++;
318     object->_updateTotalHRefCount(1);
320     return object;
323 /**
324  * Decrease weak refcount.
325  *
326  * Hrefcount is used for weak references, for example, to determine whether
327  * any graphical element references a certain gradient node.
328  * \param owner Ignored.
329  * \return always NULL
330  * \pre object points to real object and hrefcount>0
331  */
332 SPObject *
333 sp_object_hunref(SPObject *object, gpointer /*owner*/)
335     g_return_val_if_fail(object != NULL, NULL);
336     g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
337     g_return_val_if_fail(object->hrefcount > 0, NULL);
339     object->hrefcount--;
340     object->_updateTotalHRefCount(-1);
342     return NULL;
345 /**
346  * Adds increment to _total_hrefcount of object and its parents.
347  */
348 void
349 SPObject::_updateTotalHRefCount(int increment) {
350     SPObject *topmost_collectable = NULL;
351     for ( SPObject *iter = this ; iter ; iter = SP_OBJECT_PARENT(iter) ) {
352         iter->_total_hrefcount += increment;
353         if ( iter->_total_hrefcount < iter->hrefcount ) {
354             g_critical("HRefs overcounted");
355         }
356         if ( iter->_total_hrefcount == 0 &&
357              iter->_collection_policy != COLLECT_WITH_PARENT )
358         {
359             topmost_collectable = iter;
360         }
361     }
362     if (topmost_collectable) {
363         topmost_collectable->requestOrphanCollection();
364     }
367 /**
368  * True if object is non-NULL and this is some in/direct parent of object.
369  */
370 bool
371 SPObject::isAncestorOf(SPObject const *object) const {
372     g_return_val_if_fail(object != NULL, false);
373     object = SP_OBJECT_PARENT(object);
374     while (object) {
375         if ( object == this ) {
376             return true;
377         }
378         object = SP_OBJECT_PARENT(object);
379     }
380     return false;
383 namespace {
385 bool same_objects(SPObject const &a, SPObject const &b) {
386     return &a == &b;
391 /**
392  * Returns youngest object being parent to this and object.
393  */
394 SPObject const *
395 SPObject::nearestCommonAncestor(SPObject const *object) const {
396     g_return_val_if_fail(object != NULL, NULL);
398     using Inkscape::Algorithms::longest_common_suffix;
399     return longest_common_suffix<SPObject::ConstParentIterator>(this, object, NULL, &same_objects);
402 SPObject const *AncestorSon(SPObject const *obj, SPObject const *ancestor) {
403     if (obj == NULL || ancestor == NULL)
404         return NULL;
405     if (SP_OBJECT_PARENT(obj) == ancestor)
406         return obj;
407     return AncestorSon(SP_OBJECT_PARENT(obj), ancestor);
410 /**
411  * Compares height of objects in tree.
412  *
413  * Works for different-parent objects, so long as they have a common ancestor.
414  * \return \verbatim
415  *    0    positions are equivalent
416  *    1    first object's position is greater than the second
417  *   -1    first object's position is less than the second   \endverbatim
418  */
419 int
420 sp_object_compare_position(SPObject const *first, SPObject const *second)
422     if (first == second) return 0;
424     SPObject const *ancestor = first->nearestCommonAncestor(second);
425     if (ancestor == NULL) return 0; // cannot compare, no common ancestor!
427     // we have an object and its ancestor (should not happen when sorting selection)
428     if (ancestor == first)
429         return 1;
430     if (ancestor == second)
431         return -1;
433     SPObject const *to_first = AncestorSon(first, ancestor);
434     SPObject const *to_second = AncestorSon(second, ancestor);
436     g_assert(SP_OBJECT_PARENT(to_second) == SP_OBJECT_PARENT(to_first));
438     return sp_repr_compare_position(SP_OBJECT_REPR(to_first), SP_OBJECT_REPR(to_second));
442 /**
443  * Append repr as child of this object.
444  * \pre this is not a cloned object
445  */
446 SPObject *
447 SPObject::appendChildRepr(Inkscape::XML::Node *repr) {
448     if (!SP_OBJECT_IS_CLONED(this)) {
449         SP_OBJECT_REPR(this)->appendChild(repr);
450         return SP_OBJECT_DOCUMENT(this)->getObjectByRepr(repr);
451     } else {
452         g_critical("Attempt to append repr as child of cloned object");
453         return NULL;
454     }
457 /**
458  * Retrieves the children as a GSList object, optionally ref'ing the children
459  * in the process, if add_ref is specified.
460  */
461 GSList *SPObject::childList(bool add_ref, Action) {
462     GSList *l = NULL;
463     for (SPObject *child = sp_object_first_child(this) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
464         if (add_ref)
465             g_object_ref (G_OBJECT (child));
467         l = g_slist_prepend (l, child);
468     }
469     return l;
473 /** Gets the label property for the object or a default if no label
474  *  is defined.
475  */
476 gchar const *
477 SPObject::label() const {
478     return _label;
481 /** Returns a default label property for the object. */
482 gchar const *
483 SPObject::defaultLabel() const {
484     if (_label) {
485         return _label;
486     } else {
487         if (!_default_label) {
488             gchar const *id=SP_OBJECT_ID(this);
489             if (id) {
490                 _default_label = g_strdup_printf("#%s", id);
491             } else {
492                 _default_label = g_strdup_printf("<%s>", SP_OBJECT_REPR(this)->name());
493             }
494         }
495         return _default_label;
496     }
499 /** Sets the label property for the object */
500 void
501 SPObject::setLabel(gchar const *label) {
502     SP_OBJECT_REPR(this)->setAttribute("inkscape:label", label, false);
506 /** Queues the object for orphan collection */
507 void
508 SPObject::requestOrphanCollection() {
509     g_return_if_fail(document != NULL);
511     // do not remove style or script elements (Bug #276244)
512     if (SP_IS_STYLE_ELEM(this))
513         return;
514     if (SP_IS_SCRIPT(this))
515         return;
517     document->queueForOrphanCollection(this);
519     /** \todo
520      * This is a temporary hack added to make fill&stroke rebuild its
521      * gradient list when the defs are vacuumed.  gradient-vector.cpp
522      * listens to the modified signal on defs, and now we give it that
523      * signal.  Mental says that this should be made automatic by
524      * merging SPObjectGroup with SPObject; SPObjectGroup would issue
525      * this signal automatically. Or maybe just derive SPDefs from
526      * SPObjectGroup?
527      */
529     this->requestModified(SP_OBJECT_CHILD_MODIFIED_FLAG);
532 /** Sends the delete signal to all children of this object recursively */
533 void
534 SPObject::_sendDeleteSignalRecursive() {
535     for (SPObject *child = sp_object_first_child(this); child; child = SP_OBJECT_NEXT(child)) {
536         child->_delete_signal.emit(child);
537         child->_sendDeleteSignalRecursive();
538     }
541 /**
542  * Deletes the object reference, unparenting it from its parent.
543  *
544  * If the \a propagate parameter is set to true, it emits a delete
545  * signal.  If the \a propagate_descendants parameter is true, it
546  * recursively sends the delete signal to children.
547  */
548 void
549 SPObject::deleteObject(bool propagate, bool propagate_descendants)
551     sp_object_ref(this, NULL);
552     if (propagate) {
553         _delete_signal.emit(this);
554     }
555     if (propagate_descendants) {
556         this->_sendDeleteSignalRecursive();
557     }
559     Inkscape::XML::Node *repr=SP_OBJECT_REPR(this);
560     if (repr && sp_repr_parent(repr)) {
561         sp_repr_unparent(repr);
562     }
564     if (_successor) {
565         _successor->deleteObject(propagate, propagate_descendants);
566     }
567     sp_object_unref(this, NULL);
570 /**
571  * Put object into object tree, under parent, and behind prev;
572  * also update object's XML space.
573  */
574 void
575 sp_object_attach(SPObject *parent, SPObject *object, SPObject *prev)
577     g_return_if_fail(parent != NULL);
578     g_return_if_fail(SP_IS_OBJECT(parent));
579     g_return_if_fail(object != NULL);
580     g_return_if_fail(SP_IS_OBJECT(object));
581     g_return_if_fail(!prev || SP_IS_OBJECT(prev));
582     g_return_if_fail(!prev || prev->parent == parent);
583     g_return_if_fail(!object->parent);
585     sp_object_ref(object, parent);
586     object->parent = parent;
587     parent->_updateTotalHRefCount(object->_total_hrefcount);
589     SPObject *next;
590     if (prev) {
591         next = prev->next;
592         prev->next = object;
593     } else {
594         next = parent->children;
595         parent->children = object;
596     }
597     object->next = next;
598     if (!next) {
599         parent->_last_child = object;
600     }
601     if (!object->xml_space.set)
602         object->xml_space.value = parent->xml_space.value;
605 /**
606  * In list of object's siblings, move object behind prev.
607  */
608 void
609 sp_object_reorder(SPObject *object, SPObject *prev) {
610     g_return_if_fail(object != NULL);
611     g_return_if_fail(SP_IS_OBJECT(object));
612     g_return_if_fail(object->parent != NULL);
613     g_return_if_fail(object != prev);
614     g_return_if_fail(!prev || SP_IS_OBJECT(prev));
615     g_return_if_fail(!prev || prev->parent == object->parent);
617     SPObject *const parent=object->parent;
619     SPObject *old_prev=NULL;
620     for ( SPObject *child = parent->children ; child && child != object ;
621           child = child->next )
622     {
623         old_prev = child;
624     }
626     SPObject *next=object->next;
627     if (old_prev) {
628         old_prev->next = next;
629     } else {
630         parent->children = next;
631     }
632     if (!next) {
633         parent->_last_child = old_prev;
634     }
635     if (prev) {
636         next = prev->next;
637         prev->next = object;
638     } else {
639         next = parent->children;
640         parent->children = object;
641     }
642     object->next = next;
643     if (!next) {
644         parent->_last_child = object;
645     }
648 /**
649  * Remove object from parent's children, release and unref it.
650  */
651 void
652 sp_object_detach(SPObject *parent, SPObject *object) {
653     g_return_if_fail(parent != NULL);
654     g_return_if_fail(SP_IS_OBJECT(parent));
655     g_return_if_fail(object != NULL);
656     g_return_if_fail(SP_IS_OBJECT(object));
657     g_return_if_fail(object->parent == parent);
659     object->releaseReferences();
661     SPObject *prev=NULL;
662     for ( SPObject *child = parent->children ; child && child != object ;
663           child = child->next )
664     {
665         prev = child;
666     }
668     SPObject *next=object->next;
669     if (prev) {
670         prev->next = next;
671     } else {
672         parent->children = next;
673     }
674     if (!next) {
675         parent->_last_child = prev;
676     }
678     object->next = NULL;
679     object->parent = NULL;
681     parent->_updateTotalHRefCount(-object->_total_hrefcount);
682     sp_object_unref(object, parent);
685 /**
686  * Return object's child whose node pointer equals repr.
687  */
688 SPObject *
689 sp_object_get_child_by_repr(SPObject *object, Inkscape::XML::Node *repr)
691     g_return_val_if_fail(object != NULL, NULL);
692     g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
693     g_return_val_if_fail(repr != NULL, NULL);
695     if (object->_last_child && SP_OBJECT_REPR(object->_last_child) == repr)
696         return object->_last_child;   // optimization for common scenario
697     for ( SPObject *child = object->children ; child ; child = child->next ) {
698         if ( SP_OBJECT_REPR(child) == repr ) {
699             return child;
700         }
701     }
703     return NULL;
706 /**
707  * Callback for child_added event.
708  * Invoked whenever the given mutation event happens in the XML tree.
709  */
710 static void
711 sp_object_child_added(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref)
713     GType type = sp_repr_type_lookup(child);
714     if (!type) {
715         return;
716     }
717     SPObject *ochild = SP_OBJECT(g_object_new(type, 0));
718     SPObject *prev = ref ? sp_object_get_child_by_repr(object, ref) : NULL;
719     sp_object_attach(object, ochild, prev);
720     sp_object_unref(ochild, NULL);
722     sp_object_invoke_build(ochild, object->document, child, SP_OBJECT_IS_CLONED(object));
725 /**
726  * Removes, releases and unrefs all children of object.
727  *
728  * This is the opposite of build. It has to be invoked as soon as the
729  * object is removed from the tree, even if it is still alive according
730  * to reference count. The frontend unregisters the object from the
731  * document and releases the SPRepr bindings; implementations should free
732  * state data and release all child objects.  Invoking release on
733  * SPRoot destroys the whole document tree.
734  * \see sp_object_build()
735  */
736 static void sp_object_release(SPObject *object)
738     debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object));
739     while (object->children) {
740         sp_object_detach(object, object->children);
741     }
744 /**
745  * Remove object's child whose node equals repr, release and
746  * unref it.
747  *
748  * Invoked whenever the given mutation event happens in the XML
749  * tree, BEFORE removal from the XML tree happens, so grouping
750  * objects can safely release the child data.
751  */
752 static void
753 sp_object_remove_child(SPObject *object, Inkscape::XML::Node *child)
755     debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object));
756     SPObject *ochild = sp_object_get_child_by_repr(object, child);
757     g_return_if_fail (ochild != NULL || !strcmp("comment", child->name())); // comments have no objects
758     if (ochild)
759         sp_object_detach(object, ochild);
762 /**
763  * Move object corresponding to child after sibling object corresponding
764  * to new_ref.
765  * Invoked whenever the given mutation event happens in the XML tree.
766  * \param old_ref Ignored
767  */
768 static void sp_object_order_changed(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node */*old_ref*/,
769                                     Inkscape::XML::Node *new_ref)
771     SPObject *ochild = sp_object_get_child_by_repr(object, child);
772     g_return_if_fail(ochild != NULL);
773     SPObject *prev = new_ref ? sp_object_get_child_by_repr(object, new_ref) : NULL;
774     sp_object_reorder(ochild, prev);
775     ochild->_position_changed_signal.emit(ochild);
778 /**
779  * Virtual build callback.
780  *
781  * This has to be invoked immediately after creation of an SPObject. The
782  * frontend method ensures that the new object is properly attached to
783  * the document and repr; implementation then will parse all of the attributes,
784  * generate the children objects and so on.  Invoking build on the SPRoot
785  * object results in creation of the whole document tree (this is, what
786  * SPDocument does after the creation of the XML tree).
787  * \see sp_object_release()
788  */
789 static void
790 sp_object_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
792     /* Nothing specific here */
793     debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object));
795     sp_object_read_attr(object, "xml:space");
796     sp_object_read_attr(object, "inkscape:label");
797     sp_object_read_attr(object, "inkscape:collect");
799     for (Inkscape::XML::Node *rchild = repr->firstChild() ; rchild != NULL; rchild = rchild->next()) {
800         GType type = sp_repr_type_lookup(rchild);
801         if (!type) {
802             continue;
803         }
804         SPObject *child = SP_OBJECT(g_object_new(type, 0));
805         sp_object_attach(object, child, object->lastChild());
806         sp_object_unref(child, NULL);
807         sp_object_invoke_build(child, document, rchild, SP_OBJECT_IS_CLONED(object));
808     }
811 void
812 sp_object_invoke_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr, unsigned int cloned)
814     debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object));
816     g_assert(object != NULL);
817     g_assert(SP_IS_OBJECT(object));
818     g_assert(document != NULL);
819     g_assert(repr != NULL);
821     g_assert(object->document == NULL);
822     g_assert(object->repr == NULL);
823     g_assert(object->id == NULL);
825     /* Bookkeeping */
827     object->document = document;
828     object->repr = repr;
829     if (!cloned)
830         Inkscape::GC::anchor(repr);
831     object->cloned = cloned;
833     if (!SP_OBJECT_IS_CLONED(object)) {
834         object->document->bindObjectToRepr(object->repr, object);
836         if (Inkscape::XML::id_permitted(object->repr)) {
837             /* If we are not cloned, and not seeking, force unique id */
838             gchar const *id = object->repr->attribute("id");
839             if (!document->isSeeking()) {
840                 gchar *realid = sp_object_get_unique_id(object, id);
841                 g_assert(realid != NULL);
843                 object->document->bindObjectToId(realid, object);
844                 object->id = realid;
846                 /* Redefine ID, if required */
847                 if ((id == NULL) || (strcmp(id, realid) != 0)) {
848                     object->repr->setAttribute("id", realid);
849                 }
850             } else if (id) {
851                 // bind if id, but no conflict -- otherwise, we can expect
852                 // a subsequent setting of the id attribute
853                 if (!object->document->getObjectById(id)) {
854                     object->document->bindObjectToId(id, object);
855                     object->id = g_strdup(id);
856                 }
857             }
858         }
859     } else {
860         g_assert(object->id == NULL);
861     }
863     /* Invoke derived methods, if any */
864     if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->build) {
865         (*((SPObjectClass *) G_OBJECT_GET_CLASS(object))->build)(object, document, repr);
866     }
868     /* Signalling (should be connected AFTER processing derived methods */
869     sp_repr_add_listener(repr, &object_event_vector, object);
872 void SPObject::releaseReferences() {
873     g_assert(this->document);
874     g_assert(this->repr);
876     sp_repr_remove_listener_by_data(this->repr, this);
878     this->_release_signal.emit(this);
879     SPObjectClass *klass=(SPObjectClass *)G_OBJECT_GET_CLASS(this);
880     if (klass->release) {
881         klass->release(this);
882     }
884     /* all hrefs should be released by the "release" handlers */
885     g_assert(this->hrefcount == 0);
887     if (!SP_OBJECT_IS_CLONED(this)) {
888         if (this->id) {
889             this->document->bindObjectToId(this->id, NULL);
890         }
891         g_free(this->id);
892         this->id = NULL;
894         g_free(this->_default_label);
895         this->_default_label = NULL;
897         this->document->bindObjectToRepr(this->repr, NULL);
899         Inkscape::GC::release(this->repr);
900     } else {
901         g_assert(!this->id);
902     }
904     if (this->style) {
905         this->style = sp_style_unref(this->style);
906     }
908     this->document = NULL;
909     this->repr = NULL;
912 /**
913  * Callback for child_added node event.
914  */
915 static void
916 sp_object_repr_child_added(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, gpointer data)
918     SPObject *object = SP_OBJECT(data);
920     if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->child_added)
921         (*((SPObjectClass *)G_OBJECT_GET_CLASS(object))->child_added)(object, child, ref);
924 /**
925  * Callback for remove_child node event.
926  */
927 static void
928 sp_object_repr_child_removed(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node */*ref*/, gpointer data)
930     SPObject *object = SP_OBJECT(data);
932     if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->remove_child) {
933         (* ((SPObjectClass *)G_OBJECT_GET_CLASS(object))->remove_child)(object, child);
934     }
937 /**
938  * Callback for order_changed node event.
939  *
940  * \todo fixme:
941  */
942 static void
943 sp_object_repr_order_changed(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node *old, Inkscape::XML::Node *newer, gpointer data)
945     SPObject *object = SP_OBJECT(data);
947     if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->order_changed) {
948         (* ((SPObjectClass *)G_OBJECT_GET_CLASS(object))->order_changed)(object, child, old, newer);
949     }
952 /**
953  * Callback for set event.
954  */
955 static void
956 sp_object_private_set(SPObject *object, unsigned int key, gchar const *value)
958     g_assert(key != SP_ATTR_INVALID);
960     switch (key) {
961         case SP_ATTR_ID:
962             if ( !SP_OBJECT_IS_CLONED(object) && object->repr->type() == Inkscape::XML::ELEMENT_NODE ) {
963                 SPDocument *document=object->document;
964                 SPObject *conflict=NULL;
966                 gchar const *new_id = value;
968                 if (new_id) {
969                     conflict = document->getObjectById((char const *)new_id);
970                 }
972                 if ( conflict && conflict != object ) {
973                     if (!document->isSeeking()) {
974                         sp_object_ref(conflict, NULL);
975                         // give the conflicting object a new ID
976                         gchar *new_conflict_id = sp_object_get_unique_id(conflict, NULL);
977                         SP_OBJECT_REPR(conflict)->setAttribute("id", new_conflict_id);
978                         g_free(new_conflict_id);
979                         sp_object_unref(conflict, NULL);
980                     } else {
981                         new_id = NULL;
982                     }
983                 }
985                 if (object->id) {
986                     document->bindObjectToId(object->id, NULL);
987                     g_free(object->id);
988                 }
990                 if (new_id) {
991                     object->id = g_strdup((char const*)new_id);
992                     document->bindObjectToId(object->id, object);
993                 } else {
994                     object->id = NULL;
995                 }
997                 g_free(object->_default_label);
998                 object->_default_label = NULL;
999             }
1000             break;
1001         case SP_ATTR_INKSCAPE_LABEL:
1002             g_free(object->_label);
1003             if (value) {
1004                 object->_label = g_strdup(value);
1005             } else {
1006                 object->_label = NULL;
1007             }
1008             g_free(object->_default_label);
1009             object->_default_label = NULL;
1010             break;
1011         case SP_ATTR_INKSCAPE_COLLECT:
1012             if ( value && !strcmp(value, "always") ) {
1013                 object->setCollectionPolicy(SPObject::ALWAYS_COLLECT);
1014             } else {
1015                 object->setCollectionPolicy(SPObject::COLLECT_WITH_PARENT);
1016             }
1017             break;
1018         case SP_ATTR_XML_SPACE:
1019             if (value && !strcmp(value, "preserve")) {
1020                 object->xml_space.value = SP_XML_SPACE_PRESERVE;
1021                 object->xml_space.set = TRUE;
1022             } else if (value && !strcmp(value, "default")) {
1023                 object->xml_space.value = SP_XML_SPACE_DEFAULT;
1024                 object->xml_space.set = TRUE;
1025             } else if (object->parent) {
1026                 SPObject *parent;
1027                 parent = object->parent;
1028                 object->xml_space.value = parent->xml_space.value;
1029             }
1030             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1031             break;
1032         case SP_ATTR_STYLE:
1033             sp_style_read_from_object(object->style, object);
1034             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1035             break;
1036         default:
1037             break;
1038     }
1041 /**
1042  * Call virtual set() function of object.
1043  */
1044 void
1045 sp_object_set(SPObject *object, unsigned int key, gchar const *value)
1047     g_assert(object != NULL);
1048     g_assert(SP_IS_OBJECT(object));
1050     if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->set) {
1051         ((SPObjectClass *) G_OBJECT_GET_CLASS(object))->set(object, key, value);
1052     }
1055 /**
1056  * Read value of key attribute from XML node into object.
1057  */
1058 void
1059 sp_object_read_attr(SPObject *object, gchar const *key)
1061     g_assert(object != NULL);
1062     g_assert(SP_IS_OBJECT(object));
1063     g_assert(key != NULL);
1065     g_assert(object->repr != NULL);
1067     unsigned int keyid = sp_attribute_lookup(key);
1068     if (keyid != SP_ATTR_INVALID) {
1069         /* Retrieve the 'key' attribute from the object's XML representation */
1070         gchar const *value = object->repr->attribute(key);
1072         sp_object_set(object, keyid, value);
1073     }
1076 /**
1077  * Callback for attr_changed node event.
1078  */
1079 static void
1080 sp_object_repr_attr_changed(Inkscape::XML::Node */*repr*/, gchar const *key, gchar const */*oldval*/, gchar const */*newval*/, bool is_interactive, gpointer data)
1082     SPObject *object = SP_OBJECT(data);
1084     sp_object_read_attr(object, key);
1086     // manual changes to extension attributes require the normal
1087     // attributes, which depend on them, to be updated immediately
1088     if (is_interactive) {
1089         object->updateRepr(0);
1090     }
1093 /**
1094  * Callback for content_changed node event.
1095  */
1096 static void
1097 sp_object_repr_content_changed(Inkscape::XML::Node */*repr*/, gchar const */*oldcontent*/, gchar const */*newcontent*/, gpointer data)
1099     SPObject *object = SP_OBJECT(data);
1101     if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->read_content)
1102         (*((SPObjectClass *) G_OBJECT_GET_CLASS(object))->read_content)(object);
1105 /**
1106  * Return string representation of space value.
1107  */
1108 static gchar const*
1109 sp_xml_get_space_string(unsigned int space)
1111     switch (space) {
1112         case SP_XML_SPACE_DEFAULT:
1113             return "default";
1114         case SP_XML_SPACE_PRESERVE:
1115             return "preserve";
1116         default:
1117             return NULL;
1118     }
1121 /**
1122  * Callback for write event.
1123  */
1124 static Inkscape::XML::Node *
1125 sp_object_private_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags)
1127     if (!repr && (flags & SP_OBJECT_WRITE_BUILD)) {
1128         repr = SP_OBJECT_REPR(object)->duplicate(doc);
1129         if (!( flags & SP_OBJECT_WRITE_EXT )) {
1130             repr->setAttribute("inkscape:collect", NULL);
1131         }
1132     } else {
1133         repr->setAttribute("id", object->id);
1135         if (object->xml_space.set) {
1136             char const *xml_space;
1137             xml_space = sp_xml_get_space_string(object->xml_space.value);
1138             repr->setAttribute("xml:space", xml_space);
1139         }
1141         if ( flags & SP_OBJECT_WRITE_EXT &&
1142              object->collectionPolicy() == SPObject::ALWAYS_COLLECT )
1143         {
1144             repr->setAttribute("inkscape:collect", "always");
1145         } else {
1146             repr->setAttribute("inkscape:collect", NULL);
1147         }
1148  
1149         SPStyle const *const obj_style = SP_OBJECT_STYLE(object);
1150         if (obj_style) {
1151             gchar *s = sp_style_write_string(obj_style, SP_STYLE_FLAG_IFSET);
1152             repr->setAttribute("style", ( *s ? s : NULL ));
1153             g_free(s);
1154         } else {
1155             /** \todo I'm not sure what to do in this case.  Bug #1165868
1156              * suggests that it can arise, but the submitter doesn't know
1157              * how to do so reliably.  The main two options are either
1158              * leave repr's style attribute unchanged, or explicitly clear it.
1159              * Must also consider what to do with property attributes for
1160              * the element; see below.
1161              */
1162             char const *style_str = repr->attribute("style");
1163             if (!style_str) {
1164                 style_str = "NULL";
1165             }
1166             g_warning("Item's style is NULL; repr style attribute is %s", style_str);
1167         }
1169         /** \note We treat object->style as authoritative.  Its effects have
1170          * been written to the style attribute above; any properties that are
1171          * unset we take to be deliberately unset (e.g. so that clones can
1172          * override the property).
1173          *
1174          * Note that the below has an undesirable consequence of changing the
1175          * appearance on renderers that lack CSS support (e.g. SVG tiny);
1176          * possibly we should write property attributes instead of a style
1177          * attribute.
1178          */
1179         sp_style_unset_property_attrs (object);
1180     }
1182     return repr;
1185 /**
1186  * Update this object's XML node with flags value.
1187  */
1188 Inkscape::XML::Node *
1189 SPObject::updateRepr(unsigned int flags) {
1190     if (!SP_OBJECT_IS_CLONED(this)) {
1191         Inkscape::XML::Node *repr=SP_OBJECT_REPR(this);
1192         if (repr) {
1193             return updateRepr(repr->document(), repr, flags);
1194         } else {
1195             g_critical("Attempt to update non-existent repr");
1196             return NULL;
1197         }
1198     } else {
1199         /* cloned objects have no repr */
1200         return NULL;
1201     }
1204 /** Used both to create reprs in the original document, and to create 
1205  *  reprs in another document (e.g. a temporary document used when
1206  *  saving as "Plain SVG"
1207  */
1208 Inkscape::XML::Node *
1209 SPObject::updateRepr(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, unsigned int flags) {
1210     g_assert(doc != NULL);
1212     if (SP_OBJECT_IS_CLONED(this)) {
1213         /* cloned objects have no repr */
1214         return NULL;
1215     }
1216     if (((SPObjectClass *) G_OBJECT_GET_CLASS(this))->write) {
1217         if (!(flags & SP_OBJECT_WRITE_BUILD) && !repr) {
1218             repr = SP_OBJECT_REPR(this);
1219         }
1220         return ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->write(this, doc, repr, flags);
1221     } else {
1222         g_warning("Class %s does not implement ::write", G_OBJECT_TYPE_NAME(this));
1223         if (!repr) {
1224             if (flags & SP_OBJECT_WRITE_BUILD) {
1225                 repr = SP_OBJECT_REPR(this)->duplicate(doc);
1226             }
1227             /// \todo FIXME: else probably error (Lauris) */
1228         } else {
1229             repr->mergeFrom(SP_OBJECT_REPR(this), "id");
1230         }
1231         return repr;
1232     }
1235 /* Modification */
1237 /**
1238  * Add \a flags to \a object's as dirtiness flags, and
1239  * recursively add CHILD_MODIFIED flag to
1240  * parent and ancestors (as far up as necessary).
1241  */
1242 void
1243 SPObject::requestDisplayUpdate(unsigned int flags)
1245     g_return_if_fail( this->document != NULL );
1247     if (update_in_progress) {
1248         g_print("WARNING: Requested update while update in progress, counter = %d\n", update_in_progress);
1249     }
1251     /* requestModified must be used only to set one of SP_OBJECT_MODIFIED_FLAG or
1252      * SP_OBJECT_CHILD_MODIFIED_FLAG */
1253     g_return_if_fail(!(flags & SP_OBJECT_PARENT_MODIFIED_FLAG));
1254     g_return_if_fail((flags & SP_OBJECT_MODIFIED_FLAG) || (flags & SP_OBJECT_CHILD_MODIFIED_FLAG));
1255     g_return_if_fail(!((flags & SP_OBJECT_MODIFIED_FLAG) && (flags & SP_OBJECT_CHILD_MODIFIED_FLAG)));
1257     bool already_propagated = (!(this->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG)));
1259     this->uflags |= flags;
1261     /* If requestModified has already been called on this object or one of its children, then we
1262      * don't need to set CHILD_MODIFIED on our ancestors because it's already been done.
1263      */
1264     if (already_propagated) {
1265         SPObject *parent = SP_OBJECT_PARENT(this);
1266         if (parent) {
1267             parent->requestDisplayUpdate(SP_OBJECT_CHILD_MODIFIED_FLAG);
1268         } else {
1269             sp_document_request_modified(SP_OBJECT_DOCUMENT(this));
1270         }
1271     }
1274 /**
1275  * Update views
1276  */
1277 void
1278 SPObject::updateDisplay(SPCtx *ctx, unsigned int flags)
1280     g_return_if_fail(!(flags & ~SP_OBJECT_MODIFIED_CASCADE));
1282     update_in_progress ++;
1284 #ifdef SP_OBJECT_DEBUG_CASCADE
1285     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);
1286 #endif
1288     /* Get this flags */
1289     flags |= this->uflags;
1290     /* Copy flags to modified cascade for later processing */
1291     this->mflags |= this->uflags;
1292     /* We have to clear flags here to allow rescheduling update */
1293     this->uflags = 0;
1295     // Merge style if we have good reasons to think that parent style is changed */
1296     /** \todo
1297      * I am not sure whether we should check only propagated
1298      * flag. We are currently assuming that style parsing is
1299      * done immediately. I think this is correct (Lauris).
1300      */
1301     if ((flags & SP_OBJECT_STYLE_MODIFIED_FLAG) && (flags & SP_OBJECT_PARENT_MODIFIED_FLAG)) {
1302         if (this->style && this->parent) {
1303             sp_style_merge_from_parent(this->style, this->parent->style);
1304         }
1305     }
1307     try
1308     {
1309         if (((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update)
1310             ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update(this, ctx, flags);
1311     }
1312     catch(...)
1313     {
1314         /** \todo 
1315         * in case of catching an exception we need to inform the user somehow that the document is corrupted
1316         * maybe by implementing an document flag documentOk
1317         * or by a modal error dialog
1318         */
1319         g_warning("SPObject::updateDisplay(SPCtx *ctx, unsigned int flags) : throw in ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update(this, ctx, flags);");
1320     }
1322     update_in_progress --;
1325 /**
1326  * Request modified always bubbles *up* the tree, as opposed to 
1327  * request display update, which trickles down and relies on the 
1328  * flags set during this pass...
1329  */
1330 void
1331 SPObject::requestModified(unsigned int flags)
1333     g_return_if_fail( this->document != NULL );
1335     /* requestModified must be used only to set one of SP_OBJECT_MODIFIED_FLAG or
1336      * SP_OBJECT_CHILD_MODIFIED_FLAG */
1337     g_return_if_fail(!(flags & SP_OBJECT_PARENT_MODIFIED_FLAG));
1338     g_return_if_fail((flags & SP_OBJECT_MODIFIED_FLAG) || (flags & SP_OBJECT_CHILD_MODIFIED_FLAG));
1339     g_return_if_fail(!((flags & SP_OBJECT_MODIFIED_FLAG) && (flags & SP_OBJECT_CHILD_MODIFIED_FLAG)));
1341     bool already_propagated = (!(this->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG)));
1343     this->mflags |= flags;
1345     /* If requestModified has already been called on this object or one of its children, then we
1346      * don't need to set CHILD_MODIFIED on our ancestors because it's already been done.
1347      */
1348     if (already_propagated) {
1349         SPObject *parent=SP_OBJECT_PARENT(this);
1350         if (parent) {
1351             parent->requestModified(SP_OBJECT_CHILD_MODIFIED_FLAG);
1352         } else {
1353             sp_document_request_modified(SP_OBJECT_DOCUMENT(this));
1354         }
1355     }
1358 /** 
1359  *  Emits the MODIFIED signal with the object's flags.
1360  *  The object's mflags are the original set aside during the update pass for 
1361  *  later delivery here.  Once emitModified() is called, those flags don't
1362  *  need to be stored any longer.
1363  */
1364 void
1365 SPObject::emitModified(unsigned int flags)
1367     /* only the MODIFIED_CASCADE flag is legal here */
1368     g_return_if_fail(!(flags & ~SP_OBJECT_MODIFIED_CASCADE));
1370 #ifdef SP_OBJECT_DEBUG_CASCADE
1371     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);
1372 #endif
1374     flags |= this->mflags;
1375     /* We have to clear mflags beforehand, as signal handlers may
1376      * make changes and therefore queue new modification notifications
1377      * themselves. */
1378     this->mflags = 0;
1380     g_object_ref(G_OBJECT(this));
1381     SPObjectClass *klass=(SPObjectClass *)G_OBJECT_GET_CLASS(this);
1382     if (klass->modified) {
1383         klass->modified(this, flags);
1384     }
1385     _modified_signal.emit(this, flags);
1386     g_object_unref(G_OBJECT(this));
1389 gchar const *
1390 sp_object_tagName_get(SPObject const *object, SPException *ex)
1392     /* If exception is not clear, return */
1393     if (!SP_EXCEPTION_IS_OK(ex)) {
1394         return NULL;
1395     }
1397     /// \todo fixme: Exception if object is NULL? */
1398     return object->repr->name();
1401 gchar const *
1402 sp_object_getAttribute(SPObject const *object, gchar const *key, SPException *ex)
1404     /* If exception is not clear, return */
1405     if (!SP_EXCEPTION_IS_OK(ex)) {
1406         return NULL;
1407     }
1409     /// \todo fixme: Exception if object is NULL? */
1410     return (gchar const *) object->repr->attribute(key);
1413 void
1414 sp_object_setAttribute(SPObject *object, gchar const *key, gchar const *value, SPException *ex)
1416     /* If exception is not clear, return */
1417     g_return_if_fail(SP_EXCEPTION_IS_OK(ex));
1419     /// \todo fixme: Exception if object is NULL? */
1420     object->repr->setAttribute(key, value, false);
1423 void
1424 sp_object_removeAttribute(SPObject *object, gchar const *key, SPException *ex)
1426     /* If exception is not clear, return */
1427     g_return_if_fail(SP_EXCEPTION_IS_OK(ex));
1429     /// \todo fixme: Exception if object is NULL? */
1430     object->repr->setAttribute(key, NULL, false);
1433 /* Helper */
1435 static gchar *
1436 sp_object_get_unique_id(SPObject *object, gchar const *id)
1438     static unsigned long count = 0;
1440     g_assert(SP_IS_OBJECT(object));
1442     count++;
1444     gchar const *name = object->repr->name();
1445     g_assert(name != NULL);
1447     gchar const *local = strchr(name, ':');
1448     if (local) {
1449         name = local + 1;
1450     }
1452     if (id != NULL) {
1453         if (object->document->getObjectById(id) == NULL) {
1454             return g_strdup(id);
1455         }
1456     }
1458     size_t const name_len = strlen(name);
1459     size_t const buflen = name_len + (sizeof(count) * 10 / 4) + 1;
1460     gchar *const buf = (gchar *) g_malloc(buflen);
1461     memcpy(buf, name, name_len);
1462     gchar *const count_buf = buf + name_len;
1463     size_t const count_buflen = buflen - name_len;
1464     do {
1465         ++count;
1466         g_snprintf(count_buf, count_buflen, "%lu", count);
1467     } while ( object->document->getObjectById(buf) != NULL );
1468     return buf;
1471 /* Style */
1473 /**
1474  * Returns an object style property.
1475  *
1476  * \todo
1477  * fixme: Use proper CSS parsing.  The current version is buggy
1478  * in a number of situations where key is a substring of the
1479  * style string other than as a property name (including
1480  * where key is a substring of a property name), and is also
1481  * buggy in its handling of inheritance for properties that
1482  * aren't inherited by default.  It also doesn't allow for
1483  * the case where the property is specified but with an invalid
1484  * value (in which case I believe the CSS2 error-handling
1485  * behaviour applies, viz. behave as if the property hadn't
1486  * been specified).  Also, the current code doesn't use CRSelEng
1487  * stuff to take a value from stylesheets.  Also, we aren't
1488  * setting any hooks to force an update for changes in any of
1489  * the inputs (i.e., in any of the elements that this function
1490  * queries).
1491  *
1492  * \par
1493  * Given that the default value for a property depends on what
1494  * property it is (e.g., whether to inherit or not), and given
1495  * the above comment about ignoring invalid values, and that the
1496  * repr parent isn't necessarily the right element to inherit
1497  * from (e.g., maybe we need to inherit from the referencing
1498  * <use> element instead), we should probably make the caller
1499  * responsible for ascending the repr tree as necessary.
1500  */
1501 gchar const *
1502 sp_object_get_style_property(SPObject const *object, gchar const *key, gchar const *def)
1504     g_return_val_if_fail(object != NULL, NULL);
1505     g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
1506     g_return_val_if_fail(key != NULL, NULL);
1508     gchar const *style = object->repr->attribute("style");
1509     if (style) {
1510         size_t const len = strlen(key);
1511         char const *p;
1512         while ( (p = strstr(style, key))
1513                 != NULL )
1514         {
1515             p += len;
1516             while ((*p <= ' ') && *p) p++;
1517             if (*p++ != ':') break;
1518             while ((*p <= ' ') && *p) p++;
1519             size_t const inherit_len = sizeof("inherit") - 1;
1520             if (*p
1521                 && !(strneq(p, "inherit", inherit_len)
1522                      && (p[inherit_len] == '\0'
1523                          || p[inherit_len] == ';'
1524                          || g_ascii_isspace(p[inherit_len])))) {
1525                 return p;
1526             }
1527         }
1528     }
1529     gchar const *val = object->repr->attribute(key);
1530     if (val && !streq(val, "inherit")) {
1531         return val;
1532     }
1533     if (object->parent) {
1534         return sp_object_get_style_property(object->parent, key, def);
1535     }
1537     return def;
1540 /**
1541  * Lifts SVG version of all root objects to version.
1542  */
1543 void
1544 SPObject::_requireSVGVersion(Inkscape::Version version) {
1545     for ( SPObject::ParentIterator iter=this ; iter ; ++iter ) {
1546         SPObject *object=iter;
1547         if (SP_IS_ROOT(object)) {
1548             SPRoot *root=SP_ROOT(object);
1549             if ( root->version.svg < version ) {
1550                 root->version.svg = version;
1551             }
1552         }
1553     }
1556 /**
1557  * Returns previous object in sibling list or NULL.
1558  */
1559 SPObject *
1560 sp_object_prev(SPObject *child)
1562     SPObject *parent = SP_OBJECT_PARENT(child);
1563     for ( SPObject *i = sp_object_first_child(parent); i; i = SP_OBJECT_NEXT(i) ) {
1564         if (SP_OBJECT_NEXT(i) == child)
1565             return i;
1566     }
1567     return NULL;
1570 /* Titles and descriptions */
1572 /* Note:
1573    Titles and descriptions are stored in 'title' and 'desc' child elements
1574    (see section 5.4 of the SVG 1.0 and 1.1 specifications).  The spec allows
1575    an element to have more than one 'title' child element, but strongly
1576    recommends against this and requires using the first one if a choice must
1577    be made.  The same applies to 'desc' elements.  Therefore, these functions
1578    ignore all but the first 'title' child element and first 'desc' child
1579    element, except when deleting a title or description.
1580 */
1582 /**
1583  * Returns the title of this object, or NULL if there is none.
1584  * The caller must free the returned string using g_free() - see comment
1585  * for getTitleOrDesc() below.
1586  */
1587 gchar *
1588 SPObject::title() const
1590     return getTitleOrDesc("svg:title");
1593 /**
1594  * Sets the title of this object
1595  * A NULL first argument is interpreted as meaning that the existing title
1596  * (if any) should be deleted.
1597  * The second argument is optional - see setTitleOrDesc() below for details.
1598  */
1599 bool
1600 SPObject::setTitle(gchar const *title, bool verbatim)
1602     return setTitleOrDesc(title, "svg:title", verbatim);
1605 /**
1606  * Returns the description of this object, or NULL if there is none.
1607  * The caller must free the returned string using g_free() - see comment
1608  * for getTitleOrDesc() below.
1609  */
1610 gchar *
1611 SPObject::desc() const
1613     return getTitleOrDesc("svg:desc");
1616 /**
1617  * Sets the description of this object.
1618  * A NULL first argument is interpreted as meaning that the existing
1619  * description (if any) should be deleted.
1620  * The second argument is optional - see setTitleOrDesc() below for details.
1621  */
1622 bool
1623 SPObject::setDesc(gchar const *desc, bool verbatim)
1625     return setTitleOrDesc(desc, "svg:desc", verbatim);
1628 /**
1629  * Returns the title or description of this object, or NULL if there is none.
1630  *
1631  * The SVG spec allows 'title' and 'desc' elements to contain text marked up
1632  * using elements from other namespaces.  Therefore, this function cannot
1633  * in general just return a pointer to an existing string - it must instead
1634  * construct a string containing the title or description without the mark-up.
1635  * Consequently, the return value is a newly allocated string (or NULL), and
1636  * must be freed (using g_free()) by the caller.
1637  */
1638 gchar *
1639 SPObject::getTitleOrDesc(gchar const *svg_tagname) const
1641     SPObject *elem = findFirstChild(svg_tagname);
1642     if (elem == NULL) return NULL;
1643     return g_string_free(elem->textualContent(), FALSE);
1646 /**
1647  * Sets or deletes the title or description of this object.
1648  * A NULL 'value' argument causes the title or description to be deleted.
1649  *
1650  * 'verbatim' parameter:
1651  * If verbatim==true, then the title or description is set to exactly the
1652  * specified value.  If verbatim==false then two exceptions are made:
1653  *   (1) If the specified value is just whitespace, then the title/description
1654  *       is deleted.
1655  *   (2) If the specified value is the same as the current value except for
1656  *       mark-up, then the current value is left unchanged.
1657  * This is usually the desired behaviour, so 'verbatim' defaults to false for
1658  * setTitle() and setDesc().
1659  *
1660  * The return value is true if a change was made to the title/description,
1661  * and usually false otherwise.
1662  */
1663 bool
1664 SPObject::setTitleOrDesc(gchar const *value, gchar const *svg_tagname, bool verbatim)
1666     if (!verbatim) {
1667         // If the new title/description is just whitespace,
1668         // treat it as though it were NULL.
1669         if (value) {
1670             bool just_whitespace = true;
1671             for (const gchar *cp = value; *cp; ++cp) {
1672                 if (!std::strchr("\r\n \t", *cp)) {
1673                     just_whitespace = false;
1674                     break;
1675                 }
1676             }
1677             if (just_whitespace) value = NULL;
1678         }
1679         // Don't stomp on mark-up if there is no real change.
1680         if (value) {
1681             gchar *current_value = getTitleOrDesc(svg_tagname);
1682             if (current_value) {
1683                 bool different = std::strcmp(current_value, value);
1684                 g_free(current_value);
1685                 if (!different) return false;
1686             }
1687         }
1688     }
1690     SPObject *elem = findFirstChild(svg_tagname);
1692     if (value == NULL) {
1693         if (elem == NULL) return false;
1694         // delete the title/description(s)
1695         while (elem) {
1696             elem->deleteObject();
1697             elem = findFirstChild(svg_tagname);
1698         }
1699         return true;
1700     }
1702     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
1704     if (elem == NULL) {
1705         // create a new 'title' or 'desc' element, putting it at the
1706         // beginning (in accordance with the spec's recommendations)
1707         Inkscape::XML::Node *xml_elem = xml_doc->createElement(svg_tagname);
1708         repr->addChild(xml_elem, NULL);
1709         elem = document->getObjectByRepr(xml_elem);
1710         Inkscape::GC::release(xml_elem);
1711     }
1712     else {
1713         // remove the current content of the 'text' or 'desc' element
1714         SPObject *child;
1715         while (NULL != (child = elem->firstChild())) child->deleteObject();
1716     }
1718     // add the new content
1719     elem->appendChildRepr(xml_doc->createTextNode(value));
1720     return true;
1723 /**
1724  * Find the first child of this object with a given tag name,
1725  * and return it.  Returns NULL if there is no matching child.
1726  */
1727 SPObject *
1728 SPObject::findFirstChild(gchar const *tagname) const
1730     for (SPObject *child = children; child; child = child->next)
1731     {
1732         if (child->repr->type() == Inkscape::XML::ELEMENT_NODE &&
1733             !strcmp(child->repr->name(), tagname)) return child;
1734     }
1735     return NULL;
1738 /**
1739  * Return the full textual content of an element (typically all the
1740  * content except the tags).
1741  * Must not be used on anything except elements.
1742  */
1743 GString*
1744 SPObject::textualContent() const
1746     GString* text = g_string_new("");
1748     for (const SPObject *child = firstChild(); child; child = child->next)
1749     {
1750         Inkscape::XML::NodeType child_type = child->repr->type();
1751         
1752         if (child_type == Inkscape::XML::ELEMENT_NODE) {
1753             GString * new_text = child->textualContent();
1754             g_string_append(text, new_text->str);
1755             g_string_free(new_text, TRUE);
1756         }
1757         else if (child_type == Inkscape::XML::TEXT_NODE) {
1758             g_string_append(text, child->repr->content());
1759         }
1760     }
1761     return text;
1764 /*
1765   Local Variables:
1766   mode:c++
1767   c-file-style:"stroustrup"
1768   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1769   indent-tabs-mode:nil
1770   fill-column:99
1771   End:
1772 */
1773 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :