Code

Pot and Dutch translation update
[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 "color-profile-fns.h"
42 #include "document.h"
43 #include "style.h"
44 #include "sp-object-repr.h"
45 #include "sp-root.h"
46 #include "sp-style-elem.h"
47 #include "sp-script.h"
48 #include "streq.h"
49 #include "strneq.h"
50 #include "xml/repr.h"
51 #include "xml/node-fns.h"
52 #include "debug/event-tracker.h"
53 #include "debug/simple-event.h"
54 #include "debug/demangle.h"
55 #include "util/share.h"
56 #include "util/format.h"
57 #include "util/longest-common-suffix.h"
59 using std::memcpy;
60 using std::strchr;
61 using std::strcmp;
62 using std::strlen;
63 using std::strstr;
65 #define noSP_OBJECT_DEBUG_CASCADE
67 #define noSP_OBJECT_DEBUG
69 #ifdef SP_OBJECT_DEBUG
70 # define debug(f, a...) { g_print("%s(%d) %s:", \
71                                   __FILE__,__LINE__,__FUNCTION__); \
72                           g_print(f, ## a); \
73                           g_print("\n"); \
74                         }
75 #else
76 # define debug(f, a...) /**/
77 #endif
79 static void sp_object_class_init(SPObjectClass *klass);
80 static void sp_object_init(SPObject *object);
81 static void sp_object_finalize(GObject *object);
83 static void sp_object_child_added(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref);
84 static void sp_object_remove_child(SPObject *object, Inkscape::XML::Node *child);
85 static void sp_object_order_changed(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *old_ref, Inkscape::XML::Node *new_ref);
87 static void sp_object_release(SPObject *object);
88 static void sp_object_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
90 static void sp_object_private_set(SPObject *object, unsigned int key, gchar const *value);
91 static Inkscape::XML::Node *sp_object_private_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags);
93 /* Real handlers of repr signals */
95 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);
97 static void sp_object_repr_content_changed(Inkscape::XML::Node *repr, gchar const *oldcontent, gchar const *newcontent, gpointer data);
99 static void sp_object_repr_child_added(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, gpointer data);
100 static void sp_object_repr_child_removed(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, void *data);
102 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);
104 static gchar *sp_object_get_unique_id(SPObject *object, gchar const *defid);
106 guint update_in_progress = 0; // guard against update-during-update
108 Inkscape::XML::NodeEventVector object_event_vector = {
109     sp_object_repr_child_added,
110     sp_object_repr_child_removed,
111     sp_object_repr_attr_changed,
112     sp_object_repr_content_changed,
113     sp_object_repr_order_changed
114 };
116 // A friend class used to set internal members on SPObject so as to not expose settors in SPObject's public API
117 class SPObjectImpl
119 public:
121 /**
122  * Null's the id member of an SPObject without attempting to free prior contents.
123  */
124     static void setIdNull( SPObject* obj ) {
125         if (obj) {
126             obj->id = 0;
127         }
128     }
130 /**
131  * Sets the id member of an object, freeing any prior content.
132  */
133     static void setId( SPObject* obj, gchar const* id ) {
134         if (obj && (id != obj->id) ) {
135             if (obj->id) {
136                 g_free(obj->id);
137                 obj->id = 0;
138             }
139             if (id) {
140                 obj->id = g_strdup(id);
141             }
142         }
143     }
144 };
147 static GObjectClass *parent_class;
149 /**
150  * Registers the SPObject class with Gdk and returns its type number.
151  */
152 GType
153 sp_object_get_type(void)
155     static GType type = 0;
156     if (!type) {
157         GTypeInfo info = {
158             sizeof(SPObjectClass),
159             NULL, NULL,
160             (GClassInitFunc) sp_object_class_init,
161             NULL, NULL,
162             sizeof(SPObject),
163             16,
164             (GInstanceInitFunc) sp_object_init,
165             NULL
166         };
167         type = g_type_register_static(G_TYPE_OBJECT, "SPObject", &info, (GTypeFlags)0);
168     }
169     return type;
172 /**
173  * Initializes the SPObject vtable.
174  */
175 static void
176 sp_object_class_init(SPObjectClass *klass)
178     GObjectClass *object_class;
180     object_class = (GObjectClass *) klass;
182     parent_class = (GObjectClass *) g_type_class_ref(G_TYPE_OBJECT);
184     object_class->finalize = sp_object_finalize;
186     klass->child_added = sp_object_child_added;
187     klass->remove_child = sp_object_remove_child;
188     klass->order_changed = sp_object_order_changed;
190     klass->release = sp_object_release;
192     klass->build = sp_object_build;
194     klass->set = sp_object_private_set;
195     klass->write = sp_object_private_write;
198 /**
199  * Callback to initialize the SPObject object.
200  */
201 static void
202 sp_object_init(SPObject *object)
204     debug("id=%x, typename=%s",object, g_type_name_from_instance((GTypeInstance*)object));
206     object->hrefcount = 0;
207     object->_total_hrefcount = 0;
208     object->document = NULL;
209     object->children = object->_last_child = NULL;
210     object->parent = object->next = NULL;
211     object->repr = NULL;
212     SPObjectImpl::setIdNull(object);
214     object->_collection_policy = SPObject::COLLECT_WITH_PARENT;
216     new (&object->_release_signal) sigc::signal<void, SPObject *>();
217     new (&object->_modified_signal) sigc::signal<void, SPObject *, unsigned int>();
218     new (&object->_delete_signal) sigc::signal<void, SPObject *>();
219     new (&object->_position_changed_signal) sigc::signal<void, SPObject *>();
220     object->_successor = NULL;
222     // FIXME: now we create style for all objects, but per SVG, only the following can have style attribute:
223     // vg, g, defs, desc, title, symbol, use, image, switch, path, rect, circle, ellipse, line, polyline,
224     // polygon, text, tspan, tref, textPath, altGlyph, glyphRef, marker, linearGradient, radialGradient,
225     // stop, pattern, clipPath, mask, filter, feImage, a, font, glyph, missing-glyph, foreignObject
226     object->style = sp_style_new_from_object(object);
228     object->_label = NULL;
229     object->_default_label = NULL;
232 /**
233  * Callback to destroy all members and connections of object and itself.
234  */
235 static void
236 sp_object_finalize(GObject *object)
238     SPObject *spobject = (SPObject *)object;
240     g_free(spobject->_label);
241     g_free(spobject->_default_label);
242     spobject->_label = NULL;
243     spobject->_default_label = NULL;
245     if (spobject->_successor) {
246         sp_object_unref(spobject->_successor, NULL);
247         spobject->_successor = NULL;
248     }
250     if (((GObjectClass *) (parent_class))->finalize) {
251         (* ((GObjectClass *) (parent_class))->finalize)(object);
252     }
254     spobject->_release_signal.~signal();
255     spobject->_modified_signal.~signal();
256     spobject->_delete_signal.~signal();
257     spobject->_position_changed_signal.~signal();
260 namespace {
262 namespace Debug = Inkscape::Debug;
263 namespace Util = Inkscape::Util;
265 typedef Debug::SimpleEvent<Debug::Event::REFCOUNT> BaseRefCountEvent;
267 class RefCountEvent : public BaseRefCountEvent {
268 public:
269     RefCountEvent(SPObject *object, int bias, Util::ptr_shared<char> name)
270     : BaseRefCountEvent(name)
271     {
272         _addProperty("object", Util::format("%p", object));
273         _addProperty("class", Debug::demangle(g_type_name(G_TYPE_FROM_INSTANCE(object))));
274         _addProperty("new-refcount", Util::format("%d", G_OBJECT(object)->ref_count + bias));
275     }
276 };
278 class RefEvent : public RefCountEvent {
279 public:
280     RefEvent(SPObject *object)
281     : RefCountEvent(object, 1, Util::share_static_string("sp-object-ref"))
282     {}
283 };
285 class UnrefEvent : public RefCountEvent {
286 public:
287     UnrefEvent(SPObject *object)
288     : RefCountEvent(object, -1, Util::share_static_string("sp-object-unref"))
289     {}
290 };
294 gchar const* SPObject::getId() const {
295     return id;
298 /**
299  * Increase reference count of object, with possible debugging.
300  *
301  * \param owner If non-NULL, make debug log entry.
302  * \return object, NULL is error.
303  * \pre object points to real object
304  */
305 SPObject *
306 sp_object_ref(SPObject *object, SPObject *owner)
308     g_return_val_if_fail(object != NULL, NULL);
309     g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
310     g_return_val_if_fail(!owner || SP_IS_OBJECT(owner), NULL);
312     Inkscape::Debug::EventTracker<RefEvent> tracker(object);
313     g_object_ref(G_OBJECT(object));
314     return object;
317 /**
318  * Decrease reference count of object, with possible debugging and
319  * finalization.
320  *
321  * \param owner If non-NULL, make debug log entry.
322  * \return always NULL
323  * \pre object points to real object
324  */
325 SPObject *
326 sp_object_unref(SPObject *object, SPObject *owner)
328     g_return_val_if_fail(object != NULL, NULL);
329     g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
330     g_return_val_if_fail(!owner || SP_IS_OBJECT(owner), NULL);
332     Inkscape::Debug::EventTracker<UnrefEvent> tracker(object);
333     g_object_unref(G_OBJECT(object));
334     return NULL;
337 /**
338  * Increase weak refcount.
339  *
340  * Hrefcount is used for weak references, for example, to
341  * determine whether any graphical element references a certain gradient
342  * node.
343  * \param owner Ignored.
344  * \return object, NULL is error
345  * \pre object points to real object
346  */
347 SPObject *
348 sp_object_href(SPObject *object, gpointer /*owner*/)
350     g_return_val_if_fail(object != NULL, NULL);
351     g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
353     object->hrefcount++;
354     object->_updateTotalHRefCount(1);
356     return object;
359 /**
360  * Decrease weak refcount.
361  *
362  * Hrefcount is used for weak references, for example, to determine whether
363  * any graphical element references a certain gradient node.
364  * \param owner Ignored.
365  * \return always NULL
366  * \pre object points to real object and hrefcount>0
367  */
368 SPObject *
369 sp_object_hunref(SPObject *object, gpointer /*owner*/)
371     g_return_val_if_fail(object != NULL, NULL);
372     g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
373     g_return_val_if_fail(object->hrefcount > 0, NULL);
375     object->hrefcount--;
376     object->_updateTotalHRefCount(-1);
378     return NULL;
381 /**
382  * Adds increment to _total_hrefcount of object and its parents.
383  */
384 void
385 SPObject::_updateTotalHRefCount(int increment) {
386     SPObject *topmost_collectable = NULL;
387     for ( SPObject *iter = this ; iter ; iter = SP_OBJECT_PARENT(iter) ) {
388         iter->_total_hrefcount += increment;
389         if ( iter->_total_hrefcount < iter->hrefcount ) {
390             g_critical("HRefs overcounted");
391         }
392         if ( iter->_total_hrefcount == 0 &&
393              iter->_collection_policy != COLLECT_WITH_PARENT )
394         {
395             topmost_collectable = iter;
396         }
397     }
398     if (topmost_collectable) {
399         topmost_collectable->requestOrphanCollection();
400     }
403 /**
404  * True if object is non-NULL and this is some in/direct parent of object.
405  */
406 bool
407 SPObject::isAncestorOf(SPObject const *object) const {
408     g_return_val_if_fail(object != NULL, false);
409     object = SP_OBJECT_PARENT(object);
410     while (object) {
411         if ( object == this ) {
412             return true;
413         }
414         object = SP_OBJECT_PARENT(object);
415     }
416     return false;
419 namespace {
421 bool same_objects(SPObject const &a, SPObject const &b) {
422     return &a == &b;
427 /**
428  * Returns youngest object being parent to this and object.
429  */
430 SPObject const *
431 SPObject::nearestCommonAncestor(SPObject const *object) const {
432     g_return_val_if_fail(object != NULL, NULL);
434     using Inkscape::Algorithms::longest_common_suffix;
435     return longest_common_suffix<SPObject::ConstParentIterator>(this, object, NULL, &same_objects);
438 SPObject const *AncestorSon(SPObject const *obj, SPObject const *ancestor) {
439     if (obj == NULL || ancestor == NULL)
440         return NULL;
441     if (SP_OBJECT_PARENT(obj) == ancestor)
442         return obj;
443     return AncestorSon(SP_OBJECT_PARENT(obj), ancestor);
446 /**
447  * Compares height of objects in tree.
448  *
449  * Works for different-parent objects, so long as they have a common ancestor.
450  * \return \verbatim
451  *    0    positions are equivalent
452  *    1    first object's position is greater than the second
453  *   -1    first object's position is less than the second   \endverbatim
454  */
455 int
456 sp_object_compare_position(SPObject const *first, SPObject const *second)
458     if (first == second) return 0;
460     SPObject const *ancestor = first->nearestCommonAncestor(second);
461     if (ancestor == NULL) return 0; // cannot compare, no common ancestor!
463     // we have an object and its ancestor (should not happen when sorting selection)
464     if (ancestor == first)
465         return 1;
466     if (ancestor == second)
467         return -1;
469     SPObject const *to_first = AncestorSon(first, ancestor);
470     SPObject const *to_second = AncestorSon(second, ancestor);
472     g_assert(SP_OBJECT_PARENT(to_second) == SP_OBJECT_PARENT(to_first));
474     return sp_repr_compare_position(SP_OBJECT_REPR(to_first), SP_OBJECT_REPR(to_second));
478 /**
479  * Append repr as child of this object.
480  * \pre this is not a cloned object
481  */
482 SPObject *
483 SPObject::appendChildRepr(Inkscape::XML::Node *repr) {
484     if (!SP_OBJECT_IS_CLONED(this)) {
485         SP_OBJECT_REPR(this)->appendChild(repr);
486         return SP_OBJECT_DOCUMENT(this)->getObjectByRepr(repr);
487     } else {
488         g_critical("Attempt to append repr as child of cloned object");
489         return NULL;
490     }
493 /**
494  * Retrieves the children as a GSList object, optionally ref'ing the children
495  * in the process, if add_ref is specified.
496  */
497 GSList *SPObject::childList(bool add_ref, Action) {
498     GSList *l = NULL;
499     for (SPObject *child = sp_object_first_child(this) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
500         if (add_ref)
501             g_object_ref (G_OBJECT (child));
503         l = g_slist_prepend (l, child);
504     }
505     return l;
509 /** Gets the label property for the object or a default if no label
510  *  is defined.
511  */
512 gchar const *
513 SPObject::label() const {
514     return _label;
517 /** Returns a default label property for the object. */
518 gchar const *
519 SPObject::defaultLabel() const {
520     if (_label) {
521         return _label;
522     } else {
523         if (!_default_label) {
524             gchar const *id=SP_OBJECT_ID(this);
525             if (id) {
526                 _default_label = g_strdup_printf("#%s", id);
527             } else {
528                 _default_label = g_strdup_printf("<%s>", SP_OBJECT_REPR(this)->name());
529             }
530         }
531         return _default_label;
532     }
535 /** Sets the label property for the object */
536 void
537 SPObject::setLabel(gchar const *label) {
538     SP_OBJECT_REPR(this)->setAttribute("inkscape:label", label, false);
542 /** Queues the object for orphan collection */
543 void 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         // leave it
549     } else if (SP_IS_SCRIPT(this)) {
550         // leave it
551     } else if (SP_IS_PAINT_SERVER(this) && static_cast<SPPaintServer*>(this)->isSwatch() ) {
552         // leave it
553     } else if (IS_COLORPROFILE(this)) {
554         // leave it
555     } else {
556         document->queueForOrphanCollection(this);
558         /** \todo
559          * This is a temporary hack added to make fill&stroke rebuild its
560          * gradient list when the defs are vacuumed.  gradient-vector.cpp
561          * listens to the modified signal on defs, and now we give it that
562          * signal.  Mental says that this should be made automatic by
563          * merging SPObjectGroup with SPObject; SPObjectGroup would issue
564          * this signal automatically. Or maybe just derive SPDefs from
565          * SPObjectGroup?
566          */
568         this->requestModified(SP_OBJECT_CHILD_MODIFIED_FLAG);
569     }
572 /** Sends the delete signal to all children of this object recursively */
573 void
574 SPObject::_sendDeleteSignalRecursive() {
575     for (SPObject *child = sp_object_first_child(this); child; child = SP_OBJECT_NEXT(child)) {
576         child->_delete_signal.emit(child);
577         child->_sendDeleteSignalRecursive();
578     }
581 /**
582  * Deletes the object reference, unparenting it from its parent.
583  *
584  * If the \a propagate parameter is set to true, it emits a delete
585  * signal.  If the \a propagate_descendants parameter is true, it
586  * recursively sends the delete signal to children.
587  */
588 void
589 SPObject::deleteObject(bool propagate, bool propagate_descendants)
591     sp_object_ref(this, NULL);
592     if (propagate) {
593         _delete_signal.emit(this);
594     }
595     if (propagate_descendants) {
596         this->_sendDeleteSignalRecursive();
597     }
599     Inkscape::XML::Node *repr=SP_OBJECT_REPR(this);
600     if (repr && sp_repr_parent(repr)) {
601         sp_repr_unparent(repr);
602     }
604     if (_successor) {
605         _successor->deleteObject(propagate, propagate_descendants);
606     }
607     sp_object_unref(this, NULL);
610 /**
611  * Put object into object tree, under parent, and behind prev;
612  * also update object's XML space.
613  */
614 void
615 sp_object_attach(SPObject *parent, SPObject *object, SPObject *prev)
617     g_return_if_fail(parent != NULL);
618     g_return_if_fail(SP_IS_OBJECT(parent));
619     g_return_if_fail(object != NULL);
620     g_return_if_fail(SP_IS_OBJECT(object));
621     g_return_if_fail(!prev || SP_IS_OBJECT(prev));
622     g_return_if_fail(!prev || prev->parent == parent);
623     g_return_if_fail(!object->parent);
625     sp_object_ref(object, parent);
626     object->parent = parent;
627     parent->_updateTotalHRefCount(object->_total_hrefcount);
629     SPObject *next;
630     if (prev) {
631         next = prev->next;
632         prev->next = object;
633     } else {
634         next = parent->children;
635         parent->children = object;
636     }
637     object->next = next;
638     if (!next) {
639         parent->_last_child = object;
640     }
641     if (!object->xml_space.set)
642         object->xml_space.value = parent->xml_space.value;
645 /**
646  * In list of object's siblings, move object behind prev.
647  */
648 void
649 sp_object_reorder(SPObject *object, SPObject *prev) {
650     g_return_if_fail(object != NULL);
651     g_return_if_fail(SP_IS_OBJECT(object));
652     g_return_if_fail(object->parent != NULL);
653     g_return_if_fail(object != prev);
654     g_return_if_fail(!prev || SP_IS_OBJECT(prev));
655     g_return_if_fail(!prev || prev->parent == object->parent);
657     SPObject *const parent=object->parent;
659     SPObject *old_prev=NULL;
660     for ( SPObject *child = parent->children ; child && child != object ;
661           child = child->next )
662     {
663         old_prev = child;
664     }
666     SPObject *next=object->next;
667     if (old_prev) {
668         old_prev->next = next;
669     } else {
670         parent->children = next;
671     }
672     if (!next) {
673         parent->_last_child = old_prev;
674     }
675     if (prev) {
676         next = prev->next;
677         prev->next = object;
678     } else {
679         next = parent->children;
680         parent->children = object;
681     }
682     object->next = next;
683     if (!next) {
684         parent->_last_child = object;
685     }
688 /**
689  * Remove object from parent's children, release and unref it.
690  */
691 void
692 sp_object_detach(SPObject *parent, SPObject *object) {
693     g_return_if_fail(parent != NULL);
694     g_return_if_fail(SP_IS_OBJECT(parent));
695     g_return_if_fail(object != NULL);
696     g_return_if_fail(SP_IS_OBJECT(object));
697     g_return_if_fail(object->parent == parent);
699     object->releaseReferences();
701     SPObject *prev=NULL;
702     for ( SPObject *child = parent->children ; child && child != object ;
703           child = child->next )
704     {
705         prev = child;
706     }
708     SPObject *next=object->next;
709     if (prev) {
710         prev->next = next;
711     } else {
712         parent->children = next;
713     }
714     if (!next) {
715         parent->_last_child = prev;
716     }
718     object->next = NULL;
719     object->parent = NULL;
721     parent->_updateTotalHRefCount(-object->_total_hrefcount);
722     sp_object_unref(object, parent);
725 /**
726  * Return object's child whose node pointer equals repr.
727  */
728 SPObject *
729 sp_object_get_child_by_repr(SPObject *object, Inkscape::XML::Node *repr)
731     g_return_val_if_fail(object != NULL, NULL);
732     g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
733     g_return_val_if_fail(repr != NULL, NULL);
735     if (object->_last_child && SP_OBJECT_REPR(object->_last_child) == repr)
736         return object->_last_child;   // optimization for common scenario
737     for ( SPObject *child = object->children ; child ; child = child->next ) {
738         if ( SP_OBJECT_REPR(child) == repr ) {
739             return child;
740         }
741     }
743     return NULL;
746 /**
747  * Callback for child_added event.
748  * Invoked whenever the given mutation event happens in the XML tree.
749  */
750 static void
751 sp_object_child_added(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref)
753     GType type = sp_repr_type_lookup(child);
754     if (!type) {
755         return;
756     }
757     SPObject *ochild = SP_OBJECT(g_object_new(type, 0));
758     SPObject *prev = ref ? sp_object_get_child_by_repr(object, ref) : NULL;
759     sp_object_attach(object, ochild, prev);
760     sp_object_unref(ochild, NULL);
762     sp_object_invoke_build(ochild, object->document, child, SP_OBJECT_IS_CLONED(object));
765 /**
766  * Removes, releases and unrefs all children of object.
767  *
768  * This is the opposite of build. It has to be invoked as soon as the
769  * object is removed from the tree, even if it is still alive according
770  * to reference count. The frontend unregisters the object from the
771  * document and releases the SPRepr bindings; implementations should free
772  * state data and release all child objects.  Invoking release on
773  * SPRoot destroys the whole document tree.
774  * \see sp_object_build()
775  */
776 static void sp_object_release(SPObject *object)
778     debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object));
779     while (object->children) {
780         sp_object_detach(object, object->children);
781     }
784 /**
785  * Remove object's child whose node equals repr, release and
786  * unref it.
787  *
788  * Invoked whenever the given mutation event happens in the XML
789  * tree, BEFORE removal from the XML tree happens, so grouping
790  * objects can safely release the child data.
791  */
792 static void
793 sp_object_remove_child(SPObject *object, Inkscape::XML::Node *child)
795     debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object));
796     SPObject *ochild = sp_object_get_child_by_repr(object, child);
797     g_return_if_fail (ochild != NULL || !strcmp("comment", child->name())); // comments have no objects
798     if (ochild)
799         sp_object_detach(object, ochild);
802 /**
803  * Move object corresponding to child after sibling object corresponding
804  * to new_ref.
805  * Invoked whenever the given mutation event happens in the XML tree.
806  * \param old_ref Ignored
807  */
808 static void sp_object_order_changed(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node */*old_ref*/,
809                                     Inkscape::XML::Node *new_ref)
811     SPObject *ochild = sp_object_get_child_by_repr(object, child);
812     g_return_if_fail(ochild != NULL);
813     SPObject *prev = new_ref ? sp_object_get_child_by_repr(object, new_ref) : NULL;
814     sp_object_reorder(ochild, prev);
815     ochild->_position_changed_signal.emit(ochild);
818 /**
819  * Virtual build callback.
820  *
821  * This has to be invoked immediately after creation of an SPObject. The
822  * frontend method ensures that the new object is properly attached to
823  * the document and repr; implementation then will parse all of the attributes,
824  * generate the children objects and so on.  Invoking build on the SPRoot
825  * object results in creation of the whole document tree (this is, what
826  * SPDocument does after the creation of the XML tree).
827  * \see sp_object_release()
828  */
829 static void
830 sp_object_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
832     /* Nothing specific here */
833     debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object));
835     sp_object_read_attr(object, "xml:space");
836     sp_object_read_attr(object, "inkscape:label");
837     sp_object_read_attr(object, "inkscape:collect");
839     for (Inkscape::XML::Node *rchild = repr->firstChild() ; rchild != NULL; rchild = rchild->next()) {
840         GType type = sp_repr_type_lookup(rchild);
841         if (!type) {
842             continue;
843         }
844         SPObject *child = SP_OBJECT(g_object_new(type, 0));
845         sp_object_attach(object, child, object->lastChild());
846         sp_object_unref(child, NULL);
847         sp_object_invoke_build(child, document, rchild, SP_OBJECT_IS_CLONED(object));
848     }
851 void sp_object_invoke_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr, unsigned int cloned)
853     debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object));
855     g_assert(object != NULL);
856     g_assert(SP_IS_OBJECT(object));
857     g_assert(document != NULL);
858     g_assert(repr != NULL);
860     g_assert(object->document == NULL);
861     g_assert(object->repr == NULL);
862     g_assert(object->getId() == NULL);
864     /* Bookkeeping */
866     object->document = document;
867     object->repr = repr;
868     if (!cloned)
869         Inkscape::GC::anchor(repr);
870     object->cloned = cloned;
872     if (!SP_OBJECT_IS_CLONED(object)) {
873         object->document->bindObjectToRepr(object->repr, object);
875         if (Inkscape::XML::id_permitted(object->repr)) {
876             /* If we are not cloned, and not seeking, force unique id */
877             gchar const *id = object->repr->attribute("id");
878             if (!document->isSeeking()) {
879                 {
880                     gchar *realid = sp_object_get_unique_id(object, id);
881                     g_assert(realid != NULL);
883                     object->document->bindObjectToId(realid, object);
884                     SPObjectImpl::setId(object, realid);
885                     g_free(realid);
886                 }
888                 /* Redefine ID, if required */
889                 if ((id == NULL) || (strcmp(id, object->getId()) != 0)) {
890                     object->repr->setAttribute("id", object->getId());
891                 }
892             } else if (id) {
893                 // bind if id, but no conflict -- otherwise, we can expect
894                 // a subsequent setting of the id attribute
895                 if (!object->document->getObjectById(id)) {
896                     object->document->bindObjectToId(id, object);
897                     SPObjectImpl::setId(object, id);
898                 }
899             }
900         }
901     } else {
902         g_assert(object->getId() == NULL);
903     }
905     /* Invoke derived methods, if any */
906     if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->build) {
907         (*((SPObjectClass *) G_OBJECT_GET_CLASS(object))->build)(object, document, repr);
908     }
910     /* Signalling (should be connected AFTER processing derived methods */
911     sp_repr_add_listener(repr, &object_event_vector, object);
914 void SPObject::releaseReferences() {
915     g_assert(this->document);
916     g_assert(this->repr);
918     sp_repr_remove_listener_by_data(this->repr, this);
920     this->_release_signal.emit(this);
921     SPObjectClass *klass=(SPObjectClass *)G_OBJECT_GET_CLASS(this);
922     if (klass->release) {
923         klass->release(this);
924     }
926     /* all hrefs should be released by the "release" handlers */
927     g_assert(this->hrefcount == 0);
929     if (!SP_OBJECT_IS_CLONED(this)) {
930         if (this->id) {
931             this->document->bindObjectToId(this->id, NULL);
932         }
933         g_free(this->id);
934         this->id = NULL;
936         g_free(this->_default_label);
937         this->_default_label = NULL;
939         this->document->bindObjectToRepr(this->repr, NULL);
941         Inkscape::GC::release(this->repr);
942     } else {
943         g_assert(!this->id);
944     }
946     if (this->style) {
947         this->style = sp_style_unref(this->style);
948     }
950     this->document = NULL;
951     this->repr = NULL;
955 SPObject *SPObject::getNext()
957     return next;
960 SPObject *SPObject::getPrev()
962     return sp_object_prev(this);
965 /**
966  * Callback for child_added node event.
967  */
968 static void
969 sp_object_repr_child_added(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, gpointer data)
971     SPObject *object = SP_OBJECT(data);
973     if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->child_added)
974         (*((SPObjectClass *)G_OBJECT_GET_CLASS(object))->child_added)(object, child, ref);
977 /**
978  * Callback for remove_child node event.
979  */
980 static void
981 sp_object_repr_child_removed(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node */*ref*/, gpointer data)
983     SPObject *object = SP_OBJECT(data);
985     if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->remove_child) {
986         (* ((SPObjectClass *)G_OBJECT_GET_CLASS(object))->remove_child)(object, child);
987     }
990 /**
991  * Callback for order_changed node event.
992  *
993  * \todo fixme:
994  */
995 static void
996 sp_object_repr_order_changed(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node *old, Inkscape::XML::Node *newer, gpointer data)
998     SPObject *object = SP_OBJECT(data);
1000     if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->order_changed) {
1001         (* ((SPObjectClass *)G_OBJECT_GET_CLASS(object))->order_changed)(object, child, old, newer);
1002     }
1005 /**
1006  * Callback for set event.
1007  */
1008 static void
1009 sp_object_private_set(SPObject *object, unsigned int key, gchar const *value)
1011     g_assert(key != SP_ATTR_INVALID);
1013     switch (key) {
1014         case SP_ATTR_ID:
1015             if ( !SP_OBJECT_IS_CLONED(object) && object->repr->type() == Inkscape::XML::ELEMENT_NODE ) {
1016                 SPDocument *document=object->document;
1017                 SPObject *conflict=NULL;
1019                 gchar const *new_id = value;
1021                 if (new_id) {
1022                     conflict = document->getObjectById((char const *)new_id);
1023                 }
1025                 if ( conflict && conflict != object ) {
1026                     if (!document->isSeeking()) {
1027                         sp_object_ref(conflict, NULL);
1028                         // give the conflicting object a new ID
1029                         gchar *new_conflict_id = sp_object_get_unique_id(conflict, NULL);
1030                         SP_OBJECT_REPR(conflict)->setAttribute("id", new_conflict_id);
1031                         g_free(new_conflict_id);
1032                         sp_object_unref(conflict, NULL);
1033                     } else {
1034                         new_id = NULL;
1035                     }
1036                 }
1038                 if (object->getId()) {
1039                     document->bindObjectToId(object->getId(), NULL);
1040                     SPObjectImpl::setId(object, 0);
1041                 }
1043                 if (new_id) {
1044                     SPObjectImpl::setId(object, new_id);
1045                     document->bindObjectToId(object->getId(), object);
1046                 }
1048                 g_free(object->_default_label);
1049                 object->_default_label = NULL;
1050             }
1051             break;
1052         case SP_ATTR_INKSCAPE_LABEL:
1053             g_free(object->_label);
1054             if (value) {
1055                 object->_label = g_strdup(value);
1056             } else {
1057                 object->_label = NULL;
1058             }
1059             g_free(object->_default_label);
1060             object->_default_label = NULL;
1061             break;
1062         case SP_ATTR_INKSCAPE_COLLECT:
1063             if ( value && !strcmp(value, "always") ) {
1064                 object->setCollectionPolicy(SPObject::ALWAYS_COLLECT);
1065             } else {
1066                 object->setCollectionPolicy(SPObject::COLLECT_WITH_PARENT);
1067             }
1068             break;
1069         case SP_ATTR_XML_SPACE:
1070             if (value && !strcmp(value, "preserve")) {
1071                 object->xml_space.value = SP_XML_SPACE_PRESERVE;
1072                 object->xml_space.set = TRUE;
1073             } else if (value && !strcmp(value, "default")) {
1074                 object->xml_space.value = SP_XML_SPACE_DEFAULT;
1075                 object->xml_space.set = TRUE;
1076             } else if (object->parent) {
1077                 SPObject *parent;
1078                 parent = object->parent;
1079                 object->xml_space.value = parent->xml_space.value;
1080             }
1081             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1082             break;
1083         case SP_ATTR_STYLE:
1084             sp_style_read_from_object(object->style, object);
1085             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1086             break;
1087         default:
1088             break;
1089     }
1092 /**
1093  * Call virtual set() function of object.
1094  */
1095 void
1096 sp_object_set(SPObject *object, unsigned int key, gchar const *value)
1098     g_assert(object != NULL);
1099     g_assert(SP_IS_OBJECT(object));
1101     if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->set) {
1102         ((SPObjectClass *) G_OBJECT_GET_CLASS(object))->set(object, key, value);
1103     }
1106 /**
1107  * Read value of key attribute from XML node into object.
1108  */
1109 void
1110 sp_object_read_attr(SPObject *object, gchar const *key)
1112     g_assert(object != NULL);
1113     g_assert(SP_IS_OBJECT(object));
1114     g_assert(key != NULL);
1116     g_assert(object->repr != NULL);
1118     unsigned int keyid = sp_attribute_lookup(key);
1119     if (keyid != SP_ATTR_INVALID) {
1120         /* Retrieve the 'key' attribute from the object's XML representation */
1121         gchar const *value = object->repr->attribute(key);
1123         sp_object_set(object, keyid, value);
1124     }
1127 /**
1128  * Callback for attr_changed node event.
1129  */
1130 static void
1131 sp_object_repr_attr_changed(Inkscape::XML::Node */*repr*/, gchar const *key, gchar const */*oldval*/, gchar const */*newval*/, bool is_interactive, gpointer data)
1133     SPObject *object = SP_OBJECT(data);
1135     sp_object_read_attr(object, key);
1137     // manual changes to extension attributes require the normal
1138     // attributes, which depend on them, to be updated immediately
1139     if (is_interactive) {
1140         object->updateRepr(0);
1141     }
1144 /**
1145  * Callback for content_changed node event.
1146  */
1147 static void
1148 sp_object_repr_content_changed(Inkscape::XML::Node */*repr*/, gchar const */*oldcontent*/, gchar const */*newcontent*/, gpointer data)
1150     SPObject *object = SP_OBJECT(data);
1152     if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->read_content)
1153         (*((SPObjectClass *) G_OBJECT_GET_CLASS(object))->read_content)(object);
1156 /**
1157  * Return string representation of space value.
1158  */
1159 static gchar const*
1160 sp_xml_get_space_string(unsigned int space)
1162     switch (space) {
1163         case SP_XML_SPACE_DEFAULT:
1164             return "default";
1165         case SP_XML_SPACE_PRESERVE:
1166             return "preserve";
1167         default:
1168             return NULL;
1169     }
1172 /**
1173  * Callback for write event.
1174  */
1175 static Inkscape::XML::Node *
1176 sp_object_private_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags)
1178     if (!repr && (flags & SP_OBJECT_WRITE_BUILD)) {
1179         repr = SP_OBJECT_REPR(object)->duplicate(doc);
1180         if (!( flags & SP_OBJECT_WRITE_EXT )) {
1181             repr->setAttribute("inkscape:collect", NULL);
1182         }
1183     } else {
1184         repr->setAttribute("id", object->getId());
1186         if (object->xml_space.set) {
1187             char const *xml_space;
1188             xml_space = sp_xml_get_space_string(object->xml_space.value);
1189             repr->setAttribute("xml:space", xml_space);
1190         }
1192         if ( flags & SP_OBJECT_WRITE_EXT &&
1193              object->collectionPolicy() == SPObject::ALWAYS_COLLECT )
1194         {
1195             repr->setAttribute("inkscape:collect", "always");
1196         } else {
1197             repr->setAttribute("inkscape:collect", NULL);
1198         }
1200         SPStyle const *const obj_style = SP_OBJECT_STYLE(object);
1201         if (obj_style) {
1202             gchar *s = sp_style_write_string(obj_style, SP_STYLE_FLAG_IFSET);
1203             repr->setAttribute("style", ( *s ? s : NULL ));
1204             g_free(s);
1205         } else {
1206             /** \todo I'm not sure what to do in this case.  Bug #1165868
1207              * suggests that it can arise, but the submitter doesn't know
1208              * how to do so reliably.  The main two options are either
1209              * leave repr's style attribute unchanged, or explicitly clear it.
1210              * Must also consider what to do with property attributes for
1211              * the element; see below.
1212              */
1213             char const *style_str = repr->attribute("style");
1214             if (!style_str) {
1215                 style_str = "NULL";
1216             }
1217             g_warning("Item's style is NULL; repr style attribute is %s", style_str);
1218         }
1220         /** \note We treat object->style as authoritative.  Its effects have
1221          * been written to the style attribute above; any properties that are
1222          * unset we take to be deliberately unset (e.g. so that clones can
1223          * override the property).
1224          *
1225          * Note that the below has an undesirable consequence of changing the
1226          * appearance on renderers that lack CSS support (e.g. SVG tiny);
1227          * possibly we should write property attributes instead of a style
1228          * attribute.
1229          */
1230         sp_style_unset_property_attrs (object);
1231     }
1233     return repr;
1236 /**
1237  * Update this object's XML node with flags value.
1238  */
1239 Inkscape::XML::Node *
1240 SPObject::updateRepr(unsigned int flags) {
1241     if (!SP_OBJECT_IS_CLONED(this)) {
1242         Inkscape::XML::Node *repr=SP_OBJECT_REPR(this);
1243         if (repr) {
1244             return updateRepr(repr->document(), repr, flags);
1245         } else {
1246             g_critical("Attempt to update non-existent repr");
1247             return NULL;
1248         }
1249     } else {
1250         /* cloned objects have no repr */
1251         return NULL;
1252     }
1255 /** Used both to create reprs in the original document, and to create
1256  *  reprs in another document (e.g. a temporary document used when
1257  *  saving as "Plain SVG"
1258  */
1259 Inkscape::XML::Node *
1260 SPObject::updateRepr(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, unsigned int flags) {
1261     g_assert(doc != NULL);
1263     if (SP_OBJECT_IS_CLONED(this)) {
1264         /* cloned objects have no repr */
1265         return NULL;
1266     }
1267     if (((SPObjectClass *) G_OBJECT_GET_CLASS(this))->write) {
1268         if (!(flags & SP_OBJECT_WRITE_BUILD) && !repr) {
1269             repr = SP_OBJECT_REPR(this);
1270         }
1271         return ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->write(this, doc, repr, flags);
1272     } else {
1273         g_warning("Class %s does not implement ::write", G_OBJECT_TYPE_NAME(this));
1274         if (!repr) {
1275             if (flags & SP_OBJECT_WRITE_BUILD) {
1276                 repr = SP_OBJECT_REPR(this)->duplicate(doc);
1277             }
1278             /// \todo FIXME: else probably error (Lauris) */
1279         } else {
1280             repr->mergeFrom(SP_OBJECT_REPR(this), "id");
1281         }
1282         return repr;
1283     }
1286 /* Modification */
1288 /**
1289  * Add \a flags to \a object's as dirtiness flags, and
1290  * recursively add CHILD_MODIFIED flag to
1291  * parent and ancestors (as far up as necessary).
1292  */
1293 void
1294 SPObject::requestDisplayUpdate(unsigned int flags)
1296     g_return_if_fail( this->document != NULL );
1298     if (update_in_progress) {
1299         g_print("WARNING: Requested update while update in progress, counter = %d\n", update_in_progress);
1300     }
1302     /* requestModified must be used only to set one of SP_OBJECT_MODIFIED_FLAG or
1303      * SP_OBJECT_CHILD_MODIFIED_FLAG */
1304     g_return_if_fail(!(flags & SP_OBJECT_PARENT_MODIFIED_FLAG));
1305     g_return_if_fail((flags & SP_OBJECT_MODIFIED_FLAG) || (flags & SP_OBJECT_CHILD_MODIFIED_FLAG));
1306     g_return_if_fail(!((flags & SP_OBJECT_MODIFIED_FLAG) && (flags & SP_OBJECT_CHILD_MODIFIED_FLAG)));
1308     bool already_propagated = (!(this->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG)));
1310     this->uflags |= flags;
1312     /* If requestModified has already been called on this object or one of its children, then we
1313      * don't need to set CHILD_MODIFIED on our ancestors because it's already been done.
1314      */
1315     if (already_propagated) {
1316         SPObject *parent = SP_OBJECT_PARENT(this);
1317         if (parent) {
1318             parent->requestDisplayUpdate(SP_OBJECT_CHILD_MODIFIED_FLAG);
1319         } else {
1320             sp_document_request_modified(SP_OBJECT_DOCUMENT(this));
1321         }
1322     }
1325 /**
1326  * Update views
1327  */
1328 void
1329 SPObject::updateDisplay(SPCtx *ctx, unsigned int flags)
1331     g_return_if_fail(!(flags & ~SP_OBJECT_MODIFIED_CASCADE));
1333     update_in_progress ++;
1335 #ifdef SP_OBJECT_DEBUG_CASCADE
1336     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);
1337 #endif
1339     /* Get this flags */
1340     flags |= this->uflags;
1341     /* Copy flags to modified cascade for later processing */
1342     this->mflags |= this->uflags;
1343     /* We have to clear flags here to allow rescheduling update */
1344     this->uflags = 0;
1346     // Merge style if we have good reasons to think that parent style is changed */
1347     /** \todo
1348      * I am not sure whether we should check only propagated
1349      * flag. We are currently assuming that style parsing is
1350      * done immediately. I think this is correct (Lauris).
1351      */
1352     if ((flags & SP_OBJECT_STYLE_MODIFIED_FLAG) && (flags & SP_OBJECT_PARENT_MODIFIED_FLAG)) {
1353         if (this->style && this->parent) {
1354             sp_style_merge_from_parent(this->style, this->parent->style);
1355         }
1356     }
1358     try
1359     {
1360         if (((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update)
1361             ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update(this, ctx, flags);
1362     }
1363     catch(...)
1364     {
1365         /** \todo
1366         * in case of catching an exception we need to inform the user somehow that the document is corrupted
1367         * maybe by implementing an document flag documentOk
1368         * or by a modal error dialog
1369         */
1370         g_warning("SPObject::updateDisplay(SPCtx *ctx, unsigned int flags) : throw in ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update(this, ctx, flags);");
1371     }
1373     update_in_progress --;
1376 /**
1377  * Request modified always bubbles *up* the tree, as opposed to
1378  * request display update, which trickles down and relies on the
1379  * flags set during this pass...
1380  */
1381 void
1382 SPObject::requestModified(unsigned int flags)
1384     g_return_if_fail( this->document != NULL );
1386     /* requestModified must be used only to set one of SP_OBJECT_MODIFIED_FLAG or
1387      * SP_OBJECT_CHILD_MODIFIED_FLAG */
1388     g_return_if_fail(!(flags & SP_OBJECT_PARENT_MODIFIED_FLAG));
1389     g_return_if_fail((flags & SP_OBJECT_MODIFIED_FLAG) || (flags & SP_OBJECT_CHILD_MODIFIED_FLAG));
1390     g_return_if_fail(!((flags & SP_OBJECT_MODIFIED_FLAG) && (flags & SP_OBJECT_CHILD_MODIFIED_FLAG)));
1392     bool already_propagated = (!(this->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG)));
1394     this->mflags |= flags;
1396     /* If requestModified has already been called on this object or one of its children, then we
1397      * don't need to set CHILD_MODIFIED on our ancestors because it's already been done.
1398      */
1399     if (already_propagated) {
1400         SPObject *parent=SP_OBJECT_PARENT(this);
1401         if (parent) {
1402             parent->requestModified(SP_OBJECT_CHILD_MODIFIED_FLAG);
1403         } else {
1404             sp_document_request_modified(SP_OBJECT_DOCUMENT(this));
1405         }
1406     }
1409 /**
1410  *  Emits the MODIFIED signal with the object's flags.
1411  *  The object's mflags are the original set aside during the update pass for
1412  *  later delivery here.  Once emitModified() is called, those flags don't
1413  *  need to be stored any longer.
1414  */
1415 void
1416 SPObject::emitModified(unsigned int flags)
1418     /* only the MODIFIED_CASCADE flag is legal here */
1419     g_return_if_fail(!(flags & ~SP_OBJECT_MODIFIED_CASCADE));
1421 #ifdef SP_OBJECT_DEBUG_CASCADE
1422     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);
1423 #endif
1425     flags |= this->mflags;
1426     /* We have to clear mflags beforehand, as signal handlers may
1427      * make changes and therefore queue new modification notifications
1428      * themselves. */
1429     this->mflags = 0;
1431     g_object_ref(G_OBJECT(this));
1432     SPObjectClass *klass=(SPObjectClass *)G_OBJECT_GET_CLASS(this);
1433     if (klass->modified) {
1434         klass->modified(this, flags);
1435     }
1436     _modified_signal.emit(this, flags);
1437     g_object_unref(G_OBJECT(this));
1440 gchar const *
1441 sp_object_tagName_get(SPObject const *object, SPException *ex)
1443     /* If exception is not clear, return */
1444     if (!SP_EXCEPTION_IS_OK(ex)) {
1445         return NULL;
1446     }
1448     /// \todo fixme: Exception if object is NULL? */
1449     return object->repr->name();
1452 gchar const *
1453 sp_object_getAttribute(SPObject const *object, gchar const *key, SPException *ex)
1455     /* If exception is not clear, return */
1456     if (!SP_EXCEPTION_IS_OK(ex)) {
1457         return NULL;
1458     }
1460     /// \todo fixme: Exception if object is NULL? */
1461     return (gchar const *) object->repr->attribute(key);
1464 void
1465 sp_object_setAttribute(SPObject *object, gchar const *key, gchar const *value, SPException *ex)
1467     /* If exception is not clear, return */
1468     g_return_if_fail(SP_EXCEPTION_IS_OK(ex));
1470     /// \todo fixme: Exception if object is NULL? */
1471     object->repr->setAttribute(key, value, false);
1474 void
1475 sp_object_removeAttribute(SPObject *object, gchar const *key, SPException *ex)
1477     /* If exception is not clear, return */
1478     g_return_if_fail(SP_EXCEPTION_IS_OK(ex));
1480     /// \todo fixme: Exception if object is NULL? */
1481     object->repr->setAttribute(key, NULL, false);
1484 /* Helper */
1486 static gchar *
1487 sp_object_get_unique_id(SPObject *object, gchar const *id)
1489     static unsigned long count = 0;
1491     g_assert(SP_IS_OBJECT(object));
1493     count++;
1495     gchar const *name = object->repr->name();
1496     g_assert(name != NULL);
1498     gchar const *local = strchr(name, ':');
1499     if (local) {
1500         name = local + 1;
1501     }
1503     if (id != NULL) {
1504         if (object->document->getObjectById(id) == NULL) {
1505             return g_strdup(id);
1506         }
1507     }
1509     size_t const name_len = strlen(name);
1510     size_t const buflen = name_len + (sizeof(count) * 10 / 4) + 1;
1511     gchar *const buf = (gchar *) g_malloc(buflen);
1512     memcpy(buf, name, name_len);
1513     gchar *const count_buf = buf + name_len;
1514     size_t const count_buflen = buflen - name_len;
1515     do {
1516         ++count;
1517         g_snprintf(count_buf, count_buflen, "%lu", count);
1518     } while ( object->document->getObjectById(buf) != NULL );
1519     return buf;
1522 /* Style */
1524 /**
1525  * Returns an object style property.
1526  *
1527  * \todo
1528  * fixme: Use proper CSS parsing.  The current version is buggy
1529  * in a number of situations where key is a substring of the
1530  * style string other than as a property name (including
1531  * where key is a substring of a property name), and is also
1532  * buggy in its handling of inheritance for properties that
1533  * aren't inherited by default.  It also doesn't allow for
1534  * the case where the property is specified but with an invalid
1535  * value (in which case I believe the CSS2 error-handling
1536  * behaviour applies, viz. behave as if the property hadn't
1537  * been specified).  Also, the current code doesn't use CRSelEng
1538  * stuff to take a value from stylesheets.  Also, we aren't
1539  * setting any hooks to force an update for changes in any of
1540  * the inputs (i.e., in any of the elements that this function
1541  * queries).
1542  *
1543  * \par
1544  * Given that the default value for a property depends on what
1545  * property it is (e.g., whether to inherit or not), and given
1546  * the above comment about ignoring invalid values, and that the
1547  * repr parent isn't necessarily the right element to inherit
1548  * from (e.g., maybe we need to inherit from the referencing
1549  * <use> element instead), we should probably make the caller
1550  * responsible for ascending the repr tree as necessary.
1551  */
1552 gchar const *
1553 sp_object_get_style_property(SPObject const *object, gchar const *key, gchar const *def)
1555     g_return_val_if_fail(object != NULL, NULL);
1556     g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
1557     g_return_val_if_fail(key != NULL, NULL);
1559     gchar const *style = object->repr->attribute("style");
1560     if (style) {
1561         size_t const len = strlen(key);
1562         char const *p;
1563         while ( (p = strstr(style, key))
1564                 != NULL )
1565         {
1566             p += len;
1567             while ((*p <= ' ') && *p) p++;
1568             if (*p++ != ':') break;
1569             while ((*p <= ' ') && *p) p++;
1570             size_t const inherit_len = sizeof("inherit") - 1;
1571             if (*p
1572                 && !(strneq(p, "inherit", inherit_len)
1573                      && (p[inherit_len] == '\0'
1574                          || p[inherit_len] == ';'
1575                          || g_ascii_isspace(p[inherit_len])))) {
1576                 return p;
1577             }
1578         }
1579     }
1580     gchar const *val = object->repr->attribute(key);
1581     if (val && !streq(val, "inherit")) {
1582         return val;
1583     }
1584     if (object->parent) {
1585         return sp_object_get_style_property(object->parent, key, def);
1586     }
1588     return def;
1591 /**
1592  * Lifts SVG version of all root objects to version.
1593  */
1594 void
1595 SPObject::_requireSVGVersion(Inkscape::Version version) {
1596     for ( SPObject::ParentIterator iter=this ; iter ; ++iter ) {
1597         SPObject *object=iter;
1598         if (SP_IS_ROOT(object)) {
1599             SPRoot *root=SP_ROOT(object);
1600             if ( root->version.svg < version ) {
1601                 root->version.svg = version;
1602             }
1603         }
1604     }
1607 /**
1608  * Returns previous object in sibling list or NULL.
1609  */
1610 SPObject *
1611 sp_object_prev(SPObject *child)
1613     SPObject *parent = SP_OBJECT_PARENT(child);
1614     for ( SPObject *i = sp_object_first_child(parent); i; i = SP_OBJECT_NEXT(i) ) {
1615         if (SP_OBJECT_NEXT(i) == child)
1616             return i;
1617     }
1618     return NULL;
1621 /* Titles and descriptions */
1623 /* Note:
1624    Titles and descriptions are stored in 'title' and 'desc' child elements
1625    (see section 5.4 of the SVG 1.0 and 1.1 specifications).  The spec allows
1626    an element to have more than one 'title' child element, but strongly
1627    recommends against this and requires using the first one if a choice must
1628    be made.  The same applies to 'desc' elements.  Therefore, these functions
1629    ignore all but the first 'title' child element and first 'desc' child
1630    element, except when deleting a title or description.
1631 */
1633 /**
1634  * Returns the title of this object, or NULL if there is none.
1635  * The caller must free the returned string using g_free() - see comment
1636  * for getTitleOrDesc() below.
1637  */
1638 gchar *
1639 SPObject::title() const
1641     return getTitleOrDesc("svg:title");
1644 /**
1645  * Sets the title of this object
1646  * A NULL first argument is interpreted as meaning that the existing title
1647  * (if any) should be deleted.
1648  * The second argument is optional - see setTitleOrDesc() below for details.
1649  */
1650 bool
1651 SPObject::setTitle(gchar const *title, bool verbatim)
1653     return setTitleOrDesc(title, "svg:title", verbatim);
1656 /**
1657  * Returns the description of this object, or NULL if there is none.
1658  * The caller must free the returned string using g_free() - see comment
1659  * for getTitleOrDesc() below.
1660  */
1661 gchar *
1662 SPObject::desc() const
1664     return getTitleOrDesc("svg:desc");
1667 /**
1668  * Sets the description of this object.
1669  * A NULL first argument is interpreted as meaning that the existing
1670  * description (if any) should be deleted.
1671  * The second argument is optional - see setTitleOrDesc() below for details.
1672  */
1673 bool
1674 SPObject::setDesc(gchar const *desc, bool verbatim)
1676     return setTitleOrDesc(desc, "svg:desc", verbatim);
1679 /**
1680  * Returns the title or description of this object, or NULL if there is none.
1681  *
1682  * The SVG spec allows 'title' and 'desc' elements to contain text marked up
1683  * using elements from other namespaces.  Therefore, this function cannot
1684  * in general just return a pointer to an existing string - it must instead
1685  * construct a string containing the title or description without the mark-up.
1686  * Consequently, the return value is a newly allocated string (or NULL), and
1687  * must be freed (using g_free()) by the caller.
1688  */
1689 gchar *
1690 SPObject::getTitleOrDesc(gchar const *svg_tagname) const
1692     SPObject *elem = findFirstChild(svg_tagname);
1693     if (elem == NULL) return NULL;
1694     return g_string_free(elem->textualContent(), FALSE);
1697 /**
1698  * Sets or deletes the title or description of this object.
1699  * A NULL 'value' argument causes the title or description to be deleted.
1700  *
1701  * 'verbatim' parameter:
1702  * If verbatim==true, then the title or description is set to exactly the
1703  * specified value.  If verbatim==false then two exceptions are made:
1704  *   (1) If the specified value is just whitespace, then the title/description
1705  *       is deleted.
1706  *   (2) If the specified value is the same as the current value except for
1707  *       mark-up, then the current value is left unchanged.
1708  * This is usually the desired behaviour, so 'verbatim' defaults to false for
1709  * setTitle() and setDesc().
1710  *
1711  * The return value is true if a change was made to the title/description,
1712  * and usually false otherwise.
1713  */
1714 bool
1715 SPObject::setTitleOrDesc(gchar const *value, gchar const *svg_tagname, bool verbatim)
1717     if (!verbatim) {
1718         // If the new title/description is just whitespace,
1719         // treat it as though it were NULL.
1720         if (value) {
1721             bool just_whitespace = true;
1722             for (const gchar *cp = value; *cp; ++cp) {
1723                 if (!std::strchr("\r\n \t", *cp)) {
1724                     just_whitespace = false;
1725                     break;
1726                 }
1727             }
1728             if (just_whitespace) value = NULL;
1729         }
1730         // Don't stomp on mark-up if there is no real change.
1731         if (value) {
1732             gchar *current_value = getTitleOrDesc(svg_tagname);
1733             if (current_value) {
1734                 bool different = std::strcmp(current_value, value);
1735                 g_free(current_value);
1736                 if (!different) return false;
1737             }
1738         }
1739     }
1741     SPObject *elem = findFirstChild(svg_tagname);
1743     if (value == NULL) {
1744         if (elem == NULL) return false;
1745         // delete the title/description(s)
1746         while (elem) {
1747             elem->deleteObject();
1748             elem = findFirstChild(svg_tagname);
1749         }
1750         return true;
1751     }
1753     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
1755     if (elem == NULL) {
1756         // create a new 'title' or 'desc' element, putting it at the
1757         // beginning (in accordance with the spec's recommendations)
1758         Inkscape::XML::Node *xml_elem = xml_doc->createElement(svg_tagname);
1759         repr->addChild(xml_elem, NULL);
1760         elem = document->getObjectByRepr(xml_elem);
1761         Inkscape::GC::release(xml_elem);
1762     }
1763     else {
1764         // remove the current content of the 'text' or 'desc' element
1765         SPObject *child;
1766         while (NULL != (child = elem->firstChild())) child->deleteObject();
1767     }
1769     // add the new content
1770     elem->appendChildRepr(xml_doc->createTextNode(value));
1771     return true;
1774 /**
1775  * Find the first child of this object with a given tag name,
1776  * and return it.  Returns NULL if there is no matching child.
1777  */
1778 SPObject *
1779 SPObject::findFirstChild(gchar const *tagname) const
1781     for (SPObject *child = children; child; child = child->next)
1782     {
1783         if (child->repr->type() == Inkscape::XML::ELEMENT_NODE &&
1784             !strcmp(child->repr->name(), tagname)) return child;
1785     }
1786     return NULL;
1789 /**
1790  * Return the full textual content of an element (typically all the
1791  * content except the tags).
1792  * Must not be used on anything except elements.
1793  */
1794 GString*
1795 SPObject::textualContent() const
1797     GString* text = g_string_new("");
1799     for (const SPObject *child = firstChild(); child; child = child->next)
1800     {
1801         Inkscape::XML::NodeType child_type = child->repr->type();
1803         if (child_type == Inkscape::XML::ELEMENT_NODE) {
1804             GString * new_text = child->textualContent();
1805             g_string_append(text, new_text->str);
1806             g_string_free(new_text, TRUE);
1807         }
1808         else if (child_type == Inkscape::XML::TEXT_NODE) {
1809             g_string_append(text, child->repr->content());
1810         }
1811     }
1812     return text;
1815 /*
1816   Local Variables:
1817   mode:c++
1818   c-file-style:"stroustrup"
1819   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1820   indent-tabs-mode:nil
1821   fill-column:99
1822   End:
1823 */
1824 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :