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