Code

Cleanup on id access.
[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 // A friend class used to set internal members on SPObject so as to not expose settors in SPObject's public API
116 class SPObjectImpl
118 public:
120 /**
121  * Null's the id member of an SPObject without attempting to free prior contents.
122  */
123     static void setIdNull( SPObject* obj ) {
124         if (obj) {
125             obj->id = 0;
126         }
127     }
129 /**
130  * Sets the id member of an object, freeing any prior content.
131  */
132     static void setId( SPObject* obj, gchar const* id ) {
133         if (obj && (id != obj->id) ) {
134             if (obj->id) {
135                 g_free(obj->id);
136                 obj->id = 0;
137             }
138             if (id) {
139                 obj->id = g_strdup(id);
140             }
141         }
142     }
143 };
146 static GObjectClass *parent_class;
148 /**
149  * Registers the SPObject class with Gdk and returns its type number.
150  */
151 GType
152 sp_object_get_type(void)
154     static GType type = 0;
155     if (!type) {
156         GTypeInfo info = {
157             sizeof(SPObjectClass),
158             NULL, NULL,
159             (GClassInitFunc) sp_object_class_init,
160             NULL, NULL,
161             sizeof(SPObject),
162             16,
163             (GInstanceInitFunc) sp_object_init,
164             NULL
165         };
166         type = g_type_register_static(G_TYPE_OBJECT, "SPObject", &info, (GTypeFlags)0);
167     }
168     return type;
171 /**
172  * Initializes the SPObject vtable.
173  */
174 static void
175 sp_object_class_init(SPObjectClass *klass)
177     GObjectClass *object_class;
179     object_class = (GObjectClass *) klass;
181     parent_class = (GObjectClass *) g_type_class_ref(G_TYPE_OBJECT);
183     object_class->finalize = sp_object_finalize;
185     klass->child_added = sp_object_child_added;
186     klass->remove_child = sp_object_remove_child;
187     klass->order_changed = sp_object_order_changed;
189     klass->release = sp_object_release;
191     klass->build = sp_object_build;
193     klass->set = sp_object_private_set;
194     klass->write = sp_object_private_write;
197 /**
198  * Callback to initialize the SPObject object.
199  */
200 static void
201 sp_object_init(SPObject *object)
203     debug("id=%x, typename=%s",object, g_type_name_from_instance((GTypeInstance*)object));
205     object->hrefcount = 0;
206     object->_total_hrefcount = 0;
207     object->document = NULL;
208     object->children = object->_last_child = NULL;
209     object->parent = object->next = NULL;
210     object->repr = NULL;
211     SPObjectImpl::setIdNull(object);
213     object->_collection_policy = SPObject::COLLECT_WITH_PARENT;
215     new (&object->_release_signal) sigc::signal<void, SPObject *>();
216     new (&object->_modified_signal) sigc::signal<void, SPObject *, unsigned int>();
217     new (&object->_delete_signal) sigc::signal<void, SPObject *>();
218     new (&object->_position_changed_signal) sigc::signal<void, SPObject *>();
219     object->_successor = NULL;
221     // FIXME: now we create style for all objects, but per SVG, only the following can have style attribute:
222     // vg, g, defs, desc, title, symbol, use, image, switch, path, rect, circle, ellipse, line, polyline,
223     // polygon, text, tspan, tref, textPath, altGlyph, glyphRef, marker, linearGradient, radialGradient,
224     // stop, pattern, clipPath, mask, filter, feImage, a, font, glyph, missing-glyph, foreignObject
225     object->style = sp_style_new_from_object(object);
227     object->_label = NULL;
228     object->_default_label = NULL;
231 /**
232  * Callback to destroy all members and connections of object and itself.
233  */
234 static void
235 sp_object_finalize(GObject *object)
237     SPObject *spobject = (SPObject *)object;
239     g_free(spobject->_label);
240     g_free(spobject->_default_label);
241     spobject->_label = NULL;
242     spobject->_default_label = NULL;
244     if (spobject->_successor) {
245         sp_object_unref(spobject->_successor, NULL);
246         spobject->_successor = NULL;
247     }
249     if (((GObjectClass *) (parent_class))->finalize) {
250         (* ((GObjectClass *) (parent_class))->finalize)(object);
251     }
253     spobject->_release_signal.~signal();
254     spobject->_modified_signal.~signal();
255     spobject->_delete_signal.~signal();
256     spobject->_position_changed_signal.~signal();
259 namespace {
261 namespace Debug = Inkscape::Debug;
262 namespace Util = Inkscape::Util;
264 typedef Debug::SimpleEvent<Debug::Event::REFCOUNT> BaseRefCountEvent;
266 class RefCountEvent : public BaseRefCountEvent {
267 public:
268     RefCountEvent(SPObject *object, int bias, Util::ptr_shared<char> name)
269     : BaseRefCountEvent(name)
270     {
271         _addProperty("object", Util::format("%p", object));
272         _addProperty("class", Debug::demangle(g_type_name(G_TYPE_FROM_INSTANCE(object))));
273         _addProperty("new-refcount", Util::format("%d", G_OBJECT(object)->ref_count + bias));
274     }
275 };
277 class RefEvent : public RefCountEvent {
278 public:
279     RefEvent(SPObject *object)
280     : RefCountEvent(object, 1, Util::share_static_string("sp-object-ref"))
281     {}
282 };
284 class UnrefEvent : public RefCountEvent {
285 public:
286     UnrefEvent(SPObject *object)
287     : RefCountEvent(object, -1, Util::share_static_string("sp-object-unref"))
288     {}
289 };
293 gchar const* SPObject::getId() const {
294     return id;
297 /**
298  * Increase reference count of object, with possible debugging.
299  *
300  * \param owner If non-NULL, make debug log entry.
301  * \return object, NULL is error.
302  * \pre object points to real object
303  */
304 SPObject *
305 sp_object_ref(SPObject *object, SPObject *owner)
307     g_return_val_if_fail(object != NULL, NULL);
308     g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
309     g_return_val_if_fail(!owner || SP_IS_OBJECT(owner), NULL);
311     Inkscape::Debug::EventTracker<RefEvent> tracker(object);
312     g_object_ref(G_OBJECT(object));
313     return object;
316 /**
317  * Decrease reference count of object, with possible debugging and
318  * finalization.
319  *
320  * \param owner If non-NULL, make debug log entry.
321  * \return always NULL
322  * \pre object points to real object
323  */
324 SPObject *
325 sp_object_unref(SPObject *object, SPObject *owner)
327     g_return_val_if_fail(object != NULL, NULL);
328     g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
329     g_return_val_if_fail(!owner || SP_IS_OBJECT(owner), NULL);
331     Inkscape::Debug::EventTracker<UnrefEvent> tracker(object);
332     g_object_unref(G_OBJECT(object));
333     return NULL;
336 /**
337  * Increase weak refcount.
338  *
339  * Hrefcount is used for weak references, for example, to
340  * determine whether any graphical element references a certain gradient
341  * node.
342  * \param owner Ignored.
343  * \return object, NULL is error
344  * \pre object points to real object
345  */
346 SPObject *
347 sp_object_href(SPObject *object, gpointer /*owner*/)
349     g_return_val_if_fail(object != NULL, NULL);
350     g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
352     object->hrefcount++;
353     object->_updateTotalHRefCount(1);
355     return object;
358 /**
359  * Decrease weak refcount.
360  *
361  * Hrefcount is used for weak references, for example, to determine whether
362  * any graphical element references a certain gradient node.
363  * \param owner Ignored.
364  * \return always NULL
365  * \pre object points to real object and hrefcount>0
366  */
367 SPObject *
368 sp_object_hunref(SPObject *object, gpointer /*owner*/)
370     g_return_val_if_fail(object != NULL, NULL);
371     g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
372     g_return_val_if_fail(object->hrefcount > 0, NULL);
374     object->hrefcount--;
375     object->_updateTotalHRefCount(-1);
377     return NULL;
380 /**
381  * Adds increment to _total_hrefcount of object and its parents.
382  */
383 void
384 SPObject::_updateTotalHRefCount(int increment) {
385     SPObject *topmost_collectable = NULL;
386     for ( SPObject *iter = this ; iter ; iter = SP_OBJECT_PARENT(iter) ) {
387         iter->_total_hrefcount += increment;
388         if ( iter->_total_hrefcount < iter->hrefcount ) {
389             g_critical("HRefs overcounted");
390         }
391         if ( iter->_total_hrefcount == 0 &&
392              iter->_collection_policy != COLLECT_WITH_PARENT )
393         {
394             topmost_collectable = iter;
395         }
396     }
397     if (topmost_collectable) {
398         topmost_collectable->requestOrphanCollection();
399     }
402 /**
403  * True if object is non-NULL and this is some in/direct parent of object.
404  */
405 bool
406 SPObject::isAncestorOf(SPObject const *object) const {
407     g_return_val_if_fail(object != NULL, false);
408     object = SP_OBJECT_PARENT(object);
409     while (object) {
410         if ( object == this ) {
411             return true;
412         }
413         object = SP_OBJECT_PARENT(object);
414     }
415     return false;
418 namespace {
420 bool same_objects(SPObject const &a, SPObject const &b) {
421     return &a == &b;
426 /**
427  * Returns youngest object being parent to this and object.
428  */
429 SPObject const *
430 SPObject::nearestCommonAncestor(SPObject const *object) const {
431     g_return_val_if_fail(object != NULL, NULL);
433     using Inkscape::Algorithms::longest_common_suffix;
434     return longest_common_suffix<SPObject::ConstParentIterator>(this, object, NULL, &same_objects);
437 SPObject const *AncestorSon(SPObject const *obj, SPObject const *ancestor) {
438     if (obj == NULL || ancestor == NULL)
439         return NULL;
440     if (SP_OBJECT_PARENT(obj) == ancestor)
441         return obj;
442     return AncestorSon(SP_OBJECT_PARENT(obj), ancestor);
445 /**
446  * Compares height of objects in tree.
447  *
448  * Works for different-parent objects, so long as they have a common ancestor.
449  * \return \verbatim
450  *    0    positions are equivalent
451  *    1    first object's position is greater than the second
452  *   -1    first object's position is less than the second   \endverbatim
453  */
454 int
455 sp_object_compare_position(SPObject const *first, SPObject const *second)
457     if (first == second) return 0;
459     SPObject const *ancestor = first->nearestCommonAncestor(second);
460     if (ancestor == NULL) return 0; // cannot compare, no common ancestor!
462     // we have an object and its ancestor (should not happen when sorting selection)
463     if (ancestor == first)
464         return 1;
465     if (ancestor == second)
466         return -1;
468     SPObject const *to_first = AncestorSon(first, ancestor);
469     SPObject const *to_second = AncestorSon(second, ancestor);
471     g_assert(SP_OBJECT_PARENT(to_second) == SP_OBJECT_PARENT(to_first));
473     return sp_repr_compare_position(SP_OBJECT_REPR(to_first), SP_OBJECT_REPR(to_second));
477 /**
478  * Append repr as child of this object.
479  * \pre this is not a cloned object
480  */
481 SPObject *
482 SPObject::appendChildRepr(Inkscape::XML::Node *repr) {
483     if (!SP_OBJECT_IS_CLONED(this)) {
484         SP_OBJECT_REPR(this)->appendChild(repr);
485         return SP_OBJECT_DOCUMENT(this)->getObjectByRepr(repr);
486     } else {
487         g_critical("Attempt to append repr as child of cloned object");
488         return NULL;
489     }
492 /**
493  * Retrieves the children as a GSList object, optionally ref'ing the children
494  * in the process, if add_ref is specified.
495  */
496 GSList *SPObject::childList(bool add_ref, Action) {
497     GSList *l = NULL;
498     for (SPObject *child = sp_object_first_child(this) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
499         if (add_ref)
500             g_object_ref (G_OBJECT (child));
502         l = g_slist_prepend (l, child);
503     }
504     return l;
508 /** Gets the label property for the object or a default if no label
509  *  is defined.
510  */
511 gchar const *
512 SPObject::label() const {
513     return _label;
516 /** Returns a default label property for the object. */
517 gchar const *
518 SPObject::defaultLabel() const {
519     if (_label) {
520         return _label;
521     } else {
522         if (!_default_label) {
523             gchar const *id=SP_OBJECT_ID(this);
524             if (id) {
525                 _default_label = g_strdup_printf("#%s", id);
526             } else {
527                 _default_label = g_strdup_printf("<%s>", SP_OBJECT_REPR(this)->name());
528             }
529         }
530         return _default_label;
531     }
534 /** Sets the label property for the object */
535 void
536 SPObject::setLabel(gchar const *label) {
537     SP_OBJECT_REPR(this)->setAttribute("inkscape:label", label, false);
541 /** Queues the object for orphan collection */
542 void
543 SPObject::requestOrphanCollection() {
544     g_return_if_fail(document != NULL);
546     // do not remove style or script elements (Bug #276244)
547     if (SP_IS_STYLE_ELEM(this))
548         return;
549     if (SP_IS_SCRIPT(this))
550         return;
552     document->queueForOrphanCollection(this);
554     /** \todo
555      * This is a temporary hack added to make fill&stroke rebuild its
556      * gradient list when the defs are vacuumed.  gradient-vector.cpp
557      * listens to the modified signal on defs, and now we give it that
558      * signal.  Mental says that this should be made automatic by
559      * merging SPObjectGroup with SPObject; SPObjectGroup would issue
560      * this signal automatically. Or maybe just derive SPDefs from
561      * SPObjectGroup?
562      */
564     this->requestModified(SP_OBJECT_CHILD_MODIFIED_FLAG);
567 /** Sends the delete signal to all children of this object recursively */
568 void
569 SPObject::_sendDeleteSignalRecursive() {
570     for (SPObject *child = sp_object_first_child(this); child; child = SP_OBJECT_NEXT(child)) {
571         child->_delete_signal.emit(child);
572         child->_sendDeleteSignalRecursive();
573     }
576 /**
577  * Deletes the object reference, unparenting it from its parent.
578  *
579  * If the \a propagate parameter is set to true, it emits a delete
580  * signal.  If the \a propagate_descendants parameter is true, it
581  * recursively sends the delete signal to children.
582  */
583 void
584 SPObject::deleteObject(bool propagate, bool propagate_descendants)
586     sp_object_ref(this, NULL);
587     if (propagate) {
588         _delete_signal.emit(this);
589     }
590     if (propagate_descendants) {
591         this->_sendDeleteSignalRecursive();
592     }
594     Inkscape::XML::Node *repr=SP_OBJECT_REPR(this);
595     if (repr && sp_repr_parent(repr)) {
596         sp_repr_unparent(repr);
597     }
599     if (_successor) {
600         _successor->deleteObject(propagate, propagate_descendants);
601     }
602     sp_object_unref(this, NULL);
605 /**
606  * Put object into object tree, under parent, and behind prev;
607  * also update object's XML space.
608  */
609 void
610 sp_object_attach(SPObject *parent, SPObject *object, SPObject *prev)
612     g_return_if_fail(parent != NULL);
613     g_return_if_fail(SP_IS_OBJECT(parent));
614     g_return_if_fail(object != NULL);
615     g_return_if_fail(SP_IS_OBJECT(object));
616     g_return_if_fail(!prev || SP_IS_OBJECT(prev));
617     g_return_if_fail(!prev || prev->parent == parent);
618     g_return_if_fail(!object->parent);
620     sp_object_ref(object, parent);
621     object->parent = parent;
622     parent->_updateTotalHRefCount(object->_total_hrefcount);
624     SPObject *next;
625     if (prev) {
626         next = prev->next;
627         prev->next = object;
628     } else {
629         next = parent->children;
630         parent->children = object;
631     }
632     object->next = next;
633     if (!next) {
634         parent->_last_child = object;
635     }
636     if (!object->xml_space.set)
637         object->xml_space.value = parent->xml_space.value;
640 /**
641  * In list of object's siblings, move object behind prev.
642  */
643 void
644 sp_object_reorder(SPObject *object, SPObject *prev) {
645     g_return_if_fail(object != NULL);
646     g_return_if_fail(SP_IS_OBJECT(object));
647     g_return_if_fail(object->parent != NULL);
648     g_return_if_fail(object != prev);
649     g_return_if_fail(!prev || SP_IS_OBJECT(prev));
650     g_return_if_fail(!prev || prev->parent == object->parent);
652     SPObject *const parent=object->parent;
654     SPObject *old_prev=NULL;
655     for ( SPObject *child = parent->children ; child && child != object ;
656           child = child->next )
657     {
658         old_prev = child;
659     }
661     SPObject *next=object->next;
662     if (old_prev) {
663         old_prev->next = next;
664     } else {
665         parent->children = next;
666     }
667     if (!next) {
668         parent->_last_child = old_prev;
669     }
670     if (prev) {
671         next = prev->next;
672         prev->next = object;
673     } else {
674         next = parent->children;
675         parent->children = object;
676     }
677     object->next = next;
678     if (!next) {
679         parent->_last_child = object;
680     }
683 /**
684  * Remove object from parent's children, release and unref it.
685  */
686 void
687 sp_object_detach(SPObject *parent, SPObject *object) {
688     g_return_if_fail(parent != NULL);
689     g_return_if_fail(SP_IS_OBJECT(parent));
690     g_return_if_fail(object != NULL);
691     g_return_if_fail(SP_IS_OBJECT(object));
692     g_return_if_fail(object->parent == parent);
694     object->releaseReferences();
696     SPObject *prev=NULL;
697     for ( SPObject *child = parent->children ; child && child != object ;
698           child = child->next )
699     {
700         prev = child;
701     }
703     SPObject *next=object->next;
704     if (prev) {
705         prev->next = next;
706     } else {
707         parent->children = next;
708     }
709     if (!next) {
710         parent->_last_child = prev;
711     }
713     object->next = NULL;
714     object->parent = NULL;
716     parent->_updateTotalHRefCount(-object->_total_hrefcount);
717     sp_object_unref(object, parent);
720 /**
721  * Return object's child whose node pointer equals repr.
722  */
723 SPObject *
724 sp_object_get_child_by_repr(SPObject *object, Inkscape::XML::Node *repr)
726     g_return_val_if_fail(object != NULL, NULL);
727     g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
728     g_return_val_if_fail(repr != NULL, NULL);
730     if (object->_last_child && SP_OBJECT_REPR(object->_last_child) == repr)
731         return object->_last_child;   // optimization for common scenario
732     for ( SPObject *child = object->children ; child ; child = child->next ) {
733         if ( SP_OBJECT_REPR(child) == repr ) {
734             return child;
735         }
736     }
738     return NULL;
741 /**
742  * Callback for child_added event.
743  * Invoked whenever the given mutation event happens in the XML tree.
744  */
745 static void
746 sp_object_child_added(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref)
748     GType type = sp_repr_type_lookup(child);
749     if (!type) {
750         return;
751     }
752     SPObject *ochild = SP_OBJECT(g_object_new(type, 0));
753     SPObject *prev = ref ? sp_object_get_child_by_repr(object, ref) : NULL;
754     sp_object_attach(object, ochild, prev);
755     sp_object_unref(ochild, NULL);
757     sp_object_invoke_build(ochild, object->document, child, SP_OBJECT_IS_CLONED(object));
760 /**
761  * Removes, releases and unrefs all children of object.
762  *
763  * This is the opposite of build. It has to be invoked as soon as the
764  * object is removed from the tree, even if it is still alive according
765  * to reference count. The frontend unregisters the object from the
766  * document and releases the SPRepr bindings; implementations should free
767  * state data and release all child objects.  Invoking release on
768  * SPRoot destroys the whole document tree.
769  * \see sp_object_build()
770  */
771 static void sp_object_release(SPObject *object)
773     debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object));
774     while (object->children) {
775         sp_object_detach(object, object->children);
776     }
779 /**
780  * Remove object's child whose node equals repr, release and
781  * unref it.
782  *
783  * Invoked whenever the given mutation event happens in the XML
784  * tree, BEFORE removal from the XML tree happens, so grouping
785  * objects can safely release the child data.
786  */
787 static void
788 sp_object_remove_child(SPObject *object, Inkscape::XML::Node *child)
790     debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object));
791     SPObject *ochild = sp_object_get_child_by_repr(object, child);
792     g_return_if_fail (ochild != NULL || !strcmp("comment", child->name())); // comments have no objects
793     if (ochild)
794         sp_object_detach(object, ochild);
797 /**
798  * Move object corresponding to child after sibling object corresponding
799  * to new_ref.
800  * Invoked whenever the given mutation event happens in the XML tree.
801  * \param old_ref Ignored
802  */
803 static void sp_object_order_changed(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node */*old_ref*/,
804                                     Inkscape::XML::Node *new_ref)
806     SPObject *ochild = sp_object_get_child_by_repr(object, child);
807     g_return_if_fail(ochild != NULL);
808     SPObject *prev = new_ref ? sp_object_get_child_by_repr(object, new_ref) : NULL;
809     sp_object_reorder(ochild, prev);
810     ochild->_position_changed_signal.emit(ochild);
813 /**
814  * Virtual build callback.
815  *
816  * This has to be invoked immediately after creation of an SPObject. The
817  * frontend method ensures that the new object is properly attached to
818  * the document and repr; implementation then will parse all of the attributes,
819  * generate the children objects and so on.  Invoking build on the SPRoot
820  * object results in creation of the whole document tree (this is, what
821  * SPDocument does after the creation of the XML tree).
822  * \see sp_object_release()
823  */
824 static void
825 sp_object_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
827     /* Nothing specific here */
828     debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object));
830     sp_object_read_attr(object, "xml:space");
831     sp_object_read_attr(object, "inkscape:label");
832     sp_object_read_attr(object, "inkscape:collect");
834     for (Inkscape::XML::Node *rchild = repr->firstChild() ; rchild != NULL; rchild = rchild->next()) {
835         GType type = sp_repr_type_lookup(rchild);
836         if (!type) {
837             continue;
838         }
839         SPObject *child = SP_OBJECT(g_object_new(type, 0));
840         sp_object_attach(object, child, object->lastChild());
841         sp_object_unref(child, NULL);
842         sp_object_invoke_build(child, document, rchild, SP_OBJECT_IS_CLONED(object));
843     }
846 void sp_object_invoke_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr, unsigned int cloned)
848     debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object));
850     g_assert(object != NULL);
851     g_assert(SP_IS_OBJECT(object));
852     g_assert(document != NULL);
853     g_assert(repr != NULL);
855     g_assert(object->document == NULL);
856     g_assert(object->repr == NULL);
857     g_assert(object->getId() == NULL);
859     /* Bookkeeping */
861     object->document = document;
862     object->repr = repr;
863     if (!cloned)
864         Inkscape::GC::anchor(repr);
865     object->cloned = cloned;
867     if (!SP_OBJECT_IS_CLONED(object)) {
868         object->document->bindObjectToRepr(object->repr, object);
870         if (Inkscape::XML::id_permitted(object->repr)) {
871             /* If we are not cloned, and not seeking, force unique id */
872             gchar const *id = object->repr->attribute("id");
873             if (!document->isSeeking()) {
874                 {
875                     gchar *realid = sp_object_get_unique_id(object, id);
876                     g_assert(realid != NULL);
878                     object->document->bindObjectToId(realid, object);
879                     SPObjectImpl::setId(object, realid);
880                     g_free(realid);
881                 }
883                 /* Redefine ID, if required */
884                 if ((id == NULL) || (strcmp(id, object->getId()) != 0)) {
885                     object->repr->setAttribute("id", object->getId());
886                 }
887             } else if (id) {
888                 // bind if id, but no conflict -- otherwise, we can expect
889                 // a subsequent setting of the id attribute
890                 if (!object->document->getObjectById(id)) {
891                     object->document->bindObjectToId(id, object);
892                     SPObjectImpl::setId(object, id);
893                 }
894             }
895         }
896     } else {
897         g_assert(object->getId() == NULL);
898     }
900     /* Invoke derived methods, if any */
901     if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->build) {
902         (*((SPObjectClass *) G_OBJECT_GET_CLASS(object))->build)(object, document, repr);
903     }
905     /* Signalling (should be connected AFTER processing derived methods */
906     sp_repr_add_listener(repr, &object_event_vector, object);
909 void SPObject::releaseReferences() {
910     g_assert(this->document);
911     g_assert(this->repr);
913     sp_repr_remove_listener_by_data(this->repr, this);
915     this->_release_signal.emit(this);
916     SPObjectClass *klass=(SPObjectClass *)G_OBJECT_GET_CLASS(this);
917     if (klass->release) {
918         klass->release(this);
919     }
921     /* all hrefs should be released by the "release" handlers */
922     g_assert(this->hrefcount == 0);
924     if (!SP_OBJECT_IS_CLONED(this)) {
925         if (this->id) {
926             this->document->bindObjectToId(this->id, NULL);
927         }
928         g_free(this->id);
929         this->id = NULL;
931         g_free(this->_default_label);
932         this->_default_label = NULL;
934         this->document->bindObjectToRepr(this->repr, NULL);
936         Inkscape::GC::release(this->repr);
937     } else {
938         g_assert(!this->id);
939     }
941     if (this->style) {
942         this->style = sp_style_unref(this->style);
943     }
945     this->document = NULL;
946     this->repr = NULL;
949 /**
950  * Callback for child_added node event.
951  */
952 static void
953 sp_object_repr_child_added(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, gpointer data)
955     SPObject *object = SP_OBJECT(data);
957     if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->child_added)
958         (*((SPObjectClass *)G_OBJECT_GET_CLASS(object))->child_added)(object, child, ref);
961 /**
962  * Callback for remove_child node event.
963  */
964 static void
965 sp_object_repr_child_removed(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node */*ref*/, gpointer data)
967     SPObject *object = SP_OBJECT(data);
969     if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->remove_child) {
970         (* ((SPObjectClass *)G_OBJECT_GET_CLASS(object))->remove_child)(object, child);
971     }
974 /**
975  * Callback for order_changed node event.
976  *
977  * \todo fixme:
978  */
979 static void
980 sp_object_repr_order_changed(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node *old, Inkscape::XML::Node *newer, gpointer data)
982     SPObject *object = SP_OBJECT(data);
984     if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->order_changed) {
985         (* ((SPObjectClass *)G_OBJECT_GET_CLASS(object))->order_changed)(object, child, old, newer);
986     }
989 /**
990  * Callback for set event.
991  */
992 static void
993 sp_object_private_set(SPObject *object, unsigned int key, gchar const *value)
995     g_assert(key != SP_ATTR_INVALID);
997     switch (key) {
998         case SP_ATTR_ID:
999             if ( !SP_OBJECT_IS_CLONED(object) && object->repr->type() == Inkscape::XML::ELEMENT_NODE ) {
1000                 SPDocument *document=object->document;
1001                 SPObject *conflict=NULL;
1003                 gchar const *new_id = value;
1005                 if (new_id) {
1006                     conflict = document->getObjectById((char const *)new_id);
1007                 }
1009                 if ( conflict && conflict != object ) {
1010                     if (!document->isSeeking()) {
1011                         sp_object_ref(conflict, NULL);
1012                         // give the conflicting object a new ID
1013                         gchar *new_conflict_id = sp_object_get_unique_id(conflict, NULL);
1014                         SP_OBJECT_REPR(conflict)->setAttribute("id", new_conflict_id);
1015                         g_free(new_conflict_id);
1016                         sp_object_unref(conflict, NULL);
1017                     } else {
1018                         new_id = NULL;
1019                     }
1020                 }
1022                 if (object->getId()) {
1023                     document->bindObjectToId(object->getId(), NULL);
1024                     SPObjectImpl::setId(object, 0);
1025                 }
1027                 if (new_id) {
1028                     SPObjectImpl::setId(object, new_id);
1029                     document->bindObjectToId(object->getId(), object);
1030                 }
1032                 g_free(object->_default_label);
1033                 object->_default_label = NULL;
1034             }
1035             break;
1036         case SP_ATTR_INKSCAPE_LABEL:
1037             g_free(object->_label);
1038             if (value) {
1039                 object->_label = g_strdup(value);
1040             } else {
1041                 object->_label = NULL;
1042             }
1043             g_free(object->_default_label);
1044             object->_default_label = NULL;
1045             break;
1046         case SP_ATTR_INKSCAPE_COLLECT:
1047             if ( value && !strcmp(value, "always") ) {
1048                 object->setCollectionPolicy(SPObject::ALWAYS_COLLECT);
1049             } else {
1050                 object->setCollectionPolicy(SPObject::COLLECT_WITH_PARENT);
1051             }
1052             break;
1053         case SP_ATTR_XML_SPACE:
1054             if (value && !strcmp(value, "preserve")) {
1055                 object->xml_space.value = SP_XML_SPACE_PRESERVE;
1056                 object->xml_space.set = TRUE;
1057             } else if (value && !strcmp(value, "default")) {
1058                 object->xml_space.value = SP_XML_SPACE_DEFAULT;
1059                 object->xml_space.set = TRUE;
1060             } else if (object->parent) {
1061                 SPObject *parent;
1062                 parent = object->parent;
1063                 object->xml_space.value = parent->xml_space.value;
1064             }
1065             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1066             break;
1067         case SP_ATTR_STYLE:
1068             sp_style_read_from_object(object->style, object);
1069             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1070             break;
1071         default:
1072             break;
1073     }
1076 /**
1077  * Call virtual set() function of object.
1078  */
1079 void
1080 sp_object_set(SPObject *object, unsigned int key, gchar const *value)
1082     g_assert(object != NULL);
1083     g_assert(SP_IS_OBJECT(object));
1085     if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->set) {
1086         ((SPObjectClass *) G_OBJECT_GET_CLASS(object))->set(object, key, value);
1087     }
1090 /**
1091  * Read value of key attribute from XML node into object.
1092  */
1093 void
1094 sp_object_read_attr(SPObject *object, gchar const *key)
1096     g_assert(object != NULL);
1097     g_assert(SP_IS_OBJECT(object));
1098     g_assert(key != NULL);
1100     g_assert(object->repr != NULL);
1102     unsigned int keyid = sp_attribute_lookup(key);
1103     if (keyid != SP_ATTR_INVALID) {
1104         /* Retrieve the 'key' attribute from the object's XML representation */
1105         gchar const *value = object->repr->attribute(key);
1107         sp_object_set(object, keyid, value);
1108     }
1111 /**
1112  * Callback for attr_changed node event.
1113  */
1114 static void
1115 sp_object_repr_attr_changed(Inkscape::XML::Node */*repr*/, gchar const *key, gchar const */*oldval*/, gchar const */*newval*/, bool is_interactive, gpointer data)
1117     SPObject *object = SP_OBJECT(data);
1119     sp_object_read_attr(object, key);
1121     // manual changes to extension attributes require the normal
1122     // attributes, which depend on them, to be updated immediately
1123     if (is_interactive) {
1124         object->updateRepr(0);
1125     }
1128 /**
1129  * Callback for content_changed node event.
1130  */
1131 static void
1132 sp_object_repr_content_changed(Inkscape::XML::Node */*repr*/, gchar const */*oldcontent*/, gchar const */*newcontent*/, gpointer data)
1134     SPObject *object = SP_OBJECT(data);
1136     if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->read_content)
1137         (*((SPObjectClass *) G_OBJECT_GET_CLASS(object))->read_content)(object);
1140 /**
1141  * Return string representation of space value.
1142  */
1143 static gchar const*
1144 sp_xml_get_space_string(unsigned int space)
1146     switch (space) {
1147         case SP_XML_SPACE_DEFAULT:
1148             return "default";
1149         case SP_XML_SPACE_PRESERVE:
1150             return "preserve";
1151         default:
1152             return NULL;
1153     }
1156 /**
1157  * Callback for write event.
1158  */
1159 static Inkscape::XML::Node *
1160 sp_object_private_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags)
1162     if (!repr && (flags & SP_OBJECT_WRITE_BUILD)) {
1163         repr = SP_OBJECT_REPR(object)->duplicate(doc);
1164         if (!( flags & SP_OBJECT_WRITE_EXT )) {
1165             repr->setAttribute("inkscape:collect", NULL);
1166         }
1167     } else {
1168         repr->setAttribute("id", object->getId());
1170         if (object->xml_space.set) {
1171             char const *xml_space;
1172             xml_space = sp_xml_get_space_string(object->xml_space.value);
1173             repr->setAttribute("xml:space", xml_space);
1174         }
1176         if ( flags & SP_OBJECT_WRITE_EXT &&
1177              object->collectionPolicy() == SPObject::ALWAYS_COLLECT )
1178         {
1179             repr->setAttribute("inkscape:collect", "always");
1180         } else {
1181             repr->setAttribute("inkscape:collect", NULL);
1182         }
1184         SPStyle const *const obj_style = SP_OBJECT_STYLE(object);
1185         if (obj_style) {
1186             gchar *s = sp_style_write_string(obj_style, SP_STYLE_FLAG_IFSET);
1187             repr->setAttribute("style", ( *s ? s : NULL ));
1188             g_free(s);
1189         } else {
1190             /** \todo I'm not sure what to do in this case.  Bug #1165868
1191              * suggests that it can arise, but the submitter doesn't know
1192              * how to do so reliably.  The main two options are either
1193              * leave repr's style attribute unchanged, or explicitly clear it.
1194              * Must also consider what to do with property attributes for
1195              * the element; see below.
1196              */
1197             char const *style_str = repr->attribute("style");
1198             if (!style_str) {
1199                 style_str = "NULL";
1200             }
1201             g_warning("Item's style is NULL; repr style attribute is %s", style_str);
1202         }
1204         /** \note We treat object->style as authoritative.  Its effects have
1205          * been written to the style attribute above; any properties that are
1206          * unset we take to be deliberately unset (e.g. so that clones can
1207          * override the property).
1208          *
1209          * Note that the below has an undesirable consequence of changing the
1210          * appearance on renderers that lack CSS support (e.g. SVG tiny);
1211          * possibly we should write property attributes instead of a style
1212          * attribute.
1213          */
1214         sp_style_unset_property_attrs (object);
1215     }
1217     return repr;
1220 /**
1221  * Update this object's XML node with flags value.
1222  */
1223 Inkscape::XML::Node *
1224 SPObject::updateRepr(unsigned int flags) {
1225     if (!SP_OBJECT_IS_CLONED(this)) {
1226         Inkscape::XML::Node *repr=SP_OBJECT_REPR(this);
1227         if (repr) {
1228             return updateRepr(repr->document(), repr, flags);
1229         } else {
1230             g_critical("Attempt to update non-existent repr");
1231             return NULL;
1232         }
1233     } else {
1234         /* cloned objects have no repr */
1235         return NULL;
1236     }
1239 /** Used both to create reprs in the original document, and to create
1240  *  reprs in another document (e.g. a temporary document used when
1241  *  saving as "Plain SVG"
1242  */
1243 Inkscape::XML::Node *
1244 SPObject::updateRepr(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, unsigned int flags) {
1245     g_assert(doc != NULL);
1247     if (SP_OBJECT_IS_CLONED(this)) {
1248         /* cloned objects have no repr */
1249         return NULL;
1250     }
1251     if (((SPObjectClass *) G_OBJECT_GET_CLASS(this))->write) {
1252         if (!(flags & SP_OBJECT_WRITE_BUILD) && !repr) {
1253             repr = SP_OBJECT_REPR(this);
1254         }
1255         return ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->write(this, doc, repr, flags);
1256     } else {
1257         g_warning("Class %s does not implement ::write", G_OBJECT_TYPE_NAME(this));
1258         if (!repr) {
1259             if (flags & SP_OBJECT_WRITE_BUILD) {
1260                 repr = SP_OBJECT_REPR(this)->duplicate(doc);
1261             }
1262             /// \todo FIXME: else probably error (Lauris) */
1263         } else {
1264             repr->mergeFrom(SP_OBJECT_REPR(this), "id");
1265         }
1266         return repr;
1267     }
1270 /* Modification */
1272 /**
1273  * Add \a flags to \a object's as dirtiness flags, and
1274  * recursively add CHILD_MODIFIED flag to
1275  * parent and ancestors (as far up as necessary).
1276  */
1277 void
1278 SPObject::requestDisplayUpdate(unsigned int flags)
1280     g_return_if_fail( this->document != NULL );
1282     if (update_in_progress) {
1283         g_print("WARNING: Requested update while update in progress, counter = %d\n", update_in_progress);
1284     }
1286     /* requestModified must be used only to set one of SP_OBJECT_MODIFIED_FLAG or
1287      * SP_OBJECT_CHILD_MODIFIED_FLAG */
1288     g_return_if_fail(!(flags & SP_OBJECT_PARENT_MODIFIED_FLAG));
1289     g_return_if_fail((flags & SP_OBJECT_MODIFIED_FLAG) || (flags & SP_OBJECT_CHILD_MODIFIED_FLAG));
1290     g_return_if_fail(!((flags & SP_OBJECT_MODIFIED_FLAG) && (flags & SP_OBJECT_CHILD_MODIFIED_FLAG)));
1292     bool already_propagated = (!(this->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG)));
1294     this->uflags |= flags;
1296     /* If requestModified has already been called on this object or one of its children, then we
1297      * don't need to set CHILD_MODIFIED on our ancestors because it's already been done.
1298      */
1299     if (already_propagated) {
1300         SPObject *parent = SP_OBJECT_PARENT(this);
1301         if (parent) {
1302             parent->requestDisplayUpdate(SP_OBJECT_CHILD_MODIFIED_FLAG);
1303         } else {
1304             sp_document_request_modified(SP_OBJECT_DOCUMENT(this));
1305         }
1306     }
1309 /**
1310  * Update views
1311  */
1312 void
1313 SPObject::updateDisplay(SPCtx *ctx, unsigned int flags)
1315     g_return_if_fail(!(flags & ~SP_OBJECT_MODIFIED_CASCADE));
1317     update_in_progress ++;
1319 #ifdef SP_OBJECT_DEBUG_CASCADE
1320     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);
1321 #endif
1323     /* Get this flags */
1324     flags |= this->uflags;
1325     /* Copy flags to modified cascade for later processing */
1326     this->mflags |= this->uflags;
1327     /* We have to clear flags here to allow rescheduling update */
1328     this->uflags = 0;
1330     // Merge style if we have good reasons to think that parent style is changed */
1331     /** \todo
1332      * I am not sure whether we should check only propagated
1333      * flag. We are currently assuming that style parsing is
1334      * done immediately. I think this is correct (Lauris).
1335      */
1336     if ((flags & SP_OBJECT_STYLE_MODIFIED_FLAG) && (flags & SP_OBJECT_PARENT_MODIFIED_FLAG)) {
1337         if (this->style && this->parent) {
1338             sp_style_merge_from_parent(this->style, this->parent->style);
1339         }
1340     }
1342     try
1343     {
1344         if (((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update)
1345             ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update(this, ctx, flags);
1346     }
1347     catch(...)
1348     {
1349         /** \todo
1350         * in case of catching an exception we need to inform the user somehow that the document is corrupted
1351         * maybe by implementing an document flag documentOk
1352         * or by a modal error dialog
1353         */
1354         g_warning("SPObject::updateDisplay(SPCtx *ctx, unsigned int flags) : throw in ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update(this, ctx, flags);");
1355     }
1357     update_in_progress --;
1360 /**
1361  * Request modified always bubbles *up* the tree, as opposed to
1362  * request display update, which trickles down and relies on the
1363  * flags set during this pass...
1364  */
1365 void
1366 SPObject::requestModified(unsigned int flags)
1368     g_return_if_fail( this->document != NULL );
1370     /* requestModified must be used only to set one of SP_OBJECT_MODIFIED_FLAG or
1371      * SP_OBJECT_CHILD_MODIFIED_FLAG */
1372     g_return_if_fail(!(flags & SP_OBJECT_PARENT_MODIFIED_FLAG));
1373     g_return_if_fail((flags & SP_OBJECT_MODIFIED_FLAG) || (flags & SP_OBJECT_CHILD_MODIFIED_FLAG));
1374     g_return_if_fail(!((flags & SP_OBJECT_MODIFIED_FLAG) && (flags & SP_OBJECT_CHILD_MODIFIED_FLAG)));
1376     bool already_propagated = (!(this->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG)));
1378     this->mflags |= flags;
1380     /* If requestModified has already been called on this object or one of its children, then we
1381      * don't need to set CHILD_MODIFIED on our ancestors because it's already been done.
1382      */
1383     if (already_propagated) {
1384         SPObject *parent=SP_OBJECT_PARENT(this);
1385         if (parent) {
1386             parent->requestModified(SP_OBJECT_CHILD_MODIFIED_FLAG);
1387         } else {
1388             sp_document_request_modified(SP_OBJECT_DOCUMENT(this));
1389         }
1390     }
1393 /**
1394  *  Emits the MODIFIED signal with the object's flags.
1395  *  The object's mflags are the original set aside during the update pass for
1396  *  later delivery here.  Once emitModified() is called, those flags don't
1397  *  need to be stored any longer.
1398  */
1399 void
1400 SPObject::emitModified(unsigned int flags)
1402     /* only the MODIFIED_CASCADE flag is legal here */
1403     g_return_if_fail(!(flags & ~SP_OBJECT_MODIFIED_CASCADE));
1405 #ifdef SP_OBJECT_DEBUG_CASCADE
1406     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);
1407 #endif
1409     flags |= this->mflags;
1410     /* We have to clear mflags beforehand, as signal handlers may
1411      * make changes and therefore queue new modification notifications
1412      * themselves. */
1413     this->mflags = 0;
1415     g_object_ref(G_OBJECT(this));
1416     SPObjectClass *klass=(SPObjectClass *)G_OBJECT_GET_CLASS(this);
1417     if (klass->modified) {
1418         klass->modified(this, flags);
1419     }
1420     _modified_signal.emit(this, flags);
1421     g_object_unref(G_OBJECT(this));
1424 gchar const *
1425 sp_object_tagName_get(SPObject const *object, SPException *ex)
1427     /* If exception is not clear, return */
1428     if (!SP_EXCEPTION_IS_OK(ex)) {
1429         return NULL;
1430     }
1432     /// \todo fixme: Exception if object is NULL? */
1433     return object->repr->name();
1436 gchar const *
1437 sp_object_getAttribute(SPObject const *object, gchar const *key, SPException *ex)
1439     /* If exception is not clear, return */
1440     if (!SP_EXCEPTION_IS_OK(ex)) {
1441         return NULL;
1442     }
1444     /// \todo fixme: Exception if object is NULL? */
1445     return (gchar const *) object->repr->attribute(key);
1448 void
1449 sp_object_setAttribute(SPObject *object, gchar const *key, gchar const *value, SPException *ex)
1451     /* If exception is not clear, return */
1452     g_return_if_fail(SP_EXCEPTION_IS_OK(ex));
1454     /// \todo fixme: Exception if object is NULL? */
1455     object->repr->setAttribute(key, value, false);
1458 void
1459 sp_object_removeAttribute(SPObject *object, gchar const *key, SPException *ex)
1461     /* If exception is not clear, return */
1462     g_return_if_fail(SP_EXCEPTION_IS_OK(ex));
1464     /// \todo fixme: Exception if object is NULL? */
1465     object->repr->setAttribute(key, NULL, false);
1468 /* Helper */
1470 static gchar *
1471 sp_object_get_unique_id(SPObject *object, gchar const *id)
1473     static unsigned long count = 0;
1475     g_assert(SP_IS_OBJECT(object));
1477     count++;
1479     gchar const *name = object->repr->name();
1480     g_assert(name != NULL);
1482     gchar const *local = strchr(name, ':');
1483     if (local) {
1484         name = local + 1;
1485     }
1487     if (id != NULL) {
1488         if (object->document->getObjectById(id) == NULL) {
1489             return g_strdup(id);
1490         }
1491     }
1493     size_t const name_len = strlen(name);
1494     size_t const buflen = name_len + (sizeof(count) * 10 / 4) + 1;
1495     gchar *const buf = (gchar *) g_malloc(buflen);
1496     memcpy(buf, name, name_len);
1497     gchar *const count_buf = buf + name_len;
1498     size_t const count_buflen = buflen - name_len;
1499     do {
1500         ++count;
1501         g_snprintf(count_buf, count_buflen, "%lu", count);
1502     } while ( object->document->getObjectById(buf) != NULL );
1503     return buf;
1506 /* Style */
1508 /**
1509  * Returns an object style property.
1510  *
1511  * \todo
1512  * fixme: Use proper CSS parsing.  The current version is buggy
1513  * in a number of situations where key is a substring of the
1514  * style string other than as a property name (including
1515  * where key is a substring of a property name), and is also
1516  * buggy in its handling of inheritance for properties that
1517  * aren't inherited by default.  It also doesn't allow for
1518  * the case where the property is specified but with an invalid
1519  * value (in which case I believe the CSS2 error-handling
1520  * behaviour applies, viz. behave as if the property hadn't
1521  * been specified).  Also, the current code doesn't use CRSelEng
1522  * stuff to take a value from stylesheets.  Also, we aren't
1523  * setting any hooks to force an update for changes in any of
1524  * the inputs (i.e., in any of the elements that this function
1525  * queries).
1526  *
1527  * \par
1528  * Given that the default value for a property depends on what
1529  * property it is (e.g., whether to inherit or not), and given
1530  * the above comment about ignoring invalid values, and that the
1531  * repr parent isn't necessarily the right element to inherit
1532  * from (e.g., maybe we need to inherit from the referencing
1533  * <use> element instead), we should probably make the caller
1534  * responsible for ascending the repr tree as necessary.
1535  */
1536 gchar const *
1537 sp_object_get_style_property(SPObject const *object, gchar const *key, gchar const *def)
1539     g_return_val_if_fail(object != NULL, NULL);
1540     g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
1541     g_return_val_if_fail(key != NULL, NULL);
1543     gchar const *style = object->repr->attribute("style");
1544     if (style) {
1545         size_t const len = strlen(key);
1546         char const *p;
1547         while ( (p = strstr(style, key))
1548                 != NULL )
1549         {
1550             p += len;
1551             while ((*p <= ' ') && *p) p++;
1552             if (*p++ != ':') break;
1553             while ((*p <= ' ') && *p) p++;
1554             size_t const inherit_len = sizeof("inherit") - 1;
1555             if (*p
1556                 && !(strneq(p, "inherit", inherit_len)
1557                      && (p[inherit_len] == '\0'
1558                          || p[inherit_len] == ';'
1559                          || g_ascii_isspace(p[inherit_len])))) {
1560                 return p;
1561             }
1562         }
1563     }
1564     gchar const *val = object->repr->attribute(key);
1565     if (val && !streq(val, "inherit")) {
1566         return val;
1567     }
1568     if (object->parent) {
1569         return sp_object_get_style_property(object->parent, key, def);
1570     }
1572     return def;
1575 /**
1576  * Lifts SVG version of all root objects to version.
1577  */
1578 void
1579 SPObject::_requireSVGVersion(Inkscape::Version version) {
1580     for ( SPObject::ParentIterator iter=this ; iter ; ++iter ) {
1581         SPObject *object=iter;
1582         if (SP_IS_ROOT(object)) {
1583             SPRoot *root=SP_ROOT(object);
1584             if ( root->version.svg < version ) {
1585                 root->version.svg = version;
1586             }
1587         }
1588     }
1591 /**
1592  * Returns previous object in sibling list or NULL.
1593  */
1594 SPObject *
1595 sp_object_prev(SPObject *child)
1597     SPObject *parent = SP_OBJECT_PARENT(child);
1598     for ( SPObject *i = sp_object_first_child(parent); i; i = SP_OBJECT_NEXT(i) ) {
1599         if (SP_OBJECT_NEXT(i) == child)
1600             return i;
1601     }
1602     return NULL;
1605 /* Titles and descriptions */
1607 /* Note:
1608    Titles and descriptions are stored in 'title' and 'desc' child elements
1609    (see section 5.4 of the SVG 1.0 and 1.1 specifications).  The spec allows
1610    an element to have more than one 'title' child element, but strongly
1611    recommends against this and requires using the first one if a choice must
1612    be made.  The same applies to 'desc' elements.  Therefore, these functions
1613    ignore all but the first 'title' child element and first 'desc' child
1614    element, except when deleting a title or description.
1615 */
1617 /**
1618  * Returns the title of this object, or NULL if there is none.
1619  * The caller must free the returned string using g_free() - see comment
1620  * for getTitleOrDesc() below.
1621  */
1622 gchar *
1623 SPObject::title() const
1625     return getTitleOrDesc("svg:title");
1628 /**
1629  * Sets the title of this object
1630  * A NULL first argument is interpreted as meaning that the existing title
1631  * (if any) should be deleted.
1632  * The second argument is optional - see setTitleOrDesc() below for details.
1633  */
1634 bool
1635 SPObject::setTitle(gchar const *title, bool verbatim)
1637     return setTitleOrDesc(title, "svg:title", verbatim);
1640 /**
1641  * Returns the description of this object, or NULL if there is none.
1642  * The caller must free the returned string using g_free() - see comment
1643  * for getTitleOrDesc() below.
1644  */
1645 gchar *
1646 SPObject::desc() const
1648     return getTitleOrDesc("svg:desc");
1651 /**
1652  * Sets the description of this object.
1653  * A NULL first argument is interpreted as meaning that the existing
1654  * description (if any) should be deleted.
1655  * The second argument is optional - see setTitleOrDesc() below for details.
1656  */
1657 bool
1658 SPObject::setDesc(gchar const *desc, bool verbatim)
1660     return setTitleOrDesc(desc, "svg:desc", verbatim);
1663 /**
1664  * Returns the title or description of this object, or NULL if there is none.
1665  *
1666  * The SVG spec allows 'title' and 'desc' elements to contain text marked up
1667  * using elements from other namespaces.  Therefore, this function cannot
1668  * in general just return a pointer to an existing string - it must instead
1669  * construct a string containing the title or description without the mark-up.
1670  * Consequently, the return value is a newly allocated string (or NULL), and
1671  * must be freed (using g_free()) by the caller.
1672  */
1673 gchar *
1674 SPObject::getTitleOrDesc(gchar const *svg_tagname) const
1676     SPObject *elem = findFirstChild(svg_tagname);
1677     if (elem == NULL) return NULL;
1678     return g_string_free(elem->textualContent(), FALSE);
1681 /**
1682  * Sets or deletes the title or description of this object.
1683  * A NULL 'value' argument causes the title or description to be deleted.
1684  *
1685  * 'verbatim' parameter:
1686  * If verbatim==true, then the title or description is set to exactly the
1687  * specified value.  If verbatim==false then two exceptions are made:
1688  *   (1) If the specified value is just whitespace, then the title/description
1689  *       is deleted.
1690  *   (2) If the specified value is the same as the current value except for
1691  *       mark-up, then the current value is left unchanged.
1692  * This is usually the desired behaviour, so 'verbatim' defaults to false for
1693  * setTitle() and setDesc().
1694  *
1695  * The return value is true if a change was made to the title/description,
1696  * and usually false otherwise.
1697  */
1698 bool
1699 SPObject::setTitleOrDesc(gchar const *value, gchar const *svg_tagname, bool verbatim)
1701     if (!verbatim) {
1702         // If the new title/description is just whitespace,
1703         // treat it as though it were NULL.
1704         if (value) {
1705             bool just_whitespace = true;
1706             for (const gchar *cp = value; *cp; ++cp) {
1707                 if (!std::strchr("\r\n \t", *cp)) {
1708                     just_whitespace = false;
1709                     break;
1710                 }
1711             }
1712             if (just_whitespace) value = NULL;
1713         }
1714         // Don't stomp on mark-up if there is no real change.
1715         if (value) {
1716             gchar *current_value = getTitleOrDesc(svg_tagname);
1717             if (current_value) {
1718                 bool different = std::strcmp(current_value, value);
1719                 g_free(current_value);
1720                 if (!different) return false;
1721             }
1722         }
1723     }
1725     SPObject *elem = findFirstChild(svg_tagname);
1727     if (value == NULL) {
1728         if (elem == NULL) return false;
1729         // delete the title/description(s)
1730         while (elem) {
1731             elem->deleteObject();
1732             elem = findFirstChild(svg_tagname);
1733         }
1734         return true;
1735     }
1737     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
1739     if (elem == NULL) {
1740         // create a new 'title' or 'desc' element, putting it at the
1741         // beginning (in accordance with the spec's recommendations)
1742         Inkscape::XML::Node *xml_elem = xml_doc->createElement(svg_tagname);
1743         repr->addChild(xml_elem, NULL);
1744         elem = document->getObjectByRepr(xml_elem);
1745         Inkscape::GC::release(xml_elem);
1746     }
1747     else {
1748         // remove the current content of the 'text' or 'desc' element
1749         SPObject *child;
1750         while (NULL != (child = elem->firstChild())) child->deleteObject();
1751     }
1753     // add the new content
1754     elem->appendChildRepr(xml_doc->createTextNode(value));
1755     return true;
1758 /**
1759  * Find the first child of this object with a given tag name,
1760  * and return it.  Returns NULL if there is no matching child.
1761  */
1762 SPObject *
1763 SPObject::findFirstChild(gchar const *tagname) const
1765     for (SPObject *child = children; child; child = child->next)
1766     {
1767         if (child->repr->type() == Inkscape::XML::ELEMENT_NODE &&
1768             !strcmp(child->repr->name(), tagname)) return child;
1769     }
1770     return NULL;
1773 /**
1774  * Return the full textual content of an element (typically all the
1775  * content except the tags).
1776  * Must not be used on anything except elements.
1777  */
1778 GString*
1779 SPObject::textualContent() const
1781     GString* text = g_string_new("");
1783     for (const SPObject *child = firstChild(); child; child = child->next)
1784     {
1785         Inkscape::XML::NodeType child_type = child->repr->type();
1787         if (child_type == Inkscape::XML::ELEMENT_NODE) {
1788             GString * new_text = child->textualContent();
1789             g_string_append(text, new_text->str);
1790             g_string_free(new_text, TRUE);
1791         }
1792         else if (child_type == Inkscape::XML::TEXT_NODE) {
1793             g_string_append(text, child->repr->content());
1794         }
1795     }
1796     return text;
1799 /*
1800   Local Variables:
1801   mode:c++
1802   c-file-style:"stroustrup"
1803   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1804   indent-tabs-mode:nil
1805   fill-column:99
1806   End:
1807 */
1808 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :